livepilot 1.16.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +344 -5
- package/README.md +16 -15
- package/m4l_device/LivePilot_Analyzer.amxd +0 -0
- package/m4l_device/livepilot_bridge.js +1 -1
- package/mcp_server/__init__.py +1 -1
- package/mcp_server/atlas/__init__.py +85 -0
- package/mcp_server/atlas/device_atlas.json +3183 -382
- package/mcp_server/atlas/device_techniques_index.json +1510 -0
- package/mcp_server/atlas/enrichments/__init__.py +1 -0
- package/mcp_server/atlas/enrichments/audio_effects/amp.yaml +112 -0
- package/mcp_server/atlas/enrichments/audio_effects/audio_effect_rack.yaml +77 -0
- package/mcp_server/atlas/enrichments/audio_effects/cabinet.yaml +81 -0
- package/mcp_server/atlas/enrichments/audio_effects/corpus.yaml +128 -0
- package/mcp_server/atlas/enrichments/audio_effects/envelope_follower.yaml +99 -0
- package/mcp_server/atlas/enrichments/audio_effects/external_audio_effect.yaml +64 -0
- package/mcp_server/atlas/enrichments/audio_effects/looper.yaml +85 -0
- package/mcp_server/atlas/enrichments/audio_effects/pitch_hack.yaml +61 -0
- package/mcp_server/atlas/enrichments/audio_effects/pitchloop89.yaml +111 -0
- package/mcp_server/atlas/enrichments/audio_effects/re_enveloper.yaml +51 -0
- package/mcp_server/atlas/enrichments/audio_effects/resonators.yaml +121 -0
- package/mcp_server/atlas/enrichments/audio_effects/snipper.yaml +53 -0
- package/mcp_server/atlas/enrichments/audio_effects/spectral_blur.yaml +64 -0
- package/mcp_server/atlas/enrichments/audio_effects/spectrum.yaml +61 -0
- package/mcp_server/atlas/enrichments/audio_effects/tuner.yaml +43 -0
- package/mcp_server/atlas/enrichments/audio_effects/utility.yaml +118 -0
- package/mcp_server/atlas/enrichments/audio_effects/vocoder.yaml +94 -0
- package/mcp_server/atlas/enrichments/instruments/analog.yaml +11 -0
- package/mcp_server/atlas/enrichments/instruments/bass.yaml +11 -0
- package/mcp_server/atlas/enrichments/instruments/bell_tower.yaml +75 -0
- package/mcp_server/atlas/enrichments/instruments/collision.yaml +11 -0
- package/mcp_server/atlas/enrichments/instruments/drift.yaml +11 -0
- package/mcp_server/atlas/enrichments/instruments/drum_rack.yaml +142 -0
- package/mcp_server/atlas/enrichments/instruments/electric.yaml +11 -0
- package/mcp_server/atlas/enrichments/instruments/emit.yaml +11 -0
- package/mcp_server/atlas/enrichments/instruments/granulator_iii.yaml +124 -0
- package/mcp_server/atlas/enrichments/instruments/harmonic_drone_generator.yaml +83 -0
- package/mcp_server/atlas/enrichments/instruments/impulse.yaml +47 -0
- package/mcp_server/atlas/enrichments/instruments/meld.yaml +11 -0
- package/mcp_server/atlas/enrichments/instruments/operator.yaml +11 -0
- package/mcp_server/atlas/enrichments/instruments/poli.yaml +11 -0
- package/mcp_server/atlas/enrichments/instruments/sampler.yaml +12 -0
- package/mcp_server/atlas/enrichments/instruments/simpler.yaml +15 -0
- package/mcp_server/atlas/enrichments/instruments/sting_iftah.yaml +44 -0
- package/mcp_server/atlas/enrichments/instruments/tension.yaml +11 -0
- package/mcp_server/atlas/enrichments/instruments/vector_fm.yaml +11 -0
- package/mcp_server/atlas/enrichments/instruments/vector_grain.yaml +11 -0
- package/mcp_server/atlas/enrichments/instruments/wavetable.yaml +11 -0
- package/mcp_server/atlas/enrichments/midi_effects/expressive_chords.yaml +38 -0
- package/mcp_server/atlas/enrichments/midi_effects/filler.yaml +49 -0
- package/mcp_server/atlas/enrichments/midi_effects/microtuner.yaml +83 -0
- package/mcp_server/atlas/enrichments/midi_effects/patterns_iftah.yaml +38 -0
- package/mcp_server/atlas/enrichments/midi_effects/phase_pattern.yaml +51 -0
- package/mcp_server/atlas/enrichments/midi_effects/polyrhythm.yaml +46 -0
- package/mcp_server/atlas/enrichments/midi_effects/retrigger.yaml +40 -0
- package/mcp_server/atlas/enrichments/midi_effects/slice_shuffler.yaml +39 -0
- package/mcp_server/atlas/enrichments/midi_effects/sq_sequencer.yaml +39 -0
- package/mcp_server/atlas/enrichments/midi_effects/stages.yaml +38 -0
- package/mcp_server/atlas/enrichments/utility/arrangement_looper.yaml +31 -0
- package/mcp_server/atlas/enrichments/utility/cv_clock_in.yaml +25 -0
- package/mcp_server/atlas/enrichments/utility/cv_clock_out.yaml +25 -0
- package/mcp_server/atlas/enrichments/utility/cv_envelope_follower.yaml +38 -0
- package/mcp_server/atlas/enrichments/utility/cv_in.yaml +26 -0
- package/mcp_server/atlas/enrichments/utility/cv_instrument.yaml +34 -0
- package/mcp_server/atlas/enrichments/utility/cv_lfo.yaml +38 -0
- package/mcp_server/atlas/enrichments/utility/cv_shaper.yaml +35 -0
- package/mcp_server/atlas/enrichments/utility/cv_triggers.yaml +26 -0
- package/mcp_server/atlas/enrichments/utility/cv_utility.yaml +37 -0
- package/mcp_server/atlas/enrichments/utility/performer.yaml +51 -0
- package/mcp_server/atlas/enrichments/utility/prearranger.yaml +36 -0
- package/mcp_server/atlas/enrichments/utility/rotating_rhythm_generator.yaml +35 -0
- package/mcp_server/atlas/enrichments/utility/surround_panner.yaml +40 -0
- package/mcp_server/atlas/enrichments/utility/variations.yaml +40 -0
- package/mcp_server/atlas/enrichments/utility/vector_map.yaml +57 -0
- package/mcp_server/atlas/tools.py +291 -0
- package/mcp_server/m4l_bridge.py +19 -2
- package/mcp_server/sample_engine/tools.py +190 -72
- package/mcp_server/server.py +18 -6
- package/mcp_server/splice_client/client.py +90 -18
- package/mcp_server/splice_client/http_bridge.py +414 -138
- package/mcp_server/splice_client/models.py +12 -0
- package/mcp_server/tools/analyzer.py +150 -1
- package/mcp_server/tools/automation.py +168 -0
- package/package.json +2 -2
- package/remote_script/LivePilot/__init__.py +1 -1
- package/remote_script/LivePilot/arrangement.py +216 -1
- package/server.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,349 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 1.
|
|
3
|
+
## 1.17.0 — 2026-04-22 handoff close-out (April 22 2026, late)
|
|
4
|
+
|
|
5
|
+
Closes every item in the 2026-04-22 handoff document: Splice's
|
|
6
|
+
Describe-a-Sound + Variations go LIVE via captured GraphQL
|
|
7
|
+
endpoints, the M4L Analyzer gains a 9th spectrum band (sub_low
|
|
8
|
+
20-60 Hz), track-level arrangement automation lands via a
|
|
9
|
+
two-phase session-record workaround, the atlas gains a
|
|
10
|
+
by_pack index + `atlas_pack_info` MCP tool, the device atlas
|
|
11
|
+
adds 13 enrichments including a Drum Rack container entry, and
|
|
12
|
+
Tier C of the pack-knowledge reference expands from 7 clusters
|
|
13
|
+
to 14 individually-documented packs.
|
|
14
|
+
|
|
15
|
+
**Tool count**: 422 → 426 (+5 new, −1 retired):
|
|
16
|
+
- `+` `atlas_pack_info` — inspect a pack's device list + enrichment coverage
|
|
17
|
+
- `+` `atlas_describe_chain` — free-text describe-a-chain, mirror of
|
|
18
|
+
`splice_describe_sound` for the device library. "A granular pad like
|
|
19
|
+
Tim Hecker" → device chain proposal via artist + genre vocabulary
|
|
20
|
+
matching
|
|
21
|
+
- `+` `atlas_techniques_for_device` — reverse-lookup: what techniques
|
|
22
|
+
reference this device? Reads an auto-generated index of 146 technique
|
|
23
|
+
cross-references across 58 devices
|
|
24
|
+
- `+` `set_arrangement_automation_via_session_record` — T5 workaround,
|
|
25
|
+
two-phase protocol with tempo-scaled sleep
|
|
26
|
+
- `+` `splice_http_diagnose` — per-endpoint verified-status reporter
|
|
27
|
+
- `−` `splice_search_with_sound` — retired (user handles
|
|
28
|
+
audio-reference search directly in Splice's UI; capture recipe
|
|
29
|
+
preserved at `docs/2026-04-22-splice-https-capture-recipe.md`)
|
|
30
|
+
|
|
31
|
+
**Domain count**: unchanged at 52.
|
|
32
|
+
**Enrichment YAMLs**: 107 → 120 (+13).
|
|
33
|
+
**Atlas pack coverage**: 0 → 641 devices indexed (614 Core Library
|
|
34
|
+
via auto-heuristic + 27 explicit-pack YAML declarations).
|
|
35
|
+
**Signature-technique coverage**: 32 → 47 enrichment files have the
|
|
36
|
+
`signature_techniques` field (+15 files × 3 techniques each = +45
|
|
37
|
+
technique entries from native-synth backfill). Coverage rose from
|
|
38
|
+
27% → 39% of enrichments.
|
|
39
|
+
**Tests**: 2644 → 2696+ (+52 new regression guards).
|
|
40
|
+
|
|
41
|
+
### Live-verified end-to-end
|
|
42
|
+
|
|
43
|
+
Against a running Ableton 12.4 + Splice desktop 5.4.9 session on
|
|
44
|
+
2026-04-22:
|
|
45
|
+
|
|
46
|
+
- `get_master_spectrum()` → 9 keys with `sub_low` first, real-audio
|
|
47
|
+
energy distribution (`sub_low=0.0003 sub=0.0008 low=0.0064 …`)
|
|
48
|
+
- `atlas_pack_info("Drone Lab")` → Harmonic Drone Generator in
|
|
49
|
+
both Sounds and M4L variants with URIs
|
|
50
|
+
- `splice_describe_sound("warm dub techno chord stab", limit=5)` →
|
|
51
|
+
5 real samples out of 4100 total hits (Dub Techno 2 pluck, NEONIC
|
|
52
|
+
atmos, Visions chord, Organic Elements 2, Underground Techno),
|
|
53
|
+
with Splice's ML rephrasing `rephrased_query_string: "dub techno,
|
|
54
|
+
chords, stabs, warm"`
|
|
55
|
+
|
|
56
|
+
### Splice HTTPS bridge — endpoint capture
|
|
57
|
+
|
|
58
|
+
Captured via mitmproxy against Splice desktop v5.4.9 (unpinned TLS,
|
|
59
|
+
intercepts cleanly once CA is trusted). Two GraphQL operations
|
|
60
|
+
wired:
|
|
61
|
+
|
|
62
|
+
- **`SamplesSearch`** at `surfaces-graphql.splice.com/graphql` —
|
|
63
|
+
describe + keyword search in one operation, flagged via
|
|
64
|
+
`semantic=1` + `rephrase=true`. 5938-char query embedded as
|
|
65
|
+
`mcp_server/splice_client/graphql_queries/samples_search.graphql`
|
|
66
|
+
- **`AssetSimilarSoundsQuery`** at the same endpoint — Splice's
|
|
67
|
+
"Variations" / "Similar Sounds" feature. Input is a sample
|
|
68
|
+
`uuid` + `isLegacy` bool; returns up to 10 recommendations.
|
|
69
|
+
886-char query embedded as
|
|
70
|
+
`mcp_server/splice_client/graphql_queries/asset_similar_sounds.graphql`
|
|
71
|
+
- Auth: `Authorization: Bearer <JWT>` via local gRPC `GetSession`
|
|
72
|
+
RPC — no stored credentials, token rotates with the running
|
|
73
|
+
Splice desktop app
|
|
74
|
+
- User-Agent: LivePilot default (override via env var if
|
|
75
|
+
Cloudflare blocks — mimic pattern: `Splice Baelish/darwin/
|
|
76
|
+
arm64/arm64 5.4.9/<hash>/stable`)
|
|
77
|
+
- Response normalizer `_flatten_sample_item()` is shared between
|
|
78
|
+
both operations so samples from describe + variations flatten
|
|
79
|
+
to identical dicts — 14 new unit tests lock this invariant
|
|
80
|
+
- Per-endpoint `describe_verified` / `variation_verified` flags
|
|
81
|
+
replace the coarse `is_user_configured` gate
|
|
82
|
+
|
|
83
|
+
### Changed
|
|
84
|
+
|
|
85
|
+
- `splice_generate_variation` signature changed: `(file_hash,
|
|
86
|
+
target_key, target_bpm, count)` → `(uuid, is_legacy)`. The
|
|
87
|
+
operation is a recommender lookup, not AI audio synthesis, so
|
|
88
|
+
target-key / target-bpm / count aren't API-supported. The
|
|
89
|
+
underlying GraphQL returns up to 10 similar samples with the
|
|
90
|
+
same flat shape as `splice_describe_sound` results.
|
|
91
|
+
- `splice_describe_sound` — status flipped from "scaffolded" to
|
|
92
|
+
"LIVE". Adds new `rephrase: bool = True` parameter surfacing
|
|
93
|
+
Splice's `rephrased_query_string` in the response.
|
|
94
|
+
|
|
95
|
+
### Added
|
|
96
|
+
|
|
97
|
+
**Sub_low spectrum band** (T3 from handoff):
|
|
98
|
+
- M4L Analyzer `fffb~` filter bank: 8 bands → 9 bands. New band
|
|
99
|
+
center frequencies: `35 85 175 350 700 1400 2800 5600 12000`
|
|
100
|
+
(Hz), with `sub_low` (20-60 Hz) prepended to separate kick
|
|
101
|
+
fundamental from DC rumble
|
|
102
|
+
- `mcp_server/m4l_bridge.py`: `BAND_NAMES_8` + `BAND_NAMES_9`
|
|
103
|
+
auto-selected based on payload length — older frozen .amxd
|
|
104
|
+
builds (pre-v1.17) still work without re-freeze
|
|
105
|
+
- `LivePilot_Analyzer.amxd` re-frozen from modified
|
|
106
|
+
`LivePilot_Analyzer.maxpat` source, 6751650 bytes, synced to
|
|
107
|
+
repo / Max 9 Library cache / Ableton User Library
|
|
108
|
+
- `docs/2026-04-22-sub-low-band-max-workflow.md` — runbook for
|
|
109
|
+
future band-count changes (Max editor walkthrough)
|
|
110
|
+
|
|
111
|
+
**Track-level arrangement automation workaround** (T5 from handoff):
|
|
112
|
+
- New MCP tool `set_arrangement_automation_via_session_record` —
|
|
113
|
+
async two-phase protocol that creates a session clip with the
|
|
114
|
+
automation, arms the track, records into arrangement at a
|
|
115
|
+
target beat, cleans up
|
|
116
|
+
- Two new remote-script handlers:
|
|
117
|
+
`arrangement_automation_via_session_record_start` returns the
|
|
118
|
+
live `song.tempo` so the MCP layer can compute the sleep
|
|
119
|
+
duration, `_complete` stops record + locates the new
|
|
120
|
+
arrangement clip
|
|
121
|
+
- MCP layer sleeps `duration_beats × 60/tempo + 0.5s` with a
|
|
122
|
+
600s ceiling and graceful exception handling — incomplete
|
|
123
|
+
sleep still tries to complete so tracks don't stay armed
|
|
124
|
+
- 17 new contract tests (two-phase ordering, tempo scaling,
|
|
125
|
+
ceiling, default-tempo fallback, start-failure short-circuit)
|
|
126
|
+
|
|
127
|
+
**Atlas `by_pack` index + `atlas_pack_info` tool** (T4):
|
|
128
|
+
- New `_by_pack` index on `AtlasManager`, populated from
|
|
129
|
+
enrichment YAML `pack:` fields plus a Core Library
|
|
130
|
+
auto-heuristic for native instruments/effects without an
|
|
131
|
+
explicit pack declaration
|
|
132
|
+
- New `pack_info(name)` and `list_packs()` methods
|
|
133
|
+
- New MCP tool `atlas_pack_info(pack_name)` — `""` returns the
|
|
134
|
+
full pack list with device counts, otherwise returns
|
|
135
|
+
`{pack, device_count, enriched_count, devices[...]}` for one
|
|
136
|
+
pack. Case-insensitive name lookup
|
|
137
|
+
- `genre_affinity` (enriched field) now feeds the `_by_genre`
|
|
138
|
+
index alongside the raw `genres` field, so
|
|
139
|
+
`microhouse`/`deep_minimal`/`dub_techno` tags added to YAMLs
|
|
140
|
+
post-v1.11 finally surface in genre lookups
|
|
141
|
+
|
|
142
|
+
**Drum Rack atlas enrichment + 12 audio-effects YAMLs** (T1/T2):
|
|
143
|
+
- New `instruments/drum_rack.yaml` — treats Drum Rack as a
|
|
144
|
+
meta-type container with per-pad key_parameters, 3 starter
|
|
145
|
+
recipes, 8 gotchas (including MIDI pitch conventions, chain
|
|
146
|
+
volume vs pad volume)
|
|
147
|
+
- 12 new audio-effects enrichments: `utility`, `corpus`,
|
|
148
|
+
`vocoder`, `tuner`, `spectrum`, `amp`, `cabinet`, `resonators`,
|
|
149
|
+
`looper`, `envelope_follower`, `audio_effect_rack`,
|
|
150
|
+
`external_audio_effect`. Takes `audio_effects/` count from 40
|
|
151
|
+
→ 52
|
|
152
|
+
- Simpler + Sampler enrichments gain 3 new gotchas (Snap=OFF
|
|
153
|
+
silence bug, -12 dB default, slice base pitch = 36+N)
|
|
154
|
+
- `pack` field added to `_ENRICHMENT_FIELDS` allowlist — was
|
|
155
|
+
silently dropped from atlas JSON before; now drives the
|
|
156
|
+
`by_pack` index
|
|
157
|
+
|
|
158
|
+
**Pack-knowledge reference** (T7):
|
|
159
|
+
- `livepilot/skills/livepilot-core/references/pack-knowledge.md`
|
|
160
|
+
Tier C expanded from 7 merged clusters (~62 lines) to 14
|
|
161
|
+
individually-documented packs (Build and Drop, Drive and Glow,
|
|
162
|
+
Punch and Tilt, Skitter and Step, Trap Drums, Beat Tools, Drum
|
|
163
|
+
Essentials, SONiVOX Orchestral Brass/Strings/Woodwinds, Grand
|
|
164
|
+
Piano, Synth Essentials, Session Drums Club + Studio, Core
|
|
165
|
+
Library) with consistent Essence/Scores/Top/Use/Avoid structure
|
|
166
|
+
|
|
167
|
+
**Deepened shallow enrichments** (T8):
|
|
168
|
+
- Added `pairs_well_with` + expanded `gotchas` +
|
|
169
|
+
`signature_techniques` for: `snipper`, `bell_tower`,
|
|
170
|
+
`performer`, `vector_map`, `filler`
|
|
171
|
+
|
|
172
|
+
### Concept surface — knowledge the LLM can reason over
|
|
173
|
+
|
|
174
|
+
Late-v1.17 audit found LivePilot's concept-vs-recipe ratio was already
|
|
175
|
+
healthy (sample-philosophy.md, sound-design-deep.md, per-device
|
|
176
|
+
`signature_techniques`, aesthetic-tagged `character_tags`), but two gaps
|
|
177
|
+
were worth closing before the release: native-synth enrichments heavy
|
|
178
|
+
on `starter_recipes` but thin on `signature_techniques`, and no
|
|
179
|
+
structured artist/genre → device-vocabulary bridge.
|
|
180
|
+
|
|
181
|
+
Added:
|
|
182
|
+
|
|
183
|
+
- **`livepilot/skills/livepilot-core/references/artist-vocabularies.md`** —
|
|
184
|
+
~25 producers (Villalobos, Hawtin/Plastikman, Basic Channel, Gas,
|
|
185
|
+
Basinski, Stars of the Lid, Hecker, Aphex, Autechre, OPN, Arca,
|
|
186
|
+
Dilla, Premier, Madlib, Burial, Mala, Jeff Mills, Moodymann, Daft
|
|
187
|
+
Punk, Rashad, Photek, Com Truise, Boards of Canada, etc.) with
|
|
188
|
+
four-field structured entries (sonic fingerprint / reach for /
|
|
189
|
+
avoid / key techniques). Each entry cross-references
|
|
190
|
+
`signature_techniques` in per-device YAML or technique names in
|
|
191
|
+
`sample-techniques.md` / `sound-design-deep.md` — no duplication,
|
|
192
|
+
just a translation layer from producer names to LivePilot devices
|
|
193
|
+
- **`livepilot/skills/livepilot-core/references/genre-vocabularies.md`** —
|
|
194
|
+
15 genres (microhouse, dub_techno, deep_minimal, minimal_techno,
|
|
195
|
+
ambient, idm, modern_classical, hip_hop, trap, dubstep, house,
|
|
196
|
+
dnb, garage, experimental, synthwave) with tempo / kick / bass /
|
|
197
|
+
percussion / harmonic / texture / reach-for / avoid structure.
|
|
198
|
+
Read-before-tool-selection reference for genre-driven workflows
|
|
199
|
+
- **Native-synth `signature_techniques` backfill** — 15 YAMLs that
|
|
200
|
+
had starter_recipes but no aesthetic-level guidance now have 3
|
|
201
|
+
techniques each, tagged to known producers (Hawtin subtractive pad,
|
|
202
|
+
303 acid bass, Reese bass for DnB, J Dilla micro-timed kit, Villalobos
|
|
203
|
+
sub-bass layer, Basinski tape degradation, Tim Hecker grain cloud,
|
|
204
|
+
etc.). Files updated: analog, operator, wavetable, drift, collision,
|
|
205
|
+
tension, electric, simpler, sampler, bass, poli, emit, meld,
|
|
206
|
+
vector_fm, vector_grain
|
|
207
|
+
- **`mcp_server/atlas/device_techniques_index.json`** — auto-generated
|
|
208
|
+
reverse-index of 146 device→technique cross-references across 58
|
|
209
|
+
devices, mined from per-device `signature_techniques` +
|
|
210
|
+
`sample-techniques.md` + `sound-design-deep.md`
|
|
211
|
+
|
|
212
|
+
### Docs
|
|
213
|
+
|
|
214
|
+
- `docs/2026-04-22-release-verification.md` — end-to-end
|
|
215
|
+
verification report with live-verified evidence per task
|
|
216
|
+
- `docs/2026-04-22-splice-https-capture-recipe.md` — mitmproxy
|
|
217
|
+
runbook for capturing additional Splice GraphQL operations
|
|
218
|
+
- `docs/2026-04-22-live-api-probe-arrangement-automation.md` —
|
|
219
|
+
design rationale for the T5 two-phase split
|
|
220
|
+
- `docs/2026-04-22-sub-low-band-max-workflow.md` — Max
|
|
221
|
+
editor runbook for future filter-bank edits
|
|
222
|
+
- `docs/manual/index.md` — rewrote domain map from source-of-truth
|
|
223
|
+
per-domain counts (was drifting 30+ entries). Added 7 missing
|
|
224
|
+
domains (scales, grooves, take_lanes, follow_actions, miditool,
|
|
225
|
+
diagnostics, synthesis_brain)
|
|
226
|
+
- `docs/manual/tool-reference.md` — new sections for Device Atlas,
|
|
227
|
+
Sample Engine & Splice, Diagnostics; added v1.17 tools
|
|
228
|
+
(`atlas_pack_info`, `set_arrangement_automation_via_session_record`,
|
|
229
|
+
`splice_http_diagnose`, `splice_describe_sound` LIVE,
|
|
230
|
+
`splice_generate_variation` LIVE); added `add_drum_rack_pad` +
|
|
231
|
+
`verify_device_alive` + `verify_all_devices_health` that were
|
|
232
|
+
missing since v1.16
|
|
233
|
+
- `docs/manual/tool-catalog.md` — replaced hand-curated drifting
|
|
234
|
+
version with single auto-generated file (run
|
|
235
|
+
`python3 scripts/generate_tool_catalog.py > docs/manual/tool-catalog.md`).
|
|
236
|
+
Retired `docs/manual/tool-catalog-generated.md` (duplicate
|
|
237
|
+
with -generated suffix)
|
|
238
|
+
- **Deleted**: `docs/TOOL_REFERENCE.md` (v1.7-era, 317 tools /
|
|
239
|
+
43 domains — misleadingly stale)
|
|
240
|
+
- **Archived**: `docs/manual/release-smoke-board.md` → `docs/archive/release-smoke-board-v1.10-era.md`
|
|
241
|
+
|
|
242
|
+
### Internal
|
|
243
|
+
|
|
244
|
+
- `mcp_server/splice_client/graphql_queries/` — new directory,
|
|
245
|
+
holds captured `.graphql` query strings, loaded lazily via
|
|
246
|
+
`_load_graphql_query()` with caching
|
|
247
|
+
- `docs/research/splice-api-capture/` — gitignored local archive
|
|
248
|
+
of 4 operation captures (request + response pairs):
|
|
249
|
+
`SamplesSearch`, `SoundsSearchAutocomplete`,
|
|
250
|
+
`RefreshSamplePreview`, `AssetSimilarSoundsQuery`
|
|
251
|
+
- 5 additional GraphQL operations captured but not wired —
|
|
252
|
+
candidates for future tools: `DesktopPackSearch`,
|
|
253
|
+
`SampleAssetSidebarQuery`, `BrowseCarousels`,
|
|
254
|
+
`CreditsStoreQuery`, `UserService`
|
|
255
|
+
|
|
256
|
+
### Known non-blockers
|
|
257
|
+
|
|
258
|
+
- Splice Search-with-Sound (audio-file reference search) is
|
|
259
|
+
retired — feature not exposed in Splice desktop v5.4.9's UI,
|
|
260
|
+
user handles it manually. Recipe preserved if a future Splice
|
|
261
|
+
build re-exposes it.
|
|
262
|
+
- T5 live end-to-end verification in a real Ableton session
|
|
263
|
+
deferred — contract tests (17) lock the protocol; first real
|
|
264
|
+
invocation will confirm Live LOM assumptions (classic
|
|
265
|
+
v1.16.0 → v1.16.1 pattern — any surprises fix in one edit).
|
|
266
|
+
- `splice_http_diagnose` token-availability probe reported a
|
|
267
|
+
false negative pre-1.17 (walked the wrong context path); fixed
|
|
268
|
+
to use `ctx.lifespan_context["splice_client"]` + actually call
|
|
269
|
+
`fetch_session_token`.
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
## 1.16.1 — Post-publish live-verification bug sweep (April 22 2026)
|
|
273
|
+
|
|
274
|
+
Three rounds of live verification after 1.16.0 shipped caught five
|
|
275
|
+
runtime bugs that unit tests missed. All unit-test-clean paths that
|
|
276
|
+
failed on first live invocation against a running Splice desktop +
|
|
277
|
+
Ableton 12.4 are now both fixed and guarded by source-grep regression
|
|
278
|
+
tests. Plus one new tool and two observations addressed.
|
|
279
|
+
|
|
280
|
+
**Tool count**: 421 → 422 (+1: `verify_all_devices_health` —
|
|
281
|
+
session-wide silent-track detector).
|
|
282
|
+
**Domain count**: unchanged at 52.
|
|
283
|
+
**Tests**: 2627 → 2644 (+17 new regression guards). No regressions.
|
|
284
|
+
|
|
285
|
+
### Live-verified bugs fixed
|
|
286
|
+
|
|
287
|
+
- `add_drum_rack_pad` crashed with `ImportError` on first invocation
|
|
288
|
+
— inline `from .._analyzer_engine.sample import ...` resolved to
|
|
289
|
+
`mcp_server._analyzer_engine` (nonexistent; the real package is
|
|
290
|
+
`mcp_server.tools._analyzer_engine`). The helper is already imported
|
|
291
|
+
at module scope on line 29; inline form removed.
|
|
292
|
+
- `splice_preview_sample` returned "No preview URL available" for
|
|
293
|
+
un-downloaded catalog samples. `SampleInfo` RPC only carries
|
|
294
|
+
`PreviewURL` for downloaded/purchased items. Now falls back to
|
|
295
|
+
`SearchSamples(FileHash=...)` which always has catalog metadata.
|
|
296
|
+
- `splice_pack_info` crashed with
|
|
297
|
+
`AttributeError: 'AppStub' object has no attribute 'SamplePackInfo'`.
|
|
298
|
+
The `SamplePackInfoRequest/Response` messages exist in the proto
|
|
299
|
+
descriptor but no RPC on the `App` service binds them. Rewrote to
|
|
300
|
+
paginate `ListSamplePacks` + client-side UUID match. Limitation
|
|
301
|
+
documented: only finds packs the user has engaged with.
|
|
302
|
+
- `splice_pack_info` on an OWNED pack with an extended-format UUID
|
|
303
|
+
(43 chars, e.g. `...887a0dd7f26bf5a3951`) failed because the first
|
|
304
|
+
fix aggressively truncated to `[:36]`, discarding legitimate bytes.
|
|
305
|
+
Correct behavior: Splice uses both UUID formats; build a `targets`
|
|
306
|
+
set of the submitted form AND its canonical truncation, match each
|
|
307
|
+
server-returned UUID in both forms.
|
|
308
|
+
- Startup warning `STARTUP SELF-TEST WARNING — returned 385 tools,
|
|
309
|
+
expects 422`. `_get_all_tools()` took the first non-empty probe,
|
|
310
|
+
which could be a stale `_tool_manager._tools` view lagging behind
|
|
311
|
+
the authoritative `_local_provider._components`. Now takes the
|
|
312
|
+
largest view across all probes.
|
|
313
|
+
- Startup `RuntimeWarning: coroutine 'FastMCP.list_tools' was never
|
|
314
|
+
awaited` on every server import. The `list_tools` probe wrapped an
|
|
315
|
+
async coroutine in `list()` without awaiting. Removed with an
|
|
316
|
+
explanatory comment.
|
|
317
|
+
|
|
318
|
+
### Added
|
|
319
|
+
|
|
320
|
+
- `verify_all_devices_health(test_midi_note=60, skip_audio_tracks=True,
|
|
321
|
+
skip_empty_tracks=True, threshold=0.005)` — session-wide silent-
|
|
322
|
+
track detector. Fires a test note on every eligible track and reports
|
|
323
|
+
alive / dead / skipped with per-track peak-level evidence.
|
|
324
|
+
- `~/.livepilot/splice.json` config key `plan_kind_override` — pin
|
|
325
|
+
the Splice plan_kind when the gRPC classifier lands on a safe
|
|
326
|
+
default (e.g. `sounds_plan_id=6` with empty `features` map is
|
|
327
|
+
classified as SOUNDS_PLUS but may actually be any of several tiers).
|
|
328
|
+
Values: `ableton_live`, `sounds_plus`, `creator`, `creator_plus`,
|
|
329
|
+
`free`. `get_splice_credits` response now includes
|
|
330
|
+
`plan_kind_override: <value|null>`.
|
|
331
|
+
|
|
332
|
+
### Internal
|
|
333
|
+
|
|
334
|
+
- M4L bridge source (`livepilot_bridge.js`) ping version drift
|
|
335
|
+
`1.14.1 → 1.16.1` (source had been stale; binary had already been
|
|
336
|
+
patched in a prior release).
|
|
337
|
+
- `SpliceGRPCClient.get_pack_info` return signature changed from
|
|
338
|
+
`Optional[SplicePack]` to `tuple[Optional[SplicePack], Optional[str]]`.
|
|
339
|
+
Callers now receive a structured error message when the lookup
|
|
340
|
+
fails, instead of `None` swallowing the cause.
|
|
341
|
+
|
|
342
|
+
## 1.16.0 — Minimal-techno session bug batch + Splice plan model (April 22 2026)
|
|
4
343
|
|
|
5
344
|
Hardens the 1.15.0 beta into a full release. Resolves 18 of the 19 bugs
|
|
6
345
|
catalogued in `docs/2026-04-22-bugs-discovered.md` during an end-to-end
|
|
7
|
-
|
|
346
|
+
minimal-techno production session, ships a plan-aware Splice download
|
|
8
347
|
model, adds drum-rack pad-by-pad construction, and lands clip-length +
|
|
9
348
|
note-range invariants so programmatic workflows stop corrupting
|
|
10
349
|
arrangement timing.
|
|
@@ -525,7 +864,7 @@ every bug now has a named regression guard in the test suite.
|
|
|
525
864
|
## 1.12.1 — Silent-failure fixes + slice classifier (April 18 2026)
|
|
526
865
|
|
|
527
866
|
Reconciles the "separate git stash" called out under v1.12.0's Known
|
|
528
|
-
limitations — the 2026-04-18
|
|
867
|
+
limitations — the 2026-04-18 minimal-groove session surfaced four
|
|
529
868
|
silent-failure bugs and the need for a drum-slice spectral classifier.
|
|
530
869
|
|
|
531
870
|
**+1 tool (397 → 398): `classify_simpler_slices`.** +43 regression
|
|
@@ -561,7 +900,7 @@ guards (pure-Python, run without a live Ableton).
|
|
|
561
900
|
FFT-based spectral analysis on a Simpler's slice boundaries, returns
|
|
562
901
|
each slice labeled as KICK / SNARE / HAT / ghost plus feature
|
|
563
902
|
breakdown (peak, rms, band %). Validated thresholds from the
|
|
564
|
-
2026-04-18
|
|
903
|
+
2026-04-18 minimal-groove session on "Break Ghosts 90 bpm":
|
|
565
904
|
- KICK: sub+low ≥ 45%, high < 40%
|
|
566
905
|
- HAT: high ≥ 70% AND mid < 25%
|
|
567
906
|
- SNARE: mid ≥ 25% AND high ≥ 40% AND peak ≥ 0.6
|
|
@@ -578,7 +917,7 @@ guards (pure-Python, run without a live Ableton).
|
|
|
578
917
|
|
|
579
918
|
### Documented bug entries
|
|
580
919
|
|
|
581
|
-
- `BUGS.md` gains a new **"F. 2026-04-18
|
|
920
|
+
- `BUGS.md` gains a new **"F. 2026-04-18 minimal-groove creative
|
|
582
921
|
session"** section: F1-F4 fixed here, F5-F7 scoped to v1.13+, F8
|
|
583
922
|
wontfix (workaround documented).
|
|
584
923
|
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
19
19
|
An agentic production system for Ableton Live 12.<br>
|
|
20
|
-
|
|
20
|
+
426 tools. 52 domains. Device atlas. Plan-aware Splice integration. Auto-composition. Spectral perception. Technique memory. Drum-rack pad builder. Live dead-device detection.
|
|
21
21
|
</p>
|
|
22
22
|
|
|
23
23
|
<br>
|
|
@@ -33,16 +33,17 @@
|
|
|
33
33
|
|
|
34
34
|
## What LivePilot Does
|
|
35
35
|
|
|
36
|
-
Most MCP servers are tool collections — they execute commands. LivePilot is an **agentic production system**. It has
|
|
36
|
+
Most MCP servers are tool collections — they execute commands. LivePilot is an **agentic production system**. It has seven layers that work together:
|
|
37
37
|
|
|
38
38
|
| Layer | What it provides |
|
|
39
39
|
|-------|-----------------|
|
|
40
40
|
| **Deterministic Tools** | Direct control: transport, tracks, clips, notes, devices, scenes, mixing, arrangement, browser, automation |
|
|
41
|
-
| **Device Atlas** | Knowledge of every device in Ableton's library — 1305 devices indexed by name, URI, category, tag, and
|
|
42
|
-
| **
|
|
43
|
-
| **
|
|
41
|
+
| **Device Atlas** | Knowledge of every device in Ableton's library — 1305 devices indexed by name, URI, category, tag, genre, and pack (641 pack-indexed). 120 enriched with sonic intelligence (47 with aesthetic-tagged `signature_techniques`). 683 drum kits mapped. **Free-text `atlas_describe_chain`** ("a granular pad like Tim Hecker") and **reverse-lookup `atlas_techniques_for_device`** (what techniques reference this device?) added in v1.17 |
|
|
42
|
+
| **Concept Surface** | **New in v1.17.** Two reference files let the LLM's training translate into LivePilot: `artist-vocabularies.md` maps ~25 producers (Villalobos, Hawtin, Basic Channel, Gas, Basinski, Hecker, Aphex, Autechre, Dilla, Burial, Henke, Daft Punk, …) to `reach_for` / `avoid` / `key_techniques`; `genre-vocabularies.md` maps 15 genres to tempo / kick / bass / percussion / harmonic / texture / devices. 146 cross-references between devices and techniques. The LLM reads "sound like Gas" and gets a concrete device chain, not guesswork |
|
|
43
|
+
| **Sample Engine** | Three-source sample intelligence — Ableton's browser, your filesystem, and Splice's catalog (plan-aware: Ableton Live plan uses daily quota, Sounds+/Creator uses credits, free samples bypass both). 6 fitness critics. 29 processing techniques. Collections, presets, preview-URL audition, **LIVE Describe-a-Sound + Variations via Splice GraphQL (v1.17)** |
|
|
44
|
+
| **Spectral Perception** | Real-time ears via M4L — **9-band FFT (v1.17+, sub_low split at 20-60 Hz)**, RMS/peak metering, Krumhansl-Schmuckler key detection, pitch tracking, FluCoMa mel/chroma/onset. Closes the feedback loop so the AI hears its own changes |
|
|
44
45
|
| **Technique Memory** | Persistent library of production decisions. Save a beat pattern, device chain, or mix template. Recall by mood, genre, or texture across sessions |
|
|
45
|
-
| **Creative Intelligence** |
|
|
46
|
+
| **Creative Intelligence** | A dozen engines that understand song identity, learn your taste, diagnose stuck sessions, generate creative options, and evaluate results before claiming success |
|
|
46
47
|
|
|
47
48
|
<br>
|
|
48
49
|
|
|
@@ -56,9 +57,9 @@ Most MCP servers are tool collections — they execute commands. LivePilot is an
|
|
|
56
57
|
│ KNOWLEDGE PERCEPTION MEMORY │
|
|
57
58
|
│ ────────────── ────────────── ────────────── │
|
|
58
59
|
│ │
|
|
59
|
-
│ Device Atlas
|
|
60
|
+
│ Device Atlas 9-band FFT recall by mood, │
|
|
60
61
|
│ 1305 devices RMS / peak genre, texture │
|
|
61
|
-
│
|
|
62
|
+
│ 120 enriched pitch tracking 29 techniques │
|
|
62
63
|
│ 683 drum kits key detection replay into session │
|
|
63
64
|
│ │
|
|
64
65
|
│ Sample Engine Corpus Intelligence Taste Graph │
|
|
@@ -79,7 +80,7 @@ Most MCP servers are tool collections — they execute commands. LivePilot is an
|
|
|
79
80
|
│ └─────────────────┼──────────────────┘ │
|
|
80
81
|
│ ▼ │
|
|
81
82
|
│ ┌─────────────────┐ │
|
|
82
|
-
│ │
|
|
83
|
+
│ │ 426 MCP Tools │ │
|
|
83
84
|
│ │ 52 domains │ │
|
|
84
85
|
│ └────────┬────────┘ │
|
|
85
86
|
│ │ │
|
|
@@ -102,7 +103,7 @@ Most MCP servers are tool collections — they execute commands. LivePilot is an
|
|
|
102
103
|
|
|
103
104
|
**M4L Bridge** (`m4l_device/`) — Optional Max for Live Audio Effect on the master track. Provides deep LOM access through Max's LiveAPI that the ControlSurface API can't reach. UDP 9880 (M4L to server) carries spectral data and LiveAPI responses. OSC 9881 (server to M4L) sends commands. The 32 spectral/analyzer tools strictly require the bridge; device and sample tools that call the bridge also have graceful fallbacks, so core functionality works without it. Backed by 30 bridge commands for hidden parameters, Simpler internals, warp markers, display values, and Simpler warp / Compressor sidechain writes that live on child objects Python can't reach.
|
|
104
105
|
|
|
105
|
-
**Device Atlas** (`mcp_server/atlas/`) — In-memory indexed JSON database. 1305 devices with browser URIs,
|
|
106
|
+
**Device Atlas** (`mcp_server/atlas/`) — In-memory indexed JSON database. 1305 devices with browser URIs, 120 enriched with YAML sonic intelligence profiles (mood, genre, texture, recommended chains). 6 indexes: by_id, by_name, by_uri, by_category, by_tag, by_genre. The AI never hallucinates a device name or preset — it always resolves against the atlas first.
|
|
106
107
|
|
|
107
108
|
**Sample Engine** (`mcp_server/sample_engine/`) — Searches three sources simultaneously: BrowserSource (Ableton's library), SpliceSource (local Splice catalog via SQLite), FilesystemSource (user directories). Every result passes through a 6-critic fitness battery (key, tempo, spectral, genre, mood, technical). 29 processing techniques (Surgeon precision vs. Alchemist experimentation). Builds complete sample processing plans with warp, slice, and effect recommendations.
|
|
108
109
|
|
|
@@ -120,7 +121,7 @@ Most MCP servers are tool collections — they execute commands. LivePilot is an
|
|
|
120
121
|
|
|
121
122
|
## The Intelligence Layer
|
|
122
123
|
|
|
123
|
-
12 engines sit on top of the
|
|
124
|
+
12 engines sit on top of the 426 tools. They give the AI musical judgment, not just musical execution.
|
|
124
125
|
|
|
125
126
|
### SongBrain — What the Song Is
|
|
126
127
|
|
|
@@ -172,7 +173,7 @@ Every engine follows: **measure before → act → measure after → compare**.
|
|
|
172
173
|
|
|
173
174
|
## Tools
|
|
174
175
|
|
|
175
|
-
|
|
176
|
+
426 tools across 52 domains. Highlights below — [full catalog here](docs/manual/tool-catalog.md).
|
|
176
177
|
|
|
177
178
|
<br>
|
|
178
179
|
|
|
@@ -232,7 +233,7 @@ The atlas is an in-memory indexed database of Ableton's entire device library.
|
|
|
232
233
|
|
|
233
234
|
```
|
|
234
235
|
1305 devices total
|
|
235
|
-
|
|
236
|
+
120 enriched with sonic intelligence (mood, genre, texture, chains)
|
|
236
237
|
683 drum kits mapped with note assignments
|
|
237
238
|
6 indexes: by_id, by_name, by_uri, by_category, by_tag, by_genre
|
|
238
239
|
```
|
|
@@ -360,7 +361,7 @@ The V2 intelligence layer. These tools analyze, diagnose, plan, evaluate, and le
|
|
|
360
361
|
| Creative Constraints | 5 | constraint activation, reference-inspired variants |
|
|
361
362
|
| Preview Studio | 5 | variant creation, preview rendering, comparison, commit |
|
|
362
363
|
|
|
363
|
-
> **[View all
|
|
364
|
+
> **[View all 426 tools →](docs/manual/tool-catalog.md)**
|
|
364
365
|
|
|
365
366
|
<br>
|
|
366
367
|
|
|
@@ -587,7 +588,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for architecture details, code guidelines
|
|
|
587
588
|
|
|
588
589
|
| Document | What's inside |
|
|
589
590
|
|----------|---------------|
|
|
590
|
-
| [Manual](docs/manual/index.md) | Complete reference: architecture, all
|
|
591
|
+
| [Manual](docs/manual/index.md) | Complete reference: architecture, all 426 tools, workflows |
|
|
591
592
|
| [Intelligence Layer](docs/manual/intelligence.md) | How the 12 engines connect — conductor, moves, preview, evaluation |
|
|
592
593
|
| [Device Atlas](docs/manual/device-atlas.md) | 1305 devices indexed — search, suggest, chain building |
|
|
593
594
|
| [Samples & Slicing](docs/manual/samples.md) | 3-source search, fitness critics, slice workflows |
|
|
Binary file
|
package/mcp_server/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""LivePilot MCP Server — bridges MCP protocol to Ableton Live."""
|
|
2
|
-
__version__ = "1.
|
|
2
|
+
__version__ = "1.17.0"
|
|
@@ -28,6 +28,7 @@ class AtlasManager:
|
|
|
28
28
|
self._by_category: Dict[str, List[Dict[str, Any]]] = {}
|
|
29
29
|
self._by_tag: Dict[str, List[Dict[str, Any]]] = {}
|
|
30
30
|
self._by_genre: Dict[str, List[Dict[str, Any]]] = {}
|
|
31
|
+
self._by_pack: Dict[str, List[Dict[str, Any]]] = {}
|
|
31
32
|
|
|
32
33
|
for dev in self._devices:
|
|
33
34
|
dev_id = dev.get("id", "")
|
|
@@ -55,6 +56,30 @@ class AtlasManager:
|
|
|
55
56
|
self._by_genre.setdefault(genre.lower(), []).append(dev)
|
|
56
57
|
for genre in dev.get("genres", {}).get("secondary", []):
|
|
57
58
|
self._by_genre.setdefault(genre.lower(), []).append(dev)
|
|
59
|
+
# Also read the enriched genre_affinity field, which is where
|
|
60
|
+
# curated YAML entries land after merge (scanners emit `genres`,
|
|
61
|
+
# enrichments emit `genre_affinity` — both must feed the index
|
|
62
|
+
# or new minimal/microhouse/dub_techno tags would never surface).
|
|
63
|
+
aff = dev.get("genre_affinity", {}) or {}
|
|
64
|
+
for genre in aff.get("primary", []) if isinstance(aff, dict) else []:
|
|
65
|
+
self._by_genre.setdefault(str(genre).lower(), []).append(dev)
|
|
66
|
+
for genre in aff.get("secondary", []) if isinstance(aff, dict) else []:
|
|
67
|
+
self._by_genre.setdefault(str(genre).lower(), []).append(dev)
|
|
68
|
+
|
|
69
|
+
# Pack index — enriched devices declare `pack:` in YAML. Native
|
|
70
|
+
# Live devices without an explicit pack default to "Core Library"
|
|
71
|
+
# so the index covers everything shipped with the stock install.
|
|
72
|
+
pack_name = dev.get("pack")
|
|
73
|
+
if not pack_name and dev.get("source") in (None, "native", "browser"):
|
|
74
|
+
# Heuristic: if we have no explicit pack and the scan source
|
|
75
|
+
# is the built-in browser, treat as Core Library. Don't fake
|
|
76
|
+
# a pack for plugins/user samples where the source isn't the
|
|
77
|
+
# stock library.
|
|
78
|
+
if dev_category in ("instruments", "audio_effects",
|
|
79
|
+
"midi_effects", "max_for_live"):
|
|
80
|
+
pack_name = "Core Library"
|
|
81
|
+
if pack_name:
|
|
82
|
+
self._by_pack.setdefault(pack_name, []).append(dev)
|
|
58
83
|
|
|
59
84
|
# ── Properties ──────────────────────────────────────────────────
|
|
60
85
|
|
|
@@ -83,9 +108,69 @@ class AtlasManager:
|
|
|
83
108
|
"by_category": len(self._by_category),
|
|
84
109
|
"by_tag": len(self._by_tag),
|
|
85
110
|
"by_genre": len(self._by_genre),
|
|
111
|
+
"by_pack": len(self._by_pack),
|
|
86
112
|
},
|
|
87
113
|
}
|
|
88
114
|
|
|
115
|
+
# ── Pack lookup ────────────────────────────────────────────────
|
|
116
|
+
def pack_info(self, pack_name: str) -> Dict[str, Any]:
|
|
117
|
+
"""Return summary of a pack — device list + enrichment coverage.
|
|
118
|
+
|
|
119
|
+
Matches the pack name case-insensitively; the index stores the
|
|
120
|
+
canonical casing from the YAML (e.g. "Core Library", "Drone Lab").
|
|
121
|
+
"""
|
|
122
|
+
if not pack_name:
|
|
123
|
+
return {"pack": "", "device_count": 0, "enriched_count": 0,
|
|
124
|
+
"devices": []}
|
|
125
|
+
|
|
126
|
+
target = pack_name.strip().lower()
|
|
127
|
+
# Find canonical name
|
|
128
|
+
canonical = None
|
|
129
|
+
for name in self._by_pack.keys():
|
|
130
|
+
if name.lower() == target:
|
|
131
|
+
canonical = name
|
|
132
|
+
break
|
|
133
|
+
|
|
134
|
+
if canonical is None:
|
|
135
|
+
return {
|
|
136
|
+
"pack": pack_name,
|
|
137
|
+
"device_count": 0,
|
|
138
|
+
"enriched_count": 0,
|
|
139
|
+
"devices": [],
|
|
140
|
+
"available_packs": sorted(self._by_pack.keys()),
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
devices = self._by_pack[canonical]
|
|
144
|
+
enriched = [d for d in devices if d.get("enriched")]
|
|
145
|
+
return {
|
|
146
|
+
"pack": canonical,
|
|
147
|
+
"device_count": len(devices),
|
|
148
|
+
"enriched_count": len(enriched),
|
|
149
|
+
"devices": [
|
|
150
|
+
{
|
|
151
|
+
"id": d.get("id", ""),
|
|
152
|
+
"name": d.get("name", ""),
|
|
153
|
+
"category": d.get("category", ""),
|
|
154
|
+
"enriched": bool(d.get("enriched")),
|
|
155
|
+
"uri": d.get("uri", ""),
|
|
156
|
+
}
|
|
157
|
+
for d in devices
|
|
158
|
+
],
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
def list_packs(self) -> List[Dict[str, Any]]:
|
|
162
|
+
"""All known packs with device counts, sorted by size descending."""
|
|
163
|
+
packs = [
|
|
164
|
+
{
|
|
165
|
+
"name": name,
|
|
166
|
+
"device_count": len(devs),
|
|
167
|
+
"enriched_count": sum(1 for d in devs if d.get("enriched")),
|
|
168
|
+
}
|
|
169
|
+
for name, devs in self._by_pack.items()
|
|
170
|
+
]
|
|
171
|
+
packs.sort(key=lambda p: -p["device_count"])
|
|
172
|
+
return packs
|
|
173
|
+
|
|
89
174
|
# ── Lookup ──────────────────────────────────────────────────────
|
|
90
175
|
|
|
91
176
|
def lookup(self, name_or_id: str) -> Optional[Dict[str, Any]]:
|