@zeph-to/hook-sdk 1.10.0 → 1.10.1

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 CHANGED
@@ -1,5 +1,10 @@
1
1
  # @zeph-to/hook-sdk
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/@zeph-to/hook-sdk.svg)](https://www.npmjs.com/package/@zeph-to/hook-sdk)
4
+ [![downloads](https://img.shields.io/npm/dm/@zeph-to/hook-sdk.svg)](https://www.npmjs.com/package/@zeph-to/hook-sdk)
5
+ [![node](https://img.shields.io/node/v/@zeph-to/hook-sdk.svg)](https://nodejs.org)
6
+ [![license](https://img.shields.io/npm/l/@zeph-to/hook-sdk.svg)](./LICENSE)
7
+
3
8
  Push notification SDK + CLI for [Zeph](https://zeph.to), with an optional
4
9
  resident listener that **drives Claude Code / Codex / Gemini sessions
5
10
  from your phone** by injecting messages into named tmux sessions.
@@ -50,7 +50,8 @@ export declare const parseSessionName: (name: string) => {
50
50
  * directory of a tmux pane. Mirrors `mcp-server/config.ts`'s
51
51
  * detectClaudeSessionId: CC writes per-session jsonl files at
52
52
  * `~/.claude/projects/<projectHash>/<UUID>.jsonl` where the hash is
53
- * the cwd with `/` replaced by `-`.
53
+ * the cwd with `/` replaced by `-`. Cached for 60s — see
54
+ * claudeSessionCache.
54
55
  */
55
56
  export declare const detectClaudeSessionId: (cwd: string) => string | null;
56
57
  export interface CollectResult {
@@ -1 +1 @@
1
- {"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../src/listener.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAuBH,KAAK,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;AAG/C,UAAU,YAAY;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,SAAS,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AA2BD,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,EAAE,MAAK,MAAmB,KAAG,OAgB1E,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,KAAG,MAAM,GAAG,IAO7D,CAAC;AA6QF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAK3F,CAAC;AAIF;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,IAkB5D,CAAC;AAoEF,MAAM,WAAW,aAAa;IAC1B,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,0EAA0E;IAC1E,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrD;AAED;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,QAAO,aA0DzC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,QAAO,YAAY,EAAuC,CAAC;AAIvF,UAAU,QAAQ;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,UAAU,cAAc;IACpB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACjD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACpD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB;AAgCD;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,GACnB,MAAM,QAAQ,EACd,OAAM,cAAmB,KAC1B,OAQF,CAAC;AA2BF;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GAAI,OAAM,MAAmB,KAAG,MAGnE,CAAC;AAyLF,eAAO,MAAM,cAAc,GAAU,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAG,OAAO,CAAC,MAAM,CA6E3F,CAAC"}
1
+ {"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../src/listener.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAuBH,KAAK,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;AAG/C,UAAU,YAAY;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,SAAS,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AA2BD,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,EAAE,MAAK,MAAmB,KAAG,OAgB1E,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,KAAG,MAAM,GAAG,IAO7D,CAAC;AA6QF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAK3F,CAAC;AAuCF;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,IAiB5D,CAAC;AAoEF,MAAM,WAAW,aAAa;IAC1B,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,0EAA0E;IAC1E,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrD;AAED;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,QAAO,aA0DzC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,QAAO,YAAY,EAAuC,CAAC;AAIvF,UAAU,QAAQ;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,UAAU,cAAc;IACpB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACjD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACpD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB;AAgCD;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,GACnB,MAAM,QAAQ,EACd,OAAM,cAAmB,KAC1B,OAQF,CAAC;AA2BF;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GAAI,OAAM,MAAmB,KAAG,MAGnE,CAAC;AAyLF,eAAO,MAAM,cAAc,GAAU,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAG,OAAO,CAAC,MAAM,CAyF3F,CAAC"}
package/dist/listener.js CHANGED
@@ -379,13 +379,20 @@ const parseSessionName = (name) => {
379
379
  exports.parseSessionName = parseSessionName;
380
380
  const CLAUDE_PROJECTS_DIR = (0, path_1.join)((0, os_1.homedir)(), '.claude', 'projects');
381
381
  /**
382
- * Locate the most recent Claude Code session UUID for the working
383
- * directory of a tmux pane. Mirrors `mcp-server/config.ts`'s
384
- * detectClaudeSessionId: CC writes per-session jsonl files at
385
- * `~/.claude/projects/<projectHash>/<UUID>.jsonl` where the hash is
386
- * the cwd with `/` replaced by `-`.
382
+ * Cache for detectClaudeSessionId. The function walks every jsonl file
383
+ * in `~/.claude/projects/<hash>/` on each call after weeks of CC use
384
+ * that directory holds hundreds of session files, and we were calling
385
+ * this per tmux session per 5-second report cycle. Heavy disk I/O
386
+ * compounded with multiple sessions caused the report cycle to spike
387
+ * CPU and starve the host shell.
388
+ *
389
+ * The current-session UUID only changes when a new CC session starts
390
+ * in that directory (rare, on the order of hours), so a 60-second TTL
391
+ * is safe and cuts the per-cycle stat count by ~12×.
387
392
  */
388
- const detectClaudeSessionId = (cwd) => {
393
+ const claudeSessionCache = new Map();
394
+ const CLAUDE_SESSION_CACHE_TTL_MS = 60_000;
395
+ const doDetectClaudeSessionId = (cwd) => {
389
396
  try {
390
397
  const projectHash = cwd.replace(/\//g, '-');
391
398
  const sessionsDir = (0, path_1.join)(CLAUDE_PROJECTS_DIR, projectHash);
@@ -407,6 +414,32 @@ const detectClaudeSessionId = (cwd) => {
407
414
  return null;
408
415
  }
409
416
  };
417
+ /**
418
+ * Locate the most recent Claude Code session UUID for the working
419
+ * directory of a tmux pane. Mirrors `mcp-server/config.ts`'s
420
+ * detectClaudeSessionId: CC writes per-session jsonl files at
421
+ * `~/.claude/projects/<projectHash>/<UUID>.jsonl` where the hash is
422
+ * the cwd with `/` replaced by `-`. Cached for 60s — see
423
+ * claudeSessionCache.
424
+ */
425
+ const detectClaudeSessionId = (cwd) => {
426
+ const now = Date.now();
427
+ const cached = claudeSessionCache.get(cwd);
428
+ if (cached && cached.expiresAt > now)
429
+ return cached.sessionId;
430
+ // Cap cache size so a long-lived listener that's seen many cwds
431
+ // doesn't grow unbounded. 64 is plenty for any realistic setup.
432
+ if (claudeSessionCache.size >= 64) {
433
+ // Evict the oldest-expiring entry — Map iteration order is
434
+ // insertion order, so the first key we hit is the oldest.
435
+ const firstKey = claudeSessionCache.keys().next().value;
436
+ if (firstKey !== undefined)
437
+ claudeSessionCache.delete(firstKey);
438
+ }
439
+ const sessionId = doDetectClaudeSessionId(cwd);
440
+ claudeSessionCache.set(cwd, { sessionId, expiresAt: now + CLAUDE_SESSION_CACHE_TTL_MS });
441
+ return sessionId;
442
+ };
410
443
  exports.detectClaudeSessionId = detectClaudeSessionId;
411
444
  // U+241F "Symbol for Unit Separator" — a *printable* Unicode glyph
412
445
  // (3-byte UTF-8) that visually represents the C0 Unit Separator but is
@@ -842,6 +875,17 @@ const handleListener = async (args) => {
842
875
  log(`zeph listener starting — ${wsUrl}`);
843
876
  log(`device=${(0, exports.computeListenerDeviceId)()} host=${(0, os_1.hostname)()} pid=${process.pid}`);
844
877
  log("Waiting for 'agent.command' pushes from the phone picker. Ctrl-C to stop.");
878
+ // Heartbeat memory log — once an hour. Lets the user (and us) spot
879
+ // gradual growth in a long-running daemon before it gets bad enough
880
+ // to make the host shell unresponsive. The MB counter is human-
881
+ // readable and tiny enough not to bloat the log.
882
+ const HEAP_LOG_INTERVAL_MS = 60 * 60 * 1000;
883
+ const heapLogTimer = setInterval(() => {
884
+ const m = process.memoryUsage();
885
+ const mb = (n) => Math.round(n / 1024 / 1024);
886
+ log(`heap: rss=${mb(m.rss)}MB heapUsed=${mb(m.heapUsed)}MB external=${mb(m.external)}MB`);
887
+ }, HEAP_LOG_INTERVAL_MS);
888
+ heapLogTimer.unref();
845
889
  let shuttingDown = false;
846
890
  let activeHandle = null;
847
891
  const stop = (sig) => {
@@ -1 +1 @@
1
- {"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../src/wrapper.ts"],"names":[],"mappings":"AAwBA,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;AAkGF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,EAAE,QAAO,MAAM,EAAO,KAAG,OAAO,CAAC,MAAM,CAmCtF,CAAC"}
1
+ {"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../src/wrapper.ts"],"names":[],"mappings":"AAwBA,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;AAmHF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,EAAE,QAAO,MAAM,EAAO,KAAG,OAAO,CAAC,MAAM,CAmCtF,CAAC"}
package/dist/wrapper.js CHANGED
@@ -145,6 +145,23 @@ const resolveCliPath = () => {
145
145
  * Failure here is non-fatal — `zeph cc` still launches the agent. The
146
146
  * user just loses the phone-bridge feature until they restart.
147
147
  */
148
+ /**
149
+ * Rotate the listener log once it grows past 5 MB. The daemon runs for
150
+ * days and writes 2-3 lines per 5-s cycle, so without rotation the file
151
+ * climbs into the tens of megabytes range pretty quickly. We keep the
152
+ * previous run's tail under `.old` for post-mortem and start fresh.
153
+ */
154
+ const LISTENER_LOG_MAX_BYTES = 5 * 1024 * 1024;
155
+ const rotateListenerLogIfLarge = () => {
156
+ try {
157
+ if (!(0, fs_1.existsSync)(LISTENER_LOG_FILE))
158
+ return;
159
+ if ((0, fs_1.statSync)(LISTENER_LOG_FILE).size <= LISTENER_LOG_MAX_BYTES)
160
+ return;
161
+ (0, fs_1.renameSync)(LISTENER_LOG_FILE, LISTENER_LOG_FILE + '.old');
162
+ }
163
+ catch { /* best-effort */ }
164
+ };
148
165
  const ensureListenerRunning = () => {
149
166
  if (listenerAlive())
150
167
  return;
@@ -153,6 +170,7 @@ const ensureListenerRunning = () => {
153
170
  return;
154
171
  try {
155
172
  (0, fs_1.mkdirSync)(ZEPH_DIR, { recursive: true });
173
+ rotateListenerLogIfLarge();
156
174
  const out = (0, fs_1.openSync)(LISTENER_LOG_FILE, 'a');
157
175
  const child = (0, child_process_1.spawn)(process.execPath, [cliPath, 'listener'], {
158
176
  detached: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeph-to/hook-sdk",
3
- "version": "1.10.0",
3
+ "version": "1.10.1",
4
4
  "description": "Zeph push notification SDK + CLI for AI agents",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",