livepilot 1.0.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +409 -0
  4. package/bin/livepilot.js +390 -0
  5. package/installer/install.js +95 -0
  6. package/installer/paths.js +79 -0
  7. package/mcp_server/__init__.py +2 -0
  8. package/mcp_server/__main__.py +5 -0
  9. package/mcp_server/connection.py +210 -0
  10. package/mcp_server/memory/__init__.py +5 -0
  11. package/mcp_server/memory/technique_store.py +296 -0
  12. package/mcp_server/server.py +87 -0
  13. package/mcp_server/tools/__init__.py +1 -0
  14. package/mcp_server/tools/arrangement.py +407 -0
  15. package/mcp_server/tools/browser.py +86 -0
  16. package/mcp_server/tools/clips.py +218 -0
  17. package/mcp_server/tools/devices.py +256 -0
  18. package/mcp_server/tools/memory.py +198 -0
  19. package/mcp_server/tools/mixing.py +121 -0
  20. package/mcp_server/tools/notes.py +269 -0
  21. package/mcp_server/tools/scenes.py +89 -0
  22. package/mcp_server/tools/tracks.py +175 -0
  23. package/mcp_server/tools/transport.py +117 -0
  24. package/package.json +37 -0
  25. package/plugin/agents/livepilot-producer/AGENT.md +62 -0
  26. package/plugin/commands/beat.md +18 -0
  27. package/plugin/commands/memory.md +22 -0
  28. package/plugin/commands/mix.md +15 -0
  29. package/plugin/commands/session.md +13 -0
  30. package/plugin/commands/sounddesign.md +16 -0
  31. package/plugin/plugin.json +19 -0
  32. package/plugin/skills/livepilot-core/SKILL.md +208 -0
  33. package/plugin/skills/livepilot-core/references/ableton-workflow-patterns.md +831 -0
  34. package/plugin/skills/livepilot-core/references/device-atlas/00-index.md +110 -0
  35. package/plugin/skills/livepilot-core/references/device-atlas/distortion-and-character.md +687 -0
  36. package/plugin/skills/livepilot-core/references/device-atlas/drums-and-percussion.md +753 -0
  37. package/plugin/skills/livepilot-core/references/device-atlas/dynamics-and-punch.md +525 -0
  38. package/plugin/skills/livepilot-core/references/device-atlas/eq-and-filtering.md +402 -0
  39. package/plugin/skills/livepilot-core/references/device-atlas/midi-tools.md +963 -0
  40. package/plugin/skills/livepilot-core/references/device-atlas/movement-and-modulation.md +874 -0
  41. package/plugin/skills/livepilot-core/references/device-atlas/space-and-depth.md +571 -0
  42. package/plugin/skills/livepilot-core/references/device-atlas/spectral-and-weird.md +714 -0
  43. package/plugin/skills/livepilot-core/references/device-atlas/synths-native.md +953 -0
  44. package/plugin/skills/livepilot-core/references/m4l-devices.md +352 -0
  45. package/plugin/skills/livepilot-core/references/memory-guide.md +107 -0
  46. package/plugin/skills/livepilot-core/references/midi-recipes.md +402 -0
  47. package/plugin/skills/livepilot-core/references/mixing-patterns.md +578 -0
  48. package/plugin/skills/livepilot-core/references/overview.md +209 -0
  49. package/plugin/skills/livepilot-core/references/sound-design.md +392 -0
  50. package/remote_script/LivePilot/__init__.py +42 -0
  51. package/remote_script/LivePilot/arrangement.py +693 -0
  52. package/remote_script/LivePilot/browser.py +424 -0
  53. package/remote_script/LivePilot/clips.py +211 -0
  54. package/remote_script/LivePilot/devices.py +596 -0
  55. package/remote_script/LivePilot/diagnostics.py +198 -0
  56. package/remote_script/LivePilot/mixing.py +194 -0
  57. package/remote_script/LivePilot/notes.py +339 -0
  58. package/remote_script/LivePilot/router.py +74 -0
  59. package/remote_script/LivePilot/scenes.py +99 -0
  60. package/remote_script/LivePilot/server.py +293 -0
  61. package/remote_script/LivePilot/tracks.py +268 -0
  62. package/remote_script/LivePilot/transport.py +151 -0
  63. package/remote_script/LivePilot/utils.py +123 -0
  64. package/requirements.txt +2 -0
