@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,133 @@
1
+ "use strict";
2
+ /**
3
+ * API client for ekkOS_synk server
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.ApiClient = void 0;
10
+ const axios_1 = __importDefault(require("axios"));
11
+ const encryption_1 = require("./encryption");
12
+ const config_1 = require("./config");
13
+ class ApiClient {
14
+ static async create(credential) {
15
+ return new ApiClient(credential);
16
+ }
17
+ constructor(credential) {
18
+ this.credential = credential;
19
+ }
20
+ /** Create a new session or load existing one with the given tag */
21
+ async getOrCreateSession(opts) {
22
+ let dataEncryptionKey = null;
23
+ let encryptionKey;
24
+ let encryptionVariant;
25
+ if (this.credential.encryption.type === 'dataKey') {
26
+ encryptionKey = (0, encryption_1.getRandomBytes)(32);
27
+ encryptionVariant = 'dataKey';
28
+ const encryptedDataKey = (0, encryption_1.libsodiumEncryptForPublicKey)(encryptionKey, this.credential.encryption.publicKey);
29
+ dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
30
+ dataEncryptionKey.set([0], 0);
31
+ dataEncryptionKey.set(encryptedDataKey, 1);
32
+ }
33
+ else {
34
+ encryptionKey = this.credential.encryption.secret;
35
+ encryptionVariant = 'legacy';
36
+ }
37
+ try {
38
+ const response = await axios_1.default.post(`${config_1.synkConfig.serverUrl}/v1/sessions`, {
39
+ tag: opts.tag,
40
+ metadata: (0, encryption_1.encodeBase64)((0, encryption_1.encrypt)(encryptionKey, encryptionVariant, opts.metadata)),
41
+ agentState: opts.state ? (0, encryption_1.encodeBase64)((0, encryption_1.encrypt)(encryptionKey, encryptionVariant, opts.state)) : null,
42
+ dataEncryptionKey: dataEncryptionKey ? (0, encryption_1.encodeBase64)(dataEncryptionKey) : null,
43
+ }, {
44
+ headers: {
45
+ 'Authorization': `Bearer ${this.credential.token}`,
46
+ 'Content-Type': 'application/json',
47
+ },
48
+ timeout: 60000,
49
+ });
50
+ const raw = response.data.session;
51
+ return {
52
+ id: raw.id,
53
+ seq: raw.seq,
54
+ metadata: (0, encryption_1.decrypt)(encryptionKey, encryptionVariant, (0, encryption_1.decodeBase64)(raw.metadata)),
55
+ metadataVersion: raw.metadataVersion,
56
+ agentState: raw.agentState ? (0, encryption_1.decrypt)(encryptionKey, encryptionVariant, (0, encryption_1.decodeBase64)(raw.agentState)) : null,
57
+ agentStateVersion: raw.agentStateVersion,
58
+ encryptionKey,
59
+ encryptionVariant,
60
+ };
61
+ }
62
+ catch (error) {
63
+ if (axios_1.default.isAxiosError(error)) {
64
+ const status = error.response?.status;
65
+ if (status === 404 || (status && status >= 500) || error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') {
66
+ return null;
67
+ }
68
+ }
69
+ throw new Error(`Failed to create session: ${error instanceof Error ? error.message : 'Unknown error'}`);
70
+ }
71
+ }
72
+ /** Register or update machine with the server */
73
+ async getOrCreateMachine(opts) {
74
+ let dataEncryptionKey = null;
75
+ let encryptionKey;
76
+ let encryptionVariant;
77
+ if (this.credential.encryption.type === 'dataKey') {
78
+ encryptionVariant = 'dataKey';
79
+ encryptionKey = this.credential.encryption.machineKey;
80
+ const encryptedDataKey = (0, encryption_1.libsodiumEncryptForPublicKey)(this.credential.encryption.machineKey, this.credential.encryption.publicKey);
81
+ dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
82
+ dataEncryptionKey.set([0], 0);
83
+ dataEncryptionKey.set(encryptedDataKey, 1);
84
+ }
85
+ else {
86
+ encryptionKey = this.credential.encryption.secret;
87
+ encryptionVariant = 'legacy';
88
+ }
89
+ const createMinimalMachine = () => ({
90
+ id: opts.machineId,
91
+ encryptionKey,
92
+ encryptionVariant,
93
+ metadata: opts.metadata,
94
+ metadataVersion: 0,
95
+ daemonState: opts.daemonState || null,
96
+ daemonStateVersion: 0,
97
+ });
98
+ try {
99
+ const response = await axios_1.default.post(`${config_1.synkConfig.serverUrl}/v1/machines`, {
100
+ id: opts.machineId,
101
+ metadata: (0, encryption_1.encodeBase64)((0, encryption_1.encrypt)(encryptionKey, encryptionVariant, opts.metadata)),
102
+ daemonState: opts.daemonState ? (0, encryption_1.encodeBase64)((0, encryption_1.encrypt)(encryptionKey, encryptionVariant, opts.daemonState)) : undefined,
103
+ dataEncryptionKey: dataEncryptionKey ? (0, encryption_1.encodeBase64)(dataEncryptionKey) : undefined,
104
+ }, {
105
+ headers: {
106
+ 'Authorization': `Bearer ${this.credential.token}`,
107
+ 'Content-Type': 'application/json',
108
+ },
109
+ timeout: 60000,
110
+ });
111
+ const raw = response.data.machine;
112
+ return {
113
+ id: raw.id,
114
+ encryptionKey,
115
+ encryptionVariant,
116
+ metadata: raw.metadata ? (0, encryption_1.decrypt)(encryptionKey, encryptionVariant, (0, encryption_1.decodeBase64)(raw.metadata)) : opts.metadata,
117
+ metadataVersion: raw.metadataVersion || 0,
118
+ daemonState: raw.daemonState ? (0, encryption_1.decrypt)(encryptionKey, encryptionVariant, (0, encryption_1.decodeBase64)(raw.daemonState)) : null,
119
+ daemonStateVersion: raw.daemonStateVersion || 0,
120
+ };
121
+ }
122
+ catch (error) {
123
+ if (axios_1.default.isAxiosError(error)) {
124
+ const status = error.response?.status;
125
+ if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND' || status === 404 || (status && status >= 500)) {
126
+ return createMinimalMachine();
127
+ }
128
+ }
129
+ return createMinimalMachine();
130
+ }
131
+ }
132
+ }
133
+ exports.ApiClient = ApiClient;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Authentication for ekkOS_synk server
3
+ */
4
+ /** Authenticate with synk-server and obtain a bearer token */
5
+ export declare function authGetToken(secret: Uint8Array): Promise<string>;
6
+ /** Generate a deep link URL for mobile app pairing */
7
+ export declare function generateAppUrl(secret: Uint8Array): string;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ /**
3
+ * Authentication for ekkOS_synk server
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.authGetToken = authGetToken;
10
+ exports.generateAppUrl = generateAppUrl;
11
+ const axios_1 = __importDefault(require("axios"));
12
+ const encryption_1 = require("./encryption");
13
+ const config_1 = require("./config");
14
+ /** Authenticate with synk-server and obtain a bearer token */
15
+ async function authGetToken(secret) {
16
+ const { challenge, publicKey, signature } = (0, encryption_1.authChallenge)(secret);
17
+ const response = await axios_1.default.post(`${config_1.synkConfig.serverUrl}/v1/auth`, {
18
+ challenge: (0, encryption_1.encodeBase64)(challenge),
19
+ publicKey: (0, encryption_1.encodeBase64)(publicKey),
20
+ signature: (0, encryption_1.encodeBase64)(signature),
21
+ });
22
+ if (!response.data.success || !response.data.token) {
23
+ throw new Error('Authentication failed');
24
+ }
25
+ return response.data.token;
26
+ }
27
+ /** Generate a deep link URL for mobile app pairing */
28
+ function generateAppUrl(secret) {
29
+ return `ekkos-synk://${(0, encryption_1.encodeBase64Url)(secret)}`;
30
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Configuration for ekkOS_synk — remote session sync
3
+ */
4
+ declare class SynkConfig {
5
+ readonly serverUrl: string;
6
+ readonly synkHomeDir: string;
7
+ readonly logsDir: string;
8
+ readonly settingsFile: string;
9
+ readonly credentialsFile: string;
10
+ readonly daemonStateFile: string;
11
+ readonly daemonLockFile: string;
12
+ readonly isDaemonProcess: boolean;
13
+ readonly disableCaffeinate: boolean;
14
+ constructor();
15
+ ensureDirectories(): void;
16
+ }
17
+ export declare const synkConfig: SynkConfig;
18
+ export {};
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ /**
3
+ * Configuration for ekkOS_synk — remote session sync
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.synkConfig = void 0;
7
+ const node_fs_1 = require("node:fs");
8
+ const node_os_1 = require("node:os");
9
+ const node_path_1 = require("node:path");
10
+ class SynkConfig {
11
+ constructor() {
12
+ this.serverUrl = process.env.SYNK_SERVER_URL || 'https://synk.ekkos.dev';
13
+ const args = process.argv.slice(2);
14
+ this.isDaemonProcess = args.length >= 3 && args[0] === 'synk' && args[1] === 'daemon' && args[2] === 'start-sync';
15
+ if (process.env.SYNK_HOME_DIR) {
16
+ this.synkHomeDir = process.env.SYNK_HOME_DIR.replace(/^~/, (0, node_os_1.homedir)());
17
+ }
18
+ else {
19
+ this.synkHomeDir = (0, node_path_1.join)((0, node_os_1.homedir)(), '.ekkos', 'synk');
20
+ }
21
+ this.logsDir = (0, node_path_1.join)(this.synkHomeDir, 'logs');
22
+ this.settingsFile = (0, node_path_1.join)(this.synkHomeDir, 'settings.json');
23
+ this.credentialsFile = (0, node_path_1.join)(this.synkHomeDir, 'credentials.json');
24
+ this.daemonStateFile = (0, node_path_1.join)(this.synkHomeDir, 'daemon.state.json');
25
+ this.daemonLockFile = (0, node_path_1.join)(this.synkHomeDir, 'daemon.state.json.lock');
26
+ this.disableCaffeinate = ['true', '1', 'yes'].includes(process.env.SYNK_DISABLE_CAFFEINATE?.toLowerCase() || '');
27
+ }
28
+ ensureDirectories() {
29
+ if (!(0, node_fs_1.existsSync)(this.synkHomeDir)) {
30
+ (0, node_fs_1.mkdirSync)(this.synkHomeDir, { recursive: true });
31
+ }
32
+ if (!(0, node_fs_1.existsSync)(this.logsDir)) {
33
+ (0, node_fs_1.mkdirSync)(this.logsDir, { recursive: true });
34
+ }
35
+ }
36
+ }
37
+ exports.synkConfig = new SynkConfig();
@@ -0,0 +1,11 @@
1
+ /**
2
+ * HTTP client for communicating with a running synk daemon
3
+ */
4
+ import type { Metadata } from '../types';
5
+ export declare function notifyDaemonSessionStarted(sessionId: string, metadata: Metadata): Promise<any>;
6
+ export declare function listDaemonSessions(): Promise<any[]>;
7
+ export declare function stopDaemonSession(sessionId: string): Promise<boolean>;
8
+ export declare function spawnDaemonSession(directory: string, sessionId?: string): Promise<any>;
9
+ export declare function stopDaemonHttp(): Promise<void>;
10
+ export declare function checkIfDaemonRunning(): Promise<boolean>;
11
+ export declare function stopDaemon(): Promise<void>;
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ /**
3
+ * HTTP client for communicating with a running synk daemon
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.notifyDaemonSessionStarted = notifyDaemonSessionStarted;
7
+ exports.listDaemonSessions = listDaemonSessions;
8
+ exports.stopDaemonSession = stopDaemonSession;
9
+ exports.spawnDaemonSession = spawnDaemonSession;
10
+ exports.stopDaemonHttp = stopDaemonHttp;
11
+ exports.checkIfDaemonRunning = checkIfDaemonRunning;
12
+ exports.stopDaemon = stopDaemon;
13
+ const persistence_1 = require("../persistence");
14
+ async function daemonPost(path, body) {
15
+ const state = await (0, persistence_1.readDaemonState)();
16
+ if (!state?.httpPort) {
17
+ return { error: 'No daemon running, no state file found' };
18
+ }
19
+ try {
20
+ process.kill(state.pid, 0);
21
+ }
22
+ catch {
23
+ return { error: 'Daemon is not running, state file is stale' };
24
+ }
25
+ try {
26
+ const timeout = process.env.SYNK_DAEMON_HTTP_TIMEOUT
27
+ ? parseInt(process.env.SYNK_DAEMON_HTTP_TIMEOUT)
28
+ : 10000;
29
+ const response = await fetch(`http://127.0.0.1:${state.httpPort}${path}`, {
30
+ method: 'POST',
31
+ headers: { 'Content-Type': 'application/json' },
32
+ body: JSON.stringify(body || {}),
33
+ signal: AbortSignal.timeout(timeout),
34
+ });
35
+ if (!response.ok) {
36
+ return { error: `Request failed: ${path}, HTTP ${response.status}` };
37
+ }
38
+ return await response.json();
39
+ }
40
+ catch (error) {
41
+ return { error: `Request failed: ${path}, ${error instanceof Error ? error.message : 'Unknown error'}` };
42
+ }
43
+ }
44
+ async function notifyDaemonSessionStarted(sessionId, metadata) {
45
+ return await daemonPost('/session-started', { sessionId, metadata });
46
+ }
47
+ async function listDaemonSessions() {
48
+ const result = await daemonPost('/list');
49
+ return result.children || [];
50
+ }
51
+ async function stopDaemonSession(sessionId) {
52
+ const result = await daemonPost('/stop-session', { sessionId });
53
+ return result.success || false;
54
+ }
55
+ async function spawnDaemonSession(directory, sessionId) {
56
+ return await daemonPost('/spawn-session', { directory, sessionId });
57
+ }
58
+ async function stopDaemonHttp() {
59
+ await daemonPost('/stop');
60
+ }
61
+ async function checkIfDaemonRunning() {
62
+ const state = await (0, persistence_1.readDaemonState)();
63
+ if (!state)
64
+ return false;
65
+ try {
66
+ process.kill(state.pid, 0);
67
+ return true;
68
+ }
69
+ catch {
70
+ await (0, persistence_1.clearDaemonState)();
71
+ return false;
72
+ }
73
+ }
74
+ async function stopDaemon() {
75
+ try {
76
+ const state = await (0, persistence_1.readDaemonState)();
77
+ if (!state)
78
+ return;
79
+ // Try HTTP graceful stop
80
+ try {
81
+ await stopDaemonHttp();
82
+ const start = Date.now();
83
+ while (Date.now() - start < 2000) {
84
+ try {
85
+ process.kill(state.pid, 0);
86
+ await new Promise(resolve => setTimeout(resolve, 100));
87
+ }
88
+ catch {
89
+ return; // Process is dead
90
+ }
91
+ }
92
+ }
93
+ catch { }
94
+ // Force kill
95
+ try {
96
+ process.kill(state.pid, 'SIGKILL');
97
+ }
98
+ catch { }
99
+ }
100
+ catch { }
101
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Local HTTP control server for synk daemon
3
+ * Listens on 127.0.0.1 only — provides endpoints for session management and shutdown
4
+ */
5
+ import type { Metadata } from '../types';
6
+ export interface TrackedSession {
7
+ pid: number;
8
+ startedBy: string;
9
+ synkSessionId?: string;
10
+ startedAt: number;
11
+ }
12
+ export declare function startDaemonControlServer(opts: {
13
+ getChildren: () => TrackedSession[];
14
+ stopSession: (sessionId: string) => boolean;
15
+ spawnSession: (options: {
16
+ directory: string;
17
+ sessionId?: string;
18
+ }) => Promise<any>;
19
+ requestShutdown: () => void;
20
+ onSessionWebhook: (sessionId: string, metadata: Metadata) => void;
21
+ }): Promise<{
22
+ port: number;
23
+ stop: () => Promise<void>;
24
+ }>;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ /**
3
+ * Local HTTP control server for synk daemon
4
+ * Listens on 127.0.0.1 only — provides endpoints for session management and shutdown
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.startDaemonControlServer = startDaemonControlServer;
11
+ const node_http_1 = __importDefault(require("node:http"));
12
+ function startDaemonControlServer(opts) {
13
+ return new Promise((resolve, reject) => {
14
+ const server = node_http_1.default.createServer(async (req, res) => {
15
+ if (req.method !== 'POST') {
16
+ res.writeHead(405, { 'Content-Type': 'application/json' });
17
+ res.end(JSON.stringify({ error: 'Method not allowed' }));
18
+ return;
19
+ }
20
+ let body = '';
21
+ req.on('data', (chunk) => { body += chunk; });
22
+ req.on('end', async () => {
23
+ let parsed = {};
24
+ try {
25
+ parsed = body ? JSON.parse(body) : {};
26
+ }
27
+ catch { }
28
+ try {
29
+ switch (req.url) {
30
+ case '/session-started': {
31
+ const { sessionId, metadata } = parsed;
32
+ if (sessionId)
33
+ opts.onSessionWebhook(sessionId, metadata);
34
+ res.writeHead(200, { 'Content-Type': 'application/json' });
35
+ res.end(JSON.stringify({ status: 'ok' }));
36
+ break;
37
+ }
38
+ case '/list': {
39
+ const children = opts.getChildren()
40
+ .filter(c => c.synkSessionId)
41
+ .map(c => ({ startedBy: c.startedBy, synkSessionId: c.synkSessionId, pid: c.pid }));
42
+ res.writeHead(200, { 'Content-Type': 'application/json' });
43
+ res.end(JSON.stringify({ children }));
44
+ break;
45
+ }
46
+ case '/stop-session': {
47
+ const success = opts.stopSession(parsed.sessionId || '');
48
+ res.writeHead(200, { 'Content-Type': 'application/json' });
49
+ res.end(JSON.stringify({ success }));
50
+ break;
51
+ }
52
+ case '/spawn-session': {
53
+ const result = await opts.spawnSession({
54
+ directory: parsed.directory || '.',
55
+ sessionId: parsed.sessionId,
56
+ });
57
+ res.writeHead(200, { 'Content-Type': 'application/json' });
58
+ res.end(JSON.stringify(result));
59
+ break;
60
+ }
61
+ case '/stop': {
62
+ res.writeHead(200, { 'Content-Type': 'application/json' });
63
+ res.end(JSON.stringify({ status: 'stopping' }));
64
+ setTimeout(() => opts.requestShutdown(), 50);
65
+ break;
66
+ }
67
+ default: {
68
+ res.writeHead(404, { 'Content-Type': 'application/json' });
69
+ res.end(JSON.stringify({ error: 'Not found' }));
70
+ }
71
+ }
72
+ }
73
+ catch (error) {
74
+ if (!res.headersSent) {
75
+ res.writeHead(500, { 'Content-Type': 'application/json' });
76
+ }
77
+ res.end(JSON.stringify({ error: error instanceof Error ? error.message : 'Internal error' }));
78
+ }
79
+ });
80
+ });
81
+ server.listen(0, '127.0.0.1', () => {
82
+ const addr = server.address();
83
+ const port = typeof addr === 'object' && addr ? addr.port : 0;
84
+ resolve({
85
+ port,
86
+ stop: () => new Promise((res) => server.close(() => res())),
87
+ });
88
+ });
89
+ server.on('error', reject);
90
+ });
91
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Synk daemon — background process for session management and remote control
3
+ *
4
+ * Lifecycle:
5
+ * 1. Acquire lock file (prevent multiple daemons)
6
+ * 2. Authenticate with synk-server
7
+ * 3. Start local HTTP control server
8
+ * 4. Register machine via REST API
9
+ * 5. Connect MachineClient WebSocket
10
+ * 6. Heartbeat loop
11
+ * 7. Await shutdown signal
12
+ * 8. Cleanup
13
+ */
14
+ export declare function startDaemon(): Promise<void>;