livepilot 1.1.1 → 1.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.2 — 2026-03-17
4
+
5
+ ### Fixes
6
+ - Added missing `clip_index` validation to 4 arrangement MCP tools
7
+ - Standardized `json` import (removed `_json` alias)
8
+ - Added index range to arrangement clip error messages for consistency
9
+ - Made installer version-agnostic (`/^Live \d+/` regex instead of hardcoded "Live 12")
10
+ - Standardized project description across all config files
11
+
3
12
  ## 1.1.1 — 2026-03-17
4
13
 
5
14
  ### Fixes
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  [![CI](https://github.com/dreamrec/LivePilot/actions/workflows/ci.yml/badge.svg)](https://github.com/dreamrec/LivePilot/actions/workflows/ci.yml)
12
12
  [![GitHub stars](https://img.shields.io/github/stars/dreamrec/LivePilot)](https://github.com/dreamrec/LivePilot/stargazers)
13
13
 
14
- **AI copilot for Ableton Live 12** — 91 MCP tools for real-time music production, sound design, and mixing.
14
+ **AI copilot for Ableton Live 12** — 91 MCP tools for music production, sound design, and mixing.
15
15
 
16
16
  Control your entire Ableton session through natural language. Create tracks, program MIDI, load instruments, tweak parameters, arrange songs, and mix — all without leaving the keyboard.
17
17
 
@@ -26,7 +26,7 @@ function findAbletonPaths() {
26
26
  try {
27
27
  const entries = fs.readdirSync(prefsDir);
28
28
  for (const entry of entries) {
29
- if (entry.startsWith("Live 12")) {
29
+ if (/^Live \d+/.test(entry)) {
30
30
  candidates.push({
31
31
  path: path.join(prefsDir, entry, "User Remote Scripts"),
32
32
  description: `Ableton Preferences (${entry})`,
@@ -53,7 +53,7 @@ function findAbletonPaths() {
53
53
  try {
54
54
  const entries = fs.readdirSync(abletonAppData);
55
55
  for (const entry of entries) {
56
- if (entry.startsWith("Live 12")) {
56
+ if (/^Live \d+/.test(entry)) {
57
57
  candidates.push({
58
58
  path: path.join(abletonAppData, entry, "Preferences", "User Remote Scripts"),
59
59
  description: `Ableton AppData (${entry})`,
@@ -3,7 +3,7 @@
3
3
  19 tools matching the Remote Script arrangement domain.
4
4
  """
5
5
 
6
- import json as _json
6
+ import json
7
7
 
8
8
  from fastmcp import Context
9
9
 
@@ -147,8 +147,9 @@ def add_arrangement_notes(
147
147
  start_time in notes is relative to the clip start, not the song timeline.
148
148
  """
149
149
  _validate_track_index(track_index)
150
+ _validate_clip_index(clip_index)
150
151
  if isinstance(notes, str):
151
- notes = _json.loads(notes)
152
+ notes = json.loads(notes)
152
153
  return _get_ableton(ctx).send_command("add_arrangement_notes", {
153
154
  "track_index": track_index,
154
155
  "clip_index": clip_index,
@@ -181,6 +182,7 @@ def set_arrangement_automation(
181
182
  For parameter_type="send": send_index required (0=A, 1=B, ...).
182
183
  """
183
184
  _validate_track_index(track_index)
185
+ _validate_clip_index(clip_index)
184
186
  if parameter_type not in ("device", "volume", "panning", "send"):
185
187
  raise ValueError("parameter_type must be 'device', 'volume', 'panning', or 'send'")
186
188
  if parameter_type == "device":
@@ -189,7 +191,7 @@ def set_arrangement_automation(
189
191
  if parameter_type == "send" and send_index is None:
190
192
  raise ValueError("send_index required for parameter_type='send'")
191
193
  if isinstance(points, str):
192
- points = _json.loads(points)
194
+ points = json.loads(points)
193
195
  if not points:
194
196
  raise ValueError("points list cannot be empty")
195
197
  params: dict = {
@@ -224,6 +226,7 @@ def transpose_arrangement_notes(
224
226
  time_span: length of note range in beats (defaults to full clip)
225
227
  """
226
228
  _validate_track_index(track_index)
229
+ _validate_clip_index(clip_index)
227
230
  if not -127 <= semitones <= 127:
228
231
  raise ValueError("semitones must be between -127 and 127")
229
232
  params: dict = {
@@ -248,6 +251,7 @@ def set_arrangement_clip_name(
248
251
  ) -> dict:
249
252
  """Rename an arrangement clip by its index in the track's arrangement_clips list."""
250
253
  _validate_track_index(track_index)
254
+ _validate_clip_index(clip_index)
251
255
  if not name.strip():
252
256
  raise ValueError("name cannot be empty")
253
257
  return _get_ableton(ctx).send_command("set_arrangement_clip_name", {
@@ -333,7 +337,7 @@ def remove_arrangement_notes_by_id(
333
337
  _validate_track_index(track_index)
334
338
  _validate_clip_index(clip_index)
335
339
  if isinstance(note_ids, str):
336
- note_ids = _json.loads(note_ids)
340
+ note_ids = json.loads(note_ids)
337
341
  if not note_ids:
338
342
  raise ValueError("note_ids list cannot be empty")
339
343
  return _get_ableton(ctx).send_command("remove_arrangement_notes_by_id", {
@@ -355,7 +359,7 @@ def modify_arrangement_notes(
355
359
  _validate_track_index(track_index)
356
360
  _validate_clip_index(clip_index)
357
361
  if isinstance(modifications, str):
358
- modifications = _json.loads(modifications)
362
+ modifications = json.loads(modifications)
359
363
  if not modifications:
360
364
  raise ValueError("modifications list cannot be empty")
361
365
  for mod in modifications:
@@ -388,7 +392,7 @@ def duplicate_arrangement_notes(
388
392
  _validate_track_index(track_index)
389
393
  _validate_clip_index(clip_index)
390
394
  if isinstance(note_ids, str):
391
- note_ids = _json.loads(note_ids)
395
+ note_ids = json.loads(note_ids)
392
396
  if not note_ids:
393
397
  raise ValueError("note_ids list cannot be empty")
394
398
  return _get_ableton(ctx).send_command("duplicate_arrangement_notes", {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "mcpName": "io.github.dreamrec/livepilot",
5
- "description": "AI copilot for Ableton Live 12 — 91 MCP tools for production, sound design, and mixing",
5
+ "description": "AI copilot for Ableton Live 12 — 91 MCP tools for music production, sound design, and mixing",
6
6
  "author": "Pilot Studio",
7
7
  "license": "MIT",
8
8
  "type": "commonjs",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "livepilot",
3
- "version": "1.1.0",
4
- "description": "AI copilot for Ableton Live 12 — 91 tools for production, sound design, and mixing",
3
+ "version": "1.1.2",
4
+ "description": "AI copilot for Ableton Live 12 — 91 MCP tools for music production, sound design, and mixing",
5
5
  "author": "Pilot Studio",
6
6
  "skills": [
7
7
  "skills/livepilot-core"
@@ -583,7 +583,10 @@ def set_arrangement_clip_name(song, params):
583
583
  track = get_track(song, track_index)
584
584
  arr_clips = list(track.arrangement_clips)
585
585
  if clip_index < 0 or clip_index >= len(arr_clips):
586
- raise IndexError("Arrangement clip index out of range")
586
+ raise IndexError(
587
+ "Arrangement clip index %d out of range (0..%d)"
588
+ % (clip_index, len(arr_clips) - 1)
589
+ )
587
590
  arr_clips[clip_index].name = name
588
591
  return {"track_index": track_index, "clip_index": clip_index, "name": name}
589
592