@kernel.chat/kbot 3.71.0 → 3.73.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/buddy.d.ts +21 -0
- package/dist/buddy.js +97 -1
- package/dist/cli.js +64 -2
- package/dist/integrations/ableton-bridge.d.ts +3 -3
- package/dist/integrations/ableton-bridge.js +6 -6
- package/dist/integrations/ableton-m4l.d.ts +4 -4
- package/dist/integrations/ableton-m4l.js +7 -7
- package/dist/integrations/install-remote-script.d.ts +1 -1
- package/dist/integrations/install-remote-script.js +4 -4
- package/dist/integrations/mobile-mcp-client.d.ts +111 -0
- package/dist/integrations/mobile-mcp-client.js +343 -0
- package/dist/serve.d.ts +3 -0
- package/dist/serve.js +51 -7
- package/dist/tools/buddy-tools.js +55 -2
- package/dist/tools/index.js +2 -0
- package/dist/tools/iphone.d.ts +2 -0
- package/dist/tools/iphone.js +800 -0
- package/dist/tools/mobile-automation.d.ts +2 -0
- package/dist/tools/mobile-automation.js +612 -0
- package/dist/tools/serum2-preset.js +27 -0
- package/package.json +2 -2
package/dist/buddy.d.ts
CHANGED
|
@@ -74,6 +74,27 @@ export declare function addBuddyXP(amount: number): {
|
|
|
74
74
|
levelInfo: BuddyLevelInfo;
|
|
75
75
|
leveledUp: boolean;
|
|
76
76
|
};
|
|
77
|
+
/**
|
|
78
|
+
* Sync buddy stats to the cloud leaderboard.
|
|
79
|
+
* Anonymous — uses a SHA-256 hash of hostname+homedir, not user identity.
|
|
80
|
+
* Requires a kernel.chat token (cloud sync enabled).
|
|
81
|
+
*/
|
|
82
|
+
export declare function syncBuddyToCloud(): Promise<boolean>;
|
|
83
|
+
/**
|
|
84
|
+
* Fetch the buddy leaderboard from the cloud.
|
|
85
|
+
* Returns ranked entries sorted by XP descending.
|
|
86
|
+
*/
|
|
87
|
+
export declare function fetchBuddyLeaderboard(opts?: {
|
|
88
|
+
limit?: number;
|
|
89
|
+
species?: string;
|
|
90
|
+
}): Promise<Array<{
|
|
91
|
+
species: string;
|
|
92
|
+
level: number;
|
|
93
|
+
xp: number;
|
|
94
|
+
achievement_count: number;
|
|
95
|
+
sessions: number;
|
|
96
|
+
rank: number;
|
|
97
|
+
}>>;
|
|
77
98
|
/**
|
|
78
99
|
* Get the buddy's current level info without modifying state.
|
|
79
100
|
* Includes level, XP, XP to next level, and species-specific title.
|
package/dist/buddy.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// Achievements: milestones that unlock as the user uses kbot. Persisted in buddy.json.
|
|
8
8
|
//
|
|
9
9
|
// Persists buddy name + achievements to ~/.kbot/buddy.json
|
|
10
|
-
import { homedir } from 'node:os';
|
|
10
|
+
import { homedir, hostname } from 'node:os';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
13
13
|
import { createHash } from 'node:crypto';
|
|
@@ -15,6 +15,7 @@ import { createInterface } from 'node:readline';
|
|
|
15
15
|
import { getDreamStatus, getDreamPrompt } from './dream.js';
|
|
16
16
|
import { getExtendedStats, getProfileSummary } from './learning.js';
|
|
17
17
|
import { getToolMetrics } from './tools/index.js';
|
|
18
|
+
import { getCloudToken } from './cloud-sync.js';
|
|
18
19
|
// ── Paths ──
|
|
19
20
|
const KBOT_DIR = join(homedir(), '.kbot');
|
|
20
21
|
const BUDDY_FILE = join(KBOT_DIR, 'buddy.json');
|
|
@@ -930,6 +931,8 @@ export function addBuddyXP(amount) {
|
|
|
930
931
|
config.evolution = evo;
|
|
931
932
|
saveBuddyConfig(config);
|
|
932
933
|
cachedEvolution = evo;
|
|
934
|
+
// Debounced cloud sync — leaderboard update
|
|
935
|
+
scheduleBuddySync();
|
|
933
936
|
const species = resolveSpecies();
|
|
934
937
|
const nextLevel = (newLevel < 3 ? (newLevel + 1) : null);
|
|
935
938
|
const xpToNext = nextLevel !== null ? LEVEL_THRESHOLDS[nextLevel] - evo.xp : null;
|
|
@@ -943,6 +946,99 @@ export function addBuddyXP(amount) {
|
|
|
943
946
|
leveledUp,
|
|
944
947
|
};
|
|
945
948
|
}
|
|
949
|
+
// ── Cloud Sync — Buddy Leaderboard ──
|
|
950
|
+
const ENGINE_URL = 'https://eoxxpyixdieprsxlpwcs.supabase.co/functions/v1/kbot-engine';
|
|
951
|
+
const BUDDY_SYNC_DEBOUNCE_MS = 5 * 60 * 1000; // max once per 5 minutes
|
|
952
|
+
let buddySyncTimer = null;
|
|
953
|
+
let lastBuddySync = 0;
|
|
954
|
+
/** Generate an anonymous device hash from hostname + homedir */
|
|
955
|
+
function getDeviceHash() {
|
|
956
|
+
return createHash('sha256')
|
|
957
|
+
.update(`${hostname()}:${homedir()}`)
|
|
958
|
+
.digest('hex');
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Sync buddy stats to the cloud leaderboard.
|
|
962
|
+
* Anonymous — uses a SHA-256 hash of hostname+homedir, not user identity.
|
|
963
|
+
* Requires a kernel.chat token (cloud sync enabled).
|
|
964
|
+
*/
|
|
965
|
+
export async function syncBuddyToCloud() {
|
|
966
|
+
const token = getCloudToken();
|
|
967
|
+
if (!token)
|
|
968
|
+
return false;
|
|
969
|
+
try {
|
|
970
|
+
const buddy = getBuddy();
|
|
971
|
+
const lvl = getBuddyLevel();
|
|
972
|
+
const achievements = getAchievements();
|
|
973
|
+
const stats = getExtendedStats();
|
|
974
|
+
const unlockedCount = achievements.filter(a => a.unlockedAt !== null).length;
|
|
975
|
+
const res = await fetch(`${ENGINE_URL}/sync`, {
|
|
976
|
+
method: 'POST',
|
|
977
|
+
headers: {
|
|
978
|
+
'Content-Type': 'application/json',
|
|
979
|
+
'Authorization': `Bearer ${token}`,
|
|
980
|
+
},
|
|
981
|
+
body: JSON.stringify({
|
|
982
|
+
action: 'buddy_sync',
|
|
983
|
+
device_hash: getDeviceHash(),
|
|
984
|
+
species: buddy.species,
|
|
985
|
+
level: lvl.level,
|
|
986
|
+
xp: lvl.xp,
|
|
987
|
+
achievement_count: unlockedCount,
|
|
988
|
+
sessions: stats.sessions,
|
|
989
|
+
}),
|
|
990
|
+
signal: AbortSignal.timeout(10_000),
|
|
991
|
+
});
|
|
992
|
+
return res.ok;
|
|
993
|
+
}
|
|
994
|
+
catch {
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
/** Debounced buddy sync — called from addBuddyXP, max once per 5 minutes */
|
|
999
|
+
function scheduleBuddySync() {
|
|
1000
|
+
const now = Date.now();
|
|
1001
|
+
if (now - lastBuddySync < BUDDY_SYNC_DEBOUNCE_MS)
|
|
1002
|
+
return;
|
|
1003
|
+
if (buddySyncTimer)
|
|
1004
|
+
return;
|
|
1005
|
+
buddySyncTimer = setTimeout(() => {
|
|
1006
|
+
buddySyncTimer = null;
|
|
1007
|
+
lastBuddySync = Date.now();
|
|
1008
|
+
syncBuddyToCloud().catch(() => { }); // fire and forget
|
|
1009
|
+
}, 1000); // short delay to batch rapid XP gains
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Fetch the buddy leaderboard from the cloud.
|
|
1013
|
+
* Returns ranked entries sorted by XP descending.
|
|
1014
|
+
*/
|
|
1015
|
+
export async function fetchBuddyLeaderboard(opts) {
|
|
1016
|
+
const token = getCloudToken();
|
|
1017
|
+
if (!token)
|
|
1018
|
+
return [];
|
|
1019
|
+
try {
|
|
1020
|
+
const res = await fetch(`${ENGINE_URL}/sync`, {
|
|
1021
|
+
method: 'POST',
|
|
1022
|
+
headers: {
|
|
1023
|
+
'Content-Type': 'application/json',
|
|
1024
|
+
'Authorization': `Bearer ${token}`,
|
|
1025
|
+
},
|
|
1026
|
+
body: JSON.stringify({
|
|
1027
|
+
action: 'buddy_leaderboard',
|
|
1028
|
+
limit: opts?.limit ?? 50,
|
|
1029
|
+
...(opts?.species ? { species: opts.species } : {}),
|
|
1030
|
+
}),
|
|
1031
|
+
signal: AbortSignal.timeout(10_000),
|
|
1032
|
+
});
|
|
1033
|
+
if (!res.ok)
|
|
1034
|
+
return [];
|
|
1035
|
+
const data = await res.json();
|
|
1036
|
+
return data.leaderboard ?? [];
|
|
1037
|
+
}
|
|
1038
|
+
catch {
|
|
1039
|
+
return [];
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
946
1042
|
/**
|
|
947
1043
|
* Get the buddy's current level info without modifying state.
|
|
948
1044
|
* Includes level, XP, XP to next level, and species-specific title.
|
package/dist/cli.js
CHANGED
|
@@ -27,7 +27,7 @@ import { banner, bannerCompact, bannerAuth, prompt as kbotPrompt, printError, pr
|
|
|
27
27
|
import { checkForUpdate, selfUpdate } from './updater.js';
|
|
28
28
|
import { runTutorial } from './tutorial.js';
|
|
29
29
|
import { syncOnStartup, schedulePush, flushCloudSync, isCloudSyncEnabled, setCloudToken, getCloudToken } from './cloud-sync.js';
|
|
30
|
-
import { getBuddy, getBuddyGreeting, formatBuddyStatus, getBuddyDreamNarration, renameBuddy, buddyChat, getAchievements, getBuddyLevel } from './buddy.js';
|
|
30
|
+
import { getBuddy, getBuddyGreeting, formatBuddyStatus, getBuddyDreamNarration, renameBuddy, buddyChat, getAchievements, getBuddyLevel, fetchBuddyLeaderboard } from './buddy.js';
|
|
31
31
|
import chalk from 'chalk';
|
|
32
32
|
import { createRequire } from 'node:module';
|
|
33
33
|
const __require = createRequire(import.meta.url);
|
|
@@ -1962,16 +1962,22 @@ async function main() {
|
|
|
1962
1962
|
});
|
|
1963
1963
|
program
|
|
1964
1964
|
.command('serve')
|
|
1965
|
-
.description('Start HTTP server — expose all
|
|
1965
|
+
.description('Start HTTP/HTTPS server — expose all tools for kernel.chat, Claude Cowork, or any client')
|
|
1966
1966
|
.option('-p, --port <port>', 'Port to listen on', '7437')
|
|
1967
1967
|
.option('--token <token>', 'Require auth token for all requests')
|
|
1968
1968
|
.option('--computer-use', 'Enable computer use tools')
|
|
1969
|
+
.option('--https', 'Enable HTTPS with auto-generated self-signed cert (~/.kbot/certs/)')
|
|
1970
|
+
.option('--cert <path>', 'Path to TLS certificate file (implies HTTPS)')
|
|
1971
|
+
.option('--key <path>', 'Path to TLS private key file (implies HTTPS)')
|
|
1969
1972
|
.action(async (opts) => {
|
|
1970
1973
|
const { startServe } = await import('./serve.js');
|
|
1971
1974
|
await startServe({
|
|
1972
1975
|
port: parseInt(opts.port, 10),
|
|
1973
1976
|
token: opts.token,
|
|
1974
1977
|
computerUse: opts.computerUse,
|
|
1978
|
+
https: opts.https,
|
|
1979
|
+
cert: opts.cert,
|
|
1980
|
+
key: opts.key,
|
|
1975
1981
|
});
|
|
1976
1982
|
});
|
|
1977
1983
|
program
|
|
@@ -3573,6 +3579,62 @@ async function main() {
|
|
|
3573
3579
|
console.log();
|
|
3574
3580
|
process.exit(0);
|
|
3575
3581
|
});
|
|
3582
|
+
buddyCmd
|
|
3583
|
+
.command('leaderboard')
|
|
3584
|
+
.description('Show the global buddy leaderboard — anonymous rankings across all kbot installs')
|
|
3585
|
+
.option('-l, --limit <n>', 'Number of entries to show', '20')
|
|
3586
|
+
.option('-s, --species <species>', 'Filter by species (fox, owl, cat, robot, ghost, mushroom, octopus, dragon)')
|
|
3587
|
+
.action(async (opts) => {
|
|
3588
|
+
const limit = Math.min(Math.max(parseInt(opts.limit, 10) || 20, 1), 200);
|
|
3589
|
+
const species = opts.species?.toLowerCase();
|
|
3590
|
+
const validSpecies = ['fox', 'owl', 'cat', 'robot', 'ghost', 'mushroom', 'octopus', 'dragon'];
|
|
3591
|
+
if (species && !validSpecies.includes(species)) {
|
|
3592
|
+
printError(`Unknown species "${species}". Valid: ${validSpecies.join(', ')}`);
|
|
3593
|
+
process.exit(1);
|
|
3594
|
+
}
|
|
3595
|
+
printInfo('Fetching leaderboard...');
|
|
3596
|
+
const entries = await fetchBuddyLeaderboard({ limit, species });
|
|
3597
|
+
if (entries.length === 0) {
|
|
3598
|
+
console.log();
|
|
3599
|
+
printWarn('No entries on the leaderboard yet.');
|
|
3600
|
+
printInfo('Use kbot to earn XP and enable cloud sync to appear on the leaderboard.');
|
|
3601
|
+
console.log();
|
|
3602
|
+
process.exit(0);
|
|
3603
|
+
}
|
|
3604
|
+
const SPECIES_ICONS = {
|
|
3605
|
+
fox: '[fox]', owl: '[owl]', cat: '[cat]', robot: '[bot]',
|
|
3606
|
+
ghost: '[gho]', mushroom: '[msh]', octopus: '[oct]', dragon: '[drg]',
|
|
3607
|
+
};
|
|
3608
|
+
const LEVEL_TITLES_SHORT = {
|
|
3609
|
+
0: 'Novice', 1: 'Adept', 2: 'Master', 3: 'Legend',
|
|
3610
|
+
};
|
|
3611
|
+
const header = species
|
|
3612
|
+
? `Buddy Leaderboard — ${species}`
|
|
3613
|
+
: 'Global Buddy Leaderboard';
|
|
3614
|
+
console.log();
|
|
3615
|
+
console.log(` ${chalk.bold(header)}`);
|
|
3616
|
+
console.log(` ${chalk.dim('─'.repeat(56))}`);
|
|
3617
|
+
console.log(` ${chalk.dim('#'.padStart(3))} ${chalk.dim('Species'.padEnd(7))} ${chalk.dim('Level'.padEnd(12))} ${chalk.dim('XP'.padStart(6))} ${chalk.dim('Achv'.padStart(4))} ${chalk.dim('Sessions'.padStart(8))}`);
|
|
3618
|
+
console.log(` ${chalk.dim('─'.repeat(56))}`);
|
|
3619
|
+
for (const entry of entries) {
|
|
3620
|
+
const icon = SPECIES_ICONS[entry.species] || entry.species.slice(0, 5);
|
|
3621
|
+
const title = LEVEL_TITLES_SHORT[entry.level] ?? `L${entry.level}`;
|
|
3622
|
+
const levelStr = `${entry.level} ${title}`;
|
|
3623
|
+
const rankStr = String(entry.rank).padStart(3);
|
|
3624
|
+
const xpStr = String(entry.xp).padStart(6);
|
|
3625
|
+
const achvStr = String(entry.achievement_count).padStart(4);
|
|
3626
|
+
const sessStr = String(entry.sessions).padStart(8);
|
|
3627
|
+
// Highlight top 3
|
|
3628
|
+
const rankColor = entry.rank === 1 ? chalk.hex('#FFD700') :
|
|
3629
|
+
entry.rank === 2 ? chalk.hex('#C0C0C0') :
|
|
3630
|
+
entry.rank === 3 ? chalk.hex('#CD7F32') : chalk.white;
|
|
3631
|
+
console.log(` ${rankColor(rankStr)} ${chalk.hex('#A78BFA')(icon.padEnd(7))} ${levelStr.padEnd(12)} ${chalk.hex('#4ADE80')(xpStr)} ${achvStr} ${chalk.dim(sessStr)}`);
|
|
3632
|
+
}
|
|
3633
|
+
console.log();
|
|
3634
|
+
console.log(` ${chalk.dim(`${entries.length} entries shown`)}`);
|
|
3635
|
+
console.log();
|
|
3636
|
+
process.exit(0);
|
|
3637
|
+
});
|
|
3576
3638
|
buddyCmd.action(() => {
|
|
3577
3639
|
buddyCmd.commands.find(c => c.name() === 'status')?.parse(['', '', 'status']);
|
|
3578
3640
|
});
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Fallback chain (used by tools):
|
|
13
13
|
* 1. AbletonBridge (port 9001) — full browser API
|
|
14
|
-
* 2. KBotBridge (port
|
|
14
|
+
* 2. KBotBridge (port 9997) — kbot's own Remote Script
|
|
15
15
|
* 3. Error with install instructions
|
|
16
16
|
*
|
|
17
17
|
* Follows the same singleton + newline-delimited JSON pattern as AbletonM4L.
|
|
@@ -106,7 +106,7 @@ export declare class AbletonBridgeClient {
|
|
|
106
106
|
getEffectChain(trackIndex: number): Promise<Device[]>;
|
|
107
107
|
}
|
|
108
108
|
/**
|
|
109
|
-
* Lightweight TCP probe for the kbot Remote Script on port
|
|
109
|
+
* Lightweight TCP probe for the kbot Remote Script on port 9997.
|
|
110
110
|
* Uses the same newline-delimited JSON protocol as AbletonM4L.
|
|
111
111
|
*/
|
|
112
112
|
export declare class KBotRemoteClient {
|
|
@@ -139,7 +139,7 @@ export declare class KBotRemoteClient {
|
|
|
139
139
|
*/
|
|
140
140
|
export declare function tryAbletonBridge(): Promise<AbletonBridgeClient | null>;
|
|
141
141
|
/**
|
|
142
|
-
* Try to connect to KBotBridge Remote Script (port
|
|
142
|
+
* Try to connect to KBotBridge Remote Script (port 9997).
|
|
143
143
|
* Returns the connected client or null if unavailable.
|
|
144
144
|
*/
|
|
145
145
|
export declare function tryKBotRemote(): Promise<KBotRemoteClient | null>;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Fallback chain (used by tools):
|
|
13
13
|
* 1. AbletonBridge (port 9001) — full browser API
|
|
14
|
-
* 2. KBotBridge (port
|
|
14
|
+
* 2. KBotBridge (port 9997) — kbot's own Remote Script
|
|
15
15
|
* 3. Error with install instructions
|
|
16
16
|
*
|
|
17
17
|
* Follows the same singleton + newline-delimited JSON pattern as AbletonM4L.
|
|
@@ -268,9 +268,9 @@ export class AbletonBridgeClient {
|
|
|
268
268
|
}));
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
|
-
// ── KBotBridge fallback (port
|
|
271
|
+
// ── KBotBridge fallback (port 9997) ────────────────────────────────────
|
|
272
272
|
/**
|
|
273
|
-
* Lightweight TCP probe for the kbot Remote Script on port
|
|
273
|
+
* Lightweight TCP probe for the kbot Remote Script on port 9997.
|
|
274
274
|
* Uses the same newline-delimited JSON protocol as AbletonM4L.
|
|
275
275
|
*/
|
|
276
276
|
export class KBotRemoteClient {
|
|
@@ -280,7 +280,7 @@ export class KBotRemoteClient {
|
|
|
280
280
|
pending = new Map();
|
|
281
281
|
nextId = 1;
|
|
282
282
|
buffer = '';
|
|
283
|
-
static PORT =
|
|
283
|
+
static PORT = 9997;
|
|
284
284
|
static HOST = '127.0.0.1';
|
|
285
285
|
static TIMEOUT = 10_000;
|
|
286
286
|
static CONNECT_TIMEOUT = 3_000;
|
|
@@ -431,7 +431,7 @@ export async function tryAbletonBridge() {
|
|
|
431
431
|
return ok ? client : null;
|
|
432
432
|
}
|
|
433
433
|
/**
|
|
434
|
-
* Try to connect to KBotBridge Remote Script (port
|
|
434
|
+
* Try to connect to KBotBridge Remote Script (port 9997).
|
|
435
435
|
* Returns the connected client or null if unavailable.
|
|
436
436
|
*/
|
|
437
437
|
export async function tryKBotRemote() {
|
|
@@ -478,7 +478,7 @@ export function formatBridgeError() {
|
|
|
478
478
|
' kbot\'s own Remote Script. Install:',
|
|
479
479
|
' 1. Run `kbot ableton install` or copy KBotBridge to Remote Scripts',
|
|
480
480
|
' 2. Enable in Ableton: Preferences → Link/Tempo/MIDI → Control Surface → KBotBridge',
|
|
481
|
-
' 3. Verify: TCP server starts on localhost:
|
|
481
|
+
' 3. Verify: TCP server starts on localhost:9997',
|
|
482
482
|
'',
|
|
483
483
|
'Both require Ableton Live to be running.',
|
|
484
484
|
].join('\n');
|
|
@@ -124,7 +124,7 @@ export interface BrowserCategory {
|
|
|
124
124
|
child_count: number;
|
|
125
125
|
}
|
|
126
126
|
/**
|
|
127
|
-
* Client for the KBotBridge Remote Script (TCP
|
|
127
|
+
* Client for the KBotBridge Remote Script (TCP 9997).
|
|
128
128
|
*
|
|
129
129
|
* This is separate from the M4L bridge (9999) because the Browser API
|
|
130
130
|
* (browser.load_item) is ONLY available from Python Remote Scripts,
|
|
@@ -146,7 +146,7 @@ export declare class AbletonBrowserBridge {
|
|
|
146
146
|
private constructor();
|
|
147
147
|
static getInstance(): AbletonBrowserBridge;
|
|
148
148
|
/**
|
|
149
|
-
* Connect to the KBotBridge Remote Script on port
|
|
149
|
+
* Connect to the KBotBridge Remote Script on port 9997.
|
|
150
150
|
* Returns true if connected and the bridge responds to ping.
|
|
151
151
|
*/
|
|
152
152
|
connect(): Promise<boolean>;
|
|
@@ -195,12 +195,12 @@ export declare class AbletonBrowserBridge {
|
|
|
195
195
|
*/
|
|
196
196
|
export declare function ensureM4L(): Promise<AbletonM4L>;
|
|
197
197
|
/**
|
|
198
|
-
* Get a connected Browser bridge instance (KBotBridge Remote Script on port
|
|
198
|
+
* Get a connected Browser bridge instance (KBotBridge Remote Script on port 9997).
|
|
199
199
|
* Throws if not available.
|
|
200
200
|
*/
|
|
201
201
|
export declare function ensureBrowserBridge(): Promise<AbletonBrowserBridge>;
|
|
202
202
|
/**
|
|
203
|
-
* Connect to both M4L bridge (9999) and Browser bridge (
|
|
203
|
+
* Connect to both M4L bridge (9999) and Browser bridge (9997).
|
|
204
204
|
* Returns whichever connections succeed. At least one must connect.
|
|
205
205
|
*/
|
|
206
206
|
export declare function connectBrowser(): Promise<{
|
|
@@ -302,7 +302,7 @@ export class AbletonM4L {
|
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
304
|
/**
|
|
305
|
-
* Client for the KBotBridge Remote Script (TCP
|
|
305
|
+
* Client for the KBotBridge Remote Script (TCP 9997).
|
|
306
306
|
*
|
|
307
307
|
* This is separate from the M4L bridge (9999) because the Browser API
|
|
308
308
|
* (browser.load_item) is ONLY available from Python Remote Scripts,
|
|
@@ -318,7 +318,7 @@ export class AbletonBrowserBridge {
|
|
|
318
318
|
pending = new Map();
|
|
319
319
|
nextId = 1;
|
|
320
320
|
buffer = '';
|
|
321
|
-
static PORT =
|
|
321
|
+
static PORT = 9997;
|
|
322
322
|
static HOST = '127.0.0.1';
|
|
323
323
|
static TIMEOUT = 15_000; // Browser operations can be slow
|
|
324
324
|
constructor() { }
|
|
@@ -329,7 +329,7 @@ export class AbletonBrowserBridge {
|
|
|
329
329
|
return AbletonBrowserBridge.instance;
|
|
330
330
|
}
|
|
331
331
|
/**
|
|
332
|
-
* Connect to the KBotBridge Remote Script on port
|
|
332
|
+
* Connect to the KBotBridge Remote Script on port 9997.
|
|
333
333
|
* Returns true if connected and the bridge responds to ping.
|
|
334
334
|
*/
|
|
335
335
|
async connect() {
|
|
@@ -518,7 +518,7 @@ export async function ensureM4L() {
|
|
|
518
518
|
return m4l;
|
|
519
519
|
}
|
|
520
520
|
/**
|
|
521
|
-
* Get a connected Browser bridge instance (KBotBridge Remote Script on port
|
|
521
|
+
* Get a connected Browser bridge instance (KBotBridge Remote Script on port 9997).
|
|
522
522
|
* Throws if not available.
|
|
523
523
|
*/
|
|
524
524
|
export async function ensureBrowserBridge() {
|
|
@@ -531,13 +531,13 @@ export async function ensureBrowserBridge() {
|
|
|
531
531
|
'Make sure:\n' +
|
|
532
532
|
'1. Ableton Live is running\n' +
|
|
533
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
|
|
534
|
+
'3. Ableton status bar shows "KBotBridge: Listening on port 9997"\n\n' +
|
|
535
535
|
'To install: kbot ableton install-bridge\n');
|
|
536
536
|
}
|
|
537
537
|
return bridge;
|
|
538
538
|
}
|
|
539
539
|
/**
|
|
540
|
-
* Connect to both M4L bridge (9999) and Browser bridge (
|
|
540
|
+
* Connect to both M4L bridge (9999) and Browser bridge (9997).
|
|
541
541
|
* Returns whichever connections succeed. At least one must connect.
|
|
542
542
|
*/
|
|
543
543
|
export async function connectBrowser() {
|
|
@@ -582,7 +582,7 @@ export function formatBrowserBridgeError() {
|
|
|
582
582
|
'5. Close Preferences',
|
|
583
583
|
'',
|
|
584
584
|
'This runs alongside the M4L bridge — they use different ports:',
|
|
585
|
-
'- KBotBridge: TCP
|
|
585
|
+
'- KBotBridge: TCP 9997 (Browser API, device loading)',
|
|
586
586
|
'- M4L Bridge: TCP 9999 (LOM access, clips, mixing)',
|
|
587
587
|
].join('\n');
|
|
588
588
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* install-remote-script.ts — Install KBotBridge Remote Script into Ableton Live
|
|
3
3
|
*
|
|
4
4
|
* Copies the KBotBridge Python Remote Script to Ableton's User Library,
|
|
5
|
-
* enabling the Browser API bridge on TCP port
|
|
5
|
+
* enabling the Browser API bridge on TCP port 9997.
|
|
6
6
|
*
|
|
7
7
|
* The Remote Script exposes Ableton's browser.load_item() API, which is
|
|
8
8
|
* ONLY available from Python Remote Scripts (not from Max for Live).
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* install-remote-script.ts — Install KBotBridge Remote Script into Ableton Live
|
|
3
3
|
*
|
|
4
4
|
* Copies the KBotBridge Python Remote Script to Ableton's User Library,
|
|
5
|
-
* enabling the Browser API bridge on TCP port
|
|
5
|
+
* enabling the Browser API bridge on TCP port 9997.
|
|
6
6
|
*
|
|
7
7
|
* The Remote Script exposes Ableton's browser.load_item() API, which is
|
|
8
8
|
* ONLY available from Python Remote Scripts (not from Max for Live).
|
|
@@ -84,11 +84,11 @@ export async function installKBotBridge() {
|
|
|
84
84
|
log(' 5. Close Preferences');
|
|
85
85
|
log('');
|
|
86
86
|
log('Verify:');
|
|
87
|
-
log(' - Ableton status bar shows "KBotBridge: Listening on port
|
|
88
|
-
log(' - Run: echo \'{"id":1,"action":"ping"}\\n\' | nc localhost
|
|
87
|
+
log(' - Ableton status bar shows "KBotBridge: Listening on port 9997"');
|
|
88
|
+
log(' - Run: echo \'{"id":1,"action":"ping"}\\n\' | nc localhost 9997');
|
|
89
89
|
log('');
|
|
90
90
|
log('KBotBridge runs alongside AbletonOSC — they use different ports:');
|
|
91
|
-
log(' - KBotBridge: TCP
|
|
91
|
+
log(' - KBotBridge: TCP 9997 (Browser API, device loading)');
|
|
92
92
|
log(' - M4L Bridge: TCP 9999 (LOM access, clips, mixing)');
|
|
93
93
|
log(' - AbletonOSC: UDP 11000/11001 (OSC, legacy)');
|
|
94
94
|
return lines.join('\n');
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mobile-mcp-client.ts — kbot <-> mobile-mcp integration
|
|
3
|
+
*
|
|
4
|
+
* Singleton client that manages the mobile-mcp server process lifecycle.
|
|
5
|
+
* Communicates via MCP protocol over stdio transport.
|
|
6
|
+
* Auto-installs @mobilenext/mobile-mcp via npm if not present.
|
|
7
|
+
*
|
|
8
|
+
* mobile-mcp provides native accessibility-tree-based automation for
|
|
9
|
+
* iOS and Android devices connected via USB or WiFi.
|
|
10
|
+
*
|
|
11
|
+
* @see https://github.com/mobile-next/mobile-mcp
|
|
12
|
+
*/
|
|
13
|
+
export interface MobileDevice {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
platform: 'ios' | 'android';
|
|
17
|
+
type: 'real' | 'simulator' | 'emulator';
|
|
18
|
+
version: string;
|
|
19
|
+
state: 'online' | 'offline';
|
|
20
|
+
}
|
|
21
|
+
export interface MobileElement {
|
|
22
|
+
type: string;
|
|
23
|
+
text?: string;
|
|
24
|
+
label?: string;
|
|
25
|
+
name?: string;
|
|
26
|
+
value?: string;
|
|
27
|
+
identifier?: string;
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
}
|
|
33
|
+
export interface MobileScreenSize {
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
}
|
|
37
|
+
export declare class MobileMCPClient {
|
|
38
|
+
private static instance;
|
|
39
|
+
private process;
|
|
40
|
+
private messageId;
|
|
41
|
+
private pending;
|
|
42
|
+
private buffer;
|
|
43
|
+
private initialized;
|
|
44
|
+
private activeDeviceId;
|
|
45
|
+
static getInstance(): MobileMCPClient;
|
|
46
|
+
/** Whether the MCP server process is running and initialized */
|
|
47
|
+
get isConnected(): boolean;
|
|
48
|
+
/** The device ID currently being controlled */
|
|
49
|
+
get currentDeviceId(): string | null;
|
|
50
|
+
/** Start the mobile-mcp server process and perform MCP handshake */
|
|
51
|
+
start(): Promise<void>;
|
|
52
|
+
/** Stop the mobile-mcp server process */
|
|
53
|
+
stop(): void;
|
|
54
|
+
private parseMessages;
|
|
55
|
+
private sendRequest;
|
|
56
|
+
private sendNotification;
|
|
57
|
+
/** Call a tool on the mobile-mcp server */
|
|
58
|
+
callTool(toolName: string, args: Record<string, unknown>): Promise<unknown>;
|
|
59
|
+
/** Extract text content from an MCP tool result */
|
|
60
|
+
extractText(result: unknown): string;
|
|
61
|
+
/** Extract image content (base64) from an MCP tool result */
|
|
62
|
+
extractImage(result: unknown): {
|
|
63
|
+
data: string;
|
|
64
|
+
mimeType: string;
|
|
65
|
+
} | null;
|
|
66
|
+
/** List all available devices */
|
|
67
|
+
listDevices(): Promise<MobileDevice[]>;
|
|
68
|
+
/** Set the active device for subsequent operations */
|
|
69
|
+
setActiveDevice(deviceId: string): void;
|
|
70
|
+
/** Get the active device ID, throwing if none set */
|
|
71
|
+
private requireDevice;
|
|
72
|
+
/** List apps on the active device */
|
|
73
|
+
listApps(deviceId?: string): Promise<string>;
|
|
74
|
+
/** Launch an app by bundle ID */
|
|
75
|
+
launchApp(packageName: string, deviceId?: string): Promise<string>;
|
|
76
|
+
/** Take a screenshot, returns base64 image data */
|
|
77
|
+
takeScreenshot(deviceId?: string): Promise<{
|
|
78
|
+
data: string;
|
|
79
|
+
mimeType: string;
|
|
80
|
+
} | string>;
|
|
81
|
+
/** Save screenshot to a file */
|
|
82
|
+
saveScreenshot(saveTo: string, deviceId?: string): Promise<string>;
|
|
83
|
+
/** List UI elements on screen via accessibility tree */
|
|
84
|
+
listElements(deviceId?: string): Promise<string>;
|
|
85
|
+
/** Tap at coordinates */
|
|
86
|
+
tap(x: number, y: number, deviceId?: string): Promise<string>;
|
|
87
|
+
/** Swipe on screen */
|
|
88
|
+
swipe(direction: 'up' | 'down' | 'left' | 'right', opts?: {
|
|
89
|
+
x?: number;
|
|
90
|
+
y?: number;
|
|
91
|
+
distance?: number;
|
|
92
|
+
deviceId?: string;
|
|
93
|
+
}): Promise<string>;
|
|
94
|
+
/** Type text */
|
|
95
|
+
typeText(text: string, submit?: boolean, deviceId?: string): Promise<string>;
|
|
96
|
+
/** Press a device button */
|
|
97
|
+
pressButton(button: 'HOME' | 'BACK' | 'VOLUME_UP' | 'VOLUME_DOWN' | 'ENTER', deviceId?: string): Promise<string>;
|
|
98
|
+
/** Get screen size */
|
|
99
|
+
getScreenSize(deviceId?: string): Promise<string>;
|
|
100
|
+
/** Open a URL in the device browser */
|
|
101
|
+
openUrl(url: string, deviceId?: string): Promise<string>;
|
|
102
|
+
/** Get device orientation */
|
|
103
|
+
getOrientation(deviceId?: string): Promise<string>;
|
|
104
|
+
/** Terminate an app */
|
|
105
|
+
terminateApp(packageName: string, deviceId?: string): Promise<string>;
|
|
106
|
+
/** Double tap at coordinates */
|
|
107
|
+
doubleTap(x: number, y: number, deviceId?: string): Promise<string>;
|
|
108
|
+
/** Long press at coordinates */
|
|
109
|
+
longPress(x: number, y: number, duration?: number, deviceId?: string): Promise<string>;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=mobile-mcp-client.d.ts.map
|