@ekkos/cli 0.2.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 (135) hide show
  1. package/dist/cache/LocalSessionStore.d.ts +129 -0
  2. package/dist/cache/LocalSessionStore.js +688 -0
  3. package/dist/cache/capture.d.ts +26 -0
  4. package/dist/cache/capture.js +461 -0
  5. package/dist/cache/index.d.ts +7 -0
  6. package/dist/cache/index.js +23 -0
  7. package/dist/cache/types.d.ts +147 -0
  8. package/dist/cache/types.js +40 -0
  9. package/dist/commands/init.d.ts +9 -0
  10. package/dist/commands/init.js +478 -0
  11. package/dist/commands/run.d.ts +12 -0
  12. package/dist/commands/run.js +829 -0
  13. package/dist/commands/setup.d.ts +6 -0
  14. package/dist/commands/setup.js +658 -0
  15. package/dist/commands/status.d.ts +1 -0
  16. package/dist/commands/status.js +109 -0
  17. package/dist/commands/test.d.ts +1 -0
  18. package/dist/commands/test.js +157 -0
  19. package/dist/deploy/agents.d.ts +15 -0
  20. package/dist/deploy/agents.js +72 -0
  21. package/dist/deploy/hooks.d.ts +16 -0
  22. package/dist/deploy/hooks.js +121 -0
  23. package/dist/deploy/index.d.ts +7 -0
  24. package/dist/deploy/index.js +24 -0
  25. package/dist/deploy/instructions.d.ts +12 -0
  26. package/dist/deploy/instructions.js +36 -0
  27. package/dist/deploy/mcp.d.ts +19 -0
  28. package/dist/deploy/mcp.js +109 -0
  29. package/dist/deploy/plugins.d.ts +19 -0
  30. package/dist/deploy/plugins.js +62 -0
  31. package/dist/deploy/settings.d.ts +8 -0
  32. package/dist/deploy/settings.js +84 -0
  33. package/dist/deploy/skills.d.ts +19 -0
  34. package/dist/deploy/skills.js +60 -0
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.js +71 -0
  37. package/dist/restore/RestoreOrchestrator.d.ts +48 -0
  38. package/dist/restore/RestoreOrchestrator.js +481 -0
  39. package/dist/restore/index.d.ts +4 -0
  40. package/dist/restore/index.js +20 -0
  41. package/dist/utils/platform.d.ts +29 -0
  42. package/dist/utils/platform.js +65 -0
  43. package/dist/utils/session-words.json +119 -0
  44. package/dist/utils/state.d.ts +57 -0
  45. package/dist/utils/state.js +186 -0
  46. package/dist/utils/templates.d.ts +24 -0
  47. package/dist/utils/templates.js +118 -0
  48. package/package.json +48 -0
  49. package/templates/CLAUDE.md +287 -0
  50. package/templates/README.md +378 -0
  51. package/templates/agents/README.md +182 -0
  52. package/templates/agents/code-reviewer.md +166 -0
  53. package/templates/agents/debug-detective.md +169 -0
  54. package/templates/agents/ekkOS_Vercel.md +99 -0
  55. package/templates/agents/extension-manager.md +229 -0
  56. package/templates/agents/git-companion.md +185 -0
  57. package/templates/agents/github-test-agent.md +321 -0
  58. package/templates/agents/railway-manager.md +179 -0
  59. package/templates/claude-plugins/PHASE2_COMPLETION.md +346 -0
  60. package/templates/claude-plugins/PLUGIN_PROPOSALS.md +1776 -0
  61. package/templates/claude-plugins/README.md +587 -0
  62. package/templates/claude-plugins/agents/code-reviewer.json +14 -0
  63. package/templates/claude-plugins/agents/debug-detective.json +15 -0
  64. package/templates/claude-plugins/agents/git-companion.json +14 -0
  65. package/templates/claude-plugins/blog-manager/.claude-plugin/plugin.json +8 -0
  66. package/templates/claude-plugins/blog-manager/commands/blog.md +691 -0
  67. package/templates/claude-plugins/golden-loop-monitor/.claude-plugin/plugin.json +8 -0
  68. package/templates/claude-plugins/golden-loop-monitor/commands/loop-status.md +434 -0
  69. package/templates/claude-plugins/learning-tracker/.claude-plugin/plugin.json +8 -0
  70. package/templates/claude-plugins/learning-tracker/commands/my-patterns.md +282 -0
  71. package/templates/claude-plugins/memory-lens/.claude-plugin/plugin.json +8 -0
  72. package/templates/claude-plugins/memory-lens/commands/memory-search.md +181 -0
  73. package/templates/claude-plugins/pattern-coach/.claude-plugin/plugin.json +8 -0
  74. package/templates/claude-plugins/pattern-coach/commands/forge.md +365 -0
  75. package/templates/claude-plugins/project-schema-validator/.claude-plugin/plugin.json +8 -0
  76. package/templates/claude-plugins/project-schema-validator/commands/validate-schema.md +582 -0
  77. package/templates/claude-plugins-admin/AGENT_TEAM_PROPOSALS.md +819 -0
  78. package/templates/claude-plugins-admin/README.md +446 -0
  79. package/templates/claude-plugins-admin/autonomous-admin-agent/.claude-plugin/plugin.json +8 -0
  80. package/templates/claude-plugins-admin/autonomous-admin-agent/commands/agent.md +595 -0
  81. package/templates/claude-plugins-admin/backend-agent/.claude-plugin/plugin.json +8 -0
  82. package/templates/claude-plugins-admin/backend-agent/commands/backend.md +798 -0
  83. package/templates/claude-plugins-admin/deploy-guardian/.claude-plugin/plugin.json +8 -0
  84. package/templates/claude-plugins-admin/deploy-guardian/commands/deploy.md +554 -0
  85. package/templates/claude-plugins-admin/frontend-agent/.claude-plugin/plugin.json +8 -0
  86. package/templates/claude-plugins-admin/frontend-agent/commands/frontend.md +881 -0
  87. package/templates/claude-plugins-admin/mcp-server-manager/.claude-plugin/plugin.json +8 -0
  88. package/templates/claude-plugins-admin/mcp-server-manager/commands/mcp.md +85 -0
  89. package/templates/claude-plugins-admin/memory-system-monitor/.claude-plugin/plugin.json +8 -0
  90. package/templates/claude-plugins-admin/memory-system-monitor/commands/memory-health.md +569 -0
  91. package/templates/claude-plugins-admin/qa-agent/.claude-plugin/plugin.json +8 -0
  92. package/templates/claude-plugins-admin/qa-agent/commands/qa.md +863 -0
  93. package/templates/claude-plugins-admin/tech-lead-agent/.claude-plugin/plugin.json +8 -0
  94. package/templates/claude-plugins-admin/tech-lead-agent/commands/lead.md +732 -0
  95. package/templates/commands/continue.md +47 -0
  96. package/templates/cursor-hooks/after-agent-response.sh +117 -0
  97. package/templates/cursor-hooks/before-submit-prompt.sh +419 -0
  98. package/templates/cursor-hooks/hooks.json +20 -0
  99. package/templates/cursor-hooks/lib/contract.sh +320 -0
  100. package/templates/cursor-hooks/stop.sh +75 -0
  101. package/templates/cursor-rules/ekkos-memory.md +187 -0
  102. package/templates/hooks/assistant-response.sh +96 -0
  103. package/templates/hooks/hooks.json +28 -0
  104. package/templates/hooks/lib/contract.sh +320 -0
  105. package/templates/hooks/lib/state.sh +158 -0
  106. package/templates/hooks/session-start.ps1 +41 -0
  107. package/templates/hooks/session-start.sh +318 -0
  108. package/templates/hooks/stop.ps1 +16 -0
  109. package/templates/hooks/stop.sh +989 -0
  110. package/templates/hooks/user-prompt-submit.ps1 +174 -0
  111. package/templates/hooks/user-prompt-submit.sh +587 -0
  112. package/templates/hooks-node/lib/state.js +187 -0
  113. package/templates/hooks-node/stop.js +416 -0
  114. package/templates/hooks-node/user-prompt-submit.js +337 -0
  115. package/templates/plan-template.md +306 -0
  116. package/templates/rules/00-hooks-contract.mdc +89 -0
  117. package/templates/rules/30-ekkos-core.mdc +188 -0
  118. package/templates/rules/31-ekkos-messages.mdc +78 -0
  119. package/templates/skills/continue/SKILL.md +169 -0
  120. package/templates/skills/ekkOS_Deep_Recall/Skill.md +282 -0
  121. package/templates/skills/ekkOS_Learn/Skill.md +265 -0
  122. package/templates/skills/ekkOS_Memory_First/Skill.md +206 -0
  123. package/templates/skills/ekkOS_Plan_Assist/Skill.md +302 -0
  124. package/templates/skills/ekkOS_Preferences/Skill.md +247 -0
  125. package/templates/skills/ekkOS_Reflect/Skill.md +257 -0
  126. package/templates/skills/ekkOS_Safety/Skill.md +265 -0
  127. package/templates/skills/ekkOS_Schema/Skill.md +251 -0
  128. package/templates/skills/ekkOS_Summary/Skill.md +257 -0
  129. package/templates/skills/ekkOS_Vault/Skill.md +287 -0
  130. package/templates/skills/permissions/Skill.md +322 -0
  131. package/templates/spec-template.md +159 -0
  132. package/templates/windsurf-hooks/before-submit-prompt.sh +238 -0
  133. package/templates/windsurf-hooks/hooks.json +10 -0
  134. package/templates/windsurf-hooks/lib/contract.sh +320 -0
  135. package/templates/windsurf-rules/ekkos-memory.md +129 -0
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ekkOS Fast Capture & Restore - CLI for local cache operations
4
+ *
5
+ * Capture Commands:
6
+ * capture user <session_id> <session_name> <turn_id> <query> [project_path]
7
+ * capture response <session_id> <turn_id> <response> [tools] [files]
8
+ *
9
+ * Restore Commands:
10
+ * capture restore [session_name] [--json|--markdown|--n=N]
11
+ *
12
+ * ACK & Sync Commands (Phase 5):
13
+ * capture ack <session_id> <turn_id> - Update ACK cursor after Redis success
14
+ * capture sync [session_id] - Sync unACKed turns to Redis
15
+ * capture prune <session_id> - Remove safely ACKed turns
16
+ * capture cleanup - Prune all + evict old sessions
17
+ *
18
+ * Query Commands:
19
+ * capture list - List all cached sessions
20
+ * capture get <session_id> [n] - Get last N turns
21
+ * capture stats - Cache statistics
22
+ *
23
+ * This is a lightweight script designed for hook integration.
24
+ * Writes to local JSONL cache with minimal latency.
25
+ */
26
+ export {};
@@ -0,0 +1,461 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * ekkOS Fast Capture & Restore - CLI for local cache operations
5
+ *
6
+ * Capture Commands:
7
+ * capture user <session_id> <session_name> <turn_id> <query> [project_path]
8
+ * capture response <session_id> <turn_id> <response> [tools] [files]
9
+ *
10
+ * Restore Commands:
11
+ * capture restore [session_name] [--json|--markdown|--n=N]
12
+ *
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
18
+ *
19
+ * Query Commands:
20
+ * capture list - List all cached sessions
21
+ * capture get <session_id> [n] - Get last N turns
22
+ * capture stats - Cache statistics
23
+ *
24
+ * This is a lightweight script designed for hook integration.
25
+ * Writes to local JSONL cache with minimal latency.
26
+ */
27
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ var desc = Object.getOwnPropertyDescriptor(m, k);
30
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
31
+ desc = { enumerable: true, get: function() { return m[k]; } };
32
+ }
33
+ Object.defineProperty(o, k2, desc);
34
+ }) : (function(o, m, k, k2) {
35
+ if (k2 === undefined) k2 = k;
36
+ o[k2] = m[k];
37
+ }));
38
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
39
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
40
+ }) : function(o, v) {
41
+ o["default"] = v;
42
+ });
43
+ var __importStar = (this && this.__importStar) || (function () {
44
+ var ownKeys = function(o) {
45
+ ownKeys = Object.getOwnPropertyNames || function (o) {
46
+ var ar = [];
47
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
48
+ return ar;
49
+ };
50
+ return ownKeys(o);
51
+ };
52
+ return function (mod) {
53
+ if (mod && mod.__esModule) return mod;
54
+ var result = {};
55
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
56
+ __setModuleDefault(result, mod);
57
+ return result;
58
+ };
59
+ })();
60
+ Object.defineProperty(exports, "__esModule", { value: true });
61
+ const fs = __importStar(require("fs"));
62
+ const path = __importStar(require("path"));
63
+ const os = __importStar(require("os"));
64
+ const LocalSessionStore_js_1 = require("./LocalSessionStore.js");
65
+ const RestoreOrchestrator_js_1 = require("../restore/RestoreOrchestrator.js");
66
+ const store = new LocalSessionStore_js_1.LocalSessionStore();
67
+ // API configuration
68
+ const MEMORY_API_URL = process.env.EKKOS_API_URL || 'https://api.ekkos.dev';
69
+ const CONFIG_PATH = path.join(os.homedir(), '.ekkos', 'config.json');
70
+ /**
71
+ * Load auth token from config
72
+ */
73
+ function loadAuthToken() {
74
+ try {
75
+ if (fs.existsSync(CONFIG_PATH)) {
76
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
77
+ return config.hookApiKey || config.apiKey || '';
78
+ }
79
+ }
80
+ catch {
81
+ // Ignore config errors
82
+ }
83
+ return '';
84
+ }
85
+ async function main() {
86
+ const args = process.argv.slice(2);
87
+ const command = args[0];
88
+ if (!command) {
89
+ console.error('Usage: capture <user|response> ...');
90
+ process.exit(1);
91
+ }
92
+ try {
93
+ switch (command) {
94
+ 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 }));
113
+ }
114
+ else {
115
+ console.error(JSON.stringify({ success: false, error: result.error }));
116
+ process.exit(1);
117
+ }
118
+ break;
119
+ }
120
+ 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 }));
133
+ }
134
+ else {
135
+ console.error(JSON.stringify({ success: false, error: result.error }));
136
+ process.exit(1);
137
+ }
138
+ break;
139
+ }
140
+ case 'list': {
141
+ // capture list
142
+ const sessions = store.listSessions();
143
+ console.log(JSON.stringify(sessions, null, 2));
144
+ break;
145
+ }
146
+ case 'get': {
147
+ // capture get <session_id> [n]
148
+ const [, sessionId, nStr] = args;
149
+ if (!sessionId) {
150
+ console.error('Usage: capture get <session_id> [n]');
151
+ process.exit(1);
152
+ }
153
+ const n = nStr ? parseInt(nStr, 10) : 10;
154
+ const result = store.getLastTurns(sessionId, n);
155
+ if (result.success) {
156
+ console.log(JSON.stringify(result.data, null, 2));
157
+ }
158
+ else {
159
+ console.error(JSON.stringify({ error: result.error }));
160
+ process.exit(1);
161
+ }
162
+ break;
163
+ }
164
+ case 'stats': {
165
+ const stats = store.getStats();
166
+ console.log(JSON.stringify(stats, null, 2));
167
+ break;
168
+ }
169
+ case 'restore': {
170
+ // capture restore [session_name] [--json|--markdown|--n=N]
171
+ const orchestrator = new RestoreOrchestrator_js_1.RestoreOrchestrator();
172
+ let sessionName;
173
+ let outputFormat = 'json';
174
+ let lastN = 10;
175
+ for (let i = 1; i < args.length; i++) {
176
+ const arg = args[i];
177
+ if (arg === '--json') {
178
+ outputFormat = 'json';
179
+ }
180
+ else if (arg === '--markdown' || arg === '--md') {
181
+ outputFormat = 'markdown';
182
+ }
183
+ else if (arg.startsWith('--n=')) {
184
+ lastN = parseInt(arg.slice(4), 10) || 10;
185
+ }
186
+ else if (!arg.startsWith('-')) {
187
+ sessionName = arg;
188
+ }
189
+ }
190
+ const result = await orchestrator.restore({
191
+ session_name: sessionName,
192
+ last_n: lastN,
193
+ });
194
+ if (result.success && result.data) {
195
+ if (outputFormat === 'markdown') {
196
+ console.log(orchestrator.formatAsSystemReminder(result.data));
197
+ }
198
+ else {
199
+ console.log(JSON.stringify({
200
+ success: true,
201
+ source: result.data.source,
202
+ latency_ms: result.latency_ms,
203
+ session_name: result.data.session_name,
204
+ session_id: result.data.session_id,
205
+ turns_restored: result.data.restored_turns.length,
206
+ token_estimate: result.data.metadata.token_estimate,
207
+ }, null, 2));
208
+ }
209
+ }
210
+ else {
211
+ console.error(JSON.stringify({
212
+ success: false,
213
+ error: result.error,
214
+ latency_ms: result.latency_ms,
215
+ }));
216
+ process.exit(1);
217
+ }
218
+ break;
219
+ }
220
+ case 'ack': {
221
+ // capture ack <session_id> <turn_id>
222
+ // Update ACK cursor after successful Redis flush
223
+ const [, sessionId, turnIdStr] = args;
224
+ if (!sessionId || !turnIdStr) {
225
+ console.error('Usage: capture ack <session_id> <turn_id>');
226
+ process.exit(1);
227
+ }
228
+ const turnId = parseInt(turnIdStr, 10);
229
+ const result = store.ack(sessionId, turnId);
230
+ if (result.success) {
231
+ console.log(JSON.stringify({ success: true, acked_turn_id: turnId, latency_ms: result.latency_ms }));
232
+ }
233
+ else {
234
+ console.error(JSON.stringify({ success: false, error: result.error }));
235
+ process.exit(1);
236
+ }
237
+ break;
238
+ }
239
+ case 'prune': {
240
+ // capture prune <session_id>
241
+ // Remove turns safely below ACK threshold
242
+ const [, sessionId] = args;
243
+ if (!sessionId) {
244
+ console.error('Usage: capture prune <session_id>');
245
+ process.exit(1);
246
+ }
247
+ const meta = store.getSessionMeta(sessionId);
248
+ if (!meta) {
249
+ console.error(JSON.stringify({ success: false, error: 'Session not found' }));
250
+ process.exit(1);
251
+ }
252
+ const result = store.prune(sessionId);
253
+ if (result.success) {
254
+ console.log(JSON.stringify({
255
+ success: true,
256
+ pruned_turns: result.data,
257
+ acked_turn_id: meta.acked_turn_id,
258
+ latency_ms: result.latency_ms,
259
+ }));
260
+ }
261
+ else {
262
+ console.error(JSON.stringify({ success: false, error: result.error }));
263
+ process.exit(1);
264
+ }
265
+ break;
266
+ }
267
+ case 'sync': {
268
+ // capture sync [session_id]
269
+ // Sync unACKed turns to Redis, update ACK on success
270
+ const [, sessionIdArg] = args;
271
+ const authToken = loadAuthToken();
272
+ if (!authToken) {
273
+ console.error(JSON.stringify({ success: false, error: 'No auth token configured' }));
274
+ process.exit(1);
275
+ }
276
+ // Get sessions to sync
277
+ const sessions = sessionIdArg
278
+ ? [{ session_id: sessionIdArg, session_name: store.getSessionName(sessionIdArg) || 'unknown' }]
279
+ : store.listSessions();
280
+ let totalSynced = 0;
281
+ let totalFailed = 0;
282
+ for (const session of sessions) {
283
+ const meta = store.getSessionMeta(session.session_id);
284
+ if (!meta)
285
+ continue;
286
+ // Get unACKed turns (turns after acked_turn_id)
287
+ const turnsResult = store.getLastTurns(session.session_id, 100);
288
+ if (!turnsResult.success || !turnsResult.data)
289
+ continue;
290
+ const unackedTurns = turnsResult.data.filter((t) => t.turn_id > meta.acked_turn_id && t.assistant_response // Only complete turns
291
+ );
292
+ if (unackedTurns.length === 0)
293
+ continue;
294
+ // Sync each turn to Redis
295
+ let maxSyncedTurn = meta.acked_turn_id;
296
+ for (const turn of unackedTurns) {
297
+ try {
298
+ const payload = {
299
+ session_name: meta.session_name || session.session_name,
300
+ turn_number: turn.turn_id,
301
+ user_query: turn.user_query,
302
+ agent_response: turn.assistant_response,
303
+ tools_used: turn.tools_used || [],
304
+ files_referenced: turn.files_referenced || [],
305
+ };
306
+ const response = await fetch(`${MEMORY_API_URL}/api/v1/working/turn`, {
307
+ method: 'POST',
308
+ headers: {
309
+ Authorization: `Bearer ${authToken}`,
310
+ 'Content-Type': 'application/json',
311
+ },
312
+ body: JSON.stringify(payload),
313
+ signal: AbortSignal.timeout(5000),
314
+ });
315
+ if (response.ok) {
316
+ maxSyncedTurn = Math.max(maxSyncedTurn, turn.turn_id);
317
+ totalSynced++;
318
+ }
319
+ else {
320
+ totalFailed++;
321
+ }
322
+ }
323
+ catch {
324
+ totalFailed++;
325
+ }
326
+ }
327
+ // Update ACK cursor if any turns synced
328
+ if (maxSyncedTurn > meta.acked_turn_id) {
329
+ store.ack(session.session_id, maxSyncedTurn);
330
+ }
331
+ }
332
+ console.log(JSON.stringify({
333
+ success: true,
334
+ turns_synced: totalSynced,
335
+ turns_failed: totalFailed,
336
+ sessions_processed: sessions.length,
337
+ }));
338
+ break;
339
+ }
340
+ 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;
345
+ const authToken = loadAuthToken();
346
+ if (!authToken) {
347
+ console.error(JSON.stringify({ success: false, error: 'No auth token configured' }));
348
+ process.exit(1);
349
+ }
350
+ // Get sessions to sync
351
+ const sessions = sessionIdArg
352
+ ? [{ session_id: sessionIdArg, session_name: store.getSessionName(sessionIdArg) || 'unknown' }]
353
+ : store.listSessions();
354
+ let totalSynced = 0;
355
+ let totalFailed = 0;
356
+ let totalSkipped = 0;
357
+ for (const session of sessions) {
358
+ const meta = store.getSessionMeta(session.session_id);
359
+ if (!meta)
360
+ continue;
361
+ // Get ALL turns from local cache
362
+ const turnsResult = store.getLastTurns(session.session_id, 1000);
363
+ if (!turnsResult.success || !turnsResult.data)
364
+ continue;
365
+ // Filter to turns not yet ACKed for Supabase
366
+ const supabaseAck = meta.supabase_acked_turn_id || 0;
367
+ const unackedTurns = turnsResult.data.filter((t) => t.turn_id > supabaseAck && t.assistant_response // Only complete turns
368
+ );
369
+ if (unackedTurns.length === 0) {
370
+ totalSkipped += turnsResult.data.length;
371
+ continue;
372
+ }
373
+ console.error(`[sync-supabase] Syncing ${unackedTurns.length} turns for ${meta.session_name}...`);
374
+ let maxSyncedTurn = supabaseAck;
375
+ for (const turn of unackedTurns) {
376
+ try {
377
+ const payload = {
378
+ user_query: turn.user_query,
379
+ assistant_response: turn.assistant_response,
380
+ session_id: session.session_id,
381
+ metadata: {
382
+ source: 'claude-code',
383
+ turn_number: turn.turn_id,
384
+ session_name: meta.session_name,
385
+ tools_used: turn.tools_used || [],
386
+ files_referenced: turn.files_referenced || [],
387
+ backfill: true,
388
+ },
389
+ };
390
+ const response = await fetch(`${MEMORY_API_URL}/api/v1/memory/capture`, {
391
+ method: 'POST',
392
+ headers: {
393
+ Authorization: `Bearer ${authToken}`,
394
+ 'Content-Type': 'application/json',
395
+ },
396
+ body: JSON.stringify(payload),
397
+ signal: AbortSignal.timeout(10000),
398
+ });
399
+ if (response.ok) {
400
+ maxSyncedTurn = Math.max(maxSyncedTurn, turn.turn_id);
401
+ totalSynced++;
402
+ }
403
+ else {
404
+ const errorText = await response.text();
405
+ console.error(`[sync-supabase] Failed turn ${turn.turn_id}: ${response.status} - ${errorText}`);
406
+ totalFailed++;
407
+ }
408
+ }
409
+ catch (err) {
410
+ console.error(`[sync-supabase] Error syncing turn ${turn.turn_id}:`, err);
411
+ totalFailed++;
412
+ }
413
+ }
414
+ // Update Supabase ACK cursor if any turns synced
415
+ if (maxSyncedTurn > supabaseAck) {
416
+ store.ackSupabase(session.session_id, maxSyncedTurn);
417
+ }
418
+ }
419
+ console.log(JSON.stringify({
420
+ success: true,
421
+ turns_synced: totalSynced,
422
+ turns_failed: totalFailed,
423
+ turns_skipped: totalSkipped,
424
+ sessions_processed: sessions.length,
425
+ }));
426
+ break;
427
+ }
428
+ case 'cleanup': {
429
+ // capture cleanup
430
+ // Evict old sessions and prune all sessions
431
+ const sessions = store.listSessions();
432
+ let totalPruned = 0;
433
+ // Prune all sessions
434
+ for (const session of sessions) {
435
+ const result = store.prune(session.session_id);
436
+ if (result.success && result.data) {
437
+ totalPruned += result.data;
438
+ }
439
+ }
440
+ // Evict old sessions if over limit
441
+ const evicted = store.evictOldSessions();
442
+ console.log(JSON.stringify({
443
+ success: true,
444
+ turns_pruned: totalPruned,
445
+ sessions_evicted: evicted,
446
+ remaining_sessions: store.listSessions().length,
447
+ }));
448
+ break;
449
+ }
450
+ default:
451
+ console.error(`Unknown command: ${command}`);
452
+ console.error('Commands: user, response, list, get, stats, restore, ack, prune, sync, sync-supabase, cleanup');
453
+ process.exit(1);
454
+ }
455
+ }
456
+ catch (err) {
457
+ console.error(JSON.stringify({ success: false, error: String(err) }));
458
+ process.exit(1);
459
+ }
460
+ }
461
+ main();
@@ -0,0 +1,7 @@
1
+ /**
2
+ * ekkOS Fast /continue - Local Cache Module
3
+ *
4
+ * Tier 0 of the 3-tier restore chain for near-zero context loss.
5
+ */
6
+ export * from './types.js';
7
+ export * from './LocalSessionStore.js';
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ /**
3
+ * ekkOS Fast /continue - Local Cache Module
4
+ *
5
+ * Tier 0 of the 3-tier restore chain for near-zero context loss.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
19
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ __exportStar(require("./types.js"), exports);
23
+ __exportStar(require("./LocalSessionStore.js"), exports);
@@ -0,0 +1,147 @@
1
+ /**
2
+ * ekkOS Fast /continue - Local Cache Types
3
+ *
4
+ * Defines the data structures for the 3-tier restore chain:
5
+ * Tier 0: Local JSONL cache (~20ms)
6
+ * Tier 1: Redis hot cache (~150ms)
7
+ * Tier 2: Supabase cold store (~500ms)
8
+ */
9
+ /**
10
+ * A single conversation turn (user + assistant pair)
11
+ */
12
+ export interface Turn {
13
+ turn_id: number;
14
+ ts: string;
15
+ user_query: string;
16
+ assistant_response: string;
17
+ tools_used: string[];
18
+ files_referenced: string[];
19
+ diffs?: string[];
20
+ token_estimate?: number;
21
+ is_complete?: boolean;
22
+ }
23
+ /**
24
+ * Known placeholder strings that indicate incomplete/invalid responses
25
+ * These should NOT be treated as valid assistant_response content
26
+ */
27
+ export declare const INVALID_RESPONSE_PATTERNS: string[];
28
+ /**
29
+ * Check if an assistant_response is valid (complete, not a placeholder)
30
+ */
31
+ export declare function isValidAssistantResponse(response: string | undefined | null): boolean;
32
+ /**
33
+ * Session metadata stored in index.json
34
+ */
35
+ export interface SessionIndex {
36
+ [session_name: string]: SessionIndexEntry;
37
+ }
38
+ export interface SessionIndexEntry {
39
+ session_id: string;
40
+ last_active_ts: string;
41
+ last_turn_id: number;
42
+ acked_turn_id: number;
43
+ project_path?: string;
44
+ }
45
+ /**
46
+ * Per-session metadata stored in {session_id}.meta.json
47
+ */
48
+ export interface SessionMeta {
49
+ session_id: string;
50
+ session_name: string;
51
+ acked_turn_id: number;
52
+ supabase_acked_turn_id?: number;
53
+ last_flush_ts: string;
54
+ turn_count: number;
55
+ project_path?: string;
56
+ created_at: string;
57
+ }
58
+ /**
59
+ * Restore payload returned by RestoreOrchestrator
60
+ */
61
+ export interface RestorePayload {
62
+ session_id: string;
63
+ session_name: string;
64
+ source: 'local' | 'redis' | 'supabase';
65
+ restored_turns: Turn[];
66
+ latest: {
67
+ user_query: string;
68
+ assistant_response: string;
69
+ };
70
+ pending_turn?: {
71
+ turn_id: number;
72
+ user_query: string;
73
+ ts: string;
74
+ };
75
+ directives: Directive[];
76
+ patterns: Pattern[];
77
+ metadata: {
78
+ acked_turn_id: number;
79
+ last_flush_ts: string;
80
+ token_estimate: number;
81
+ complete_turn_count: number;
82
+ has_pending: boolean;
83
+ };
84
+ }
85
+ /**
86
+ * User directive (MUST/NEVER/PREFER/AVOID)
87
+ */
88
+ export interface Directive {
89
+ id: string;
90
+ type: 'MUST' | 'NEVER' | 'PREFER' | 'AVOID';
91
+ rule: string;
92
+ reason?: string;
93
+ priority: number;
94
+ }
95
+ /**
96
+ * Pattern from ekkOS memory
97
+ */
98
+ export interface Pattern {
99
+ id: string;
100
+ title: string;
101
+ problem: string;
102
+ solution: string;
103
+ success_rate: number;
104
+ applied_count: number;
105
+ }
106
+ /**
107
+ * Options for restore operation
108
+ */
109
+ export interface RestoreOptions {
110
+ session_name?: string;
111
+ session_id?: string;
112
+ last_n?: number;
113
+ include_directives?: boolean;
114
+ include_patterns?: boolean;
115
+ max_tokens?: number;
116
+ }
117
+ /**
118
+ * Result of a cache operation
119
+ */
120
+ export interface CacheResult<T> {
121
+ success: boolean;
122
+ data?: T;
123
+ error?: string;
124
+ source?: 'local' | 'redis' | 'supabase';
125
+ latency_ms?: number;
126
+ }
127
+ /**
128
+ * Local cache configuration
129
+ */
130
+ export interface LocalCacheConfig {
131
+ cache_dir: string;
132
+ max_sessions: number;
133
+ max_turns_per_session: number;
134
+ safety_margin: number;
135
+ flush_interval_ms: number;
136
+ }
137
+ /**
138
+ * Session list entry (for /continue sessions)
139
+ */
140
+ export interface SessionListEntry {
141
+ session_name: string;
142
+ session_id: string;
143
+ last_active_ts: string;
144
+ turn_count: number;
145
+ project_path?: string;
146
+ is_current: boolean;
147
+ }