@mushi-mushi/cli 0.17.2 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CONTRIBUTING.md CHANGED
@@ -58,12 +58,12 @@ packages/
58
58
  react-native/ React Native SDK (MIT)
59
59
  cli/ CLI tool (MIT)
60
60
  mcp/ MCP server for coding agents (MIT)
61
- server/ Supabase Edge Functions (BSL)
62
- agents/ Agentic fix pipeline (BSL)
63
- verify/ Fix verification (BSL)
61
+ server/ Supabase Edge Functions (AGPLv3)
62
+ agents/ Agentic fix pipeline (AGPLv3)
63
+ verify/ Fix verification (AGPLv3)
64
64
  apps/
65
65
  admin/ Admin dashboard (React + Tailwind)
66
- docs/ Documentation site (planned)
66
+ docs/ Documentation site (Nextra — apps/docs)
67
67
  tooling/
68
68
  eslint-config/ Shared ESLint flat config
69
69
  tsconfig/ Shared TypeScript configs
@@ -81,6 +81,30 @@ tooling/
81
81
  ```
82
82
  6. Open a pull request
83
83
 
84
+ ### Documentation (docs-as-code)
85
+
86
+ If your PR changes **user-visible SDK or CLI behavior**, update docs in the **same PR**:
87
+
88
+ | Surface | When to update |
89
+ | --- | --- |
90
+ | `packages/*/README.md` | npm-facing install/API/config changes for that package |
91
+ | `apps/docs/content/**/*.mdx` | Long-form guides, quickstarts, admin console docs |
92
+ | Root `README.md` | Marketing stats only when counts change — run `pnpm check:docs-stats` |
93
+
94
+ Guardrails (run locally before pushing):
95
+
96
+ ```bash
97
+ pnpm check:docs-stats # root README stat badges
98
+ pnpm check:sdk-version-matrix # apps/docs/content/sdks/index.mdx versions
99
+ pnpm check:internal-doc-links # dead /foo links in MDX
100
+ pnpm check:onboarding-drift # phantom MCP env vars / stale tool names
101
+ pnpm check:bundle-docs # @mushi-mushi/web size-limit vs README
102
+ pnpm gen:llms-txt # regenerate apps/docs/public/llms.txt after nav changes
103
+ pnpm gen:mcp-tools-doc # regenerate sdks/mcp-tools.generated.mdx from catalog.ts
104
+ ```
105
+
106
+ README changes on npm packages only appear on [npmjs.com](https://www.npmjs.com) after the next **Changesets publish**.
107
+
84
108
  ## Changesets
85
109
 
86
110
  We use [Changesets](https://github.com/changesets/changesets) for versioning. If your PR modifies a published package (`core`, `web`, `react`, `vue`, `svelte`, `angular`, `react-native`, `cli`, `mcp`), add a changeset:
@@ -93,13 +117,30 @@ Select the affected packages, the semver bump type, and write a summary. The cha
93
117
 
94
118
  ## Release flow
95
119
 
96
- Releases are fully automated. Maintainers don't run `npm publish` by hand.
120
+ Releases are fully automated once the version PR is merged. Maintainers don't run
121
+ `npm publish` by hand for routine bumps. See **[`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md)**
122
+ for the full production runbook (edge functions, admin SPA, docs site, migrations).
97
123
 
98
124
  1. PRs land on `master` with one or more changeset files in `.changeset/`.
99
- 2. `release.yml` runs on every push to `master`. It opens (or updates) a `chore: version packages` PR that bumps every affected `package.json`, rolls up the changelogs, and deletes the consumed changesets.
100
- 3. Merging that "Version Packages" PR re-fires `release.yml`. The publish step authenticates to npm via **OpenID Connect (OIDC) Trusted Publishers** — no long-lived `NPM_TOKEN` is exchanged — and every tarball ships with a **Sigstore provenance attestation** uploaded to the public transparency log.
125
+ Orphaned changesets (only ignored packages) fail `pnpm check:changeset-orphans`.
126
+ 2. `release.yml` runs on every push to `master`. It opens (or updates) a
127
+ **`chore: version packages`** PR on `changeset-release/master` that bumps
128
+ affected `package.json` files, rolls up changelogs, and deletes consumed
129
+ changesets.
130
+ 3. **Wait for CI on the version PR.** If checks don't appear (bot-branch
131
+ suppression), push an empty commit to `changeset-release/master`. If
132
+ `check:sdk-version-matrix` fails, run `pnpm gen:sdk-version-matrix` on that
133
+ branch and commit `apps/docs/content/sdks/index.mdx`.
134
+ 4. Merge the version PR. The publish step uses **OIDC Trusted Publishers** +
135
+ Sigstore provenance (with `NPM_TOKEN` only for brand-new package bootstrap).
136
+ 5. **Manually dispatch Release** unless a run starts within ~2 minutes:
137
+ **Actions → Release → Run workflow → `master`**. Squash-merges attributed to
138
+ `github-actions[bot]` usually suppress the auto `push` trigger — manual
139
+ dispatch is the normal path, not a fallback of last resort.
101
140
 
