@zeph-to/cli 1.20.0 → 1.22.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
@@ -33,36 +33,50 @@ npx @zeph-to/cli notify --title "Hello"
33
33
 
34
34
  ## Quick Start
35
35
 
36
- ```bash
37
- # Sign in via browser — auto-fetches your API key + hook into
38
- # ~/.zeph/config.json (no copy-paste)
39
- npx @zeph-to/cli login
36
+ Install the CLI globally, then run setup:
40
37
 
41
- # Install for your AI agents (rules + hooks + MCP). Reuses the saved
42
- # config; detects Claude Code / Cursor / Windsurf / Gemini / Codex / …
43
- npx @zeph-to/cli install
38
+ ```bash
39
+ npm install -g @zeph-to/cli
40
+ zeph install
44
41
  ```
45
42
 
46
- `login` opens a browser to the Zeph web app, asks you to confirm "connect
47
- this computer", then issues a fresh API key (labelled with your hostname)
48
- and reuses or creates a hook writing both back to `~/.zeph/config.json`
49
- over a localhost loopback. No credential ever gets pasted into the
50
- terminal.
43
+ `zeph install` opens a browser sign-in on a fresh machine the web app
44
+ issues an API key + hook as a matched pair and the CLI writes them to
45
+ `~/.zeph/config.json` (no copy-paste). Then it installs rules + hooks +
46
+ MCP for every detected agent (Claude Code / Cursor / Windsurf / Gemini /
47
+ Codex / …). Safe to re-run: a saved login is reused untouched (no
48
+ prompts), so a re-run just refreshes the agent integrations. To switch
49
+ account, `zeph install --relogin` forces a fresh sign-in.
50
+
51
+ **Why global, not `npx`?** `zeph cc` (drive a session from your phone)
52
+ needs `zeph` on your `PATH`, and the agent hooks this installs are
53
+ `$(command -v zeph || npx …)` — a global binary skips an npx cold-start
54
+ on *every* notification. For notifications only, with no phone control,
55
+ `npx @zeph-to/cli install` is a lighter alternative that skips the global
56
+ binary.
51
57
 
52
- Prefer to paste credentials yourself (or on a headless box with no
53
- browser)? Skip `login` and pass them to `install` directly:
58
+ `~/.zeph/config.json` is the single source of truth the CLI, the MCP
59
+ server, the plugin hooks, and the listener all read it. You never need
60
+ `ZEPH_API_KEY`-style env vars for a normal setup; they exist as overrides
61
+ (second account, CI).
62
+
63
+ Variants:
54
64
 
55
65
  ```bash
56
- npx @zeph-to/cli install --key ak_... --hook hook_...
57
- ```
66
+ # Headless box (no browser): paste credentials from another machine's
67
+ # ~/.zeph/config.json
68
+ zeph install --key ak_... --hook hook_...
58
69
 
59
- Either way it saves to `~/.zeph/config.json`. All Zeph tools (CLI, MCP
60
- server, plugin hooks, listener) read this file.
70
+ # Skip the interactive agent picker
71
+ zeph install --only claude,cursor
61
72
 
62
- On a fresh machine `install` alone is enough: with no `--key` and no
63
- saved config it opens the same browser login automatically (headless
64
- boxes fall back to manual key entry). Pass `--only claude,cursor,…` to
65
- skip the interactive agent picker.
73
+ # Switch account force a fresh browser sign-in over a saved login
74
+ zeph install --relogin
75
+
76
+ # Refresh credentials only (no agent re-install) — assumes you already
77
+ # ran `zeph install` once; on its own it does NOT wire MCP into agents
78
+ zeph login
79
+ ```
66
80
 
67
81
  To **send** notifications:
68
82
 
package/dist/cli.js CHANGED
@@ -107,10 +107,13 @@ Login options:
107
107
 
108
108
  Install options:
109
109
  (no --key, no saved config → opens browser login automatically;
110
- headless falls back to manual key entry)
110
+ a saved login is reused untouched on re-run; headless falls back
111
+ to manual key entry)
111
112
  --key <api-key> API key (non-interactive; skips browser login)
