@kernel.chat/kbot 3.70.0 → 3.71.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/dist/integrations/ableton-bridge.d.ts +158 -0
- package/dist/integrations/ableton-bridge.js +486 -0
- package/dist/integrations/ableton-m4l.d.ts +94 -0
- package/dist/integrations/ableton-m4l.js +252 -1
- package/dist/integrations/install-remote-script.d.ts +23 -0
- package/dist/integrations/install-remote-script.js +121 -0
- package/dist/tools/ableton-bridge-tools.d.ts +14 -0
- package/dist/tools/ableton-bridge-tools.js +327 -0
- package/dist/tools/index.js +1 -0
- package/package.json +2 -2
|
@@ -112,13 +112,107 @@ export declare class AbletonM4L {
|
|
|
112
112
|
/** Generic LOM method call — call any method at any path */
|
|
113
113
|
lomCall(path: string, method: string, args?: unknown[]): Promise<M4LResponse>;
|
|
114
114
|
}
|
|
115
|
+
export interface BrowserSearchResult {
|
|
116
|
+
name: string;
|
|
117
|
+
uri: string;
|
|
118
|
+
is_loadable: boolean;
|
|
119
|
+
is_device: boolean;
|
|
120
|
+
}
|
|
121
|
+
export interface BrowserCategory {
|
|
122
|
+
name: string;
|
|
123
|
+
display_name: string;
|
|
124
|
+
child_count: number;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Client for the KBotBridge Remote Script (TCP 9998).
|
|
128
|
+
*
|
|
129
|
+
* This is separate from the M4L bridge (9999) because the Browser API
|
|
130
|
+
* (browser.load_item) is ONLY available from Python Remote Scripts,
|
|
131
|
+
* not from Max for Live.
|
|
132
|
+
*
|
|
133
|
+
* Use this to programmatically load any native device (Saturator,
|
|
134
|
+
* EQ Eight, Compressor, etc.) onto any track.
|
|
135
|
+
*/
|
|
136
|
+
export declare class AbletonBrowserBridge {
|
|
137
|
+
private static instance;
|
|
138
|
+
private socket;
|
|
139
|
+
private connected;
|
|
140
|
+
private pending;
|
|
141
|
+
private nextId;
|
|
142
|
+
private buffer;
|
|
143
|
+
static PORT: number;
|
|
144
|
+
static HOST: string;
|
|
145
|
+
static TIMEOUT: number;
|
|
146
|
+
private constructor();
|
|
147
|
+
static getInstance(): AbletonBrowserBridge;
|
|
148
|
+
/**
|
|
149
|
+
* Connect to the KBotBridge Remote Script on port 9998.
|
|
150
|
+
* Returns true if connected and the bridge responds to ping.
|
|
151
|
+
*/
|
|
152
|
+
connect(): Promise<boolean>;
|
|
153
|
+
disconnect(): void;
|
|
154
|
+
send(cmd: Omit<M4LCommand, 'id'> & {
|
|
155
|
+
action: string;
|
|
156
|
+
}): Promise<M4LResponse>;
|
|
157
|
+
get isConnected(): boolean;
|
|
158
|
+
private handleResponse;
|
|
159
|
+
private handleDisconnect;
|
|
160
|
+
ping(): Promise<boolean>;
|
|
161
|
+
/**
|
|
162
|
+
* Search Ableton's browser for items matching a query.
|
|
163
|
+
* @param query - Search string (case-insensitive)
|
|
164
|
+
* @param category - instruments/audio_effects/midi_effects/drums/samples/all
|
|
165
|
+
*/
|
|
166
|
+
browserSearch(query: string, category?: string): Promise<BrowserSearchResult[]>;
|
|
167
|
+
/**
|
|
168
|
+
* Load a browser item by URI onto a track.
|
|
169
|
+
* Use the URI from a browserSearch() result.
|
|
170
|
+
*/
|
|
171
|
+
browserLoad(track: number, uri: string): Promise<M4LResponse>;
|
|
172
|
+
/**
|
|
173
|
+
* Search + load in one step. Finds first loadable match and loads it.
|
|
174
|
+
* @param track - 0-indexed track number
|
|
175
|
+
* @param name - Device name to search for (e.g., "Saturator", "EQ Eight")
|
|
176
|
+
* @param category - instruments/audio_effects/midi_effects/drums/samples/all
|
|
177
|
+
*/
|
|
178
|
+
browserLoadByName(track: number, name: string, category?: string): Promise<M4LResponse>;
|
|
179
|
+
/**
|
|
180
|
+
* List top-level browser categories with child counts.
|
|
181
|
+
*/
|
|
182
|
+
browserCategories(): Promise<BrowserCategory[]>;
|
|
183
|
+
/**
|
|
184
|
+
* List all tracks with names and device counts.
|
|
185
|
+
*/
|
|
186
|
+
listTracks(): Promise<M4LResponse>;
|
|
187
|
+
/**
|
|
188
|
+
* List devices on a track with full parameter details.
|
|
189
|
+
*/
|
|
190
|
+
listDevices(track: number): Promise<M4LResponse>;
|
|
191
|
+
}
|
|
115
192
|
/**
|
|
116
193
|
* Get a connected M4L bridge instance.
|
|
117
194
|
* Throws if the bridge is not available.
|
|
118
195
|
*/
|
|
119
196
|
export declare function ensureM4L(): Promise<AbletonM4L>;
|
|
197
|
+
/**
|
|
198
|
+
* Get a connected Browser bridge instance (KBotBridge Remote Script on port 9998).
|
|
199
|
+
* Throws if not available.
|
|
200
|
+
*/
|
|
201
|
+
export declare function ensureBrowserBridge(): Promise<AbletonBrowserBridge>;
|
|
202
|
+
/**
|
|
203
|
+
* Connect to both M4L bridge (9999) and Browser bridge (9998).
|
|
204
|
+
* Returns whichever connections succeed. At least one must connect.
|
|
205
|
+
*/
|
|
206
|
+
export declare function connectBrowser(): Promise<{
|
|
207
|
+
m4l: AbletonM4L | null;
|
|
208
|
+
browser: AbletonBrowserBridge | null;
|
|
209
|
+
}>;
|
|
120
210
|
/**
|
|
121
211
|
* Format a friendly error message for M4L connection failures.
|
|
122
212
|
*/
|
|
123
213
|
export declare function formatM4LError(): string;
|
|
214
|
+
/**
|
|
215
|
+
* Format a friendly error message for Browser bridge connection failures.
|
|
216
|
+
*/
|
|
217
|
+
export declare function formatBrowserBridgeError(): string;
|
|
124
218
|
//# sourceMappingURL=ableton-m4l.d.ts.map
|
|
@@ -301,7 +301,204 @@ export class AbletonM4L {
|
|
|
301
301
|
return this.send({ action: 'lom_call', path, method, args });
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
|
-
|
|
304
|
+
/**
|
|
305
|
+
* Client for the KBotBridge Remote Script (TCP 9998).
|
|
306
|
+
*
|
|
307
|
+
* This is separate from the M4L bridge (9999) because the Browser API
|
|
308
|
+
* (browser.load_item) is ONLY available from Python Remote Scripts,
|
|
309
|
+
* not from Max for Live.
|
|
310
|
+
*
|
|
311
|
+
* Use this to programmatically load any native device (Saturator,
|
|
312
|
+
* EQ Eight, Compressor, etc.) onto any track.
|
|
313
|
+
*/
|
|
314
|
+
export class AbletonBrowserBridge {
|
|
315
|
+
static instance = null;
|
|
316
|
+
socket = null;
|
|
317
|
+
connected = false;
|
|
318
|
+
pending = new Map();
|
|
319
|
+
nextId = 1;
|
|
320
|
+
buffer = '';
|
|
321
|
+
static PORT = 9998;
|
|
322
|
+
static HOST = '127.0.0.1';
|
|
323
|
+
static TIMEOUT = 15_000; // Browser operations can be slow
|
|
324
|
+
constructor() { }
|
|
325
|
+
static getInstance() {
|
|
326
|
+
if (!AbletonBrowserBridge.instance) {
|
|
327
|
+
AbletonBrowserBridge.instance = new AbletonBrowserBridge();
|
|
328
|
+
}
|
|
329
|
+
return AbletonBrowserBridge.instance;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Connect to the KBotBridge Remote Script on port 9998.
|
|
333
|
+
* Returns true if connected and the bridge responds to ping.
|
|
334
|
+
*/
|
|
335
|
+
async connect() {
|
|
336
|
+
if (this.connected && this.socket) {
|
|
337
|
+
try {
|
|
338
|
+
await this.send({ action: 'ping' });
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
this.disconnect();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return new Promise((resolve) => {
|
|
346
|
+
this.socket = new net.Socket();
|
|
347
|
+
this.buffer = '';
|
|
348
|
+
this.socket.on('data', (data) => {
|
|
349
|
+
this.buffer += data.toString();
|
|
350
|
+
const lines = this.buffer.split('\n');
|
|
351
|
+
this.buffer = lines.pop() || '';
|
|
352
|
+
for (const line of lines) {
|
|
353
|
+
const trimmed = line.trim();
|
|
354
|
+
if (!trimmed)
|
|
355
|
+
continue;
|
|
356
|
+
try {
|
|
357
|
+
const response = JSON.parse(trimmed);
|
|
358
|
+
this.handleResponse(response);
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
// Malformed JSON — skip
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
this.socket.on('error', () => {
|
|
366
|
+
if (!this.connected)
|
|
367
|
+
resolve(false);
|
|
368
|
+
this.handleDisconnect();
|
|
369
|
+
});
|
|
370
|
+
this.socket.on('close', () => {
|
|
371
|
+
this.handleDisconnect();
|
|
372
|
+
});
|
|
373
|
+
this.socket.connect(AbletonBrowserBridge.PORT, AbletonBrowserBridge.HOST, async () => {
|
|
374
|
+
this.connected = true;
|
|
375
|
+
try {
|
|
376
|
+
const pong = await this.send({ action: 'ping' });
|
|
377
|
+
resolve(pong.ok);
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
resolve(false);
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
setTimeout(() => {
|
|
384
|
+
if (!this.connected) {
|
|
385
|
+
this.socket?.destroy();
|
|
386
|
+
resolve(false);
|
|
387
|
+
}
|
|
388
|
+
}, 5000);
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
disconnect() {
|
|
392
|
+
this.connected = false;
|
|
393
|
+
if (this.socket) {
|
|
394
|
+
this.socket.destroy();
|
|
395
|
+
this.socket = null;
|
|
396
|
+
}
|
|
397
|
+
for (const [, req] of this.pending) {
|
|
398
|
+
clearTimeout(req.timer);
|
|
399
|
+
req.reject(new Error('Disconnected'));
|
|
400
|
+
}
|
|
401
|
+
this.pending.clear();
|
|
402
|
+
this.buffer = '';
|
|
403
|
+
}
|
|
404
|
+
async send(cmd) {
|
|
405
|
+
if (!this.connected || !this.socket) {
|
|
406
|
+
throw new Error('Not connected to KBotBridge Remote Script.\n' +
|
|
407
|
+
'Make sure KBotBridge is selected as a Control Surface in Ableton Preferences.');
|
|
408
|
+
}
|
|
409
|
+
const id = this.nextId++;
|
|
410
|
+
const fullCmd = { id, ...cmd };
|
|
411
|
+
return new Promise((resolve, reject) => {
|
|
412
|
+
const timer = setTimeout(() => {
|
|
413
|
+
this.pending.delete(id);
|
|
414
|
+
reject(new Error(`Timeout: ${cmd.action}`));
|
|
415
|
+
}, AbletonBrowserBridge.TIMEOUT);
|
|
416
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
417
|
+
this.socket.write(JSON.stringify(fullCmd) + '\n');
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
get isConnected() {
|
|
421
|
+
return this.connected;
|
|
422
|
+
}
|
|
423
|
+
handleResponse(response) {
|
|
424
|
+
if (response.id && this.pending.has(response.id)) {
|
|
425
|
+
const req = this.pending.get(response.id);
|
|
426
|
+
this.pending.delete(response.id);
|
|
427
|
+
clearTimeout(req.timer);
|
|
428
|
+
req.resolve(response);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
handleDisconnect() {
|
|
432
|
+
if (!this.connected)
|
|
433
|
+
return;
|
|
434
|
+
this.connected = false;
|
|
435
|
+
this.socket = null;
|
|
436
|
+
for (const [, req] of this.pending) {
|
|
437
|
+
clearTimeout(req.timer);
|
|
438
|
+
req.reject(new Error('Connection lost'));
|
|
439
|
+
}
|
|
440
|
+
this.pending.clear();
|
|
441
|
+
}
|
|
442
|
+
// ── Browser convenience methods ─────────────────────────────────
|
|
443
|
+
async ping() {
|
|
444
|
+
try {
|
|
445
|
+
const r = await this.send({ action: 'ping' });
|
|
446
|
+
return r.ok;
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Search Ableton's browser for items matching a query.
|
|
454
|
+
* @param query - Search string (case-insensitive)
|
|
455
|
+
* @param category - instruments/audio_effects/midi_effects/drums/samples/all
|
|
456
|
+
*/
|
|
457
|
+
async browserSearch(query, category = 'all') {
|
|
458
|
+
const r = await this.send({ action: 'browser_search', query, category });
|
|
459
|
+
if (!r.ok)
|
|
460
|
+
throw new Error(r.error || 'Browser search failed');
|
|
461
|
+
return r.results || [];
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Load a browser item by URI onto a track.
|
|
465
|
+
* Use the URI from a browserSearch() result.
|
|
466
|
+
*/
|
|
467
|
+
async browserLoad(track, uri) {
|
|
468
|
+
return this.send({ action: 'browser_load', track, uri });
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Search + load in one step. Finds first loadable match and loads it.
|
|
472
|
+
* @param track - 0-indexed track number
|
|
473
|
+
* @param name - Device name to search for (e.g., "Saturator", "EQ Eight")
|
|
474
|
+
* @param category - instruments/audio_effects/midi_effects/drums/samples/all
|
|
475
|
+
*/
|
|
476
|
+
async browserLoadByName(track, name, category = 'all') {
|
|
477
|
+
return this.send({ action: 'browser_load_by_name', track, name, category });
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* List top-level browser categories with child counts.
|
|
481
|
+
*/
|
|
482
|
+
async browserCategories() {
|
|
483
|
+
const r = await this.send({ action: 'browser_categories' });
|
|
484
|
+
if (!r.ok)
|
|
485
|
+
throw new Error(r.error || 'Failed to list categories');
|
|
486
|
+
return r.categories || [];
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* List all tracks with names and device counts.
|
|
490
|
+
*/
|
|
491
|
+
async listTracks() {
|
|
492
|
+
return this.send({ action: 'list_tracks' });
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* List devices on a track with full parameter details.
|
|
496
|
+
*/
|
|
497
|
+
async listDevices(track) {
|
|
498
|
+
return this.send({ action: 'list_devices', track });
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
// ── Convenience exports ─────────────────────────────────────────────
|
|
305
502
|
/**
|
|
306
503
|
* Get a connected M4L bridge instance.
|
|
307
504
|
* Throws if the bridge is not available.
|
|
@@ -320,6 +517,41 @@ export async function ensureM4L() {
|
|
|
320
517
|
}
|
|
321
518
|
return m4l;
|
|
322
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* Get a connected Browser bridge instance (KBotBridge Remote Script on port 9998).
|
|
522
|
+
* Throws if not available.
|
|
523
|
+
*/
|
|
524
|
+
export async function ensureBrowserBridge() {
|
|
525
|
+
const bridge = AbletonBrowserBridge.getInstance();
|
|
526
|
+
if (bridge.isConnected)
|
|
527
|
+
return bridge;
|
|
528
|
+
const ok = await bridge.connect();
|
|
529
|
+
if (!ok) {
|
|
530
|
+
throw new Error('Cannot connect to KBotBridge Remote Script.\n\n' +
|
|
531
|
+
'Make sure:\n' +
|
|
532
|
+
'1. Ableton Live is running\n' +
|
|
533
|
+
'2. KBotBridge is selected as a Control Surface in Preferences > Link, Tempo & MIDI\n' +
|
|
534
|
+
'3. Ableton status bar shows "KBotBridge: Listening on port 9998"\n\n' +
|
|
535
|
+
'To install: kbot ableton install-bridge\n');
|
|
536
|
+
}
|
|
537
|
+
return bridge;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Connect to both M4L bridge (9999) and Browser bridge (9998).
|
|
541
|
+
* Returns whichever connections succeed. At least one must connect.
|
|
542
|
+
*/
|
|
543
|
+
export async function connectBrowser() {
|
|
544
|
+
const m4l = AbletonM4L.getInstance();
|
|
545
|
+
const browser = AbletonBrowserBridge.getInstance();
|
|
546
|
+
const [m4lOk, browserOk] = await Promise.all([
|
|
547
|
+
m4l.connect().catch(() => false),
|
|
548
|
+
browser.connect().catch(() => false),
|
|
549
|
+
]);
|
|
550
|
+
return {
|
|
551
|
+
m4l: m4lOk ? m4l : null,
|
|
552
|
+
browser: browserOk ? browser : null,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
323
555
|
/**
|
|
324
556
|
* Format a friendly error message for M4L connection failures.
|
|
325
557
|
*/
|
|
@@ -335,4 +567,23 @@ export function formatM4LError() {
|
|
|
335
567
|
'The M4L bridge gives kbot full control over Ableton — instruments, effects, clips, mixing, everything.',
|
|
336
568
|
].join('\n');
|
|
337
569
|
}
|
|
570
|
+
/**
|
|
571
|
+
* Format a friendly error message for Browser bridge connection failures.
|
|
572
|
+
*/
|
|
573
|
+
export function formatBrowserBridgeError() {
|
|
574
|
+
return [
|
|
575
|
+
'**KBotBridge Remote Script not connected**',
|
|
576
|
+
'',
|
|
577
|
+
'The Browser API (for loading native devices) requires the KBotBridge Remote Script:',
|
|
578
|
+
'1. Install: `kbot ableton install-bridge`',
|
|
579
|
+
'2. Open Ableton Live Preferences (Cmd+,)',
|
|
580
|
+
'3. Go to Link, Tempo & MIDI',
|
|
581
|
+
'4. Set a Control Surface to "KBotBridge"',
|
|
582
|
+
'5. Close Preferences',
|
|
583
|
+
'',
|
|
584
|
+
'This runs alongside the M4L bridge — they use different ports:',
|
|
585
|
+
'- KBotBridge: TCP 9998 (Browser API, device loading)',
|
|
586
|
+
'- M4L Bridge: TCP 9999 (LOM access, clips, mixing)',
|
|
587
|
+
].join('\n');
|
|
588
|
+
}
|
|
338
589
|
//# sourceMappingURL=ableton-m4l.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* install-remote-script.ts — Install KBotBridge Remote Script into Ableton Live
|
|
3
|
+
*
|
|
4
|
+
* Copies the KBotBridge Python Remote Script to Ableton's User Library,
|
|
5
|
+
* enabling the Browser API bridge on TCP port 9998.
|
|
6
|
+
*
|
|
7
|
+
* The Remote Script exposes Ableton's browser.load_item() API, which is
|
|
8
|
+
* ONLY available from Python Remote Scripts (not from Max for Live).
|
|
9
|
+
* This lets kbot programmatically load any native device (Saturator,
|
|
10
|
+
* EQ Eight, Compressor, etc.) onto any track.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* npx tsx packages/kbot/src/integrations/install-remote-script.ts
|
|
14
|
+
* kbot ableton install-bridge
|
|
15
|
+
*/
|
|
16
|
+
export declare function installKBotBridge(): Promise<string>;
|
|
17
|
+
export declare function isKBotBridgeInstalled(): boolean;
|
|
18
|
+
export declare function uninstallKBotBridge(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Get the path to the KBotBridge log file inside Ableton's Remote Scripts.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getKBotBridgeLogPath(): string | null;
|
|
23
|
+
//# sourceMappingURL=install-remote-script.d.ts.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* install-remote-script.ts — Install KBotBridge Remote Script into Ableton Live
|
|
3
|
+
*
|
|
4
|
+
* Copies the KBotBridge Python Remote Script to Ableton's User Library,
|
|
5
|
+
* enabling the Browser API bridge on TCP port 9998.
|
|
6
|
+
*
|
|
7
|
+
* The Remote Script exposes Ableton's browser.load_item() API, which is
|
|
8
|
+
* ONLY available from Python Remote Scripts (not from Max for Live).
|
|
9
|
+
* This lets kbot programmatically load any native device (Saturator,
|
|
10
|
+
* EQ Eight, Compressor, etc.) onto any track.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* npx tsx packages/kbot/src/integrations/install-remote-script.ts
|
|
14
|
+
* kbot ableton install-bridge
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from 'node:fs';
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
import * as os from 'node:os';
|
|
19
|
+
const SCRIPT_NAME = 'KBotBridge';
|
|
20
|
+
const SOURCE_FILES = [
|
|
21
|
+
'__init__.py',
|
|
22
|
+
'kbot_control_surface.py',
|
|
23
|
+
'tcp_server.py',
|
|
24
|
+
];
|
|
25
|
+
function getRemoteScriptsDir() {
|
|
26
|
+
const home = os.homedir();
|
|
27
|
+
return path.join(home, 'Music', 'Ableton', 'User Library', 'Remote Scripts');
|
|
28
|
+
}
|
|
29
|
+
function getDestDir() {
|
|
30
|
+
return path.join(getRemoteScriptsDir(), SCRIPT_NAME);
|
|
31
|
+
}
|
|
32
|
+
function getSourceDir() {
|
|
33
|
+
// Source files live alongside this installer in the integrations directory
|
|
34
|
+
return path.join(__dirname, SCRIPT_NAME);
|
|
35
|
+
}
|
|
36
|
+
export async function installKBotBridge() {
|
|
37
|
+
const lines = [];
|
|
38
|
+
const log = (msg) => { lines.push(msg); console.log(msg); };
|
|
39
|
+
log('Installing KBotBridge Remote Script...');
|
|
40
|
+
log('');
|
|
41
|
+
const sourceDir = getSourceDir();
|
|
42
|
+
const destDir = getDestDir();
|
|
43
|
+
const remoteScriptsDir = getRemoteScriptsDir();
|
|
44
|
+
// Verify source files exist
|
|
45
|
+
for (const file of SOURCE_FILES) {
|
|
46
|
+
const srcPath = path.join(sourceDir, file);
|
|
47
|
+
if (!fs.existsSync(srcPath)) {
|
|
48
|
+
log(` ERROR: Source file missing: ${srcPath}`);
|
|
49
|
+
log(' Run from the kbot package directory.');
|
|
50
|
+
return lines.join('\n');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Create Remote Scripts directory if it doesn't exist
|
|
54
|
+
if (!fs.existsSync(remoteScriptsDir)) {
|
|
55
|
+
fs.mkdirSync(remoteScriptsDir, { recursive: true });
|
|
56
|
+
log(` Created: ${remoteScriptsDir}`);
|
|
57
|
+
}
|
|
58
|
+
// Remove old installation if present
|
|
59
|
+
if (fs.existsSync(destDir)) {
|
|
60
|
+
fs.rmSync(destDir, { recursive: true });
|
|
61
|
+
log(' Removed previous KBotBridge installation');
|
|
62
|
+
}
|
|
63
|
+
// Copy files
|
|
64
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
65
|
+
for (const file of SOURCE_FILES) {
|
|
66
|
+
const src = path.join(sourceDir, file);
|
|
67
|
+
const dst = path.join(destDir, file);
|
|
68
|
+
fs.copyFileSync(src, dst);
|
|
69
|
+
log(` Copied: ${file}`);
|
|
70
|
+
}
|
|
71
|
+
// Create logs directory (the script will log here)
|
|
72
|
+
const logsDir = path.join(destDir, 'logs');
|
|
73
|
+
if (!fs.existsSync(logsDir)) {
|
|
74
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
log('');
|
|
77
|
+
log(`KBotBridge installed to: ${destDir}`);
|
|
78
|
+
log('');
|
|
79
|
+
log('To activate:');
|
|
80
|
+
log(' 1. Open Ableton Live (or restart if already running)');
|
|
81
|
+
log(' 2. Preferences (Cmd+,) > Link, Tempo & MIDI');
|
|
82
|
+
log(' 3. Set a Control Surface slot to "KBotBridge"');
|
|
83
|
+
log(' 4. Input/Output can be left as "None"');
|
|
84
|
+
log(' 5. Close Preferences');
|
|
85
|
+
log('');
|
|
86
|
+
log('Verify:');
|
|
87
|
+
log(' - Ableton status bar shows "KBotBridge: Listening on port 9998"');
|
|
88
|
+
log(' - Run: echo \'{"id":1,"action":"ping"}\\n\' | nc localhost 9998');
|
|
89
|
+
log('');
|
|
90
|
+
log('KBotBridge runs alongside AbletonOSC — they use different ports:');
|
|
91
|
+
log(' - KBotBridge: TCP 9998 (Browser API, device loading)');
|
|
92
|
+
log(' - M4L Bridge: TCP 9999 (LOM access, clips, mixing)');
|
|
93
|
+
log(' - AbletonOSC: UDP 11000/11001 (OSC, legacy)');
|
|
94
|
+
return lines.join('\n');
|
|
95
|
+
}
|
|
96
|
+
export function isKBotBridgeInstalled() {
|
|
97
|
+
const destDir = getDestDir();
|
|
98
|
+
return fs.existsSync(path.join(destDir, '__init__.py'))
|
|
99
|
+
&& fs.existsSync(path.join(destDir, 'kbot_control_surface.py'))
|
|
100
|
+
&& fs.existsSync(path.join(destDir, 'tcp_server.py'));
|
|
101
|
+
}
|
|
102
|
+
export function uninstallKBotBridge() {
|
|
103
|
+
const destDir = getDestDir();
|
|
104
|
+
if (fs.existsSync(destDir)) {
|
|
105
|
+
fs.rmSync(destDir, { recursive: true });
|
|
106
|
+
return `KBotBridge removed from ${destDir}`;
|
|
107
|
+
}
|
|
108
|
+
return 'KBotBridge was not installed.';
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get the path to the KBotBridge log file inside Ableton's Remote Scripts.
|
|
112
|
+
*/
|
|
113
|
+
export function getKBotBridgeLogPath() {
|
|
114
|
+
const logPath = path.join(getDestDir(), 'logs', 'kbot_bridge.log');
|
|
115
|
+
return fs.existsSync(logPath) ? logPath : null;
|
|
116
|
+
}
|
|
117
|
+
// ── CLI entrypoint ──────────────────────────────────────────────────
|
|
118
|
+
if (require.main === module || process.argv[1]?.includes('install-remote-script')) {
|
|
119
|
+
installKBotBridge().catch(console.error);
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=install-remote-script.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ableton-bridge-tools.ts — Ableton Browser & Device Loading Tools
|
|
3
|
+
*
|
|
4
|
+
* Uses AbletonBridge (port 9001) for full Ableton Browser API access.
|
|
5
|
+
* Falls back to KBotBridge (port 9998) if AbletonBridge is unavailable.
|
|
6
|
+
*
|
|
7
|
+
* Tools:
|
|
8
|
+
* ableton_load_effect — Load any Ableton native effect by name onto a track
|
|
9
|
+
* ableton_browse — Search Ableton's browser (instruments, effects, presets, samples)
|
|
10
|
+
* ableton_load_preset — Load a preset onto a device
|
|
11
|
+
* ableton_effect_chain — Apply a full chain of effects to a track in sequence
|
|
12
|
+
*/
|
|
13
|
+
export declare function registerAbletonBridgeTools(): void;
|
|
14
|
+
//# sourceMappingURL=ableton-bridge-tools.d.ts.map
|