@mushi-mushi/cli 0.11.2 → 0.13.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/README.md +62 -0
- package/dist/chunk-IMDLL4EO.js +6 -0
- package/dist/index.js +670 -50
- package/dist/init.js +8 -5
- package/dist/version.js +1 -1
- package/package.json +10 -10
- package/dist/chunk-GPF6KFBM.js +0 -6
package/README.md
CHANGED
|
@@ -52,6 +52,9 @@ mushi --version
|
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
54
|
mushi login --api-key mushi_xxx # store credentials in ~/.mushirc (mode 0o600)
|
|
55
|
+
mushi connect --api-key mushi_xxx --project-id <uuid> --endpoint <url> --wait
|
|
56
|
+
# one-shot wiring: ~/.mushirc + .env.local + .cursor/mcp.json + heartbeat wait
|
|
57
|
+
mushi upgrade # bump installed @mushi-mushi/* packages to latest stable
|
|
55
58
|
mushi status # project overview
|
|
56
59
|
mushi reports list # recent reports
|
|
57
60
|
mushi reports show <id> # one report
|
|
@@ -65,6 +68,51 @@ mushi config endpoint https://... # set API endpoint (https:// required outsi
|
|
|
65
68
|
mushi sourcemaps upload --release <ver> --dir <dist> # upload .js.map / .css.map (sha256-idempotent)
|
|
66
69
|
```
|
|
67
70
|
|
|
71
|
+
### `mushi connect`
|
|
72
|
+
|
|
73
|
+
Non-interactive equivalent of `mushi init` for agents and scripts — wires a
|
|
74
|
+
repo to an existing project in one shot:
|
|
75
|
+
|
|
76
|
+
1. Saves credentials to `~/.mushirc` (mode `0o600`).
|
|
77
|
+
2. Merges `MUSHI_*` / framework-prefixed env vars into `.env.local` —
|
|
78
|
+
existing keys are never overwritten (skip with `--no-env`).
|
|
79
|
+
3. Writes the `@mushi-mushi/mcp` server block into `.cursor/mcp.json` and
|
|
80
|
+
ensures that file is gitignored, since it embeds the API key (skip with
|
|
81
|
+
`--no-ide`).
|
|
82
|
+
4. With `--wait`, polls `GET /v1/sync/ingest-setup` every 3 s until the SDK
|
|
83
|
+
heartbeat (or first report) lands, up to `--wait-timeout <sec>`
|
|
84
|
+
(default 120). Rejected credentials (401/403/404) fail fast instead of
|
|
85
|
+
polling out the timeout.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Recommended: pass the key via env so it stays out of shell history
|
|
89
|
+
MUSHI_API_KEY=mushi_xxx mushi connect --project-id <uuid> \
|
|
90
|
+
--endpoint https://<ref>.supabase.co/functions/v1/api --wait
|
|
91
|
+
mushi connect --api-key mushi_xxx --project-id <uuid> --endpoint <url> --no-ide --json
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Exits non-zero when `--wait` times out or credentials are rejected, so it
|
|
95
|
+
composes in setup scripts and CI.
|
|
96
|
+
|
|
97
|
+
### `mushi upgrade`
|
|
98
|
+
|
|
99
|
+
Reads `package.json`, checks the npm registry for each installed
|
|
100
|
+
`@mushi-mushi/*` package, and runs the right install command for your package
|
|
101
|
+
manager (npm / pnpm / yarn / bun) to bump them to the latest stable release.
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
mushi upgrade # plan + install
|
|
105
|
+
mushi upgrade --dry-run # print the install command without running it
|
|
106
|
+
mushi upgrade --json # machine-readable plan + result
|
|
107
|
+
mushi upgrade --cwd ../app # target another repo
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Pre-release versions are never auto-selected, registry versions are validated
|
|
111
|
+
against strict semver before being interpolated into the install command, and
|
|
112
|
+
non-registry specifiers (`workspace:`, `file:`, git URLs, dist-tags) are left
|
|
113
|
+
untouched. Legacy `@mushi-mushi/react` installs get a migration note pointing
|
|
114
|
+
at `@mushi-mushi/web`.
|
|
115
|
+
|
|
68
116
|
### `mushi sourcemaps upload`
|
|
69
117
|
|
|
70
118
|
Recursively scans `--dir` for `.js.map` and `.css.map` files and uploads them
|
|
@@ -110,6 +158,7 @@ reachability only.
|
|
|
110
158
|
```bash
|
|
111
159
|
mushi doctor # local checks only
|
|
112
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)
|
|
113
162
|
mushi doctor --json # machine-readable JSON output (exits 1 if any check fails)
|
|
114
163
|
```
|
|
115
164
|
|
|
@@ -135,6 +184,13 @@ server checks (`key` values returned by the endpoint):
|
|
|
135
184
|
`--server` requires `adminOrApiKey` credentials — set `MUSHI_API_KEY` to an
|
|
136
185
|
admin key (not a public SDK key).
|
|
137
186
|
|
|
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.
|
|
193
|
+
|
|
138
194
|
### `mushi reset <projectId>`
|
|
139
195
|
|
|
140
196
|
Archives a project and wipes its test data so the full onboarding flow can be
|
|
@@ -169,3 +225,9 @@ Exits automatically when the job reaches a terminal status (`completed`,
|
|
|
169
225
|
## License
|
|
170
226
|
|
|
171
227
|
MIT
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
<!-- mushi-readme-stats-footer -->
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
<sub>Monorepo scale (June 2026): 43 edge functions · 234 SQL migrations · 13 outbound plugins · 11 inbound adapters. Canonical counts: <a href="https://github.com/kensaurus/mushi-mushi/blob/master/docs/stats.md">docs/stats.md</a> · <code>pnpm docs-stats</code></sub>
|
package/dist/index.js
CHANGED
|
@@ -35,10 +35,11 @@ function loadConfig(path = CONFIG_PATH) {
|
|
|
35
35
|
} else if (path === CONFIG_PATH && existsSync(LEGACY_CONFIG_PATH)) {
|
|
36
36
|
file = migrateLegacyConfig() ?? {};
|
|
37
37
|
}
|
|
38
|
+
const endpointFromEnv = process.env["MUSHI_API_ENDPOINT"] ?? process.env["MUSHI_ENDPOINT"] ?? void 0;
|
|
38
39
|
const fromEnv = {
|
|
39
40
|
...process.env["MUSHI_API_KEY"] ? { apiKey: process.env["MUSHI_API_KEY"] } : {},
|
|
40
41
|
...process.env["MUSHI_PROJECT_ID"] ? { projectId: process.env["MUSHI_PROJECT_ID"] } : {},
|
|
41
|
-
...
|
|
42
|
+
...endpointFromEnv ? { endpoint: endpointFromEnv } : {}
|
|
42
43
|
};
|
|
43
44
|
return { ...file, ...fromEnv };
|
|
44
45
|
}
|
|
@@ -444,7 +445,7 @@ function normalizeEndpoint(url) {
|
|
|
444
445
|
var REGISTRY = "https://registry.npmjs.org";
|
|
445
446
|
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
446
447
|
async function checkFreshness(packageName, currentVersion, opts = {}) {
|
|
447
|
-
if (process.env.MUSHI_NO_UPDATE_CHECK === "1") return null;
|
|
448
|
+
if (!opts.ignoreOptOut && process.env.MUSHI_NO_UPDATE_CHECK === "1") return null;
|
|
448
449
|
const registry = opts.registry ?? REGISTRY;
|
|
449
450
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
450
451
|
const controller = new AbortController();
|
|
@@ -482,11 +483,13 @@ function isNewerStableVersion(latest, current) {
|
|
|
482
483
|
return lc > cc;
|
|
483
484
|
}
|
|
484
485
|
function stripPreRelease(version) {
|
|
485
|
-
const idx = version.
|
|
486
|
+
const idx = version.search(/[-+]/);
|
|
486
487
|
return idx === -1 ? version : version.slice(0, idx);
|
|
487
488
|
}
|
|
488
489
|
function hasPreReleaseTag(version) {
|
|
489
|
-
|
|
490
|
+
const plus = version.indexOf("+");
|
|
491
|
+
const hyphen = version.indexOf("-");
|
|
492
|
+
return hyphen !== -1 && (plus === -1 || hyphen < plus);
|
|
490
493
|
}
|
|
491
494
|
function parse(version) {
|
|
492
495
|
const parts = version.split(".").map((part) => Number(part));
|
|
@@ -595,7 +598,7 @@ function getFrameworkFromPkg(pkg) {
|
|
|
595
598
|
}
|
|
596
599
|
|
|
597
600
|
// src/version.ts
|
|
598
|
-
var MUSHI_CLI_VERSION = true ? "0.
|
|
601
|
+
var MUSHI_CLI_VERSION = true ? "0.13.0" : "0.0.0-dev";
|
|
599
602
|
|
|
600
603
|
// src/init.ts
|
|
601
604
|
var ENV_FILES = [".env.local", ".env"];
|
|
@@ -744,7 +747,7 @@ async function installPackages(pm, packages, cwd) {
|
|
|
744
747
|
function runCommand(pm, packages, cwd) {
|
|
745
748
|
const verb = pm === "npm" ? "install" : "add";
|
|
746
749
|
const command = process.platform === "win32" ? `${pm}.cmd` : pm;
|
|
747
|
-
return new Promise((
|
|
750
|
+
return new Promise((resolve4, reject) => {
|
|
748
751
|
const child = spawn(command, [verb, ...packages], {
|
|
749
752
|
stdio: "inherit",
|
|
750
753
|
shell: false,
|
|
@@ -753,7 +756,7 @@ function runCommand(pm, packages, cwd) {
|
|
|
753
756
|
});
|
|
754
757
|
child.on("error", reject);
|
|
755
758
|
child.on("exit", (code) => {
|
|
756
|
-
if (code === 0)
|
|
759
|
+
if (code === 0) resolve4();
|
|
757
760
|
else reject(new Error(`${pm} exited with code ${code ?? "null"}`));
|
|
758
761
|
});
|
|
759
762
|
});
|
|
@@ -1108,11 +1111,11 @@ async function findMapFiles(dir) {
|
|
|
1108
1111
|
return results;
|
|
1109
1112
|
}
|
|
1110
1113
|
function fileHash(path) {
|
|
1111
|
-
return new Promise((
|
|
1114
|
+
return new Promise((resolve4, reject) => {
|
|
1112
1115
|
const hash = createHash("sha256");
|
|
1113
1116
|
const stream = createReadStream(path);
|
|
1114
1117
|
stream.on("data", (chunk) => hash.update(chunk));
|
|
1115
|
-
stream.on("end", () =>
|
|
1118
|
+
stream.on("end", () => resolve4(hash.digest("hex")));
|
|
1116
1119
|
stream.on("error", reject);
|
|
1117
1120
|
});
|
|
1118
1121
|
}
|
|
@@ -1416,13 +1419,82 @@ function renderNudgeExplainer(phase) {
|
|
|
1416
1419
|
return lines.join("\n");
|
|
1417
1420
|
}
|
|
1418
1421
|
|
|
1422
|
+
// src/heartbeat-wait.ts
|
|
1423
|
+
var IngestSetupHttpError = class extends Error {
|
|
1424
|
+
status;
|
|
1425
|
+
constructor(status) {
|
|
1426
|
+
super(`ingest-setup HTTP ${status}`);
|
|
1427
|
+
this.name = "IngestSetupHttpError";
|
|
1428
|
+
this.status = status;
|
|
1429
|
+
}
|
|
1430
|
+
};
|
|
1431
|
+
var NON_RETRYABLE_STATUSES = /* @__PURE__ */ new Set([401, 403, 404]);
|
|
1432
|
+
async function fetchIngestSetup(config, doFetch = globalThis.fetch) {
|
|
1433
|
+
const res = await doFetch(`${config.endpoint}/v1/sync/ingest-setup`, {
|
|
1434
|
+
headers: {
|
|
1435
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
1436
|
+
"X-Mushi-Api-Key": config.apiKey,
|
|
1437
|
+
...config.projectId ? { "X-Mushi-Project": config.projectId } : {}
|
|
1438
|
+
},
|
|
1439
|
+
signal: AbortSignal.timeout(8e3)
|
|
1440
|
+
});
|
|
1441
|
+
if (!res.ok) {
|
|
1442
|
+
if (NON_RETRYABLE_STATUSES.has(res.status)) throw new IngestSetupHttpError(res.status);
|
|
1443
|
+
return null;
|
|
1444
|
+
}
|
|
1445
|
+
const body = await res.json();
|
|
1446
|
+
return body.ok && body.data ? body.data : null;
|
|
1447
|
+
}
|
|
1448
|
+
async function waitForIngestReady(options) {
|
|
1449
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
1450
|
+
const maxAttempts = options.maxAttempts ?? 40;
|
|
1451
|
+
const intervalMs = options.intervalMs ?? 3e3;
|
|
1452
|
+
let lastPayload = null;
|
|
1453
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1454
|
+
if (options.signal?.aborted) {
|
|
1455
|
+
return { ok: false, payload: lastPayload, attempts: attempt - 1, reason: "aborted" };
|
|
1456
|
+
}
|
|
1457
|
+
try {
|
|
1458
|
+
lastPayload = await fetchIngestSetup(
|
|
1459
|
+
{ endpoint: options.endpoint, apiKey: options.apiKey, projectId: options.projectId },
|
|
1460
|
+
doFetch
|
|
1461
|
+
);
|
|
1462
|
+
if (lastPayload) {
|
|
1463
|
+
options.onPoll?.(lastPayload, attempt);
|
|
1464
|
+
if (lastPayload.ready) {
|
|
1465
|
+
return { ok: true, payload: lastPayload, attempts: attempt, reason: "ready" };
|
|
1466
|
+
}
|
|
1467
|
+
const sdkStep = lastPayload.steps.find((s) => s.id === "sdk_installed");
|
|
1468
|
+
if (sdkStep?.complete) {
|
|
1469
|
+
return { ok: true, payload: lastPayload, attempts: attempt, reason: "heartbeat" };
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
} catch (err) {
|
|
1473
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1474
|
+
if (err instanceof IngestSetupHttpError) {
|
|
1475
|
+
return { ok: false, payload: lastPayload, attempts: attempt, reason: "unauthorized", error: msg };
|
|
1476
|
+
}
|
|
1477
|
+
if (options.signal?.aborted) {
|
|
1478
|
+
return { ok: false, payload: lastPayload, attempts: attempt, reason: "aborted", error: msg };
|
|
1479
|
+
}
|
|
1480
|
+
if (attempt === maxAttempts) {
|
|
1481
|
+
return { ok: false, payload: lastPayload, attempts: attempt, reason: "fetch-error", error: msg };
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
if (attempt < maxAttempts) {
|
|
1485
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
return { ok: false, payload: lastPayload, attempts: maxAttempts, reason: "timeout" };
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1419
1491
|
// src/doctor.ts
|
|
1420
1492
|
function checkCliConfig(config) {
|
|
1421
1493
|
return [
|
|
1422
1494
|
{
|
|
1423
1495
|
name: "CLI config file",
|
|
1424
1496
|
ok: Boolean(config.endpoint),
|
|
1425
|
-
detail: config.endpoint ? `endpoint=${config.endpoint}` : "No endpoint
|
|
1497
|
+
detail: config.endpoint ? `endpoint=${config.endpoint}` : "No endpoint \u2014 set MUSHI_API_ENDPOINT, run `mushi connect`, or `mushi config endpoint <url>`"
|
|
1426
1498
|
},
|
|
1427
1499
|
{
|
|
1428
1500
|
name: "API key configured",
|
|
@@ -1453,11 +1525,11 @@ async function checkEndpointReachability(endpoint, doFetch = globalThis.fetch) {
|
|
|
1453
1525
|
}
|
|
1454
1526
|
async function checkSdkInstall(cwd) {
|
|
1455
1527
|
try {
|
|
1456
|
-
const { readFile:
|
|
1457
|
-
const { join:
|
|
1458
|
-
const root =
|
|
1459
|
-
const pkgPath =
|
|
1460
|
-
const pkg = JSON.parse(await
|
|
1528
|
+
const { readFile: readFile3 } = await import("fs/promises");
|
|
1529
|
+
const { join: join7, resolve: resolve4 } = await import("path");
|
|
1530
|
+
const root = resolve4(cwd);
|
|
1531
|
+
const pkgPath = join7(root, "package.json");
|
|
1532
|
+
const pkg = JSON.parse(await readFile3(pkgPath, "utf8"));
|
|
1461
1533
|
const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
1462
1534
|
const sdks = [
|
|
1463
1535
|
"@mushi-mushi/react",
|
|
@@ -1519,6 +1591,44 @@ async function checkServerPreflight(config, doFetch = globalThis.fetch) {
|
|
|
1519
1591
|
return [{ name: "Server preflight", ok: false, detail: `Fetch failed: ${msg}` }];
|
|
1520
1592
|
}
|
|
1521
1593
|
}
|
|
1594
|
+
async function checkIngestSetup(config, doFetch = globalThis.fetch) {
|
|
1595
|
+
if (!config.apiKey || !config.endpoint) {
|
|
1596
|
+
return [
|
|
1597
|
+
{
|
|
1598
|
+
name: "Ingest setup",
|
|
1599
|
+
ok: false,
|
|
1600
|
+
detail: "Need apiKey and endpoint. Run `mushi connect`."
|
|
1601
|
+
}
|
|
1602
|
+
];
|
|
1603
|
+
}
|
|
1604
|
+
try {
|
|
1605
|
+
const data = await fetchIngestSetup(
|
|
1606
|
+
{ endpoint: config.endpoint, apiKey: config.apiKey, projectId: config.projectId },
|
|
1607
|
+
doFetch
|
|
1608
|
+
);
|
|
1609
|
+
if (!data) {
|
|
1610
|
+
return [{ name: "Ingest setup", ok: false, detail: "Request to /v1/sync/ingest-setup failed or returned invalid payload" }];
|
|
1611
|
+
}
|
|
1612
|
+
const steps = data.steps ?? [];
|
|
1613
|
+
const checks = steps.filter((s) => s.required).map((s) => ({
|
|
1614
|
+
name: `[ingest] ${s.label}`,
|
|
1615
|
+
ok: s.complete,
|
|
1616
|
+
detail: s.hint ?? ""
|
|
1617
|
+
}));
|
|
1618
|
+
const diag = data.diagnostic;
|
|
1619
|
+
if (diag?.last_sdk_seen_at) {
|
|
1620
|
+
checks.push({
|
|
1621
|
+
name: "[ingest] Last SDK heartbeat",
|
|
1622
|
+
ok: true,
|
|
1623
|
+
detail: `${diag.last_sdk_seen_at}${diag.last_sdk_endpoint_host ? ` @ ${diag.last_sdk_endpoint_host}` : ""}`
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
return checks.length > 0 ? checks : [{ name: "Ingest setup", ok: false, detail: "Empty response from /v1/sync/ingest-setup" }];
|
|
1627
|
+
} catch (err) {
|
|
1628
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1629
|
+
return [{ name: "Ingest setup", ok: false, detail: `Fetch failed: ${msg}` }];
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1522
1632
|
async function runDoctor(config, options = {}) {
|
|
1523
1633
|
const doFetch = options.fetch ?? globalThis.fetch;
|
|
1524
1634
|
const checks = [];
|
|
@@ -1532,6 +1642,10 @@ async function runDoctor(config, options = {}) {
|
|
|
1532
1642
|
const serverChecks = await checkServerPreflight(config, doFetch);
|
|
1533
1643
|
checks.push(...serverChecks);
|
|
1534
1644
|
}
|
|
1645
|
+
if (options.ingest) {
|
|
1646
|
+
const ingestChecks = await checkIngestSetup(config, doFetch);
|
|
1647
|
+
checks.push(...ingestChecks);
|
|
1648
|
+
}
|
|
1535
1649
|
return { checks, ready: checks.every((c) => c.ok) };
|
|
1536
1650
|
}
|
|
1537
1651
|
function formatDoctorResult(result) {
|
|
@@ -1553,6 +1667,232 @@ ${failed.length} check${failed.length === 1 ? "" : "s"} failed.`);
|
|
|
1553
1667
|
return lines.join("\n");
|
|
1554
1668
|
}
|
|
1555
1669
|
|
|
1670
|
+
// src/upgrade.ts
|
|
1671
|
+
import { resolve as resolve2 } from "path";
|
|
1672
|
+
import { execSync } from "child_process";
|
|
1673
|
+
var SAFE_NPM_VERSION = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
1674
|
+
var MUSHI_PACKAGES = [
|
|
1675
|
+
"@mushi-mushi/web",
|
|
1676
|
+
"@mushi-mushi/core",
|
|
1677
|
+
"@mushi-mushi/react",
|
|
1678
|
+
"@mushi-mushi/react-native",
|
|
1679
|
+
"@mushi-mushi/capacitor",
|
|
1680
|
+
"@mushi-mushi/node",
|
|
1681
|
+
"@mushi-mushi/cli"
|
|
1682
|
+
];
|
|
1683
|
+
async function planUpgrade(cwd) {
|
|
1684
|
+
const root = resolve2(cwd);
|
|
1685
|
+
const pkg = readPackageJson(root);
|
|
1686
|
+
const pm = detectPackageManager(root);
|
|
1687
|
+
const deps = {
|
|
1688
|
+
...pkg?.dependencies ?? {},
|
|
1689
|
+
...pkg?.devDependencies ?? {}
|
|
1690
|
+
};
|
|
1691
|
+
const installed2 = MUSHI_PACKAGES.filter((name) => deps[name]);
|
|
1692
|
+
const entries = await Promise.all(
|
|
1693
|
+
installed2.map(async (name) => {
|
|
1694
|
+
const current = deps[name] ?? "";
|
|
1695
|
+
const currentCore = current.replace(/^[\^~>=<]*/, "");
|
|
1696
|
+
if (!/^\d/.test(currentCore)) {
|
|
1697
|
+
return { name, current, latest: null, willUpgrade: false };
|
|
1698
|
+
}
|
|
1699
|
+
const freshness = await checkFreshness(name, currentCore, {
|
|
1700
|
+
timeoutMs: 4e3,
|
|
1701
|
+
ignoreOptOut: true
|
|
1702
|
+
});
|
|
1703
|
+
const latest = freshness?.latest ?? null;
|
|
1704
|
+
const safeLatest = latest && SAFE_NPM_VERSION.test(latest) ? latest : null;
|
|
1705
|
+
const willUpgrade = Boolean(freshness?.isOutdated && safeLatest);
|
|
1706
|
+
return {
|
|
1707
|
+
name,
|
|
1708
|
+
current,
|
|
1709
|
+
latest: safeLatest,
|
|
1710
|
+
willUpgrade,
|
|
1711
|
+
migrateToWeb: name === "@mushi-mushi/react" && willUpgrade
|
|
1712
|
+
};
|
|
1713
|
+
})
|
|
1714
|
+
);
|
|
1715
|
+
const toBump = entries.filter((e) => e.willUpgrade && e.latest).map((e) => `${e.name}@${e.latest}`);
|
|
1716
|
+
return {
|
|
1717
|
+
cwd: root,
|
|
1718
|
+
packageManager: pm,
|
|
1719
|
+
entries,
|
|
1720
|
+
installCmd: toBump.length > 0 ? installCommand(pm, toBump) : null
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
async function runUpgrade(opts = {}) {
|
|
1724
|
+
const plan = await planUpgrade(opts.cwd ?? process.cwd());
|
|
1725
|
+
if (plan.entries.length === 0) {
|
|
1726
|
+
return {
|
|
1727
|
+
plan,
|
|
1728
|
+
upgraded: false,
|
|
1729
|
+
message: "No @mushi-mushi/* packages in package.json \u2014 run `mushi init` first."
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
if (!plan.installCmd) {
|
|
1733
|
+
const semverEntries = plan.entries.filter((e) => /\d/.test(e.current));
|
|
1734
|
+
const allChecksFailed = semverEntries.length > 0 && semverEntries.every((e) => e.latest === null);
|
|
1735
|
+
return {
|
|
1736
|
+
plan,
|
|
1737
|
+
upgraded: false,
|
|
1738
|
+
message: allChecksFailed ? "Could not reach the npm registry to check for updates \u2014 try again in a moment." : "All installed Mushi packages are already at the latest stable version."
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
if (opts.dryRun) {
|
|
1742
|
+
return { plan, upgraded: false, message: `[dry-run] Would run: ${plan.installCmd}` };
|
|
1743
|
+
}
|
|
1744
|
+
try {
|
|
1745
|
+
execSync(plan.installCmd, { cwd: plan.cwd, stdio: "inherit", env: process.env });
|
|
1746
|
+
} catch {
|
|
1747
|
+
return {
|
|
1748
|
+
plan,
|
|
1749
|
+
upgraded: false,
|
|
1750
|
+
message: `Install command failed \u2014 fix the error above or run it manually:
|
|
1751
|
+
${plan.installCmd}`
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
const reactEntry = plan.entries.find((e) => e.migrateToWeb);
|
|
1755
|
+
const migrateNote = reactEntry ? "\nNote: @mushi-mushi/react is legacy \u2014 prefer @mushi-mushi/web for Vite/Capacitor/SPA apps." : "";
|
|
1756
|
+
return {
|
|
1757
|
+
plan,
|
|
1758
|
+
upgraded: true,
|
|
1759
|
+
message: `Upgraded Mushi SDK packages.
|
|
1760
|
+
${plan.installCmd}${migrateNote}`
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
// src/connect.ts
|
|
1765
|
+
import { appendFile, mkdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
1766
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1767
|
+
import { join as join6, resolve as resolve3 } from "path";
|
|
1768
|
+
function envKeyPresent(content, key) {
|
|
1769
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1770
|
+
return new RegExp(`^${escaped}=`, "m").test(content);
|
|
1771
|
+
}
|
|
1772
|
+
async function mergeEnvFile(path, lines) {
|
|
1773
|
+
const block = `
|
|
1774
|
+
# Mushi \u2014 added by mushi connect
|
|
1775
|
+
${lines.join("\n")}
|
|
1776
|
+
`;
|
|
1777
|
+
if (existsSync5(path)) {
|
|
1778
|
+
const content = await readFile2(path, "utf8");
|
|
1779
|
+
const needs = lines.filter((line) => {
|
|
1780
|
+
const key = line.split("=")[0];
|
|
1781
|
+
return !envKeyPresent(content, key);
|
|
1782
|
+
});
|
|
1783
|
+
if (needs.length === 0) return false;
|
|
1784
|
+
await appendFile(path, `
|
|
1785
|
+
# Mushi \u2014 added by mushi connect
|
|
1786
|
+
${needs.join("\n")}
|
|
1787
|
+
`, "utf8");
|
|
1788
|
+
return true;
|
|
1789
|
+
}
|
|
1790
|
+
await writeFile(path, block, "utf8");
|
|
1791
|
+
return true;
|
|
1792
|
+
}
|
|
1793
|
+
async function ensureMcpJsonGitignored(cwd, messages) {
|
|
1794
|
+
const gitignorePath = join6(cwd, ".gitignore");
|
|
1795
|
+
const patterns = [".cursor/mcp.json", ".cursor/"];
|
|
1796
|
+
if (!existsSync5(gitignorePath)) {
|
|
1797
|
+
messages.push(
|
|
1798
|
+
"\u26A0 No .gitignore found \u2014 .cursor/mcp.json contains your API key. Add `.cursor/mcp.json` before committing."
|
|
1799
|
+
);
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
const content = await readFile2(gitignorePath, "utf8");
|
|
1803
|
+
const covered = patterns.some((p3) => content.split("\n").some((line) => line.trim() === p3 || line.trim() === `${p3}/`));
|
|
1804
|
+
if (covered) return;
|
|
1805
|
+
await appendFile(gitignorePath, "\n# Mushi \u2014 keep MCP credentials out of git\n.cursor/mcp.json\n", "utf8");
|
|
1806
|
+
messages.push("\u2713 Added .cursor/mcp.json to .gitignore (contains API key)");
|
|
1807
|
+
}
|
|
1808
|
+
async function runConnect(opts, baseConfig = {}) {
|
|
1809
|
+
const cwd = resolve3(opts.cwd ?? process.cwd());
|
|
1810
|
+
const endpoint = assertEndpoint(opts.endpoint);
|
|
1811
|
+
const messages = [];
|
|
1812
|
+
const config = {
|
|
1813
|
+
...baseConfig,
|
|
1814
|
+
apiKey: opts.apiKey,
|
|
1815
|
+
projectId: opts.projectId,
|
|
1816
|
+
endpoint
|
|
1817
|
+
};
|
|
1818
|
+
saveConfig(config);
|
|
1819
|
+
messages.push(`\u2713 Credentials saved to ${CONFIG_PATH}`);
|
|
1820
|
+
let envPath = null;
|
|
1821
|
+
if (opts.writeEnv !== false) {
|
|
1822
|
+
const pkg = readPackageJson(cwd);
|
|
1823
|
+
const framework = detectFramework(cwd, pkg);
|
|
1824
|
+
const lines = envVarsToWrite(opts.apiKey, opts.projectId, framework).split("\n");
|
|
1825
|
+
envPath = join6(cwd, ".env.local");
|
|
1826
|
+
const wrote = await mergeEnvFile(envPath, lines);
|
|
1827
|
+
messages.push(
|
|
1828
|
+
wrote ? `\u2713 Env vars merged into ${envPath}` : `\u2713 Env vars already present in ${envPath} (existing values left untouched)`
|
|
1829
|
+
);
|
|
1830
|
+
}
|
|
1831
|
+
let mcpPath = null;
|
|
1832
|
+
if (opts.wireIde !== false) {
|
|
1833
|
+
const mcpDir = join6(cwd, ".cursor");
|
|
1834
|
+
mcpPath = join6(mcpDir, "mcp.json");
|
|
1835
|
+
const serverName = `mushi-${opts.projectId.slice(0, 8)}`;
|
|
1836
|
+
const mcpServerBlock = {
|
|
1837
|
+
command: "npx",
|
|
1838
|
+
args: ["-y", "@mushi-mushi/mcp@latest"],
|
|
1839
|
+
env: {
|
|
1840
|
+
MUSHI_API_ENDPOINT: endpoint,
|
|
1841
|
+
MUSHI_PROJECT_ID: opts.projectId,
|
|
1842
|
+
MUSHI_API_KEY: opts.apiKey
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
let merged = { mcpServers: {} };
|
|
1846
|
+
if (existsSync5(mcpPath)) {
|
|
1847
|
+
try {
|
|
1848
|
+
merged = JSON.parse(await readFile2(mcpPath, "utf8"));
|
|
1849
|
+
} catch {
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
const servers = merged.mcpServers ?? {};
|
|
1853
|
+
servers[serverName] = mcpServerBlock;
|
|
1854
|
+
merged.mcpServers = servers;
|
|
1855
|
+
await mkdir(mcpDir, { recursive: true });
|
|
1856
|
+
await writeFile(mcpPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
1857
|
+
await ensureMcpJsonGitignored(cwd, messages);
|
|
1858
|
+
messages.push(`\u2713 Wired ${mcpPath} \u2014 restart Cursor and run "list mushi tools"`);
|
|
1859
|
+
}
|
|
1860
|
+
let heartbeat = null;
|
|
1861
|
+
if (opts.wait) {
|
|
1862
|
+
const timeoutSec = opts.waitTimeoutSec ?? 120;
|
|
1863
|
+
const maxAttempts = Math.max(1, Math.ceil(timeoutSec * 1e3 / 3e3));
|
|
1864
|
+
messages.push(`\u2026 Waiting for SDK heartbeat (up to ${timeoutSec}s) \u2014 start your dev server with the snippet installed`);
|
|
1865
|
+
heartbeat = await waitForIngestReady({
|
|
1866
|
+
endpoint,
|
|
1867
|
+
apiKey: opts.apiKey,
|
|
1868
|
+
projectId: opts.projectId,
|
|
1869
|
+
maxAttempts,
|
|
1870
|
+
onPoll: (payload, attempt) => {
|
|
1871
|
+
if (!opts.json && attempt % 3 === 0) {
|
|
1872
|
+
const sdk = payload.steps.find((s) => s.id === "sdk_installed");
|
|
1873
|
+
const seen = payload.diagnostic?.last_sdk_seen_at ?? "never";
|
|
1874
|
+
process.stdout.write(` poll ${attempt}: sdk_installed=${sdk?.complete ? "yes" : "no"} last_seen=${seen}
|
|
1875
|
+
`);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
});
|
|
1879
|
+
if (heartbeat.ok) {
|
|
1880
|
+
const label = heartbeat.reason === "heartbeat" ? "SDK heartbeat detected" : "Ingest setup complete";
|
|
1881
|
+
messages.push(`\u2713 ${label} \u2014 ingest pipeline is live`);
|
|
1882
|
+
} else if (heartbeat.reason === "unauthorized") {
|
|
1883
|
+
messages.push(
|
|
1884
|
+
`\u2717 The backend rejected these credentials (${heartbeat.error ?? "auth error"}). Double-check --api-key, --project-id, and --endpoint, then re-run \`mushi connect --wait\`.`
|
|
1885
|
+
);
|
|
1886
|
+
} else {
|
|
1887
|
+
messages.push(
|
|
1888
|
+
`\u2717 No heartbeat before timeout (${heartbeat.reason}). Confirm env vars are in your build, restart the dev server, then re-run \`mushi connect --wait\`.`
|
|
1889
|
+
);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
const ok = !opts.wait || Boolean(heartbeat?.ok);
|
|
1893
|
+
return { ok, envPath, mcpPath, heartbeat, messages };
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1556
1896
|
// src/index.ts
|
|
1557
1897
|
installSignalHandlers();
|
|
1558
1898
|
var API_TIMEOUT_MS = 15e3;
|
|
@@ -2016,6 +2356,31 @@ reports.command("dismiss <id>").description("Dismiss a report (not a real bug /
|
|
|
2016
2356
|
console.log(`\u2713 Dismissed report ${id}`);
|
|
2017
2357
|
}
|
|
2018
2358
|
});
|
|
2359
|
+
reports.command("reply <id> <message>").description("Send a visible reply to the reporter widget for a report").option("--author <name>", 'Display name for the sender (default: "Mushi Admin")').option("--json", "Machine-readable JSON output").addHelpText("after", `
|
|
2360
|
+
Examples:
|
|
2361
|
+
mushi reports reply abc123 "Thanks for reporting \u2014 fixing this in the next release."
|
|
2362
|
+
mushi reports reply abc123 "Can you share a screenshot?" --author "Alice"`).action(async (id, message, opts) => {
|
|
2363
|
+
const config = requireConfig();
|
|
2364
|
+
const body = { message };
|
|
2365
|
+
if (opts.author) body["author_name"] = opts.author;
|
|
2366
|
+
const result = await apiCall(`/v1/sync/reports/${id}/reply`, config, {
|
|
2367
|
+
method: "POST",
|
|
2368
|
+
body: JSON.stringify(body)
|
|
2369
|
+
});
|
|
2370
|
+
if (!result.ok) {
|
|
2371
|
+
if (result.httpStatus === 404 || result.error.code === "NOT_FOUND") {
|
|
2372
|
+
process.stderr.write(`error: report "${id}" not found
|
|
2373
|
+
`);
|
|
2374
|
+
process.exit(3);
|
|
2375
|
+
}
|
|
2376
|
+
die(result);
|
|
2377
|
+
}
|
|
2378
|
+
if (opts.json) {
|
|
2379
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
2380
|
+
} else {
|
|
2381
|
+
console.log(`\u2713 Reply sent to reporter for report ${id}`);
|
|
2382
|
+
}
|
|
2383
|
+
});
|
|
2019
2384
|
reports.command("search <query>").description("Search reports by keyword in summary and description").option("--limit <n>", "Max results (1\u201350)", "10").option("--status <status>", "Filter by status").option("--json", "Machine-readable JSON output").addHelpText("after", `
|
|
2020
2385
|
Examples:
|
|
2021
2386
|
mushi reports search "login button"
|
|
@@ -2117,7 +2482,7 @@ AI code review context.
|
|
|
2117
2482
|
Typical CI usage:
|
|
2118
2483
|
MUSHI_API_KEY=$KEY MUSHI_PROJECT_ID=$PID MUSHI_API_ENDPOINT=$URL \\
|
|
2119
2484
|
npx @mushi-mushi/cli sync-lessons --cwd .`).action(async (opts) => {
|
|
2120
|
-
const { writeFile, mkdir } = await import("fs/promises");
|
|
2485
|
+
const { writeFile: writeFile2, mkdir: mkdir2 } = await import("fs/promises");
|
|
2121
2486
|
const nodePath = await import("path");
|
|
2122
2487
|
const config = requireConfig();
|
|
2123
2488
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -2144,8 +2509,8 @@ Typical CI usage:
|
|
|
2144
2509
|
console.log(JSON.stringify(output, null, 2));
|
|
2145
2510
|
return;
|
|
2146
2511
|
}
|
|
2147
|
-
await
|
|
2148
|
-
await
|
|
2512
|
+
await mkdir2(nodePath.dirname(target), { recursive: true });
|
|
2513
|
+
await writeFile2(target, JSON.stringify(output, null, 2) + "\n", "utf8");
|
|
2149
2514
|
if (opts.json) {
|
|
2150
2515
|
console.log(JSON.stringify({ ok: true, path: target, count: lessons2.length }));
|
|
2151
2516
|
} else {
|
|
@@ -2194,7 +2559,7 @@ Examples:
|
|
|
2194
2559
|
mushi index ./src
|
|
2195
2560
|
mushi index ./src --language ts --dry-run`).action(async (path, opts) => {
|
|
2196
2561
|
const config = requireConfig({ needsProject: true });
|
|
2197
|
-
const { readdir: readdir2, readFile:
|
|
2562
|
+
const { readdir: readdir2, readFile: readFile3, stat } = await import("fs/promises");
|
|
2198
2563
|
const nodePath = await import("path");
|
|
2199
2564
|
const SKIP = /node_modules|\.git|dist|build|\.next|\.turbo|coverage/;
|
|
2200
2565
|
const EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs"]);
|
|
@@ -2221,7 +2586,7 @@ Examples:
|
|
|
2221
2586
|
`);
|
|
2222
2587
|
continue;
|
|
2223
2588
|
}
|
|
2224
|
-
const source = await
|
|
2589
|
+
const source = await readFile3(file, "utf8");
|
|
2225
2590
|
const relative2 = nodePath.relative(root, file).replaceAll("\\", "/");
|
|
2226
2591
|
count++;
|
|
2227
2592
|
bytes += source.length;
|
|
@@ -2278,8 +2643,8 @@ Typical first-time flow:
|
|
|
2278
2643
|
# Browser opens \u2192 sign up / magic-link \u2192 come back to terminal
|
|
2279
2644
|
# CLI writes .env.local and .cursor/mcp.json
|
|
2280
2645
|
# mushi whoami to confirm`).action(async (opts) => {
|
|
2281
|
-
const { writeFile, mkdir } = await import("fs/promises");
|
|
2282
|
-
const { existsSync:
|
|
2646
|
+
const { writeFile: writeFile2, mkdir: mkdir2 } = await import("fs/promises");
|
|
2647
|
+
const { existsSync: existsSync6 } = await import("fs");
|
|
2283
2648
|
const nodePath = await import("path");
|
|
2284
2649
|
const endpoint = opts.endpoint ?? loadConfig().endpoint ?? "https://api.mushimushi.dev";
|
|
2285
2650
|
const signUpUrl = "https://kensaur.us/mushi-mushi/sign-up";
|
|
@@ -2304,7 +2669,7 @@ Typical first-time flow:
|
|
|
2304
2669
|
console.log("");
|
|
2305
2670
|
const { createInterface } = await import("readline");
|
|
2306
2671
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
2307
|
-
const ask = (q) => new Promise((
|
|
2672
|
+
const ask = (q) => new Promise((resolve4) => rl.question(q, (a) => resolve4(a.trim())));
|
|
2308
2673
|
const projectId = await ask(" Project ID (uuid): ");
|
|
2309
2674
|
const apiKey = await ask(" API key (mushi_...): ");
|
|
2310
2675
|
rl.close();
|
|
@@ -2326,18 +2691,18 @@ Typical first-time flow:
|
|
|
2326
2691
|
`MUSHI_API_KEY=${apiKey}`,
|
|
2327
2692
|
""
|
|
2328
2693
|
];
|
|
2329
|
-
const envExisting =
|
|
2330
|
-
await
|
|
2694
|
+
const envExisting = existsSync6(envPath);
|
|
2695
|
+
await writeFile2(envPath, envLines.join("\n"), "utf8");
|
|
2331
2696
|
console.log(`
|
|
2332
2697
|
\u2713 ${envExisting ? "Updated" : "Created"} .env.local`);
|
|
2333
2698
|
const mcpDir = nodePath.join(cwd, ".cursor");
|
|
2334
|
-
await
|
|
2699
|
+
await mkdir2(mcpDir, { recursive: true });
|
|
2335
2700
|
const mcpPath = nodePath.join(mcpDir, "mcp.json");
|
|
2336
2701
|
const mcpJson = {
|
|
2337
2702
|
mcpServers: {
|
|
2338
2703
|
mushi: {
|
|
2339
2704
|
command: "npx",
|
|
2340
|
-
args: ["-y", "mushi-mcp@latest"],
|
|
2705
|
+
args: ["-y", "@mushi-mushi/mcp@latest"],
|
|
2341
2706
|
env: {
|
|
2342
2707
|
MUSHI_API_ENDPOINT: endpoint,
|
|
2343
2708
|
MUSHI_PROJECT_ID: projectId,
|
|
@@ -2346,19 +2711,19 @@ Typical first-time flow:
|
|
|
2346
2711
|
}
|
|
2347
2712
|
}
|
|
2348
2713
|
};
|
|
2349
|
-
const mcpExisting =
|
|
2714
|
+
const mcpExisting = existsSync6(mcpPath);
|
|
2350
2715
|
if (mcpExisting) {
|
|
2351
2716
|
try {
|
|
2352
|
-
const { readFile:
|
|
2353
|
-
const raw = JSON.parse(await
|
|
2717
|
+
const { readFile: readFile3 } = await import("fs/promises");
|
|
2718
|
+
const raw = JSON.parse(await readFile3(mcpPath, "utf8"));
|
|
2354
2719
|
const existing = raw;
|
|
2355
2720
|
existing.mcpServers = { ...existing.mcpServers ?? {}, mushi: mcpJson.mcpServers.mushi };
|
|
2356
|
-
await
|
|
2721
|
+
await writeFile2(mcpPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
2357
2722
|
} catch {
|
|
2358
|
-
await
|
|
2723
|
+
await writeFile2(mcpPath, JSON.stringify(mcpJson, null, 2) + "\n", "utf8");
|
|
2359
2724
|
}
|
|
2360
2725
|
} else {
|
|
2361
|
-
await
|
|
2726
|
+
await writeFile2(mcpPath, JSON.stringify(mcpJson, null, 2) + "\n", "utf8");
|
|
2362
2727
|
}
|
|
2363
2728
|
console.log(` \u2713 ${mcpExisting ? "Updated" : "Created"} .cursor/mcp.json`);
|
|
2364
2729
|
console.log("");
|
|
@@ -2379,8 +2744,8 @@ Supported IDEs:
|
|
|
2379
2744
|
zed \u2014 writes ~/.config/zed/settings.json mcpServers block
|
|
2380
2745
|
|
|
2381
2746
|
The command reads credentials from ~/.mushirc (run \`mushi login\` first).`).action(async (opts) => {
|
|
2382
|
-
const { writeFile, mkdir, readFile:
|
|
2383
|
-
const { existsSync:
|
|
2747
|
+
const { writeFile: writeFile2, mkdir: mkdir2, readFile: readFile3 } = await import("fs/promises");
|
|
2748
|
+
const { existsSync: existsSync6 } = await import("fs");
|
|
2384
2749
|
const nodePath = await import("path");
|
|
2385
2750
|
const os = await import("os");
|
|
2386
2751
|
const config = requireConfig({ needsProject: true });
|
|
@@ -2401,7 +2766,7 @@ The command reads credentials from ~/.mushirc (run \`mushi login\` first).`).act
|
|
|
2401
2766
|
const serverName = `mushi-${slug}`;
|
|
2402
2767
|
const mcpServerBlock = {
|
|
2403
2768
|
command: "npx",
|
|
2404
|
-
args: ["-y", "mushi-mcp@latest"],
|
|
2769
|
+
args: ["-y", "@mushi-mushi/mcp@latest"],
|
|
2405
2770
|
env: {
|
|
2406
2771
|
MUSHI_API_ENDPOINT: config.endpoint,
|
|
2407
2772
|
MUSHI_PROJECT_ID: config.projectId ?? "",
|
|
@@ -2412,9 +2777,9 @@ The command reads credentials from ~/.mushirc (run \`mushi login\` first).`).act
|
|
|
2412
2777
|
const configPath = nodePath.join(configDir, ideEntry.file);
|
|
2413
2778
|
if (ideEntry.format === "mcp-json") {
|
|
2414
2779
|
let merged = { mcpServers: {} };
|
|
2415
|
-
if (
|
|
2780
|
+
if (existsSync6(configPath)) {
|
|
2416
2781
|
try {
|
|
2417
|
-
const raw = await
|
|
2782
|
+
const raw = await readFile3(configPath, "utf8");
|
|
2418
2783
|
merged = JSON.parse(raw);
|
|
2419
2784
|
} catch {
|
|
2420
2785
|
}
|
|
@@ -2427,15 +2792,15 @@ The command reads credentials from ~/.mushirc (run \`mushi login\` first).`).act
|
|
|
2427
2792
|
console.log(`[dry-run] Would write ${configPath}:`);
|
|
2428
2793
|
console.log(output);
|
|
2429
2794
|
} else {
|
|
2430
|
-
await
|
|
2431
|
-
await
|
|
2795
|
+
await mkdir2(configDir, { recursive: true });
|
|
2796
|
+
await writeFile2(configPath, output, "utf8");
|
|
2432
2797
|
console.log(`\u2713 Written ${configPath}`);
|
|
2433
2798
|
}
|
|
2434
2799
|
} else if (ideEntry.format === "zed") {
|
|
2435
2800
|
let settings = {};
|
|
2436
|
-
if (
|
|
2801
|
+
if (existsSync6(configPath)) {
|
|
2437
2802
|
try {
|
|
2438
|
-
const raw = await
|
|
2803
|
+
const raw = await readFile3(configPath, "utf8");
|
|
2439
2804
|
settings = JSON.parse(raw);
|
|
2440
2805
|
} catch {
|
|
2441
2806
|
}
|
|
@@ -2444,7 +2809,7 @@ The command reads credentials from ~/.mushirc (run \`mushi login\` first).`).act
|
|
|
2444
2809
|
servers[serverName] = {
|
|
2445
2810
|
command: {
|
|
2446
2811
|
path: "npx",
|
|
2447
|
-
args: ["-y", "mushi-mcp@latest"],
|
|
2812
|
+
args: ["-y", "@mushi-mushi/mcp@latest"],
|
|
2448
2813
|
env: {
|
|
2449
2814
|
MUSHI_API_ENDPOINT: config.endpoint,
|
|
2450
2815
|
MUSHI_PROJECT_ID: config.projectId ?? "",
|
|
@@ -2459,8 +2824,8 @@ The command reads credentials from ~/.mushirc (run \`mushi login\` first).`).act
|
|
|
2459
2824
|
console.log(`[dry-run] Would write ${configPath}:`);
|
|
2460
2825
|
console.log(output);
|
|
2461
2826
|
} else {
|
|
2462
|
-
await
|
|
2463
|
-
await
|
|
2827
|
+
await mkdir2(configDir, { recursive: true });
|
|
2828
|
+
await writeFile2(configPath, output, "utf8");
|
|
2464
2829
|
console.log(`\u2713 Written ${configPath}`);
|
|
2465
2830
|
}
|
|
2466
2831
|
}
|
|
@@ -2494,7 +2859,7 @@ The command reads credentials from ~/.mushirc (run \`mushi login\` first).`).act
|
|
|
2494
2859
|
if (opts.dryRun) {
|
|
2495
2860
|
console.log(`[dry-run] Would write ${rulesPath}`);
|
|
2496
2861
|
} else {
|
|
2497
|
-
await
|
|
2862
|
+
await writeFile2(rulesPath, rulesContent, "utf8");
|
|
2498
2863
|
console.log(`\u2713 Written .cursorrules`);
|
|
2499
2864
|
}
|
|
2500
2865
|
} else if (opts.ide === "claude") {
|
|
@@ -2503,8 +2868,8 @@ The command reads credentials from ~/.mushirc (run \`mushi login\` first).`).act
|
|
|
2503
2868
|
if (opts.dryRun) {
|
|
2504
2869
|
console.log(`[dry-run] Would write ${rulesPath}`);
|
|
2505
2870
|
} else {
|
|
2506
|
-
await
|
|
2507
|
-
await
|
|
2871
|
+
await mkdir2(rulesDir, { recursive: true });
|
|
2872
|
+
await writeFile2(rulesPath, rulesContent, "utf8");
|
|
2508
2873
|
console.log(`\u2713 Written .claude/rules/mushi.md`);
|
|
2509
2874
|
}
|
|
2510
2875
|
}
|
|
@@ -2558,6 +2923,7 @@ Examples:
|
|
|
2558
2923
|
emitEvent("dispatch.start", { reportId, agent: opts.agent, model: opts.model ?? null });
|
|
2559
2924
|
const body = {
|
|
2560
2925
|
reportId,
|
|
2926
|
+
projectId: cfg.projectId,
|
|
2561
2927
|
agent: opts.agent
|
|
2562
2928
|
};
|
|
2563
2929
|
if (opts.agent === "cursor_cloud") {
|
|
@@ -2638,14 +3004,64 @@ program.command("nudge").description(
|
|
|
2638
3004
|
}
|
|
2639
3005
|
console.log(renderNudgeSnippet({ phase, overrides }));
|
|
2640
3006
|
});
|
|
3007
|
+
program.command("upgrade").description("Bump installed @mushi-mushi/* packages to the latest stable npm release").option("--cwd <path>", "Target repo (default: cwd)").option("--dry-run", "Print the install command without running it").option("--json", "Machine-readable plan + result").addHelpText("after", `
|
|
3008
|
+
Examples:
|
|
3009
|
+
mushi upgrade
|
|
3010
|
+
mushi upgrade --dry-run
|
|
3011
|
+
mushi upgrade --cwd ../glot.it`).action(async (opts) => {
|
|
3012
|
+
const result = await runUpgrade({ cwd: opts.cwd, dryRun: opts.dryRun, json: opts.json });
|
|
3013
|
+
if (opts.json) {
|
|
3014
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3015
|
+
} else {
|
|
3016
|
+
console.log(result.message);
|
|
3017
|
+
for (const e of result.plan.entries) {
|
|
3018
|
+
const tag = e.willUpgrade && e.latest ? `\u2192 v${e.latest}` : "(current)";
|
|
3019
|
+
console.log(` ${e.name}@${e.current} ${tag}`);
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
if (!result.upgraded && result.plan.entries.some((e) => e.willUpgrade) && !opts.dryRun) {
|
|
3023
|
+
process.exit(1);
|
|
3024
|
+
}
|
|
3025
|
+
if (result.plan.entries.length === 0) process.exit(1);
|
|
3026
|
+
});
|
|
3027
|
+
program.command("connect").description("Save credentials, merge env vars, wire Cursor MCP, optionally wait for SDK heartbeat").option("--api-key <key>", "Mushi API key (mushi_\u2026) \u2014 or set MUSHI_API_KEY to keep it out of shell history").requiredOption("--project-id <id>", "Project UUID").requiredOption("--endpoint <url>", "Supabase edge function URL").option("--cwd <path>", "Target repo").option("--no-env", "Skip writing .env.local").option("--no-ide", "Skip writing .cursor/mcp.json").option("--wait", "Poll ingest-setup until SDK heartbeat lands").option("--wait-timeout <sec>", "Max seconds for --wait", "120").option("--json", "Machine-readable output").addHelpText("after", `
|
|
3028
|
+
Examples:
|
|
3029
|
+
MUSHI_API_KEY=mushi_xxx mushi connect --project-id <uuid> --endpoint https://<ref>.supabase.co/functions/v1/api --wait
|
|
3030
|
+
mushi connect --api-key mushi_xxx --project-id <uuid> --endpoint <url> --no-ide`).action(async (opts) => {
|
|
3031
|
+
const apiKey = process.env.MUSHI_API_KEY ?? opts.apiKey;
|
|
3032
|
+
if (!apiKey) {
|
|
3033
|
+
console.error("Provide the API key via the MUSHI_API_KEY env var (recommended) or --api-key <key>.");
|
|
3034
|
+
process.exit(1);
|
|
3035
|
+
}
|
|
3036
|
+
const result = await runConnect({
|
|
3037
|
+
apiKey,
|
|
3038
|
+
projectId: opts.projectId,
|
|
3039
|
+
endpoint: opts.endpoint,
|
|
3040
|
+
cwd: opts.cwd,
|
|
3041
|
+
writeEnv: opts.env !== false,
|
|
3042
|
+
wireIde: opts.ide !== false,
|
|
3043
|
+
wait: opts.wait,
|
|
3044
|
+
waitTimeoutSec: parseInt(opts.waitTimeout, 10) || 120,
|
|
3045
|
+
json: opts.json
|
|
3046
|
+
});
|
|
3047
|
+
if (opts.json) {
|
|
3048
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3049
|
+
} else {
|
|
3050
|
+
for (const line of result.messages) console.log(line);
|
|
3051
|
+
}
|
|
3052
|
+
if (!result.ok) process.exit(1);
|
|
3053
|
+
});
|
|
2641
3054
|
program.command("doctor").description(
|
|
2642
3055
|
"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."
|
|
2643
3056
|
).option("--cwd <path>", "Run package detection from a different directory").option("--json", "Machine-readable output").option(
|
|
2644
3057
|
"--server",
|
|
2645
3058
|
"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."
|
|
3059
|
+
).option(
|
|
3060
|
+
"--ingest",
|
|
3061
|
+
"Also call GET /v1/sync/ingest-setup for the 4 required ingest steps (API key, SDK heartbeat, first report). Composable with --server."
|
|
2646
3062
|
).action(async (opts) => {
|
|
2647
3063
|
const config = loadConfig();
|
|
2648
|
-
const result = await runDoctor(config, { cwd: opts.cwd, server: opts.server });
|
|
3064
|
+
const result = await runDoctor(config, { cwd: opts.cwd, server: opts.server, ingest: opts.ingest });
|
|
2649
3065
|
const { checks } = result;
|
|
2650
3066
|
if (opts.json) {
|
|
2651
3067
|
console.log(JSON.stringify({ checks, ready: result.ready }, null, 2));
|
|
@@ -2756,4 +3172,208 @@ fixes.command("tail").description(
|
|
|
2756
3172
|
}
|
|
2757
3173
|
}
|
|
2758
3174
|
});
|
|
2759
|
-
program.
|
|
3175
|
+
var stories = program.command("stories").description("TDD story mapping and test generation");
|
|
3176
|
+
stories.command("map").description("Crawl a live app URL and automatically discover user stories (writes inventory proposal)").requiredOption("--url <url>", "Live app URL to crawl (e.g. https://your-app.vercel.app)").option("--max-pages <n>", "Max pages to crawl", "20").option("--provider <p>", "Crawl provider: firecrawl (default) or browserbase", "firecrawl").option("--cursor-refine", "Open a Cursor Cloud PR to refine the draft against repo code").option("--wait", "Wait for the crawl to complete and print results").action(async (opts) => {
|
|
3177
|
+
const config = loadConfig();
|
|
3178
|
+
if (!config.apiKey || !config.projectId) {
|
|
3179
|
+
console.error("Run `mushi login` and `mushi init` first");
|
|
3180
|
+
process.exit(1);
|
|
3181
|
+
}
|
|
3182
|
+
const res = await apiCall(
|
|
3183
|
+
`/v1/admin/inventory/${config.projectId}/map-from-live`,
|
|
3184
|
+
config,
|
|
3185
|
+
{
|
|
3186
|
+
method: "POST",
|
|
3187
|
+
body: JSON.stringify({
|
|
3188
|
+
base_url: opts.url,
|
|
3189
|
+
max_pages: parseInt(opts.maxPages, 10),
|
|
3190
|
+
provider: opts.provider,
|
|
3191
|
+
cursor_cloud_refine: opts.cursorRefine ?? false
|
|
3192
|
+
})
|
|
3193
|
+
}
|
|
3194
|
+
);
|
|
3195
|
+
if (!res.ok) {
|
|
3196
|
+
console.error(`Error: ${res.error.message}`);
|
|
3197
|
+
process.exit(1);
|
|
3198
|
+
}
|
|
3199
|
+
console.log(`\u2713 Crawl started \u2014 run id: ${res.data.runId}`);
|
|
3200
|
+
console.log(` Crawling ${opts.url} with ${opts.provider}\u2026`);
|
|
3201
|
+
if (opts.wait) {
|
|
3202
|
+
console.log(" Polling for results\u2026");
|
|
3203
|
+
for (let i = 0; i < 40; i++) {
|
|
3204
|
+
await new Promise((r) => setTimeout(r, 5e3));
|
|
3205
|
+
const runsRes = await apiCall(
|
|
3206
|
+
`/v1/admin/inventory/${config.projectId}/map-runs`,
|
|
3207
|
+
config
|
|
3208
|
+
);
|
|
3209
|
+
if (!runsRes.ok) break;
|
|
3210
|
+
const run = runsRes.data.runs.find((r) => r.id === res.data.runId);
|
|
3211
|
+
if (!run) break;
|
|
3212
|
+
if (run.status === "completed") {
|
|
3213
|
+
console.log(`
|
|
3214
|
+
\u2713 Done! ${run.pages_crawled ?? 0} pages crawled.`);
|
|
3215
|
+
if (run.proposal_id) console.log(` Proposal id: ${run.proposal_id}`);
|
|
3216
|
+
console.log(` Review in the console: Inventory \u2192 Discovery \u2192 Past proposals`);
|
|
3217
|
+
break;
|
|
3218
|
+
}
|
|
3219
|
+
if (run.status === "failed") {
|
|
3220
|
+
console.error(`
|
|
3221
|
+
\u2717 Crawl failed: ${run.error_message ?? "unknown"}`);
|
|
3222
|
+
process.exit(1);
|
|
3223
|
+
}
|
|
3224
|
+
process.stdout.write(".");
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
});
|
|
3228
|
+
var tdd = program.command("tdd").description("TDD test generation and management");
|
|
3229
|
+
tdd.command("gen <storyId>").description("Generate a Playwright TDD test from an inventory user story id").option("--mode <m>", "Gate mode: auto (run immediately) | review (needs approval) | approve (manual)", "review").option("--no-pr", "Skip opening a GitHub PR").action(async (storyId, opts) => {
|
|
3230
|
+
const config = loadConfig();
|
|
3231
|
+
if (!config.apiKey || !config.projectId) {
|
|
3232
|
+
console.error("Run `mushi login` first");
|
|
3233
|
+
process.exit(1);
|
|
3234
|
+
}
|
|
3235
|
+
console.log(`Generating TDD test for story: ${storyId}\u2026`);
|
|
3236
|
+
const res = await apiCall(
|
|
3237
|
+
`/v1/admin/inventory/${config.projectId}/stories/${storyId}/generate-test`,
|
|
3238
|
+
config,
|
|
3239
|
+
{ method: "POST", body: JSON.stringify({ automation_mode: opts.mode, open_pr: opts.pr }) }
|
|
3240
|
+
);
|
|
3241
|
+
if (!res.ok) {
|
|
3242
|
+
console.error(`Error: ${res.error.message}`);
|
|
3243
|
+
process.exit(1);
|
|
3244
|
+
}
|
|
3245
|
+
console.log(`\u2713 Test generated \u2014 qa_story id: ${res.data.qaStoryId}`);
|
|
3246
|
+
console.log(` Approval status: ${res.data.approvalStatus}`);
|
|
3247
|
+
if (res.data.prUrl) console.log(` PR: ${res.data.prUrl}`);
|
|
3248
|
+
if (res.data.needsHumanReview) console.log(` \u26A0 Human review recommended \u2014 some selectors or flows are uncertain.`);
|
|
3249
|
+
});
|
|
3250
|
+
tdd.command("improve").description("Run PDCA auto-improve on recently failed QA tests").action(async () => {
|
|
3251
|
+
const config = loadConfig();
|
|
3252
|
+
if (!config.apiKey || !config.projectId) {
|
|
3253
|
+
console.error("Run `mushi login` first");
|
|
3254
|
+
process.exit(1);
|
|
3255
|
+
}
|
|
3256
|
+
console.log("Running PDCA QA story improver\u2026");
|
|
3257
|
+
const res = await apiCall(
|
|
3258
|
+
"/v1/admin/pdca/improve-qa-stories",
|
|
3259
|
+
config,
|
|
3260
|
+
{ method: "POST", body: JSON.stringify({ project_id: config.projectId }) }
|
|
3261
|
+
);
|
|
3262
|
+
if (!res.ok) {
|
|
3263
|
+
console.error(`Error: ${res.error.message}`);
|
|
3264
|
+
process.exit(1);
|
|
3265
|
+
}
|
|
3266
|
+
console.log(`\u2713 Improved ${res.data.improved} QA stories.`);
|
|
3267
|
+
});
|
|
3268
|
+
tdd.command("run <qaStoryId>").description("Trigger a manual run for a QA story").action(async (qaStoryId) => {
|
|
3269
|
+
const config = loadConfig();
|
|
3270
|
+
if (!config.apiKey || !config.projectId) {
|
|
3271
|
+
console.error("Run `mushi login` first");
|
|
3272
|
+
process.exit(1);
|
|
3273
|
+
}
|
|
3274
|
+
const res = await apiCall(
|
|
3275
|
+
`/v1/admin/projects/${config.projectId}/qa-stories/${qaStoryId}/run`,
|
|
3276
|
+
config,
|
|
3277
|
+
{ method: "POST" }
|
|
3278
|
+
);
|
|
3279
|
+
if (!res.ok) {
|
|
3280
|
+
console.error(`Error: ${res.error.message}`);
|
|
3281
|
+
process.exit(1);
|
|
3282
|
+
}
|
|
3283
|
+
console.log(`\u2713 Run queued \u2014 id: ${res.data.runId}`);
|
|
3284
|
+
});
|
|
3285
|
+
tdd.command("pending").description("List QA tests pending review").action(async () => {
|
|
3286
|
+
const config = loadConfig();
|
|
3287
|
+
if (!config.apiKey || !config.projectId) {
|
|
3288
|
+
console.error("Run `mushi login` first");
|
|
3289
|
+
process.exit(1);
|
|
3290
|
+
}
|
|
3291
|
+
const res = await apiCall(
|
|
3292
|
+
`/v1/admin/inventory/${config.projectId}/stories/pending-review`,
|
|
3293
|
+
config
|
|
3294
|
+
);
|
|
3295
|
+
if (!res.ok) {
|
|
3296
|
+
console.error(`Error: ${res.error.message}`);
|
|
3297
|
+
process.exit(1);
|
|
3298
|
+
}
|
|
3299
|
+
if (res.data.stories.length === 0) {
|
|
3300
|
+
console.log("No stories pending review.");
|
|
3301
|
+
return;
|
|
3302
|
+
}
|
|
3303
|
+
console.log(`${res.data.stories.length} stories pending review:
|
|
3304
|
+
`);
|
|
3305
|
+
for (const s of res.data.stories) {
|
|
3306
|
+
console.log(` ${s.id} ${s.name}${s.origin_story_node_id ? ` (story: ${s.origin_story_node_id})` : ""}`);
|
|
3307
|
+
if (s.generated_pr_url) console.log(` PR: ${s.generated_pr_url}`);
|
|
3308
|
+
}
|
|
3309
|
+
console.log(`
|
|
3310
|
+
Approve: mushi tdd approve <id>`);
|
|
3311
|
+
});
|
|
3312
|
+
tdd.command("approve <qaStoryId>").description("Approve a pending QA story (enables it in the schedule)").option("--reject", "Reject instead of approve").action(async (qaStoryId, opts) => {
|
|
3313
|
+
const config = loadConfig();
|
|
3314
|
+
if (!config.apiKey || !config.projectId) {
|
|
3315
|
+
console.error("Run `mushi login` first");
|
|
3316
|
+
process.exit(1);
|
|
3317
|
+
}
|
|
3318
|
+
const status = opts.reject ? "rejected" : "approved";
|
|
3319
|
+
const res = await apiCall(
|
|
3320
|
+
`/v1/admin/inventory/${config.projectId}/stories/${qaStoryId}/approval`,
|
|
3321
|
+
config,
|
|
3322
|
+
{ method: "PATCH", body: JSON.stringify({ status }) }
|
|
3323
|
+
);
|
|
3324
|
+
if (!res.ok) {
|
|
3325
|
+
console.error(`Error: ${res.error.message}`);
|
|
3326
|
+
process.exit(1);
|
|
3327
|
+
}
|
|
3328
|
+
console.log(`\u2713 Story ${status}.`);
|
|
3329
|
+
});
|
|
3330
|
+
var keys = program.command("keys").description("Manage API key pool (BYOK)");
|
|
3331
|
+
keys.command("list").description("List all API keys in the pool with their status").action(async () => {
|
|
3332
|
+
const config = loadConfig();
|
|
3333
|
+
if (!config.apiKey || !config.projectId) {
|
|
3334
|
+
console.error("Run `mushi login` first");
|
|
3335
|
+
process.exit(1);
|
|
3336
|
+
}
|
|
3337
|
+
const res = await apiCall(
|
|
3338
|
+
`/v1/admin/byok/keys?project_id=${encodeURIComponent(config.projectId)}`,
|
|
3339
|
+
config
|
|
3340
|
+
);
|
|
3341
|
+
if (!res.ok) {
|
|
3342
|
+
console.error(`Error: ${res.error.message}`);
|
|
3343
|
+
process.exit(1);
|
|
3344
|
+
}
|
|
3345
|
+
if (res.data.keys.length === 0) {
|
|
3346
|
+
console.log("No keys configured.");
|
|
3347
|
+
return;
|
|
3348
|
+
}
|
|
3349
|
+
for (const k of res.data.keys) {
|
|
3350
|
+
const cooldown = k.cooldown_until && new Date(k.cooldown_until) > /* @__PURE__ */ new Date() ? ` [cooldown until ${new Date(k.cooldown_until).toLocaleTimeString()}]` : "";
|
|
3351
|
+
console.log(`${k.provider_slug.padEnd(14)} [${k.status}] p=${k.priority} ${k.label ?? "(no label)"}${cooldown} \u2014 ${k.id}`);
|
|
3352
|
+
}
|
|
3353
|
+
});
|
|
3354
|
+
keys.command("add").description("Add a new API key to the pool").requiredOption("--provider <p>", "Provider: anthropic, openai, firecrawl, browserbase, cursor").option("--key <k>", "The API key value (prefer the MUSHI_BYOK_KEY env var to keep it out of shell history)").option("--label <l>", "Human-readable label").option("--priority <n>", "Priority (lower = higher priority)", "100").action(async (opts) => {
|
|
3355
|
+
const config = loadConfig();
|
|
3356
|
+
if (!config.apiKey || !config.projectId) {
|
|
3357
|
+
console.error("Run `mushi login` first");
|
|
3358
|
+
process.exit(1);
|
|
3359
|
+
}
|
|
3360
|
+
const key = process.env.MUSHI_BYOK_KEY ?? opts.key;
|
|
3361
|
+
if (!key) {
|
|
3362
|
+
console.error("Provide the key via the MUSHI_BYOK_KEY env var (recommended) or --key <value>.");
|
|
3363
|
+
process.exit(1);
|
|
3364
|
+
}
|
|
3365
|
+
const res = await apiCall(
|
|
3366
|
+
"/v1/admin/byok/keys",
|
|
3367
|
+
config,
|
|
3368
|
+
{ method: "POST", body: JSON.stringify({ project_id: config.projectId, provider_slug: opts.provider, key, label: opts.label, priority: parseInt(opts.priority, 10) }) }
|
|
3369
|
+
);
|
|
3370
|
+
if (!res.ok) {
|
|
3371
|
+
console.error(`Error: ${res.error.message}`);
|
|
3372
|
+
process.exit(1);
|
|
3373
|
+
}
|
|
3374
|
+
console.log(`\u2713 Key added \u2014 id: ${res.data.id}`);
|
|
3375
|
+
});
|
|
3376
|
+
program.parseAsync().catch((err) => {
|
|
3377
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3378
|
+
process.exit(1);
|
|
3379
|
+
});
|
package/dist/init.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-NYPX5KXR.js";
|
|
9
9
|
import {
|
|
10
10
|
MUSHI_CLI_VERSION
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-IMDLL4EO.js";
|
|
12
12
|
|
|
13
13
|
// src/init.ts
|
|
14
14
|
import * as p from "@clack/prompts";
|
|
@@ -49,10 +49,11 @@ function loadConfig(path = CONFIG_PATH) {
|
|
|
49
49
|
} else if (path === CONFIG_PATH && existsSync(LEGACY_CONFIG_PATH)) {
|
|
50
50
|
file = migrateLegacyConfig() ?? {};
|
|
51
51
|
}
|
|
52
|
+
const endpointFromEnv = process.env["MUSHI_API_ENDPOINT"] ?? process.env["MUSHI_ENDPOINT"] ?? void 0;
|
|
52
53
|
const fromEnv = {
|
|
53
54
|
...process.env["MUSHI_API_KEY"] ? { apiKey: process.env["MUSHI_API_KEY"] } : {},
|
|
54
55
|
...process.env["MUSHI_PROJECT_ID"] ? { projectId: process.env["MUSHI_PROJECT_ID"] } : {},
|
|
55
|
-
...
|
|
56
|
+
...endpointFromEnv ? { endpoint: endpointFromEnv } : {}
|
|
56
57
|
};
|
|
57
58
|
return { ...file, ...fromEnv };
|
|
58
59
|
}
|
|
@@ -126,7 +127,7 @@ function normalizeEndpoint(url) {
|
|
|
126
127
|
var REGISTRY = "https://registry.npmjs.org";
|
|
127
128
|
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
128
129
|
async function checkFreshness(packageName, currentVersion, opts = {}) {
|
|
129
|
-
if (process.env.MUSHI_NO_UPDATE_CHECK === "1") return null;
|
|
130
|
+
if (!opts.ignoreOptOut && process.env.MUSHI_NO_UPDATE_CHECK === "1") return null;
|
|
130
131
|
const registry = opts.registry ?? REGISTRY;
|
|
131
132
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
132
133
|
const controller = new AbortController();
|
|
@@ -164,11 +165,13 @@ function isNewerStableVersion(latest, current) {
|
|
|
164
165
|
return lc > cc;
|
|
165
166
|
}
|
|
166
167
|
function stripPreRelease(version) {
|
|
167
|
-
const idx = version.
|
|
168
|
+
const idx = version.search(/[-+]/);
|
|
168
169
|
return idx === -1 ? version : version.slice(0, idx);
|
|
169
170
|
}
|
|
170
171
|
function hasPreReleaseTag(version) {
|
|
171
|
-
|
|
172
|
+
const plus = version.indexOf("+");
|
|
173
|
+
const hyphen = version.indexOf("-");
|
|
174
|
+
return hyphen !== -1 && (plus === -1 || hyphen < plus);
|
|
172
175
|
}
|
|
173
176
|
function parse(version) {
|
|
174
177
|
const parts = version.split(".").map((part) => Number(part));
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,28 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mushi-mushi/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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
|
-
"scripts": {
|
|
10
|
-
"build": "tsup",
|
|
11
|
-
"lint": "eslint src/",
|
|
12
|
-
"test": "vitest run",
|
|
13
|
-
"typecheck": "tsc --noEmit"
|
|
14
|
-
},
|
|
15
9
|
"dependencies": {
|
|
16
10
|
"@clack/prompts": "^1.3.0",
|
|
17
11
|
"commander": "^14.0.3"
|
|
18
12
|
},
|
|
19
13
|
"devDependencies": {
|
|
20
|
-
"@mushi-mushi/eslint-config": "workspace:*",
|
|
21
14
|
"@types/node": "^22.19.17",
|
|
22
15
|
"eslint": "^10.3.0",
|
|
23
16
|
"tsup": "^8.5.1",
|
|
24
17
|
"typescript": "^6.0.3",
|
|
25
|
-
"vitest": "^4.1.5"
|
|
18
|
+
"vitest": "^4.1.5",
|
|
19
|
+
"@mushi-mushi/eslint-config": "0.0.0"
|
|
26
20
|
},
|
|
27
21
|
"author": "Kenji Sakuramoto",
|
|
28
22
|
"repository": {
|
|
@@ -109,5 +103,11 @@
|
|
|
109
103
|
"type": "module",
|
|
110
104
|
"engines": {
|
|
111
105
|
"node": ">=20"
|
|
106
|
+
},
|
|
107
|
+
"scripts": {
|
|
108
|
+
"build": "tsup",
|
|
109
|
+
"lint": "eslint src/",
|
|
110
|
+
"test": "vitest run",
|
|
111
|
+
"typecheck": "tsc --noEmit"
|
|
112
112
|
}
|
|
113
|
-
}
|
|
113
|
+
}
|