livepilot 1.8.0 → 1.8.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.
Files changed (39) hide show
  1. package/.claude-plugin/marketplace.json +21 -0
  2. package/CHANGELOG.md +15 -0
  3. package/livepilot/.claude-plugin/plugin.json +8 -0
  4. package/{plugin → livepilot}/skills/livepilot-release/SKILL.md +6 -6
  5. package/m4l_device/LivePilot_Analyzer.amxd +0 -0
  6. package/m4l_device/LivePilot_Analyzer.maxpat +172 -16
  7. package/m4l_device/livepilot_bridge.js +1 -1
  8. package/mcp_server/__init__.py +1 -1
  9. package/mcp_server/m4l_bridge.py +8 -6
  10. package/mcp_server/server.py +48 -2
  11. package/mcp_server/tools/_theory_engine.py +17 -2
  12. package/package.json +1 -1
  13. package/remote_script/LivePilot/__init__.py +2 -2
  14. package/plugin/plugin.json +0 -20
  15. /package/{plugin → livepilot}/agents/livepilot-producer/AGENT.md +0 -0
  16. /package/{plugin → livepilot}/commands/beat.md +0 -0
  17. /package/{plugin → livepilot}/commands/memory.md +0 -0
  18. /package/{plugin → livepilot}/commands/mix.md +0 -0
  19. /package/{plugin → livepilot}/commands/session.md +0 -0
  20. /package/{plugin → livepilot}/commands/sounddesign.md +0 -0
  21. /package/{plugin → livepilot}/skills/livepilot-core/SKILL.md +0 -0
  22. /package/{plugin → livepilot}/skills/livepilot-core/references/ableton-workflow-patterns.md +0 -0
  23. /package/{plugin → livepilot}/skills/livepilot-core/references/automation-atlas.md +0 -0
  24. /package/{plugin → livepilot}/skills/livepilot-core/references/device-atlas/00-index.md +0 -0
  25. /package/{plugin → livepilot}/skills/livepilot-core/references/device-atlas/distortion-and-character.md +0 -0
  26. /package/{plugin → livepilot}/skills/livepilot-core/references/device-atlas/drums-and-percussion.md +0 -0
  27. /package/{plugin → livepilot}/skills/livepilot-core/references/device-atlas/dynamics-and-punch.md +0 -0
  28. /package/{plugin → livepilot}/skills/livepilot-core/references/device-atlas/eq-and-filtering.md +0 -0
  29. /package/{plugin → livepilot}/skills/livepilot-core/references/device-atlas/midi-tools.md +0 -0
  30. /package/{plugin → livepilot}/skills/livepilot-core/references/device-atlas/movement-and-modulation.md +0 -0
  31. /package/{plugin → livepilot}/skills/livepilot-core/references/device-atlas/space-and-depth.md +0 -0
  32. /package/{plugin → livepilot}/skills/livepilot-core/references/device-atlas/spectral-and-weird.md +0 -0
  33. /package/{plugin → livepilot}/skills/livepilot-core/references/device-atlas/synths-native.md +0 -0
  34. /package/{plugin → livepilot}/skills/livepilot-core/references/m4l-devices.md +0 -0
  35. /package/{plugin → livepilot}/skills/livepilot-core/references/memory-guide.md +0 -0
  36. /package/{plugin → livepilot}/skills/livepilot-core/references/midi-recipes.md +0 -0
  37. /package/{plugin → livepilot}/skills/livepilot-core/references/mixing-patterns.md +0 -0
  38. /package/{plugin → livepilot}/skills/livepilot-core/references/overview.md +0 -0
  39. /package/{plugin → livepilot}/skills/livepilot-core/references/sound-design.md +0 -0
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
3
+ "name": "dreamrec-LivePilot",
4
+ "description": "Agentic MCP production system for Ableton Live 12 — 168 tools, 17 domains",
5
+ "owner": {
6
+ "name": "dreamrec",
7
+ "email": "dreamrec@users.noreply.github.com"
8
+ },
9
+ "plugins": [
10
+ {
11
+ "name": "livepilot",
12
+ "description": "Agentic production system for Ableton Live 12 — 168 tools, 17 domains, device atlas, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
13
+ "version": "1.8.1",
14
+ "author": {
15
+ "name": "Pilot Studio"
16
+ },
17
+ "source": "./livepilot",
18
+ "category": "integration"
19
+ }
20
+ ]
21
+ }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.8.2 — FluCoMa Wiring + Analyzer Fix (March 2026)
4
+
5
+ - Fix: wire 6 FluCoMa DSP objects into LivePilot_Analyzer.maxpat (spectral shape, mel bands, chroma, loudness, onset, novelty)
6
+ - Fix: onset/novelty Python handlers now accept 1 arg (fluid.onsetfeature~/noveltyfeature~ output single float)
7
+ - Fix: rebuild .amxd with FluCoMa objects + binary patch openinpresentation
8
+ - FluCoMa perception tools now fully functional when FluCoMa package is installed
9
+
10
+ ## 1.8.1 — Patch (March 2026)
11
+
12
+ - Fix: `parse_key()` now accepts shorthand key notation ("Am", "C#m", "Bbm") in addition to "A minor" / "C# major"
13
+ - Fix: re-freeze LivePilot_Analyzer.amxd with v1.8.0 bridge + patch openinpresentation
14
+ - Fix: address audit findings from fresh v1.8 code review
15
+ - Fix: update bridge version string
16
+ - Fix: restructure plugin directory for Claude Code marketplace compatibility (`plugin/` → `livepilot/.claude-plugin/`)
17
+
3
18
  ## 1.8.0 — Perception Layer (March 2026)
