@mushi-mushi/cli 0.27.1 → 0.29.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 +6 -1
- package/dist/chunk-F4EGZNKA.js +10 -0
- package/dist/{chunk-M7CDSRZM.js → chunk-FDDSGZSC.js} +111 -50
- package/dist/chunk-KOBEOGNF.js +475 -0
- package/dist/{chunk-NNB2NONG.js → chunk-O7PLRNM2.js} +191 -500
- package/dist/chunk-SXSI2W6Z.js +42 -0
- package/dist/chunk-UZZUGLI2.js +8 -0
- package/dist/{connect-5EQA7G3U.js → connect-SEFUBQCA.js} +74 -42
- package/dist/connect-TGIJLXAN.js +13 -0
- package/dist/detect-4PYRZXVK.js +22 -0
- package/dist/endpoint-NHECZL47.js +15 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8449 -4388
- package/dist/init.d.ts +179 -1
- package/dist/init.js +468 -225
- package/dist/{version-AYMUW3UQ.js → version-WHAZE7TY.js} +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/wizard-args.d.ts +2 -2
- package/dist/wizard-args.js +5 -3
- package/package.json +10 -15
- package/CODE_OF_CONDUCT.md +0 -51
- package/CONTRIBUTING.md +0 -226
- package/SECURITY.md +0 -359
- package/dist/chunk-DBAEB56N.js +0 -10
- package/dist/chunk-XLGHUOKY.js +0 -8
- package/dist/connect-TWO2ZNFB.js +0 -10
package/README.md
CHANGED
|
@@ -181,10 +181,15 @@ local bumps; use the PR flow when you want review + CI on the dependency change.
|
|
|
181
181
|
```bash
|
|
182
182
|
mushi upgrade # plan + install
|
|
183
183
|
mushi upgrade --dry-run # print the install command without running it
|
|
184
|
+
mushi upgrade --check # plan only: exit 0 current, 1 outdated, 2 registry down
|
|
184
185
|
mushi upgrade --json # machine-readable plan + result
|
|
185
186
|
mushi upgrade --cwd ../app # target another repo
|
|
186
187
|
```
|
|
187
188
|
|
|
189
|
+
`--check` never installs. Use it in a **non-required** CI job so a one-patch
|
|
190
|
+
drift cannot freeze production deploys. Exit `2` (registry unreachable) should
|
|
191
|
+
not fail a required gate — retry or treat as a warning.
|
|
192
|
+
|
|
188
193
|
Pre-release versions are never auto-selected, registry versions are validated
|
|
189
194
|
against strict semver before being interpolated into the install command, and
|
|
190
195
|
non-registry specifiers (`workspace:`, `file:`, git URLs, dist-tags) are left
|
|
@@ -440,4 +445,4 @@ The CLI is also importable for tooling (used by `create-mushi-mushi`):
|
|
|
440
445
|
MIT
|
|
441
446
|
|
|
442
447
|
<!-- mushi-readme-stats-footer -->
|
|
443
|
-
<sub>Monorepo scale (July 2026):
|
|
448
|
+
<sub>Monorepo scale (July 2026): 64 edge functions · 410 SQL migrations · 13 outbound plugins · 11 inbound adapters · 19 pipeline agents. 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>
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MUSHI_CLI_VERSION
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-UZZUGLI2.js";
|
|
4
|
+
import {
|
|
5
|
+
assertEndpoint,
|
|
6
|
+
normalizeEndpoint
|
|
7
|
+
} from "./chunk-SXSI2W6Z.js";
|
|
4
8
|
import {
|
|
5
9
|
__require
|
|
6
10
|
} from "./chunk-3RG5ZIWI.js";
|
|
7
11
|
|
|
8
|
-
// src/config.ts
|
|
9
|
-
import { randomUUID } from "crypto";
|
|
10
|
-
import { spawnSync } from "child_process";
|
|
11
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
12
|
-
import { homedir } from "os";
|
|
13
|
-
import { dirname, join } from "path";
|
|
14
|
-
|
|
15
12
|
// src/keychain.ts
|
|
16
13
|
var KEYCHAIN_SERVICE = "mushi-cli";
|
|
17
14
|
function accountName(profile = "default") {
|
|
@@ -29,6 +26,18 @@ function getEntryClass() {
|
|
|
29
26
|
}
|
|
30
27
|
return _EntryClass ?? null;
|
|
31
28
|
}
|
|
29
|
+
function trySaveKeyToKeychain(apiKey, profile = "default") {
|
|
30
|
+
if (!apiKey) return false;
|
|
31
|
+
const EntryClass = getEntryClass();
|
|
32
|
+
if (!EntryClass) return false;
|
|
33
|
+
try {
|
|
34
|
+
const entry = new EntryClass(KEYCHAIN_SERVICE, accountName(profile));
|
|
35
|
+
entry.setPassword(apiKey);
|
|
36
|
+
return true;
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
32
41
|
function tryLoadKeyFromKeychain(profile = "default") {
|
|
33
42
|
const EntryClass = getEntryClass();
|
|
34
43
|
if (!EntryClass) return null;
|
|
@@ -42,10 +51,20 @@ function tryLoadKeyFromKeychain(profile = "default") {
|
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
// src/config.ts
|
|
54
|
+
import { randomUUID } from "crypto";
|
|
55
|
+
import { spawnSync } from "child_process";
|
|
56
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
57
|
+
import { homedir } from "os";
|
|
58
|
+
import { dirname, join } from "path";
|
|
45
59
|
var DEFAULT_PROFILE = "default";
|
|
46
60
|
function isMultiProfileFile(value) {
|
|
47
61
|
return typeof value === "object" && value !== null && "profiles" in value && typeof value.profiles === "object" && value.profiles !== null;
|
|
48
62
|
}
|
|
63
|
+
function savedSdkKeyFor(config, projectId) {
|
|
64
|
+
const saved = config.sdkKey;
|
|
65
|
+
if (!projectId || !saved || typeof saved !== "object") return void 0;
|
|
66
|
+
return saved.projectId === projectId && typeof saved.key === "string" && saved.key ? saved.key : void 0;
|
|
67
|
+
}
|
|
49
68
|
function resolveProfileName(explicit, fileActive) {
|
|
50
69
|
const fromEnv = process.env["MUSHI_PROFILE"]?.trim();
|
|
51
70
|
return explicit?.trim() || fromEnv || fileActive || DEFAULT_PROFILE;
|
|
@@ -225,6 +244,47 @@ function tightenWindowsAcl(path, type) {
|
|
|
225
244
|
}
|
|
226
245
|
}
|
|
227
246
|
|
|
247
|
+
// src/key-scopes.ts
|
|
248
|
+
var SDK_KEY_SCOPES = ["report:write"];
|
|
249
|
+
var CLI_KEY_SCOPES = ["report:write", "mcp:read"];
|
|
250
|
+
var SDK_KEY_LABEL = "sdk-ingest";
|
|
251
|
+
var CLI_KEY_LABEL = "cli-login";
|
|
252
|
+
async function probeKeyScope(endpoint, apiKey, projectId, doFetch = globalThis.fetch) {
|
|
253
|
+
let base = endpoint;
|
|
254
|
+
while (base.endsWith("/")) base = base.slice(0, -1);
|
|
255
|
+
let res;
|
|
256
|
+
try {
|
|
257
|
+
res = await doFetch(`${base}/v1/admin/mcp/account-overview`, {
|
|
258
|
+
headers: { "X-Mushi-Api-Key": apiKey, "X-Mushi-Project": projectId },
|
|
259
|
+
signal: AbortSignal.timeout(8e3)
|
|
260
|
+
});
|
|
261
|
+
} catch (err) {
|
|
262
|
+
return { result: "unreachable", message: err instanceof Error ? err.message : String(err) };
|
|
263
|
+
}
|
|
264
|
+
if (res.ok) return { result: "mcp", status: res.status };
|
|
265
|
+
if (res.status === 401) return { result: "invalid", status: res.status };
|
|
266
|
+
if (res.status === 403) {
|
|
267
|
+
const body = await res.json().catch(() => null);
|
|
268
|
+
const code = body?.error?.code ?? body?.code;
|
|
269
|
+
if (code === "INSUFFICIENT_SCOPE") return { result: "ingest-only", status: res.status };
|
|
270
|
+
}
|
|
271
|
+
return { result: "unknown", status: res.status };
|
|
272
|
+
}
|
|
273
|
+
function describeUnsafeSdkKey(probe, endpoint) {
|
|
274
|
+
switch (probe.result) {
|
|
275
|
+
case "mcp":
|
|
276
|
+
return "this key can read your bug reports (mcp scope), and SDK env vars ship inside the app bundle";
|
|
277
|
+
case "invalid":
|
|
278
|
+
return "the backend rejected this key";
|
|
279
|
+
case "unreachable":
|
|
280
|
+
return `could not reach ${endpoint} to confirm the key is ingest-only (${probe.message ?? "network error"})`;
|
|
281
|
+
case "unknown":
|
|
282
|
+
return `could not confirm the key is ingest-only (HTTP ${probe.status ?? "?"})`;
|
|
283
|
+
case "ingest-only":
|
|
284
|
+
return "the key is ingest-only";
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
228
288
|
// src/console-url.ts
|
|
229
289
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
230
290
|
import { dirname as dirname2, join as join2 } from "path";
|
|
@@ -341,41 +401,6 @@ async function openInBrowser(url) {
|
|
|
341
401
|
});
|
|
342
402
|
}
|
|
343
403
|
|
|
344
|
-
// src/endpoint.ts
|
|
345
|
-
var TEST_REPORT_TIMEOUT_MS = 1e4;
|
|
346
|
-
var TEST_REPORT_FETCH_TIMEOUT_MS = TEST_REPORT_TIMEOUT_MS;
|
|
347
|
-
var CLOUD_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
|
|
348
|
-
function resolveCloudEndpoint(explicit) {
|
|
349
|
-
if (explicit?.trim()) return explicit.trim();
|
|
350
|
-
const fromEnv = process.env.MUSHI_API_ENDPOINT?.trim();
|
|
351
|
-
if (fromEnv) return fromEnv;
|
|
352
|
-
return CLOUD_API_ENDPOINT;
|
|
353
|
-
}
|
|
354
|
-
function assertEndpoint(url) {
|
|
355
|
-
let parsed;
|
|
356
|
-
try {
|
|
357
|
-
parsed = new URL(url);
|
|
358
|
-
} catch {
|
|
359
|
-
throw new Error(`Invalid endpoint URL: ${url}`);
|
|
360
|
-
}
|
|
361
|
-
const host = parsed.hostname;
|
|
362
|
-
const isLocal = host === "localhost" || host === "127.0.0.1" || host === "::1" || host.endsWith(".local");
|
|
363
|
-
if (parsed.protocol !== "https:" && !isLocal) {
|
|
364
|
-
throw new Error(`Endpoint must use https:// (got ${parsed.protocol}//${host}).`);
|
|
365
|
-
}
|
|
366
|
-
return normalizeEndpoint(parsed.origin + parsed.pathname);
|
|
367
|
-
}
|
|
368
|
-
function normalizeEndpoint(url) {
|
|
369
|
-
if (!url) {
|
|
370
|
-
throw new Error(
|
|
371
|
-
"No API endpoint configured. Run `mushi init` or set MUSHI_API_ENDPOINT. Set endpoint to your Supabase edge function URL, e.g. https://xyz.supabase.co/functions/v1/api"
|
|
372
|
-
);
|
|
373
|
-
}
|
|
374
|
-
let end = url.length;
|
|
375
|
-
while (end > 0 && url.charCodeAt(end - 1) === 47) end--;
|
|
376
|
-
return url.slice(0, end);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
404
|
// src/errors.ts
|
|
380
405
|
var EXIT_CODE_MAP = {
|
|
381
406
|
E_AUTH_MISSING: 2,
|
|
@@ -446,7 +471,30 @@ function requireUuid(value, label = "id") {
|
|
|
446
471
|
);
|
|
447
472
|
}
|
|
448
473
|
var API_TIMEOUT_MS = 15e3;
|
|
449
|
-
|
|
474
|
+
function normalizeApiError(error, httpStatus) {
|
|
475
|
+
const fallbackCode = httpStatus ? `HTTP_${httpStatus}` : "API_ERROR";
|
|
476
|
+
if (typeof error === "string") return { code: fallbackCode, message: error };
|
|
477
|
+
if (error && typeof error === "object") {
|
|
478
|
+
const e = error;
|
|
479
|
+
if (typeof e["code"] === "string" && typeof e["message"] === "string") {
|
|
480
|
+
return e;
|
|
481
|
+
}
|
|
482
|
+
const formErrors = Array.isArray(e["formErrors"]) ? e["formErrors"].map(String) : [];
|
|
483
|
+
const fieldErrors = e["fieldErrors"] && typeof e["fieldErrors"] === "object" ? Object.entries(e["fieldErrors"]).map(
|
|
484
|
+
([field, msgs]) => `${field}: ${Array.isArray(msgs) ? msgs.join(", ") : String(msgs)}`
|
|
485
|
+
) : [];
|
|
486
|
+
const parts = [...formErrors, ...fieldErrors];
|
|
487
|
+
if (parts.length > 0) return { code: "VALIDATION_ERROR", message: parts.join("; ") };
|
|
488
|
+
return {
|
|
489
|
+
...e,
|
|
490
|
+
code: typeof e["code"] === "string" ? e["code"] : fallbackCode,
|
|
491
|
+
message: typeof e["message"] === "string" ? e["message"] : `Request failed${httpStatus ? ` (${httpStatus})` : ""}`
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
return { code: fallbackCode, message: `Request failed${httpStatus ? ` (${httpStatus})` : ""}` };
|
|
495
|
+
}
|
|
496
|
+
async function apiCall(path, config, options = {}, callOptions = {}) {
|
|
497
|
+
const timeoutMs = callOptions.timeoutMs ?? API_TIMEOUT_MS;
|
|
450
498
|
const endpoint = config.endpoint;
|
|
451
499
|
if (!endpoint) {
|
|
452
500
|
return {
|
|
@@ -458,7 +506,7 @@ async function apiCall(path, config, options = {}) {
|
|
|
458
506
|
};
|
|
459
507
|
}
|
|
460
508
|
const controller = new AbortController();
|
|
461
|
-
const timer = setTimeout(() => controller.abort(),
|
|
509
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
462
510
|
const signals = [controller.signal, getAbortSignal()];
|
|
463
511
|
const compositeSignal = AbortSignal.any ? AbortSignal.any(signals) : controller.signal;
|
|
464
512
|
try {
|
|
@@ -487,6 +535,7 @@ async function apiCall(path, config, options = {}) {
|
|
|
487
535
|
}
|
|
488
536
|
} else {
|
|
489
537
|
const text = await res.text();
|
|
538
|
+
if (callOptions.text && res.ok) return { ok: true, data: text };
|
|
490
539
|
try {
|
|
491
540
|
body = JSON.parse(text);
|
|
492
541
|
} catch {
|
|
@@ -510,6 +559,13 @@ async function apiCall(path, config, options = {}) {
|
|
|
510
559
|
error: { code: errCode, message: errMsg }
|
|
511
560
|
};
|
|
512
561
|
}
|
|
562
|
+
if (typeof body === "object" && body !== null && body.ok === false) {
|
|
563
|
+
return {
|
|
564
|
+
ok: false,
|
|
565
|
+
httpStatus: res.status,
|
|
566
|
+
error: normalizeApiError(body.error, res.status)
|
|
567
|
+
};
|
|
568
|
+
}
|
|
513
569
|
return body;
|
|
514
570
|
} catch (err) {
|
|
515
571
|
clearTimeout(timer);
|
|
@@ -518,7 +574,7 @@ async function apiCall(path, config, options = {}) {
|
|
|
518
574
|
ok: false,
|
|
519
575
|
error: {
|
|
520
576
|
code: "TIMEOUT",
|
|
521
|
-
message: `Request timed out after ${
|
|
577
|
+
message: `Request timed out after ${timeoutMs / 1e3}s. Check your network or endpoint.`
|
|
522
578
|
}
|
|
523
579
|
};
|
|
524
580
|
}
|
|
@@ -644,11 +700,20 @@ async function waitForIngestReady(options) {
|
|
|
644
700
|
}
|
|
645
701
|
|
|
646
702
|
export {
|
|
703
|
+
trySaveKeyToKeychain,
|
|
704
|
+
savedSdkKeyFor,
|
|
705
|
+
resolveProfileName,
|
|
647
706
|
CONFIG_PATH,
|
|
648
707
|
loadConfig,
|
|
649
708
|
ensureClientId,
|
|
650
709
|
maybeShowTelemetryNotice,
|
|
651
710
|
saveConfig,
|
|
711
|
+
SDK_KEY_SCOPES,
|
|
712
|
+
CLI_KEY_SCOPES,
|
|
713
|
+
SDK_KEY_LABEL,
|
|
714
|
+
CLI_KEY_LABEL,
|
|
715
|
+
probeKeyScope,
|
|
716
|
+
describeUnsafeSdkKey,
|
|
652
717
|
consoleUrl,
|
|
653
718
|
resolveConsoleUrlSync,
|
|
654
719
|
resolveConsoleUrl,
|
|
@@ -657,10 +722,6 @@ export {
|
|
|
657
722
|
cliSetupDeepLink,
|
|
658
723
|
reportsUrl,
|
|
659
724
|
openInBrowser,
|
|
660
|
-
TEST_REPORT_FETCH_TIMEOUT_MS,
|
|
661
|
-
resolveCloudEndpoint,
|
|
662
|
-
assertEndpoint,
|
|
663
|
-
normalizeEndpoint,
|
|
664
725
|
requireUuid,
|
|
665
726
|
apiCall,
|
|
666
727
|
sanitizeEndpoint,
|