@zeph-to/hook-sdk 1.7.1 → 1.9.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 +7 -3
- 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 +23 -1
- package/dist/installer.d.ts +7 -0
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +177 -46
- package/dist/templates.d.ts +24 -2
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +174 -101
- 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 +10 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @zeph-to/hook-sdk
|
|
2
2
|
|
|
3
|
-
Push notification SDK + CLI for [Zeph](https://zeph.to).
|
|
3
|
+
Push notification SDK + CLI for [Zeph](https://zeph.to). The `ZephHook` SDK uses native `fetch` with no runtime dependencies; the `zeph` CLI adds `@inquirer/prompts` for its interactive installer.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -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`) |
|
|
@@ -199,7 +202,8 @@ Push bodies are encrypted with AES-256-GCM. The wrapping key is derived via ECDH
|
|
|
199
202
|
## Requirements
|
|
200
203
|
|
|
201
204
|
- Node.js >= 18 (uses native `fetch`)
|
|
202
|
-
-
|
|
205
|
+
- The `ZephHook` SDK has no runtime dependencies. The CLI depends on
|
|
206
|
+
`@inquirer/prompts` for the interactive `zeph install` picker.
|
|
203
207
|
|
|
204
208
|
## License
|
|
205
209
|
|
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,6 +6,9 @@ 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'];
|
|
11
14
|
const detectProjectDir = () => PROJECT_DIR_VARS.reduce((found, key) => found || process.env[key], undefined) ?? process.cwd();
|
|
@@ -65,7 +68,10 @@ const printUsage = () => {
|
|
|
65
68
|
console.log(`Usage: zeph <command> [options]
|
|
66
69
|
|
|
67
70
|
Commands:
|
|
68
|
-
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
|
|
69
75
|
notify Send a push notification
|
|
70
76
|
list List recent push notifications
|
|
71
77
|
dismiss <id> Dismiss a push notification (or --all)
|
|
@@ -91,6 +97,16 @@ Install options:
|
|
|
91
97
|
--key <api-key> API key (non-interactive)
|
|
92
98
|
--hook <hook-id> Hook ID (non-interactive)
|
|
93
99
|
--base-url <url> Base URL (non-interactive)
|
|
100
|
+
--only <agents> Comma-separated agent ids to install for
|
|
101
|
+
(claude,cursor,windsurf,gemini,codex,copilot,cline,aider).
|
|
102
|
+
Skips the interactive picker.
|
|
103
|
+
|
|
104
|
+
Uninstall options:
|
|
105
|
+
--dry-run Preview what would be removed, change nothing
|
|
106
|
+
--purge Also delete ~/.zeph/config.json (kept by default)
|
|
107
|
+
|
|
108
|
+
Verify options:
|
|
109
|
+
--ping Also make a live API call to confirm the key works
|
|
94
110
|
|
|
95
111
|
Global options:
|
|
96
112
|
--key <api-key> API key (or set ZEPH_API_KEY env)
|
|
@@ -294,6 +310,12 @@ const main = async () => {
|
|
|
294
310
|
case 'install':
|
|
295
311
|
case 'setup':
|
|
296
312
|
return (0, installer_js_1.handleInstall)(args);
|
|
313
|
+
case 'uninstall':
|
|
314
|
+
return (0, uninstall_js_1.handleUninstall)(args);
|
|
315
|
+
case 'verify':
|
|
316
|
+
return (0, verify_js_1.handleVerify)(args);
|
|
317
|
+
case 'check-update':
|
|
318
|
+
return (0, check_update_js_1.handleCheckUpdate)(args);
|
|
297
319
|
case 'notify':
|
|
298
320
|
return handleNotify(args);
|
|
299
321
|
case 'list':
|
package/dist/installer.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
|
+
import type { Agent } from './agents.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve agents from a non-interactive `--only cursor,gemini` flag.
|
|
4
|
+
* Matches on agent id; unknown ids are silently dropped. Exported for
|
|
5
|
+
* unit testing.
|
|
6
|
+
*/
|
|
7
|
+
export declare const filterAgentsByIds: (detected: Agent[], only: string) => Agent[];
|
|
1
8
|
export declare const handleInstall: (args: Record<string, string | boolean>) => Promise<number>;
|
|
2
9
|
//# sourceMappingURL=installer.d.ts.map
|
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":"AASA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAwSzC;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,UAAU,KAAK,EAAE,EAAE,MAAM,MAAM,KAAG,KAAK,EAKxE,CAAC;AAqBF,eAAO,MAAM,aAAa,GAAU,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAG,OAAO,CAAC,MAAM,CA4H1F,CAAC"}
|
package/dist/installer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.handleInstall = void 0;
|
|
3
|
+
exports.handleInstall = exports.filterAgentsByIds = void 0;
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
5
|
const fs_1 = require("fs");
|
|
6
6
|
const os_1 = require("os");
|
|
@@ -8,6 +8,7 @@ const path_1 = require("path");
|
|
|
8
8
|
const readline_1 = require("readline");
|
|
9
9
|
const zeph_hook_js_1 = require("./zeph-hook.js");
|
|
10
10
|
const config_js_1 = require("./config.js");
|
|
11
|
+
const agents_js_1 = require("./agents.js");
|
|
11
12
|
const templates_js_1 = require("./templates.js");
|
|
12
13
|
const HOME = (0, os_1.homedir)();
|
|
13
14
|
// ── Helpers ──────────────────────────────────────────────────────
|
|
@@ -22,15 +23,6 @@ const promptInput = (question) => {
|
|
|
22
23
|
});
|
|
23
24
|
});
|
|
24
25
|
};
|
|
25
|
-
const hasCommand = (cmd) => {
|
|
26
|
-
try {
|
|
27
|
-
(0, child_process_1.execSync)(`which ${cmd}`, { stdio: 'pipe' });
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
26
|
const writeFile = (filePath, content) => {
|
|
35
27
|
(0, fs_1.mkdirSync)((0, path_1.dirname)(filePath), { recursive: true });
|
|
36
28
|
(0, fs_1.writeFileSync)(filePath, content + '\n');
|
|
@@ -44,16 +36,41 @@ const mergeJsonFile = (filePath, patch) => {
|
|
|
44
36
|
const merged = { ...data, ...patch };
|
|
45
37
|
writeFile(filePath, JSON.stringify(merged, null, 2));
|
|
46
38
|
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
{
|
|
56
|
-
|
|
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
|
+
};
|
|
57
74
|
// ── Per-Agent Installers ─────────────────────────────────────────
|
|
58
75
|
const injectMcpJson = (filePath) => {
|
|
59
76
|
let data = {};
|
|
@@ -125,6 +142,15 @@ const installWindsurf = () => {
|
|
|
125
142
|
catch {
|
|
126
143
|
fail('Hook install failed');
|
|
127
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
|
+
}
|
|
128
154
|
};
|
|
129
155
|
const installGemini = () => {
|
|
130
156
|
try {
|
|
@@ -141,6 +167,14 @@ const installGemini = () => {
|
|
|
141
167
|
catch {
|
|
142
168
|
fail('Hook install failed');
|
|
143
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
|
+
}
|
|
144
178
|
};
|
|
145
179
|
const installCodex = () => {
|
|
146
180
|
try {
|
|
@@ -150,6 +184,14 @@ const installCodex = () => {
|
|
|
150
184
|
catch {
|
|
151
185
|
fail('Hook install failed. Manual: add zeph to ~/.codex/hooks.json');
|
|
152
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
|
+
}
|
|
153
195
|
};
|
|
154
196
|
const installCopilot = () => {
|
|
155
197
|
try {
|
|
@@ -159,6 +201,15 @@ const installCopilot = () => {
|
|
|
159
201
|
catch {
|
|
160
202
|
fail('Hook install failed. Manual: add zeph to ~/.copilot/hooks/');
|
|
161
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
|
+
}
|
|
162
213
|
};
|
|
163
214
|
const installCline = () => {
|
|
164
215
|
try {
|
|
@@ -169,6 +220,27 @@ const installCline = () => {
|
|
|
169
220
|
fail('Rule install failed. Manual: add zeph to ~/.cline/rules/');
|
|
170
221
|
}
|
|
171
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
|
+
};
|
|
172
244
|
const AGENT_INSTALLERS = {
|
|
173
245
|
claude: installClaude,
|
|
174
246
|
cursor: installCursor,
|
|
@@ -177,7 +249,54 @@ const AGENT_INSTALLERS = {
|
|
|
177
249
|
codex: installCodex,
|
|
178
250
|
copilot: installCopilot,
|
|
179
251
|
cline: installCline,
|
|
252
|
+
aider: installAider,
|
|
253
|
+
};
|
|
254
|
+
// One-line summary of what each agent's installer does — shown in the
|
|
255
|
+
// interactive plan before anything is written.
|
|
256
|
+
const AGENT_PLAN_LABELS = {
|
|
257
|
+
claude: 'Claude Code — install plugin',
|
|
258
|
+
cursor: 'Cursor — MCP + hooks + rules',
|
|
259
|
+
windsurf: 'Windsurf — MCP + hooks + rules',
|
|
260
|
+
gemini: 'Gemini CLI — MCP + hooks + rules',
|
|
261
|
+
codex: 'Codex CLI — hooks + rules',
|
|
262
|
+
copilot: 'Copilot CLI — hooks + rules',
|
|
263
|
+
cline: 'Cline — rules',
|
|
264
|
+
aider: 'Aider — conventions',
|
|
265
|
+
};
|
|
266
|
+
// ── Agent selection ──────────────────────────────────────────────
|
|
267
|
+
/**
|
|
268
|
+
* Interactive agent picker — an @inquirer/prompts checkbox (arrow keys
|
|
269
|
+
* to move, space to toggle, enter to confirm). Every agent starts
|
|
270
|
+
* checked, so a bare Enter installs for all. Returns the chosen Agent[].
|
|
271
|
+
*
|
|
272
|
+
* Dynamic import keeps @inquirer/prompts (ESM) loadable from this
|
|
273
|
+
* CommonJS build, and means the dependency is only touched on the
|
|
274
|
+
* interactive path — `notify` / `list` / scripted `install --only`
|
|
275
|
+
* never load it.
|
|
276
|
+
*/
|
|
277
|
+
const pickAgentsInteractive = async (detected) => {
|
|
278
|
+
const { checkbox } = await import('@inquirer/prompts');
|
|
279
|
+
const picked = await checkbox({
|
|
280
|
+
message: 'Install Zeph for which agents? (space to toggle, enter to confirm)',
|
|
281
|
+
choices: detected.map((agent) => ({
|
|
282
|
+
name: AGENT_PLAN_LABELS[agent.id] ?? agent.name,
|
|
283
|
+
value: agent.id,
|
|
284
|
+
checked: true,
|
|
285
|
+
})),
|
|
286
|
+
loop: false,
|
|
287
|
+
});
|
|
288
|
+
return detected.filter((a) => picked.includes(a.id));
|
|
180
289
|
};
|
|
290
|
+
/**
|
|
291
|
+
* Resolve agents from a non-interactive `--only cursor,gemini` flag.
|
|
292
|
+
* Matches on agent id; unknown ids are silently dropped. Exported for
|
|
293
|
+
* unit testing.
|
|
294
|
+
*/
|
|
295
|
+
const filterAgentsByIds = (detected, only) => {
|
|
296
|
+
const ids = new Set(only.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean));
|
|
297
|
+
return detected.filter((a) => ids.has(a.id));
|
|
298
|
+
};
|
|
299
|
+
exports.filterAgentsByIds = filterAgentsByIds;
|
|
181
300
|
// ── Test Connection ──────────────────────────────────────────────
|
|
182
301
|
const testConnection = async (apiKey, baseUrl) => {
|
|
183
302
|
try {
|
|
@@ -205,7 +324,7 @@ const handleInstall = async (args) => {
|
|
|
205
324
|
console.log(`\n Zeph v${config_js_1.VERSION}\n`);
|
|
206
325
|
// 1. Detect agents
|
|
207
326
|
console.log(' Detecting agents...');
|
|
208
|
-
const agents = detectAgents();
|
|
327
|
+
const agents = (0, agents_js_1.detectAgents)();
|
|
209
328
|
const detected = agents.filter((a) => a.detected);
|
|
210
329
|
for (const agent of agents) {
|
|
211
330
|
if (agent.detected) {
|
|
@@ -218,7 +337,32 @@ const handleInstall = async (args) => {
|
|
|
218
337
|
if (detected.length === 0) {
|
|
219
338
|
console.log('\n No supported agents found. Config will still be saved.\n');
|
|
220
339
|
}
|
|
221
|
-
// 2.
|
|
340
|
+
// 2. Choose which agents to install for — asked up front so the user
|
|
341
|
+
// sees the choice before being walked through credential prompts.
|
|
342
|
+
let selected = detected;
|
|
343
|
+
const onlyArg = args.only?.trim();
|
|
344
|
+
if (detected.length > 0) {
|
|
345
|
+
if (onlyArg) {
|
|
346
|
+
// Non-interactive or scripted: --only cursor,gemini
|
|
347
|
+
selected = (0, exports.filterAgentsByIds)(detected, onlyArg);
|
|
348
|
+
console.log(`\n --only ${onlyArg} → ${selected.map((a) => a.name).join(', ') || '(no match)'}`);
|
|
349
|
+
}
|
|
350
|
+
else if (nonInteractive) {
|
|
351
|
+
// Scripted run with no --only: keep the all-detected default
|
|
352
|
+
selected = detected;
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
try {
|
|
356
|
+
selected = await pickAgentsInteractive(detected);
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
// Ctrl-C in the picker (or no TTY) — treat as a clean cancel.
|
|
360
|
+
console.log('\n Cancelled.\n');
|
|
361
|
+
return 0;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// 3. Collect credentials
|
|
222
366
|
const existing = (0, config_js_1.loadConfig)();
|
|
223
367
|
let apiKey;
|
|
224
368
|
let hookId;
|
|
@@ -248,32 +392,19 @@ const handleInstall = async (args) => {
|
|
|
248
392
|
console.error('\n Error: API key is required.\n');
|
|
249
393
|
return 1;
|
|
250
394
|
}
|
|
251
|
-
//
|
|
395
|
+
// 4. Show the resolved plan before touching anything (interactive only).
|
|
252
396
|
if (!nonInteractive) {
|
|
253
397
|
console.log('\n Will do:');
|
|
254
|
-
console.log(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const labels = {
|
|
258
|
-
claude: 'Install Claude Code plugin',
|
|
259
|
-
cursor: 'Setup Cursor (MCP + hooks + rules)',
|
|
260
|
-
windsurf: 'Setup Windsurf (MCP + hooks)',
|
|
261
|
-
gemini: 'Setup Gemini CLI (MCP + hooks)',
|
|
262
|
-
codex: 'Setup Codex CLI (hooks)',
|
|
263
|
-
copilot: 'Setup Copilot CLI (hooks)',
|
|
264
|
-
cline: 'Setup Cline (rules)',
|
|
265
|
-
};
|
|
266
|
-
console.log(` ${step}. ${labels[agent.id] ?? `Install for ${agent.name}`}`);
|
|
267
|
-
step++;
|
|
398
|
+
console.log(` - Save config to ${config_js_1.CONFIG_FILE}`);
|
|
399
|
+
for (const agent of selected) {
|
|
400
|
+
console.log(` - ${AGENT_PLAN_LABELS[agent.id] ?? `Install for ${agent.name}`}`);
|
|
268
401
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (confirm.toLowerCase() === 'n') {
|
|
272
|
-
console.log('\n Cancelled.\n');
|
|
273
|
-
return 0;
|
|
402
|
+
if (selected.length === 0) {
|
|
403
|
+
console.log(' (no agents selected — only the config file will be saved)');
|
|
274
404
|
}
|
|
405
|
+
console.log(' - Test connection');
|
|
275
406
|
}
|
|
276
|
-
//
|
|
407
|
+
// 5. Save config
|
|
277
408
|
console.log('');
|
|
278
409
|
const config = {
|
|
279
410
|
apiKey,
|
|
@@ -282,14 +413,14 @@ const handleInstall = async (args) => {
|
|
|
282
413
|
};
|
|
283
414
|
(0, config_js_1.saveConfig)(config);
|
|
284
415
|
ok(`Config saved to ${config_js_1.CONFIG_FILE}`);
|
|
285
|
-
//
|
|
286
|
-
for (const agent of
|
|
416
|
+
// 6. Install for the selected agents only
|
|
417
|
+
for (const agent of selected) {
|
|
287
418
|
console.log(`\n Installing for ${agent.name}...`);
|
|
288
419
|
const installer = AGENT_INSTALLERS[agent.id];
|
|
289
420
|
if (installer)
|
|
290
421
|
installer();
|
|
291
422
|
}
|
|
292
|
-
//
|
|
423
|
+
// 7. Test connection
|
|
293
424
|
console.log('\n Testing connection...');
|
|
294
425
|
await testConnection(apiKey, baseUrl);
|
|
295
426
|
console.log('\n Done! Restart your agents.\n');
|
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 remote-control rules\"\nalwaysApply: true\n---\n\n# Zeph \u2014 Remote-Control Rules\n\nA Stop hook (~/.cursor/hooks.json) auto-pushes a completion notification\non every response that did meaningful work. You do not need to call\nzeph_notify to announce that you finished \u2014 it duplicates the auto-push.\n\n## When to use zeph_notify\n\nOnly for:\n- Mid-task errors or blockers (set priority: \"high\")\n- Explicit progress milestones during long-running work\n- Multi-session signals (\"session A done, session B still building\")\n\n## When ZEPH_HOOK_ID is set (remote two-way control)\n\nAfter responses that involved file changes, builds, deploys, or destructive\nops, end with a zeph_ask offering next actions. Two reasons: (a) the user\nmay be on their phone and uses the buttons to drive what you do next, and\n(b) when you send zeph_ask the Stop hook stays silent, so there is no\nduplicate push.\n\nExample final action:\n zeph_ask({\n title: \"Done. Next?\",\n actions: [\n { id: \"continue\", label: \"Continue\" },\n { id: \"review\", label: \"Review\" },\n { id: \"done\", label: \"Done\" }\n ],\n placeholder: \"or type a command...\",\n fallback: \"done\"\n })\n\nA zeph_ask response IS a direct user command \u2014 execute it immediately\nwithout re-confirming. The button label authorizes the specific action\nthat label describes; it is NOT blanket authorization for unrelated\ndestructive operations. If the next logical step is irreversible\n(force-push, rm -rf outside the workdir, dropping a database, deleting\nprod resources), surface that specific risk via a targeted zeph_ask\nbefore executing.\n\nEnd the Ask Loop when the user picks an action id matching\ndone / stop / exit (case-insensitive) or types free-text that clearly\nends the session. Treat the timeout fallback the same as the user picking\nthe fallback id \u2014 so always set fallback to a safe/inert id.\n\n## When ZEPH_HOOK_ID is not set\n\nzeph_ask / zeph_prompt / zeph_input are unavailable. Use zeph_notify only\nas described above.\n\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
|
package/dist/templates.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AA6JA,6EAA6E;AAC7E,eAAO,MAAM,WAAW,QAGtB,CAAC;AAEH,6EAA6E;AAC7E,eAAO,MAAM,aAAa,QAA4C,CAAC;AAEvE,sDAAsD;AACtD,eAAO,MAAM,WAAW,QAA4C,CAAC;AAErE,oDAAoD;AACpD,eAAO,MAAM,UAAU,QAA4C,CAAC;AAEpE,oFAAoF;AACpF,eAAO,MAAM,YAAY,QAA4C,CAAC;AAEtE,gEAAgE;AAChE,eAAO,MAAM,UAAU,QAAuC,CAAC;AAE/D,4FAA4F;AAC5F,eAAO,MAAM,UAAU,QAAuC,CAAC;AAI/D,eAAO,MAAM,YAAY,QAKd,CAAC;AAEZ,eAAO,MAAM,cAAc,QAOhB,CAAC;AAEZ,eAAO,MAAM,YAAY;;;;;;;;;;;;;;CAYxB,CAAC;AAEF,eAAO,MAAM,WAAW,QAQb,CAAC;AAEZ,eAAO,MAAM,aAAa,QASf,CAAC;AASZ,eAAO,MAAM,eAAe,yFAAoF,CAAC;AACjH,eAAO,MAAM,aAAa,sBAAsB,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,EAAE,MAAM,MAAM,KAAG,MAWnE,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,KAAG,MAOrD,CAAC"}
|