4
19
 
5
20
  **13 new tools (155 → 168), 1 new domain (perception), FluCoMa real-time DSP, offline audio analysis, audio capture.**
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "livepilot",
3
+ "version": "1.8.2",
4
+ "description": "Agentic production system for Ableton Live 12 — 168 tools, 17 domains, device atlas, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
5
+ "author": {
6
+ "name": "Pilot Studio"
7
+ }
8
+ }
@@ -12,24 +12,24 @@ Run this checklist EVERY time the user says "update everything", "push", "releas
12
12
  - [ ] `package.json` → `"version"`
13
13
  - [ ] `package-lock.json` → `"version"` (run `npm install --package-lock-only` if stale)
14
14
  - [ ] `server.json` → `"version"` (TWO locations: top-level and package)
15
- - [ ] `plugin/plugin.json` → `"version"`
15
+ - [ ] `livepilot/.claude-plugin/plugin.json` → `"version"`
16
16
  - [ ] `mcp_server/__init__.py` → `__version__`
17
17
  - [ ] `remote_script/LivePilot/__init__.py` → version in log message
18
18
  - [ ] `m4l_device/livepilot_bridge.js` → version in ping response
19
19
  - [ ] `CHANGELOG.md` → latest version header
20
20
  - [ ] `docs/social-banner.html` → version display
21
21
 
22
- **How to check:** `grep -rn "1\.[0-9]\.[0-9]" package.json server.json plugin/plugin.json mcp_server/__init__.py remote_script/LivePilot/__init__.py m4l_device/livepilot_bridge.js CHANGELOG.md docs/social-banner.html`
22
+ **How to check:** `grep -rn "1\.[0-9]\.[0-9]" package.json server.json livepilot/.claude-plugin/plugin.json mcp_server/__init__.py remote_script/LivePilot/__init__.py m4l_device/livepilot_bridge.js CHANGELOG.md docs/social-banner.html`
23
23
 
24
24
  ## 2. Tool Count (must ALL match)
25
25
 
26
26
  - [ ] `README.md` — every occurrence
27
27
  - [ ] `package.json` → `"description"`
28
28
  - [ ] `server.json` → `"description"`
29
- - [ ] `plugin/plugin.json` → `"description"`
29
+ - [ ] `livepilot/.claude-plugin/plugin.json` → `"description"`
30
30
  - [ ] `CLAUDE.md`
