@mushi-mushi/cli 0.18.2 → 0.19.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/CONTRIBUTING.md +12 -0
- package/README.md +85 -24
- package/dist/chunk-D5IDNBJO.js +750 -0
- package/dist/chunk-T4XX5FMH.js +6 -0
- package/dist/chunk-XEXYGDQV.js +137 -0
- package/dist/connect-IC7I76Z2.js +297 -0
- package/dist/connect-W2Q6N7K3.js +8 -0
- package/dist/index.js +682 -744
- package/dist/init.js +317 -116
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-A2NHESSJ.js +0 -313
- package/dist/chunk-E4IZPV6D.js +0 -6
- package/dist/cli-shared-XH3XYIS7.js +0 -18
package/dist/init.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MUSHI_CLI_VERSION
|
|
3
|
+
} from "./chunk-T4XX5FMH.js";
|
|
4
|
+
import {
|
|
5
|
+
TEST_REPORT_FETCH_TIMEOUT_MS,
|
|
6
|
+
loadConfig,
|
|
7
|
+
normalizeEndpoint,
|
|
8
|
+
resolveCloudEndpoint,
|
|
9
|
+
saveConfig
|
|
10
|
+
} from "./chunk-XEXYGDQV.js";
|
|
1
11
|
import {
|
|
2
12
|
FRAMEWORKS,
|
|
3
13
|
detectFramework,
|
|
@@ -6,9 +16,6 @@ import {
|
|
|
6
16
|
installCommand,
|
|
7
17
|
readPackageJson
|
|
8
18
|
} from "./chunk-7Q44KWGN.js";
|
|
9
|
-
import {
|
|
10
|
-
MUSHI_CLI_VERSION
|
|
11
|
-
} from "./chunk-E4IZPV6D.js";
|
|
12
19
|
|
|
13
20
|
// src/init.ts
|
|
14
21
|
import * as p from "@clack/prompts";
|
|
@@ -17,117 +24,191 @@ import { randomUUID } from "crypto";
|
|
|
17
24
|
import { appendFileSync, existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
18
25
|
import { join as join3 } from "path";
|
|
19
26
|
|
|
20
|
-
// src/
|
|
21
|
-
import {
|
|
22
|
-
import { homedir } from "os";
|
|
27
|
+
// src/console-url.ts
|
|
28
|
+
import { existsSync, readFileSync } from "fs";
|
|
23
29
|
import { dirname, join } from "path";
|
|
24
|
-
var
|
|
25
|
-
var
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
+
return url.trim().replace(/\/+$/, "");
|
|
35
|
+
}
|
|
36
|
+
function consoleUrl(base, route) {
|
|
37
|
+
const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
|
|
38
|
+
return `${normalizeConsoleBase(base)}${normalizedRoute}`;
|
|
39
|
+
}
|
|
40
|
+
function resolveConsoleUrlFromEnv() {
|
|
41
|
+
const raw = process.env["MUSHI_CONSOLE_URL"]?.trim();
|
|
42
|
+
return raw ? normalizeConsoleBase(raw) : void 0;
|
|
43
|
+
}
|
|
44
|
+
function isInsideMushiMonorepo(cwd) {
|
|
45
|
+
let dir = cwd;
|
|
46
|
+
for (let depth = 0; depth < 14; depth += 1) {
|
|
47
|
+
const pkgPath = join(dir, "package.json");
|
|
48
|
+
if (existsSync(pkgPath)) {
|
|
49
|
+
try {
|
|
50
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
51
|
+
if (pkg.name === "mushi-mushi") return true;
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
48
54
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const endpointFromEnv = process.env["MUSHI_API_ENDPOINT"] ?? process.env["MUSHI_ENDPOINT"] ?? void 0;
|
|
53
|
-
const fromEnv = {
|
|
54
|
-
...process.env["MUSHI_API_KEY"] ? { apiKey: process.env["MUSHI_API_KEY"] } : {},
|
|
55
|
-
...process.env["MUSHI_PROJECT_ID"] ? { projectId: process.env["MUSHI_PROJECT_ID"] } : {},
|
|
56
|
-
...endpointFromEnv ? { endpoint: endpointFromEnv } : {}
|
|
57
|
-
};
|
|
58
|
-
return { ...file, ...fromEnv };
|
|
59
|
-
}
|
|
60
|
-
function saveConfig(config, path = CONFIG_PATH) {
|
|
61
|
-
const dir = dirname(path);
|
|
62
|
-
if (!existsSync(dir)) {
|
|
63
|
-
mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
64
|
-
} else {
|
|
65
|
-
tightenDirPermissions(dir);
|
|
55
|
+
const parent = dirname(dir);
|
|
56
|
+
if (parent === dir) break;
|
|
57
|
+
dir = parent;
|
|
66
58
|
}
|
|
67
|
-
|
|
68
|
-
tightenPermissions(path);
|
|
59
|
+
return false;
|
|
69
60
|
}
|
|
70
|
-
function
|
|
71
|
-
if (!existsSync(legacyPath)) return null;
|
|
72
|
-
let parsed;
|
|
73
|
-
try {
|
|
74
|
-
parsed = JSON.parse(readFileSync(legacyPath, "utf-8"));
|
|
75
|
-
} catch {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
const dir = dirname(destPath);
|
|
79
|
-
if (!existsSync(dir)) {
|
|
80
|
-
mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
81
|
-
}
|
|
61
|
+
async function probeLocalAdminConsole() {
|
|
82
62
|
try {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
63
|
+
const controller = new AbortController();
|
|
64
|
+
const timer = setTimeout(() => controller.abort(), LOCAL_PROBE_MS);
|
|
65
|
+
const res = await fetch(LOCAL_ADMIN_URL, {
|
|
66
|
+
signal: controller.signal,
|
|
67
|
+
method: "HEAD"
|
|
68
|
+
});
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
if (res.ok || res.status === 404 || res.status === 401) {
|
|
71
|
+
return LOCAL_ADMIN_URL;
|
|
89
72
|
}
|
|
90
|
-
}
|
|
91
|
-
tightenPermissions(destPath);
|
|
92
|
-
tightenDirPermissions(dir);
|
|
93
|
-
return parsed;
|
|
94
|
-
}
|
|
95
|
-
function tightenPermissions(path) {
|
|
96
|
-
if (process.platform === "win32") return;
|
|
97
|
-
try {
|
|
98
|
-
const current = statSync(path).mode & 511;
|
|
99
|
-
if (current !== SECURE_FILE_MODE) chmodSync(path, SECURE_FILE_MODE);
|
|
100
73
|
} catch {
|
|
101
74
|
}
|
|
75
|
+
return null;
|
|
102
76
|
}
|
|
103
|
-
function
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
77
|
+
function resolveConsoleUrlSync(cwd = process.cwd()) {
|
|
78
|
+
const fromEnv = resolveConsoleUrlFromEnv();
|
|
79
|
+
if (fromEnv) return fromEnv;
|
|
80
|
+
const config = loadConfig();
|
|
81
|
+
if (config.consoleUrl) return normalizeConsoleBase(config.consoleUrl);
|
|
82
|
+
if (isInsideMushiMonorepo(cwd)) return LOCAL_ADMIN_URL;
|
|
83
|
+
return HOSTED_CONSOLE_BASE;
|
|
110
84
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
var TEST_REPORT_TIMEOUT_MS = 1e4;
|
|
114
|
-
var TEST_REPORT_FETCH_TIMEOUT_MS = TEST_REPORT_TIMEOUT_MS;
|
|
115
|
-
var CLOUD_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
|
|
116
|
-
function resolveCloudEndpoint(explicit) {
|
|
117
|
-
if (explicit?.trim()) return explicit.trim();
|
|
118
|
-
const fromEnv = process.env.MUSHI_API_ENDPOINT?.trim();
|
|
85
|
+
async function resolveConsoleUrl(options) {
|
|
86
|
+
const fromEnv = resolveConsoleUrlFromEnv();
|
|
119
87
|
if (fromEnv) return fromEnv;
|
|
120
|
-
|
|
88
|
+
const config = loadConfig();
|
|
89
|
+
if (config.consoleUrl) return normalizeConsoleBase(config.consoleUrl);
|
|
90
|
+
const local = await probeLocalAdminConsole();
|
|
91
|
+
if (local) return local;
|
|
92
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
93
|
+
if (isInsideMushiMonorepo(cwd)) return LOCAL_ADMIN_URL;
|
|
94
|
+
return HOSTED_CONSOLE_BASE;
|
|
95
|
+
}
|
|
96
|
+
function projectIdHint(base) {
|
|
97
|
+
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).`;
|
|
98
|
+
}
|
|
99
|
+
function apiKeyHint(base) {
|
|
100
|
+
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.`;
|
|
101
|
+
}
|
|
102
|
+
function cliSetupDeepLink(base) {
|
|
103
|
+
return consoleUrl(base, "/onboarding?tab=steps&setup=cli");
|
|
104
|
+
}
|
|
105
|
+
function reportsUrl(base, reportId) {
|
|
106
|
+
return reportId ? consoleUrl(base, `/reports/${reportId}`) : consoleUrl(base, "/reports");
|
|
107
|
+
}
|
|
108
|
+
async function openInBrowser(url) {
|
|
109
|
+
const { exec } = await import("child_process");
|
|
110
|
+
const openCmd = process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
|
|
111
|
+
await new Promise((resolve2) => {
|
|
112
|
+
exec(openCmd, () => resolve2());
|
|
113
|
+
});
|
|
121
114
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
115
|
+
|
|
116
|
+
// src/signals.ts
|
|
117
|
+
var activeController = null;
|
|
118
|
+
function getAbortSignal(external) {
|
|
119
|
+
if (external) return external.signal;
|
|
120
|
+
if (!activeController || activeController.signal.aborted) {
|
|
121
|
+
activeController = new AbortController();
|
|
122
|
+
}
|
|
123
|
+
return activeController.signal;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/cli-shared.ts
|
|
127
|
+
var API_TIMEOUT_MS = 15e3;
|
|
128
|
+
async function apiCall(path, config, options = {}) {
|
|
129
|
+
const endpoint = config.endpoint;
|
|
130
|
+
if (!endpoint) {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
error: {
|
|
134
|
+
code: "NO_ENDPOINT",
|
|
135
|
+
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"
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const controller = new AbortController();
|
|
140
|
+
const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
|
|
141
|
+
const signals = [controller.signal, getAbortSignal()];
|
|
142
|
+
const compositeSignal = AbortSignal.any ? AbortSignal.any(signals) : controller.signal;
|
|
143
|
+
try {
|
|
144
|
+
const res = await fetch(`${endpoint}${path}`, {
|
|
145
|
+
...options,
|
|
146
|
+
signal: compositeSignal,
|
|
147
|
+
headers: {
|
|
148
|
+
"Content-Type": "application/json",
|
|
149
|
+
// `apiKeyAuth` reads X-Mushi-Api-Key; the Authorization header is a
|
|
150
|
+
// fallback accepted by some older middleware. Both are sent.
|
|
151
|
+
"Authorization": `Bearer ${config.apiKey ?? ""}`,
|
|
152
|
+
"X-Mushi-Api-Key": config.apiKey ?? "",
|
|
153
|
+
"X-Mushi-Project": config.projectId ?? "",
|
|
154
|
+
"X-Mushi-Cli-Version": MUSHI_CLI_VERSION,
|
|
155
|
+
...options.headers
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
clearTimeout(timer);
|
|
159
|
+
let body;
|
|
160
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
161
|
+
if (contentType.includes("application/json")) {
|
|
162
|
+
try {
|
|
163
|
+
body = await res.json();
|
|
164
|
+
} catch {
|
|
165
|
+
body = null;
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
const text2 = await res.text();
|
|
169
|
+
try {
|
|
170
|
+
body = JSON.parse(text2);
|
|
171
|
+
} catch {
|
|
172
|
+
body = {
|
|
173
|
+
ok: false,
|
|
174
|
+
error: {
|
|
175
|
+
code: `HTTP_${res.status}`,
|
|
176
|
+
message: text2.trim().slice(0, 300) || `HTTP ${res.status}`
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (!res.ok && typeof body === "object" && body !== null && !("ok" in body)) {
|
|
182
|
+
const b = body;
|
|
183
|
+
return {
|
|
184
|
+
ok: false,
|
|
185
|
+
httpStatus: res.status,
|
|
186
|
+
error: {
|
|
187
|
+
code: b["code"] ?? `HTTP_${res.status}`,
|
|
188
|
+
message: b["message"] ?? `Request failed (${res.status})`
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return body;
|
|
193
|
+
} catch (err) {
|
|
194
|
+
clearTimeout(timer);
|
|
195
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
196
|
+
return {
|
|
197
|
+
ok: false,
|
|
198
|
+
error: {
|
|
199
|
+
code: "TIMEOUT",
|
|
200
|
+
message: `Request timed out after ${API_TIMEOUT_MS / 1e3}s. Check your network or endpoint.`
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
ok: false,
|
|
206
|
+
error: {
|
|
207
|
+
code: "NETWORK_ERROR",
|
|
208
|
+
message: err instanceof Error ? err.message : String(err)
|
|
209
|
+
}
|
|
210
|
+
};
|
|
127
211
|
}
|
|
128
|
-
let end = url.length;
|
|
129
|
-
while (end > 0 && url.charCodeAt(end - 1) === 47) end--;
|
|
130
|
-
return url.slice(0, end);
|
|
131
212
|
}
|
|
132
213
|
|
|
133
214
|
// src/freshness.ts
|
|
@@ -190,7 +271,7 @@ function parse(version) {
|
|
|
190
271
|
}
|
|
191
272
|
|
|
192
273
|
// src/monorepo.ts
|
|
193
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync
|
|
274
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
|
|
194
275
|
import { dirname as dirname2, join as join2, resolve } from "path";
|
|
195
276
|
var WORKSPACE_GLOB_CANDIDATES = ["apps/*", "packages/*", "examples/*"];
|
|
196
277
|
var FRAMEWORK_DEPS = {
|
|
@@ -269,7 +350,7 @@ function readPackageJsonSafely(path) {
|
|
|
269
350
|
}
|
|
270
351
|
function isFileSafe(path) {
|
|
271
352
|
try {
|
|
272
|
-
return existsSync2(path) &&
|
|
353
|
+
return existsSync2(path) && statSync(path).isFile();
|
|
273
354
|
} catch {
|
|
274
355
|
return false;
|
|
275
356
|
}
|
|
@@ -310,7 +391,10 @@ async function runInit(options = {}) {
|
|
|
310
391
|
}
|
|
311
392
|
const detected = detectFramework(cwd, pkg);
|
|
312
393
|
const framework = await chooseFramework(detected, options);
|
|
313
|
-
const
|
|
394
|
+
const consoleBase = await resolveConsoleUrl({ cwd });
|
|
395
|
+
await maybeGuideConsoleSetup(options, consoleBase);
|
|
396
|
+
const credentials = await collectCredentials(options, consoleBase);
|
|
397
|
+
await verifyCredentials(credentials, options, consoleBase);
|
|
314
398
|
const pm = detectPackageManager(cwd);
|
|
315
399
|
const packagesToInstall = framework.needsWebPackage ? [framework.packageName, "@mushi-mushi/web"] : [framework.packageName];
|
|
316
400
|
if (!options.skipInstall) {
|
|
@@ -322,8 +406,9 @@ async function runInit(options = {}) {
|
|
|
322
406
|
writeEnvFile(cwd, credentials.apiKey, credentials.projectId, framework);
|
|
323
407
|
persistCliConfig(credentials.apiKey, credentials.projectId, endpoint);
|
|
324
408
|
const enableRewards = await maybeEnableRewards(options);
|
|
325
|
-
printNextSteps(framework,
|
|
326
|
-
await maybeSendTestReport(credentials, { ...options, endpoint });
|
|
409
|
+
printNextSteps(framework, consoleBase, enableRewards);
|
|
410
|
+
await maybeSendTestReport(credentials, { ...options, endpoint, consoleBase });
|
|
411
|
+
await maybeOfferConnect(credentials, options, consoleBase);
|
|
327
412
|
p.outro("Setup complete. Happy bug squashing \u{1F41B}");
|
|
328
413
|
}
|
|
329
414
|
function ensureInteractiveOrBailOut(options) {
|
|
@@ -364,25 +449,93 @@ async function chooseFramework(detected, options) {
|
|
|
364
449
|
}
|
|
365
450
|
return FRAMEWORKS[confirmed];
|
|
366
451
|
}
|
|
367
|
-
async function
|
|
452
|
+
async function maybeGuideConsoleSetup(options, consoleBase) {
|
|
453
|
+
if (options.yes) return;
|
|
454
|
+
if (options.projectId && options.apiKey) return;
|
|
455
|
+
const existing = loadConfig();
|
|
456
|
+
if (existing.projectId && existing.apiKey) return;
|
|
457
|
+
const choice = await p.select({
|
|
458
|
+
message: "Do you already have a Mushi project?",
|
|
459
|
+
options: [
|
|
460
|
+
{
|
|
461
|
+
value: "create",
|
|
462
|
+
label: "No \u2014 open the console to create one",
|
|
463
|
+
hint: "Recommended for first-time setup"
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
value: "have",
|
|
467
|
+
label: "Yes \u2014 I have Project ID + API key",
|
|
468
|
+
hint: "Paste values from the admin console"
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
value: "login",
|
|
472
|
+
label: "Use mushi login first",
|
|
473
|
+
hint: "Run `mushi login` in another terminal, then re-run this wizard"
|
|
474
|
+
}
|
|
475
|
+
]
|
|
476
|
+
});
|
|
477
|
+
if (p.isCancel(choice)) {
|
|
478
|
+
p.cancel("Aborted.");
|
|
479
|
+
process.exit(0);
|
|
480
|
+
}
|
|
481
|
+
if (choice === "login") {
|
|
482
|
+
p.log.info("Run `mushi login` to save credentials, then re-run `mushi init`.");
|
|
483
|
+
p.cancel("Aborted.");
|
|
484
|
+
process.exit(0);
|
|
485
|
+
}
|
|
486
|
+
if (choice !== "create") return;
|
|
487
|
+
const setupUrl = cliSetupDeepLink(consoleBase);
|
|
488
|
+
p.log.info(`Opening the Mushi console:
|
|
489
|
+
${setupUrl}`);
|
|
490
|
+
await openInBrowser(setupUrl);
|
|
491
|
+
p.log.message("In the console:");
|
|
492
|
+
p.log.message(" 1. Enter a project name and click Create");
|
|
493
|
+
p.log.message(" 2. Copy the Project ID from the success panel");
|
|
494
|
+
p.log.message(" 3. Generate an API key (Verify tab \u2014 report:write scope)");
|
|
495
|
+
p.log.message(" 4. Return here and paste both values");
|
|
496
|
+
const ready = await p.confirm({
|
|
497
|
+
message: "Created your project and ready to paste credentials?",
|
|
498
|
+
initialValue: false
|
|
499
|
+
});
|
|
500
|
+
if (p.isCancel(ready) || !ready) {
|
|
501
|
+
p.log.warn(`Finish in the console first: ${setupUrl}`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
async function collectCredentials(options, consoleBase) {
|
|
368
505
|
const existing = loadConfig();
|
|
506
|
+
if (!options.projectId && !options.apiKey && existing.projectId && existing.apiKey) {
|
|
507
|
+
const reuse = options.yes ? true : await p.confirm({
|
|
508
|
+
message: "Use Project ID + API key from ~/.config/mushi/config.json?",
|
|
509
|
+
initialValue: true
|
|
510
|
+
});
|
|
511
|
+
if (p.isCancel(reuse)) {
|
|
512
|
+
p.cancel("Aborted.");
|
|
513
|
+
process.exit(0);
|
|
514
|
+
}
|
|
515
|
+
if (reuse) {
|
|
516
|
+
return {
|
|
517
|
+
projectId: sanitizeSecret(existing.projectId),
|
|
518
|
+
apiKey: sanitizeSecret(existing.apiKey)
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
}
|
|
369
522
|
const rawProjectId = options.projectId ?? existing.projectId ?? await promptText({
|
|
370
523
|
message: "Project ID",
|
|
371
524
|
placeholder: "e.g. bdafa28d-b153-482f-bd4f-42981f3fd3a4",
|
|
372
|
-
hint:
|
|
373
|
-
validate: (v) => PROJECT_ID_PATTERN.test(v.trim()) ? void 0 : "Expected a UUID
|
|
525
|
+
hint: projectIdHint(consoleBase),
|
|
526
|
+
validate: (v) => PROJECT_ID_PATTERN.test(v.trim()) ? void 0 : "Expected a UUID \u2014 copy it from the Projects page or the panel right after you create a project."
|
|
374
527
|
});
|
|
375
528
|
const rawApiKey = options.apiKey ?? existing.apiKey ?? await promptText({
|
|
376
529
|
message: "API key",
|
|
377
530
|
placeholder: "mushi_xxxxxxxxxxxx",
|
|
378
|
-
hint:
|
|
531
|
+
hint: apiKeyHint(consoleBase),
|
|
379
532
|
validate: (v) => API_KEY_PATTERN.test(v) ? void 0 : "Expected format: mushi_ followed by 10+ alphanumeric characters"
|
|
380
533
|
});
|
|
381
534
|
const projectId = sanitizeSecret(rawProjectId);
|
|
382
535
|
const apiKey = sanitizeSecret(rawApiKey);
|
|
383
536
|
if (!PROJECT_ID_PATTERN.test(projectId)) {
|
|
384
537
|
throw new Error(
|
|
385
|
-
`Invalid project ID. Expected a UUID (e.g. bdafa28d-b153-482f-bd4f-42981f3fd3a4) or the proj_* prefixed form. Got: ${redact(projectId)} \u2014 copy it from
|
|
538
|
+
`Invalid project ID. Expected a UUID (e.g. bdafa28d-b153-482f-bd4f-42981f3fd3a4) or the proj_* prefixed form. Got: ${redact(projectId)} \u2014 copy it from ${projectIdHint(consoleBase)}`
|
|
386
539
|
);
|
|
387
540
|
}
|
|
388
541
|
if (!API_KEY_PATTERN.test(apiKey)) {
|
|
@@ -392,6 +545,23 @@ async function collectCredentials(options) {
|
|
|
392
545
|
}
|
|
393
546
|
return { projectId, apiKey };
|
|
394
547
|
}
|
|
548
|
+
async function verifyCredentials(credentials, options, consoleBase) {
|
|
549
|
+
const endpoint = resolveCloudEndpoint(options.endpoint);
|
|
550
|
+
const spinner2 = p.spinner();
|
|
551
|
+
spinner2.start("Verifying credentials\u2026");
|
|
552
|
+
const result = await apiCall(
|
|
553
|
+
"/v1/sync/whoami",
|
|
554
|
+
{ apiKey: credentials.apiKey, projectId: credentials.projectId, endpoint }
|
|
555
|
+
);
|
|
556
|
+
if (!result.ok) {
|
|
557
|
+
spinner2.stop("Credentials could not be verified.");
|
|
558
|
+
p.log.error(result.error?.message ?? "Authentication failed.");
|
|
559
|
+
p.log.info(`Double-check values in the console:
|
|
560
|
+
${cliSetupDeepLink(consoleBase)}`);
|
|
561
|
+
throw new Error("Credential verification failed \u2014 fix Project ID / API key and re-run.");
|
|
562
|
+
}
|
|
563
|
+
spinner2.stop(`Connected to ${result.data.project_name}`);
|
|
564
|
+
}
|
|
395
565
|
function sanitizeSecret(raw) {
|
|
396
566
|
return raw.trim().replace(/^['"]|['"]$/g, "").replace(/[\r\n\0]/g, "");
|
|
397
567
|
}
|
|
@@ -501,7 +671,7 @@ function persistCliConfig(apiKey, projectId, endpoint) {
|
|
|
501
671
|
const existing = loadConfig();
|
|
502
672
|
saveConfig({ ...existing, apiKey, projectId, endpoint });
|
|
503
673
|
}
|
|
504
|
-
function printNextSteps(framework,
|
|
674
|
+
function printNextSteps(framework, consoleBase, enableRewards = false) {
|
|
505
675
|
p.note(framework.snippet(), "Add this to your app:");
|
|
506
676
|
if (enableRewards) {
|
|
507
677
|
const badgeSnippet = framework.id === "react" ? `// Add to your user menu or profile UI:
|
|
@@ -512,13 +682,44 @@ import { MushiRewardsBadge } from '@mushi-mushi/react';
|
|
|
512
682
|
// import { MushiRewardsBadge } from '@mushi-mushi/react';
|
|
513
683
|
// <MushiRewardsBadge showPoints />`;
|
|
514
684
|
p.note(badgeSnippet, "Rewards badge snippet:");
|
|
515
|
-
p.log.info(
|
|
685
|
+
p.log.info(`Enable rewards in your project settings at ${consoleUrl(consoleBase, "/rewards")}`);
|
|
516
686
|
p.log.info("Users will earn points for bug reports, screen navigation, and app activity.");
|
|
517
687
|
}
|
|
518
688
|
p.log.message("Verify the install:");
|
|
519
689
|
p.log.message(" \u2022 Start your dev server");
|
|
520
690
|
p.log.message(" \u2022 Look for the \u{1F41B} button in the bottom-right corner (or shake on mobile)");
|
|
521
|
-
p.log.message(
|
|
691
|
+
p.log.message(` \u2022 Submit a test report \u2014 it should appear at ${reportsUrl(consoleBase)}`);
|
|
692
|
+
p.log.info(
|
|
693
|
+
"Tip: `mushi connect --write-env --wire-ide --wait` also wires Cursor MCP and waits for SDK heartbeat."
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
async function maybeOfferConnect(credentials, options, _consoleBase) {
|
|
697
|
+
if (options.yes) return;
|
|
698
|
+
const answer = await p.confirm({
|
|
699
|
+
message: "Run `mushi connect --write-env --wire-ide --wait` now? (SDK env + Cursor MCP + heartbeat check)",
|
|
700
|
+
initialValue: false
|
|
701
|
+
});
|
|
702
|
+
if (p.isCancel(answer) || !answer) return;
|
|
703
|
+
const endpoint = resolveCloudEndpoint(options.endpoint);
|
|
704
|
+
try {
|
|
705
|
+
const { runConnect } = await import("./connect-IC7I76Z2.js");
|
|
706
|
+
await runConnect({
|
|
707
|
+
apiKey: credentials.apiKey,
|
|
708
|
+
projectId: credentials.projectId,
|
|
709
|
+
endpoint,
|
|
710
|
+
cwd: options.cwd,
|
|
711
|
+
writeEnv: true,
|
|
712
|
+
wireIde: true,
|
|
713
|
+
wait: true
|
|
714
|
+
});
|
|
715
|
+
} catch (err) {
|
|
716
|
+
p.log.warn(
|
|
717
|
+
err instanceof Error ? err.message : String(err)
|
|
718
|
+
);
|
|
719
|
+
p.log.info(
|
|
720
|
+
"You can run manually: mushi connect --write-env --wire-ide --wait"
|
|
721
|
+
);
|
|
722
|
+
}
|
|
522
723
|
}
|
|
523
724
|
async function maybeEnableRewards(options) {
|
|
524
725
|
if (options.yes) return false;
|
|
@@ -589,8 +790,8 @@ async function maybeSendTestReport(credentials, options) {
|
|
|
589
790
|
reportId = body.data?.reportId;
|
|
590
791
|
} catch {
|
|
591
792
|
}
|
|
592
|
-
const
|
|
593
|
-
p.log.success(`View it at
|
|
793
|
+
const consoleBase = options.consoleBase ?? resolveConsoleUrlSync(options.cwd);
|
|
794
|
+
p.log.success(`View it at ${reportsUrl(consoleBase, reportId)}`);
|
|
594
795
|
} catch (err) {
|
|
595
796
|
const aborted = err instanceof Error && err.name === "AbortError";
|
|
596
797
|
spinner2.stop(aborted ? "Timed out reaching the Mushi API." : "Could not reach the Mushi API.");
|
package/dist/version.js
CHANGED
package/package.json
CHANGED