@ekkos/cli 1.3.1 → 1.3.2

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 (82) hide show
  1. package/dist/commands/dashboard.js +147 -57
  2. package/dist/commands/init.d.ts +1 -0
  3. package/dist/commands/init.js +54 -16
  4. package/dist/commands/run.js +163 -44
  5. package/dist/commands/status.d.ts +4 -1
  6. package/dist/commands/status.js +165 -27
  7. package/dist/commands/synk.d.ts +7 -0
  8. package/dist/commands/synk.js +339 -0
  9. package/dist/deploy/settings.d.ts +6 -5
  10. package/dist/deploy/settings.js +27 -17
  11. package/dist/index.js +12 -82
  12. package/dist/lib/usage-parser.d.ts +1 -1
  13. package/dist/lib/usage-parser.js +5 -3
  14. package/dist/local/index.d.ts +14 -0
  15. package/dist/local/index.js +28 -0
  16. package/dist/local/local-embeddings.d.ts +49 -0
  17. package/dist/local/local-embeddings.js +232 -0
  18. package/dist/local/offline-fallback.d.ts +44 -0
  19. package/dist/local/offline-fallback.js +159 -0
  20. package/dist/local/sqlite-store.d.ts +126 -0
  21. package/dist/local/sqlite-store.js +393 -0
  22. package/dist/local/sync-engine.d.ts +42 -0
  23. package/dist/local/sync-engine.js +223 -0
  24. package/dist/synk/api.d.ts +22 -0
  25. package/dist/synk/api.js +133 -0
  26. package/dist/synk/auth.d.ts +7 -0
  27. package/dist/synk/auth.js +30 -0
  28. package/dist/synk/config.d.ts +18 -0
  29. package/dist/synk/config.js +37 -0
  30. package/dist/synk/daemon/control-client.d.ts +11 -0
  31. package/dist/synk/daemon/control-client.js +101 -0
  32. package/dist/synk/daemon/control-server.d.ts +24 -0
  33. package/dist/synk/daemon/control-server.js +91 -0
  34. package/dist/synk/daemon/run.d.ts +14 -0
  35. package/dist/synk/daemon/run.js +338 -0
  36. package/dist/synk/encryption.d.ts +17 -0
  37. package/dist/synk/encryption.js +133 -0
  38. package/dist/synk/index.d.ts +13 -0
  39. package/dist/synk/index.js +36 -0
  40. package/dist/synk/machine-client.d.ts +42 -0
  41. package/dist/synk/machine-client.js +218 -0
  42. package/dist/synk/persistence.d.ts +51 -0
  43. package/dist/synk/persistence.js +211 -0
  44. package/dist/synk/qr.d.ts +5 -0
  45. package/dist/synk/qr.js +33 -0
  46. package/dist/synk/session-bridge.d.ts +58 -0
  47. package/dist/synk/session-bridge.js +171 -0
  48. package/dist/synk/session-client.d.ts +46 -0
  49. package/dist/synk/session-client.js +240 -0
  50. package/dist/synk/types.d.ts +574 -0
  51. package/dist/synk/types.js +74 -0
  52. package/dist/utils/platform.d.ts +5 -1
  53. package/dist/utils/platform.js +24 -4
  54. package/dist/utils/proxy-url.d.ts +10 -0
  55. package/dist/utils/proxy-url.js +19 -0
  56. package/dist/utils/state.d.ts +1 -1
  57. package/dist/utils/state.js +11 -3
  58. package/package.json +13 -4
  59. package/templates/claude-plugins-admin/AGENT_TEAM_PROPOSALS.md +0 -819
  60. package/templates/claude-plugins-admin/README.md +0 -446
  61. package/templates/claude-plugins-admin/autonomous-admin-agent/.claude-plugin/plugin.json +0 -8
  62. package/templates/claude-plugins-admin/autonomous-admin-agent/commands/agent.md +0 -595
  63. package/templates/claude-plugins-admin/backend-agent/.claude-plugin/plugin.json +0 -8
  64. package/templates/claude-plugins-admin/backend-agent/commands/backend.md +0 -798
  65. package/templates/claude-plugins-admin/deploy-guardian/.claude-plugin/plugin.json +0 -8
  66. package/templates/claude-plugins-admin/deploy-guardian/commands/deploy.md +0 -554
  67. package/templates/claude-plugins-admin/frontend-agent/.claude-plugin/plugin.json +0 -8
  68. package/templates/claude-plugins-admin/frontend-agent/commands/frontend.md +0 -881
  69. package/templates/claude-plugins-admin/mcp-server-manager/.claude-plugin/plugin.json +0 -8
  70. package/templates/claude-plugins-admin/mcp-server-manager/commands/mcp.md +0 -85
  71. package/templates/claude-plugins-admin/memory-system-monitor/.claude-plugin/plugin.json +0 -8
  72. package/templates/claude-plugins-admin/memory-system-monitor/commands/memory-health.md +0 -569
  73. package/templates/claude-plugins-admin/qa-agent/.claude-plugin/plugin.json +0 -8
  74. package/templates/claude-plugins-admin/qa-agent/commands/qa.md +0 -863
  75. package/templates/claude-plugins-admin/tech-lead-agent/.claude-plugin/plugin.json +0 -8
  76. package/templates/claude-plugins-admin/tech-lead-agent/commands/lead.md +0 -732
  77. package/templates/hooks-node/lib/state.js +0 -187
  78. package/templates/hooks-node/stop.js +0 -416
  79. package/templates/hooks-node/user-prompt-submit.js +0 -337
  80. package/templates/rules/00-hooks-contract.mdc +0 -89
  81. package/templates/rules/30-ekkos-core.mdc +0 -188
  82. package/templates/rules/31-ekkos-messages.mdc +0 -78
