@agentdeck/bridge 1.0.4 → 1.0.5
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/adapters/claude-code.d.ts.map +1 -1
- package/dist/adapters/claude-code.js +3 -0
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adb-reverse.d.ts.map +1 -1
- package/dist/adb-reverse.js +4 -2
- package/dist/adb-reverse.js.map +1 -1
- package/dist/apme/collector.js +2 -2
- package/dist/apme/collector.js.map +1 -1
- package/dist/apme/outcome.js +2 -2
- package/dist/apme/outcome.js.map +1 -1
- package/dist/apme/runner.js +3 -3
- package/dist/apme/runner.js.map +1 -1
- package/dist/ble-sync-spawn.js +1 -1
- package/dist/ble-sync-spawn.js.map +1 -1
- package/dist/calendar.d.ts +82 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +324 -0
- package/dist/calendar.js.map +1 -0
- package/dist/card-feed.d.ts +63 -5
- package/dist/card-feed.d.ts.map +1 -1
- package/dist/card-feed.js +218 -14
- package/dist/card-feed.js.map +1 -1
- package/dist/card-modules.d.ts +4 -0
- package/dist/card-modules.d.ts.map +1 -1
- package/dist/card-modules.js +5 -3
- package/dist/card-modules.js.map +1 -1
- package/dist/check-deps.js +4 -4
- package/dist/check-deps.js.map +1 -1
- package/dist/cli.d.ts +35 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +269 -9
- package/dist/cli.js.map +1 -1
- package/dist/codex-otel.d.ts +89 -0
- package/dist/codex-otel.d.ts.map +1 -0
- package/dist/codex-otel.js +487 -0
- package/dist/codex-otel.js.map +1 -0
- package/dist/codex-rate-limits.d.ts.map +1 -1
- package/dist/codex-rate-limits.js +15 -3
- package/dist/codex-rate-limits.js.map +1 -1
- package/dist/daemon-server.d.ts +4 -0
- package/dist/daemon-server.d.ts.map +1 -1
- package/dist/daemon-server.js +797 -54
- package/dist/daemon-server.js.map +1 -1
- package/dist/daemon-ws-client.d.ts +108 -24
- package/dist/daemon-ws-client.d.ts.map +1 -1
- package/dist/daemon-ws-client.js +175 -39
- package/dist/daemon-ws-client.js.map +1 -1
- package/dist/daemon.js +1 -1
- package/dist/device-voice-reply.d.ts.map +1 -1
- package/dist/device-voice-reply.js +15 -3
- package/dist/device-voice-reply.js.map +1 -1
- package/dist/device-voice.d.ts +37 -0
- package/dist/device-voice.d.ts.map +1 -1
- package/dist/device-voice.js +130 -1
- package/dist/device-voice.js.map +1 -1
- package/dist/diag-analyzer.d.ts.map +1 -1
- package/dist/diag-analyzer.js +1 -0
- package/dist/diag-analyzer.js.map +1 -1
- package/dist/display-monitor.d.ts.map +1 -1
- package/dist/display-monitor.js +1 -0
- package/dist/display-monitor.js.map +1 -1
- package/dist/esp32-serial.d.ts +4 -0
- package/dist/esp32-serial.d.ts.map +1 -1
- package/dist/esp32-serial.js +6 -3
- package/dist/esp32-serial.js.map +1 -1
- package/dist/foundation-models-helper.d.ts +23 -0
- package/dist/foundation-models-helper.d.ts.map +1 -1
- package/dist/foundation-models-helper.js +32 -0
- package/dist/foundation-models-helper.js.map +1 -1
- package/dist/gateway-probe.js +1 -1
- package/dist/gateway-probe.js.map +1 -1
- package/dist/glance-frame.d.ts +64 -0
- package/dist/glance-frame.d.ts.map +1 -0
- package/dist/glance-frame.js +364 -0
- package/dist/glance-frame.js.map +1 -0
- package/dist/hook-codex-sessions.d.ts +40 -0
- package/dist/hook-codex-sessions.d.ts.map +1 -0
- package/dist/hook-codex-sessions.js +174 -0
- package/dist/hook-codex-sessions.js.map +1 -0
- package/dist/idotmatrix/idotmatrix-discover.d.ts.map +1 -1
- package/dist/idotmatrix/idotmatrix-discover.js +7 -4
- package/dist/idotmatrix/idotmatrix-discover.js.map +1 -1
- package/dist/idotmatrix/idotmatrix-settings.d.ts +15 -0
- package/dist/idotmatrix/idotmatrix-settings.d.ts.map +1 -1
- package/dist/idotmatrix/idotmatrix-settings.js +21 -0
- package/dist/idotmatrix/idotmatrix-settings.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +88 -7
- package/dist/index.js.map +1 -1
- package/dist/linux-service.d.ts +52 -0
- package/dist/linux-service.d.ts.map +1 -0
- package/dist/linux-service.js +206 -0
- package/dist/linux-service.js.map +1 -0
- package/dist/logger.d.ts +6 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +15 -0
- package/dist/logger.js.map +1 -1
- package/dist/mdns-discover.d.ts +19 -0
- package/dist/mdns-discover.d.ts.map +1 -0
- package/dist/mdns-discover.js +109 -0
- package/dist/mdns-discover.js.map +1 -0
- package/dist/model-catalog.d.ts.map +1 -1
- package/dist/model-catalog.js +1 -0
- package/dist/model-catalog.js.map +1 -1
- package/dist/modules/adb-module.js +1 -1
- package/dist/modules/adb-module.js.map +1 -1
- package/dist/output-parser.d.ts +9 -0
- package/dist/output-parser.d.ts.map +1 -1
- package/dist/output-parser.js +67 -16
- package/dist/output-parser.js.map +1 -1
- package/dist/passive-observer.d.ts +57 -8
- package/dist/passive-observer.d.ts.map +1 -1
- package/dist/passive-observer.js +210 -74
- package/dist/passive-observer.js.map +1 -1
- package/dist/pixoo/pixoo-bridge.d.ts.map +1 -1
- package/dist/pixoo/pixoo-bridge.js +47 -6
- package/dist/pixoo/pixoo-bridge.js.map +1 -1
- package/dist/pixoo/pixoo-renderer.d.ts +2 -1
- package/dist/pixoo/pixoo-renderer.d.ts.map +1 -1
- package/dist/pixoo/pixoo-renderer.js +137 -8
- package/dist/pixoo/pixoo-renderer.js.map +1 -1
- package/dist/pocket-autonomy.d.ts +78 -0
- package/dist/pocket-autonomy.d.ts.map +1 -0
- package/dist/pocket-autonomy.js +529 -0
- package/dist/pocket-autonomy.js.map +1 -0
- package/dist/pty-manager.d.ts.map +1 -1
- package/dist/pty-manager.js +3 -2
- package/dist/pty-manager.js.map +1 -1
- package/dist/python-ble-runtime.d.ts +2 -0
- package/dist/python-ble-runtime.d.ts.map +1 -1
- package/dist/python-ble-runtime.js +2 -1
- package/dist/python-ble-runtime.js.map +1 -1
- package/dist/remote-sessions.d.ts +64 -0
- package/dist/remote-sessions.d.ts.map +1 -0
- package/dist/remote-sessions.js +81 -0
- package/dist/remote-sessions.js.map +1 -0
- package/dist/review-runner.js +2 -2
- package/dist/review-runner.js.map +1 -1
- package/dist/session-aggregator.d.ts +2 -0
- package/dist/session-aggregator.d.ts.map +1 -1
- package/dist/session-aggregator.js +6 -1
- package/dist/session-aggregator.js.map +1 -1
- package/dist/session-focus-relay.d.ts +47 -1
- package/dist/session-focus-relay.d.ts.map +1 -1
- package/dist/session-focus-relay.js +130 -15
- package/dist/session-focus-relay.js.map +1 -1
- package/dist/session-push-channel.d.ts +31 -0
- package/dist/session-push-channel.d.ts.map +1 -0
- package/dist/session-push-channel.js +94 -0
- package/dist/session-push-channel.js.map +1 -0
- package/dist/session-registry.d.ts +126 -0
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/session-registry.js +180 -3
- package/dist/session-registry.js.map +1 -1
- package/dist/subagent-timeline.d.ts +34 -0
- package/dist/subagent-timeline.d.ts.map +1 -0
- package/dist/subagent-timeline.js +156 -0
- package/dist/subagent-timeline.js.map +1 -0
- package/dist/timebox/timebox-discover.js +1 -1
- package/dist/timebox/timebox-discover.js.map +1 -1
- package/dist/tui/gauge.d.ts +4 -2
- package/dist/tui/gauge.d.ts.map +1 -1
- package/dist/tui/gauge.js +8 -4
- package/dist/tui/gauge.js.map +1 -1
- package/dist/tui/renderer.d.ts.map +1 -1
- package/dist/tui/renderer.js +28 -0
- package/dist/tui/renderer.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/usage-api.d.ts +43 -0
- package/dist/usage-api.d.ts.map +1 -1
- package/dist/usage-api.js +114 -9
- package/dist/usage-api.js.map +1 -1
- package/dist/usage-event.d.ts +19 -0
- package/dist/usage-event.d.ts.map +1 -1
- package/dist/usage-event.js +35 -0
- package/dist/usage-event.js.map +1 -1
- package/dist/utils/project-name.d.ts.map +1 -1
- package/dist/utils/project-name.js +1 -0
- package/dist/utils/project-name.js.map +1 -1
- package/dist/version-check.d.ts.map +1 -1
- package/dist/version-check.js +3 -2
- package/dist/version-check.js.map +1 -1
- package/dist/voice-assistant.d.ts.map +1 -1
- package/dist/voice-assistant.js +2 -1
- package/dist/voice-assistant.js.map +1 -1
- package/dist/voice.d.ts.map +1 -1
- package/dist/voice.js +1 -0
- package/dist/voice.js.map +1 -1
- package/dist/weather.d.ts +72 -0
- package/dist/weather.d.ts.map +1 -0
- package/dist/weather.js +206 -0
- package/dist/weather.js.map +1 -0
- package/dist/windows-service.js +5 -5
- package/dist/windows-service.js.map +1 -1
- package/fm-helper/AgentDeckFMHelper.swift +200 -0
- package/package.json +15 -13
- package/src/idotmatrix/identity_generated.py +50 -0
- package/src/idotmatrix/scan.py +33 -13
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
2
|
+
import { Command, InvalidArgumentError } from 'commander';
|
|
3
3
|
import { writeFileSync, unlinkSync, existsSync, realpathSync } from 'fs';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
import { dirname, join } from 'path';
|
|
@@ -8,13 +8,42 @@ 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 { SESSION_WEIGHT_MIN, SESSION_WEIGHT_MAX } from '@agentdeck/shared';
|
|
11
12
|
import { ensureBleRuntime, getBleRuntimeStatus } from './python-ble-runtime.js';
|
|
12
13
|
import { TASK_NAME, installWindowsTask, taskExists, runWindowsTask, endWindowsTask, deleteWindowsTask, } from './windows-service.js';
|
|
14
|
+
import { deriveRemoteAttachOpts } from './session-registry.js';
|
|
15
|
+
import { SERVICE_NAME, hasSystemctl, unitExists, installUnit, startUnit, disableUnit, getUnitPath, getDataDir, } from './linux-service.js';
|
|
13
16
|
const require = createRequire(import.meta.url);
|
|
14
17
|
const packageJson = require('../package.json');
|
|
18
|
+
/**
|
|
19
|
+
* Pre-PTY warning for a host hint given without the opt-in switch. Must run in
|
|
20
|
+
* the CLI action handler BEFORE the bridge starts: once the PTY takes the
|
|
21
|
+
* terminal, `log()` output is swallowed and the user would never see it.
|
|
22
|
+
*/
|
|
23
|
+
function warnIfDaemonHostIgnored(opts) {
|
|
24
|
+
if (deriveRemoteAttachOpts(opts).ignoredHostHint) {
|
|
25
|
+
console.error('[agentdeck] --daemon-host / AGENTDECK_DAEMON_HOST is set but ignored: remote attach requires --remote-daemon (or AGENTDECK_REMOTE_DAEMON=1). Session stays local-only.');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
15
28
|
function log(msg) {
|
|
16
29
|
process.stderr.write(msg + '\n');
|
|
17
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Parse a `--weight <n>` value into an integer sort override. Rejects
|
|
33
|
+
* non-integers so a typo (`--weight foo`) fails loudly instead of silently
|
|
34
|
+
* collapsing to 0, and rejects values outside the documented cross-platform
|
|
35
|
+
* range (shared SESSION_WEIGHT_MIN/MAX) — the wire value must fit every
|
|
36
|
+
* consumer's integer type (Kotlin `Int` in particular), and comparators on
|
|
37
|
+
* fixed-width platforms must never see values that could overflow. Lower
|
|
38
|
+
* sorts first (see shared sortSessions).
|
|
39
|
+
*/
|
|
40
|
+
function parseWeight(value) {
|
|
41
|
+
const n = Number(value);
|
|
42
|
+
if (!Number.isInteger(n) || n < SESSION_WEIGHT_MIN || n > SESSION_WEIGHT_MAX) {
|
|
43
|
+
throw new InvalidArgumentError(`weight must be an integer between ${SESSION_WEIGHT_MIN} and ${SESSION_WEIGHT_MAX} (e.g. --weight 1, --weight -5)`);
|
|
44
|
+
}
|
|
45
|
+
return n;
|
|
46
|
+
}
|
|
18
47
|
function formatBytes(value) {
|
|
19
48
|
const bytes = typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
20
49
|
if (bytes >= 1024 * 1024)
|
|
@@ -141,7 +170,7 @@ function projectRootPath() {
|
|
|
141
170
|
}
|
|
142
171
|
async function runBlePython(python, args, captureStdout = false) {
|
|
143
172
|
return new Promise((resolve, reject) => {
|
|
144
|
-
const child = spawn(python, args, captureStdout ? { stdio: ['ignore', 'pipe', 'inherit'] } : { stdio: 'inherit' });
|
|
173
|
+
const child = spawn(python, args, captureStdout ? { stdio: ['ignore', 'pipe', 'inherit'], windowsHide: true } : { stdio: 'inherit', windowsHide: true });
|
|
145
174
|
let stdout = '';
|
|
146
175
|
child.stdout?.on('data', (data) => {
|
|
147
176
|
stdout += data.toString();
|
|
@@ -204,6 +233,117 @@ function platformioBin() {
|
|
|
204
233
|
return '/opt/homebrew/bin/pio';
|
|
205
234
|
return 'pio';
|
|
206
235
|
}
|
|
236
|
+
// ===== Env-var default CLI args =====
|
|
237
|
+
//
|
|
238
|
+
// Two layers of environment-variable defaults, so a shell profile can set
|
|
239
|
+
// repeated flags once:
|
|
240
|
+
// AGENTDECK_COMMANDER_ARGS → args for the `agentdeck` (commander) layer,
|
|
241
|
+
// spliced into argv after the session subcommand.
|
|
242
|
+
// AGENTDECK_<AGENT>_ARGS → args appended to the spawned agent command,
|
|
243
|
+
// weaving into an explicit `-c` when present.
|
|
244
|
+
// See docs and cli.test.ts for behavior.
|
|
245
|
+
/** Session subcommands the global env-args var applies to. */
|
|
246
|
+
const SESSION_COMMANDS = ['claude', 'codex', 'opencode', 'monitor'];
|
|
247
|
+
/** Env var holding extra args for the spawned agent command, per agent type. */
|
|
248
|
+
const AGENT_ARGS_ENV = {
|
|
249
|
+
'claude-code': 'AGENTDECK_CLAUDE_ARGS',
|
|
250
|
+
'codex-cli': 'AGENTDECK_CODEX_ARGS',
|
|
251
|
+
opencode: 'AGENTDECK_OPENCODE_ARGS',
|
|
252
|
+
};
|
|
253
|
+
/**
|
|
254
|
+
* Split an env-provided arg string into tokens, honoring single/double quotes
|
|
255
|
+
* so values like `--flag "a b"` survive. No shell expansion (no $VAR,
|
|
256
|
+
* globbing, or escapes beyond quote grouping) — this only groups whitespace.
|
|
257
|
+
* Cross-platform: pure JS, never invokes a shell.
|
|
258
|
+
*/
|
|
259
|
+
export function tokenizeArgString(s) {
|
|
260
|
+
if (!s)
|
|
261
|
+
return [];
|
|
262
|
+
const tokens = [];
|
|
263
|
+
// Match: double-quoted run | single-quoted run | bare non-space run.
|
|
264
|
+
const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
265
|
+
let cur = '';
|
|
266
|
+
let curActive = false;
|
|
267
|
+
let lastIndex = 0;
|
|
268
|
+
let m;
|
|
269
|
+
while ((m = re.exec(s)) !== null) {
|
|
270
|
+
// A gap of whitespace between matches ends the current token.
|
|
271
|
+
if (curActive && m.index > lastIndex) {
|
|
272
|
+
tokens.push(cur);
|
|
273
|
+
cur = '';
|
|
274
|
+
curActive = false;
|
|
275
|
+
}
|
|
276
|
+
cur += m[1] ?? m[2] ?? m[3] ?? '';
|
|
277
|
+
curActive = true;
|
|
278
|
+
lastIndex = re.lastIndex;
|
|
279
|
+
}
|
|
280
|
+
if (curActive)
|
|
281
|
+
tokens.push(cur);
|
|
282
|
+
return tokens;
|
|
283
|
+
}
|
|
284
|
+
/** Per-invocation escape hatch: disables BOTH env-default layers when typed. */
|
|
285
|
+
const NO_ENV_ARGS_FLAG = '--no-env-args';
|
|
286
|
+
/**
|
|
287
|
+
* If argv's subcommand is a session command and AGENTDECK_COMMANDER_ARGS is set,
|
|
288
|
+
* splice its tokens in immediately after the subcommand name. Env tokens land
|
|
289
|
+
* before any user-typed tokens, giving last-write override for SCALAR options
|
|
290
|
+
* (retype `--weight`/`--daemon-host` to win); boolean flags have no inverse
|
|
291
|
+
* spelling, so the per-invocation override is the typed `--no-env-args` hatch,
|
|
292
|
+
* which skips the splice entirely (and, parsed by commander, turns the
|
|
293
|
+
* per-agent append off in the session actions too).
|
|
294
|
+
* Returns a new argv; no-op (returns input) when unset or not a session command.
|
|
295
|
+
*/
|
|
296
|
+
export function applyGlobalEnvArgs(argv, env = process.env) {
|
|
297
|
+
// The hatch itself is stripped from the env tokens: an env-smuggled
|
|
298
|
+
// --no-env-args would otherwise splice normally and then parse to
|
|
299
|
+
// envArgs=false, silently disabling only the per-agent layer — an
|
|
300
|
+
// undocumented half-state. The env var cannot turn the hatch on or off;
|
|
301
|
+
// only a typed flag can.
|
|
302
|
+
const extra = tokenizeArgString(env.AGENTDECK_COMMANDER_ARGS)
|
|
303
|
+
.filter((tok) => tok !== NO_ENV_ARGS_FLAG);
|
|
304
|
+
if (extra.length === 0)
|
|
305
|
+
return argv;
|
|
306
|
+
// The CLI has a fixed top-level command position: argv[0]=node,
|
|
307
|
+
// argv[1]=script, argv[2]=subcommand. Keying the decision to argv[2] (not a
|
|
308
|
+
// token scan) keeps non-session commands a true no-op even when a
|
|
309
|
+
// positional VALUE equals a session command name (`agentdeck speak b claude`).
|
|
310
|
+
const sub = argv[2];
|
|
311
|
+
if (sub === undefined || !SESSION_COMMANDS.includes(sub))
|
|
312
|
+
return argv;
|
|
313
|
+
// Typed escape hatch — checked on the RAW argv before splicing, because
|
|
314
|
+
// post-parse is too late (env tokens would already have parsed into opts).
|
|
315
|
+
// Exact-token match: a flag VALUE lexically equal to --no-env-args would
|
|
316
|
+
// false-positive, but producing that requires a deliberately pathological
|
|
317
|
+
// invocation (`-c --no-env-args`), and the argv[2] gate above bounds the
|
|
318
|
+
// scan to session commands.
|
|
319
|
+
if (argv.slice(3).includes(NO_ENV_ARGS_FLAG))
|
|
320
|
+
return argv;
|
|
321
|
+
const next = argv.slice();
|
|
322
|
+
next.splice(3, 0, ...extra);
|
|
323
|
+
return next;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Append the per-agent env var to the resolved `-c` command string. Raw append
|
|
327
|
+
* (single space + trim) — the PTY shell re-parses the whole string at spawn, so
|
|
328
|
+
* this rides the same platform shell path (cmd.exe / POSIX) that already handles
|
|
329
|
+
* a user's `-c`. No-op (returns `command`) when the var is unset/empty.
|
|
330
|
+
*/
|
|
331
|
+
export function weaveAgentCommand(agentType, command, env = process.env) {
|
|
332
|
+
const extra = (env[AGENT_ARGS_ENV[agentType]] ?? '').trim();
|
|
333
|
+
if (!extra)
|
|
334
|
+
return command;
|
|
335
|
+
return `${command} ${extra}`.trim();
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* The session actions' one-call selection for the spawned agent command:
|
|
339
|
+
* weave the per-agent env args unless the user typed `--no-env-args`
|
|
340
|
+
* (commander parses it as `opts.envArgs === false`; the hatch covers BOTH
|
|
341
|
+
* env layers, and the commander-layer half is handled pre-parse in
|
|
342
|
+
* `applyGlobalEnvArgs`).
|
|
343
|
+
*/
|
|
344
|
+
export function resolveAgentCommand(agentType, command, envArgsEnabled, env = process.env) {
|
|
345
|
+
return envArgsEnabled ? weaveAgentCommand(agentType, command, env) : command;
|
|
346
|
+
}
|
|
207
347
|
// ===== Program =====
|
|
208
348
|
const program = new Command();
|
|
209
349
|
program
|
|
@@ -223,16 +363,24 @@ program
|
|
|
223
363
|
.option('--no-adb', 'Disable ADB reverse setup')
|
|
224
364
|
.option('--no-postit', 'Disable terminal tab title updates')
|
|
225
365
|
.option('--wake-word', 'Enable wake word voice assistant ("오픈클로")')
|
|
366
|
+
.option('--remote-daemon', 'Opt in to attaching this session to a daemon on another machine (gates --daemon-host and mDNS discovery)')
|
|
367
|
+
.option('--daemon-host <host>', 'Explicit remote daemon endpoint (host or host:port); requires --remote-daemon')
|
|
368
|
+
.option('--no-env-args', 'Ignore AGENTDECK_COMMANDER_ARGS and AGENTDECK_<AGENT>_ARGS for this invocation')
|
|
369
|
+
.option('--weight <n>', 'Deck/tab sort order override (integer, lower first; default 0)', parseWeight)
|
|
226
370
|
.action(async (opts) => {
|
|
371
|
+
warnIfDaemonHostIgnored(opts);
|
|
227
372
|
const { startSession } = await import('./index.js');
|
|
228
373
|
await startSession({
|
|
229
374
|
agentType: 'claude-code',
|
|
230
375
|
port: parseInt(opts.port, 10),
|
|
231
|
-
command: opts.command,
|
|
376
|
+
command: resolveAgentCommand('claude-code', opts.command, opts.envArgs !== false),
|
|
232
377
|
debug: opts.debug,
|
|
233
378
|
noUpdateCheck: opts.updateCheck === false,
|
|
234
379
|
postit: opts.postit !== false,
|
|
235
380
|
wakeWord: !!opts.wakeWord,
|
|
381
|
+
remoteDaemon: !!opts.remoteDaemon,
|
|
382
|
+
daemonHost: opts.daemonHost,
|
|
383
|
+
weight: opts.weight,
|
|
236
384
|
modules: opts.local ? { mdns: false, adb: false, serial: false, pixoo: false, timebox: false } : {
|
|
237
385
|
mdns: false, // daemon-only — session bridges never advertise mDNS
|
|
238
386
|
adb: opts.adb !== false ? 'auto' : false,
|
|
@@ -252,7 +400,12 @@ program
|
|
|
252
400
|
.option('--no-adb', 'Disable ADB reverse setup')
|
|
253
401
|
.option('--no-postit', 'Disable terminal tab title updates')
|
|
254
402
|
.option('--no-codex-hooks', 'Skip ~/.codex/config.toml hook install')
|
|
403
|
+
.option('--remote-daemon', 'Opt in to attaching this session to a daemon on another machine (gates --daemon-host and mDNS discovery)')
|
|
404
|
+
.option('--daemon-host <host>', 'Explicit remote daemon endpoint (host or host:port); requires --remote-daemon')
|
|
405
|
+
.option('--no-env-args', 'Ignore AGENTDECK_COMMANDER_ARGS and AGENTDECK_<AGENT>_ARGS for this invocation')
|
|
406
|
+
.option('--weight <n>', 'Deck/tab sort order override (integer, lower first; default 0)', parseWeight)
|
|
255
407
|
.action(async (opts) => {
|
|
408
|
+
warnIfDaemonHostIgnored(opts);
|
|
256
409
|
// Install Codex lifecycle hooks before starting the session so the
|
|
257
410
|
// first prompt's UserPromptSubmit / Stop events reach the daemon.
|
|
258
411
|
// Idempotent: re-running with the same daemon port is a no-op.
|
|
@@ -281,9 +434,12 @@ program
|
|
|
281
434
|
await startSession({
|
|
282
435
|
agentType: 'codex-cli',
|
|
283
436
|
port: parseInt(opts.port, 10),
|
|
284
|
-
command: opts.command,
|
|
437
|
+
command: resolveAgentCommand('codex-cli', opts.command, opts.envArgs !== false),
|
|
285
438
|
debug: opts.debug,
|
|
286
439
|
postit: opts.postit !== false,
|
|
440
|
+
remoteDaemon: !!opts.remoteDaemon,
|
|
441
|
+
daemonHost: opts.daemonHost,
|
|
442
|
+
weight: opts.weight,
|
|
287
443
|
modules: opts.local ? { mdns: false, adb: false, serial: false, pixoo: false, timebox: false } : {
|
|
288
444
|
mdns: false, // daemon-only
|
|
289
445
|
adb: opts.adb !== false ? 'auto' : false,
|
|
@@ -303,7 +459,12 @@ program
|
|
|
303
459
|
.option('--no-adb', 'Disable ADB reverse setup')
|
|
304
460
|
.option('--no-postit', 'Disable terminal tab title updates')
|
|
305
461
|
.option('--no-opencode-hooks', 'Skip OpenCode observer plugin install')
|
|
462
|
+
.option('--remote-daemon', 'Opt in to attaching this session to a daemon on another machine (gates --daemon-host and mDNS discovery)')
|
|
463
|
+
.option('--daemon-host <host>', 'Explicit remote daemon endpoint (host or host:port); requires --remote-daemon')
|
|
464
|
+
.option('--no-env-args', 'Ignore AGENTDECK_COMMANDER_ARGS and AGENTDECK_<AGENT>_ARGS for this invocation')
|
|
465
|
+
.option('--weight <n>', 'Deck/tab sort order override (integer, lower first; default 0)', parseWeight)
|
|
306
466
|
.action(async (opts) => {
|
|
467
|
+
warnIfDaemonHostIgnored(opts);
|
|
307
468
|
// Install the OpenCode observer plugin so standalone `opencode` runs
|
|
308
469
|
// (outside this managed session) also reach the daemon timeline. The
|
|
309
470
|
// plugin self-disables inside managed PTYs via AGENTDECK_PORT, so this
|
|
@@ -331,9 +492,12 @@ program
|
|
|
331
492
|
await startSession({
|
|
332
493
|
agentType: 'opencode',
|
|
333
494
|
port: parseInt(opts.port, 10),
|
|
334
|
-
command: opts.command,
|
|
495
|
+
command: resolveAgentCommand('opencode', opts.command, opts.envArgs !== false),
|
|
335
496
|
debug: opts.debug,
|
|
336
497
|
postit: opts.postit !== false,
|
|
498
|
+
remoteDaemon: !!opts.remoteDaemon,
|
|
499
|
+
daemonHost: opts.daemonHost,
|
|
500
|
+
weight: opts.weight,
|
|
337
501
|
modules: opts.local ? { mdns: false, adb: false, serial: false, pixoo: false, timebox: false } : {
|
|
338
502
|
mdns: false,
|
|
339
503
|
adb: opts.adb !== false ? 'auto' : false,
|
|
@@ -349,7 +513,13 @@ program
|
|
|
349
513
|
.option('-p, --port <port>', 'Bridge server port', String(BRIDGE_WS_PORT))
|
|
350
514
|
.option('-d, --debug', 'Enable debug logging')
|
|
351
515
|
.option('--local', 'Disable all device modules')
|
|
516
|
+
.option('--remote-daemon', 'Opt in to attaching this session to a daemon on another machine (gates --daemon-host and mDNS discovery)')
|
|
517
|
+
.option('--daemon-host <host>', 'Explicit remote daemon endpoint (host or host:port); requires --remote-daemon')
|
|
518
|
+
// Commander-layer half only: monitor spawns no agent command to weave into.
|
|
519
|
+
.option('--no-env-args', 'Ignore AGENTDECK_COMMANDER_ARGS for this invocation')
|
|
520
|
+
.option('--weight <n>', 'Deck/tab sort order override (integer, lower first; default 0)', parseWeight)
|
|
352
521
|
.action(async (opts) => {
|
|
522
|
+
warnIfDaemonHostIgnored(opts);
|
|
353
523
|
const { startSession } = await import('./index.js');
|
|
354
524
|
const port = parseInt(opts.port, 10);
|
|
355
525
|
log(`\nMonitor mode: hook server on port ${port}`);
|
|
@@ -358,6 +528,9 @@ program
|
|
|
358
528
|
agentType: 'monitor',
|
|
359
529
|
port,
|
|
360
530
|
debug: opts.debug,
|
|
531
|
+
remoteDaemon: !!opts.remoteDaemon,
|
|
532
|
+
daemonHost: opts.daemonHost,
|
|
533
|
+
weight: opts.weight,
|
|
361
534
|
modules: opts.local ? { mdns: false, adb: false, serial: false, pixoo: false, timebox: false } : undefined,
|
|
362
535
|
});
|
|
363
536
|
});
|
|
@@ -465,6 +638,7 @@ daemon
|
|
|
465
638
|
const child = spawn(process.execPath, args, {
|
|
466
639
|
detached: true,
|
|
467
640
|
stdio: ['ignore', out, err],
|
|
641
|
+
windowsHide: true,
|
|
468
642
|
});
|
|
469
643
|
child.unref();
|
|
470
644
|
log(`Daemon started (PID ${child.pid})`);
|
|
@@ -502,6 +676,7 @@ daemon
|
|
|
502
676
|
const child = spawn(process.execPath, args, {
|
|
503
677
|
detached: true,
|
|
504
678
|
stdio: 'ignore',
|
|
679
|
+
windowsHide: true,
|
|
505
680
|
});
|
|
506
681
|
child.unref();
|
|
507
682
|
log(`Daemon restarted (PID ${child.pid})`);
|
|
@@ -534,7 +709,7 @@ daemon
|
|
|
534
709
|
});
|
|
535
710
|
daemon
|
|
536
711
|
.command('install')
|
|
537
|
-
.description('Install daemon auto-start (LaunchAgent on macOS, Scheduled Task on Windows)')
|
|
712
|
+
.description('Install daemon auto-start (LaunchAgent on macOS, Scheduled Task on Windows, systemd --user unit on Linux)')
|
|
538
713
|
.action(async () => {
|
|
539
714
|
if (process.platform === 'win32') {
|
|
540
715
|
try {
|
|
@@ -588,6 +763,62 @@ daemon
|
|
|
588
763
|
catch { /* hooks package not built yet — task install still succeeds */ }
|
|
589
764
|
process.exit(0);
|
|
590
765
|
}
|
|
766
|
+
if (process.platform === 'linux') {
|
|
767
|
+
if (!hasSystemctl()) {
|
|
768
|
+
log('systemd --user is not available on this host.');
|
|
769
|
+
log('Run the daemon manually with: agentdeck daemon start');
|
|
770
|
+
log('(or add your own autostart entry).');
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
try {
|
|
774
|
+
installUnit();
|
|
775
|
+
startUnit();
|
|
776
|
+
log(`systemd user unit '${SERVICE_NAME}' installed and started.`);
|
|
777
|
+
log(`Unit file: ${getUnitPath()}`);
|
|
778
|
+
if (process.env.AGENTDECK_DATA_DIR) {
|
|
779
|
+
log(`Data dir: ${getDataDir()} (AGENTDECK_DATA_DIR persisted into the unit — re-run 'agentdeck daemon install' to change it)`);
|
|
780
|
+
}
|
|
781
|
+
else {
|
|
782
|
+
log(`Data dir: ${getDataDir()}`);
|
|
783
|
+
}
|
|
784
|
+
log('For boot-without-login on a headless host, run once: loginctl enable-linger $USER');
|
|
785
|
+
}
|
|
786
|
+
catch (e) {
|
|
787
|
+
const detail = e.stderr?.toString().trim() || e.message;
|
|
788
|
+
log('Failed to install the systemd user unit.');
|
|
789
|
+
if (detail)
|
|
790
|
+
log(detail);
|
|
791
|
+
log('You can still run the daemon manually with: agentdeck daemon start');
|
|
792
|
+
process.exit(1);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
// Install Codex + OpenCode hooks for parity with the macOS/Windows paths.
|
|
796
|
+
try {
|
|
797
|
+
const { installCodexHooksIfNeeded } = await import('@agentdeck/hooks');
|
|
798
|
+
const result = installCodexHooksIfNeeded();
|
|
799
|
+
if (result.installed) {
|
|
800
|
+
log('Codex lifecycle hooks installed in ~/.codex/config.toml');
|
|
801
|
+
if (result.warning)
|
|
802
|
+
log(`Codex hooks degraded: ${result.warning}`);
|
|
803
|
+
}
|
|
804
|
+
else if (result.reason) {
|
|
805
|
+
log(`Codex hooks skipped: ${result.reason}`);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
catch { /* hooks package not built yet — install still succeeds */ }
|
|
809
|
+
try {
|
|
810
|
+
const { installOpenCodeHooksIfNeeded, opencodePluginPath } = await import('@agentdeck/hooks');
|
|
811
|
+
const result = installOpenCodeHooksIfNeeded();
|
|
812
|
+
if (result.installed) {
|
|
813
|
+
log(`OpenCode observer plugin installed at ${opencodePluginPath()}`);
|
|
814
|
+
}
|
|
815
|
+
else if (result.reason) {
|
|
816
|
+
log(`OpenCode observer plugin skipped: ${result.reason}`);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
catch { /* hooks package not built yet — install still succeeds */ }
|
|
820
|
+
process.exit(0);
|
|
821
|
+
}
|
|
591
822
|
if (process.platform !== 'darwin') {
|
|
592
823
|
log('LaunchAgent is macOS-only');
|
|
593
824
|
process.exit(1);
|
|
@@ -630,7 +861,7 @@ daemon
|
|
|
630
861
|
});
|
|
631
862
|
daemon
|
|
632
863
|
.command('uninstall')
|
|
633
|
-
.description('Remove daemon auto-start (LaunchAgent on macOS, Scheduled Task on Windows)')
|
|
864
|
+
.description('Remove daemon auto-start (LaunchAgent on macOS, Scheduled Task on Windows, systemd --user unit on Linux)')
|
|
634
865
|
.action(async () => {
|
|
635
866
|
if (process.platform === 'win32') {
|
|
636
867
|
if (!taskExists()) {
|
|
@@ -656,6 +887,17 @@ daemon
|
|
|
656
887
|
}
|
|
657
888
|
process.exit(0);
|
|
658
889
|
}
|
|
890
|
+
if (process.platform === 'linux') {
|
|
891
|
+
if (!unitExists()) {
|
|
892
|
+
log(`systemd user unit '${SERVICE_NAME}' is not installed.`);
|
|
893
|
+
process.exit(0);
|
|
894
|
+
}
|
|
895
|
+
// Graceful daemon shutdown first, then disable + remove the unit.
|
|
896
|
+
await stopDaemon(BRIDGE_WS_PORT);
|
|
897
|
+
disableUnit();
|
|
898
|
+
log(`systemd user unit '${SERVICE_NAME}' removed.`);
|
|
899
|
+
process.exit(0);
|
|
900
|
+
}
|
|
659
901
|
if (!existsSync(PLIST_PATH)) {
|
|
660
902
|
log('LaunchAgent not installed');
|
|
661
903
|
return;
|
|
@@ -961,6 +1203,7 @@ program
|
|
|
961
1203
|
.option('-e, --env <env>', 'PlatformIO environment for default firmware path')
|
|
962
1204
|
.option('-f, --firmware <path>', 'Firmware .bin path')
|
|
963
1205
|
.option('--build', 'Build the PlatformIO environment before upload')
|
|
1206
|
+
.option('--stage', 'Stage for pull OTA: the board installs it on its next feed pull (no live WS needed)')
|
|
964
1207
|
.action(async (target, opts) => {
|
|
965
1208
|
const { readDaemonInfo, findDaemonPort } = await import('./session-registry.js');
|
|
966
1209
|
const info = readDaemonInfo();
|
|
@@ -974,12 +1217,25 @@ program
|
|
|
974
1217
|
execFileSync(pio, ['run', '-e', env], {
|
|
975
1218
|
cwd: join(projectRootPath(), 'esp32'),
|
|
976
1219
|
stdio: 'inherit',
|
|
1220
|
+
windowsHide: true,
|
|
977
1221
|
});
|
|
978
1222
|
}
|
|
979
1223
|
// Send the canonical board string, not the raw alias — the daemon matches
|
|
980
1224
|
// `target` against device_info.board, so `ttgo` must become `ttgo_t_display`.
|
|
981
1225
|
const daemonTarget = resolveEsp32OtaDaemonTarget(target);
|
|
982
1226
|
const firmwarePath = resolveEsp32FirmwarePath(target, opts.env, opts.firmware);
|
|
1227
|
+
if (opts.stage) {
|
|
1228
|
+
// Pull-OTA staging: for wake-sync-sleep boards (XTeink X3/X4) that never
|
|
1229
|
+
// hold a live WS. The daemon adverts the build on the board's feed pulls;
|
|
1230
|
+
// the board downloads + flashes itself on its next wake.
|
|
1231
|
+
const { statusCode, body } = await postJsonWithTimeout(`http://127.0.0.1:${port}/esp32/ota`, { target: daemonTarget, firmwarePath, stage: true }, 30_000);
|
|
1232
|
+
if (statusCode < 200 || statusCode >= 300 || body.ok === false) {
|
|
1233
|
+
throw new Error(String(body.error ?? `HTTP ${statusCode}`));
|
|
1234
|
+
}
|
|
1235
|
+
log(`Staged for ${body.board}: ${formatBytes(body.bytes)} md5=${body.md5}`);
|
|
1236
|
+
log('The board installs it on its next feed pull (battery cadence: typically within 15-60 min).');
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
983
1239
|
log(`Uploading ${firmwarePath} to ${daemonTarget} via daemon :${port}...`);
|
|
984
1240
|
let { statusCode, body } = await postJsonWithTimeout(`http://127.0.0.1:${port}/esp32/ota`, { target: daemonTarget, firmwarePath }, 15 * 60_000);
|
|
985
1241
|
if (body.ok === false && String(body.error ?? '').startsWith('firmware_unreadable')) {
|
|
@@ -1194,7 +1450,11 @@ idotmatrix
|
|
|
1194
1450
|
return;
|
|
1195
1451
|
log('Scanning for BLE devices (5 seconds)...');
|
|
1196
1452
|
try {
|
|
1197
|
-
|
|
1453
|
+
// Panels are identified by advertised service UUID first and known name
|
|
1454
|
+
// families second; settings.json `idotmatrixNamePrefixes` widens the
|
|
1455
|
+
// latter for a rebranded panel that hides its service.
|
|
1456
|
+
const { loadIDotMatrixNamePrefixes } = await import('./idotmatrix/idotmatrix-settings.js');
|
|
1457
|
+
const { code, stdout } = await runBlePython(runtime.python, [runtime.paths.scripts.idotmatrixScan, ...loadIDotMatrixNamePrefixes()], true);
|
|
1198
1458
|
if (code !== 0) {
|
|
1199
1459
|
log('Failed to run scan script.');
|
|
1200
1460
|
process.exitCode = 1;
|
|
@@ -2329,7 +2589,7 @@ export async function runCli(argv = process.argv) {
|
|
|
2329
2589
|
await showDefaultStatusOrHelp();
|
|
2330
2590
|
return;
|
|
2331
2591
|
}
|
|
2332
|
-
program.parse(argv);
|
|
2592
|
+
program.parse(applyGlobalEnvArgs(argv));
|
|
2333
2593
|
}
|
|
2334
2594
|
export { program };
|
|
2335
2595
|
if (isMainModule()) {
|