@zeph-to/hook-sdk 1.7.0 → 1.8.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 +12 -5
- 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.js +50 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -1
- package/dist/crypto.d.ts +26 -3
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +28 -4
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +105 -25
- package/dist/templates.d.ts +24 -2
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +204 -24
- 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/package.json +5 -2
package/README.md
CHANGED
|
@@ -50,7 +50,10 @@ zeph notify --title "Hello" --json
|
|
|
50
50
|
|
|
51
51
|
| Command | Description |
|
|
52
52
|
|---------|-------------|
|
|
53
|
-
| `install` | One-command setup: detect agents, save config, install
|
|
53
|
+
| `install` | One-command setup: detect agents, save config, install rules + hooks + MCP |
|
|
54
|
+
| `uninstall` | Remove Zeph from all detected agents (`--dry-run`, `--purge`) |
|
|
55
|
+
| `verify` | Check installation health across detected agents (`--ping` for a live API call) |
|
|
56
|
+
| `check-update` | Check whether a newer Zeph version is on npm |
|
|
54
57
|
| `notify` | Send a push notification |
|
|
55
58
|
| `list` | List recent push notifications |
|
|
56
59
|
| `dismiss <id>` | Dismiss a push (or `--all`) |
|
|
@@ -60,13 +63,15 @@ zeph notify --title "Hello" --json
|
|
|
60
63
|
|
|
61
64
|
| Flag | Description |
|
|
62
65
|
|------|-------------|
|
|
63
|
-
| `--title <text>` | Push title |
|
|
64
|
-
| `--body <text>` | Push body |
|
|
66
|
+
| `--title <text>` | Push title (default: `"Task done"`) |
|
|
67
|
+
| `--body <text>` | Push body (default: `"<project> · <branch>"` if cwd is a git repo, else `"<project>"`) |
|
|
65
68
|
| `--url <url>` | URL to include |
|
|
66
69
|
| `--type <type>` | Push type: `note`, `link`, `file`, `hook` |
|
|
67
70
|
| `--priority <p>` | Priority: `low`, `normal`, `high`, `urgent` |
|
|
68
71
|
| `--device <id>` | Target device ID |
|
|
69
72
|
|
|
73
|
+
The defaults are tuned for hook-driven invocations (e.g. Stop hooks calling `zeph notify --title "Task done"` without a body) — you'll see which project + branch finished without writing per-IDE wrappers. Pass `--body ""` explicitly to suppress.
|
|
74
|
+
|
|
70
75
|
### List Options
|
|
71
76
|
|
|
72
77
|
| Flag | Description |
|
|
@@ -188,9 +193,11 @@ try {
|
|
|
188
193
|
| Copilot CLI | Session end hook |
|
|
189
194
|
| Cline | Rules file |
|
|
190
195
|
|
|
191
|
-
##
|
|
196
|
+
## Encryption
|
|
197
|
+
|
|
198
|
+
Push bodies are encrypted with AES-256-GCM. The wrapping key is derived via ECDH P-256 and synced across your own devices on first run so every device can read the same push. Toggle encryption in the Zeph app (Settings → Encryption); when disabled, the CLI sends plaintext. No configuration needed.
|
|
192
199
|
|
|
193
|
-
|
|
200
|
+
**Threat model honesty:** keys are persisted on the Zeph backend to enable cross-device sync, so this is *device-shared* encryption — not true end-to-end. It protects push contents from passive network observers and from a leaked database snapshot taken without the key store, but it does **not** protect against the Zeph backend itself (it has the keys it serves to your devices). A true E2E mode (per-device keypairs, server stores only public keys, no key escrow) is on the roadmap.
|
|
194
201
|
|
|
195
202
|
## Requirements
|
|
196
203
|
|
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/hook-sdk', '@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 hook-sdk'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/hook-sdk' ? 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/hook-sdk install\n');
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
console.log('');
|
|
77
|
+
}
|
|
78
|
+
return 0;
|
|
79
|
+
};
|
|
80
|
+
exports.handleCheckUpdate = handleCheckUpdate;
|
package/dist/cli.js
CHANGED
|
@@ -6,11 +6,15 @@ const child_process_1 = require("child_process");
|
|
|
6
6
|
const zeph_hook_js_1 = require("./zeph-hook.js");
|
|
7
7
|
const errors_js_1 = require("./errors.js");
|
|
8
8
|
const installer_js_1 = require("./installer.js");
|
|
9
|
+
const uninstall_js_1 = require("./uninstall.js");
|
|
10
|
+
const verify_js_1 = require("./verify.js");
|
|
11
|
+
const check_update_js_1 = require("./check-update.js");
|
|
9
12
|
const config_js_1 = require("./config.js");
|
|
10
13
|
const PROJECT_DIR_VARS = ['CLAUDE_PROJECT_DIR', 'CURSOR_PROJECT_DIR', 'WINDSURF_PROJECT_DIR'];
|
|
14
|
+
const detectProjectDir = () => PROJECT_DIR_VARS.reduce((found, key) => found || process.env[key], undefined) ?? process.cwd();
|
|
11
15
|
const isMuted = () => {
|
|
12
16
|
try {
|
|
13
|
-
const dir =
|
|
17
|
+
const dir = detectProjectDir();
|
|
14
18
|
const raw = (0, child_process_1.execFileSync)('cksum', { input: dir, encoding: 'utf-8' });
|
|
15
19
|
const hash = raw.split(' ')[0];
|
|
16
20
|
return (0, fs_1.existsSync)(`/tmp/zeph-muted-${hash}`);
|
|
@@ -19,6 +23,21 @@ const isMuted = () => {
|
|
|
19
23
|
return false;
|
|
20
24
|
}
|
|
21
25
|
};
|
|
26
|
+
const detectBranchAndProject = () => {
|
|
27
|
+
const dir = detectProjectDir();
|
|
28
|
+
const project = dir.split('/').filter(Boolean).pop() ?? 'project';
|
|
29
|
+
let branch;
|
|
30
|
+
try {
|
|
31
|
+
branch = (0, child_process_1.execFileSync)('git', ['-C', dir, 'rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
32
|
+
encoding: 'utf-8',
|
|
33
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
34
|
+
}).trim();
|
|
35
|
+
if (!branch || branch === 'HEAD')
|
|
36
|
+
branch = undefined;
|
|
37
|
+
}
|
|
38
|
+
catch { /* not a git repo */ }
|
|
39
|
+
return { branch, project };
|
|
40
|
+
};
|
|
22
41
|
// ── Arg Parser ──────────────────────────────────────────────────
|
|
23
42
|
const parseArgs = (argv) => {
|
|
24
43
|
const result = {};
|
|
@@ -49,7 +68,10 @@ const printUsage = () => {
|
|
|
49
68
|
console.log(`Usage: zeph <command> [options]
|
|
50
69
|
|
|
51
70
|
Commands:
|
|
52
|
-
install One-command setup: detect agents, save config, install
|
|
71
|
+
install One-command setup: detect agents, save config, install rules
|
|
72
|
+
uninstall Remove Zeph from all detected agents
|
|
73
|
+
verify Check installation health across detected agents
|
|
74
|
+
check-update Check whether a newer Zeph version is available
|
|
53
75
|
notify Send a push notification
|
|
54
76
|
list List recent push notifications
|
|
55
77
|
dismiss <id> Dismiss a push notification (or --all)
|
|
@@ -76,6 +98,13 @@ Install options:
|
|
|
76
98
|
--hook <hook-id> Hook ID (non-interactive)
|
|
77
99
|
--base-url <url> Base URL (non-interactive)
|
|
78
100
|
|
|
101
|
+
Uninstall options:
|
|
102
|
+
--dry-run Preview what would be removed, change nothing
|
|
103
|
+
--purge Also delete ~/.zeph/config.json (kept by default)
|
|
104
|
+
|
|
105
|
+
Verify options:
|
|
106
|
+
--ping Also make a live API call to confirm the key works
|
|
107
|
+
|
|
79
108
|
Global options:
|
|
80
109
|
--key <api-key> API key (or set ZEPH_API_KEY env)
|
|
81
110
|
--base-url <url> API base URL (or set ZEPH_BASE_URL env)
|
|
@@ -122,9 +151,20 @@ const handleNotify = async (args) => {
|
|
|
122
151
|
return 3;
|
|
123
152
|
try {
|
|
124
153
|
const sessionId = args.session || (0, config_js_1.resolvedEnv)('ZEPH_SESSION_ID') || undefined;
|
|
154
|
+
// When body isn't supplied (common case for hook-driven invocations like
|
|
155
|
+
// `zeph notify --title "Task done"`), auto-fill with branch + project so
|
|
156
|
+
// the user can tell which session finished without opening the app.
|
|
157
|
+
let title = args.title;
|
|
158
|
+
let body = args.body;
|
|
159
|
+
if (!body) {
|
|
160
|
+
const { branch, project } = detectBranchAndProject();
|
|
161
|
+
body = branch ? `${project} · ${branch}` : project;
|
|
162
|
+
}
|
|
163
|
+
if (!title)
|
|
164
|
+
title = 'Task done';
|
|
125
165
|
const result = await hook.notify({
|
|
126
|
-
title
|
|
127
|
-
body
|
|
166
|
+
title,
|
|
167
|
+
body,
|
|
128
168
|
url: args.url,
|
|
129
169
|
type: args.type || 'hook',
|
|
130
170
|
priority: args.priority || undefined,
|
|
@@ -267,6 +307,12 @@ const main = async () => {
|
|
|
267
307
|
case 'install':
|
|
268
308
|
case 'setup':
|
|
269
309
|
return (0, installer_js_1.handleInstall)(args);
|
|
310
|
+
case 'uninstall':
|
|
311
|
+
return (0, uninstall_js_1.handleUninstall)(args);
|
|
312
|
+
case 'verify':
|
|
313
|
+
return (0, verify_js_1.handleVerify)(args);
|
|
314
|
+
case 'check-update':
|
|
315
|
+
return (0, check_update_js_1.handleCheckUpdate)(args);
|
|
270
316
|
case 'notify':
|
|
271
317
|
return handleNotify(args);
|
|
272
318
|
case 'list':
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
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,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
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.VERSION = exports.saveConfig = exports.loadConfig = exports.resolvedEnv = exports.CONFIG_FILE = exports.CONFIG_DIR = void 0;
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
|
+
const os_1 = require("os");
|
|
5
6
|
const path_1 = require("path");
|
|
6
|
-
exports.CONFIG_DIR = (0, path_1.join)(
|
|
7
|
+
exports.CONFIG_DIR = (0, path_1.join)((0, os_1.homedir)(), '.zeph');
|
|
7
8
|
exports.CONFIG_FILE = (0, path_1.join)(exports.CONFIG_DIR, 'config.json');
|
|
8
9
|
const resolvedEnv = (key) => {
|
|
9
10
|
const val = process.env[key];
|
package/dist/crypto.d.ts
CHANGED
|
@@ -1,7 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Mirrors @zeph/crypto API but bundled inline (no external
|
|
4
|
-
* Uses Web Crypto API (globalThis.crypto.subtle) — Node.js 18+.
|
|
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.
|
|
5
28
|
*/
|
|
6
29
|
/**
|
|
7
30
|
* Initialize crypto: sync keys with server, then fallback to local/generate.
|
package/dist/crypto.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA
|
|
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"}
|
package/dist/crypto.js
CHANGED
|
@@ -1,13 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* Mirrors @zeph/crypto API but bundled inline (no external
|
|
5
|
-
* Uses Web Crypto API (globalThis.crypto.subtle) — Node.js 18+.
|
|
3
|
+
* Device-shared encryption for Hook SDK — self-contained ECDH P-256 +
|
|
4
|
+
* AES-256-GCM. Mirrors @zeph/crypto API but bundled inline (no external
|
|
5
|
+
* dependency). Uses Web Crypto API (globalThis.crypto.subtle) — Node.js 18+.
|
|
6
|
+
*
|
|
7
|
+
* Threat model honesty (do not call this "E2E" without a footnote):
|
|
8
|
+
*
|
|
9
|
+
* The Zeph backend persists the per-user private key in plaintext so it
|
|
10
|
+
* can be synced down to a fresh device (fetchServerKeys / uploadServerKeys
|
|
11
|
+
* below). That means the backend can decrypt any push body — this is NOT
|
|
12
|
+
* end-to-end in the standard sense. What it gives you is:
|
|
13
|
+
* • Protection against passive network observers
|
|
14
|
+
* • Protection against a leaked DB snapshot taken without the key store
|
|
15
|
+
* • Cross-device readability (all your devices share one keypair)
|
|
16
|
+
* What it does NOT give you:
|
|
17
|
+
* • Protection against the Zeph backend itself
|
|
18
|
+
* • Forward secrecy — encryptPushBodyForSelf / encryptFileForSelf do
|
|
19
|
+
* ECDH(self, self), which collapses to a static derived key. A single
|
|
20
|
+
* device compromise (since all your devices share the same keypair)
|
|
21
|
+
* lets the attacker decrypt every past push for which they have the
|
|
22
|
+
* ciphertext. The per-message AES key is random, but its wrap key is
|
|
23
|
+
* static, so wrapped keys are decryptable forever.
|
|
24
|
+
*
|
|
25
|
+
* True E2E would require a per-device keypair (server stores only public
|
|
26
|
+
* keys; senders wrap the message key once per recipient device public
|
|
27
|
+
* key). That refactor is on the roadmap; until then, treat push bodies as
|
|
28
|
+
* sensitive-but-not-secret.
|
|
6
29
|
*/
|
|
7
30
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
31
|
exports.encryptFileForSelf = exports.encryptFileForRecipient = exports.encryptPushBodyForSelf = exports.encryptPushBody = exports.getPublicKey = exports.getKeyPair = exports.initCrypto = void 0;
|
|
9
32
|
/// <reference lib="dom" />
|
|
10
33
|
const fs_1 = require("fs");
|
|
34
|
+
const os_1 = require("os");
|
|
11
35
|
const path_1 = require("path");
|
|
12
36
|
// ─── Base64 helpers ───
|
|
13
37
|
const toBase64 = (buffer) => {
|
|
@@ -79,7 +103,7 @@ const encryptFileContent = async (content, senderPrivateKey, recipientPublicKey)
|
|
|
79
103
|
};
|
|
80
104
|
};
|
|
81
105
|
// ─── Key persistence (~/.config/zeph/keys.json) ───
|
|
82
|
-
const KEYS_DIR = (0, path_1.join)(
|
|
106
|
+
const KEYS_DIR = (0, path_1.join)((0, os_1.homedir)(), '.config', 'zeph');
|
|
83
107
|
const KEYS_PATH = (0, path_1.join)(KEYS_DIR, 'keys.json');
|
|
84
108
|
const loadStoredKeys = () => {
|
|
85
109
|
try {
|
package/dist/installer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"AA4RA,eAAO,MAAM,aAAa,GAAU,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAG,OAAO,CAAC,MAAM,CAoH1F,CAAC"}
|
package/dist/installer.js
CHANGED
|
@@ -3,12 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.handleInstall = void 0;
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
5
|
const fs_1 = require("fs");
|
|
6
|
+
const os_1 = require("os");
|
|
6
7
|
const path_1 = require("path");
|
|
7
8
|
const readline_1 = require("readline");
|
|
8
9
|
const zeph_hook_js_1 = require("./zeph-hook.js");
|
|
9
10
|
const config_js_1 = require("./config.js");
|
|
11
|
+
const agents_js_1 = require("./agents.js");
|
|
10
12
|
const templates_js_1 = require("./templates.js");
|
|
11
|
-
const HOME =
|
|
13
|
+
const HOME = (0, os_1.homedir)();
|
|
12
14
|
// ── Helpers ──────────────────────────────────────────────────────
|
|
13
15
|
const ok = (msg) => console.log(` + ${msg}`);
|
|
14
16
|
const fail = (msg) => console.log(` - ${msg}`);
|
|
@@ -21,15 +23,6 @@ const promptInput = (question) => {
|
|
|
21
23
|
});
|
|
22
24
|
});
|
|
23
25
|
};
|
|
24
|
-
const hasCommand = (cmd) => {
|
|
25
|
-
try {
|
|
26
|
-
(0, child_process_1.execSync)(`which ${cmd}`, { stdio: 'pipe' });
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
26
|
const writeFile = (filePath, content) => {
|
|
34
27
|
(0, fs_1.mkdirSync)((0, path_1.dirname)(filePath), { recursive: true });
|
|
35
28
|
(0, fs_1.writeFileSync)(filePath, content + '\n');
|
|
@@ -43,16 +36,41 @@ const mergeJsonFile = (filePath, patch) => {
|
|
|
43
36
|
const merged = { ...data, ...patch };
|
|
44
37
|
writeFile(filePath, JSON.stringify(merged, null, 2));
|
|
45
38
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
{
|
|
55
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Write a Zeph rule into a SHARED agent rule file (Windsurf global_rules.md,
|
|
41
|
+
* Gemini GEMINI.md, Codex AGENTS.md) without clobbering the user's own
|
|
42
|
+
* content. The rule lands inside <!-- ZEPH:START/END --> markers; a re-run
|
|
43
|
+
* replaces just that block.
|
|
44
|
+
*/
|
|
45
|
+
const writeManagedRule = (filePath, rule) => {
|
|
46
|
+
let existing = '';
|
|
47
|
+
try {
|
|
48
|
+
existing = (0, fs_1.readFileSync)(filePath, 'utf-8');
|
|
49
|
+
}
|
|
50
|
+
catch { /* new file */ }
|
|
51
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(filePath), { recursive: true });
|
|
52
|
+
(0, fs_1.writeFileSync)(filePath, (0, templates_js_1.upsertManagedBlock)(existing, rule));
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Add a `read:` entry to ~/.aider.conf.yml so Aider always loads the Zeph
|
|
56
|
+
* conventions file. Idempotent — skips if the path is already referenced.
|
|
57
|
+
* Aider's config is YAML; we do a minimal text-level append to avoid
|
|
58
|
+
* pulling in a YAML dependency (the SDK is zero-dep by design).
|
|
59
|
+
*/
|
|
60
|
+
const addAiderReadDirective = (confPath, conventionsPath) => {
|
|
61
|
+
let conf = '';
|
|
62
|
+
try {
|
|
63
|
+
conf = (0, fs_1.readFileSync)(confPath, 'utf-8');
|
|
64
|
+
}
|
|
65
|
+
catch { /* new file */ }
|
|
66
|
+
if (conf.includes(conventionsPath))
|
|
67
|
+
return; // already wired up
|
|
68
|
+
const marker = '# Added by Zeph';
|
|
69
|
+
const line = `${marker}\nread: ${conventionsPath}\n`;
|
|
70
|
+
const base = conf.replace(/\n*$/, '');
|
|
71
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(confPath), { recursive: true });
|
|
72
|
+
(0, fs_1.writeFileSync)(confPath, (base ? `${base}\n\n` : '') + line);
|
|
73
|
+
};
|
|
56
74
|
// ── Per-Agent Installers ─────────────────────────────────────────
|
|
57
75
|
const injectMcpJson = (filePath) => {
|
|
58
76
|
let data = {};
|
|
@@ -62,9 +80,14 @@ const injectMcpJson = (filePath) => {
|
|
|
62
80
|
catch { /* new file */ }
|
|
63
81
|
if (!data.mcpServers)
|
|
64
82
|
data.mcpServers = {};
|
|
83
|
+
// Pass through env explicitly so the MCP server doesn't have to rely on
|
|
84
|
+
// process-env inheritance (which behaves differently per IDE — Cursor and
|
|
85
|
+
// Windsurf spawn the MCP from a graphical context that may not inherit
|
|
86
|
+
// shell env). Mirrors plugin/.mcp.json.
|
|
65
87
|
data.mcpServers.zeph = {
|
|
66
88
|
command: 'npx',
|
|
67
89
|
args: ['-y', '@zeph-to/mcp-server'],
|
|
90
|
+
env: { ZEPH_API_KEY: '${ZEPH_API_KEY}' },
|
|
68
91
|
};
|
|
69
92
|
(0, fs_1.mkdirSync)((0, path_1.dirname)(filePath), { recursive: true });
|
|
70
93
|
(0, fs_1.writeFileSync)(filePath, JSON.stringify(data, null, 2) + '\n');
|
|
@@ -119,6 +142,15 @@ const installWindsurf = () => {
|
|
|
119
142
|
catch {
|
|
120
143
|
fail('Hook install failed');
|
|
121
144
|
}
|
|
145
|
+
try {
|
|
146
|
+
// Windsurf reads ~/.codeium/windsurf/memories/global_rules.md as always-on
|
|
147
|
+
// global rules. Managed-block append preserves the user's own rules.
|
|
148
|
+
writeManagedRule((0, path_1.join)(HOME, '.codeium', 'windsurf', 'memories', 'global_rules.md'), templates_js_1.WINDSURF_RULE);
|
|
149
|
+
ok('Rules added to global_rules.md');
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
fail('Rule install failed. Manual: add zeph rules to ~/.codeium/windsurf/memories/global_rules.md');
|
|
153
|
+
}
|
|
122
154
|
};
|
|
123
155
|
const installGemini = () => {
|
|
124
156
|
try {
|
|
@@ -135,6 +167,14 @@ const installGemini = () => {
|
|
|
135
167
|
catch {
|
|
136
168
|
fail('Hook install failed');
|
|
137
169
|
}
|
|
170
|
+
try {
|
|
171
|
+
// Gemini CLI loads ~/.gemini/GEMINI.md as global context every prompt.
|
|
172
|
+
writeManagedRule((0, path_1.join)(HOME, '.gemini', 'GEMINI.md'), templates_js_1.GEMINI_RULE);
|
|
173
|
+
ok('Rules added to GEMINI.md');
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
fail('Rule install failed. Manual: add zeph rules to ~/.gemini/GEMINI.md');
|
|
177
|
+
}
|
|
138
178
|
};
|
|
139
179
|
const installCodex = () => {
|
|
140
180
|
try {
|
|
@@ -144,6 +184,14 @@ const installCodex = () => {
|
|
|
144
184
|
catch {
|
|
145
185
|
fail('Hook install failed. Manual: add zeph to ~/.codex/hooks.json');
|
|
146
186
|
}
|
|
187
|
+
try {
|
|
188
|
+
// Codex CLI loads ~/.codex/AGENTS.md as global instructions.
|
|
189
|
+
writeManagedRule((0, path_1.join)(HOME, '.codex', 'AGENTS.md'), templates_js_1.CODEX_RULE);
|
|
190
|
+
ok('Rules added to AGENTS.md');
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
fail('Rule install failed. Manual: add zeph rules to ~/.codex/AGENTS.md');
|
|
194
|
+
}
|
|
147
195
|
};
|
|
148
196
|
const installCopilot = () => {
|
|
149
197
|
try {
|
|
@@ -153,6 +201,15 @@ const installCopilot = () => {
|
|
|
153
201
|
catch {
|
|
154
202
|
fail('Hook install failed. Manual: add zeph to ~/.copilot/hooks/');
|
|
155
203
|
}
|
|
204
|
+
try {
|
|
205
|
+
// Copilot CLI loads ~/.copilot/instructions/*.instructions.md globally.
|
|
206
|
+
// A dedicated file means no merge needed — overwrite is safe.
|
|
207
|
+
writeFile((0, path_1.join)(HOME, '.copilot', 'instructions', 'zeph.instructions.md'), templates_js_1.COPILOT_RULE);
|
|
208
|
+
ok('Rule file added');
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
fail('Rule install failed. Manual: add zeph rules to ~/.copilot/instructions/');
|
|
212
|
+
}
|
|
156
213
|
};
|
|
157
214
|
const installCline = () => {
|
|
158
215
|
try {
|
|
@@ -163,6 +220,27 @@ const installCline = () => {
|
|
|
163
220
|
fail('Rule install failed. Manual: add zeph to ~/.cline/rules/');
|
|
164
221
|
}
|
|
165
222
|
};
|
|
223
|
+
const installAider = () => {
|
|
224
|
+
// Aider has no hooks; rules reach it via a conventions file loaded by the
|
|
225
|
+
// `read:` directive in ~/.aider.conf.yml. We keep the conventions file in
|
|
226
|
+
// ~/.zeph/ (our own dir — no conflict) and just wire the read directive.
|
|
227
|
+
const conventionsPath = (0, path_1.join)(HOME, '.zeph', 'aider-conventions.md');
|
|
228
|
+
try {
|
|
229
|
+
writeFile(conventionsPath, templates_js_1.AIDER_RULE);
|
|
230
|
+
ok('Conventions file added');
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
fail('Conventions install failed. Manual: save zeph rules somewhere readable');
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
addAiderReadDirective((0, path_1.join)(HOME, '.aider.conf.yml'), conventionsPath);
|
|
238
|
+
ok('read: directive added to ~/.aider.conf.yml');
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
fail(`Config wiring failed. Manual: add "read: ${conventionsPath}" to ~/.aider.conf.yml`);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
166
244
|
const AGENT_INSTALLERS = {
|
|
167
245
|
claude: installClaude,
|
|
168
246
|
cursor: installCursor,
|
|
@@ -171,6 +249,7 @@ const AGENT_INSTALLERS = {
|
|
|
171
249
|
codex: installCodex,
|
|
172
250
|
copilot: installCopilot,
|
|
173
251
|
cline: installCline,
|
|
252
|
+
aider: installAider,
|
|
174
253
|
};
|
|
175
254
|
// ── Test Connection ──────────────────────────────────────────────
|
|
176
255
|
const testConnection = async (apiKey, baseUrl) => {
|
|
@@ -199,7 +278,7 @@ const handleInstall = async (args) => {
|
|
|
199
278
|
console.log(`\n Zeph v${config_js_1.VERSION}\n`);
|
|
200
279
|
// 1. Detect agents
|
|
201
280
|
console.log(' Detecting agents...');
|
|
202
|
-
const agents = detectAgents();
|
|
281
|
+
const agents = (0, agents_js_1.detectAgents)();
|
|
203
282
|
const detected = agents.filter((a) => a.detected);
|
|
204
283
|
for (const agent of agents) {
|
|
205
284
|
if (agent.detected) {
|
|
@@ -251,11 +330,12 @@ const handleInstall = async (args) => {
|
|
|
251
330
|
const labels = {
|
|
252
331
|
claude: 'Install Claude Code plugin',
|
|
253
332
|
cursor: 'Setup Cursor (MCP + hooks + rules)',
|
|
254
|
-
windsurf: 'Setup Windsurf (MCP + hooks)',
|
|
255
|
-
gemini: 'Setup Gemini CLI (MCP + hooks)',
|
|
256
|
-
codex: 'Setup Codex CLI (hooks)',
|
|
257
|
-
copilot: 'Setup Copilot CLI (hooks)',
|
|
333
|
+
windsurf: 'Setup Windsurf (MCP + hooks + rules)',
|
|
334
|
+
gemini: 'Setup Gemini CLI (MCP + hooks + rules)',
|
|
335
|
+
codex: 'Setup Codex CLI (hooks + rules)',
|
|
336
|
+
copilot: 'Setup Copilot CLI (hooks + rules)',
|
|
258
337
|
cline: 'Setup Cline (rules)',
|
|
338
|
+
aider: 'Setup Aider (conventions)',
|
|
259
339
|
};
|
|
260
340
|
console.log(` ${step}. ${labels[agent.id] ?? `Install for ${agent.name}`}`);
|
|
261
341
|
step++;
|
package/dist/templates.d.ts
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
|
+
/** Cursor — written to ~/.cursor/rules/zeph.mdc (needs .mdc frontmatter). */
|
|
2
|
+
export declare const CURSOR_RULE: string;
|
|
3
|
+
/** Windsurf — appended into ~/.codeium/windsurf/memories/global_rules.md. */
|
|
4
|
+
export declare const WINDSURF_RULE: string;
|
|
5
|
+
/** Gemini CLI — appended into ~/.gemini/GEMINI.md. */
|
|
6
|
+
export declare const GEMINI_RULE: string;
|
|
7
|
+
/** Codex CLI — appended into ~/.codex/AGENTS.md. */
|
|
8
|
+
export declare const CODEX_RULE: string;
|
|
9
|
+
/** GitHub Copilot CLI — written to ~/.copilot/instructions/zeph.instructions.md. */
|
|
10
|
+
export declare const COPILOT_RULE: string;
|
|
11
|
+
/** Cline — written to ~/.cline/rules/zeph.md (no Stop hook). */
|
|
12
|
+
export declare const CLINE_RULE: string;
|
|
13
|
+
/** Aider — written to a standalone conventions file, loaded via .aider.conf.yml `read:`. */
|
|
14
|
+
export declare const AIDER_RULE: string;
|
|
1
15
|
export declare const CURSOR_HOOKS: string;
|
|
2
|
-
export declare const CURSOR_RULE = "---\ndescription: \"Zeph notification rules\"\nalwaysApply: true\n---\n\nWhen you complete a coding task, call the zeph_notify MCP tool with a brief summary.\nUse zeph_prompt for user decisions (2-4 options). Use zeph_input for free-form text.\nDo not notify for trivial operations (file reads, simple searches).\n";
|
|
3
16
|
export declare const WINDSURF_HOOKS: string;
|
|
4
17
|
export declare const GEMINI_HOOKS: {
|
|
5
18
|
hooks: {
|
|
@@ -18,5 +31,14 @@ export declare const GEMINI_HOOKS: {
|
|
|
18
31
|
};
|
|
19
32
|
export declare const CODEX_HOOKS: string;
|
|
20
33
|
export declare const COPILOT_HOOKS: string;
|
|
21
|
-
export declare const
|
|
34
|
+
export declare const ZEPH_MARK_START = "<!-- ZEPH:START \u2014 managed by @zeph-to/hook-sdk, do not edit between markers -->";
|
|
35
|
+
export declare const ZEPH_MARK_END = "<!-- ZEPH:END -->";
|
|
36
|
+
/**
|
|
37
|
+
* Return `existing` with the Zeph-managed block inserted or replaced.
|
|
38
|
+
* If the markers are already present, the content between them is
|
|
39
|
+
* swapped; otherwise the block is appended.
|
|
40
|
+
*/
|
|
41
|
+
export declare const upsertManagedBlock: (existing: string, rule: string) => string;
|
|
42
|
+
/** Strip the Zeph-managed block from a shared file (for uninstall). */
|
|
43
|
+
export declare const removeManagedBlock: (existing: string) => string;
|
|
22
44
|
//# sourceMappingURL=templates.d.ts.map
|