@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
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
import {
|
|
3
3
|
MUSHI_CLI_VERSION,
|
|
4
4
|
MUSHI_MCP_PIN_SPEC
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-F4EGZNKA.js";
|
|
6
|
+
import {
|
|
7
|
+
detectFramework,
|
|
8
|
+
envVarsToWrite,
|
|
9
|
+
readPackageJson
|
|
10
|
+
} from "./chunk-KOBEOGNF.js";
|
|
6
11
|
import {
|
|
7
12
|
CLOUD_API_ENDPOINT,
|
|
8
13
|
assertEndpoint,
|
|
@@ -14,7 +19,7 @@ import {
|
|
|
14
19
|
|
|
15
20
|
// src/connect.ts
|
|
16
21
|
import { appendFile, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
17
|
-
import { join as
|
|
22
|
+
import { join as join3, resolve } from "path";
|
|
18
23
|
|
|
19
24
|
// src/config.ts
|
|
20
25
|
import { randomUUID } from "crypto";
|
|
@@ -69,6 +74,11 @@ var DEFAULT_PROFILE = "default";
|
|
|
69
74
|
function isMultiProfileFile(value) {
|
|
70
75
|
return typeof value === "object" && value !== null && "profiles" in value && typeof value.profiles === "object" && value.profiles !== null;
|
|
71
76
|
}
|
|
77
|
+
function savedSdkKeyFor(config, projectId) {
|
|
78
|
+
const saved = config.sdkKey;
|
|
79
|
+
if (!projectId || !saved || typeof saved !== "object") return void 0;
|
|
80
|
+
return saved.projectId === projectId && typeof saved.key === "string" && saved.key ? saved.key : void 0;
|
|
81
|
+
}
|
|
72
82
|
function resolveProfileName(explicit, fileActive) {
|
|
73
83
|
const fromEnv = process.env["MUSHI_PROFILE"]?.trim();
|
|
74
84
|
return explicit?.trim() || fromEnv || fileActive || DEFAULT_PROFILE;
|
|
@@ -530,7 +540,30 @@ async function probeEndpointHealth(endpoint) {
|
|
|
530
540
|
throw err;
|
|
531
541
|
}
|
|
532
542
|
}
|
|
533
|
-
|
|
543
|
+
function normalizeApiError(error, httpStatus) {
|
|
544
|
+
const fallbackCode = httpStatus ? `HTTP_${httpStatus}` : "API_ERROR";
|
|
545
|
+
if (typeof error === "string") return { code: fallbackCode, message: error };
|
|
546
|
+
if (error && typeof error === "object") {
|
|
547
|
+
const e = error;
|
|
548
|
+
if (typeof e["code"] === "string" && typeof e["message"] === "string") {
|
|
549
|
+
return e;
|
|
550
|
+
}
|
|
551
|
+
const formErrors = Array.isArray(e["formErrors"]) ? e["formErrors"].map(String) : [];
|
|
552
|
+
const fieldErrors = e["fieldErrors"] && typeof e["fieldErrors"] === "object" ? Object.entries(e["fieldErrors"]).map(
|
|
553
|
+
([field, msgs]) => `${field}: ${Array.isArray(msgs) ? msgs.join(", ") : String(msgs)}`
|
|
554
|
+
) : [];
|
|
555
|
+
const parts = [...formErrors, ...fieldErrors];
|
|
556
|
+
if (parts.length > 0) return { code: "VALIDATION_ERROR", message: parts.join("; ") };
|
|
557
|
+
return {
|
|
558
|
+
...e,
|
|
559
|
+
code: typeof e["code"] === "string" ? e["code"] : fallbackCode,
|
|
560
|
+
message: typeof e["message"] === "string" ? e["message"] : `Request failed${httpStatus ? ` (${httpStatus})` : ""}`
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
return { code: fallbackCode, message: `Request failed${httpStatus ? ` (${httpStatus})` : ""}` };
|
|
564
|
+
}
|
|
565
|
+
async function apiCall(path, config, options = {}, callOptions = {}) {
|
|
566
|
+
const timeoutMs = callOptions.timeoutMs ?? API_TIMEOUT_MS;
|
|
534
567
|
const endpoint = config.endpoint;
|
|
535
568
|
if (!endpoint) {
|
|
536
569
|
return {
|
|
@@ -542,7 +575,7 @@ async function apiCall(path, config, options = {}) {
|
|
|
542
575
|
};
|
|
543
576
|
}
|
|
544
577
|
const controller = new AbortController();
|
|
545
|
-
const timer = setTimeout(() => controller.abort(),
|
|
578
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
546
579
|
const signals = [controller.signal, getAbortSignal()];
|
|
547
580
|
const compositeSignal = AbortSignal.any ? AbortSignal.any(signals) : controller.signal;
|
|
548
581
|
try {
|
|
@@ -571,6 +604,7 @@ async function apiCall(path, config, options = {}) {
|
|
|
571
604
|
}
|
|
572
605
|
} else {
|
|
573
606
|
const text = await res.text();
|
|
607
|
+
if (callOptions.text && res.ok) return { ok: true, data: text };
|
|
574
608
|
try {
|
|
575
609
|
body = JSON.parse(text);
|
|
576
610
|
} catch {
|
|
@@ -594,6 +628,13 @@ async function apiCall(path, config, options = {}) {
|
|
|
594
628
|
error: { code: errCode, message: errMsg }
|
|
595
629
|
};
|
|
596
630
|
}
|
|
631
|
+
if (typeof body === "object" && body !== null && body.ok === false) {
|
|
632
|
+
return {
|
|
633
|
+
ok: false,
|
|
634
|
+
httpStatus: res.status,
|
|
635
|
+
error: normalizeApiError(body.error, res.status)
|
|
636
|
+
};
|
|
637
|
+
}
|
|
597
638
|
return body;
|
|
598
639
|
} catch (err) {
|
|
599
640
|
clearTimeout(timer);
|
|
@@ -602,7 +643,7 @@ async function apiCall(path, config, options = {}) {
|
|
|
602
643
|
ok: false,
|
|
603
644
|
error: {
|
|
604
645
|
code: "TIMEOUT",
|
|
605
|
-
message: `Request timed out after ${
|
|
646
|
+
message: `Request timed out after ${timeoutMs / 1e3}s. Check your network or endpoint.`
|
|
606
647
|
}
|
|
607
648
|
};
|
|
608
649
|
}
|
|
@@ -670,446 +711,6 @@ function pad(s, width) {
|
|
|
670
711
|
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
671
712
|
}
|
|
672
713
|
|
|
673
|
-
// src/detect.ts
|
|
674
|
-
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
675
|
-
import { join as join3 } from "path";
|
|
676
|
-
var FRAMEWORKS = {
|
|
677
|
-
next: {
|
|
678
|
-
id: "next",
|
|
679
|
-
label: "Next.js",
|
|
680
|
-
packageName: "@mushi-mushi/react",
|
|
681
|
-
needsWebPackage: false,
|
|
682
|
-
snippet: () => `// app/providers.tsx (or pages/_app.tsx for /pages router)
|
|
683
|
-
'use client'
|
|
684
|
-
import { MushiProvider } from '@mushi-mushi/react'
|
|
685
|
-
|
|
686
|
-
export function Providers({ children }: { children: React.ReactNode }) {
|
|
687
|
-
return (
|
|
688
|
-
<MushiProvider config={{
|
|
689
|
-
projectId: process.env.NEXT_PUBLIC_MUSHI_PROJECT_ID!,
|
|
690
|
-
apiKey: process.env.NEXT_PUBLIC_MUSHI_API_KEY!,
|
|
691
|
-
}}>
|
|
692
|
-
{children}
|
|
693
|
-
</MushiProvider>
|
|
694
|
-
)
|
|
695
|
-
}`
|
|
696
|
-
},
|
|
697
|
-
react: {
|
|
698
|
-
id: "react",
|
|
699
|
-
label: "React",
|
|
700
|
-
packageName: "@mushi-mushi/react",
|
|
701
|
-
needsWebPackage: false,
|
|
702
|
-
snippet: () => `// src/main.tsx
|
|
703
|
-
import { MushiProvider } from '@mushi-mushi/react'
|
|
704
|
-
|
|
705
|
-
createRoot(document.getElementById('root')!).render(
|
|
706
|
-
<MushiProvider config={{
|
|
707
|
-
projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
|
|
708
|
-
apiKey: import.meta.env.VITE_MUSHI_API_KEY,
|
|
709
|
-
}}>
|
|
710
|
-
<App />
|
|
711
|
-
</MushiProvider>
|
|
712
|
-
)`
|
|
713
|
-
},
|
|
714
|
-
cra: {
|
|
715
|
-
id: "cra",
|
|
716
|
-
label: "Create React App",
|
|
717
|
-
packageName: "@mushi-mushi/react",
|
|
718
|
-
needsWebPackage: false,
|
|
719
|
-
// CRA inlines only REACT_APP_*-prefixed vars into process.env at build time.
|
|
720
|
-
snippet: () => `// src/index.tsx
|
|
721
|
-
import { MushiProvider } from '@mushi-mushi/react'
|
|
722
|
-
|
|
723
|
-
root.render(
|
|
724
|
-
<MushiProvider config={{
|
|
725
|
-
projectId: process.env.REACT_APP_MUSHI_PROJECT_ID,
|
|
726
|
-
apiKey: process.env.REACT_APP_MUSHI_API_KEY,
|
|
727
|
-
}}>
|
|
728
|
-
<App />
|
|
729
|
-
</MushiProvider>
|
|
730
|
-
)`
|
|
731
|
-
},
|
|
732
|
-
remix: {
|
|
733
|
-
id: "remix",
|
|
734
|
-
label: "Remix",
|
|
735
|
-
packageName: "@mushi-mushi/react",
|
|
736
|
-
needsWebPackage: false,
|
|
737
|
-
// Remix doesn't inline client env. Expose public values at runtime via the
|
|
738
|
-
// root loader + window.ENV (works on both the classic compiler and Vite).
|
|
739
|
-
snippet: () => `// app/root.tsx
|
|
740
|
-
import { MushiProvider } from '@mushi-mushi/react'
|
|
741
|
-
import { useLoaderData } from '@remix-run/react'
|
|
742
|
-
|
|
743
|
-
export async function loader() {
|
|
744
|
-
return { ENV: {
|
|
745
|
-
MUSHI_PROJECT_ID: process.env.MUSHI_PROJECT_ID,
|
|
746
|
-
MUSHI_API_KEY: process.env.MUSHI_API_KEY,
|
|
747
|
-
} }
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
export default function App() {
|
|
751
|
-
const { ENV } = useLoaderData<typeof loader>()
|
|
752
|
-
return (
|
|
753
|
-
<html>
|
|
754
|
-
<body>
|
|
755
|
-
<MushiProvider config={{ projectId: ENV.MUSHI_PROJECT_ID, apiKey: ENV.MUSHI_API_KEY }}>
|
|
756
|
-
<Outlet />
|
|
757
|
-
</MushiProvider>
|
|
758
|
-
<Scripts />
|
|
759
|
-
</body>
|
|
760
|
-
</html>
|
|
761
|
-
)
|
|
762
|
-
}`
|
|
763
|
-
},
|
|
764
|
-
astro: {
|
|
765
|
-
id: "astro",
|
|
766
|
-
label: "Astro",
|
|
767
|
-
packageName: "@mushi-mushi/web",
|
|
768
|
-
needsWebPackage: false,
|
|
769
|
-
// Astro exposes only PUBLIC_*-prefixed vars to client code (Vite-based).
|
|
770
|
-
snippet: () => `---
|
|
771
|
-
// src/layouts/Layout.astro (add this <script> once, e.g. in your base layout)
|
|
772
|
-
---
|
|
773
|
-
<script>
|
|
774
|
-
import { Mushi } from '@mushi-mushi/web'
|
|
775
|
-
Mushi.init({
|
|
776
|
-
projectId: import.meta.env.PUBLIC_MUSHI_PROJECT_ID,
|
|
777
|
-
apiKey: import.meta.env.PUBLIC_MUSHI_API_KEY,
|
|
778
|
-
})
|
|
779
|
-
</script>`
|
|
780
|
-
},
|
|
781
|
-
solid: {
|
|
782
|
-
id: "solid",
|
|
783
|
-
label: "Solid",
|
|
784
|
-
packageName: "@mushi-mushi/web",
|
|
785
|
-
needsWebPackage: false,
|
|
786
|
-
// Solid / SolidStart are Vite-based → VITE_*-prefixed client env.
|
|
787
|
-
snippet: () => `// src/index.tsx (or src/app.tsx for SolidStart)
|
|
788
|
-
import { onMount } from 'solid-js'
|
|
789
|
-
import { Mushi } from '@mushi-mushi/web'
|
|
790
|
-
|
|
791
|
-
onMount(() => {
|
|
792
|
-
Mushi.init({
|
|
793
|
-
projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
|
|
794
|
-
apiKey: import.meta.env.VITE_MUSHI_API_KEY,
|
|
795
|
-
})
|
|
796
|
-
})`
|
|
797
|
-
},
|
|
798
|
-
vue: {
|
|
799
|
-
id: "vue",
|
|
800
|
-
label: "Vue 3",
|
|
801
|
-
packageName: "@mushi-mushi/vue",
|
|
802
|
-
needsWebPackage: false,
|
|
803
|
-
snippet: () => `// src/main.ts
|
|
804
|
-
import { MushiPlugin } from '@mushi-mushi/vue'
|
|
805
|
-
|
|
806
|
-
app.use(MushiPlugin, {
|
|
807
|
-
projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
|
|
808
|
-
apiKey: import.meta.env.VITE_MUSHI_API_KEY,
|
|
809
|
-
})`
|
|
810
|
-
},
|
|
811
|
-
nuxt: {
|
|
812
|
-
id: "nuxt",
|
|
813
|
-
label: "Nuxt",
|
|
814
|
-
packageName: "@mushi-mushi/vue",
|
|
815
|
-
needsWebPackage: false,
|
|
816
|
-
snippet: () => `// plugins/mushi.client.ts
|
|
817
|
-
import { MushiPlugin } from '@mushi-mushi/vue'
|
|
818
|
-
|
|
819
|
-
export default defineNuxtPlugin((nuxtApp) => {
|
|
820
|
-
nuxtApp.vueApp.use(MushiPlugin, {
|
|
821
|
-
projectId: import.meta.env.NUXT_PUBLIC_MUSHI_PROJECT_ID,
|
|
822
|
-
apiKey: import.meta.env.NUXT_PUBLIC_MUSHI_API_KEY,
|
|
823
|
-
})
|
|
824
|
-
})`
|
|
825
|
-
},
|
|
826
|
-
svelte: {
|
|
827
|
-
id: "svelte",
|
|
828
|
-
label: "Svelte",
|
|
829
|
-
packageName: "@mushi-mushi/svelte",
|
|
830
|
-
needsWebPackage: false,
|
|
831
|
-
snippet: () => `// src/main.ts
|
|
832
|
-
import { initMushi } from '@mushi-mushi/svelte'
|
|
833
|
-
|
|
834
|
-
initMushi({
|
|
835
|
-
projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
|
|
836
|
-
apiKey: import.meta.env.VITE_MUSHI_API_KEY,
|
|
837
|
-
})`
|
|
838
|
-
},
|
|
839
|
-
sveltekit: {
|
|
840
|
-
id: "sveltekit",
|
|
841
|
-
label: "SvelteKit",
|
|
842
|
-
packageName: "@mushi-mushi/svelte",
|
|
843
|
-
needsWebPackage: false,
|
|
844
|
-
snippet: () => `// src/routes/+layout.svelte
|
|
845
|
-
<script>
|
|
846
|
-
import { onMount } from 'svelte'
|
|
847
|
-
|
|
848
|
-
onMount(async () => {
|
|
849
|
-
const { initMushi } = await import('@mushi-mushi/svelte')
|
|
850
|
-
initMushi({
|
|
851
|
-
projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
|
|
852
|
-
apiKey: import.meta.env.VITE_MUSHI_API_KEY,
|
|
853
|
-
})
|
|
854
|
-
})
|
|
855
|
-
</script>`
|
|
856
|
-
},
|
|
857
|
-
angular: {
|
|
858
|
-
id: "angular",
|
|
859
|
-
label: "Angular",
|
|
860
|
-
packageName: "@mushi-mushi/angular",
|
|
861
|
-
needsWebPackage: false,
|
|
862
|
-
snippet: () => `// src/main.ts
|
|
863
|
-
import { provideMushi } from '@mushi-mushi/angular'
|
|
864
|
-
|
|
865
|
-
bootstrapApplication(AppComponent, {
|
|
866
|
-
providers: [
|
|
867
|
-
provideMushi({
|
|
868
|
-
projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
|
|
869
|
-
apiKey: import.meta.env.VITE_MUSHI_API_KEY,
|
|
870
|
-
}),
|
|
871
|
-
],
|
|
872
|
-
})`
|
|
873
|
-
},
|
|
874
|
-
expo: {
|
|
875
|
-
id: "expo",
|
|
876
|
-
label: "Expo",
|
|
877
|
-
packageName: "@mushi-mushi/react-native",
|
|
878
|
-
needsWebPackage: false,
|
|
879
|
-
snippet: () => `// App.tsx
|
|
880
|
-
import { MushiProvider } from '@mushi-mushi/react-native'
|
|
881
|
-
|
|
882
|
-
export default function App() {
|
|
883
|
-
return (
|
|
884
|
-
<MushiProvider
|
|
885
|
-
projectId={process.env.EXPO_PUBLIC_MUSHI_PROJECT_ID!}
|
|
886
|
-
apiKey={process.env.EXPO_PUBLIC_MUSHI_API_KEY!}
|
|
887
|
-
>
|
|
888
|
-
<YourApp />
|
|
889
|
-
</MushiProvider>
|
|
890
|
-
)
|
|
891
|
-
}`
|
|
892
|
-
},
|
|
893
|
-
"react-native": {
|
|
894
|
-
id: "react-native",
|
|
895
|
-
label: "React Native",
|
|
896
|
-
packageName: "@mushi-mushi/react-native",
|
|
897
|
-
needsWebPackage: false,
|
|
898
|
-
snippet: () => `// App.tsx
|
|
899
|
-
import { MushiProvider } from '@mushi-mushi/react-native'
|
|
900
|
-
|
|
901
|
-
export default function App() {
|
|
902
|
-
return (
|
|
903
|
-
<MushiProvider
|
|
904
|
-
projectId={process.env.MUSHI_PROJECT_ID!}
|
|
905
|
-
apiKey={process.env.MUSHI_API_KEY!}
|
|
906
|
-
>
|
|
907
|
-
<YourApp />
|
|
908
|
-
</MushiProvider>
|
|
909
|
-
)
|
|
910
|
-
}`
|
|
911
|
-
},
|
|
912
|
-
capacitor: {
|
|
913
|
-
id: "capacitor",
|
|
914
|
-
label: "Capacitor (Ionic)",
|
|
915
|
-
packageName: "@mushi-mushi/capacitor",
|
|
916
|
-
needsWebPackage: false,
|
|
917
|
-
/* The Capacitor plugin's public API is `Mushi.configure(...)`, not
|
|
918
|
-
* `Mushi.init(...)` — see packages/capacitor/src/definitions.ts. We
|
|
919
|
-
* shipped the wrong call here for two releases and users got a runtime
|
|
920
|
-
* `TypeError: Mushi.init is not a function`. The accompanying admin
|
|
921
|
-
* console snippet (apps/admin/src/lib/sdkSnippets.ts) emits the same
|
|
922
|
-
* `Mushi.configure(...)` shape; both are pinned by tests. */
|
|
923
|
-
snippet: () => `// src/main.ts \u2014 after install, run \`npx cap sync\`
|
|
924
|
-
import { Mushi } from '@mushi-mushi/capacitor'
|
|
925
|
-
|
|
926
|
-
await Mushi.configure({
|
|
927
|
-
projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
|
|
928
|
-
apiKey: import.meta.env.VITE_MUSHI_API_KEY,
|
|
929
|
-
})`
|
|
930
|
-
},
|
|
931
|
-
express: {
|
|
932
|
-
id: "express",
|
|
933
|
-
label: "Express",
|
|
934
|
-
packageName: "@mushi-mushi/node",
|
|
935
|
-
needsWebPackage: false,
|
|
936
|
-
snippet: () => `// src/instrument.ts \u2014 load with: node --import ./dist/instrument.js
|
|
937
|
-
import { MushiNodeClient, attachUnhandledHook } from '@mushi-mushi/node'
|
|
938
|
-
import { mushiExpressErrorHandler } from '@mushi-mushi/node/express'
|
|
939
|
-
import type { Express } from 'express'
|
|
940
|
-
|
|
941
|
-
export const mushi = new MushiNodeClient({
|
|
942
|
-
projectId: process.env.MUSHI_PROJECT_ID!,
|
|
943
|
-
apiKey: process.env.MUSHI_API_KEY!,
|
|
944
|
-
environment: process.env.NODE_ENV ?? 'production',
|
|
945
|
-
})
|
|
946
|
-
attachUnhandledHook({ client: mushi })
|
|
947
|
-
|
|
948
|
-
export function attachMushi(app: Express) {
|
|
949
|
-
app.use(mushiExpressErrorHandler({ client: mushi }))
|
|
950
|
-
}`
|
|
951
|
-
},
|
|
952
|
-
fastify: {
|
|
953
|
-
id: "fastify",
|
|
954
|
-
label: "Fastify",
|
|
955
|
-
packageName: "@mushi-mushi/node",
|
|
956
|
-
needsWebPackage: false,
|
|
957
|
-
snippet: () => `// src/instrument.ts \u2014 load with: node --import ./dist/instrument.js
|
|
958
|
-
import { MushiNodeClient, attachUnhandledHook } from '@mushi-mushi/node'
|
|
959
|
-
import { mushiFastifyPlugin } from '@mushi-mushi/node/fastify'
|
|
960
|
-
import Fastify from 'fastify'
|
|
961
|
-
|
|
962
|
-
export const mushi = new MushiNodeClient({
|
|
963
|
-
projectId: process.env.MUSHI_PROJECT_ID!,
|
|
964
|
-
apiKey: process.env.MUSHI_API_KEY!,
|
|
965
|
-
environment: process.env.NODE_ENV ?? 'production',
|
|
966
|
-
})
|
|
967
|
-
attachUnhandledHook({ client: mushi })
|
|
968
|
-
|
|
969
|
-
const app = Fastify()
|
|
970
|
-
mushiFastifyPlugin(app, { client: mushi })`
|
|
971
|
-
},
|
|
972
|
-
hono: {
|
|
973
|
-
id: "hono",
|
|
974
|
-
label: "Hono",
|
|
975
|
-
packageName: "@mushi-mushi/node",
|
|
976
|
-
needsWebPackage: false,
|
|
977
|
-
snippet: () => `// src/instrument.ts \u2014 load with: node --import ./dist/instrument.js
|
|
978
|
-
import { MushiNodeClient, attachUnhandledHook } from '@mushi-mushi/node'
|
|
979
|
-
import { mushiHonoErrorHandler } from '@mushi-mushi/node/hono'
|
|
980
|
-
import { Hono } from 'hono'
|
|
981
|
-
|
|
982
|
-
export const mushi = new MushiNodeClient({
|
|
983
|
-
projectId: process.env.MUSHI_PROJECT_ID!,
|
|
984
|
-
apiKey: process.env.MUSHI_API_KEY!,
|
|
985
|
-
environment: process.env.NODE_ENV ?? 'production',
|
|
986
|
-
})
|
|
987
|
-
attachUnhandledHook({ client: mushi })
|
|
988
|
-
|
|
989
|
-
const app = new Hono()
|
|
990
|
-
app.onError(
|
|
991
|
-
mushiHonoErrorHandler({ client: mushi }, (err, c) =>
|
|
992
|
-
c.text('Internal Server Error', 500),
|
|
993
|
-
),
|
|
994
|
-
)`
|
|
995
|
-
},
|
|
996
|
-
vanilla: {
|
|
997
|
-
id: "vanilla",
|
|
998
|
-
label: "Vanilla JS / unknown",
|
|
999
|
-
packageName: "@mushi-mushi/web",
|
|
1000
|
-
needsWebPackage: false,
|
|
1001
|
-
snippet: () => `// Anywhere in your client bundle
|
|
1002
|
-
import { Mushi } from '@mushi-mushi/web'
|
|
1003
|
-
|
|
1004
|
-
Mushi.init({
|
|
1005
|
-
projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
|
|
1006
|
-
apiKey: import.meta.env.VITE_MUSHI_API_KEY,
|
|
1007
|
-
})`
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
function readPackageJson(cwd) {
|
|
1011
|
-
const path = join3(cwd, "package.json");
|
|
1012
|
-
if (!existsSync3(path)) return null;
|
|
1013
|
-
try {
|
|
1014
|
-
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
1015
|
-
} catch {
|
|
1016
|
-
return null;
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
function detectFramework(cwd, pkg) {
|
|
1020
|
-
const deps = collectDeps(pkg);
|
|
1021
|
-
if (deps.has("next")) return FRAMEWORKS.next;
|
|
1022
|
-
if (deps.has("nuxt")) return FRAMEWORKS.nuxt;
|
|
1023
|
-
if (deps.has("@sveltejs/kit")) return FRAMEWORKS.sveltekit;
|
|
1024
|
-
if (deps.has("@angular/core")) return FRAMEWORKS.angular;
|
|
1025
|
-
if (deps.has("expo")) return FRAMEWORKS.expo;
|
|
1026
|
-
if (deps.has("react-native")) return FRAMEWORKS["react-native"];
|
|
1027
|
-
if (deps.has("@remix-run/react") || deps.has("@remix-run/node") || deps.has("@remix-run/serve")) {
|
|
1028
|
-
return FRAMEWORKS.remix;
|
|
1029
|
-
}
|
|
1030
|
-
if (deps.has("astro")) return FRAMEWORKS.astro;
|
|
1031
|
-
if (deps.has("@solidjs/start") || deps.has("solid-js")) return FRAMEWORKS.solid;
|
|
1032
|
-
if (deps.has("@capacitor/core") && deps.has("react")) return FRAMEWORKS.react;
|
|
1033
|
-
if (deps.has("@capacitor/core")) return FRAMEWORKS.capacitor;
|
|
1034
|
-
if (deps.has("svelte")) return FRAMEWORKS.svelte;
|
|
1035
|
-
if (deps.has("vue")) return FRAMEWORKS.vue;
|
|
1036
|
-
if (deps.has("react-scripts")) return FRAMEWORKS.cra;
|
|
1037
|
-
if (deps.has("react")) return FRAMEWORKS.react;
|
|
1038
|
-
if (deps.has("express")) return FRAMEWORKS.express;
|
|
1039
|
-
if (deps.has("fastify")) return FRAMEWORKS.fastify;
|
|
1040
|
-
if (deps.has("hono") || deps.has("@hono/hono")) return FRAMEWORKS.hono;
|
|
1041
|
-
if (existsSync3(join3(cwd, "next.config.js")) || existsSync3(join3(cwd, "next.config.ts"))) {
|
|
1042
|
-
return FRAMEWORKS.next;
|
|
1043
|
-
}
|
|
1044
|
-
if (existsSync3(join3(cwd, "nuxt.config.ts")) || existsSync3(join3(cwd, "nuxt.config.js"))) {
|
|
1045
|
-
return FRAMEWORKS.nuxt;
|
|
1046
|
-
}
|
|
1047
|
-
if (existsSync3(join3(cwd, "svelte.config.js"))) return FRAMEWORKS.svelte;
|
|
1048
|
-
if (existsSync3(join3(cwd, "angular.json"))) return FRAMEWORKS.angular;
|
|
1049
|
-
if (existsSync3(join3(cwd, "remix.config.js")) || existsSync3(join3(cwd, "remix.config.ts"))) {
|
|
1050
|
-
return FRAMEWORKS.remix;
|
|
1051
|
-
}
|
|
1052
|
-
if (existsSync3(join3(cwd, "astro.config.mjs")) || existsSync3(join3(cwd, "astro.config.ts")) || existsSync3(join3(cwd, "astro.config.js"))) {
|
|
1053
|
-
return FRAMEWORKS.astro;
|
|
1054
|
-
}
|
|
1055
|
-
return FRAMEWORKS.vanilla;
|
|
1056
|
-
}
|
|
1057
|
-
function detectPackageManager(cwd) {
|
|
1058
|
-
if (existsSync3(join3(cwd, "bun.lockb")) || existsSync3(join3(cwd, "bun.lock"))) return "bun";
|
|
1059
|
-
if (existsSync3(join3(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
1060
|
-
if (existsSync3(join3(cwd, "yarn.lock"))) return "yarn";
|
|
1061
|
-
if (existsSync3(join3(cwd, "package-lock.json"))) return "npm";
|
|
1062
|
-
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
1063
|
-
if (userAgent.startsWith("bun")) return "bun";
|
|
1064
|
-
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
1065
|
-
if (userAgent.startsWith("yarn")) return "yarn";
|
|
1066
|
-
return "npm";
|
|
1067
|
-
}
|
|
1068
|
-
function installCommand(pm, packages) {
|
|
1069
|
-
const verb = pm === "npm" ? "install" : "add";
|
|
1070
|
-
return `${pm} ${verb} ${packages.join(" ")}`;
|
|
1071
|
-
}
|
|
1072
|
-
var SERVER_FRAMEWORK_IDS = /* @__PURE__ */ new Set(["express", "fastify", "hono"]);
|
|
1073
|
-
function envVarsToWrite(apiKey, projectId, framework, endpoint) {
|
|
1074
|
-
const CLOUD_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
|
|
1075
|
-
const endpointLine = endpoint && endpoint !== CLOUD_ENDPOINT ? `MUSHI_API_ENDPOINT=${endpoint}` : null;
|
|
1076
|
-
if (SERVER_FRAMEWORK_IDS.has(framework.id)) {
|
|
1077
|
-
return [
|
|
1078
|
-
`MUSHI_PROJECT_ID=${projectId}`,
|
|
1079
|
-
`MUSHI_API_KEY=${apiKey}`,
|
|
1080
|
-
endpointLine
|
|
1081
|
-
].filter(Boolean).join("\n");
|
|
1082
|
-
}
|
|
1083
|
-
if (framework.id === "react-native" || framework.id === "remix") {
|
|
1084
|
-
return [
|
|
1085
|
-
`MUSHI_PROJECT_ID=${projectId}`,
|
|
1086
|
-
`MUSHI_API_KEY=${apiKey}`,
|
|
1087
|
-
endpointLine
|
|
1088
|
-
].filter(Boolean).join("\n");
|
|
1089
|
-
}
|
|
1090
|
-
if (framework.id === "expo") {
|
|
1091
|
-
return [
|
|
1092
|
-
`EXPO_PUBLIC_MUSHI_PROJECT_ID=${projectId}`,
|
|
1093
|
-
`EXPO_PUBLIC_MUSHI_API_KEY=${apiKey}`,
|
|
1094
|
-
endpointLine ? `EXPO_PUBLIC_${endpointLine}` : null
|
|
1095
|
-
].filter(Boolean).join("\n");
|
|
1096
|
-
}
|
|
1097
|
-
const prefix = framework.id === "next" ? "NEXT_PUBLIC_" : framework.id === "nuxt" ? "NUXT_PUBLIC_" : framework.id === "cra" ? "REACT_APP_" : framework.id === "astro" ? "PUBLIC_" : "VITE_";
|
|
1098
|
-
return [
|
|
1099
|
-
`${prefix}MUSHI_PROJECT_ID=${projectId}`,
|
|
1100
|
-
`${prefix}MUSHI_API_KEY=${apiKey}`,
|
|
1101
|
-
endpointLine ? `${prefix}${endpointLine}` : null
|
|
1102
|
-
].filter(Boolean).join("\n");
|
|
1103
|
-
}
|
|
1104
|
-
function collectDeps(pkg) {
|
|
1105
|
-
if (!pkg) return /* @__PURE__ */ new Set();
|
|
1106
|
-
return /* @__PURE__ */ new Set([
|
|
1107
|
-
...Object.keys(pkg.dependencies ?? {}),
|
|
1108
|
-
...Object.keys(pkg.devDependencies ?? {}),
|
|
1109
|
-
...Object.keys(pkg.peerDependencies ?? {})
|
|
1110
|
-
]);
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
714
|
// src/sanitize-config.ts
|
|
1114
715
|
var PROJECT_ID_RE = /^(?:proj_[A-Za-z0-9_-]{10,}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
|
|
1115
716
|
var API_KEY_RE = /^(mushi_|mush_pk_)[A-Za-z0-9_-]{10,}$/;
|
|
@@ -1221,55 +822,133 @@ async function waitForIngestReady(options) {
|
|
|
1221
822
|
return { ok: false, payload: lastPayload, attempts: maxAttempts, reason: "timeout" };
|
|
1222
823
|
}
|
|
1223
824
|
|
|
825
|
+
// src/key-scopes.ts
|
|
826
|
+
var SDK_KEY_SCOPES = ["report:write"];
|
|
827
|
+
var CLI_KEY_SCOPES = ["report:write", "mcp:read"];
|
|
828
|
+
var FULL_CLI_KEY_SCOPES = ["report:write", "mcp:read", "mcp:write"];
|
|
829
|
+
var SDK_KEY_LABEL = "sdk-ingest";
|
|
830
|
+
var CLI_KEY_LABEL = "cli-login";
|
|
831
|
+
async function probeKeyScope(endpoint, apiKey, projectId, doFetch = globalThis.fetch) {
|
|
832
|
+
let base = endpoint;
|
|
833
|
+
while (base.endsWith("/")) base = base.slice(0, -1);
|
|
834
|
+
let res;
|
|
835
|
+
try {
|
|
836
|
+
res = await doFetch(`${base}/v1/admin/mcp/account-overview`, {
|
|
837
|
+
headers: { "X-Mushi-Api-Key": apiKey, "X-Mushi-Project": projectId },
|
|
838
|
+
signal: AbortSignal.timeout(8e3)
|
|
839
|
+
});
|
|
840
|
+
} catch (err) {
|
|
841
|
+
return { result: "unreachable", message: err instanceof Error ? err.message : String(err) };
|
|
842
|
+
}
|
|
843
|
+
if (res.ok) return { result: "mcp", status: res.status };
|
|
844
|
+
if (res.status === 401) return { result: "invalid", status: res.status };
|
|
845
|
+
if (res.status === 403) {
|
|
846
|
+
const body = await res.json().catch(() => null);
|
|
847
|
+
const code = body?.error?.code ?? body?.code;
|
|
848
|
+
if (code === "INSUFFICIENT_SCOPE") return { result: "ingest-only", status: res.status };
|
|
849
|
+
}
|
|
850
|
+
return { result: "unknown", status: res.status };
|
|
851
|
+
}
|
|
852
|
+
function describeUnsafeSdkKey(probe, endpoint) {
|
|
853
|
+
switch (probe.result) {
|
|
854
|
+
case "mcp":
|
|
855
|
+
return "this key can read your bug reports (mcp scope), and SDK env vars ship inside the app bundle";
|
|
856
|
+
case "invalid":
|
|
857
|
+
return "the backend rejected this key";
|
|
858
|
+
case "unreachable":
|
|
859
|
+
return `could not reach ${endpoint} to confirm the key is ingest-only (${probe.message ?? "network error"})`;
|
|
860
|
+
case "unknown":
|
|
861
|
+
return `could not confirm the key is ingest-only (HTTP ${probe.status ?? "?"})`;
|
|
862
|
+
case "ingest-only":
|
|
863
|
+
return "the key is ingest-only";
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
1224
867
|
// src/mcp-config.ts
|
|
1225
868
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
1226
869
|
import { dirname as dirname3 } from "path";
|
|
1227
870
|
var DEFAULT_MUSHI_FEATURES = "triage,fixes,inventory,setup,docs";
|
|
871
|
+
var MCP_CLIENTS = ["cursor", "claude", "continue", "zed"];
|
|
872
|
+
function isMcpClient(value) {
|
|
873
|
+
return typeof value === "string" && MCP_CLIENTS.includes(value);
|
|
874
|
+
}
|
|
875
|
+
var MCP_CLIENT_LABEL = {
|
|
876
|
+
cursor: "Cursor",
|
|
877
|
+
claude: "Claude Code",
|
|
878
|
+
continue: "Continue",
|
|
879
|
+
zed: "Zed"
|
|
880
|
+
};
|
|
881
|
+
function buildHostedMcpServerBlock(url) {
|
|
882
|
+
return { type: "http", url };
|
|
883
|
+
}
|
|
884
|
+
function isPlainObject(value) {
|
|
885
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
886
|
+
}
|
|
1228
887
|
async function writeMcpServerEntry(opts) {
|
|
1229
888
|
const { configPath, serverName, serverBlock, dryRun = false } = opts;
|
|
1230
|
-
let
|
|
1231
|
-
let created = false;
|
|
889
|
+
let existing = null;
|
|
1232
890
|
try {
|
|
1233
|
-
|
|
891
|
+
existing = await readFile(configPath, "utf8");
|
|
892
|
+
} catch {
|
|
893
|
+
existing = null;
|
|
894
|
+
}
|
|
895
|
+
let merged = {};
|
|
896
|
+
if (existing !== null && existing.trim() !== "") {
|
|
897
|
+
let parsed;
|
|
1234
898
|
try {
|
|
1235
|
-
|
|
899
|
+
parsed = JSON.parse(existing);
|
|
1236
900
|
} catch {
|
|
1237
|
-
|
|
901
|
+
throw new Error(`${configPath} is not valid JSON \u2014 fix or remove it, then re-run (nothing was written).`);
|
|
1238
902
|
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
903
|
+
if (!isPlainObject(parsed)) {
|
|
904
|
+
throw new Error(`${configPath} is not a JSON object \u2014 fix or remove it, then re-run (nothing was written).`);
|
|
905
|
+
}
|
|
906
|
+
merged = parsed;
|
|
907
|
+
}
|
|
908
|
+
const currentServers = merged.mcpServers;
|
|
909
|
+
if (currentServers !== void 0 && !isPlainObject(currentServers)) {
|
|
910
|
+
throw new Error(`${configPath} has an "mcpServers" field that is not an object \u2014 fix it, then re-run (nothing was written).`);
|
|
1242
911
|
}
|
|
1243
|
-
|
|
1244
|
-
servers[serverName] = serverBlock;
|
|
1245
|
-
merged.mcpServers = servers;
|
|
912
|
+
merged.mcpServers = { ...currentServers ?? {}, [serverName]: serverBlock };
|
|
1246
913
|
const output = JSON.stringify(merged, null, 2) + "\n";
|
|
1247
914
|
if (!dryRun) {
|
|
1248
915
|
await mkdir(dirname3(configPath), { recursive: true });
|
|
1249
916
|
await writeFile(configPath, output, "utf8");
|
|
1250
917
|
}
|
|
1251
|
-
return { created, path: configPath };
|
|
918
|
+
return { created: existing === null, path: configPath };
|
|
919
|
+
}
|
|
920
|
+
function apiKeyPlaceholderFor(client) {
|
|
921
|
+
switch (client) {
|
|
922
|
+
case "cursor":
|
|
923
|
+
return "${env:MUSHI_API_KEY}";
|
|
924
|
+
case "claude":
|
|
925
|
+
return "${MUSHI_API_KEY:-}";
|
|
926
|
+
case "continue":
|
|
927
|
+
case "zed":
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
1252
930
|
}
|
|
1253
931
|
function buildMcpServerBlock(opts) {
|
|
932
|
+
const keyValue = opts.inlineKey ? opts.apiKey : apiKeyPlaceholderFor(opts.client);
|
|
1254
933
|
return {
|
|
1255
934
|
command: "npx",
|
|
1256
935
|
args: ["-y", MUSHI_MCP_PIN_SPEC],
|
|
1257
936
|
env: {
|
|
1258
937
|
MUSHI_API_ENDPOINT: opts.endpoint,
|
|
1259
938
|
MUSHI_PROJECT_ID: opts.projectId,
|
|
1260
|
-
|
|
939
|
+
...keyValue !== null ? { MUSHI_API_KEY: keyValue } : {},
|
|
1261
940
|
MUSHI_FEATURES: DEFAULT_MUSHI_FEATURES
|
|
1262
941
|
}
|
|
1263
942
|
};
|
|
1264
943
|
}
|
|
1265
|
-
function printKeyExportHint(
|
|
1266
|
-
console.log("");
|
|
1267
|
-
console.log(" Your API key was NOT written into the config file (prevents git leaks).");
|
|
1268
|
-
console.log(" Add it to your shell profile or .env.local:");
|
|
944
|
+
function printKeyExportHint(client, configPath = CONFIG_PATH) {
|
|
1269
945
|
console.log("");
|
|
1270
|
-
console.log(
|
|
1271
|
-
console.log(
|
|
1272
|
-
|
|
946
|
+
console.log(" No API key was written into the MCP config (safe to commit).");
|
|
947
|
+
console.log(` The MCP server uses the key \`mushi login\` saved in ${configPath}.`);
|
|
948
|
+
if (apiKeyPlaceholderFor(client) !== null) {
|
|
949
|
+
console.log(` To use a different key, export MUSHI_API_KEY in the environment ${MCP_CLIENT_LABEL[client]} is launched from.`);
|
|
950
|
+
}
|
|
951
|
+
console.log(" To write the key into the file instead (keep it out of git), re-run with --inline-key.");
|
|
1273
952
|
}
|
|
1274
953
|
function buildMcpServerName(opts) {
|
|
1275
954
|
if (opts.legacy) return "mushi";
|
|
@@ -1281,6 +960,12 @@ function buildMcpServerName(opts) {
|
|
|
1281
960
|
}
|
|
1282
961
|
|
|
1283
962
|
// src/connect.ts
|
|
963
|
+
async function resolveSdkEnvKey(opts) {
|
|
964
|
+
const candidate = opts.sdkKey ?? savedSdkKeyFor(opts.baseConfig, opts.projectId) ?? opts.apiKey;
|
|
965
|
+
const probe = await opts.probe(opts.endpoint, candidate, opts.projectId);
|
|
966
|
+
if (probe.result === "ingest-only") return { ok: true, key: candidate };
|
|
967
|
+
return { ok: false, reason: describeUnsafeSdkKey(probe, opts.endpoint) };
|
|
968
|
+
}
|
|
1284
969
|
function envKeyPresent(content, key) {
|
|
1285
970
|
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1286
971
|
return new RegExp(`^${escaped}=`, "m").test(content);
|
|
@@ -1310,44 +995,41 @@ ${needs.join("\n")}
|
|
|
1310
995
|
await writeFile2(path, block, "utf8");
|
|
1311
996
|
return true;
|
|
1312
997
|
}
|
|
1313
|
-
async function ensureMcpJsonGitignored(cwd, messages) {
|
|
1314
|
-
const gitignorePath = join4(cwd, ".gitignore");
|
|
1315
|
-
const patterns = [".cursor/mcp.json", ".cursor/"];
|
|
1316
|
-
let content = null;
|
|
1317
|
-
try {
|
|
1318
|
-
content = await readFile2(gitignorePath, "utf8");
|
|
1319
|
-
} catch {
|
|
1320
|
-
}
|
|
1321
|
-
if (content === null) {
|
|
1322
|
-
messages.push(
|
|
1323
|
-
"\u26A0 No .gitignore found \u2014 .cursor/mcp.json contains your API key. Add `.cursor/mcp.json` before committing."
|
|
1324
|
-
);
|
|
1325
|
-
return;
|
|
1326
|
-
}
|
|
1327
|
-
const covered = patterns.some((p) => content.split("\n").some((line) => line.trim() === p || line.trim() === `${p}/`));
|
|
1328
|
-
if (covered) return;
|
|
1329
|
-
await appendFile(gitignorePath, "\n# Mushi \u2014 keep MCP credentials out of git\n.cursor/mcp.json\n", "utf8");
|
|
1330
|
-
messages.push("\u2713 Added .cursor/mcp.json to .gitignore (contains API key)");
|
|
1331
|
-
}
|
|
1332
998
|
async function runConnect(opts, baseConfig = {}) {
|
|
1333
999
|
const cwd = resolve(opts.cwd ?? process.cwd());
|
|
1334
1000
|
const endpoint = assertEndpoint(opts.endpoint);
|
|
1335
1001
|
const projectId = requireUuid(opts.projectId, "projectId");
|
|
1336
1002
|
const messages = [];
|
|
1003
|
+
const writeEnv = opts.writeEnv !== false;
|
|
1004
|
+
const sdkEnvKey = writeEnv ? await resolveSdkEnvKey({
|
|
1005
|
+
sdkKey: opts.sdkKey,
|
|
1006
|
+
apiKey: opts.apiKey,
|
|
1007
|
+
projectId,
|
|
1008
|
+
endpoint,
|
|
1009
|
+
baseConfig,
|
|
1010
|
+
probe: opts.probeKeyScope ?? probeKeyScope
|
|
1011
|
+
}) : null;
|
|
1337
1012
|
const config = {
|
|
1338
1013
|
...baseConfig,
|
|
1339
1014
|
apiKey: opts.apiKey,
|
|
1340
1015
|
projectId,
|
|
1341
|
-
endpoint
|
|
1016
|
+
endpoint,
|
|
1017
|
+
...sdkEnvKey?.ok ? { sdkKey: { projectId, key: sdkEnvKey.key } } : {}
|
|
1342
1018
|
};
|
|
1343
1019
|
saveConfig(config);
|
|
1344
1020
|
messages.push(`\u2713 Credentials saved to ${CONFIG_PATH}`);
|
|
1345
1021
|
let envPath = null;
|
|
1346
|
-
|
|
1022
|
+
let envRefused = false;
|
|
1023
|
+
if (sdkEnvKey && !sdkEnvKey.ok) {
|
|
1024
|
+
envRefused = true;
|
|
1025
|
+
messages.push(
|
|
1026
|
+
`\u2717 Did not write SDK env vars: ${sdkEnvKey.reason}. Pass an ingest-only key with --sdk-key <key> (console \u2192 Projects \u2192 API keys, scope report:write), or run \`npx mushi-mushi\`, which mints one.`
|
|
1027
|
+
);
|
|
1028
|
+
} else if (sdkEnvKey?.ok) {
|
|
1347
1029
|
const pkg = readPackageJson(cwd);
|
|
1348
1030
|
const framework = detectFramework(cwd, pkg);
|
|
1349
|
-
const lines = envVarsToWrite(
|
|
1350
|
-
envPath =
|
|
1031
|
+
const lines = envVarsToWrite(sdkEnvKey.key, projectId, framework).split("\n");
|
|
1032
|
+
envPath = join3(cwd, ".env.local");
|
|
1351
1033
|
const wrote = await mergeEnvFile(envPath, lines);
|
|
1352
1034
|
messages.push(
|
|
1353
1035
|
wrote ? `\u2713 Env vars merged into ${envPath}` : `\u2713 Env vars already present in ${envPath} (existing values left untouched)`
|
|
@@ -1361,20 +1043,20 @@ async function runConnect(opts, baseConfig = {}) {
|
|
|
1361
1043
|
}
|
|
1362
1044
|
let mcpPath = null;
|
|
1363
1045
|
if (opts.wireIde !== false) {
|
|
1364
|
-
const mcpDir =
|
|
1365
|
-
mcpPath =
|
|
1046
|
+
const mcpDir = join3(cwd, ".cursor");
|
|
1047
|
+
mcpPath = join3(mcpDir, "mcp.json");
|
|
1366
1048
|
const serverName = buildMcpServerName({ projectId });
|
|
1367
1049
|
const serverBlock = buildMcpServerBlock({
|
|
1368
1050
|
endpoint,
|
|
1369
1051
|
projectId,
|
|
1370
1052
|
apiKey: opts.apiKey,
|
|
1053
|
+
client: "cursor",
|
|
1371
1054
|
inlineKey: false
|
|
1372
|
-
//
|
|
1055
|
+
// no secret in the file; the MCP server reads the saved CLI key
|
|
1373
1056
|
});
|
|
1374
1057
|
await writeMcpServerEntry({ configPath: mcpPath, serverName, serverBlock });
|
|
1375
|
-
await ensureMcpJsonGitignored(cwd, messages);
|
|
1376
1058
|
messages.push(`\u2713 Wired ${mcpPath} \u2014 restart Cursor and run "list mushi tools"`);
|
|
1377
|
-
messages.push(`
|
|
1059
|
+
messages.push(` The MCP server reads the key saved in ${CONFIG_PATH} (export MUSHI_API_KEY before launching Cursor to override it).`);
|
|
1378
1060
|
}
|
|
1379
1061
|
let heartbeat = null;
|
|
1380
1062
|
if (opts.wait) {
|
|
@@ -1408,27 +1090,31 @@ async function runConnect(opts, baseConfig = {}) {
|
|
|
1408
1090
|
);
|
|
1409
1091
|
}
|
|
1410
1092
|
}
|
|
1411
|
-
const ok = !opts.wait || Boolean(heartbeat?.ok);
|
|
1093
|
+
const ok = !envRefused && (!opts.wait || Boolean(heartbeat?.ok));
|
|
1412
1094
|
return { ok, envPath, mcpPath, heartbeat, messages };
|
|
1413
1095
|
}
|
|
1414
1096
|
|
|
1415
1097
|
export {
|
|
1098
|
+
MushiCliError,
|
|
1416
1099
|
printAndExit,
|
|
1417
1100
|
installSignalHandlers,
|
|
1418
1101
|
trySaveKeyToKeychain,
|
|
1102
|
+
savedSdkKeyFor,
|
|
1419
1103
|
resolveProfileName,
|
|
1104
|
+
CONFIG_PATH,
|
|
1420
1105
|
loadConfig,
|
|
1421
1106
|
ensureClientId,
|
|
1422
1107
|
maybeShowTelemetryNotice,
|
|
1423
1108
|
saveConfig,
|
|
1424
1109
|
listProfiles,
|
|
1425
1110
|
setActiveProfile,
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1111
|
+
SDK_KEY_SCOPES,
|
|
1112
|
+
CLI_KEY_SCOPES,
|
|
1113
|
+
FULL_CLI_KEY_SCOPES,
|
|
1114
|
+
SDK_KEY_LABEL,
|
|
1115
|
+
CLI_KEY_LABEL,
|
|
1116
|
+
probeKeyScope,
|
|
1117
|
+
describeUnsafeSdkKey,
|
|
1432
1118
|
consoleUrl,
|
|
1433
1119
|
resolveConsoleUrlSync,
|
|
1434
1120
|
resolveConsoleUrl,
|
|
@@ -1453,9 +1139,14 @@ export {
|
|
|
1453
1139
|
sanitizeCliCredentials,
|
|
1454
1140
|
apiKeyHeaders,
|
|
1455
1141
|
fetchIngestSetup,
|
|
1142
|
+
MCP_CLIENTS,
|
|
1143
|
+
isMcpClient,
|
|
1144
|
+
MCP_CLIENT_LABEL,
|
|
1145
|
+
buildHostedMcpServerBlock,
|
|
1456
1146
|
writeMcpServerEntry,
|
|
1457
1147
|
buildMcpServerBlock,
|
|
1458
1148
|
printKeyExportHint,
|
|
1459
1149
|
buildMcpServerName,
|
|
1150
|
+
resolveSdkEnvKey,
|
|
1460
1151
|
runConnect
|
|
1461
1152
|
};
|