@mushi-mushi/cli 0.6.1 → 0.8.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
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-XHD3H54W.js";
9
9
  import {
10
10
  MUSHI_CLI_VERSION
11
- } from "./chunk-ZL3BTW32.js";
11
+ } from "./chunk-KUQZQFOL.js";
12
12
 
13
13
  // src/init.ts
14
14
  import * as p from "@clack/prompts";
@@ -24,13 +24,20 @@ import { homedir } from "os";
24
24
  var CONFIG_PATH = join(homedir(), ".mushirc");
25
25
  var SECURE_FILE_MODE = 384;
26
26
  function loadConfig(path = CONFIG_PATH) {
27
- if (!existsSync(path)) return {};
28
- tightenPermissions(path);
29
- try {
30
- return JSON.parse(readFileSync(path, "utf-8"));
31
- } catch {
32
- return {};
27
+ let file = {};
28
+ if (existsSync(path)) {
29
+ tightenPermissions(path);
30
+ try {
31
+ file = JSON.parse(readFileSync(path, "utf-8"));
32
+ } catch {
33
+ }
33
34
  }
35
+ const fromEnv = {
36
+ ...process.env["MUSHI_API_KEY"] ? { apiKey: process.env["MUSHI_API_KEY"] } : {},
37
+ ...process.env["MUSHI_PROJECT_ID"] ? { projectId: process.env["MUSHI_PROJECT_ID"] } : {},
38
+ ...process.env["MUSHI_API_ENDPOINT"] ? { endpoint: process.env["MUSHI_API_ENDPOINT"] } : {}
39
+ };
40
+ return { ...file, ...fromEnv };
34
41
  }
35
42
  function saveConfig(config, path = CONFIG_PATH) {
36
43
  writeFileSync(path, JSON.stringify(config, null, 2), { mode: SECURE_FILE_MODE });
@@ -46,14 +53,17 @@ function tightenPermissions(path) {
46
53
  }
47
54
 
48
55
  // src/endpoint.ts
49
- var DEFAULT_ENDPOINT = "https://api.mushimushi.dev";
50
56
  var TEST_REPORT_TIMEOUT_MS = 1e4;
51
57
  var TEST_REPORT_FETCH_TIMEOUT_MS = TEST_REPORT_TIMEOUT_MS;
52
58
  function normalizeEndpoint(url) {
53
- const input = url ?? DEFAULT_ENDPOINT;
54
- let end = input.length;
55
- while (end > 0 && input.charCodeAt(end - 1) === 47) end--;
56
- return input.slice(0, end);
59
+ if (!url) {
60
+ throw new Error(
61
+ "No API endpoint configured. Run `mushi init` or set MUSHI_API_ENDPOINT. Set endpoint to your Supabase edge function URL, e.g. https://xyz.supabase.co/functions/v1/api"
62
+ );
63
+ }
64
+ let end = url.length;
65
+ while (end > 0 && url.charCodeAt(end - 1) === 47) end--;
66
+ return url.slice(0, end);
57
67
  }
58
68
 
59
69
  // src/freshness.ts
@@ -212,7 +222,7 @@ function getFrameworkFromPkg(pkg) {
212
222
 
213
223
  // src/init.ts
214
224
  var ENV_FILES = [".env.local", ".env"];
215
- var PROJECT_ID_PATTERN = /^proj_[A-Za-z0-9_-]{10,}$/;
225
+ 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;
216
226
  var API_KEY_PATTERN = /^mushi_[A-Za-z0-9_-]{10,}$/;
217
227
  async function runInit(options = {}) {
218
228
  const cwd = options.cwd ?? process.cwd();
@@ -244,7 +254,8 @@ async function runInit(options = {}) {
244
254
  }
245
255
  writeEnvFile(cwd, credentials.apiKey, credentials.projectId, framework);
246
256
  persistCliConfig(credentials.apiKey, credentials.projectId);
247
- printNextSteps(framework, credentials.apiKey, credentials.projectId);
257
+ const enableRewards = await maybeEnableRewards(options);
258
+ printNextSteps(framework, credentials.apiKey, credentials.projectId, enableRewards);
248
259
  await maybeSendTestReport(credentials, options);
249
260
  p.outro("Setup complete. Happy bug squashing \u{1F41B}");
250
261
  }
@@ -256,7 +267,7 @@ function ensureInteractiveOrBailOut(options) {
256
267
  );
257
268
  if (hasAllFlags) return;
258
269
  process.stderr.write(
259
- "mushi-mushi: non-interactive terminal detected.\nPass all of --yes (or --framework), --project-id, and --api-key to run unattended.\nExample: npx mushi-mushi --yes --project-id proj_xxx --api-key mushi_xxx\n"
270
+ "mushi-mushi: non-interactive terminal detected.\nPass all of --yes (or --framework), --project-id, and --api-key to run unattended.\nExample: npx mushi-mushi --yes --project-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --api-key mushi_xxx\nYour project ID is the UUID shown in the Projects page of the Mushi admin console.\n"
260
271
  );
261
272
  process.exit(1);
262
273
  }
@@ -290,21 +301,22 @@ async function collectCredentials(options) {
290
301
  const existing = loadConfig();
291
302
  const rawProjectId = options.projectId ?? existing.projectId ?? await promptText({
292
303
  message: "Project ID",
293
- placeholder: "proj_xxxxxxxxxxxx",
294
- hint: "Find this at https://kensaur.us/mushi-mushi/projects",
295
- validate: (v) => PROJECT_ID_PATTERN.test(v) ? void 0 : "Expected format: proj_ followed by 10+ alphanumeric characters"
304
+ placeholder: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
305
+ hint: "Where to find it: https://kensaur.us/mushi-mushi/projects \u2192 click your project \u2192 copy the UUID below the project name.",
306
+ validate: (v) => PROJECT_ID_PATTERN.test(v) ? void 0 : "Expected a UUID (xxxxxxxx-xxxx-...) \u2014 copy it from the Mushi admin console Projects page."
296
307
  });
297
308
  const rawApiKey = options.apiKey ?? existing.apiKey ?? await promptText({
298
309
  message: "API key",
299
310
  placeholder: "mushi_xxxxxxxxxxxx",
300
- hint: "Treat this like a password \u2014 it goes in your env file, not in source.",
311
+ hint: "Where to find it: https://kensaur.us/mushi-mushi/settings \u2192 API Keys tab. Treat it like a password \u2014 env file only, never commit it.",
301
312
  validate: (v) => API_KEY_PATTERN.test(v) ? void 0 : "Expected format: mushi_ followed by 10+ alphanumeric characters"
302
313
  });
303
314
  const projectId = sanitizeSecret(rawProjectId);
304
315
  const apiKey = sanitizeSecret(rawApiKey);
305
316
  if (!PROJECT_ID_PATTERN.test(projectId)) {
306
317
  throw new Error(
307
- `Invalid project ID. Expected format: proj_[A-Za-z0-9_-]{10,}. Got: ${redact(projectId)}`
318
+ `Invalid project ID. Got: ${redact(projectId)}
319
+ Expected a UUID (e.g. 542b34e0-019e-41fe-b900-7b637717bb86) \u2014 copy it from the Projects page in the Mushi console at https://kensaur.us/mushi-mushi/projects`
308
320
  );
309
321
  }
310
322
  if (!API_KEY_PATTERN.test(apiKey)) {
@@ -322,6 +334,7 @@ function redact(value) {
322
334
  return `${value.slice(0, 4)}\u2026${value.slice(-2)}`;
323
335
  }
324
336
  async function promptText(opts) {
337
+ if (opts.hint) p.log.info(opts.hint);
325
338
  const value = await p.text({
326
339
  message: opts.message,
327
340
  placeholder: opts.placeholder,
@@ -338,7 +351,6 @@ async function promptText(opts) {
338
351
  p.cancel("Aborted.");
339
352
  process.exit(0);
340
353
  }
341
- if (opts.hint) p.log.info(opts.hint);
342
354
  return value;
343
355
  }
344
356
  async function installPackages(pm, packages, cwd) {
@@ -423,13 +435,34 @@ function persistCliConfig(apiKey, projectId) {
423
435
  const existing = loadConfig();
424
436
  saveConfig({ ...existing, apiKey, projectId });
425
437
  }
426
- function printNextSteps(framework, apiKey, projectId) {
438
+ function printNextSteps(framework, apiKey, projectId, enableRewards = false) {
427
439
  p.note(framework.snippet(apiKey, projectId), "Add this to your app:");
440
+ if (enableRewards) {
441
+ const badgeSnippet = framework.id === "react" ? `// Add to your user menu or profile UI:
442
+ import { MushiRewardsBadge } from '@mushi-mushi/react';
443
+
444
+ // Inside your component:
445
+ <MushiRewardsBadge showPoints />` : `// Add to your user menu:
446
+ // import { MushiRewardsBadge } from '@mushi-mushi/react';
447
+ // <MushiRewardsBadge showPoints />`;
448
+ p.note(badgeSnippet, "Rewards badge snippet:");
449
+ p.log.info("Enable rewards in your project settings at https://kensaur.us/mushi-mushi/rewards");
450
+ p.log.info("Users will earn points for bug reports, screen navigation, and app activity.");
451
+ }
428
452
  p.log.message("Verify the install:");
429
453
  p.log.message(" \u2022 Start your dev server");
430
454
  p.log.message(" \u2022 Look for the \u{1F41B} button in the bottom-right corner (or shake on mobile)");
431
455
  p.log.message(" \u2022 Submit a test report \u2014 it should appear at https://kensaur.us/mushi-mushi/reports");
432
456
  }
457
+ async function maybeEnableRewards(options) {
458
+ if (options.yes) return false;
459
+ const answer = await p.confirm({
460
+ message: "Enable Mushi Rewards? (users earn points for bug reports + app activity)",
461
+ initialValue: false
462
+ });
463
+ if (p.isCancel(answer)) return false;
464
+ return Boolean(answer);
465
+ }
433
466
  async function maybeSendTestReport(credentials, options) {
434
467
  if (options.sendTestReport === false) return;
435
468
  let shouldSend;
@@ -444,6 +477,13 @@ async function maybeSendTestReport(credentials, options) {
444
477
  shouldSend = answer;
445
478
  }
446
479
  if (!shouldSend) return;
480
+ if (!options.endpoint) {
481
+ p.note(
482
+ "No endpoint configured \u2014 skipping test report.\nSet endpoint to your Supabase edge function URL, e.g. https://xyz.supabase.co/functions/v1/api",
483
+ "Skipped"
484
+ );
485
+ return;
486
+ }
447
487
  const spinner2 = p.spinner();
448
488
  spinner2.start("Sending test report\u2026");
449
489
  const endpoint = normalizeEndpoint(options.endpoint);
@@ -484,7 +524,14 @@ async function maybeSendTestReport(credentials, options) {
484
524
  return;
485
525
  }
486
526
  spinner2.stop("Test report sent.");
487
- p.log.success("View it at https://kensaur.us/mushi-mushi/reports");
527
+ let reportId;
528
+ try {
529
+ const body = await res.json();
530
+ reportId = body.data?.reportId;
531
+ } catch {
532
+ }
533
+ const reportPath = reportId ? `/reports/${reportId}` : "/reports";
534
+ p.log.success(`View it at https://kensaur.us/mushi-mushi/admin${reportPath}`);
488
535
  } catch (err) {
489
536
  const aborted = err instanceof Error && err.name === "AbortError";
490
537
  spinner2.stop(aborted ? "Timed out reaching the Mushi API." : "Could not reach the Mushi API.");
package/dist/version.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MUSHI_CLI_VERSION
3
- } from "./chunk-ZL3BTW32.js";
3
+ } from "./chunk-KUQZQFOL.js";
4
4
  export {
5
5
  MUSHI_CLI_VERSION
6
6
  };
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "@mushi-mushi/cli",
3
- "version": "0.6.1",
3
+ "version": "0.8.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": {
7
7
  "mushi": "./dist/index.js"
8
8
  },
9
9
  "dependencies": {
10
- "@clack/prompts": "^1.2.0",
10
+ "@clack/prompts": "^1.3.0",
11
11
  "commander": "^14.0.3"
12
12
  },
13
13
  "devDependencies": {
14
- "@types/node": "^22.15.3",
15
- "eslint": "^10.2.0",
14
+ "@types/node": "^22.19.17",
15
+ "eslint": "^10.3.0",
16
16
  "tsup": "^8.5.1",
17
- "typescript": "^6.0.2",
18
- "vitest": "^4.1.4",
17
+ "typescript": "^6.0.3",
18
+ "vitest": "^4.1.5",
19
19
  "@mushi-mushi/eslint-config": "0.0.0"
20
20
  },
21
21
  "author": "Kenji Sakuramoto",
@@ -1,6 +0,0 @@
1
- // src/version.ts
2
- var MUSHI_CLI_VERSION = true ? "0.6.1" : "0.0.0-dev";
3
-
4
- export {
5
- MUSHI_CLI_VERSION
6
- };