@agentdeck/bridge 1.0.2 → 1.0.4
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/assets/fm-helper/agentdeck-fm-helper +0 -0
- package/dist/apme/collector.d.ts.map +1 -1
- package/dist/apme/collector.js +5 -2
- package/dist/apme/collector.js.map +1 -1
- package/dist/apme/runner.d.ts +11 -0
- package/dist/apme/runner.d.ts.map +1 -1
- package/dist/apme/runner.js +60 -4
- package/dist/apme/runner.js.map +1 -1
- package/dist/apme/settings.d.ts +10 -0
- package/dist/apme/settings.d.ts.map +1 -1
- package/dist/apme/settings.js +14 -0
- package/dist/apme/settings.js.map +1 -1
- package/dist/awaiting-overlay.d.ts +44 -10
- package/dist/awaiting-overlay.d.ts.map +1 -1
- package/dist/awaiting-overlay.js +96 -28
- package/dist/awaiting-overlay.js.map +1 -1
- package/dist/bridge-core.d.ts +10 -0
- package/dist/bridge-core.d.ts.map +1 -1
- package/dist/bridge-core.js +47 -12
- package/dist/bridge-core.js.map +1 -1
- package/dist/card-feed.d.ts +107 -0
- package/dist/card-feed.d.ts.map +1 -0
- package/dist/card-feed.js +277 -0
- package/dist/card-feed.js.map +1 -0
- package/dist/card-modules.d.ts +87 -0
- package/dist/card-modules.d.ts.map +1 -0
- package/dist/card-modules.js +179 -0
- package/dist/card-modules.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +232 -69
- package/dist/cli.js.map +1 -1
- package/dist/daemon-server.d.ts.map +1 -1
- package/dist/daemon-server.js +1008 -47
- package/dist/daemon-server.js.map +1 -1
- package/dist/daemon-ws-client.d.ts +2 -1
- package/dist/daemon-ws-client.d.ts.map +1 -1
- package/dist/daemon-ws-client.js +2 -1
- package/dist/daemon-ws-client.js.map +1 -1
- package/dist/daemon.js +1 -1
- package/dist/device-photo.d.ts +86 -0
- package/dist/device-photo.d.ts.map +1 -0
- package/dist/device-photo.js +200 -0
- package/dist/device-photo.js.map +1 -0
- package/dist/device-voice-reply.d.ts +124 -0
- package/dist/device-voice-reply.d.ts.map +1 -0
- package/dist/device-voice-reply.js +236 -0
- package/dist/device-voice-reply.js.map +1 -0
- package/dist/device-voice.d.ts +56 -0
- package/dist/device-voice.d.ts.map +1 -0
- package/dist/device-voice.js +134 -0
- package/dist/device-voice.js.map +1 -0
- package/dist/esp32-serial.d.ts +40 -0
- package/dist/esp32-serial.d.ts.map +1 -1
- package/dist/esp32-serial.js +109 -13
- package/dist/esp32-serial.js.map +1 -1
- package/dist/foundation-models-helper.d.ts +30 -0
- package/dist/foundation-models-helper.d.ts.map +1 -1
- package/dist/foundation-models-helper.js +45 -0
- package/dist/foundation-models-helper.js.map +1 -1
- package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts.map +1 -1
- package/dist/idotmatrix/idotmatrix-daemon-sync.js +5 -18
- package/dist/idotmatrix/idotmatrix-daemon-sync.js.map +1 -1
- package/dist/idotmatrix/idotmatrix-discover.d.ts.map +1 -1
- package/dist/idotmatrix/idotmatrix-discover.js +6 -14
- package/dist/idotmatrix/idotmatrix-discover.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/mdns.d.ts +11 -0
- package/dist/mdns.d.ts.map +1 -1
- package/dist/mdns.js +13 -0
- package/dist/mdns.js.map +1 -1
- package/dist/observed-inject.d.ts +119 -0
- package/dist/observed-inject.d.ts.map +1 -0
- package/dist/observed-inject.js +405 -0
- package/dist/observed-inject.js.map +1 -0
- package/dist/passive-observer.d.ts +14 -0
- package/dist/passive-observer.d.ts.map +1 -1
- package/dist/passive-observer.js +35 -5
- package/dist/passive-observer.js.map +1 -1
- package/dist/peripheral-mapping.d.ts +65 -0
- package/dist/peripheral-mapping.d.ts.map +1 -0
- package/dist/peripheral-mapping.js +115 -0
- package/dist/peripheral-mapping.js.map +1 -0
- package/dist/python-ble-runtime.d.ts +62 -0
- package/dist/python-ble-runtime.d.ts.map +1 -0
- package/dist/python-ble-runtime.js +215 -0
- package/dist/python-ble-runtime.js.map +1 -0
- package/dist/session-aggregator.d.ts +4 -1
- package/dist/session-aggregator.d.ts.map +1 -1
- package/dist/session-aggregator.js +5 -5
- package/dist/session-aggregator.js.map +1 -1
- package/dist/session-id-resolve.d.ts +17 -0
- package/dist/session-id-resolve.d.ts.map +1 -0
- package/dist/session-id-resolve.js +35 -0
- package/dist/session-id-resolve.js.map +1 -0
- package/dist/session-transcript-timeline.d.ts +7 -0
- package/dist/session-transcript-timeline.d.ts.map +1 -1
- package/dist/session-transcript-timeline.js +16 -6
- package/dist/session-transcript-timeline.js.map +1 -1
- package/dist/timebox/timebox-daemon-sync.d.ts.map +1 -1
- package/dist/timebox/timebox-daemon-sync.js +5 -20
- package/dist/timebox/timebox-daemon-sync.js.map +1 -1
- package/dist/timebox/timebox-discover.d.ts.map +1 -1
- package/dist/timebox/timebox-discover.js +6 -15
- package/dist/timebox/timebox-discover.js.map +1 -1
- package/dist/timeline-store.js +2 -2
- package/dist/timeline-store.js.map +1 -1
- package/dist/usage-event.d.ts.map +1 -1
- package/dist/usage-event.js +14 -7
- package/dist/usage-event.js.map +1 -1
- package/dist/voice.d.ts +13 -10
- package/dist/voice.d.ts.map +1 -1
- package/dist/voice.js +26 -272
- package/dist/voice.js.map +1 -1
- package/dist/ws-server.d.ts +14 -0
- package/dist/ws-server.d.ts.map +1 -1
- package/dist/ws-server.js +66 -1
- package/dist/ws-server.js.map +1 -1
- package/fm-helper/AgentDeckFMHelper.swift +303 -0
- package/package.json +8 -4
- package/python/requirements-ble.txt +5 -0
- package/src/idotmatrix/brightness.py +40 -0
- package/src/idotmatrix/scan.py +35 -0
- package/src/idotmatrix/sync.py +400 -0
- package/src/pysync/matrix_sync_common.py +89 -0
- package/src/timebox/scan_ble.py +52 -0
- package/src/timebox/sync_ble.py +394 -0
- package/dist/d200h/hid-protocol.d.ts +0 -62
- package/dist/d200h/hid-protocol.d.ts.map +0 -1
- package/dist/d200h/hid-protocol.js +0 -124
- package/dist/d200h/hid-protocol.js.map +0 -1
- package/dist/d200h/image-renderer.d.ts +0 -24
- package/dist/d200h/image-renderer.d.ts.map +0 -1
- package/dist/d200h/image-renderer.js +0 -321
- package/dist/d200h/image-renderer.js.map +0 -1
- package/dist/hook-migration.d.ts +0 -2
- package/dist/hook-migration.d.ts.map +0 -1
- package/dist/hook-migration.js +0 -104
- package/dist/hook-migration.js.map +0 -1
- package/dist/modules/d200h-module.d.ts +0 -65
- package/dist/modules/d200h-module.d.ts.map +0 -1
- package/dist/modules/d200h-module.js +0 -495
- package/dist/modules/d200h-module.js.map +0 -1
- package/dist/modules/d200h-renderer.d.ts +0 -7
- package/dist/modules/d200h-renderer.d.ts.map +0 -1
- package/dist/modules/d200h-renderer.js +0 -321
- package/dist/modules/d200h-renderer.js.map +0 -1
- package/dist/modules/trmnl-module.d.ts +0 -38
- package/dist/modules/trmnl-module.d.ts.map +0 -1
- package/dist/modules/trmnl-module.js +0 -111
- package/dist/modules/trmnl-module.js.map +0 -1
- package/dist/trmnl/byos-server.d.ts +0 -39
- package/dist/trmnl/byos-server.d.ts.map +0 -1
- package/dist/trmnl/byos-server.js +0 -301
- package/dist/trmnl/byos-server.js.map +0 -1
- package/dist/trmnl/frame-cache.d.ts +0 -37
- package/dist/trmnl/frame-cache.d.ts.map +0 -1
- package/dist/trmnl/frame-cache.js +0 -157
- package/dist/trmnl/frame-cache.js.map +0 -1
- package/dist/trmnl/image-renderer.d.ts +0 -27
- package/dist/trmnl/image-renderer.d.ts.map +0 -1
- package/dist/trmnl/image-renderer.js +0 -235
- package/dist/trmnl/image-renderer.js.map +0 -1
- package/dist/trmnl/trmnl-settings.d.ts +0 -96
- package/dist/trmnl/trmnl-settings.d.ts.map +0 -1
- package/dist/trmnl/trmnl-settings.js +0 -177
- package/dist/trmnl/trmnl-settings.js.map +0 -1
- package/dist/trmnl/trmnl-telemetry.d.ts +0 -47
- package/dist/trmnl/trmnl-telemetry.d.ts.map +0 -1
- package/dist/trmnl/trmnl-telemetry.js +0 -56
- package/dist/trmnl/trmnl-telemetry.js.map +0 -1
- package/dist/whisper-server-manager.d.ts +0 -19
- package/dist/whisper-server-manager.d.ts.map +0 -1
- package/dist/whisper-server-manager.js +0 -171
- package/dist/whisper-server-manager.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'url';
|
|
|
8
8
|
import { createRequire } from 'module';
|
|
9
9
|
import { request } from 'http';
|
|
10
10
|
import { BRIDGE_WS_PORT } from './types.js';
|
|
11
|
+
import { ensureBleRuntime, getBleRuntimeStatus } from './python-ble-runtime.js';
|
|
11
12
|
import { TASK_NAME, installWindowsTask, taskExists, runWindowsTask, endWindowsTask, deleteWindowsTask, } from './windows-service.js';
|
|
12
13
|
const require = createRequire(import.meta.url);
|
|
13
14
|
const packageJson = require('../package.json');
|
|
@@ -135,19 +136,30 @@ async function isDaemonPort(port) {
|
|
|
135
136
|
return false;
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
|
-
function resolveTimeboxSyncPaths() {
|
|
139
|
-
const distPath = dirname(fileURLToPath(import.meta.url));
|
|
140
|
-
const projectRoot = join(distPath, '..', '..');
|
|
141
|
-
const timeboxDir = join(projectRoot, 'bridge', 'src', 'timebox');
|
|
142
|
-
return {
|
|
143
|
-
python: join(projectRoot, '.venv', 'bin', 'python'),
|
|
144
|
-
bleScript: join(timeboxDir, 'sync_ble.py'),
|
|
145
|
-
scanScript: join(timeboxDir, 'scan_ble.py'),
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
139
|
function projectRootPath() {
|
|
149
140
|
return join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
150
141
|
}
|
|
142
|
+
async function runBlePython(python, args, captureStdout = false) {
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
const child = spawn(python, args, captureStdout ? { stdio: ['ignore', 'pipe', 'inherit'] } : { stdio: 'inherit' });
|
|
145
|
+
let stdout = '';
|
|
146
|
+
child.stdout?.on('data', (data) => {
|
|
147
|
+
stdout += data.toString();
|
|
148
|
+
});
|
|
149
|
+
child.once('error', reject);
|
|
150
|
+
child.once('close', (code) => resolve({ code, stdout }));
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function prepareBleRuntime() {
|
|
154
|
+
try {
|
|
155
|
+
return ensureBleRuntime({ log });
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
log(`BLE support unavailable: ${err instanceof Error ? err.message : String(err)}`);
|
|
159
|
+
process.exitCode = 1;
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
151
163
|
const ESP32_OTA_BOARDS = [
|
|
152
164
|
{ board: 'inkdeck', env: 'inkdeck', aliases: [] },
|
|
153
165
|
{ board: 'ulanzi_tc001', env: 'led8x32', aliases: ['led8x32'] },
|
|
@@ -156,6 +168,8 @@ const ESP32_OTA_BOARDS = [
|
|
|
156
168
|
{ board: 'ips_35', env: 'ips35', aliases: ['ips35'] },
|
|
157
169
|
{ board: '86box', env: 'box_86', aliases: ['box_86', 'box_40'] },
|
|
158
170
|
{ board: 'ips_10', env: 'ips10', aliases: ['ips10', 'ips_101'] },
|
|
171
|
+
{ board: 't_embed', env: 't_embed', aliases: ['tembed', 'knob'] },
|
|
172
|
+
{ board: 't_display_pro', env: 't_display_pro', aliases: ['tdisplaypro', 'ticker', 's3pro'] },
|
|
159
173
|
];
|
|
160
174
|
// alias/board/env-key → { env, board }. Built from the SSOT above.
|
|
161
175
|
export const ESP32_OTA_BY_TARGET = {};
|
|
@@ -712,6 +726,45 @@ program
|
|
|
712
726
|
}
|
|
713
727
|
});
|
|
714
728
|
// ===== Device commands =====
|
|
729
|
+
// Bench test for the playback half of the voice loop. The production path only
|
|
730
|
+
// speaks after a dictation arms a board and its session finishes a turn, which
|
|
731
|
+
// is no way to check whether a freshly flashed amplifier works.
|
|
732
|
+
program
|
|
733
|
+
.command('speak <board> <text>')
|
|
734
|
+
.description('Speak text on a board\'s speaker (voice playback bench test)')
|
|
735
|
+
.option('-p, --port <port>', 'Bridge server port')
|
|
736
|
+
.option('-l, --locale <bcp47>', 'TTS locale, e.g. ko-KR (default: settings voice.locale)')
|
|
737
|
+
.option('-f, --force', 'Stream even if the board does not advertise audio_out')
|
|
738
|
+
.action(async (board, text, opts) => {
|
|
739
|
+
const { readDaemonInfo, findDaemonPort } = await import('./session-registry.js');
|
|
740
|
+
const info = readDaemonInfo();
|
|
741
|
+
const port = opts.port != null
|
|
742
|
+
? parseInt(opts.port, 10)
|
|
743
|
+
: (info?.httpPort ?? info?.port ?? findDaemonPort() ?? BRIDGE_WS_PORT);
|
|
744
|
+
try {
|
|
745
|
+
const res = await fetch(`http://127.0.0.1:${port}/voice/speak`, {
|
|
746
|
+
method: 'POST',
|
|
747
|
+
headers: { 'Content-Type': 'application/json' },
|
|
748
|
+
body: JSON.stringify({ board, text, force: opts.force === true, locale: opts.locale }),
|
|
749
|
+
// Synthesis plus paced playback: a 700-char reply runs well past a
|
|
750
|
+
// minute at speech rate, and the daemon answers only once it is done.
|
|
751
|
+
signal: AbortSignal.timeout(180_000),
|
|
752
|
+
});
|
|
753
|
+
const data = await res.json();
|
|
754
|
+
if (data.error) {
|
|
755
|
+
log(`✗ ${data.error}`);
|
|
756
|
+
process.exitCode = 1;
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
log(data.spoke ? `✓ spoke on ${board} — ${data.detail}` : `✗ ${data.detail}`);
|
|
760
|
+
if (!data.spoke)
|
|
761
|
+
process.exitCode = 1;
|
|
762
|
+
}
|
|
763
|
+
catch (err) {
|
|
764
|
+
log(`✗ daemon not reachable on port ${port}: ${String(err)}`);
|
|
765
|
+
process.exitCode = 1;
|
|
766
|
+
}
|
|
767
|
+
});
|
|
715
768
|
program
|
|
716
769
|
.command('devices')
|
|
717
770
|
.description('Show connected devices (WebSocket, ESP32, Pixoo, Timebox, ADB)')
|
|
@@ -780,6 +833,26 @@ program
|
|
|
780
833
|
total += devices.length;
|
|
781
834
|
}
|
|
782
835
|
}
|
|
836
|
+
else if (d.type === 'card-feed') {
|
|
837
|
+
// Pull-sync (M6) clients. A battery board is asleep almost always, so
|
|
838
|
+
// "last pull + observed interval" is its liveness — not a socket. The
|
|
839
|
+
// interval vs. the advertised cadence is the timer-wake evidence.
|
|
840
|
+
const clients = (d.clients ?? []);
|
|
841
|
+
if (clients.length) {
|
|
842
|
+
lines.push(` Card feed ${clients.length} pull client${clients.length !== 1 ? 's' : ''}`);
|
|
843
|
+
for (const c of clients) {
|
|
844
|
+
const who = c.board ? `${c.board} @ ${c.client}` : c.client;
|
|
845
|
+
const ago = `${Math.round((Date.now() - c.lastPullAt) / 1000)}s ago`;
|
|
846
|
+
const cadence = c.lastIntervalSec !== undefined
|
|
847
|
+
? `, last gap ${c.lastIntervalSec}s vs ${c.lastNextPullSec}s asked`
|
|
848
|
+
: '';
|
|
849
|
+
const honoured = c.pulls > 1
|
|
850
|
+
? ` [${c.cadenceHonouredCount}/${c.pulls - 1} on cadence]`
|
|
851
|
+
: '';
|
|
852
|
+
lines.push(` ${who} — ${c.pulls} pull${c.pulls !== 1 ? 's' : ''}, last ${ago}${cadence}${honoured}`);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
783
856
|
else if (d.type === 'pixoo') {
|
|
784
857
|
// Node daemon: { details: [...] }, Swift daemon: { deviceIps: [...] }
|
|
785
858
|
if (d.details) {
|
|
@@ -958,6 +1031,38 @@ program
|
|
|
958
1031
|
// qrcode not available
|
|
959
1032
|
}
|
|
960
1033
|
});
|
|
1034
|
+
program
|
|
1035
|
+
.command('inject-test')
|
|
1036
|
+
.description('Test observed-answer injection into a terminal or app (tuning aid)')
|
|
1037
|
+
.option('--tty <tty>', 'Controlling terminal, e.g. ttys007')
|
|
1038
|
+
.option('--app <name>', 'Hosting app for GUI sessions, e.g. Claude / ChatGPT')
|
|
1039
|
+
.option('--label <label>', 'Option label — app hosts press the matching button first')
|
|
1040
|
+
.option('-i, --index <n>', 'Zero-based option index (Down presses)', '0')
|
|
1041
|
+
.option('--text <text>', 'Type this text and submit it, instead of picking an option')
|
|
1042
|
+
.action(async (opts) => {
|
|
1043
|
+
if (!opts.tty && !opts.app) {
|
|
1044
|
+
console.error('Give --tty <ttysNNN> or --app <Name>. Find a session\'s tty with:');
|
|
1045
|
+
console.error(" ps -eo pid=,tty=,command= | grep -E ' claude| codex'");
|
|
1046
|
+
process.exit(1);
|
|
1047
|
+
}
|
|
1048
|
+
const { injectObservedSelection, injectObservedText } = await import('./observed-inject.js');
|
|
1049
|
+
const index = parseInt(opts.index, 10) || 0;
|
|
1050
|
+
// --text exercises the dictation path (voice → prompt), which submits a
|
|
1051
|
+
// whole line; without it only the option-picking path was testable, and
|
|
1052
|
+
// "the text arrived but nothing was submitted" had no repro.
|
|
1053
|
+
const result = opts.text
|
|
1054
|
+
? await injectObservedText({ tty: opts.tty, appName: opts.app }, opts.text)
|
|
1055
|
+
: await injectObservedSelection({ tty: opts.tty, appName: opts.app, label: opts.label }, index);
|
|
1056
|
+
if (result.ok) {
|
|
1057
|
+
console.log(opts.text
|
|
1058
|
+
? `Delivered via ${result.via} (text).`
|
|
1059
|
+
: `Delivered via ${result.via} (index ${index}).`);
|
|
1060
|
+
}
|
|
1061
|
+
else {
|
|
1062
|
+
console.error(`Not delivered: ${result.reason}`);
|
|
1063
|
+
process.exit(2);
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
961
1066
|
program
|
|
962
1067
|
.command('diag')
|
|
963
1068
|
.description('Generate diagnostic dump')
|
|
@@ -1056,35 +1161,51 @@ task
|
|
|
1056
1161
|
.action(async (opts) => {
|
|
1057
1162
|
await postTaskClose({ signal: 'manual', outcome: 'abandoned', sessionId: opts.session });
|
|
1058
1163
|
});
|
|
1164
|
+
// ===== Optional Python BLE runtime =====
|
|
1165
|
+
const ble = program.command('ble').description('Prepare optional Python support for BLE display devices');
|
|
1166
|
+
ble
|
|
1167
|
+
.command('setup')
|
|
1168
|
+
.description('Create the private BLE environment and install its Python dependencies')
|
|
1169
|
+
.action(() => {
|
|
1170
|
+
const runtime = prepareBleRuntime();
|
|
1171
|
+
if (runtime)
|
|
1172
|
+
log(`BLE support ready (${runtime.python}).`);
|
|
1173
|
+
});
|
|
1174
|
+
ble
|
|
1175
|
+
.command('status')
|
|
1176
|
+
.description('Show whether optional BLE support is ready')
|
|
1177
|
+
.action(() => {
|
|
1178
|
+
const status = getBleRuntimeStatus();
|
|
1179
|
+
if (status.ready) {
|
|
1180
|
+
log(`BLE support ready (${status.python}).`);
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
log(`BLE support is not ready: ${status.reason}. Run \`agentdeck ble setup\`.`);
|
|
1184
|
+
process.exitCode = 1;
|
|
1185
|
+
});
|
|
1059
1186
|
// ===== iDotMatrix commands =====
|
|
1060
1187
|
const idotmatrix = program.command('idotmatrix').description('Manage iDotMatrix BLE pixel display devices');
|
|
1061
1188
|
idotmatrix
|
|
1062
1189
|
.command('scan')
|
|
1063
1190
|
.description('Discover iDotMatrix devices via BLE')
|
|
1064
1191
|
.action(async () => {
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
const distPath = dirname(fileURLToPath(import.meta.url));
|
|
1069
|
-
const projectRoot = join(distPath, '..', '..');
|
|
1070
|
-
const venvPython = join(projectRoot, '.venv', 'bin', 'python');
|
|
1071
|
-
const scanScript = join(projectRoot, 'bridge', 'src', 'idotmatrix', 'scan.py');
|
|
1192
|
+
const runtime = prepareBleRuntime();
|
|
1193
|
+
if (!runtime)
|
|
1194
|
+
return;
|
|
1072
1195
|
log('Scanning for BLE devices (5 seconds)...');
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
py.stdout.on('data', (data) => {
|
|
1076
|
-
stdoutData += data.toString();
|
|
1077
|
-
});
|
|
1078
|
-
py.on('close', (code) => {
|
|
1196
|
+
try {
|
|
1197
|
+
const { code, stdout } = await runBlePython(runtime.python, [runtime.paths.scripts.idotmatrixScan], true);
|
|
1079
1198
|
if (code !== 0) {
|
|
1080
1199
|
log('Failed to run scan script.');
|
|
1081
|
-
process.
|
|
1200
|
+
process.exitCode = 1;
|
|
1201
|
+
return;
|
|
1082
1202
|
}
|
|
1083
1203
|
try {
|
|
1084
|
-
const devices = JSON.parse(
|
|
1204
|
+
const devices = JSON.parse(stdout.trim());
|
|
1085
1205
|
if (devices.error) {
|
|
1086
1206
|
log(`Scan error: ${devices.error}`);
|
|
1087
|
-
process.
|
|
1207
|
+
process.exitCode = 1;
|
|
1208
|
+
return;
|
|
1088
1209
|
}
|
|
1089
1210
|
if (!Array.isArray(devices) || devices.length === 0) {
|
|
1090
1211
|
log('No BLE devices found.');
|
|
@@ -1096,10 +1217,15 @@ idotmatrix
|
|
|
1096
1217
|
log(`${prefix}${d.name} (${d.address})`);
|
|
1097
1218
|
}
|
|
1098
1219
|
}
|
|
1099
|
-
catch
|
|
1100
|
-
log(`Failed to parse scan output: ${
|
|
1220
|
+
catch {
|
|
1221
|
+
log(`Failed to parse scan output: ${stdout}`);
|
|
1222
|
+
process.exitCode = 1;
|
|
1101
1223
|
}
|
|
1102
|
-
}
|
|
1224
|
+
}
|
|
1225
|
+
catch (err) {
|
|
1226
|
+
log(`Failed to start BLE scan: ${err instanceof Error ? err.message : String(err)}`);
|
|
1227
|
+
process.exitCode = 1;
|
|
1228
|
+
}
|
|
1103
1229
|
});
|
|
1104
1230
|
idotmatrix
|
|
1105
1231
|
.command('add <address>')
|
|
@@ -1150,9 +1276,6 @@ idotmatrix
|
|
|
1150
1276
|
.option('-a, --address <address>', 'BLE Address (defaults to first configured device)')
|
|
1151
1277
|
.action(async (valueStr, opts) => {
|
|
1152
1278
|
const { loadIDotMatrixDevices } = await import('./idotmatrix/idotmatrix-settings.js');
|
|
1153
|
-
const { dirname } = await import('path');
|
|
1154
|
-
const { fileURLToPath } = await import('url');
|
|
1155
|
-
const { spawn } = await import('child_process');
|
|
1156
1279
|
const value = parseInt(valueStr, 10);
|
|
1157
1280
|
if (isNaN(value) || value < 5 || value > 100) {
|
|
1158
1281
|
log('Brightness value must be between 5 and 100.');
|
|
@@ -1167,22 +1290,30 @@ idotmatrix
|
|
|
1167
1290
|
}
|
|
1168
1291
|
targetAddress = devices[0].address;
|
|
1169
1292
|
}
|
|
1170
|
-
const
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
const brightnessScript = join(projectRoot, 'bridge', 'src', 'idotmatrix', 'brightness.py');
|
|
1293
|
+
const runtime = prepareBleRuntime();
|
|
1294
|
+
if (!runtime)
|
|
1295
|
+
return;
|
|
1174
1296
|
log(`Setting brightness of ${targetAddress} to ${value}%...`);
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1297
|
+
try {
|
|
1298
|
+
const { code } = await runBlePython(runtime.python, [
|
|
1299
|
+
runtime.paths.scripts.idotmatrixBrightness,
|
|
1300
|
+
'-a',
|
|
1301
|
+
targetAddress,
|
|
1302
|
+
'-b',
|
|
1303
|
+
String(value),
|
|
1304
|
+
]);
|
|
1179
1305
|
if (code === 0) {
|
|
1180
1306
|
log('Brightness updated successfully.');
|
|
1181
1307
|
}
|
|
1182
1308
|
else {
|
|
1183
1309
|
log(`Failed to update brightness, process exited with code ${code}`);
|
|
1310
|
+
process.exitCode = code ?? 1;
|
|
1184
1311
|
}
|
|
1185
|
-
}
|
|
1312
|
+
}
|
|
1313
|
+
catch (err) {
|
|
1314
|
+
log(`Failed to start brightness command: ${err instanceof Error ? err.message : String(err)}`);
|
|
1315
|
+
process.exitCode = 1;
|
|
1316
|
+
}
|
|
1186
1317
|
});
|
|
1187
1318
|
idotmatrix
|
|
1188
1319
|
.command('sync [address]')
|
|
@@ -1193,9 +1324,6 @@ idotmatrix
|
|
|
1193
1324
|
.action(async (addressOpt, opts) => {
|
|
1194
1325
|
const { loadIDotMatrixDevices } = await import('./idotmatrix/idotmatrix-settings.js');
|
|
1195
1326
|
const { readDaemonInfo, findDaemonPort } = await import('./session-registry.js');
|
|
1196
|
-
const { dirname } = await import('path');
|
|
1197
|
-
const { fileURLToPath } = await import('url');
|
|
1198
|
-
const { spawn } = await import('child_process');
|
|
1199
1327
|
let targetAddress = addressOpt;
|
|
1200
1328
|
let defaultBrightness = 100;
|
|
1201
1329
|
const devices = loadIDotMatrixDevices();
|
|
@@ -1221,17 +1349,30 @@ idotmatrix
|
|
|
1221
1349
|
? parseInt(opts.port, 10)
|
|
1222
1350
|
: (info?.httpPort ?? info?.port ?? findDaemonPort() ?? BRIDGE_WS_PORT);
|
|
1223
1351
|
const url = `http://127.0.0.1:${port}`;
|
|
1224
|
-
const
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
const syncScript = join(projectRoot, 'bridge', 'src', 'idotmatrix', 'sync.py');
|
|
1352
|
+
const runtime = prepareBleRuntime();
|
|
1353
|
+
if (!runtime)
|
|
1354
|
+
return;
|
|
1228
1355
|
log(`Starting BLE sync client linking to bridge at ${url} (brightness ${brightness}%, boost ${boost}x)...`);
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1356
|
+
try {
|
|
1357
|
+
const { code } = await runBlePython(runtime.python, [
|
|
1358
|
+
runtime.paths.scripts.idotmatrixSync,
|
|
1359
|
+
'-a',
|
|
1360
|
+
targetAddress,
|
|
1361
|
+
'-u',
|
|
1362
|
+
url,
|
|
1363
|
+
'-b',
|
|
1364
|
+
String(brightness),
|
|
1365
|
+
'--boost',
|
|
1366
|
+
String(boost),
|
|
1367
|
+
]);
|
|
1233
1368
|
log(`Sync process exited with code ${code}`);
|
|
1234
|
-
|
|
1369
|
+
if (code !== 0)
|
|
1370
|
+
process.exitCode = code ?? 1;
|
|
1371
|
+
}
|
|
1372
|
+
catch (err) {
|
|
1373
|
+
log(`Failed to start BLE sync: ${err instanceof Error ? err.message : String(err)}`);
|
|
1374
|
+
process.exitCode = 1;
|
|
1375
|
+
}
|
|
1235
1376
|
});
|
|
1236
1377
|
// ===== Divoom Timebox Mini Light commands =====
|
|
1237
1378
|
const timebox = program.command('timebox').description('Manage Divoom Timebox Mini devices (Bluetooth LE)');
|
|
@@ -1239,22 +1380,22 @@ timebox
|
|
|
1239
1380
|
.command('scan')
|
|
1240
1381
|
.description('Discover BLE TimeBox-mini-light peripherals')
|
|
1241
1382
|
.action(async () => {
|
|
1242
|
-
const
|
|
1383
|
+
const runtime = prepareBleRuntime();
|
|
1384
|
+
if (!runtime)
|
|
1385
|
+
return;
|
|
1243
1386
|
log('Scanning for BLE devices (5 seconds)...');
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
py.stdout.on('data', (data) => {
|
|
1247
|
-
stdoutData += data.toString();
|
|
1248
|
-
});
|
|
1249
|
-
py.on('close', (code) => {
|
|
1387
|
+
try {
|
|
1388
|
+
const { code, stdout } = await runBlePython(runtime.python, [runtime.paths.scripts.timeboxScan], true);
|
|
1250
1389
|
if (code !== 0) {
|
|
1251
|
-
log('BLE scan failed
|
|
1390
|
+
log('BLE scan failed.');
|
|
1391
|
+
process.exitCode = 1;
|
|
1252
1392
|
return;
|
|
1253
1393
|
}
|
|
1254
1394
|
try {
|
|
1255
|
-
const devices = JSON.parse(
|
|
1395
|
+
const devices = JSON.parse(stdout.trim());
|
|
1256
1396
|
if (devices.error) {
|
|
1257
1397
|
log(`BLE scan error: ${devices.error}`);
|
|
1398
|
+
process.exitCode = 1;
|
|
1258
1399
|
return;
|
|
1259
1400
|
}
|
|
1260
1401
|
if (!Array.isArray(devices) || devices.length === 0) {
|
|
@@ -1270,9 +1411,14 @@ timebox
|
|
|
1270
1411
|
log('\nAdd the starred BLE device with: agentdeck timebox add <address>');
|
|
1271
1412
|
}
|
|
1272
1413
|
catch {
|
|
1273
|
-
log(`Failed to parse BLE scan output: ${
|
|
1414
|
+
log(`Failed to parse BLE scan output: ${stdout}`);
|
|
1415
|
+
process.exitCode = 1;
|
|
1274
1416
|
}
|
|
1275
|
-
}
|
|
1417
|
+
}
|
|
1418
|
+
catch (err) {
|
|
1419
|
+
log(`Failed to start BLE scan: ${err instanceof Error ? err.message : String(err)}`);
|
|
1420
|
+
process.exitCode = 1;
|
|
1421
|
+
}
|
|
1276
1422
|
});
|
|
1277
1423
|
timebox
|
|
1278
1424
|
.command('add <address>')
|
|
@@ -1349,11 +1495,22 @@ async function runTimeboxSync(targetOpt, opts, once) {
|
|
|
1349
1495
|
? parseInt(opts.bridgePort, 10)
|
|
1350
1496
|
: (info?.httpPort ?? info?.port ?? findDaemonPort() ?? BRIDGE_WS_PORT);
|
|
1351
1497
|
const url = `http://127.0.0.1:${bridgePort}`;
|
|
1352
|
-
const
|
|
1353
|
-
|
|
1498
|
+
const runtime = prepareBleRuntime();
|
|
1499
|
+
if (!runtime)
|
|
1500
|
+
return;
|
|
1501
|
+
const args = [
|
|
1502
|
+
runtime.paths.scripts.timeboxSync,
|
|
1503
|
+
'--address',
|
|
1504
|
+
id,
|
|
1505
|
+
'--url',
|
|
1506
|
+
url,
|
|
1507
|
+
'--brightness',
|
|
1508
|
+
String(brightness),
|
|
1509
|
+
...(once ? ['--once'] : []),
|
|
1510
|
+
];
|
|
1354
1511
|
log(`${once ? 'Sending one Timebox frame' : 'Starting Timebox sync'} via BLE ${id} from ${url} (brightness ${brightness}%)...`);
|
|
1355
|
-
|
|
1356
|
-
|
|
1512
|
+
try {
|
|
1513
|
+
const { code } = await runBlePython(runtime.python, args);
|
|
1357
1514
|
if (once) {
|
|
1358
1515
|
if (code === 0) {
|
|
1359
1516
|
log('Timebox test frame sent.');
|
|
@@ -1365,8 +1522,14 @@ async function runTimeboxSync(targetOpt, opts, once) {
|
|
|
1365
1522
|
}
|
|
1366
1523
|
else {
|
|
1367
1524
|
log(`Timebox sync process exited with code ${code}`);
|
|
1525
|
+
if (code !== 0)
|
|
1526
|
+
process.exitCode = code ?? 1;
|
|
1368
1527
|
}
|
|
1369
|
-
}
|
|
1528
|
+
}
|
|
1529
|
+
catch (err) {
|
|
1530
|
+
log(`Failed to start Timebox BLE command: ${err instanceof Error ? err.message : String(err)}`);
|
|
1531
|
+
process.exitCode = 1;
|
|
1532
|
+
}
|
|
1370
1533
|
}
|
|
1371
1534
|
timebox
|
|
1372
1535
|
.command('test [target]')
|