@ekkos/cli 0.2.9 → 0.2.11

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 (44) hide show
  1. package/dist/agent/daemon.d.ts +86 -0
  2. package/dist/agent/daemon.js +297 -0
  3. package/dist/agent/pty-runner.d.ts +51 -0
  4. package/dist/agent/pty-runner.js +184 -0
  5. package/dist/cache/LocalSessionStore.d.ts +34 -21
  6. package/dist/cache/LocalSessionStore.js +169 -53
  7. package/dist/cache/capture.d.ts +19 -11
  8. package/dist/cache/capture.js +243 -76
  9. package/dist/cache/types.d.ts +14 -1
  10. package/dist/commands/agent.d.ts +44 -0
  11. package/dist/commands/agent.js +300 -0
  12. package/dist/commands/doctor.d.ts +10 -0
  13. package/dist/commands/doctor.js +175 -87
  14. package/dist/commands/hooks.d.ts +109 -0
  15. package/dist/commands/hooks.js +668 -0
  16. package/dist/commands/run.d.ts +2 -0
  17. package/dist/commands/run.js +357 -85
  18. package/dist/commands/setup-remote.d.ts +20 -0
  19. package/dist/commands/setup-remote.js +467 -0
  20. package/dist/index.js +116 -1
  21. package/dist/restore/RestoreOrchestrator.d.ts +17 -3
  22. package/dist/restore/RestoreOrchestrator.js +64 -22
  23. package/dist/utils/paths.d.ts +125 -0
  24. package/dist/utils/paths.js +283 -0
  25. package/dist/utils/state.d.ts +2 -0
  26. package/package.json +1 -1
  27. package/templates/ekkos-manifest.json +223 -0
  28. package/templates/helpers/json-parse.cjs +101 -0
  29. package/templates/hooks/assistant-response.ps1 +256 -0
  30. package/templates/hooks/assistant-response.sh +124 -64
  31. package/templates/hooks/session-start.ps1 +107 -2
  32. package/templates/hooks/session-start.sh +201 -166
  33. package/templates/hooks/stop.ps1 +124 -3
  34. package/templates/hooks/stop.sh +470 -843
  35. package/templates/hooks/user-prompt-submit.ps1 +107 -22
  36. package/templates/hooks/user-prompt-submit.sh +403 -393
  37. package/templates/project-stubs/session-start.ps1 +63 -0
  38. package/templates/project-stubs/session-start.sh +55 -0
  39. package/templates/project-stubs/stop.ps1 +63 -0
  40. package/templates/project-stubs/stop.sh +55 -0
  41. package/templates/project-stubs/user-prompt-submit.ps1 +63 -0
  42. package/templates/project-stubs/user-prompt-submit.sh +55 -0
  43. package/templates/shared/hooks-enabled.json +22 -0
  44. package/templates/shared/session-words.json +45 -0
