@zeph-to/cli 1.26.0 → 1.27.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 CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  **Your agent works, hits a decision, and asks your phone. You tap a button (or type a reply), and the answer lands back in the live session — so the agent keeps going.**
9
9
 
10
- `@zeph-to/cli` is the terminal side of that round trip: a zero-dependency push SDK, a `zeph` CLI that wires up 8 AI agents in one command, and a resident listener that lets your phone **drive Claude Code / Codex / Gemini sessions** by typing straight into named tmux sessions.
10
+ `@zeph-to/cli` is the terminal side of that round trip: a zero-dependency push SDK, a `zeph` CLI that wires up 8 AI agents in one command, and a resident listener that lets your phone **drive Claude Code / Codex / Cursor / Gemini sessions** by typing straight into named tmux sessions.
11
11
 
12
12
  <p align="center">
13
13
  <img src="https://zeph.to/readme/demo.gif" alt="Agent asks 'Deploy to prod?' on your phone; you tap Deploy; the session ships" width="560"><br>
@@ -78,7 +78,7 @@ To **send** notifications:
78
78
  zeph notify --title "Deploy done" --body "v2.1.0 shipped"
79
79
  ```
80
80
 
81
- To **drive a Claude Code / Codex / Gemini session from your phone**, see
81
+ To **drive a Claude Code / Codex / Cursor / Gemini session from your phone**, see
82
82
  [Remote Control](#remote-control) below.
83
83
 
84
84
  ## Remote Control
@@ -111,7 +111,7 @@ tmux session via `tmux send-keys`.
111
111
  [zeph listener — resident daemon, started by `zeph cc` automatically]
112
112
  │ tmux send-keys -l -t zeph-myapp "리팩토링 마무리해줘" + Enter
113
113
 
114
- [tmux session "zeph-myapp" running claude / codex / gemini]
114
+ [tmux session "zeph-myapp" running claude / codex / cursor-agent / gemini]
115
115
  ```
116
116
 
117
117
  The listener polls its tmux session inventory every 5 seconds and
@@ -136,10 +136,29 @@ an answerable `zeph_ask`).
136
136
  | Claude Code | `UserPromptSubmit` → plugin's `zeph-remote.sh` | Zeph plugin |
137
137
  | Gemini CLI | `BeforeAgent` → `zeph remote-hook gemini` | `zeph setup` |
138
138
  | Codex CLI | `UserPromptSubmit` → `zeph remote-hook codex` | `zeph setup` |
139
+ | Cursor CLI | — none yet | — |
139
140
 
140
141
  Detection is exact-match: a terminal keystroke racing a phone message
141
142
  can never false-flag. Muted projects are never flagged.
142
143
 
144
+ `zeph cursor` has no remote-origin hook, so it never enters sticky REMOTE
145
+ mode by itself. **Just ask for it** — one line, once per session:
146
+
147
+ > I'm driving this session from my phone. End every response with
148
+ > `zeph_ask` so I can answer with a button.
149
+
150
+ Nothing else is missing: injection works, and the MCP tools are all
151
+ there, `zeph_ask` included. Check them with `cursor-agent mcp list-tools
152
+ zeph` — `mcp list` prints only the *approved* list, not what's
153
+ configured, so it reads as empty even when the server is wired up.
154
+
155
+ Why it isn't automatic: `hooks.json` is honored by the Cursor IDE but not
156
+ by `cursor-agent`, and `beforeSubmitPrompt`'s output schema is
157
+ `{continue, user_message}` — no context channel to deliver a marker match
158
+ through. Same reason the `stop`-hook auto-push `zeph setup` installs
159
+ covers the Cursor **IDE** but not `zeph cursor` panes; ask for a
160
+ `zeph_notify` when you want one.
161
+
143
162
  ### Setup
144
163
 
145
164
  1. **Install tmux.** The listener uses `send-keys`; the wrapper spawns
