@zeph-to/cli 2.12.0 → 2.12.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
@@ -527,7 +527,7 @@ zeph notify --title "Hello" --json
527
527
  | `dismiss <id>` | Dismiss a push (or `--all`) |
528
528
  | `rename <name>` | Set the current agent session's display name in the app — run inside a `zeph cc` session (`--clear` resets). Auto-detects the tmux session + this machine's listener device id, so the alias lands on the right device |
529
529
  | `test` | Verify connection and API key |
530
- | `cc` · `codex` · `gemini` | Run the agent in a `zeph-<project>` tmux session — reattaches a detached session of that project (newest suffix first) when there is one, else auto-suffixes `-2`, `-3`, …. Auto-spawns the background listener on first invocation so the phone picker just works. Trailing args pass through to the agent (`zeph cc --resume "..."`) |
530
+ | `cc` · `codex` · `gemini` | Run the agent in a `zeph-<project>` tmux session — reattaches a detached session of that project (newest suffix first) when there is one, else auto-suffixes `-2`, `-3`, …. Auto-spawns the background listener on first invocation so the phone picker just works. Trailing args pass through to the agent (`zeph cc --resume "..."`). On node 22.15+ the wrapper hands its process to tmux rather than waiting on it, so a running session shows no `zeph cc` of its own in `ps` — older runtimes and Windows keep the waiting wrapper |
531
531
  | `mcp` | Run the MCP server on stdio, in this process. What agent MCP configs launch — `zeph install` registers it and you never type it. Replaces the old `npx -y @zeph-to/mcp-server` registration, which left an `npm exec` launcher resident alongside the server for the life of the session. `@zeph-to/mcp-server` ships as a dependency now, so it updates with the CLI rather than being re-fetched by `npx` on every launch — `zeph check-update` reports the version you actually have |
532
532
  | `listener` | (Usually unnecessary — `zeph cc` autospawns it.) Resident daemon: subscribes via WebSocket, reports tmux session inventory every 5 s, injects `agent.command` pushes into the matching session. Run in the foreground for SDK development; otherwise let `zeph cc` manage it |
533
533
 
package/dist/agents.d.ts CHANGED
@@ -3,6 +3,13 @@ export interface Agent {
3
3
  id: string;
4
4
  detected: boolean;
5
5
  }
6
+ /**
7
+ * Absolute path of a command on PATH, or null when it isn't there.
8
+ * `execFileSync`, not a shell: `process.execve` needs a real path (it does no
9
+ * PATH lookup of its own), and building `which ${cmd}` as a shell string would
10
+ * let a path containing a space split into two words.
11
+ */
12
+ export declare const resolveCommand: (cmd: string) => string | null;
6
13
  export declare const hasCommand: (cmd: string) => boolean;
7
14
  export declare const detectAgents: () => Agent[];
8
15
  //# sourceMappingURL=agents.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,KAAK;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,OAAO,CAAC;CACrB;AAID,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,OAOxC,CAAC;AAEF,eAAO,MAAM,YAAY,QAAO,KAAK,EAWpC,CAAC"}
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,KAAK;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,OAAO,CAAC;CACrB;AAID;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,IAMrD,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,OAAuC,CAAC;AAEjF,eAAO,MAAM,YAAY,QAAO,KAAK,EAWpC,CAAC"}
package/dist/agents.js CHANGED
@@ -1,20 +1,27 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.detectAgents = exports.hasCommand = void 0;
3
+ exports.detectAgents = exports.hasCommand = exports.resolveCommand = void 0;
4
4
  const child_process_1 = require("child_process");
5
5
  const fs_1 = require("fs");
6
6
  const os_1 = require("os");
7
7
  const path_1 = require("path");
8
8
  const HOME = (0, os_1.homedir)();
