@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.
- package/dist/cache/LocalSessionStore.d.ts +129 -0
- package/dist/cache/LocalSessionStore.js +688 -0
- package/dist/cache/capture.d.ts +26 -0
- package/dist/cache/capture.js +461 -0
- package/dist/cache/index.d.ts +7 -0
- package/dist/cache/index.js +23 -0
- package/dist/cache/types.d.ts +147 -0
- package/dist/cache/types.js +40 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.js +478 -0
- package/dist/commands/run.d.ts +12 -0
- package/dist/commands/run.js +829 -0
- package/dist/commands/setup.d.ts +6 -0
- package/dist/commands/setup.js +658 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +109 -0
- package/dist/commands/test.d.ts +1 -0
- package/dist/commands/test.js +157 -0
- package/dist/deploy/agents.d.ts +15 -0
- package/dist/deploy/agents.js +72 -0
- package/dist/deploy/hooks.d.ts +16 -0
- package/dist/deploy/hooks.js +121 -0
- package/dist/deploy/index.d.ts +7 -0
- package/dist/deploy/index.js +24 -0
- package/dist/deploy/instructions.d.ts +12 -0
- package/dist/deploy/instructions.js +36 -0
- package/dist/deploy/mcp.d.ts +19 -0
- package/dist/deploy/mcp.js +109 -0
- package/dist/deploy/plugins.d.ts +19 -0
- package/dist/deploy/plugins.js +62 -0
- package/dist/deploy/settings.d.ts +8 -0
- package/dist/deploy/settings.js +84 -0
- package/dist/deploy/skills.d.ts +19 -0
- package/dist/deploy/skills.js +60 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +71 -0
- package/dist/restore/RestoreOrchestrator.d.ts +48 -0
- package/dist/restore/RestoreOrchestrator.js +481 -0
- package/dist/restore/index.d.ts +4 -0
- package/dist/restore/index.js +20 -0
- package/dist/utils/platform.d.ts +29 -0
- package/dist/utils/platform.js +65 -0
- package/dist/utils/session-words.json +119 -0
- package/dist/utils/state.d.ts +57 -0
- package/dist/utils/state.js +186 -0
- package/dist/utils/templates.d.ts +24 -0
- package/dist/utils/templates.js +118 -0
- package/package.json +48 -0
- package/templates/CLAUDE.md +287 -0
- package/templates/README.md +378 -0
- package/templates/agents/README.md +182 -0
- package/templates/agents/code-reviewer.md +166 -0
- package/templates/agents/debug-detective.md +169 -0
- package/templates/agents/ekkOS_Vercel.md +99 -0
- package/templates/agents/extension-manager.md +229 -0
- package/templates/agents/git-companion.md +185 -0
- package/templates/agents/github-test-agent.md +321 -0
- package/templates/agents/railway-manager.md +179 -0
- package/templates/claude-plugins/PHASE2_COMPLETION.md +346 -0
- package/templates/claude-plugins/PLUGIN_PROPOSALS.md +1776 -0
- package/templates/claude-plugins/README.md +587 -0
- package/templates/claude-plugins/agents/code-reviewer.json +14 -0
- package/templates/claude-plugins/agents/debug-detective.json +15 -0
- package/templates/claude-plugins/agents/git-companion.json +14 -0
- package/templates/claude-plugins/blog-manager/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/blog-manager/commands/blog.md +691 -0
- package/templates/claude-plugins/golden-loop-monitor/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/golden-loop-monitor/commands/loop-status.md +434 -0
- package/templates/claude-plugins/learning-tracker/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/learning-tracker/commands/my-patterns.md +282 -0
- package/templates/claude-plugins/memory-lens/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/memory-lens/commands/memory-search.md +181 -0
- package/templates/claude-plugins/pattern-coach/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/pattern-coach/commands/forge.md +365 -0
- package/templates/claude-plugins/project-schema-validator/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/project-schema-validator/commands/validate-schema.md +582 -0
- package/templates/claude-plugins-admin/AGENT_TEAM_PROPOSALS.md +819 -0
- package/templates/claude-plugins-admin/README.md +446 -0
- package/templates/claude-plugins-admin/autonomous-admin-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/autonomous-admin-agent/commands/agent.md +595 -0
- package/templates/claude-plugins-admin/backend-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/backend-agent/commands/backend.md +798 -0
- package/templates/claude-plugins-admin/deploy-guardian/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/deploy-guardian/commands/deploy.md +554 -0
- package/templates/claude-plugins-admin/frontend-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/frontend-agent/commands/frontend.md +881 -0
- package/templates/claude-plugins-admin/mcp-server-manager/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/mcp-server-manager/commands/mcp.md +85 -0
- package/templates/claude-plugins-admin/memory-system-monitor/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/memory-system-monitor/commands/memory-health.md +569 -0
- package/templates/claude-plugins-admin/qa-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/qa-agent/commands/qa.md +863 -0
- package/templates/claude-plugins-admin/tech-lead-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/tech-lead-agent/commands/lead.md +732 -0
- package/templates/commands/continue.md +47 -0
- package/templates/cursor-hooks/after-agent-response.sh +117 -0
- package/templates/cursor-hooks/before-submit-prompt.sh +419 -0
- package/templates/cursor-hooks/hooks.json +20 -0
- package/templates/cursor-hooks/lib/contract.sh +320 -0
- package/templates/cursor-hooks/stop.sh +75 -0
- package/templates/cursor-rules/ekkos-memory.md +187 -0
- package/templates/hooks/assistant-response.sh +96 -0
- package/templates/hooks/hooks.json +28 -0
- package/templates/hooks/lib/contract.sh +320 -0
- package/templates/hooks/lib/state.sh +158 -0
- package/templates/hooks/session-start.ps1 +41 -0
- package/templates/hooks/session-start.sh +318 -0
- package/templates/hooks/stop.ps1 +16 -0
- package/templates/hooks/stop.sh +989 -0
- package/templates/hooks/user-prompt-submit.ps1 +174 -0
- package/templates/hooks/user-prompt-submit.sh +587 -0
- package/templates/hooks-node/lib/state.js +187 -0
- package/templates/hooks-node/stop.js +416 -0
- package/templates/hooks-node/user-prompt-submit.js +337 -0
- package/templates/plan-template.md +306 -0
- package/templates/rules/00-hooks-contract.mdc +89 -0
- package/templates/rules/30-ekkos-core.mdc +188 -0
- package/templates/rules/31-ekkos-messages.mdc +78 -0
- package/templates/skills/continue/SKILL.md +169 -0
- package/templates/skills/ekkOS_Deep_Recall/Skill.md +282 -0
- package/templates/skills/ekkOS_Learn/Skill.md +265 -0
- package/templates/skills/ekkOS_Memory_First/Skill.md +206 -0
- package/templates/skills/ekkOS_Plan_Assist/Skill.md +302 -0
- package/templates/skills/ekkOS_Preferences/Skill.md +247 -0
- package/templates/skills/ekkOS_Reflect/Skill.md +257 -0
- package/templates/skills/ekkOS_Safety/Skill.md +265 -0
- package/templates/skills/ekkOS_Schema/Skill.md +251 -0
- package/templates/skills/ekkOS_Summary/Skill.md +257 -0
- package/templates/skills/ekkOS_Vault/Skill.md +287 -0
- package/templates/skills/permissions/Skill.md +322 -0
- package/templates/spec-template.md +159 -0
- package/templates/windsurf-hooks/before-submit-prompt.sh +238 -0
- package/templates/windsurf-hooks/hooks.json +10 -0
- package/templates/windsurf-hooks/lib/contract.sh +320 -0
- 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,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
|
+
}
|