31
- - [ ] `plugin/skills/livepilot-core/SKILL.md` — header and domain breakdown
32
- - [ ] `plugin/skills/livepilot-core/references/overview.md`
31
+ - [ ] `livepilot/skills/livepilot-core/SKILL.md` — header and domain breakdown
32
+ - [ ] `livepilot/skills/livepilot-core/references/overview.md`
33
33
  - [ ] `docs/manual/index.md`
34
34
  - [ ] `docs/manual/tool-reference.md`
35
35
  - [ ] `docs/TOOL_REFERENCE.md`
@@ -97,5 +97,5 @@ Run this checklist EVERY time the user says "update everything", "push", "releas
97
97
 
98
98
  Run this one-liner to catch most issues:
99
99
  ```bash
100
- echo "=== Versions ===" && grep -h '"version"' package.json server.json plugin/plugin.json | head -5 && grep __version__ mcp_server/__init__.py && echo "=== Tool count ===" && grep -rc "@mcp.tool" mcp_server/tools/ | tail -1 && echo "=== npm ===" && npm view livepilot version 2>/dev/null && echo "=== GitHub release ===" && gh release list --limit 1 && echo "=== Tags ===" && git tag -l
100
+ echo "=== Versions ===" && grep -h '"version"' package.json server.json livepilot/.claude-plugin/plugin.json | head -5 && grep __version__ mcp_server/__init__.py && echo "=== Tool count ===" && grep -rc "@mcp.tool" mcp_server/tools/ | tail -1 && echo "=== npm ===" && npm view livepilot version 2>/dev/null && echo "=== GitHub release ===" && gh release list --limit 1 && echo "=== Tags ===" && git tag -l
101
101
  ```
Binary file
@@ -37,6 +37,20 @@
37
37
  "subpatcher_template": "",
38
38
  "assistshowspatchername": 0,
