@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.
- package/LICENSE +190 -0
- package/README.md +499 -0
- package/dist/agents.d.ts +8 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +29 -0
- package/dist/check-update.d.ts +4 -0
- package/dist/check-update.d.ts.map +1 -0
- package/dist/check-update.js +80 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +374 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +36 -0
- package/dist/crypto.d.ts +82 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +291 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +28 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/installer.d.ts +14 -0
- package/dist/installer.d.ts.map +1 -0
- package/dist/installer.js +464 -0
- package/dist/listener.d.ts +126 -0
- package/dist/listener.d.ts.map +1 -0
- package/dist/listener.js +1008 -0
- package/dist/login.d.ts +38 -0
- package/dist/login.d.ts.map +1 -0
- package/dist/login.js +182 -0
- package/dist/templates.d.ts +44 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +257 -0
- package/dist/types.d.ts +54 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/uninstall.d.ts +2 -0
- package/dist/uninstall.d.ts.map +1 -0
- package/dist/uninstall.js +217 -0
- package/dist/verify.d.ts +2 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +109 -0
- package/dist/wrapper.d.ts +26 -0
- package/dist/wrapper.d.ts.map +1 -0
- package/dist/wrapper.js +238 -0
- package/dist/zeph-hook.d.ts +23 -0
- package/dist/zeph-hook.d.ts.map +1 -0
- package/dist/zeph-hook.js +196 -0
- package/package.json +75 -0
package/dist/agents.d.ts
ADDED
|
@@ -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 @@
|
|
|
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));
|
package/dist/config.d.ts
ADDED
|
@@ -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
|
+
})();
|
package/dist/crypto.d.ts
ADDED
|
@@ -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"}
|