@mushi-mushi/cli 0.22.0 → 0.23.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/dist/init.js CHANGED
@@ -1,13 +1,19 @@
1
- import {
2
- MUSHI_CLI_VERSION
3
- } from "./chunk-5KN6S63C.js";
4
1
  import {
5
2
  TEST_REPORT_FETCH_TIMEOUT_MS,
3
+ apiCall,
4
+ apiKeyHint,
5
+ cliSetupDeepLink,
6
+ consoleUrl,
6
7
  loadConfig,
7
8
  normalizeEndpoint,
9
+ openInBrowser,
10
+ projectIdHint,
11
+ reportsUrl,
8
12
  resolveCloudEndpoint,
13
+ resolveConsoleUrl,
14
+ resolveConsoleUrlSync,
9
15
  saveConfig
10
- } from "./chunk-XEXYGDQV.js";
16
+ } from "./chunk-WLLFV26V.js";
11
17
  import {
12
18
  FRAMEWORKS,
13
19
  detectFramework,
@@ -15,228 +21,17 @@ import {
15
21
  envVarsToWrite,
16
22
  installCommand,
17
23
  readPackageJson
18
- } from "./chunk-JAUKBLPK.js";
24
+ } from "./chunk-AIZRI665.js";
25
+ import {
26
+ MUSHI_CLI_VERSION
27
+ } from "./chunk-BY5SZYRO.js";
19
28
 
20
29
  // src/init.ts
21
30
  import * as p from "@clack/prompts";
22
31
  import { spawn } from "child_process";
23
32
  import { randomUUID } from "crypto";