39
39
  "boxes": [
40
+ {
41
+ "box": {
42
+ "id": "obj-panel",
43
+ "maxclass": "panel",
44
+ "numinlets": 1,
45
+ "numoutlets": 0,
46
+ "patching_rect": [50.0, 790.0, 350.0, 82.0],
47
+ "presentation": 1,
48
+ "presentation_rect": [0.0, 0.0, 350.0, 170.0],
49
+ "bgcolor": [0.12, 0.12, 0.12, 1.0],
50
+ "bordercolor": [0.2, 0.2, 0.2, 1.0],
51
+ "border": 1
52
+ }
53
+ },
40
54
  {
41
55
  "box": {
42
56
  "id": "obj-1",
@@ -415,7 +429,7 @@
415
429
  "id": "obj-js",
416
430
  "maxclass": "newobj",
417
431
  "text": "js livepilot_bridge.js",
418
- "numinlets": 1,
432
+ "numinlets": 2,
419
433
  "numoutlets": 2,
420
434
  "outlettype": ["", ""],
421
435
  "patching_rect": [500.0, 550.0, 130.0, 22.0]
@@ -443,20 +457,6 @@
443
457
  "patching_rect": [700.0, 500.0, 95.0, 22.0]
444
458
  }
445
459
  },
446
- {
447
- "box": {
448
- "id": "obj-panel",
449
- "maxclass": "panel",
450
- "numinlets": 1,
451
- "numoutlets": 0,
452
- "patching_rect": [50.0, 790.0, 350.0, 82.0],
453
- "presentation": 1,
454
- "presentation_rect": [0.0, 0.0, 350.0, 170.0],
455
- "bgcolor": [0.12, 0.12, 0.12, 1.0],
456
- "bordercolor": [0.2, 0.2, 0.2, 1.0],
457
- "border": 1
458
- }
459
- },
460
460
  {
461
461
  "box": {
462
462
  "id": "obj-multislider",
@@ -592,6 +592,138 @@
592
592
  "outlettype": [""],
593
593
  "patching_rect": [810.0, 620.0, 72.0, 22.0]
594
594
  }
595
+ },
596
+ {
597
+ "box": {
598
+ "id": "obj-fc-spectralshape",
599
+ "maxclass": "newobj",
600
+ "text": "fluid.spectralshape~ @fftsettings 2048 512",
601
+ "numinlets": 1,
602
+ "numoutlets": 2,
603
+ "outlettype": ["", ""],
604
+ "patching_rect": [50.0, 880.0, 260.0, 22.0]
605
+ }
606
+ },
607
+ {
608
+ "box": {
609
+ "id": "obj-fc-prepend-spectralshape",
610
+ "maxclass": "newobj",
611
+ "text": "prepend /spectral_shape",
612
+ "numinlets": 1,
613
+ "numoutlets": 1,
614
+ "outlettype": [""],
615
+ "patching_rect": [50.0, 920.0, 140.0, 22.0]
616
+ }
617
+ },
618
+ {
619
+ "box": {
620
+ "id": "obj-fc-melbands",
621
+ "maxclass": "newobj",
622
+ "text": "fluid.melbands~ 40 @fftsettings 2048 512",
623
+ "numinlets": 1,
624
+ "numoutlets": 2,
625
+ "outlettype": ["", ""],
626
+ "patching_rect": [320.0, 880.0, 250.0, 22.0]
627
+ }
628
+ },
629
+ {
630
+ "box": {
631
+ "id": "obj-fc-prepend-melbands",
632
+ "maxclass": "newobj",
633
+ "text": "prepend /mel_bands",
634
+ "numinlets": 1,
635
+ "numoutlets": 1,
636
+ "outlettype": [""],
637
+ "patching_rect": [320.0, 920.0, 115.0, 22.0]
638
+ }
639
+ },
640
+ {
641
+ "box": {
642
+ "id": "obj-fc-chroma",
643
+ "maxclass": "newobj",
644
+ "text": "fluid.chroma~ @fftsettings 4096 1024",
645
+ "numinlets": 1,
646
+ "numoutlets": 2,
647
+ "outlettype": ["", ""],
648
+ "patching_rect": [580.0, 880.0, 230.0, 22.0]
649
+ }
650
+ },
651
+ {
652
+ "box": {
653
+ "id": "obj-fc-prepend-chroma",
654
+ "maxclass": "newobj",
655
+ "text": "prepend /chroma",
656
+ "numinlets": 1,
657
+ "numoutlets": 1,
658
+ "outlettype": [""],
659
+ "patching_rect": [580.0, 920.0, 100.0, 22.0]
660
+ }
661
+ },
662
+ {
663
+ "box": {
664
+ "id": "obj-fc-loudness",
665
+ "maxclass": "newobj",
666
+ "text": "fluid.loudness~ @kweighting 1 @truepeak 1",
667
+ "numinlets": 1,
668
+ "numoutlets": 2,
669
+ "outlettype": ["", ""],
670
+ "patching_rect": [820.0, 880.0, 260.0, 22.0]
671
+ }
672
+ },
673
+ {
674
+ "box": {
675
+ "id": "obj-fc-prepend-loudness",
676
+ "maxclass": "newobj",
677
+ "text": "prepend /loudness",
678
+ "numinlets": 1,
679
+ "numoutlets": 1,
680
+ "outlettype": [""],
681
+ "patching_rect": [820.0, 920.0, 108.0, 22.0]
682
+ }
683
+ },
684
+ {
685
+ "box": {
686
+ "id": "obj-fc-onsetfeature",
687
+ "maxclass": "newobj",
688
+ "text": "fluid.onsetfeature~",
689
+ "numinlets": 1,
690
+ "numoutlets": 2,
691
+ "outlettype": ["", ""],
692
+ "patching_rect": [50.0, 960.0, 120.0, 22.0]
693
+ }
694
+ },
695
+ {
696
+ "box": {
697
+ "id": "obj-fc-prepend-onset",
698
+ "maxclass": "newobj",
699
+ "text": "prepend /onset",
700
+ "numinlets": 1,
701
+ "numoutlets": 1,
702
+ "outlettype": [""],
703
+ "patching_rect": [50.0, 1000.0, 90.0, 22.0]
704
+ }
705
+ },
706
+ {
707
+ "box": {
708
+ "id": "obj-fc-noveltyfeature",
709
+ "maxclass": "newobj",
710
+ "text": "fluid.noveltyfeature~ @kernelsize 21",
711
+ "numinlets": 1,
712
+ "numoutlets": 2,
713
+ "outlettype": ["", ""],
714
+ "patching_rect": [320.0, 960.0, 220.0, 22.0]
715
+ }
716
+ },
717
+ {
718
+ "box": {
719
+ "id": "obj-fc-prepend-novelty",
720
+ "maxclass": "newobj",
721
+ "text": "prepend /novelty",
722
+ "numinlets": 1,
723
+ "numoutlets": 1,
724
+ "outlettype": [""],
725
+ "patching_rect": [320.0, 1000.0, 100.0, 22.0]
726
+ }
595
727
  }
596
728
  ],
