@mushi-mushi/cli 0.22.1 → 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/README.md +2 -2
- package/dist/{chunk-JAUKBLPK.js → chunk-AIZRI665.js} +102 -2
- package/dist/chunk-BY5SZYRO.js +6 -0
- package/dist/{chunk-R2HPR2FV.js → chunk-GSQO35YD.js} +520 -27
- package/dist/chunk-WLLFV26V.js +423 -0
- package/dist/{connect-2RM43XHO.js → connect-KX4UGY6Z.js} +10 -7
- package/dist/{connect-JIEIWTF3.js → connect-XFVSH4JZ.js} +1 -1
- package/dist/detect.d.ts +1 -1
- package/dist/detect.js +1 -1
- package/dist/index.js +75 -407
- package/dist/init.js +40 -241
- package/dist/version.js +1 -1
- package/package.json +12 -14
- package/dist/chunk-XEXYGDQV.js +0 -137
- package/dist/chunk-YK4LF7AM.js +0 -6
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MUSHI_CLI_VERSION
|
|
3
|
+
} from "./chunk-BY5SZYRO.js";
|
|
4
|
+
|
|
5
|
+
// src/config.ts
|
|
6
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { dirname, join } from "path";
|
|
9
|
+
var SECURE_FILE_MODE = 384;
|
|
10
|
+
var SECURE_DIR_MODE = 448;
|
|
11
|
+
function resolveXdgConfigPath() {
|
|
12
|
+
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
13
|
+
if (xdg && xdg.length > 0) {
|
|
14
|
+
return join(xdg, "mushi", "config.json");
|
|
15
|
+
}
|
|
16
|
+
if (process.platform === "win32") {
|
|
17
|
+
const appData = process.env["APPDATA"];
|
|
18
|
+
if (appData && appData.length > 0) {
|
|
19
|
+
return join(appData, "mushi", "config.json");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return join(homedir(), ".config", "mushi", "config.json");
|
|
23
|
+
}
|
|
24
|
+
var LEGACY_CONFIG_PATH = join(homedir(), ".mushirc");
|
|
25
|
+
var CONFIG_PATH = resolveXdgConfigPath();
|
|
26
|
+
function loadConfig(path = CONFIG_PATH) {
|
|
27
|
+
let file = {};
|
|
28
|
+
if (existsSync(path)) {
|
|
29
|
+
tightenPermissions(path);
|
|
30
|
+
try {
|
|
31
|
+
file = JSON.parse(readFileSync(path, "utf-8"));
|
|
32
|
+
} catch {
|
|
33
|
+
}
|
|
34
|
+
} else if (path === CONFIG_PATH && existsSync(LEGACY_CONFIG_PATH)) {
|
|
35
|
+
file = migrateLegacyConfig() ?? {};
|
|
36
|
+
}
|
|
37
|
+
const endpointFromEnv = process.env["MUSHI_API_ENDPOINT"] ?? process.env["MUSHI_ENDPOINT"] ?? void 0;
|
|
38
|
+
const fromEnv = {
|
|
39
|
+
...process.env["MUSHI_API_KEY"] ? { apiKey: process.env["MUSHI_API_KEY"] } : {},
|
|
40
|
+
...process.env["MUSHI_PROJECT_ID"] ? { projectId: process.env["MUSHI_PROJECT_ID"] } : {},
|
|
41
|
+
...endpointFromEnv ? { endpoint: endpointFromEnv } : {},
|
|
42
|
+
...process.env["MUSHI_CONSOLE_URL"] ? { consoleUrl: process.env["MUSHI_CONSOLE_URL"].trim() } : {}
|
|
43
|
+
};
|
|
44
|
+
return { ...file, ...fromEnv };
|
|
45
|
+
}
|
|
46
|
+
function saveConfig(config, path = CONFIG_PATH) {
|
|
47
|
+
const dir = dirname(path);
|
|
48
|
+
if (!existsSync(dir)) {
|
|
49
|
+
mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
50
|
+
} else {
|
|
51
|
+
tightenDirPermissions(dir);
|
|
52
|
+
}
|
|
53
|
+
writeFileSync(path, JSON.stringify(config, null, 2), { mode: SECURE_FILE_MODE });
|
|
54
|
+
tightenPermissions(path);
|
|
55
|
+
}
|
|
56
|
+
function migrateLegacyConfig(legacyPath = LEGACY_CONFIG_PATH, destPath = CONFIG_PATH) {
|
|
57
|
+
if (!existsSync(legacyPath)) return null;
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(readFileSync(legacyPath, "utf-8"));
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const dir = dirname(destPath);
|
|
65
|
+
if (!existsSync(dir)) {
|
|
66
|
+
mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
renameSync(legacyPath, destPath);
|
|
70
|
+
} catch {
|
|
71
|
+
writeFileSync(destPath, JSON.stringify(parsed, null, 2), { mode: SECURE_FILE_MODE });
|
|
72
|
+
try {
|
|
73
|
+
unlinkSync(legacyPath);
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
tightenPermissions(destPath);
|
|
78
|
+
tightenDirPermissions(dir);
|
|
79
|
+
return parsed;
|
|
80
|
+
}
|
|
81
|
+
function tightenPermissions(path) {
|
|
82
|
+
if (process.platform === "win32") return;
|
|
83
|
+
try {
|
|
84
|
+
const current = statSync(path).mode & 511;
|
|
85
|
+
if (current !== SECURE_FILE_MODE) chmodSync(path, SECURE_FILE_MODE);
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function tightenDirPermissions(path) {
|
|
90
|
+
if (process.platform === "win32") return;
|
|
91
|
+
try {
|
|
92
|
+
const current = statSync(path).mode & 511;
|
|
93
|
+
if (current !== SECURE_DIR_MODE) chmodSync(path, SECURE_DIR_MODE);
|
|
94
|
+
} catch {
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/console-url.ts
|
|
99
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
100
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
101
|
+
var HOSTED_CONSOLE_BASE = "https://kensaur.us/mushi-mushi/admin";
|
|
102
|
+
var LOCAL_ADMIN_URL = "http://localhost:6464";
|
|
103
|
+
var LOCAL_PROBE_MS = 800;
|
|
104
|
+
function normalizeConsoleBase(url) {
|
|
105
|
+
const trimmed = url.trim();
|
|
106
|
+
let end = trimmed.length;
|
|
107
|
+
while (end > 0 && trimmed.charCodeAt(end - 1) === 47) end--;
|
|
108
|
+
return trimmed.slice(0, end);
|
|
109
|
+
}
|
|
110
|
+
function consoleUrl(base, route) {
|
|
111
|
+
const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
|
|
112
|
+
return `${normalizeConsoleBase(base)}${normalizedRoute}`;
|
|
113
|
+
}
|
|
114
|
+
function resolveConsoleUrlFromEnv() {
|
|
115
|
+
const raw = process.env["MUSHI_CONSOLE_URL"]?.trim();
|
|
116
|
+
return raw ? normalizeConsoleBase(raw) : void 0;
|
|
117
|
+
}
|
|
118
|
+
function isInsideMushiMonorepo(cwd) {
|
|
119
|
+
let dir = cwd;
|
|
120
|
+
for (let depth = 0; depth < 14; depth += 1) {
|
|
121
|
+
const pkgPath = join2(dir, "package.json");
|
|
122
|
+
if (existsSync2(pkgPath)) {
|
|
123
|
+
try {
|
|
124
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
125
|
+
if (pkg.name === "mushi-mushi") return true;
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const parent = dirname2(dir);
|
|
130
|
+
if (parent === dir) break;
|
|
131
|
+
dir = parent;
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
async function probeLocalAdminConsole() {
|
|
136
|
+
try {
|
|
137
|
+
const controller = new AbortController();
|
|
138
|
+
const timer = setTimeout(() => controller.abort(), LOCAL_PROBE_MS);
|
|
139
|
+
const res = await fetch(LOCAL_ADMIN_URL, {
|
|
140
|
+
signal: controller.signal,
|
|
141
|
+
method: "HEAD"
|
|
142
|
+
});
|
|
143
|
+
clearTimeout(timer);
|
|
144
|
+
if (res.ok || res.status === 404 || res.status === 401) {
|
|
145
|
+
return LOCAL_ADMIN_URL;
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
function resolveConsoleUrlSync(cwd = process.cwd()) {
|
|
152
|
+
const fromEnv = resolveConsoleUrlFromEnv();
|
|
153
|
+
if (fromEnv) return fromEnv;
|
|
154
|
+
const config = loadConfig();
|
|
155
|
+
if (config.consoleUrl) return normalizeConsoleBase(config.consoleUrl);
|
|
156
|
+
if (isInsideMushiMonorepo(cwd)) return LOCAL_ADMIN_URL;
|
|
157
|
+
return HOSTED_CONSOLE_BASE;
|
|
158
|
+
}
|
|
159
|
+
async function resolveConsoleUrl(options) {
|
|
160
|
+
const fromEnv = resolveConsoleUrlFromEnv();
|
|
161
|
+
if (fromEnv) return fromEnv;
|
|
162
|
+
const config = loadConfig();
|
|
163
|
+
if (config.consoleUrl) return normalizeConsoleBase(config.consoleUrl);
|
|
164
|
+
const local = await probeLocalAdminConsole();
|
|
165
|
+
if (local) return local;
|
|
166
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
167
|
+
if (isInsideMushiMonorepo(cwd)) return LOCAL_ADMIN_URL;
|
|
168
|
+
return HOSTED_CONSOLE_BASE;
|
|
169
|
+
}
|
|
170
|
+
function projectIdHint(base) {
|
|
171
|
+
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).`;
|
|
172
|
+
}
|
|
173
|
+
function apiKeyHint(base) {
|
|
174
|
+
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.`;
|
|
175
|
+
}
|
|
176
|
+
function cliSetupDeepLink(base) {
|
|
177
|
+
return consoleUrl(base, "/onboarding?tab=steps&setup=cli");
|
|
178
|
+
}
|
|
179
|
+
function reportsUrl(base, reportId) {
|
|
180
|
+
return reportId ? consoleUrl(base, `/reports/${reportId}`) : consoleUrl(base, "/reports");
|
|
181
|
+
}
|
|
182
|
+
async function openInBrowser(url) {
|
|
183
|
+
let parsed;
|
|
184
|
+
try {
|
|
185
|
+
parsed = new URL(url);
|
|
186
|
+
} catch {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const safeUrl = parsed.toString();
|
|
193
|
+
const { spawn } = await import("child_process");
|
|
194
|
+
const [command, args] = process.platform === "win32" ? (
|
|
195
|
+
// Open via rundll32's FileProtocolHandler rather than `cmd /c start`.
|
|
196
|
+
// `start` is a cmd builtin, so the URL would be re-parsed by cmd.exe and
|
|
197
|
+
// characters like `&` / `|` / `^` could still break out — exactly the
|
|
198
|
+
// injection surface CodeQL flags. rundll32 never invokes a shell: the URL
|
|
199
|
+
// is handed to the default browser as one opaque argument.
|
|
200
|
+
["rundll32", ["url.dll,FileProtocolHandler", safeUrl]]
|
|
201
|
+
) : process.platform === "darwin" ? ["open", [safeUrl]] : ["xdg-open", [safeUrl]];
|
|
202
|
+
await new Promise((resolve) => {
|
|
203
|
+
try {
|
|
204
|
+
const child = spawn(command, [...args], { stdio: "ignore", shell: false });
|
|
205
|
+
child.on("error", () => resolve());
|
|
206
|
+
child.on("exit", () => resolve());
|
|
207
|
+
child.unref();
|
|
208
|
+
} catch {
|
|
209
|
+
resolve();
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/endpoint.ts
|
|
215
|
+
var TEST_REPORT_TIMEOUT_MS = 1e4;
|
|
216
|
+
var TEST_REPORT_FETCH_TIMEOUT_MS = TEST_REPORT_TIMEOUT_MS;
|
|
217
|
+
var CLOUD_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
|
|
218
|
+
function resolveCloudEndpoint(explicit) {
|
|
219
|
+
if (explicit?.trim()) return explicit.trim();
|
|
220
|
+
const fromEnv = process.env.MUSHI_API_ENDPOINT?.trim();
|
|
221
|
+
if (fromEnv) return fromEnv;
|
|
222
|
+
return CLOUD_API_ENDPOINT;
|
|
223
|
+
}
|
|
224
|
+
function assertEndpoint(url) {
|
|
225
|
+
let parsed;
|
|
226
|
+
try {
|
|
227
|
+
parsed = new URL(url);
|
|
228
|
+
} catch {
|
|
229
|
+
throw new Error(`Invalid endpoint URL: ${url}`);
|
|
230
|
+
}
|
|
231
|
+
const host = parsed.hostname;
|
|
232
|
+
const isLocal = host === "localhost" || host === "127.0.0.1" || host === "::1" || host.endsWith(".local");
|
|
233
|
+
if (parsed.protocol !== "https:" && !isLocal) {
|
|
234
|
+
throw new Error(`Endpoint must use https:// (got ${parsed.protocol}//${host}).`);
|
|
235
|
+
}
|
|
236
|
+
return normalizeEndpoint(parsed.origin + parsed.pathname);
|
|
237
|
+
}
|
|
238
|
+
function normalizeEndpoint(url) {
|
|
239
|
+
if (!url) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
"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"
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
let end = url.length;
|
|
245
|
+
while (end > 0 && url.charCodeAt(end - 1) === 47) end--;
|
|
246
|
+
return url.slice(0, end);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/errors.ts
|
|
250
|
+
var EXIT_CODE_MAP = {
|
|
251
|
+
E_AUTH_MISSING: 2,
|
|
252
|
+
E_AUTH_INVALID: 2,
|
|
253
|
+
E_PROJECT_MISSING: 2,
|
|
254
|
+
E_ENDPOINT_INVALID: 2,
|
|
255
|
+
E_INVALID_INPUT: 2,
|
|
256
|
+
E_NOT_INTERACTIVE: 2,
|
|
257
|
+
E_NETWORK: 3,
|
|
258
|
+
E_TIMEOUT: 3,
|
|
259
|
+
E_API_ERROR: 3,
|
|
260
|
+
E_RATE_LIMITED: 3,
|
|
261
|
+
E_FILE_NOT_FOUND: 1,
|
|
262
|
+
E_FILE_PERMISSION: 1,
|
|
263
|
+
E_FRESHNESS_STALE: 1,
|
|
264
|
+
E_INTERRUPTED: 130,
|
|
265
|
+
// 128 + SIGINT (2). POSIX convention.
|
|
266
|
+
E_INTERNAL: 1
|
|
267
|
+
};
|
|
268
|
+
var MushiCliError = class extends Error {
|
|
269
|
+
code;
|
|
270
|
+
hint;
|
|
271
|
+
cause;
|
|
272
|
+
constructor(code, message, hint, cause) {
|
|
273
|
+
super(message);
|
|
274
|
+
this.name = "MushiCliError";
|
|
275
|
+
this.code = code;
|
|
276
|
+
if (hint) this.hint = hint;
|
|
277
|
+
if (cause !== void 0) this.cause = cause;
|
|
278
|
+
}
|
|
279
|
+
/** Exit code POSIX-aware shell scripts should branch on. */
|
|
280
|
+
get exitCode() {
|
|
281
|
+
return EXIT_CODE_MAP[this.code];
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Format for `--json` output. Used by the JSON writer when a CLI
|
|
285
|
+
* subcommand was invoked with `--json` and an error escapes.
|
|
286
|
+
*/
|
|
287
|
+
toJSON() {
|
|
288
|
+
return {
|
|
289
|
+
error: {
|
|
290
|
+
code: this.code,
|
|
291
|
+
message: this.message,
|
|
292
|
+
...this.hint ? { hint: this.hint } : {}
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// src/signals.ts
|
|
299
|
+
var activeController = null;
|
|
300
|
+
function getAbortSignal(external) {
|
|
301
|
+
if (external) return external.signal;
|
|
302
|
+
if (!activeController || activeController.signal.aborted) {
|
|
303
|
+
activeController = new AbortController();
|
|
304
|
+
}
|
|
305
|
+
return activeController.signal;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/cli-shared.ts
|
|
309
|
+
var UUID_RX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
310
|
+
function requireUuid(value, label = "id") {
|
|
311
|
+
if (UUID_RX.test(value)) return value;
|
|
312
|
+
throw new MushiCliError(
|
|
313
|
+
"E_INVALID_INPUT",
|
|
314
|
+
`${label} must be a valid UUID`,
|
|
315
|
+
"copy the full id from `mushi reports list` or the console URL"
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
var API_TIMEOUT_MS = 15e3;
|
|
319
|
+
async function apiCall(path, config, options = {}) {
|
|
320
|
+
const endpoint = config.endpoint;
|
|
321
|
+
if (!endpoint) {
|
|
322
|
+
return {
|
|
323
|
+
ok: false,
|
|
324
|
+
error: {
|
|
325
|
+
code: "NO_ENDPOINT",
|
|
326
|
+
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"
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
const controller = new AbortController();
|
|
331
|
+
const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
|
|
332
|
+
const signals = [controller.signal, getAbortSignal()];
|
|
333
|
+
const compositeSignal = AbortSignal.any ? AbortSignal.any(signals) : controller.signal;
|
|
334
|
+
try {
|
|
335
|
+
const res = await fetch(`${endpoint}${path}`, {
|
|
336
|
+
...options,
|
|
337
|
+
signal: compositeSignal,
|
|
338
|
+
headers: {
|
|
339
|
+
"Content-Type": "application/json",
|
|
340
|
+
// `apiKeyAuth` reads X-Mushi-Api-Key; the Authorization header is a
|
|
341
|
+
// fallback accepted by some older middleware. Both are sent.
|
|
342
|
+
"Authorization": `Bearer ${config.apiKey ?? ""}`,
|
|
343
|
+
"X-Mushi-Api-Key": config.apiKey ?? "",
|
|
344
|
+
"X-Mushi-Project": config.projectId ?? "",
|
|
345
|
+
"X-Mushi-Cli-Version": MUSHI_CLI_VERSION,
|
|
346
|
+
...options.headers
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
clearTimeout(timer);
|
|
350
|
+
let body;
|
|
351
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
352
|
+
if (contentType.includes("application/json")) {
|
|
353
|
+
try {
|
|
354
|
+
body = await res.json();
|
|
355
|
+
} catch {
|
|
356
|
+
body = null;
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
const text = await res.text();
|
|
360
|
+
try {
|
|
361
|
+
body = JSON.parse(text);
|
|
362
|
+
} catch {
|
|
363
|
+
body = {
|
|
364
|
+
ok: false,
|
|
365
|
+
error: {
|
|
366
|
+
code: `HTTP_${res.status}`,
|
|
367
|
+
message: text.trim().slice(0, 300) || `HTTP ${res.status}`
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (!res.ok && typeof body === "object" && body !== null && !("ok" in body)) {
|
|
373
|
+
const b = body;
|
|
374
|
+
const nested = b["error"];
|
|
375
|
+
const errCode = nested?.["code"] ?? b["code"] ?? `HTTP_${res.status}`;
|
|
376
|
+
const errMsg = nested?.["message"] ?? b["message"] ?? `Request failed (${res.status})`;
|
|
377
|
+
return {
|
|
378
|
+
ok: false,
|
|
379
|
+
httpStatus: res.status,
|
|
380
|
+
error: { code: errCode, message: errMsg }
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
return body;
|
|
384
|
+
} catch (err) {
|
|
385
|
+
clearTimeout(timer);
|
|
386
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
387
|
+
return {
|
|
388
|
+
ok: false,
|
|
389
|
+
error: {
|
|
390
|
+
code: "TIMEOUT",
|
|
391
|
+
message: `Request timed out after ${API_TIMEOUT_MS / 1e3}s. Check your network or endpoint.`
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
return {
|
|
396
|
+
ok: false,
|
|
397
|
+
error: {
|
|
398
|
+
code: "NETWORK_ERROR",
|
|
399
|
+
message: err instanceof Error ? err.message : String(err)
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export {
|
|
406
|
+
CONFIG_PATH,
|
|
407
|
+
loadConfig,
|
|
408
|
+
saveConfig,
|
|
409
|
+
consoleUrl,
|
|
410
|
+
resolveConsoleUrlSync,
|
|
411
|
+
resolveConsoleUrl,
|
|
412
|
+
projectIdHint,
|
|
413
|
+
apiKeyHint,
|
|
414
|
+
cliSetupDeepLink,
|
|
415
|
+
reportsUrl,
|
|
416
|
+
openInBrowser,
|
|
417
|
+
TEST_REPORT_FETCH_TIMEOUT_MS,
|
|
418
|
+
resolveCloudEndpoint,
|
|
419
|
+
assertEndpoint,
|
|
420
|
+
normalizeEndpoint,
|
|
421
|
+
requireUuid,
|
|
422
|
+
apiCall
|
|
423
|
+
};
|
|
@@ -2,13 +2,15 @@ import {
|
|
|
2
2
|
CONFIG_PATH,
|
|
3
3
|
assertEndpoint,
|
|
4
4
|
normalizeEndpoint,
|
|
5
|
+
requireUuid,
|
|
5
6
|
saveConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-WLLFV26V.js";
|
|
7
8
|
import {
|
|
8
9
|
detectFramework,
|
|
9
10
|
envVarsToWrite,
|
|
10
11
|
readPackageJson
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-AIZRI665.js";
|
|
13
|
+
import "./chunk-BY5SZYRO.js";
|
|
12
14
|
|
|
13
15
|
// src/connect.ts
|
|
14
16
|
import { appendFile, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
@@ -217,11 +219,12 @@ async function ensureMcpJsonGitignored(cwd, messages) {
|
|
|
217
219
|
async function runConnect(opts, baseConfig = {}) {
|
|
218
220
|
const cwd = resolve(opts.cwd ?? process.cwd());
|
|
219
221
|
const endpoint = assertEndpoint(opts.endpoint);
|
|
222
|
+
const projectId = requireUuid(opts.projectId, "projectId");
|
|
220
223
|
const messages = [];
|
|
221
224
|
const config = {
|
|
222
225
|
...baseConfig,
|
|
223
226
|
apiKey: opts.apiKey,
|
|
224
|
-
projectId
|
|
227
|
+
projectId,
|
|
225
228
|
endpoint
|
|
226
229
|
};
|
|
227
230
|
saveConfig(config);
|
|
@@ -230,7 +233,7 @@ async function runConnect(opts, baseConfig = {}) {
|
|
|
230
233
|
if (opts.writeEnv !== false) {
|
|
231
234
|
const pkg = readPackageJson(cwd);
|
|
232
235
|
const framework = detectFramework(cwd, pkg);
|
|
233
|
-
const lines = envVarsToWrite(opts.apiKey,
|
|
236
|
+
const lines = envVarsToWrite(opts.apiKey, projectId, framework).split("\n");
|
|
234
237
|
envPath = join(cwd, ".env.local");
|
|
235
238
|
const wrote = await mergeEnvFile(envPath, lines);
|
|
236
239
|
messages.push(
|
|
@@ -247,10 +250,10 @@ async function runConnect(opts, baseConfig = {}) {
|
|
|
247
250
|
if (opts.wireIde !== false) {
|
|
248
251
|
const mcpDir = join(cwd, ".cursor");
|
|
249
252
|
mcpPath = join(mcpDir, "mcp.json");
|
|
250
|
-
const serverName = buildMcpServerName({ projectId
|
|
253
|
+
const serverName = buildMcpServerName({ projectId });
|
|
251
254
|
const serverBlock = buildMcpServerBlock({
|
|
252
255
|
endpoint,
|
|
253
|
-
projectId
|
|
256
|
+
projectId,
|
|
254
257
|
apiKey: opts.apiKey
|
|
255
258
|
});
|
|
256
259
|
await writeMcpServerEntry({ configPath: mcpPath, serverName, serverBlock });
|
|
@@ -265,7 +268,7 @@ async function runConnect(opts, baseConfig = {}) {
|
|
|
265
268
|
heartbeat = await waitForIngestReady({
|
|
266
269
|
endpoint,
|
|
267
270
|
apiKey: opts.apiKey,
|
|
268
|
-
projectId
|
|
271
|
+
projectId,
|
|
269
272
|
maxAttempts,
|
|
270
273
|
onPoll: (payload, attempt) => {
|
|
271
274
|
if (!opts.json && attempt % 3 === 0) {
|
package/dist/detect.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* PURPOSE: Pure detection helpers for framework, package manager, and project state.
|
|
4
4
|
* Kept side-effect-free so the wizard remains unit-testable.
|
|
5
5
|
*/
|
|
6
|
-
type FrameworkId = 'next' | 'react' | 'vue' | 'nuxt' | 'svelte' | 'sveltekit' | 'angular' | 'expo' | 'react-native' | 'capacitor' | 'express' | 'fastify' | 'hono' | 'vanilla';
|
|
6
|
+
type FrameworkId = 'next' | 'react' | 'cra' | 'remix' | 'astro' | 'solid' | 'vue' | 'nuxt' | 'svelte' | 'sveltekit' | 'angular' | 'expo' | 'react-native' | 'capacitor' | 'express' | 'fastify' | 'hono' | 'vanilla';
|
|
7
7
|
interface Framework {
|
|
8
8
|
id: FrameworkId;
|
|
9
9
|
label: string;
|