@ekkos/cli 0.2.18 → 1.0.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 (98) hide show
  1. package/README.md +57 -0
  2. package/dist/agent/daemon.d.ts +27 -0
  3. package/dist/agent/daemon.js +254 -29
  4. package/dist/agent/health-check.d.ts +35 -0
  5. package/dist/agent/health-check.js +243 -0
  6. package/dist/agent/pty-runner.d.ts +1 -0
  7. package/dist/agent/pty-runner.js +6 -1
  8. package/dist/capture/eviction-client.d.ts +139 -0
  9. package/dist/capture/eviction-client.js +454 -0
  10. package/dist/capture/index.d.ts +2 -0
  11. package/dist/capture/index.js +2 -0
  12. package/dist/capture/jsonl-rewriter.d.ts +96 -0
  13. package/dist/capture/jsonl-rewriter.js +1369 -0
  14. package/dist/capture/transcript-repair.d.ts +51 -0
  15. package/dist/capture/transcript-repair.js +319 -0
  16. package/dist/commands/agent.d.ts +6 -0
  17. package/dist/commands/agent.js +244 -0
  18. package/dist/commands/dashboard.d.ts +25 -0
  19. package/dist/commands/dashboard.js +1175 -0
  20. package/dist/commands/doctor.js +23 -1
  21. package/dist/commands/run.d.ts +5 -0
  22. package/dist/commands/run.js +1605 -516
  23. package/dist/commands/setup-remote.js +146 -37
  24. package/dist/commands/swarm-dashboard.d.ts +20 -0
  25. package/dist/commands/swarm-dashboard.js +735 -0
  26. package/dist/commands/swarm-setup.d.ts +10 -0
  27. package/dist/commands/swarm-setup.js +956 -0
  28. package/dist/commands/swarm.d.ts +46 -0
  29. package/dist/commands/swarm.js +441 -0
  30. package/dist/commands/test-claude.d.ts +16 -0
  31. package/dist/commands/test-claude.js +156 -0
  32. package/dist/commands/usage/blocks.d.ts +8 -0
  33. package/dist/commands/usage/blocks.js +60 -0
  34. package/dist/commands/usage/daily.d.ts +9 -0
  35. package/dist/commands/usage/daily.js +96 -0
  36. package/dist/commands/usage/dashboard.d.ts +8 -0
  37. package/dist/commands/usage/dashboard.js +104 -0
  38. package/dist/commands/usage/formatters.d.ts +41 -0
  39. package/dist/commands/usage/formatters.js +147 -0
  40. package/dist/commands/usage/index.d.ts +13 -0
  41. package/dist/commands/usage/index.js +87 -0
  42. package/dist/commands/usage/monthly.d.ts +8 -0
  43. package/dist/commands/usage/monthly.js +66 -0
  44. package/dist/commands/usage/session.d.ts +11 -0
  45. package/dist/commands/usage/session.js +193 -0
  46. package/dist/commands/usage/weekly.d.ts +9 -0
  47. package/dist/commands/usage/weekly.js +61 -0
  48. package/dist/commands/usage.d.ts +7 -0
  49. package/dist/commands/usage.js +214 -0
  50. package/dist/cron/index.d.ts +7 -0
  51. package/dist/cron/index.js +13 -0
  52. package/dist/cron/promoter.d.ts +70 -0
  53. package/dist/cron/promoter.js +403 -0
  54. package/dist/deploy/instructions.d.ts +5 -2
  55. package/dist/deploy/instructions.js +11 -8
  56. package/dist/index.js +262 -5
  57. package/dist/lib/tmux-scrollbar.d.ts +14 -0
  58. package/dist/lib/tmux-scrollbar.js +296 -0
  59. package/dist/lib/usage-monitor.d.ts +47 -0
  60. package/dist/lib/usage-monitor.js +124 -0
  61. package/dist/lib/usage-parser.d.ts +162 -0
  62. package/dist/lib/usage-parser.js +583 -0
  63. package/dist/restore/RestoreOrchestrator.d.ts +4 -0
  64. package/dist/restore/RestoreOrchestrator.js +118 -30
  65. package/dist/utils/log-rotate.d.ts +18 -0
  66. package/dist/utils/log-rotate.js +74 -0
  67. package/dist/utils/platform.d.ts +2 -0
  68. package/dist/utils/platform.js +3 -1
  69. package/dist/utils/session-binding.d.ts +5 -0
  70. package/dist/utils/session-binding.js +46 -0
  71. package/dist/utils/state.js +4 -0
  72. package/dist/utils/verify-remote-terminal.d.ts +10 -0
  73. package/dist/utils/verify-remote-terminal.js +415 -0
  74. package/package.json +9 -2
  75. package/templates/CLAUDE.md +135 -23
  76. package/templates/ekkos-manifest.json +5 -5
  77. package/templates/hooks/lib/contract.sh +43 -31
  78. package/templates/hooks/lib/count-tokens.cjs +86 -0
  79. package/templates/hooks/lib/ekkos-reminders.sh +98 -0
  80. package/templates/hooks/lib/state.sh +53 -1
  81. package/templates/hooks/stop.sh +150 -388
  82. package/templates/hooks/user-prompt-submit.sh +353 -443
  83. package/templates/windsurf-hooks/README.md +212 -0
  84. package/templates/windsurf-hooks/hooks.json +9 -2
  85. package/templates/windsurf-hooks/install.sh +148 -0
  86. package/templates/windsurf-hooks/lib/contract.sh +2 -0
  87. package/templates/windsurf-hooks/post-cascade-response.sh +251 -0
  88. package/templates/windsurf-hooks/pre-user-prompt.sh +435 -0
  89. package/templates/windsurf-skills/ekkos-memory/SKILL.md +219 -0
  90. package/templates/agents/README.md +0 -182
  91. package/templates/agents/code-reviewer.md +0 -166
  92. package/templates/agents/debug-detective.md +0 -169
  93. package/templates/agents/ekkOS_Vercel.md +0 -99
  94. package/templates/agents/extension-manager.md +0 -229
  95. package/templates/agents/git-companion.md +0 -185
  96. package/templates/agents/github-test-agent.md +0 -321
  97. package/templates/agents/railway-manager.md +0 -215
  98. package/templates/windsurf-hooks/before-submit-prompt.sh +0 -238
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ /**
3
+ * Health check for ekkOS agent daemon
4
+ *
5
+ * Verifies:
6
+ * - Service is installed and loaded
7
+ * - Process is running
8
+ * - Recent activity in logs
9
+ * - Network connectivity to relay server
10
+ */
11
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
+ desc = { enumerable: true, get: function() { return m[k]; } };
16
+ }
17
+ Object.defineProperty(o, k2, desc);
18
+ }) : (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ o[k2] = m[k];
21
+ }));
22
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
24
+ }) : function(o, v) {
25
+ o["default"] = v;
26
+ });
27
+ var __importStar = (this && this.__importStar) || (function () {
28
+ var ownKeys = function(o) {
29
+ ownKeys = Object.getOwnPropertyNames || function (o) {
30
+ var ar = [];
31
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
+ return ar;
33
+ };
34
+ return ownKeys(o);
35
+ };
36
+ return function (mod) {
37
+ if (mod && mod.__esModule) return mod;
38
+ var result = {};
39
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
+ __setModuleDefault(result, mod);
41
+ return result;
42
+ };
43
+ })();
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.checkDaemonHealth = checkDaemonHealth;
49
+ exports.formatHealthStatus = formatHealthStatus;
50
+ const os = __importStar(require("os"));
51
+ const fs = __importStar(require("fs"));
52
+ const path = __importStar(require("path"));
53
+ const child_process_1 = require("child_process");
54
+ const ws_1 = __importDefault(require("ws"));
55
+ const RELAY_URL = process.env.RELAY_WS_URL || 'wss://ekkos-relay-production.up.railway.app';
56
+ /**
57
+ * Check agent daemon health
58
+ */
59
+ async function checkDaemonHealth() {
60
+ const status = {
61
+ ok: true,
62
+ service: {
63
+ installed: false,
64
+ loaded: false,
65
+ running: false,
66
+ },
67
+ logs: {
68
+ recentErrors: [],
69
+ },
70
+ relay: {
71
+ reachable: false,
72
+ },
73
+ };
74
+ // Check if service is installed
75
+ const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', 'dev.ekkos.agent.plist');
76
+ status.service.installed = fs.existsSync(plistPath);
77
+ // Check if service is loaded
78
+ try {
79
+ const output = (0, child_process_1.execSync)('launchctl list | grep dev.ekkos.agent', { encoding: 'utf-8' }).trim();
80
+ status.service.loaded = !!output;
81
+ // Extract PID if running
82
+ const pidMatch = output.match(/^(\d+)\s+/);
83
+ if (pidMatch) {
84
+ status.service.pid = parseInt(pidMatch[1], 10);
85
+ status.service.running = status.service.pid > 0;
86
+ }
87
+ }
88
+ catch {
89
+ // Service not loaded
90
+ status.service.loaded = false;
91
+ status.service.running = false;
92
+ }
93
+ // Check logs
94
+ const logDir = path.join(os.homedir(), '.ekkos');
95
+ const errLogPath = path.join(logDir, 'agent.err.log');
96
+ const outLogPath = path.join(logDir, 'agent.out.log');
97
+ if (fs.existsSync(errLogPath)) {
98
+ try {
99
+ const errLog = fs.readFileSync(errLogPath, 'utf-8');
100
+ if (errLog) {
101
+ status.logs.lastActivity = new Date(fs.statSync(errLogPath).mtime);
102
+ status.logs.recentErrors = extractRecentErrors(errLog, 10);
103
+ }
104
+ }
105
+ catch {
106
+ // Ignore log read errors
107
+ }
108
+ }
109
+ if (fs.existsSync(outLogPath)) {
110
+ try {
111
+ const stat = fs.statSync(outLogPath);
112
+ const mtime = new Date(stat.mtime);
113
+ if (!status.logs.lastActivity || mtime > status.logs.lastActivity) {
114
+ status.logs.lastActivity = mtime;
115
+ }
116
+ }
117
+ catch {
118
+ // Ignore
119
+ }
120
+ }
121
+ // Check relay connectivity
122
+ try {
123
+ status.relay.reachable = await checkRelayConnectivity();
124
+ }
125
+ catch (err) {
126
+ status.relay.reachable = false;
127
+ status.relay.lastError = err.message;
128
+ }
129
+ // Overall health
130
+ status.ok = status.service.running && status.relay.reachable && status.logs.recentErrors.length === 0;
131
+ return status;
132
+ }
133
+ /**
134
+ * Check if relay server is reachable
135
+ */
136
+ async function checkRelayConnectivity() {
137
+ return new Promise((resolve) => {
138
+ const timeout = setTimeout(() => {
139
+ ws.terminate();
140
+ resolve(false);
141
+ }, 5000);
142
+ const ws = new ws_1.default(`${RELAY_URL}/health`);
143
+ ws.on('open', () => {
144
+ clearTimeout(timeout);
145
+ ws.close();
146
+ resolve(true);
147
+ });
148
+ ws.on('error', () => {
149
+ clearTimeout(timeout);
150
+ resolve(false);
151
+ });
152
+ ws.on('close', () => {
153
+ clearTimeout(timeout);
154
+ resolve(false);
155
+ });
156
+ });
157
+ }
158
+ /**
159
+ * Extract recent error lines from log
160
+ */
161
+ function extractRecentErrors(log, count) {
162
+ return log
163
+ .split('\n')
164
+ .filter((line) => {
165
+ const lower = line.toLowerCase();
166
+ return lower.includes('error') || lower.includes('failed') || lower.includes('exception');
167
+ })
168
+ .slice(-count);
169
+ }
170
+ /**
171
+ * Format health status for console output
172
+ */
173
+ function formatHealthStatus(status) {
174
+ const lines = [];
175
+ lines.push('ekkOS Agent Daemon Health Check');
176
+ lines.push('================================\n');
177
+ // Service status
178
+ lines.push(`Service Installation: ${status.service.installed ? '✓' : '✗'} ${status.service.installed ? 'Installed' : 'Not installed'}`);
179
+ lines.push(`Service Loaded: ${status.service.loaded ? '✓' : '✗'} ${status.service.loaded ? 'Loaded' : 'Not loaded'}`);
180
+ if (status.service.running) {
181
+ lines.push(`Service Running: ✓ Running (PID ${status.service.pid})`);
182
+ }
183
+ else {
184
+ lines.push('Service Running: ✗ Not running');
185
+ }
186
+ lines.push('');
187
+ // Logs status
188
+ if (status.logs.lastActivity) {
189
+ const now = new Date();
190
+ const age = now.getTime() - status.logs.lastActivity.getTime();
191
+ const ageStr = formatAge(age);
192
+ lines.push(`Last Activity: ${ageStr} ago (${status.logs.lastActivity.toISOString()})`);
193
+ }
194
+ else {
195
+ lines.push('Last Activity: No logs found');
196
+ }
197
+ if (status.logs.recentErrors.length > 0) {
198
+ lines.push(`Recent Errors (${status.logs.recentErrors.length}):`);
199
+ for (const err of status.logs.recentErrors) {
200
+ lines.push(` - ${err.substring(0, 100)}`);
201
+ }
202
+ }
203
+ lines.push('');
204
+ // Relay status
205
+ lines.push(`Relay Server: ${status.relay.reachable ? '✓' : '✗'} ${status.relay.reachable ? 'Reachable' : 'Unreachable'}`);
206
+ if (status.relay.lastError) {
207
+ lines.push(` Error: ${status.relay.lastError}`);
208
+ }
209
+ lines.push('');
210
+ // Overall status
211
+ if (status.ok) {
212
+ lines.push('Overall: ✓ Healthy - All systems operational');
213
+ }
214
+ else {
215
+ lines.push('Overall: ✗ Unhealthy - Issues detected');
216
+ if (!status.service.running) {
217
+ lines.push(' → Service is not running. Run: launchctl start dev.ekkos.agent');
218
+ }
219
+ if (!status.relay.reachable) {
220
+ lines.push(' → Cannot reach relay server. Check network connectivity.');
221
+ }
222
+ if (status.logs.recentErrors.length > 0) {
223
+ lines.push(' → Recent errors found in logs. Check ~/.ekkos/agent.err.log');
224
+ }
225
+ }
226
+ return lines.join('\n');
227
+ }
228
+ /**
229
+ * Format age duration
230
+ */
231
+ function formatAge(ms) {
232
+ const seconds = Math.floor(ms / 1000);
233
+ const minutes = Math.floor(seconds / 60);
234
+ const hours = Math.floor(minutes / 60);
235
+ const days = Math.floor(hours / 24);
236
+ if (days > 0)
237
+ return `${days}d ${hours % 24}h`;
238
+ if (hours > 0)
239
+ return `${hours}h ${minutes % 60}m`;
240
+ if (minutes > 0)
241
+ return `${minutes}m ${seconds % 60}s`;
242
+ return `${seconds}s`;
243
+ }
@@ -12,6 +12,7 @@ interface PTYRunnerConfig {
12
12
  cols?: number;
13
13
  rows?: number;
14
14
  cwd?: string;
15
+ env?: NodeJS.ProcessEnv;
15
16
  verbose?: boolean;
16
17
  }