9
- const hasCommand = (cmd) => {
9
+ /**
10
+ * Absolute path of a command on PATH, or null when it isn't there.
11
+ * `execFileSync`, not a shell: `process.execve` needs a real path (it does no
12
+ * PATH lookup of its own), and building `which ${cmd}` as a shell string would
13
+ * let a path containing a space split into two words.
14
+ */
15
+ const resolveCommand = (cmd) => {
10
16
  try {
11
- (0, child_process_1.execSync)(`which ${cmd}`, { stdio: 'pipe' });
12
- return true;
17
+ return (0, child_process_1.execFileSync)('which', [cmd], { encoding: 'utf-8' }).trim() || null;
13
18
  }
14
19
  catch {
15
- return false;
20
+ return null;
16
21
  }
17
22
  };
23
+ exports.resolveCommand = resolveCommand;
24
+ const hasCommand = (cmd) => (0, exports.resolveCommand)(cmd) !== null;
18
25
  exports.hasCommand = hasCommand;
19
26
  const detectAgents = () => [
20
27
  { name: 'Claude Code', id: 'claude', detected: (0, exports.hasCommand)('claude') },
package/dist/verify.d.ts CHANGED
@@ -8,10 +8,10 @@
8
8
  export declare const registeredMcpArgv: (filePath: string, key: string) => string[] | null;
9
9
  /**
10
10
  * Can this launch binary actually be run? Registered commands come out of a
11
- * config file, so they can be an absolute path with spaces in it — the shared
12
- * `hasCommand` interpolates into `which ${cmd}`, where such a path splits into
13
- * two words and reports a perfectly good binary as missing (and where any `;`
14
- * in the file would reach the shell). Nothing here touches a shell.
11
+ * config file, so unlike everywhere else in the CLI they can be an absolute
12
+ * path — `which` is the wrong question for one of those, hence the split.
13
+ * The bare-name half is `resolveCommand`; neither half touches a shell, so a
14
+ * path with a space in it stays one word and a `;` in the file stays inert.
15
15
  */
16
16
  export declare const binaryResolves: (bin: string) => boolean;
17
17
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AA6DA;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,EAAE,KAAK,MAAM,KAAG,MAAM,EAAE,GAAG,IAa5E,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,OAQ5C,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GACrB;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;CAAE,GAC7B;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAElE,wFAAwF;AACxF,eAAO,MAAM,uBAAuB,GAChC,MAAM,MAAM,EAAE,GAAG,IAAI,EACrB,UAAU,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,KACnC,eAAe,GAAG,IAMpB,CAAC;AAYF,eAAO,MAAM,YAAY,GAAU,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAG,OAAO,CAAC,MAAM,CA+GzF,CAAC"}
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AA4DA;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,EAAE,KAAK,MAAM,KAAG,MAAM,EAAE,GAAG,IAa5E,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,OAQ5C,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GACrB;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;CAAE,GAC7B;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAElE,wFAAwF;AACxF,eAAO,MAAM,uBAAuB,GAChC,MAAM,MAAM,EAAE,GAAG,IAAI,EACrB,UAAU,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,KACnC,eAAe,GAAG,IAMpB,CAAC;AAYF,eAAO,MAAM,YAAY,GAAU,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAG,OAAO,CAAC,MAAM,CA+GzF,CAAC"}
package/dist/verify.js CHANGED
@@ -4,7 +4,6 @@ exports.handleVerify = exports.classifyMcpRegistration = exports.binaryResolves
4
4
  const fs_1 = require("fs");
5
5
  const os_1 = require("os");
6
6
  const path_1 = require("path");
7
- const child_process_1 = require("child_process");
8
7
  const agents_js_1 = require("./agents.js");
9
8
  const config_js_1 = require("./config.js");
10
9
  const listener_service_js_1 = require("./listener-service.js");
@@ -85,17 +84,16 @@ const registeredMcpArgv = (filePath, key) => {
85
84
  exports.registeredMcpArgv = registeredMcpArgv;
86
85
  /**
87
86
  * Can this launch binary actually be run? Registered commands come out of a
88
- * config file, so they can be an absolute path with spaces in it — the shared
89
- * `hasCommand` interpolates into `which ${cmd}`, where such a path splits into
90
- * two words and reports a perfectly good binary as missing (and where any `;`
91
- * in the file would reach the shell). Nothing here touches a shell.
87
+ * config file, so unlike everywhere else in the CLI they can be an absolute
88
+ * path — `which` is the wrong question for one of those, hence the split.
89
+ * The bare-name half is `resolveCommand`; neither half touches a shell, so a
90
+ * path with a space in it stays one word and a `;` in the file stays inert.
92
91
  */
93
92
  const binaryResolves = (bin) => {
93
+ if (!bin.includes('/'))
94
+ return (0, agents_js_1.resolveCommand)(bin) !== null;
94
95
  try {
95
- if (bin.includes('/'))
96
- (0, fs_1.accessSync)(bin, fs_1.constants.X_OK);
97
- else
98
- (0, child_process_1.execFileSync)('which', [bin], { stdio: 'pipe' });
96
+ (0, fs_1.accessSync)(bin, fs_1.constants.X_OK);
99
97
  return true;
100
98
  }
101
99
  catch {
package/dist/wrapper.d.ts CHANGED
@@ -27,6 +27,19 @@ export declare const tmuxSessionName: (project: string) => string;
27
27
  * this" from "the user closed the terminal and will come back".
28
28
  */
29
29
  export declare const findAvailableSession: (base: string) => string;
30
+ interface SpawnTarget {
31
+ /**
32
+ * `tmux-new` opens (or reattaches) a session and needs a terminal to
33
+ * attach to; `direct` runs the agent in the pane we are already in and
34
+ * does not. Several decisions downstream turn on which one this is, so
35
+ * `targetForAgent` states it rather than letting them re-derive it from
36
+ * `cmd === 'tmux'`.
37
+ */
38
+ kind: 'direct' | 'tmux-new';
39
+ cmd: string;
40
+ args: string[];
41
+ }
42
+ export declare const targetForAgent: (agent: string, extra: string[]) => SpawnTarget;
30
43
  /**
31
44
  * Whether the daemon on record is running a different build than the one
32
45
  * we're launching from. `npm i -g @zeph-to/cli` swaps dist/ but leaves the
@@ -58,5 +71,27 @@ export declare const ensureListenerRunning: () => Promise<void>;
58
71
  * `zeph cc --resume foo` runs `claude --resume foo` inside the session.
59
72
  * Returns when the agent exits.
60
73
  */
74
+ /** `process.execve` with the optionality resolved — see handOffExec. */
75
+ export type ProcessHandOff = NonNullable<typeof process.execve>;
76
+ /**
77
+ * The exec that replaces this process with the child, or null when the wrapper
78
+ * has to stay and wait instead. Returning the function rather than a boolean
79
+ * keeps one check where two would otherwise drift: the platform gate and the
80
+ * policy are the same answer.
81
+ *
82
+ * `process.execve` is declared optional by @types/node, and is absent on node
83
+ * <22.15 (added there) and on Windows (its doc: "not available on Windows or
84
+ * IBM i"), so its presence covers both without a version check. `engines.node`
85
+ * is `>=18`, so the spawn path below is not a transitional fallback — it stays.
86
+ *
87
+ * `isTTY` is `undefined` rather than `false` on a pipe, hence every `=== true`.
88
+ */
89
+ export declare const handOffExec: (deps: {
90
+ execve: ProcessHandOff | undefined;
91
+ kind: SpawnTarget["kind"];
92
+ stdinTTY: boolean | undefined;
93
+ stdoutTTY: boolean | undefined;
94
+ }) => ProcessHandOff | null;
61
95
  export declare const handleAgentSession: (agent: RemoteAgent, extra?: string[]) => Promise<number>;
96
+ export {};
62
97
  //# sourceMappingURL=wrapper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../src/wrapper.ts"],"names":[],"mappings":"AAuBA,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;AAkC9E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,oBAAoB,GAAI,MAAM,MAAM,KAAG,MAOnD,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,CAgC1D,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAU,OAAO,WAAW,EAAE,QAAO,MAAM,EAAO,KAAG,OAAO,CAAC,MAAM,CAmCjG,CAAC"}
1
+ {"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../src/wrapper.ts"],"names":[],"mappings":"AAwBA,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;AAkC9E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,oBAAoB,GAAI,MAAM,MAAM,KAAG,MAOnD,CAAC;AAEF,UAAU,WAAW;IACjB;;;;;;OAMG;IACH,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,EAAE,CAAC;CAClB;AAOD,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,EAAE,OAAO,MAAM,EAAE,KAAG,WAmB/D,CAAC;AAIF;;;;;;;;;;;;;;;;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,CAgC1D,CAAC;AAEF;;;;;GAKG;AACH,wEAAwE;AACxE,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;AAEhE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM;IAC9B,MAAM,EAAE,cAAc,GAAG,SAAS,CAAC;IACnC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1B,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC;CAClC,KAAG,cAAc,GAAG,IASpB,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAU,OAAO,WAAW,EAAE,QAAO,MAAM,EAAO,KAAG,OAAO,CAAC,MAAM,CA+DjG,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.ensureListenerRunning = exports.listenerVersionDrifted = exports.findAvailableSession = exports.tmuxSessionName = exports.detectProjectName = void 0;
3
+ exports.handleAgentSession = exports.handOffExec = exports.ensureListenerRunning = exports.listenerVersionDrifted = exports.targetForAgent = 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
@@ -14,6 +14,7 @@ exports.handleAgentSession = exports.ensureListenerRunning = exports.listenerVer
14
14
  */
15
15
  const child_process_1 = require("child_process");
16
16
  const path_1 = require("path");
17
+ const agents_js_1 = require("./agents.js");
17
18
  const check_update_js_1 = require("./check-update.js");
18
19
  const config_js_1 = require("./config.js");
19
20
  const listener_process_js_1 = require("./listener-process.js");
@@ -114,7 +115,7 @@ const targetForAgent = (agent, extra) => {
114
115
  // current pane. Nested tmux prefix collisions are confusing and the
115
116
  // listener can't reach a session it didn't name anyway.
116
117
  if (process.env.TMUX) {
117
- return { cmd: agent, args: extra };
118
+ return { kind: 'direct', cmd: agent, args: extra };
118
119
  }
119
120
  const base = (0, exports.tmuxSessionName)((0, exports.detectProjectName)());
120
121
  // Reattach a detached session of this project when there is one,
@@ -127,8 +128,9 @@ const targetForAgent = (agent, extra) => {
127
128
  // `--resume` would be eaten by tmux's own parser. Build one quoted
128
129
  // shell string instead, which tmux passes through verbatim.
129
130
  const shellCmd = [agent, ...extra].map(shellQuote).join(' ');
130
- return { cmd: 'tmux', args: ['new', '-A', '-s', session, shellCmd] };
131
+ return { kind: 'tmux-new', cmd: 'tmux', args: ['new', '-A', '-s', session, shellCmd] };
131
132
  };
133
+ exports.targetForAgent = targetForAgent;
132
134
  // ── Background listener auto-start ────────────────────────────────────
133
135
  /**
134
136
  * Whether the daemon on record is running a different build than the one
@@ -193,27 +195,74 @@ const ensureListenerRunning = async () => {
193
195
  };
194
196
  exports.ensureListenerRunning = ensureListenerRunning;
195
197
  /**
196
- * Launch the agent in a named tmux session (or directly if nested) and
197
- * forward its exit code. `extra` is appended to the agent invocation, so
198
- * `zeph cc --resume foo` runs `claude --resume foo` inside the session.
199
- * Returns when the agent exits.
198
+ * The exec that replaces this process with the child, or null when the wrapper
199
+ * has to stay and wait instead. Returning the function rather than a boolean
200
+ * keeps one check where two would otherwise drift: the platform gate and the
201
+ * policy are the same answer.
202
+ *
203
+ * `process.execve` is declared optional by @types/node, and is absent on node
204
+ * <22.15 (added there) and on Windows (its doc: "not available on Windows or
205
+ * IBM i"), so its presence covers both without a version check. `engines.node`
206
+ * is `>=18`, so the spawn path below is not a transitional fallback — it stays.
207
+ *
208
+ * `isTTY` is `undefined` rather than `false` on a pipe, hence every `=== true`.
200
209
  */
210
+ const handOffExec = (deps) => {
211
+ if (!deps.execve)
212
+ return null;
213
+ // execve runs no cleanup handler, and node only writes stdout synchronously
214
+ // to a TTY on POSIX — anything buffered to a pipe would vanish with us.
215
+ if (deps.stdoutTTY !== true)
216
+ return null;
217
+ // `tmux new` additionally needs a terminal of its own to attach to. That is
218
+ // the failure the spawn path below exists to explain, so send it there.
219
+ if (deps.kind === 'tmux-new' && deps.stdinTTY !== true)
220
+ return null;
221
+ return deps.execve;
222
+ };
223
+ exports.handOffExec = handOffExec;
201
224
  const handleAgentSession = async (agent, extra = []) => {
202
225
  // Best-effort: make sure the phone-bridge daemon is running, and running
203
226
  // the build we were launched from. The user shouldn't need to remember a
204
227
  // second command for the picker on their phone to work.
205
228
  await (0, exports.ensureListenerRunning)();
229
+ const { kind, cmd, args } = (0, exports.targetForAgent)(agent.binary, extra);
230
+ // Hand the terminal over and stop existing. Waiting on the child is all
231
+ // this process does for the rest of the session, and it holds a whole node
232
+ // heap to do it — one resident process per `zeph cc`, purely to forward an
233
+ // exit code that execve gives us for free by being the same process.
234
+ const handOff = (0, exports.handOffExec)({
235
+ execve: process.execve,
236
+ kind,
237
+ stdinTTY: process.stdin.isTTY,
238
+ stdoutTTY: process.stdout.isTTY,
239
+ });
240
+ if (handOff) {
241
+ const file = (0, agents_js_1.resolveCommand)(cmd);
242
+ // execve does no PATH lookup, so resolving is mandatory — and the
243
+ // resolve failing is the same fact the spawn path learns from ENOENT.
244
+ if (!file) {
245
+ console.error(`zeph: '${cmd}' not found on PATH`);
246
+ return 127;
247
+ }
248
+ // Nothing buffered survives: execve runs no cleanup handler and fires
249
+ // no exit event. Safe here only because stdout is a TTY, which node
250
+ // writes synchronously on POSIX — every `zeph:` line above is already out.
251
+ // The env argument is left off: it defaults to process.env.
252
+ handOff(file, [cmd, ...args]);
253
+ }
206
254
  return new Promise((resolve) => {
207
- const { cmd, args } = targetForAgent(agent.binary, extra);
208
255
  const start = Date.now();
209
256
  const child = (0, child_process_1.spawn)(cmd, args, { stdio: 'inherit' });
210
257
  child.on('exit', (code) => {
211
258
  const dur = Date.now() - start;
212
259
  // Short-lived non-zero exits are the symptom of "ran from a
213
- // pane that isn't a real TTY" (iTerm tmux integration pane,
214
- // some IDE terminals). The user otherwise just sees their
215
- // shell return with `[exited]` and no clue what went wrong.
216
- if (code && code !== 0 && dur < 2000) {
260
+ // pane that isn't a real TTY" (some IDE terminals). The user
261
+ // otherwise just sees their shell return with `[exited]` and no
262
+ // clue what went wrong. Only `tmux new` fails that way telling
263
+ // someone whose agent died inside a perfectly good pane to "run
264
+ // from a plain shell pane" would send them after the wrong thing.
265
+ if (kind === 'tmux-new' && code && code !== 0 && dur < 2000) {
217
266
  console.error(`zeph: ${cmd} ${args.join(' ')} exited ${code} after ${dur}ms.\n` +
218
267
  ` If this terminal is itself inside tmux (or an iTerm/Warp\n` +
219
268
  ` tmux-integration pane), run \`zeph cc\` from a plain shell\n` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeph-to/cli",
3
- "version": "2.12.0",
3
+ "version": "2.12.1",
4
4
  "description": "Zeph CLI + push notification SDK for AI agents",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",