@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.
Files changed (88) hide show
  1. package/dist/__tests__/rules-engine.test.d.ts +1 -0
  2. package/dist/__tests__/rules-engine.test.js +69 -0
  3. package/dist/__tests__/rules-engine.test.js.map +1 -0
  4. package/dist/__tests__/transport-encryption.test.d.ts +1 -0
  5. package/dist/__tests__/transport-encryption.test.js +95 -0
  6. package/dist/__tests__/transport-encryption.test.js.map +1 -0
  7. package/dist/api/client.d.ts +27 -0
  8. package/dist/api/client.js +91 -0
  9. package/dist/api/client.js.map +1 -0
  10. package/dist/cli/bootstrap.d.ts +12 -0
  11. package/dist/cli/bootstrap.js +132 -0
  12. package/dist/cli/bootstrap.js.map +1 -0
  13. package/dist/cli/index.d.ts +2 -0
  14. package/dist/cli/index.js +534 -0
  15. package/dist/cli/index.js.map +1 -0
  16. package/dist/crypto/keys.d.ts +40 -0
  17. package/dist/crypto/keys.js +125 -0
  18. package/dist/crypto/keys.js.map +1 -0
  19. package/dist/crypto/transport-encryption.d.ts +13 -0
  20. package/dist/crypto/transport-encryption.js +62 -0
  21. package/dist/crypto/transport-encryption.js.map +1 -0
  22. package/dist/install/setup.d.ts +2 -0
  23. package/dist/install/setup.js +80 -0
  24. package/dist/install/setup.js.map +1 -0
  25. package/dist/lib/budget.d.ts +14 -0
  26. package/dist/lib/budget.js +93 -0
  27. package/dist/lib/budget.js.map +1 -0
  28. package/dist/lib/claude-process.d.ts +16 -0
  29. package/dist/lib/claude-process.js +98 -0
  30. package/dist/lib/claude-process.js.map +1 -0
  31. package/dist/lib/daemon.d.ts +6 -0
  32. package/dist/lib/daemon.js +218 -0
  33. package/dist/lib/daemon.js.map +1 -0
  34. package/dist/lib/diff.d.ts +5 -0
  35. package/dist/lib/diff.js +85 -0
  36. package/dist/lib/diff.js.map +1 -0
  37. package/dist/lib/doctor.d.ts +1 -0
  38. package/dist/lib/doctor.js +108 -0
  39. package/dist/lib/doctor.js.map +1 -0
  40. package/dist/lib/history.d.ts +22 -0
  41. package/dist/lib/history.js +62 -0
  42. package/dist/lib/history.js.map +1 -0
  43. package/dist/lib/logger.d.ts +11 -0
  44. package/dist/lib/logger.js +62 -0
  45. package/dist/lib/logger.js.map +1 -0
  46. package/dist/lib/modes.d.ts +22 -0
  47. package/dist/lib/modes.js +58 -0
  48. package/dist/lib/modes.js.map +1 -0
  49. package/dist/lib/overrides.d.ts +13 -0
  50. package/dist/lib/overrides.js +74 -0
  51. package/dist/lib/overrides.js.map +1 -0
  52. package/dist/lib/session.d.ts +32 -0
  53. package/dist/lib/session.js +68 -0
  54. package/dist/lib/session.js.map +1 -0
  55. package/dist/lib/summarizer.d.ts +5 -0
  56. package/dist/lib/summarizer.js +46 -0
  57. package/dist/lib/summarizer.js.map +1 -0
  58. package/dist/lib/tunnel.d.ts +2 -0
  59. package/dist/lib/tunnel.js +48 -0
  60. package/dist/lib/tunnel.js.map +1 -0
  61. package/dist/relay/pending.d.ts +27 -0
  62. package/dist/relay/pending.js +65 -0
  63. package/dist/relay/pending.js.map +1 -0
  64. package/dist/rules/engine.d.ts +31 -0
  65. package/dist/rules/engine.js +203 -0
  66. package/dist/rules/engine.js.map +1 -0
  67. package/dist/server/http.d.ts +8 -0
  68. package/dist/server/http.js +359 -0
  69. package/dist/server/http.js.map +1 -0
  70. package/dist/socket/client.d.ts +16 -0
  71. package/dist/socket/client.js +81 -0
  72. package/dist/socket/client.js.map +1 -0
  73. package/dist/transport/cloudkit.d.ts +30 -0
  74. package/dist/transport/cloudkit.js +162 -0
  75. package/dist/transport/cloudkit.js.map +1 -0
  76. package/dist/transport/factory.d.ts +6 -0
  77. package/dist/transport/factory.js +20 -0
  78. package/dist/transport/factory.js.map +1 -0
  79. package/dist/transport/interface.d.ts +25 -0
  80. package/dist/transport/interface.js +13 -0
  81. package/dist/transport/interface.js.map +1 -0
  82. package/dist/transport/local.d.ts +46 -0
  83. package/dist/transport/local.js +320 -0
  84. package/dist/transport/local.js.map +1 -0
  85. package/dist/transport/remote.d.ts +17 -0
  86. package/dist/transport/remote.js +83 -0
  87. package/dist/transport/remote.js.map +1 -0
  88. 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,2 @@
1
+ export declare function installHook(port?: number): void;
2
+ export declare function uninstallHook(port?: number): void;
@@ -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;