112
113
  --hook <hook-id> Hook ID (non-interactive)
113
114
  --base-url <url> Base URL (non-interactive)
115
+ --relogin Force a fresh browser sign-in even if a login is
116
+ already saved (switch account)
114
117
  --web-url <url> Login web app URL [default: https://app.zeph.to]
115
118
  --only <agents> Comma-separated agent ids to install for
116
119
  (claude,cursor,windsurf,gemini,codex,copilot,cline,aider).
@@ -3,7 +3,12 @@ import type { Agent } from './agents.js';
3
3
  * True when install should auto-open browser login (ADR 0002): interactive
4
4
  * context with no existing credential (--key/env/config all absent).
5
5
  */
6
- export declare const shouldTriggerLogin: (nonInteractive: boolean, currentKey: string | undefined) => boolean;
6
+ /**
7
+ * Interactive install, past the `--key` path: open a browser login only when
8
+ * there's nothing saved yet, or the user explicitly asked to re-authenticate
9
+ * with `--relogin`. A plain re-run keeps the saved login untouched.
10
+ */
11
+ export declare const shouldReauth: (currentKey: string | undefined, relogin: boolean) => boolean;
7
12
  /** Exported for tests (installGemini shells out to the real `gemini` binary,
8
13
  * so the merge semantics can't be covered through it deterministically). */
9
14
  export declare const mergeJsonFile: (filePath: string, patch: Record<string, unknown>) => void;
@@ -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,eAAO,MAAM,kBAAkB,GAAI,gBAAgB,OAAO,EAAE,YAAY,MAAM,GAAG,SAAS,KAAG,OAC7D,CAAC;AAoBjC;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;AA4FF,eAAO,MAAM,aAAa,GAAU,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAG,OAAO,CAAC,MAAM,CAkG1F,CAAC"}
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,CAkG1F,CAAC"}
package/dist/installer.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.handleInstall = exports.filterAgentsByIds = exports.mergeJsonFile = exports.shouldTriggerLogin = void 0;
3
+ exports.handleInstall = exports.filterAgentsByIds = exports.mergeJsonFile = exports.shouldReauth = 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");
@@ -19,8 +19,13 @@ const fail = (msg) => console.log(` - ${msg}`);
19
19
  * True when install should auto-open browser login (ADR 0002): interactive
20
20
  * context with no existing credential (--key/env/config all absent).
21
21
  */
22
- const shouldTriggerLogin = (nonInteractive, currentKey) => !nonInteractive && !currentKey;
23
- exports.shouldTriggerLogin = shouldTriggerLogin;
22
+ /**
23
+ * Interactive install, past the `--key` path: open a browser login only when
24
+ * there's nothing saved yet, or the user explicitly asked to re-authenticate
25
+ * with `--relogin`. A plain re-run keeps the saved login untouched.
26
+ */
27
+ const shouldReauth = (currentKey, relogin) => !currentKey || relogin;
28
+ exports.shouldReauth = shouldReauth;
24
29
  const promptInput = (question) => {
25
30
  const rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stdout });
