@zeph-to/hook-sdk 1.7.1 → 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 +4 -1
- 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 +20 -1
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +98 -24
- 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 +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`) |
|
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)
|
|
@@ -92,6 +98,13 @@ Install options:
|
|
|
92
98
|
--hook <hook-id> Hook ID (non-interactive)
|
|
93
99
|
--base-url <url> Base URL (non-interactive)
|
|
94
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
|
+
|
|
95
108
|
Global options:
|
|
96
109
|
--key <api-key> API key (or set ZEPH_API_KEY env)
|
|
97
110
|
--base-url <url> API base URL (or set ZEPH_BASE_URL env)
|
|
@@ -294,6 +307,12 @@ const main = async () => {
|
|
|
294
307
|
case 'install':
|
|
295
308
|
case 'setup':
|
|
296
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);
|
|
297
316
|
case 'notify':
|
|
298
317
|
return handleNotify(args);
|
|
299
318
|
case 'list':
|
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
|
@@ -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,6 +249,7 @@ const AGENT_INSTALLERS = {
|
|
|
177
249
|
codex: installCodex,
|
|
178
250
|
copilot: installCopilot,
|
|
179
251
|
cline: installCline,
|
|
252
|
+
aider: installAider,
|
|
180
253
|
};
|
|
181
254
|
// ── Test Connection ──────────────────────────────────────────────
|
|
182
255
|
const testConnection = async (apiKey, baseUrl) => {
|
|
@@ -205,7 +278,7 @@ const handleInstall = async (args) => {
|
|
|
205
278
|
console.log(`\n Zeph v${config_js_1.VERSION}\n`);
|
|
206
279
|
// 1. Detect agents
|
|
207
280
|
console.log(' Detecting agents...');
|
|
208
|
-
const agents = detectAgents();
|
|
281
|
+
const agents = (0, agents_js_1.detectAgents)();
|
|
209
282
|
const detected = agents.filter((a) => a.detected);
|
|
210
283
|
for (const agent of agents) {
|
|
211
284
|
if (agent.detected) {
|
|
@@ -257,11 +330,12 @@ const handleInstall = async (args) => {
|
|
|
257
330
|
const labels = {
|
|
258
331
|
claude: 'Install Claude Code plugin',
|
|
259
332
|
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)',
|
|
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)',
|
|
264
337
|
cline: 'Setup Cline (rules)',
|
|
338
|
+
aider: 'Setup Aider (conventions)',
|
|
265
339
|
};
|
|
266
340
|
console.log(` ${step}. ${labels[agent.id] ?? `Install for ${agent.name}`}`);
|
|
267
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 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"}
|
package/dist/templates.js
CHANGED
|
@@ -1,62 +1,68 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// ── Hook & Rule templates for each agent ─────────────────────────
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
4
|
+
// Every supported agent gets the SAME behavioral rules so Zeph behaves
|
|
5
|
+
// identically everywhere. The rule text is assembled from one shared
|
|
6
|
+
// core (ZEPH_CORE) plus a per-agent notification preamble:
|
|
6
7
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
8
|
+
// - Hook-driven agents (Cursor, Windsurf, Gemini, Codex, Copilot) have
|
|
9
|
+
// a Stop-equivalent hook installed that auto-pushes on completion, so
|
|
10
|
+
// they must NOT manually call zeph_notify for "done".
|
|
11
|
+
// - Rule-only agents (Cline, Aider) have no Stop hook, so they DO call
|
|
12
|
+
// zeph_notify for meaningful completions.
|
|
12
13
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
14
|
+
// The Ask-Loop / sticky-REMOTE / question-mandate rules are identical for
|
|
15
|
+
// all of them — that is the whole point of the shared ZEPH_CORE.
|
|
16
|
+
//
|
|
17
|
+
// Keeping this in one place means a rule change lands everywhere at once
|
|
18
|
+
// and the agents can't drift apart.
|
|
16
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.
|
|
20
|
+
exports.removeManagedBlock = exports.upsertManagedBlock = exports.ZEPH_MARK_END = exports.ZEPH_MARK_START = exports.COPILOT_HOOKS = exports.CODEX_HOOKS = exports.GEMINI_HOOKS = exports.WINDSURF_HOOKS = exports.CURSOR_HOOKS = exports.AIDER_RULE = exports.CLINE_RULE = exports.COPILOT_RULE = exports.CODEX_RULE = exports.GEMINI_RULE = exports.WINDSURF_RULE = exports.CURSOR_RULE = void 0;
|
|
18
21
|
// Graceful resolution: prefer the installed `zeph` CLI, but fall back to
|
|
19
22
|
// `npx -y @zeph-to/hook-sdk` so the hook still fires when the user
|
|
20
23
|
// installed via a non-standard prefix and the binary isn't on PATH at hook
|
|
21
24
|
// fire time (e.g. ~/.local/bin without PATH update). This mirrors the
|
|
22
25
|
// pattern in plugin/hooks/zeph-{stop,ask}.sh.
|
|
23
26
|
const NOTIFY_CMD = '$(command -v zeph || echo "npx -y @zeph-to/hook-sdk") notify --title "Task done" 2>/dev/null || true';
|
|
24
|
-
// ──
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
27
|
+
// ── Shared behavioral core ───────────────────────────────────────
|
|
28
|
+
//
|
|
29
|
+
// Identical across every agent. Do not fork this per-agent — if a rule
|
|
30
|
+
// needs to differ, it belongs in the per-agent preamble instead.
|
|
31
|
+
const ZEPH_CORE = `## NEVER end a response with a plain-text question
|
|
32
|
+
|
|
33
|
+
When ZEPH_HOOK_ID is set the user is driving you remotely, often from
|
|
34
|
+
their phone. A "?" written in your reply text is invisible to them. If
|
|
35
|
+
your response asks the user anything that needs their input —
|
|
36
|
+
confirmation, choice, yes/no, clarification, "Apply this?", "Proceed?",
|
|
37
|
+
"Which option?" — the FINAL tool call MUST be zeph_ask. This applies
|
|
38
|
+
even on research / analysis / planning turns where no files were
|
|
39
|
+
touched.
|
|
40
|
+
|
|
41
|
+
Anti-pattern (wrong):
|
|
42
|
+
"...build/commit on hold. Apply A?"
|
|
43
|
+
Correct:
|
|
44
|
+
zeph_ask({
|
|
45
|
+
title: "Apply solution A?",
|
|
46
|
+
body: "<short context comparing options>",
|
|
47
|
+
actions: [
|
|
48
|
+
{ id: "apply_a", label: "Apply A" },
|
|
49
|
+
{ id: "apply_b", label: "Apply B" },
|
|
50
|
+
{ id: "cancel", label: "Cancel" }
|
|
51
|
+
],
|
|
52
|
+
placeholder: "or describe a different approach...",
|
|
53
|
+
fallback: "cancel"
|
|
54
|
+
})
|
|
50
55
|
|
|
51
|
-
##
|
|
56
|
+
## zeph_ask is the DEFAULT after substantial work
|
|
52
57
|
|
|
53
|
-
After
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
After file changes, commits, builds, tests, deploys, destructive ops,
|
|
59
|
+
or milestone completions, end with zeph_ask. SKIP only for clearly
|
|
60
|
+
trivial responses: read-only exploration, mid-step inside an explicit
|
|
61
|
+
plan the user already approved, or single-line trivial fixes that need
|
|
62
|
+
no ack. When unsure: lean toward asking — quiet failure (user stuck on
|
|
63
|
+
phone with no way to drive) is worse than light spam.
|
|
58
64
|
|
|
59
|
-
Example
|
|
65
|
+
Example shape:
|
|
60
66
|
zeph_ask({
|
|
61
67
|
title: "Done. Next?",
|
|
62
68
|
actions: [
|
|
@@ -68,6 +74,8 @@ Example final action:
|
|
|
68
74
|
fallback: "done"
|
|
69
75
|
})
|
|
70
76
|
|
|
77
|
+
## Handling the response
|
|
78
|
+
|
|
71
79
|
A zeph_ask response IS a direct user command — execute it immediately
|
|
72
80
|
without re-confirming. The button label authorizes the specific action
|
|
73
81
|
that label describes; it is NOT blanket authorization for unrelated
|
|
@@ -76,19 +84,95 @@ destructive operations. If the next logical step is irreversible
|
|
|
76
84
|
prod resources), surface that specific risk via a targeted zeph_ask
|
|
77
85
|
before executing.
|
|
78
86
|
|
|
79
|
-
|
|
80
|
-
done / stop / exit (case-insensitive) or types free-text that clearly
|
|
81
|
-
ends the session. Treat the timeout fallback the same as the user picking
|
|
82
|
-
the fallback id — so always set fallback to a safe/inert id.
|
|
87
|
+
## Sticky REMOTE mode
|
|
83
88
|
|
|
84
|
-
|
|
89
|
+
The Ask Loop has two states — REMOTE and NORMAL — detected by scanning
|
|
90
|
+
the conversation in reverse for whichever appears first:
|
|
85
91
|
|
|
86
|
-
zeph_ask
|
|
87
|
-
|
|
92
|
+
(a) A tool_result for a zeph_ask you sent where the action id is NOT
|
|
93
|
+
in {done, stop, exit} and the response is NOT a clear
|
|
94
|
+
session-ender ("thanks, that's it"). -> REMOTE.
|
|
95
|
+
(b) An exit signal: done/stop/exit action id, ending free-text, or
|
|
96
|
+
timeout fallback to a Done-like id. -> NORMAL.
|
|
97
|
+
(c) No zeph_ask history at all. -> NORMAL.
|
|
88
98
|
|
|
89
|
-
|
|
99
|
+
While in REMOTE: end EVERY response with zeph_ask — non-negotiable.
|
|
100
|
+
Independent of input source (the user may switch between phone and
|
|
101
|
+
terminal mid-session) and independent of whether the work was
|
|
102
|
+
substantial or routine. Sticky REMOTE keeps the channel driveable from
|
|
103
|
+
either side.
|
|
104
|
+
|
|
105
|
+
While in NORMAL: apply the substantial-vs-routine logic from the
|
|
106
|
+
'DEFAULT after substantial work' section above.
|
|
107
|
+
|
|
108
|
+
Exit: the moment the user picks done / stop / exit (case-insensitive),
|
|
109
|
+
types ending free-text, or the zeph_ask times out to a Done-like
|
|
110
|
+
fallback, flip to NORMAL. Don't send zeph_ask on the response that
|
|
111
|
+
processes the exit. Always set fallback to a safe/inert id.
|
|
112
|
+
|
|
113
|
+
## When ZEPH_HOOK_ID is not set
|
|
114
|
+
|
|
115
|
+
zeph_ask / zeph_prompt / zeph_input are unavailable — only zeph_notify
|
|
116
|
+
works. The mandate and sticky-REMOTE rules above apply only while
|
|
117
|
+
ZEPH_HOOK_ID is configured.`;
|
|
118
|
+
// Notification preamble — hook-driven agents (a Stop-equivalent hook is
|
|
119
|
+
// installed, so manual completion notifications would duplicate).
|
|
120
|
+
const HOOK_DRIVEN_NOTIFY = `## Notification discipline
|
|
121
|
+
|
|
122
|
+
A Stop-equivalent hook is installed that auto-pushes a completion
|
|
123
|
+
notification on every response with meaningful work. Do NOT call
|
|
124
|
+
zeph_notify just to announce completion — it duplicates the auto-push.
|
|
125
|
+
Use zeph_notify only for mid-task errors/blockers (priority: "high"),
|
|
126
|
+
explicit progress milestones during long-running work, or multi-session
|
|
127
|
+
signals ("session A done, session B still building").`;
|
|
128
|
+
// Notification preamble — rule-only agents (no Stop hook; the AI is the
|
|
129
|
+
// only source of completion notifications).
|
|
130
|
+
const MANUAL_NOTIFY = `## Notification discipline
|
|
131
|
+
|
|
132
|
+
This agent has no Stop hook, so completion notifications must come from
|
|
133
|
+
you. After meaningful task completion (build, test, deploy, large
|
|
134
|
+
refactor, multi-file changes) call zeph_notify. Skip it for trivial
|
|
135
|
+
operations (file reads, simple searches). Set priority "high" for
|
|
136
|
+
errors/blockers.`;
|
|
137
|
+
/** Assemble a full rule document from optional frontmatter + preamble + core. */
|
|
138
|
+
const buildRule = (opts) => {
|
|
139
|
+
const fm = opts.frontmatter ? `${opts.frontmatter}\n\n` : '';
|
|
140
|
+
return `${fm}# Zeph — Remote-Control Rules
|
|
141
|
+
|
|
142
|
+
Zeph lets the user steer this session from their phone via zeph_ask
|
|
143
|
+
buttons. Use it judiciously — too many asks is noisy, too few strands
|
|
144
|
+
the user.
|
|
145
|
+
|
|
146
|
+
${opts.notify}
|
|
147
|
+
|
|
148
|
+
${ZEPH_CORE}
|
|
90
149
|
`;
|
|
91
|
-
|
|
150
|
+
};
|
|
151
|
+
// ── Per-agent rule documents ─────────────────────────────────────
|
|
152
|
+
/** Cursor — written to ~/.cursor/rules/zeph.mdc (needs .mdc frontmatter). */
|
|
153
|
+
exports.CURSOR_RULE = buildRule({
|
|
154
|
+
frontmatter: '---\ndescription: "Zeph remote-control rules"\nalwaysApply: true\n---',
|
|
155
|
+
notify: HOOK_DRIVEN_NOTIFY,
|
|
156
|
+
});
|
|
157
|
+
/** Windsurf — appended into ~/.codeium/windsurf/memories/global_rules.md. */
|
|
158
|
+
exports.WINDSURF_RULE = buildRule({ notify: HOOK_DRIVEN_NOTIFY });
|
|
159
|
+
/** Gemini CLI — appended into ~/.gemini/GEMINI.md. */
|
|
160
|
+
exports.GEMINI_RULE = buildRule({ notify: HOOK_DRIVEN_NOTIFY });
|
|
161
|
+
/** Codex CLI — appended into ~/.codex/AGENTS.md. */
|
|
162
|
+
exports.CODEX_RULE = buildRule({ notify: HOOK_DRIVEN_NOTIFY });
|
|
163
|
+
/** GitHub Copilot CLI — written to ~/.copilot/instructions/zeph.instructions.md. */
|
|
164
|
+
exports.COPILOT_RULE = buildRule({ notify: HOOK_DRIVEN_NOTIFY });
|
|
165
|
+
/** Cline — written to ~/.cline/rules/zeph.md (no Stop hook). */
|
|
166
|
+
exports.CLINE_RULE = buildRule({ notify: MANUAL_NOTIFY });
|
|
167
|
+
/** Aider — written to a standalone conventions file, loaded via .aider.conf.yml `read:`. */
|
|
168
|
+
exports.AIDER_RULE = buildRule({ notify: MANUAL_NOTIFY });
|
|
169
|
+
// ── Hook configs (notification side, unchanged) ──────────────────
|
|
170
|
+
exports.CURSOR_HOOKS = JSON.stringify({
|
|
171
|
+
version: 1,
|
|
172
|
+
hooks: {
|
|
173
|
+
stop: [{ command: NOTIFY_CMD }],
|
|
174
|
+
},
|
|
175
|
+
}, null, 2);
|
|
92
176
|
exports.WINDSURF_HOOKS = JSON.stringify({
|
|
93
177
|
hooks: {
|
|
94
178
|
post_cascade_response: [{
|
|
@@ -97,7 +181,6 @@ exports.WINDSURF_HOOKS = JSON.stringify({
|
|
|
97
181
|
}],
|
|
98
182
|
},
|
|
99
183
|
}, null, 2);
|
|
100
|
-
// ── Gemini ───────────────────────────────────────────────────────
|
|
101
184
|
exports.GEMINI_HOOKS = {
|
|
102
185
|
hooks: {
|
|
103
186
|
AfterAgent: [{
|
|
@@ -111,7 +194,6 @@ exports.GEMINI_HOOKS = {
|
|
|
111
194
|
},
|
|
112
195
|
hooksConfig: { enabled: true },
|
|
113
196
|
};
|
|
114
|
-
// ── Codex ────────────────────────────────────────────────────────
|
|
115
197
|
exports.CODEX_HOOKS = JSON.stringify({
|
|
116
198
|
version: 1,
|
|
117
199
|
hooks: {
|
|
@@ -121,7 +203,6 @@ exports.CODEX_HOOKS = JSON.stringify({
|
|
|
121
203
|
}],
|
|
122
204
|
},
|
|
123
205
|
}, null, 2);
|
|
124
|
-
// ── Copilot ──────────────────────────────────────────────────────
|
|
125
206
|
exports.COPILOT_HOOKS = JSON.stringify({
|
|
126
207
|
version: 1,
|
|
127
208
|
hooks: {
|
|
@@ -132,48 +213,40 @@ exports.COPILOT_HOOKS = JSON.stringify({
|
|
|
132
213
|
}],
|
|
133
214
|
},
|
|
134
215
|
}, null, 2);
|
|
135
|
-
// ──
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
prod resources), surface that specific risk via a targeted zeph_ask
|
|
173
|
-
before executing.
|
|
174
|
-
|
|
175
|
-
End the Ask Loop when the user picks an action id matching
|
|
176
|
-
done / stop / exit (case-insensitive). Treat the timeout fallback the
|
|
177
|
-
same as the user picking the fallback id — so always set fallback to a
|
|
178
|
-
safe/inert id.
|
|
179
|
-
`;
|
|
216
|
+
// ── Marker-section helpers for shared global rule files ──────────
|
|
217
|
+
//
|
|
218
|
+
// Windsurf / Gemini / Codex all use a single shared global rule file
|
|
219
|
+
// that the user may already own. We never overwrite it — we manage just
|
|
220
|
+
// our own block, delimited by these markers, so install/uninstall is
|
|
221
|
+
// idempotent and the user's content is preserved.
|
|
222
|
+
exports.ZEPH_MARK_START = '<!-- ZEPH:START — managed by @zeph-to/hook-sdk, do not edit between markers -->';
|
|
223
|
+
exports.ZEPH_MARK_END = '<!-- ZEPH:END -->';
|
|
224
|
+
/**
|
|
225
|
+
* Return `existing` with the Zeph-managed block inserted or replaced.
|
|
226
|
+
* If the markers are already present, the content between them is
|
|
227
|
+
* swapped; otherwise the block is appended.
|
|
228
|
+
*/
|
|
229
|
+
const upsertManagedBlock = (existing, rule) => {
|
|
230
|
+
const block = `${exports.ZEPH_MARK_START}\n${rule}\n${exports.ZEPH_MARK_END}`;
|
|
231
|
+
const startIdx = existing.indexOf(exports.ZEPH_MARK_START);
|
|
232
|
+
const endIdx = existing.indexOf(exports.ZEPH_MARK_END);
|
|
233
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
234
|
+
const before = existing.slice(0, startIdx).replace(/\n*$/, '');
|
|
235
|
+
const after = existing.slice(endIdx + exports.ZEPH_MARK_END.length).replace(/^\n*/, '');
|
|
236
|
+
return [before, block, after].filter(Boolean).join('\n\n') + '\n';
|
|
237
|
+
}
|
|
238
|
+
const base = existing.replace(/\n*$/, '');
|
|
239
|
+
return (base ? `${base}\n\n` : '') + block + '\n';
|
|
240
|
+
};
|
|
241
|
+
exports.upsertManagedBlock = upsertManagedBlock;
|
|
242
|
+
/** Strip the Zeph-managed block from a shared file (for uninstall). */
|
|
243
|
+
const removeManagedBlock = (existing) => {
|
|
244
|
+
const startIdx = existing.indexOf(exports.ZEPH_MARK_START);
|
|
245
|
+
const endIdx = existing.indexOf(exports.ZEPH_MARK_END);
|
|
246
|
+
if (startIdx === -1 || endIdx === -1 || endIdx < startIdx)
|
|
247
|
+
return existing;
|
|
248
|
+
const before = existing.slice(0, startIdx).replace(/\n*$/, '');
|
|
249
|
+
const after = existing.slice(endIdx + exports.ZEPH_MARK_END.length).replace(/^\n*/, '');
|
|
250
|
+
return [before, after].filter(Boolean).join('\n\n') + (before || after ? '\n' : '');
|
|
251
|
+
};
|
|
252
|
+
exports.removeManagedBlock = removeManagedBlock;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uninstall.d.ts","sourceRoot":"","sources":["../src/uninstall.ts"],"names":[],"mappings":"AA2KA,eAAO,MAAM,eAAe,GAAU,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAG,OAAO,CAAC,MAAM,CA8B5F,CAAC"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleUninstall = 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 agents_js_1 = require("./agents.js");
|
|
9
|
+
const templates_js_1 = require("./templates.js");
|
|
10
|
+
const config_js_1 = require("./config.js");
|
|
11
|
+
const HOME = (0, os_1.homedir)();
|
|
12
|
+
const ok = (msg) => console.log(` + ${msg}`);
|
|
13
|
+
const skip = (msg) => console.log(` - ${msg}`);
|
|
14
|
+
// ── Removal primitives ───────────────────────────────────────────
|
|
15
|
+
// Each primitive returns a short human description of what it did (or
|
|
16
|
+
// would do, in dry-run), or null when there was nothing to remove.
|
|
17
|
+
/** Past/conditional verb so dry-run output reads honestly. */
|
|
18
|
+
const verb = (dry) => (dry ? 'would remove' : 'removed');
|
|
19
|
+
/** Delete a file Zeph fully owns. */
|
|
20
|
+
const rmFile = (filePath, dry) => {
|
|
21
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
22
|
+
return null;
|
|
23
|
+
if (!dry)
|
|
24
|
+
(0, fs_1.rmSync)(filePath, { force: true });
|
|
25
|
+
return `${verb(dry)} ${filePath}`;
|
|
26
|
+
};
|
|
27
|
+
/** Remove just the `zeph` entry from an mcpServers JSON file. */
|
|
28
|
+
const rmMcpEntry = (filePath, dry) => {
|
|
29
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
30
|
+
return null;
|
|
31
|
+
let data;
|
|
32
|
+
try {
|
|
33
|
+
data = JSON.parse((0, fs_1.readFileSync)(filePath, 'utf-8'));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const servers = data.mcpServers;
|
|
39
|
+
if (!servers || !('zeph' in servers))
|
|
40
|
+
return null;
|
|
41
|
+
if (!dry) {
|
|
42
|
+
delete servers.zeph;
|
|
43
|
+
(0, fs_1.writeFileSync)(filePath, JSON.stringify(data, null, 2) + '\n');
|
|
44
|
+
}
|
|
45
|
+
return `${verb(dry)} zeph from ${filePath}`;
|
|
46
|
+
};
|
|
47
|
+
/** Strip the <!-- ZEPH:START/END --> block from a shared rule file. */
|
|
48
|
+
const stripManagedRule = (filePath, dry) => {
|
|
49
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
50
|
+
return null;
|
|
51
|
+
const existing = (0, fs_1.readFileSync)(filePath, 'utf-8');
|
|
52
|
+
const stripped = (0, templates_js_1.removeManagedBlock)(existing);
|
|
53
|
+
if (stripped === existing)
|
|
54
|
+
return null; // no Zeph block present
|
|
55
|
+
if (!dry) {
|
|
56
|
+
if (stripped.trim() === '') {
|
|
57
|
+
(0, fs_1.rmSync)(filePath, { force: true }); // file was ours alone
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
(0, fs_1.writeFileSync)(filePath, stripped);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return `${verb(dry)} Zeph block from ${filePath}`;
|
|
64
|
+
};
|
|
65
|
+
/** Drop the Zeph `read:` directive from ~/.aider.conf.yml. */
|
|
66
|
+
const rmAiderReadDirective = (confPath, dry) => {
|
|
67
|
+
if (!(0, fs_1.existsSync)(confPath))
|
|
68
|
+
return null;
|
|
69
|
+
const conf = (0, fs_1.readFileSync)(confPath, 'utf-8');
|
|
70
|
+
if (!conf.includes('# Added by Zeph'))
|
|
71
|
+
return null;
|
|
72
|
+
// Drop the "# Added by Zeph" line and the "read:" line that follows it.
|
|
73
|
+
const lines = conf.split('\n');
|
|
74
|
+
const out = [];
|
|
75
|
+
for (let i = 0; i < lines.length; i++) {
|
|
76
|
+
if (lines[i].trim() === '# Added by Zeph') {
|
|
77
|
+
if (lines[i + 1]?.trimStart().startsWith('read:'))
|
|
78
|
+
i++; // skip read: too
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
out.push(lines[i]);
|
|
82
|
+
}
|
|
83
|
+
if (!dry)
|
|
84
|
+
(0, fs_1.writeFileSync)(confPath, out.join('\n').replace(/\n{3,}/g, '\n\n'));
|
|
85
|
+
return `${verb(dry)} Zeph read: directive from ${confPath}`;
|
|
86
|
+
};
|
|
87
|
+
/** Remove just the zeph-notify entry from Gemini's settings.json. */
|
|
88
|
+
const rmGeminiHook = (filePath, dry) => {
|
|
89
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
90
|
+
return null;
|
|
91
|
+
let data;
|
|
92
|
+
try {
|
|
93
|
+
data = JSON.parse((0, fs_1.readFileSync)(filePath, 'utf-8'));
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const hooks = data.hooks;
|
|
99
|
+
const afterAgent = hooks?.AfterAgent;
|
|
100
|
+
if (!Array.isArray(afterAgent))
|
|
101
|
+
return null;
|
|
102
|
+
const kept = afterAgent.filter((entry) => !(entry.hooks ?? []).some((h) => h.name === 'zeph-notify'));
|
|
103
|
+
if (kept.length === afterAgent.length)
|
|
104
|
+
return null; // nothing of ours
|
|
105
|
+
if (!dry) {
|
|
106
|
+
if (kept.length === 0) {
|
|
107
|
+
delete hooks.AfterAgent;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
hooks.AfterAgent = kept;
|
|
111
|
+
}
|
|
112
|
+
(0, fs_1.writeFileSync)(filePath, JSON.stringify(data, null, 2) + '\n');
|
|
113
|
+
}
|
|
114
|
+
return `${verb(dry)} zeph-notify hook from ${filePath}`;
|
|
115
|
+
};
|
|
116
|
+
const runSteps = (steps) => {
|
|
117
|
+
let did = false;
|
|
118
|
+
for (const step of steps) {
|
|
119
|
+
const result = step();
|
|
120
|
+
if (result) {
|
|
121
|
+
ok(result);
|
|
122
|
+
did = true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!did)
|
|
126
|
+
skip('nothing to remove');
|
|
127
|
+
};
|
|
128
|
+
const AGENT_UNINSTALLERS = {
|
|
129
|
+
claude: (dry) => {
|
|
130
|
+
if (dry) {
|
|
131
|
+
skip('would run: claude plugin uninstall zeph@zeph');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
(0, child_process_1.execSync)('claude plugin uninstall zeph@zeph', { stdio: 'pipe' });
|
|
136
|
+
ok('plugin uninstalled');
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
skip('plugin not installed (or claude CLI unavailable)');
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
cursor: (dry) => runSteps([
|
|
143
|
+
() => rmMcpEntry((0, path_1.join)(HOME, '.cursor', 'mcp.json'), dry),
|
|
144
|
+
() => rmFile((0, path_1.join)(HOME, '.cursor', 'hooks.json'), dry),
|
|
145
|
+
() => rmFile((0, path_1.join)(HOME, '.cursor', 'rules', 'zeph.mdc'), dry),
|
|
146
|
+
]),
|
|
147
|
+
windsurf: (dry) => runSteps([
|
|
148
|
+
() => rmMcpEntry((0, path_1.join)(HOME, '.codeium', 'windsurf', 'mcp_config.json'), dry),
|
|
149
|
+
() => rmFile((0, path_1.join)(HOME, '.codeium', 'windsurf', 'hooks.json'), dry),
|
|
150
|
+
() => stripManagedRule((0, path_1.join)(HOME, '.codeium', 'windsurf', 'memories', 'global_rules.md'), dry),
|
|
151
|
+
]),
|
|
152
|
+
gemini: (dry) => {
|
|
153
|
+
if (!dry) {
|
|
154
|
+
try {
|
|
155
|
+
(0, child_process_1.execSync)('gemini mcp remove zeph', { stdio: 'pipe' });
|
|
156
|
+
ok('MCP server removed');
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
skip('gemini MCP entry not found');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
skip('would run: gemini mcp remove zeph');
|
|
164
|
+
}
|
|
165
|
+
runSteps([
|
|
166
|
+
() => rmGeminiHook((0, path_1.join)(HOME, '.gemini', 'settings.json'), dry),
|
|
167
|
+
() => stripManagedRule((0, path_1.join)(HOME, '.gemini', 'GEMINI.md'), dry),
|
|
168
|
+
]);
|
|
169
|
+
},
|
|
170
|
+
codex: (dry) => runSteps([
|
|
171
|
+
() => rmFile((0, path_1.join)(HOME, '.codex', 'hooks.json'), dry),
|
|
172
|
+
() => stripManagedRule((0, path_1.join)(HOME, '.codex', 'AGENTS.md'), dry),
|
|
173
|
+
]),
|
|
174
|
+
copilot: (dry) => runSteps([
|
|
175
|
+
() => rmFile((0, path_1.join)(HOME, '.copilot', 'hooks', 'zeph.json'), dry),
|
|
176
|
+
() => rmFile((0, path_1.join)(HOME, '.copilot', 'instructions', 'zeph.instructions.md'), dry),
|
|
177
|
+
]),
|
|
178
|
+
cline: (dry) => runSteps([
|
|
179
|
+
() => rmFile((0, path_1.join)(HOME, '.cline', 'rules', 'zeph.md'), dry),
|
|
180
|
+
]),
|
|
181
|
+
aider: (dry) => runSteps([
|
|
182
|
+
() => rmFile((0, path_1.join)(HOME, '.zeph', 'aider-conventions.md'), dry),
|
|
183
|
+
() => rmAiderReadDirective((0, path_1.join)(HOME, '.aider.conf.yml'), dry),
|
|
184
|
+
]),
|
|
185
|
+
};
|
|
186
|
+
// ── Entry point ──────────────────────────────────────────────────
|
|
187
|
+
const handleUninstall = async (args) => {
|
|
188
|
+
const dry = args['dry-run'] === true;
|
|
189
|
+
const purge = args.purge === true;
|
|
190
|
+
console.log(`\n Zeph uninstall${dry ? ' (dry-run)' : ''} — v${config_js_1.VERSION}\n`);
|
|
191
|
+
const detected = (0, agents_js_1.detectAgents)().filter((a) => a.detected);
|
|
192
|
+
if (detected.length === 0) {
|
|
193
|
+
console.log(' No supported agents detected.\n');
|
|
194
|
+
}
|
|
195
|
+
for (const agent of detected) {
|
|
196
|
+
console.log(` ${agent.name}:`);
|
|
197
|
+
AGENT_UNINSTALLERS[agent.id]?.(dry);
|
|
198
|
+
}
|
|
199
|
+
// ~/.zeph/config.json holds the API key — kept by default so a
|
|
200
|
+
// re-install doesn't need the key re-entered. --purge removes it.
|
|
201
|
+
console.log('\n Config:');
|
|
202
|
+
if (purge) {
|
|
203
|
+
const removed = rmFile(config_js_1.CONFIG_FILE, dry);
|
|
204
|
+
if (removed)
|
|
205
|
+
ok(removed);
|
|
206
|
+
else
|
|
207
|
+
skip('no config file');
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
skip(`kept ${config_js_1.CONFIG_FILE} (pass --purge to remove)`);
|
|
211
|
+
}
|
|
212
|
+
console.log(dry
|
|
213
|
+
? '\n Dry-run complete — nothing was changed.\n'
|
|
214
|
+
: '\n Uninstall complete. Restart your agents.\n');
|
|
215
|
+
return 0;
|
|
216
|
+
};
|
|
217
|
+
exports.handleUninstall = handleUninstall;
|
package/dist/verify.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AA0CA,eAAO,MAAM,YAAY,GAAU,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAG,OAAO,CAAC,MAAM,CAwEzF,CAAC"}
|
package/dist/verify.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleVerify = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const os_1 = require("os");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const agents_js_1 = require("./agents.js");
|
|
8
|
+
const config_js_1 = require("./config.js");
|
|
9
|
+
const zeph_hook_js_1 = require("./zeph-hook.js");
|
|
10
|
+
const HOME = (0, os_1.homedir)();
|
|
11
|
+
const pass = (msg) => console.log(` ✓ ${msg}`);
|
|
12
|
+
const warn = (msg) => console.log(` ! ${msg}`);
|
|
13
|
+
const failMsg = (msg) => console.log(` ✗ ${msg}`);
|
|
14
|
+
/** Does a shared rule file contain the Zeph managed block? */
|
|
15
|
+
const hasManagedBlock = (filePath) => {
|
|
16
|
+
try {
|
|
17
|
+
return (0, fs_1.readFileSync)(filePath, 'utf-8').includes('ZEPH:START');
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
// Per-agent: report whether the rule artifact Zeph installs is present.
|
|
24
|
+
const AGENT_RULE_PRESENT = {
|
|
25
|
+
claude: () => {
|
|
26
|
+
try {
|
|
27
|
+
return /zeph/.test((0, fs_1.readFileSync)((0, path_1.join)(HOME, '.claude.json'), 'utf-8'));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return (0, fs_1.existsSync)((0, path_1.join)(HOME, '.claude', 'plugins'));
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
cursor: () => (0, fs_1.existsSync)((0, path_1.join)(HOME, '.cursor', 'rules', 'zeph.mdc')),
|
|
34
|
+
windsurf: () => hasManagedBlock((0, path_1.join)(HOME, '.codeium', 'windsurf', 'memories', 'global_rules.md')),
|
|
35
|
+
gemini: () => hasManagedBlock((0, path_1.join)(HOME, '.gemini', 'GEMINI.md')),
|
|
36
|
+
codex: () => hasManagedBlock((0, path_1.join)(HOME, '.codex', 'AGENTS.md')),
|
|
37
|
+
copilot: () => (0, fs_1.existsSync)((0, path_1.join)(HOME, '.copilot', 'instructions', 'zeph.instructions.md')),
|
|
38
|
+
cline: () => (0, fs_1.existsSync)((0, path_1.join)(HOME, '.cline', 'rules', 'zeph.md')),
|
|
39
|
+
aider: () => (0, fs_1.existsSync)((0, path_1.join)(HOME, '.zeph', 'aider-conventions.md')),
|
|
40
|
+
};
|
|
41
|
+
const handleVerify = async (args) => {
|
|
42
|
+
const doPing = args.ping === true;
|
|
43
|
+
const checks = [];
|
|
44
|
+
const record = (label, state) => {
|
|
45
|
+
checks.push({ label, state });
|
|
46
|
+
if (state === 'pass')
|
|
47
|
+
pass(label);
|
|
48
|
+
else if (state === 'warn')
|
|
49
|
+
warn(label);
|
|
50
|
+
else
|
|
51
|
+
failMsg(label);
|
|
52
|
+
};
|
|
53
|
+
console.log(`\n Zeph verify — v${config_js_1.VERSION}\n`);
|
|
54
|
+
// ── Credentials ──────────────────────────────────────────────
|
|
55
|
+
console.log(' Credentials:');
|
|
56
|
+
const config = (0, config_js_1.loadConfig)();
|
|
57
|
+
const apiKey = (0, config_js_1.resolvedEnv)('ZEPH_API_KEY') || config.apiKey;
|
|
58
|
+
const hookId = (0, config_js_1.resolvedEnv)('ZEPH_HOOK_ID') || config.hookId;
|
|
59
|
+
record(apiKey ? 'ZEPH_API_KEY is set' : 'ZEPH_API_KEY not set (env or ~/.zeph/config.json)', apiKey ? 'pass' : 'fail');
|
|
60
|
+
record(hookId
|
|
61
|
+
? 'ZEPH_HOOK_ID is set (two-way zeph_ask/prompt/input enabled)'
|
|
62
|
+
: 'ZEPH_HOOK_ID not set (notify-only — set it for remote control)', hookId ? 'pass' : 'warn');
|
|
63
|
+
// ── Runtime ──────────────────────────────────────────────────
|
|
64
|
+
console.log('\n Runtime:');
|
|
65
|
+
record((0, agents_js_1.hasCommand)('node') ? 'node available' : 'node not found', (0, agents_js_1.hasCommand)('node') ? 'pass' : 'fail');
|
|
66
|
+
record((0, agents_js_1.hasCommand)('npx') ? 'npx available (MCP server runs via npx)' : 'npx not found', (0, agents_js_1.hasCommand)('npx') ? 'pass' : 'fail');
|
|
67
|
+
record((0, agents_js_1.hasCommand)('zeph')
|
|
68
|
+
? 'zeph CLI on PATH'
|
|
69
|
+
: 'zeph CLI not on PATH (hooks fall back to npx — slower first call)', (0, agents_js_1.hasCommand)('zeph') ? 'pass' : 'warn');
|
|
70
|
+
// ── Per-agent config ─────────────────────────────────────────
|
|
71
|
+
console.log('\n Agents:');
|
|
72
|
+
const detected = (0, agents_js_1.detectAgents)().filter((a) => a.detected);
|
|
73
|
+
if (detected.length === 0) {
|
|
74
|
+
warn('no supported agents detected');
|
|
75
|
+
}
|
|
76
|
+
for (const agent of detected) {
|
|
77
|
+
const present = AGENT_RULE_PRESENT[agent.id]?.() ?? false;
|
|
78
|
+
record(`${agent.name}: ${present ? 'Zeph rules installed' : 'Zeph rules NOT installed — run: zeph install'}`, present ? 'pass' : 'warn');
|
|
79
|
+
}
|
|
80
|
+
// ── Optional live API ping ───────────────────────────────────
|
|
81
|
+
if (doPing) {
|
|
82
|
+
console.log('\n API ping:');
|
|
83
|
+
if (!apiKey) {
|
|
84
|
+
record('skipped — no API key', 'warn');
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
try {
|
|
88
|
+
const hook = new zeph_hook_js_1.ZephHook({ apiKey, ...(config.baseUrl && { baseUrl: config.baseUrl }) });
|
|
89
|
+
await hook.list({ limit: 1 });
|
|
90
|
+
record('API reachable, key accepted', 'pass');
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
record(`API call failed: ${err instanceof Error ? err.message : 'unknown'}`, 'fail');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// ── Summary ──────────────────────────────────────────────────
|
|
98
|
+
const fails = checks.filter((c) => c.state === 'fail').length;
|
|
99
|
+
const warns = checks.filter((c) => c.state === 'warn').length;
|
|
100
|
+
console.log('');
|
|
101
|
+
if (fails === 0 && warns === 0) {
|
|
102
|
+
console.log(' ✓ All checks passed.\n');
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
console.log(` ${fails} failed, ${warns} warnings.${doPing ? '' : ' (run with --ping to test the API)'}\n`);
|
|
106
|
+
}
|
|
107
|
+
return fails === 0 ? 0 : 1;
|
|
108
|
+
};
|
|
109
|
+
exports.handleVerify = handleVerify;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeph-to/hook-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Zeph push notification SDK + CLI — zero dependencies",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -22,11 +22,14 @@
|
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
24
|
"build": "tsc",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"test:watch": "vitest",
|
|
25
27
|
"prepublishOnly": "npm run build"
|
|
26
28
|
},
|
|
27
29
|
"devDependencies": {
|
|
30
|
+
"@types/node": "^22.0.0",
|
|
28
31
|
"typescript": "^5.8.0",
|
|
29
|
-
"
|
|
32
|
+
"vitest": "^2.1.9"
|
|
30
33
|
},
|
|
31
34
|
"release": {
|
|
32
35
|
"branches": [
|