@@ -0,0 +1,338 @@
1
+ "use strict";
2
+ /**
3
+ * Synk daemon — background process for session management and remote control
4
+ *
5
+ * Lifecycle:
6
+ * 1. Acquire lock file (prevent multiple daemons)
7
+ * 2. Authenticate with synk-server
8
+ * 3. Start local HTTP control server
9
+ * 4. Register machine via REST API
10
+ * 5. Connect MachineClient WebSocket
11
+ * 6. Heartbeat loop
12
+ * 7. Await shutdown signal
13
+ * 8. Cleanup
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.startDaemon = startDaemon;
17
+ const node_os_1 = require("node:os");
18
+ const node_path_1 = require("node:path");
19
+ const node_fs_1 = require("node:fs");
20
+ const node_child_process_1 = require("node:child_process");
21
+ const node_crypto_1 = require("node:crypto");
22
+ const config_1 = require("../config");
23
+ const persistence_1 = require("../persistence");
24
+ const api_1 = require("../api");
25
+ const machine_client_1 = require("../machine-client");
26
+ const control_server_1 = require("./control-server");
27
+ const control_client_1 = require("./control-client");
28
+ const proxy_url_1 = require("../../utils/proxy-url");
29
+ const state_1 = require("../../utils/state");
30
+ const HEARTBEAT_INTERVAL = parseInt(process.env.SYNK_DAEMON_HEARTBEAT_INTERVAL || '60000');
31
+ let logStream = null;
32
+ function getCliVersion() {
33
+ try {
34
+ const pkgPath = (0, node_path_1.resolve)(__dirname, '../../../package.json');
35
+ const pkg = JSON.parse((0, node_fs_1.readFileSync)(pkgPath, 'utf-8'));
36
+ return pkg.version || '0.0.0';
37
+ }
38
+ catch {
39
+ return '0.0.0';
40
+ }
41
+ }
42
+ function daemonLog(message, ...args) {
43
+ const line = `[${new Date().toISOString()}] ${message} ${args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ')}`;
44
+ if (logStream)
45
+ logStream.write(line + '\n');
46
+ if (!config_1.synkConfig.isDaemonProcess)
47
+ console.log(line);
48
+ }
49
+ async function startDaemon() {
50
+ config_1.synkConfig.ensureDirectories();
51
+ const cliVersion = getCliVersion();
52
+ // Setup log file
53
+ const logPath = (0, node_path_1.join)(config_1.synkConfig.logsDir, `${new Date().toISOString().replace(/[:.]/g, '-')}-daemon.log`);
54
+ if (!(0, node_fs_1.existsSync)(config_1.synkConfig.logsDir))
55
+ (0, node_fs_1.mkdirSync)(config_1.synkConfig.logsDir, { recursive: true });
56
+ logStream = (0, node_fs_1.createWriteStream)(logPath, { flags: 'a' });
57
+ daemonLog(`Starting synk daemon v${cliVersion}`);
58
+ // Check if daemon already running
59
+ const existingState = await (0, persistence_1.readDaemonState)();
60
+ if (existingState) {
61
+ try {
62
+ process.kill(existingState.pid, 0);
63
+ daemonLog('Daemon already running, stopping old instance');
64
+ await (0, control_client_1.stopDaemon)();
65
+ }
66
+ catch {
67
+ daemonLog('Stale daemon state, cleaning up');
68
+ await (0, persistence_1.clearDaemonState)();
69
+ }
70
+ }
71
+ // Acquire lock
72
+ const lockHandle = await (0, persistence_1.acquireDaemonLock)();
73
+ if (!lockHandle) {
74
+ daemonLog('Failed to acquire daemon lock — another daemon may be running');
75
+ process.exit(1);
76
+ }
77
+ // Authenticate
78
+ const credentials = await (0, persistence_1.readCredentials)();
79
+ if (!credentials) {
80
+ daemonLog('No credentials found. Run "ekkos synk auth" first.');
81
+ await (0, persistence_1.releaseDaemonLock)(lockHandle);
82
+ process.exit(1);
83
+ }
84
+ // Get or create machine ID
85
+ const settings = await (0, persistence_1.readSettings)();
86
+ let machineId = settings.machineId;
87
+ if (!machineId) {
88
+ machineId = (0, node_crypto_1.randomUUID)();
89
+ await (0, persistence_1.updateSettings)(s => ({ ...s, machineId }));
90
+ daemonLog('Generated new machine ID:', machineId);
91
+ }
92
+ // Session tracking — keyed by sessionId (not PID) to avoid overwrite bugs
93
+ const trackedSessions = new Map();
94
+ let sessionCounter = 0;
95
+ // Shutdown handling
96
+ let shutdownRequested = false;
97
+ let resolveShutdown;
98
+ const shutdownPromise = new Promise(resolve => { resolveShutdown = resolve; });
99
+ const requestShutdown = () => {
100
+ if (shutdownRequested)
101
+ return;
102
+ shutdownRequested = true;
103
+ daemonLog('Shutdown requested');
104
+ resolveShutdown();
105
+ };
106
+ process.on('SIGINT', requestShutdown);
107
+ process.on('SIGTERM', requestShutdown);
108
+ process.on('uncaughtException', (error) => {
109
+ daemonLog('Uncaught exception:', error);
110
+ requestShutdown();
111
+ });
112
+ // --- Spawn session implementation ---
113
+ const spawnSession = async (options) => {
114
+ const { directory } = options;
115
+ daemonLog('Spawning session in:', directory);
116
+ if (!(0, node_fs_1.existsSync)(directory)) {
117
+ return { type: 'error', errorMessage: `Directory does not exist: ${directory}` };
118
+ }
119
+ // Generate session identity
120
+ const sessionId = options.sessionId || (0, node_crypto_1.randomUUID)();
121
+ const sessionName = (0, state_1.uuidToWords)(sessionId);
122
+ // Build proxy env vars (unless disabled)
123
+ const proxyEnv = {};
124
+ if (process.env.SYNK_DISABLE_PROXY !== '1') {
125
+ const ekkosConfig = (0, state_1.getConfig)();
126
+ let userId = ekkosConfig?.userId || 'anonymous';
127
+ if (userId === 'anonymous') {
128
+ const authToken = (0, state_1.getAuthToken)();
129
+ if (authToken?.startsWith('ekk_')) {
130
+ const parts = authToken.split('_');
131
+ if (parts.length >= 2)
132
+ userId = parts[1];
133
+ }
134
+ }
135
+ proxyEnv.ANTHROPIC_BASE_URL = (0, proxy_url_1.buildProxyUrl)(userId, sessionName, directory, sessionId);
136
+ proxyEnv.EKKOS_PROXY_MODE = '1';
137
+ proxyEnv.EKKOS_ULTRA_MINIMAL = '1';
138
+ }
139
+ // Merge env vars: process.env + options.environmentVariables + proxy (highest priority)
140
+ const childEnv = {
141
+ ...process.env,
142
+ ...(options.environmentVariables || {}),
143
+ ...proxyEnv,
144
+ };
145
+ try {
146
+ // Create log file for spawned session
147
+ const sessionLogPath = (0, node_path_1.join)(config_1.synkConfig.logsDir, `${new Date().toISOString().replace(/[:.]/g, '-')}-session-${sessionName}.log`);
148
+ const sessionLogFd = (0, node_fs_1.createWriteStream)(sessionLogPath, { flags: 'a' });
149
+ const child = (0, node_child_process_1.spawn)('ekkos', ['run', '--started-by', 'daemon'], {
150
+ cwd: directory,
151
+ env: childEnv,
152
+ detached: true,
153
+ stdio: ['ignore', sessionLogFd, sessionLogFd],
154
+ });
155
+ child.unref();
156
+ const trackKey = `spawn-${++sessionCounter}`;
157
+ const tracked = {
158
+ pid: child.pid || 0,
159
+ startedBy: 'daemon',
160
+ startedAt: Date.now(),
161
+ };
162
+ trackedSessions.set(trackKey, tracked);
163
+ // Wait for session to report itself via webhook (max 15s)
164
+ const webhookSessionId = await new Promise((resolve) => {
165
+ const timeout = setTimeout(() => resolve(null), 15000);
166
+ const checkInterval = setInterval(() => {
167
+ if (tracked.synkSessionId) {
168
+ clearInterval(checkInterval);
169
+ clearTimeout(timeout);
170
+ resolve(tracked.synkSessionId);
171
+ }
172
+ }, 200);
173
+ });
174
+ if (webhookSessionId) {
175
+ // Re-key tracked session by its actual session ID
176
+ trackedSessions.delete(trackKey);
177
+ trackedSessions.set(webhookSessionId, tracked);
178
+ daemonLog('Session spawned and registered:', webhookSessionId);
179
+ return { type: 'success', sessionId: webhookSessionId };
180
+ }
181
+ else {
182
+ daemonLog('Session spawned but webhook timeout — PID:', child.pid);
183
+ return { type: 'success', sessionId: `pending-${child.pid}` };
184
+ }
185
+ }
186
+ catch (error) {
187
+ daemonLog('Failed to spawn session:', error);
188
+ return { type: 'error', errorMessage: error instanceof Error ? error.message : 'Failed to spawn session' };
189
+ }
190
+ };
191
+ // Start HTTP control server
192
+ const controlServer = await (0, control_server_1.startDaemonControlServer)({
193
+ getChildren: () => Array.from(trackedSessions.values()),
194
+ stopSession: (sessionId) => {
195
+ for (const [key, session] of trackedSessions) {
196
+ if (session.synkSessionId === sessionId || key === sessionId) {
197
+ try {
198
+ process.kill(session.pid, 'SIGTERM');
199
+ }
200
+ catch { }
201
+ return true;
202
+ }
203
+ }
204
+ return false;
205
+ },
206
+ spawnSession: async (options) => {
207
+ const result = await spawnSession(options);
208
+ if (result.type === 'success')
209
+ return { success: true, sessionId: result.sessionId };
210
+ return { success: false, error: result.errorMessage };
211
+ },
212
+ requestShutdown,
213
+ onSessionWebhook: (sessionId, metadata) => {
214
+ daemonLog('Session webhook:', sessionId);
215
+ // Find tracked session by PID from metadata
216
+ const hostPid = metadata?.hostPid;
217
+ let matched = false;
218
+ if (hostPid) {
219
+ for (const [, session] of trackedSessions) {
220
+ if (session.pid === hostPid && !session.synkSessionId) {
221
+ session.synkSessionId = sessionId;
222
+ matched = true;
223
+ break;
224
+ }
225
+ }
226
+ }
227
+ if (!matched) {
228
+ // Terminal-spawned session reporting itself
229
+ trackedSessions.set(sessionId, {
230
+ pid: hostPid || process.pid,
231
+ startedBy: 'terminal',
232
+ synkSessionId: sessionId,
233
+ startedAt: Date.now(),
234
+ });
235
+ }
236
+ },
237
+ });
238
+ // Write daemon state
239
+ (0, persistence_1.writeDaemonState)({
240
+ pid: process.pid,
241
+ httpPort: controlServer.port,
242
+ startTime: new Date().toLocaleString(),
243
+ startedWithCliVersion: cliVersion,
244
+ daemonLogPath: logPath,
245
+ });
246
+ daemonLog(`Control server on port ${controlServer.port}`);
247
+ // Register machine with synk-server via REST
248
+ const machineMetadata = {
249
+ host: (0, node_os_1.hostname)(),
250
+ platform: (0, node_os_1.platform)(),
251
+ synkCliVersion: cliVersion,
252
+ homeDir: (0, node_os_1.homedir)(),
253
+ synkHomeDir: config_1.synkConfig.synkHomeDir,
254
+ };
255
+ const initialDaemonState = {
256
+ status: 'starting',
257
+ pid: process.pid,
258
+ httpPort: controlServer.port,
259
+ startedAt: Date.now(),
260
+ };
261
+ const apiClient = await api_1.ApiClient.create(credentials);
262
+ const machine = await apiClient.getOrCreateMachine({
263
+ machineId,
264
+ metadata: machineMetadata,
265
+ daemonState: initialDaemonState,
266
+ });
267
+ daemonLog('Machine registered:', machine.id);
268
+ // Connect WebSocket via MachineClient
269
+ const machineClient = new machine_client_1.MachineClient(credentials.token, machine, daemonLog);
270
+ machineClient.setRPCHandlers({
271
+ spawnSession,
272
+ stopSession: (sessionId) => {
273
+ for (const [key, session] of trackedSessions) {
274
+ if (session.synkSessionId === sessionId || key === sessionId) {
275
+ try {
276
+ process.kill(session.pid, 'SIGTERM');
277
+ }
278
+ catch { }
279
+ return true;
280
+ }
281
+ }
282
+ return false;
283
+ },
284
+ requestShutdown,
285
+ });
286
+ machineClient.connect();
287
+ // Heartbeat loop
288
+ const heartbeatInterval = setInterval(async () => {
289
+ if (shutdownRequested)
290
+ return;
291
+ // Update last heartbeat
292
+ try {
293
+ const state = await (0, persistence_1.readDaemonState)();
294
+ if (state) {
295
+ (0, persistence_1.writeDaemonState)({ ...state, lastHeartbeat: new Date().toLocaleString() });
296
+ }
297
+ }
298
+ catch { }
299
+ // Prune dead sessions
300
+ for (const [key, session] of trackedSessions) {
301
+ if (session.pid === 0 || session.pid === process.pid)
302
+ continue;
303
+ try {
304
+ process.kill(session.pid, 0);
305
+ }
306
+ catch {
307
+ daemonLog('Pruning dead session:', session.synkSessionId || key);
308
+ trackedSessions.delete(key);
309
+ }
310
+ }
311
+ }, HEARTBEAT_INTERVAL);
312
+ daemonLog('Daemon ready. Waiting for shutdown signal...');
313
+ // Wait for shutdown
314
+ await shutdownPromise;
315
+ daemonLog('Shutting down...');
316
+ // Cleanup
317
+ clearInterval(heartbeatInterval);
318
+ // Update daemon state on server via MachineClient
319
+ try {
320
+ await machineClient.updateDaemonState(() => ({
321
+ status: 'shutting-down',
322
+ shutdownRequestedAt: Date.now(),
323
+ }));
324
+ }
325
+ catch { }
326
+ // Close WebSocket
327
+ machineClient.shutdown();
328
+ // Stop control server
329
+ await controlServer.stop();
330
+ // Clear daemon state file
331
+ await (0, persistence_1.clearDaemonState)();
332
+ // Release lock
333
+ await (0, persistence_1.releaseDaemonLock)(lockHandle);
334
+ if (logStream)
335
+ logStream.end();
336
+ daemonLog('Daemon stopped cleanly');
337
+ process.exit(0);
338
+ }
@@ -0,0 +1,17 @@
1
+ export declare function encodeBase64(buffer: Uint8Array, variant?: 'base64' | 'base64url'): string;
2
+ export declare function encodeBase64Url(buffer: Uint8Array): string;
3
+ export declare function decodeBase64(base64: string, variant?: 'base64' | 'base64url'): Uint8Array;
4
+ export declare function getRandomBytes(size: number): Uint8Array;
5
+ export declare function libsodiumPublicKeyFromSecretKey(seed: Uint8Array): Uint8Array;
6
+ export declare function libsodiumEncryptForPublicKey(data: Uint8Array, recipientPublicKey: Uint8Array): Uint8Array;
7
+ export declare function encryptLegacy(data: any, secret: Uint8Array): Uint8Array;
8
+ export declare function decryptLegacy(data: Uint8Array, secret: Uint8Array): any | null;
9
+ export declare function encryptWithDataKey(data: any, dataKey: Uint8Array): Uint8Array;
10
+ export declare function decryptWithDataKey(bundle: Uint8Array, dataKey: Uint8Array): any | null;
11
+ export declare function encrypt(key: Uint8Array, variant: 'legacy' | 'dataKey', data: any): Uint8Array;
12
+ export declare function decrypt(key: Uint8Array, variant: 'legacy' | 'dataKey', data: Uint8Array): any | null;
13
+ export declare function authChallenge(secret: Uint8Array): {
14
+ challenge: Uint8Array;
15
+ publicKey: Uint8Array;
16
+ signature: Uint8Array;
17
+ };
@@ -0,0 +1,133 @@
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.encodeBase64 = encodeBase64;
7
+ exports.encodeBase64Url = encodeBase64Url;
8
+ exports.decodeBase64 = decodeBase64;
9
+ exports.getRandomBytes = getRandomBytes;
10
+ exports.libsodiumPublicKeyFromSecretKey = libsodiumPublicKeyFromSecretKey;
11
+ exports.libsodiumEncryptForPublicKey = libsodiumEncryptForPublicKey;
12
+ exports.encryptLegacy = encryptLegacy;
13
+ exports.decryptLegacy = decryptLegacy;
14
+ exports.encryptWithDataKey = encryptWithDataKey;
15
+ exports.decryptWithDataKey = decryptWithDataKey;
16
+ exports.encrypt = encrypt;
17
+ exports.decrypt = decrypt;
18
+ exports.authChallenge = authChallenge;
19
+ const node_crypto_1 = require("node:crypto");
20
+ const tweetnacl_1 = __importDefault(require("tweetnacl"));
21
+ function encodeBase64(buffer, variant = 'base64') {
22
+ if (variant === 'base64url') {
23
+ return encodeBase64Url(buffer);
24
+ }
25
+ return Buffer.from(buffer).toString('base64');
26
+ }
27
+ function encodeBase64Url(buffer) {
28
+ return Buffer.from(buffer)
29
+ .toString('base64')
30
+ .replace(/\+/g, '-')
31
+ .replace(/\//g, '_')
32
+ .replace(/=/g, '');
33
+ }
34
+ function decodeBase64(base64, variant = 'base64') {
35
+ if (variant === 'base64url') {
36
+ const base64Standard = base64
37
+ .replace(/-/g, '+')
38
+ .replace(/_/g, '/')
39
+ + '='.repeat((4 - base64.length % 4) % 4);
40
+ return new Uint8Array(Buffer.from(base64Standard, 'base64'));
41
+ }
42
+ return new Uint8Array(Buffer.from(base64, 'base64'));
43
+ }
44
+ function getRandomBytes(size) {
45
+ return new Uint8Array((0, node_crypto_1.randomBytes)(size));
46
+ }
47
+ function libsodiumPublicKeyFromSecretKey(seed) {
48
+ const hashedSeed = new Uint8Array((0, node_crypto_1.createHash)('sha512').update(seed).digest());
49
+ const secretKey = hashedSeed.slice(0, 32);
50
+ return new Uint8Array(tweetnacl_1.default.box.keyPair.fromSecretKey(secretKey).publicKey);
51
+ }
52
+ function libsodiumEncryptForPublicKey(data, recipientPublicKey) {
53
+ const ephemeralKeyPair = tweetnacl_1.default.box.keyPair();
54
+ const nonce = getRandomBytes(tweetnacl_1.default.box.nonceLength);
55
+ const encrypted = tweetnacl_1.default.box(data, nonce, recipientPublicKey, ephemeralKeyPair.secretKey);
56
+ const result = new Uint8Array(ephemeralKeyPair.publicKey.length + nonce.length + encrypted.length);
57
+ result.set(ephemeralKeyPair.publicKey, 0);
58
+ result.set(nonce, ephemeralKeyPair.publicKey.length);
59
+ result.set(encrypted, ephemeralKeyPair.publicKey.length + nonce.length);
60
+ return result;
61
+ }
62
+ function encryptLegacy(data, secret) {
63
+ const nonce = getRandomBytes(tweetnacl_1.default.secretbox.nonceLength);
64
+ const encrypted = tweetnacl_1.default.secretbox(new TextEncoder().encode(JSON.stringify(data)), nonce, secret);
65
+ const result = new Uint8Array(nonce.length + encrypted.length);
66
+ result.set(nonce);
67
+ result.set(encrypted, nonce.length);
68
+ return result;
69
+ }
70
+ function decryptLegacy(data, secret) {
71
+ const nonce = data.slice(0, tweetnacl_1.default.secretbox.nonceLength);
72
+ const encrypted = data.slice(tweetnacl_1.default.secretbox.nonceLength);
73
+ const decrypted = tweetnacl_1.default.secretbox.open(encrypted, nonce, secret);
74
+ if (!decrypted) {
75
+ return null;
76
+ }
77
+ return JSON.parse(new TextDecoder().decode(decrypted));
78
+ }
79
+ function encryptWithDataKey(data, dataKey) {
80
+ const nonce = getRandomBytes(12);
81
+ const cipher = (0, node_crypto_1.createCipheriv)('aes-256-gcm', dataKey, nonce);
82
+ const plaintext = new TextEncoder().encode(JSON.stringify(data));
83
+ const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
84
+ const authTag = cipher.getAuthTag();
85
+ const bundle = new Uint8Array(12 + encrypted.length + 16 + 1);
86
+ bundle.set([0], 0);
87
+ bundle.set(nonce, 1);
88
+ bundle.set(new Uint8Array(encrypted), 13);
89
+ bundle.set(new Uint8Array(authTag), 13 + encrypted.length);
90
+ return bundle;
91
+ }
92
+ function decryptWithDataKey(bundle, dataKey) {
93
+ if (bundle.length < 1)
94
+ return null;
95
+ if (bundle[0] !== 0)
96
+ return null;
97
+ if (bundle.length < 12 + 16 + 1)
98
+ return null;
99
+ const nonce = bundle.slice(1, 13);
100
+ const authTag = bundle.slice(bundle.length - 16);
101
+ const ciphertext = bundle.slice(13, bundle.length - 16);
102
+ try {
103
+ const decipher = (0, node_crypto_1.createDecipheriv)('aes-256-gcm', dataKey, nonce);
104
+ decipher.setAuthTag(authTag);
105
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
106
+ return JSON.parse(new TextDecoder().decode(decrypted));
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ }
112
+ function encrypt(key, variant, data) {
113
+ if (variant === 'legacy') {
114
+ return encryptLegacy(data, key);
115
+ }
116
+ else {
117
+ return encryptWithDataKey(data, key);
118
+ }
119
+ }
120
+ function decrypt(key, variant, data) {
121
+ if (variant === 'legacy') {
122
+ return decryptLegacy(data, key);
123
+ }
124
+ else {
125
+ return decryptWithDataKey(data, key);
126
+ }
127
+ }
128
+ function authChallenge(secret) {
129
+ const keypair = tweetnacl_1.default.sign.keyPair.fromSeed(secret);
130
+ const challenge = getRandomBytes(32);
131
+ const signature = tweetnacl_1.default.sign.detached(challenge, keypair.secretKey);
132
+ return { challenge, publicKey: keypair.publicKey, signature };
133
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * ekkOS_synk — Remote session sync for Claude Code
3
+ */
4
+ export { synkConfig } from './config';
5
+ export * from './encryption';
6
+ export * from './auth';
7
+ export * from './persistence';
8
+ export * from './types';
9
+ export { displayQRCode } from './qr';
10
+ export { ApiClient } from './api';
11
+ export { SessionClient } from './session-client';
12
+ export { MachineClient } from './machine-client';
13
+ export { SynkSessionBridge } from './session-bridge';
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ /**
3
+ * ekkOS_synk — Remote session sync for Claude Code
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
17
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.SynkSessionBridge = exports.MachineClient = exports.SessionClient = exports.ApiClient = exports.displayQRCode = exports.synkConfig = void 0;
21
+ var config_1 = require("./config");
22
+ Object.defineProperty(exports, "synkConfig", { enumerable: true, get: function () { return config_1.synkConfig; } });
23
+ __exportStar(require("./encryption"), exports);
24
+ __exportStar(require("./auth"), exports);
25
+ __exportStar(require("./persistence"), exports);
26
+ __exportStar(require("./types"), exports);
27
+ var qr_1 = require("./qr");
28
+ Object.defineProperty(exports, "displayQRCode", { enumerable: true, get: function () { return qr_1.displayQRCode; } });
29
+ var api_1 = require("./api");
30
+ Object.defineProperty(exports, "ApiClient", { enumerable: true, get: function () { return api_1.ApiClient; } });
31
+ var session_client_1 = require("./session-client");
32
+ Object.defineProperty(exports, "SessionClient", { enumerable: true, get: function () { return session_client_1.SessionClient; } });
33
+ var machine_client_1 = require("./machine-client");
34
+ Object.defineProperty(exports, "MachineClient", { enumerable: true, get: function () { return machine_client_1.MachineClient; } });
35
+ var session_bridge_1 = require("./session-bridge");
36
+ Object.defineProperty(exports, "SynkSessionBridge", { enumerable: true, get: function () { return session_bridge_1.SynkSessionBridge; } });
@@ -0,0 +1,42 @@
1
+ /**
2
+ * WebSocket client for machine/daemon communication with synk-server
3
+ * Manages keep-alive heartbeat, daemon state updates, and RPC handler registration
4
+ *
5
+ * This is SEPARATE from SessionClient — SessionClient is per-session, MachineClient is per-daemon.
6
+ */
7
+ import type { MachineMetadata, DaemonState, Machine } from './types';
8
+ export interface SpawnSessionOptions {
9
+ machineId?: string;
10
+ directory: string;
11
+ sessionId?: string;
12
+ environmentVariables?: Record<string, string>;
13
+ }
14
+ export type SpawnSessionResult = {
15
+ type: 'success';
16
+ sessionId: string;
17
+ } | {
18
+ type: 'error';
19
+ errorMessage: string;
20
+ };
21
+ interface MachineRpcHandlers {
22
+ spawnSession: (options: SpawnSessionOptions) => Promise<SpawnSessionResult>;
23
+ stopSession: (sessionId: string) => boolean;
24
+ requestShutdown: () => void;
25
+ }
26
+ export declare class MachineClient {
27
+ private readonly token;
28
+ private readonly machine;
29
+ private socket;
30
+ private keepAliveInterval;
31
+ private rpcHandlerManager;
32
+ private log;
33
+ constructor(token: string, machine: Machine, logger?: (msg: string, ...args: any[]) => void);
34
+ setRPCHandlers({ spawnSession, stopSession, requestShutdown }: MachineRpcHandlers): void;
35
+ updateMachineMetadata(handler: (metadata: MachineMetadata | null) => MachineMetadata): Promise<void>;
36
+ updateDaemonState(handler: (state: DaemonState | null) => DaemonState): Promise<void>;
37
+ connect(): void;
38
+ private startKeepAlive;
39
+ private stopKeepAlive;
40
+ shutdown(): void;
41
+ }
42
+ export {};