597
729
  "lines": [
@@ -665,7 +797,31 @@
665
797
  {"patchline": {"source": ["obj-route-status", 0], "destination": ["obj-set-status", 0]}},
666
798
  {"patchline": {"source": ["obj-set-status", 0], "destination": ["obj-status", 0]}},
667
799
  {"patchline": {"source": ["obj-route-status", 1], "destination": ["obj-set-key", 0]}},
668
- {"patchline": {"source": ["obj-set-key", 0], "destination": ["obj-key-display", 0]}}
800
+ {"patchline": {"source": ["obj-set-key", 0], "destination": ["obj-key-display", 0]}},
801
+
802
+ {"patchline": {"source": ["obj-4", 0], "destination": ["obj-fc-spectralshape", 0]}},
803
+ {"patchline": {"source": ["obj-fc-spectralshape", 0], "destination": ["obj-fc-prepend-spectralshape", 0]}},
804
+ {"patchline": {"source": ["obj-fc-prepend-spectralshape", 0], "destination": ["obj-udpsend", 0]}},
805
+
806
+ {"patchline": {"source": ["obj-4", 0], "destination": ["obj-fc-melbands", 0]}},
807
+ {"patchline": {"source": ["obj-fc-melbands", 0], "destination": ["obj-fc-prepend-melbands", 0]}},
808
+ {"patchline": {"source": ["obj-fc-prepend-melbands", 0], "destination": ["obj-udpsend", 0]}},
809
+
810
+ {"patchline": {"source": ["obj-4", 0], "destination": ["obj-fc-chroma", 0]}},
811
+ {"patchline": {"source": ["obj-fc-chroma", 0], "destination": ["obj-fc-prepend-chroma", 0]}},
812
+ {"patchline": {"source": ["obj-fc-prepend-chroma", 0], "destination": ["obj-udpsend", 0]}},
813
+
814
+ {"patchline": {"source": ["obj-4", 0], "destination": ["obj-fc-loudness", 0]}},
815
+ {"patchline": {"source": ["obj-fc-loudness", 0], "destination": ["obj-fc-prepend-loudness", 0]}},
816
+ {"patchline": {"source": ["obj-fc-prepend-loudness", 0], "destination": ["obj-udpsend", 0]}},
817
+
818
+ {"patchline": {"source": ["obj-4", 0], "destination": ["obj-fc-onsetfeature", 0]}},
819
+ {"patchline": {"source": ["obj-fc-onsetfeature", 0], "destination": ["obj-fc-prepend-onset", 0]}},
820
+ {"patchline": {"source": ["obj-fc-prepend-onset", 0], "destination": ["obj-udpsend", 0]}},
821
+
822
+ {"patchline": {"source": ["obj-4", 0], "destination": ["obj-fc-noveltyfeature", 0]}},
823
+ {"patchline": {"source": ["obj-fc-noveltyfeature", 0], "destination": ["obj-fc-prepend-novelty", 0]}},
824
+ {"patchline": {"source": ["obj-fc-prepend-novelty", 0], "destination": ["obj-udpsend", 0]}}
669
825
  ],
