@mthines/reaper-mcp 0.19.0-beta.19.2 → 0.19.0-beta.20.2

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/main.js CHANGED
@@ -27,10 +27,10 @@ var SCOPE_NAME = "reaper-mcp-server";
27
27
  function readServiceVersion() {
28
28
  try {
29
29
  const require2 = createRequire(import.meta.url);
30
- const __dirname3 = dirname(fileURLToPath(import.meta.url));
30
+ const __dirname2 = dirname(fileURLToPath(import.meta.url));
31
31
  const pkgPaths = [
32
- join(__dirname3, "..", "package.json"),
33
- join(__dirname3, "package.json")
32
+ join(__dirname2, "..", "package.json"),
33
+ join(__dirname2, "package.json")
34
34
  ];
35
35
  for (const p of pkgPaths) {
36
36
  try {
@@ -1375,6 +1375,59 @@ function registerMidiTools(server) {
1375
1375
  return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
1376
1376
  }
1377
1377
  );
1378
+ server.tool(
1379
+ "send_midi_cc",
1380
+ "Send a MIDI CC (continuous controller) event in real-time to a track's MIDI output. Auto-inserts the MCP MIDI Emitter JSFX if not present. Common CC numbers: 1=modulation, 7=volume, 10=pan, 11=expression, 64=sustain, 74=filter cutoff.",
1381
+ {
1382
+ trackIndex: z10.coerce.number().min(0).describe("0-based track index"),
1383
+ cc: z10.coerce.number().min(0).max(127).describe("CC number (0-127)"),
1384
+ value: z10.coerce.number().min(0).max(127).describe("CC value (0-127)"),
1385
+ channel: z10.coerce.number().min(0).max(15).optional().describe("MIDI channel 0-15 (default 0)")
1386
+ },
1387
+ async ({ trackIndex, cc, value, channel }) => {
1388
+ const res = await sendCommand("send_midi_cc", { trackIndex, cc, value, channel: channel ?? 0 });
1389
+ if (!res.success) {
1390
+ return { content: [{ type: "text", text: `Error: ${res.error}` }], isError: true };
1391
+ }
1392
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
1393
+ }
1394
+ );
1395
+ server.tool(
1396
+ "send_midi_pc",
1397
+ "Send a MIDI program change in real-time, optionally preceded by bank select (CC0 MSB + CC32 LSB). Auto-inserts the MCP MIDI Emitter JSFX if not present.",
1398
+ {
1399
+ trackIndex: z10.coerce.number().min(0).describe("0-based track index"),
1400
+ program: z10.coerce.number().min(0).max(127).describe("Program number (0-127)"),
1401
+ channel: z10.coerce.number().min(0).max(15).optional().describe("MIDI channel 0-15 (default 0)"),
1402
+ bankMsb: z10.coerce.number().min(0).max(127).optional().describe("Bank select MSB (CC0, 0-127) \u2014 omit if no bank select needed"),
1403
+ bankLsb: z10.coerce.number().min(0).max(127).optional().describe("Bank select LSB (CC32, 0-127) \u2014 omit if no bank select needed")
1404
+ },
1405
+ async ({ trackIndex, program, channel, bankMsb, bankLsb }) => {
1406
+ const res = await sendCommand("send_midi_pc", { trackIndex, program, channel: channel ?? 0, bankMsb, bankLsb });
1407
+ if (!res.success) {
1408
+ return { content: [{ type: "text", text: `Error: ${res.error}` }], isError: true };
1409
+ }
1410
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
1411
+ }
1412
+ );
1413
+ server.tool(
1414
+ "send_midi_note",
1415
+ "Send a MIDI note-on in real-time to a track's MIDI output. If durationMs is provided, a note-off is automatically scheduled after that many milliseconds. Auto-inserts the MCP MIDI Emitter JSFX if not present.",
1416
+ {
1417
+ trackIndex: z10.coerce.number().min(0).describe("0-based track index"),
1418
+ pitch: z10.coerce.number().min(0).max(127).describe("MIDI note number (0-127, 60=C4/Middle C)"),
1419
+ velocity: z10.coerce.number().min(1).max(127).describe("Note velocity (1-127)"),
1420
+ channel: z10.coerce.number().min(0).max(15).optional().describe("MIDI channel 0-15 (default 0)"),
1421
+ durationMs: z10.coerce.number().min(1).int().optional().describe("Note duration in milliseconds (\u22651) \u2014 if provided, schedules an automatic note-off")
1422
+ },
1423
+ async ({ trackIndex, pitch, velocity, channel, durationMs }) => {
1424
+ const res = await sendCommand("send_midi_note", { trackIndex, pitch, velocity, channel: channel ?? 0, durationMs });
1425
+ if (!res.success) {
1426
+ return { content: [{ type: "text", text: `Error: ${res.error}` }], isError: true };
1427
+ }
1428
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
1429
+ }
1430
+ );
1378
1431
  }
1379
1432
 
1380
1433
  // apps/reaper-mcp-server/src/tools/media.ts
@@ -2179,288 +2232,6 @@ function registerCategoryTools(server) {
2179
2232
  );
2180
2233
  }
2181
2234
 
2182
- // apps/reaper-mcp-server/src/tools/aesthetics.ts
2183
- import { z as z17 } from "zod/v4";
2184
- import { unlink as unlink2 } from "node:fs/promises";
2185
- import { randomUUID as randomUUID2 } from "node:crypto";
2186
-
2187
- // apps/reaper-mcp-server/src/sidecar.ts
2188
- import { spawn } from "node:child_process";
2189
- import { existsSync } from "node:fs";
2190
- import { join as join3 } from "node:path";
2191
- import { homedir as homedir2, platform as platform2 } from "node:os";
2192
- var VENV_BIN = platform2() === "win32" ? "Scripts" : "bin";
2193
- var VENV_PYTHON_NAME = platform2() === "win32" ? "python.exe" : "python";
2194
- var SIDECAR_VENV_PATH = join3(homedir2(), ".reaper-mcp", "sidecar-venv");
2195
- var SIDECAR_SCRIPT_PATH = join3(homedir2(), ".reaper-mcp", "sidecar", "server.py");
2196
- var VENV_PYTHON = join3(SIDECAR_VENV_PATH, VENV_BIN, VENV_PYTHON_NAME);
2197
- var _rawTimeout = Number(process.env["REAPER_MCP_SIDECAR_TIMEOUT_MS"]);
2198
- var ANALYZE_TIMEOUT_MS = Number.isFinite(_rawTimeout) && _rawTimeout > 0 ? _rawTimeout : 6e4;
2199
- var SidecarClientImpl = class {
2200
- process = null;
2201
- pending = /* @__PURE__ */ new Map();
2202
- nextId = 1;
2203
- lineBuffer = "";
2204
- restarting = false;
2205
- restartAttempted = false;
2206
- /** Cached spawn promise — prevents double-spawn on concurrent cold-start calls. */
2207
- spawnPromise = null;
2208
- isAvailable() {
2209
- return existsSync(SIDECAR_SCRIPT_PATH) && existsSync(VENV_PYTHON);
2210
- }
2211
- async analyze(wavPath, startTime, endTime) {
2212
- if (!this.isAvailable()) {
2213
- throw new Error(
2214
- "Audio understanding sidecar not installed. Run: node dist/apps/reaper-mcp-server/main.js setup-sidecar"
2215
- );
2216
- }
2217
- await this.ensureRunning();
2218
- return new Promise((resolve, reject) => {
2219
- const id = this.nextId++;
2220
- const request = {
2221
- jsonrpc: "2.0",
2222
- id,
2223
- method: "analyze",
2224
- params: { path: wavPath, startTime, endTime }
2225
- };
2226
- const timer = setTimeout(() => {
2227
- if (this.pending.delete(id)) {
2228
- process.stderr.write(
2229
- `[reaper-mcp] Sidecar analyze() timed out after ${ANALYZE_TIMEOUT_MS}ms \u2014 killing process
2230
- `
2231
- );
2232
- if (this.process) {
2233
- this.process.kill("SIGKILL");
2234
- this.process = null;
2235
- }
2236
- reject(new Error(
2237
- `Sidecar timeout after ${ANALYZE_TIMEOUT_MS}ms. Override with REAPER_MCP_SIDECAR_TIMEOUT_MS env var.`
2238
- ));
2239
- }
2240
- }, ANALYZE_TIMEOUT_MS);
2241
- this.pending.set(id, {
2242
- resolve: (r) => {
2243
- clearTimeout(timer);
2244
- resolve(r);
2245
- },
2246
- reject: (e) => {
2247
- clearTimeout(timer);
2248
- reject(e);
2249
- }
2250
- });
2251
- const line = JSON.stringify(request) + "\n";
2252
- try {
2253
- const proc = this.process;
2254
- if (!proc || !proc.stdin) {
2255
- throw new Error("Sidecar process not running");
2256
- }
2257
- proc.stdin.write(line);
2258
- } catch (err) {
2259
- clearTimeout(timer);
2260
- this.pending.delete(id);
2261
- reject(new Error(`Failed to write to sidecar stdin: ${err instanceof Error ? err.message : String(err)}`));
2262
- }
2263
- });
2264
- }
2265
- shutdown() {
2266
- if (this.process) {
2267
- this.process.kill("SIGTERM");
2268
- this.process = null;
2269
- }
2270
- for (const [id, { reject }] of this.pending.entries()) {
2271
- reject(new Error("Sidecar shut down"));
2272
- this.pending.delete(id);
2273
- }
2274
- }
2275
- // -------------------------------------------------------------------------
2276
- // Private
2277
- // -------------------------------------------------------------------------
2278
- async ensureRunning() {
2279
- if (this.process && !this.process.killed) return;
2280
- if (!this.spawnPromise) {
2281
- this.spawnPromise = this.spawn().finally(() => {
2282
- this.spawnPromise = null;
2283
- });
2284
- }
2285
- await this.spawnPromise;
2286
- }
2287
- async spawn() {
2288
- const child = spawn(VENV_PYTHON, [SIDECAR_SCRIPT_PATH], {
2289
- stdio: ["pipe", "pipe", "pipe"]
2290
- });
2291
- if (child.stdout) {
2292
- child.stdout.setEncoding("utf8");
2293
- child.stdout.on("data", (chunk) => this.onStdout(chunk));
2294
- }
2295
- if (child.stderr) {
2296
- child.stderr.setEncoding("utf8");
2297
- child.stderr.on("data", (data) => {
2298
- process.stderr.write(`[reaper-mcp-sidecar] ${data}`);
2299
- });
2300
- }
2301
- child.on("exit", (code) => {
2302
- process.stderr.write(`[reaper-mcp] Sidecar process exited (code ${code})
2303
- `);
2304
- this.process = null;
2305
- this.handleProcessDeath();
2306
- });
2307
- child.on("error", (err) => {
2308
- process.stderr.write(`[reaper-mcp] Sidecar spawn error: ${err.message}
2309
- `);
2310
- this.process = null;
2311
- this.handleProcessDeath();
2312
- });
2313
- this.process = child;
2314
- this.lineBuffer = "";
2315
- this.restarting = false;
2316
- this.restartAttempted = false;
2317
- }
2318
- onStdout(chunk) {
2319
- this.lineBuffer += chunk;
2320
- let newlineIdx;
2321
- while ((newlineIdx = this.lineBuffer.indexOf("\n")) !== -1) {
2322
- const line = this.lineBuffer.slice(0, newlineIdx).trim();
2323
- this.lineBuffer = this.lineBuffer.slice(newlineIdx + 1);
2324
- if (line) this.onResponse(line);
2325
- }
2326
- }
2327
- onResponse(line) {
2328
- let response;
2329
- try {
2330
- response = JSON.parse(line);
2331
- } catch {
2332
- process.stderr.write(`[reaper-mcp] Sidecar returned malformed JSON: ${line}
2333
- `);
2334
- for (const [id, { reject }] of this.pending.entries()) {
2335
- reject(new Error(`Sidecar returned malformed JSON: ${line}`));
2336
- this.pending.delete(id);
2337
- }
2338
- return;
2339
- }
2340
- const pending = this.pending.get(response.id);
2341
- if (!pending) {
2342
- process.stderr.write(`[reaper-mcp] Sidecar response for unknown id ${response.id}
2343
- `);
2344
- return;
2345
- }
2346
- this.pending.delete(response.id);
2347
- if (response.error) {
2348
- pending.reject(new Error(response.error.message));
2349
- } else if (response.result) {
2350
- pending.resolve(response.result);
2351
- } else {
2352
- pending.reject(new Error("Sidecar response missing both result and error fields"));
2353
- }
2354
- }
2355
- handleProcessDeath() {
2356
- if (this.restarting || this.pending.size === 0) {
2357
- return;
2358
- }
2359
- if (!this.restartAttempted) {
2360
- this.restarting = true;
2361
- this.restartAttempted = true;
2362
- process.stderr.write("[reaper-mcp] Sidecar crashed; attempting one restart...\n");
2363
- this.spawn().then(() => {
2364
- for (const [id, { reject }] of this.pending.entries()) {
2365
- reject(new Error("Sidecar restarted; please retry the operation"));
2366
- this.pending.delete(id);
2367
- }
2368
- }).catch((err) => {
2369
- for (const [id, { reject }] of this.pending.entries()) {
2370
- reject(new Error(`Sidecar restart failed: ${err.message}. Run: node dist/apps/reaper-mcp-server/main.js setup-sidecar`));
2371
- this.pending.delete(id);
2372
- }
2373
- });
2374
- } else {
2375
- for (const [id, { reject }] of this.pending.entries()) {
2376
- reject(new Error("Python sidecar unavailable after restart attempt. Run: node dist/apps/reaper-mcp-server/main.js setup-sidecar"));
2377
- this.pending.delete(id);
2378
- }
2379
- }
2380
- }
2381
- };
2382
- var _singleton = null;
2383
- function getSidecarClient() {
2384
- if (!_singleton) {
2385
- _singleton = new SidecarClientImpl();
2386
- }
2387
- return _singleton;
2388
- }
2389
-
2390
- // apps/reaper-mcp-server/src/tools/aesthetics.ts
2391
- var SIDECAR_NOT_INSTALLED_MSG = "Audio understanding sidecar not installed. Run: node dist/apps/reaper-mcp-server/main.js setup-sidecar";
2392
- function registerAestheticsTools(server) {
2393
- server.tool(
2394
- "analyze_track_aesthetics",
2395
- "Analyze the perceptual aesthetic quality of a track's audio using Meta's Audiobox Aesthetics model (CC-BY 4.0). Bounces the track to a temp WAV file (post-FX, post-fader) and runs 4-axis perceptual quality scoring: Production Quality (PQ), Production Complexity (PC), Content Enjoyment (CE), Content Usefulness (CU). Returns scores 0-10. Requires the Python sidecar (run: node dist/apps/reaper-mcp-server/main.js setup-sidecar).",
2396
- {
2397
- trackIndex: z17.coerce.number().int().min(0).describe("Zero-based track index"),
2398
- startTime: z17.coerce.number().min(0).optional().describe("Start time in seconds (default: 0 or use REAPER time selection)"),
2399
- endTime: z17.coerce.number().min(0).optional().describe("End time in seconds (default: startTime + durationSeconds)"),
2400
- durationSeconds: z17.coerce.number().min(0.5).max(30).optional().default(5).describe("Duration to analyze in seconds (default 5, max 30). Ignored if endTime is provided.")
2401
- },
2402
- async ({ trackIndex, startTime, endTime, durationSeconds }) => {
2403
- const sidecar = getSidecarClient();
2404
- if (!sidecar.isAvailable()) {
2405
- return {
2406
- content: [{ type: "text", text: SIDECAR_NOT_INSTALLED_MSG }],
2407
- isError: true
2408
- };
2409
- }
2410
- const resolvedStart = startTime ?? 0;
2411
- const resolvedEnd = endTime ?? resolvedStart + (durationSeconds ?? 5);
2412
- if (resolvedEnd <= resolvedStart) {
2413
- return {
2414
- content: [{ type: "text", text: "endTime must be greater than startTime" }],
2415
- isError: true
2416
- };
2417
- }
2418
- const commandId = randomUUID2();
2419
- const renderRes = await sendCommand("render_track_to_wav", {
2420
- trackIndex,
2421
- startTime: resolvedStart,
2422
- endTime: resolvedEnd,
2423
- commandId
2424
- });
2425
- if (!renderRes.success) {
2426
- return {
2427
- content: [{ type: "text", text: `Render failed: ${renderRes.error}` }],
2428
- isError: true
2429
- };
2430
- }
2431
- const renderData = renderRes.data;
2432
- const wavPath = renderData.wavPath;
2433
- try {
2434
- const scores = await sidecar.analyze(wavPath, resolvedStart, resolvedEnd);
2435
- const result = {
2436
- trackIndex,
2437
- trackName: renderData.trackName,
2438
- productionQuality: scores.PQ,
2439
- productionComplexity: scores.PC,
2440
- contentEnjoyment: scores.CE,
2441
- contentUsefulness: scores.CU,
2442
- startTime: resolvedStart,
2443
- endTime: resolvedEnd,
2444
- durationSeconds: resolvedEnd - resolvedStart,
2445
- modelVersion: scores.modelVersion
2446
- };
2447
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2448
- } catch (err) {
2449
- return {
2450
- content: [{
2451
- type: "text",
2452
- text: err instanceof Error ? err.message : String(err)
2453
- }],
2454
- isError: true
2455
- };
2456
- } finally {
2457
- await unlink2(wavPath).catch(() => {
2458
- });
2459
- }
2460
- }
2461
- );
2462
- }
2463
-
2464
2235
  // apps/reaper-mcp-server/src/server.ts
2465
2236
  function instrumentToolHandlers(server) {
2466
2237
  const originalTool = server.tool.bind(server);
@@ -2520,43 +2291,40 @@ function createServer() {
2520
2291
  registerEnvelopeTools(server);
2521
2292
  registerBatchTools(server);
2522
2293
  registerCategoryTools(server);
2523
- registerAestheticsTools(server);
2524
2294
  return server;
2525
2295
  }
2526
2296
 
2527
2297
  // apps/reaper-mcp-server/src/main.ts
2528
- import { existsSync as existsSync5, mkdirSync as mkdirSync4 } from "node:fs";
2529
- import { join as join7, dirname as dirname3 } from "node:path";
2530
- import { fileURLToPath as fileURLToPath3 } from "node:url";
2531
- import { homedir as homedir5, platform as platform4 } from "node:os";
2532
- import { exec as execCb } from "node:child_process";
2533
- import { promisify as promisifyUtil } from "node:util";
2298
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3 } from "node:fs";
2299
+ import { join as join5, dirname as dirname2 } from "node:path";
2300
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
2301
+ import { homedir as homedir3 } from "node:os";
2534
2302
 
2535
2303
  // apps/reaper-mcp-server/src/cli.ts
2536
- import { copyFileSync, existsSync as existsSync2, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
2537
- import { join as join4 } from "node:path";
2304
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
2305
+ import { join as join3 } from "node:path";
2538
2306
  function resolveAssetDir(baseDir, name) {
2539
- const sibling = join4(baseDir, name);
2540
- if (existsSync2(sibling)) return sibling;
2541
- const parent = join4(baseDir, "..", name);
2542
- if (existsSync2(parent)) return parent;
2307
+ const sibling = join3(baseDir, name);
2308
+ if (existsSync(sibling)) return sibling;
2309
+ const parent = join3(baseDir, "..", name);
2310
+ if (existsSync(parent)) return parent;
2543
2311
  let dir = baseDir;
2544
2312
  for (let i = 0; i < 5; i++) {
2545
- const candidate = join4(dir, name);
2546
- if (existsSync2(candidate)) return candidate;
2547
- const up = join4(dir, "..");
2313
+ const candidate = join3(dir, name);
2314
+ if (existsSync(candidate)) return candidate;
2315
+ const up = join3(dir, "..");
2548
2316
  if (up === dir) break;
2549
2317
  dir = up;
2550
2318
  }
2551
2319
  return sibling;
2552
2320
  }
2553
2321
  function copyDirSync(src, dest) {
2554
- if (!existsSync2(src)) return 0;
2322
+ if (!existsSync(src)) return 0;
2555
2323
  mkdirSync(dest, { recursive: true });
2556
2324
  let count = 0;
2557
2325
  for (const entry of readdirSync(src)) {
2558
- const srcPath = join4(src, entry);
2559
- const destPath = join4(dest, entry);
2326
+ const srcPath = join3(src, entry);
2327
+ const destPath = join3(dest, entry);
2560
2328
  if (statSync(srcPath).isDirectory()) {
2561
2329
  count += copyDirSync(srcPath, destPath);
2562
2330
  } else {
@@ -2567,14 +2335,14 @@ function copyDirSync(src, dest) {
2567
2335
  return count;
2568
2336
  }
2569
2337
  function installFile(src, dest) {
2570
- if (existsSync2(src)) {
2338
+ if (existsSync(src)) {
2571
2339
  copyFileSync(src, dest);
2572
2340
  return true;
2573
2341
  }
2574
2342
  return false;
2575
2343
  }
2576
2344
  function createMcpJson(targetPath) {
2577
- if (existsSync2(targetPath)) return false;
2345
+ if (existsSync(targetPath)) return false;
2578
2346
  const config = JSON.stringify({
2579
2347
  mcpServers: {
2580
2348
  reaper: {
@@ -2592,6 +2360,7 @@ var REAPER_ASSETS = [
2592
2360
  "mcp_lufs_meter.jsfx",
2593
2361
  "mcp_correlation_meter.jsfx",
2594
2362
  "mcp_crest_factor.jsfx",
2363
+ "mcp_midi_emitter.jsfx",
2595
2364
  "mcp_snapshot_manager.lua",
2596
2365
  "mcp_snapshot_lib.lua",
2597
2366
  "mcp_snapshot_next.lua",
@@ -2652,6 +2421,9 @@ var MCP_TOOL_NAMES = [
2652
2421
  "delete_midi_cc",
2653
2422
  "get_midi_item_properties",
2654
2423
  "set_midi_item_properties",
2424
+ "send_midi_cc",
2425
+ "send_midi_pc",
2426
+ "send_midi_note",
2655
2427
  // media
2656
2428
  "list_media_items",
2657
2429
  "get_media_item_properties",
@@ -2697,16 +2469,12 @@ var MCP_TOOL_NAMES = [
2697
2469
  // progressive discovery
2698
2470
  "list_tool_categories",
2699
2471
  "enable_tool_category",
2700
- "disable_tool_category",
2701
- // semantic audio analysis (requires Python sidecar — opt-in)
2702
- "analyze_track_aesthetics"
2703
- // Note: 'render_track_to_wav' is an internal Lua bridge command, NOT a public MCP tool.
2704
- // It must NOT be listed here — MCP_TOOL_NAMES drives the Claude Code allowlist.
2472
+ "disable_tool_category"
2705
2473
  ];
2706
2474
  function ensureClaudeSettings(settingsPath) {
2707
2475
  const allowList = MCP_TOOL_NAMES.map((t) => `mcp__reaper__${t}`);
2708
- if (!existsSync2(settingsPath)) {
2709
- mkdirSync(join4(settingsPath, ".."), { recursive: true });
2476
+ if (!existsSync(settingsPath)) {
2477
+ mkdirSync(join3(settingsPath, ".."), { recursive: true });
2710
2478
  const config = { permissions: { allow: allowList } };
2711
2479
  writeFileSync(settingsPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
2712
2480
  return "created";
@@ -2723,21 +2491,21 @@ function ensureClaudeSettings(settingsPath) {
2723
2491
  }
2724
2492
  function resolveAssetDirWithFallback(baseDir, buildName, sourceName) {
2725
2493
  const resolved = resolveAssetDir(baseDir, buildName);
2726
- if (existsSync2(resolved)) return resolved;
2494
+ if (existsSync(resolved)) return resolved;
2727
2495
  return resolveAssetDir(baseDir, sourceName);
2728
2496
  }
2729
2497
 
2730
2498
  // apps/reaper-mcp-server/src/init.ts
2731
2499
  import { checkbox, select } from "@inquirer/prompts";
2732
- import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "node:fs";
2733
- import { join as join5 } from "node:path";
2734
- import { homedir as homedir3 } from "node:os";
2500
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "node:fs";
2501
+ import { join as join4 } from "node:path";
2502
+ import { homedir as homedir2 } from "node:os";
2735
2503
  async function runInit(opts, dirResolver) {
2736
2504
  const isTTY = Boolean(process.stdin.isTTY);
2737
2505
  const headless = opts.yes || !isTTY;
2738
2506
  console.log("REAPER MCP \u2014 Interactive Setup\n");
2739
2507
  const bridgeDir = await ensureBridgeDir();
2740
- console.log(`REAPER resource path: ${join5(bridgeDir, "..", "..")}
2508
+ console.log(`REAPER resource path: ${join4(bridgeDir, "..", "..")}
2741
2509
  `);
2742
2510
  let selections;
2743
2511
  if (headless) {
@@ -2782,27 +2550,27 @@ async function runInit(opts, dirResolver) {
2782
2550
  skillsScope
2783
2551
  };
2784
2552
  }
2785
- const __dirname3 = dirResolver();
2553
+ const __dirname2 = dirResolver();
2786
2554
  if (selections.bridge) {
2787
2555
  console.log("Installing REAPER Bridge...");
2788
2556
  const scriptsDir = getReaperScriptsPath();
2789
2557
  mkdirSync2(scriptsDir, { recursive: true });
2790
- const reaperDir = resolveAssetDir(__dirname3, "reaper");
2558
+ const reaperDir = resolveAssetDir(__dirname2, "reaper");
2791
2559
  for (const luaFile of ["mcp_bridge.lua", "mcp_snapshot_manager.lua"]) {
2792
- const src = join5(reaperDir, luaFile);
2793
- const dest = join5(scriptsDir, luaFile);
2560
+ const src = join4(reaperDir, luaFile);
2561
+ const dest = join4(scriptsDir, luaFile);
2794
2562
  if (installFile(src, dest)) {
2795
2563
  console.log(` Installed: ${luaFile}`);
2796
2564
  } else {
2797
2565
  console.log(` Not found: ${src}`);
2798
2566
  }
2799
2567
  }
2800
- const effectsDir = join5(getReaperEffectsPath(), "reaper-mcp");
2568
+ const effectsDir = join4(getReaperEffectsPath(), "reaper-mcp");
2801
2569
  mkdirSync2(effectsDir, { recursive: true });
2802
2570
  for (const asset of REAPER_ASSETS) {
2803
2571
  if (asset.endsWith(".lua")) continue;
2804
- const src = join5(reaperDir, asset);
2805
- const dest = join5(effectsDir, asset);
2572
+ const src = join4(reaperDir, asset);
2573
+ const dest = join4(effectsDir, asset);
2806
2574
  if (installFile(src, dest)) {
2807
2575
  console.log(` Installed: reaper-mcp/${asset}`);
2808
2576
  } else {
@@ -2813,32 +2581,32 @@ async function runInit(opts, dirResolver) {
2813
2581
  }
2814
2582
  if (selections.skills) {
2815
2583
  const isGlobal = selections.skillsScope === "global";
2816
- const baseDir = isGlobal ? join5(homedir3(), ".claude") : process.cwd();
2817
- const claudeDir = isGlobal ? baseDir : join5(baseDir, ".claude");
2584
+ const baseDir = isGlobal ? join4(homedir2(), ".claude") : process.cwd();
2585
+ const claudeDir = isGlobal ? baseDir : join4(baseDir, ".claude");
2818
2586
  console.log(`Installing AI Skills & Agents (${selections.skillsScope})...`);
2819
- const knowledgeSrc = resolveAssetDir(__dirname3, "knowledge");
2820
- if (existsSync3(knowledgeSrc)) {
2821
- const dest = join5(isGlobal ? join5(homedir3(), ".claude") : process.cwd(), "knowledge");
2587
+ const knowledgeSrc = resolveAssetDir(__dirname2, "knowledge");
2588
+ if (existsSync2(knowledgeSrc)) {
2589
+ const dest = join4(isGlobal ? join4(homedir2(), ".claude") : process.cwd(), "knowledge");
2822
2590
  const count = copyDirSync(knowledgeSrc, dest);
2823
2591
  console.log(` Installed knowledge base: ${count} files`);
2824
2592
  } else {
2825
2593
  console.log(" Knowledge base not found in package. Skipping.");
2826
2594
  }
2827
- const rulesSrc = resolveAssetDirWithFallback(__dirname3, "claude-rules", join5(".claude", "rules"));
2828
- if (existsSync3(rulesSrc)) {
2829
- const dest = join5(claudeDir, "rules");
2595
+ const rulesSrc = resolveAssetDirWithFallback(__dirname2, "claude-rules", join4(".claude", "rules"));
2596
+ if (existsSync2(rulesSrc)) {
2597
+ const dest = join4(claudeDir, "rules");
2830
2598
  const count = copyDirSync(rulesSrc, dest);
2831
2599
  console.log(` Installed Claude rules: ${count} files`);
2832
2600
  }
2833
- const skillsSrc = resolveAssetDirWithFallback(__dirname3, "claude-skills", join5(".claude", "skills"));
2834
- if (existsSync3(skillsSrc)) {
2835
- const dest = join5(claudeDir, "skills");
2601
+ const skillsSrc = resolveAssetDirWithFallback(__dirname2, "claude-skills", join4(".claude", "skills"));
2602
+ if (existsSync2(skillsSrc)) {
2603
+ const dest = join4(claudeDir, "skills");
2836
2604
  const count = copyDirSync(skillsSrc, dest);
2837
2605
  console.log(` Installed Claude skills: ${count} files`);
2838
2606
  }
2839
- const agentsSrc = resolveAssetDirWithFallback(__dirname3, "claude-agents", join5(".claude", "agents"));
2840
- if (existsSync3(agentsSrc)) {
2841
- const dest = join5(claudeDir, "agents");
2607
+ const agentsSrc = resolveAssetDirWithFallback(__dirname2, "claude-agents", join4(".claude", "agents"));
2608
+ if (existsSync2(agentsSrc)) {
2609
+ const dest = join4(claudeDir, "agents");
2842
2610
  const count = copyDirSync(agentsSrc, dest);
2843
2611
  console.log(` Installed Claude agents: ${count} files`);
2844
2612
  }
@@ -2846,8 +2614,8 @@ async function runInit(opts, dirResolver) {
2846
2614
  }
2847
2615
  if (selections.settings) {
2848
2616
  console.log("Configuring Claude Code Settings...");
2849
- const settingsDir = join5(homedir3(), ".claude");
2850
- const settingsPath = join5(settingsDir, "settings.json");
2617
+ const settingsDir = join4(homedir2(), ".claude");
2618
+ const settingsPath = join4(settingsDir, "settings.json");
2851
2619
  const result = ensureClaudeSettings(settingsPath);
2852
2620
  if (result === "created") {
2853
2621
  console.log(` Created: ${settingsPath}`);
@@ -2860,7 +2628,7 @@ async function runInit(opts, dirResolver) {
2860
2628
  }
2861
2629
  if (selections.projectConfig) {
2862
2630
  console.log("Creating Project Config...");
2863
- const mcpJsonPath = join5(process.cwd(), ".mcp.json");
2631
+ const mcpJsonPath = join4(process.cwd(), ".mcp.json");
2864
2632
  if (createMcpJson(mcpJsonPath)) {
2865
2633
  console.log(` Created: ${mcpJsonPath}`);
2866
2634
  } else {
@@ -2871,18 +2639,18 @@ async function runInit(opts, dirResolver) {
2871
2639
  console.log("Running system check...");
2872
2640
  const bridgeRunning = await isBridgeRunning();
2873
2641
  console.log(` Lua bridge: ${bridgeRunning ? "Connected" : "Not detected (start after REAPER is open)"}`);
2874
- const globalClaudeDir = join5(homedir3(), ".claude");
2875
- const localAgents = existsSync3(join5(process.cwd(), ".claude", "agents"));
2876
- const globalAgents = existsSync3(join5(globalClaudeDir, "agents"));
2642
+ const globalClaudeDir = join4(homedir2(), ".claude");
2643
+ const localAgents = existsSync2(join4(process.cwd(), ".claude", "agents"));
2644
+ const globalAgents = existsSync2(join4(globalClaudeDir, "agents"));
2877
2645
  const agentsExist = localAgents || globalAgents;
2878
2646
  console.log(` Mix agents: ${agentsExist ? "Installed" : "Not installed"}`);
2879
- const mcpJsonExists = existsSync3(join5(process.cwd(), ".mcp.json"));
2647
+ const mcpJsonExists = existsSync2(join4(process.cwd(), ".mcp.json"));
2880
2648
  console.log(` MCP config: ${mcpJsonExists ? ".mcp.json found" : ".mcp.json not present"}`);
2881
2649
  console.log("");
2882
2650
  console.log("Setup complete! Next steps:");
2883
2651
  if (selections.bridge) {
2884
2652
  const scriptsDir = getReaperScriptsPath();
2885
- const luaDest = join5(scriptsDir, "mcp_bridge.lua");
2653
+ const luaDest = join4(scriptsDir, "mcp_bridge.lua");
2886
2654
  console.log(" 1. Open REAPER");
2887
2655
  console.log(" 2. Actions > Show action list > Load ReaScript");
2888
2656
  console.log(` 3. Select: ${luaDest}`);
@@ -2897,206 +2665,33 @@ async function runInit(opts, dirResolver) {
2897
2665
  }
2898
2666
  }
2899
2667
 
2900
- // apps/reaper-mcp-server/src/setup-sidecar.ts
2901
- import { exec as execImpl } from "node:child_process";
2902
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, copyFileSync as copyFileSync2, rmSync } from "node:fs";
2903
- import { join as join6, dirname as dirname2 } from "node:path";
2904
- import { homedir as homedir4, platform as platform3 } from "node:os";
2905
- import { fileURLToPath as fileURLToPath2 } from "node:url";
2906
- var VENV_BIN2 = platform3() === "win32" ? "Scripts" : "bin";
2907
- var VENV_PYTHON_NAME2 = platform3() === "win32" ? "python.exe" : "python";
2908
- var VENV_PIP_NAME = platform3() === "win32" ? "pip.exe" : "pip";
2909
- function exec(cmd) {
2910
- return new Promise((resolve, reject) => {
2911
- execImpl(cmd, (err, stdout, stderr) => {
2912
- if (err) reject(err);
2913
- else resolve({ stdout, stderr });
2914
- });
2915
- });
2916
- }
2917
- var __dirname = dirname2(fileURLToPath2(import.meta.url));
2918
- var SIDECAR_VENV_PATH2 = join6(homedir4(), ".reaper-mcp", "sidecar-venv");
2919
- var SIDECAR_DIR = join6(homedir4(), ".reaper-mcp", "sidecar");
2920
- var SIDECAR_SCRIPT_DEST = join6(SIDECAR_DIR, "server.py");
2921
- var MODEL_ID = "facebook/audiobox-aesthetics";
2922
- var MIN_PYTHON_MAJOR = 3;
2923
- var MIN_PYTHON_MINOR = 10;
2924
- function getPythonBin() {
2925
- return process.env["PYTHON_BIN"] ?? "python3";
2926
- }
2927
- function parsePythonVersion(output) {
2928
- const match = output.match(/Python\s+(\d+)\.(\d+)\.(\d+)/i);
2929
- if (!match) return null;
2930
- return [parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3], 10)];
2931
- }
2932
- async function checkPython(pythonBin) {
2933
- let stdout;
2934
- try {
2935
- const result = await exec(`"${pythonBin}" --version`);
2936
- stdout = (result.stdout + result.stderr).trim();
2937
- } catch {
2938
- const notFound = `Python interpreter not found: ${pythonBin}
2939
- Install Python 3.10+ from https://python.org or set the PYTHON_BIN environment variable.
2940
- Example: PYTHON_BIN=/usr/local/bin/python3.11 node dist/apps/reaper-mcp-server/main.js setup-sidecar`;
2941
- throw new Error(notFound);
2942
- }
2943
- const version = parsePythonVersion(stdout);
2944
- if (!version) {
2945
- throw new Error(`Could not parse Python version from: "${stdout}". Is PYTHON_BIN set correctly?`);
2946
- }
2947
- const [major, minor, patch] = version;
2948
- if (major < MIN_PYTHON_MAJOR || major === MIN_PYTHON_MAJOR && minor < MIN_PYTHON_MINOR) {
2949
- throw new Error(
2950
- `Python ${MIN_PYTHON_MAJOR}.${MIN_PYTHON_MINOR}+ required. Found: ${major}.${minor}.${patch} at ${pythonBin}.
2951
- Install Python 3.10+ from https://python.org or set PYTHON_BIN to a newer interpreter.`
2952
- );
2953
- }
2954
- return `${major}.${minor}.${patch}`;
2955
- }
2956
- async function createVenv(pythonBin) {
2957
- mkdirSync3(join6(homedir4(), ".reaper-mcp"), { recursive: true });
2958
- await exec(`"${pythonBin}" -m venv --clear "${SIDECAR_VENV_PATH2}"`);
2959
- }
2960
- function cleanupBrokenVenv() {
2961
- try {
2962
- if (existsSync4(SIDECAR_VENV_PATH2)) {
2963
- rmSync(SIDECAR_VENV_PATH2, { recursive: true, force: true });
2964
- }
2965
- } catch {
2966
- }
2967
- }
2968
- function resolveRequirementsTxt(baseDir) {
2969
- const sidecarDir = resolveAssetDir(baseDir, "sidecar");
2970
- return join6(sidecarDir, "requirements.txt");
2971
- }
2972
- async function installDeps(requirementsPath) {
2973
- const pip = join6(SIDECAR_VENV_PATH2, VENV_BIN2, VENV_PIP_NAME);
2974
- await exec(`"${pip}" install --upgrade pip`);
2975
- const { stdout, stderr } = await exec(`"${pip}" install -r "${requirementsPath}"`);
2976
- if (stdout) process.stdout.write(stdout);
2977
- if (stderr) process.stderr.write(stderr);
2978
- }
2979
- async function downloadModelWeights() {
2980
- const python = join6(SIDECAR_VENV_PATH2, VENV_BIN2, VENV_PYTHON_NAME2);
2981
- const script = [
2982
- "from huggingface_hub import snapshot_download",
2983
- `print("Downloading ${MODEL_ID} weights to ~/.cache/huggingface/hub/ ...")`,
2984
- `snapshot_download("${MODEL_ID}")`,
2985
- `print("Download complete.")`
2986
- ].join("; ");
2987
- const { stdout, stderr } = await exec(`"${python}" -c "${script}"`);
2988
- if (stdout) process.stdout.write(stdout);
2989
- if (stderr) process.stderr.write(stderr);
2990
- }
2991
- function copySidecarScript(baseDir) {
2992
- const sidecarDir = resolveAssetDir(baseDir, "sidecar");
2993
- const src = join6(sidecarDir, "server.py");
2994
- if (!existsSync4(src)) {
2995
- throw new Error(
2996
- `sidecar/server.py not found at: ${src}
2997
- Run \`pnpm nx build reaper-mcp-server\` first.`
2998
- );
2999
- }
3000
- mkdirSync3(SIDECAR_DIR, { recursive: true });
3001
- copyFileSync2(src, SIDECAR_SCRIPT_DEST);
3002
- }
3003
- async function setupSidecar() {
3004
- console.log("REAPER MCP \u2014 Setup Python Sidecar\n");
3005
- console.log("This installs the opt-in audio AI subsystem for perceptual analysis.");
3006
- console.log("Requires Python 3.10+ and an internet connection (~831 MB download).\n");
3007
- const pythonBin = getPythonBin();
3008
- console.log(`Checking Python interpreter: ${pythonBin}`);
3009
- let version;
3010
- try {
3011
- version = await checkPython(pythonBin);
3012
- } catch (err) {
3013
- console.error(`
3014
- Error: ${err instanceof Error ? err.message : String(err)}`);
3015
- process.exit(1);
3016
- }
3017
- console.log(` Using Python interpreter: ${pythonBin} (${version})`);
3018
- console.log(`
3019
- Creating virtual environment at: ${SIDECAR_VENV_PATH2}`);
3020
- try {
3021
- await createVenv(pythonBin);
3022
- console.log(" Virtual environment created.");
3023
- } catch (err) {
3024
- console.error(`
3025
- Failed to create venv: ${err instanceof Error ? err.message : String(err)}`);
3026
- process.exit(1);
3027
- }
3028
- const requirementsPath = resolveRequirementsTxt(__dirname);
3029
- console.log(`
3030
- Installing Python dependencies from: ${requirementsPath}`);
3031
- if (!existsSync4(requirementsPath)) {
3032
- console.error(`
3033
- Error: requirements.txt not found at: ${requirementsPath}`);
3034
- console.error("Run `pnpm nx build reaper-mcp-server` first.");
3035
- process.exit(1);
3036
- }
3037
- try {
3038
- await installDeps(requirementsPath);
3039
- console.log(" Dependencies installed.");
3040
- } catch (err) {
3041
- console.error(`
3042
- Failed to install dependencies: ${err instanceof Error ? err.message : String(err)}`);
3043
- cleanupBrokenVenv();
3044
- console.error("Removed partial venv so the next setup-sidecar run starts clean.");
3045
- process.exit(1);
3046
- }
3047
- console.log("\nPre-downloading Audiobox Aesthetics model weights (~831 MB)...");
3048
- console.log("This may take several minutes depending on your internet connection.");
3049
- try {
3050
- await downloadModelWeights();
3051
- } catch (err) {
3052
- console.error(`
3053
- Model download failed: ${err instanceof Error ? err.message : String(err)}`);
3054
- console.error("Check your internet connection and run setup-sidecar again.");
3055
- process.exit(1);
3056
- }
3057
- console.log(`
3058
- Installing sidecar script to: ${SIDECAR_SCRIPT_DEST}`);
3059
- try {
3060
- copySidecarScript(__dirname);
3061
- console.log(" Sidecar script installed.");
3062
- } catch (err) {
3063
- console.error(`
3064
- Failed to install sidecar script: ${err instanceof Error ? err.message : String(err)}`);
3065
- process.exit(1);
3066
- }
3067
- console.log("\nSidecar setup complete!\n");
3068
- console.log("The analyze_track_aesthetics tool is now available.");
3069
- console.log("Run `node dist/apps/reaper-mcp-server/main.js doctor` to verify.\n");
3070
- }
3071
-
3072
2668
  // apps/reaper-mcp-server/src/main.ts
3073
- var execAsync = promisifyUtil(execCb);
3074
- var __dirname2 = dirname3(fileURLToPath3(import.meta.url));
2669
+ var __dirname = dirname2(fileURLToPath2(import.meta.url));
3075
2670
  async function setup() {
3076
2671
  console.log("REAPER MCP Server \u2014 Setup\n");
3077
2672
  const bridgeDir = await ensureBridgeDir();
3078
2673
  console.log(`Bridge directory: ${bridgeDir}
3079
2674
  `);
3080
2675
  const scriptsDir = getReaperScriptsPath();
3081
- mkdirSync4(scriptsDir, { recursive: true });
3082
- const reaperDir = resolveAssetDir(__dirname2, "reaper");
2676
+ mkdirSync3(scriptsDir, { recursive: true });
2677
+ const reaperDir = resolveAssetDir(__dirname, "reaper");
3083
2678
  console.log("Installing Lua scripts...");
3084
2679
  for (const luaFile of ["mcp_bridge.lua", "mcp_snapshot_manager.lua"]) {
3085
- const src = join7(reaperDir, luaFile);
3086
- const dest = join7(scriptsDir, luaFile);
2680
+ const src = join5(reaperDir, luaFile);
2681
+ const dest = join5(scriptsDir, luaFile);
3087
2682
  if (installFile(src, dest)) {
3088
2683
  console.log(` Installed: ${luaFile}`);
3089
2684
  } else {
3090
2685
  console.log(` Not found: ${src}`);
3091
2686
  }
3092
2687
  }
3093
- const effectsDir = join7(getReaperEffectsPath(), "reaper-mcp");
3094
- mkdirSync4(effectsDir, { recursive: true });
2688
+ const effectsDir = join5(getReaperEffectsPath(), "reaper-mcp");
2689
+ mkdirSync3(effectsDir, { recursive: true });
3095
2690
  console.log("\nInstalling JSFX analyzers...");
3096
2691
  for (const asset of REAPER_ASSETS) {
3097
2692
  if (asset.endsWith(".lua")) continue;
3098
- const src = join7(reaperDir, asset);
3099
- const dest = join7(effectsDir, asset);
2693
+ const src = join5(reaperDir, asset);
2694
+ const dest = join5(effectsDir, asset);
3100
2695
  if (installFile(src, dest)) {
3101
2696
  console.log(` Installed: reaper-mcp/${asset}`);
3102
2697
  } else {
@@ -3107,7 +2702,7 @@ async function setup() {
3107
2702
  console.log("Next steps:");
3108
2703
  console.log(" 1. Open REAPER");
3109
2704
  console.log(" 2. Actions > Show action list > Load ReaScript");
3110
- console.log(` 3. Select: ${join7(scriptsDir, "mcp_bridge.lua")}`);
2705
+ console.log(` 3. Select: ${join5(scriptsDir, "mcp_bridge.lua")}`);
3111
2706
  console.log(" 4. Run the script (it will keep running in background via defer loop)");
3112
2707
  console.log(" 5. Add reaper-mcp to your Claude Code config (see: npx @mthines/reaper-mcp doctor)");
3113
2708
  }
@@ -3119,41 +2714,41 @@ async function installSkills(scope) {
3119
2714
  console.log(`REAPER MCP \u2014 Install AI Mix Engineer Skills (scope: ${scope})
3120
2715
  `);
3121
2716
  const isGlobal = scope === "global";
3122
- const baseDir = isGlobal ? join7(homedir5(), ".claude") : process.cwd();
3123
- const claudeDir = isGlobal ? baseDir : join7(baseDir, ".claude");
3124
- const knowledgeSrc = resolveAssetDir(__dirname2, "knowledge");
3125
- if (existsSync5(knowledgeSrc)) {
3126
- const dest = join7(baseDir, "knowledge");
2717
+ const baseDir = isGlobal ? join5(homedir3(), ".claude") : process.cwd();
2718
+ const claudeDir = isGlobal ? baseDir : join5(baseDir, ".claude");
2719
+ const knowledgeSrc = resolveAssetDir(__dirname, "knowledge");
2720
+ if (existsSync3(knowledgeSrc)) {
2721
+ const dest = join5(baseDir, "knowledge");
3127
2722
  const count = copyDirSync(knowledgeSrc, dest);
3128
2723
  console.log(`Installed knowledge base: ${count} files \u2192 ${dest}`);
3129
2724
  } else {
3130
2725
  console.log("Knowledge base not found in package. Skipping.");
3131
2726
  }
3132
- const rulesSrc = resolveAssetDirWithFallback(__dirname2, "claude-rules", join7(".claude", "rules"));
3133
- if (existsSync5(rulesSrc)) {
3134
- const dest = join7(claudeDir, "rules");
2727
+ const rulesSrc = resolveAssetDirWithFallback(__dirname, "claude-rules", join5(".claude", "rules"));
2728
+ if (existsSync3(rulesSrc)) {
2729
+ const dest = join5(claudeDir, "rules");
3135
2730
  const count = copyDirSync(rulesSrc, dest);
3136
2731
  console.log(`Installed Claude rules: ${count} files \u2192 ${dest}`);
3137
2732
  } else {
3138
2733
  console.log("Claude rules not found in package. Skipping.");
3139
2734
  }
3140
- const skillsSrc = resolveAssetDirWithFallback(__dirname2, "claude-skills", join7(".claude", "skills"));
3141
- if (existsSync5(skillsSrc)) {
3142
- const dest = join7(claudeDir, "skills");
2735
+ const skillsSrc = resolveAssetDirWithFallback(__dirname, "claude-skills", join5(".claude", "skills"));
2736
+ if (existsSync3(skillsSrc)) {
2737
+ const dest = join5(claudeDir, "skills");
3143
2738
  const count = copyDirSync(skillsSrc, dest);
3144
2739
  console.log(`Installed Claude skills: ${count} files \u2192 ${dest}`);
3145
2740
  } else {
3146
2741
  console.log("Claude skills not found in package. Skipping.");
3147
2742
  }
3148
- const agentsSrc = resolveAssetDirWithFallback(__dirname2, "claude-agents", join7(".claude", "agents"));
3149
- if (existsSync5(agentsSrc)) {
3150
- const dest = join7(claudeDir, "agents");
2743
+ const agentsSrc = resolveAssetDirWithFallback(__dirname, "claude-agents", join5(".claude", "agents"));
2744
+ if (existsSync3(agentsSrc)) {
2745
+ const dest = join5(claudeDir, "agents");
3151
2746
  const count = copyDirSync(agentsSrc, dest);
3152
2747
  console.log(`Installed Claude agents: ${count} files \u2192 ${dest}`);
3153
2748
  } else {
3154
2749
  console.log("Claude agents not found in package. Skipping.");
3155
2750
  }
3156
- const settingsPath = join7(claudeDir, "settings.json");
2751
+ const settingsPath = join5(claudeDir, "settings.json");
3157
2752
  const result = ensureClaudeSettings(settingsPath);
3158
2753
  if (result === "created") {
3159
2754
  console.log(`Created Claude settings: ${settingsPath}`);
@@ -3163,7 +2758,7 @@ async function installSkills(scope) {
3163
2758
  console.log(`Claude settings already has all REAPER tools: ${settingsPath}`);
3164
2759
  }
3165
2760
  if (!isGlobal) {
3166
- const mcpJsonPath = join7(baseDir, ".mcp.json");
2761
+ const mcpJsonPath = join5(baseDir, ".mcp.json");
3167
2762
  if (createMcpJson(mcpJsonPath)) {
3168
2763
  console.log(`
3169
2764
  Created: ${mcpJsonPath}`);
@@ -3184,92 +2779,31 @@ async function doctor() {
3184
2779
  if (!bridgeRunning) {
3185
2780
  console.log(' \u2192 Run "npx @mthines/reaper-mcp setup" then load mcp_bridge.lua in REAPER');
3186
2781
  }
3187
- const globalClaudeDir = join7(homedir5(), ".claude");
3188
- const localAgents = existsSync5(join7(process.cwd(), ".claude", "agents"));
3189
- const globalAgents = existsSync5(join7(globalClaudeDir, "agents"));
2782
+ const globalClaudeDir = join5(homedir3(), ".claude");
2783
+ const localAgents = existsSync3(join5(process.cwd(), ".claude", "agents"));
2784
+ const globalAgents = existsSync3(join5(globalClaudeDir, "agents"));
3190
2785
  const agentsExist = localAgents || globalAgents;
3191
2786
  const agentsLocation = localAgents ? ".claude/agents/" : globalAgents ? "~/.claude/agents/" : "";
3192
2787
  console.log(`Mix agents: ${agentsExist ? `\u2713 Found (${agentsLocation})` : "\u2717 Not installed"}`);
3193
2788
  if (!agentsExist) {
3194
2789
  console.log(' \u2192 Run "npx @mthines/reaper-mcp install-skills"');
3195
2790
  }
3196
- const localKnowledge = existsSync5(join7(process.cwd(), "knowledge"));
3197
- const globalKnowledge = existsSync5(join7(globalClaudeDir, "knowledge"));
2791
+ const localKnowledge = existsSync3(join5(process.cwd(), "knowledge"));
2792
+ const globalKnowledge = existsSync3(join5(globalClaudeDir, "knowledge"));
3198
2793
  const knowledgeExists = localKnowledge || globalKnowledge;
3199
2794
  const knowledgeLocation = localKnowledge ? "project" : globalKnowledge ? "~/.claude/" : "";
3200
2795
  console.log(`Knowledge base: ${knowledgeExists ? `\u2713 Found (${knowledgeLocation})` : "\u2717 Not installed"}`);
3201
2796
  if (!knowledgeExists) {
3202
2797
  console.log(' \u2192 Run "npx @mthines/reaper-mcp install-skills"');
3203
2798
  }
3204
- const mcpJsonExists = existsSync5(join7(process.cwd(), ".mcp.json"));
2799
+ const mcpJsonExists = existsSync3(join5(process.cwd(), ".mcp.json"));
3205
2800
  console.log(`MCP config: ${mcpJsonExists ? "\u2713 .mcp.json found" : "\u2717 .mcp.json missing"}`);
3206
2801
  if (!mcpJsonExists) {
3207
2802
  console.log(' \u2192 Run "npx @mthines/reaper-mcp install-skills --project" to create .mcp.json');
3208
2803
  }
3209
2804
  console.log('\nTo check SWS Extensions, start REAPER and use the "list_available_fx" tool.');
3210
2805
  console.log("SWS provides enhanced plugin discovery and snapshot support.\n");
3211
- console.log("Python Sidecar (opt-in, for analyze_track_aesthetics):");
3212
- const sidecarVenvPath = join7(homedir5(), ".reaper-mcp", "sidecar-venv");
3213
- const venvBin = platform4() === "win32" ? "Scripts" : "bin";
3214
- const venvPythonName = platform4() === "win32" ? "python.exe" : "python";
3215
- const venvPython = join7(sidecarVenvPath, venvBin, venvPythonName);
3216
- const hfCachePath = join7(homedir5(), ".cache", "huggingface", "hub");
3217
- let pythonOk = false;
3218
- let pythonDetail = "not found";
3219
- try {
3220
- const pythonBin = process.env["PYTHON_BIN"] ?? "python3";
3221
- const { stdout, stderr } = await execAsync(`"${pythonBin}" --version`);
3222
- const versionStr = (stdout + stderr).trim();
3223
- const match = versionStr.match(/Python\s+(\d+)\.(\d+)/i);
3224
- if (match) {
3225
- const major = parseInt(match[1], 10);
3226
- const minor = parseInt(match[2], 10);
3227
- pythonOk = major > 3 || major === 3 && minor >= 10;
3228
- pythonDetail = versionStr;
3229
- }
3230
- } catch {
3231
- pythonDetail = "not found";
3232
- }
3233
- console.log(` Python \u2265 3.10: ${pythonOk ? `\u2713 ${pythonDetail}` : `\u2717 ${pythonDetail}`}`);
3234
- if (!pythonOk) {
3235
- console.log(" \u2192 Install Python 3.10+ from https://python.org or set PYTHON_BIN");
3236
- }
3237
- const venvExists = existsSync5(sidecarVenvPath);
3238
- console.log(` Venv: ${venvExists ? `\u2713 ${sidecarVenvPath}` : "\u2717 Not installed"}`);
3239
- if (!venvExists) {
3240
- console.log(" \u2192 Run: node dist/apps/reaper-mcp-server/main.js setup-sidecar");
3241
- }
3242
- let depsOk = false;
3243
- if (venvExists && existsSync5(venvPython)) {
3244
- try {
3245
- await execAsync(`"${venvPython}" -c "import audiobox_aesthetics"`);
3246
- depsOk = true;
3247
- } catch {
3248
- depsOk = false;
3249
- }
3250
- }
3251
- console.log(` Dependencies: ${depsOk ? "\u2713 audiobox-aesthetics importable" : "\u2717 Not installed"}`);
3252
- if (!depsOk && venvExists) {
3253
- console.log(" \u2192 Run: node dist/apps/reaper-mcp-server/main.js setup-sidecar");
3254
- }
3255
- const weightsPath = join7(hfCachePath, "models--facebook--audiobox-aesthetics");
3256
- const weightsExist = existsSync5(weightsPath);
3257
- console.log(` Model weights: ${weightsExist ? `\u2713 ${weightsPath}` : "\u2717 Not downloaded"}`);
3258
- if (!weightsExist) {
3259
- console.log(" \u2192 Run: node dist/apps/reaper-mcp-server/main.js setup-sidecar");
3260
- }
3261
- const sidecarReady = venvExists && depsOk && weightsExist;
3262
- const sidecarOptedIn = venvExists || weightsExist;
3263
- const sidecarBroken = sidecarOptedIn && !sidecarReady;
3264
- if (!sidecarOptedIn) {
3265
- console.log("\n Sidecar: not installed (opt-in). Run setup-sidecar to enable audio AI tools.");
3266
- } else if (sidecarBroken) {
3267
- console.log("\n Sidecar: PARTIALLY INSTALLED \u2014 run setup-sidecar to repair.");
3268
- } else {
3269
- console.log("\n Sidecar: fully installed and ready.");
3270
- }
3271
- console.log("");
3272
- process.exit(bridgeRunning && knowledgeExists && mcpJsonExists && !sidecarBroken ? 0 : 1);
2806
+ process.exit(bridgeRunning && knowledgeExists && mcpJsonExists ? 0 : 1);
3273
2807
  }
3274
2808
  async function serve() {
3275
2809
  const log = (...args) => console.error("[reaper-mcp]", ...args);
@@ -3278,7 +2812,7 @@ async function serve() {
3278
2812
  startDiagnosticsPoller();
3279
2813
  startEventsPoller();
3280
2814
  log("Starting REAPER MCP Server...");
3281
- log(`Entry: ${fileURLToPath3(import.meta.url)}`);
2815
+ log(`Entry: ${fileURLToPath2(import.meta.url)}`);
3282
2816
  const tracer = getTracer();
3283
2817
  await tracer.startActiveSpan("mcp.server.startup", { kind: SpanKind3.INTERNAL }, async (startupSpan) => {
3284
2818
  try {
@@ -3332,7 +2866,7 @@ switch (command) {
3332
2866
  case "init":
3333
2867
  runInit(
3334
2868
  { yes: hasYesFlag, project: hasProjectFlag },
3335
- () => __dirname2
2869
+ () => __dirname
3336
2870
  ).catch((err) => {
3337
2871
  console.error("Init failed:", err);
3338
2872
  process.exit(1);
@@ -3356,12 +2890,6 @@ switch (command) {
3356
2890
  process.exit(1);
3357
2891
  });
3358
2892
  break;
3359
- case "setup-sidecar":
3360
- setupSidecar().catch((err) => {
3361
- console.error("Sidecar setup failed:", err);
3362
- process.exit(1);
3363
- });
3364
- break;
3365
2893
  case "status": {
3366
2894
  (async () => {
3367
2895
  const running = await isBridgeRunning();
@@ -3387,7 +2915,6 @@ Usage:
3387
2915
  npx @mthines/reaper-mcp init --yes Non-interactive setup (install everything with defaults)
3388
2916
  npx @mthines/reaper-mcp init --project Include .mcp.json in current directory
3389
2917
  npx @mthines/reaper-mcp setup Install Lua bridge + JSFX analyzers into REAPER
3390
- npx @mthines/reaper-mcp setup-sidecar Install Python sidecar for perceptual audio analysis (opt-in, ~831 MB)
3391
2918
  npx @mthines/reaper-mcp install-skills Install AI knowledge + agents globally (default)
3392
2919
  npx @mthines/reaper-mcp install-skills --project Install into current project directory
3393
2920
  npx @mthines/reaper-mcp install-skills --global Install into ~/.claude/ (default)
@@ -3409,31 +2936,24 @@ Tip: install globally for shorter commands:
3409
2936
  `);
3410
2937
  break;
3411
2938
  }
3412
- function shutdownAll() {
3413
- stopPollers();
3414
- try {
3415
- getSidecarClient().shutdown();
3416
- } catch {
3417
- }
3418
- }
3419
2939
  process.on("SIGINT", () => {
3420
2940
  console.error("[reaper-mcp] Interrupted");
3421
- shutdownAll();
2941
+ stopPollers();
3422
2942
  shutdownTelemetry().finally(() => process.exit(0));
3423
2943
  });
3424
2944
  process.on("SIGTERM", () => {
3425
2945
  console.error("[reaper-mcp] Terminated");
3426
- shutdownAll();
2946
+ stopPollers();
3427
2947
  shutdownTelemetry().finally(() => process.exit(0));
3428
2948
  });
3429
2949
  process.on("uncaughtException", (err) => {
3430
2950
  console.error("[reaper-mcp] Uncaught exception:", err);
3431
- shutdownAll();
2951
+ stopPollers();
3432
2952
  shutdownTelemetry().finally(() => process.exit(1));
3433
2953
  });
3434
2954
  process.on("unhandledRejection", (reason) => {
3435
2955
  console.error("[reaper-mcp] Unhandled rejection:", reason);
3436
- shutdownAll();
2956
+ stopPollers();
3437
2957
  shutdownTelemetry().finally(() => process.exit(1));
3438
2958
  });
3439
2959
  //# sourceMappingURL=main.js.map