@two7722/sentinel-guard 1.1.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/dist/__tests__/rules-engine.test.d.ts +1 -0
- package/dist/__tests__/rules-engine.test.js +69 -0
- package/dist/__tests__/rules-engine.test.js.map +1 -0
- package/dist/__tests__/transport-encryption.test.d.ts +1 -0
- package/dist/__tests__/transport-encryption.test.js +95 -0
- package/dist/__tests__/transport-encryption.test.js.map +1 -0
- package/dist/api/client.d.ts +27 -0
- package/dist/api/client.js +91 -0
- package/dist/api/client.js.map +1 -0
- package/dist/cli/bootstrap.d.ts +12 -0
- package/dist/cli/bootstrap.js +132 -0
- package/dist/cli/bootstrap.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +534 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/crypto/keys.d.ts +40 -0
- package/dist/crypto/keys.js +125 -0
- package/dist/crypto/keys.js.map +1 -0
- package/dist/crypto/transport-encryption.d.ts +13 -0
- package/dist/crypto/transport-encryption.js +62 -0
- package/dist/crypto/transport-encryption.js.map +1 -0
- package/dist/install/setup.d.ts +2 -0
- package/dist/install/setup.js +80 -0
- package/dist/install/setup.js.map +1 -0
- package/dist/lib/budget.d.ts +14 -0
- package/dist/lib/budget.js +93 -0
- package/dist/lib/budget.js.map +1 -0
- package/dist/lib/claude-process.d.ts +16 -0
- package/dist/lib/claude-process.js +98 -0
- package/dist/lib/claude-process.js.map +1 -0
- package/dist/lib/daemon.d.ts +6 -0
- package/dist/lib/daemon.js +218 -0
- package/dist/lib/daemon.js.map +1 -0
- package/dist/lib/diff.d.ts +5 -0
- package/dist/lib/diff.js +85 -0
- package/dist/lib/diff.js.map +1 -0
- package/dist/lib/doctor.d.ts +1 -0
- package/dist/lib/doctor.js +108 -0
- package/dist/lib/doctor.js.map +1 -0
- package/dist/lib/history.d.ts +22 -0
- package/dist/lib/history.js +62 -0
- package/dist/lib/history.js.map +1 -0
- package/dist/lib/logger.d.ts +11 -0
- package/dist/lib/logger.js +62 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/modes.d.ts +22 -0
- package/dist/lib/modes.js +58 -0
- package/dist/lib/modes.js.map +1 -0
- package/dist/lib/overrides.d.ts +13 -0
- package/dist/lib/overrides.js +74 -0
- package/dist/lib/overrides.js.map +1 -0
- package/dist/lib/session.d.ts +32 -0
- package/dist/lib/session.js +68 -0
- package/dist/lib/session.js.map +1 -0
- package/dist/lib/summarizer.d.ts +5 -0
- package/dist/lib/summarizer.js +46 -0
- package/dist/lib/summarizer.js.map +1 -0
- package/dist/lib/tunnel.d.ts +2 -0
- package/dist/lib/tunnel.js +48 -0
- package/dist/lib/tunnel.js.map +1 -0
- package/dist/relay/pending.d.ts +27 -0
- package/dist/relay/pending.js +65 -0
- package/dist/relay/pending.js.map +1 -0
- package/dist/rules/engine.d.ts +31 -0
- package/dist/rules/engine.js +203 -0
- package/dist/rules/engine.js.map +1 -0
- package/dist/server/http.d.ts +8 -0
- package/dist/server/http.js +359 -0
- package/dist/server/http.js.map +1 -0
- package/dist/socket/client.d.ts +16 -0
- package/dist/socket/client.js +81 -0
- package/dist/socket/client.js.map +1 -0
- package/dist/transport/cloudkit.d.ts +30 -0
- package/dist/transport/cloudkit.js +162 -0
- package/dist/transport/cloudkit.js.map +1 -0
- package/dist/transport/factory.d.ts +6 -0
- package/dist/transport/factory.js +20 -0
- package/dist/transport/factory.js.map +1 -0
- package/dist/transport/interface.d.ts +25 -0
- package/dist/transport/interface.js +13 -0
- package/dist/transport/interface.js.map +1 -0
- package/dist/transport/local.d.ts +46 -0
- package/dist/transport/local.js +320 -0
- package/dist/transport/local.js.map +1 -0
- package/dist/transport/remote.d.ts +17 -0
- package/dist/transport/remote.js +83 -0
- package/dist/transport/remote.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.decodeBase64 = exports.encodeBase64 = void 0;
|
|
7
|
+
exports.getKeyPair = getKeyPair;
|
|
8
|
+
exports.getPublicKeyBase64 = getPublicKeyBase64;
|
|
9
|
+
exports.authChallenge = authChallenge;
|
|
10
|
+
exports.encryptForDevice = encryptForDevice;
|
|
11
|
+
exports.decryptFromDevice = decryptFromDevice;
|
|
12
|
+
exports.getSentinelDir = getSentinelDir;
|
|
13
|
+
const tweetnacl_1 = __importDefault(require("tweetnacl"));
|
|
14
|
+
const tweetnacl_util_1 = require("tweetnacl-util");
|
|
15
|
+
Object.defineProperty(exports, "encodeBase64", { enumerable: true, get: function () { return tweetnacl_util_1.encodeBase64; } });
|
|
16
|
+
Object.defineProperty(exports, "decodeBase64", { enumerable: true, get: function () { return tweetnacl_util_1.decodeBase64; } });
|
|
17
|
+
const crypto_1 = require("crypto");
|
|
18
|
+
const fs_1 = require("fs");
|
|
19
|
+
const path_1 = require("path");
|
|
20
|
+
const os_1 = require("os");
|
|
21
|
+
const SENTINEL_DIR = (0, path_1.join)((0, os_1.homedir)(), '.sentinel');
|
|
22
|
+
const IDENTITY_PATH = (0, path_1.join)(SENTINEL_DIR, 'identity.json');
|
|
23
|
+
// ==================== Identity (Ed25519) ====================
|
|
24
|
+
function ensureDir() {
|
|
25
|
+
if (!(0, fs_1.existsSync)(SENTINEL_DIR)) {
|
|
26
|
+
(0, fs_1.mkdirSync)(SENTINEL_DIR, { recursive: true, mode: 0o700 });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 首次运行生成 Ed25519 seed(32 随机字节),存 ~/.sentinel/identity.json
|
|
31
|
+
* 后续直接读取
|
|
32
|
+
*/
|
|
33
|
+
function loadOrCreateSeed() {
|
|
34
|
+
ensureDir();
|
|
35
|
+
if ((0, fs_1.existsSync)(IDENTITY_PATH)) {
|
|
36
|
+
const raw = JSON.parse((0, fs_1.readFileSync)(IDENTITY_PATH, 'utf-8'));
|
|
37
|
+
return (0, tweetnacl_util_1.decodeBase64)(raw.seed);
|
|
38
|
+
}
|
|
39
|
+
const seed = (0, crypto_1.randomBytes)(32);
|
|
40
|
+
const identity = {
|
|
41
|
+
seed: (0, tweetnacl_util_1.encodeBase64)(seed),
|
|
42
|
+
createdAt: new Date().toISOString(),
|
|
43
|
+
};
|
|
44
|
+
(0, fs_1.writeFileSync)(IDENTITY_PATH, JSON.stringify(identity, null, 2), { mode: 0o600 });
|
|
45
|
+
return seed;
|
|
46
|
+
}
|
|
47
|
+
let _seed = null;
|
|
48
|
+
function getSeed() {
|
|
49
|
+
if (!_seed)
|
|
50
|
+
_seed = loadOrCreateSeed();
|
|
51
|
+
return _seed;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 获取 Ed25519 签名密钥对(从 seed 派生)
|
|
55
|
+
*/
|
|
56
|
+
function getKeyPair() {
|
|
57
|
+
return tweetnacl_1.default.sign.keyPair.fromSeed(getSeed());
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 公钥 base64
|
|
61
|
+
*/
|
|
62
|
+
function getPublicKeyBase64() {
|
|
63
|
+
return (0, tweetnacl_util_1.encodeBase64)(getKeyPair().publicKey);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 生成认证 challenge:
|
|
67
|
+
* 1. 生成 32 字节随机 challenge
|
|
68
|
+
* 2. 用 Ed25519 私钥签名
|
|
69
|
+
* 3. 返回 { challenge, publicKey, signature } — 全部 base64
|
|
70
|
+
*/
|
|
71
|
+
function authChallenge() {
|
|
72
|
+
const kp = getKeyPair();
|
|
73
|
+
const challengeBytes = (0, crypto_1.randomBytes)(32);
|
|
74
|
+
const signature = tweetnacl_1.default.sign.detached(challengeBytes, kp.secretKey);
|
|
75
|
+
return {
|
|
76
|
+
challenge: (0, tweetnacl_util_1.encodeBase64)(challengeBytes),
|
|
77
|
+
publicKey: (0, tweetnacl_util_1.encodeBase64)(kp.publicKey),
|
|
78
|
+
signature: (0, tweetnacl_util_1.encodeBase64)(signature),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// ==================== Encryption (X25519 box) ====================
|
|
82
|
+
/**
|
|
83
|
+
* 加密数据给目标设备(参考 Happy encryption.ts):
|
|
84
|
+
*
|
|
85
|
+
* 1. 生成 ephemeral X25519 密钥对
|
|
86
|
+
* 2. 生成 24 字节随机 nonce
|
|
87
|
+
* 3. nacl.box(data, nonce, recipientPublicKey, ephemeral.secretKey)
|
|
88
|
+
* 4. 返回: ephemeralPubKey(32) + nonce(24) + encryptedData
|
|
89
|
+
*/
|
|
90
|
+
function encryptForDevice(data, recipientPublicKey) {
|
|
91
|
+
const ephemeral = tweetnacl_1.default.box.keyPair();
|
|
92
|
+
const nonce = (0, crypto_1.randomBytes)(24);
|
|
93
|
+
const encrypted = tweetnacl_1.default.box(data, nonce, recipientPublicKey, ephemeral.secretKey);
|
|
94
|
+
if (!encrypted)
|
|
95
|
+
throw new Error('Encryption failed');
|
|
96
|
+
// concat: ephemeralPubKey(32) + nonce(24) + encrypted
|
|
97
|
+
const bundle = new Uint8Array(32 + 24 + encrypted.length);
|
|
98
|
+
bundle.set(ephemeral.publicKey, 0);
|
|
99
|
+
bundle.set(nonce, 32);
|
|
100
|
+
bundle.set(encrypted, 56);
|
|
101
|
+
return bundle;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 解密来自设备的数据(反向 encryptForDevice):
|
|
105
|
+
*
|
|
106
|
+
* 1. 提取 ephemeralPubKey(32) + nonce(24) + encryptedData
|
|
107
|
+
* 2. nacl.box.open(encryptedData, nonce, ephemeralPubKey, mySecretKey)
|
|
108
|
+
*/
|
|
109
|
+
function decryptFromDevice(bundle, mySecretKey) {
|
|
110
|
+
if (bundle.length < 57)
|
|
111
|
+
throw new Error('Bundle too short');
|
|
112
|
+
const ephemeralPubKey = bundle.slice(0, 32);
|
|
113
|
+
const nonce = bundle.slice(32, 56);
|
|
114
|
+
const ciphertext = bundle.slice(56);
|
|
115
|
+
const decrypted = tweetnacl_1.default.box.open(ciphertext, nonce, ephemeralPubKey, mySecretKey);
|
|
116
|
+
if (!decrypted)
|
|
117
|
+
throw new Error('Decryption failed — invalid key or corrupted data');
|
|
118
|
+
return decrypted;
|
|
119
|
+
}
|
|
120
|
+
// ==================== Helpers ====================
|
|
121
|
+
function getSentinelDir() {
|
|
122
|
+
ensureDir();
|
|
123
|
+
return SENTINEL_DIR;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/crypto/keys.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport encryption using NaCl secretbox (XSalsa20-Poly1305).
|
|
3
|
+
* Shared key generated on first start, stored in ~/.sentinel/transport.key.
|
|
4
|
+
* iOS receives the key during Bonjour handshake.
|
|
5
|
+
*/
|
|
6
|
+
/** Get or create the 32-byte shared transport key */
|
|
7
|
+
export declare function getTransportKey(): Uint8Array;
|
|
8
|
+
/** Encrypt a JSON message string. Returns base64(nonce + ciphertext) */
|
|
9
|
+
export declare function encryptMessage(plaintext: string, key: Uint8Array): string;
|
|
10
|
+
/** Decrypt a base64-encoded message. Returns the JSON string */
|
|
11
|
+
export declare function decryptMessage(encoded: string, key: Uint8Array): string | null;
|
|
12
|
+
/** Get transport key as base64 (for sharing with iOS during handshake) */
|
|
13
|
+
export declare function getTransportKeyBase64(): string;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getTransportKey = getTransportKey;
|
|
7
|
+
exports.encryptMessage = encryptMessage;
|
|
8
|
+
exports.decryptMessage = decryptMessage;
|
|
9
|
+
exports.getTransportKeyBase64 = getTransportKeyBase64;
|
|
10
|
+
const tweetnacl_1 = __importDefault(require("tweetnacl"));
|
|
11
|
+
const tweetnacl_util_1 = require("tweetnacl-util");
|
|
12
|
+
const fs_1 = require("fs");
|
|
13
|
+
const path_1 = require("path");
|
|
14
|
+
const keys_1 = require("./keys");
|
|
15
|
+
const SHARED_KEY_PATH = (0, path_1.join)((0, keys_1.getSentinelDir)(), 'transport.key');
|
|
16
|
+
/**
|
|
17
|
+
* Transport encryption using NaCl secretbox (XSalsa20-Poly1305).
|
|
18
|
+
* Shared key generated on first start, stored in ~/.sentinel/transport.key.
|
|
19
|
+
* iOS receives the key during Bonjour handshake.
|
|
20
|
+
*/
|
|
21
|
+
/** Get or create the 32-byte shared transport key */
|
|
22
|
+
function getTransportKey() {
|
|
23
|
+
if ((0, fs_1.existsSync)(SHARED_KEY_PATH)) {
|
|
24
|
+
return (0, tweetnacl_util_1.decodeBase64)((0, fs_1.readFileSync)(SHARED_KEY_PATH, 'utf-8').trim());
|
|
25
|
+
}
|
|
26
|
+
const key = tweetnacl_1.default.randomBytes(32);
|
|
27
|
+
(0, fs_1.writeFileSync)(SHARED_KEY_PATH, (0, tweetnacl_util_1.encodeBase64)(key), { mode: 0o600 });
|
|
28
|
+
return key;
|
|
29
|
+
}
|
|
30
|
+
/** Encrypt a JSON message string. Returns base64(nonce + ciphertext) */
|
|
31
|
+
function encryptMessage(plaintext, key) {
|
|
32
|
+
const nonce = tweetnacl_1.default.randomBytes(24);
|
|
33
|
+
const msgBytes = new TextEncoder().encode(plaintext);
|
|
34
|
+
const encrypted = tweetnacl_1.default.secretbox(msgBytes, nonce, key);
|
|
35
|
+
// Concat nonce(24) + ciphertext
|
|
36
|
+
const combined = new Uint8Array(24 + encrypted.length);
|
|
37
|
+
combined.set(nonce, 0);
|
|
38
|
+
combined.set(encrypted, 24);
|
|
39
|
+
return (0, tweetnacl_util_1.encodeBase64)(combined);
|
|
40
|
+
}
|
|
41
|
+
/** Decrypt a base64-encoded message. Returns the JSON string */
|
|
42
|
+
function decryptMessage(encoded, key) {
|
|
43
|
+
try {
|
|
44
|
+
const combined = (0, tweetnacl_util_1.decodeBase64)(encoded);
|
|
45
|
+
if (combined.length < 25)
|
|
46
|
+
return null;
|
|
47
|
+
const nonce = combined.slice(0, 24);
|
|
48
|
+
const ciphertext = combined.slice(24);
|
|
49
|
+
const decrypted = tweetnacl_1.default.secretbox.open(ciphertext, nonce, key);
|
|
50
|
+
if (!decrypted)
|
|
51
|
+
return null;
|
|
52
|
+
return new TextDecoder().decode(decrypted);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Get transport key as base64 (for sharing with iOS during handshake) */
|
|
59
|
+
function getTransportKeyBase64() {
|
|
60
|
+
return (0, tweetnacl_util_1.encodeBase64)(getTransportKey());
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=transport-encryption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport-encryption.js","sourceRoot":"","sources":["../../src/crypto/transport-encryption.ts"],"names":[],"mappings":";;;;;AAgBA,0CAOC;AAGD,wCAWC;AAGD,wCAcC;AAGD,sDAEC;AA3DD,0DAA6B;AAC7B,mDAA4D;AAE5D,2BAA6D;AAC7D,+BAA4B;AAC5B,iCAAwC;AAExC,MAAM,eAAe,GAAG,IAAA,WAAI,EAAC,IAAA,qBAAc,GAAE,EAAE,eAAe,CAAC,CAAC;AAEhE;;;;GAIG;AAEH,qDAAqD;AACrD,SAAgB,eAAe;IAC7B,IAAI,IAAA,eAAU,EAAC,eAAe,CAAC,EAAE,CAAC;QAChC,OAAO,IAAA,6BAAY,EAAC,IAAA,iBAAY,EAAC,eAAe,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,GAAG,GAAG,mBAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IACjC,IAAA,kBAAa,EAAC,eAAe,EAAE,IAAA,6BAAY,EAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,wEAAwE;AACxE,SAAgB,cAAc,CAAC,SAAiB,EAAE,GAAe;IAC/D,MAAM,KAAK,GAAG,mBAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IACnC,MAAM,QAAQ,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACrD,MAAM,SAAS,GAAG,mBAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IAEvD,gCAAgC;IAChC,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IACvD,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACvB,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAE5B,OAAO,IAAA,6BAAY,EAAC,QAAQ,CAAC,CAAC;AAChC,CAAC;AAED,gEAAgE;AAChE,SAAgB,cAAc,CAAC,OAAe,EAAE,GAAe;IAC7D,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAA,6BAAY,EAAC,OAAO,CAAC,CAAC;QACvC,IAAI,QAAQ,CAAC,MAAM,GAAG,EAAE;YAAE,OAAO,IAAI,CAAC;QAEtC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACpC,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACtC,MAAM,SAAS,GAAG,mBAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QAC9D,IAAI,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC;QAE5B,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,0EAA0E;AAC1E,SAAgB,qBAAqB;IACnC,OAAO,IAAA,6BAAY,EAAC,eAAe,EAAE,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.installHook = installHook;
|
|
4
|
+
exports.uninstallHook = uninstallHook;
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const os_1 = require("os");
|
|
8
|
+
const logger_1 = require("../lib/logger");
|
|
9
|
+
const CLAUDE_DIR = (0, path_1.join)((0, os_1.homedir)(), '.claude');
|
|
10
|
+
const SETTINGS_PATH = (0, path_1.join)(CLAUDE_DIR, 'settings.json');
|
|
11
|
+
const HOOK_TYPES = ['PreToolUse', 'PostToolUse', 'Notification', 'Stop'];
|
|
12
|
+
function makeHookEntry(port, endpoint) {
|
|
13
|
+
return {
|
|
14
|
+
hooks: [{
|
|
15
|
+
type: 'command',
|
|
16
|
+
command: `curl -s -X POST http://localhost:${port}${endpoint} -H 'Content-Type: application/json' -d @-`,
|
|
17
|
+
}],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function installHook(port = 7749) {
|
|
21
|
+
if (!(0, fs_1.existsSync)(CLAUDE_DIR)) {
|
|
22
|
+
(0, fs_1.mkdirSync)(CLAUDE_DIR, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
let settings = {};
|
|
25
|
+
if ((0, fs_1.existsSync)(SETTINGS_PATH)) {
|
|
26
|
+
try {
|
|
27
|
+
settings = JSON.parse((0, fs_1.readFileSync)(SETTINGS_PATH, 'utf-8'));
|
|
28
|
+
}
|
|
29
|
+
catch { }
|
|
30
|
+
}
|
|
31
|
+
// Check if already installed
|
|
32
|
+
const existing = JSON.stringify(settings.hooks ?? {});
|
|
33
|
+
if (existing.includes(`localhost:${port}/hook`)) {
|
|
34
|
+
logger_1.log.info('Sentinel hooks already installed');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (!settings.hooks)
|
|
38
|
+
settings.hooks = {};
|
|
39
|
+
// PreToolUse → /hook (approval), others → /event (fire-and-forget)
|
|
40
|
+
const hookMap = {
|
|
41
|
+
PreToolUse: '/hook',
|
|
42
|
+
PostToolUse: '/event',
|
|
43
|
+
Notification: '/event',
|
|
44
|
+
Stop: '/event',
|
|
45
|
+
};
|
|
46
|
+
for (const [hookType, endpoint] of Object.entries(hookMap)) {
|
|
47
|
+
if (!Array.isArray(settings.hooks[hookType])) {
|
|
48
|
+
settings.hooks[hookType] = [];
|
|
49
|
+
}
|
|
50
|
+
settings.hooks[hookType].push(makeHookEntry(port, endpoint));
|
|
51
|
+
}
|
|
52
|
+
(0, fs_1.writeFileSync)(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
53
|
+
logger_1.log.success('Hooks installed (PreToolUse, PostToolUse, Notification, Stop)');
|
|
54
|
+
logger_1.log.dim(` Config: ${SETTINGS_PATH}`);
|
|
55
|
+
}
|
|
56
|
+
function uninstallHook(port = 7749) {
|
|
57
|
+
if (!(0, fs_1.existsSync)(SETTINGS_PATH))
|
|
58
|
+
return;
|
|
59
|
+
try {
|
|
60
|
+
const settings = JSON.parse((0, fs_1.readFileSync)(SETTINGS_PATH, 'utf-8'));
|
|
61
|
+
if (!settings.hooks)
|
|
62
|
+
return;
|
|
63
|
+
for (const hookType of Object.keys(settings.hooks)) {
|
|
64
|
+
if (Array.isArray(settings.hooks[hookType])) {
|
|
65
|
+
settings.hooks[hookType] = settings.hooks[hookType].filter((entry) => {
|
|
66
|
+
const hooks = entry?.hooks;
|
|
67
|
+
if (!Array.isArray(hooks))
|
|
68
|
+
return true;
|
|
69
|
+
return !hooks.some((h) => h.type === 'command' && typeof h.command === 'string' && h.command.includes(`localhost:${port}`));
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
(0, fs_1.writeFileSync)(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
74
|
+
logger_1.log.success('All Sentinel hooks removed');
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
logger_1.log.warn('Could not update settings.json');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/install/setup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function setBudgetLimit(amount: number): void;
|
|
2
|
+
export declare function getBudgetLimit(): number;
|
|
3
|
+
export declare function getTodaySpend(): number;
|
|
4
|
+
/** Estimate cost for a specific tool call */
|
|
5
|
+
export declare function estimateToolCost(toolName: string): number;
|
|
6
|
+
export declare function isOverBudget(): boolean;
|
|
7
|
+
export declare function getBudgetStatus(): {
|
|
8
|
+
limit: number;
|
|
9
|
+
spent: number;
|
|
10
|
+
remaining: number;
|
|
11
|
+
overBudget: boolean;
|
|
12
|
+
calls: number;
|
|
13
|
+
};
|
|
14
|
+
export declare function resetTodayBudget(): void;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setBudgetLimit = setBudgetLimit;
|
|
4
|
+
exports.getBudgetLimit = getBudgetLimit;
|
|
5
|
+
exports.getTodaySpend = getTodaySpend;
|
|
6
|
+
exports.estimateToolCost = estimateToolCost;
|
|
7
|
+
exports.isOverBudget = isOverBudget;
|
|
8
|
+
exports.getBudgetStatus = getBudgetStatus;
|
|
9
|
+
exports.resetTodayBudget = resetTodayBudget;
|
|
10
|
+
const fs_1 = require("fs");
|
|
11
|
+
const path_1 = require("path");
|
|
12
|
+
const keys_1 = require("../crypto/keys");
|
|
13
|
+
const history_1 = require("./history");
|
|
14
|
+
const BUDGET_PATH = (0, path_1.join)((0, keys_1.getSentinelDir)(), 'budget.json');
|
|
15
|
+
// Per-tool cost estimation (input + output tokens)
|
|
16
|
+
// Based on typical Claude Sonnet usage patterns
|
|
17
|
+
const TOOL_COST_ESTIMATES = {
|
|
18
|
+
Bash: { input: 800, output: 500 }, // commands + output
|
|
19
|
+
Write: { input: 600, output: 300 }, // file content
|
|
20
|
+
Edit: { input: 500, output: 200 }, // partial edits
|
|
21
|
+
Read: { input: 200, output: 800 }, // small request, large file
|
|
22
|
+
Glob: { input: 200, output: 300 },
|
|
23
|
+
Grep: { input: 300, output: 400 },
|
|
24
|
+
};
|
|
25
|
+
const DEFAULT_TOOL_ESTIMATE = { input: 500, output: 200 };
|
|
26
|
+
function estimateCost(toolName, config) {
|
|
27
|
+
const est = TOOL_COST_ESTIMATES[toolName] ?? DEFAULT_TOOL_ESTIMATE;
|
|
28
|
+
return (est.input / 1_000_000) * config.inputCostPerM +
|
|
29
|
+
(est.output / 1_000_000) * config.outputCostPerM;
|
|
30
|
+
}
|
|
31
|
+
const DEFAULT_CONFIG = {
|
|
32
|
+
dailyLimit: 5.0,
|
|
33
|
+
currency: 'USD',
|
|
34
|
+
inputCostPerM: 3.0, // Claude Sonnet
|
|
35
|
+
outputCostPerM: 15.0, // Claude Sonnet
|
|
36
|
+
};
|
|
37
|
+
function loadBudget() {
|
|
38
|
+
if (!(0, fs_1.existsSync)(BUDGET_PATH))
|
|
39
|
+
return { ...DEFAULT_CONFIG };
|
|
40
|
+
try {
|
|
41
|
+
const saved = JSON.parse((0, fs_1.readFileSync)(BUDGET_PATH, 'utf-8'));
|
|
42
|
+
return { ...DEFAULT_CONFIG, ...saved };
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return { ...DEFAULT_CONFIG };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function saveBudget(config) {
|
|
49
|
+
(0, fs_1.writeFileSync)(BUDGET_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
50
|
+
}
|
|
51
|
+
function setBudgetLimit(amount) {
|
|
52
|
+
const config = loadBudget();
|
|
53
|
+
config.dailyLimit = amount;
|
|
54
|
+
saveBudget(config);
|
|
55
|
+
}
|
|
56
|
+
function getBudgetLimit() {
|
|
57
|
+
return loadBudget().dailyLimit;
|
|
58
|
+
}
|
|
59
|
+
function getTodaySpend() {
|
|
60
|
+
const stats = (0, history_1.getTodayStats)();
|
|
61
|
+
const config = loadBudget();
|
|
62
|
+
// Use a weighted average estimate per call
|
|
63
|
+
const avgCost = estimateCost('_average', config);
|
|
64
|
+
const calls = stats.allowed + stats.autoAllow;
|
|
65
|
+
return calls * avgCost;
|
|
66
|
+
}
|
|
67
|
+
/** Estimate cost for a specific tool call */
|
|
68
|
+
function estimateToolCost(toolName) {
|
|
69
|
+
const config = loadBudget();
|
|
70
|
+
return estimateCost(toolName, config);
|
|
71
|
+
}
|
|
72
|
+
function isOverBudget() {
|
|
73
|
+
return getTodaySpend() >= getBudgetLimit();
|
|
74
|
+
}
|
|
75
|
+
function getBudgetStatus() {
|
|
76
|
+
const limit = getBudgetLimit();
|
|
77
|
+
const spent = getTodaySpend();
|
|
78
|
+
const stats = (0, history_1.getTodayStats)();
|
|
79
|
+
return {
|
|
80
|
+
limit,
|
|
81
|
+
spent,
|
|
82
|
+
remaining: Math.max(0, limit - spent),
|
|
83
|
+
overBudget: spent >= limit,
|
|
84
|
+
calls: stats.allowed + stats.autoAllow,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function resetTodayBudget() {
|
|
88
|
+
// Budget reset = just info, actual history stays
|
|
89
|
+
// The stats are derived from history which filters by today's date
|
|
90
|
+
// So "reset" means clearing today's history entries
|
|
91
|
+
// For simplicity, we just note the reset — actual cost tracking continues
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../../src/lib/budget.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare function isClaudeRunning(): boolean;
|
|
2
|
+
/**
|
|
3
|
+
* Spawn Claude Code as a managed child process.
|
|
4
|
+
* stdio: 'inherit' so Claude gets a real TTY and runs in interactive mode.
|
|
5
|
+
* SIGINT can be sent via interruptClaude() to stop mid-task.
|
|
6
|
+
* On exit (unless shutting down), auto-restarts with --continue.
|
|
7
|
+
* @param args Extra args passed to `claude` on first launch
|
|
8
|
+
*/
|
|
9
|
+
export declare function startClaude(args?: string[]): void;
|
|
10
|
+
/**
|
|
11
|
+
* Send SIGINT to the running Claude process (equivalent to Ctrl+C).
|
|
12
|
+
* Returns false if Claude is not running.
|
|
13
|
+
*/
|
|
14
|
+
export declare function interruptClaude(): boolean;
|
|
15
|
+
/** Kill Claude process on sentinel shutdown. */
|
|
16
|
+
export declare function stopClaude(): void;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isClaudeRunning = isClaudeRunning;
|
|
4
|
+
exports.startClaude = startClaude;
|
|
5
|
+
exports.interruptClaude = interruptClaude;
|
|
6
|
+
exports.stopClaude = stopClaude;
|
|
7
|
+
// packages/sentinel-cli/src/lib/claude-process.ts
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const logger_1 = require("./logger");
|
|
10
|
+
let child = null;
|
|
11
|
+
let shuttingDown = false;
|
|
12
|
+
let spawnCwd = process.cwd();
|
|
13
|
+
let restartCount = 0;
|
|
14
|
+
let lastExitTime = 0;
|
|
15
|
+
function isClaudeRunning() {
|
|
16
|
+
return child !== null && !child.killed && child.exitCode === null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Spawn Claude Code as a managed child process.
|
|
20
|
+
* stdio: 'inherit' so Claude gets a real TTY and runs in interactive mode.
|
|
21
|
+
* SIGINT can be sent via interruptClaude() to stop mid-task.
|
|
22
|
+
* On exit (unless shutting down), auto-restarts with --continue.
|
|
23
|
+
* @param args Extra args passed to `claude` on first launch
|
|
24
|
+
*/
|
|
25
|
+
function startClaude(args = []) {
|
|
26
|
+
if (isClaudeRunning()) {
|
|
27
|
+
logger_1.log.warn('[claude-process] Already running — ignoring startClaude call');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
shuttingDown = false;
|
|
31
|
+
restartCount = 0;
|
|
32
|
+
lastExitTime = 0;
|
|
33
|
+
spawnCwd = process.cwd();
|
|
34
|
+
spawnClaude(args);
|
|
35
|
+
}
|
|
36
|
+
function spawnClaude(args) {
|
|
37
|
+
(0, logger_1.silenceForClaude)(); // suppress sentinel logs while Claude TUI is active
|
|
38
|
+
logger_1.log.info(`[claude-process] Spawning: claude ${args.join(' ')}`);
|
|
39
|
+
child = (0, child_process_1.spawn)('claude', args, {
|
|
40
|
+
stdio: 'inherit',
|
|
41
|
+
cwd: spawnCwd,
|
|
42
|
+
env: { ...process.env },
|
|
43
|
+
});
|
|
44
|
+
child.on('exit', (code) => {
|
|
45
|
+
(0, logger_1.unsilence)(); // restore terminal logs between Claude sessions
|
|
46
|
+
logger_1.log.info(`[claude-process] Exited with code ${code}`);
|
|
47
|
+
child = null;
|
|
48
|
+
if (!shuttingDown) {
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
const elapsed = now - lastExitTime;
|
|
51
|
+
lastExitTime = now;
|
|
52
|
+
// Reset counter if Claude ran for more than 10 seconds
|
|
53
|
+
if (elapsed > 10_000) {
|
|
54
|
+
restartCount = 0;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
restartCount++;
|
|
58
|
+
}
|
|
59
|
+
// Exponential backoff: 500ms, 1s, 2s, 4s... max 30s
|
|
60
|
+
const delay = Math.min(500 * Math.pow(2, restartCount - 1), 30_000);
|
|
61
|
+
if (restartCount > 1) {
|
|
62
|
+
logger_1.log.warn(`[claude-process] Fast exit detected (${restartCount}x). Restarting in ${delay}ms...`);
|
|
63
|
+
}
|
|
64
|
+
setTimeout(() => {
|
|
65
|
+
if (!shuttingDown) {
|
|
66
|
+
logger_1.log.info('[claude-process] Restarting with --continue...');
|
|
67
|
+
spawnClaude(['--continue']);
|
|
68
|
+
}
|
|
69
|
+
}, delay);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
child.on('error', (err) => {
|
|
73
|
+
logger_1.log.error(`[claude-process] Spawn error: ${err.message}`);
|
|
74
|
+
child = null;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Send SIGINT to the running Claude process (equivalent to Ctrl+C).
|
|
79
|
+
* Returns false if Claude is not running.
|
|
80
|
+
*/
|
|
81
|
+
function interruptClaude() {
|
|
82
|
+
if (!isClaudeRunning()) {
|
|
83
|
+
logger_1.log.warn('[claude-process] interruptClaude called but no process running');
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
logger_1.log.info('[claude-process] Sending SIGINT to Claude');
|
|
87
|
+
child.kill('SIGINT');
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
/** Kill Claude process on sentinel shutdown. */
|
|
91
|
+
function stopClaude() {
|
|
92
|
+
shuttingDown = true;
|
|
93
|
+
if (child && !child.killed) {
|
|
94
|
+
child.kill('SIGTERM');
|
|
95
|
+
child = null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=claude-process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-process.js","sourceRoot":"","sources":["../../src/lib/claude-process.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function daemonStart(mode: string, port: number, serverUrl?: string): void;
|
|
2
|
+
export declare function daemonStop(): void;
|
|
3
|
+
export declare function daemonStatus(): Promise<void>;
|
|
4
|
+
export declare function daemonRestart(mode: string, port: number, serverUrl?: string): void;
|
|
5
|
+
/** Generate macOS launchd plist for auto-start */
|
|
6
|
+
export declare function daemonInstallLaunchd(mode: string, port: number): void;
|