@zeph-to/cli 1.12.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.
Files changed (51) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +499 -0
  3. package/dist/agents.d.ts +8 -0
  4. package/dist/agents.d.ts.map +1 -0
  5. package/dist/agents.js +29 -0
  6. package/dist/check-update.d.ts +4 -0
  7. package/dist/check-update.d.ts.map +1 -0
  8. package/dist/check-update.js +80 -0
  9. package/dist/cli.d.ts +3 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +374 -0
  12. package/dist/config.d.ts +14 -0
  13. package/dist/config.d.ts.map +1 -0
  14. package/dist/config.js +36 -0
  15. package/dist/crypto.d.ts +82 -0
  16. package/dist/crypto.d.ts.map +1 -0
  17. package/dist/crypto.js +291 -0
  18. package/dist/errors.d.ts +12 -0
  19. package/dist/errors.d.ts.map +1 -0
  20. package/dist/errors.js +28 -0
  21. package/dist/index.d.ts +4 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +9 -0
  24. package/dist/installer.d.ts +14 -0
  25. package/dist/installer.d.ts.map +1 -0
  26. package/dist/installer.js +464 -0
  27. package/dist/listener.d.ts +126 -0
  28. package/dist/listener.d.ts.map +1 -0
  29. package/dist/listener.js +1008 -0
  30. package/dist/login.d.ts +38 -0
  31. package/dist/login.d.ts.map +1 -0
  32. package/dist/login.js +182 -0
  33. package/dist/templates.d.ts +44 -0
  34. package/dist/templates.d.ts.map +1 -0
  35. package/dist/templates.js +257 -0
  36. package/dist/types.d.ts +54 -0
  37. package/dist/types.d.ts.map +1 -0
  38. package/dist/types.js +2 -0
  39. package/dist/uninstall.d.ts +2 -0
  40. package/dist/uninstall.d.ts.map +1 -0
  41. package/dist/uninstall.js +217 -0
  42. package/dist/verify.d.ts +2 -0
  43. package/dist/verify.d.ts.map +1 -0
  44. package/dist/verify.js +109 -0
  45. package/dist/wrapper.d.ts +26 -0
  46. package/dist/wrapper.d.ts.map +1 -0
  47. package/dist/wrapper.js +238 -0
  48. package/dist/zeph-hook.d.ts +23 -0
  49. package/dist/zeph-hook.d.ts.map +1 -0
  50. package/dist/zeph-hook.js +196 -0
  51. package/package.json +75 -0