17
18
  export declare class PTYRunner {
@@ -85,6 +85,7 @@ class PTYRunner {
85
85
  cwd: this.config.cwd || process.cwd(),
86
86
  env: {
87
87
  ...process.env,
88
+ ...(this.config.env || {}),
88
89
  TERM: 'xterm-256color',
89
90
  COLORTERM: 'truecolor',
90
91
  },
@@ -107,7 +108,10 @@ class PTYRunner {
107
108
  this.spawnProcess = (0, child_process_1.spawn)('cmd.exe', ['/c', this.config.command, ...this.config.args], {
108
109
  stdio: ['pipe', 'pipe', 'pipe'],
109
110
  cwd,
110
- env: process.env,
111
+ env: {
112
+ ...process.env,
113
+ ...(this.config.env || {}),
114
+ },
111
115
  });
112
116
  }
113
117
  else {
@@ -120,6 +124,7 @@ class PTYRunner {
120
124
  cwd,
121
125
  env: {
122
126
  ...process.env,
127
+ ...(this.config.env || {}),
123
128
  TERM: 'xterm-256color',
124
129
  },
125
130
  });
@@ -0,0 +1,139 @@
1
+ /**
2
+ * EVICTION CLIENT
3
+ * ================
4
+ *
5
+ * Client module for the Handshake Eviction Protocol.
6
+ * Provides functions to call ekkOS_EvictPrepare and ekkOS_EvictConfirm
7
+ * ensuring zero data loss during context eviction.
8
+ *
9
+ * Protocol:
10
+ * 1. prepareEviction() - Write to R2, get ACK
11
+ * 2. (caller deletes locally)
12
+ * 3. confirmEviction() - Mark as committed
13
+ *
14
+ * Features:
15
+ * - Retry with exponential backoff
16
+ * - Timeout handling
17
+ * - Fallback detection
18
+ * - Idempotency via clientNonce
19
+ */
20
+ /**
21
+ * Queue a failed eviction for retry when R2 reconnects
22
+ */
23
+ export declare function queueEvictionForRetry(messages: Message[], indices: number[], estimatedTokens: number, reason: 'threshold' | 'emergency' | 'manual', options: HandshakeEvictionOptions): void;
24
+ /**
25
+ * Drain the retry queue — called after a successful handshake
26
+ * Non-blocking: runs in background, doesn't delay current eviction
27
+ */
28
+ export declare function drainRetryQueue(apiUrl: string, authToken: string): Promise<{
29
+ drained: number;
30
+ failed: number;
31
+ remaining: number;
32
+ }>;
33
+ /**
34
+ * Get retry queue stats (for monitoring)
35
+ */
36
+ export declare function getRetryQueueStats(): {
37
+ size: number;
38
+ oldestAge: number | null;
39
+ };
40
+ interface ContentBlock {
41
+ type: 'text' | 'image' | 'tool_use' | 'tool_result' | 'thinking';
42
+ text?: string;
43
+ id?: string;
44
+ name?: string;
45
+ input?: Record<string, unknown>;
46
+ tool_use_id?: string;
47
+ content?: string | ContentBlock[];
48
+ thinking?: string;
49
+ }
50
+ export interface Message {
51
+ role: 'user' | 'assistant';
52
+ content: string | ContentBlock[];
53
+ }
54
+ interface EvictionManifest {
55
+ evictionId: string;
56
+ messageIndices: number[];
57
+ fingerprints: string[];
58
+ estimatedTokens: number;
59
+ evictionReason: 'threshold' | 'emergency' | 'manual';
60
+ }
61
+ interface PrepareResult {
62
+ success: boolean;
63
+ evictionId: string;
64
+ r2Key?: string;
65
+ bytesWritten?: number;
66
+ checksum?: string;
67
+ status?: 'prepared' | 'already_exists';
68
+ error?: string;
69
+ clientNonce: string;
70
+ }
71
+ interface ConfirmResult {
72
+ success: boolean;
73
+ status?: 'committed' | 'already_committed' | 'not_found';
74
+ error?: string;
75
+ }
76
+ /**
77
+ * Create deterministic eviction ID from messages
78
+ * Same messages = same evictionId (enables dedup)
79
+ */
80
+ export declare function createEvictionId(messages: Message[]): string;
81
+ /**
82
+ * Create fingerprint for a message
83
+ */
84
+ export declare function createFingerprint(msg: Message): string;
85
+ /**
86
+ * Check if the eviction API is available
87
+ * Returns true if proxy is reachable and healthy
88
+ */
89
+ export declare function checkEvictionHealth(apiUrl: string, authToken: string): Promise<boolean>;
90
+ /**
91
+ * Prepare eviction by writing to R2 via the proxy.
92
+ * Returns ACK with evictionId if successful.
93
+ * Caller must NOT delete locally until this succeeds.
94
+ */
95
+ export declare function prepareEviction(apiUrl: string, authToken: string, sessionId: string, sessionName: string, userId: string, tenantId: string, messages: Message[], manifest: EvictionManifest, projectPath?: string): Promise<PrepareResult>;
96
+ /**
97
+ * Confirm eviction after local deletion.
98
+ * This is optional but recommended for audit completeness.
99
+ * Failure here is non-critical - data is already safe in R2.
100
+ */
101
+ export declare function confirmEviction(apiUrl: string, authToken: string, evictionId: string, clientNonce: string, localDeletedCount: number): Promise<ConfirmResult>;
102
+ export interface HandshakeEvictionOptions {
103
+ apiUrl: string;
104
+ authToken: string;
105
+ sessionId: string;
106
+ sessionName: string;
107
+ userId: string;
108
+ tenantId: string;
109
+ projectPath?: string;
110
+ }
111
+ export interface HandshakeEvictionResult {
112
+ success: boolean;
113
+ evictionId: string;
114
+ r2Key?: string;
115
+ bytesWritten?: number;
116
+ status: 'prepared' | 'committed' | 'failed' | 'skipped';
117
+ error?: string;
118
+ clientNonce?: string;
119
+ }
120
+ /**
121
+ * Full handshake eviction - call this instead of fire-and-forget
122
+ *
123
+ * Returns:
124
+ * - success=true, status='prepared' → Safe to delete locally, then call confirmEviction
125
+ * - success=false → DO NOT delete locally
126
+ */
127
+ export declare function handshakeEviction(messages: Message[], indices: number[], estimatedTokens: number, reason: 'threshold' | 'emergency' | 'manual', options: HandshakeEvictionOptions): Promise<HandshakeEvictionResult>;
128
+ declare const _default: {
129
+ prepareEviction: typeof prepareEviction;
130
+ confirmEviction: typeof confirmEviction;
131
+ handshakeEviction: typeof handshakeEviction;
132
+ checkEvictionHealth: typeof checkEvictionHealth;
133
+ createEvictionId: typeof createEvictionId;
134
+ createFingerprint: typeof createFingerprint;
135
+ queueEvictionForRetry: typeof queueEvictionForRetry;
136
+ drainRetryQueue: typeof drainRetryQueue;
137
+ getRetryQueueStats: typeof getRetryQueueStats;
138
+ };
139
+ export default _default;