24
- import { appendFileSync, existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync } from "fs";
25
- import { join as join3 } from "path";
26
-
27
- // src/console-url.ts
28
- import { existsSync, readFileSync } from "fs";
29
- import { dirname, join } from "path";
30
- var HOSTED_CONSOLE_BASE = "https://kensaur.us/mushi-mushi/admin";
31
- var LOCAL_ADMIN_URL = "http://localhost:6464";
32
- var LOCAL_PROBE_MS = 800;
33
- function normalizeConsoleBase(url) {
34
- const trimmed = url.trim();
35
- let end = trimmed.length;
36
- while (end > 0 && trimmed.charCodeAt(end - 1) === 47) end--;
37
- return trimmed.slice(0, end);
38
- }
39
- function consoleUrl(base, route) {
40
- const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
41
- return `${normalizeConsoleBase(base)}${normalizedRoute}`;
42
- }
43
- function resolveConsoleUrlFromEnv() {
44
- const raw = process.env["MUSHI_CONSOLE_URL"]?.trim();
45
- return raw ? normalizeConsoleBase(raw) : void 0;
46
- }
47
- function isInsideMushiMonorepo(cwd) {
48
- let dir = cwd;
49
- for (let depth = 0; depth < 14; depth += 1) {
50
- const pkgPath = join(dir, "package.json");
51
- if (existsSync(pkgPath)) {
52
- try {
53
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
54
- if (pkg.name === "mushi-mushi") return true;
55
- } catch {
56
- }
57
- }
58
- const parent = dirname(dir);
59
- if (parent === dir) break;
60
- dir = parent;
61
- }
62
- return false;
63
- }
64
- async function probeLocalAdminConsole() {
65
- try {
66
- const controller = new AbortController();
67
- const timer = setTimeout(() => controller.abort(), LOCAL_PROBE_MS);
68
- const res = await fetch(LOCAL_ADMIN_URL, {
69
- signal: controller.signal,
70
- method: "HEAD"
71
- });
72
- clearTimeout(timer);
73
- if (res.ok || res.status === 404 || res.status === 401) {
74
- return LOCAL_ADMIN_URL;
75
- }
76
- } catch {
77
- }
78
- return null;
79
- }
80
- function resolveConsoleUrlSync(cwd = process.cwd()) {
81
- const fromEnv = resolveConsoleUrlFromEnv();
82
- if (fromEnv) return fromEnv;
83
- const config = loadConfig();
84
- if (config.consoleUrl) return normalizeConsoleBase(config.consoleUrl);
85
- if (isInsideMushiMonorepo(cwd)) return LOCAL_ADMIN_URL;
86
- return HOSTED_CONSOLE_BASE;
87
- }
88
- async function resolveConsoleUrl(options) {
89
- const fromEnv = resolveConsoleUrlFromEnv();
90
- if (fromEnv) return fromEnv;
91
- const config = loadConfig();
92
- if (config.consoleUrl) return normalizeConsoleBase(config.consoleUrl);
93
- const local = await probeLocalAdminConsole();
94
- if (local) return local;
95
- const cwd = options?.cwd ?? process.cwd();
96
- if (isInsideMushiMonorepo(cwd)) return LOCAL_ADMIN_URL;
97
- return HOSTED_CONSOLE_BASE;
98
- }
99
- function projectIdHint(base) {
100
- return `Where to find it: ${consoleUrl(base, "/projects")} \u2192 select your project \u2192 click the UUID chip to copy (or copy it from the panel right after you create the project).`;
101
- }
102
- function apiKeyHint(base) {
103
- return `Where to find it: ${consoleUrl(base, "/onboarding?tab=verify")} \u2192 Generate API key (SDK ingest needs report:write \u2014 not Settings \u2192 BYOK keys). Treat it like a password \u2014 env file only, never commit.`;
104
- }
105
- function cliSetupDeepLink(base) {
106
- return consoleUrl(base, "/onboarding?tab=steps&setup=cli");
107
- }
108
- function reportsUrl(base, reportId) {
109
- return reportId ? consoleUrl(base, `/reports/${reportId}`) : consoleUrl(base, "/reports");
110
- }
111
- async function openInBrowser(url) {
112
- let parsed;
113
- try {
114
- parsed = new URL(url);
115
- } catch {
116
- return;
117
- }
118
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
119
- return;
120
- }
121
- const safeUrl = parsed.toString();
122
- const { spawn: spawn2 } = await import("child_process");
123
- const [command, args] = process.platform === "win32" ? (
124
- // Open via rundll32's FileProtocolHandler rather than `cmd /c start`.
125
- // `start` is a cmd builtin, so the URL would be re-parsed by cmd.exe and
126
- // characters like `&` / `|` / `^` could still break out — exactly the
127
- // injection surface CodeQL flags. rundll32 never invokes a shell: the URL
128
- // is handed to the default browser as one opaque argument.
129
- ["rundll32", ["url.dll,FileProtocolHandler", safeUrl]]
130
- ) : process.platform === "darwin" ? ["open", [safeUrl]] : ["xdg-open", [safeUrl]];
131
- await new Promise((resolve2) => {
132
- try {
133
- const child = spawn2(command, [...args], { stdio: "ignore", shell: false });
134
- child.on("error", () => resolve2());
135
- child.on("exit", () => resolve2());
136
- child.unref();
137
- } catch {
138
- resolve2();
139
- }
140
- });
141
- }
142
-
143
- // src/signals.ts
144
- var activeController = null;
145
- function getAbortSignal(external) {
146
- if (external) return external.signal;
147
- if (!activeController || activeController.signal.aborted) {
148
- activeController = new AbortController();
149
- }
150
- return activeController.signal;
151
- }
152
-
153
- // src/cli-shared.ts
154
- var API_TIMEOUT_MS = 15e3;
155
- async function apiCall(path, config, options = {}) {
156
- const endpoint = config.endpoint;
157
- if (!endpoint) {
158
- return {
159
- ok: false,
160
- error: {
161
- code: "NO_ENDPOINT",
162
- 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"
163
- }
164
- };
165
- }
166
- const controller = new AbortController();
167
- const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
168
- const signals = [controller.signal, getAbortSignal()];
169
- const compositeSignal = AbortSignal.any ? AbortSignal.any(signals) : controller.signal;
170
- try {
171
- const res = await fetch(`${endpoint}${path}`, {
172
- ...options,
173
- signal: compositeSignal,
174
- headers: {
175
- "Content-Type": "application/json",
176
- // `apiKeyAuth` reads X-Mushi-Api-Key; the Authorization header is a
177
- // fallback accepted by some older middleware. Both are sent.
178
- "Authorization": `Bearer ${config.apiKey ?? ""}`,
179
- "X-Mushi-Api-Key": config.apiKey ?? "",
180
- "X-Mushi-Project": config.projectId ?? "",
181
- "X-Mushi-Cli-Version": MUSHI_CLI_VERSION,
182
- ...options.headers
183
- }
184
- });
185
- clearTimeout(timer);
186
- let body;
187
- const contentType = res.headers.get("content-type") ?? "";
188
- if (contentType.includes("application/json")) {
189
- try {
190
- body = await res.json();
191
- } catch {
192
- body = null;
193
- }
194
- } else {
195
- const text2 = await res.text();
196
- try {
197
- body = JSON.parse(text2);
198
- } catch {
199
- body = {
200
- ok: false,
201
- error: {
202
- code: `HTTP_${res.status}`,
203
- message: text2.trim().slice(0, 300) || `HTTP ${res.status}`
204
- }
205
- };
206
- }
207
- }
208
- if (!res.ok && typeof body === "object" && body !== null && !("ok" in body)) {
209
- const b = body;
210
- const nested = b["error"];
211
- const errCode = nested?.["code"] ?? b["code"] ?? `HTTP_${res.status}`;
212
- const errMsg = nested?.["message"] ?? b["message"] ?? `Request failed (${res.status})`;
213
- return {
214
- ok: false,
215
- httpStatus: res.status,
216
- error: { code: errCode, message: errMsg }
217
- };
218
- }
219
- return body;
220
- } catch (err) {
221
- clearTimeout(timer);
222
- if (err instanceof Error && err.name === "AbortError") {
223
- return {
224
- ok: false,
225
- error: {
226
- code: "TIMEOUT",
227
- message: `Request timed out after ${API_TIMEOUT_MS / 1e3}s. Check your network or endpoint.`
228
- }
229
- };
230
- }
231
- return {
232
- ok: false,
233
- error: {
234
- code: "NETWORK_ERROR",
235
- message: err instanceof Error ? err.message : String(err)
236
- }
237
- };
238
- }
239
- }
33
+ import { appendFileSync, existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
34
+ import { join as join2 } from "path";
240
35
 
241
36
  // src/device-auth.ts
242
37
  var DEVICE_FETCH_TIMEOUT_MS = 15e3;
@@ -350,7 +145,11 @@ async function listProjects(endpoint, cliToken) {
350
145
  const res = await deviceFetch(`${base}/v1/cli/projects`, { headers: cliAuthHeaders(cliToken) });
351
146
  const json = await res.json().catch(() => null);
352
147
  if (res.ok && json?.ok) return json.data?.projects ?? [];
353
- } catch {
148
+ } catch (err) {
149
+ console.error(
150
+ "[mushi] Failed to list projects:",
151
+ err instanceof Error ? err.message : err
152
+ );
354
153
  }
355
154
  return [];
356
155
  }