@@ -0,0 +1,8 @@
1
+ export interface Agent {
2
+ name: string;
3
+ id: string;
4
+ detected: boolean;
5
+ }
6
+ export declare const hasCommand: (cmd: string) => boolean;
7
+ export declare const detectAgents: () => Agent[];
8
+ //# sourceMappingURL=agents.d.ts.map
@@ -0,0 +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,EASpC,CAAC"}
package/dist/agents.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectAgents = exports.hasCommand = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const fs_1 = require("fs");
6
+ const os_1 = require("os");
7
+ const path_1 = require("path");
8
+ const HOME = (0, os_1.homedir)();
9
+ const hasCommand = (cmd) => {
10
+ try {
11
+ (0, child_process_1.execSync)(`which ${cmd}`, { stdio: 'pipe' });
12
+ return true;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ };
18
+ exports.hasCommand = hasCommand;
19
+ const detectAgents = () => [
20
+ { name: 'Claude Code', id: 'claude', detected: (0, exports.hasCommand)('claude') },
21
+ { name: 'Cursor', id: 'cursor', detected: (0, fs_1.existsSync)((0, path_1.join)(HOME, '.cursor')) },
22
+ { name: 'Windsurf', id: 'windsurf', detected: (0, fs_1.existsSync)((0, path_1.join)(HOME, '.codeium')) },
23
+ { name: 'Gemini CLI', id: 'gemini', detected: (0, exports.hasCommand)('gemini') },
24
+ { name: 'Codex CLI', id: 'codex', detected: (0, exports.hasCommand)('codex') },
25
+ { name: 'Copilot CLI', id: 'copilot', detected: (0, fs_1.existsSync)((0, path_1.join)(HOME, '.copilot')) },
26
+ { name: 'Cline', id: 'cline', detected: (0, fs_1.existsSync)((0, path_1.join)(HOME, '.cline')) },
27
+ { name: 'Aider', id: 'aider', detected: (0, exports.hasCommand)('aider') },
28
+ ];
29
+ exports.detectAgents = detectAgents;
@@ -0,0 +1,4 @@
1
+ /** Semver-ish compare: returns true when `latest` is strictly newer than `current`. */
2
+ export declare const isNewer: (latest: string, current: string) => boolean;
3
+ export declare const handleCheckUpdate: (args: Record<string, string | boolean>) => Promise<number>;
4
+ //# sourceMappingURL=check-update.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-update.d.ts","sourceRoot":"","sources":["../src/check-update.ts"],"names":[],"mappings":"AAsBA,uFAAuF;AACvF,eAAO,MAAM,OAAO,GAAI,QAAQ,MAAM,EAAE,SAAS,MAAM,KAAG,OAQzD,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAU,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAG,OAAO,CAAC,MAAM,CA4C9F,CAAC"}
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleCheckUpdate = exports.isNewer = void 0;
4
+ const config_js_1 = require("./config.js");
5
+ // Compares installed versions against the npm registry. Pure read-only —
6
+ // never installs anything; just tells the user if a newer release exists.
7
+ const PACKAGES = ['@zeph-to/cli', '@zeph-to/mcp-server'];
8
+ /** Fetch the `latest` dist-tag version for a package from the npm registry. */
9
+ const fetchLatest = async (pkg) => {
10
+ try {
11
+ const res = await fetch(`https://registry.npmjs.org/${pkg}/latest`, {
12
+ headers: { Accept: 'application/json' },
13
+ signal: AbortSignal.timeout(10_000),
14
+ });
15
+ if (!res.ok)
16
+ return null;
17
+ const json = await res.json();
18
+ return json.version ?? null;
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ };
24
+ /** Semver-ish compare: returns true when `latest` is strictly newer than `current`. */
25
+ const isNewer = (latest, current) => {
26
+ const norm = (v) => v.replace(/^v/, '').split('-')[0].split('.').map((n) => parseInt(n, 10) || 0);
27
+ const [a, b] = [norm(latest), norm(current)];
28
+ for (let i = 0; i < 3; i++) {
29
+ if ((a[i] ?? 0) > (b[i] ?? 0))
30
+ return true;
31
+ if ((a[i] ?? 0) < (b[i] ?? 0))
32
+ return false;
33
+ }
34
+ return false;
35
+ };
36
+ exports.isNewer = isNewer;
37
+ const handleCheckUpdate = async (args) => {
38
+ const isJson = args.json === true;
39
+ // The CLI's own installed version is known from package.json.
40
+ // mcp-server's installed version isn't reliably knowable from here
41
+ // (it's a separate package, often run via npx), so we only report its
42
+ // latest — the user compares against whatever they have.
43
+ const results = [];
44
+ for (const pkg of PACKAGES) {
45
+ const latest = await fetchLatest(pkg);
46
+ const current = pkg === '@zeph-to/cli' ? config_js_1.VERSION : null;
47
+ const outdated = !!(latest && current && (0, exports.isNewer)(latest, current));
48
+ results.push({ pkg, current, latest, outdated });
49
+ }
50
+ if (isJson) {
51
+ console.log(JSON.stringify({ results }, null, 2));
52
+ return results.some((r) => r.outdated) ? 0 : 0;
53
+ }
54
+ console.log('\n Zeph — update check\n');
55
+ let anyOutdated = false;
56
+ for (const r of results) {
57
+ if (!r.latest) {
58
+ console.log(` ? ${r.pkg}: could not reach npm registry`);
59
+ continue;
60
+ }
61
+ if (r.current === null) {
62
+ console.log(` • ${r.pkg}: latest is v${r.latest}`);
63
+ }
64
+ else if (r.outdated) {
65
+ anyOutdated = true;
66
+ console.log(` ⬆ ${r.pkg}: v${r.current} → v${r.latest} (update available)`);
67
+ }
68
+ else {
69
+ console.log(` ✓ ${r.pkg}: v${r.current} (up to date)`);
70
+ }
71
+ }
72
+ if (anyOutdated) {
73
+ console.log('\n Update with: npx @zeph-to/cli install\n');
74
+ }
75
+ else {
76
+ console.log('');
77
+ }
78
+ return 0;
79
+ };
80
+ exports.handleCheckUpdate = handleCheckUpdate;
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,374 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const fs_1 = require("fs");
5
+ const child_process_1 = require("child_process");
6
+ const zeph_hook_js_1 = require("./zeph-hook.js");
7
+ const errors_js_1 = require("./errors.js");
8
+ const installer_js_1 = require("./installer.js");
9
+ const login_js_1 = require("./login.js");
10
+ const uninstall_js_1 = require("./uninstall.js");
11
+ const verify_js_1 = require("./verify.js");
12
+ const check_update_js_1 = require("./check-update.js");
13
+ const wrapper_js_1 = require("./wrapper.js");
14
+ const listener_js_1 = require("./listener.js");
15
+ const config_js_1 = require("./config.js");
16
+ const PROJECT_DIR_VARS = ['CLAUDE_PROJECT_DIR', 'CURSOR_PROJECT_DIR', 'WINDSURF_PROJECT_DIR'];
17
+ const detectProjectDir = () => PROJECT_DIR_VARS.reduce((found, key) => found || process.env[key], undefined) ?? process.cwd();
18
+ const isMuted = () => {
19
+ try {
20
+ const dir = detectProjectDir();
21
+ const raw = (0, child_process_1.execFileSync)('cksum', { input: dir, encoding: 'utf-8' });
22
+ const hash = raw.split(' ')[0];
23
+ return (0, fs_1.existsSync)(`/tmp/zeph-muted-${hash}`);
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ };
29
+ const detectBranchAndProject = () => {
30
+ const dir = detectProjectDir();
31
+ const project = dir.split('/').filter(Boolean).pop() ?? 'project';
32
+ let branch;
33
+ try {
34
+ branch = (0, child_process_1.execFileSync)('git', ['-C', dir, 'rev-parse', '--abbrev-ref', 'HEAD'], {
35
+ encoding: 'utf-8',
36
+ stdio: ['pipe', 'pipe', 'pipe'],
37
+ }).trim();
38
+ if (!branch || branch === 'HEAD')
39
+ branch = undefined;
40
+ }
41
+ catch { /* not a git repo */ }
42
+ return { branch, project };
43
+ };
44
+ // ── Arg Parser ──────────────────────────────────────────────────
45
+ const parseArgs = (argv) => {
46
+ const result = {};
47
+ const positional = [];
48
+ const args = argv.slice(2);
49
+ for (let i = 0; i < args.length; i++) {
50
+ const arg = args[i];
51
+ if (!arg.startsWith('--')) {
52
+ positional.push(arg);
53
+ continue;
54
+ }
55
+ const key = arg.slice(2);
56
+ const next = args[i + 1];
57
+ if (!next || next.startsWith('--')) {
58
+ result[key] = true;
59
+ }
60
+ else {
61
+ result[key] = next;
62
+ i++;
63
+ }
64
+ }
65
+ result._command = positional[0] ?? '';
66
+ result._arg1 = positional[1] ?? '';
67
+ return result;
68
+ };
69
+ // ── Output ──────────────────────────────────────────────────────
70
+ const printUsage = () => {
71
+ console.log(`Usage: zeph <command> [options]
72
+
73
+ Commands:
74
+ install One-command setup: detect agents, save config, install rules
75
+ login Browser sign-in: auto-fetch API key + hook into ~/.zeph/config.json
76
+ uninstall Remove Zeph from all detected agents
77
+ verify Check installation health across detected agents
78
+ check-update Check whether a newer Zeph version is available
79
+ notify Send a push notification
80
+ list List recent push notifications
81
+ dismiss <id> Dismiss a push notification (or --all)
82
+ test Send a test notification to verify setup
83
+ cc [args…] Run 'claude' in a named tmux session ('zeph-<project>')
84
+ codex [args…] Run 'codex' in a named tmux session
85
+ gemini [args…] Run 'gemini' in a named tmux session
86
+ (auto-suffixed -2/-3/… when another zeph cc is already
87
+ attached to the default name; any args after the
88
+ subcommand are forwarded verbatim, e.g.
89
+ 'zeph cc --resume')
90
+ listener Resident daemon — receives 'agent.command' pushes from
91
+ the phone picker and injects them into the matching
92
+ tmux session.
93
+
94
+ Notify options:
95
+ --title <text> Push title
96
+ --body <text> Push body
97
+ --url <url> URL to include
98
+ --type <type> Push type (note|link|file|hook) [default: hook]
99
+ --priority <p> Priority (low|normal|high|urgent) [default: normal]
100
+ --device <id> Target device ID
101
+ --session <id> AI session ID (or set ZEPH_SESSION_ID env)
102
+
103
+ List options:
104
+ --limit <n> Number of pushes (1-20, default 5)
105
+ --type <type> Filter by push type
106
+
107
+ Dismiss options:
108
+ --all Dismiss all notifications
109
+
110
+ Login options:
111
+ --web-url <url> Web app URL to sign in at [default: https://app.zeph.to]
112
+ --timeout <sec> Seconds to wait for the browser [default: 300]
113
+
114
+ Install options:
115
+ (no --key, no saved config → opens browser login automatically;
116
+ headless falls back to manual key entry)
117
+ --key <api-key> API key (non-interactive; skips browser login)
118
+ --hook <hook-id> Hook ID (non-interactive)
119
+ --base-url <url> Base URL (non-interactive)
120
+ --web-url <url> Login web app URL [default: https://app.zeph.to]
121
+ --only <agents> Comma-separated agent ids to install for
122
+ (claude,cursor,windsurf,gemini,codex,copilot,cline,aider).
123
+ Skips the interactive picker.
124
+
125
+ Uninstall options:
126
+ --dry-run Preview what would be removed, change nothing
127
+ --purge Also delete ~/.zeph/config.json (kept by default)
128
+
129
+ Verify options:
130
+ --ping Also make a live API call to confirm the key works
131
+
132
+ Global options:
133
+ --key <api-key> API key (or set ZEPH_API_KEY env)
134
+ --base-url <url> API base URL (or set ZEPH_BASE_URL env)
135
+ --json Output JSON format
136
+ --version Show version
137
+
138
+ Environment:
139
+ ZEPH_API_KEY API key (fallback when --key not provided)
140
+ ZEPH_BASE_URL API base URL (fallback when --base-url not provided)
141
+ ZEPH_SESSION_ID AI session ID (fallback when --session not provided)`);
142
+ };
143
+ const printError = (message, isJson) => {
144
+ if (isJson) {
145
+ console.error(JSON.stringify({ error: message, status: 'error' }));
146
+ }
147
+ else {
148
+ console.error(`Error: ${message}`);
149
+ }
150
+ };
151
+ const printJson = (data) => {
152
+ console.log(JSON.stringify(data, null, 2));
153
+ };
154
+ // ── Commands ────────────────────────────────────────────────────
155
+ const createHook = (args) => {
156
+ const config = (0, config_js_1.loadConfig)();
157
+ const apiKey = args.key || (0, config_js_1.resolvedEnv)('ZEPH_API_KEY') || config.apiKey;
158
+ const isJson = args.json === true;
159
+ if (!apiKey) {
160
+ printError('API key required. Run "zeph install" or set ZEPH_API_KEY', isJson);
161
+ return null;
162
+ }
163
+ const baseUrl = args['base-url'] || (0, config_js_1.resolvedEnv)('ZEPH_BASE_URL') || config.baseUrl;
164
+ return new zeph_hook_js_1.ZephHook({
165
+ apiKey,
166
+ ...(baseUrl && { baseUrl }),
167
+ });
168
+ };
169
+ const handleNotify = async (args) => {
170
+ const isJson = args.json === true;
171
+ if (isMuted())
172
+ return 0;
173
+ const hook = createHook(args);
174
+ if (!hook)
175
+ return 3;
176
+ try {
177
+ const sessionId = args.session || (0, config_js_1.resolvedEnv)('ZEPH_SESSION_ID') || undefined;
178
+ // When body isn't supplied (common case for hook-driven invocations like
179
+ // `zeph notify --title "Task done"`), auto-fill with branch + project so
180
+ // the user can tell which session finished without opening the app.
181
+ let title = args.title;
182
+ let body = args.body;
183
+ if (!body) {
184
+ const { branch, project } = detectBranchAndProject();
185
+ body = branch ? `${project} · ${branch}` : project;
186
+ }
187
+ if (!title)
188
+ title = 'Task done';
189
+ const result = await hook.notify({
190
+ title,
191
+ body,
192
+ url: args.url,
193
+ type: args.type || 'hook',
194
+ priority: args.priority || undefined,
195
+ targetDeviceId: args.device,
196
+ sessionId,
197
+ });
198
+ if (isJson) {
199
+ printJson({ pushId: result.pushId, status: 'ok' });
200
+ }
201
+ else {
202
+ console.log(`Push sent: ${result.pushId}`);
203
+ }
204
+ return 0;
205
+ }
206
+ catch (err) {
207
+ return handleError(err, isJson);
208
+ }
209
+ };
210
+ const handleList = async (args) => {
211
+ const isJson = args.json === true;
212
+ const hook = createHook(args);
213
+ if (!hook)
214
+ return 3;
215
+ try {
216
+ const limit = args.limit ? Number(args.limit) : undefined;
217
+ const result = await hook.list({
218
+ limit,
219
+ type: args.type,
220
+ });
221
+ if (isJson) {
222
+ printJson(result);
223
+ }
224
+ else {
225
+ if (result.pushes.length === 0) {
226
+ console.log('No pushes found.');
227
+ }
228
+ else {
229
+ for (const p of result.pushes) {
230
+ const title = p.title ?? '(no title)';
231
+ const time = new Date(p.createdAt).toLocaleString();
232
+ console.log(` ${p.pushId} [${p.type}] ${title} (${time})`);
233
+ }
234
+ if (result.hasMore)
235
+ console.log(` ... more available (use --limit to increase)`);
236
+ }
237
+ }
238
+ return 0;
239
+ }
240
+ catch (err) {
241
+ return handleError(err, isJson);
242
+ }
243
+ };
244
+ const handleDismiss = async (args) => {
245
+ const isJson = args.json === true;
246
+ const hook = createHook(args);
247
+ if (!hook)
248
+ return 3;
249
+ try {
250
+ if (args.all === true) {
251
+ const result = await hook.dismissAll();
252
+ if (isJson) {
253
+ printJson({ dismissed: result.dismissed, status: 'ok' });
254
+ }
255
+ else {
256
+ console.log(`Dismissed ${result.dismissed} pushes.`);
257
+ }
258
+ }
259
+ else {
260
+ const pushId = args._arg1;
261
+ if (!pushId) {
262
+ printError('Push ID required. Usage: zeph dismiss <push-id> or zeph dismiss --all', isJson);
263
+ return 1;
264
+ }
265
+ await hook.dismiss(pushId);
266
+ if (isJson) {
267
+ printJson({ dismissed: true, pushId, status: 'ok' });
268
+ }
269
+ else {
270
+ console.log(`Dismissed: ${pushId}`);
271
+ }
272
+ }
273
+ return 0;
274
+ }
275
+ catch (err) {
276
+ return handleError(err, isJson);
277
+ }
278
+ };
279
+ const handleTest = async (args) => {
280
+ const isJson = args.json === true;
281
+ const hook = createHook(args);
282
+ if (!hook)
283
+ return 3;
284
+ try {
285
+ const result = await hook.notify({
286
+ title: 'Zeph Test',
287
+ body: `CLI connected successfully (v${config_js_1.VERSION})`,
288
+ });
289
+ if (isJson) {
290
+ printJson({ pushId: result.pushId, status: 'ok', message: 'Test notification sent' });
291
+ }
292
+ else {
293
+ console.log(`Test notification sent: ${result.pushId}`);
294
+ }
295
+ return 0;
296
+ }
297
+ catch (err) {
298
+ return handleError(err, isJson);
299
+ }
300
+ };
301
+ // ── Error Handler ───────────────────────────────────────────────
302
+ const handleError = (err, isJson) => {
303
+ if (err instanceof errors_js_1.QuotaExceededError) {
304
+ printError(err.message, isJson);
305
+ return 2;
306
+ }
307
+ if (err instanceof errors_js_1.AuthenticationError) {
308
+ printError(err.message, isJson);
309
+ return 3;
310
+ }
311
+ if (err instanceof errors_js_1.ZephError) {
312
+ printError(err.message, isJson);
313
+ return 1;
314
+ }
315
+ printError(err instanceof Error ? err.message : 'Unknown error', isJson);
316
+ return 1;
317
+ };
318
+ // ── Passthrough ─────────────────────────────────────────────────
319
+ /**
320
+ * Collect raw argv after the given subcommand token so flags like
321
+ * `--resume` reach the wrapped agent verbatim instead of being swallowed
322
+ * by `parseArgs`. Returns [] when the command isn't found.
323
+ */
324
+ const collectPassthrough = (argv, cmd) => {
325
+ const idx = argv.indexOf(cmd, 2);
326
+ return idx >= 0 ? argv.slice(idx + 1) : [];
327
+ };
328
+ // ── Main ────────────────────────────────────────────────────────
329
+ const main = async () => {
330
+ const args = parseArgs(process.argv);
331
+ const command = args._command;
332
+ if (args.version === true) {
333
+ console.log(config_js_1.VERSION);
334
+ return 0;
335
+ }
336
+ if (!command || command === 'help') {
337
+ printUsage();
338
+ return 0;
339
+ }
340
+ switch (command) {
341
+ case 'install':
342
+ case 'setup':
343
+ return (0, installer_js_1.handleInstall)(args);
344
+ case 'login':
345
+ return (0, login_js_1.handleLogin)(args);
346
+ case 'uninstall':
347
+ return (0, uninstall_js_1.handleUninstall)(args);
348
+ case 'verify':
349
+ return (0, verify_js_1.handleVerify)(args);
350
+ case 'check-update':
351
+ return (0, check_update_js_1.handleCheckUpdate)(args);
352
+ case 'notify':
353
+ return handleNotify(args);
354
+ case 'list':
355
+ return handleList(args);
356
+ case 'dismiss':
357
+ return handleDismiss(args);
358
+ case 'test':
359
+ return handleTest(args);
360
+ case 'cc':
361
+ return (0, wrapper_js_1.handleAgentSession)('claude', collectPassthrough(process.argv, 'cc'));
362
+ case 'codex':
363
+ return (0, wrapper_js_1.handleAgentSession)('codex', collectPassthrough(process.argv, 'codex'));
364
+ case 'gemini':
365
+ return (0, wrapper_js_1.handleAgentSession)('gemini', collectPassthrough(process.argv, 'gemini'));
366
+ case 'listener':
367
+ return (0, listener_js_1.handleListener)(args);
368
+ default:
369
+ printError(`Unknown command: ${command}`, args.json === true);
370
+ printUsage();
371
+ return 1;
372
+ }
373
+ };
374
+ main().then((code) => process.exit(code));
@@ -0,0 +1,14 @@
1
+ export declare const CONFIG_DIR: string;
2
+ export declare const CONFIG_FILE: string;
3
+ export interface ZephConfig {
4
+ apiKey?: string;
5
+ hookId?: string;
6
+ baseUrl?: string;
7
+ wsUrl?: string;
8
+ deviceId?: string;
9
+ }
10
+ export declare const resolvedEnv: (key: string) => string | undefined;
11
+ export declare const loadConfig: () => ZephConfig;
12
+ export declare const saveConfig: (config: ZephConfig) => void;
13
+ export declare const VERSION: string;
14
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,UAAU,QAA2B,CAAC;AACnD,eAAO,MAAM,WAAW,QAAkC,CAAC;AAE3D,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,SAGlD,CAAC;AAEF,eAAO,MAAM,UAAU,QAAO,UAM7B,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,QAAQ,UAAU,KAAG,IAG/C,CAAC;AAEF,eAAO,MAAM,OAAO,QAOhB,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VERSION = exports.saveConfig = exports.loadConfig = exports.resolvedEnv = exports.CONFIG_FILE = exports.CONFIG_DIR = void 0;
4
+ const fs_1 = require("fs");
5
+ const os_1 = require("os");
6
+ const path_1 = require("path");
7
+ exports.CONFIG_DIR = (0, path_1.join)((0, os_1.homedir)(), '.zeph');
8
+ exports.CONFIG_FILE = (0, path_1.join)(exports.CONFIG_DIR, 'config.json');
9
+ const resolvedEnv = (key) => {
10
+ const val = process.env[key];
11
+ return val && !val.startsWith('${') ? val : undefined;
12
+ };
13
+ exports.resolvedEnv = resolvedEnv;
14
+ const loadConfig = () => {
15
+ try {
16
+ return JSON.parse((0, fs_1.readFileSync)(exports.CONFIG_FILE, 'utf-8'));
17
+ }
18
+ catch {
19
+ return {};
20
+ }
21
+ };
22
+ exports.loadConfig = loadConfig;
23
+ const saveConfig = (config) => {
24
+ (0, fs_1.mkdirSync)(exports.CONFIG_DIR, { recursive: true });
25
+ (0, fs_1.writeFileSync)(exports.CONFIG_FILE, JSON.stringify(config, null, 2) + '\n');
26
+ };
27
+ exports.saveConfig = saveConfig;
28
+ exports.VERSION = (() => {
29
+ try {
30
+ const pkg = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '..', 'package.json'), 'utf-8'));
31
+ return pkg.version;
32
+ }
33
+ catch {
34
+ return '0.0.0';
35
+ }
36
+ })();
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Device-shared encryption for Hook SDK — self-contained ECDH P-256 +
3
+ * AES-256-GCM. Mirrors @zeph/crypto API but bundled inline (no external
4
+ * dependency). Uses Web Crypto API (globalThis.crypto.subtle) — Node.js 18+.
5
+ *
6
+ * Threat model honesty (do not call this "E2E" without a footnote):
7
+ *
8
+ * The Zeph backend persists the per-user private key in plaintext so it
9
+ * can be synced down to a fresh device (fetchServerKeys / uploadServerKeys
10
+ * below). That means the backend can decrypt any push body — this is NOT
11
+ * end-to-end in the standard sense. What it gives you is:
12
+ * • Protection against passive network observers
13
+ * • Protection against a leaked DB snapshot taken without the key store
14
+ * • Cross-device readability (all your devices share one keypair)
15
+ * What it does NOT give you:
16
+ * • Protection against the Zeph backend itself
17
+ * • Forward secrecy — encryptPushBodyForSelf / encryptFileForSelf do
18
+ * ECDH(self, self), which collapses to a static derived key. A single
19
+ * device compromise (since all your devices share the same keypair)
20
+ * lets the attacker decrypt every past push for which they have the
21
+ * ciphertext. The per-message AES key is random, but its wrap key is
22
+ * static, so wrapped keys are decryptable forever.
23
+ *
24
+ * True E2E would require a per-device keypair (server stores only public
25
+ * keys; senders wrap the message key once per recipient device public
26
+ * key). That refactor is on the roadmap; until then, treat push bodies as
27
+ * sensitive-but-not-secret.
28
+ */
29
+ /**
30
+ * Initialize crypto: sync keys with server, then fallback to local/generate.
31
+ * Server is source of truth for per-user key pair.
32
+ * Safe to call concurrently — deduplicates to single init.
33
+ * Returns the exported public key (Base64 SPKI).
34
+ */
35
+ export declare const initCrypto: (apiKey?: string, baseUrl?: string) => Promise<string>;
36
+ export declare const getKeyPair: () => CryptoKeyPair | null;
37
+ export declare const getPublicKey: () => string | null;
38
+ /**
39
+ * Encrypt push body for a recipient.
40
+ * Returns fields ready to merge into the sendPush payload.
41
+ */
42
+ export declare const encryptPushBody: (input: {
43
+ title?: string;
44
+ body?: string;
45
+ url?: string;
46
+ }, recipientPublicKeyRaw: string) => Promise<{
47
+ body: string;
48
+ encryptedKey: string;
49
+ senderPublicKey: string;
50
+ isEncrypted: true;
51
+ }>;
52
+ /**
53
+ * Encrypt push body for self (all own devices).
54
+ */
55
+ export declare const encryptPushBodyForSelf: (input: {
56
+ title?: string;
57
+ body?: string;
58
+ url?: string;
59
+ }) => Promise<{
60
+ body: string;
61
+ encryptedKey: string;
62
+ senderPublicKey: string;
63
+ isEncrypted: true;
64
+ }>;
65
+ /**
66
+ * Encrypt file content for a recipient.
67
+ * Returns encrypted buffer + key material for file attachment metadata.
68
+ */
69
+ export declare const encryptFileForRecipient: (content: string, recipientPublicKeyRaw: string) => Promise<{
70
+ ciphertext: Buffer;
71
+ iv: string;
72
+ encryptedKey: string;
73
+ }>;
74
+ /**
75
+ * Encrypt file content for self (all own devices).
76
+ */
77
+ export declare const encryptFileForSelf: (content: string) => Promise<{
78
+ ciphertext: Buffer;
79
+ iv: string;
80
+ encryptedKey: string;
81
+ }>;
82
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA2JH;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,CAsE5E,CAAC;AAqCF,eAAO,MAAM,UAAU,QAAO,aAAa,GAAG,IAAqB,CAAC;AACpE,eAAO,MAAM,YAAY,QAAO,MAAM,GAAG,IAA+B,CAAC;AAEzE;;;GAGG;AACH,eAAO,MAAM,eAAe,GAC1B,OAAO;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,EACtD,uBAAuB,MAAM,KAC5B,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,IAAI,CAAC;CACnB,CAeA,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB,GACjC,OAAO;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,KACrD,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,IAAI,CAAC;CACnB,CAaA,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GAClC,SAAS,MAAM,EACf,uBAAuB,MAAM,KAC5B,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CASlE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAC7B,SAAS,MAAM,KACd,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAQlE,CAAC"}