@mushi-mushi/cli 0.16.0 → 0.17.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/README.md CHANGED
@@ -152,16 +152,23 @@ real-catalog regression guard).
152
152
 
153
153
  ### `mushi doctor`
154
154
 
155
- Checks CLI and SDK health. Without flags it verifies local config and endpoint
156
- reachability only.
155
+ Checks CLI and SDK health. **Ingest and dispatch (server) checks run by
156
+ default** — pass `--no-server` / `--no-ingest` to limit it to local checks.
157
157
 
158
158
  ```bash
159
- mushi doctor # local checks only
160
- mushi doctor --server # + calls /preflight on the backend (all 4 dispatch checks)
161
- mushi doctor --ingest # + calls /v1/sync/ingest-setup (API key → heartbeat first report)
159
+ mushi doctor # local + ingest + dispatch readiness (the full picture)
160
+ mushi doctor --no-server # skip the dispatch-readiness /preflight checks
161
+ mushi doctor --no-ingest # skip the ingest-setup checks (SDK heartbeat, first report)
162
+ mushi doctor --fix # apply safe local fixes, then re-run and report the post-fix state
162
163
  mushi doctor --json # machine-readable JSON output (exits 1 if any check fails)
164
+ mushi doctor --qa-stories # also flag enabled QA stories with setup issues (needs server creds)
165
+ mushi doctor --host-app # also verify host-app wiring (env vars, Cursor MCP, Capacitor notes)
163
166
  ```
164
167
 
168
+ `--fix` writes any missing `.env.local` lines and wires the Cursor MCP config
169
+ (via `mushi connect`), then re-runs every check so the printed result and exit
170
+ code reflect the post-fix state rather than the stale pre-fix failures.
171
+
165
172
  Local checks performed:
166
173
 
167
174
  | Check | What it verifies |
@@ -170,9 +177,10 @@ Local checks performed:
170
177
  | Endpoint reachability | `GET /v1/sdk/config?project_id=...` returns 200 |
171
178
  | SDK install | `@mushi-mushi/web` or framework-specific SDK is in `node_modules` |
172
179
 
173
- With `--server`, also calls `GET /v1/admin/projects/:id/preflight` (same 4 checks
174
- the admin console dispatch popover uses) and merges the results. The four
175
- server checks (`key` values returned by the endpoint):
180
+ The dispatch-readiness checks (on by default; skip with `--no-server`) call
181
+ `GET /v1/admin/projects/:id/preflight` (the same 4 checks the admin console
182
+ dispatch popover uses) and merge the results. The four server checks (`key`
183
+ values returned by the endpoint):
176
184
 
177
185
  | `key` | What it verifies |
178
186
  |---|---|
@@ -181,15 +189,16 @@ server checks (`key` values returned by the endpoint):
181
189
  | `anthropic` | A BYOK Anthropic key is stored in Supabase Vault (`project_settings.byok_anthropic_key_ref`) |
182
190
  | `autofix` | The autofix toggle is ON (`project_settings.autofix_enabled = true`) |
183
191
 
184
- `--server` requires `adminOrApiKey` credentials — set `MUSHI_API_KEY` to an
185
- admin key (not a public SDK key).
192
+ The dispatch-readiness checks require `adminOrApiKey` credentials — set
193
+ `MUSHI_API_KEY` to an admin key (not a public SDK key). If you only have an SDK
194
+ key, pass `--no-server` to skip them.
186
195
 
187
- With `--ingest`, also calls `GET /v1/sync/ingest-setup` (authenticated with the
188
- SDK API key) and reports each **required ingest step** — project exists, active
189
- API key, SDK heartbeat, at least one ingested report — plus a
190
- `Last SDK heartbeat` diagnostic with the timestamp and endpoint host. This is
191
- the same payload `mushi connect --wait` polls, so a failing step here tells you
192
- exactly why the banner isn't showing up.
196
+ The ingest checks (on by default; skip with `--no-ingest`) call
197
+ `GET /v1/sync/ingest-setup` (authenticated with the SDK API key) and report each
198
+ **required ingest step** — project exists, active API key, SDK heartbeat, at
199
+ least one ingested report — plus a `Last SDK heartbeat` diagnostic with the
200
+ timestamp and endpoint host. This is the same payload `mushi connect --wait`
201
+ polls, so a failing step here tells you exactly why the banner isn't showing up.
193
202
 
194
203
  ### `mushi reset <projectId>`
195
204
 
