@mushi-mushi/cli 0.17.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 +25 -16
- package/dist/{chunk-NYPX5KXR.js → chunk-S4WQHEGH.js} +1 -0
- package/dist/chunk-XVLIMBPU.js +6 -0
- package/dist/detect.js +1 -1
- package/dist/index.js +234 -25
- package/dist/init.js +15 -14
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-OTQQZKPK.js +0 -6
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.
|
|
156
|
-
|
|
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
|
|
160
|
-
mushi doctor --server
|
|
161
|
-
mushi doctor --ingest
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
server checks (`key`
|
|
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
|
-
|
|
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
|
-
|
|
188
|
-
SDK API key) and
|
|
189
|
-
API key, SDK heartbeat, at
|
|
190
|
-
`Last SDK heartbeat` diagnostic with the
|
|
191
|
-
the same payload `mushi connect --wait`
|
|
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;
|
package/dist/detect.js
CHANGED
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 +
|
|
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.17.
|
|
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
|
-
|
|
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 (
|
|
1836
|
+
if (runServer) {
|
|
1760
1837
|
const serverChecks = await checkServerPreflight(config, doFetch);
|
|
1761
1838
|
checks.push(...serverChecks);
|
|
1762
1839
|
}
|
|
1763
|
-
if (
|
|
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);
|
|
@@ -2549,6 +2680,37 @@ Examples:
|
|
|
2549
2680
|
console.log("");
|
|
2550
2681
|
}
|
|
2551
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
|
+
});
|
|
2552
2714
|
var lessons = program.command("lessons").description("Manage learned mistake rules");
|
|
2553
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", `
|
|
2554
2716
|
Lessons are mistake rules extracted from past bug reports by the clustering
|
|
@@ -3191,19 +3353,32 @@ Examples:
|
|
|
3191
3353
|
if (!result.ok) process.exit(1);
|
|
3192
3354
|
});
|
|
3193
3355
|
program.command("doctor").description(
|
|
3194
|
-
"Run pre-flight checks: CLI config, endpoint reachability,
|
|
3195
|
-
).option("--cwd <path>", "Run package detection from a different directory").option("--json", "Machine-readable output").option(
|
|
3196
|
-
"--server",
|
|
3197
|
-
"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."
|
|
3198
|
-
).option(
|
|
3199
|
-
"--ingest",
|
|
3200
|
-
"Also call GET /v1/sync/ingest-setup for the 4 required ingest steps (API key, SDK heartbeat, first report). Composable with --server."
|
|
3201
|
-
).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(
|
|
3202
3358
|
"--qa-stories",
|
|
3203
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."
|
|
3204
3366
|
).action(async (opts) => {
|
|
3205
3367
|
const config = loadConfig();
|
|
3206
|
-
const
|
|
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
|
+
}
|
|
3207
3382
|
const { checks } = result;
|
|
3208
3383
|
if (opts.json) {
|
|
3209
3384
|
console.log(JSON.stringify({ checks, ready: result.ready }, null, 2));
|
|
@@ -4075,6 +4250,40 @@ skills.command("sync").description("Trigger skill sync for all configured skill
|
|
|
4075
4250
|
console.log();
|
|
4076
4251
|
});
|
|
4077
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
|
+
});
|
|
4078
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) => {
|
|
4079
4288
|
const config = loadConfig();
|
|
4080
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-
|
|
8
|
+
} from "./chunk-S4WQHEGH.js";
|
|
9
9
|
import {
|
|
10
10
|
MUSHI_CLI_VERSION
|
|
11
|
-
} from "./chunk-
|
|
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
|
-
|
|
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
package/package.json
CHANGED