@@ -162,11 +181,16 @@ can never false-flag. Muted projects are never flagged.
162
181
  3. **Run agents through the wrapper.** That's it.
163
182
 
164
183
  ```bash
165
- zeph cc # claude → tmux session "zeph-<project>"
166
- zeph codex # codex → tmux session "zeph-<project>"
167
- zeph gemini # gemini → tmux session "zeph-<project>"
184
+ zeph cc # claude → tmux session "zeph-<project>"
185
+ zeph codex # codex → tmux session "zeph-<project>"
186
+ zeph cursor # cursor-agent → tmux session "zeph-<project>"
187
+ zeph gemini # gemini → tmux session "zeph-<project>"
168
188
  ```
169
189
 
190
+ `zeph cursor` runs **`cursor-agent`**, Cursor's terminal agent — a
191
+ separate install from the Cursor IDE (the bare `cursor` on your PATH
192
+ is the editor launcher, which exits immediately and can't be driven).
193
+
170
194
  The first `zeph cc` on a machine **auto-spawns a background
171
195
  listener** (singleton, PID file at `~/.zeph/listener.pid`,
172
196
  stdout/stderr at `~/.zeph/listener.log`). You never run
@@ -360,6 +384,7 @@ zeph test
360
384
  # Run an agent in a named tmux session (so the listener can reach it)
361
385
  zeph cc # claude
362
386
  zeph codex # codex
387
+ zeph cursor # cursor-agent (Cursor CLI, not the IDE)
363
388
  zeph gemini # gemini
364
389
 
365
390
  # Run the resident listener (foreground; background it as you like)
@@ -174,7 +174,14 @@ export interface StreamControl {
174
174
  sessionName?: string;
175
175
  /** Subscriber's device public key (Base64 SPKI) — presence turns on E2EE. */
176
176
  subscriberPublicKey?: string;
177
+ /**
178
+ * Subscriber promises to send `agent.stream.renew` while it's watching.
179
+ * Only such a subscriber gets the short lease; clients that predate the
180
+ * renew protocol keep the 5-minute orphan guard (see STREAM_LEASE_MS).
181
+ */
182
+ renew?: boolean;
177
183
  }
184
+ export declare const STREAM_LEASE_MS = 15000;
178
185
  export declare const MAX_CONCURRENT_STREAMS = 3;
179
186
  export declare const stopStream: (sessionName: string) => void;
180
187
  export declare const stopAllStreams: () => void;
@@ -198,7 +205,7 @@ export type StreamFramePayload = {
198
205
  export type StreamErrorFrame = {
199
206
  subtype: 'agent.stream.frame';
200
207
  sessionName: string;
201
- error: 'unknown_session' | 'e2ee_unavailable' | 'encrypt_failed' | 'stream_limit';
208
+ error: 'unknown_session' | 'e2ee_unavailable' | 'encrypt_failed' | 'stream_limit' | 'stream_gone';
202
209
  };
203
210
  /**
204
211
  * Per-listener concurrency guard, as a pure predicate so the boundary is
@@ -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.resolveWsUrl = exports.computeListenerDeviceId = exports.computeBackoff = exports.handlePush = exports.gcAttachments = exports.setAttachmentContext = exports.writeRemoteMarker = exports.collectSessions = exports.collectSessionsVerbose = exports.handleStreamControl = exports.buildStreamFrame = exports.isStreamCapReached = exports.stopAllStreams = exports.stopStream = exports.MAX_CONCURRENT_STREAMS = 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;
28
+ exports.handleListener = exports.resolveWsUrl = exports.computeListenerDeviceId = exports.computeBackoff = exports.handlePush = exports.gcAttachments = exports.setAttachmentContext = exports.writeRemoteMarker = exports.collectSessions = exports.collectSessionsVerbose = exports.handleStreamControl = exports.buildStreamFrame = exports.isStreamCapReached = exports.stopAllStreams = exports.stopStream = exports.MAX_CONCURRENT_STREAMS = exports.STREAM_LEASE_MS = 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");
@@ -766,10 +766,27 @@ exports.scheduleInjectSnapshots = scheduleInjectSnapshots;
766
766
  // still caps the actual payload, so very wide panes get top-truncated.
767
767
  const STREAM_CAPTURE_LINES = 200;
768
768
  const STREAM_INTERVAL_MS = 400;
769
- // Orphan guard: a phone that dies without sending agent.stream.stop must not
770
- // leak an interval forever. Auto-stop after this long; the phone re-subscribes
771
- // on reopen.
769
+ // Orphan guard for subscribers that can't renew (clients older than the renew
770
+ // protocol): a phone that dies without sending agent.stream.stop must not leak
771
+ // an interval forever. Auto-stop after this long; the phone re-subscribes on
772
+ // reopen.
772
773
  const STREAM_MAX_MS = 5 * 60_000;
774
+ // Lease for a renewal-capable subscriber (`start` carried `renew: true`).
775
+ // stream.stop is NOT a reliable slot-release signal: the native terminal runs
776
+ // in its own WebView that the OS destroys on swipe-back, killing the page mid-
777
+ // flight — no unmount, no stop, and the relay is pass-through so we never see
778
+ // the phone's socket drop either. Three such opens used to wedge
779
+ // MAX_CONCURRENT_STREAMS for a full STREAM_MAX_MS with every retry answering
780
+ // stream_limit. A lease inverts the burden: the viewer must keep proving it's
781
+ // there.
782
+ //
783
+ // This is also the worst-case wait before a vanished viewer's slot comes back,
784
+ // so it wants to be short — but a lease can only be as tight as the proof is
785
+ // frequent. At the web's 5s renew cadence this tolerates 3 consecutive dropped
786
+ // renews, the same margin as before, while cutting the wedge from 5 minutes to
787
+ // 15 seconds. A viewer that loses the race gets its stream reaped and re-
788
+ // subscribes on the next renew (see the stream_gone reply below).
789
+ exports.STREAM_LEASE_MS = 15_000;
773
790
  // Per-listener concurrency guard: the API Gateway WS stage throttle (50 rps)
774
791
  // is SHARED across every user, so a runaway machine (many sessions, or a
775
792
  // client re-subscribe bug) streaming at 2.5 fps each can starve push delivery
@@ -785,6 +802,8 @@ const STREAM_MAX_ENCRYPT_FAILURES = 3;
785
802
  // the log every STREAM_LOG_INTERVAL_MS and summarized on stop — the data that
786
803
  // decides cloud-throttled (B-lite) vs direct/Tailscale (B-full).
787
804
  const STREAM_LOG_INTERVAL_MS = 5_000;
805
+ /** How far a start (and each renew) pushes the deadline out. */
806
+ const leaseFor = (renewing) => (renewing ? exports.STREAM_LEASE_MS : STREAM_MAX_MS);
788
807
  const activeStreams = new Map();
789
808
  const maybeLogStreamStats = (sessionName, stats) => {
790
809
  const elapsed = Date.now() - stats.lastLogAt;
@@ -823,6 +842,28 @@ exports.stopAllStreams = stopAllStreams;
823
842
  */
824
843
  const isStreamCapReached = (activeCount) => activeCount >= exports.MAX_CONCURRENT_STREAMS;
825
844
  exports.isStreamCapReached = isStreamCapReached;
845
+ /**
846
+ * At the cap, hand the slot to the new subscriber when the stalest holder
847
+ * can't prove it's still watching — an expired lease, or a client too old to
848
+ * renew at all (those hold a slot for STREAM_MAX_MS on nothing but hope, and
849
+ * a cached web build can keep producing them long after a deploy). Three
850
+ * actively-renewing viewers are all real, so that case still refuses.
851
+ * Returns the evicted session name, or null when every holder is live.
852
+ */
853
+ const evictStalestStream = (now) => {
854
+ let victim = null;
855
+ for (const [name, entry] of activeStreams) {
856
+ if (entry.renewing && entry.expiresAt > now)
857
+ continue; // provably watched
858
+ if (!victim || entry.expiresAt < victim.expiresAt)
859
+ victim = { name, expiresAt: entry.expiresAt };
860
+ }
861
+ if (!victim)
862
+ return null;
863
+ log(`⧉ stream ${victim.name}: evicted — slot handed to a newer subscriber (no live lease)`);
864
+ (0, exports.stopStream)(victim.name);
865
+ return victim.name;
866
+ };
826
867
  const streamErrorFrame = (sessionName, error) => ({
827
868
  subtype: 'agent.stream.frame',
828
869
  sessionName,
@@ -859,16 +900,40 @@ exports.buildStreamFrame = buildStreamFrame;
859
900
  * path). start is idempotent — a repeat restarts the loop.
860
901
  */
861
902
  const handleStreamControl = (req, send) => {
903
+ if (req.subtype !== 'agent.stream.start' && req.subtype !== 'agent.stream.stop' && req.subtype !== 'agent.stream.renew') {
904
+ return false;
905
+ }
906
+ // Every stream-control message names one machine, and the relay fans them
907
+ // out to all of this user's connections — so decide addressing once, here.
908
+ // Two machines can run the same tmux session name, and stop/renew carry no
909
+ // other identity: without this gate a stop meant for one listener would
910
+ // reach into the other's stream of that name (and a renew would refresh it).
911
+ if (req.targetDeviceId !== (0, exports.computeListenerDeviceId)())
912
+ return false;
862
913
  if (req.subtype === 'agent.stream.stop') {
863
914
  if (req.sessionName)
864
915
  (0, exports.stopStream)(req.sessionName);
865
916
  return true;
866
917
  }
867
- if (req.subtype !== 'agent.stream.start')
868
- return false;
869
- // Not addressed to this machine — let other listeners answer.
870
- if (req.targetDeviceId !== (0, exports.computeListenerDeviceId)())
871
- return false;
918
+ // Lease renewal — the subscriber is still watching.
919
+ if (req.subtype === 'agent.stream.renew') {
920
+ if (!req.sessionName)
921
+ return true;
922
+ const entry = activeStreams.get(req.sessionName);
923
+ if (entry) {
924
+ entry.expiresAt = Date.now() + leaseFor(entry.renewing);
925
+ return true;
926
+ }
927
+ // Addressed to us but we hold no such stream: it was reaped (lost
928
+ // renews), evicted, or dropped when our socket last reconnected. The
929
+ // viewer has no other way to learn that — it just keeps painting a
930
+ // frozen pane under a LIVE badge — so tell it to re-subscribe. Renew
931
+ // carries no subscriber key, so restarting it here would silently
932
+ // downgrade an E2EE stream to plaintext; only the client can redo the
933
+ // handshake.
934
+ send(streamErrorFrame(req.sessionName, 'stream_gone'));
935
+ return true;
936
+ }
872
937
  const sessionName = req.sessionName;
873
938
  if (!sessionName)
874
939
  return true;
@@ -880,7 +945,7 @@ const handleStreamControl = (req, send) => {
880
945
  // Concurrency guard AFTER the restart-stop: re-subscribing to an already
881
946
  // active session doesn't count against the cap, only a genuinely new one
882
947
  // does. Refuse the new stream instead of adding to the shared-throttle load.
883
- if ((0, exports.isStreamCapReached)(activeStreams.size)) {
948
+ if ((0, exports.isStreamCapReached)(activeStreams.size) && !evictStalestStream(Date.now())) {
884
949
  log(`⧉ stream ${sessionName}: refused — ${activeStreams.size}/${exports.MAX_CONCURRENT_STREAMS} streams already active on this listener`);
885
950
  send(streamErrorFrame(sessionName, 'stream_limit'));
886
951
  return true;
@@ -915,6 +980,15 @@ const handleStreamControl = (req, send) => {
915
980
  let wireSeq = 0;
916
981
  let encryptFailures = 0;
917
982
  const timer = setInterval(() => {
983
+ // Lease check rides the capture tick: one deadline field, no second
984
+ // timer to leak. (The previous per-start expiry setTimeout was never
985
+ // cleared on stop, so a restart left the old one armed to kill the new
986
+ // incarnation.) Reaping here is what frees the slot for everyone whose
987
+ // stop never arrived.
988
+ if (Date.now() >= (activeStreams.get(sessionName)?.expiresAt ?? 0)) {
989
+ (0, exports.stopStream)(sessionName);
990
+ return;
991
+ }
918
992
  const captured = capturePane(sessionName, true, STREAM_CAPTURE_LINES);
919
993
  if (!captured || captured.content === lastContent) {
920
994
  stats.skipped++;
@@ -967,9 +1041,15 @@ const handleStreamControl = (req, send) => {
967
1041
  });
968
1042
  }, STREAM_INTERVAL_MS);
969
1043
  timer.unref?.();
970
- activeStreams.set(sessionName, { timer, stats });
971
- const expiry = setTimeout(() => (0, exports.stopStream)(sessionName), STREAM_MAX_MS);
972
- expiry.unref?.();
1044
+ // A renewing subscriber gets the short lease; anything older keeps the
1045
+ // 5-minute orphan guard so a version-skewed client isn't cut off mid-view.
1046
+ const renewing = !!req.renew;
1047
+ activeStreams.set(sessionName, {
1048
+ timer,
1049
+ stats,
1050
+ expiresAt: Date.now() + leaseFor(renewing),
1051
+ renewing,
1052
+ });
973
1053
  return true;
974
1054
  };
975
1055
  exports.handleStreamControl = handleStreamControl;
@@ -54,13 +54,18 @@ declare const REMOTE_AGENT_TABLE: readonly [{
54
54
  readonly displayName: "Codex CLI";
55
55
  readonly binary: "codex";
56
56
  readonly subcommands: readonly ["codex"];
57
+ }, {
58
+ readonly kind: "cursor";
59
+ readonly displayName: "Cursor CLI";
60
+ readonly binary: "cursor-agent";
61
+ readonly subcommands: readonly ["cursor", "cursor-agent"];
57
62
  }, {
58
63
  readonly kind: "gemini";
59
64
  readonly displayName: "Gemini CLI";
60
65
  readonly binary: "gemini";
61
66
  readonly subcommands: readonly ["gemini"];
62
67
  }];
63
- /** Closed union of remote-controllable agent kinds ('claude' | 'codex' | 'gemini'). */
68
+ /** Closed union of remote-controllable agent kinds ('claude' | 'codex' | 'cursor' | 'gemini'). */
64
69
  export type AgentKind = (typeof REMOTE_AGENT_TABLE)[number]['kind'];
65
70
  /** A registry row: the uniform RemoteAgent shape with `kind` narrowed to the closed union. */
66
71
  export type RegisteredRemoteAgent = RemoteAgent & {
@@ -1 +1 @@
1
- {"version":3,"file":"remote-agents.d.ts","sourceRoot":"","sources":["../src/remote-agents.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,WAAW;IACxB,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CAC3E;AAyCD;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,IAiB5D,CAAC;AAaF,UAAU,gBAAgB;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACf;AA4ED,4CAA4C;AAC5C,MAAM,WAAW,cAAc;IAC3B,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC7B,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC7B;AAED;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,GACnC,SAAS,MAAM,EACf,SAAS,MAAM,GAAG,IAAI,EACtB,OAAM,cAAmB,KAC1B,MAAM,GAAG,IAOX,CAAC;AAIF,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;EAwBmB,CAAC;AAE5C,uFAAuF;AACvF,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE,8FAA8F;AAC9F,MAAM,MAAM,qBAAqB,GAAG,WAAW,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAEtE,eAAO,MAAM,aAAa,EAAE,SAAS,qBAAqB,EAAuB,CAAC;AAElF,eAAO,MAAM,qBAAqB,GAAI,KAAK,MAAM,KAAG,qBAAqB,GAAG,SAClB,CAAC;AAE3D,eAAO,MAAM,uBAAuB,GAAI,MAAM,MAAM,KAAG,qBAAqB,GAAG,SAG9E,CAAC"}
1
+ {"version":3,"file":"remote-agents.d.ts","sourceRoot":"","sources":["../src/remote-agents.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,WAAW;IACxB,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CAC3E;AAyCD;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,IAiB5D,CAAC;AAaF,UAAU,gBAAgB;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACf;AA4ED,4CAA4C;AAC5C,MAAM,WAAW,cAAc;IAC3B,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC7B,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC7B;AAED;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,GACnC,SAAS,MAAM,EACf,SAAS,MAAM,GAAG,IAAI,EACtB,OAAM,cAAmB,KAC1B,MAAM,GAAG,IAOX,CAAC;AAIF,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;EAkCmB,CAAC;AAE5C,kGAAkG;AAClG,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE,8FAA8F;AAC9F,MAAM,MAAM,qBAAqB,GAAG,WAAW,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAEtE,eAAO,MAAM,aAAa,EAAE,SAAS,qBAAqB,EAAuB,CAAC;AAElF,eAAO,MAAM,qBAAqB,GAAI,KAAK,MAAM,KAAG,qBAAqB,GAAG,SAClB,CAAC;AAE3D,eAAO,MAAM,uBAAuB,GAAI,MAAM,MAAM,KAAG,qBAAqB,GAAG,SAG9E,CAAC"}
@@ -10,9 +10,11 @@ exports.matchAgentByPaneCommand = exports.findAgentBySubcommand = exports.REMOTE
10
10
  * and the phone picker, which may validate the enum).
11
11
  *
12
12
  * This is deliberately NOT merged into `agents.ts`: that table drives
13
- * install/uninstall/verify detection (8 agents, incl. Cursor/Windsurf
14
- * which can never be driven via tmux), and the two tables carry different
15
- * name axes — install id vs subcommand alias vs pane binary.
13
+ * install/uninstall/verify detection (8 agents, incl. Windsurf, which can
14
+ * never be driven via tmux), and the two tables carry different name axes
15
+ * — install id vs subcommand alias vs pane binary. Cursor is the clearest
16
+ * case for keeping them apart: the `cursor` install id there means the
17
+ * IDE, while the drivable binary here is `cursor-agent`, its terminal TUI.
16
18
  */
17
19
  const fs_1 = require("fs");
18
20
  const os_1 = require("os");
@@ -198,6 +200,16 @@ const REMOTE_AGENT_TABLE = [
198
200
  binary: 'codex',
199
201
  subcommands: ['codex'],
200
202
  },
203
+ {
204
+ // The pane binary is `cursor-agent`, Cursor's terminal TUI — NOT the
205
+ // bare `cursor` on PATH, which is the IDE launcher script: it opens
206
+ // the app and exits, so a pane started with it dies before the
207
+ // listener can address it.
208
+ kind: 'cursor',
209
+ displayName: 'Cursor CLI',
210
+ binary: 'cursor-agent',
211
+ subcommands: ['cursor', 'cursor-agent'],
212
+ },
201
213
  {
202
214
  kind: 'gemini',
203
215
  displayName: 'Gemini CLI',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeph-to/cli",
3
- "version": "1.26.0",
3
+ "version": "1.27.0",
4
4
  "description": "Zeph CLI + push notification SDK for AI agents",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",