@@ -1,25 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
  /**
4
- * ekkOS Fast Capture & Restore - CLI for local cache operations
4
+ * ekkOS Fast Capture & Restore - CLI for local cache operations (Instance-Aware)
5
5
  *
6
- * Capture Commands:
6
+ * Per ekkOS Onboarding Spec v1.2 FINAL + ADDENDUM:
7
+ * - All Tier 0 cache paths MUST be: ~/.ekkos/cache/sessions/{instanceId}/{sessionId}.jsonl
8
+ * - All persisted records MUST include: instanceId, sessionId, sessionName
9
+ *
10
+ * Capture Commands (NEW format with instanceId):
11
+ * capture user <instance_id> <session_id> <session_name> <turn_id> <query> [project_path]
12
+ * capture response <instance_id> <session_id> <turn_id> <response> [tools] [files]
13
+ *
14
+ * Capture Commands (LEGACY format - backward compatible, uses default instanceId):
7
15
  * capture user <session_id> <session_name> <turn_id> <query> [project_path]
8
16
  * capture response <session_id> <turn_id> <response> [tools] [files]
9
17
  *
10
18
  * Restore Commands:
11
- * capture restore [session_name] [--json|--markdown|--n=N]
19
+ * capture restore [session_name] [--json|--markdown|--n=N] [--instance=ID]
12
20
  *
13
- * ACK & Sync Commands (Phase 5):
14
- * capture ack <session_id> <turn_id> - Update ACK cursor after Redis success
15
- * capture sync [session_id] - Sync unACKed turns to Redis
16
- * capture prune <session_id> - Remove safely ACKed turns
17
- * capture cleanup - Prune all + evict old sessions
21
+ * ACK & Sync Commands:
22
+ * capture ack <session_id> <turn_id> [--instance=ID]
23
+ * capture sync [session_id] [--instance=ID]
24
+ * capture prune <session_id> [--instance=ID]
25
+ * capture cleanup [--instance=ID]
18
26
  *
19
27
  * Query Commands:
20
- * capture list - List all cached sessions
21
- * capture get <session_id> [n] - Get last N turns
22
- * capture stats - Cache statistics
28
+ * capture list [--instance=ID|--all]
29
+ * capture get <session_id> [n] [--instance=ID]
30
+ * capture stats [--instance=ID]
23
31
  *
24
32
  * This is a lightweight script designed for hook integration.
25
33
  * Writes to local JSONL cache with minimal latency.
@@ -63,7 +71,7 @@ const path = __importStar(require("path"));
63
71
  const os = __importStar(require("os"));
64
72
  const LocalSessionStore_js_1 = require("./LocalSessionStore.js");
65
73
  const RestoreOrchestrator_js_1 = require("../restore/RestoreOrchestrator.js");
66
- const store = new LocalSessionStore_js_1.LocalSessionStore();
74
+ const paths_js_1 = require("../utils/paths.js");
67
75
  // API configuration
68
76
  const MEMORY_API_URL = process.env.EKKOS_API_URL || 'https://api.ekkos.dev';
69
77
  const CONFIG_PATH = path.join(os.homedir(), '.ekkos', 'config.json');
@@ -82,75 +90,236 @@ function loadAuthToken() {
82
90
  }
83
91
  return '';
84
92
  }
93
+ /**
94
+ * Parse --instance=ID flag from args
95
+ */
96
+ function parseInstanceFlag(args) {
97
+ let instanceId = process.env.EKKOS_INSTANCE_ID || paths_js_1.DEFAULT_INSTANCE_ID;
98
+ const cleanArgs = [];
99
+ for (const arg of args) {
100
+ if (arg.startsWith('--instance=')) {
101
+ instanceId = arg.slice('--instance='.length);
102
+ }
103
+ else {
104
+ cleanArgs.push(arg);
105
+ }
106
+ }
107
+ return { instanceId: (0, paths_js_1.normalizeInstanceId)(instanceId), cleanArgs };
108
+ }
109
+ /**
110
+ * Detect if args match the NEW format (with instanceId as first positional arg)
111
+ *
112
+ * NEW user format: <instance_id> <session_id> <session_name> <turn_id> <query> [project_path]
113
+ * OLD user format: <session_id> <session_name> <turn_id> <query> [project_path]
114
+ *
115
+ * Detection heuristic:
116
+ * - If arg[3] is a number (turn_id), it's NEW format
117
+ * - If arg[2] is a number (turn_id), it's OLD format
118
+ */
119
+ function isNewUserFormat(args) {
120
+ // NEW format has at least 5 args: instanceId, sessionId, sessionName, turnId, query
121
+ if (args.length < 5)
122
+ return false;
123
+ // In NEW format, arg[3] should be turn_id (a number)
124
+ // In OLD format, arg[2] should be turn_id (a number)
125
+ const arg3 = args[3];
126
+ const arg2 = args[2];
127
+ // If arg[3] is numeric but arg[2] is not, it's NEW format
128
+ if (/^\d+$/.test(arg3) && !/^\d+$/.test(arg2)) {
129
+ return true;
130
+ }
131
+ // If arg[2] is numeric, it's OLD format
132
+ if (/^\d+$/.test(arg2)) {
133
+ return false;
134
+ }
135
+ // Default to NEW format if ambiguous and we have enough args
136
+ return args.length >= 5;
137
+ }
138
+ /**
139
+ * Similar detection for response command
140
+ *
141
+ * NEW response format: <instance_id> <session_id> <turn_id> <response> [tools] [files]
142
+ * OLD response format: <session_id> <turn_id> <response> [tools] [files]
143
+ */
144
+ function isNewResponseFormat(args) {
145
+ // NEW format has at least 4 args: instanceId, sessionId, turnId, response
146
+ if (args.length < 4)
147
+ return false;
148
+ // In NEW format, arg[2] should be turn_id (a number)
149
+ // In OLD format, arg[1] should be turn_id (a number)
150
+ const arg2 = args[2];
151
+ const arg1 = args[1];
152
+ // If arg[2] is numeric but arg[1] is not, it's NEW format
153
+ if (/^\d+$/.test(arg2) && !/^\d+$/.test(arg1)) {
154
+ return true;
155
+ }
156
+ // If arg[1] is numeric, it's OLD format
157
+ if (/^\d+$/.test(arg1)) {
158
+ return false;
159
+ }
160
+ // Default to NEW format if ambiguous
161
+ return args.length >= 4;
162
+ }
85
163
  async function main() {
86
164
  const args = process.argv.slice(2);
87
165
  const command = args[0];
88
166
  if (!command) {
89
- console.error('Usage: capture <user|response> ...');
167
+ console.error('ekkOS Capture CLI - Instance-Aware Local Cache');
168
+ console.error('');
169
+ console.error('Usage: ekkos-capture <command> [args...]');
170
+ console.error('');
171
+ console.error('Commands:');
172
+ console.error(' user Capture user query (start of turn)');
173
+ console.error(' response Capture assistant response (end of turn)');
174
+ console.error(' restore Restore session context');
175
+ console.error(' ack Acknowledge turn synced to Redis');
176
+ console.error(' sync Sync unACKed turns to Redis');
177
+ console.error(' prune Remove safely ACKed turns');
178
+ console.error(' cleanup Prune all + evict old sessions');
179
+ console.error(' list List cached sessions');
180
+ console.error(' get Get turns from a session');
181
+ console.error(' stats Show cache statistics');
182
+ console.error('');
183
+ console.error('Global options:');
184
+ console.error(' --instance=ID Override instance ID (default: $EKKOS_INSTANCE_ID or "default")');
90
185
  process.exit(1);
91
186
  }
92
187
  try {
188
+ // Parse global --instance flag
189
+ const { instanceId, cleanArgs } = parseInstanceFlag(args.slice(1));
190
+ const store = (0, LocalSessionStore_js_1.createLocalSessionStore)(instanceId);
93
191
  switch (command) {
94
192
  case 'user': {
95
- // capture user <session_id> <session_name> <turn_id> <query> [project_path]
96
- const [, sessionId, sessionName, turnIdStr, query, projectPath] = args;
97
- if (!sessionId || !sessionName || !turnIdStr || !query) {
98
- console.error('Usage: capture user <session_id> <session_name> <turn_id> <query> [project_path]');
99
- process.exit(1);
100
- }
101
- const turnId = parseInt(turnIdStr, 10);
102
- const turn = {
103
- turn_id: turnId,
104
- ts: new Date().toISOString(),
105
- user_query: query,
106
- assistant_response: '', // Will be filled by response capture
107
- tools_used: [],
108
- files_referenced: [],
109
- };
110
- const result = store.appendTurn(sessionId, sessionName, turn, projectPath);
111
- if (result.success) {
112
- console.log(JSON.stringify({ success: true, latency_ms: result.latency_ms }));
193
+ // Detect format and parse accordingly
194
+ if (isNewUserFormat(cleanArgs)) {
195
+ // NEW format: <instance_id> <session_id> <session_name> <turn_id> <query> [project_path]
196
+ const [instId, sessionId, sessionName, turnIdStr, query, projectPath] = cleanArgs;
197
+ if (!instId || !sessionId || !sessionName || !turnIdStr || !query) {
198
+ console.error('Usage (new): capture user <instance_id> <session_id> <session_name> <turn_id> <query> [project_path]');
199
+ console.error('Usage (old): capture user <session_id> <session_name> <turn_id> <query> [project_path]');
200
+ process.exit(1);
201
+ }
202
+ // Use the provided instanceId
203
+ store.setInstanceId(instId);
204
+ const turnId = parseInt(turnIdStr, 10);
205
+ const turn = {
206
+ turn_id: turnId,
207
+ ts: new Date().toISOString(),
208
+ user_query: query,
209
+ assistant_response: '', // Will be filled by response capture
210
+ tools_used: [],
211
+ files_referenced: [],
212
+ instance_id: instId,
213
+ session_id: sessionId,
214
+ session_name: sessionName,
215
+ };
216
+ const result = store.appendTurn(sessionId, sessionName, turn, projectPath);
217
+ if (result.success) {
218
+ console.log(JSON.stringify({ success: true, instance_id: instId, latency_ms: result.latency_ms }));
219
+ }
220
+ else {
221
+ console.error(JSON.stringify({ success: false, error: result.error }));
222
+ process.exit(1);
223
+ }
113
224
  }
114
225
  else {
115
- console.error(JSON.stringify({ success: false, error: result.error }));
116
- process.exit(1);
226
+ // OLD format: <session_id> <session_name> <turn_id> <query> [project_path]
227
+ const [sessionId, sessionName, turnIdStr, query, projectPath] = cleanArgs;
228
+ if (!sessionId || !sessionName || !turnIdStr || !query) {
229
+ console.error('Usage (new): capture user <instance_id> <session_id> <session_name> <turn_id> <query> [project_path]');
230
+ console.error('Usage (old): capture user <session_id> <session_name> <turn_id> <query> [project_path]');
231
+ process.exit(1);
232
+ }
233
+ const turnId = parseInt(turnIdStr, 10);
234
+ const turn = {
235
+ turn_id: turnId,
236
+ ts: new Date().toISOString(),
237
+ user_query: query,
238
+ assistant_response: '', // Will be filled by response capture
239
+ tools_used: [],
240
+ files_referenced: [],
241
+ instance_id: instanceId, // Use parsed/default instanceId
242
+ session_id: sessionId,
243
+ session_name: sessionName,
244
+ };
245
+ const result = store.appendTurn(sessionId, sessionName, turn, projectPath);
246
+ if (result.success) {
247
+ console.log(JSON.stringify({ success: true, instance_id: instanceId, latency_ms: result.latency_ms }));
248
+ }
249
+ else {
250
+ console.error(JSON.stringify({ success: false, error: result.error }));
251
+ process.exit(1);
252
+ }
117
253
  }
118
254
  break;
119
255
  }
120
256
  case 'response': {
121
- // capture response <session_id> <turn_id> <response> [tools_json] [files_json]
122
- const [, sessionId, turnIdStr, response, toolsJson, filesJson] = args;
123
- if (!sessionId || !turnIdStr || !response) {
124
- console.error('Usage: capture response <session_id> <turn_id> <response> [tools_json] [files_json]');
125
- process.exit(1);
126
- }
127
- const turnId = parseInt(turnIdStr, 10);
128
- const tools = toolsJson ? JSON.parse(toolsJson) : [];
129
- const files = filesJson ? JSON.parse(filesJson) : [];
130
- const result = store.updateTurnResponse(sessionId, turnId, response, tools, files);
131
- if (result.success) {
132
- console.log(JSON.stringify({ success: true, latency_ms: result.latency_ms }));
257
+ // Detect format and parse accordingly
258
+ if (isNewResponseFormat(cleanArgs)) {
259
+ // NEW format: <instance_id> <session_id> <turn_id> <response> [tools_json] [files_json]
260
+ const [instId, sessionId, turnIdStr, response, toolsJson, filesJson] = cleanArgs;
261
+ if (!instId || !sessionId || !turnIdStr || !response) {
262
+ console.error('Usage (new): capture response <instance_id> <session_id> <turn_id> <response> [tools_json] [files_json]');
263
+ console.error('Usage (old): capture response <session_id> <turn_id> <response> [tools_json] [files_json]');
264
+ process.exit(1);
265
+ }
266
+ // Use the provided instanceId
267
+ store.setInstanceId(instId);
268
+ const turnId = parseInt(turnIdStr, 10);
269
+ const tools = toolsJson ? JSON.parse(toolsJson) : [];
270
+ const files = filesJson ? JSON.parse(filesJson) : [];
271
+ const result = store.updateTurnResponse(sessionId, turnId, response, tools, files);
272
+ if (result.success) {
273
+ console.log(JSON.stringify({ success: true, instance_id: instId, latency_ms: result.latency_ms }));
274
+ }
275
+ else {
276
+ console.error(JSON.stringify({ success: false, error: result.error }));
277
+ process.exit(1);
278
+ }
133
279
  }
134
280
  else {
135
- console.error(JSON.stringify({ success: false, error: result.error }));
136
- process.exit(1);
281
+ // OLD format: <session_id> <turn_id> <response> [tools_json] [files_json]
282
+ const [sessionId, turnIdStr, response, toolsJson, filesJson] = cleanArgs;
283
+ if (!sessionId || !turnIdStr || !response) {
284
+ console.error('Usage (new): capture response <instance_id> <session_id> <turn_id> <response> [tools_json] [files_json]');
285
+ console.error('Usage (old): capture response <session_id> <turn_id> <response> [tools_json] [files_json]');
286
+ process.exit(1);
287
+ }
288
+ const turnId = parseInt(turnIdStr, 10);
289
+ const tools = toolsJson ? JSON.parse(toolsJson) : [];
290
+ const files = filesJson ? JSON.parse(filesJson) : [];
291
+ const result = store.updateTurnResponse(sessionId, turnId, response, tools, files);
292
+ if (result.success) {
293
+ console.log(JSON.stringify({ success: true, instance_id: instanceId, latency_ms: result.latency_ms }));
294
+ }
295
+ else {
296
+ console.error(JSON.stringify({ success: false, error: result.error }));
297
+ process.exit(1);
298
+ }
137
299
  }
138
300
  break;
139
301
  }
140
302
  case 'list': {
141
- // capture list
142
- const sessions = store.listSessions();
143
- console.log(JSON.stringify(sessions, null, 2));
303
+ // Check for --all flag
304
+ const showAll = cleanArgs.includes('--all');
305
+ if (showAll) {
306
+ const sessions = store.listAllSessions();
307
+ console.log(JSON.stringify(sessions, null, 2));
308
+ }
309
+ else {
310
+ const sessions = store.listSessions();
311
+ console.log(JSON.stringify(sessions, null, 2));
312
+ }
144
313
  break;
145
314
  }
146
315
  case 'get': {
147
- // capture get <session_id> [n]
148
- const [, sessionId, nStr] = args;
316
+ const sessionId = cleanArgs[0];
317
+ const nStr = cleanArgs[1];
149
318
  if (!sessionId) {
150
- console.error('Usage: capture get <session_id> [n]');
319
+ console.error('Usage: capture get <session_id> [n] [--instance=ID]');
151
320
  process.exit(1);
152
321
  }
153
- const n = nStr ? parseInt(nStr, 10) : 10;
322
+ const n = nStr && /^\d+$/.test(nStr) ? parseInt(nStr, 10) : 10;
154
323
  const result = store.getLastTurns(sessionId, n);
155
324
  if (result.success) {
156
325
  console.log(JSON.stringify(result.data, null, 2));
@@ -167,13 +336,12 @@ async function main() {
167
336
  break;
168
337
  }
169
338
  case 'restore': {
170
- // capture restore [session_name] [--json|--markdown|--n=N]
171
- const orchestrator = new RestoreOrchestrator_js_1.RestoreOrchestrator();
339
+ const orchestrator = new RestoreOrchestrator_js_1.RestoreOrchestrator(instanceId);
172
340
  let sessionName;
173
341
  let outputFormat = 'json';
174
342
  let lastN = 10;
175
- for (let i = 1; i < args.length; i++) {
176
- const arg = args[i];
343
+ for (let i = 0; i < cleanArgs.length; i++) {
344
+ const arg = cleanArgs[i];
177
345
  if (arg === '--json') {
178
346
  outputFormat = 'json';
179
347
  }
@@ -189,6 +357,7 @@ async function main() {
189
357
  }
190
358
  const result = await orchestrator.restore({
191
359
  session_name: sessionName,
360
+ instance_id: instanceId,
192
361
  last_n: lastN,
193
362
  });
194
363
  if (result.success && result.data) {
@@ -199,6 +368,7 @@ async function main() {
199
368
  console.log(JSON.stringify({
200
369
  success: true,
201
370
  source: result.data.source,
371
+ instance_id: instanceId,
202
372
  latency_ms: result.latency_ms,
203
373
  session_name: result.data.session_name,
204
374
  session_id: result.data.session_id,
@@ -211,6 +381,7 @@ async function main() {
211
381
  console.error(JSON.stringify({
212
382
  success: false,
213
383
  error: result.error,
384
+ instance_id: instanceId,
214
385
  latency_ms: result.latency_ms,
215
386
  }));
216
387
  process.exit(1);
@@ -218,17 +389,16 @@ async function main() {
218
389
  break;
219
390
  }
220
391
  case 'ack': {
221
- // capture ack <session_id> <turn_id>
222
- // Update ACK cursor after successful Redis flush
223
- const [, sessionId, turnIdStr] = args;
392
+ const sessionId = cleanArgs[0];
393
+ const turnIdStr = cleanArgs[1];
224
394
  if (!sessionId || !turnIdStr) {
225
- console.error('Usage: capture ack <session_id> <turn_id>');
395
+ console.error('Usage: capture ack <session_id> <turn_id> [--instance=ID]');
226
396
  process.exit(1);
227
397
  }
228
398
  const turnId = parseInt(turnIdStr, 10);
229
399
  const result = store.ack(sessionId, turnId);
230
400
  if (result.success) {
231
- console.log(JSON.stringify({ success: true, acked_turn_id: turnId, latency_ms: result.latency_ms }));
401
+ console.log(JSON.stringify({ success: true, acked_turn_id: turnId, instance_id: instanceId, latency_ms: result.latency_ms }));
232
402
  }
233
403
  else {
234
404
  console.error(JSON.stringify({ success: false, error: result.error }));
@@ -237,11 +407,9 @@ async function main() {
237
407
  break;
238
408
  }
239
409
  case 'prune': {
240
- // capture prune <session_id>
241
- // Remove turns safely below ACK threshold
242
- const [, sessionId] = args;
410
+ const sessionId = cleanArgs[0];
243
411
  if (!sessionId) {
244
- console.error('Usage: capture prune <session_id>');
412
+ console.error('Usage: capture prune <session_id> [--instance=ID]');
245
413
  process.exit(1);
246
414
  }
247
415
  const meta = store.getSessionMeta(sessionId);
@@ -255,6 +423,7 @@ async function main() {
255
423
  success: true,
256
424
  pruned_turns: result.data,
257
425
  acked_turn_id: meta.acked_turn_id,
426
+ instance_id: instanceId,
258
427
  latency_ms: result.latency_ms,
259
428
  }));
260
429
  }
@@ -265,9 +434,7 @@ async function main() {
265
434
  break;
266
435
  }
267
436
  case 'sync': {
268
- // capture sync [session_id]
269
- // Sync unACKed turns to Redis, update ACK on success
270
- const [, sessionIdArg] = args;
437
+ const sessionIdArg = cleanArgs[0];
271
438
  const authToken = loadAuthToken();
272
439
  if (!authToken) {
273
440
  console.error(JSON.stringify({ success: false, error: 'No auth token configured' }));
@@ -297,6 +464,7 @@ async function main() {
297
464
  try {
298
465
  const payload = {
299
466
  session_name: meta.session_name || session.session_name,
467
+ instance_id: instanceId,
300
468
  turn_number: turn.turn_id,
301
469
  user_query: turn.user_query,
302
470
  agent_response: turn.assistant_response,
@@ -334,14 +502,12 @@ async function main() {
334
502
  turns_synced: totalSynced,
335
503
  turns_failed: totalFailed,
336
504
  sessions_processed: sessions.length,
505
+ instance_id: instanceId,
337
506
  }));
338
507
  break;
339
508
  }
340
509
  case 'sync-supabase': {
341
- // capture sync-supabase [session_id]
342
- // Sync ALL local cache turns to Supabase (episodic memory)
343
- // This backfills any missing turns that failed during capture
344
- const [, sessionIdArg] = args;
510
+ const sessionIdArg = cleanArgs[0];
345
511
  const authToken = loadAuthToken();
346
512
  if (!authToken) {
347
513
  console.error(JSON.stringify({ success: false, error: 'No auth token configured' }));
@@ -380,6 +546,7 @@ async function main() {
380
546
  session_id: session.session_id,
381
547
  metadata: {
382
548
  source: 'claude-code',
549
+ instance_id: instanceId,
383
550
  turn_number: turn.turn_id,
384
551
  session_name: meta.session_name,
385
552
  tools_used: turn.tools_used || [],
@@ -422,12 +589,11 @@ async function main() {
422
589
  turns_failed: totalFailed,
423
590
  turns_skipped: totalSkipped,
424
591
  sessions_processed: sessions.length,
592
+ instance_id: instanceId,
425
593
  }));
426
594
  break;
427
595
  }
428
596
  case 'cleanup': {
429
- // capture cleanup
430
- // Evict old sessions and prune all sessions
431
597
  const sessions = store.listSessions();
432
598
  let totalPruned = 0;
433
599
  // Prune all sessions
@@ -444,6 +610,7 @@ async function main() {
444
610
  turns_pruned: totalPruned,
445
611
  sessions_evicted: evicted,
446
612
  remaining_sessions: store.listSessions().length,
613
+ instance_id: instanceId,
447
614
  }));
448
615
  break;
449
616
  }
@@ -8,6 +8,9 @@
8
8
  */
9
9
  /**
10
10
  * A single conversation turn (user + assistant pair)
11
+ *
12
+ * Per ekkOS Onboarding Spec v1.2 ADDENDUM:
13
+ * All persisted records MUST include: instanceId, sessionId, sessionName
11
14
  */
12
15
  export interface Turn {
13
16
  turn_id: number;
@@ -19,6 +22,9 @@ export interface Turn {
19
22
  diffs?: string[];
20
23
  token_estimate?: number;
21
24
  is_complete?: boolean;
25
+ instance_id?: string;
26
+ session_id?: string;
27
+ session_name?: string;
22
28
  }
23
29
  /**
24
30
  * Known placeholder strings that indicate incomplete/invalid responses
@@ -37,17 +43,22 @@ export interface SessionIndex {
37
43
  }
38
44
  export interface SessionIndexEntry {
39
45
  session_id: string;
46
+ instance_id?: string;
40
47
  last_active_ts: string;
41
48
  last_turn_id: number;
42
49
  acked_turn_id: number;
43
50
  project_path?: string;
44
51
  }
45
52
  /**
46
- * Per-session metadata stored in {session_id}.meta.json
53
+ * Per-session metadata stored in {instanceId}/{session_id}.meta.json
54
+ *
55
+ * Per ekkOS Onboarding Spec v1.2 ADDENDUM:
56
+ * All persisted records MUST include: instanceId, sessionId, sessionName
47
57
  */
48
58
  export interface SessionMeta {
49
59
  session_id: string;
50
60
  session_name: string;
61
+ instance_id?: string;
51
62
  acked_turn_id: number;
52
63
  supabase_acked_turn_id?: number;
53
64
  last_flush_ts: string;
@@ -68,6 +79,7 @@ export interface OpenLoop {
68
79
  export interface RestorePayload {
69
80
  session_id: string;
70
81
  session_name: string;
82
+ instance_id?: string;
71
83
  source: 'local' | 'redis' | 'supabase' | 'stream';
72
84
  restored_turns: Turn[];
73
85
  latest: {
@@ -118,6 +130,7 @@ export interface Pattern {
118
130
  export interface RestoreOptions {
119
131
  session_name?: string;
120
132
  session_id?: string;
133
+ instance_id?: string;
121
134
  last_n?: number;
122
135
  include_directives?: boolean;
123
136
  include_patterns?: boolean;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * ekkos agent - Agent management commands for remote terminal
3
+ *
4
+ * Subcommands:
5
+ * - daemon: Run the agent daemon (used by service)
6
+ * - start: Start the agent service
7
+ * - stop: Stop the agent service
8
+ * - restart: Restart the agent service
9
+ * - status: Check agent status
10
+ * - uninstall: Remove agent service
11
+ */
12
+ export interface AgentOptions {
13
+ verbose?: boolean;
14
+ }
15
+ /**
16
+ * Run the agent daemon (foreground)
17
+ */
18
+ export declare function agentDaemon(options?: AgentOptions): Promise<void>;
19
+ /**
20
+ * Start the agent service
21
+ */
22
+ export declare function agentStart(options?: AgentOptions): Promise<void>;
23
+ /**
24
+ * Stop the agent service
25
+ */
26
+ export declare function agentStop(options?: AgentOptions): Promise<void>;
27
+ /**
28
+ * Restart the agent service
29
+ */
30
+ export declare function agentRestart(options?: AgentOptions): Promise<void>;
31
+ /**
32
+ * Check agent status
33
+ */
34
+ export declare function agentStatus(options?: AgentOptions): Promise<void>;
35
+ /**
36
+ * Uninstall the agent service
37
+ */
38
+ export declare function agentUninstall(options?: AgentOptions): Promise<void>;
39
+ /**
40
+ * Show agent logs
41
+ */
42
+ export declare function agentLogs(options?: {
43
+ follow?: boolean;
44
+ }): Promise<void>;