@ekkos/cli 1.3.2 → 1.3.5
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/capture/jsonl-rewriter.d.ts +1 -1
- package/dist/capture/jsonl-rewriter.js +3 -3
- package/dist/capture/transcript-repair.d.ts +2 -2
- package/dist/capture/transcript-repair.js +2 -2
- package/dist/commands/claw.d.ts +13 -0
- package/dist/commands/claw.js +253 -0
- package/dist/commands/dashboard.js +617 -83
- package/dist/commands/doctor.d.ts +3 -3
- package/dist/commands/doctor.js +6 -79
- package/dist/commands/gemini.d.ts +19 -0
- package/dist/commands/gemini.js +193 -0
- package/dist/commands/init.js +2 -25
- package/dist/commands/run.d.ts +0 -1
- package/dist/commands/run.js +147 -241
- package/dist/commands/scan.d.ts +21 -0
- package/dist/commands/scan.js +386 -0
- package/dist/commands/swarm-dashboard.js +156 -28
- package/dist/commands/swarm.d.ts +1 -1
- package/dist/commands/swarm.js +1 -1
- package/dist/commands/test-claude.d.ts +2 -2
- package/dist/commands/test-claude.js +3 -3
- package/dist/deploy/index.d.ts +0 -2
- package/dist/deploy/index.js +0 -2
- package/dist/deploy/settings.d.ts +2 -2
- package/dist/deploy/settings.js +42 -4
- package/dist/deploy/skills.js +1 -2
- package/dist/index.js +79 -19
- package/dist/lib/usage-parser.js +4 -3
- package/dist/utils/proxy-url.d.ts +12 -1
- package/dist/utils/proxy-url.js +16 -1
- package/dist/utils/templates.js +1 -1
- package/package.json +4 -6
- package/templates/CLAUDE.md +49 -107
- package/dist/agent/daemon.d.ts +0 -130
- package/dist/agent/daemon.js +0 -606
- package/dist/agent/health-check.d.ts +0 -35
- package/dist/agent/health-check.js +0 -243
- package/dist/agent/pty-runner.d.ts +0 -53
- package/dist/agent/pty-runner.js +0 -190
- package/dist/commands/agent.d.ts +0 -50
- package/dist/commands/agent.js +0 -544
- package/dist/commands/setup-remote.d.ts +0 -20
- package/dist/commands/setup-remote.js +0 -582
- package/dist/commands/synk.d.ts +0 -7
- package/dist/commands/synk.js +0 -339
- package/dist/synk/api.d.ts +0 -22
- package/dist/synk/api.js +0 -133
- package/dist/synk/auth.d.ts +0 -7
- package/dist/synk/auth.js +0 -30
- package/dist/synk/config.d.ts +0 -18
- package/dist/synk/config.js +0 -37
- package/dist/synk/daemon/control-client.d.ts +0 -11
- package/dist/synk/daemon/control-client.js +0 -101
- package/dist/synk/daemon/control-server.d.ts +0 -24
- package/dist/synk/daemon/control-server.js +0 -91
- package/dist/synk/daemon/run.d.ts +0 -14
- package/dist/synk/daemon/run.js +0 -338
- package/dist/synk/encryption.d.ts +0 -17
- package/dist/synk/encryption.js +0 -133
- package/dist/synk/index.d.ts +0 -13
- package/dist/synk/index.js +0 -36
- package/dist/synk/machine-client.d.ts +0 -42
- package/dist/synk/machine-client.js +0 -218
- package/dist/synk/persistence.d.ts +0 -51
- package/dist/synk/persistence.js +0 -211
- package/dist/synk/qr.d.ts +0 -5
- package/dist/synk/qr.js +0 -33
- package/dist/synk/session-bridge.d.ts +0 -58
- package/dist/synk/session-bridge.js +0 -171
- package/dist/synk/session-client.d.ts +0 -46
- package/dist/synk/session-client.js +0 -240
- package/dist/synk/types.d.ts +0 -574
- package/dist/synk/types.js +0 -74
- package/dist/utils/verify-remote-terminal.d.ts +0 -10
- package/dist/utils/verify-remote-terminal.js +0 -415
- package/templates/README.md +0 -378
- package/templates/claude-plugins/PHASE2_COMPLETION.md +0 -346
- package/templates/claude-plugins/PLUGIN_PROPOSALS.md +0 -1776
- package/templates/claude-plugins/README.md +0 -587
- package/templates/claude-plugins/agents/code-reviewer.json +0 -14
- package/templates/claude-plugins/agents/debug-detective.json +0 -15
- package/templates/claude-plugins/agents/git-companion.json +0 -14
- package/templates/claude-plugins/blog-manager/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/blog-manager/commands/blog.md +0 -691
- package/templates/claude-plugins/golden-loop-monitor/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/golden-loop-monitor/commands/loop-status.md +0 -434
- package/templates/claude-plugins/learning-tracker/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/learning-tracker/commands/my-patterns.md +0 -282
- package/templates/claude-plugins/memory-lens/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/memory-lens/commands/memory-search.md +0 -181
- package/templates/claude-plugins/pattern-coach/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/pattern-coach/commands/forge.md +0 -365
- package/templates/claude-plugins/project-schema-validator/.claude-plugin/plugin.json +0 -8
- package/templates/claude-plugins/project-schema-validator/commands/validate-schema.md +0 -582
- package/templates/commands/continue.md +0 -47
- package/templates/cursor-rules/ekkos-memory.md +0 -127
- package/templates/ekkos-manifest.json +0 -223
- package/templates/helpers/json-parse.cjs +0 -101
- package/templates/plan-template.md +0 -306
- package/templates/shared/hooks-enabled.json +0 -22
- package/templates/shared/session-words.json +0 -45
- package/templates/skills/ekkOS_Deep_Recall/Skill.md +0 -282
- package/templates/skills/ekkOS_Learn/Skill.md +0 -265
- package/templates/skills/ekkOS_Memory_First/Skill.md +0 -206
- package/templates/skills/ekkOS_Plan_Assist/Skill.md +0 -302
- package/templates/skills/ekkOS_Preferences/Skill.md +0 -247
- package/templates/skills/ekkOS_Reflect/Skill.md +0 -257
- package/templates/skills/ekkOS_Safety/Skill.md +0 -265
- package/templates/skills/ekkOS_Schema/Skill.md +0 -251
- package/templates/skills/ekkOS_Summary/Skill.md +0 -257
- package/templates/spec-template.md +0 -159
- package/templates/windsurf-rules/ekkos-memory.md +0 -127
- package/templates/windsurf-skills/README.md +0 -58
- package/templates/windsurf-skills/ekkos-continue/SKILL.md +0 -81
- package/templates/windsurf-skills/ekkos-golden-loop/SKILL.md +0 -225
- package/templates/windsurf-skills/ekkos-insights/SKILL.md +0 -138
- package/templates/windsurf-skills/ekkos-recall/SKILL.md +0 -96
- package/templates/windsurf-skills/ekkos-safety/SKILL.md +0 -89
- package/templates/windsurf-skills/ekkos-vault/SKILL.md +0 -86
|
@@ -1,101 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
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
|
-
}>;
|
|
@@ -1,91 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
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>;
|
package/dist/synk/daemon/run.js
DELETED
|
@@ -1,338 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
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
|
-
};
|