670
826
  "dependency_cache": [
671
827
  {
@@ -83,7 +83,7 @@ function anything() {
83
83
  function dispatch(cmd, args) {
84
84
  switch(cmd) {
85
85
  case "ping":
86
- send_response({"ok": true, "version": "1.8.0"});
86
+ send_response({"ok": true, "version": "1.8.2"});
87
87
  break;
88
88
  case "get_params":
89
89
  cmd_get_params(args);
@@ -1,2 +1,2 @@
1
1
  """LivePilot MCP Server — bridges MCP protocol to Ableton Live."""
2
- __version__ = "1.8.0"
2
+ __version__ = "1.8.2"
@@ -189,16 +189,18 @@ class SpectralReceiver(asyncio.DatagramProtocol):
189
189
  elif address == "/chroma" and len(args) >= 12:
190
190
  self.cache.update("chroma", [round(float(a), 4) for a in args[:12]])
191
191
 
192
- elif address == "/onset" and len(args) >= 2:
192
+ elif address == "/onset" and len(args) >= 1:
193
+ strength = round(float(args[0]), 4)
193
194
  self.cache.update("onset", {
194
- "detected": float(args[0]) > 0.5,
195
- "strength": round(float(args[1]), 4),
195
+ "detected": strength > 0.5,
196
+ "strength": strength,
196
197
  })
197
198
 
198
- elif address == "/novelty" and len(args) >= 2:
199
+ elif address == "/novelty" and len(args) >= 1:
200
+ score = round(float(args[0]), 4)
199
201
  self.cache.update("novelty", {
200
- "score": round(float(args[0]), 4),
201
- "boundary": float(args[1]) > 0.5,
202
+ "score": score,
203
+ "boundary": score > 0.5,
202
204
  })
203
205
 
204
206
  elif address == "/loudness" and len(args) >= 2:
@@ -2,6 +2,9 @@
2
2
 
3
3
  from contextlib import asynccontextmanager
4
4
  import asyncio
5
+ import os
6
+ import signal
7
+ import subprocess
5
8
 
6
9
  from fastmcp import FastMCP, Context # noqa: F401
7
10
 
@@ -9,6 +12,28 @@ from .connection import AbletonConnection
9
12
  from .m4l_bridge import SpectralCache, SpectralReceiver, M4LBridge
10
13
 
11
14
 
15
+ def _kill_port_holder(port: int) -> None:
16
+ """Kill whichever process holds the given UDP port.
17
+
18
+ Used to reclaim port 9880 when a stale duplicate server instance
19
+ is hogging it (common when both project .mcp.json and plugin
20
+ .mcp.json launch the server).
21
+ """
22
+ try:
23
+ out = subprocess.check_output(
24
+ ["lsof", "-t", "-i", f"UDP:{port}"],
25
+ text=True,
26
+ timeout=3,
27
+ ).strip()
28
+ my_pid = os.getpid()
29
+ for pid_str in out.splitlines():
30
+ pid = int(pid_str)
31
+ if pid != my_pid:
32
+ os.kill(pid, signal.SIGTERM)
33
+ except (subprocess.CalledProcessError, FileNotFoundError, ValueError):
34
+ pass # lsof not found or no process — nothing to kill
35
+
36
+
12
37
  @asynccontextmanager
13
38
  async def lifespan(server):
14
39
  """Create and yield the shared AbletonConnection + M4L bridge."""
@@ -19,14 +44,35 @@ async def lifespan(server):
19
44
 
20
45
  # Start UDP listener for incoming M4L spectral data (port 9880)
21
46
  loop = asyncio.get_running_loop()
47
+ transport = None
22
48
  try:
23
49
  transport, _ = await loop.create_datagram_endpoint(
24
50
  lambda: receiver,
25
51
  local_addr=('127.0.0.1', 9880),
26
52
  )
27
53
  except OSError:
28
- # Port in useM4L bridge won't work but core tools still function
29
- transport = None
54
+ # Port 9880 already bound likely a duplicate server instance
55
+ # (project .mcp.json + plugin .mcp.json both launching).
56
+ # Kill the stale holder so this instance gets the port.
57
+ import sys
58
+ print(
59
+ "LivePilot: UDP port 9880 in use — reclaiming from stale instance",
60
+ file=sys.stderr,
61
+ )
62
+ _kill_port_holder(9880)
63
+ await asyncio.sleep(0.3)
64
+ try:
65
+ transport, _ = await loop.create_datagram_endpoint(
66
+ lambda: receiver,
67
+ local_addr=('127.0.0.1', 9880),
68
+ )
69
+ except OSError:
70
+ print(
71
+ "LivePilot: WARNING — could not bind UDP 9880, "
72
+ "analyzer tools will be unavailable",
73
+ file=sys.stderr,
74
+ )
75
+ transport = None
30
76
 
31
77
  try:
32
78
  yield {
@@ -82,8 +82,23 @@ def pitch_name(midi: int) -> str:
82
82
 
83
83
 
84
84
  def parse_key(key_str: str) -> dict:
85
- """Parse key string -> {tonic: 0-11, tonic_name: str, mode: str}."""
86
- parts = key_str.strip().split()
85
+ """Parse key string -> {tonic: 0-11, tonic_name: str, mode: str}.
86
+
87
+ Accepts: "D minor", "Dm", "C# major", "C#m", "Bb", "Bbm", "F#m".
88
+ """
89
+ s = key_str.strip()
90
+
91
+ # Shorthand: trailing 'm' after a note name means minor (e.g. "Am", "C#m", "Bbm")
92
+ # But not "Dm" vs "D" — check if removing 'm' leaves a valid tonic
93
+ if len(s) >= 2 and s[-1] == 'm' and ' ' not in s:
94
+ candidate = s[:-1]
95
+ norm = candidate[0].upper() + candidate[1:]
96
+ if norm in ENHARMONIC:
97
+ norm = ENHARMONIC[norm]
98
+ if norm in NOTE_NAMES:
99
+ return {"tonic": NOTE_NAMES.index(norm), "tonic_name": norm, "mode": "minor"}
100
+
101
+ parts = s.split()
87
102
  raw_tonic = parts[0]
88
103
  mode = parts[1].lower() if len(parts) > 1 else 'major'
89
104
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.8.0",
3
+ "version": "1.8.2",
4
4
  "mcpName": "io.github.dreamrec/livepilot",
5
5
  "description": "Agentic production system for Ableton Live 12 — 168 tools, 17 domains, device atlas, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
6
6
  "author": "Pilot Studio",
@@ -5,7 +5,7 @@ Entry point for the ControlSurface. Ableton calls create_instance(c_instance)
5
5
  when this script is selected in Preferences > Link, Tempo & MIDI.
6
6
  """
7
7
 
8
- __version__ = "1.8.0"
8
+ __version__ = "1.8.2"
9
9
 
10
10
  from _Framework.ControlSurface import ControlSurface
11
11
  from .server import LivePilotServer
@@ -34,7 +34,7 @@ class LivePilot(ControlSurface):
34
34
  ControlSurface.__init__(self, c_instance)
35
35
  self._server = LivePilotServer(self)
36
36
  self._server.start()
37
- self.log_message("LivePilot v1.8.0 initialized")
37
+ self.log_message("LivePilot v1.8.1 initialized")
38
38
  self.show_message("LivePilot: Listening on port 9878")
39
39
 
40
40
  def disconnect(self):
@@ -1,20 +0,0 @@
1
- {
2
- "name": "livepilot",
3
- "version": "1.8.0",
4
- "description": "Agentic production system for Ableton Live 12 — 168 tools, 17 domains, device atlas, spectral perception, technique memory, neo-Riemannian harmony, Euclidean rhythm, species counterpoint, MIDI I/O",
5
- "author": "Pilot Studio",
6
- "skills": [
7
- "skills/livepilot-core",
8
- "skills/livepilot-release"
9
- ],
10
- "commands": [
11
- "commands/session.md",
12
- "commands/beat.md",
13
- "commands/mix.md",
14
- "commands/sounddesign.md",
15
- "commands/memory.md"
16
- ],
17
- "agents": [
18
- "agents/livepilot-producer"
19
- ]
20
- }
File without changes
File without changes
File without changes
File without changes
File without changes