102
- If GitHub's anti-loop protection suppresses the auto re-fire (the squash merge can be attributed to `github-actions[bot]`), trigger the workflow manually: **Actions → release → Run workflow → master**.
141
+ ```bash
142
+ gh workflow run release.yml --ref master
143
+ ```
103
144
 
104
145
  ### Adding a brand-new publishable package
105
146
 
@@ -146,7 +187,7 @@ chore: bump dependencies
146
187
  ## License
147
188
 
148
189
  - SDK packages are MIT — your contributions will be MIT-licensed
149
- - Server/agents/verify are BSL 1.1 — contributions to those packages fall under BSL
190
+ - Server/agents/verify are AGPLv3 — contributions to those packages fall under AGPLv3
150
191
 
151
192
  ## Questions?
152
193
 
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # @mushi-mushi/cli
2
2
 
3
+ > **Your AI wrote it. Mushi tells you why it broke.**
4
+ > Plain-English diagnosis + a paste-ready fix, right inside Cursor. MIT-licensed SDKs · self-hostable · no second LLM key.
5
+
3
6
  CLI for Mushi Mushi — set up the SDK in one command, then triage reports and monitor the pipeline from your terminal.
4
7
 
5
8
  ## One-command setup
@@ -51,9 +54,9 @@ mushi --version
51
54
  ## Other commands
52
55
 
53
56
  ```bash
54
- mushi login --api-key mushi_xxx # store credentials in ~/.mushirc (mode 0o600)
57
+ mushi login --api-key mushi_xxx # store credentials in ~/.config/mushi/config.json (mode 0o600)
55
58
  mushi connect --api-key mushi_xxx --project-id <uuid> --endpoint <url> --wait
56
- # one-shot wiring: ~/.mushirc + .env.local + .cursor/mcp.json + heartbeat wait
59
+ # one-shot wiring: ~/.config/mushi/config.json + .env.local + .cursor/mcp.json + heartbeat wait
57
60
  mushi upgrade # bump installed @mushi-mushi/* packages to latest stable
58
61
  mushi status # project overview
59
62
  mushi reports list # recent reports
@@ -73,7 +76,7 @@ mushi sourcemaps upload --release <ver> --dir <dist> # upload .js.map / .css.m
73
76
  Non-interactive equivalent of `mushi init` for agents and scripts — wires a
74
77
  repo to an existing project in one shot:
75
78
 
76
- 1. Saves credentials to `~/.mushirc` (mode `0o600`).
79
+ 1. Saves credentials to `~/.config/mushi/config.json` (mode `0o600`).
77
80
  2. Merges `MUSHI_*` / framework-prefixed env vars into `.env.local` —
78
81
  existing keys are never overwritten (skip with `--no-env`).
79
82
  3. Writes the `@mushi-mushi/mcp` server block into `.cursor/mcp.json` and
@@ -177,7 +180,7 @@ Local checks performed:
177
180
 
178
181
  | Check | What it verifies |
179
182
  |---|---|
180
- | CLI config | `~/.mushirc` exists, `projectId` and `apiKey` fields are present |
183
+ | CLI config | `~/.config/mushi/config.json` exists, `projectId` and `apiKey` fields are present |
181
184
  | Endpoint reachability | `GET /v1/sdk/config?project_id=...` returns 200 |
182
185
  | SDK install | `@mushi-mushi/web` or framework-specific SDK is in `node_modules` |
183
186
 
@@ -301,11 +304,21 @@ mushi slack test
301
304
 
302
305
  ## Security notes
303
306
 
304
- - `~/.mushirc` is written with mode `0o600` on Unix. Legacy configs with looser permissions are tightened on load.
307
+ - `~/.config/mushi/config.json` is written with mode `0o600` on Unix. Legacy configs with looser permissions are tightened on load.
305
308
  - `--endpoint` values are parsed through `new URL()` and required to use `https://` except for `localhost` / `127.0.0.1` / `*.local`.
