@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,481 @@
1
+ "use strict";
2
+ /**
3
+ * ekkOS Fast /continue - Restore Orchestrator
4
+ *
5
+ * Implements the 3-tier restore chain for near-zero context loss:
6
+ * - Tier 0: Local JSONL cache (~20ms)
7
+ * - Tier 1: Redis hot cache (~150ms)
8
+ * - Tier 2: Supabase cold store (~500ms)
9
+ *
10
+ * Falls back through tiers on miss, tracks which tier succeeded.
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.restoreOrchestrator = exports.RestoreOrchestrator = void 0;
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
49
+ const os = __importStar(require("os"));
50
+ const LocalSessionStore_js_1 = require("../cache/LocalSessionStore.js");
51
+ const types_js_1 = require("../cache/types.js");
52
+ // API configuration
53
+ const MEMORY_API_URL = process.env.EKKOS_API_URL || 'https://api.ekkos.dev';
54
+ const CONFIG_PATH = path.join(os.homedir(), '.ekkos', 'config.json');
55
+ /**
56
+ * Load auth token from config
57
+ */
58
+ function loadAuthToken() {
59
+ try {
60
+ if (fs.existsSync(CONFIG_PATH)) {
61
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
62
+ return config.hookApiKey || config.apiKey || '';
63
+ }
64
+ }
65
+ catch {
66
+ // Ignore config errors
67
+ }
68
+ return '';
69
+ }
70
+ /**
71
+ * RestoreOrchestrator - Tiered restore for /continue
72
+ */
73
+ class RestoreOrchestrator {
74
+ constructor() {
75
+ this.localStore = new LocalSessionStore_js_1.LocalSessionStore();
76
+ this.authToken = loadAuthToken();
77
+ }
78
+ /**
79
+ * Main restore function - attempts tiers in order
80
+ */
81
+ async restore(options = {}) {
82
+ const startTime = Date.now();
83
+ const lastN = options.last_n || 10;
84
+ // Resolve session
85
+ let sessionId = options.session_id;
86
+ let sessionName = options.session_name;
87
+ if (sessionName && !sessionId) {
88
+ sessionId = this.localStore.getSessionId(sessionName) || undefined;
89
+ }
90
+ if (!sessionId && !sessionName) {
91
+ // Get most recent session
92
+ const sessions = this.localStore.listSessions();
93
+ if (sessions.length > 0) {
94
+ sessionId = sessions[0].session_id;
95
+ sessionName = sessions[0].session_name;
96
+ }
97
+ }
98
+ if (!sessionId) {
99
+ return {
100
+ success: false,
101
+ error: 'No session specified and no recent sessions found',
102
+ latency_ms: Date.now() - startTime,
103
+ };
104
+ }
105
+ // Try Tier 0: Local cache
106
+ const localResult = await this.restoreFromLocal(sessionId, sessionName || '', lastN);
107
+ if (localResult.success && localResult.data) {
108
+ return {
109
+ ...localResult,
110
+ latency_ms: Date.now() - startTime,
111
+ };
112
+ }
113
+ // Try Tier 1: Redis
114
+ const redisResult = await this.restoreFromRedis(sessionName || sessionId, lastN);
115
+ if (redisResult.success && redisResult.data) {
116
+ return {
117
+ ...redisResult,
118
+ latency_ms: Date.now() - startTime,
119
+ };
120
+ }
121
+ // Try Tier 2: Supabase
122
+ const supabaseResult = await this.restoreFromSupabase(sessionId, lastN);
123
+ if (supabaseResult.success && supabaseResult.data) {
124
+ return {
125
+ ...supabaseResult,
126
+ latency_ms: Date.now() - startTime,
127
+ };
128
+ }
129
+ // All tiers failed
130
+ return {
131
+ success: false,
132
+ error: `All restore tiers failed. Local: ${localResult.error}, Redis: ${redisResult.error}, Supabase: ${supabaseResult.error}`,
133
+ latency_ms: Date.now() - startTime,
134
+ };
135
+ }
136
+ /**
137
+ * Tier 0: Restore from local JSONL cache
138
+ */
139
+ async restoreFromLocal(sessionId, sessionName, lastN) {
140
+ const startTime = Date.now();
141
+ const turnsResult = this.localStore.getLastTurns(sessionId, lastN);
142
+ if (!turnsResult.success || !turnsResult.data || turnsResult.data.length === 0) {
143
+ return {
144
+ success: false,
145
+ error: turnsResult.error || 'No turns found in local cache',
146
+ source: 'local',
147
+ latency_ms: Date.now() - startTime,
148
+ };
149
+ }
150
+ // Mark each turn as complete or incomplete using stricter validation
151
+ const allTurns = turnsResult.data.map((t) => ({
152
+ ...t,
153
+ is_complete: (0, types_js_1.isValidAssistantResponse)(t.assistant_response),
154
+ }));
155
+ // Separate complete turns from incomplete (pending) turns
156
+ const completeTurns = allTurns.filter((t) => t.is_complete);
157
+ const pendingTurns = allTurns.filter((t) => !t.is_complete);
158
+ // Get the most recent pending turn (user query not yet answered)
159
+ const pendingTurn = pendingTurns.length > 0 ? pendingTurns[pendingTurns.length - 1] : undefined;
160
+ if (completeTurns.length === 0) {
161
+ // No complete turns, but might have pending user query
162
+ if (pendingTurn) {
163
+ return {
164
+ success: false,
165
+ error: `No complete turns found. Pending user query: "${pendingTurn.user_query.slice(0, 50)}..."`,
166
+ source: 'local',
167
+ latency_ms: Date.now() - startTime,
168
+ };
169
+ }
170
+ return {
171
+ success: false,
172
+ error: 'No complete turns found (all turns missing assistant_response)',
173
+ source: 'local',
174
+ latency_ms: Date.now() - startTime,
175
+ };
176
+ }
177
+ // Use complete turns for context, but include pending for awareness
178
+ const turns = completeTurns;
179
+ const latest = turns[turns.length - 1]; // Guaranteed to be complete
180
+ const meta = this.localStore.getSessionMeta(sessionId);
181
+ // Estimate tokens (rough: 4 chars per token)
182
+ const totalChars = turns.reduce((sum, t) => sum + t.user_query.length + t.assistant_response.length, 0);
183
+ const payload = {
184
+ session_id: sessionId,
185
+ session_name: sessionName || this.localStore.getSessionName(sessionId) || 'unknown',
186
+ source: 'local',
187
+ restored_turns: turns,
188
+ latest: {
189
+ user_query: latest.user_query,
190
+ assistant_response: latest.assistant_response,
191
+ },
192
+ pending_turn: pendingTurn
193
+ ? {
194
+ turn_id: pendingTurn.turn_id,
195
+ user_query: pendingTurn.user_query,
196
+ ts: pendingTurn.ts,
197
+ }
198
+ : undefined,
199
+ directives: [], // Will be fetched separately if needed
200
+ patterns: [],
201
+ metadata: {
202
+ acked_turn_id: meta?.acked_turn_id || 0,
203
+ last_flush_ts: meta?.last_flush_ts || new Date().toISOString(),
204
+ token_estimate: Math.ceil(totalChars / 4),
205
+ complete_turn_count: completeTurns.length,
206
+ has_pending: !!pendingTurn,
207
+ },
208
+ };
209
+ return {
210
+ success: true,
211
+ data: payload,
212
+ source: 'local',
213
+ latency_ms: Date.now() - startTime,
214
+ };
215
+ }
216
+ /**
217
+ * Tier 1: Restore from Redis via API
218
+ */
219
+ async restoreFromRedis(sessionName, lastN) {
220
+ const startTime = Date.now();
221
+ if (!this.authToken) {
222
+ return {
223
+ success: false,
224
+ error: 'No auth token configured',
225
+ source: 'redis',
226
+ latency_ms: Date.now() - startTime,
227
+ };
228
+ }
229
+ try {
230
+ const url = `${MEMORY_API_URL}/api/v1/working/restore?session=${encodeURIComponent(sessionName)}&full=true&limit=${lastN}`;
231
+ const response = await fetch(url, {
232
+ method: 'GET',
233
+ headers: {
234
+ Authorization: `Bearer ${this.authToken}`,
235
+ 'Content-Type': 'application/json',
236
+ },
237
+ signal: AbortSignal.timeout(5000), // 5 second timeout
238
+ });
239
+ if (!response.ok) {
240
+ return {
241
+ success: false,
242
+ error: `Redis API returned ${response.status}`,
243
+ source: 'redis',
244
+ latency_ms: Date.now() - startTime,
245
+ };
246
+ }
247
+ const data = await response.json();
248
+ if (data.mode === 'full_restore' && data.turns && data.turns.length > 0) {
249
+ const allTurns = data.turns.map((t, idx) => ({
250
+ turn_id: t.turn_number || idx + 1,
251
+ ts: t.timestamp || new Date().toISOString(),
252
+ user_query: t.user?.query || '',
253
+ assistant_response: t.agent?.response || '',
254
+ tools_used: t.agent?.tools_used || [],
255
+ files_referenced: t.user?.files_referenced || [],
256
+ is_complete: (0, types_js_1.isValidAssistantResponse)(t.agent?.response),
257
+ }));
258
+ // Separate complete turns from pending
259
+ const completeTurns = allTurns.filter((t) => t.is_complete);
260
+ const pendingTurns = allTurns.filter((t) => !t.is_complete);
261
+ const pendingTurn = pendingTurns.length > 0 ? pendingTurns[pendingTurns.length - 1] : undefined;
262
+ if (completeTurns.length === 0) {
263
+ return {
264
+ success: false,
265
+ error: pendingTurn
266
+ ? `No complete turns in Redis. Pending: "${pendingTurn.user_query.slice(0, 50)}..."`
267
+ : 'No complete turns found in Redis',
268
+ source: 'redis',
269
+ latency_ms: Date.now() - startTime,
270
+ };
271
+ }
272
+ const turns = completeTurns;
273
+ const latest = turns[turns.length - 1]; // Guaranteed to be complete
274
+ const payload = {
275
+ session_id: data.session_id || sessionName,
276
+ session_name: data.session_name || sessionName,
277
+ source: 'redis',
278
+ restored_turns: turns,
279
+ latest: {
280
+ user_query: latest.user_query,
281
+ assistant_response: latest.assistant_response,
282
+ },
283
+ pending_turn: pendingTurn
284
+ ? {
285
+ turn_id: pendingTurn.turn_id,
286
+ user_query: pendingTurn.user_query,
287
+ ts: pendingTurn.ts,
288
+ }
289
+ : undefined,
290
+ directives: [],
291
+ patterns: [],
292
+ metadata: {
293
+ acked_turn_id: turns.length,
294
+ last_flush_ts: new Date().toISOString(),
295
+ token_estimate: data.token_estimate || 0,
296
+ complete_turn_count: completeTurns.length,
297
+ has_pending: !!pendingTurn,
298
+ },
299
+ };
300
+ return {
301
+ success: true,
302
+ data: payload,
303
+ source: 'redis',
304
+ latency_ms: Date.now() - startTime,
305
+ };
306
+ }
307
+ return {
308
+ success: false,
309
+ error: 'No turns found in Redis',
310
+ source: 'redis',
311
+ latency_ms: Date.now() - startTime,
312
+ };
313
+ }
314
+ catch (err) {
315
+ return {
316
+ success: false,
317
+ error: err instanceof Error ? err.message : String(err),
318
+ source: 'redis',
319
+ latency_ms: Date.now() - startTime,
320
+ };
321
+ }
322
+ }
323
+ /**
324
+ * Tier 2: Restore from Supabase via API
325
+ */
326
+ async restoreFromSupabase(sessionId, lastN) {
327
+ const startTime = Date.now();
328
+ if (!this.authToken) {
329
+ return {
330
+ success: false,
331
+ error: 'No auth token configured',
332
+ source: 'supabase',
333
+ latency_ms: Date.now() - startTime,
334
+ };
335
+ }
336
+ try {
337
+ // Use the episodic memory API to get conversations
338
+ const url = `${MEMORY_API_URL}/api/v1/memory/recall?session_id=${encodeURIComponent(sessionId)}&limit=${lastN}`;
339
+ const response = await fetch(url, {
340
+ method: 'GET',
341
+ headers: {
342
+ Authorization: `Bearer ${this.authToken}`,
343
+ 'Content-Type': 'application/json',
344
+ },
345
+ signal: AbortSignal.timeout(10000), // 10 second timeout for cold storage
346
+ });
347
+ if (!response.ok) {
348
+ return {
349
+ success: false,
350
+ error: `Supabase API returned ${response.status}`,
351
+ source: 'supabase',
352
+ latency_ms: Date.now() - startTime,
353
+ };
354
+ }
355
+ const data = await response.json();
356
+ if (data.conversations && data.conversations.length > 0) {
357
+ const allTurns = data.conversations.map((c, idx) => ({
358
+ turn_id: idx + 1,
359
+ ts: c.processed_at || c.created_at || new Date().toISOString(),
360
+ user_query: c.user_query || '',
361
+ assistant_response: c.assistant_response || '',
362
+ tools_used: [],
363
+ files_referenced: c.metadata?.file_changes?.map((f) => f.path) || [],
364
+ is_complete: (0, types_js_1.isValidAssistantResponse)(c.assistant_response),
365
+ }));
366
+ // Separate complete turns from pending
367
+ const completeTurns = allTurns.filter((t) => t.is_complete);
368
+ const pendingTurns = allTurns.filter((t) => !t.is_complete);
369
+ const pendingTurn = pendingTurns.length > 0 ? pendingTurns[pendingTurns.length - 1] : undefined;
370
+ if (completeTurns.length === 0) {
371
+ return {
372
+ success: false,
373
+ error: pendingTurn
374
+ ? `No complete turns in Supabase. Pending: "${pendingTurn.user_query.slice(0, 50)}..."`
375
+ : 'No complete turns found in Supabase',
376
+ source: 'supabase',
377
+ latency_ms: Date.now() - startTime,
378
+ };
379
+ }
380
+ const turns = completeTurns;
381
+ const latest = turns[turns.length - 1]; // Guaranteed to be complete
382
+ const payload = {
383
+ session_id: sessionId,
384
+ session_name: data.session_name || 'unknown',
385
+ source: 'supabase',
386
+ restored_turns: turns,
387
+ latest: {
388
+ user_query: latest.user_query,
389
+ assistant_response: latest.assistant_response,
390
+ },
391
+ pending_turn: pendingTurn
392
+ ? {
393
+ turn_id: pendingTurn.turn_id,
394
+ user_query: pendingTurn.user_query,
395
+ ts: pendingTurn.ts,
396
+ }
397
+ : undefined,
398
+ directives: [],
399
+ patterns: [],
400
+ metadata: {
401
+ acked_turn_id: turns.length,
402
+ last_flush_ts: new Date().toISOString(),
403
+ token_estimate: 0,
404
+ complete_turn_count: completeTurns.length,
405
+ has_pending: !!pendingTurn,
406
+ },
407
+ };
408
+ return {
409
+ success: true,
410
+ data: payload,
411
+ source: 'supabase',
412
+ latency_ms: Date.now() - startTime,
413
+ };
414
+ }
415
+ return {
416
+ success: false,
417
+ error: 'No conversations found in Supabase',
418
+ source: 'supabase',
419
+ latency_ms: Date.now() - startTime,
420
+ };
421
+ }
422
+ catch (err) {
423
+ return {
424
+ success: false,
425
+ error: err instanceof Error ? err.message : String(err),
426
+ source: 'supabase',
427
+ latency_ms: Date.now() - startTime,
428
+ };
429
+ }
430
+ }
431
+ /**
432
+ * Format RestorePayload as system-reminder markdown
433
+ */
434
+ formatAsSystemReminder(payload) {
435
+ const lines = [
436
+ '<system-reminder>',
437
+ 'CONTEXT RESTORED (ekkOS /continue)',
438
+ `Session: ${payload.session_name} (${payload.session_id})`,
439
+ `Source: ${payload.source}`,
440
+ `Turns restored: ${payload.restored_turns.length}`,
441
+ '',
442
+ '## Last User Request',
443
+ payload.latest.user_query,
444
+ '',
445
+ '## Last Assistant Response',
446
+ payload.latest.assistant_response.slice(0, 2000),
447
+ payload.latest.assistant_response.length > 2000 ? '\n[...truncated...]' : '',
448
+ '',
449
+ '## Recent Turns (older → newer)',
450
+ ];
451
+ // Add turn summaries (skip last one since it's shown in detail above)
452
+ const turnsToShow = payload.restored_turns.slice(0, -1);
453
+ for (let i = 0; i < turnsToShow.length; i++) {
454
+ const turn = turnsToShow[i];
455
+ const userSnippet = turn.user_query.slice(0, 100) + (turn.user_query.length > 100 ? '...' : '');
456
+ const assistantSnippet = turn.assistant_response.slice(0, 100) + (turn.assistant_response.length > 100 ? '...' : '');
457
+ lines.push(`${i + 1}) U: ${userSnippet}`);
458
+ lines.push(` A: ${assistantSnippet}`);
459
+ }
460
+ lines.push('');
461
+ lines.push('INSTRUCTION: Resume seamlessly where you left off.');
462
+ lines.push('Do not ask "what were we doing?"');
463
+ lines.push('Start your response with: "✓ Continuing -"');
464
+ lines.push('</system-reminder>');
465
+ return lines.join('\n');
466
+ }
467
+ /**
468
+ * Get restore statistics
469
+ */
470
+ getStats() {
471
+ const stats = this.localStore.getStats();
472
+ return {
473
+ local_sessions: stats.session_count,
474
+ local_turns: stats.total_turns,
475
+ local_size_bytes: stats.cache_size_bytes,
476
+ };
477
+ }
478
+ }
479
+ exports.RestoreOrchestrator = RestoreOrchestrator;
480
+ // Export singleton instance
481
+ exports.restoreOrchestrator = new RestoreOrchestrator();
@@ -0,0 +1,4 @@
1
+ /**
2
+ * ekkOS Fast /continue - Restore Module
3
+ */
4
+ export * from './RestoreOrchestrator.js';
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ /**
3
+ * ekkOS Fast /continue - Restore Module
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
17
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ __exportStar(require("./RestoreOrchestrator.js"), exports);
@@ -0,0 +1,29 @@
1
+ export declare const isWindows: boolean;
2
+ export declare const isMac: boolean;
3
+ export declare const isLinux: boolean;
4
+ export declare const PLATFORM_URL = "https://platform.ekkos.dev";
5
+ export declare const MCP_API_URL = "https://mcp.ekkos.dev";
6
+ export declare const HOME_DIR: string;
7
+ export declare const EKKOS_DIR: string;
8
+ export declare const EKKOS_CONFIG: string;
9
+ export declare const CLAUDE_DIR: string;
10
+ export declare const CLAUDE_CONFIG: string;
11
+ export declare const CLAUDE_SETTINGS: string;
12
+ export declare const CLAUDE_HOOKS_DIR: string;
13
+ export declare const CLAUDE_SKILLS_DIR: string;
14
+ export declare const CLAUDE_AGENTS_DIR: string;
15
+ export declare const CLAUDE_PLUGINS_DIR: string;
16
+ export declare const CLAUDE_STATE_DIR: string;
17
+ export declare const CLAUDE_MD: string;
18
+ export declare const CURSOR_DIR: string;
19
+ export declare const CURSOR_MCP: string;
20
+ export declare const WINDSURF_DIR: string;
21
+ export declare const WINDSURF_MCP: string;
22
+ /**
23
+ * Detect which IDEs are installed on this system
24
+ */
25
+ export declare function detectInstalledIDEs(): string[];
26
+ /**
27
+ * Get the current running IDE from environment
28
+ */
29
+ export declare function detectCurrentIDE(): string | null;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WINDSURF_MCP = exports.WINDSURF_DIR = exports.CURSOR_MCP = exports.CURSOR_DIR = exports.CLAUDE_MD = exports.CLAUDE_STATE_DIR = exports.CLAUDE_PLUGINS_DIR = exports.CLAUDE_AGENTS_DIR = exports.CLAUDE_SKILLS_DIR = exports.CLAUDE_HOOKS_DIR = exports.CLAUDE_SETTINGS = exports.CLAUDE_CONFIG = exports.CLAUDE_DIR = exports.EKKOS_CONFIG = exports.EKKOS_DIR = exports.HOME_DIR = exports.MCP_API_URL = exports.PLATFORM_URL = exports.isLinux = exports.isMac = exports.isWindows = void 0;
4
+ exports.detectInstalledIDEs = detectInstalledIDEs;
5
+ exports.detectCurrentIDE = detectCurrentIDE;
6
+ const os_1 = require("os");
7
+ const path_1 = require("path");
8
+ const fs_1 = require("fs");
9
+ exports.isWindows = (0, os_1.platform)() === 'win32';
10
+ exports.isMac = (0, os_1.platform)() === 'darwin';
11
+ exports.isLinux = (0, os_1.platform)() === 'linux';
12
+ exports.PLATFORM_URL = 'https://platform.ekkos.dev';
13
+ exports.MCP_API_URL = 'https://mcp.ekkos.dev';
14
+ // Common directories
15
+ exports.HOME_DIR = (0, os_1.homedir)();
16
+ exports.EKKOS_DIR = (0, path_1.join)(exports.HOME_DIR, '.ekkos');
17
+ exports.EKKOS_CONFIG = (0, path_1.join)(exports.EKKOS_DIR, 'config.json');
18
+ exports.CLAUDE_DIR = (0, path_1.join)(exports.HOME_DIR, '.claude');
19
+ exports.CLAUDE_CONFIG = (0, path_1.join)(exports.HOME_DIR, '.claude.json');
20
+ exports.CLAUDE_SETTINGS = (0, path_1.join)(exports.CLAUDE_DIR, 'settings.json');
21
+ exports.CLAUDE_HOOKS_DIR = (0, path_1.join)(exports.CLAUDE_DIR, 'hooks');
22
+ exports.CLAUDE_SKILLS_DIR = (0, path_1.join)(exports.CLAUDE_DIR, 'skills');
23
+ exports.CLAUDE_AGENTS_DIR = (0, path_1.join)(exports.CLAUDE_DIR, 'agents');
24
+ exports.CLAUDE_PLUGINS_DIR = (0, path_1.join)(exports.CLAUDE_DIR, 'plugins', 'ekkos');
25
+ exports.CLAUDE_STATE_DIR = (0, path_1.join)(exports.CLAUDE_DIR, 'state');
26
+ exports.CLAUDE_MD = (0, path_1.join)(exports.CLAUDE_DIR, 'CLAUDE.md');
27
+ exports.CURSOR_DIR = (0, path_1.join)(exports.HOME_DIR, '.cursor');
28
+ exports.CURSOR_MCP = (0, path_1.join)(exports.CURSOR_DIR, 'mcp.json');
29
+ exports.WINDSURF_DIR = (0, path_1.join)(exports.HOME_DIR, '.codeium', 'windsurf');
30
+ exports.WINDSURF_MCP = (0, path_1.join)(exports.WINDSURF_DIR, 'mcp_config.json');
31
+ /**
32
+ * Detect which IDEs are installed on this system
33
+ */
34
+ function detectInstalledIDEs() {
35
+ const ides = [];
36
+ // Check Claude Code
37
+ if ((0, fs_1.existsSync)(exports.CLAUDE_DIR) || process.env.CLAUDE_CODE) {
38
+ ides.push('claude');
39
+ }
40
+ // Check Cursor
41
+ if ((0, fs_1.existsSync)(exports.CURSOR_DIR)) {
42
+ ides.push('cursor');
43
+ }
44
+ // Check Windsurf
45
+ if ((0, fs_1.existsSync)(exports.WINDSURF_DIR)) {
46
+ ides.push('windsurf');
47
+ }
48
+ return ides;
49
+ }
50
+ /**
51
+ * Get the current running IDE from environment
52
+ */
53
+ function detectCurrentIDE() {
54
+ const termProgram = process.env.TERM_PROGRAM?.toLowerCase() || '';
55
+ const termEmulator = process.env.TERMINAL_EMULATOR?.toLowerCase() || '';
56
+ if (termProgram.includes('cursor'))
57
+ return 'cursor';
58
+ if (termProgram.includes('vscode') || termEmulator.includes('vscode'))
59
+ return 'vscode';
60
+ if (termProgram.includes('windsurf') || termProgram.includes('codeium'))
61
+ return 'windsurf';
62
+ if (process.env.CLAUDE_CODE)
63
+ return 'claude';
64
+ return null;
65
+ }