@zeph-to/cli 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +499 -0
- package/dist/agents.d.ts +8 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +29 -0
- package/dist/check-update.d.ts +4 -0
- package/dist/check-update.d.ts.map +1 -0
- package/dist/check-update.js +80 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +374 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +36 -0
- package/dist/crypto.d.ts +82 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +291 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +28 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/installer.d.ts +14 -0
- package/dist/installer.d.ts.map +1 -0
- package/dist/installer.js +464 -0
- package/dist/listener.d.ts +126 -0
- package/dist/listener.d.ts.map +1 -0
- package/dist/listener.js +1008 -0
- package/dist/login.d.ts +38 -0
- package/dist/login.d.ts.map +1 -0
- package/dist/login.js +182 -0
- package/dist/templates.d.ts +44 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +257 -0
- package/dist/types.d.ts +54 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/uninstall.d.ts +2 -0
- package/dist/uninstall.d.ts.map +1 -0
- package/dist/uninstall.js +217 -0
- package/dist/verify.d.ts +2 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +109 -0
- package/dist/wrapper.d.ts +26 -0
- package/dist/wrapper.d.ts.map +1 -0
- package/dist/wrapper.js +238 -0
- package/dist/zeph-hook.d.ts +23 -0
- package/dist/zeph-hook.d.ts.map +1 -0
- package/dist/zeph-hook.js +196 -0
- package/package.json +75 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ZephHook = void 0;
|
|
4
|
+
const errors_js_1 = require("./errors.js");
|
|
5
|
+
const crypto_js_1 = require("./crypto.js");
|
|
6
|
+
const DEFAULT_BASE_URL = 'https://api.zeph.to/v1';
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
8
|
+
const BODY_FILE_THRESHOLD = 512;
|
|
9
|
+
const PREVIEW_LENGTH = 200;
|
|
10
|
+
const inferMimeType = (fileName) => {
|
|
11
|
+
const ext = fileName.split('.').pop()?.toLowerCase();
|
|
12
|
+
const map = { md: 'text/markdown', txt: 'text/plain', json: 'application/json' };
|
|
13
|
+
return map[ext ?? ''] ?? 'text/plain';
|
|
14
|
+
};
|
|
15
|
+
class ZephHook {
|
|
16
|
+
apiKey;
|
|
17
|
+
baseUrl;
|
|
18
|
+
timeoutMs;
|
|
19
|
+
cryptoInitialized = false;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
if (!options.apiKey) {
|
|
22
|
+
throw new errors_js_1.ZephError('apiKey is required', 'INVALID_OPTIONS', 400);
|
|
23
|
+
}
|
|
24
|
+
this.apiKey = options.apiKey;
|
|
25
|
+
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, '');
|
|
26
|
+
this.timeoutMs = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
27
|
+
}
|
|
28
|
+
async ensureCrypto() {
|
|
29
|
+
if (this.cryptoInitialized)
|
|
30
|
+
return !!(0, crypto_js_1.getKeyPair)();
|
|
31
|
+
try {
|
|
32
|
+
await (0, crypto_js_1.initCrypto)(this.apiKey, this.baseUrl);
|
|
33
|
+
this.cryptoInitialized = true;
|
|
34
|
+
return !!(0, crypto_js_1.getKeyPair)();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
this.cryptoInitialized = true;
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async notify(payload) {
|
|
42
|
+
const canEncrypt = await this.ensureCrypto();
|
|
43
|
+
const body = payload.body;
|
|
44
|
+
const bodyBytes = body ? new TextEncoder().encode(body).byteLength : 0;
|
|
45
|
+
const isLongBody = bodyBytes > BODY_FILE_THRESHOLD;
|
|
46
|
+
if (isLongBody && body) {
|
|
47
|
+
return this.notifyWithFile(payload, body, bodyBytes, canEncrypt);
|
|
48
|
+
}
|
|
49
|
+
// Encrypt push body if possible
|
|
50
|
+
let sendPayload = { ...payload };
|
|
51
|
+
if (canEncrypt) {
|
|
52
|
+
try {
|
|
53
|
+
const enc = await (0, crypto_js_1.encryptPushBodyForSelf)({ title: payload.title, body: payload.body, url: payload.url });
|
|
54
|
+
sendPayload = { ...sendPayload, title: undefined, body: enc.body, isEncrypted: true, encryptedKey: enc.encryptedKey, senderPublicKey: enc.senderPublicKey };
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
console.error('[Crypto] Push encryption failed, sending plaintext:', err);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const json = await this.request('POST', '/pushes/send', sendPayload);
|
|
61
|
+
const pushId = json.data?.pushId;
|
|
62
|
+
if (!pushId) {
|
|
63
|
+
throw new errors_js_1.ZephError('Server returned no pushId', 'INVALID_RESPONSE', 500);
|
|
64
|
+
}
|
|
65
|
+
return { pushId };
|
|
66
|
+
}
|
|
67
|
+
async notifyWithFile(payload, body, fileSize, canEncrypt) {
|
|
68
|
+
const fileName = 'response.md';
|
|
69
|
+
let fileType = inferMimeType(fileName);
|
|
70
|
+
// Encrypt file content if possible
|
|
71
|
+
let uploadContent = body;
|
|
72
|
+
let uploadSize = fileSize;
|
|
73
|
+
let fileIv;
|
|
74
|
+
let fileEncryptedKey;
|
|
75
|
+
if (canEncrypt) {
|
|
76
|
+
try {
|
|
77
|
+
const encrypted = await (0, crypto_js_1.encryptFileForSelf)(body);
|
|
78
|
+
uploadContent = encrypted.ciphertext;
|
|
79
|
+
uploadSize = encrypted.ciphertext.length;
|
|
80
|
+
fileType = 'application/octet-stream';
|
|
81
|
+
fileIv = encrypted.iv;
|
|
82
|
+
fileEncryptedKey = encrypted.encryptedKey;
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
console.error('[Crypto] File encryption failed, sending plaintext:', err);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const upload = await this.requestUpload({ fileName, fileType, fileSize: uploadSize });
|
|
89
|
+
await this.uploadToS3(upload.uploadUrl, uploadContent, fileType);
|
|
90
|
+
const preview = body.length > PREVIEW_LENGTH ? body.slice(0, PREVIEW_LENGTH) + '...' : body;
|
|
91
|
+
// Encrypt push body
|
|
92
|
+
let sendPayload = {
|
|
93
|
+
...payload,
|
|
94
|
+
body: preview,
|
|
95
|
+
type: payload.type ?? 'file',
|
|
96
|
+
files: [{ fileKey: upload.fileKey, fileName, fileSize, fileType: inferMimeType(fileName), iv: fileIv, encryptedKey: fileEncryptedKey }],
|
|
97
|
+
};
|
|
98
|
+
if (canEncrypt) {
|
|
99
|
+
try {
|
|
100
|
+
const enc = await (0, crypto_js_1.encryptPushBodyForSelf)({ title: payload.title, body: preview, url: payload.url });
|
|
101
|
+
sendPayload = { ...sendPayload, title: undefined, body: enc.body, isEncrypted: true, encryptedKey: enc.encryptedKey, senderPublicKey: enc.senderPublicKey };
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
console.error('[Crypto] Push encryption failed, sending plaintext:', err);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const json = await this.request('POST', '/pushes/send', sendPayload);
|
|
108
|
+
const pushId = json.data?.pushId;
|
|
109
|
+
if (!pushId) {
|
|
110
|
+
throw new errors_js_1.ZephError('Server returned no pushId', 'INVALID_RESPONSE', 500);
|
|
111
|
+
}
|
|
112
|
+
return { pushId, fileKey: upload.fileKey, autoFile: true };
|
|
113
|
+
}
|
|
114
|
+
async requestUpload(params) {
|
|
115
|
+
const json = await this.request('POST', '/files/upload-request', params);
|
|
116
|
+
return json.data;
|
|
117
|
+
}
|
|
118
|
+
async uploadToS3(url, content, contentType) {
|
|
119
|
+
const isText = typeof content === 'string';
|
|
120
|
+
const body = isText ? content : new Uint8Array(content);
|
|
121
|
+
const response = await fetch(url, {
|
|
122
|
+
method: 'PUT',
|
|
123
|
+
headers: { 'Content-Type': isText ? `${contentType}; charset=utf-8` : contentType },
|
|
124
|
+
body,
|
|
125
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
126
|
+
});
|
|
127
|
+
if (!response.ok) {
|
|
128
|
+
throw new errors_js_1.ZephError(`S3 upload failed with status ${response.status}`, 'UPLOAD_FAILED', response.status);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async list(params) {
|
|
132
|
+
const query = new URLSearchParams();
|
|
133
|
+
if (params?.limit)
|
|
134
|
+
query.set('limit', String(params.limit));
|
|
135
|
+
if (params?.type)
|
|
136
|
+
query.set('type', params.type);
|
|
137
|
+
const qs = query.toString();
|
|
138
|
+
const json = await this.request('GET', `/pushes${qs ? `?${qs}` : ''}`);
|
|
139
|
+
const pushes = json.data.map((p) => ({
|
|
140
|
+
pushId: p.pushId,
|
|
141
|
+
type: p.type,
|
|
142
|
+
title: p.title,
|
|
143
|
+
body: p.body?.slice(0, 100),
|
|
144
|
+
createdAt: p.createdAt,
|
|
145
|
+
}));
|
|
146
|
+
return { pushes, count: pushes.length, hasMore: json.pagination?.hasMore ?? false };
|
|
147
|
+
}
|
|
148
|
+
async dismiss(pushId) {
|
|
149
|
+
await this.request('POST', `/pushes/${encodeURIComponent(pushId)}/dismiss`);
|
|
150
|
+
return { dismissed: true };
|
|
151
|
+
}
|
|
152
|
+
async dismissAll() {
|
|
153
|
+
const json = await this.request('POST', '/pushes/dismiss-all');
|
|
154
|
+
return { dismissed: json.data?.dismissed ?? 0 };
|
|
155
|
+
}
|
|
156
|
+
async request(method, path, body) {
|
|
157
|
+
const controller = new AbortController();
|
|
158
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
159
|
+
const headers = { 'X-API-Key': this.apiKey };
|
|
160
|
+
if (body)
|
|
161
|
+
headers['Content-Type'] = 'application/json';
|
|
162
|
+
let response;
|
|
163
|
+
try {
|
|
164
|
+
response = await fetch(`${this.baseUrl}${path}`, {
|
|
165
|
+
method,
|
|
166
|
+
headers,
|
|
167
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
168
|
+
signal: controller.signal,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
if (err instanceof DOMException && err.name === 'AbortError') {
|
|
173
|
+
throw new errors_js_1.ZephError(`Request timed out after ${this.timeoutMs}ms`, 'TIMEOUT', 408);
|
|
174
|
+
}
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
clearTimeout(timer);
|
|
179
|
+
}
|
|
180
|
+
const json = await response.json();
|
|
181
|
+
if (!response.ok) {
|
|
182
|
+
throw this.parseError(response.status, json);
|
|
183
|
+
}
|
|
184
|
+
return json;
|
|
185
|
+
}
|
|
186
|
+
parseError(status, body) {
|
|
187
|
+
const message = body.error?.message ?? `Request failed with status ${status}`;
|
|
188
|
+
const code = body.error?.code ?? 'UNKNOWN_ERROR';
|
|
189
|
+
if (status === 401)
|
|
190
|
+
return new errors_js_1.AuthenticationError(message);
|
|
191
|
+
if (status === 403 && code === 'QUOTA_EXCEEDED')
|
|
192
|
+
return new errors_js_1.QuotaExceededError(message);
|
|
193
|
+
return new errors_js_1.ZephError(message, code, status);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
exports.ZephHook = ZephHook;
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zeph-to/cli",
|
|
3
|
+
"version": "1.12.0",
|
|
4
|
+
"description": "Zeph CLI + push notification SDK for AI agents",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"require": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"zeph": "./dist/cli.js"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18.0.0"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"!dist/**/*.tsbuildinfo"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"test:watch": "vitest",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^22.0.0",
|
|
31
|
+
"@types/ws": "^8.18.1",
|
|
32
|
+
"typescript": "^5.8.0",
|
|
33
|
+
"vitest": "^2.1.9"
|
|
34
|
+
},
|
|
35
|
+
"release": {
|
|
36
|
+
"branches": [
|
|
37
|
+
"main"
|
|
38
|
+
],
|
|
39
|
+
"plugins": [
|
|
40
|
+
"@semantic-release/commit-analyzer",
|
|
41
|
+
"@semantic-release/release-notes-generator",
|
|
42
|
+
"@semantic-release/npm",
|
|
43
|
+
"@semantic-release/github"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"author": "Zeph <dev@zeph.to>",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/zeph-to/cli"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/zeph-to/cli",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/zeph-to/cli/issues"
|
|
54
|
+
},
|
|
55
|
+
"keywords": [
|
|
56
|
+
"zeph",
|
|
57
|
+
"push",
|
|
58
|
+
"notification",
|
|
59
|
+
"cli",
|
|
60
|
+
"webhook",
|
|
61
|
+
"ai-agent",
|
|
62
|
+
"mcp",
|
|
63
|
+
"claude",
|
|
64
|
+
"devtools"
|
|
65
|
+
],
|
|
66
|
+
"license": "Apache-2.0",
|
|
67
|
+
"publishConfig": {
|
|
68
|
+
"access": "public",
|
|
69
|
+
"registry": "https://registry.npmjs.org/"
|
|
70
|
+
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"@inquirer/prompts": "^8.4.3",
|
|
73
|
+
"ws": "^8.21.0"
|
|
74
|
+
}
|
|
75
|
+
}
|