@zeph-to/cli 1.19.3 → 1.21.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/listener.d.ts +24 -0
- package/dist/listener.d.ts.map +1 -1
- package/dist/listener.js +138 -24
- package/package.json +1 -1
package/dist/listener.d.ts
CHANGED
|
@@ -157,6 +157,16 @@ export interface ScreenSnapshot {
|
|
|
157
157
|
* token bucket as command injection.
|
|
158
158
|
*/
|
|
159
159
|
export declare const handleScreenRequest: (req: ScreenRequest) => ScreenSnapshot | null;
|
|
160
|
+
/**
|
|
161
|
+
* After the phone injects keys, push fresh frames instead of waiting for its
|
|
162
|
+
* next screen request — the phone's post-tap re-pull raced the key landing
|
|
163
|
+
* (its timer starts at tap time, the key lands a REST+WS hop later), so it
|
|
164
|
+
* often re-captured the pre-keypress screen and the next update was a full
|
|
165
|
+
* auto-refresh period away. Unsolicited frames carry no requestId; the phone
|
|
166
|
+
* matches them by deviceId + sessionName. A newer tap cancels pending
|
|
167
|
+
* captures so a burst of arrow presses yields frames after the LAST press.
|
|
168
|
+
*/
|
|
169
|
+
export declare const scheduleInjectSnapshots: (sessionName: string, send: (data: Record<string, unknown>) => void, delays?: number[]) => void;
|
|
160
170
|
export interface CollectResult {
|
|
161
171
|
sessions: AgentSession[];
|
|
162
172
|
/** Diagnostic notes per rejected session — surfaced under `--verbose`. */
|
|
@@ -221,6 +231,9 @@ interface HandlePushDeps {
|
|
|
221
231
|
downloadAttachments?: (pushId: string, files: PushFileAttachment[]) => Promise<string[]>;
|
|
222
232
|
/** Pane cwd lookup for the remote-origin marker (ADR-0002). */
|
|
223
233
|
paneCwd?: (session: string) => string | null;
|
|
234
|
+
/** Fired after a successful named-key injection so the WS loop can push
|
|
235
|
+
* fresh screen frames to the phone (scheduleInjectSnapshots). */
|
|
236
|
+
onKeysInjected?: (session: string) => void;
|
|
224
237
|
}
|
|
225
238
|
/**
|
|
226
239
|
* Record a successful phone→pane text injection so a prompt-submit hook
|
|
@@ -253,6 +266,17 @@ export declare const gcAttachments: (now?: number, dir?: string, ttl?: number) =
|
|
|
253
266
|
export declare const handlePush: (push: PushItem, deps?: HandlePushDeps) => Promise<boolean>;
|
|
254
267
|
export declare const computeBackoff: (attempt: number) => number;
|
|
255
268
|
export declare const computeListenerDeviceId: (host?: string) => string;
|
|
269
|
+
/**
|
|
270
|
+
* A user who only set ZEPH_API_KEY (no `zeph login`, no config file) gets the
|
|
271
|
+
* prod socket by default — mirroring the DEFAULT_API_BASE fallback for REST.
|
|
272
|
+
* The default only applies when the base URL is also the prod default: with a
|
|
273
|
+
* custom base URL, silently pointing the listener at the prod socket while
|
|
274
|
+
* REST talks to another stage would split the device across environments, so
|
|
275
|
+
* that combination still errors loudly.
|
|
276
|
+
*/
|
|
277
|
+
export declare const resolveWsUrl: (args: Record<string, string | boolean>, config: {
|
|
278
|
+
wsUrl?: string;
|
|
279
|
+
}, baseUrl: string) => string | null;
|
|
256
280
|
export declare const handleListener: (args: Record<string, string | boolean>) => Promise<number>;
|
|
257
281
|
export {};
|
|
258
282
|
//# sourceMappingURL=listener.d.ts.map
|
package/dist/listener.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../src/listener.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../src/listener.ts"],"names":[],"mappings":"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"}
|
package/dist/listener.js
CHANGED
|
@@ -25,7 +25,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
25
25
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
26
26
|
};
|
|
27
27
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
-
exports.handleListener = exports.computeListenerDeviceId = exports.computeBackoff = exports.handlePush = exports.gcAttachments = exports.setAttachmentContext = exports.writeRemoteMarker = exports.collectSessions = exports.collectSessionsVerbose = exports.handleScreenRequest = exports.collectWatchHits = exports.resetPatternWatches = exports.setPatternWatches = exports.deriveSessionState = exports.resetSessionStates = exports.detectRemoteAgent = exports.parseSessionName = exports.invalidateTmuxSocketCache = exports.resolveKeys = exports.paneCurrentCommand = exports.checkRateLimit = exports.AUTH_FAILURE_CODES = exports.sessionsReportDue = exports.sessionsFingerprint = exports.SESSION_REPORT_HEARTBEAT_MS = void 0;
|
|
28
|
+
exports.handleListener = exports.resolveWsUrl = exports.computeListenerDeviceId = exports.computeBackoff = exports.handlePush = exports.gcAttachments = exports.setAttachmentContext = exports.writeRemoteMarker = exports.collectSessions = exports.collectSessionsVerbose = exports.scheduleInjectSnapshots = exports.handleScreenRequest = exports.collectWatchHits = exports.resetPatternWatches = exports.setPatternWatches = exports.deriveSessionState = exports.resetSessionStates = exports.detectRemoteAgent = exports.parseSessionName = exports.invalidateTmuxSocketCache = exports.resolveKeys = exports.paneCurrentCommand = exports.checkRateLimit = exports.AUTH_FAILURE_CODES = exports.sessionsReportDue = exports.sessionsFingerprint = exports.SESSION_REPORT_HEARTBEAT_MS = void 0;
|
|
29
29
|
const child_process_1 = require("child_process");
|
|
30
30
|
const crypto_1 = require("crypto");
|
|
31
31
|
const fs_1 = require("fs");
|
|
@@ -683,13 +683,22 @@ const handleScreenRequest = (req) => {
|
|
|
683
683
|
if (!(0, exports.collectSessions)().some((s) => s.name === req.sessionName)) {
|
|
684
684
|
return { ...base, error: 'unknown_session' };
|
|
685
685
|
}
|
|
686
|
-
const
|
|
686
|
+
const captured = capturePane(req.sessionName);
|
|
687
|
+
if (!captured) {
|
|
688
|
+
return { ...base, error: 'capture_failed' };
|
|
689
|
+
}
|
|
690
|
+
return { ...base, ...captured };
|
|
691
|
+
};
|
|
692
|
+
exports.handleScreenRequest = handleScreenRequest;
|
|
693
|
+
/** Raw pane capture + size cap. Shared by the request path and the
|
|
694
|
+
* post-injection auto-snapshot. */
|
|
695
|
+
const capturePane = (sessionName) => {
|
|
696
|
+
const r = (0, child_process_1.spawnSync)('tmux', tmuxArgs(['capture-pane', '-p', '-t', sessionName, '-S', `-${SCREEN_PEEK_LINES}`]), {
|
|
687
697
|
encoding: 'utf-8',
|
|
688
698
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
689
699
|
});
|
|
690
|
-
if (r.status !== 0)
|
|
691
|
-
return
|
|
692
|
-
}
|
|
700
|
+
if (r.status !== 0)
|
|
701
|
+
return null;
|
|
693
702
|
let content = r.stdout ?? '';
|
|
694
703
|
let truncated = false;
|
|
695
704
|
while (Buffer.byteLength(content, 'utf-8') > SCREEN_PEEK_MAX_BYTES) {
|
|
@@ -698,9 +707,42 @@ const handleScreenRequest = (req) => {
|
|
|
698
707
|
content = cut > 0 ? content.slice(cut + 1) : content.slice(-SCREEN_PEEK_MAX_BYTES);
|
|
699
708
|
truncated = true;
|
|
700
709
|
}
|
|
701
|
-
return {
|
|
710
|
+
return { content, truncated };
|
|
702
711
|
};
|
|
703
|
-
|
|
712
|
+
/** Delays after a key injection at which the pane is re-captured and pushed
|
|
713
|
+
* to the phone unsolicited. The first frame lands right after most TUI
|
|
714
|
+
* redraws; the second catches slow animations and is skipped when nothing
|
|
715
|
+
* changed since the first. */
|
|
716
|
+
const INJECT_SNAPSHOT_DELAYS_MS = [250, 900];
|
|
717
|
+
const pendingInjectSnapshots = new Map();
|
|
718
|
+
/**
|
|
719
|
+
* After the phone injects keys, push fresh frames instead of waiting for its
|
|
720
|
+
* next screen request — the phone's post-tap re-pull raced the key landing
|
|
721
|
+
* (its timer starts at tap time, the key lands a REST+WS hop later), so it
|
|
722
|
+
* often re-captured the pre-keypress screen and the next update was a full
|
|
723
|
+
* auto-refresh period away. Unsolicited frames carry no requestId; the phone
|
|
724
|
+
* matches them by deviceId + sessionName. A newer tap cancels pending
|
|
725
|
+
* captures so a burst of arrow presses yields frames after the LAST press.
|
|
726
|
+
*/
|
|
727
|
+
const scheduleInjectSnapshots = (sessionName, send, delays = INJECT_SNAPSHOT_DELAYS_MS) => {
|
|
728
|
+
for (const t of pendingInjectSnapshots.get(sessionName) ?? [])
|
|
729
|
+
clearTimeout(t);
|
|
730
|
+
let lastContent = null;
|
|
731
|
+
const timers = delays.map((delay) => setTimeout(() => {
|
|
732
|
+
const captured = capturePane(sessionName);
|
|
733
|
+
if (!captured || captured.content === lastContent)
|
|
734
|
+
return;
|
|
735
|
+
lastContent = captured.content;
|
|
736
|
+
send({
|
|
737
|
+
subtype: 'agent.screen.snapshot',
|
|
738
|
+
sessionName,
|
|
739
|
+
capturedAt: new Date().toISOString(),
|
|
740
|
+
...captured,
|
|
741
|
+
});
|
|
742
|
+
}, delay));
|
|
743
|
+
pendingInjectSnapshots.set(sessionName, timers);
|
|
744
|
+
};
|
|
745
|
+
exports.scheduleInjectSnapshots = scheduleInjectSnapshots;
|
|
704
746
|
/**
|
|
705
747
|
* Inventory pass that also records *why* each `zeph-*` session was
|
|
706
748
|
* skipped. The verbose log uses the rejection notes to explain empty
|
|
@@ -997,7 +1039,10 @@ const handlePush = async (push, deps = {}) => {
|
|
|
997
1039
|
log(`! ${push.agentSessionName}: unknown key(s) [${push.keys.join(' ')}] — drop`);
|
|
998
1040
|
return false;
|
|
999
1041
|
}
|
|
1000
|
-
|
|
1042
|
+
const injected = tryInjectKeys(push.agentSessionName, tokens, deps);
|
|
1043
|
+
if (injected)
|
|
1044
|
+
deps.onKeysInjected?.(push.agentSessionName);
|
|
1045
|
+
return injected;
|
|
1001
1046
|
}
|
|
1002
1047
|
// Download any attachments BEFORE injecting so the agent can read the
|
|
1003
1048
|
// local paths immediately. A download-phase failure is isolated: the
|
|
@@ -1037,23 +1082,75 @@ exports.computeBackoff = computeBackoff;
|
|
|
1037
1082
|
* time `zeph listener` rebinds). `dev_listener_<sha8(hostname)>` keeps it
|
|
1038
1083
|
* human-recognisable in dev logs without leaking the raw hostname.
|
|
1039
1084
|
*
|
|
1040
|
-
* The no-arg result
|
|
1041
|
-
*
|
|
1042
|
-
*
|
|
1043
|
-
*
|
|
1044
|
-
*
|
|
1085
|
+
* The no-arg result must identify the MACHINE, stably:
|
|
1086
|
+
* - NOT the hostname: macOS rewrites it on network changes (observed
|
|
1087
|
+
* flip-flopping between 'Mac' and '<name>.local' within hours), which
|
|
1088
|
+
* split one machine into several device rows — duplicated session lists
|
|
1089
|
+
* on the phone, and screen-peek requests addressed to an id no live
|
|
1090
|
+
* process answers for.
|
|
1091
|
+
* - NOT a file under ~/.zeph alone: users copy ~/.zeph to a new machine
|
|
1092
|
+
* to carry their API key, and a copied id makes two machines answer for
|
|
1093
|
+
* each other.
|
|
1094
|
+
* So: hash the platform machine id (IOPlatformUUID / /etc/machine-id) —
|
|
1095
|
+
* deterministic per machine, immune to both. The sticky file is only the
|
|
1096
|
+
* fallback for platforms where no machine id is readable.
|
|
1045
1097
|
*/
|
|
1046
1098
|
let processListenerDeviceId;
|
|
1099
|
+
const LISTENER_ID_FILE = (0, path_1.join)((0, os_1.homedir)(), '.zeph', 'listener-device-id');
|
|
1100
|
+
const hashListenerId = (seed) => `dev_listener_${(0, crypto_1.createHash)('sha256').update(seed).digest('hex').slice(0, 8)}`;
|
|
1101
|
+
const readMachineId = () => {
|
|
1102
|
+
try {
|
|
1103
|
+
if (process.platform === 'darwin') {
|
|
1104
|
+
const r = (0, child_process_1.spawnSync)('ioreg', ['-rd1', '-c', 'IOPlatformExpertDevice'], {
|
|
1105
|
+
encoding: 'utf-8',
|
|
1106
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1107
|
+
});
|
|
1108
|
+
const m = r.stdout?.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
|
|
1109
|
+
if (m)
|
|
1110
|
+
return m[1];
|
|
1111
|
+
}
|
|
1112
|
+
if (process.platform === 'linux') {
|
|
1113
|
+
for (const p of ['/etc/machine-id', '/var/lib/dbus/machine-id']) {
|
|
1114
|
+
try {
|
|
1115
|
+
const v = (0, fs_1.readFileSync)(p, 'utf-8').trim();
|
|
1116
|
+
if (v)
|
|
1117
|
+
return v;
|
|
1118
|
+
}
|
|
1119
|
+
catch { /* try next */ }
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
catch { /* fall through to the sticky-file fallback */ }
|
|
1124
|
+
return null;
|
|
1125
|
+
};
|
|
1047
1126
|
const computeListenerDeviceId = (host) => {
|
|
1048
|
-
if (host !== undefined)
|
|
1049
|
-
|
|
1050
|
-
|
|
1127
|
+
if (host !== undefined)
|
|
1128
|
+
return hashListenerId(host);
|
|
1129
|
+
if (processListenerDeviceId)
|
|
1130
|
+
return processListenerDeviceId;
|
|
1131
|
+
const machineId = readMachineId();
|
|
1132
|
+
if (machineId) {
|
|
1133
|
+
processListenerDeviceId = hashListenerId(machineId);
|
|
1134
|
+
return processListenerDeviceId;
|
|
1051
1135
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1136
|
+
// No readable machine id — pin the first hostname-derived id to a file
|
|
1137
|
+
// so at least hostname drift can't split this machine.
|
|
1138
|
+
try {
|
|
1139
|
+
const saved = (0, fs_1.readFileSync)(LISTENER_ID_FILE, 'utf-8').trim();
|
|
1140
|
+
if (/^dev_listener_[0-9a-f]{8}$/.test(saved)) {
|
|
1141
|
+
processListenerDeviceId = saved;
|
|
1142
|
+
return saved;
|
|
1143
|
+
}
|
|
1055
1144
|
}
|
|
1056
|
-
|
|
1145
|
+
catch { /* first run — fall through to compute + persist */ }
|
|
1146
|
+
const fresh = hashListenerId((0, os_1.hostname)());
|
|
1147
|
+
try {
|
|
1148
|
+
(0, fs_1.mkdirSync)((0, path_1.join)((0, os_1.homedir)(), '.zeph'), { recursive: true });
|
|
1149
|
+
(0, fs_1.writeFileSync)(LISTENER_ID_FILE, fresh);
|
|
1150
|
+
}
|
|
1151
|
+
catch { /* persistence is best-effort; in-memory pin still holds */ }
|
|
1152
|
+
processListenerDeviceId = fresh;
|
|
1153
|
+
return fresh;
|
|
1057
1154
|
};
|
|
1058
1155
|
exports.computeListenerDeviceId = computeListenerDeviceId;
|
|
1059
1156
|
/**
|
|
@@ -1247,7 +1344,13 @@ const streamSession = (wsUrl, apiKey) => {
|
|
|
1247
1344
|
// Fire-and-forget: handlePush is async (attachment download)
|
|
1248
1345
|
// but the WS read loop must stay responsive. Errors are
|
|
1249
1346
|
// logged, never thrown into the socket handler.
|
|
1250
|
-
void (0, exports.handlePush)(m.data
|
|
1347
|
+
void (0, exports.handlePush)(m.data, {
|
|
1348
|
+
onKeysInjected: (session) => (0, exports.scheduleInjectSnapshots)(session, (data) => {
|
|
1349
|
+
if (sock.readyState === ws_1.default.OPEN) {
|
|
1350
|
+
sock.send(JSON.stringify({ type: 'ephemeral', data }));
|
|
1351
|
+
}
|
|
1352
|
+
}),
|
|
1353
|
+
}).catch((err) => log(`! handlePush: ${err.message}`));
|
|
1251
1354
|
}
|
|
1252
1355
|
if (m.type === 'ephemeral' && m.data) {
|
|
1253
1356
|
// Screen peek (§S4): the phone asks for this machine's live
|
|
@@ -1286,10 +1389,21 @@ const streamSession = (wsUrl, apiKey) => {
|
|
|
1286
1389
|
terminate: () => { ws?.terminate(); },
|
|
1287
1390
|
};
|
|
1288
1391
|
};
|
|
1289
|
-
const
|
|
1392
|
+
const DEFAULT_WS_URL = 'wss://ws.zeph.to';
|
|
1393
|
+
/**
|
|
1394
|
+
* A user who only set ZEPH_API_KEY (no `zeph login`, no config file) gets the
|
|
1395
|
+
* prod socket by default — mirroring the DEFAULT_API_BASE fallback for REST.
|
|
1396
|
+
* The default only applies when the base URL is also the prod default: with a
|
|
1397
|
+
* custom base URL, silently pointing the listener at the prod socket while
|
|
1398
|
+
* REST talks to another stage would split the device across environments, so
|
|
1399
|
+
* that combination still errors loudly.
|
|
1400
|
+
*/
|
|
1401
|
+
const resolveWsUrl = (args, config, baseUrl) => {
|
|
1290
1402
|
const fromArg = typeof args['ws-url'] === 'string' ? args['ws-url'] : null;
|
|
1291
|
-
return fromArg || (0, config_js_1.resolvedEnv)('ZEPH_WS_URL') || config.wsUrl
|
|
1403
|
+
return fromArg || (0, config_js_1.resolvedEnv)('ZEPH_WS_URL') || config.wsUrl
|
|
1404
|
+
|| (baseUrl === DEFAULT_API_BASE ? DEFAULT_WS_URL : null);
|
|
1292
1405
|
};
|
|
1406
|
+
exports.resolveWsUrl = resolveWsUrl;
|
|
1293
1407
|
// ── Singleton guard (PID file) ──────────────────────────────────────
|
|
1294
1408
|
const ZEPH_DIR = (0, path_1.join)((0, os_1.homedir)(), '.zeph');
|
|
1295
1409
|
const LISTENER_PID_FILE = (0, path_1.join)(ZEPH_DIR, 'listener.pid');
|
|
@@ -1360,7 +1474,7 @@ const handleListener = async (args) => {
|
|
|
1360
1474
|
// resolution order as the rest of the CLI; falls back to the prod API.
|
|
1361
1475
|
const baseUrl = args['base-url'] || (0, config_js_1.resolvedEnv)('ZEPH_BASE_URL') || config.baseUrl || DEFAULT_API_BASE;
|
|
1362
1476
|
(0, exports.setAttachmentContext)({ apiKey, baseUrl });
|
|
1363
|
-
const wsUrl = resolveWsUrl(args, config);
|
|
1477
|
+
const wsUrl = (0, exports.resolveWsUrl)(args, config, baseUrl);
|
|
1364
1478
|
if (!wsUrl) {
|
|
1365
1479
|
console.error('zeph listener: WebSocket URL not set. Either:\n' +
|
|
1366
1480
|
' • add "wsUrl": "wss://..." to ~/.zeph/config.json\n' +
|