@@ -443,8 +242,8 @@ function parse(version) {
443
242
  }
444
243
 
445
244
  // src/monorepo.ts
446
- import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
447
- import { dirname as dirname2, join as join2, resolve } from "path";
245
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
246
+ import { dirname, join, resolve } from "path";
448
247
  var WORKSPACE_GLOB_CANDIDATES = ["apps/*", "packages/*", "examples/*"];
449
248
  var FRAMEWORK_DEPS = {
450
249
  next: "Next.js",
@@ -461,9 +260,9 @@ var FRAMEWORK_DEPS = {
461
260
  function detectWorkspaceHint(cwd) {
462
261
  const root = findWorkspaceRoot(cwd);
463
262
  if (!root) return null;
464
- const rootPkg = readPackageJsonSafely(join2(root, "package.json"));
263
+ const rootPkg = readPackageJsonSafely(join(root, "package.json"));
465
264
  if (rootPkg && getFrameworkFromPkg(rootPkg)) return null;
466
- const source = existsSync2(join2(root, "pnpm-workspace.yaml")) ? "pnpm-workspace" : root === cwd ? "package-json" : "parent";
265
+ const source = existsSync(join(root, "pnpm-workspace.yaml")) ? "pnpm-workspace" : root === cwd ? "package-json" : "parent";
467
266
  const apps = collectAppsFromGlobs(root);
468
267
  if (apps.length === 0) return null;
469
268
  return { root, apps, source };
@@ -472,15 +271,15 @@ function findWorkspaceRoot(start) {
472
271
  let dir = resolve(start);
473
272
  for (let i = 0; i < 8; i++) {
474
273
  if (isWorkspaceRoot(dir)) return dir;
475
- const parent = dirname2(dir);
274
+ const parent = dirname(dir);
476
275
  if (parent === dir) break;
477
276
  dir = parent;
478
277
  }
479
278
  return null;
480
279
  }
481
280
  function isWorkspaceRoot(dir) {
482
- if (existsSync2(join2(dir, "pnpm-workspace.yaml"))) return true;
483
- const pkg = readPackageJsonSafely(join2(dir, "package.json"));
281
+ if (existsSync(join(dir, "pnpm-workspace.yaml"))) return true;
282
+ const pkg = readPackageJsonSafely(join(dir, "package.json"));
484
283
  if (!pkg) return false;
485
284
  return Boolean(pkg.workspaces);
486
285
  }
@@ -488,8 +287,8 @@ function collectAppsFromGlobs(root) {
488
287
  const results = [];
489
288
  for (const glob of WORKSPACE_GLOB_CANDIDATES) {
490
289
  const prefix = glob.replace("/*", "");
491
- const parentDir = join2(root, prefix);
492
- if (!existsSync2(parentDir)) continue;
290
+ const parentDir = join(root, prefix);
291
+ if (!existsSync(parentDir)) continue;
493
292
  let entries;
494
293
  try {
495
294
  entries = readdirSync(parentDir);
@@ -497,7 +296,7 @@ function collectAppsFromGlobs(root) {
497
296
  continue;
498
297
  }
499
298
  for (const entry of entries) {
500
- const pkgPath = join2(parentDir, entry, "package.json");
299
+ const pkgPath = join(parentDir, entry, "package.json");
501
300
  if (!isFileSafe(pkgPath)) continue;
502
301
  const pkg = readPackageJsonSafely(pkgPath);
503
302
  if (!pkg) continue;
@@ -515,14 +314,14 @@ function collectAppsFromGlobs(root) {
515
314
  function readPackageJsonSafely(path) {
516
315
  if (!isFileSafe(path)) return null;
517
316
  try {
518
- return JSON.parse(readFileSync2(path, "utf-8"));
317
+ return JSON.parse(readFileSync(path, "utf-8"));
519
318
  } catch {
520
319
  return null;
521
320
  }
522
321
  }
523
322
  function isFileSafe(path) {
524
323
  try {
525
- return existsSync2(path) && statSync(path).isFile();
324
+ return existsSync(path) && statSync(path).isFile();
526
325
  } catch {
527
326
  return false;
528
327
  }
@@ -909,10 +708,10 @@ function runCommand(pm, packages, cwd) {
909
708
  });
910
709
  }
911
710
  async function writeEnvFile(cwd, apiKey, projectId, framework, endpoint, overwrite) {
912
- const target = ENV_FILES.find((f) => existsSync3(join3(cwd, f))) ?? ENV_FILES[0];
913
- const targetPath = join3(cwd, target);
711
+ const target = ENV_FILES.find((f) => existsSync2(join2(cwd, f))) ?? ENV_FILES[0];
712
+ const targetPath = join2(cwd, target);
914
713
  const newVars = envVarsToWrite(apiKey, projectId, framework, endpoint);
915
- const existing = existsSync3(targetPath) ? readFileSync3(targetPath, "utf-8") : "";
714
+ const existing = existsSync2(targetPath) ? readFileSync2(targetPath, "utf-8") : "";
916
715
  if (existing.includes("MUSHI_PROJECT_ID")) {
917
716
  let shouldOverwrite = overwrite;
918
717
  if (!shouldOverwrite) {
@@ -971,12 +770,12 @@ function escapeRegexChar(ch) {
971
770
  return /[-/\\^$+?.()|[\]{}]/.test(ch) ? `\\${ch}` : ch;
972
771
  }
973
772
  function warnIfMissingFromGitignore(cwd, envFile) {
974
- const gitignorePath = join3(cwd, ".gitignore");
975
- if (!existsSync3(gitignorePath)) {
773
+ const gitignorePath = join2(cwd, ".gitignore");
774
+ if (!existsSync2(gitignorePath)) {
976
775
  p.log.warn(`No .gitignore found \u2014 make sure ${envFile} is not committed.`);
977
776
  return;
978
777
  }
979
- const content = readFileSync3(gitignorePath, "utf-8");
778
+ const content = readFileSync2(gitignorePath, "utf-8");
980
779
  if (!isEnvFileCoveredByGitignore(content, envFile)) {
981
780
  p.log.warn(`${envFile} is not in .gitignore \u2014 add it before committing.`);
982
781
  }
@@ -1018,7 +817,7 @@ async function maybeOfferConnect(credentials, options, _consoleBase) {
1018
817
  if (p.isCancel(answer) || !answer) return;
1019
818
  const endpoint = resolveCloudEndpoint(options.endpoint);
1020
819
  try {
1021
- const { runConnect } = await import("./connect-2RM43XHO.js");
820
+ const { runConnect } = await import("./connect-KX4UGY6Z.js");
1022
821
  await runConnect({
1023
822
  apiKey: credentials.apiKey,
1024
823
  projectId: credentials.projectId,
package/dist/version.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MUSHI_CLI_VERSION
3
- } from "./chunk-5KN6S63C.js";
3
+ } from "./chunk-BY5SZYRO.js";
4
4
  export {
5
5
  MUSHI_CLI_VERSION
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mushi-mushi/cli",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "license": "MIT",
5
5
  "description": "CLI for Mushi Mushi — `mushi init` wizard installs the right SDK for your framework, plus report triage and pipeline health commands",
6
6
  "bin": {
@@ -47,18 +47,19 @@
47
47
  "sideEffects": false,
48
48
  "keywords": [
49
49
  "mushi-mushi",
50
- "bug-reporting",
51
- "user-feedback",
52
- "user-report",
53
- "feedback-widget",
54
- "session-replay",
55
- "screenshot",
56
- "shake-to-report",
57
- "sdk",
50
+ "bug-translation",
51
+ "vibe-coding",
52
+ "ai-debugging",
53
+ "ai-triage",
54
+ "auto-fix",
55
+ "mcp",
56
+ "cursor",
57
+ "claude-code",
58
+ "cli",
58
59
  "wizard",
59
60
  "setup",
60
- "cli",
61
61
  "init",
62
+ "sdk",
62
63
  "next.js",
63
64
  "nextjs",
64
65
  "react",
@@ -72,10 +73,7 @@
72
73
  "capacitor",
73
74
  "sentry-companion",
74
75
  "sentry-alternative",
75
- "error-tracking",
76
- "ai-triage",
77
- "auto-fix",
78
- "llm-ops"
76
+ "ai-agent"
79
77
  ],
80
78
  "exports": {
81
79
  ".": {
@@ -1,6 +0,0 @@
1
- // src/version.ts
2
- var MUSHI_CLI_VERSION = true ? "0.22.0" : "0.0.0-dev";
3
-
4
- export {
5
- MUSHI_CLI_VERSION
6
- };
@@ -1,137 +0,0 @@
1
- // src/config.ts
2
- import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
3
- import { homedir } from "os";
4
- import { dirname, join } from "path";
5
- var SECURE_FILE_MODE = 384;
6
- var SECURE_DIR_MODE = 448;
7
- function resolveXdgConfigPath() {
8
- const xdg = process.env["XDG_CONFIG_HOME"];
9
- if (xdg && xdg.length > 0) {
10
- return join(xdg, "mushi", "config.json");
11
- }
12
- if (process.platform === "win32") {
13
- const appData = process.env["APPDATA"];
14
- if (appData && appData.length > 0) {
15
- return join(appData, "mushi", "config.json");
16
- }
17
- }
18
- return join(homedir(), ".config", "mushi", "config.json");
19
- }
20
- var LEGACY_CONFIG_PATH = join(homedir(), ".mushirc");
21
- var CONFIG_PATH = resolveXdgConfigPath();
22
- function loadConfig(path = CONFIG_PATH) {
23
- let file = {};
24
- if (existsSync(path)) {
25
- tightenPermissions(path);
26
- try {
27
- file = JSON.parse(readFileSync(path, "utf-8"));
28
- } catch {
29
- }
30
- } else if (path === CONFIG_PATH && existsSync(LEGACY_CONFIG_PATH)) {
31
- file = migrateLegacyConfig() ?? {};
32
- }
33
- const endpointFromEnv = process.env["MUSHI_API_ENDPOINT"] ?? process.env["MUSHI_ENDPOINT"] ?? void 0;
34
- const fromEnv = {
35
- ...process.env["MUSHI_API_KEY"] ? { apiKey: process.env["MUSHI_API_KEY"] } : {},
36
- ...process.env["MUSHI_PROJECT_ID"] ? { projectId: process.env["MUSHI_PROJECT_ID"] } : {},
37
- ...endpointFromEnv ? { endpoint: endpointFromEnv } : {},
38
- ...process.env["MUSHI_CONSOLE_URL"] ? { consoleUrl: process.env["MUSHI_CONSOLE_URL"].trim() } : {}
39
- };
40
- return { ...file, ...fromEnv };
41
- }
42
- function saveConfig(config, path = CONFIG_PATH) {
43
- const dir = dirname(path);
44
- if (!existsSync(dir)) {
45
- mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
46
- } else {
47
- tightenDirPermissions(dir);
48
- }
49
- writeFileSync(path, JSON.stringify(config, null, 2), { mode: SECURE_FILE_MODE });
50
- tightenPermissions(path);
51
- }
52
- function migrateLegacyConfig(legacyPath = LEGACY_CONFIG_PATH, destPath = CONFIG_PATH) {
53
- if (!existsSync(legacyPath)) return null;
54
- let parsed;
55
- try {
56
- parsed = JSON.parse(readFileSync(legacyPath, "utf-8"));
57
- } catch {
58
- return null;
59
- }
60
- const dir = dirname(destPath);
61
- if (!existsSync(dir)) {
62
- mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
63
- }
64
- try {
65
- renameSync(legacyPath, destPath);
66
- } catch {
67
- writeFileSync(destPath, JSON.stringify(parsed, null, 2), { mode: SECURE_FILE_MODE });
68
- try {
69
- unlinkSync(legacyPath);
70
- } catch {
71
- }
72
- }
73
- tightenPermissions(destPath);
74
- tightenDirPermissions(dir);
75
- return parsed;
76
- }
77
- function tightenPermissions(path) {
78
- if (process.platform === "win32") return;
79
- try {
80
- const current = statSync(path).mode & 511;
81
- if (current !== SECURE_FILE_MODE) chmodSync(path, SECURE_FILE_MODE);
82
- } catch {
83
- }
84
- }
85
- function tightenDirPermissions(path) {
86
- if (process.platform === "win32") return;
87
- try {
88
- const current = statSync(path).mode & 511;
89
- if (current !== SECURE_DIR_MODE) chmodSync(path, SECURE_DIR_MODE);
90
- } catch {
91
- }
92
- }
93
-
94
- // src/endpoint.ts
95
- var TEST_REPORT_TIMEOUT_MS = 1e4;
96
- var TEST_REPORT_FETCH_TIMEOUT_MS = TEST_REPORT_TIMEOUT_MS;
97
- var CLOUD_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
98
- function resolveCloudEndpoint(explicit) {
99
- if (explicit?.trim()) return explicit.trim();
100
- const fromEnv = process.env.MUSHI_API_ENDPOINT?.trim();
101
- if (fromEnv) return fromEnv;
102
- return CLOUD_API_ENDPOINT;
103
- }
104
- function assertEndpoint(url) {
105
- let parsed;
106
- try {
107
- parsed = new URL(url);
108
- } catch {
109
- throw new Error(`Invalid endpoint URL: ${url}`);
110
- }
111
- const host = parsed.hostname;
112
- const isLocal = host === "localhost" || host === "127.0.0.1" || host === "::1" || host.endsWith(".local");
113
- if (parsed.protocol !== "https:" && !isLocal) {
114
- throw new Error(`Endpoint must use https:// (got ${parsed.protocol}//${host}).`);
115
- }
116
- return normalizeEndpoint(parsed.origin + parsed.pathname);
117
- }
118
- function normalizeEndpoint(url) {
119
- if (!url) {
120
- throw new Error(
121
- "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"
122
- );
123
- }
124
- let end = url.length;
125
- while (end > 0 && url.charCodeAt(end - 1) === 47) end--;
126
- return url.slice(0, end);
127
- }
128
-
129
- export {
130
- CONFIG_PATH,
131
- loadConfig,
132
- saveConfig,
133
- TEST_REPORT_FETCH_TIMEOUT_MS,
134
- resolveCloudEndpoint,
135
- assertEndpoint,
136
- normalizeEndpoint
137
- };