memorix 1.4.5 → 1.5.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/CHANGELOG.md +78 -0
- package/CLAUDE.md +7 -3
- package/README.md +5 -2
- package/dist/cli/index.js +1470 -1014
- package/dist/index.js +1279 -989
- package/dist/{maintenance-jobs-B1-W0ssz.d.ts → maintenance-jobs-DVRF-46r.d.ts} +1 -1
- package/dist/maintenance-runner.d.ts +1 -1
- package/dist/maintenance-runner.js +1769 -830
- package/dist/media-audio-runner.js +1774 -836
- package/dist/media-video-runner.js +1769 -831
- package/dist/memcode-runtime/CHANGELOG.md +78 -0
- package/dist/sdk.js +1271 -988
- package/dist/types.d.ts +6 -0
- package/dist/vector-backfill-runner.d.ts +1 -1
- package/dist/vector-backfill-runner.js +1765 -827
- package/docs/1.4.5-AGENT-AUTO-MEMORY-RESEARCH.md +95 -0
- package/docs/API_REFERENCE.md +23 -4
- package/docs/CONFIGURATION.md +29 -3
- package/docs/SETUP.md +5 -3
- package/package.json +1 -1
- package/plugins/antigravity/memorix/mcp_config.json +3 -1
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/memorix/.mcp.json +1 -1
- package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
- package/plugins/codebuddy/memorix/.mcp.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/.mcp.json +1 -1
- package/plugins/copilot/memorix/.mcp.json +1 -1
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/gemini/memorix/gemini-extension.json +4 -2
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/openclaw/memorix/.mcp.json +3 -1
- package/plugins/pi/memorix/package.json +1 -1
- package/server.json +2 -2
- package/src/cli/commands/dashboard.ts +14 -3
- package/src/cli/commands/ingest-commit.ts +47 -3
- package/src/cli/commands/memory.ts +17 -4
- package/src/cli/commands/purge.ts +120 -0
- package/src/cli/commands/serve-http.ts +39 -5
- package/src/cli/commands/setup.ts +3 -1
- package/src/cli/index.ts +3 -804
- package/src/codegraph/auto-context.ts +82 -1
- package/src/config/yaml-loader.ts +3 -8
- package/src/config.ts +23 -7
- package/src/evaluation/session-replay.ts +98 -0
- package/src/git/extractor.ts +5 -3
- package/src/hooks/installers/index.ts +15 -0
- package/src/knowledge/workset.ts +58 -0
- package/src/memory/long-term.ts +97 -2
- package/src/runtime/control-plane-maintenance.ts +1 -0
- package/src/runtime/isolated-maintenance.ts +1 -0
- package/src/runtime/lifecycle.ts +19 -0
- package/src/runtime/maintenance-jobs.ts +1 -0
- package/src/runtime/project-maintenance.ts +8 -0
- package/src/search/surfaced-registry.ts +62 -0
- package/src/server.ts +74 -14
- package/src/store/orama-store.ts +17 -1
- package/src/types.ts +6 -0
- package/src/cli/commands/audit-list.ts +0 -89
- package/src/cli/commands/purge-all-memory.ts +0 -85
- package/src/cli/commands/purge-project-memory.ts +0 -83
- package/src/cli/commands/uninstall-project-artifacts.ts +0 -86
- package/src/cli/tui/Sidebar.tsx +0 -179
- package/src/cli/tui/interactive-commands.ts +0 -261
- package/src/cli/tui/markdown-render.tsx +0 -371
- package/src/cli/tui/use-mouse.ts +0 -157
- package/src/cli/tui/useNavigation.ts +0 -56
- package/src/orchestrate/context-compact.ts +0 -188
- package/src/team/file-locks.ts +0 -173
- package/src/team/index.ts +0 -23
- package/src/team/messages.ts +0 -203
- package/src/team/persistence.ts +0 -96
- package/src/team/registry.ts +0 -158
- package/src/team/tasks.ts +0 -211
package/src/cli/index.ts
CHANGED
|
@@ -52,679 +52,10 @@ function ensureMemcodePackageDir(): void {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
// ============================================================
|
|
56
|
-
// Workbench — Terminal-native memory control plane
|
|
57
|
-
// ============================================================
|
|
58
|
-
|
|
59
55
|
const DIM = '\x1b[2m';
|
|
60
56
|
const RESET = '\x1b[0m';
|
|
61
|
-
const BOLD = '\x1b[1m';
|
|
62
57
|
const CYAN = '\x1b[36m';
|
|
63
|
-
const GREEN = '\x1b[32m';
|
|
64
58
|
const YELLOW = '\x1b[33m';
|
|
65
|
-
const BLUE = '\x1b[34m';
|
|
66
|
-
|
|
67
|
-
type SpinnerLike = {
|
|
68
|
-
start: (message: string) => void;
|
|
69
|
-
stop: (message?: string) => void;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
function createSpinnerForCurrentOutput(): SpinnerLike {
|
|
73
|
-
if (process.stdout.isTTY && process.stdin.isTTY) {
|
|
74
|
-
return p.spinner();
|
|
75
|
-
}
|
|
76
|
-
return {
|
|
77
|
-
start: () => {},
|
|
78
|
-
stop: () => {},
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function getWorkbenchHeader(): Promise<string[]> {
|
|
83
|
-
const lines: string[] = [];
|
|
84
|
-
const ver = `v${getCliVersion()}`;
|
|
85
|
-
|
|
86
|
-
// Detect project
|
|
87
|
-
let projectLabel = `${YELLOW}no git repo${RESET} ${DIM}— run \`git init\` to enable${RESET}`;
|
|
88
|
-
let projectDetected = false;
|
|
89
|
-
try {
|
|
90
|
-
const { detectProject } = await import('../project/detector.js');
|
|
91
|
-
const proj = detectProject(process.cwd());
|
|
92
|
-
if (proj) {
|
|
93
|
-
projectLabel = `${BOLD}${proj.name}${RESET} ${DIM}(${proj.id})${RESET}`;
|
|
94
|
-
projectDetected = true;
|
|
95
|
-
}
|
|
96
|
-
} catch { /* ignore */ }
|
|
97
|
-
|
|
98
|
-
// Detect mode
|
|
99
|
-
let modeLabel = `${DIM}CLI${RESET}`;
|
|
100
|
-
try {
|
|
101
|
-
const { existsSync, readFileSync } = await import('node:fs');
|
|
102
|
-
const { join } = await import('node:path');
|
|
103
|
-
const { homedir } = await import('node:os');
|
|
104
|
-
const bgPath = join(homedir(), '.memorix', 'background.json');
|
|
105
|
-
if (existsSync(bgPath)) {
|
|
106
|
-
const bg = JSON.parse(readFileSync(bgPath, 'utf-8'));
|
|
107
|
-
try { process.kill(bg.pid, 0); modeLabel = `${GREEN}Background${RESET} ${DIM}(port ${bg.port})${RESET}`; } catch { /* dead */ }
|
|
108
|
-
}
|
|
109
|
-
} catch { /* ignore */ }
|
|
110
|
-
|
|
111
|
-
// Detect embedding
|
|
112
|
-
let searchLabel = `${DIM}fulltext (BM25)${RESET}`;
|
|
113
|
-
try {
|
|
114
|
-
const { getEmbeddingMode } = await import('../config.js');
|
|
115
|
-
const mode = getEmbeddingMode();
|
|
116
|
-
if (mode !== 'off') {
|
|
117
|
-
searchLabel = `${CYAN}hybrid${RESET} ${DIM}(BM25 + vector)${RESET}`;
|
|
118
|
-
}
|
|
119
|
-
} catch { /* ignore */ }
|
|
120
|
-
|
|
121
|
-
// Count memories
|
|
122
|
-
let memLabel = `${DIM}unknown${RESET}`;
|
|
123
|
-
if (projectDetected) {
|
|
124
|
-
try {
|
|
125
|
-
const { detectProject } = await import('../project/detector.js');
|
|
126
|
-
const { getProjectDataDir } = await import('../store/persistence.js');
|
|
127
|
-
const { initObservationStore: initStore } = await import('../store/obs-store.js');
|
|
128
|
-
const proj = detectProject(process.cwd());
|
|
129
|
-
if (proj) {
|
|
130
|
-
const dataDir = await getProjectDataDir(proj.id);
|
|
131
|
-
await initStore(dataDir);
|
|
132
|
-
const { getObservationStore: getStore } = await import('../store/obs-store.js');
|
|
133
|
-
const { filterReadableObservations } = await import('../memory/visibility.js');
|
|
134
|
-
const obs = filterReadableObservations(await getStore().loadAll(), { projectId: proj.id });
|
|
135
|
-
const active = obs.filter((o) => (o.status ?? 'active') === 'active').length;
|
|
136
|
-
if (active > 0) {
|
|
137
|
-
memLabel = `${BOLD}${active}${RESET} ${DIM}active${RESET}`;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
} catch { /* ignore */ }
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
lines.push('');
|
|
144
|
-
lines.push(` ${BOLD}Memorix Workbench${RESET}${DIM}${' '.repeat(36)}${ver}${RESET}`);
|
|
145
|
-
lines.push(` ${DIM}${'─'.repeat(55)}${RESET}`);
|
|
146
|
-
lines.push(` ${DIM}Project${RESET} ${projectLabel}`);
|
|
147
|
-
lines.push(` ${DIM}Mode${RESET} ${modeLabel}`);
|
|
148
|
-
lines.push(` ${DIM}Search${RESET} ${searchLabel}`);
|
|
149
|
-
lines.push(` ${DIM}Memories${RESET} ${memLabel}`);
|
|
150
|
-
lines.push(` ${DIM}${'─'.repeat(55)}${RESET}`);
|
|
151
|
-
lines.push('');
|
|
152
|
-
|
|
153
|
-
return lines;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function printSlashHelp(): void {
|
|
157
|
-
console.log('');
|
|
158
|
-
console.log(` ${BOLD}Commands${RESET}`);
|
|
159
|
-
console.log(` ${DIM}${'─'.repeat(45)}${RESET}`);
|
|
160
|
-
console.log(` ${CYAN}/search${RESET} ${DIM}<query>${RESET} Search memories`);
|
|
161
|
-
console.log(` ${CYAN}/remember${RESET} ${DIM}<text>${RESET} Store a quick memory`);
|
|
162
|
-
console.log(` ${CYAN}/recent${RESET} View recent memories`);
|
|
163
|
-
console.log(` ${CYAN}/doctor${RESET} System diagnostics`);
|
|
164
|
-
console.log(` ${CYAN}/project${RESET} Project details`);
|
|
165
|
-
console.log(` ${CYAN}/background${RESET} Background service`);
|
|
166
|
-
console.log(` ${CYAN}/dashboard${RESET} Open dashboard`);
|
|
167
|
-
console.log(` ${CYAN}/configure${RESET} Settings`);
|
|
168
|
-
console.log(` ${CYAN}/integrate${RESET} Set up an IDE`);
|
|
169
|
-
console.log(` ${CYAN}/exit${RESET} Exit workbench`);
|
|
170
|
-
console.log(` ${DIM}${'─'.repeat(45)}${RESET}`);
|
|
171
|
-
console.log(` ${DIM}Or just type to search memories directly.${RESET}`);
|
|
172
|
-
console.log('');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async function runRemember(text: string): Promise<void> {
|
|
176
|
-
const s = createSpinnerForCurrentOutput();
|
|
177
|
-
s.start('Storing memory...');
|
|
178
|
-
|
|
179
|
-
try {
|
|
180
|
-
const { detectProject } = await import('../project/detector.js');
|
|
181
|
-
const { getProjectDataDir } = await import('../store/persistence.js');
|
|
182
|
-
const { initObservations, storeObservation } = await import('../memory/observations.js');
|
|
183
|
-
const { initObservationStore } = await import('../store/obs-store.js');
|
|
184
|
-
|
|
185
|
-
const proj = detectProject(process.cwd());
|
|
186
|
-
if (!proj) { s.stop('No git repo'); p.log.error(NO_GIT_MSG); return; }
|
|
187
|
-
const dataDir = await getProjectDataDir(proj.id);
|
|
188
|
-
await initObservationStore(dataDir);
|
|
189
|
-
await initObservations(dataDir);
|
|
190
|
-
|
|
191
|
-
const result = await storeObservation({
|
|
192
|
-
entityName: 'quick-note',
|
|
193
|
-
type: 'discovery',
|
|
194
|
-
title: text.slice(0, 100),
|
|
195
|
-
narrative: text,
|
|
196
|
-
facts: [],
|
|
197
|
-
projectId: proj.id,
|
|
198
|
-
sourceDetail: 'explicit',
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
s.stop('Stored');
|
|
202
|
-
p.log.success(`Memory #${result.observation.id} saved: ${text.slice(0, 60)}${text.length > 60 ? '...' : ''}`);
|
|
203
|
-
} catch (err) {
|
|
204
|
-
s.stop('Failed');
|
|
205
|
-
p.log.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async function workbench(): Promise<void> {
|
|
210
|
-
// Print header
|
|
211
|
-
const header = await getWorkbenchHeader();
|
|
212
|
-
for (const line of header) console.log(line);
|
|
213
|
-
|
|
214
|
-
console.log(` ${DIM}Type to search memories, or /help for commands.${RESET}`);
|
|
215
|
-
console.log('');
|
|
216
|
-
|
|
217
|
-
// Main input loop
|
|
218
|
-
while (true) {
|
|
219
|
-
const input = await p.text({
|
|
220
|
-
message: `${CYAN}>${RESET}`,
|
|
221
|
-
placeholder: 'search memories or /command',
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
if (p.isCancel(input)) {
|
|
225
|
-
p.outro(`${DIM}Goodbye${RESET}`);
|
|
226
|
-
process.exit(0);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const raw = (input || '').trim();
|
|
230
|
-
if (!raw) continue;
|
|
231
|
-
|
|
232
|
-
// Slash command routing
|
|
233
|
-
if (raw.startsWith('/')) {
|
|
234
|
-
const parts = raw.slice(1).split(/\s+/);
|
|
235
|
-
const cmd = parts[0]?.toLowerCase();
|
|
236
|
-
const arg = parts.slice(1).join(' ');
|
|
237
|
-
|
|
238
|
-
switch (cmd) {
|
|
239
|
-
case 'search':
|
|
240
|
-
case 's':
|
|
241
|
-
if (arg) { await runSearch(arg); } else {
|
|
242
|
-
const q = await p.text({ message: 'Search query:', placeholder: 'e.g., authentication bug' });
|
|
243
|
-
if (!p.isCancel(q) && q) await runSearch(q);
|
|
244
|
-
}
|
|
245
|
-
break;
|
|
246
|
-
case 'remember':
|
|
247
|
-
case 'r':
|
|
248
|
-
if (arg) { await runRemember(arg); } else {
|
|
249
|
-
const t = await p.text({ message: 'What to remember:', placeholder: 'e.g., Use path.join for Windows' });
|
|
250
|
-
if (!p.isCancel(t) && t) await runRemember(t);
|
|
251
|
-
}
|
|
252
|
-
break;
|
|
253
|
-
case 'recent':
|
|
254
|
-
await runList();
|
|
255
|
-
break;
|
|
256
|
-
case 'doctor':
|
|
257
|
-
await runCommand('doctor');
|
|
258
|
-
break;
|
|
259
|
-
case 'project':
|
|
260
|
-
case 'status':
|
|
261
|
-
await runCommand('status');
|
|
262
|
-
break;
|
|
263
|
-
case 'background':
|
|
264
|
-
case 'bg':
|
|
265
|
-
await runBackgroundMenu();
|
|
266
|
-
break;
|
|
267
|
-
case 'dashboard':
|
|
268
|
-
case 'dash':
|
|
269
|
-
await runCommand('dashboard');
|
|
270
|
-
return;
|
|
271
|
-
case 'configure':
|
|
272
|
-
case 'config':
|
|
273
|
-
await runConfigure();
|
|
274
|
-
break;
|
|
275
|
-
case 'integrate':
|
|
276
|
-
case 'setup': {
|
|
277
|
-
const m = await import('./commands/integrate.js');
|
|
278
|
-
await m.default.run?.({ args: { _: [] }, rawArgs: [], cmd: m.default } as any);
|
|
279
|
-
break;
|
|
280
|
-
}
|
|
281
|
-
case 'serve':
|
|
282
|
-
p.log.info('Starting MCP server on stdio...');
|
|
283
|
-
await runCommand('serve');
|
|
284
|
-
return;
|
|
285
|
-
case 'serve-http':
|
|
286
|
-
p.log.info('Starting control plane...');
|
|
287
|
-
await runCommand('serve-http');
|
|
288
|
-
return;
|
|
289
|
-
case 'sync':
|
|
290
|
-
await runCommand('sync');
|
|
291
|
-
break;
|
|
292
|
-
case 'cleanup':
|
|
293
|
-
await runCleanupMenu();
|
|
294
|
-
break;
|
|
295
|
-
case 'ingest':
|
|
296
|
-
await runIngestMenu();
|
|
297
|
-
break;
|
|
298
|
-
case 'help':
|
|
299
|
-
case '?':
|
|
300
|
-
printSlashHelp();
|
|
301
|
-
break;
|
|
302
|
-
case 'exit':
|
|
303
|
-
case 'quit':
|
|
304
|
-
case 'q':
|
|
305
|
-
p.outro(`${DIM}Goodbye${RESET}`);
|
|
306
|
-
process.exit(0);
|
|
307
|
-
break; // unreachable
|
|
308
|
-
default:
|
|
309
|
-
p.log.warn(`Unknown command: /${cmd}. Type /help for available commands.`);
|
|
310
|
-
}
|
|
311
|
-
} else {
|
|
312
|
-
// Default: treat as search query
|
|
313
|
-
await runSearch(raw);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
console.log(''); // spacing
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
async function runBackgroundMenu(): Promise<void> {
|
|
321
|
-
const action = await p.select({
|
|
322
|
-
message: 'Background Control Plane:',
|
|
323
|
-
options: [
|
|
324
|
-
{ value: 'start', label: 'Start', hint: 'launch control plane in background' },
|
|
325
|
-
{ value: 'stop', label: 'Stop', hint: 'stop the background service' },
|
|
326
|
-
{ value: 'status', label: 'Status', hint: 'show running state & health' },
|
|
327
|
-
{ value: 'restart', label: 'Restart', hint: 'stop + start' },
|
|
328
|
-
{ value: 'logs', label: 'View logs', hint: 'show recent log output' },
|
|
329
|
-
{ value: 'back', label: '\u2190 Back', hint: 'return to main menu' },
|
|
330
|
-
],
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
if (p.isCancel(action) || action === 'back') return;
|
|
334
|
-
|
|
335
|
-
const m = await import('./commands/background.js');
|
|
336
|
-
// Synthesize citty args with the chosen subcommand
|
|
337
|
-
await m.default.run?.({
|
|
338
|
-
args: { _: [action], port: '3211', follow: false, lines: '50' },
|
|
339
|
-
rawArgs: [action],
|
|
340
|
-
cmd: m.default,
|
|
341
|
-
} as any);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
async function runHooksMenu(): Promise<void> {
|
|
345
|
-
const action = await p.select({
|
|
346
|
-
message: 'Hooks management:',
|
|
347
|
-
options: [
|
|
348
|
-
{ value: 'install', label: 'Install hooks', hint: 'legacy compatibility path' },
|
|
349
|
-
{ value: 'preview', label: 'Preview installation', hint: 'show files to be created' },
|
|
350
|
-
{ value: 'uninstall', label: 'Uninstall hooks', hint: 'remove from all agents' },
|
|
351
|
-
{ value: 'status', label: 'Status', hint: 'show installed hooks' },
|
|
352
|
-
{ value: 'back', label: '← Back', hint: 'return to main menu' },
|
|
353
|
-
],
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
if (p.isCancel(action) || action === 'back') return;
|
|
357
|
-
|
|
358
|
-
switch (action) {
|
|
359
|
-
case 'install': {
|
|
360
|
-
const m = await import('./commands/hooks-install.js');
|
|
361
|
-
await m.default.run?.({ args: { _: [] }, rawArgs: [], cmd: m.default } as any);
|
|
362
|
-
break;
|
|
363
|
-
}
|
|
364
|
-
case 'preview': {
|
|
365
|
-
const m = await import('./commands/hooks-preview.js');
|
|
366
|
-
await m.default.run?.({ args: { _: [] }, rawArgs: [], cmd: m.default } as any);
|
|
367
|
-
break;
|
|
368
|
-
}
|
|
369
|
-
case 'uninstall': {
|
|
370
|
-
const m = await import('./commands/hooks-uninstall.js');
|
|
371
|
-
await m.default.run?.({ args: { _: [] }, rawArgs: [], cmd: m.default } as any);
|
|
372
|
-
break;
|
|
373
|
-
}
|
|
374
|
-
case 'status': {
|
|
375
|
-
const m = await import('./commands/hooks-status.js');
|
|
376
|
-
await m.default.run?.({ args: { _: [] }, rawArgs: [], cmd: m.default } as any);
|
|
377
|
-
break;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
async function runCleanupMenu(): Promise<void> {
|
|
383
|
-
const action = await p.select({
|
|
384
|
-
message: 'Cleanup options:',
|
|
385
|
-
options: [
|
|
386
|
-
{ value: 'project-artifacts', label: 'Uninstall project artifacts', hint: 'remove hook files only' },
|
|
387
|
-
{ value: 'project-memory', label: 'Purge project memory', hint: 'delete current project memories' },
|
|
388
|
-
{ value: 'all-memory', label: 'Purge all memory', hint: '[WARN] delete ALL memories' },
|
|
389
|
-
{ value: 'back', label: '← Back', hint: 'return to main menu' },
|
|
390
|
-
],
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
if (p.isCancel(action) || action === 'back') return;
|
|
394
|
-
|
|
395
|
-
switch (action) {
|
|
396
|
-
case 'project-artifacts': {
|
|
397
|
-
const m = await import('./commands/uninstall-project-artifacts.js');
|
|
398
|
-
await m.default.run?.({ args: { _: [] }, rawArgs: [], cmd: m.default } as any);
|
|
399
|
-
break;
|
|
400
|
-
}
|
|
401
|
-
case 'project-memory': {
|
|
402
|
-
const m = await import('./commands/purge-project-memory.js');
|
|
403
|
-
await m.default.run?.({ args: { _: [] }, rawArgs: [], cmd: m.default } as any);
|
|
404
|
-
break;
|
|
405
|
-
}
|
|
406
|
-
case 'all-memory': {
|
|
407
|
-
const m = await import('./commands/purge-all-memory.js');
|
|
408
|
-
await m.default.run?.({ args: { _: [] }, rawArgs: [], cmd: m.default } as any);
|
|
409
|
-
break;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
async function runIngestMenu(): Promise<void> {
|
|
415
|
-
const action = await p.select({
|
|
416
|
-
message: 'Git → Memory:',
|
|
417
|
-
options: [
|
|
418
|
-
{ value: 'commit', label: 'Ingest commit', hint: 'single commit → memory' },
|
|
419
|
-
{ value: 'log', label: 'Ingest log', hint: 'batch recent commits → memories' },
|
|
420
|
-
{ value: 'git-hook', label: 'Install git hook', hint: 'auto-capture on every commit' },
|
|
421
|
-
{ value: 'git-hook-uninstall', label: 'Uninstall git hook', hint: 'remove auto-capture' },
|
|
422
|
-
{ value: 'back', label: '← Back', hint: 'return to main menu' },
|
|
423
|
-
],
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
if (p.isCancel(action) || action === 'back') return;
|
|
427
|
-
|
|
428
|
-
switch (action) {
|
|
429
|
-
case 'commit': {
|
|
430
|
-
const m = await import('./commands/ingest-commit.js');
|
|
431
|
-
await m.default.run?.({ args: { _: [] }, rawArgs: [], cmd: m.default } as any);
|
|
432
|
-
break;
|
|
433
|
-
}
|
|
434
|
-
case 'log': {
|
|
435
|
-
const m = await import('./commands/ingest-log.js');
|
|
436
|
-
await m.default.run?.({ args: { _: [] }, rawArgs: [], cmd: m.default } as any);
|
|
437
|
-
break;
|
|
438
|
-
}
|
|
439
|
-
case 'git-hook': {
|
|
440
|
-
const m = await import('./commands/git-hook-install.js');
|
|
441
|
-
await m.default.run?.({ args: { _: [] }, rawArgs: [], cmd: m.default } as any);
|
|
442
|
-
break;
|
|
443
|
-
}
|
|
444
|
-
case 'git-hook-uninstall': {
|
|
445
|
-
const m = await import('./commands/git-hook-uninstall.js');
|
|
446
|
-
await m.default.run?.({ args: { _: [] }, rawArgs: [], cmd: m.default } as any);
|
|
447
|
-
break;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
async function runAuditList(): Promise<void> {
|
|
453
|
-
const m = await import('./commands/audit-list.js');
|
|
454
|
-
await m.default.run?.({ args: { _: [] }, rawArgs: [], cmd: m.default } as any);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
async function runConfigure(): Promise<void> {
|
|
458
|
-
const configPath = `${process.env.HOME || process.env.USERPROFILE}/.memorix/config.json`;
|
|
459
|
-
|
|
460
|
-
// Helper: load config from disk
|
|
461
|
-
const loadConfig = async () => {
|
|
462
|
-
try {
|
|
463
|
-
const fs = await import('node:fs');
|
|
464
|
-
if (fs.existsSync(configPath)) {
|
|
465
|
-
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
466
|
-
}
|
|
467
|
-
} catch { /* ignore */ }
|
|
468
|
-
return {};
|
|
469
|
-
};
|
|
470
|
-
|
|
471
|
-
// Helper: save config to disk
|
|
472
|
-
const saveConfig = async (config: Record<string, unknown>) => {
|
|
473
|
-
const fs = await import('node:fs');
|
|
474
|
-
const dir = `${process.env.HOME || process.env.USERPROFILE}/.memorix`;
|
|
475
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
476
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
// Loop: configure multiple things without going back to main menu
|
|
480
|
-
while (true) {
|
|
481
|
-
const config = await loadConfig();
|
|
482
|
-
|
|
483
|
-
const section = await p.select({
|
|
484
|
-
message: 'What would you like to configure?',
|
|
485
|
-
options: [
|
|
486
|
-
{ value: 'llm', label: 'LLM Enhanced Mode', hint: 'smart dedup + fact extraction' },
|
|
487
|
-
{ value: 'embedding', label: 'Embedding Provider', hint: 'semantic search' },
|
|
488
|
-
{ value: 'behavior', label: 'Behavior Settings', hint: 'session inject, auto-cleanup, sync advisory' },
|
|
489
|
-
{ value: 'show', label: 'Show current config', hint: 'view settings' },
|
|
490
|
-
{ value: 'back', label: '\u2190 Back', hint: 'return to main menu' },
|
|
491
|
-
],
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
if (p.isCancel(section) || section === 'back') return;
|
|
495
|
-
|
|
496
|
-
if (section === 'show') {
|
|
497
|
-
console.log('\nCurrent configuration:');
|
|
498
|
-
console.log(` Config file: ${configPath}`);
|
|
499
|
-
console.log(` LLM Provider: ${config.llm?.provider ?? 'not configured'}`);
|
|
500
|
-
console.log(` LLM Model: ${config.llm?.model ?? 'default'}`);
|
|
501
|
-
console.log(` LLM Base URL: ${config.llm?.baseUrl ?? '(default)'}`);
|
|
502
|
-
console.log(` LLM API Key: ${config.llm?.apiKey ? '***configured***' : 'not set'}`);
|
|
503
|
-
console.log(` Embedding: ${config.embedding ?? 'off (BM25 only)'}`);
|
|
504
|
-
if (config.embedding === 'api') {
|
|
505
|
-
const apiConf = config.embeddingApi;
|
|
506
|
-
if (apiConf) {
|
|
507
|
-
console.log(` Embedding Model: ${apiConf.model ?? 'text-embedding-3-small'}`);
|
|
508
|
-
console.log(` Embedding Base URL: ${apiConf.baseUrl ?? '(default)'}`);
|
|
509
|
-
console.log(` Embedding API Key: ${apiConf.apiKey ? '***configured***' : '(reusing LLM key)'}`);
|
|
510
|
-
if (apiConf.dimensions) console.log(` Embedding Dimensions: ${apiConf.dimensions}`);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
console.log('\nEnvironment overrides (take priority over config.json):');
|
|
514
|
-
console.log(` MEMORIX_LLM_API_KEY: ${process.env.MEMORIX_LLM_API_KEY ? '***set***' : 'not set'}`);
|
|
515
|
-
console.log(` OPENAI_API_KEY: ${process.env.OPENAI_API_KEY ? '***set***' : 'not set'}`);
|
|
516
|
-
console.log(` MEMORIX_EMBEDDING: ${process.env.MEMORIX_EMBEDDING ?? 'not set'}`);
|
|
517
|
-
if (process.env.MEMORIX_EMBEDDING === 'api') {
|
|
518
|
-
console.log(` MEMORIX_EMBEDDING_API_KEY: ${process.env.MEMORIX_EMBEDDING_API_KEY ? '***set***' : 'not set'}`);
|
|
519
|
-
console.log(` MEMORIX_EMBEDDING_BASE_URL: ${process.env.MEMORIX_EMBEDDING_BASE_URL ?? 'not set'}`);
|
|
520
|
-
console.log(` MEMORIX_EMBEDDING_MODEL: ${process.env.MEMORIX_EMBEDDING_MODEL ?? 'text-embedding-3-small'}`);
|
|
521
|
-
console.log(` MEMORIX_EMBEDDING_DIMENSIONS: ${process.env.MEMORIX_EMBEDDING_DIMENSIONS ?? 'auto'}`);
|
|
522
|
-
}
|
|
523
|
-
console.log('');
|
|
524
|
-
continue; // Back to configure menu
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
if (section === 'llm') {
|
|
528
|
-
const provider = await p.select({
|
|
529
|
-
message: 'Select LLM provider:',
|
|
530
|
-
options: [
|
|
531
|
-
{ value: 'openai', label: 'OpenAI', hint: 'gpt-4o-mini recommended' },
|
|
532
|
-
{ value: 'anthropic', label: 'Anthropic', hint: 'claude-3-haiku' },
|
|
533
|
-
{ value: 'openrouter', label: 'OpenRouter', hint: 'multi-provider' },
|
|
534
|
-
{ value: 'custom', label: 'Custom endpoint', hint: 'OpenAI-compatible proxy / local' },
|
|
535
|
-
{ value: 'disable', label: 'Disable LLM', hint: 'use free heuristic mode' },
|
|
536
|
-
],
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
if (p.isCancel(provider)) continue; // Back to configure menu
|
|
540
|
-
|
|
541
|
-
if (provider === 'disable') {
|
|
542
|
-
config.llm = undefined;
|
|
543
|
-
await saveConfig(config);
|
|
544
|
-
p.log.success('LLM mode disabled. Using free heuristic deduplication.');
|
|
545
|
-
continue;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
const apiKey = await p.password({
|
|
549
|
-
message: 'Enter API key:',
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
if (p.isCancel(apiKey) || !apiKey) {
|
|
553
|
-
continue; // Back to configure menu
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
let defaultModel = 'gpt-4o-mini';
|
|
557
|
-
if (provider === 'anthropic') defaultModel = 'claude-3-haiku-20240307';
|
|
558
|
-
|
|
559
|
-
// Custom endpoint always asks for base URL first
|
|
560
|
-
let baseUrl: string | undefined;
|
|
561
|
-
if (provider === 'custom') {
|
|
562
|
-
const url = await p.text({
|
|
563
|
-
message: 'Base URL (OpenAI-compatible):',
|
|
564
|
-
placeholder: 'http://localhost:11434/v1',
|
|
565
|
-
});
|
|
566
|
-
if (p.isCancel(url)) { continue; }
|
|
567
|
-
if (url) baseUrl = url;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
const customModel = await p.text({
|
|
571
|
-
message: 'Model name:',
|
|
572
|
-
placeholder: defaultModel,
|
|
573
|
-
defaultValue: defaultModel,
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
if (p.isCancel(customModel)) { continue; }
|
|
577
|
-
|
|
578
|
-
config.llm = {
|
|
579
|
-
provider: provider === 'custom' ? 'openai' : provider,
|
|
580
|
-
apiKey,
|
|
581
|
-
model: customModel || defaultModel,
|
|
582
|
-
baseUrl,
|
|
583
|
-
};
|
|
584
|
-
|
|
585
|
-
await saveConfig(config);
|
|
586
|
-
p.log.success(`LLM configured: ${config.llm.model} @ ${config.llm.baseUrl || 'default'}`);
|
|
587
|
-
p.log.info('Saved to config.json. Restart MCP server to apply.');
|
|
588
|
-
continue;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
if (section === 'embedding') {
|
|
592
|
-
const embedding = await p.select({
|
|
593
|
-
message: 'Select embedding provider:',
|
|
594
|
-
options: [
|
|
595
|
-
{ value: 'off', label: 'Off (default)', hint: 'BM25 fulltext only, ~50MB RAM' },
|
|
596
|
-
{ value: 'api', label: 'API (recommended)', hint: 'OpenAI-compatible, zero local RAM, best quality' },
|
|
597
|
-
{ value: 'fastembed', label: 'FastEmbed', hint: 'local ONNX, ~300MB RAM' },
|
|
598
|
-
{ value: 'transformers', label: 'Transformers', hint: 'local JS/WASM, ~500MB RAM' },
|
|
599
|
-
],
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
if (p.isCancel(embedding)) continue; // Back to configure menu
|
|
603
|
-
|
|
604
|
-
if (embedding === 'api') {
|
|
605
|
-
const apiKey = await p.password({
|
|
606
|
-
message: 'Embedding API key (leave empty to use BM25 fallback):',
|
|
607
|
-
});
|
|
608
|
-
|
|
609
|
-
if (p.isCancel(apiKey)) continue;
|
|
610
|
-
|
|
611
|
-
const baseUrl = await p.text({
|
|
612
|
-
message: 'Base URL:',
|
|
613
|
-
placeholder: 'https://api.openai.com/v1',
|
|
614
|
-
defaultValue: '',
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
if (p.isCancel(baseUrl)) continue;
|
|
618
|
-
|
|
619
|
-
const modelChoice = await p.select({
|
|
620
|
-
message: 'Embedding model:',
|
|
621
|
-
options: [
|
|
622
|
-
{ value: 'text-embedding-3-small', label: 'OpenAI text-embedding-3-small', hint: '1536d, $0.02/1M tokens' },
|
|
623
|
-
{ value: 'text-embedding-3-large', label: 'OpenAI text-embedding-3-large', hint: '3072d, best quality' },
|
|
624
|
-
{ value: 'text-embedding-v3', label: 'Qwen text-embedding-v3', hint: '1024d, Chinese+English' },
|
|
625
|
-
{ value: 'text-embedding-v4', label: 'Qwen text-embedding-v4', hint: 'latest, Chinese+English' },
|
|
626
|
-
{ value: 'custom', label: 'Custom model', hint: 'enter model name' },
|
|
627
|
-
],
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
if (p.isCancel(modelChoice)) continue;
|
|
631
|
-
|
|
632
|
-
let model: string = modelChoice;
|
|
633
|
-
if (modelChoice === 'custom') {
|
|
634
|
-
const customName = await p.text({
|
|
635
|
-
message: 'Model name:',
|
|
636
|
-
placeholder: 'e.g., BAAI/bge-m3',
|
|
637
|
-
});
|
|
638
|
-
if (p.isCancel(customName) || !customName) continue;
|
|
639
|
-
model = customName;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
const dimInput = await p.text({
|
|
643
|
-
message: 'Dimension override (optional, press Enter to auto-detect):',
|
|
644
|
-
placeholder: 'e.g., 512 for cost savings',
|
|
645
|
-
defaultValue: '',
|
|
646
|
-
});
|
|
647
|
-
|
|
648
|
-
const dims = (!p.isCancel(dimInput) && dimInput) ? parseInt(dimInput, 10) : null;
|
|
649
|
-
|
|
650
|
-
config.embedding = 'api';
|
|
651
|
-
config.embeddingApi = {
|
|
652
|
-
apiKey: apiKey || undefined,
|
|
653
|
-
baseUrl: baseUrl || undefined,
|
|
654
|
-
model,
|
|
655
|
-
dimensions: (dims && !isNaN(dims)) ? dims : undefined,
|
|
656
|
-
};
|
|
657
|
-
|
|
658
|
-
await saveConfig(config);
|
|
659
|
-
p.log.success(`API embedding configured: ${model}`);
|
|
660
|
-
p.log.info('Saved to config.json. Restart MCP server to apply.');
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
config.embedding = embedding;
|
|
665
|
-
delete config.embeddingApi;
|
|
666
|
-
|
|
667
|
-
await saveConfig(config);
|
|
668
|
-
|
|
669
|
-
if (embedding === 'off') {
|
|
670
|
-
p.log.success('Embedding disabled. Using BM25 fulltext search.');
|
|
671
|
-
} else {
|
|
672
|
-
p.log.success(`Embedding set to: ${embedding}`);
|
|
673
|
-
p.log.info(`Install with: npm install -g ${embedding === 'fastembed' ? 'fastembed' : '@huggingface/transformers'}`);
|
|
674
|
-
}
|
|
675
|
-
p.log.info('Saved to config.json. Restart MCP server to apply.');
|
|
676
|
-
continue;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
if (section === 'behavior') {
|
|
680
|
-
const current = config.behavior ?? {};
|
|
681
|
-
|
|
682
|
-
const sessionInject = await p.select({
|
|
683
|
-
message: `Session start injection (current: ${current.sessionInject ?? 'minimal'})`,
|
|
684
|
-
options: [
|
|
685
|
-
{ value: 'full', label: 'Full', hint: 'inject top 5 memories on session start' },
|
|
686
|
-
{ value: 'minimal', label: 'Minimal (default)', hint: 'one-line hint only' },
|
|
687
|
-
{ value: 'silent', label: 'Silent', hint: 'no injection, rely on rules/AGENTS.md' },
|
|
688
|
-
],
|
|
689
|
-
});
|
|
690
|
-
if (p.isCancel(sessionInject)) continue;
|
|
691
|
-
|
|
692
|
-
const syncAdvisory = await p.confirm({
|
|
693
|
-
message: `Show sync advisory on first search? (current: ${current.syncAdvisory !== false ? 'yes' : 'no'})`,
|
|
694
|
-
initialValue: current.syncAdvisory !== false,
|
|
695
|
-
});
|
|
696
|
-
if (p.isCancel(syncAdvisory)) continue;
|
|
697
|
-
|
|
698
|
-
const autoCleanup = await p.confirm({
|
|
699
|
-
message: `Auto-archive expired memories on startup? (current: ${current.autoCleanup !== false ? 'yes' : 'no'})`,
|
|
700
|
-
initialValue: current.autoCleanup !== false,
|
|
701
|
-
});
|
|
702
|
-
if (p.isCancel(autoCleanup)) continue;
|
|
703
|
-
|
|
704
|
-
const formationMode = await p.select({
|
|
705
|
-
message: `Formation Pipeline mode (current: ${current.formationMode ?? 'active'})`,
|
|
706
|
-
options: [
|
|
707
|
-
{ value: 'active', label: 'Active (default)', hint: 'Formation decides storage (new/merge/evolve/discard)' },
|
|
708
|
-
{ value: 'shadow', label: 'Shadow', hint: 'Formation observes only, old compact decides' },
|
|
709
|
-
{ value: 'fallback', label: 'Fallback', hint: 'Old compact decides (safe rollback)' },
|
|
710
|
-
],
|
|
711
|
-
});
|
|
712
|
-
if (p.isCancel(formationMode)) continue;
|
|
713
|
-
|
|
714
|
-
config.behavior = {
|
|
715
|
-
sessionInject,
|
|
716
|
-
syncAdvisory,
|
|
717
|
-
autoCleanup,
|
|
718
|
-
formationMode,
|
|
719
|
-
};
|
|
720
|
-
|
|
721
|
-
await saveConfig(config);
|
|
722
|
-
p.log.success('Behavior settings updated.');
|
|
723
|
-
p.log.info('Saved to config.json. Restart MCP server to apply.');
|
|
724
|
-
continue;
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
59
|
|
|
729
60
|
async function runAsk(question: string): Promise<void> {
|
|
730
61
|
// Use a smooth dots-style spinner (same frames as TUI ink-spinner "dots")
|
|
@@ -794,140 +125,6 @@ async function runAsk(question: string): Promise<void> {
|
|
|
794
125
|
}
|
|
795
126
|
}
|
|
796
127
|
|
|
797
|
-
async function runSearch(query: string): Promise<void> {
|
|
798
|
-
const s = createSpinnerForCurrentOutput();
|
|
799
|
-
s.start('Searching memories...');
|
|
800
|
-
const perf = !!process.env.MEMORIX_PERF;
|
|
801
|
-
const t0 = perf ? performance.now() : 0;
|
|
802
|
-
const mark = (label: string) => { if (perf) { const now = performance.now(); process.stderr.write(`[perf] ${label}: ${(now - t0).toFixed(0)}ms\n`); } };
|
|
803
|
-
|
|
804
|
-
try {
|
|
805
|
-
const { searchObservations, getDb, hydrateIndex } = await import('../store/orama-store.js');
|
|
806
|
-
const { filterReadableObservations } = await import('../memory/visibility.js');
|
|
807
|
-
const { getProjectDataDir } = await import('../store/persistence.js');
|
|
808
|
-
const { detectProject } = await import('../project/detector.js');
|
|
809
|
-
const { initObservations } = await import('../memory/observations.js');
|
|
810
|
-
const { initObservationStore } = await import('../store/obs-store.js');
|
|
811
|
-
mark('imports');
|
|
812
|
-
|
|
813
|
-
const project = detectProject(process.cwd());
|
|
814
|
-
if (!project) { s.stop('No git repo'); p.log.error(NO_GIT_MSG); return; }
|
|
815
|
-
const dataDir = await getProjectDataDir(project.id);
|
|
816
|
-
mark('detectProject');
|
|
817
|
-
await initObservationStore(dataDir);
|
|
818
|
-
await initObservations(dataDir);
|
|
819
|
-
mark('initObservations');
|
|
820
|
-
|
|
821
|
-
// Parallel: getDb (embedding provider init) + store.loadAll (disk I/O)
|
|
822
|
-
const { getObservationStore: getStore } = await import('../store/obs-store.js');
|
|
823
|
-
const [, allObs] = await Promise.all([
|
|
824
|
-
getDb(),
|
|
825
|
-
getStore().loadAll() as Promise<any[]>,
|
|
826
|
-
]);
|
|
827
|
-
mark(`getDb+loadObs(${allObs.length})`);
|
|
828
|
-
await hydrateIndex(allObs);
|
|
829
|
-
mark('hydrateIndex');
|
|
830
|
-
|
|
831
|
-
const results = await searchObservations({ query, limit: 10, projectId: project.id, reader: { projectId: project.id } });
|
|
832
|
-
mark('search');
|
|
833
|
-
s.stop('Search complete');
|
|
834
|
-
|
|
835
|
-
if (results.length === 0) {
|
|
836
|
-
p.log.warn('No memories found matching your query.');
|
|
837
|
-
return;
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
p.log.success(`Found ${results.length} memories:`);
|
|
841
|
-
console.log('');
|
|
842
|
-
for (const r of results) {
|
|
843
|
-
console.log(` ${r.icon} #${r.id} ${r.title}`);
|
|
844
|
-
console.log(` ${r.time} | ${r.tokens} tokens | score: ${(r.score ?? 0).toFixed(2)}`);
|
|
845
|
-
console.log('');
|
|
846
|
-
}
|
|
847
|
-
} catch (err) {
|
|
848
|
-
s.stop('Search failed');
|
|
849
|
-
p.log.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
async function runList(): Promise<void> {
|
|
854
|
-
const s = createSpinnerForCurrentOutput();
|
|
855
|
-
s.start('Loading recent memories...');
|
|
856
|
-
|
|
857
|
-
try {
|
|
858
|
-
const { getProjectDataDir } = await import('../store/persistence.js');
|
|
859
|
-
const { detectProject } = await import('../project/detector.js');
|
|
860
|
-
const { initObservationStore: initStore, getObservationStore: getStore } = await import('../store/obs-store.js');
|
|
861
|
-
const { filterReadableObservations } = await import('../memory/visibility.js');
|
|
862
|
-
|
|
863
|
-
const project = detectProject(process.cwd());
|
|
864
|
-
if (!project) { s.stop('No git repo'); p.log.error(NO_GIT_MSG); return; }
|
|
865
|
-
const dataDir = await getProjectDataDir(project.id);
|
|
866
|
-
await initStore(dataDir);
|
|
867
|
-
const observations = filterReadableObservations(
|
|
868
|
-
await getStore().loadAll(),
|
|
869
|
-
{ projectId: project.id },
|
|
870
|
-
);
|
|
871
|
-
|
|
872
|
-
const active = observations.filter(o => (o.status ?? 'active') === 'active');
|
|
873
|
-
const recent = active.slice(-10).reverse();
|
|
874
|
-
|
|
875
|
-
s.stop(`Project: ${project.name} (${active.length} active memories)`);
|
|
876
|
-
|
|
877
|
-
if (recent.length === 0) {
|
|
878
|
-
p.log.warn('No memories found.');
|
|
879
|
-
return;
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
console.log('');
|
|
883
|
-
for (const o of recent) {
|
|
884
|
-
const typeLabel = ({ gotcha: '[!]', decision: '[D]', 'problem-solution': '[S]', discovery: '[?]', 'how-it-works': '[H]', 'what-changed': '[C]' } as Record<string, string>)[o.type] ?? '[·]';
|
|
885
|
-
console.log(` ${typeLabel} #${o.id} ${o.title?.slice(0, 60) ?? '(untitled)'}`);
|
|
886
|
-
}
|
|
887
|
-
console.log('');
|
|
888
|
-
} catch (err) {
|
|
889
|
-
s.stop('Failed to load memories');
|
|
890
|
-
p.log.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
async function runCommand(cmd: string, _args: string[] = []): Promise<void> {
|
|
895
|
-
// Direct imports to ensure bundler includes them
|
|
896
|
-
// Using 'as any' to bypass citty's strict type checking for manual invocation
|
|
897
|
-
switch (cmd) {
|
|
898
|
-
case 'dashboard': {
|
|
899
|
-
const m = await import('./commands/dashboard.js');
|
|
900
|
-
await m.default.run?.({ args: { _: [] }, rawArgs: [], cmd: m.default } as any);
|
|
901
|
-
break;
|
|
902
|
-
}
|
|
903
|
-
case 'status': {
|
|
904
|
-
const m = await import('./commands/status.js');
|
|
905
|
-
await m.default.run?.({ args: { _: [] }, rawArgs: [], cmd: m.default } as any);
|
|
906
|
-
break;
|
|
907
|
-
}
|
|
908
|
-
case 'cleanup': {
|
|
909
|
-
const m = await import('./commands/cleanup.js');
|
|
910
|
-
await m.default.run?.({ args: { _: [], dry: false, force: false }, rawArgs: [], cmd: m.default } as any);
|
|
911
|
-
break;
|
|
912
|
-
}
|
|
913
|
-
case 'serve-http': {
|
|
914
|
-
const m = await import('./commands/serve-http.js');
|
|
915
|
-
await m.default.run?.({ args: { _: [], port: 3211 }, rawArgs: [], cmd: m.default } as any);
|
|
916
|
-
break;
|
|
917
|
-
}
|
|
918
|
-
case 'sync': {
|
|
919
|
-
const m = await import('./commands/sync.js');
|
|
920
|
-
await m.default.run?.({ args: { _: [], dry: false }, rawArgs: [], cmd: m.default } as any);
|
|
921
|
-
break;
|
|
922
|
-
}
|
|
923
|
-
case 'serve': {
|
|
924
|
-
const m = await import('./commands/serve.js');
|
|
925
|
-
await m.default.run?.({ args: { _: [] }, rawArgs: [], cmd: m.default } as any);
|
|
926
|
-
break;
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
|
|
931
128
|
// ============================================================
|
|
932
129
|
// Main command
|
|
933
130
|
// ============================================================
|
|
@@ -1092,6 +289,7 @@ const main = defineCommand({
|
|
|
1092
289
|
repair: () => import('./commands/repair.js').then(m => m.default),
|
|
1093
290
|
dashboard: () => import('./commands/dashboard.js').then(m => m.default),
|
|
1094
291
|
cleanup: () => import('./commands/cleanup.js').then(m => m.default),
|
|
292
|
+
purge: () => import('./commands/purge.js').then(m => m.default),
|
|
1095
293
|
uninstall: () => import('./commands/uninstall.js').then(m => m.default),
|
|
1096
294
|
orchestrate: () => import('./commands/orchestrate.js').then(m => m.default),
|
|
1097
295
|
workbench: () => Promise.resolve(defineCommand({
|
|
@@ -1124,7 +322,7 @@ const main = defineCommand({
|
|
|
1124
322
|
'receipt',
|
|
1125
323
|
'serve', 'serve-http', 'status', 'sync',
|
|
1126
324
|
'hook', 'hooks', 'ingest', 'media', 'git-hook', 'git-hook-uninstall',
|
|
1127
|
-
'background', 'bg', 'bs', 'doctor', 'repair', 'dashboard', 'cleanup', 'uninstall', 'orchestrate'];
|
|
325
|
+
'background', 'bg', 'bs', 'doctor', 'repair', 'dashboard', 'cleanup', 'purge', 'uninstall', 'orchestrate'];
|
|
1128
326
|
if (firstArg && knownSubs.includes(firstArg)) return;
|
|
1129
327
|
|
|
1130
328
|
// No subcommand provided — enter memcode TUI (native coding agent)
|
|
@@ -1189,6 +387,7 @@ const main = defineCommand({
|
|
|
1189
387
|
console.error(' dashboard Open standalone dashboard (read-mostly)');
|
|
1190
388
|
console.error(' hooks Open legacy hook installer menu');
|
|
1191
389
|
console.error(' cleanup Remove old memories');
|
|
390
|
+
console.error(' purge Retire all memories (project by default, --all for everything)');
|
|
1192
391
|
console.error(' sync Rules/workspace sync plus interactive wizard');
|
|
1193
392
|
console.error(' ingest Ingest commit, log, or image knowledge');
|
|
1194
393
|
console.error('\nRun `memorix` in an interactive terminal for memcode TUI.');
|