306
309
  - The `--api-key` flag leaks into `ps -ef` — prefer the interactive prompt on shared machines.
307
310
  - Full stack traces on error: `DEBUG=mushi mushi init`.
308
311
 
312
+ ## Programmatic imports
313
+
314
+ The CLI is also importable for tooling (used by `create-mushi-mushi`):
315
+
316
+ | Subpath | Exports |
317
+ | --- | --- |
318
+ | `@mushi-mushi/cli/init` | `runInit`, `InitOptions` |
319
+ | `@mushi-mushi/cli/detect` | Framework / package-manager detection |
320
+ | `@mushi-mushi/cli/version` | `MUSHI_CLI_VERSION` |
321
+
309
322
  ## License
310
323
 
311
324
  MIT
@@ -26,15 +26,15 @@ var FRAMEWORKS = {
26
26
  label: "Next.js",
27
27
  packageName: "@mushi-mushi/react",
28
28
  needsWebPackage: false,
29
- snippet: (apiKey, projectId) => `// app/providers.tsx (or pages/_app.tsx for /pages router)
29
+ snippet: () => `// app/providers.tsx (or pages/_app.tsx for /pages router)
30
30
  'use client'
31
31
  import { MushiProvider } from '@mushi-mushi/react'
32
32
 
33
33
  export function Providers({ children }: { children: React.ReactNode }) {
34
34
  return (
35
35
  <MushiProvider config={{
36
- projectId: '${projectId}',
37
- apiKey: '${apiKey}',
36
+ projectId: process.env.NEXT_PUBLIC_MUSHI_PROJECT_ID!,
37
+ apiKey: process.env.NEXT_PUBLIC_MUSHI_API_KEY!,
38
38
  }}>
39
39
  {children}
40
40
  </MushiProvider>
@@ -46,13 +46,13 @@ export function Providers({ children }: { children: React.ReactNode }) {
46
46
  label: "React",
47
47
  packageName: "@mushi-mushi/react",
48
48
  needsWebPackage: false,
49
- snippet: (apiKey, projectId) => `// src/main.tsx
49
+ snippet: () => `// src/main.tsx
50
50
  import { MushiProvider } from '@mushi-mushi/react'
51
51
 
52
52
  createRoot(document.getElementById('root')!).render(
53
53
  <MushiProvider config={{
54
- projectId: '${projectId}',
55
- apiKey: '${apiKey}',
54
+ projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
55
+ apiKey: import.meta.env.VITE_MUSHI_API_KEY,
56
56
  }}>
57
57
  <App />
58
58
  </MushiProvider>
@@ -62,57 +62,58 @@ createRoot(document.getElementById('root')!).render(
62
62
  id: "vue",
63
63
  label: "Vue 3",
64
64
  packageName: "@mushi-mushi/vue",
65
- needsWebPackage: true,
66
- snippet: (apiKey, projectId) => `// src/main.ts
65
+ needsWebPackage: false,
66
+ snippet: () => `// src/main.ts
67
67
  import { MushiPlugin } from '@mushi-mushi/vue'
68
- import { Mushi } from '@mushi-mushi/web'
69
68
 
70
- app.use(MushiPlugin, { projectId: '${projectId}', apiKey: '${apiKey}' })
71
- Mushi.init({ projectId: '${projectId}', apiKey: '${apiKey}' })`
69
+ app.use(MushiPlugin, {
70
+ projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
71
+ apiKey: import.meta.env.VITE_MUSHI_API_KEY,
72
+ })`
72
73
  },
73
74
  nuxt: {
74
75
  id: "nuxt",
75
76
  label: "Nuxt",
76
77
  packageName: "@mushi-mushi/vue",
77
- needsWebPackage: true,
78
- snippet: (apiKey, projectId) => `// plugins/mushi.client.ts
78
+ needsWebPackage: false,
79
+ snippet: () => `// plugins/mushi.client.ts
79
80
  import { MushiPlugin } from '@mushi-mushi/vue'
80
- import { Mushi } from '@mushi-mushi/web'
81
81
 
82
82
  export default defineNuxtPlugin((nuxtApp) => {
83
83
  nuxtApp.vueApp.use(MushiPlugin, {
84
- projectId: '${projectId}',
85
- apiKey: '${apiKey}',
84
+ projectId: import.meta.env.NUXT_PUBLIC_MUSHI_PROJECT_ID,
85
+ apiKey: import.meta.env.NUXT_PUBLIC_MUSHI_API_KEY,
86
86
  })
87
- Mushi.init({ projectId: '${projectId}', apiKey: '${apiKey}' })
88
87
  })`
89
88
  },
90
89
  svelte: {
91
90
  id: "svelte",
92
91
  label: "Svelte",
93
92
  packageName: "@mushi-mushi/svelte",
94
- needsWebPackage: true,
95
- snippet: (apiKey, projectId) => `// src/main.ts (or +layout.svelte for SvelteKit)
93
+ needsWebPackage: false,
94
+ snippet: () => `// src/main.ts
96
95
  import { initMushi } from '@mushi-mushi/svelte'
97
- import { Mushi } from '@mushi-mushi/web'
98
96
 
99
- initMushi({ projectId: '${projectId}', apiKey: '${apiKey}' })
100
- Mushi.init({ projectId: '${projectId}', apiKey: '${apiKey}' })`
97
+ initMushi({
98
+ projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
99
+ apiKey: import.meta.env.VITE_MUSHI_API_KEY,
100
+ })`
101
101
  },
102
102
  sveltekit: {
103
103
  id: "sveltekit",
104
104
  label: "SvelteKit",
105
105
  packageName: "@mushi-mushi/svelte",
106
- needsWebPackage: true,
107
- snippet: (apiKey, projectId) => `// src/routes/+layout.svelte
106
+ needsWebPackage: false,
107
+ snippet: () => `// src/routes/+layout.svelte
108
108
  <script>
109
109
  import { onMount } from 'svelte'
110
- import { initMushi } from '@mushi-mushi/svelte'
111
110
 
112
111
  onMount(async () => {
113
- const { Mushi } = await import('@mushi-mushi/web')
114
- initMushi({ projectId: '${projectId}', apiKey: '${apiKey}' })
115
- Mushi.init({ projectId: '${projectId}', apiKey: '${apiKey}' })
112
+ const { initMushi } = await import('@mushi-mushi/svelte')
113
+ initMushi({
114
+ projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
115
+ apiKey: import.meta.env.VITE_MUSHI_API_KEY,
116
+ })
116
117
  })
117
118
  </script>`
118
119
  },
@@ -120,29 +121,33 @@ Mushi.init({ projectId: '${projectId}', apiKey: '${apiKey}' })`
120
121
  id: "angular",
121
122
  label: "Angular",
122
123
  packageName: "@mushi-mushi/angular",
123
- needsWebPackage: true,
124
- snippet: (apiKey, projectId) => `// src/main.ts
124
+ needsWebPackage: false,
125
+ snippet: () => `// src/main.ts
125
126
  import { provideMushi } from '@mushi-mushi/angular'
126
- import { Mushi } from '@mushi-mushi/web'
127
127
 
128
128
  bootstrapApplication(AppComponent, {
129
129
  providers: [
130
- provideMushi({ projectId: '${projectId}', apiKey: '${apiKey}' }),
130
+ provideMushi({
131
+ projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
132
+ apiKey: import.meta.env.VITE_MUSHI_API_KEY,
133
+ }),
131
134
  ],
132
- })
133
- Mushi.init({ projectId: '${projectId}', apiKey: '${apiKey}' })`
135
+ })`
134
136
  },
135
137
  expo: {
136
138
  id: "expo",
137
139
  label: "Expo",
138
140
  packageName: "@mushi-mushi/react-native",
139
141
  needsWebPackage: false,
140
- snippet: (apiKey, projectId) => `// App.tsx
142
+ snippet: () => `// App.tsx
141
143
  import { MushiProvider } from '@mushi-mushi/react-native'
142
144
 
143
145
  export default function App() {
144
146
  return (
145
- <MushiProvider projectId="${projectId}" apiKey="${apiKey}">
147
+ <MushiProvider
148
+ projectId={process.env.EXPO_PUBLIC_MUSHI_PROJECT_ID!}
149
+ apiKey={process.env.EXPO_PUBLIC_MUSHI_API_KEY!}
150
+ >
146
151
  <YourApp />
147
152
  </MushiProvider>
148
153
  )
@@ -153,12 +158,15 @@ export default function App() {
153
158
  label: "React Native",
154
159
  packageName: "@mushi-mushi/react-native",
155
160
  needsWebPackage: false,
156
- snippet: (apiKey, projectId) => `// App.tsx
161
+ snippet: () => `// App.tsx
157
162
  import { MushiProvider } from '@mushi-mushi/react-native'
158
163
 
159
164
  export default function App() {
160
165
  return (
161
- <MushiProvider projectId="${projectId}" apiKey="${apiKey}">
166
+ <MushiProvider
167
+ projectId={process.env.MUSHI_PROJECT_ID!}
168
+ apiKey={process.env.MUSHI_API_KEY!}
169
+ >
162
170
  <YourApp />
163
171
  </MushiProvider>
164
172
  )
@@ -175,24 +183,27 @@ export default function App() {
175
183
  * `TypeError: Mushi.init is not a function`. The accompanying admin
176
184
  * console snippet (apps/admin/src/lib/sdkSnippets.ts) emits the same
177
185
  * `Mushi.configure(...)` shape; both are pinned by tests. */
178
- snippet: (apiKey, projectId) => `// src/main.ts \u2014 after install, run \`npx cap sync\`
186
+ snippet: () => `// src/main.ts \u2014 after install, run \`npx cap sync\`
179
187
  import { Mushi } from '@mushi-mushi/capacitor'
180
188
 
181
- await Mushi.configure({ projectId: '${projectId}', apiKey: '${apiKey}' })`
189
+ await Mushi.configure({
190
+ projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
191
+ apiKey: import.meta.env.VITE_MUSHI_API_KEY,
192
+ })`
182
193
  },
183
194
  express: {
184
195
  id: "express",
185
196
  label: "Express",
186
197
  packageName: "@mushi-mushi/node",
187
198
  needsWebPackage: false,
188
- snippet: (apiKey, projectId) => `// src/instrument.ts \u2014 load with: node --import ./dist/instrument.js
199
+ snippet: () => `// src/instrument.ts \u2014 load with: node --import ./dist/instrument.js
189
200
  import { MushiNodeClient, attachUnhandledHook } from '@mushi-mushi/node'
190
201
  import { mushiExpressErrorHandler } from '@mushi-mushi/node/express'
191
202
  import type { Express } from 'express'
192
203
 
193
204
  export const mushi = new MushiNodeClient({
194
- projectId: '${projectId}',
195
- apiKey: '${apiKey}',
205
+ projectId: process.env.MUSHI_PROJECT_ID!,
206
+ apiKey: process.env.MUSHI_API_KEY!,
196
207
  environment: process.env.NODE_ENV ?? 'production',
197
208
  })
198
209
  attachUnhandledHook({ client: mushi })
@@ -206,14 +217,14 @@ export function attachMushi(app: Express) {
206
217
  label: "Fastify",
207
218
  packageName: "@mushi-mushi/node",
208
219
  needsWebPackage: false,
209
- snippet: (apiKey, projectId) => `// src/instrument.ts \u2014 load with: node --import ./dist/instrument.js
220
+ snippet: () => `// src/instrument.ts \u2014 load with: node --import ./dist/instrument.js
210
221
  import { MushiNodeClient, attachUnhandledHook } from '@mushi-mushi/node'
211
222
  import { mushiFastifyPlugin } from '@mushi-mushi/node/fastify'
212
223
  import Fastify from 'fastify'
213
224
 
214
225
  export const mushi = new MushiNodeClient({
215
- projectId: '${projectId}',
216
- apiKey: '${apiKey}',
226
+ projectId: process.env.MUSHI_PROJECT_ID!,
227
+ apiKey: process.env.MUSHI_API_KEY!,
217
228
  environment: process.env.NODE_ENV ?? 'production',
218
229
  })
219
230
  attachUnhandledHook({ client: mushi })
@@ -226,14 +237,14 @@ mushiFastifyPlugin(app, { client: mushi })`
226
237
  label: "Hono",
227
238
  packageName: "@mushi-mushi/node",
228
239
  needsWebPackage: false,
229
- snippet: (apiKey, projectId) => `// src/instrument.ts \u2014 load with: node --import ./dist/instrument.js
240
+ snippet: () => `// src/instrument.ts \u2014 load with: node --import ./dist/instrument.js
230
241
  import { MushiNodeClient, attachUnhandledHook } from '@mushi-mushi/node'
231
242
  import { mushiHonoErrorHandler } from '@mushi-mushi/node/hono'
232
243
  import { Hono } from 'hono'
233
244
 
234
245
  export const mushi = new MushiNodeClient({
235
- projectId: '${projectId}',
236
- apiKey: '${apiKey}',
246
+ projectId: process.env.MUSHI_PROJECT_ID!,
247
+ apiKey: process.env.MUSHI_API_KEY!,
237
248
  environment: process.env.NODE_ENV ?? 'production',
238
249
  })
239
250
  attachUnhandledHook({ client: mushi })
@@ -250,10 +261,13 @@ app.onError(
250
261
  label: "Vanilla JS / unknown",
251
262
  packageName: "@mushi-mushi/web",
252
263
  needsWebPackage: false,
253
- snippet: (apiKey, projectId) => `// Anywhere in your client bundle
264
+ snippet: () => `// Anywhere in your client bundle
254
265
  import { Mushi } from '@mushi-mushi/web'
255
266
 
256
- Mushi.init({ projectId: '${projectId}', apiKey: '${apiKey}' })`
267
+ Mushi.init({
268
+ projectId: import.meta.env.VITE_MUSHI_PROJECT_ID,
269
+ apiKey: import.meta.env.VITE_MUSHI_API_KEY,
270
+ })`
257
271
  }
258
272
  };
259
273
  function readPackageJson(cwd) {
@@ -314,6 +328,18 @@ function envVarsToWrite(apiKey, projectId, framework) {
314
328
  `MUSHI_API_KEY=${apiKey}`
315
329
  ].join("\n");
316
330
  }
331
+ if (framework.id === "react-native") {
332
+ return [
333
+ `MUSHI_PROJECT_ID=${projectId}`,
334
+ `MUSHI_API_KEY=${apiKey}`
335
+ ].join("\n");
336
+ }
337
+ if (framework.id === "expo") {
338
+ return [
339
+ `EXPO_PUBLIC_MUSHI_PROJECT_ID=${projectId}`,
340
+ `EXPO_PUBLIC_MUSHI_API_KEY=${apiKey}`
341
+ ].join("\n");
342
+ }
317
343
  const prefix = framework.id === "next" ? "NEXT_PUBLIC_" : framework.id === "nuxt" ? "NUXT_PUBLIC_" : "VITE_";
318
344
  return [
319
345
  `${prefix}MUSHI_PROJECT_ID=${projectId}`,
@@ -0,0 +1,6 @@
1
+ // src/version.ts
2
+ var MUSHI_CLI_VERSION = true ? "0.18.1" : "0.0.0-dev";
3
+
4
+ export {
5
+ MUSHI_CLI_VERSION
6
+ };
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/endpoint.ts
4
+ var TEST_REPORT_TIMEOUT_MS = 1e4;
5
+ var TEST_REPORT_FETCH_TIMEOUT_MS = TEST_REPORT_TIMEOUT_MS;
6
+ var CLOUD_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
7
+ function resolveCloudEndpoint(explicit) {
8
+ if (explicit?.trim()) return explicit.trim();
9
+ const fromEnv = process.env.MUSHI_API_ENDPOINT?.trim();
10
+ if (fromEnv) return fromEnv;
11
+ return CLOUD_API_ENDPOINT;
12
+ }
13
+ function assertEndpoint(url) {
14
+ let parsed;
15
+ try {
16
+ parsed = new URL(url);
17
+ } catch {
18
+ throw new Error(`Invalid endpoint URL: ${url}`);
19
+ }
20
+ const host = parsed.hostname;
21
+ const isLocal = host === "localhost" || host === "127.0.0.1" || host === "::1" || host.endsWith(".local");
22
+ if (parsed.protocol !== "https:" && !isLocal) {
23
+ throw new Error(`Endpoint must use https:// (got ${parsed.protocol}//${host}).`);
24
+ }
25
+ return normalizeEndpoint(parsed.origin + parsed.pathname);
26
+ }
27
+ function normalizeEndpoint(url) {
28
+ if (!url) {
29
+ throw new Error(
30
+ "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"
31
+ );
32
+ }
33
+ let end = url.length;
34
+ while (end > 0 && url.charCodeAt(end - 1) === 47) end--;
35
+ return url.slice(0, end);
36
+ }
37
+
38
+ export {
39
+ TEST_REPORT_FETCH_TIMEOUT_MS,
40
+ CLOUD_API_ENDPOINT,
41
+ resolveCloudEndpoint,
42
+ assertEndpoint,
43
+ normalizeEndpoint
44
+ };