@mushi-mushi/cli 0.18.0 → 0.18.2
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/CONTRIBUTING.md +27 -8
- package/README.md +8 -5
- package/dist/{chunk-S4WQHEGH.js → chunk-7Q44KWGN.js} +77 -51
- package/dist/chunk-A2NHESSJ.js +313 -0
- package/dist/chunk-E4IZPV6D.js +6 -0
- package/dist/chunk-LDPFTQLE.js +44 -0
- package/dist/cli-shared-XH3XYIS7.js +18 -0
- package/dist/detect.d.ts +1 -1
- package/dist/detect.js +1 -1
- package/dist/endpoint-DGS73AVM.js +15 -0
- package/dist/index.js +330 -560
- package/dist/init.js +5 -5
- package/dist/version.js +1 -1
- package/package.json +6 -6
- package/dist/chunk-ZB6ITDCQ.js +0 -6
package/dist/index.js
CHANGED
|
@@ -1,207 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
API_TIMEOUT_MS,
|
|
4
|
+
CONFIG_PATH,
|
|
5
|
+
MUSHI_CLI_VERSION,
|
|
6
|
+
apiCall,
|
|
7
|
+
die,
|
|
8
|
+
fmtDate,
|
|
9
|
+
installSignalHandlers,
|
|
10
|
+
loadConfig,
|
|
11
|
+
pad,
|
|
12
|
+
requireConfig,
|
|
13
|
+
saveConfig
|
|
14
|
+
} from "./chunk-A2NHESSJ.js";
|
|
15
|
+
import {
|
|
16
|
+
TEST_REPORT_FETCH_TIMEOUT_MS,
|
|
17
|
+
assertEndpoint,
|
|
18
|
+
normalizeEndpoint,
|
|
19
|
+
resolveCloudEndpoint
|
|
20
|
+
} from "./chunk-LDPFTQLE.js";
|
|
2
21
|
|
|
3
22
|
// src/index.ts
|
|
4
23
|
import { Command } from "commander";
|
|
5
24
|
|
|
6
|
-
// src/errors.ts
|
|
7
|
-
var EXIT_CODE_MAP = {
|
|
8
|
-
E_AUTH_MISSING: 2,
|
|
9
|
-
E_AUTH_INVALID: 2,
|
|
10
|
-
E_PROJECT_MISSING: 2,
|
|
11
|
-
E_ENDPOINT_INVALID: 2,
|
|
12
|
-
E_INVALID_INPUT: 2,
|
|
13
|
-
E_NOT_INTERACTIVE: 2,
|
|
14
|
-
E_NETWORK: 3,
|
|
15
|
-
E_TIMEOUT: 3,
|
|
16
|
-
E_API_ERROR: 3,
|
|
17
|
-
E_RATE_LIMITED: 3,
|
|
18
|
-
E_FILE_NOT_FOUND: 1,
|
|
19
|
-
E_FILE_PERMISSION: 1,
|
|
20
|
-
E_FRESHNESS_STALE: 1,
|
|
21
|
-
E_INTERRUPTED: 130,
|
|
22
|
-
// 128 + SIGINT (2). POSIX convention.
|
|
23
|
-
E_INTERNAL: 1
|
|
24
|
-
};
|
|
25
|
-
var MushiCliError = class extends Error {
|
|
26
|
-
code;
|
|
27
|
-
hint;
|
|
28
|
-
cause;
|
|
29
|
-
constructor(code, message, hint, cause) {
|
|
30
|
-
super(message);
|
|
31
|
-
this.name = "MushiCliError";
|
|
32
|
-
this.code = code;
|
|
33
|
-
if (hint) this.hint = hint;
|
|
34
|
-
if (cause !== void 0) this.cause = cause;
|
|
35
|
-
}
|
|
36
|
-
/** Exit code POSIX-aware shell scripts should branch on. */
|
|
37
|
-
get exitCode() {
|
|
38
|
-
return EXIT_CODE_MAP[this.code];
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Format for `--json` output. Used by the JSON writer when a CLI
|
|
42
|
-
* subcommand was invoked with `--json` and an error escapes.
|
|
43
|
-
*/
|
|
44
|
-
toJSON() {
|
|
45
|
-
return {
|
|
46
|
-
error: {
|
|
47
|
-
code: this.code,
|
|
48
|
-
message: this.message,
|
|
49
|
-
...this.hint ? { hint: this.hint } : {}
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// src/signals.ts
|
|
56
|
-
var installed = false;
|
|
57
|
-
var activeController = null;
|
|
58
|
-
function installSignalHandlers() {
|
|
59
|
-
if (installed) return;
|
|
60
|
-
installed = true;
|
|
61
|
-
const abortAndExit = (signal) => {
|
|
62
|
-
activeController?.abort(
|
|
63
|
-
new MushiCliError(
|
|
64
|
-
"E_INTERRUPTED",
|
|
65
|
-
`aborted by ${signal}`,
|
|
66
|
-
"partial state on the server is safe to retry \u2014 re-run the same command"
|
|
67
|
-
)
|
|
68
|
-
);
|
|
69
|
-
const code = signal === "SIGINT" ? 130 : signal === "SIGTERM" ? 143 : 1;
|
|
70
|
-
process.nextTick(() => {
|
|
71
|
-
process.exit(code);
|
|
72
|
-
});
|
|
73
|
-
};
|
|
74
|
-
process.on("SIGINT", () => abortAndExit("SIGINT"));
|
|
75
|
-
process.on("SIGTERM", () => abortAndExit("SIGTERM"));
|
|
76
|
-
}
|
|
77
|
-
function getAbortSignal(external) {
|
|
78
|
-
if (external) return external.signal;
|
|
79
|
-
if (!activeController || activeController.signal.aborted) {
|
|
80
|
-
activeController = new AbortController();
|
|
81
|
-
}
|
|
82
|
-
return activeController.signal;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// src/version.ts
|
|
86
|
-
var MUSHI_CLI_VERSION = true ? "0.18.0" : "0.0.0-dev";
|
|
87
|
-
|
|
88
|
-
// src/config.ts
|
|
89
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
90
|
-
import { homedir } from "os";
|
|
91
|
-
import { dirname, join } from "path";
|
|
92
|
-
var SECURE_FILE_MODE = 384;
|
|
93
|
-
var SECURE_DIR_MODE = 448;
|
|
94
|
-
function resolveXdgConfigPath() {
|
|
95
|
-
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
96
|
-
if (xdg && xdg.length > 0) {
|
|
97
|
-
return join(xdg, "mushi", "config.json");
|
|
98
|
-
}
|
|
99
|
-
if (process.platform === "win32") {
|
|
100
|
-
const appData = process.env["APPDATA"];
|
|
101
|
-
if (appData && appData.length > 0) {
|
|
102
|
-
return join(appData, "mushi", "config.json");
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return join(homedir(), ".config", "mushi", "config.json");
|
|
106
|
-
}
|
|
107
|
-
var LEGACY_CONFIG_PATH = join(homedir(), ".mushirc");
|
|
108
|
-
var CONFIG_PATH = resolveXdgConfigPath();
|
|
109
|
-
function loadConfig(path = CONFIG_PATH) {
|
|
110
|
-
let file = {};
|
|
111
|
-
if (existsSync(path)) {
|
|
112
|
-
tightenPermissions(path);
|
|
113
|
-
try {
|
|
114
|
-
file = JSON.parse(readFileSync(path, "utf-8"));
|
|
115
|
-
} catch {
|
|
116
|
-
}
|
|
117
|
-
} else if (path === CONFIG_PATH && existsSync(LEGACY_CONFIG_PATH)) {
|
|
118
|
-
file = migrateLegacyConfig() ?? {};
|
|
119
|
-
}
|
|
120
|
-
const endpointFromEnv = process.env["MUSHI_API_ENDPOINT"] ?? process.env["MUSHI_ENDPOINT"] ?? void 0;
|
|
121
|
-
const fromEnv = {
|
|
122
|
-
...process.env["MUSHI_API_KEY"] ? { apiKey: process.env["MUSHI_API_KEY"] } : {},
|
|
123
|
-
...process.env["MUSHI_PROJECT_ID"] ? { projectId: process.env["MUSHI_PROJECT_ID"] } : {},
|
|
124
|
-
...endpointFromEnv ? { endpoint: endpointFromEnv } : {}
|
|
125
|
-
};
|
|
126
|
-
return { ...file, ...fromEnv };
|
|
127
|
-
}
|
|
128
|
-
function saveConfig(config, path = CONFIG_PATH) {
|
|
129
|
-
const dir = dirname(path);
|
|
130
|
-
if (!existsSync(dir)) {
|
|
131
|
-
mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
132
|
-
} else {
|
|
133
|
-
tightenDirPermissions(dir);
|
|
134
|
-
}
|
|
135
|
-
writeFileSync(path, JSON.stringify(config, null, 2), { mode: SECURE_FILE_MODE });
|
|
136
|
-
tightenPermissions(path);
|
|
137
|
-
}
|
|
138
|
-
function migrateLegacyConfig(legacyPath = LEGACY_CONFIG_PATH, destPath = CONFIG_PATH) {
|
|
139
|
-
if (!existsSync(legacyPath)) return null;
|
|
140
|
-
let parsed;
|
|
141
|
-
try {
|
|
142
|
-
parsed = JSON.parse(readFileSync(legacyPath, "utf-8"));
|
|
143
|
-
} catch {
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
const dir = dirname(destPath);
|
|
147
|
-
if (!existsSync(dir)) {
|
|
148
|
-
mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
149
|
-
}
|
|
150
|
-
try {
|
|
151
|
-
renameSync(legacyPath, destPath);
|
|
152
|
-
} catch {
|
|
153
|
-
writeFileSync(destPath, JSON.stringify(parsed, null, 2), { mode: SECURE_FILE_MODE });
|
|
154
|
-
try {
|
|
155
|
-
unlinkSync(legacyPath);
|
|
156
|
-
} catch {
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
tightenPermissions(destPath);
|
|
160
|
-
tightenDirPermissions(dir);
|
|
161
|
-
return parsed;
|
|
162
|
-
}
|
|
163
|
-
function tightenPermissions(path) {
|
|
164
|
-
if (process.platform === "win32") return;
|
|
165
|
-
try {
|
|
166
|
-
const current = statSync(path).mode & 511;
|
|
167
|
-
if (current !== SECURE_FILE_MODE) chmodSync(path, SECURE_FILE_MODE);
|
|
168
|
-
} catch {
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
function tightenDirPermissions(path) {
|
|
172
|
-
if (process.platform === "win32") return;
|
|
173
|
-
try {
|
|
174
|
-
const current = statSync(path).mode & 511;
|
|
175
|
-
if (current !== SECURE_DIR_MODE) chmodSync(path, SECURE_DIR_MODE);
|
|
176
|
-
} catch {
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
25
|
// src/init.ts
|
|
181
26
|
import * as p from "@clack/prompts";
|
|
182
27
|
import { spawn } from "child_process";
|
|
183
28
|
import { randomUUID } from "crypto";
|
|
184
|
-
import { appendFileSync, existsSync as
|
|
185
|
-
import { join as
|
|
29
|
+
import { appendFileSync, existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
30
|
+
import { join as join3 } from "path";
|
|
186
31
|
|
|
187
32
|
// src/detect.ts
|
|
188
|
-
import { readFileSync
|
|
189
|
-
import { join
|
|
33
|
+
import { readFileSync, existsSync } from "fs";
|
|
34
|
+
import { join } from "path";
|
|
190
35
|
var FRAMEWORKS = {
|
|
191
36
|
next: {
|
|
192
37
|
id: "next",
|
|
193
38
|
label: "Next.js",
|
|
194
39
|
packageName: "@mushi-mushi/react",
|
|
195
40
|
needsWebPackage: false,
|
|
196
|
-
snippet: (
|
|
41
|
+
snippet: () => `// app/providers.tsx (or pages/_app.tsx for /pages router)
|
|
197
42
|
'use client'
|
|
198
43
|
import { MushiProvider } from '@mushi-mushi/react'
|
|
199
44
|
|
|
200
45
|
export function Providers({ children }: { children: React.ReactNode }) {
|
|
201
46
|
return (
|
|
202
47
|
<MushiProvider config={{
|
|
203
|
-
projectId:
|
|
204
|
-
apiKey:
|
|
48
|
+
projectId: process.env.NEXT_PUBLIC_MUSHI_PROJECT_ID!,
|
|
49
|
+
apiKey: process.env.NEXT_PUBLIC_MUSHI_API_KEY!,
|
|
205
50
|
}}>
|
|
206
51
|
{children}
|
|
207
52
|
</MushiProvider>
|
|
@@ -213,13 +58,13 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
|
|
213
58
|
label: "React",
|
|
214
59
|
packageName: "@mushi-mushi/react",
|
|
215
60
|
needsWebPackage: false,
|
|
216
|
-
snippet: (
|
|
61
|
+
snippet: () => `// src/main.tsx
|
|
217
62
|
import { MushiProvider } from '@mushi-mushi/react'
|
|
218
63
|
|
|
219
64
|
createRoot(document.getElementById('root')!).render(
|
|
220
65
|
<MushiProvider config={{
|
|
221
|
-
projectId:
|
|
222
|
-
apiKey:
|
|
66
|
+
projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
|
|
67
|
+
apiKey: import.meta.env.VITE_MUSHI_API_KEY,
|
|
223
68
|
}}>
|
|
224
69
|
<App />
|
|
225
70
|
</MushiProvider>
|
|
@@ -229,57 +74,58 @@ createRoot(document.getElementById('root')!).render(
|
|
|
229
74
|
id: "vue",
|
|
230
75
|
label: "Vue 3",
|
|
231
76
|
packageName: "@mushi-mushi/vue",
|
|
232
|
-
needsWebPackage:
|
|
233
|
-
snippet: (
|
|
77
|
+
needsWebPackage: false,
|
|
78
|
+
snippet: () => `// src/main.ts
|
|
234
79
|
import { MushiPlugin } from '@mushi-mushi/vue'
|
|
235
|
-
import { Mushi } from '@mushi-mushi/web'
|
|
236
80
|
|
|
237
|
-
app.use(MushiPlugin, {
|
|
238
|
-
|
|
81
|
+
app.use(MushiPlugin, {
|
|
82
|
+
projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
|
|
83
|
+
apiKey: import.meta.env.VITE_MUSHI_API_KEY,
|
|
84
|
+
})`
|
|
239
85
|
},
|
|
240
86
|
nuxt: {
|
|
241
87
|
id: "nuxt",
|
|
242
88
|
label: "Nuxt",
|
|
243
89
|
packageName: "@mushi-mushi/vue",
|
|
244
|
-
needsWebPackage:
|
|
245
|
-
snippet: (
|
|
90
|
+
needsWebPackage: false,
|
|
91
|
+
snippet: () => `// plugins/mushi.client.ts
|
|
246
92
|
import { MushiPlugin } from '@mushi-mushi/vue'
|
|
247
|
-
import { Mushi } from '@mushi-mushi/web'
|
|
248
93
|
|
|
249
94
|
export default defineNuxtPlugin((nuxtApp) => {
|
|
250
95
|
nuxtApp.vueApp.use(MushiPlugin, {
|
|
251
|
-
projectId:
|
|
252
|
-
apiKey:
|
|
96
|
+
projectId: import.meta.env.NUXT_PUBLIC_MUSHI_PROJECT_ID,
|
|
97
|
+
apiKey: import.meta.env.NUXT_PUBLIC_MUSHI_API_KEY,
|
|
253
98
|
})
|
|
254
|
-
Mushi.init({ projectId: '${projectId}', apiKey: '${apiKey}' })
|
|
255
99
|
})`
|
|
256
100
|
},
|
|
257
101
|
svelte: {
|
|
258
102
|
id: "svelte",
|
|
259
103
|
label: "Svelte",
|
|
260
104
|
packageName: "@mushi-mushi/svelte",
|
|
261
|
-
needsWebPackage:
|
|
262
|
-
snippet: (
|
|
105
|
+
needsWebPackage: false,
|
|
106
|
+
snippet: () => `// src/main.ts
|
|
263
107
|
import { initMushi } from '@mushi-mushi/svelte'
|
|
264
|
-
import { Mushi } from '@mushi-mushi/web'
|
|
265
108
|
|
|
266
|
-
initMushi({
|
|
267
|
-
|
|
109
|
+
initMushi({
|
|
110
|
+
projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
|
|
111
|
+
apiKey: import.meta.env.VITE_MUSHI_API_KEY,
|
|
112
|
+
})`
|
|
268
113
|
},
|
|
269
114
|
sveltekit: {
|
|
270
115
|
id: "sveltekit",
|
|
271
116
|
label: "SvelteKit",
|
|
272
117
|
packageName: "@mushi-mushi/svelte",
|
|
273
|
-
needsWebPackage:
|
|
274
|
-
snippet: (
|
|
118
|
+
needsWebPackage: false,
|
|
119
|
+
snippet: () => `// src/routes/+layout.svelte
|
|
275
120
|
<script>
|
|
276
121
|
import { onMount } from 'svelte'
|
|
277
|
-
import { initMushi } from '@mushi-mushi/svelte'
|
|
278
122
|
|
|
279
123
|
onMount(async () => {
|
|
280
|
-
const {
|
|
281
|
-
initMushi({
|
|
282
|
-
|
|
124
|
+
const { initMushi } = await import('@mushi-mushi/svelte')
|
|
125
|
+
initMushi({
|
|
126
|
+
projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
|
|
127
|
+
apiKey: import.meta.env.VITE_MUSHI_API_KEY,
|
|
128
|
+
})
|
|
283
129
|
})
|
|
284
130
|
</script>`
|
|
285
131
|
},
|
|
@@ -287,29 +133,33 @@ Mushi.init({ projectId: '${projectId}', apiKey: '${apiKey}' })`
|
|
|
287
133
|
id: "angular",
|
|
288
134
|
label: "Angular",
|
|
289
135
|
packageName: "@mushi-mushi/angular",
|
|
290
|
-
needsWebPackage:
|
|
291
|
-
snippet: (
|
|
136
|
+
needsWebPackage: false,
|
|
137
|
+
snippet: () => `// src/main.ts
|
|
292
138
|
import { provideMushi } from '@mushi-mushi/angular'
|
|
293
|
-
import { Mushi } from '@mushi-mushi/web'
|
|
294
139
|
|
|
295
140
|
bootstrapApplication(AppComponent, {
|
|
296
141
|
providers: [
|
|
297
|
-
provideMushi({
|
|
142
|
+
provideMushi({
|
|
143
|
+
projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
|
|
144
|
+
apiKey: import.meta.env.VITE_MUSHI_API_KEY,
|
|
145
|
+
}),
|
|
298
146
|
],
|
|
299
|
-
})
|
|
300
|
-
Mushi.init({ projectId: '${projectId}', apiKey: '${apiKey}' })`
|
|
147
|
+
})`
|
|
301
148
|
},
|
|
302
149
|
expo: {
|
|
303
150
|
id: "expo",
|
|
304
151
|
label: "Expo",
|
|
305
152
|
packageName: "@mushi-mushi/react-native",
|
|
306
153
|
needsWebPackage: false,
|
|
307
|
-
snippet: (
|
|
154
|
+
snippet: () => `// App.tsx
|
|
308
155
|
import { MushiProvider } from '@mushi-mushi/react-native'
|
|
309
156
|
|
|
310
157
|
export default function App() {
|
|
311
158
|
return (
|
|
312
|
-
<MushiProvider
|
|
159
|
+
<MushiProvider
|
|
160
|
+
projectId={process.env.EXPO_PUBLIC_MUSHI_PROJECT_ID!}
|
|
161
|
+
apiKey={process.env.EXPO_PUBLIC_MUSHI_API_KEY!}
|
|
162
|
+
>
|
|
313
163
|
<YourApp />
|
|
314
164
|
</MushiProvider>
|
|
315
165
|
)
|
|
@@ -320,12 +170,15 @@ export default function App() {
|
|
|
320
170
|
label: "React Native",
|
|
321
171
|
packageName: "@mushi-mushi/react-native",
|
|
322
172
|
needsWebPackage: false,
|
|
323
|
-
snippet: (
|
|
173
|
+
snippet: () => `// App.tsx
|
|
324
174
|
import { MushiProvider } from '@mushi-mushi/react-native'
|
|
325
175
|
|
|
326
176
|
export default function App() {
|
|
327
177
|
return (
|
|
328
|
-
<MushiProvider
|
|
178
|
+
<MushiProvider
|
|
179
|
+
projectId={process.env.MUSHI_PROJECT_ID!}
|
|
180
|
+
apiKey={process.env.MUSHI_API_KEY!}
|
|
181
|
+
>
|
|
329
182
|
<YourApp />
|
|
330
183
|
</MushiProvider>
|
|
331
184
|
)
|
|
@@ -342,24 +195,27 @@ export default function App() {
|
|
|
342
195
|
* `TypeError: Mushi.init is not a function`. The accompanying admin
|
|
343
196
|
* console snippet (apps/admin/src/lib/sdkSnippets.ts) emits the same
|
|
344
197
|
* `Mushi.configure(...)` shape; both are pinned by tests. */
|
|
345
|
-
snippet: (
|
|
198
|
+
snippet: () => `// src/main.ts \u2014 after install, run \`npx cap sync\`
|
|
346
199
|
import { Mushi } from '@mushi-mushi/capacitor'
|
|
347
200
|
|
|
348
|
-
await Mushi.configure({
|
|
201
|
+
await Mushi.configure({
|
|
202
|
+
projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
|
|
203
|
+
apiKey: import.meta.env.VITE_MUSHI_API_KEY,
|
|
204
|
+
})`
|
|
349
205
|
},
|
|
350
206
|
express: {
|
|
351
207
|
id: "express",
|
|
352
208
|
label: "Express",
|
|
353
209
|
packageName: "@mushi-mushi/node",
|
|
354
210
|
needsWebPackage: false,
|
|
355
|
-
snippet: (
|
|
211
|
+
snippet: () => `// src/instrument.ts \u2014 load with: node --import ./dist/instrument.js
|
|
356
212
|
import { MushiNodeClient, attachUnhandledHook } from '@mushi-mushi/node'
|
|
357
213
|
import { mushiExpressErrorHandler } from '@mushi-mushi/node/express'
|
|
358
214
|
import type { Express } from 'express'
|
|
359
215
|
|
|
360
216
|
export const mushi = new MushiNodeClient({
|
|
361
|
-
projectId:
|
|
362
|
-
apiKey:
|
|
217
|
+
projectId: process.env.MUSHI_PROJECT_ID!,
|
|
218
|
+
apiKey: process.env.MUSHI_API_KEY!,
|
|
363
219
|
environment: process.env.NODE_ENV ?? 'production',
|
|
364
220
|
})
|
|
365
221
|
attachUnhandledHook({ client: mushi })
|
|
@@ -373,14 +229,14 @@ export function attachMushi(app: Express) {
|
|
|
373
229
|
label: "Fastify",
|
|
374
230
|
packageName: "@mushi-mushi/node",
|
|
375
231
|
needsWebPackage: false,
|
|
376
|
-
snippet: (
|
|
232
|
+
snippet: () => `// src/instrument.ts \u2014 load with: node --import ./dist/instrument.js
|
|
377
233
|
import { MushiNodeClient, attachUnhandledHook } from '@mushi-mushi/node'
|
|
378
234
|
import { mushiFastifyPlugin } from '@mushi-mushi/node/fastify'
|
|
379
235
|
import Fastify from 'fastify'
|
|
380
236
|
|
|
381
237
|
export const mushi = new MushiNodeClient({
|
|
382
|
-
projectId:
|
|
383
|
-
apiKey:
|
|
238
|
+
projectId: process.env.MUSHI_PROJECT_ID!,
|
|
239
|
+
apiKey: process.env.MUSHI_API_KEY!,
|
|
384
240
|
environment: process.env.NODE_ENV ?? 'production',
|
|
385
241
|
})
|
|
386
242
|
attachUnhandledHook({ client: mushi })
|
|
@@ -393,14 +249,14 @@ mushiFastifyPlugin(app, { client: mushi })`
|
|
|
393
249
|
label: "Hono",
|
|
394
250
|
packageName: "@mushi-mushi/node",
|
|
395
251
|
needsWebPackage: false,
|
|
396
|
-
snippet: (
|
|
252
|
+
snippet: () => `// src/instrument.ts \u2014 load with: node --import ./dist/instrument.js
|
|
397
253
|
import { MushiNodeClient, attachUnhandledHook } from '@mushi-mushi/node'
|
|
398
254
|
import { mushiHonoErrorHandler } from '@mushi-mushi/node/hono'
|
|
399
255
|
import { Hono } from 'hono'
|
|
400
256
|
|
|
401
257
|
export const mushi = new MushiNodeClient({
|
|
402
|
-
projectId:
|
|
403
|
-
apiKey:
|
|
258
|
+
projectId: process.env.MUSHI_PROJECT_ID!,
|
|
259
|
+
apiKey: process.env.MUSHI_API_KEY!,
|
|
404
260
|
environment: process.env.NODE_ENV ?? 'production',
|
|
405
261
|
})
|
|
406
262
|
attachUnhandledHook({ client: mushi })
|
|
@@ -417,17 +273,20 @@ app.onError(
|
|
|
417
273
|
label: "Vanilla JS / unknown",
|
|
418
274
|
packageName: "@mushi-mushi/web",
|
|
419
275
|
needsWebPackage: false,
|
|
420
|
-
snippet: (
|
|
276
|
+
snippet: () => `// Anywhere in your client bundle
|
|
421
277
|
import { Mushi } from '@mushi-mushi/web'
|
|
422
278
|
|
|
423
|
-
Mushi.init({
|
|
279
|
+
Mushi.init({
|
|
280
|
+
projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
|
|
281
|
+
apiKey: import.meta.env.VITE_MUSHI_API_KEY,
|
|
282
|
+
})`
|
|
424
283
|
}
|
|
425
284
|
};
|
|
426
285
|
function readPackageJson(cwd) {
|
|
427
|
-
const path =
|
|
428
|
-
if (!
|
|
286
|
+
const path = join(cwd, "package.json");
|
|
287
|
+
if (!existsSync(path)) return null;
|
|
429
288
|
try {
|
|
430
|
-
return JSON.parse(
|
|
289
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
431
290
|
} catch {
|
|
432
291
|
return null;
|
|
433
292
|
}
|
|
@@ -448,21 +307,21 @@ function detectFramework(cwd, pkg) {
|
|
|
448
307
|
if (deps.has("express")) return FRAMEWORKS.express;
|
|
449
308
|
if (deps.has("fastify")) return FRAMEWORKS.fastify;
|
|
450
309
|
if (deps.has("hono") || deps.has("@hono/hono")) return FRAMEWORKS.hono;
|
|
451
|
-
if (
|
|
310
|
+
if (existsSync(join(cwd, "next.config.js")) || existsSync(join(cwd, "next.config.ts"))) {
|
|
452
311
|
return FRAMEWORKS.next;
|
|
453
312
|
}
|
|
454
|
-
if (
|
|
313
|
+
if (existsSync(join(cwd, "nuxt.config.ts")) || existsSync(join(cwd, "nuxt.config.js"))) {
|
|
455
314
|
return FRAMEWORKS.nuxt;
|
|
456
315
|
}
|
|
457
|
-
if (
|
|
458
|
-
if (
|
|
316
|
+
if (existsSync(join(cwd, "svelte.config.js"))) return FRAMEWORKS.svelte;
|
|
317
|
+
if (existsSync(join(cwd, "angular.json"))) return FRAMEWORKS.angular;
|
|
459
318
|
return FRAMEWORKS.vanilla;
|
|
460
319
|
}
|
|
461
320
|
function detectPackageManager(cwd) {
|
|
462
|
-
if (
|
|
463
|
-
if (
|
|
464
|
-
if (
|
|
465
|
-
if (
|
|
321
|
+
if (existsSync(join(cwd, "bun.lockb")) || existsSync(join(cwd, "bun.lock"))) return "bun";
|
|
322
|
+
if (existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
323
|
+
if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
|
|
324
|
+
if (existsSync(join(cwd, "package-lock.json"))) return "npm";
|
|
466
325
|
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
467
326
|
if (userAgent.startsWith("bun")) return "bun";
|
|
468
327
|
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
@@ -481,6 +340,18 @@ function envVarsToWrite(apiKey, projectId, framework) {
|
|
|
481
340
|
`MUSHI_API_KEY=${apiKey}`
|
|
482
341
|
].join("\n");
|
|
483
342
|
}
|
|
343
|
+
if (framework.id === "react-native") {
|
|
344
|
+
return [
|
|
345
|
+
`MUSHI_PROJECT_ID=${projectId}`,
|
|
346
|
+
`MUSHI_API_KEY=${apiKey}`
|
|
347
|
+
].join("\n");
|
|
348
|
+
}
|
|
349
|
+
if (framework.id === "expo") {
|
|
350
|
+
return [
|
|
351
|
+
`EXPO_PUBLIC_MUSHI_PROJECT_ID=${projectId}`,
|
|
352
|
+
`EXPO_PUBLIC_MUSHI_API_KEY=${apiKey}`
|
|
353
|
+
].join("\n");
|
|
354
|
+
}
|
|
484
355
|
const prefix = framework.id === "next" ? "NEXT_PUBLIC_" : framework.id === "nuxt" ? "NUXT_PUBLIC_" : "VITE_";
|
|
485
356
|
return [
|
|
486
357
|
`${prefix}MUSHI_PROJECT_ID=${projectId}`,
|
|
@@ -496,41 +367,6 @@ function collectDeps(pkg) {
|
|
|
496
367
|
]);
|
|
497
368
|
}
|
|
498
369
|
|
|
499
|
-
// src/endpoint.ts
|
|
500
|
-
var TEST_REPORT_TIMEOUT_MS = 1e4;
|
|
501
|
-
var TEST_REPORT_FETCH_TIMEOUT_MS = TEST_REPORT_TIMEOUT_MS;
|
|
502
|
-
var CLOUD_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
|
|
503
|
-
function resolveCloudEndpoint(explicit) {
|
|
504
|
-
if (explicit?.trim()) return explicit.trim();
|
|
505
|
-
const fromEnv = process.env.MUSHI_API_ENDPOINT?.trim();
|
|
506
|
-
if (fromEnv) return fromEnv;
|
|
507
|
-
return CLOUD_API_ENDPOINT;
|
|
508
|
-
}
|
|
509
|
-
function assertEndpoint(url) {
|
|
510
|
-
let parsed;
|
|
511
|
-
try {
|
|
512
|
-
parsed = new URL(url);
|
|
513
|
-
} catch {
|
|
514
|
-
throw new Error(`Invalid endpoint URL: ${url}`);
|
|
515
|
-
}
|
|
516
|
-
const host = parsed.hostname;
|
|
517
|
-
const isLocal = host === "localhost" || host === "127.0.0.1" || host === "::1" || host.endsWith(".local");
|
|
518
|
-
if (parsed.protocol !== "https:" && !isLocal) {
|
|
519
|
-
throw new Error(`Endpoint must use https:// (got ${parsed.protocol}//${host}).`);
|
|
520
|
-
}
|
|
521
|
-
return normalizeEndpoint(parsed.origin + parsed.pathname);
|
|
522
|
-
}
|
|
523
|
-
function normalizeEndpoint(url) {
|
|
524
|
-
if (!url) {
|
|
525
|
-
throw new Error(
|
|
526
|
-
"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"
|
|
527
|
-
);
|
|
528
|
-
}
|
|
529
|
-
let end = url.length;
|
|
530
|
-
while (end > 0 && url.charCodeAt(end - 1) === 47) end--;
|
|
531
|
-
return url.slice(0, end);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
370
|
// src/freshness.ts
|
|
535
371
|
var REGISTRY = "https://registry.npmjs.org";
|
|
536
372
|
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
@@ -591,8 +427,8 @@ function parse(version) {
|
|
|
591
427
|
}
|
|
592
428
|
|
|
593
429
|
// src/monorepo.ts
|
|
594
|
-
import { existsSync as
|
|
595
|
-
import { dirname
|
|
430
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
|
|
431
|
+
import { dirname, join as join2, resolve } from "path";
|
|
596
432
|
var WORKSPACE_GLOB_CANDIDATES = ["apps/*", "packages/*", "examples/*"];
|
|
597
433
|
var FRAMEWORK_DEPS = {
|
|
598
434
|
next: "Next.js",
|
|
@@ -609,9 +445,9 @@ var FRAMEWORK_DEPS = {
|
|
|
609
445
|
function detectWorkspaceHint(cwd) {
|
|
610
446
|
const root = findWorkspaceRoot(cwd);
|
|
611
447
|
if (!root) return null;
|
|
612
|
-
const rootPkg = readPackageJsonSafely(
|
|
448
|
+
const rootPkg = readPackageJsonSafely(join2(root, "package.json"));
|
|
613
449
|
if (rootPkg && getFrameworkFromPkg(rootPkg)) return null;
|
|
614
|
-
const source =
|
|
450
|
+
const source = existsSync2(join2(root, "pnpm-workspace.yaml")) ? "pnpm-workspace" : root === cwd ? "package-json" : "parent";
|
|
615
451
|
const apps = collectAppsFromGlobs(root);
|
|
616
452
|
if (apps.length === 0) return null;
|
|
617
453
|
return { root, apps, source };
|
|
@@ -620,15 +456,15 @@ function findWorkspaceRoot(start) {
|
|
|
620
456
|
let dir = resolve(start);
|
|
621
457
|
for (let i = 0; i < 8; i++) {
|
|
622
458
|
if (isWorkspaceRoot(dir)) return dir;
|
|
623
|
-
const parent =
|
|
459
|
+
const parent = dirname(dir);
|
|
624
460
|
if (parent === dir) break;
|
|
625
461
|
dir = parent;
|
|
626
462
|
}
|
|
627
463
|
return null;
|
|
628
464
|
}
|
|
629
465
|
function isWorkspaceRoot(dir) {
|
|
630
|
-
if (
|
|
631
|
-
const pkg = readPackageJsonSafely(
|
|
466
|
+
if (existsSync2(join2(dir, "pnpm-workspace.yaml"))) return true;
|
|
467
|
+
const pkg = readPackageJsonSafely(join2(dir, "package.json"));
|
|
632
468
|
if (!pkg) return false;
|
|
633
469
|
return Boolean(pkg.workspaces);
|
|
634
470
|
}
|
|
@@ -636,8 +472,8 @@ function collectAppsFromGlobs(root) {
|
|
|
636
472
|
const results = [];
|
|
637
473
|
for (const glob of WORKSPACE_GLOB_CANDIDATES) {
|
|
638
474
|
const prefix = glob.replace("/*", "");
|
|
639
|
-
const parentDir =
|
|
640
|
-
if (!
|
|
475
|
+
const parentDir = join2(root, prefix);
|
|
476
|
+
if (!existsSync2(parentDir)) continue;
|
|
641
477
|
let entries;
|
|
642
478
|
try {
|
|
643
479
|
entries = readdirSync(parentDir);
|
|
@@ -645,7 +481,7 @@ function collectAppsFromGlobs(root) {
|
|
|
645
481
|
continue;
|
|
646
482
|
}
|
|
647
483
|
for (const entry of entries) {
|
|
648
|
-
const pkgPath =
|
|
484
|
+
const pkgPath = join2(parentDir, entry, "package.json");
|
|
649
485
|
if (!isFileSafe(pkgPath)) continue;
|
|
650
486
|
const pkg = readPackageJsonSafely(pkgPath);
|
|
651
487
|
if (!pkg) continue;
|
|
@@ -663,14 +499,14 @@ function collectAppsFromGlobs(root) {
|
|
|
663
499
|
function readPackageJsonSafely(path) {
|
|
664
500
|
if (!isFileSafe(path)) return null;
|
|
665
501
|
try {
|
|
666
|
-
return JSON.parse(
|
|
502
|
+
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
667
503
|
} catch {
|
|
668
504
|
return null;
|
|
669
505
|
}
|
|
670
506
|
}
|
|
671
507
|
function isFileSafe(path) {
|
|
672
508
|
try {
|
|
673
|
-
return
|
|
509
|
+
return existsSync2(path) && statSync(path).isFile();
|
|
674
510
|
} catch {
|
|
675
511
|
return false;
|
|
676
512
|
}
|
|
@@ -690,7 +526,7 @@ function getFrameworkFromPkg(pkg) {
|
|
|
690
526
|
// src/init.ts
|
|
691
527
|
var ENV_FILES = [".env.local", ".env"];
|
|
692
528
|
var PROJECT_ID_PATTERN = /^(?: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;
|
|
693
|
-
var API_KEY_PATTERN = /^mushi_[A-Za-z0-9_-]{10,}$/;
|
|
529
|
+
var API_KEY_PATTERN = /^(mushi_|mush_pk_)[A-Za-z0-9_-]{10,}$/;
|
|
694
530
|
async function runInit(options = {}) {
|
|
695
531
|
const cwd = options.cwd ?? process.cwd();
|
|
696
532
|
ensureInteractiveOrBailOut(options);
|
|
@@ -850,10 +686,10 @@ function runCommand(pm, packages, cwd) {
|
|
|
850
686
|
});
|
|
851
687
|
}
|
|
852
688
|
function writeEnvFile(cwd, apiKey, projectId, framework) {
|
|
853
|
-
const target = ENV_FILES.find((f) =>
|
|
854
|
-
const targetPath =
|
|
689
|
+
const target = ENV_FILES.find((f) => existsSync3(join3(cwd, f))) ?? ENV_FILES[0];
|
|
690
|
+
const targetPath = join3(cwd, target);
|
|
855
691
|
const newVars = envVarsToWrite(apiKey, projectId, framework);
|
|
856
|
-
const existing =
|
|
692
|
+
const existing = existsSync3(targetPath) ? readFileSync3(targetPath, "utf-8") : "";
|
|
857
693
|
if (existing.includes("MUSHI_PROJECT_ID")) {
|
|
858
694
|
p.log.warn(`Existing MUSHI_* vars found in ${target} \u2014 leaving them untouched.`);
|
|
859
695
|
return;
|
|
@@ -888,12 +724,12 @@ function escapeRegexChar(ch) {
|
|
|
888
724
|
return /[-/\\^$+?.()|[\]{}]/.test(ch) ? `\\${ch}` : ch;
|
|
889
725
|
}
|
|
890
726
|
function warnIfMissingFromGitignore(cwd, envFile) {
|
|
891
|
-
const gitignorePath =
|
|
892
|
-
if (!
|
|
727
|
+
const gitignorePath = join3(cwd, ".gitignore");
|
|
728
|
+
if (!existsSync3(gitignorePath)) {
|
|
893
729
|
p.log.warn(`No .gitignore found \u2014 make sure ${envFile} is not committed.`);
|
|
894
730
|
return;
|
|
895
731
|
}
|
|
896
|
-
const content =
|
|
732
|
+
const content = readFileSync3(gitignorePath, "utf-8");
|
|
897
733
|
if (!isEnvFileCoveredByGitignore(content, envFile)) {
|
|
898
734
|
p.log.warn(`${envFile} is not in .gitignore \u2014 add it before committing.`);
|
|
899
735
|
}
|
|
@@ -902,8 +738,8 @@ function persistCliConfig(apiKey, projectId, endpoint) {
|
|
|
902
738
|
const existing = loadConfig();
|
|
903
739
|
saveConfig({ ...existing, apiKey, projectId, endpoint });
|
|
904
740
|
}
|
|
905
|
-
function printNextSteps(framework,
|
|
906
|
-
p.note(framework.snippet(
|
|
741
|
+
function printNextSteps(framework, _apiKey, _projectId, enableRewards = false) {
|
|
742
|
+
p.note(framework.snippet(), "Add this to your app:");
|
|
907
743
|
if (enableRewards) {
|
|
908
744
|
const badgeSnippet = framework.id === "react" ? `// Add to your user menu or profile UI:
|
|
909
745
|
import { MushiRewardsBadge } from '@mushi-mushi/react';
|
|
@@ -1169,130 +1005,6 @@ function runMigrate(opts = {}) {
|
|
|
1169
1005
|
return { matches };
|
|
1170
1006
|
}
|
|
1171
1007
|
|
|
1172
|
-
// src/cli-shared.ts
|
|
1173
|
-
var API_TIMEOUT_MS = 15e3;
|
|
1174
|
-
async function apiCall(path, config, options = {}) {
|
|
1175
|
-
const endpoint = config.endpoint;
|
|
1176
|
-
if (!endpoint) {
|
|
1177
|
-
return {
|
|
1178
|
-
ok: false,
|
|
1179
|
-
error: {
|
|
1180
|
-
code: "NO_ENDPOINT",
|
|
1181
|
-
message: "No API endpoint configured.\n Run: mushi login --api-key <key> --endpoint <url> --project-id <id>\n Or: export MUSHI_API_ENDPOINT=https://<project-ref>.supabase.co/functions/v1/api"
|
|
1182
|
-
}
|
|
1183
|
-
};
|
|
1184
|
-
}
|
|
1185
|
-
const controller = new AbortController();
|
|
1186
|
-
const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
|
|
1187
|
-
const signals = [controller.signal, getAbortSignal()];
|
|
1188
|
-
const compositeSignal = AbortSignal.any ? AbortSignal.any(signals) : controller.signal;
|
|
1189
|
-
try {
|
|
1190
|
-
const res = await fetch(`${endpoint}${path}`, {
|
|
1191
|
-
...options,
|
|
1192
|
-
signal: compositeSignal,
|
|
1193
|
-
headers: {
|
|
1194
|
-
"Content-Type": "application/json",
|
|
1195
|
-
// `apiKeyAuth` reads X-Mushi-Api-Key; the Authorization header is a
|
|
1196
|
-
// fallback accepted by some older middleware. Both are sent.
|
|
1197
|
-
"Authorization": `Bearer ${config.apiKey ?? ""}`,
|
|
1198
|
-
"X-Mushi-Api-Key": config.apiKey ?? "",
|
|
1199
|
-
"X-Mushi-Project": config.projectId ?? "",
|
|
1200
|
-
"X-Mushi-Cli-Version": MUSHI_CLI_VERSION,
|
|
1201
|
-
...options.headers
|
|
1202
|
-
}
|
|
1203
|
-
});
|
|
1204
|
-
clearTimeout(timer);
|
|
1205
|
-
let body;
|
|
1206
|
-
const contentType = res.headers.get("content-type") ?? "";
|
|
1207
|
-
if (contentType.includes("application/json")) {
|
|
1208
|
-
try {
|
|
1209
|
-
body = await res.json();
|
|
1210
|
-
} catch {
|
|
1211
|
-
body = null;
|
|
1212
|
-
}
|
|
1213
|
-
} else {
|
|
1214
|
-
const text2 = await res.text();
|
|
1215
|
-
try {
|
|
1216
|
-
body = JSON.parse(text2);
|
|
1217
|
-
} catch {
|
|
1218
|
-
body = {
|
|
1219
|
-
ok: false,
|
|
1220
|
-
error: {
|
|
1221
|
-
code: `HTTP_${res.status}`,
|
|
1222
|
-
message: text2.trim().slice(0, 300) || `HTTP ${res.status}`
|
|
1223
|
-
}
|
|
1224
|
-
};
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
if (!res.ok && typeof body === "object" && body !== null && !("ok" in body)) {
|
|
1228
|
-
const b = body;
|
|
1229
|
-
return {
|
|
1230
|
-
ok: false,
|
|
1231
|
-
httpStatus: res.status,
|
|
1232
|
-
error: {
|
|
1233
|
-
code: b["code"] ?? `HTTP_${res.status}`,
|
|
1234
|
-
message: b["message"] ?? `Request failed (${res.status})`
|
|
1235
|
-
}
|
|
1236
|
-
};
|
|
1237
|
-
}
|
|
1238
|
-
return body;
|
|
1239
|
-
} catch (err) {
|
|
1240
|
-
clearTimeout(timer);
|
|
1241
|
-
if (err instanceof Error && err.name === "AbortError") {
|
|
1242
|
-
return {
|
|
1243
|
-
ok: false,
|
|
1244
|
-
error: {
|
|
1245
|
-
code: "TIMEOUT",
|
|
1246
|
-
message: `Request timed out after ${API_TIMEOUT_MS / 1e3}s. Check your network or endpoint.`
|
|
1247
|
-
}
|
|
1248
|
-
};
|
|
1249
|
-
}
|
|
1250
|
-
return {
|
|
1251
|
-
ok: false,
|
|
1252
|
-
error: {
|
|
1253
|
-
code: "NETWORK_ERROR",
|
|
1254
|
-
message: err instanceof Error ? err.message : String(err)
|
|
1255
|
-
}
|
|
1256
|
-
};
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
function die(result, exitCode = 1) {
|
|
1260
|
-
const { code, message } = result.error;
|
|
1261
|
-
const status = result.httpStatus ? ` [${result.httpStatus}]` : "";
|
|
1262
|
-
process.stderr.write(`error${status}: ${code} \u2014 ${message}
|
|
1263
|
-
`);
|
|
1264
|
-
process.exit(exitCode);
|
|
1265
|
-
}
|
|
1266
|
-
function requireConfig(opts = {}) {
|
|
1267
|
-
const config = loadConfig();
|
|
1268
|
-
if (!config.apiKey) {
|
|
1269
|
-
process.stderr.write(
|
|
1270
|
-
"error: API key not configured.\n Run: mushi login --api-key <key> --endpoint <url>\n Or: export MUSHI_API_KEY=<key>\n"
|
|
1271
|
-
);
|
|
1272
|
-
process.exit(2);
|
|
1273
|
-
}
|
|
1274
|
-
if (!config.endpoint) {
|
|
1275
|
-
process.stderr.write(
|
|
1276
|
-
"error: API endpoint not configured.\n Run: mushi login --endpoint https://<ref>.supabase.co/functions/v1/api\n Or: export MUSHI_API_ENDPOINT=<url>\n"
|
|
1277
|
-
);
|
|
1278
|
-
process.exit(2);
|
|
1279
|
-
}
|
|
1280
|
-
if (opts.needsProject && !config.projectId) {
|
|
1281
|
-
process.stderr.write(
|
|
1282
|
-
"error: Project ID not configured.\n Run: mushi login --project-id <uuid>\n Or: export MUSHI_PROJECT_ID=<uuid>\n Find your project ID: https://kensaur.us/mushi-mushi/projects\n"
|
|
1283
|
-
);
|
|
1284
|
-
process.exit(2);
|
|
1285
|
-
}
|
|
1286
|
-
return config;
|
|
1287
|
-
}
|
|
1288
|
-
function fmtDate(iso) {
|
|
1289
|
-
if (!iso) return "\u2014";
|
|
1290
|
-
return new Date(iso).toLocaleString(void 0, { dateStyle: "short", timeStyle: "short" });
|
|
1291
|
-
}
|
|
1292
|
-
function pad(s, width) {
|
|
1293
|
-
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
1008
|
// src/commands/account.ts
|
|
1297
1009
|
function registerAccountCommands(program2) {
|
|
1298
1010
|
program2.command("init").description("Set up the Mushi Mushi SDK in this project (auto-detects framework)").option("--project-id <id>", "Skip the prompt \u2014 pass UUID from the Projects page").option("--api-key <key>", "Skip the prompt \u2014 pass the API key (CI only)").option("--framework <id>", "Force a framework (next, react, vue, nuxt, svelte, sveltekit, angular, expo, react-native, capacitor, vanilla)").option("--skip-install", "Print the install command instead of running it").option("-y, --yes", "Accept detected framework without prompting").option("--cwd <path>", "Run the wizard in a different directory").option("--endpoint <url>", "Override the Mushi API endpoint (self-hosted)").option("--skip-test-report", 'Skip the end-of-wizard "send a test report" prompt').action(async (opts) => {
|
|
@@ -1311,17 +1023,78 @@ function registerAccountCommands(program2) {
|
|
|
1311
1023
|
const { matches } = runMigrate({ cwd: opts.cwd, json: opts.json });
|
|
1312
1024
|
if (matches.length === 0) process.exit(1);
|
|
1313
1025
|
});
|
|
1314
|
-
program2.command("login").description("
|
|
1026
|
+
program2.command("login").description("Authenticate the CLI \u2014 opens browser to get your API key, or pass --api-key to skip").option("--api-key <key>", "Mushi API key (mushi_...) \u2014 skip the browser flow").option("--endpoint <url>", "Supabase edge function URL (defaults to Mushi Cloud)").option("--project-id <id>", "Project UUID \u2014 skip the whoami project-list prompt").option("--no-browser", "Skip opening the browser (print sign-in URL instead)").addHelpText("after", `
|
|
1315
1027
|
Examples:
|
|
1316
|
-
mushi login
|
|
1317
|
-
mushi login --api-key mushi_xxx
|
|
1028
|
+
mushi login # browser-guided: open console \u2192 paste key
|
|
1029
|
+
mushi login --api-key mushi_xxx # non-interactive: save key directly
|
|
1030
|
+
mushi login --api-key mushi_xxx --project-id <uuid>`).action(async (opts) => {
|
|
1031
|
+
const { CLOUD_API_ENDPOINT, resolveCloudEndpoint: resolveCloudEndpoint2 } = await import("./endpoint-DGS73AVM.js");
|
|
1032
|
+
const { apiCall: apiCall2 } = await import("./cli-shared-XH3XYIS7.js");
|
|
1033
|
+
const endpoint = opts.endpoint ? resolveCloudEndpoint2(opts.endpoint) : CLOUD_API_ENDPOINT;
|
|
1034
|
+
const consoleUrl = "https://kensaur.us/mushi-mushi/projects";
|
|
1035
|
+
let apiKey = opts.apiKey;
|
|
1036
|
+
let projectId = opts.projectId;
|
|
1037
|
+
if (!apiKey) {
|
|
1038
|
+
const signInUrl = "https://kensaur.us/mushi-mushi/sign-in";
|
|
1039
|
+
console.log("");
|
|
1040
|
+
console.log(" Mushi login \u2014 browser-guided");
|
|
1041
|
+
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1042
|
+
if (opts.browser !== false) {
|
|
1043
|
+
console.log(" Opening the Mushi console in your browser\u2026");
|
|
1044
|
+
try {
|
|
1045
|
+
const { exec } = await import("child_process");
|
|
1046
|
+
const openCmd = process.platform === "win32" ? `start "" "${signInUrl}"` : process.platform === "darwin" ? `open "${signInUrl}"` : `xdg-open "${signInUrl}"`;
|
|
1047
|
+
exec(openCmd);
|
|
1048
|
+
} catch {
|
|
1049
|
+
}
|
|
1050
|
+
} else {
|
|
1051
|
+
console.log(` Sign in at: ${signInUrl}`);
|
|
1052
|
+
}
|
|
1053
|
+
console.log("");
|
|
1054
|
+
console.log(" After signing in, go to Projects \u2192 API Keys \u2192 New key");
|
|
1055
|
+
console.log(" then paste the key below.");
|
|
1056
|
+
console.log("");
|
|
1057
|
+
const { createInterface } = await import("readline");
|
|
1058
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1059
|
+
const ask = (q) => new Promise((resolve4) => rl.question(q, (a) => resolve4(a.trim())));
|
|
1060
|
+
apiKey = await ask(" API key (mushi_...): ");
|
|
1061
|
+
if (!projectId) {
|
|
1062
|
+
projectId = await ask(` Project ID (uuid, or leave blank to list projects): `);
|
|
1063
|
+
if (!projectId) projectId = void 0;
|
|
1064
|
+
}
|
|
1065
|
+
rl.close();
|
|
1066
|
+
if (!apiKey) {
|
|
1067
|
+
process.stderr.write("\nerror: API key is required.\n");
|
|
1068
|
+
process.exit(2);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1318
1071
|
const config = loadConfig();
|
|
1319
|
-
config.apiKey =
|
|
1320
|
-
|
|
1321
|
-
if (
|
|
1072
|
+
config.apiKey = apiKey;
|
|
1073
|
+
config.endpoint = endpoint;
|
|
1074
|
+
if (projectId) config.projectId = projectId;
|
|
1322
1075
|
saveConfig(config);
|
|
1323
|
-
console.log("
|
|
1324
|
-
console.log("
|
|
1076
|
+
console.log("");
|
|
1077
|
+
console.log(" \u2713 Credentials saved to ~/.config/mushi/config.json");
|
|
1078
|
+
const verifyResult = await apiCall2(
|
|
1079
|
+
"/v1/sync/whoami",
|
|
1080
|
+
{ apiKey, endpoint, projectId }
|
|
1081
|
+
);
|
|
1082
|
+
if (!verifyResult.ok) {
|
|
1083
|
+
console.warn(` \u26A0 Key saved, but verification failed: ${verifyResult.error?.message ?? "unknown error"}`);
|
|
1084
|
+
console.warn(` Check your key at: ${consoleUrl}`);
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
const d = verifyResult.data;
|
|
1088
|
+
if (!projectId && d.project_id) {
|
|
1089
|
+
config.projectId = d.project_id;
|
|
1090
|
+
saveConfig(config);
|
|
1091
|
+
console.log(` \u2713 Project: ${d.project_name} (${d.project_id})`);
|
|
1092
|
+
} else if (d.project_name) {
|
|
1093
|
+
console.log(` \u2713 Project: ${d.project_name} (${d.project_id})`);
|
|
1094
|
+
}
|
|
1095
|
+
console.log(` \u2713 Endpoint: ${endpoint}`);
|
|
1096
|
+
console.log("");
|
|
1097
|
+
console.log(" Run 'mushi whoami' to verify \xB7 'mushi init' to set up the SDK");
|
|
1325
1098
|
});
|
|
1326
1099
|
program2.command("whoami").description("Verify API key and display project info").option("--json", "Machine-readable JSON output").addHelpText("after", `
|
|
1327
1100
|
Verifies that MUSHI_API_KEY is valid and shows which project it belongs to.
|
|
@@ -1390,7 +1163,7 @@ Useful after 'mushi login' to confirm credentials are correct.`).action(async (o
|
|
|
1390
1163
|
console.log(`Fixes: ${d.fixes_count} total \xB7 ${d.fixes_merged} merged`);
|
|
1391
1164
|
console.log(`Lessons: ${d.lessons_count} active rules`);
|
|
1392
1165
|
});
|
|
1393
|
-
program2.command("config").description("View or update CLI config (stored in ~/.
|
|
1166
|
+
program2.command("config").description("View or update CLI config (stored in ~/.config/mushi/config.json)").argument("[key]", "Config key to set: apiKey | endpoint | projectId").argument("[value]", "New value").addHelpText("after", `
|
|
1394
1167
|
Keys:
|
|
1395
1168
|
apiKey \u2014 Mushi API key (mushi_...)
|
|
1396
1169
|
endpoint \u2014 Supabase edge function URL
|
|
@@ -1844,7 +1617,7 @@ AI code review context.
|
|
|
1844
1617
|
Typical CI usage:
|
|
1845
1618
|
MUSHI_API_KEY=$KEY MUSHI_PROJECT_ID=$PID MUSHI_API_ENDPOINT=$URL \\
|
|
1846
1619
|
npx @mushi-mushi/cli sync-lessons --cwd .`).action(async (opts) => {
|
|
1847
|
-
const { writeFile:
|
|
1620
|
+
const { writeFile: writeFile3, mkdir: mkdir2 } = await import("fs/promises");
|
|
1848
1621
|
const nodePath = await import("path");
|
|
1849
1622
|
const config = requireConfig();
|
|
1850
1623
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -1872,7 +1645,7 @@ Typical CI usage:
|
|
|
1872
1645
|
return;
|
|
1873
1646
|
}
|
|
1874
1647
|
await mkdir2(nodePath.dirname(target), { recursive: true });
|
|
1875
|
-
await
|
|
1648
|
+
await writeFile3(target, JSON.stringify(output, null, 2) + "\n", "utf8");
|
|
1876
1649
|
if (opts.json) {
|
|
1877
1650
|
console.log(JSON.stringify({ ok: true, path: target, count: lessons2.length }));
|
|
1878
1651
|
} else {
|
|
@@ -1885,14 +1658,14 @@ Typical CI usage:
|
|
|
1885
1658
|
import { createReadStream } from "fs";
|
|
1886
1659
|
import { readFile, readdir } from "fs/promises";
|
|
1887
1660
|
import { createHash } from "crypto";
|
|
1888
|
-
import { join as
|
|
1661
|
+
import { join as join4, relative, basename } from "path";
|
|
1889
1662
|
import * as p2 from "@clack/prompts";
|
|
1890
1663
|
async function findMapFiles(dir) {
|
|
1891
1664
|
const results = [];
|
|
1892
1665
|
async function walk(current) {
|
|
1893
1666
|
const entries = await readdir(current, { withFileTypes: true });
|
|
1894
1667
|
for (const e of entries) {
|
|
1895
|
-
const full =
|
|
1668
|
+
const full = join4(current, e.name);
|
|
1896
1669
|
if (e.isDirectory()) {
|
|
1897
1670
|
await walk(full);
|
|
1898
1671
|
} else if (e.isFile() && (e.name.endsWith(".js.map") || e.name.endsWith(".css.map"))) {
|
|
@@ -2067,7 +1840,7 @@ Examples:
|
|
|
2067
1840
|
mushi index ./src
|
|
2068
1841
|
mushi index ./src --language ts --dry-run`).action(async (path, opts) => {
|
|
2069
1842
|
const config = requireConfig({ needsProject: true });
|
|
2070
|
-
const { readdir: readdir2, readFile:
|
|
1843
|
+
const { readdir: readdir2, readFile: readFile4, stat } = await import("fs/promises");
|
|
2071
1844
|
const nodePath = await import("path");
|
|
2072
1845
|
const SKIP = /node_modules|\.git|dist|build|\.next|\.turbo|coverage/;
|
|
2073
1846
|
const EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs"]);
|
|
@@ -2094,7 +1867,7 @@ Examples:
|
|
|
2094
1867
|
`);
|
|
2095
1868
|
continue;
|
|
2096
1869
|
}
|
|
2097
|
-
const source = await
|
|
1870
|
+
const source = await readFile4(file, "utf8");
|
|
2098
1871
|
const relative2 = nodePath.relative(root, file).replaceAll("\\", "/");
|
|
2099
1872
|
count++;
|
|
2100
1873
|
bytes += source.length;
|
|
@@ -2141,6 +1914,56 @@ Examples:
|
|
|
2141
1914
|
});
|
|
2142
1915
|
}
|
|
2143
1916
|
|
|
1917
|
+
// src/mcp-config.ts
|
|
1918
|
+
import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
1919
|
+
import { dirname as dirname2 } from "path";
|
|
1920
|
+
var DEFAULT_MUSHI_FEATURES = "triage,fixes,inventory,setup,docs";
|
|
1921
|
+
async function writeMcpServerEntry(opts) {
|
|
1922
|
+
const { configPath, serverName, serverBlock, dryRun = false } = opts;
|
|
1923
|
+
let merged = { mcpServers: {} };
|
|
1924
|
+
let created = false;
|
|
1925
|
+
try {
|
|
1926
|
+
const existing = await readFile2(configPath, "utf8");
|
|
1927
|
+
try {
|
|
1928
|
+
merged = JSON.parse(existing);
|
|
1929
|
+
} catch {
|
|
1930
|
+
merged = { mcpServers: {} };
|
|
1931
|
+
}
|
|
1932
|
+
} catch {
|
|
1933
|
+
created = true;
|
|
1934
|
+
merged = { mcpServers: {} };
|
|
1935
|
+
}
|
|
1936
|
+
const servers = merged.mcpServers ?? {};
|
|
1937
|
+
servers[serverName] = serverBlock;
|
|
1938
|
+
merged.mcpServers = servers;
|
|
1939
|
+
const output = JSON.stringify(merged, null, 2) + "\n";
|
|
1940
|
+
if (!dryRun) {
|
|
1941
|
+
await mkdir(dirname2(configPath), { recursive: true });
|
|
1942
|
+
await writeFile(configPath, output, "utf8");
|
|
1943
|
+
}
|
|
1944
|
+
return { created, path: configPath };
|
|
1945
|
+
}
|
|
1946
|
+
function buildMcpServerBlock(opts) {
|
|
1947
|
+
return {
|
|
1948
|
+
command: "npx",
|
|
1949
|
+
args: ["-y", "@mushi-mushi/mcp@latest"],
|
|
1950
|
+
env: {
|
|
1951
|
+
MUSHI_API_ENDPOINT: opts.endpoint,
|
|
1952
|
+
MUSHI_PROJECT_ID: opts.projectId,
|
|
1953
|
+
MUSHI_API_KEY: opts.apiKey,
|
|
1954
|
+
MUSHI_FEATURES: DEFAULT_MUSHI_FEATURES
|
|
1955
|
+
}
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
function buildMcpServerName(opts) {
|
|
1959
|
+
if (opts.legacy) return "mushi";
|
|
1960
|
+
if (opts.projectName) {
|
|
1961
|
+
const slug = opts.projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 24);
|
|
1962
|
+
return `mushi-${slug}`;
|
|
1963
|
+
}
|
|
1964
|
+
return `mushi-${(opts.projectId ?? "unknown").slice(0, 8)}`;
|
|
1965
|
+
}
|
|
1966
|
+
|
|
2144
1967
|
// src/commands/project.ts
|
|
2145
1968
|
function registerProjectCommands(program2) {
|
|
2146
1969
|
const project = program2.command("project").description("Project management");
|
|
@@ -2155,8 +1978,8 @@ Typical first-time flow:
|
|
|
2155
1978
|
# Browser opens \u2192 sign up / magic-link \u2192 come back to terminal
|
|
2156
1979
|
# CLI writes .env.local and .cursor/mcp.json
|
|
2157
1980
|
# mushi whoami to confirm`).action(async (opts) => {
|
|
2158
|
-
const { writeFile:
|
|
2159
|
-
const { existsSync:
|
|
1981
|
+
const { writeFile: writeFile3 } = await import("fs/promises");
|
|
1982
|
+
const { existsSync: existsSync4 } = await import("fs");
|
|
2160
1983
|
const nodePath = await import("path");
|
|
2161
1984
|
const endpoint = opts.endpoint ?? loadConfig().endpoint ?? "https://api.mushimushi.dev";
|
|
2162
1985
|
const signUpUrl = "https://kensaur.us/mushi-mushi/sign-up";
|
|
@@ -2203,41 +2026,15 @@ Typical first-time flow:
|
|
|
2203
2026
|
`MUSHI_API_KEY=${apiKey}`,
|
|
2204
2027
|
""
|
|
2205
2028
|
];
|
|
2206
|
-
const envExisting =
|
|
2207
|
-
await
|
|
2029
|
+
const envExisting = existsSync4(envPath);
|
|
2030
|
+
await writeFile3(envPath, envLines.join("\n"), "utf8");
|
|
2208
2031
|
console.log(`
|
|
2209
2032
|
\u2713 ${envExisting ? "Updated" : "Created"} .env.local`);
|
|
2210
|
-
const
|
|
2211
|
-
|
|
2212
|
-
const
|
|
2213
|
-
const
|
|
2214
|
-
|
|
2215
|
-
mushi: {
|
|
2216
|
-
command: "npx",
|
|
2217
|
-
args: ["-y", "@mushi-mushi/mcp@latest"],
|
|
2218
|
-
env: {
|
|
2219
|
-
MUSHI_API_ENDPOINT: endpoint,
|
|
2220
|
-
MUSHI_PROJECT_ID: projectId,
|
|
2221
|
-
MUSHI_API_KEY: apiKey
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
};
|
|
2226
|
-
const mcpExisting = existsSync5(mcpPath);
|
|
2227
|
-
if (mcpExisting) {
|
|
2228
|
-
try {
|
|
2229
|
-
const { readFile: readFile3 } = await import("fs/promises");
|
|
2230
|
-
const raw = JSON.parse(await readFile3(mcpPath, "utf8"));
|
|
2231
|
-
const existing = raw;
|
|
2232
|
-
existing.mcpServers = { ...existing.mcpServers ?? {}, mushi: mcpJson.mcpServers.mushi };
|
|
2233
|
-
await writeFile2(mcpPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
2234
|
-
} catch {
|
|
2235
|
-
await writeFile2(mcpPath, JSON.stringify(mcpJson, null, 2) + "\n", "utf8");
|
|
2236
|
-
}
|
|
2237
|
-
} else {
|
|
2238
|
-
await writeFile2(mcpPath, JSON.stringify(mcpJson, null, 2) + "\n", "utf8");
|
|
2239
|
-
}
|
|
2240
|
-
console.log(` \u2713 ${mcpExisting ? "Updated" : "Created"} .cursor/mcp.json`);
|
|
2033
|
+
const mcpPath = nodePath.join(cwd, ".cursor", "mcp.json");
|
|
2034
|
+
const serverName = buildMcpServerName({ legacy: true });
|
|
2035
|
+
const serverBlock = buildMcpServerBlock({ endpoint, projectId, apiKey });
|
|
2036
|
+
const { created: mcpCreated } = await writeMcpServerEntry({ configPath: mcpPath, serverName, serverBlock });
|
|
2037
|
+
console.log(` \u2713 ${mcpCreated ? "Created" : "Updated"} .cursor/mcp.json`);
|
|
2241
2038
|
console.log("");
|
|
2242
2039
|
console.log(' Done! Restart Cursor and ask: "list mushi tools"');
|
|
2243
2040
|
console.log(" Run `mushi whoami` to verify the connection.");
|
|
@@ -2260,9 +2057,9 @@ Supported IDEs:
|
|
|
2260
2057
|
continue \u2014 writes .continue/mcp.json
|
|
2261
2058
|
zed \u2014 writes ~/.config/zed/settings.json mcpServers block
|
|
2262
2059
|
|
|
2263
|
-
The command reads credentials from ~/.
|
|
2264
|
-
const { writeFile:
|
|
2265
|
-
const { existsSync:
|
|
2060
|
+
The command reads credentials from ~/.config/mushi/config.json (run \`mushi login\` first).`).action(async (opts) => {
|
|
2061
|
+
const { writeFile: writeFile3, mkdir: mkdir2, readFile: readFile4 } = await import("fs/promises");
|
|
2062
|
+
const { existsSync: existsSync4 } = await import("fs");
|
|
2266
2063
|
const nodePath = await import("path");
|
|
2267
2064
|
const os = await import("os");
|
|
2268
2065
|
const config = requireConfig({ needsProject: true });
|
|
@@ -2345,28 +2142,17 @@ The command reads credentials from ~/.mushirc (run \`mushi login\` first).`).act
|
|
|
2345
2142
|
const configDir = ideEntry.dir.startsWith("/") ? ideEntry.dir : nodePath.join(cwd, ideEntry.dir);
|
|
2346
2143
|
const configPath = nodePath.join(configDir, ideEntry.file);
|
|
2347
2144
|
if (ideEntry.format === "mcp-json") {
|
|
2348
|
-
let merged = { mcpServers: {} };
|
|
2349
|
-
if (existsSync5(configPath)) {
|
|
2350
|
-
try {
|
|
2351
|
-
const raw = await readFile3(configPath, "utf8");
|
|
2352
|
-
merged = JSON.parse(raw);
|
|
2353
|
-
} catch {
|
|
2354
|
-
}
|
|
2355
|
-
}
|
|
2356
|
-
const servers = merged.mcpServers ?? {};
|
|
2357
2145
|
if (allProjectsList && allProjectsList.length > 0) {
|
|
2358
2146
|
for (const p3 of allProjectsList) {
|
|
2359
|
-
const
|
|
2360
|
-
const
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
}
|
|
2369
|
-
};
|
|
2147
|
+
const pServerName = buildMcpServerName({ projectId: p3.id, projectName: p3.name ?? void 0 });
|
|
2148
|
+
const pBlock = buildMcpServerBlock({
|
|
2149
|
+
endpoint: config.endpoint,
|
|
2150
|
+
projectId: p3.id,
|
|
2151
|
+
apiKey: config.apiKey
|
|
2152
|
+
});
|
|
2153
|
+
if (!opts.dryRun) {
|
|
2154
|
+
await writeMcpServerEntry({ configPath, serverName: pServerName, serverBlock: pBlock });
|
|
2155
|
+
}
|
|
2370
2156
|
}
|
|
2371
2157
|
if (opts.dryRun) {
|
|
2372
2158
|
console.log(`[dry-run] Would add ${allProjectsList.length} mushi-* entries to ${configPath}`);
|
|
@@ -2374,23 +2160,20 @@ The command reads credentials from ~/.mushirc (run \`mushi login\` first).`).act
|
|
|
2374
2160
|
console.log(`\u2713 Added ${allProjectsList.length} mushi-* server entries (${allProjectsList.map((p3) => p3.name ?? p3.id.slice(0, 8)).join(", ")})`);
|
|
2375
2161
|
}
|
|
2376
2162
|
} else {
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
await mkdir2(configDir, { recursive: true });
|
|
2386
|
-
await writeFile2(configPath, output, "utf8");
|
|
2387
|
-
if (!allProjectsList) console.log(`\u2713 Written ${configPath}`);
|
|
2163
|
+
if (opts.dryRun) {
|
|
2164
|
+
const preview = JSON.stringify({ mcpServers: { [serverName]: mcpServerBlock } }, null, 2) + "\n";
|
|
2165
|
+
console.log(`[dry-run] Would write ${configPath}:`);
|
|
2166
|
+
console.log(redactKeyForDisplay(preview));
|
|
2167
|
+
} else {
|
|
2168
|
+
await writeMcpServerEntry({ configPath, serverName, serverBlock: mcpServerBlock });
|
|
2169
|
+
console.log(`\u2713 Written ${configPath}`);
|
|
2170
|
+
}
|
|
2388
2171
|
}
|
|
2389
2172
|
} else if (ideEntry.format === "zed") {
|
|
2390
2173
|
let settings = {};
|
|
2391
|
-
if (
|
|
2174
|
+
if (existsSync4(configPath)) {
|
|
2392
2175
|
try {
|
|
2393
|
-
const raw = await
|
|
2176
|
+
const raw = await readFile4(configPath, "utf8");
|
|
2394
2177
|
settings = JSON.parse(raw);
|
|
2395
2178
|
} catch {
|
|
2396
2179
|
}
|
|
@@ -2415,7 +2198,7 @@ The command reads credentials from ~/.mushirc (run \`mushi login\` first).`).act
|
|
|
2415
2198
|
console.log(redactKeyForDisplay(output));
|
|
2416
2199
|
} else {
|
|
2417
2200
|
await mkdir2(configDir, { recursive: true });
|
|
2418
|
-
await
|
|
2201
|
+
await writeFile3(configPath, output, "utf8");
|
|
2419
2202
|
console.log(`\u2713 Written ${configPath}`);
|
|
2420
2203
|
}
|
|
2421
2204
|
}
|
|
@@ -2449,7 +2232,7 @@ The command reads credentials from ~/.mushirc (run \`mushi login\` first).`).act
|
|
|
2449
2232
|
if (opts.dryRun) {
|
|
2450
2233
|
console.log(`[dry-run] Would write ${rulesPath}`);
|
|
2451
2234
|
} else {
|
|
2452
|
-
await
|
|
2235
|
+
await writeFile3(rulesPath, rulesContent, "utf8");
|
|
2453
2236
|
console.log(`\u2713 Written .cursorrules`);
|
|
2454
2237
|
}
|
|
2455
2238
|
} else if (opts.ide === "claude") {
|
|
@@ -2459,7 +2242,7 @@ The command reads credentials from ~/.mushirc (run \`mushi login\` first).`).act
|
|
|
2459
2242
|
console.log(`[dry-run] Would write ${rulesPath}`);
|
|
2460
2243
|
} else {
|
|
2461
2244
|
await mkdir2(rulesDir, { recursive: true });
|
|
2462
|
-
await
|
|
2245
|
+
await writeFile3(rulesPath, rulesContent, "utf8");
|
|
2463
2246
|
console.log(`\u2713 Written .claude/rules/mushi.md`);
|
|
2464
2247
|
}
|
|
2465
2248
|
}
|
|
@@ -2589,7 +2372,7 @@ function renderNudgeExplainer(phase) {
|
|
|
2589
2372
|
|
|
2590
2373
|
// src/sanitize-config.ts
|
|
2591
2374
|
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;
|
|
2592
|
-
var API_KEY_RE = /^mushi_[A-Za-z0-9_-]{10,}$/;
|
|
2375
|
+
var API_KEY_RE = /^(mushi_|mush_pk_)[A-Za-z0-9_-]{10,}$/;
|
|
2593
2376
|
function sanitizeApiKey(raw) {
|
|
2594
2377
|
const key = raw.replace(/[\r\n\0]/g, "");
|
|
2595
2378
|
if (!API_KEY_RE.test(key)) {
|
|
@@ -2736,11 +2519,11 @@ async function checkEndpointReachability(endpoint, doFetch = globalThis.fetch) {
|
|
|
2736
2519
|
}
|
|
2737
2520
|
async function checkSdkInstall(cwd) {
|
|
2738
2521
|
try {
|
|
2739
|
-
const { readFile:
|
|
2740
|
-
const { join:
|
|
2522
|
+
const { readFile: readFile4 } = await import("fs/promises");
|
|
2523
|
+
const { join: join6, resolve: resolve4 } = await import("path");
|
|
2741
2524
|
const root = resolve4(cwd);
|
|
2742
|
-
const pkgPath =
|
|
2743
|
-
const pkg = JSON.parse(await
|
|
2525
|
+
const pkgPath = join6(root, "package.json");
|
|
2526
|
+
const pkg = JSON.parse(await readFile4(pkgPath, "utf8"));
|
|
2744
2527
|
const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
2745
2528
|
const sdks = [
|
|
2746
2529
|
"@mushi-mushi/react",
|
|
@@ -2748,11 +2531,11 @@ async function checkSdkInstall(cwd) {
|
|
|
2748
2531
|
"@mushi-mushi/core",
|
|
2749
2532
|
"@mushi-mushi/react-native"
|
|
2750
2533
|
];
|
|
2751
|
-
const
|
|
2534
|
+
const installed = sdks.filter((s) => deps[s]);
|
|
2752
2535
|
return {
|
|
2753
2536
|
name: "SDK installed in this repo",
|
|
2754
|
-
ok:
|
|
2755
|
-
detail:
|
|
2537
|
+
ok: installed.length > 0,
|
|
2538
|
+
detail: installed.length > 0 ? installed.map((s) => `${s}@${deps[s]}`).join(", ") : "No @mushi-mushi/* package in package.json \u2014 run `mushi init` to install"
|
|
2756
2539
|
};
|
|
2757
2540
|
} catch {
|
|
2758
2541
|
return null;
|
|
@@ -2918,18 +2701,18 @@ async function checkQaStoriesHealth(config, doFetch = globalThis.fetch) {
|
|
|
2918
2701
|
async function checkHostAppWiring(cwd) {
|
|
2919
2702
|
const checks = [];
|
|
2920
2703
|
try {
|
|
2921
|
-
const { readFile:
|
|
2922
|
-
const { join:
|
|
2704
|
+
const { readFile: readFile4, access } = await import("fs/promises");
|
|
2705
|
+
const { join: join6, resolve: resolve4 } = await import("path");
|
|
2923
2706
|
const root = resolve4(cwd);
|
|
2924
|
-
const pkgPath =
|
|
2925
|
-
const pkg = JSON.parse(await
|
|
2707
|
+
const pkgPath = join6(root, "package.json");
|
|
2708
|
+
const pkg = JSON.parse(await readFile4(pkgPath, "utf8"));
|
|
2926
2709
|
const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
2927
2710
|
const isCapHybrid = Boolean(deps["@capacitor/core"] && deps["react"]);
|
|
2928
2711
|
const envCandidates = [".env.local", ".env"];
|
|
2929
2712
|
let envContent = "";
|
|
2930
2713
|
for (const f of envCandidates) {
|
|
2931
2714
|
try {
|
|
2932
|
-
envContent = await
|
|
2715
|
+
envContent = await readFile4(join6(root, f), "utf8");
|
|
2933
2716
|
break;
|
|
2934
2717
|
} catch {
|
|
2935
2718
|
}
|
|
@@ -2943,7 +2726,7 @@ async function checkHostAppWiring(cwd) {
|
|
|
2943
2726
|
});
|
|
2944
2727
|
let mcpPresent = false;
|
|
2945
2728
|
try {
|
|
2946
|
-
await access(
|
|
2729
|
+
await access(join6(root, ".cursor", "mcp.json"));
|
|
2947
2730
|
mcpPresent = true;
|
|
2948
2731
|
} catch {
|
|
2949
2732
|
}
|
|
@@ -2976,19 +2759,19 @@ async function checkHostAppWiring(cwd) {
|
|
|
2976
2759
|
}
|
|
2977
2760
|
async function checkMcpConfig(config, cwd, doFetch = globalThis.fetch) {
|
|
2978
2761
|
const checks = [];
|
|
2979
|
-
const { readFile:
|
|
2980
|
-
const { join:
|
|
2981
|
-
const { homedir
|
|
2762
|
+
const { readFile: readFile4 } = await import("fs/promises");
|
|
2763
|
+
const { join: join6, resolve: resolve4 } = await import("path");
|
|
2764
|
+
const { homedir } = await import("os");
|
|
2982
2765
|
const root = resolve4(cwd);
|
|
2983
2766
|
const candidates = [
|
|
2984
|
-
|
|
2985
|
-
|
|
2767
|
+
join6(root, ".cursor", "mcp.json"),
|
|
2768
|
+
join6(homedir(), ".cursor", "mcp.json")
|
|
2986
2769
|
];
|
|
2987
2770
|
let mcpPath = null;
|
|
2988
2771
|
let mcpRaw = null;
|
|
2989
2772
|
for (const candidate of candidates) {
|
|
2990
2773
|
try {
|
|
2991
|
-
mcpRaw = await
|
|
2774
|
+
mcpRaw = await readFile4(candidate, "utf8");
|
|
2992
2775
|
mcpPath = candidate;
|
|
2993
2776
|
break;
|
|
2994
2777
|
} catch {
|
|
@@ -3175,9 +2958,9 @@ async function planUpgrade(cwd) {
|
|
|
3175
2958
|
...pkg?.dependencies ?? {},
|
|
3176
2959
|
...pkg?.devDependencies ?? {}
|
|
3177
2960
|
};
|
|
3178
|
-
const
|
|
2961
|
+
const installed = MUSHI_PACKAGES.filter((name) => deps[name]);
|
|
3179
2962
|
const entries = await Promise.all(
|
|
3180
|
-
|
|
2963
|
+
installed.map(async (name) => {
|
|
3181
2964
|
const current = deps[name] ?? "";
|
|
3182
2965
|
const currentCore = current.replace(/^[\^~>=<]*/, "");
|
|
3183
2966
|
if (!/^\d/.test(currentCore)) {
|
|
@@ -3249,8 +3032,8 @@ async function runUpgrade(opts = {}) {
|
|
|
3249
3032
|
}
|
|
3250
3033
|
|
|
3251
3034
|
// src/connect.ts
|
|
3252
|
-
import { appendFile,
|
|
3253
|
-
import { join as
|
|
3035
|
+
import { appendFile, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
3036
|
+
import { join as join5, resolve as resolve3 } from "path";
|
|
3254
3037
|
function envKeyPresent(content, key) {
|
|
3255
3038
|
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3256
3039
|
return new RegExp(`^${escaped}=`, "m").test(content);
|
|
@@ -3262,7 +3045,7 @@ ${lines.join("\n")}
|
|
|
3262
3045
|
`;
|
|
3263
3046
|
let existing = null;
|
|
3264
3047
|
try {
|
|
3265
|
-
existing = await
|
|
3048
|
+
existing = await readFile3(path, "utf8");
|
|
3266
3049
|
} catch {
|
|
3267
3050
|
}
|
|
3268
3051
|
if (existing !== null) {
|
|
@@ -3277,15 +3060,15 @@ ${needs.join("\n")}
|
|
|
3277
3060
|
`, "utf8");
|
|
3278
3061
|
return true;
|
|
3279
3062
|
}
|
|
3280
|
-
await
|
|
3063
|
+
await writeFile2(path, block, "utf8");
|
|
3281
3064
|
return true;
|
|
3282
3065
|
}
|
|
3283
3066
|
async function ensureMcpJsonGitignored(cwd, messages) {
|
|
3284
|
-
const gitignorePath =
|
|
3067
|
+
const gitignorePath = join5(cwd, ".gitignore");
|
|
3285
3068
|
const patterns = [".cursor/mcp.json", ".cursor/"];
|
|
3286
3069
|
let content = null;
|
|
3287
3070
|
try {
|
|
3288
|
-
content = await
|
|
3071
|
+
content = await readFile3(gitignorePath, "utf8");
|
|
3289
3072
|
} catch {
|
|
3290
3073
|
}
|
|
3291
3074
|
if (content === null) {
|
|
@@ -3316,7 +3099,7 @@ async function runConnect(opts, baseConfig = {}) {
|
|
|
3316
3099
|
const pkg = readPackageJson(cwd);
|
|
3317
3100
|
const framework = detectFramework(cwd, pkg);
|
|
3318
3101
|
const lines = envVarsToWrite(opts.apiKey, opts.projectId, framework).split("\n");
|
|
3319
|
-
envPath =
|
|
3102
|
+
envPath = join5(cwd, ".env.local");
|
|
3320
3103
|
const wrote = await mergeEnvFile(envPath, lines);
|
|
3321
3104
|
messages.push(
|
|
3322
3105
|
wrote ? `\u2713 Env vars merged into ${envPath}` : `\u2713 Env vars already present in ${envPath} (existing values left untouched)`
|
|
@@ -3330,28 +3113,15 @@ async function runConnect(opts, baseConfig = {}) {
|
|
|
3330
3113
|
}
|
|
3331
3114
|
let mcpPath = null;
|
|
3332
3115
|
if (opts.wireIde !== false) {
|
|
3333
|
-
const mcpDir =
|
|
3334
|
-
mcpPath =
|
|
3335
|
-
const serverName =
|
|
3336
|
-
const
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
MUSHI_API_KEY: opts.apiKey
|
|
3343
|
-
}
|
|
3344
|
-
};
|
|
3345
|
-
let merged = { mcpServers: {} };
|
|
3346
|
-
try {
|
|
3347
|
-
merged = JSON.parse(await readFile2(mcpPath, "utf8"));
|
|
3348
|
-
} catch {
|
|
3349
|
-
}
|
|
3350
|
-
const servers = merged.mcpServers ?? {};
|
|
3351
|
-
servers[serverName] = mcpServerBlock;
|
|
3352
|
-
merged.mcpServers = servers;
|
|
3353
|
-
await mkdir(mcpDir, { recursive: true });
|
|
3354
|
-
await writeFile(mcpPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
3116
|
+
const mcpDir = join5(cwd, ".cursor");
|
|
3117
|
+
mcpPath = join5(mcpDir, "mcp.json");
|
|
3118
|
+
const serverName = buildMcpServerName({ projectId: opts.projectId });
|
|
3119
|
+
const serverBlock = buildMcpServerBlock({
|
|
3120
|
+
endpoint,
|
|
3121
|
+
projectId: opts.projectId,
|
|
3122
|
+
apiKey: opts.apiKey
|
|
3123
|
+
});
|
|
3124
|
+
await writeMcpServerEntry({ configPath: mcpPath, serverName, serverBlock });
|
|
3355
3125
|
await ensureMcpJsonGitignored(cwd, messages);
|
|
3356
3126
|
messages.push(`\u2713 Wired ${mcpPath} \u2014 restart Cursor and run "list mushi tools"`);
|
|
3357
3127
|
}
|