@mushi-mushi/cli 0.26.0 → 0.27.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 +10 -2
- package/README.md +25 -0
- package/SECURITY.md +73 -1
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/{chunk-VIHWH3IG.js → chunk-7JZUKNN5.js} +210 -11
- package/dist/chunk-LFZ5ZIBI.js +8 -0
- package/dist/chunk-MXD5UNMP.js +10 -0
- package/dist/chunk-PDX44BCA.js +11 -0
- package/dist/{chunk-L3LUPBFG.js → chunk-TZL3F3GT.js} +242 -8
- package/dist/{connect-HCWJ6NXP.js → connect-FAXWVFG2.js} +13 -108
- package/dist/{connect-PKWAK4GL.js → connect-QHVFH77E.js} +3 -2
- package/dist/detect.js +1 -0
- package/dist/{endpoint-DGS73AVM.js → endpoint-TEBF4IH6.js} +1 -0
- package/dist/index.js +3883 -3401
- package/dist/init.d.ts +2 -0
- package/dist/init.js +841 -10
- package/dist/{snippet-inject-NDAPEL2T.js → snippet-inject-7ABYE57O.js} +2 -0
- package/dist/{snippet-inject-LPRWTQDE.js → snippet-inject-CCXILWYZ.js} +1 -0
- package/dist/version-F3ZOPM5F.js +10 -0
- package/dist/version.d.ts +9 -1
- package/dist/version.js +6 -3
- package/dist/wizard-args.d.ts +1 -0
- package/dist/wizard-args.js +6 -0
- package/package.json +4 -1
- package/dist/chunk-MCVRTQAI.js +0 -6
- package/dist/chunk-TYVJU4AH.js +0 -8
- package/dist/version-4JARRCFG.js +0 -7
|
@@ -1,12 +1,55 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MUSHI_CLI_VERSION
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LFZ5ZIBI.js";
|
|
4
|
+
import {
|
|
5
|
+
__require
|
|
6
|
+
} from "./chunk-3RG5ZIWI.js";
|
|
4
7
|
|
|
5
8
|
// src/config.ts
|
|
6
9
|
import { randomUUID } from "crypto";
|
|
10
|
+
import { spawnSync } from "child_process";
|
|
7
11
|
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
8
12
|
import { homedir } from "os";
|
|
9
13
|
import { dirname, join } from "path";
|
|
14
|
+
|
|
15
|
+
// src/keychain.ts
|
|
16
|
+
var KEYCHAIN_SERVICE = "mushi-cli";
|
|
17
|
+
function accountName(profile = "default") {
|
|
18
|
+
return `api-key:${profile}`;
|
|
19
|
+
}
|
|
20
|
+
var _EntryClass = void 0;
|
|
21
|
+
function getEntryClass() {
|
|
22
|
+
if (_EntryClass === void 0) {
|
|
23
|
+
try {
|
|
24
|
+
const mod = __require("@napi-rs/keyring");
|
|
25
|
+
_EntryClass = mod.Entry ?? null;
|
|
26
|
+
} catch {
|
|
27
|
+
_EntryClass = null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return _EntryClass ?? null;
|
|
31
|
+
}
|
|
32
|
+
function tryLoadKeyFromKeychain(profile = "default") {
|
|
33
|
+
const EntryClass = getEntryClass();
|
|
34
|
+
if (!EntryClass) return null;
|
|
35
|
+
try {
|
|
36
|
+
const entry = new EntryClass(KEYCHAIN_SERVICE, accountName(profile));
|
|
37
|
+
const key = entry.getPassword();
|
|
38
|
+
return key && key.length > 0 ? key : null;
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/config.ts
|
|
45
|
+
var DEFAULT_PROFILE = "default";
|
|
46
|
+
function isMultiProfileFile(value) {
|
|
47
|
+
return typeof value === "object" && value !== null && "profiles" in value && typeof value.profiles === "object" && value.profiles !== null;
|
|
48
|
+
}
|
|
49
|
+
function resolveProfileName(explicit, fileActive) {
|
|
50
|
+
const fromEnv = process.env["MUSHI_PROFILE"]?.trim();
|
|
51
|
+
return explicit?.trim() || fromEnv || fileActive || DEFAULT_PROFILE;
|
|
52
|
+
}
|
|
10
53
|
var SECURE_FILE_MODE = 384;
|
|
11
54
|
var SECURE_DIR_MODE = 448;
|
|
12
55
|
function resolveXdgConfigPath() {
|
|
@@ -24,17 +67,28 @@ function resolveXdgConfigPath() {
|
|
|
24
67
|
}
|
|
25
68
|
var LEGACY_CONFIG_PATH = join(homedir(), ".mushirc");
|
|
26
69
|
var CONFIG_PATH = resolveXdgConfigPath();
|
|
27
|
-
function loadConfig(path = CONFIG_PATH) {
|
|
70
|
+
function loadConfig(path = CONFIG_PATH, opts = {}) {
|
|
28
71
|
let file = {};
|
|
29
72
|
if (existsSync(path)) {
|
|
30
73
|
tightenPermissions(path);
|
|
31
74
|
try {
|
|
32
|
-
|
|
75
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
76
|
+
if (isMultiProfileFile(parsed)) {
|
|
77
|
+
const name = resolveProfileName(opts.profile, parsed.activeProfile);
|
|
78
|
+
file = parsed.profiles[name] ?? {};
|
|
79
|
+
} else {
|
|
80
|
+
file = parsed ?? {};
|
|
81
|
+
}
|
|
33
82
|
} catch {
|
|
34
83
|
}
|
|
35
84
|
} else if (path === CONFIG_PATH && existsSync(LEGACY_CONFIG_PATH)) {
|
|
36
85
|
file = migrateLegacyConfig() ?? {};
|
|
37
86
|
}
|
|
87
|
+
const profile = resolveProfileName(opts.profile, void 0);
|
|
88
|
+
const keychainKey = tryLoadKeyFromKeychain(profile);
|
|
89
|
+
if (keychainKey) {
|
|
90
|
+
file = { ...file, apiKey: keychainKey };
|
|
91
|
+
}
|
|
38
92
|
const endpointFromEnv = process.env["MUSHI_API_ENDPOINT"] ?? process.env["MUSHI_ENDPOINT"] ?? void 0;
|
|
39
93
|
const fromEnv = {
|
|
40
94
|
...process.env["MUSHI_API_KEY"] ? { apiKey: process.env["MUSHI_API_KEY"] } : {},
|
|
@@ -56,14 +110,47 @@ function ensureClientId(path = CONFIG_PATH) {
|
|
|
56
110
|
}
|
|
57
111
|
return clientId;
|
|
58
112
|
}
|
|
59
|
-
function
|
|
113
|
+
function maybeShowTelemetryNotice(config, path = CONFIG_PATH) {
|
|
114
|
+
if (config.telemetryNoticeShown) return;
|
|
115
|
+
if (process.env.MUSHI_NO_TELEMETRY) {
|
|
116
|
+
saveConfig({ ...config, telemetryNoticeShown: true }, path);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
console.log("");
|
|
120
|
+
console.log(" \u{1F4CA} Mushi collects minimal anonymous usage data to improve the CLI");
|
|
121
|
+
console.log(" (funnel step + error category \u2014 never your code or bug content).");
|
|
122
|
+
console.log(" To opt out: export MUSHI_NO_TELEMETRY=1");
|
|
123
|
+
console.log("");
|
|
124
|
+
saveConfig({ ...config, telemetryNoticeShown: true }, path);
|
|
125
|
+
}
|
|
126
|
+
function saveConfig(config, path = CONFIG_PATH, opts = {}) {
|
|
60
127
|
const dir = dirname(path);
|
|
61
128
|
if (!existsSync(dir)) {
|
|
62
129
|
mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
63
130
|
} else {
|
|
64
131
|
tightenDirPermissions(dir);
|
|
65
132
|
}
|
|
66
|
-
|
|
133
|
+
let existing = null;
|
|
134
|
+
try {
|
|
135
|
+
existing = JSON.parse(readFileSync(path, "utf-8"));
|
|
136
|
+
} catch {
|
|
137
|
+
existing = null;
|
|
138
|
+
}
|
|
139
|
+
const wantsProfile = opts.profile !== void 0 || process.env["MUSHI_PROFILE"] !== void 0 || isMultiProfileFile(existing);
|
|
140
|
+
if (!wantsProfile) {
|
|
141
|
+
writeFileSync(path, JSON.stringify(config, null, 2), { mode: SECURE_FILE_MODE });
|
|
142
|
+
tightenPermissions(path);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const file = isMultiProfileFile(existing) ? { version: 2, activeProfile: existing.activeProfile || DEFAULT_PROFILE, profiles: { ...existing.profiles } } : {
|
|
146
|
+
version: 2,
|
|
147
|
+
activeProfile: DEFAULT_PROFILE,
|
|
148
|
+
// A pre-existing flat file becomes the default profile.
|
|
149
|
+
profiles: { [DEFAULT_PROFILE]: existing ?? {} }
|
|
150
|
+
};
|
|
151
|
+
const target = resolveProfileName(opts.profile, file.activeProfile);
|
|
152
|
+
file.profiles[target] = config;
|
|
153
|
+
writeFileSync(path, JSON.stringify(file, null, 2), { mode: SECURE_FILE_MODE });
|
|
67
154
|
tightenPermissions(path);
|
|
68
155
|
}
|
|
69
156
|
function migrateLegacyConfig(legacyPath = LEGACY_CONFIG_PATH, destPath = CONFIG_PATH) {
|
|
@@ -92,7 +179,10 @@ function migrateLegacyConfig(legacyPath = LEGACY_CONFIG_PATH, destPath = CONFIG_
|
|
|
92
179
|
return parsed;
|
|
93
180
|
}
|
|
94
181
|
function tightenPermissions(path) {
|
|
95
|
-
if (process.platform === "win32")
|
|
182
|
+
if (process.platform === "win32") {
|
|
183
|
+
tightenWindowsAcl(path, "file");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
96
186
|
try {
|
|
97
187
|
const current = statSync(path).mode & 511;
|
|
98
188
|
if (current !== SECURE_FILE_MODE) chmodSync(path, SECURE_FILE_MODE);
|
|
@@ -100,13 +190,40 @@ function tightenPermissions(path) {
|
|
|
100
190
|
}
|
|
101
191
|
}
|
|
102
192
|
function tightenDirPermissions(path) {
|
|
103
|
-
if (process.platform === "win32")
|
|
193
|
+
if (process.platform === "win32") {
|
|
194
|
+
tightenWindowsAcl(path, "dir");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
104
197
|
try {
|
|
105
198
|
const current = statSync(path).mode & 511;
|
|
106
199
|
if (current !== SECURE_DIR_MODE) chmodSync(path, SECURE_DIR_MODE);
|
|
107
200
|
} catch {
|
|
108
201
|
}
|
|
109
202
|
}
|
|
203
|
+
var _cachedWindowsUser = void 0;
|
|
204
|
+
var _aclTightened = /* @__PURE__ */ new Set();
|
|
205
|
+
function getWindowsUser() {
|
|
206
|
+
if (_cachedWindowsUser !== void 0) return _cachedWindowsUser;
|
|
207
|
+
try {
|
|
208
|
+
const result = spawnSync("whoami", [], { encoding: "utf8", timeout: 500 });
|
|
209
|
+
_cachedWindowsUser = result.stdout?.trim() || null;
|
|
210
|
+
} catch {
|
|
211
|
+
_cachedWindowsUser = null;
|
|
212
|
+
}
|
|
213
|
+
return _cachedWindowsUser;
|
|
214
|
+
}
|
|
215
|
+
function tightenWindowsAcl(path, type) {
|
|
216
|
+
if (_aclTightened.has(path)) return;
|
|
217
|
+
try {
|
|
218
|
+
const user = getWindowsUser();
|
|
219
|
+
if (!user) return;
|
|
220
|
+
const args = [path, "/inheritance:r", `/grant:r`, `${user}:F`, `SYSTEM:F`];
|
|
221
|
+
if (type === "dir") args.push("/T");
|
|
222
|
+
spawnSync("icacls", args, { encoding: "utf8", timeout: 1e3 });
|
|
223
|
+
_aclTightened.add(path);
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
}
|
|
110
227
|
|
|
111
228
|
// src/console-url.ts
|
|
112
229
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
@@ -415,10 +532,122 @@ async function apiCall(path, config, options = {}) {
|
|
|
415
532
|
}
|
|
416
533
|
}
|
|
417
534
|
|
|
535
|
+
// src/sanitize-config.ts
|
|
536
|
+
var PROJECT_ID_RE = /^(?:proj_[A-Za-z0-9_-]{10,}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
|
|
537
|
+
var API_KEY_RE = /^(mushi_|mush_pk_)[A-Za-z0-9_-]{10,}$/;
|
|
538
|
+
function sanitizeApiKey(raw) {
|
|
539
|
+
const key = raw.replace(/[\r\n\0]/g, "");
|
|
540
|
+
if (!API_KEY_RE.test(key)) {
|
|
541
|
+
throw new Error(
|
|
542
|
+
"Invalid API key in config \u2014 run `mushi login --api-key <key>` to refresh credentials."
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
return key;
|
|
546
|
+
}
|
|
547
|
+
function sanitizeProjectId(raw) {
|
|
548
|
+
const id = raw.trim();
|
|
549
|
+
if (!PROJECT_ID_RE.test(id)) {
|
|
550
|
+
throw new Error(
|
|
551
|
+
"Invalid project ID in config \u2014 expected a UUID or proj_* slug from the admin console."
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
return id;
|
|
555
|
+
}
|
|
556
|
+
function sanitizeEndpoint(raw) {
|
|
557
|
+
return assertEndpoint(normalizeEndpoint(raw));
|
|
558
|
+
}
|
|
559
|
+
function sanitizeCliCredentials(config) {
|
|
560
|
+
if (!config.endpoint || !config.apiKey || !config.projectId) {
|
|
561
|
+
throw new Error("Missing endpoint, apiKey, or projectId");
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
endpoint: sanitizeEndpoint(config.endpoint),
|
|
565
|
+
apiKey: sanitizeApiKey(config.apiKey),
|
|
566
|
+
projectId: sanitizeProjectId(config.projectId)
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
function apiKeyHeaders(apiKey, projectId) {
|
|
570
|
+
const headers = {
|
|
571
|
+
Authorization: `Bearer ${apiKey}`,
|
|
572
|
+
"X-Mushi-Api-Key": apiKey
|
|
573
|
+
};
|
|
574
|
+
if (projectId) headers["X-Mushi-Project"] = projectId;
|
|
575
|
+
return headers;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/heartbeat-wait.ts
|
|
579
|
+
var IngestSetupHttpError = class extends Error {
|
|
580
|
+
status;
|
|
581
|
+
constructor(status) {
|
|
582
|
+
super(`ingest-setup HTTP ${status}`);
|
|
583
|
+
this.name = "IngestSetupHttpError";
|
|
584
|
+
this.status = status;
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
var NON_RETRYABLE_STATUSES = /* @__PURE__ */ new Set([401, 403, 404]);
|
|
588
|
+
async function fetchIngestSetup(config, doFetch = globalThis.fetch) {
|
|
589
|
+
const endpoint = sanitizeEndpoint(config.endpoint);
|
|
590
|
+
const apiKey = sanitizeApiKey(config.apiKey);
|
|
591
|
+
const projectId = config.projectId ? sanitizeProjectId(config.projectId) : void 0;
|
|
592
|
+
const res = await doFetch(`${endpoint}/v1/sync/ingest-setup`, {
|
|
593
|
+
headers: apiKeyHeaders(apiKey, projectId),
|
|
594
|
+
signal: AbortSignal.timeout(8e3)
|
|
595
|
+
});
|
|
596
|
+
if (!res.ok) {
|
|
597
|
+
if (NON_RETRYABLE_STATUSES.has(res.status)) throw new IngestSetupHttpError(res.status);
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
const body = await res.json();
|
|
601
|
+
return body.ok && body.data ? body.data : null;
|
|
602
|
+
}
|
|
603
|
+
async function waitForIngestReady(options) {
|
|
604
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
605
|
+
const maxAttempts = options.maxAttempts ?? 40;
|
|
606
|
+
const intervalMs = options.intervalMs ?? 3e3;
|
|
607
|
+
let lastPayload = null;
|
|
608
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
609
|
+
if (options.signal?.aborted) {
|
|
610
|
+
return { ok: false, payload: lastPayload, attempts: attempt - 1, reason: "aborted" };
|
|
611
|
+
}
|
|
612
|
+
try {
|
|
613
|
+
lastPayload = await fetchIngestSetup(
|
|
614
|
+
{ endpoint: options.endpoint, apiKey: options.apiKey, projectId: options.projectId },
|
|
615
|
+
doFetch
|
|
616
|
+
);
|
|
617
|
+
if (lastPayload) {
|
|
618
|
+
options.onPoll?.(lastPayload, attempt);
|
|
619
|
+
if (lastPayload.ready) {
|
|
620
|
+
return { ok: true, payload: lastPayload, attempts: attempt, reason: "ready" };
|
|
621
|
+
}
|
|
622
|
+
const sdkStep = lastPayload.steps.find((s) => s.id === "sdk_installed");
|
|
623
|
+
if (sdkStep?.complete) {
|
|
624
|
+
return { ok: true, payload: lastPayload, attempts: attempt, reason: "heartbeat" };
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
} catch (err) {
|
|
628
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
629
|
+
if (err instanceof IngestSetupHttpError) {
|
|
630
|
+
return { ok: false, payload: lastPayload, attempts: attempt, reason: "unauthorized", error: msg };
|
|
631
|
+
}
|
|
632
|
+
if (options.signal?.aborted) {
|
|
633
|
+
return { ok: false, payload: lastPayload, attempts: attempt, reason: "aborted", error: msg };
|
|
634
|
+
}
|
|
635
|
+
if (attempt === maxAttempts) {
|
|
636
|
+
return { ok: false, payload: lastPayload, attempts: attempt, reason: "fetch-error", error: msg };
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (attempt < maxAttempts) {
|
|
640
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return { ok: false, payload: lastPayload, attempts: maxAttempts, reason: "timeout" };
|
|
644
|
+
}
|
|
645
|
+
|
|
418
646
|
export {
|
|
419
647
|
CONFIG_PATH,
|
|
420
648
|
loadConfig,
|
|
421
649
|
ensureClientId,
|
|
650
|
+
maybeShowTelemetryNotice,
|
|
422
651
|
saveConfig,
|
|
423
652
|
consoleUrl,
|
|
424
653
|
resolveConsoleUrlSync,
|
|
@@ -433,5 +662,10 @@ export {
|
|
|
433
662
|
assertEndpoint,
|
|
434
663
|
normalizeEndpoint,
|
|
435
664
|
requireUuid,
|
|
436
|
-
apiCall
|
|
665
|
+
apiCall,
|
|
666
|
+
sanitizeEndpoint,
|
|
667
|
+
sanitizeCliCredentials,
|
|
668
|
+
apiKeyHeaders,
|
|
669
|
+
fetchIngestSetup,
|
|
670
|
+
waitForIngestReady
|
|
437
671
|
};
|
|
@@ -1,122 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CONFIG_PATH,
|
|
3
3
|
assertEndpoint,
|
|
4
|
-
normalizeEndpoint,
|
|
5
4
|
requireUuid,
|
|
6
|
-
saveConfig
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
saveConfig,
|
|
6
|
+
waitForIngestReady
|
|
7
|
+
} from "./chunk-TZL3F3GT.js";
|
|
8
|
+
import {
|
|
9
|
+
MUSHI_MCP_PIN_SPEC
|
|
10
|
+
} from "./chunk-LFZ5ZIBI.js";
|
|
9
11
|
import {
|
|
10
12
|
detectFramework,
|
|
11
13
|
envVarsToWrite,
|
|
12
14
|
readPackageJson
|
|
13
15
|
} from "./chunk-AIZRI665.js";
|
|
16
|
+
import "./chunk-3RG5ZIWI.js";
|
|
14
17
|
|
|
15
18
|
// src/connect.ts
|
|
16
19
|
import { appendFile, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
17
20
|
import { join, resolve } from "path";
|
|
18
21
|
|
|
19
|
-
// src/sanitize-config.ts
|
|
20
|
-
var PROJECT_ID_RE = /^(?:proj_[A-Za-z0-9_-]{10,}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
|
|
21
|
-
var API_KEY_RE = /^(mushi_|mush_pk_)[A-Za-z0-9_-]{10,}$/;
|
|
22
|
-
function sanitizeApiKey(raw) {
|
|
23
|
-
const key = raw.replace(/[\r\n\0]/g, "");
|
|
24
|
-
if (!API_KEY_RE.test(key)) {
|
|
25
|
-
throw new Error(
|
|
26
|
-
"Invalid API key in config \u2014 run `mushi login --api-key <key>` to refresh credentials."
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
return key;
|
|
30
|
-
}
|
|
31
|
-
function sanitizeProjectId(raw) {
|
|
32
|
-
const id = raw.trim();
|
|
33
|
-
if (!PROJECT_ID_RE.test(id)) {
|
|
34
|
-
throw new Error(
|
|
35
|
-
"Invalid project ID in config \u2014 expected a UUID or proj_* slug from the admin console."
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
return id;
|
|
39
|
-
}
|
|
40
|
-
function sanitizeEndpoint(raw) {
|
|
41
|
-
return assertEndpoint(normalizeEndpoint(raw));
|
|
42
|
-
}
|
|
43
|
-
function apiKeyHeaders(apiKey, projectId) {
|
|
44
|
-
const headers = {
|
|
45
|
-
Authorization: `Bearer ${apiKey}`,
|
|
46
|
-
"X-Mushi-Api-Key": apiKey
|
|
47
|
-
};
|
|
48
|
-
if (projectId) headers["X-Mushi-Project"] = projectId;
|
|
49
|
-
return headers;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// src/heartbeat-wait.ts
|
|
53
|
-
var IngestSetupHttpError = class extends Error {
|
|
54
|
-
status;
|
|
55
|
-
constructor(status) {
|
|
56
|
-
super(`ingest-setup HTTP ${status}`);
|
|
57
|
-
this.name = "IngestSetupHttpError";
|
|
58
|
-
this.status = status;
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
var NON_RETRYABLE_STATUSES = /* @__PURE__ */ new Set([401, 403, 404]);
|
|
62
|
-
async function fetchIngestSetup(config, doFetch = globalThis.fetch) {
|
|
63
|
-
const endpoint = sanitizeEndpoint(config.endpoint);
|
|
64
|
-
const apiKey = sanitizeApiKey(config.apiKey);
|
|
65
|
-
const projectId = config.projectId ? sanitizeProjectId(config.projectId) : void 0;
|
|
66
|
-
const res = await doFetch(`${endpoint}/v1/sync/ingest-setup`, {
|
|
67
|
-
headers: apiKeyHeaders(apiKey, projectId),
|
|
68
|
-
signal: AbortSignal.timeout(8e3)
|
|
69
|
-
});
|
|
70
|
-
if (!res.ok) {
|
|
71
|
-
if (NON_RETRYABLE_STATUSES.has(res.status)) throw new IngestSetupHttpError(res.status);
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
const body = await res.json();
|
|
75
|
-
return body.ok && body.data ? body.data : null;
|
|
76
|
-
}
|
|
77
|
-
async function waitForIngestReady(options) {
|
|
78
|
-
const doFetch = options.fetch ?? globalThis.fetch;
|
|
79
|
-
const maxAttempts = options.maxAttempts ?? 40;
|
|
80
|
-
const intervalMs = options.intervalMs ?? 3e3;
|
|
81
|
-
let lastPayload = null;
|
|
82
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
83
|
-
if (options.signal?.aborted) {
|
|
84
|
-
return { ok: false, payload: lastPayload, attempts: attempt - 1, reason: "aborted" };
|
|
85
|
-
}
|
|
86
|
-
try {
|
|
87
|
-
lastPayload = await fetchIngestSetup(
|
|
88
|
-
{ endpoint: options.endpoint, apiKey: options.apiKey, projectId: options.projectId },
|
|
89
|
-
doFetch
|
|
90
|
-
);
|
|
91
|
-
if (lastPayload) {
|
|
92
|
-
options.onPoll?.(lastPayload, attempt);
|
|
93
|
-
if (lastPayload.ready) {
|
|
94
|
-
return { ok: true, payload: lastPayload, attempts: attempt, reason: "ready" };
|
|
95
|
-
}
|
|
96
|
-
const sdkStep = lastPayload.steps.find((s) => s.id === "sdk_installed");
|
|
97
|
-
if (sdkStep?.complete) {
|
|
98
|
-
return { ok: true, payload: lastPayload, attempts: attempt, reason: "heartbeat" };
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
} catch (err) {
|
|
102
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
103
|
-
if (err instanceof IngestSetupHttpError) {
|
|
104
|
-
return { ok: false, payload: lastPayload, attempts: attempt, reason: "unauthorized", error: msg };
|
|
105
|
-
}
|
|
106
|
-
if (options.signal?.aborted) {
|
|
107
|
-
return { ok: false, payload: lastPayload, attempts: attempt, reason: "aborted", error: msg };
|
|
108
|
-
}
|
|
109
|
-
if (attempt === maxAttempts) {
|
|
110
|
-
return { ok: false, payload: lastPayload, attempts: attempt, reason: "fetch-error", error: msg };
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (attempt < maxAttempts) {
|
|
114
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return { ok: false, payload: lastPayload, attempts: maxAttempts, reason: "timeout" };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
22
|
// src/mcp-config.ts
|
|
121
23
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
122
24
|
import { dirname } from "path";
|
|
@@ -149,11 +51,11 @@ async function writeMcpServerEntry(opts) {
|
|
|
149
51
|
function buildMcpServerBlock(opts) {
|
|
150
52
|
return {
|
|
151
53
|
command: "npx",
|
|
152
|
-
args: ["-y",
|
|
54
|
+
args: ["-y", MUSHI_MCP_PIN_SPEC],
|
|
153
55
|
env: {
|
|
154
56
|
MUSHI_API_ENDPOINT: opts.endpoint,
|
|
155
57
|
MUSHI_PROJECT_ID: opts.projectId,
|
|
156
|
-
MUSHI_API_KEY: opts.apiKey,
|
|
58
|
+
MUSHI_API_KEY: opts.inlineKey ? opts.apiKey : "${MUSHI_API_KEY}",
|
|
157
59
|
MUSHI_FEATURES: DEFAULT_MUSHI_FEATURES
|
|
158
60
|
}
|
|
159
61
|
};
|
|
@@ -254,11 +156,14 @@ async function runConnect(opts, baseConfig = {}) {
|
|
|
254
156
|
const serverBlock = buildMcpServerBlock({
|
|
255
157
|
endpoint,
|
|
256
158
|
projectId,
|
|
257
|
-
apiKey: opts.apiKey
|
|
159
|
+
apiKey: opts.apiKey,
|
|
160
|
+
inlineKey: false
|
|
161
|
+
// use ${MUSHI_API_KEY} placeholder; caller must export the key
|
|
258
162
|
});
|
|
259
163
|
await writeMcpServerEntry({ configPath: mcpPath, serverName, serverBlock });
|
|
260
164
|
await ensureMcpJsonGitignored(cwd, messages);
|
|
261
165
|
messages.push(`\u2713 Wired ${mcpPath} \u2014 restart Cursor and run "list mushi tools"`);
|
|
166
|
+
messages.push(` Set MUSHI_API_KEY="${opts.apiKey}" in your shell / .env.local before restarting the IDE.`);
|
|
262
167
|
}
|
|
263
168
|
let heartbeat = null;
|
|
264
169
|
if (opts.wait) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runConnect
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-7JZUKNN5.js";
|
|
5
|
+
import "./chunk-MXD5UNMP.js";
|
|
6
6
|
import "./chunk-LDPFTQLE.js";
|
|
7
|
+
import "./chunk-PDX44BCA.js";
|
|
7
8
|
export {
|
|
8
9
|
runConnect
|
|
9
10
|
};
|
package/dist/detect.js
CHANGED