@@ -273,6 +273,7 @@ function detectFramework(cwd, pkg) {
273
273
  if (deps.has("@angular/core")) return FRAMEWORKS.angular;
274
274
  if (deps.has("expo")) return FRAMEWORKS.expo;
275
275
  if (deps.has("react-native")) return FRAMEWORKS["react-native"];
276
+ if (deps.has("@capacitor/core") && deps.has("react")) return FRAMEWORKS.react;
276
277
  if (deps.has("@capacitor/core")) return FRAMEWORKS.capacitor;
277
278
  if (deps.has("svelte")) return FRAMEWORKS.svelte;
278
279
  if (deps.has("vue")) return FRAMEWORKS.vue;
@@ -0,0 +1,6 @@
1
+ // src/version.ts
2
+ var MUSHI_CLI_VERSION = true ? "0.17.1" : "0.0.0-dev";
3
+
4
+ export {
5
+ MUSHI_CLI_VERSION
6
+ };
package/dist/detect.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  installCommand,
8
8
  isFrameworkId,
9
9
  readPackageJson
10
- } from "./chunk-NYPX5KXR.js";
10
+ } from "./chunk-S4WQHEGH.js";
11
11
  export {
12
12
  FRAMEWORKS,
13
13
  FRAMEWORK_IDS,
package/dist/index.js CHANGED
@@ -358,6 +358,7 @@ function detectFramework(cwd, pkg) {
358
358
  if (deps.has("@angular/core")) return FRAMEWORKS.angular;
359
359
  if (deps.has("expo")) return FRAMEWORKS.expo;
360
360
  if (deps.has("react-native")) return FRAMEWORKS["react-native"];
361
+ if (deps.has("@capacitor/core") && deps.has("react")) return FRAMEWORKS.react;
361
362
  if (deps.has("@capacitor/core")) return FRAMEWORKS.capacitor;
362
363
  if (deps.has("svelte")) return FRAMEWORKS.svelte;
363
364
  if (deps.has("vue")) return FRAMEWORKS.vue;
@@ -416,6 +417,13 @@ function collectDeps(pkg) {
416
417
  // src/endpoint.ts
417
418
  var TEST_REPORT_TIMEOUT_MS = 1e4;
418
419
  var TEST_REPORT_FETCH_TIMEOUT_MS = TEST_REPORT_TIMEOUT_MS;
420
+ var CLOUD_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
421
+ function resolveCloudEndpoint(explicit) {
422
+ if (explicit?.trim()) return explicit.trim();
423
+ const fromEnv = process.env.MUSHI_API_ENDPOINT?.trim();
424
+ if (fromEnv) return fromEnv;
425
+ return CLOUD_API_ENDPOINT;
426
+ }
419
427
  function assertEndpoint(url) {
420
428
  let parsed;
421
429
  try {
@@ -428,7 +436,7 @@ function assertEndpoint(url) {
428
436
  if (parsed.protocol !== "https:" && !isLocal) {
429
437
  throw new Error(`Endpoint must use https:// (got ${parsed.protocol}//${host}).`);
430
438
  }
431
- return parsed.origin + (parsed.pathname === "/" ? "" : parsed.pathname);
439
+ return normalizeEndpoint(parsed.origin + parsed.pathname);
432
440
  }
433
441
  function normalizeEndpoint(url) {
434
442
  if (!url) {
@@ -598,7 +606,7 @@ function getFrameworkFromPkg(pkg) {
598
606
  }
599
607
 
600
608
  // src/version.ts
601
- var MUSHI_CLI_VERSION = true ? "0.16.0" : "0.0.0-dev";
609
+ var MUSHI_CLI_VERSION = true ? "0.17.1" : "0.0.0-dev";
602
610
 
603
611
  // src/init.ts
604
612
  var ENV_FILES = [".env.local", ".env"];
@@ -632,11 +640,12 @@ async function runInit(options = {}) {
632
640
  } else {
633
641
  p.log.info(`Skipped install. Run \`${installCommand(pm, packagesToInstall)}\` yourself.`);
634
642
  }
643
+ const endpoint = resolveCloudEndpoint(options.endpoint);
635
644
  writeEnvFile(cwd, credentials.apiKey, credentials.projectId, framework);
636
- persistCliConfig(credentials.apiKey, credentials.projectId);
645
+ persistCliConfig(credentials.apiKey, credentials.projectId, endpoint);
637
646
  const enableRewards = await maybeEnableRewards(options);
638
647
  printNextSteps(framework, credentials.apiKey, credentials.projectId, enableRewards);
639
- await maybeSendTestReport(credentials, options);
648
+ await maybeSendTestReport(credentials, { ...options, endpoint });
640
649
  p.outro("Setup complete. Happy bug squashing \u{1F41B}");
641
650
  }
642
651
  function ensureInteractiveOrBailOut(options) {
@@ -810,9 +819,9 @@ function warnIfMissingFromGitignore(cwd, envFile) {
810
819
  p.log.warn(`${envFile} is not in .gitignore \u2014 add it before committing.`);
811
820
  }
812
821
  }
813
- function persistCliConfig(apiKey, projectId) {
822
+ function persistCliConfig(apiKey, projectId, endpoint) {
814
823
  const existing = loadConfig();
815
- saveConfig({ ...existing, apiKey, projectId });
824
+ saveConfig({ ...existing, apiKey, projectId, endpoint });
816
825
  }
817
826
  function printNextSteps(framework, apiKey, projectId, enableRewards = false) {
818
827
  p.note(framework.snippet(apiKey, projectId), "Add this to your app:");
@@ -856,16 +865,9 @@ async function maybeSendTestReport(credentials, options) {
856
865
  shouldSend = answer;
857
866
  }
858
867
  if (!shouldSend) return;
859
- if (!options.endpoint) {
860
- p.note(
861
- "No endpoint configured \u2014 skipping test report.\nSet endpoint to your Supabase edge function URL, e.g. https://xyz.supabase.co/functions/v1/api",
862
- "Skipped"
863
- );
864
- return;
865
- }
868
+ const endpoint = normalizeEndpoint(resolveCloudEndpoint(options.endpoint));
866
869
  const spinner3 = p.spinner();
867
870
  spinner3.start("Sending test report\u2026");
868
- const endpoint = normalizeEndpoint(options.endpoint);
869
871
  const controller = new AbortController();
870
872
  const timer = setTimeout(() => controller.abort(), TEST_REPORT_FETCH_TIMEOUT_MS);
871
873
  try {
@@ -1747,20 +1749,95 @@ async function checkQaStoriesHealth(config, doFetch = globalThis.fetch) {
1747
1749
  }
1748
1750
  return checks;
1749
1751
  }
1752
+ async function checkHostAppWiring(cwd) {
1753
+ const checks = [];
1754
+ try {
1755
+ const { readFile: readFile3, access } = await import("fs/promises");
1756
+ const { join: join7, resolve: resolve4 } = await import("path");
1757
+ const root = resolve4(cwd);
1758
+ const pkgPath = join7(root, "package.json");
1759
+ const pkg = JSON.parse(await readFile3(pkgPath, "utf8"));
1760
+ const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
1761
+ const isCapHybrid = Boolean(deps["@capacitor/core"] && deps["react"]);
1762
+ const envCandidates = [".env.local", ".env"];
1763
+ let envContent = "";
1764
+ for (const f of envCandidates) {
1765
+ try {
1766
+ envContent = await readFile3(join7(root, f), "utf8");
1767
+ break;
1768
+ } catch {
1769
+ }
1770
+ }
1771
+ const hasProjectId = /VITE_MUSHI_PROJECT_ID=|NEXT_PUBLIC_MUSHI_PROJECT_ID=|MUSHI_PROJECT_ID=/.test(envContent);
1772
+ const hasApiKey = /VITE_MUSHI_API_KEY=|NEXT_PUBLIC_MUSHI_API_KEY=|MUSHI_API_KEY=/.test(envContent);
1773
+ checks.push({
1774
+ name: "[host] Mushi env vars in .env.local",
1775
+ ok: hasProjectId && hasApiKey,
1776
+ detail: hasProjectId && hasApiKey ? "VITE_/MUSHI_ project id + API key found" : "Run `mushi connect --write-env` or add VITE_MUSHI_PROJECT_ID + VITE_MUSHI_API_KEY"
1777
+ });
1778
+ let mcpPresent = false;
1779
+ try {
1780
+ await access(join7(root, ".cursor", "mcp.json"));
1781
+ mcpPresent = true;
1782
+ } catch {
1783
+ }
1784
+ checks.push({
1785
+ name: "[host] Cursor MCP config",
1786
+ ok: mcpPresent,
1787
+ detail: mcpPresent ? ".cursor/mcp.json present" : "Run `mushi connect` to wire MCP for two-way reporter replies"
1788
+ });
1789
+ if (isCapHybrid) {
1790
+ const hasWebSdk = Boolean(deps["@mushi-mushi/web"] || deps["@mushi-mushi/react"]);
1791
+ checks.push({
1792
+ name: "[host] Capacitor hybrid \u2014 WebView SDK",
1793
+ ok: hasWebSdk,
1794
+ detail: hasWebSdk ? "Use @mushi-mushi/web or @mushi-mushi/react in the WebView (initMushi in main.tsx)" : "Install @mushi-mushi/web for Capacitor WebView reporting"
1795
+ });
1796
+ checks.push({
1797
+ name: "[host] Capacitor native plugin (optional)",
1798
+ ok: true,
1799
+ detail: deps["@mushi-mushi/capacitor"] ? `@mushi-mushi/capacitor@${deps["@mushi-mushi/capacitor"]} installed` : "Optional: @mushi-mushi/capacitor for native shell parity \u2014 WebView SDK covers most flows"
1800
+ });
1801
+ }
1802
+ } catch {
1803
+ checks.push({
1804
+ name: "[host] Host app detection",
1805
+ ok: false,
1806
+ detail: "No package.json in cwd \u2014 run from your app repo root"
1807
+ });
1808
+ }
1809
+ return checks;
1810
+ }
1811
+ var FIX_HINTS = {
1812
+ "CLI config file": "Run `mushi connect --endpoint <url> --project-id <uuid> --api-key mushi_xxx` or `mushi config endpoint <url>`.",
1813
+ "API key configured": "Mint a key in the console (Projects \u2192 API Keys) then `mushi login --api-key mushi_xxx`.",
1814
+ "Project ID configured": "Copy the project UUID from the console Projects page \u2192 `mushi config projectId <uuid>`.",
1815
+ "Endpoint reachable": "Check your network and that MUSHI_API_ENDPOINT points at `\u2026/functions/v1/api`.",
1816
+ "[ingest]": "Open the console Onboarding wizard \u2192 Install SDK \u2192 submit a test report, or run `mushi connect --wait`.",
1817
+ "[server]": "Open Settings \u2192 Integrations: connect GitHub, index codebase, add Anthropic BYOK key, enable autofix."
1818
+ };
1819
+ function fixHintForCheck(name) {
1820
+ if (FIX_HINTS[name]) return FIX_HINTS[name];
1821
+ if (name.startsWith("[ingest]")) return FIX_HINTS["[ingest]"];
1822
+ if (name.startsWith("[server]") || name.startsWith("[preflight]")) return FIX_HINTS["[server]"];
1823
+ return void 0;
1824
+ }
1750
1825
  async function runDoctor(config, options = {}) {
1751
1826
  const doFetch = options.fetch ?? globalThis.fetch;
1752
1827
  const checks = [];
1828
+ const runServer = options.server !== false;
1829
+ const runIngest = options.ingest !== false;
1753
1830
  checks.push(...checkCliConfig(config));
1754
1831
  if (config.endpoint) {
1755
1832
  checks.push(await checkEndpointReachability(config.endpoint, doFetch));
1756
1833
  }
1757
1834
  const sdkCheck = await checkSdkInstall(options.cwd ?? process.cwd());
1758
1835
  if (sdkCheck) checks.push(sdkCheck);
1759
- if (options.server) {
1836
+ if (runServer) {
1760
1837
  const serverChecks = await checkServerPreflight(config, doFetch);
1761
1838
  checks.push(...serverChecks);
1762
1839
  }
1763
- if (options.ingest) {
1840
+ if (runIngest) {
1764
1841
  const ingestChecks = await checkIngestSetup(config, doFetch);
1765
1842
  checks.push(...ingestChecks);
1766
1843
  }
@@ -1768,6 +1845,10 @@ async function runDoctor(config, options = {}) {
1768
1845
  const qaChecks = await checkQaStoriesHealth(config, doFetch);
1769
1846
  checks.push(...qaChecks);
1770
1847
  }
1848
+ if (options.hostApp) {
1849
+ const hostChecks = await checkHostAppWiring(options.cwd ?? process.cwd());
1850
+ checks.push(...hostChecks);
1851
+ }
1771
1852
  return { checks, ready: checks.every((c) => c.ok) };
1772
1853
  }
1773
1854
  function formatDoctorResult(result) {
@@ -1777,6 +1858,10 @@ function formatDoctorResult(result) {
1777
1858
  for (const c of result.checks) {
1778
1859
  lines.push(`${c.ok ? PASS : FAIL} ${c.name}`);
1779
1860
  if (c.detail) lines.push(` ${c.detail}`);
1861
+ if (!c.ok) {
1862
+ const hint = fixHintForCheck(c.name);
1863
+ if (hint) lines.push(` \u2192 Fix: ${hint}`);
1864
+ }
1780
1865
  }
1781
1866
  const failed = result.checks.filter((c) => !c.ok);
1782
1867
  if (failed.length === 0) {
@@ -1956,6 +2041,12 @@ async function runConnect(opts, baseConfig = {}) {
1956
2041
  messages.push(
1957
2042
  wrote ? `\u2713 Env vars merged into ${envPath}` : `\u2713 Env vars already present in ${envPath} (existing values left untouched)`
1958
2043
  );
2044
+ const deps = { ...pkg?.dependencies ?? {}, ...pkg?.devDependencies ?? {} };
2045
+ if (deps["@capacitor/core"] && deps["react"]) {
2046
+ messages.push(
2047
+ "\u2139 Capacitor + React detected \u2014 install @mushi-mushi/web (or @mushi-mushi/react) and call initMushi() in main.tsx. Optional @mushi-mushi/capacitor for native shell parity."
2048
+ );
2049
+ }
1959
2050
  }
1960
2051
  let mcpPath = null;
1961
2052
  if (opts.wireIde !== false) {
@@ -2348,6 +2439,46 @@ Examples:
2348
2439
  \u2026 ${result.data.total - rows.length} more. Use --limit to see more.`);
2349
2440
  }
2350
2441
  });
2442
+ reports.command("thread <id>").description("Show unified timeline for a report (comments, fixes, QA, pipelines)").option("--json", "Machine-readable JSON output").option("--watch", "Poll for new timeline entries (TTY only)").action(async (id, opts) => {
2443
+ const config = requireConfig();
2444
+ const fetchTimeline = async () => {
2445
+ const result = await apiCall(`/v1/sync/reports/${id}/timeline`, config);
2446
+ if (!result.ok) {
2447
+ if (result.httpStatus === 404 || result.error.code === "NOT_FOUND") {
2448
+ process.stderr.write(`error: report "${id}" not found
2449
+ `);
2450
+ process.exit(3);
2451
+ }
2452
+ die(result);
2453
+ }
2454
+ return result.data;
2455
+ };
2456
+ const render = (data) => {
2457
+ if (opts.json) {
2458
+ console.log(JSON.stringify(data, null, 2));
2459
+ return;
2460
+ }
2461
+ console.log(`Timeline for ${data.report_id}`);
2462
+ console.log("\u2500".repeat(72));
2463
+ for (const e of data.timeline) {
2464
+ console.log(`${fmtDate(e.at)} [${e.lane}] ${e.title}${e.status ? ` (${e.status})` : ""}`);
2465
+ if (e.body) console.log(` ${e.body.replace(/\n/g, "\n ")}`);
2466
+ }
2467
+ };
2468
+ if (opts.watch && process.stdout.isTTY) {
2469
+ let lastLen = -1;
2470
+ for (; ; ) {
2471
+ const data = await fetchTimeline();
2472
+ if (data.timeline.length !== lastLen) {
2473
+ console.clear();
2474
+ render(data);
2475
+ lastLen = data.timeline.length;
2476
+ }
2477
+ await new Promise((r) => setTimeout(r, 3e3));
2478
+ }
2479
+ }
2480
+ render(await fetchTimeline());
2481
+ });
2351
2482
  reports.command("show <id>").description("Show full details for a single report").option("--json", "Machine-readable JSON output").action(async (id, opts) => {
2352
2483
  const config = requireConfig();
2353
2484
  const result = await apiCall(`/v1/sync/reports/${id}`, config);
@@ -2441,7 +2572,7 @@ reports.command("resolve <id>").description("Mark a report as resolved (shorthan
2441
2572
  });
2442
2573
  reports.command("reopen <id>").description("Reopen a resolved or dismissed report").option("--note <text>", "Note explaining the reopen").option("--json", "Machine-readable JSON output").action(async (id, opts) => {
2443
2574
  const config = requireConfig();
2444
- const body = { status: "new" };
2575
+ const body = { status: "reopened" };
2445
2576
  if (opts.note) body["note"] = opts.note;
2446
2577
  const result = await apiCall(`/v1/sync/reports/${id}`, config, {
2447
2578
  method: "PATCH",
@@ -2461,6 +2592,18 @@ reports.command("reopen <id>").description("Reopen a resolved or dismissed repor
2461
2592
  console.log(`\u2713 Reopened report ${id}`);
2462
2593
  }
2463
2594
  });
2595
+ reports.command("verify <id>").description("Mark a fixed report as verified by the reporter (operator shortcut)").option("--note <text>", "Optional audit note").option("--json", "Machine-readable JSON output").action(async (id, opts) => {
2596
+ const config = requireConfig();
2597
+ const body = { status: "verified" };
2598
+ if (opts.note) body["note"] = opts.note;
2599
+ const result = await apiCall(`/v1/sync/reports/${id}`, config, {
2600
+ method: "PATCH",
2601
+ body: JSON.stringify(body)
2602
+ });
2603
+ if (!result.ok) die(result);
2604
+ if (opts.json) console.log(JSON.stringify(result.data, null, 2));
2605
+ else console.log(`\u2713 Verified report ${id}`);
2606
+ });
2464
2607
  reports.command("dismiss <id>").description("Dismiss a report (not a real bug / out of scope)").option("--note <text>", "Reason for dismissal").option("--json", "Machine-readable JSON output").action(async (id, opts) => {
2465
2608
  const config = requireConfig();
2466
2609
  const body = { status: "dismissed" };
@@ -2537,6 +2680,37 @@ Examples:
2537
2680
  console.log("");
2538
2681
  }
2539
2682
  });
2683
+ var feedback = program.command("feedback").description("Community feedback board (bugs + feature requests)");
2684
+ feedback.command("board").description("List open feature requests and community tickets for the current project").option("--limit <n>", "Max results (1\u201350)", "20").option("--json", "Machine-readable JSON output").addHelpText("after", `
2685
+ Examples:
2686
+ mushi feedback board
2687
+ mushi feedback board --limit 10 --json`).action(async (opts) => {
2688
+ const config = requireConfig();
2689
+ if (!config.projectId) {
2690
+ process.stderr.write("error: no projectId \u2014 run `mushi config projectId <uuid>`\n");
2691
+ process.exit(2);
2692
+ }
2693
+ const limit = Math.min(Math.max(1, parseInt(opts.limit) || 20), 50);
2694
+ const result = await apiCall(
2695
+ `/v1/admin/feature-board`,
2696
+ config
2697
+ );
2698
+ if (!result.ok) die(result);
2699
+ const rows = (result.data?.tickets ?? []).slice(0, limit);
2700
+ if (opts.json) {
2701
+ console.log(JSON.stringify(rows, null, 2));
2702
+ return;
2703
+ }
2704
+ if (rows.length === 0) {
2705
+ console.log("No feature requests on the board yet.");
2706
+ return;
2707
+ }
2708
+ console.log(`${pad("ID", 38)} ${pad("VOTES", 6)} ${pad("STATUS", 12)} ${pad("CREATED", 17)} SUBJECT`);
2709
+ console.log("\u2500".repeat(110));
2710
+ for (const t of rows) {
2711
+ console.log(`${pad(t.id, 38)} ${pad(String(t.vote_count ?? 0), 6)} ${pad(t.status, 12)} ${pad(fmtDate(t.created_at), 17)} ${(t.subject ?? "").slice(0, 40)}`);
2712
+ }
2713
+ });
2540
2714
  var lessons = program.command("lessons").description("Manage learned mistake rules");
2541
2715
  lessons.command("list").description("List active lessons (mistake rules) for the current project").option("--severity <sev>", "Filter: info|warn|critical").option("--limit <n>", "Max results (1\u2013200)", "50").option("--json", "Machine-readable JSON output").addHelpText("after", `
2542
2716
  Lessons are mistake rules extracted from past bug reports by the clustering
@@ -3179,19 +3353,32 @@ Examples:
3179
3353
  if (!result.ok) process.exit(1);
3180
3354
  });
3181
3355
  program.command("doctor").description(
3182
- "Run pre-flight checks: CLI config, endpoint reachability, API key shape, SDK install status, and (with --server) the same 4 dispatch-readiness checks shown in the Mushi console. Mirrors the in-console dispatch preflight so you can spot setup gaps before opening the admin UI."
3183
- ).option("--cwd <path>", "Run package detection from a different directory").option("--json", "Machine-readable output").option(
3184
- "--server",
3185
- "Also call GET /preflight on the backend and include the 4 dispatch checks (GitHub repo, codebase indexed, Anthropic key, autofix enabled). Requires a configured projectId and API key."
3186
- ).option(
3187
- "--ingest",
3188
- "Also call GET /v1/sync/ingest-setup for the 4 required ingest steps (API key, SDK heartbeat, first report). Composable with --server."
3189
- ).option(
3356
+ "Run pre-flight checks: CLI config, endpoint reachability, SDK install, ingest readiness (API key \u2192 heartbeat \u2192 first report), and dispatch readiness (GitHub, index, BYOK, autofix). Ingest + server checks run by default; pass --no-server or --no-ingest to skip."
3357
+ ).option("--cwd <path>", "Run package detection from a different directory").option("--json", "Machine-readable output").option("--no-server", "Skip dispatch-readiness /preflight checks").option("--no-ingest", "Skip ingest-setup checks (SDK heartbeat, first report)").option(
3190
3358
  "--qa-stories",
3191
3359
  "Check enabled QA stories for common setup issues: missing Firecrawl key, missing target URL, Slack not connected. Requires --server credentials."
3360
+ ).option(
3361
+ "--host-app",
3362
+ "Verify host-app wiring: Mushi env vars, Cursor MCP config, Capacitor hybrid SDK notes."
3363
+ ).option(
3364
+ "--fix",
3365
+ "Apply safe local fixes when checks fail: write missing .env.local lines and wire Cursor MCP config."
3192
3366
  ).action(async (opts) => {
3193
3367
  const config = loadConfig();
3194
- const result = await runDoctor(config, { cwd: opts.cwd, server: opts.server, ingest: opts.ingest, qaStories: opts.qaStories });
3368
+ const doctorOpts = { cwd: opts.cwd, server: opts.server, ingest: opts.ingest, qaStories: opts.qaStories, hostApp: opts.hostApp };
3369
+ let result = await runDoctor(config, doctorOpts);
3370
+ if (!result.ready && opts.fix && config.apiKey && config.projectId && config.endpoint) {
3371
+ const connectResult = await runConnect({
3372
+ apiKey: config.apiKey,
3373
+ projectId: config.projectId,
3374
+ endpoint: config.endpoint,
3375
+ cwd: opts.cwd ?? process.cwd(),
3376
+ writeEnv: true,
3377
+ wireIde: true
3378
+ }, config);
3379
+ for (const msg of connectResult.messages) console.log(msg);
3380
+ result = await runDoctor(config, doctorOpts);
3381
+ }
3195
3382
  const { checks } = result;
3196
3383
  if (opts.json) {
3197
3384
  console.log(JSON.stringify({ checks, ready: result.ready }, null, 2));
@@ -3807,7 +3994,7 @@ Recent runs for story ${storyId.slice(0, 8)}\u2026:
3807
3994
  const consoleUrl = config.consoleUrl ?? "https://app.mushi.ai";
3808
3995
  console.log(`
3809
3996
  Open in console: ${consoleUrl}/qa-coverage?story=${storyId}`);
3810
- console.log(` Tip: run 'mushi config consoleUrl http://localhost:6464' to set your local console URL`);
3997
+ console.log(` Tip: run 'mushi config consoleUrl <url>' to override (e.g. http://localhost:6464 for local dev)`);
3811
3998
  console.log();
3812
3999
  });
3813
4000
  qa.command("run <storyId>").description("Manually trigger a QA story run (fire-and-forget; check results with `mushi qa runs <id>`)").option("--json", "Machine-readable output").action(async (storyId, opts) => {
@@ -3882,14 +4069,11 @@ Examples:
3882
4069
  "Content-Type": "application/json",
3883
4070
  "X-Mushi-Project-Id": projectId
3884
4071
  };
3885
- const jwt = config.jwt ?? null;
3886
4072
  const apiKey = config.apiKey ?? null;
3887
- if (jwt) {
3888
- headers["Authorization"] = `Bearer ${jwt.replace(/[\r\n\0]/g, "")}`;
3889
- } else if (apiKey) {
4073
+ if (apiKey) {
3890
4074
  headers["X-Mushi-Api-Key"] = sanitizeApiKey(apiKey);
3891
4075
  } else {
3892
- process.stderr.write("error: no credentials found. Run `mushi login` first.\n");
4076
+ process.stderr.write("error: no API key found. Run `mushi login` or set MUSHI_API_KEY.\n");
3893
4077
  process.exit(1);
3894
4078
  }
3895
4079
  if (!opts.json) process.stdout.write("Running full-stack audit\u2026 ");
@@ -4066,6 +4250,40 @@ skills.command("sync").description("Trigger skill sync for all configured skill
4066
4250
  console.log();
4067
4251
  });
4068
4252
  var pipeline = program.command("pipeline").description("Manage skill pipeline runs");
4253
+ var consoleCmd = program.command("console").description("Live developer console for Mushi threads");
4254
+ consoleCmd.command("watch <reportId>").description("Watch unified report timeline (alias for mushi reports thread --watch)").option("--json", "NDJSON output for non-TTY").action(async (reportId, opts) => {
4255
+ const config = requireConfig();
4256
+ const fetchTimeline = async () => {
4257
+ const result = await apiCall(`/v1/sync/reports/${reportId}/timeline`, config);
4258
+ if (!result.ok) die(result);
4259
+ return result.data;
4260
+ };
4261
+ if (opts.json || !process.stdout.isTTY) {
4262
+ let lastLen2 = -1;
4263
+ for (; ; ) {
4264
+ const data = await fetchTimeline();
4265
+ if (data.timeline.length !== lastLen2) {
4266
+ console.log(JSON.stringify({ type: "timeline", ...data }));
4267
+ lastLen2 = data.timeline.length;
4268
+ }
4269
+ await new Promise((r) => setTimeout(r, 3e3));
4270
+ }
4271
+ }
4272
+ let lastLen = -1;
4273
+ for (; ; ) {
4274
+ const data = await fetchTimeline();
4275
+ if (data.timeline.length !== lastLen) {
4276
+ console.clear();
4277
+ console.log(`Console \xB7 ${data.report_id}`);
4278
+ for (const e of data.timeline) {
4279
+ console.log(`${fmtDate(e.at)} [${e.lane}] ${e.title}`);
4280
+ if (e.body) console.log(` ${e.body}`);
4281
+ }
4282
+ lastLen = data.timeline.length;
4283
+ }
4284
+ await new Promise((r) => setTimeout(r, 3e3));
4285
+ }
4286
+ });
4069
4287
  pipeline.command("start <reportId>").description("Start a skill pipeline for a report").requiredOption("--skill <slug>", "Root skill slug (e.g. workflow-fix-and-ship)").option("--mode <mode>", "Execution mode: handoff (default) or cloud", "handoff").option("--json", "Machine-readable output").action(async (reportId, opts) => {
4070
4288
  const config = loadConfig();
4071
4289
  if (!config.apiKey) {
package/dist/init.js CHANGED
@@ -5,10 +5,10 @@ import {
5
5
  envVarsToWrite,
6
6
  installCommand,
7
7
  readPackageJson
8
- } from "./chunk-NYPX5KXR.js";
8
+ } from "./chunk-S4WQHEGH.js";
9
9
  import {
10
10
  MUSHI_CLI_VERSION
11
- } from "./chunk-BCTHNCIY.js";
11
+ } from "./chunk-XVLIMBPU.js";
12
12
 
13
13
  // src/init.ts
14
14
  import * as p from "@clack/prompts";
@@ -112,6 +112,13 @@ function tightenDirPermissions(path) {
112
112
  // src/endpoint.ts
113
113
  var TEST_REPORT_TIMEOUT_MS = 1e4;
114
114
  var TEST_REPORT_FETCH_TIMEOUT_MS = TEST_REPORT_TIMEOUT_MS;
115
+ var CLOUD_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
116
+ function resolveCloudEndpoint(explicit) {
117
+ if (explicit?.trim()) return explicit.trim();
118
+ const fromEnv = process.env.MUSHI_API_ENDPOINT?.trim();
119
+ if (fromEnv) return fromEnv;
120
+ return CLOUD_API_ENDPOINT;
121
+ }
115
122
  function normalizeEndpoint(url) {
116
123
  if (!url) {
117
124
  throw new Error(
@@ -311,11 +318,12 @@ async function runInit(options = {}) {
311
318
  } else {
312
319
  p.log.info(`Skipped install. Run \`${installCommand(pm, packagesToInstall)}\` yourself.`);
313
320
  }
321
+ const endpoint = resolveCloudEndpoint(options.endpoint);
314
322
  writeEnvFile(cwd, credentials.apiKey, credentials.projectId, framework);
315
- persistCliConfig(credentials.apiKey, credentials.projectId);
323
+ persistCliConfig(credentials.apiKey, credentials.projectId, endpoint);
316
324
  const enableRewards = await maybeEnableRewards(options);
317
325
  printNextSteps(framework, credentials.apiKey, credentials.projectId, enableRewards);
318
- await maybeSendTestReport(credentials, options);
326
+ await maybeSendTestReport(credentials, { ...options, endpoint });
319
327
  p.outro("Setup complete. Happy bug squashing \u{1F41B}");
320
328
  }
321
329
  function ensureInteractiveOrBailOut(options) {
@@ -489,9 +497,9 @@ function warnIfMissingFromGitignore(cwd, envFile) {
489
497
  p.log.warn(`${envFile} is not in .gitignore \u2014 add it before committing.`);
490
498
  }
491
499
  }
492
- function persistCliConfig(apiKey, projectId) {
500
+ function persistCliConfig(apiKey, projectId, endpoint) {
493
501
  const existing = loadConfig();
494
- saveConfig({ ...existing, apiKey, projectId });
502
+ saveConfig({ ...existing, apiKey, projectId, endpoint });
495
503
  }
496
504
  function printNextSteps(framework, apiKey, projectId, enableRewards = false) {
497
505
  p.note(framework.snippet(apiKey, projectId), "Add this to your app:");
@@ -535,16 +543,9 @@ async function maybeSendTestReport(credentials, options) {
535
543
  shouldSend = answer;
536
544
  }
537
545
  if (!shouldSend) return;
538
- if (!options.endpoint) {
539
- p.note(
540
- "No endpoint configured \u2014 skipping test report.\nSet endpoint to your Supabase edge function URL, e.g. https://xyz.supabase.co/functions/v1/api",
541
- "Skipped"
542
- );
543
- return;
544
- }
546
+ const endpoint = normalizeEndpoint(resolveCloudEndpoint(options.endpoint));
545
547
  const spinner2 = p.spinner();
546
548
  spinner2.start("Sending test report\u2026");
547
- const endpoint = normalizeEndpoint(options.endpoint);
548
549
  const controller = new AbortController();
549
550
  const timer = setTimeout(() => controller.abort(), TEST_REPORT_FETCH_TIMEOUT_MS);
550
551
  try {
package/dist/version.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MUSHI_CLI_VERSION
3
- } from "./chunk-BCTHNCIY.js";
3
+ } from "./chunk-XVLIMBPU.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.16.0",
3
+ "version": "0.17.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": {
@@ -1,6 +0,0 @@
1
- // src/version.ts
2
- var MUSHI_CLI_VERSION = true ? "0.16.0" : "0.0.0-dev";
3
-
4
- export {
5
- MUSHI_CLI_VERSION
6
- };