@@ -0,0 +1,390 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { execFileSync, execSync, spawn } = require("child_process");
5
+ const fs = require("fs");
6
+ const net = require("net");
7
+ const path = require("path");
8
+
9
+ const ROOT = path.resolve(__dirname, "..");
10
+ const PKG = require(path.join(ROOT, "package.json"));
11
+ const VENV_DIR = path.join(ROOT, ".venv");
12
+ const REQUIREMENTS = path.join(ROOT, "requirements.txt");
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Python detection
16
+ // ---------------------------------------------------------------------------
17
+
18
+ function findPython() {
19
+ // On Windows, also try the "py -3" launcher which avoids the
20
+ // Microsoft Store stub that "python3" resolves to.
21
+ const candidates = process.platform === "win32"
22
+ ? ["python", "python3", "py"]
23
+ : ["python3", "python"];
24
+
25
+ for (const cmd of candidates) {
26
+ try {
27
+ const args = cmd === "py" ? ["-3", "--version"] : ["--version"];
28
+ const out = execFileSync(cmd, args, {
29
+ encoding: "utf-8",
30
+ timeout: 5000,
31
+ }).trim();
32
+ const match = out.match(/Python\s+(\d+)\.(\d+)/);
33
+ if (match) {
34
+ const major = parseInt(match[1], 10);
35
+ const minor = parseInt(match[2], 10);
36
+ if (major === 3 && minor >= 9) {
37
+ // For "py" launcher, the actual command to use is "py -3"
38
+ const actualCmd = cmd === "py" ? "py" : cmd;
39
+ const actualArgs = cmd === "py" ? ["-3"] : [];
40
+ return { cmd: actualCmd, version: out, prefixArgs: actualArgs };
41
+ }
42
+ }
43
+ } catch {
44
+ // command not found or failed — try next
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+
50
+ /**
51
+ * Return the path to the Python binary inside the venv.
52
+ */
53
+ function venvPython() {
54
+ const isWin = process.platform === "win32";
55
+ return path.join(VENV_DIR, isWin ? "Scripts" : "bin", isWin ? "python.exe" : "python3");
56
+ }
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Virtual environment bootstrap
60
+ // ---------------------------------------------------------------------------
61
+
62
+ /**
63
+ * Ensure a local .venv exists with dependencies installed.
64
+ * Returns the path to the venv Python binary.
65
+ */
66
+ function ensureVenv(systemPython, prefixArgs) {
67
+ const prefix = prefixArgs || [];
68
+ const venvPy = venvPython();
69
+
70
+ // Check if venv already exists and has our deps
71
+ if (fs.existsSync(venvPy)) {
72
+ try {
73
+ execFileSync(venvPy, ["-c", "import fastmcp"], {
74
+ encoding: "utf-8",
75
+ timeout: 10000,
76
+ stdio: "pipe",
77
+ });
78
+ return venvPy; // venv exists and fastmcp is importable
79
+ } catch {
80
+ // venv exists but deps missing — reinstall
81
+ console.error("LivePilot: reinstalling Python dependencies...");
82
+ execFileSync(venvPy, ["-m", "pip", "install", "-q", "-r", REQUIREMENTS], {
83
+ cwd: ROOT,
84
+ stdio: ["pipe", "pipe", "inherit"],
85
+ timeout: 120000,
86
+ });
87
+ return venvPy;
88
+ }
89
+ }
90
+
91
+ // Create venv from scratch
92
+ console.error("LivePilot: setting up Python environment (first run)...");
93
+ execFileSync(systemPython, [...prefix, "-m", "venv", VENV_DIR], {
94
+ cwd: ROOT,
95
+ stdio: ["pipe", "pipe", "inherit"],
96
+ timeout: 30000,
97
+ });
98
+
99
+ console.error("LivePilot: installing dependencies...");
100
+ execFileSync(venvPython(), ["-m", "pip", "install", "-q", "-r", REQUIREMENTS], {
101
+ cwd: ROOT,
102
+ stdio: ["pipe", "pipe", "inherit"],
103
+ timeout: 120000,
104
+ });
105
+
106
+ return venvPython();
107
+ }
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // Status check — TCP ping to Remote Script
111
+ // ---------------------------------------------------------------------------
112
+
113
+ function checkStatus() {
114
+ return new Promise((resolve) => {
115
+ const sock = new net.Socket();
116
+ const PORT = parseInt(process.env.LIVE_MCP_PORT || "9878", 10);
117
+ const HOST = process.env.LIVE_MCP_HOST || "127.0.0.1";
118
+
119
+ sock.setTimeout(3000);
120
+
121
+ sock.on("connect", () => {
122
+ const ping = JSON.stringify({ id: "ping", type: "ping" }) + "\n";
123
+ sock.write(ping);
124
+ });
125
+
126
+ let buf = "";
127
+ sock.on("data", (chunk) => {
128
+ buf += chunk.toString();
129
+ if (buf.includes("\n")) {
130
+ try {
131
+ const resp = JSON.parse(buf.split("\n")[0]);
132
+ if (resp.ok === true && resp.result && resp.result.pong) {
133
+ console.log(" Ableton Live: connected on %s:%d", HOST, PORT);
134
+ } else {
135
+ console.log(" Ableton Live: unexpected response:", JSON.stringify(resp));
136
+ }
137
+ } catch {
138
+ console.log(" Ableton Live: invalid response");
139
+ }
140
+ sock.destroy();
141
+ resolve(true);
142
+ }
143
+ });
144
+
145
+ sock.on("timeout", () => {
146
+ console.log(" Ableton Live: connection timed out");
147
+ sock.destroy();
148
+ resolve(false);
149
+ });
150
+
151
+ sock.on("error", (err) => {
152
+ if (err.code === "ECONNREFUSED") {
153
+ console.log(" Ableton Live: not running (connection refused on %s:%d)", HOST, PORT);
154
+ } else {
155
+ console.log(" Ableton Live: %s", err.message);
156
+ }
157
+ resolve(false);
158
+ });
159
+
160
+ sock.connect(PORT, HOST);
161
+ });
162
+ }
163
+
164
+ // ---------------------------------------------------------------------------
165
+ // Doctor — comprehensive diagnostic
166
+ // ---------------------------------------------------------------------------
167
+
168
+ async function doctor() {
169
+ console.log("LivePilot Doctor v%s", PKG.version);
170
+ console.log("─".repeat(50));
171
+
172
+ let ok = true;
173
+
174
+ // 1. Python
175
+ const pyInfo = findPython();
176
+ if (pyInfo) {
177
+ console.log(" Python: %s (%s)", pyInfo.version, pyInfo.cmd);
178
+ } else {
179
+ console.log(" Python: NOT FOUND (need >= 3.9)");
180
+ console.log(" Fix: install Python 3.9+ and add to PATH");
181
+ ok = false;
182
+ }
183
+
184
+ // 2. Virtual environment
185
+ const venvPy = venvPython();
186
+ if (fs.existsSync(venvPy)) {
187
+ console.log(" Venv: %s", VENV_DIR);
188
+ } else {
189
+ console.log(" Venv: NOT CREATED (run 'npx livepilot' to bootstrap)");
190
+ console.log(" Fix: run 'npx livepilot' once to auto-create the virtual environment");
191
+ ok = false;
192
+ }
193
+
194
+ // 3. fastmcp import
195
+ if (fs.existsSync(venvPy)) {
196
+ try {
197
+ const ver = execFileSync(venvPy, ["-c", "import fastmcp; print(fastmcp.__version__)"], {
198
+ encoding: "utf-8",
199
+ timeout: 10000,
200
+ stdio: "pipe",
201
+ }).trim();
202
+ console.log(" fastmcp: v%s", ver);
203
+ } catch {
204
+ console.log(" fastmcp: NOT INSTALLED in venv");
205
+ console.log(" Fix: run 'npx livepilot' to auto-install dependencies");
206
+ ok = false;
207
+ }
208
+ }
209
+
210
+ // 4. MCP server module
211
+ const serverInit = path.join(ROOT, "mcp_server", "__init__.py");
212
+ if (fs.existsSync(serverInit)) {
213
+ console.log(" MCP server: found at %s", path.join(ROOT, "mcp_server"));
214
+ } else {
215
+ console.log(" MCP server: MISSING (mcp_server/ directory not found)");
216
+ ok = false;
217
+ }
218
+
219
+ // 5. Remote Script
220
+ const remoteInit = path.join(ROOT, "remote_script", "LivePilot", "__init__.py");
221
+ if (fs.existsSync(remoteInit)) {
222
+ console.log(" Remote Script: found at %s", path.join(ROOT, "remote_script", "LivePilot"));
223
+ } else {
224
+ console.log(" Remote Script: MISSING");
225
+ ok = false;
226
+ }
227
+
228
+ // 6. Remote Script installed in Ableton?
229
+ try {
230
+ const { findAbletonPaths } = require(path.join(ROOT, "installer", "paths.js"));
231
+ const candidates = findAbletonPaths();
232
+ let installed = false;
233
+ for (const c of candidates) {
234
+ const dest = path.join(c.path, "LivePilot", "__init__.py");
235
+ if (fs.existsSync(dest)) {
236
+ console.log(" Ableton install: %s", path.join(c.path, "LivePilot"));
237
+ installed = true;
238
+ break;
239
+ }
240
+ }
241
+ if (!installed) {
242
+ console.log(" Ableton install: NOT INSTALLED");
243
+ console.log(" Fix: run 'npx livepilot --install' to copy Remote Script");
244
+ ok = false;
245
+ }
246
+ } catch {
247
+ console.log(" Ableton install: could not check (installer module error)");
248
+ }
249
+
250
+ // 7. Environment overrides
251
+ if (process.env.LIVE_MCP_HOST || process.env.LIVE_MCP_PORT) {
252
+ console.log(" Env overrides: HOST=%s PORT=%s",
253
+ process.env.LIVE_MCP_HOST || "(default 127.0.0.1)",
254
+ process.env.LIVE_MCP_PORT || "(default 9878)");
255
+ }
256
+
257
+ // 8. TCP connection to Ableton
258
+ console.log("");
259
+ console.log("Connection test:");
260
+ const connected = await checkStatus();
261
+ if (!connected) {
262
+ ok = false;
263
+ }
264
+
265
+ // Summary
266
+ console.log("");
267
+ console.log("─".repeat(50));
268
+ if (ok) {
269
+ console.log("All checks passed.");
270
+ } else {
271
+ console.log("Some checks failed — see Fix suggestions above.");
272
+ }
273
+ return ok;
274
+ }
275
+
276
+ // ---------------------------------------------------------------------------
277
+ // Main
278
+ // ---------------------------------------------------------------------------
279
+
280
+ async function main() {
281
+ const args = process.argv.slice(2);
282
+ const flag = args[0] || "";
283
+
284
+ // --version / -v
285
+ if (flag === "--version" || flag === "-v") {
286
+ console.log("livepilot v%s", PKG.version);
287
+ return;
288
+ }
289
+
290
+ // --help / -h
291
+ if (flag === "--help" || flag === "-h") {
292
+ console.log("livepilot v%s — AI copilot for Ableton Live 12", PKG.version);
293
+ console.log("");
294
+ console.log("Usage: npx livepilot [command]");
295
+ console.log("");
296
+ console.log("Commands:");
297
+ console.log(" (none) Start the MCP server");
298
+ console.log(" --install Install Remote Script into Ableton Live");
299
+ console.log(" --uninstall Remove Remote Script from Ableton Live");
300
+ console.log(" --status Check if Ableton Live is reachable");
301
+ console.log(" --doctor Run diagnostics (Python, deps, connection)");
302
+ console.log(" --version Show version");
303
+ console.log(" --help Show this help");
304
+ console.log("");
305
+ console.log("Environment:");
306
+ console.log(" LIVE_MCP_HOST Remote Script host (default: 127.0.0.1)");
307
+ console.log(" LIVE_MCP_PORT Remote Script port (default: 9878)");
308
+ return;
309
+ }
310
+
311
+ // --install
312
+ if (flag === "--install") {
313
+ const { install } = require(path.join(ROOT, "installer", "install.js"));
314
+ install();
315
+ return;
316
+ }
317
+
318
+ // --uninstall
319
+ if (flag === "--uninstall") {
320
+ const { uninstall } = require(path.join(ROOT, "installer", "install.js"));
321
+ uninstall();
322
+ return;
323
+ }
324
+
325
+ // --status
326
+ if (flag === "--status") {
327
+ const reachable = await checkStatus();
328
+ process.exit(reachable ? 0 : 1);
329
+ }
330
+
331
+ // --doctor
332
+ if (flag === "--doctor") {
333
+ const passed = await doctor();
334
+ process.exit(passed ? 0 : 1);
335
+ }
336
+
337
+ // Default: start MCP server
338
+ const pyInfo = findPython();
339
+ if (!pyInfo) {
340
+ console.error("Error: Python >= 3.9 is required but was not found.");
341
+ console.error("");
342
+ console.error("Install Python 3.9+ and ensure 'python3' or 'python' is on your PATH.");
343
+ console.error(" macOS: brew install python@3.12");
344
+ console.error(" Ubuntu: sudo apt install python3");
345
+ console.error(" Windows: https://www.python.org/downloads/");
346
+ process.exit(1);
347
+ }
348
+
349
+ // Bootstrap venv and install deps automatically
350
+ let pythonBin;
351
+ try {
352
+ pythonBin = ensureVenv(pyInfo.cmd, pyInfo.prefixArgs);
353
+ } catch (err) {
354
+ console.error("Error: failed to set up Python environment.");
355
+ console.error(" %s", err.message);
356
+ if (err.stderr) {
357
+ console.error("");
358
+ console.error("pip output:");
359
+ console.error(" %s", err.stderr.toString().trim());
360
+ }
361
+ if (err.stdout) {
362
+ console.error(" %s", err.stdout.toString().trim());
363
+ }
364
+ console.error("");
365
+ console.error("You can try manually:");
366
+ console.error(" cd %s", ROOT);
367
+ console.error(" %s -m venv .venv", pyInfo.cmd);
368
+ console.error(" .venv/bin/pip install -r requirements.txt");
369
+ process.exit(1);
370
+ }
371
+
372
+ const child = spawn(pythonBin, ["-m", "mcp_server"], {
373
+ cwd: ROOT,
374
+ stdio: "inherit",
375
+ });
376
+
377
+ child.on("error", (err) => {
378
+ console.error("Failed to start MCP server: %s", err.message);
379
+ process.exit(1);
380
+ });
381
+
382
+ child.on("exit", (code) => {
383
+ process.exit(code || 0);
384
+ });
385
+ }
386
+
387
+ main().catch((err) => {
388
+ console.error(err);
389
+ process.exit(1);
390
+ });
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { findAbletonPaths } = require("./paths");
6
+
7
+ const ROOT = path.resolve(__dirname, "..");
8
+ const SOURCE_DIR = path.join(ROOT, "remote_script", "LivePilot");
9
+
10
+ // Files / dirs to skip during copy
11
+ const SKIP = new Set(["__pycache__", ".DS_Store"]);
12
+
13
+ /**
14
+ * Recursively copy a directory, skipping __pycache__ and .DS_Store.
15
+ */
16
+ function copyDirSync(src, dest) {
17
+ fs.mkdirSync(dest, { recursive: true });
18
+ const entries = fs.readdirSync(src, { withFileTypes: true });
19
+ for (const entry of entries) {
20
+ if (SKIP.has(entry.name)) continue;
21
+ const srcPath = path.join(src, entry.name);
22
+ const destPath = path.join(dest, entry.name);
23
+ if (entry.isDirectory()) {
24
+ copyDirSync(srcPath, destPath);
25
+ } else {
26
+ fs.copyFileSync(srcPath, destPath);
27
+ }
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Install the LivePilot Remote Script into Ableton's Remote Scripts folder.
33
+ */
34
+ function install() {
35
+ const candidates = findAbletonPaths();
36
+
37
+ if (candidates.length === 0) {
38
+ console.log("Could not auto-detect an Ableton Live Remote Scripts directory.");
39
+ console.log("");
40
+ console.log("Manual install:");
41
+ console.log(" 1. Open Ableton Live > Preferences > File/Folder");
42
+ console.log(" 2. Find the User Remote Scripts folder path");
43
+ console.log(" 3. Copy the 'remote_script/LivePilot' folder into that directory");
44
+ console.log(" 4. Restart Ableton Live");
45
+ console.log(" 5. In Preferences > Link/Tempo/MIDI, set a Control Surface to 'LivePilot'");
46
+ process.exit(1);
47
+ }
48
+
49
+ // Use the first valid candidate
50
+ const target = candidates[0];
51
+ const targetBase = target.path;
52
+ const destDir = path.join(targetBase, "LivePilot");
53
+
54
+ // Ensure target base exists
55
+ fs.mkdirSync(targetBase, { recursive: true });
56
+
57
+ console.log("Installing LivePilot Remote Script...");
58
+ console.log(" Source: %s", SOURCE_DIR);
59
+ console.log(" Target: %s", destDir);
60
+ console.log(" Location: %s", target.description);
61
+ console.log("");
62
+
63
+ copyDirSync(SOURCE_DIR, destDir);
64
+
65
+ console.log("Done! Next steps:");
66
+ console.log(" 1. Restart Ableton Live (or press Cmd+, to open Preferences)");
67
+ console.log(" 2. Go to Link/Tempo/MIDI > Control Surface");
68
+ console.log(" 3. Select 'LivePilot' from the dropdown");
69
+ console.log(" 4. Run 'npx livepilot --status' to verify the connection");
70
+ }
71
+
72
+ /**
73
+ * Remove the LivePilot Remote Script from all detected locations.
74
+ */
75
+ function uninstall() {
76
+ const candidates = findAbletonPaths();
77
+ let removed = 0;
78
+
79
+ for (const candidate of candidates) {
80
+ const destDir = path.join(candidate.path, "LivePilot");
81
+ if (fs.existsSync(destDir)) {
82
+ console.log("Removing: %s", destDir);
83
+ fs.rmSync(destDir, { recursive: true, force: true });
84
+ removed++;
85
+ }
86
+ }
87
+
88
+ if (removed === 0) {
89
+ console.log("No LivePilot Remote Script installations found.");
90
+ } else {
91
+ console.log("Uninstalled %d location(s). Restart Ableton Live to complete.", removed);
92
+ }
93
+ }
94
+
95
+ module.exports = { install, uninstall };
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const os = require("os");
5
+ const path = require("path");
6
+
7
+ /**
8
+ * Return an array of {path, description} for candidate Ableton Remote Scripts
9
+ * directories on the current platform.
10
+ */
11
+ function findAbletonPaths() {
12
+ const candidates = [];
13
+ const home = os.homedir();
14
+ const platform = os.platform();
15
+
16
+ if (platform === "darwin") {
17
+ // macOS candidate 1: User Library default
18
+ candidates.push({
19
+ path: path.join(home, "Music", "Ableton", "User Library", "Remote Scripts"),
20
+ description: "Ableton User Library (default)",
21
+ });
22
+
23
+ // macOS candidate 2: Preferences — scan for Live 12.* dirs
24
+ const prefsDir = path.join(home, "Library", "Preferences", "Ableton");
25
+ if (fs.existsSync(prefsDir)) {
26
+ try {
27
+ const entries = fs.readdirSync(prefsDir);
28
+ for (const entry of entries) {
29
+ if (/^Live \d+/.test(entry)) {
30
+ candidates.push({
31
+ path: path.join(prefsDir, entry, "User Remote Scripts"),
32
+ description: `Ableton Preferences (${entry})`,
33
+ });
34
+ }
35
+ }
36
+ } catch {
37
+ // permission error — skip
38
+ }
39
+ }
40
+ } else if (platform === "win32") {
41
+ const userProfile = process.env.USERPROFILE || home;
42
+ const appData = process.env.APPDATA || path.join(userProfile, "AppData", "Roaming");
43
+
44
+ // Windows candidate 1: Documents User Library
45
+ candidates.push({
46
+ path: path.join(userProfile, "Documents", "Ableton", "User Library", "Remote Scripts"),
47
+ description: "Ableton User Library (default)",
48
+ });
49
+
50
+ // Windows candidate 2: AppData — scan for Live 12.* dirs
51
+ const abletonAppData = path.join(appData, "Ableton");
52
+ if (fs.existsSync(abletonAppData)) {
53
+ try {
54
+ const entries = fs.readdirSync(abletonAppData);
55
+ for (const entry of entries) {
56
+ if (/^Live \d+/.test(entry)) {
57
+ candidates.push({
58
+ path: path.join(abletonAppData, entry, "Preferences", "User Remote Scripts"),
59
+ description: `Ableton AppData (${entry})`,
60
+ });
61
+ }
62
+ }
63
+ } catch {
64
+ // permission error — skip
65
+ }
66
+ }
67
+ }
68
+
69
+ // Filter: keep candidate if the path itself exists, or its parent exists
70
+ return candidates.filter((c) => {
71
+ try {
72
+ return fs.existsSync(c.path) || fs.existsSync(path.dirname(c.path));
73
+ } catch {
74
+ return false;
75
+ }
76
+ });
77
+ }
78
+
79
+ module.exports = { findAbletonPaths };
@@ -0,0 +1,2 @@
1
+ """LivePilot MCP Server — bridges MCP protocol to Ableton Live."""
2
+ __version__ = "1.0.0"
@@ -0,0 +1,5 @@
1
+ """Allow running as: python -m mcp_server"""
2
+ from .server import main
3
+
4
+ if __name__ == "__main__":
5
+ main()