@mushi-mushi/cli 0.23.1 → 0.24.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/{chunk-M6L22NJU.js → chunk-BWASDQI6.js} +1 -1
- package/dist/{chunk-NZ64LBLU.js → chunk-GUVGTFVK.js} +1 -1
- package/dist/chunk-X74T7JKC.js +6 -0
- package/dist/{connect-IWFKKXGZ.js → connect-7LTQRBEW.js} +2 -2
- package/dist/{connect-5VPUYE6H.js → connect-KKN62YK2.js} +1 -1
- package/dist/index.js +298 -117
- package/dist/init.js +116 -46
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/dist/chunk-I4UK3WTN.js +0 -6
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
normalizeEndpoint,
|
|
5
5
|
requireUuid,
|
|
6
6
|
saveConfig
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-GUVGTFVK.js";
|
|
8
8
|
import {
|
|
9
9
|
detectFramework,
|
|
10
10
|
envVarsToWrite,
|
|
11
11
|
readPackageJson
|
|
12
12
|
} from "./chunk-AIZRI665.js";
|
|
13
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-X74T7JKC.js";
|
|
14
14
|
|
|
15
15
|
// src/connect.ts
|
|
16
16
|
import { appendFile, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
sanitizeProjectId,
|
|
38
38
|
saveConfig,
|
|
39
39
|
writeMcpServerEntry
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-BWASDQI6.js";
|
|
41
41
|
import {
|
|
42
42
|
TEST_REPORT_FETCH_TIMEOUT_MS,
|
|
43
43
|
assertEndpoint,
|
|
@@ -81,7 +81,9 @@ async function startDeviceAuth(endpoint, clientId) {
|
|
|
81
81
|
});
|
|
82
82
|
const json = await res.json().catch(() => null);
|
|
83
83
|
if (!res.ok || !json?.ok || !json.data) {
|
|
84
|
-
throw new Error(
|
|
84
|
+
throw new Error(
|
|
85
|
+
json?.error?.message ?? `Could not start browser sign-in (HTTP ${res.status}).`
|
|
86
|
+
);
|
|
85
87
|
}
|
|
86
88
|
return json.data;
|
|
87
89
|
}
|
|
@@ -122,10 +124,15 @@ async function pollDeviceToken(endpoint, deviceCode) {
|
|
|
122
124
|
};
|
|
123
125
|
}
|
|
124
126
|
} catch (err) {
|
|
125
|
-
return {
|
|
127
|
+
return {
|
|
128
|
+
status: "error",
|
|
129
|
+
message: err instanceof Error ? err.message : String(err),
|
|
130
|
+
retryable: true
|
|
131
|
+
};
|
|
126
132
|
}
|
|
127
133
|
}
|
|
128
134
|
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
135
|
+
var deviceAuthRetryDelayMs = 1e3;
|
|
129
136
|
async function waitForCliToken(endpoint, session, opts = {}) {
|
|
130
137
|
const sleep = opts.sleep ?? defaultSleep;
|
|
131
138
|
const now = opts.now ?? Date.now;
|
|
@@ -176,19 +183,51 @@ function cliAuthHeaders(cliToken) {
|
|
|
176
183
|
Authorization: `Bearer ${cliToken}`
|
|
177
184
|
};
|
|
178
185
|
}
|
|
186
|
+
var DeviceAuthRequestError = class extends Error {
|
|
187
|
+
step;
|
|
188
|
+
status;
|
|
189
|
+
retryable;
|
|
190
|
+
constructor(step, message, status) {
|
|
191
|
+
super(message);
|
|
192
|
+
this.name = "DeviceAuthRequestError";
|
|
193
|
+
this.step = step;
|
|
194
|
+
this.status = status;
|
|
195
|
+
this.retryable = status === void 0 || status >= 500 || status === 408;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
async function withOneRetry(run) {
|
|
199
|
+
try {
|
|
200
|
+
return await run();
|
|
201
|
+
} catch (err) {
|
|
202
|
+
if (err instanceof DeviceAuthRequestError && err.retryable) {
|
|
203
|
+
if (deviceAuthRetryDelayMs > 0) {
|
|
204
|
+
await defaultSleep(deviceAuthRetryDelayMs);
|
|
205
|
+
}
|
|
206
|
+
return run();
|
|
207
|
+
}
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
179
211
|
async function listProjects(endpoint, cliToken) {
|
|
180
212
|
const base = trimTrailingSlash(endpoint);
|
|
181
|
-
|
|
182
|
-
|
|
213
|
+
return withOneRetry(async () => {
|
|
214
|
+
let res;
|
|
215
|
+
try {
|
|
216
|
+
res = await deviceFetch(`${base}/v1/cli/projects`, { headers: cliAuthHeaders(cliToken) });
|
|
217
|
+
} catch (err) {
|
|
218
|
+
throw new DeviceAuthRequestError(
|
|
219
|
+
"list_projects",
|
|
220
|
+
`Could not reach ${base}/v1/cli/projects: ${err instanceof Error ? err.message : String(err)}`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
183
223
|
const json = await res.json().catch(() => null);
|
|
184
224
|
if (res.ok && json?.ok) return json.data?.projects ?? [];
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
225
|
+
throw new DeviceAuthRequestError(
|
|
226
|
+
"list_projects",
|
|
227
|
+
json?.error?.message ?? `Could not list projects (HTTP ${res.status}).`,
|
|
228
|
+
res.status
|
|
189
229
|
);
|
|
190
|
-
}
|
|
191
|
-
return [];
|
|
230
|
+
});
|
|
192
231
|
}
|
|
193
232
|
async function createProject(endpoint, cliToken, name) {
|
|
194
233
|
const base = trimTrailingSlash(endpoint);
|
|
@@ -206,17 +245,28 @@ async function createProject(endpoint, cliToken, name) {
|
|
|
206
245
|
}
|
|
207
246
|
async function mintProjectKey(endpoint, cliToken, projectId) {
|
|
208
247
|
const base = trimTrailingSlash(endpoint);
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
248
|
+
return withOneRetry(async () => {
|
|
249
|
+
let res;
|
|
250
|
+
try {
|
|
251
|
+
res = await deviceFetch(`${base}/v1/cli/projects/${projectId}/keys`, {
|
|
252
|
+
method: "POST",
|
|
253
|
+
headers: cliAuthHeaders(cliToken),
|
|
254
|
+
body: JSON.stringify({})
|
|
255
|
+
});
|
|
256
|
+
} catch (err) {
|
|
257
|
+
throw new DeviceAuthRequestError(
|
|
258
|
+
"mint_key",
|
|
259
|
+
`Could not reach ${base}/v1/cli/projects/\u2026/keys: ${err instanceof Error ? err.message : String(err)}`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
215
262
|
const json = await res.json().catch(() => null);
|
|
216
|
-
if (res.ok && json?.ok
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
263
|
+
if (res.ok && json?.ok && json.data?.key) return json.data.key;
|
|
264
|
+
throw new DeviceAuthRequestError(
|
|
265
|
+
"mint_key",
|
|
266
|
+
json?.error?.message ?? `Could not mint an SDK key (HTTP ${res.status}).`,
|
|
267
|
+
res.ok ? void 0 : res.status
|
|
268
|
+
);
|
|
269
|
+
});
|
|
220
270
|
}
|
|
221
271
|
|
|
222
272
|
// src/freshness.ts
|
|
@@ -460,8 +510,16 @@ async function runInit(options = {}) {
|
|
|
460
510
|
} else {
|
|
461
511
|
p.log.info(`Skipped install. Run \`${installCommand(pm, packagesToInstall)}\` yourself.`);
|
|
462
512
|
}
|
|
463
|
-
await writeEnvFile(
|
|
513
|
+
await writeEnvFile(
|
|
514
|
+
cwd,
|
|
515
|
+
credentials.apiKey,
|
|
516
|
+
credentials.projectId,
|
|
517
|
+
framework,
|
|
518
|
+
endpoint,
|
|
519
|
+
Boolean(options.yes)
|
|
520
|
+
);
|
|
464
521
|
persistCliConfig(credentials.apiKey, credentials.projectId, endpoint);
|
|
522
|
+
emitWizardFunnelEvent(credentials, endpoint, "wizard_env_written", { framework: framework.id });
|
|
465
523
|
const enableRewards = await maybeEnableRewards(options);
|
|
466
524
|
printNextSteps(framework, consoleBase, enableRewards);
|
|
467
525
|
await maybeSendTestReport(credentials, { ...options, endpoint, consoleBase });
|
|
@@ -533,7 +591,9 @@ async function acquireCredentials(options, consoleBase, endpoint) {
|
|
|
533
591
|
if (options.yes) {
|
|
534
592
|
const creds = await runBrowserSignIn(options, endpoint, consoleBase);
|
|
535
593
|
if (creds) return creds;
|
|
536
|
-
p.log.warn(
|
|
594
|
+
p.log.warn(
|
|
595
|
+
"Browser sign-in didn't complete \u2014 switching to manual entry. Run `npx mushi-mushi doctor --auth` to diagnose the sign-in path."
|
|
596
|
+
);
|
|
537
597
|
} else {
|
|
538
598
|
const method = await p.select({
|
|
539
599
|
message: "Connect this app to Mushi",
|
|
@@ -558,7 +618,9 @@ async function acquireCredentials(options, consoleBase, endpoint) {
|
|
|
558
618
|
if (method === "browser") {
|
|
559
619
|
const creds = await runBrowserSignIn(options, endpoint, consoleBase);
|
|
560
620
|
if (creds) return creds;
|
|
561
|
-
p.log.warn(
|
|
621
|
+
p.log.warn(
|
|
622
|
+
"Browser sign-in didn't complete \u2014 switching to manual entry. Run `npx mushi-mushi doctor --auth` to diagnose the sign-in path."
|
|
623
|
+
);
|
|
562
624
|
}
|
|
563
625
|
}
|
|
564
626
|
return collectCredentialsManually(options, consoleBase, endpoint);
|
|
@@ -599,7 +661,9 @@ async function runBrowserSignIn(options, endpoint, consoleBase) {
|
|
|
599
661
|
fetchSpin.start("Loading your projects\u2026");
|
|
600
662
|
try {
|
|
601
663
|
projects = await listProjects(endpoint, cliToken);
|
|
602
|
-
fetchSpin.stop(
|
|
664
|
+
fetchSpin.stop(
|
|
665
|
+
projects.length > 0 ? `Found ${projects.length} project(s).` : "No projects yet."
|
|
666
|
+
);
|
|
603
667
|
} catch (err) {
|
|
604
668
|
fetchSpin.stop("Could not load projects \u2014 you can still create a new one.");
|
|
605
669
|
p.log.warn(err instanceof Error ? err.message : String(err));
|
|
@@ -647,19 +711,14 @@ async function runBrowserSignIn(options, endpoint, consoleBase) {
|
|
|
647
711
|
const keySpin = p.spinner();
|
|
648
712
|
keySpin.start("Minting SDK key\u2026");
|
|
649
713
|
try {
|
|
650
|
-
|
|
651
|
-
apiKey = minted ?? void 0;
|
|
652
|
-
if (!apiKey) {
|
|
653
|
-
keySpin.stop("Could not mint an API key.");
|
|
654
|
-
p.log.warn(
|
|
655
|
-
`Open the console Verify tab to generate a key manually: ${consoleUrl(consoleBase, "/onboarding?tab=verify")}`
|
|
656
|
-
);
|
|
657
|
-
return null;
|
|
658
|
-
}
|
|
714
|
+
apiKey = await mintProjectKey(endpoint, cliToken, projectId);
|
|
659
715
|
keySpin.stop("SDK key ready.");
|
|
660
716
|
} catch (err) {
|
|
661
717
|
keySpin.stop("Could not mint an API key.");
|
|
662
718
|
p.log.warn(err instanceof Error ? err.message : String(err));
|
|
719
|
+
p.log.warn(
|
|
720
|
+
`You're signed in, but key minting failed. Generate one manually in the console Verify tab: ${consoleUrl(consoleBase, "/onboarding?tab=verify")}`
|
|
721
|
+
);
|
|
663
722
|
return null;
|
|
664
723
|
}
|
|
665
724
|
}
|
|
@@ -719,10 +778,11 @@ async function verifyCredentials(credentials, options, consoleBase) {
|
|
|
719
778
|
const endpoint = resolveCloudEndpoint(options.endpoint);
|
|
720
779
|
const spinner3 = p.spinner();
|
|
721
780
|
spinner3.start("Verifying credentials\u2026");
|
|
722
|
-
const result = await apiCall(
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
781
|
+
const result = await apiCall("/v1/sync/whoami", {
|
|
782
|
+
apiKey: credentials.apiKey,
|
|
783
|
+
projectId: credentials.projectId,
|
|
784
|
+
endpoint
|
|
785
|
+
});
|
|
726
786
|
if (!result.ok) {
|
|
727
787
|
spinner3.stop("Credentials could not be verified.");
|
|
728
788
|
p.log.error(result.error?.message ?? "Authentication failed.");
|
|
@@ -877,6 +937,20 @@ function persistCliConfig(apiKey, projectId, endpoint) {
|
|
|
877
937
|
const existing = loadConfig();
|
|
878
938
|
saveConfig({ ...existing, apiKey, projectId, endpoint });
|
|
879
939
|
}
|
|
940
|
+
function emitWizardFunnelEvent(credentials, endpoint, event, metadata = {}) {
|
|
941
|
+
if (process.env.MUSHI_NO_TELEMETRY) return;
|
|
942
|
+
void fetch(`${endpoint.replace(/\/$/, "")}/v1/cli/funnel`, {
|
|
943
|
+
method: "POST",
|
|
944
|
+
headers: {
|
|
945
|
+
"Content-Type": "application/json",
|
|
946
|
+
"X-Mushi-Api-Key": credentials.apiKey,
|
|
947
|
+
"X-Mushi-Project": credentials.projectId
|
|
948
|
+
},
|
|
949
|
+
body: JSON.stringify({ event, source: "cli", metadata }),
|
|
950
|
+
signal: AbortSignal.timeout(4e3)
|
|
951
|
+
}).catch(() => {
|
|
952
|
+
});
|
|
953
|
+
}
|
|
880
954
|
function printNextSteps(framework, consoleBase, enableRewards = false) {
|
|
881
955
|
p.note(framework.snippet(), "Add this to your app:");
|
|
882
956
|
if (enableRewards) {
|
|
@@ -910,7 +984,7 @@ async function maybeOfferConnect(credentials, options, _consoleBase) {
|
|
|
910
984
|
if (p.isCancel(answer) || !answer) return;
|
|
911
985
|
const endpoint = resolveCloudEndpoint(options.endpoint);
|
|
912
986
|
try {
|
|
913
|
-
const { runConnect: runConnect2 } = await import("./connect-
|
|
987
|
+
const { runConnect: runConnect2 } = await import("./connect-KKN62YK2.js");
|
|
914
988
|
await runConnect2({
|
|
915
989
|
apiKey: credentials.apiKey,
|
|
916
990
|
projectId: credentials.projectId,
|
|
@@ -921,12 +995,8 @@ async function maybeOfferConnect(credentials, options, _consoleBase) {
|
|
|
921
995
|
wait: true
|
|
922
996
|
});
|
|
923
997
|
} catch (err) {
|
|
924
|
-
p.log.warn(
|
|
925
|
-
|
|
926
|
-
);
|
|
927
|
-
p.log.info(
|
|
928
|
-
"You can run manually: mushi connect --write-env --wire-ide --wait"
|
|
929
|
-
);
|
|
998
|
+
p.log.warn(err instanceof Error ? err.message : String(err));
|
|
999
|
+
p.log.info("You can run manually: mushi connect --write-env --wire-ide --wait");
|
|
930
1000
|
}
|
|
931
1001
|
}
|
|
932
1002
|
async function maybeEnableRewards(options) {
|
|
@@ -3193,6 +3263,88 @@ async function checkEndpointReachability(endpoint, doFetch = globalThis.fetch) {
|
|
|
3193
3263
|
return { name: "Endpoint reachable", ok: false, detail: `Fetch failed: ${msg}` };
|
|
3194
3264
|
}
|
|
3195
3265
|
}
|
|
3266
|
+
async function checkCliAuthPath(config, doFetch = globalThis.fetch) {
|
|
3267
|
+
const checks = [];
|
|
3268
|
+
if (!config.endpoint) {
|
|
3269
|
+
return [
|
|
3270
|
+
{
|
|
3271
|
+
name: "Sign-in route reachable",
|
|
3272
|
+
ok: false,
|
|
3273
|
+
detail: "No endpoint configured \u2014 run `npx mushi-mushi` or pass MUSHI_API_ENDPOINT."
|
|
3274
|
+
}
|
|
3275
|
+
];
|
|
3276
|
+
}
|
|
3277
|
+
const base = sanitizeEndpoint(config.endpoint);
|
|
3278
|
+
let dateHeader = null;
|
|
3279
|
+
try {
|
|
3280
|
+
const res = await doFetch(`${base}/v1/cli/auth/device/token`, {
|
|
3281
|
+
method: "POST",
|
|
3282
|
+
headers: { "Content-Type": "application/json" },
|
|
3283
|
+
body: JSON.stringify({ device_code: "doctor-probe" }),
|
|
3284
|
+
signal: AbortSignal.timeout(8e3)
|
|
3285
|
+
});
|
|
3286
|
+
dateHeader = res.headers?.get?.("date") ?? null;
|
|
3287
|
+
const signInRouteReachable = res.status === 400 || res.status === 429;
|
|
3288
|
+
checks.push({
|
|
3289
|
+
name: "Sign-in route reachable",
|
|
3290
|
+
ok: signInRouteReachable,
|
|
3291
|
+
detail: signInRouteReachable ? `POST ${base}/v1/cli/auth/device/token \u2192 ${res.status} (route deployed and answering)` : `POST ${base}/v1/cli/auth/device/token \u2192 ${res.status} \u2014 expected HTTP 400 (invalid device_code) or 429 (slow_down); the API may be down or mis-deployed.`
|
|
3292
|
+
});
|
|
3293
|
+
} catch (err) {
|
|
3294
|
+
checks.push({
|
|
3295
|
+
name: "Sign-in route reachable",
|
|
3296
|
+
ok: false,
|
|
3297
|
+
detail: `Fetch failed: ${err instanceof Error ? err.message : String(err)} \u2014 check network/proxy/firewall to ${base}.`
|
|
3298
|
+
});
|
|
3299
|
+
}
|
|
3300
|
+
if (dateHeader) {
|
|
3301
|
+
const serverMs = Date.parse(dateHeader);
|
|
3302
|
+
if (!Number.isNaN(serverMs)) {
|
|
3303
|
+
const skewSec = Math.round(Math.abs(Date.now() - serverMs) / 1e3);
|
|
3304
|
+
const ok = skewSec <= 120;
|
|
3305
|
+
checks.push({
|
|
3306
|
+
name: "System clock in sync",
|
|
3307
|
+
ok,
|
|
3308
|
+
warn: !ok && skewSec <= 300,
|
|
3309
|
+
detail: ok ? `Local clock within ${skewSec}s of the server.` : `Local clock is ${skewSec}s off the server \u2014 sign-in codes expire fast; fix your system time.`
|
|
3310
|
+
});
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
if (config.apiKey && config.projectId) {
|
|
3314
|
+
try {
|
|
3315
|
+
const res = await doFetch(`${base}/v1/sync/whoami`, {
|
|
3316
|
+
method: "POST",
|
|
3317
|
+
headers: {
|
|
3318
|
+
"Content-Type": "application/json",
|
|
3319
|
+
...apiKeyHeaders(config.apiKey, config.projectId)
|
|
3320
|
+
},
|
|
3321
|
+
body: JSON.stringify({}),
|
|
3322
|
+
signal: AbortSignal.timeout(8e3)
|
|
3323
|
+
});
|
|
3324
|
+
const json = await res.json().catch(() => null);
|
|
3325
|
+
const ok = res.ok && json?.ok === true;
|
|
3326
|
+
checks.push({
|
|
3327
|
+
name: "Saved credentials valid",
|
|
3328
|
+
ok,
|
|
3329
|
+
detail: ok ? `API key resolves to project "${json?.data?.project_name ?? config.projectId}".` : `${json?.error?.message ?? `whoami \u2192 HTTP ${res.status}`} \u2014 re-run \`npx mushi-mushi\` to sign in again.`
|
|
3330
|
+
});
|
|
3331
|
+
} catch (err) {
|
|
3332
|
+
checks.push({
|
|
3333
|
+
name: "Saved credentials valid",
|
|
3334
|
+
ok: false,
|
|
3335
|
+
detail: `whoami fetch failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3336
|
+
});
|
|
3337
|
+
}
|
|
3338
|
+
} else {
|
|
3339
|
+
checks.push({
|
|
3340
|
+
name: "Saved credentials valid",
|
|
3341
|
+
ok: true,
|
|
3342
|
+
warn: true,
|
|
3343
|
+
detail: "No saved CLI credentials yet \u2014 normal before the first successful sign-in."
|
|
3344
|
+
});
|
|
3345
|
+
}
|
|
3346
|
+
return checks;
|
|
3347
|
+
}
|
|
3196
3348
|
async function checkSdkInstall(cwd) {
|
|
3197
3349
|
try {
|
|
3198
3350
|
const { readFile: readFile3 } = await import("fs/promises");
|
|
@@ -3229,13 +3381,10 @@ async function checkServerPreflight(config, doFetch = globalThis.fetch) {
|
|
|
3229
3381
|
}
|
|
3230
3382
|
try {
|
|
3231
3383
|
const { endpoint, apiKey, projectId } = sanitizeCliCredentials(config);
|
|
3232
|
-
const res = await doFetch(
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
signal: AbortSignal.timeout(8e3)
|
|
3237
|
-
}
|
|
3238
|
-
);
|
|
3384
|
+
const res = await doFetch(`${endpoint}/v1/admin/projects/${projectId}/preflight`, {
|
|
3385
|
+
headers: apiKeyHeaders(apiKey, projectId),
|
|
3386
|
+
signal: AbortSignal.timeout(8e3)
|
|
3387
|
+
});
|
|
3239
3388
|
if (res.ok) {
|
|
3240
3389
|
const body = await res.json();
|
|
3241
3390
|
const serverChecks = body.data?.checks ?? [];
|
|
@@ -3291,7 +3440,13 @@ async function checkIngestSetup(config, doFetch = globalThis.fetch) {
|
|
|
3291
3440
|
doFetch
|
|
3292
3441
|
);
|
|
3293
3442
|
if (!data) {
|
|
3294
|
-
return [
|
|
3443
|
+
return [
|
|
3444
|
+
{
|
|
3445
|
+
name: "Ingest setup",
|
|
3446
|
+
ok: false,
|
|
3447
|
+
detail: "Request to /v1/sync/ingest-setup failed or returned invalid payload"
|
|
3448
|
+
}
|
|
3449
|
+
];
|
|
3295
3450
|
}
|
|
3296
3451
|
const steps = data.steps ?? [];
|
|
3297
3452
|
const checks = steps.filter((s) => s.required).map((s) => ({
|
|
@@ -3327,22 +3482,27 @@ async function checkQaStoriesHealth(config, doFetch = globalThis.fetch) {
|
|
|
3327
3482
|
try {
|
|
3328
3483
|
const { endpoint, apiKey, projectId } = sanitizeCliCredentials(config);
|
|
3329
3484
|
const headers = apiKeyHeaders(apiKey, projectId);
|
|
3330
|
-
const storiesRes = await doFetch(
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
signal: AbortSignal.timeout(8e3)
|
|
3335
|
-
}
|
|
3336
|
-
);
|
|
3485
|
+
const storiesRes = await doFetch(`${endpoint}/v1/admin/projects/${projectId}/qa-coverage`, {
|
|
3486
|
+
headers,
|
|
3487
|
+
signal: AbortSignal.timeout(8e3)
|
|
3488
|
+
});
|
|
3337
3489
|
if (!storiesRes.ok) {
|
|
3338
|
-
checks.push({
|
|
3490
|
+
checks.push({
|
|
3491
|
+
name: "[qa] Fetch QA stories",
|
|
3492
|
+
ok: false,
|
|
3493
|
+
detail: `HTTP ${storiesRes.status}`
|
|
3494
|
+
});
|
|
3339
3495
|
return checks;
|
|
3340
3496
|
}
|
|
3341
3497
|
const storiesBody = await storiesRes.json();
|
|
3342
3498
|
const stories = storiesBody.data?.coverage ?? [];
|
|
3343
3499
|
const enabled = stories.filter((s) => s.enabled);
|
|
3344
3500
|
if (enabled.length === 0) {
|
|
3345
|
-
checks.push({
|
|
3501
|
+
checks.push({
|
|
3502
|
+
name: "[qa] Enabled QA stories",
|
|
3503
|
+
ok: true,
|
|
3504
|
+
detail: "No enabled stories \u2014 create one at /qa-coverage"
|
|
3505
|
+
});
|
|
3346
3506
|
return checks;
|
|
3347
3507
|
}
|
|
3348
3508
|
checks.push({
|
|
@@ -3411,7 +3571,9 @@ async function checkHostAppWiring(cwd) {
|
|
|
3411
3571
|
}
|
|
3412
3572
|
}
|
|
3413
3573
|
const hasProjectId = /VITE_MUSHI_PROJECT_ID=|NEXT_PUBLIC_MUSHI_PROJECT_ID=|MUSHI_PROJECT_ID=/.test(envContent);
|
|
3414
|
-
const hasApiKey = /VITE_MUSHI_API_KEY=|NEXT_PUBLIC_MUSHI_API_KEY=|MUSHI_API_KEY=/.test(
|
|
3574
|
+
const hasApiKey = /VITE_MUSHI_API_KEY=|NEXT_PUBLIC_MUSHI_API_KEY=|MUSHI_API_KEY=/.test(
|
|
3575
|
+
envContent
|
|
3576
|
+
);
|
|
3415
3577
|
checks.push({
|
|
3416
3578
|
name: "[host] Mushi env vars in .env.local",
|
|
3417
3579
|
ok: hasProjectId && hasApiKey,
|
|
@@ -3456,10 +3618,7 @@ async function checkMcpConfig(config, cwd, doFetch = globalThis.fetch) {
|
|
|
3456
3618
|
const { join: join4, resolve: resolve3 } = await import("path");
|
|
3457
3619
|
const { homedir } = await import("os");
|
|
3458
3620
|
const root = resolve3(cwd);
|
|
3459
|
-
const candidates = [
|
|
3460
|
-
join4(root, ".cursor", "mcp.json"),
|
|
3461
|
-
join4(homedir(), ".cursor", "mcp.json")
|
|
3462
|
-
];
|
|
3621
|
+
const candidates = [join4(root, ".cursor", "mcp.json"), join4(homedir(), ".cursor", "mcp.json")];
|
|
3463
3622
|
let mcpPath = null;
|
|
3464
3623
|
let mcpRaw = null;
|
|
3465
3624
|
for (const candidate of candidates) {
|
|
@@ -3497,11 +3656,17 @@ async function checkMcpConfig(config, cwd, doFetch = globalThis.fetch) {
|
|
|
3497
3656
|
try {
|
|
3498
3657
|
mcpConfig = JSON.parse(mcpRaw);
|
|
3499
3658
|
} catch {
|
|
3500
|
-
checks.push({
|
|
3659
|
+
checks.push({
|
|
3660
|
+
name: "[mcp] mcp.json valid JSON",
|
|
3661
|
+
ok: false,
|
|
3662
|
+
detail: "mcp.json is not valid JSON \u2014 regenerate with `mushi setup`."
|
|
3663
|
+
});
|
|
3501
3664
|
return checks;
|
|
3502
3665
|
}
|
|
3503
3666
|
const servers = mcpConfig.mcpServers ?? {};
|
|
3504
|
-
const mushiEntries = Object.entries(servers).filter(
|
|
3667
|
+
const mushiEntries = Object.entries(servers).filter(
|
|
3668
|
+
([k]) => k === "mushi" || k.startsWith("mushi-")
|
|
3669
|
+
);
|
|
3505
3670
|
if (mushiEntries.length === 0) {
|
|
3506
3671
|
checks.push({
|
|
3507
3672
|
name: "[mcp] mushi server entry",
|
|
@@ -3621,7 +3786,9 @@ async function checkOnboardingStatus(config, _consoleBase, cwd, doFetch = global
|
|
|
3621
3786
|
} catch {
|
|
3622
3787
|
}
|
|
3623
3788
|
}
|
|
3624
|
-
const hasEnvVars = /(?:VITE_|NEXT_PUBLIC_|EXPO_PUBLIC_|NUXT_PUBLIC_)?MUSHI_PROJECT_ID=/.test(
|
|
3789
|
+
const hasEnvVars = /(?:VITE_|NEXT_PUBLIC_|EXPO_PUBLIC_|NUXT_PUBLIC_)?MUSHI_PROJECT_ID=/.test(
|
|
3790
|
+
envContent
|
|
3791
|
+
);
|
|
3625
3792
|
if (!hasEnvVars) {
|
|
3626
3793
|
return {
|
|
3627
3794
|
nextAction: "Write env vars to your app: run `npx mushi-mushi` or `mushi connect --write-env`",
|
|
@@ -3710,6 +3877,9 @@ async function runDoctor(config, options = {}) {
|
|
|
3710
3877
|
const mcpChecks = await checkMcpConfig(config, options.cwd ?? process.cwd(), doFetch);
|
|
3711
3878
|
checks.push(...mcpChecks);
|
|
3712
3879
|
}
|
|
3880
|
+
if (isFull || options.auth) {
|
|
3881
|
+
checks.push(...await checkCliAuthPath(config, doFetch));
|
|
3882
|
+
}
|
|
3713
3883
|
const heartbeatPassed = checks.some((c) => c.name === "[ingest] Last SDK heartbeat" && c.ok);
|
|
3714
3884
|
if (heartbeatPassed) {
|
|
3715
3885
|
const idx = checks.findIndex((c) => c.name === "SDK installed in this repo" && !c.ok);
|
|
@@ -3742,8 +3912,10 @@ function formatDoctorResult(result) {
|
|
|
3742
3912
|
const warned = result.checks.filter((c) => c.ok && c.warn);
|
|
3743
3913
|
if (failed.length === 0) {
|
|
3744
3914
|
if (warned.length > 0) {
|
|
3745
|
-
lines.push(
|
|
3746
|
-
|
|
3915
|
+
lines.push(
|
|
3916
|
+
`
|
|
3917
|
+
All checks passed with ${warned.length} advisory warning${warned.length === 1 ? "" : "s"}. The CLI is ready.`
|
|
3918
|
+
);
|
|
3747
3919
|
} else {
|
|
3748
3920
|
lines.push("\nAll checks passed. The CLI is ready.");
|
|
3749
3921
|
}
|
|
@@ -3768,6 +3940,9 @@ function registerDoctorCommand(program2) {
|
|
|
3768
3940
|
).option(
|
|
3769
3941
|
"--mcp",
|
|
3770
3942
|
"Check Cursor MCP config: verify mushi-* server entry, credentials, and probe account-overview connectivity."
|
|
3943
|
+
).option(
|
|
3944
|
+
"--auth",
|
|
3945
|
+
"Diagnose the browser sign-in handshake: device-auth route reachability, system clock skew, and saved-credential validity. Run this when `npx mushi-mushi` browser sign-in does not complete."
|
|
3771
3946
|
).option(
|
|
3772
3947
|
"--fix",
|
|
3773
3948
|
"Apply safe local fixes when checks fail: write missing .env.local lines and wire Cursor MCP config."
|
|
@@ -3777,54 +3952,60 @@ function registerDoctorCommand(program2) {
|
|
|
3777
3952
|
).option(
|
|
3778
3953
|
"--full",
|
|
3779
3954
|
"Run ALL check categories (server, ingest, host-app, mcp, qa-stories) in one shot. Good for first-run diagnostics. Exit 0 only when all checks pass."
|
|
3780
|
-
).action(
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3955
|
+
).action(
|
|
3956
|
+
async (opts) => {
|
|
3957
|
+
const config = loadConfig();
|
|
3958
|
+
if (opts.onboarding) {
|
|
3959
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
3960
|
+
const consoleBase = await resolveConsoleUrl({ cwd });
|
|
3961
|
+
const status = await checkOnboardingStatus(config, consoleBase, cwd);
|
|
3962
|
+
const base = consoleBase.replace(/\/$/, "");
|
|
3963
|
+
if (status.done) {
|
|
3964
|
+
console.log(`OK Setup complete. ${status.nextAction}`);
|
|
3965
|
+
console.log(` ${base}${status.ctaPath}`);
|
|
3966
|
+
} else {
|
|
3967
|
+
console.log(`\u2192 Next: ${status.nextAction}`);
|
|
3968
|
+
console.log(` Open: ${base}${status.ctaPath}`);
|
|
3969
|
+
}
|
|
3970
|
+
if (!status.done) process.exit(1);
|
|
3971
|
+
return;
|
|
3793
3972
|
}
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3973
|
+
const doctorOpts = {
|
|
3974
|
+
cwd: opts.cwd,
|
|
3975
|
+
server: opts.server,
|
|
3976
|
+
ingest: opts.ingest,
|
|
3977
|
+
qaStories: opts.qaStories,
|
|
3978
|
+
hostApp: opts.hostApp,
|
|
3979
|
+
mcp: opts.mcp,
|
|
3980
|
+
auth: opts.auth,
|
|
3981
|
+
full: opts.full
|
|
3982
|
+
};
|
|
3983
|
+
let result = await runDoctor(config, doctorOpts);
|
|
3984
|
+
if (!result.ready && opts.fix && config.apiKey && config.projectId && config.endpoint) {
|
|
3985
|
+
const connectResult = await runConnect(
|
|
3986
|
+
{
|
|
3987
|
+
apiKey: config.apiKey,
|
|
3988
|
+
projectId: config.projectId,
|
|
3989
|
+
endpoint: config.endpoint,
|
|
3990
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
3991
|
+
writeEnv: true,
|
|
3992
|
+
wireIde: true
|
|
3993
|
+
},
|
|
3994
|
+
config
|
|
3995
|
+
);
|
|
3996
|
+
for (const msg of connectResult.messages) console.log(msg);
|
|
3997
|
+
result = await runDoctor(config, { ...doctorOpts, mcp: opts.mcp });
|
|
3998
|
+
}
|
|
3999
|
+
const { checks } = result;
|
|
4000
|
+
if (opts.json) {
|
|
4001
|
+
console.log(JSON.stringify({ checks, ready: result.ready }, null, 2));
|
|
4002
|
+
if (!result.ready) process.exit(1);
|
|
4003
|
+
return;
|
|
4004
|
+
}
|
|
4005
|
+
console.log(formatDoctorResult(result));
|
|
3822
4006
|
if (!result.ready) process.exit(1);
|
|
3823
|
-
return;
|
|
3824
4007
|
}
|
|
3825
|
-
|
|
3826
|
-
if (!result.ready) process.exit(1);
|
|
3827
|
-
});
|
|
4008
|
+
);
|
|
3828
4009
|
}
|
|
3829
4010
|
|
|
3830
4011
|
// src/commands/reset-cli.ts
|
package/dist/init.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
resolveConsoleUrl,
|
|
15
15
|
resolveConsoleUrlSync,
|
|
16
16
|
saveConfig
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-GUVGTFVK.js";
|
|
18
18
|
import {
|
|
19
19
|
FRAMEWORKS,
|
|
20
20
|
detectFramework,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
} from "./chunk-AIZRI665.js";
|
|
26
26
|
import {
|
|
27
27
|
MUSHI_CLI_VERSION
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-X74T7JKC.js";
|
|
29
29
|
|
|
30
30
|
// src/init.ts
|
|
31
31
|
import * as p from "@clack/prompts";
|
|
@@ -60,7 +60,9 @@ async function startDeviceAuth(endpoint, clientId) {
|
|
|
60
60
|
});
|
|
61
61
|
const json = await res.json().catch(() => null);
|
|
62
62
|
if (!res.ok || !json?.ok || !json.data) {
|
|
63
|
-
throw new Error(
|
|
63
|
+
throw new Error(
|
|
64
|
+
json?.error?.message ?? `Could not start browser sign-in (HTTP ${res.status}).`
|
|
65
|
+
);
|
|
64
66
|
}
|
|
65
67
|
return json.data;
|
|
66
68
|
}
|
|
@@ -101,10 +103,15 @@ async function pollDeviceToken(endpoint, deviceCode) {
|
|
|
101
103
|
};
|
|
102
104
|
}
|
|
103
105
|
} catch (err) {
|
|
104
|
-
return {
|
|
106
|
+
return {
|
|
107
|
+
status: "error",
|
|
108
|
+
message: err instanceof Error ? err.message : String(err),
|
|
109
|
+
retryable: true
|
|
110
|
+
};
|
|
105
111
|
}
|
|
106
112
|
}
|
|
107
113
|
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
114
|
+
var deviceAuthRetryDelayMs = 1e3;
|
|
108
115
|
async function waitForCliToken(endpoint, session, opts = {}) {
|
|
109
116
|
const sleep = opts.sleep ?? defaultSleep;
|
|
110
117
|
const now = opts.now ?? Date.now;
|
|
@@ -155,19 +162,51 @@ function cliAuthHeaders(cliToken) {
|
|
|
155
162
|
Authorization: `Bearer ${cliToken}`
|
|
156
163
|
};
|
|
157
164
|
}
|
|
165
|
+
var DeviceAuthRequestError = class extends Error {
|
|
166
|
+
step;
|
|
167
|
+
status;
|
|
168
|
+
retryable;
|
|
169
|
+
constructor(step, message, status) {
|
|
170
|
+
super(message);
|
|
171
|
+
this.name = "DeviceAuthRequestError";
|
|
172
|
+
this.step = step;
|
|
173
|
+
this.status = status;
|
|
174
|
+
this.retryable = status === void 0 || status >= 500 || status === 408;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
async function withOneRetry(run) {
|
|
178
|
+
try {
|
|
179
|
+
return await run();
|
|
180
|
+
} catch (err) {
|
|
181
|
+
if (err instanceof DeviceAuthRequestError && err.retryable) {
|
|
182
|
+
if (deviceAuthRetryDelayMs > 0) {
|
|
183
|
+
await defaultSleep(deviceAuthRetryDelayMs);
|
|
184
|
+
}
|
|
185
|
+
return run();
|
|
186
|
+
}
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
158
190
|
async function listProjects(endpoint, cliToken) {
|
|
159
191
|
const base = trimTrailingSlash(endpoint);
|
|
160
|
-
|
|
161
|
-
|
|
192
|
+
return withOneRetry(async () => {
|
|
193
|
+
let res;
|
|
194
|
+
try {
|
|
195
|
+
res = await deviceFetch(`${base}/v1/cli/projects`, { headers: cliAuthHeaders(cliToken) });
|
|
196
|
+
} catch (err) {
|
|
197
|
+
throw new DeviceAuthRequestError(
|
|
198
|
+
"list_projects",
|
|
199
|
+
`Could not reach ${base}/v1/cli/projects: ${err instanceof Error ? err.message : String(err)}`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
162
202
|
const json = await res.json().catch(() => null);
|
|
163
203
|
if (res.ok && json?.ok) return json.data?.projects ?? [];
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
204
|
+
throw new DeviceAuthRequestError(
|
|
205
|
+
"list_projects",
|
|
206
|
+
json?.error?.message ?? `Could not list projects (HTTP ${res.status}).`,
|
|
207
|
+
res.status
|
|
168
208
|
);
|
|
169
|
-
}
|
|
170
|
-
return [];
|
|
209
|
+
});
|
|
171
210
|
}
|
|
172
211
|
async function createProject(endpoint, cliToken, name) {
|
|
173
212
|
const base = trimTrailingSlash(endpoint);
|
|
@@ -185,17 +224,28 @@ async function createProject(endpoint, cliToken, name) {
|
|
|
185
224
|
}
|
|
186
225
|
async function mintProjectKey(endpoint, cliToken, projectId) {
|
|
187
226
|
const base = trimTrailingSlash(endpoint);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
227
|
+
return withOneRetry(async () => {
|
|
228
|
+
let res;
|
|
229
|
+
try {
|
|
230
|
+
res = await deviceFetch(`${base}/v1/cli/projects/${projectId}/keys`, {
|
|
231
|
+
method: "POST",
|
|
232
|
+
headers: cliAuthHeaders(cliToken),
|
|
233
|
+
body: JSON.stringify({})
|
|
234
|
+
});
|
|
235
|
+
} catch (err) {
|
|
236
|
+
throw new DeviceAuthRequestError(
|
|
237
|
+
"mint_key",
|
|
238
|
+
`Could not reach ${base}/v1/cli/projects/\u2026/keys: ${err instanceof Error ? err.message : String(err)}`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
194
241
|
const json = await res.json().catch(() => null);
|
|
195
|
-
if (res.ok && json?.ok
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
242
|
+
if (res.ok && json?.ok && json.data?.key) return json.data.key;
|
|
243
|
+
throw new DeviceAuthRequestError(
|
|
244
|
+
"mint_key",
|
|
245
|
+
json?.error?.message ?? `Could not mint an SDK key (HTTP ${res.status}).`,
|
|
246
|
+
res.ok ? void 0 : res.status
|
|
247
|
+
);
|
|
248
|
+
});
|
|
199
249
|
}
|
|
200
250
|
|
|
201
251
|
// src/freshness.ts
|
|
@@ -419,8 +469,16 @@ async function runInit(options = {}) {
|
|
|
419
469
|
} else {
|
|
420
470
|
p.log.info(`Skipped install. Run \`${installCommand(pm, packagesToInstall)}\` yourself.`);
|
|
421
471
|
}
|
|
422
|
-
await writeEnvFile(
|
|
472
|
+
await writeEnvFile(
|
|
473
|
+
cwd,
|
|
474
|
+
credentials.apiKey,
|
|
475
|
+
credentials.projectId,
|
|
476
|
+
framework,
|
|
477
|
+
endpoint,
|
|
478
|
+
Boolean(options.yes)
|
|
479
|
+
);
|
|
423
480
|
persistCliConfig(credentials.apiKey, credentials.projectId, endpoint);
|
|
481
|
+
emitWizardFunnelEvent(credentials, endpoint, "wizard_env_written", { framework: framework.id });
|
|
424
482
|
const enableRewards = await maybeEnableRewards(options);
|
|
425
483
|
printNextSteps(framework, consoleBase, enableRewards);
|
|
426
484
|
await maybeSendTestReport(credentials, { ...options, endpoint, consoleBase });
|
|
@@ -492,7 +550,9 @@ async function acquireCredentials(options, consoleBase, endpoint) {
|
|
|
492
550
|
if (options.yes) {
|
|
493
551
|
const creds = await runBrowserSignIn(options, endpoint, consoleBase);
|
|
494
552
|
if (creds) return creds;
|
|
495
|
-
p.log.warn(
|
|
553
|
+
p.log.warn(
|
|
554
|
+
"Browser sign-in didn't complete \u2014 switching to manual entry. Run `npx mushi-mushi doctor --auth` to diagnose the sign-in path."
|
|
555
|
+
);
|
|
496
556
|
} else {
|
|
497
557
|
const method = await p.select({
|
|
498
558
|
message: "Connect this app to Mushi",
|
|
@@ -517,7 +577,9 @@ async function acquireCredentials(options, consoleBase, endpoint) {
|
|
|
517
577
|
if (method === "browser") {
|
|
518
578
|
const creds = await runBrowserSignIn(options, endpoint, consoleBase);
|
|
519
579
|
if (creds) return creds;
|
|
520
|
-
p.log.warn(
|
|
580
|
+
p.log.warn(
|
|
581
|
+
"Browser sign-in didn't complete \u2014 switching to manual entry. Run `npx mushi-mushi doctor --auth` to diagnose the sign-in path."
|
|
582
|
+
);
|
|
521
583
|
}
|
|
522
584
|
}
|
|
523
585
|
return collectCredentialsManually(options, consoleBase, endpoint);
|
|
@@ -558,7 +620,9 @@ async function runBrowserSignIn(options, endpoint, consoleBase) {
|
|
|
558
620
|
fetchSpin.start("Loading your projects\u2026");
|
|
559
621
|
try {
|
|
560
622
|
projects = await listProjects(endpoint, cliToken);
|
|
561
|
-
fetchSpin.stop(
|
|
623
|
+
fetchSpin.stop(
|
|
624
|
+
projects.length > 0 ? `Found ${projects.length} project(s).` : "No projects yet."
|
|
625
|
+
);
|
|
562
626
|
} catch (err) {
|
|
563
627
|
fetchSpin.stop("Could not load projects \u2014 you can still create a new one.");
|
|
564
628
|
p.log.warn(err instanceof Error ? err.message : String(err));
|
|
@@ -606,19 +670,14 @@ async function runBrowserSignIn(options, endpoint, consoleBase) {
|
|
|
606
670
|
const keySpin = p.spinner();
|
|
607
671
|
keySpin.start("Minting SDK key\u2026");
|
|
608
672
|
try {
|
|
609
|
-
|
|
610
|
-
apiKey = minted ?? void 0;
|
|
611
|
-
if (!apiKey) {
|
|
612
|
-
keySpin.stop("Could not mint an API key.");
|
|
613
|
-
p.log.warn(
|
|
614
|
-
`Open the console Verify tab to generate a key manually: ${consoleUrl(consoleBase, "/onboarding?tab=verify")}`
|
|
615
|
-
);
|
|
616
|
-
return null;
|
|
617
|
-
}
|
|
673
|
+
apiKey = await mintProjectKey(endpoint, cliToken, projectId);
|
|
618
674
|
keySpin.stop("SDK key ready.");
|
|
619
675
|
} catch (err) {
|
|
620
676
|
keySpin.stop("Could not mint an API key.");
|
|
621
677
|
p.log.warn(err instanceof Error ? err.message : String(err));
|
|
678
|
+
p.log.warn(
|
|
679
|
+
`You're signed in, but key minting failed. Generate one manually in the console Verify tab: ${consoleUrl(consoleBase, "/onboarding?tab=verify")}`
|
|
680
|
+
);
|
|
622
681
|
return null;
|
|
623
682
|
}
|
|
624
683
|
}
|
|
@@ -678,10 +737,11 @@ async function verifyCredentials(credentials, options, consoleBase) {
|
|
|
678
737
|
const endpoint = resolveCloudEndpoint(options.endpoint);
|
|
679
738
|
const spinner2 = p.spinner();
|
|
680
739
|
spinner2.start("Verifying credentials\u2026");
|
|
681
|
-
const result = await apiCall(
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
740
|
+
const result = await apiCall("/v1/sync/whoami", {
|
|
741
|
+
apiKey: credentials.apiKey,
|
|
742
|
+
projectId: credentials.projectId,
|
|
743
|
+
endpoint
|
|
744
|
+
});
|
|
685
745
|
if (!result.ok) {
|
|
686
746
|
spinner2.stop("Credentials could not be verified.");
|
|
687
747
|
p.log.error(result.error?.message ?? "Authentication failed.");
|
|
@@ -836,6 +896,20 @@ function persistCliConfig(apiKey, projectId, endpoint) {
|
|
|
836
896
|
const existing = loadConfig();
|
|
837
897
|
saveConfig({ ...existing, apiKey, projectId, endpoint });
|
|
838
898
|
}
|
|
899
|
+
function emitWizardFunnelEvent(credentials, endpoint, event, metadata = {}) {
|
|
900
|
+
if (process.env.MUSHI_NO_TELEMETRY) return;
|
|
901
|
+
void fetch(`${endpoint.replace(/\/$/, "")}/v1/cli/funnel`, {
|
|
902
|
+
method: "POST",
|
|
903
|
+
headers: {
|
|
904
|
+
"Content-Type": "application/json",
|
|
905
|
+
"X-Mushi-Api-Key": credentials.apiKey,
|
|
906
|
+
"X-Mushi-Project": credentials.projectId
|
|
907
|
+
},
|
|
908
|
+
body: JSON.stringify({ event, source: "cli", metadata }),
|
|
909
|
+
signal: AbortSignal.timeout(4e3)
|
|
910
|
+
}).catch(() => {
|
|
911
|
+
});
|
|
912
|
+
}
|
|
839
913
|
function printNextSteps(framework, consoleBase, enableRewards = false) {
|
|
840
914
|
p.note(framework.snippet(), "Add this to your app:");
|
|
841
915
|
if (enableRewards) {
|
|
@@ -869,7 +943,7 @@ async function maybeOfferConnect(credentials, options, _consoleBase) {
|
|
|
869
943
|
if (p.isCancel(answer) || !answer) return;
|
|
870
944
|
const endpoint = resolveCloudEndpoint(options.endpoint);
|
|
871
945
|
try {
|
|
872
|
-
const { runConnect } = await import("./connect-
|
|
946
|
+
const { runConnect } = await import("./connect-7LTQRBEW.js");
|
|
873
947
|
await runConnect({
|
|
874
948
|
apiKey: credentials.apiKey,
|
|
875
949
|
projectId: credentials.projectId,
|
|
@@ -880,12 +954,8 @@ async function maybeOfferConnect(credentials, options, _consoleBase) {
|
|
|
880
954
|
wait: true
|
|
881
955
|
});
|
|
882
956
|
} catch (err) {
|
|
883
|
-
p.log.warn(
|
|
884
|
-
|
|
885
|
-
);
|
|
886
|
-
p.log.info(
|
|
887
|
-
"You can run manually: mushi connect --write-env --wire-ide --wait"
|
|
888
|
-
);
|
|
957
|
+
p.log.warn(err instanceof Error ? err.message : String(err));
|
|
958
|
+
p.log.info("You can run manually: mushi connect --write-env --wire-ide --wait");
|
|
889
959
|
}
|
|
890
960
|
}
|
|
891
961
|
async function maybeEnableRewards(options) {
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mushi-mushi/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.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": {
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
"commander": "^15.0.0"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@types/node": "^22.
|
|
15
|
-
"eslint": "^10.
|
|
14
|
+
"@types/node": "^22.20.0",
|
|
15
|
+
"eslint": "^10.6.0",
|
|
16
16
|
"tsup": "^8.5.1",
|
|
17
17
|
"typescript": "^6.0.3",
|
|
18
|
-
"vitest": "^4.1.
|
|
18
|
+
"vitest": "^4.1.9",
|
|
19
19
|
"@mushi-mushi/eslint-config": "0.0.0"
|
|
20
20
|
},
|
|
21
21
|
"author": "Kenji Sakuramoto (https://bsky.app/profile/mushimushi.dev)",
|