@mushi-mushi/cli 0.34.0 → 0.35.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 CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  The `mushi` command for [Mushi Mushi](https://github.com/kensaurus/mushi-mushi). Install the SDK in your app, connect your editor, then read bugs, dispatch fixes and merge them without leaving the terminal.
6
6
 
7
+ Docs: [CLI reference](https://kensaur.us/mushi-mushi/docs/sdks/cli)
8
+
7
9
  ```mermaid
8
10
  flowchart LR
9
11
  A["mushi init<br/>SDK in your app"] --> B["mushi setup<br/>editor reads diagnoses"] --> C["mushi reports<br/>see what broke"] --> D["mushi fix<br/>agent opens a draft PR"] --> E["mushi fixes merge"]
@@ -70,7 +72,7 @@ Other `init` flags: `--framework next`, `--cwd apps/web` (monorepos), `--endpoin
70
72
  | `MUSHI_NO_UPDATE_CHECK=1` | Skip the "newer version" hint |
71
73
  | `MUSHI_DEBUG=1` | Full stack traces on error |
72
74
 
73
- Config lives in `~/.config/mushi/config.json`, written with mode `0o600` (a legacy `~/.mushirc` is auto-migrated). `--endpoint` must be `https://` except on localhost.
75
+ Config lives in `~/.config/mushi/config.json` (`%APPDATA%\mushi\config.json` on Windows, `$XDG_CONFIG_HOME/mushi/config.json` when that is set), written with mode `0o600` on macOS and Linux and an account-only ACL on Windows (a legacy `~/.mushirc` is auto-migrated). `--endpoint` must be `https://` except on localhost.
74
76
 
75
77
  ## Programmatic use
76
78
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MUSHI_CLI_VERSION
3
- } from "./chunk-CY2BHCA5.js";
3
+ } from "./chunk-HXLBH5GE.js";
4
4
  import {
5
5
  assertEndpoint,
6
6
  normalizeEndpoint
@@ -26,11 +26,14 @@ function getEntryClass() {
26
26
  return _EntryClass ?? null;
27
27
  }
28
28
  function trySaveKeyToKeychain(apiKey, profile = "default") {
29
+ return saveSecret(accountName(profile), apiKey);
30
+ }
31
+ function saveSecret(account, apiKey) {
29
32
  if (!apiKey) return false;
30
33
  const EntryClass = getEntryClass();
31
34
  if (!EntryClass) return false;
32
35
  try {
33
- const entry = new EntryClass(KEYCHAIN_SERVICE, accountName(profile));
36
+ const entry = new EntryClass(KEYCHAIN_SERVICE, account);
34
37
  entry.setPassword(apiKey);
35
38
  return true;
36
39
  } catch {
@@ -38,10 +41,13 @@ function trySaveKeyToKeychain(apiKey, profile = "default") {
38
41
  }
39
42
  }
40
43
  function tryLoadKeyFromKeychain(profile = "default") {
44
+ return loadSecret(accountName(profile));
45
+ }
46
+ function loadSecret(account) {
41
47
  const EntryClass = getEntryClass();
42
48
  if (!EntryClass) return null;
43
49
  try {
44
- const entry = new EntryClass(KEYCHAIN_SERVICE, accountName(profile));
50
+ const entry = new EntryClass(KEYCHAIN_SERVICE, account);
45
51
  const key = entry.getPassword();
46
52
  return key && key.length > 0 ? key : null;
47
53
  } catch {
@@ -0,0 +1,432 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ detectFramework,
4
+ envVarsToWrite,
5
+ readPackageJson
6
+ } from "./chunk-KOBEOGNF.js";
7
+ import {
8
+ CONFIG_PATH,
9
+ requireUuid,
10
+ saveConfig,
11
+ savedSdkKeyFor
12
+ } from "./chunk-T36WTCFM.js";
13
+ import {
14
+ MUSHI_MCP_PIN_SPEC
15
+ } from "./chunk-GNTEONTU.js";
16
+ import {
17
+ assertEndpoint,
18
+ normalizeEndpoint
19
+ } from "./chunk-LGGE3LH7.js";
20
+
21
+ // src/connect.ts
22
+ import { appendFile, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
23
+ import { join, resolve } from "path";
24
+
25
+ // src/sanitize-config.ts
26
+ 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;
27
+ var API_KEY_RE = /^(mushi_|mush_pk_)[A-Za-z0-9_-]{10,}$/;
28
+ function sanitizeApiKey(raw) {
29
+ const key = raw.replace(/[\r\n\0]/g, "");
30
+ if (!API_KEY_RE.test(key)) {
31
+ throw new Error(
32
+ "Invalid API key in config \u2014 run `mushi login --api-key <key>` to refresh credentials."
33
+ );
34
+ }
35
+ return key;
36
+ }
37
+ function sanitizeProjectId(raw) {
38
+ const id = raw.trim();
39
+ if (!PROJECT_ID_RE.test(id)) {
40
+ throw new Error(
41
+ "Invalid project ID in config \u2014 expected a UUID or proj_* slug from the admin console."
42
+ );
43
+ }
44
+ return id;
45
+ }
46
+ function sanitizeEndpoint(raw) {
47
+ return assertEndpoint(normalizeEndpoint(raw));
48
+ }
49
+ function sanitizeCliCredentials(config) {
50
+ if (!config.endpoint || !config.apiKey || !config.projectId) {
51
+ throw new Error("Missing endpoint, apiKey, or projectId");
52
+ }
53
+ return {
54
+ endpoint: sanitizeEndpoint(config.endpoint),
55
+ apiKey: sanitizeApiKey(config.apiKey),
56
+ projectId: sanitizeProjectId(config.projectId)
57
+ };
58
+ }
59
+ function apiKeyHeaders(apiKey, projectId) {
60
+ const headers = {
61
+ Authorization: `Bearer ${apiKey}`,
62
+ "X-Mushi-Api-Key": apiKey
63
+ };
64
+ if (projectId) headers["X-Mushi-Project"] = projectId;
65
+ return headers;
66
+ }
67
+
68
+ // src/heartbeat-wait.ts
69
+ var IngestSetupHttpError = class extends Error {
70
+ status;
71
+ constructor(status) {
72
+ super(`ingest-setup HTTP ${status}`);
73
+ this.name = "IngestSetupHttpError";
74
+ this.status = status;
75
+ }
76
+ };
77
+ var NON_RETRYABLE_STATUSES = /* @__PURE__ */ new Set([401, 403, 404]);
78
+ async function fetchIngestSetup(config, doFetch = globalThis.fetch) {
79
+ const endpoint = sanitizeEndpoint(config.endpoint);
80
+ const apiKey = sanitizeApiKey(config.apiKey);
81
+ const projectId = config.projectId ? sanitizeProjectId(config.projectId) : void 0;
82
+ const res = await doFetch(`${endpoint}/v1/sync/ingest-setup`, {
83
+ headers: apiKeyHeaders(apiKey, projectId),
84
+ signal: AbortSignal.timeout(8e3)
85
+ });
86
+ if (!res.ok) {
87
+ if (NON_RETRYABLE_STATUSES.has(res.status)) throw new IngestSetupHttpError(res.status);
88
+ return null;
89
+ }
90
+ const body = await res.json();
91
+ return body.ok && body.data ? body.data : null;
92
+ }
93
+ async function waitForIngestReady(options) {
94
+ const doFetch = options.fetch ?? globalThis.fetch;
95
+ const maxAttempts = options.maxAttempts ?? 40;
96
+ const intervalMs = options.intervalMs ?? 3e3;
97
+ let lastPayload = null;
98
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
99
+ if (options.signal?.aborted) {
100
+ return { ok: false, payload: lastPayload, attempts: attempt - 1, reason: "aborted" };
101
+ }
102
+ try {
103
+ lastPayload = await fetchIngestSetup(
104
+ { endpoint: options.endpoint, apiKey: options.apiKey, projectId: options.projectId },
105
+ doFetch
106
+ );
107
+ if (lastPayload) {
108
+ options.onPoll?.(lastPayload, attempt);
109
+ if (lastPayload.ready) {
110
+ return { ok: true, payload: lastPayload, attempts: attempt, reason: "ready" };
111
+ }
112
+ const sdkStep = lastPayload.steps.find((s) => s.id === "sdk_installed");
113
+ if (sdkStep?.complete) {
114
+ return { ok: true, payload: lastPayload, attempts: attempt, reason: "heartbeat" };
115
+ }
116
+ }
117
+ } catch (err) {
118
+ const msg = err instanceof Error ? err.message : String(err);
119
+ if (err instanceof IngestSetupHttpError) {
120
+ return { ok: false, payload: lastPayload, attempts: attempt, reason: "unauthorized", error: msg };
121
+ }
122
+ if (options.signal?.aborted) {
123
+ return { ok: false, payload: lastPayload, attempts: attempt, reason: "aborted", error: msg };
124
+ }
125
+ if (attempt === maxAttempts) {
126
+ return { ok: false, payload: lastPayload, attempts: attempt, reason: "fetch-error", error: msg };
127
+ }
128
+ }
129
+ if (attempt < maxAttempts) {
130
+ await new Promise((r) => setTimeout(r, intervalMs));
131
+ }
132
+ }
133
+ return { ok: false, payload: lastPayload, attempts: maxAttempts, reason: "timeout" };
134
+ }
135
+
136
+ // src/key-scopes.ts
137
+ var SDK_KEY_SCOPES = ["report:write"];
138
+ var CLI_KEY_SCOPES = ["report:write", "mcp:read"];
139
+ var FULL_CLI_KEY_SCOPES = ["report:write", "mcp:read", "mcp:write"];
140
+ var SDK_KEY_LABEL = "sdk-ingest";
141
+ var CLI_KEY_LABEL = "cli-login";
142
+ async function probeKeyScope(endpoint, apiKey, projectId, doFetch = globalThis.fetch) {
143
+ let base = endpoint;
144
+ while (base.endsWith("/")) base = base.slice(0, -1);
145
+ let res;
146
+ try {
147
+ res = await doFetch(`${base}/v1/admin/mcp/account-overview`, {
148
+ headers: { "X-Mushi-Api-Key": apiKey, "X-Mushi-Project": projectId },
149
+ signal: AbortSignal.timeout(8e3)
150
+ });
151
+ } catch (err) {
152
+ return { result: "unreachable", message: err instanceof Error ? err.message : String(err) };
153
+ }
154
+ if (res.ok) return { result: "mcp", status: res.status };
155
+ if (res.status === 401) return { result: "invalid", status: res.status };
156
+ if (res.status === 403) {
157
+ const body = await res.json().catch(() => null);
158
+ const code = body?.error?.code ?? body?.code;
159
+ if (code === "INSUFFICIENT_SCOPE") return { result: "ingest-only", status: res.status };
160
+ }
161
+ return { result: "unknown", status: res.status };
162
+ }
163
+ function describeUnsafeSdkKey(probe, endpoint) {
164
+ switch (probe.result) {
165
+ case "mcp":
166
+ return "this key can read your bug reports (mcp scope), and SDK env vars ship inside the app bundle";
167
+ case "invalid":
168
+ return "the backend rejected this key";
169
+ case "unreachable":
170
+ return `could not reach ${endpoint} to confirm the key is ingest-only (${probe.message ?? "network error"})`;
171
+ case "unknown":
172
+ return `could not confirm the key is ingest-only (HTTP ${probe.status ?? "?"})`;
173
+ case "ingest-only":
174
+ return "the key is ingest-only";
175
+ }
176
+ }
177
+
178
+ // src/mcp-config.ts
179
+ import { mkdir, readFile, writeFile } from "fs/promises";
180
+ import { dirname } from "path";
181
+ var DEFAULT_MUSHI_FEATURES = "triage,fixes,inventory,setup,docs";
182
+ var MCP_CLIENTS = ["cursor", "claude", "continue", "zed"];
183
+ function isMcpClient(value) {
184
+ return typeof value === "string" && MCP_CLIENTS.includes(value);
185
+ }
186
+ var MCP_CLIENT_LABEL = {
187
+ cursor: "Cursor",
188
+ claude: "Claude Code",
189
+ continue: "Continue",
190
+ zed: "Zed"
191
+ };
192
+ function buildHostedMcpServerBlock(url, oauth) {
193
+ return oauth ? { type: "http", url, oauth } : { type: "http", url };
194
+ }
195
+ function isPlainObject(value) {
196
+ return typeof value === "object" && value !== null && !Array.isArray(value);
197
+ }
198
+ async function writeMcpServerEntry(opts) {
199
+ const { configPath, serverName, serverBlock, dryRun = false } = opts;
200
+ let existing = null;
201
+ try {
202
+ existing = await readFile(configPath, "utf8");
203
+ } catch {
204
+ existing = null;
205
+ }
206
+ let merged = {};
207
+ if (existing !== null && existing.trim() !== "") {
208
+ let parsed;
209
+ try {
210
+ parsed = JSON.parse(existing);
211
+ } catch {
212
+ throw new Error(`${configPath} is not valid JSON \u2014 fix or remove it, then re-run (nothing was written).`);
213
+ }
214
+ if (!isPlainObject(parsed)) {
215
+ throw new Error(`${configPath} is not a JSON object \u2014 fix or remove it, then re-run (nothing was written).`);
216
+ }
217
+ merged = parsed;
218
+ }
219
+ const currentServers = merged.mcpServers;
220
+ if (currentServers !== void 0 && !isPlainObject(currentServers)) {
221
+ throw new Error(`${configPath} has an "mcpServers" field that is not an object \u2014 fix it, then re-run (nothing was written).`);
222
+ }
223
+ merged.mcpServers = { ...currentServers ?? {}, [serverName]: serverBlock };
224
+ const output = JSON.stringify(merged, null, 2) + "\n";
225
+ if (!dryRun) {
226
+ await mkdir(dirname(configPath), { recursive: true });
227
+ await writeFile(configPath, output, "utf8");
228
+ }
229
+ return { created: existing === null, path: configPath };
230
+ }
231
+ function apiKeyPlaceholderFor(client) {
232
+ switch (client) {
233
+ case "cursor":
234
+ return "${env:MUSHI_API_KEY}";
235
+ case "claude":
236
+ return "${MUSHI_API_KEY:-}";
237
+ case "continue":
238
+ case "zed":
239
+ return null;
240
+ }
241
+ }
242
+ function buildMcpServerBlock(opts) {
243
+ const keyValue = opts.inlineKey ? opts.apiKey : apiKeyPlaceholderFor(opts.client);
244
+ return {
245
+ command: "npx",
246
+ args: ["-y", MUSHI_MCP_PIN_SPEC],
247
+ env: {
248
+ MUSHI_API_ENDPOINT: opts.endpoint,
249
+ MUSHI_PROJECT_ID: opts.projectId,
250
+ ...keyValue !== null ? { MUSHI_API_KEY: keyValue } : {},
251
+ MUSHI_FEATURES: DEFAULT_MUSHI_FEATURES
252
+ }
253
+ };
254
+ }
255
+ function printKeyExportHint(client, configPath = CONFIG_PATH) {
256
+ console.log("");
257
+ console.log(" No API key was written into the MCP config (safe to commit).");
258
+ console.log(` The MCP server uses the key \`mushi login\` saved in ${configPath}.`);
259
+ if (apiKeyPlaceholderFor(client) !== null) {
260
+ console.log(` To use a different key, export MUSHI_API_KEY in the environment ${MCP_CLIENT_LABEL[client]} is launched from.`);
261
+ }
262
+ console.log(" To write the key into the file instead (keep it out of git), re-run with --inline-key.");
263
+ }
264
+ function buildMcpServerName(opts) {
265
+ if (opts.legacy) return "mushi";
266
+ if (opts.projectName) {
267
+ const slug = opts.projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 24);
268
+ return `mushi-${slug}`;
269
+ }
270
+ return `mushi-${(opts.projectId ?? "unknown").slice(0, 8)}`;
271
+ }
272
+
273
+ // src/connect.ts
274
+ async function resolveSdkEnvKey(opts) {
275
+ const candidate = opts.sdkKey ?? savedSdkKeyFor(opts.baseConfig, opts.projectId) ?? opts.apiKey;
276
+ const probe = await opts.probe(opts.endpoint, candidate, opts.projectId);
277
+ if (probe.result === "ingest-only") return { ok: true, key: candidate };
278
+ return { ok: false, reason: describeUnsafeSdkKey(probe, opts.endpoint) };
279
+ }
280
+ function envKeyPresent(content, key) {
281
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
282
+ return new RegExp(`^${escaped}=`, "m").test(content);
283
+ }
284
+ async function mergeEnvFile(path, lines) {
285
+ const block = `
286
+ # Mushi \u2014 added by mushi connect
287
+ ${lines.join("\n")}
288
+ `;
289
+ let existing = null;
290
+ try {
291
+ existing = await readFile2(path, "utf8");
292
+ } catch {
293
+ }
294
+ if (existing !== null) {
295
+ const needs = lines.filter((line) => {
296
+ const key = line.split("=")[0];
297
+ return !envKeyPresent(existing, key);
298
+ });
299
+ if (needs.length === 0) return false;
300
+ await appendFile(path, `
301
+ # Mushi \u2014 added by mushi connect
302
+ ${needs.join("\n")}
303
+ `, "utf8");
304
+ return true;
305
+ }
306
+ await writeFile2(path, block, "utf8");
307
+ return true;
308
+ }
309
+ async function runConnect(opts, baseConfig = {}) {
310
+ const cwd = resolve(opts.cwd ?? process.cwd());
311
+ const endpoint = assertEndpoint(opts.endpoint);
312
+ const projectId = requireUuid(opts.projectId, "projectId");
313
+ const messages = [];
314
+ const writeEnv = opts.writeEnv !== false;
315
+ const sdkEnvKey = writeEnv ? await resolveSdkEnvKey({
316
+ sdkKey: opts.sdkKey,
317
+ apiKey: opts.apiKey,
318
+ projectId,
319
+ endpoint,
320
+ baseConfig,
321
+ probe: opts.probeKeyScope ?? probeKeyScope
322
+ }) : null;
323
+ const config = {
324
+ ...baseConfig,
325
+ apiKey: opts.apiKey,
326
+ projectId,
327
+ endpoint,
328
+ ...sdkEnvKey?.ok ? { sdkKey: { projectId, key: sdkEnvKey.key } } : {}
329
+ };
330
+ saveConfig(config);
331
+ messages.push(`\u2713 Credentials saved to ${CONFIG_PATH}`);
332
+ let envPath = null;
333
+ let envRefused = false;
334
+ if (sdkEnvKey && !sdkEnvKey.ok) {
335
+ envRefused = true;
336
+ messages.push(
337
+ `\u2717 Did not write SDK env vars: ${sdkEnvKey.reason}. Pass an ingest-only key with --sdk-key <key> (console \u2192 Projects \u2192 API keys, scope report:write), or run \`npx mushi-mushi\`, which mints one.`
338
+ );
339
+ } else if (sdkEnvKey?.ok) {
340
+ const pkg = readPackageJson(cwd);
341
+ const framework = detectFramework(cwd, pkg);
342
+ const lines = envVarsToWrite(sdkEnvKey.key, projectId, framework).split("\n");
343
+ envPath = join(cwd, ".env.local");
344
+ const wrote = await mergeEnvFile(envPath, lines);
345
+ messages.push(
346
+ wrote ? `\u2713 Env vars merged into ${envPath}` : `\u2713 Env vars already present in ${envPath} (existing values left untouched)`
347
+ );
348
+ const deps = { ...pkg?.dependencies ?? {}, ...pkg?.devDependencies ?? {} };
349
+ if (deps["@capacitor/core"] && deps["react"]) {
350
+ messages.push(
351
+ "\u2139 Capacitor + React detected \u2014 install @mushi-mushi/web (or @mushi-mushi/react) and call initMushi() in main.tsx. Optional @mushi-mushi/capacitor for native shell parity."
352
+ );
353
+ }
354
+ }
355
+ let mcpPath = null;
356
+ if (opts.wireIde !== false) {
357
+ const mcpDir = join(cwd, ".cursor");
358
+ mcpPath = join(mcpDir, "mcp.json");
359
+ const serverName = buildMcpServerName({ projectId });
360
+ const serverBlock = buildMcpServerBlock({
361
+ endpoint,
362
+ projectId,
363
+ apiKey: opts.apiKey,
364
+ client: "cursor",
365
+ inlineKey: false
366
+ // no secret in the file; the MCP server reads the saved CLI key
367
+ });
368
+ await writeMcpServerEntry({ configPath: mcpPath, serverName, serverBlock });
369
+ messages.push(`\u2713 Wired ${mcpPath} \u2014 restart Cursor and run "list mushi tools"`);
370
+ messages.push(` The MCP server reads the key saved in ${CONFIG_PATH} (export MUSHI_API_KEY before launching Cursor to override it).`);
371
+ }
372
+ let heartbeat = null;
373
+ if (opts.wait) {
374
+ const timeoutSec = opts.waitTimeoutSec ?? 120;
375
+ const maxAttempts = Math.max(1, Math.ceil(timeoutSec * 1e3 / 3e3));
376
+ messages.push(`\u2026 Waiting for SDK heartbeat (up to ${timeoutSec}s) \u2014 start your dev server with the snippet installed`);
377
+ heartbeat = await waitForIngestReady({
378
+ endpoint,
379
+ apiKey: opts.apiKey,
380
+ projectId,
381
+ maxAttempts,
382
+ onPoll: (payload, attempt) => {
383
+ if (!opts.json && attempt % 3 === 0) {
384
+ const sdk = payload.steps.find((s) => s.id === "sdk_installed");
385
+ const seen = payload.diagnostic?.last_sdk_seen_at ?? "never";
386
+ process.stdout.write(` poll ${attempt}: sdk_installed=${sdk?.complete ? "yes" : "no"} last_seen=${seen}
387
+ `);
388
+ }
389
+ }
390
+ });
391
+ if (heartbeat.ok) {
392
+ const label = heartbeat.reason === "heartbeat" ? "SDK heartbeat detected" : "Ingest setup complete";
393
+ messages.push(`\u2713 ${label} \u2014 ingest pipeline is live`);
394
+ } else if (heartbeat.reason === "unauthorized") {
395
+ messages.push(
396
+ `\u2717 The backend rejected these credentials (${heartbeat.error ?? "auth error"}). Double-check --api-key, --project-id, and --endpoint, then re-run \`mushi connect --wait\`.`
397
+ );
398
+ } else {
399
+ messages.push(
400
+ `\u2717 No heartbeat before timeout (${heartbeat.reason}). Confirm env vars are in your build, restart the dev server, then re-run \`mushi connect --wait\`.`
401
+ );
402
+ }
403
+ }
404
+ const ok = !envRefused && (!opts.wait || Boolean(heartbeat?.ok));
405
+ return { ok, envPath, mcpPath, heartbeat, messages };
406
+ }
407
+
408
+ export {
409
+ SDK_KEY_SCOPES,
410
+ CLI_KEY_SCOPES,
411
+ FULL_CLI_KEY_SCOPES,
412
+ SDK_KEY_LABEL,
413
+ CLI_KEY_LABEL,
414
+ probeKeyScope,
415
+ describeUnsafeSdkKey,
416
+ sanitizeApiKey,
417
+ sanitizeProjectId,
418
+ sanitizeEndpoint,
419
+ sanitizeCliCredentials,
420
+ apiKeyHeaders,
421
+ fetchIngestSetup,
422
+ MCP_CLIENTS,
423
+ isMcpClient,
424
+ MCP_CLIENT_LABEL,
425
+ buildHostedMcpServerBlock,
426
+ writeMcpServerEntry,
427
+ buildMcpServerBlock,
428
+ printKeyExportHint,
429
+ buildMcpServerName,
430
+ resolveSdkEnvKey,
431
+ runConnect
432
+ };
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/version.ts
4
+ var MUSHI_CLI_VERSION = true ? "0.35.0" : "0.0.0-dev";
5
+ var MUSHI_MCP_PIN_SPEC = "@mushi-mushi/mcp@0.30.0";
6
+
7
+ export {
8
+ MUSHI_CLI_VERSION,
9
+ MUSHI_MCP_PIN_SPEC
10
+ };
@@ -0,0 +1,8 @@
1
+ // src/version.ts
2
+ var MUSHI_CLI_VERSION = true ? "0.35.0" : "0.0.0-dev";
3
+ var MUSHI_MCP_PIN_SPEC = "@mushi-mushi/mcp@0.30.0";
4
+
5
+ export {
6
+ MUSHI_CLI_VERSION,
7
+ MUSHI_MCP_PIN_SPEC
8
+ };