@zeph-to/cli 1.24.0 → 1.26.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/README.md +72 -13
- package/dist/cli.js +4 -0
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +12 -2
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +9 -0
- package/dist/listener-process.d.ts +63 -0
- package/dist/listener-process.d.ts.map +1 -0
- package/dist/listener-process.js +209 -0
- package/dist/listener.d.ts.map +1 -1
- package/dist/listener.js +43 -25
- package/dist/wrapper.d.ts +25 -0
- package/dist/wrapper.d.ts.map +1 -1
- package/dist/wrapper.js +40 -91
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,6 +43,12 @@ on *every* notification. For notifications only, with no phone control,
|
|
|
43
43
|
`npx @zeph-to/cli install` is a lighter alternative that skips the global
|
|
44
44
|
binary.
|
|
45
45
|
|
|
46
|
+
Once installed, the hooks fire in **every** session of each configured
|
|
47
|
+
agent — `zeph cc` is the phone-control bridge, not the notification
|
|
48
|
+
switch. Turn the volume down without uninstalling: `/zeph-quiet --global`
|
|
49
|
+
(blockers only, all projects), `/zeph-mute` (silence, current project),
|
|
50
|
+
`/zeph-status` (what's in effect). See [Mute & push mode](#mute--push-mode).
|
|
51
|
+
|
|
46
52
|
`~/.zeph/config.json` is the single source of truth — the CLI, the MCP
|
|
47
53
|
server, the plugin hooks, and the listener all read it. You never need
|
|
48
54
|
`ZEPH_API_KEY`-style env vars for a normal setup; they exist as overrides
|
|
@@ -196,12 +202,22 @@ can never false-flag. Muted projects are never flagged.
|
|
|
196
202
|
|
|
197
203
|
### Diagnostics
|
|
198
204
|
|
|
199
|
-
The auto-spawned listener writes to
|
|
205
|
+
The auto-spawned listener writes to three files under `~/.zeph/`:
|
|
200
206
|
|
|
201
207
|
- `listener.pid` — the running daemon's PID. `cat ~/.zeph/listener.pid`
|
|
202
208
|
+ `ps -p <pid>` to confirm it's alive.
|
|
209
|
+
- `listener.version` — the CLI version the daemon booted from. This is
|
|
210
|
+
what `zeph cc` compares against the installed package to spot a stale
|
|
211
|
+
daemon (see below).
|
|
203
212
|
- `listener.log` — stdout + stderr from the daemon. `tail -f` to watch.
|
|
204
213
|
|
|
214
|
+
The daemon logs its version on the first line, which is the only
|
|
215
|
+
reliable way to tell which build a long-running process is on:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
[xx:xx:xx] zeph listener starting — v1.26.0 — wss://ws.zeph.to
|
|
219
|
+
```
|
|
220
|
+
|
|
205
221
|
A healthy listener log shows one line per cycle:
|
|
206
222
|
|
|
207
223
|
```
|
|
@@ -213,12 +229,31 @@ If you see `! server rejected listener.sessions: ...` instead, the
|
|
|
213
229
|
message points at the failure (auth, missing device record, etc.) so
|
|
214
230
|
you can fix the actual problem instead of guessing.
|
|
215
231
|
|
|
216
|
-
To force a restart
|
|
232
|
+
To force a restart:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
zeph listener --restart
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**After upgrading `@zeph-to/cli` you normally don't have to.** `npm i -g`
|
|
239
|
+
replaces the package on disk but never the daemon already running from
|
|
240
|
+
the old build — that daemon keeps answering pushes (so agent chat looks
|
|
241
|
+
fine) while silently ignoring every message subtype added since it
|
|
242
|
+
booted. `zeph cc` compares `listener.version` against the installed
|
|
243
|
+
version and restarts the daemon for you when the installed one is newer
|
|
244
|
+
(a daemon *newer* than the `zeph cc` you ran is left alone, so several
|
|
245
|
+
installs on one machine don't fight over it):
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
zeph: listener 1.25.0 is stale — restarting on 1.26.0
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
If the PID file is missing (a different account started the daemon, or
|
|
252
|
+
it was removed by hand) the singleton guard can't see it. Find the real
|
|
253
|
+
process instead:
|
|
217
254
|
|
|
218
255
|
```bash
|
|
219
|
-
|
|
220
|
-
rm ~/.zeph/listener.pid
|
|
221
|
-
zeph cc # autospawns the new build
|
|
256
|
+
ps aux | grep '[c]li.js listener'
|
|
222
257
|
```
|
|
223
258
|
|
|
224
259
|
To run it in the foreground (for development of the SDK itself):
|
|
@@ -363,7 +398,7 @@ zeph notify --title "Hello" --json
|
|
|
363
398
|
| `--priority <p>` | Priority: `low`, `normal`, `high`, `urgent` |
|
|
364
399
|
| `--device <id>` | Target device ID |
|
|
365
400
|
| `--session <id>` | AI session ID so the push threads into that session's chat (or `ZEPH_SESSION_ID` env) |
|
|
366
|
-
| `--auto` | Apply the push gate before sending — honors the `/zeph-quiet` / `/zeph-loud` push-mode dial; gated-out exits silently with code 0 |
|
|
401
|
+
| `--auto` | Apply the push gate before sending — honors the `/zeph-quiet` / `/zeph-loud` push-mode dial, per project or machine-wide (`--global`); gated-out exits silently with code 0 |
|
|
367
402
|
| `--marker <m>` | Push Signal marker for `--auto`: `skip`, `push`, `high` |
|
|
368
403
|
| `--tools <n>`, `--nonreadonly <n>` | Turn tool counts feeding `--auto`'s heuristic (defaults assume real work) |
|
|
369
404
|
|
|
@@ -379,6 +414,8 @@ which project + branch finished without writing per-IDE wrappers. Pass
|
|
|
379
414
|
| `--ws-url <url>` | WebSocket endpoint (or set `ZEPH_WS_URL` env, or `wsUrl` in `~/.zeph/config.json`) |
|
|
380
415
|
| `--key <api-key>` | API key (or set `ZEPH_API_KEY` env) |
|
|
381
416
|
| `--base-url <url>` | REST API base URL (or set `ZEPH_BASE_URL` env, or `baseUrl` in `~/.zeph/config.json`) |
|
|
417
|
+
| `--stop` | Stop the running daemon and clear its PID/version stamps |
|
|
418
|
+
| `--restart` | Stop it and relaunch detached (logs to `~/.zeph/listener.log`) |
|
|
382
419
|
|
|
383
420
|
The listener reconnects with exponential backoff + jitter (1 s → 30 s
|
|
384
421
|
cap). Heartbeat is ping every 25 s with a 10 s pong timeout. On an
|
|
@@ -401,23 +438,45 @@ code 3 instead of looping forever — fix the key and restart.
|
|
|
401
438
|
| `--json` | Output JSON format |
|
|
402
439
|
| `--version` | Print version |
|
|
403
440
|
|
|
404
|
-
### Mute
|
|
441
|
+
### Mute & push mode
|
|
405
442
|
|
|
406
|
-
|
|
407
|
-
|
|
443
|
+
Both live as state files under `${XDG_STATE_HOME:-~/.local/state}/zeph`,
|
|
444
|
+
keyed by a `cksum` hash of the project directory. Claude Code's
|
|
445
|
+
`/zeph-mute` / `/zeph-quiet` / `/zeph-loud` / `/zeph-normal` write them;
|
|
446
|
+
the CLI reads them (mute on every `notify`, push mode on `--auto`).
|
|
408
447
|
|
|
409
448
|
Notifications are silently skipped when a mute file exists for the
|
|
410
449
|
current project:
|
|
411
450
|
|
|
412
451
|
```bash
|
|
413
|
-
|
|
414
|
-
HASH=$(
|
|
415
|
-
|
|
452
|
+
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/zeph"
|
|
453
|
+
HASH=$(printf '%s' "$PROJECT_DIR" | cksum | cut -d' ' -f1)
|
|
454
|
+
|
|
455
|
+
# Mute (created by /zeph-mute in the Claude Code plugin)
|
|
456
|
+
mkdir -p "$STATE_DIR" && touch "$STATE_DIR/muted-$HASH"
|
|
416
457
|
|
|
417
458
|
# Unmute
|
|
418
|
-
rm /
|
|
459
|
+
rm -f "$STATE_DIR/muted-$HASH"
|
|
419
460
|
```
|
|
420
461
|
|
|
462
|
+
Push mode is a one-word file (`quiet` / `loud` / `normal`) resolved in
|
|
463
|
+
this order — first hit wins:
|
|
464
|
+
|
|
465
|
+
| Order | File | Set by |
|
|
466
|
+
|-------|------|--------|
|
|
467
|
+
| 1 | `$STATE_DIR/pushmode-<hash>` | `/zeph-quiet` · `/zeph-loud` · `/zeph-normal` |
|
|
468
|
+
| 2 | `/tmp/zeph-pushmode-<hash>` | older versions (honored only when you own the file) |
|
|
469
|
+
| 3 | `$STATE_DIR/pushmode-default` | `/zeph-quiet --global` — the machine-wide default |
|
|
470
|
+
| 4 | *(none)* | `normal` |
|
|
471
|
+
|
|
472
|
+
So `/zeph-quiet --global` quiets every project that has no dial of its
|
|
473
|
+
own, and a per-project dial always overrides it. Mute has no `-default`
|
|
474
|
+
form on purpose: it is keyed on presence, not content, so a global mute
|
|
475
|
+
could never be lifted for a single project.
|
|
476
|
+
|
|
477
|
+
Legacy `/tmp/zeph-muted-<hash>` files are still honored when owned by the
|
|
478
|
+
current user (the state dir moved out of world-writable `/tmp`).
|
|
479
|
+
|
|
421
480
|
The CLI checks `CLAUDE_PROJECT_DIR`, `CURSOR_PROJECT_DIR`,
|
|
422
481
|
`WINDSURF_PROJECT_DIR`, and falls back to `cwd`.
|
|
423
482
|
|
package/dist/cli.js
CHANGED
|
@@ -81,6 +81,10 @@ ${usageAgentLines()}
|
|
|
81
81
|
listener Resident daemon — receives 'agent.command' pushes from
|
|
82
82
|
the phone picker and injects them into the matching
|
|
83
83
|
tmux session.
|
|
84
|
+
--stop stop the running daemon
|
|
85
|
+
--restart stop it and relaunch in the background
|
|
86
|
+
(needed after an upgrade: 'npm i -g' swaps
|
|
87
|
+
the package but never the live process)
|
|
84
88
|
|
|
85
89
|
Notify options:
|
|
86
90
|
--title <text> Push title
|
package/dist/gate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAyBA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAC3D,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEvD,MAAM,WAAW,SAAS;IACxB,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,gBAAgB,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,YAAY,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAC;CAC7B;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa;;;;CAIhB,CAAC;AAEX,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,GAAG,SAAS,KAAG,UACS,CAAC;AAEpE,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,GAAG,SAAS,KAAG,YACR,CAAC;AAErD,eAAO,MAAM,UAAU,GAAI,OAAO,SAAS,KAAG,WAW7C,CAAC;AAeF,eAAO,MAAM,QAAQ,QAAO,MACoD,CAAC;
|
|
1
|
+
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAyBA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAC3D,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEvD,MAAM,WAAW,SAAS;IACxB,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,gBAAgB,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,YAAY,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAC;CAC7B;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa;;;;CAIhB,CAAC;AAEX,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,GAAG,SAAS,KAAG,UACS,CAAC;AAEpE,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,GAAG,SAAS,KAAG,YACR,CAAC;AAErD,eAAO,MAAM,UAAU,GAAI,OAAO,SAAS,KAAG,WAW7C,CAAC;AAeF,eAAO,MAAM,QAAQ,QAAO,MACoD,CAAC;AA2BjF,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,IAOlD,CAAC;AAWF,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,MAA4C,CAAC;AAE7F;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MAG1B,CAAC;AAEnB,0DAA0D;AAC1D,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,KAAG,OAGrC,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,YAU1C,CAAC"}
|
package/dist/gate.js
CHANGED
|
@@ -78,13 +78,23 @@ const ownedByCurrentUser = (path) => {
|
|
|
78
78
|
return false;
|
|
79
79
|
}
|
|
80
80
|
};
|
|
81
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Resolve a state file: current location first, then user-owned legacy /tmp,
|
|
83
|
+
* then — for `pushmode` only — the machine-wide default written by
|
|
84
|
+
* `/zeph-quiet --global`. Mirrors plugin/hooks/gate.sh zeph_state_present,
|
|
85
|
+
* including the deliberate absence of a global mute (see the comment there).
|
|
86
|
+
*/
|
|
82
87
|
const findStateFile = (kind, hash) => {
|
|
83
88
|
const current = (0, path_1.join)((0, exports.stateDir)(), `${kind}-${hash}`);
|
|
84
89
|
if ((0, fs_1.existsSync)(current))
|
|
85
90
|
return current;
|
|
86
91
|
const legacy = `/tmp/zeph-${kind}-${hash}`;
|
|
87
|
-
|
|
92
|
+
if (ownedByCurrentUser(legacy))
|
|
93
|
+
return legacy;
|
|
94
|
+
if (kind !== 'pushmode')
|
|
95
|
+
return null;
|
|
96
|
+
const globalDefault = (0, path_1.join)((0, exports.stateDir)(), 'pushmode-default');
|
|
97
|
+
return (0, fs_1.existsSync)(globalDefault) ? globalDefault : null;
|
|
88
98
|
};
|
|
89
99
|
const projectHash = (dir) => {
|
|
90
100
|
try {
|
package/dist/installer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AA4BzC;;;GAGG;AACH;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,YAAY,MAAM,GAAG,SAAS,EAAE,SAAS,OAAO,KAAG,OACxD,CAAC;AAoBzB;6EAC6E;AAC7E,eAAO,MAAM,aAAa,GAAI,UAAU,MAAM,EAAE,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,IAqBhF,CAAC;AAgQF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,UAAU,KAAK,EAAE,EAAE,MAAM,MAAM,KAAG,KAAK,EAKxE,CAAC;AA8EF,eAAO,MAAM,aAAa,GAAU,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAG,OAAO,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AA4BzC;;;GAGG;AACH;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,YAAY,MAAM,GAAG,SAAS,EAAE,SAAS,OAAO,KAAG,OACxD,CAAC;AAoBzB;6EAC6E;AAC7E,eAAO,MAAM,aAAa,GAAI,UAAU,MAAM,EAAE,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,IAqBhF,CAAC;AAgQF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,UAAU,KAAK,EAAE,EAAE,MAAM,MAAM,KAAG,KAAK,EAKxE,CAAC;AA8EF,eAAO,MAAM,aAAa,GAAU,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAG,OAAO,CAAC,MAAM,CA4G1F,CAAC"}
|
package/dist/installer.js
CHANGED
|
@@ -485,6 +485,15 @@ const handleInstall = async (args) => {
|
|
|
485
485
|
console.log('\n Testing connection...');
|
|
486
486
|
await testConnection(apiKey, baseUrl);
|
|
487
487
|
console.log('\n Done! Restart your agents.\n');
|
|
488
|
+
// The single most-reported surprise: pushes are not scoped to `zeph cc`.
|
|
489
|
+
// Hooks fire for every session of every configured agent, so say it here —
|
|
490
|
+
// at the moment the hooks get installed — together with the volume dials.
|
|
491
|
+
console.log(' Notifications now fire for EVERY session of each agent above');
|
|
492
|
+
console.log(' (not only sessions launched with `zeph cc`). Dial the volume');
|
|
493
|
+
console.log(' any time — in Claude Code:');
|
|
494
|
+
console.log(' /zeph-quiet --global only blockers + high-priority, all projects');
|
|
495
|
+
console.log(' /zeph-mute full silence, current project');
|
|
496
|
+
console.log(' /zeph-status show what is in effect\n');
|
|
488
497
|
return 0;
|
|
489
498
|
};
|
|
490
499
|
exports.handleInstall = handleInstall;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export declare const LISTENER_PID_FILE: string;
|
|
2
|
+
/** CLI version the running daemon booted from. Absent ⇒ pre-stamp build. */
|
|
3
|
+
export declare const LISTENER_VERSION_FILE: string;
|
|
4
|
+
export declare const LISTENER_LOG_FILE: string;
|
|
5
|
+
/**
|
|
6
|
+
* PID of the listener on record, or null when the file is missing or stale.
|
|
7
|
+
* Stale PID files (process gone) read as "no listener" so a crashed daemon
|
|
8
|
+
* never blocks the next autostart.
|
|
9
|
+
*/
|
|
10
|
+
export declare const runningListenerPid: () => number | null;
|
|
11
|
+
/**
|
|
12
|
+
* CLI version the running daemon started from, or null when unknown.
|
|
13
|
+
* null is meaningful: every build before the stamp existed predates the
|
|
14
|
+
* live-terminal protocol, so callers treat "unknown" as "stale".
|
|
15
|
+
*/
|
|
16
|
+
export declare const runningListenerVersion: () => string | null;
|
|
17
|
+
/** Claim the singleton slot for THIS process and stamp the version with it. */
|
|
18
|
+
export declare const writeListenerRuntime: (version: string) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Drop the PID/version stamps, but only when they still name `pid` — a
|
|
21
|
+
* successor that already claimed the slot must not be trampled by a
|
|
22
|
+
* predecessor's exit handler.
|
|
23
|
+
*/
|
|
24
|
+
export declare const clearListenerRuntime: (pid: number) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Drop stamps left behind by a daemon that died without running its exit
|
|
27
|
+
* handler (crash, SIGKILL, reboot-era leftovers). No-op while one is alive.
|
|
28
|
+
*/
|
|
29
|
+
export declare const clearStaleListenerRuntime: () => void;
|
|
30
|
+
/**
|
|
31
|
+
* Stop the daemon and wait for the process to actually go away. SIGTERM
|
|
32
|
+
* first so its exit handler clears the stamps, SIGKILL once the grace window
|
|
33
|
+
* expires. Resolves true when the process is confirmed gone.
|
|
34
|
+
*
|
|
35
|
+
* Waiting matters: the caller's next move is spawning a replacement, and two
|
|
36
|
+
* live daemons on one machine both answer the same pushes.
|
|
37
|
+
*/
|
|
38
|
+
export declare const stopListener: (pid: number, timeoutMs?: number) => Promise<boolean>;
|
|
39
|
+
/**
|
|
40
|
+
* Path to the running cli.js entry. wrapper.js/listener.js sit next to cli.js
|
|
41
|
+
* in dist/, so __dirname resolves it directly — independent of how the user
|
|
42
|
+
* invoked us.
|
|
43
|
+
*
|
|
44
|
+
* `process.argv[1]` is unreliable here: when `zeph` runs via the npm-installed
|
|
45
|
+
* bin shim (`/usr/local/bin/zeph` → cli.js via a wrapper script), argv[1] is
|
|
46
|
+
* the shim path (`.../bin/zeph`), NOT `cli.js`. That made the original
|
|
47
|
+
* `/cli\.(js|ts|mjs|cjs)$/` check silently reject the entry and the autospawn
|
|
48
|
+
* never fired — the bug only surfaced once the user switched to the global
|
|
49
|
+
* npm install.
|
|
50
|
+
*
|
|
51
|
+
* Fall back to argv[1] only when the __dirname-relative file doesn't exist
|
|
52
|
+
* (some packaging where dist layout differs).
|
|
53
|
+
*/
|
|
54
|
+
export declare const resolveCliPath: () => string | null;
|
|
55
|
+
export declare const rotateListenerLogIfLarge: () => void;
|
|
56
|
+
/**
|
|
57
|
+
* Launch `zeph listener` detached, with output appended to the listener log
|
|
58
|
+
* so nothing is lost once we let go of it. Returns false when the cli entry
|
|
59
|
+
* couldn't be resolved or the spawn failed — the caller decides how loud that
|
|
60
|
+
* is (autostart is best-effort, an explicit `--restart` is not).
|
|
61
|
+
*/
|
|
62
|
+
export declare const spawnListenerDetached: () => boolean;
|
|
63
|
+
//# sourceMappingURL=listener-process.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listener-process.d.ts","sourceRoot":"","sources":["../src/listener-process.ts"],"names":[],"mappings":"AAwBA,eAAO,MAAM,iBAAiB,QAAiC,CAAC;AAChE,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB,QAAqC,CAAC;AACxE,eAAO,MAAM,iBAAiB,QAAiC,CAAC;AAIhE;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,QAAO,MAAM,GAAG,IAS9C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAO,MAAM,GAAG,IAMlD,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,KAAG,IAItD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAAI,KAAK,MAAM,KAAG,IASlD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,yBAAyB,QAAO,IAQ5C,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAU,KAAK,MAAM,EAAE,kBAAiB,KAAG,OAAO,CAAC,OAAO,CAqBlF,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,QAAO,MAAM,GAAG,IAM1C,CAAC;AASF,eAAO,MAAM,wBAAwB,QAAO,IAM3C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,QAAO,OAiBxC,CAAC"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.spawnListenerDetached = exports.rotateListenerLogIfLarge = exports.resolveCliPath = exports.stopListener = exports.clearStaleListenerRuntime = exports.clearListenerRuntime = exports.writeListenerRuntime = exports.runningListenerVersion = exports.runningListenerPid = exports.LISTENER_LOG_FILE = exports.LISTENER_VERSION_FILE = exports.LISTENER_PID_FILE = void 0;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const node_os_1 = require("node:os");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
/**
|
|
9
|
+
* Process-level facts about the resident `zeph listener` daemon: where its
|
|
10
|
+
* PID/version stamps live, whether one is alive, how to stop it and how to
|
|
11
|
+
* respawn it detached.
|
|
12
|
+
*
|
|
13
|
+
* This lives OUTSIDE listener.ts on purpose. `wrapper.ts` (the `zeph cc` hot
|
|
14
|
+
* path) needs all of it, and importing listener.ts there would drag `ws` +
|
|
15
|
+
* the crypto stack into every agent launch. Same split as
|
|
16
|
+
* listener-device-id.ts — one small module both sides can read.
|
|
17
|
+
*
|
|
18
|
+
* Why a version stamp at all: `npm i -g @zeph-to/cli` replaces the package on
|
|
19
|
+
* disk but NOT the daemon already running from the old dist. That daemon
|
|
20
|
+
* keeps answering pushes (so chat looks fine) while silently ignoring every
|
|
21
|
+
* subtype added since it started — which is exactly how a pre-1.24 listener
|
|
22
|
+
* left the phone's live terminal spinning until the machine was rebooted.
|
|
23
|
+
* Stamping the version next to the PID makes that drift detectable.
|
|
24
|
+
*/
|
|
25
|
+
const ZEPH_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), '.zeph');
|
|
26
|
+
exports.LISTENER_PID_FILE = (0, node_path_1.join)(ZEPH_DIR, 'listener.pid');
|
|
27
|
+
/** CLI version the running daemon booted from. Absent ⇒ pre-stamp build. */
|
|
28
|
+
exports.LISTENER_VERSION_FILE = (0, node_path_1.join)(ZEPH_DIR, 'listener.version');
|
|
29
|
+
exports.LISTENER_LOG_FILE = (0, node_path_1.join)(ZEPH_DIR, 'listener.log');
|
|
30
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
31
|
+
/**
|
|
32
|
+
* PID of the listener on record, or null when the file is missing or stale.
|
|
33
|
+
* Stale PID files (process gone) read as "no listener" so a crashed daemon
|
|
34
|
+
* never blocks the next autostart.
|
|
35
|
+
*/
|
|
36
|
+
const runningListenerPid = () => {
|
|
37
|
+
try {
|
|
38
|
+
const pid = Number((0, node_fs_1.readFileSync)(exports.LISTENER_PID_FILE, 'utf-8').trim());
|
|
39
|
+
if (!Number.isFinite(pid) || pid <= 0)
|
|
40
|
+
return null;
|
|
41
|
+
process.kill(pid, 0); // signal 0 = existence check, throws when gone
|
|
42
|
+
return pid;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
exports.runningListenerPid = runningListenerPid;
|
|
49
|
+
/**
|
|
50
|
+
* CLI version the running daemon started from, or null when unknown.
|
|
51
|
+
* null is meaningful: every build before the stamp existed predates the
|
|
52
|
+
* live-terminal protocol, so callers treat "unknown" as "stale".
|
|
53
|
+
*/
|
|
54
|
+
const runningListenerVersion = () => {
|
|
55
|
+
try {
|
|
56
|
+
return (0, node_fs_1.readFileSync)(exports.LISTENER_VERSION_FILE, 'utf-8').trim() || null;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
exports.runningListenerVersion = runningListenerVersion;
|
|
63
|
+
/** Claim the singleton slot for THIS process and stamp the version with it. */
|
|
64
|
+
const writeListenerRuntime = (version) => {
|
|
65
|
+
(0, node_fs_1.mkdirSync)(ZEPH_DIR, { recursive: true });
|
|
66
|
+
(0, node_fs_1.writeFileSync)(exports.LISTENER_PID_FILE, String(process.pid));
|
|
67
|
+
(0, node_fs_1.writeFileSync)(exports.LISTENER_VERSION_FILE, version);
|
|
68
|
+
};
|
|
69
|
+
exports.writeListenerRuntime = writeListenerRuntime;
|
|
70
|
+
/**
|
|
71
|
+
* Drop the PID/version stamps, but only when they still name `pid` — a
|
|
72
|
+
* successor that already claimed the slot must not be trampled by a
|
|
73
|
+
* predecessor's exit handler.
|
|
74
|
+
*/
|
|
75
|
+
const clearListenerRuntime = (pid) => {
|
|
76
|
+
try {
|
|
77
|
+
if (!(0, node_fs_1.existsSync)(exports.LISTENER_PID_FILE))
|
|
78
|
+
return;
|
|
79
|
+
if (Number((0, node_fs_1.readFileSync)(exports.LISTENER_PID_FILE, 'utf-8').trim()) !== pid)
|
|
80
|
+
return;
|
|
81
|
+
(0, node_fs_1.unlinkSync)(exports.LISTENER_PID_FILE);
|
|
82
|
+
try {
|
|
83
|
+
(0, node_fs_1.unlinkSync)(exports.LISTENER_VERSION_FILE);
|
|
84
|
+
}
|
|
85
|
+
catch { /* best-effort — a missing stamp is already the desired state */ }
|
|
86
|
+
}
|
|
87
|
+
catch { /* best-effort */ }
|
|
88
|
+
};
|
|
89
|
+
exports.clearListenerRuntime = clearListenerRuntime;
|
|
90
|
+
/**
|
|
91
|
+
* Drop stamps left behind by a daemon that died without running its exit
|
|
92
|
+
* handler (crash, SIGKILL, reboot-era leftovers). No-op while one is alive.
|
|
93
|
+
*/
|
|
94
|
+
const clearStaleListenerRuntime = () => {
|
|
95
|
+
if ((0, exports.runningListenerPid)() !== null)
|
|
96
|
+
return;
|
|
97
|
+
try {
|
|
98
|
+
(0, node_fs_1.unlinkSync)(exports.LISTENER_PID_FILE);
|
|
99
|
+
}
|
|
100
|
+
catch { /* nothing to clear */ }
|
|
101
|
+
try {
|
|
102
|
+
(0, node_fs_1.unlinkSync)(exports.LISTENER_VERSION_FILE);
|
|
103
|
+
}
|
|
104
|
+
catch { /* nothing to clear */ }
|
|
105
|
+
};
|
|
106
|
+
exports.clearStaleListenerRuntime = clearStaleListenerRuntime;
|
|
107
|
+
/**
|
|
108
|
+
* Stop the daemon and wait for the process to actually go away. SIGTERM
|
|
109
|
+
* first so its exit handler clears the stamps, SIGKILL once the grace window
|
|
110
|
+
* expires. Resolves true when the process is confirmed gone.
|
|
111
|
+
*
|
|
112
|
+
* Waiting matters: the caller's next move is spawning a replacement, and two
|
|
113
|
+
* live daemons on one machine both answer the same pushes.
|
|
114
|
+
*/
|
|
115
|
+
const stopListener = async (pid, timeoutMs = 3_000) => {
|
|
116
|
+
try {
|
|
117
|
+
process.kill(pid, 'SIGTERM');
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return true; // already gone
|
|
121
|
+
}
|
|
122
|
+
const deadline = Date.now() + timeoutMs;
|
|
123
|
+
while (Date.now() < deadline) {
|
|
124
|
+
await sleep(50);
|
|
125
|
+
try {
|
|
126
|
+
process.kill(pid, 0);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
(0, exports.clearListenerRuntime)(pid);
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
process.kill(pid, 'SIGKILL');
|
|
135
|
+
}
|
|
136
|
+
catch { /* raced us to exit */ }
|
|
137
|
+
(0, exports.clearListenerRuntime)(pid);
|
|
138
|
+
return false;
|
|
139
|
+
};
|
|
140
|
+
exports.stopListener = stopListener;
|
|
141
|
+
/**
|
|
142
|
+
* Path to the running cli.js entry. wrapper.js/listener.js sit next to cli.js
|
|
143
|
+
* in dist/, so __dirname resolves it directly — independent of how the user
|
|
144
|
+
* invoked us.
|
|
145
|
+
*
|
|
146
|
+
* `process.argv[1]` is unreliable here: when `zeph` runs via the npm-installed
|
|
147
|
+
* bin shim (`/usr/local/bin/zeph` → cli.js via a wrapper script), argv[1] is
|
|
148
|
+
* the shim path (`.../bin/zeph`), NOT `cli.js`. That made the original
|
|
149
|
+
* `/cli\.(js|ts|mjs|cjs)$/` check silently reject the entry and the autospawn
|
|
150
|
+
* never fired — the bug only surfaced once the user switched to the global
|
|
151
|
+
* npm install.
|
|
152
|
+
*
|
|
153
|
+
* Fall back to argv[1] only when the __dirname-relative file doesn't exist
|
|
154
|
+
* (some packaging where dist layout differs).
|
|
155
|
+
*/
|
|
156
|
+
const resolveCliPath = () => {
|
|
157
|
+
const local = (0, node_path_1.join)(__dirname, 'cli.js');
|
|
158
|
+
if ((0, node_fs_1.existsSync)(local))
|
|
159
|
+
return local;
|
|
160
|
+
const entry = process.argv[1];
|
|
161
|
+
if (entry && /cli\.(js|ts|mjs|cjs)$/.test(entry))
|
|
162
|
+
return entry;
|
|
163
|
+
return null;
|
|
164
|
+
};
|
|
165
|
+
exports.resolveCliPath = resolveCliPath;
|
|
166
|
+
/**
|
|
167
|
+
* Rotate the listener log once it grows past 5 MB. The daemon runs for days
|
|
168
|
+
* and writes 2-3 lines per 5-s cycle, so without rotation the file climbs
|
|
169
|
+
* into the tens of megabytes. Keep the previous run's tail under `.old`.
|
|
170
|
+
*/
|
|
171
|
+
const LISTENER_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
172
|
+
const rotateListenerLogIfLarge = () => {
|
|
173
|
+
try {
|
|
174
|
+
if (!(0, node_fs_1.existsSync)(exports.LISTENER_LOG_FILE))
|
|
175
|
+
return;
|
|
176
|
+
if ((0, node_fs_1.statSync)(exports.LISTENER_LOG_FILE).size <= LISTENER_LOG_MAX_BYTES)
|
|
177
|
+
return;
|
|
178
|
+
(0, node_fs_1.renameSync)(exports.LISTENER_LOG_FILE, exports.LISTENER_LOG_FILE + '.old');
|
|
179
|
+
}
|
|
180
|
+
catch { /* best-effort */ }
|
|
181
|
+
};
|
|
182
|
+
exports.rotateListenerLogIfLarge = rotateListenerLogIfLarge;
|
|
183
|
+
/**
|
|
184
|
+
* Launch `zeph listener` detached, with output appended to the listener log
|
|
185
|
+
* so nothing is lost once we let go of it. Returns false when the cli entry
|
|
186
|
+
* couldn't be resolved or the spawn failed — the caller decides how loud that
|
|
187
|
+
* is (autostart is best-effort, an explicit `--restart` is not).
|
|
188
|
+
*/
|
|
189
|
+
const spawnListenerDetached = () => {
|
|
190
|
+
const cliPath = (0, exports.resolveCliPath)();
|
|
191
|
+
if (!cliPath)
|
|
192
|
+
return false;
|
|
193
|
+
try {
|
|
194
|
+
(0, node_fs_1.mkdirSync)(ZEPH_DIR, { recursive: true });
|
|
195
|
+
(0, exports.rotateListenerLogIfLarge)();
|
|
196
|
+
const out = (0, node_fs_1.openSync)(exports.LISTENER_LOG_FILE, 'a');
|
|
197
|
+
const child = (0, node_child_process_1.spawn)(process.execPath, [cliPath, 'listener'], {
|
|
198
|
+
detached: true,
|
|
199
|
+
stdio: ['ignore', out, out],
|
|
200
|
+
env: { ...process.env, ZEPH_LISTENER_AUTOSTART: '1' },
|
|
201
|
+
});
|
|
202
|
+
child.unref();
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
exports.spawnListenerDetached = spawnListenerDetached;
|
package/dist/listener.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../src/listener.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;
|
|
1
|
+
{"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../src/listener.ts"],"names":[],"mappings":"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"}
|
package/dist/listener.js
CHANGED
|
@@ -33,6 +33,7 @@ const os_1 = require("os");
|
|
|
33
33
|
const path_1 = require("path");
|
|
34
34
|
const ws_1 = __importDefault(require("ws"));
|
|
35
35
|
const config_js_1 = require("./config.js");
|
|
36
|
+
const listener_process_js_1 = require("./listener-process.js");
|
|
36
37
|
const gate_js_1 = require("./gate.js");
|
|
37
38
|
const remote_agents_js_1 = require("./remote-agents.js");
|
|
38
39
|
const agent_state_js_1 = require("./agent-state.js");
|
|
@@ -1644,8 +1645,6 @@ const resolveWsUrl = (args, config, baseUrl) => {
|
|
|
1644
1645
|
};
|
|
1645
1646
|
exports.resolveWsUrl = resolveWsUrl;
|
|
1646
1647
|
// ── Singleton guard (PID file) ──────────────────────────────────────
|
|
1647
|
-
const ZEPH_DIR = (0, path_1.join)((0, os_1.homedir)(), '.zeph');
|
|
1648
|
-
const LISTENER_PID_FILE = (0, path_1.join)(ZEPH_DIR, 'listener.pid');
|
|
1649
1648
|
/**
|
|
1650
1649
|
* Whether another `zeph listener` is already running on this machine.
|
|
1651
1650
|
* The wrapper's autostart and a user typing `zeph listener` by hand can
|
|
@@ -1656,38 +1655,54 @@ const LISTENER_PID_FILE = (0, path_1.join)(ZEPH_DIR, 'listener.pid');
|
|
|
1656
1655
|
* wrapper can recover from crashes without manual cleanup.
|
|
1657
1656
|
*/
|
|
1658
1657
|
const otherListenerAlive = () => {
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
if (!Number.isFinite(pid) || pid <= 0 || pid === process.pid)
|
|
1662
|
-
return null;
|
|
1663
|
-
process.kill(pid, 0); // existence check, throws if dead
|
|
1664
|
-
return pid;
|
|
1665
|
-
}
|
|
1666
|
-
catch {
|
|
1667
|
-
return null;
|
|
1668
|
-
}
|
|
1658
|
+
const pid = (0, listener_process_js_1.runningListenerPid)();
|
|
1659
|
+
return pid === null || pid === process.pid ? null : pid;
|
|
1669
1660
|
};
|
|
1670
1661
|
const writeListenerPid = () => {
|
|
1671
1662
|
try {
|
|
1672
|
-
|
|
1673
|
-
|
|
1663
|
+
// Stamp the version alongside the pid: `npm i -g` swaps the package
|
|
1664
|
+
// on disk without touching this already-running process, and the
|
|
1665
|
+
// stamp is how `zeph cc` notices the drift and restarts us.
|
|
1666
|
+
(0, listener_process_js_1.writeListenerRuntime)(config_js_1.VERSION);
|
|
1674
1667
|
}
|
|
1675
1668
|
catch (err) {
|
|
1676
|
-
log(`! could not write ${LISTENER_PID_FILE}: ${err.message}`);
|
|
1669
|
+
log(`! could not write ${listener_process_js_1.LISTENER_PID_FILE}: ${err.message}`);
|
|
1677
1670
|
}
|
|
1678
1671
|
};
|
|
1679
|
-
const removeListenerPid = () =>
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1672
|
+
const removeListenerPid = () => (0, listener_process_js_1.clearListenerRuntime)(process.pid);
|
|
1673
|
+
/**
|
|
1674
|
+
* `--stop` / `--restart`: the daemon is a background service, so the only
|
|
1675
|
+
* way to replace a stale one used to be hunting its pid by hand. `--restart`
|
|
1676
|
+
* relaunches detached (not in the caller's foreground) because restarting is
|
|
1677
|
+
* an ops action — the user wants their shell back, not a new daemon pinned
|
|
1678
|
+
* to it.
|
|
1679
|
+
*/
|
|
1680
|
+
const handleListenerLifecycle = async (restart) => {
|
|
1681
|
+
const pid = otherListenerAlive();
|
|
1682
|
+
if (pid) {
|
|
1683
|
+
const stopped = await (0, listener_process_js_1.stopListener)(pid);
|
|
1684
|
+
console.log(`zeph listener: stopped pid ${pid}${stopped ? '' : ' (forced)'}`);
|
|
1687
1685
|
}
|
|
1688
|
-
|
|
1686
|
+
else {
|
|
1687
|
+
console.log('zeph listener: no listener running');
|
|
1688
|
+
// A pid file left behind by a crashed daemon would otherwise keep
|
|
1689
|
+
// tripping the singleton guard on the next start.
|
|
1690
|
+
(0, listener_process_js_1.clearStaleListenerRuntime)();
|
|
1691
|
+
}
|
|
1692
|
+
if (!restart)
|
|
1693
|
+
return 0;
|
|
1694
|
+
if (!(0, listener_process_js_1.spawnListenerDetached)()) {
|
|
1695
|
+
console.error('zeph listener: could not resolve the cli entry to respawn — run `zeph listener` manually.');
|
|
1696
|
+
return 1;
|
|
1697
|
+
}
|
|
1698
|
+
console.log(`zeph listener: restarted in background (log: ${listener_process_js_1.LISTENER_LOG_FILE})`);
|
|
1699
|
+
return 0;
|
|
1689
1700
|
};
|
|
1690
1701
|
const handleListener = async (args) => {
|
|
1702
|
+
// Lifecycle flags run before verifyTmux — stopping a daemon shouldn't
|
|
1703
|
+
// require a healthy tmux.
|
|
1704
|
+
if (args.stop === true || args.restart === true)
|
|
1705
|
+
return handleListenerLifecycle(args.restart === true);
|
|
1691
1706
|
verifyTmux();
|
|
1692
1707
|
// Refuse to start when another listener is already running. The
|
|
1693
1708
|
// wrapper's autostart calls us blindly on every `zeph cc`; the user
|
|
@@ -1723,7 +1738,10 @@ const handleListener = async (args) => {
|
|
|
1723
1738
|
}
|
|
1724
1739
|
writeListenerPid();
|
|
1725
1740
|
process.on('exit', removeListenerPid);
|
|
1726
|
-
|
|
1741
|
+
// Version on the first line: the only way to tell which build a
|
|
1742
|
+
// long-lived daemon is actually running (an `npm i -g` days ago says
|
|
1743
|
+
// nothing about the process that's been up since before it).
|
|
1744
|
+
log(`zeph listener starting — v${config_js_1.VERSION} — ${wsUrl}`);
|
|
1727
1745
|
log(`device=${(0, exports.computeListenerDeviceId)()} host=${(0, os_1.hostname)()} pid=${process.pid}`);
|
|
1728
1746
|
log("Waiting for 'agent.command' pushes from the phone picker. Ctrl-C to stop.");
|
|
1729
1747
|
// Heartbeat memory log — once an hour. Lets the user (and us) spot
|
package/dist/wrapper.d.ts
CHANGED
|
@@ -17,6 +17,31 @@ export declare const tmuxSessionName: (project: string) => string;
|
|
|
17
17
|
* dependency-free against the user's running tmux server.
|
|
18
18
|
*/
|
|
19
19
|
export declare const findAvailableSession: (base: string) => string;
|
|
20
|
+
/**
|
|
21
|
+
* Whether the daemon on record is running a different build than the one
|
|
22
|
+
* we're launching from. `npm i -g @zeph-to/cli` swaps dist/ but leaves the
|
|
23
|
+
* live process untouched, so a daemon can stay days behind the installed
|
|
24
|
+
* package — answering pushes (chat looks fine) while silently ignoring every
|
|
25
|
+
* message subtype added since it booted. That is exactly how a pre-1.24
|
|
26
|
+
* listener left the phone's live terminal spinning until a reboot.
|
|
27
|
+
*
|
|
28
|
+
* A missing stamp means the daemon predates version stamping, which puts it
|
|
29
|
+
* behind by definition — treat unknown as drifted.
|
|
30
|
+
*
|
|
31
|
+
* Strictly "installed is newer", never a plain inequality: one account can
|
|
32
|
+
* have several installs at different versions (nvm node versions, a repo
|
|
33
|
+
* build alongside the global one), and `!==` would make each `zeph cc` kill
|
|
34
|
+
* and respawn the other's daemon forever. An older `zeph cc` leaves a newer
|
|
35
|
+
* daemon alone — downgrading it would reintroduce the very gap this closes.
|
|
36
|
+
*/
|
|
37
|
+
export declare const listenerVersionDrifted: (running: string | null, installed: string) => boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Make sure the phone-bridge daemon is running AND is the build we just
|
|
40
|
+
* launched from. `zeph cc` is the right moment to replace a drifted one: the
|
|
41
|
+
* user is starting fresh work rather than mid-task, so the ~1s restart window
|
|
42
|
+
* (in which a push would be dropped — WS fan-out has no queue) costs nothing.
|
|
43
|
+
*/
|
|
44
|
+
export declare const ensureListenerRunning: () => Promise<void>;
|
|
20
45
|
/**
|
|
21
46
|
* Launch the agent in a named tmux session (or directly if nested) and
|
|
22
47
|
* forward its exit code. `extra` is appended to the agent invocation, so
|
package/dist/wrapper.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../src/wrapper.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../src/wrapper.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAOtD,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,QAAO,MAapC,CAAC;AAEF,+DAA+D;AAC/D,eAAO,MAAM,eAAe,GAAI,SAAS,MAAM,KAAG,MAA2B,CAAC;AAI9E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,GAAI,MAAM,MAAM,KAAG,MAenD,CAAC;AAmCF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,sBAAsB,GAAI,SAAS,MAAM,GAAG,IAAI,EAAE,WAAW,MAAM,KAAG,OAChC,CAAC;AAEpD;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,QAAa,OAAO,CAAC,IAAI,CAa1D,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAU,OAAO,WAAW,EAAE,QAAO,MAAM,EAAO,KAAG,OAAO,CAAC,MAAM,CAmCjG,CAAC"}
|
package/dist/wrapper.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.handleAgentSession = exports.findAvailableSession = exports.tmuxSessionName = exports.detectProjectName = void 0;
|
|
3
|
+
exports.handleAgentSession = exports.ensureListenerRunning = exports.listenerVersionDrifted = exports.findAvailableSession = exports.tmuxSessionName = exports.detectProjectName = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* `zeph cc` / `zeph codex` / `zeph gemini` — spawn an agent inside a named
|
|
6
6
|
* tmux session so the resident listener (`zeph listener`) can address it
|
|
@@ -13,10 +13,10 @@ exports.handleAgentSession = exports.findAvailableSession = exports.tmuxSessionN
|
|
|
13
13
|
* agent directly — letting power users keep their own multiplexer setup.
|
|
14
14
|
*/
|
|
15
15
|
const child_process_1 = require("child_process");
|
|
16
|
-
const fs_1 = require("fs");
|
|
17
|
-
const os_1 = require("os");
|
|
18
16
|
const path_1 = require("path");
|
|
17
|
+
const check_update_js_1 = require("./check-update.js");
|
|
19
18
|
const config_js_1 = require("./config.js");
|
|
19
|
+
const listener_process_js_1 = require("./listener-process.js");
|
|
20
20
|
const FALLBACK_NAME = 'project';
|
|
21
21
|
/** basename(), with a stable fallback for edge paths like `/`. */
|
|
22
22
|
const safeBasename = (path) => (0, path_1.basename)(path) || FALLBACK_NAME;
|
|
@@ -99,111 +99,60 @@ const targetForAgent = (agent, extra) => {
|
|
|
99
99
|
return { cmd: 'tmux', args: ['new', '-A', '-s', session, shellCmd] };
|
|
100
100
|
};
|
|
101
101
|
// ── Background listener auto-start ────────────────────────────────────
|
|
102
|
-
const ZEPH_DIR = (0, path_1.join)((0, os_1.homedir)(), '.zeph');
|
|
103
|
-
const LISTENER_PID_FILE = (0, path_1.join)(ZEPH_DIR, 'listener.pid');
|
|
104
|
-
const LISTENER_LOG_FILE = (0, path_1.join)(ZEPH_DIR, 'listener.log');
|
|
105
|
-
/** True when the PID file points at a still-alive process. */
|
|
106
|
-
const listenerAlive = () => {
|
|
107
|
-
try {
|
|
108
|
-
const pid = Number((0, fs_1.readFileSync)(LISTENER_PID_FILE, 'utf-8').trim());
|
|
109
|
-
if (!Number.isFinite(pid) || pid <= 0)
|
|
110
|
-
return false;
|
|
111
|
-
// Signal 0 = existence check; throws when the process is gone.
|
|
112
|
-
process.kill(pid, 0);
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
102
|
/**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
103
|
+
* Whether the daemon on record is running a different build than the one
|
|
104
|
+
* we're launching from. `npm i -g @zeph-to/cli` swaps dist/ but leaves the
|
|
105
|
+
* live process untouched, so a daemon can stay days behind the installed
|
|
106
|
+
* package — answering pushes (chat looks fine) while silently ignoring every
|
|
107
|
+
* message subtype added since it booted. That is exactly how a pre-1.24
|
|
108
|
+
* listener left the phone's live terminal spinning until a reboot.
|
|
123
109
|
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* script), argv[1] is the shim path (`.../bin/zeph`), NOT `cli.js`.
|
|
127
|
-
* That made the original `/cli\\.(js|ts|mjs|cjs)$/` check silently
|
|
128
|
-
* reject the entry and the autospawn never fired — exactly the bug
|
|
129
|
-
* the user hit ('원래 싱글톤으로 됐었잖아' — yes, on the local alias
|
|
130
|
-
* path where argv[1] IS cli.js; the bug only surfaced once the user
|
|
131
|
-
* switched to the global npm install).
|
|
110
|
+
* A missing stamp means the daemon predates version stamping, which puts it
|
|
111
|
+
* behind by definition — treat unknown as drifted.
|
|
132
112
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
113
|
+
* Strictly "installed is newer", never a plain inequality: one account can
|
|
114
|
+
* have several installs at different versions (nvm node versions, a repo
|
|
115
|
+
* build alongside the global one), and `!==` would make each `zeph cc` kill
|
|
116
|
+
* and respawn the other's daemon forever. An older `zeph cc` leaves a newer
|
|
117
|
+
* daemon alone — downgrading it would reintroduce the very gap this closes.
|
|
135
118
|
*/
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
if ((0, fs_1.existsSync)(local))
|
|
139
|
-
return local;
|
|
140
|
-
const entry = process.argv[1];
|
|
141
|
-
if (entry && /cli\.(js|ts|mjs|cjs)$/.test(entry))
|
|
142
|
-
return entry;
|
|
143
|
-
return null;
|
|
144
|
-
};
|
|
119
|
+
const listenerVersionDrifted = (running, installed) => running === null || (0, check_update_js_1.isNewer)(installed, running);
|
|
120
|
+
exports.listenerVersionDrifted = listenerVersionDrifted;
|
|
145
121
|
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* itself writes its own PID to `~/.zeph/listener.pid` on startup and
|
|
151
|
-
* removes it on graceful exit, so subsequent `zeph cc` invocations skip
|
|
152
|
-
* the spawn when a listener is already up.
|
|
153
|
-
*
|
|
154
|
-
* Failure here is non-fatal — `zeph cc` still launches the agent. The
|
|
155
|
-
* user just loses the phone-bridge feature until they restart.
|
|
122
|
+
* Make sure the phone-bridge daemon is running AND is the build we just
|
|
123
|
+
* launched from. `zeph cc` is the right moment to replace a drifted one: the
|
|
124
|
+
* user is starting fresh work rather than mid-task, so the ~1s restart window
|
|
125
|
+
* (in which a push would be dropped — WS fan-out has no queue) costs nothing.
|
|
156
126
|
*/
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
*/
|
|
163
|
-
const LISTENER_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
164
|
-
const rotateListenerLogIfLarge = () => {
|
|
165
|
-
try {
|
|
166
|
-
if (!(0, fs_1.existsSync)(LISTENER_LOG_FILE))
|
|
167
|
-
return;
|
|
168
|
-
if ((0, fs_1.statSync)(LISTENER_LOG_FILE).size <= LISTENER_LOG_MAX_BYTES)
|
|
127
|
+
const ensureListenerRunning = async () => {
|
|
128
|
+
const pid = (0, listener_process_js_1.runningListenerPid)();
|
|
129
|
+
if (pid !== null) {
|
|
130
|
+
const running = (0, listener_process_js_1.runningListenerVersion)();
|
|
131
|
+
if (!(0, exports.listenerVersionDrifted)(running, config_js_1.VERSION))
|
|
169
132
|
return;
|
|
170
|
-
(
|
|
133
|
+
console.log(`zeph: listener ${running ?? '(pre-1.26)'} is stale — restarting on ${config_js_1.VERSION}`);
|
|
134
|
+
await (0, listener_process_js_1.stopListener)(pid);
|
|
171
135
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (listenerAlive())
|
|
176
|
-
return;
|
|
177
|
-
const cliPath = resolveCliPath();
|
|
178
|
-
if (!cliPath || !(0, fs_1.existsSync)(cliPath))
|
|
179
|
-
return;
|
|
180
|
-
try {
|
|
181
|
-
(0, fs_1.mkdirSync)(ZEPH_DIR, { recursive: true });
|
|
182
|
-
rotateListenerLogIfLarge();
|
|
183
|
-
const out = (0, fs_1.openSync)(LISTENER_LOG_FILE, 'a');
|
|
184
|
-
const child = (0, child_process_1.spawn)(process.execPath, [cliPath, 'listener'], {
|
|
185
|
-
detached: true,
|
|
186
|
-
stdio: ['ignore', out, out],
|
|
187
|
-
env: { ...process.env, ZEPH_LISTENER_AUTOSTART: '1' },
|
|
188
|
-
});
|
|
189
|
-
child.unref();
|
|
190
|
-
console.log(`zeph: listener autostarted in background (log: ${LISTENER_LOG_FILE})`);
|
|
136
|
+
if ((0, listener_process_js_1.spawnListenerDetached)()) {
|
|
137
|
+
if (pid === null)
|
|
138
|
+
console.log(`zeph: listener autostarted in background (log: ${listener_process_js_1.LISTENER_LOG_FILE})`);
|
|
191
139
|
}
|
|
192
|
-
|
|
193
|
-
console.error(
|
|
140
|
+
else {
|
|
141
|
+
console.error('zeph: listener autostart failed — run `zeph listener` manually.');
|
|
194
142
|
}
|
|
195
143
|
};
|
|
144
|
+
exports.ensureListenerRunning = ensureListenerRunning;
|
|
196
145
|
/**
|
|
197
146
|
* Launch the agent in a named tmux session (or directly if nested) and
|
|
198
147
|
* forward its exit code. `extra` is appended to the agent invocation, so
|
|
199
148
|
* `zeph cc --resume foo` runs `claude --resume foo` inside the session.
|
|
200
149
|
* Returns when the agent exits.
|
|
201
150
|
*/
|
|
202
|
-
const handleAgentSession = (agent, extra = []) => {
|
|
203
|
-
// Best-effort: make sure the phone-bridge daemon is running
|
|
204
|
-
//
|
|
205
|
-
// command for the picker on their phone to work.
|
|
206
|
-
ensureListenerRunning();
|
|
151
|
+
const handleAgentSession = async (agent, extra = []) => {
|
|
152
|
+
// Best-effort: make sure the phone-bridge daemon is running, and running
|
|
153
|
+
// the build we were launched from. The user shouldn't need to remember a
|
|
154
|
+
// second command for the picker on their phone to work.
|
|
155
|
+
await (0, exports.ensureListenerRunning)();
|
|
207
156
|
return new Promise((resolve) => {
|
|
208
157
|
const { cmd, args } = targetForAgent(agent.binary, extra);
|
|
209
158
|
const start = Date.now();
|