26
31
  return new Promise((resolve) => {
@@ -348,26 +353,12 @@ const testConnection = async (apiKey, baseUrl) => {
348
353
  return false;
349
354
  }
350
355
  };
351
- /** Interactive path when a credential already exists: prompt to keep/replace. */
352
- const promptExistingCredentials = async (currentKey, existing) => {
353
- if (currentKey)
354
- console.log(` Current API Key: ${currentKey.slice(0, 12)}...`);
355
- const keyInput = await promptInput(currentKey ? ' New API Key (Enter to keep): ' : ' API Key (from app > Settings > API Keys): ');
356
- const currentHook = (0, config_js_1.resolvedEnv)('ZEPH_HOOK_ID') || existing.hookId;
357
- if (currentHook)
358
- console.log(` Current Hook ID: ${currentHook}`);
359
- const hookInput = await promptInput(currentHook ? ' New Hook ID (Enter to keep, "none" to remove): ' : ' Hook ID (optional, for prompt/input): ');
360
- return {
361
- apiKey: keyInput || currentKey,
362
- hookId: hookInput === 'none' ? undefined : (hookInput || currentHook),
363
- baseUrl: existing.baseUrl,
364
- };
365
- };
366
356
  /**
367
- * Resolve API key + hook for install. Priority: --key/env/config (non-interactive
368
- * or "keep existing") brand-new interactive opens browser login (ADR 0002),
369
- * falling back to manual paste when headless. wsUrl/deviceId from a login are
370
- * persisted by runLoginFlow and re-read at config-save time.
357
+ * Resolve API key + hook for install. Priority: --key/env/config (non-interactive)
358
+ * a saved login is reused untouched on re-run otherwise a brand-new
359
+ * interactive install opens browser login (ADR 0002), falling back to manual
360
+ * paste when headless. wsUrl/deviceId from a login are persisted by
361
+ * runLoginFlow and re-read at config-save time.
371
362
  */
372
363
  const collectCredentials = async (args, installArgs, nonInteractive, existing) => {
373
364
  if (nonInteractive) {
@@ -379,8 +370,15 @@ const collectCredentials = async (args, installArgs, nonInteractive, existing) =
379
370
  }
380
371
  console.log('');
381
372
  const currentKey = (0, config_js_1.resolvedEnv)('ZEPH_API_KEY') || existing.apiKey;
382
- if (!(0, exports.shouldTriggerLogin)(nonInteractive, currentKey)) {
383
- return promptExistingCredentials(currentKey, existing);
373
+ // Re-run with a saved login: reuse it silently and move on to agent wiring.
374
+ // Switching accounts is `zeph login` or `zeph install --relogin`.
375
+ if (!(0, exports.shouldReauth)(currentKey, args.relogin === true)) {
376
+ console.log(` Using saved login (${currentKey.slice(0, 12)}…). Run \`zeph install --relogin\` to switch account.`);
377
+ return {
378
+ apiKey: currentKey,
379
+ hookId: (0, config_js_1.resolvedEnv)('ZEPH_HOOK_ID') || existing.hookId,
380
+ baseUrl: existing.baseUrl,
381
+ };
384
382
  }
385
383
  const result = await (0, login_js_1.runLoginFlow)({
386
384
  webUrl: (0, login_js_1.resolveWebUrl)(args['web-url']),
@@ -266,6 +266,17 @@ export declare const gcAttachments: (now?: number, dir?: string, ttl?: number) =
266
266
  export declare const handlePush: (push: PushItem, deps?: HandlePushDeps) => Promise<boolean>;
267
267
  export declare const computeBackoff: (attempt: number) => number;
268
268
  export declare const computeListenerDeviceId: (host?: string) => string;
269
+ /**
270
+ * A user who only set ZEPH_API_KEY (no `zeph login`, no config file) gets the
271
+ * prod socket by default — mirroring the DEFAULT_API_BASE fallback for REST.
272
+ * The default only applies when the base URL is also the prod default: with a
273
+ * custom base URL, silently pointing the listener at the prod socket while
274
+ * REST talks to another stage would split the device across environments, so
275
+ * that combination still errors loudly.
276
+ */
277
+ export declare const resolveWsUrl: (args: Record<string, string | boolean>, config: {
278
+ wsUrl?: string;
279
+ }, baseUrl: string) => string | null;
269
280
  export declare const handleListener: (args: Record<string, string | boolean>) => Promise<number>;
270
281
  export {};
271
282
  //# sourceMappingURL=listener.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../src/listener.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../src/listener.ts"],"names":[],"mappings":"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"}
package/dist/listener.js CHANGED
@@ -25,7 +25,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
25
25
  return (mod && mod.__esModule) ? mod : { "default": mod };
26
26
  };
27
27
  Object.defineProperty(exports, "__esModule", { value: true });
28
- exports.handleListener = exports.computeListenerDeviceId = exports.computeBackoff = exports.handlePush = exports.gcAttachments = exports.setAttachmentContext = exports.writeRemoteMarker = exports.collectSessions = exports.collectSessionsVerbose = exports.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.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");
@@ -1082,23 +1082,75 @@ exports.computeBackoff = computeBackoff;
1082
1082
  * time `zeph listener` rebinds). `dev_listener_<sha8(hostname)>` keeps it
1083
1083
  * human-recognisable in dev logs without leaking the raw hostname.
1084
1084
  *
1085
- * The no-arg result is pinned for the process lifetime: macOS renames the
1086
- * hostname with network changes, and a mid-run drift made the screen-peek
1087
- * check (fresh recompute) reject requests addressed to the id we CONNECTED
1088
- * with the phone targets the id the sessions were reported under, so
1089
- * every caller in one process must see the same value.
1085
+ * The no-arg result must identify the MACHINE, stably:
1086
+ * - NOT the hostname: macOS rewrites it on network changes (observed
1087
+ * flip-flopping between 'Mac' and '<name>.local' within hours), which
1088
+ * split one machine into several device rows duplicated session lists
1089
+ * on the phone, and screen-peek requests addressed to an id no live
1090
+ * process answers for.
1091
+ * - NOT a file under ~/.zeph alone: users copy ~/.zeph to a new machine
1092
+ * to carry their API key, and a copied id makes two machines answer for
1093
+ * each other.
1094
+ * So: hash the platform machine id (IOPlatformUUID / /etc/machine-id) —
1095
+ * deterministic per machine, immune to both. The sticky file is only the
1096
+ * fallback for platforms where no machine id is readable.
1090
1097
  */
1091
1098
  let processListenerDeviceId;
1099
+ const LISTENER_ID_FILE = (0, path_1.join)((0, os_1.homedir)(), '.zeph', 'listener-device-id');
1100
+ const hashListenerId = (seed) => `dev_listener_${(0, crypto_1.createHash)('sha256').update(seed).digest('hex').slice(0, 8)}`;
1101
+ const readMachineId = () => {
1102
+ try {
1103
+ if (process.platform === 'darwin') {
1104
+ const r = (0, child_process_1.spawnSync)('ioreg', ['-rd1', '-c', 'IOPlatformExpertDevice'], {
1105
+ encoding: 'utf-8',
1106
+ stdio: ['ignore', 'pipe', 'ignore'],
1107
+ });
1108
+ const m = r.stdout?.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
1109
+ if (m)
1110
+ return m[1];
1111
+ }
1112
+ if (process.platform === 'linux') {
1113
+ for (const p of ['/etc/machine-id', '/var/lib/dbus/machine-id']) {
1114
+ try {
1115
+ const v = (0, fs_1.readFileSync)(p, 'utf-8').trim();
1116
+ if (v)
1117
+ return v;
1118
+ }
1119
+ catch { /* try next */ }
1120
+ }
1121
+ }
1122
+ }
1123
+ catch { /* fall through to the sticky-file fallback */ }
1124
+ return null;
1125
+ };
1092
1126
  const computeListenerDeviceId = (host) => {
1093
- if (host !== undefined) {
1094
- const h = (0, crypto_1.createHash)('sha256').update(host).digest('hex').slice(0, 8);
1095
- return `dev_listener_${h}`;
1127
+ if (host !== undefined)
1128
+ return hashListenerId(host);
1129
+ if (processListenerDeviceId)
1130
+ return processListenerDeviceId;
1131
+ const machineId = readMachineId();
1132
+ if (machineId) {
1133
+ processListenerDeviceId = hashListenerId(machineId);
1134
+ return processListenerDeviceId;
1096
1135
  }
1097
- if (!processListenerDeviceId) {
1098
- const h = (0, crypto_1.createHash)('sha256').update((0, os_1.hostname)()).digest('hex').slice(0, 8);
1099
- processListenerDeviceId = `dev_listener_${h}`;
1136
+ // No readable machine id — pin the first hostname-derived id to a file
1137
+ // so at least hostname drift can't split this machine.
1138
+ try {
1139
+ const saved = (0, fs_1.readFileSync)(LISTENER_ID_FILE, 'utf-8').trim();
1140
+ if (/^dev_listener_[0-9a-f]{8}$/.test(saved)) {
1141
+ processListenerDeviceId = saved;
1142
+ return saved;
1143
+ }
1100
1144
  }
1101
- return processListenerDeviceId;
1145
+ catch { /* first run — fall through to compute + persist */ }
1146
+ const fresh = hashListenerId((0, os_1.hostname)());
1147
+ try {
1148
+ (0, fs_1.mkdirSync)((0, path_1.join)((0, os_1.homedir)(), '.zeph'), { recursive: true });
1149
+ (0, fs_1.writeFileSync)(LISTENER_ID_FILE, fresh);
1150
+ }
1151
+ catch { /* persistence is best-effort; in-memory pin still holds */ }
1152
+ processListenerDeviceId = fresh;
1153
+ return fresh;
1102
1154
  };
1103
1155
  exports.computeListenerDeviceId = computeListenerDeviceId;
1104
1156
  /**
@@ -1337,10 +1389,21 @@ const streamSession = (wsUrl, apiKey) => {
1337
1389
  terminate: () => { ws?.terminate(); },
1338
1390
  };
1339
1391
  };
1340
- const resolveWsUrl = (args, config) => {
1392
+ const DEFAULT_WS_URL = 'wss://ws.zeph.to';
1393
+ /**
1394
+ * A user who only set ZEPH_API_KEY (no `zeph login`, no config file) gets the
1395
+ * prod socket by default — mirroring the DEFAULT_API_BASE fallback for REST.
1396
+ * The default only applies when the base URL is also the prod default: with a
1397
+ * custom base URL, silently pointing the listener at the prod socket while
1398
+ * REST talks to another stage would split the device across environments, so
1399
+ * that combination still errors loudly.
1400
+ */
1401
+ const resolveWsUrl = (args, config, baseUrl) => {
1341
1402
  const fromArg = typeof args['ws-url'] === 'string' ? args['ws-url'] : null;
1342
- return fromArg || (0, config_js_1.resolvedEnv)('ZEPH_WS_URL') || config.wsUrl || null;
1403
+ return fromArg || (0, config_js_1.resolvedEnv)('ZEPH_WS_URL') || config.wsUrl
1404
+ || (baseUrl === DEFAULT_API_BASE ? DEFAULT_WS_URL : null);
1343
1405
  };
1406
+ exports.resolveWsUrl = resolveWsUrl;
1344
1407
  // ── Singleton guard (PID file) ──────────────────────────────────────
1345
1408
  const ZEPH_DIR = (0, path_1.join)((0, os_1.homedir)(), '.zeph');
1346
1409
  const LISTENER_PID_FILE = (0, path_1.join)(ZEPH_DIR, 'listener.pid');
@@ -1411,7 +1474,7 @@ const handleListener = async (args) => {
1411
1474
  // resolution order as the rest of the CLI; falls back to the prod API.
1412
1475
  const baseUrl = args['base-url'] || (0, config_js_1.resolvedEnv)('ZEPH_BASE_URL') || config.baseUrl || DEFAULT_API_BASE;
1413
1476
  (0, exports.setAttachmentContext)({ apiKey, baseUrl });
1414
- const wsUrl = resolveWsUrl(args, config);
1477
+ const wsUrl = (0, exports.resolveWsUrl)(args, config, baseUrl);
1415
1478
  if (!wsUrl) {
1416
1479
  console.error('zeph listener: WebSocket URL not set. Either:\n' +
1417
1480
  ' • add "wsUrl": "wss://..." to ~/.zeph/config.json\n' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeph-to/cli",
3
- "version": "1.20.0",
3
+ "version": "1.22.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",