@mndrk/memx 0.3.3 → 0.3.4
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/README.md +98 -77
- package/coverage/clover.xml +1160 -0
- package/coverage/coverage-final.json +3 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +131 -0
- package/coverage/lcov-report/index.js.html +7255 -0
- package/coverage/lcov-report/mcp.js.html +1009 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +2017 -0
- package/index.js +651 -243
- package/package.json +24 -2
- package/test/additional.test.js +373 -0
- package/test/branches.test.js +247 -0
- package/test/commands.test.js +663 -0
- package/test/context.test.js +185 -0
- package/test/coverage.test.js +366 -0
- package/test/dispatch.test.js +220 -0
- package/test/edge-coverage.test.js +250 -0
- package/test/edge.test.js +434 -0
- package/test/final-coverage.test.js +316 -0
- package/test/final-edges.test.js +199 -0
- package/test/init-local.test.js +316 -0
- package/test/init.test.js +122 -0
- package/test/interactive.test.js +229 -0
- package/test/main-dispatch.test.js +164 -0
- package/test/main-full.test.js +590 -0
- package/test/main.test.js +197 -0
- package/test/mcp-server.test.js +320 -0
- package/test/mcp.test.js +288 -0
- package/test/more.test.js +312 -0
- package/test/new.test.js +175 -0
- package/test/skill.test.js +247 -0
- package/test/tasks-interactive.test.js +243 -0
- package/test/utils.test.js +367 -0
package/index.js
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// ============================================================
|
|
4
|
+
// mem - Git-backed Key-Value Store for AI Agents
|
|
5
|
+
//
|
|
6
|
+
// Architecture:
|
|
7
|
+
// - All data stored in central ~/.mem git repository
|
|
8
|
+
// - Each task is a git branch (task/<name>)
|
|
9
|
+
// - index.json maps project directories → task branches
|
|
10
|
+
// - agx orchestrator calls mem commands, never accesses ~/.mem directly
|
|
11
|
+
//
|
|
12
|
+
// Storage structure (per task branch):
|
|
13
|
+
// goal.md - task goal, criteria, progress %
|
|
14
|
+
// state.md - status, provider, next step, checkpoints
|
|
15
|
+
// memory.md - learnings and insights
|
|
16
|
+
// playbook.md - global learnings (on main branch)
|
|
17
|
+
//
|
|
18
|
+
// Primitives:
|
|
19
|
+
// mem new "goal" --provider c Create task (branch + files + commit)
|
|
20
|
+
// mem branch <name> Create/switch branch
|
|
21
|
+
// mem commit "msg" Commit changes
|
|
22
|
+
// mem set <key> <value> Set frontmatter value
|
|
23
|
+
// mem get <key> Get frontmatter value
|
|
24
|
+
// mem context --json Get full task context
|
|
25
|
+
// ============================================================
|
|
26
|
+
|
|
3
27
|
const { execSync, spawn } = require('child_process');
|
|
4
28
|
const fs = require('fs');
|
|
5
29
|
const path = require('path');
|
|
@@ -26,9 +50,8 @@ Use \`mem\` to maintain state, track progress, and accumulate learnings across s
|
|
|
26
50
|
- **Git-backed**: All state is versioned and syncable
|
|
27
51
|
- **Branches = Tasks**: Each task/goal is a separate branch
|
|
28
52
|
- **Two scopes**: Task-local memory + global playbook
|
|
29
|
-
- **Wake system**: Store schedule intent, export to cron
|
|
30
53
|
|
|
31
|
-
## On
|
|
54
|
+
## On Session Start
|
|
32
55
|
|
|
33
56
|
\`\`\`bash
|
|
34
57
|
mem context # Load full state: goal, progress, learnings
|
|
@@ -75,11 +98,8 @@ mem tasks # List all tasks (branches)
|
|
|
75
98
|
mem switch <name> # Switch to different task
|
|
76
99
|
\`\`\`
|
|
77
100
|
|
|
78
|
-
###
|
|
101
|
+
### Sync
|
|
79
102
|
\`\`\`bash
|
|
80
|
-
mem wake "every 15m" # Set wake schedule
|
|
81
|
-
mem wake "8am daily" # Other patterns: monday 9am, */30 * * * *
|
|
82
|
-
mem cron export # Export as crontab entry
|
|
83
103
|
mem sync # Push/pull with remote
|
|
84
104
|
\`\`\`
|
|
85
105
|
|
|
@@ -88,14 +108,14 @@ mem sync # Push/pull with remote
|
|
|
88
108
|
\`\`\`
|
|
89
109
|
.mem/
|
|
90
110
|
goal.md # Objective + criteria + constraints
|
|
91
|
-
state.md # Progress, next step, blockers
|
|
111
|
+
state.md # Progress, next step, blockers
|
|
92
112
|
memory.md # Task-specific learnings
|
|
93
113
|
playbook.md # Global learnings (shared)
|
|
94
114
|
\`\`\`
|
|
95
115
|
|
|
96
116
|
## Typical Session Loop
|
|
97
117
|
|
|
98
|
-
1. \`mem context\` - Load state
|
|
118
|
+
1. \`mem context\` - Load current state
|
|
99
119
|
2. \`mem next\` - See what to work on
|
|
100
120
|
3. Do work
|
|
101
121
|
4. \`mem checkpoint "..."\` - Save progress
|
|
@@ -238,6 +258,23 @@ function git(memDir, ...args) {
|
|
|
238
258
|
}
|
|
239
259
|
}
|
|
240
260
|
|
|
261
|
+
// Parallel-safe: read file from branch without checkout
|
|
262
|
+
function gitShow(memDir, branch, filename) {
|
|
263
|
+
try {
|
|
264
|
+
const result = require('child_process').spawnSync('git', ['show', `${branch}:${filename}`], {
|
|
265
|
+
cwd: memDir,
|
|
266
|
+
encoding: 'utf8',
|
|
267
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
268
|
+
});
|
|
269
|
+
if (result.status !== 0) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
return result.stdout || '';
|
|
273
|
+
} catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
241
278
|
// Get current branch
|
|
242
279
|
function getCurrentBranch(memDir) {
|
|
243
280
|
return git(memDir, 'rev-parse', '--abbrev-ref', 'HEAD');
|
|
@@ -759,26 +796,221 @@ function cmdContext(memDir) {
|
|
|
759
796
|
}
|
|
760
797
|
|
|
761
798
|
// List tasks
|
|
762
|
-
|
|
799
|
+
// Uses git show for parallel-safe reading (no checkout required)
|
|
800
|
+
function cmdTasks(args, memDir) {
|
|
801
|
+
const jsonMode = args.includes('--json');
|
|
802
|
+
|
|
763
803
|
if (!memDir) {
|
|
764
|
-
|
|
804
|
+
if (jsonMode) {
|
|
805
|
+
console.log(JSON.stringify({ tasks: [], error: 'no_repo' }));
|
|
806
|
+
} else {
|
|
807
|
+
console.log(`${c.yellow}No .mem repo found.${c.reset}`);
|
|
808
|
+
}
|
|
765
809
|
return;
|
|
766
810
|
}
|
|
767
811
|
|
|
768
|
-
const
|
|
769
|
-
const branches = git(memDir, 'branch', '--list').split('\n')
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
branches.
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
812
|
+
const currentBranch = getCurrentBranch(memDir);
|
|
813
|
+
const branches = git(memDir, 'branch', '--list').split('\n')
|
|
814
|
+
.map(b => b.replace('*', '').trim())
|
|
815
|
+
.filter(b => b && b !== 'main' && b !== 'master');
|
|
816
|
+
|
|
817
|
+
if (branches.length === 0) {
|
|
818
|
+
if (jsonMode) {
|
|
819
|
+
console.log(JSON.stringify({ tasks: [] }));
|
|
820
|
+
} else {
|
|
821
|
+
console.log(`${c.yellow}No tasks found${c.reset}`);
|
|
822
|
+
}
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Build reverse index: branch -> projectDir
|
|
827
|
+
const index = loadIndex();
|
|
828
|
+
const branchToDir = {};
|
|
829
|
+
for (const [dir, branch] of Object.entries(index)) {
|
|
830
|
+
branchToDir[branch] = dir;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Load task info using git show (parallel-safe, no checkout)
|
|
834
|
+
const tasks = branches.map(branch => {
|
|
835
|
+
const taskName = branch.replace('task/', '');
|
|
836
|
+
let status = 'active';
|
|
837
|
+
let progress = '—';
|
|
838
|
+
let goal = '';
|
|
839
|
+
let provider = 'claude';
|
|
840
|
+
|
|
841
|
+
try {
|
|
842
|
+
// Use git show to read files without checkout (parallel-safe)
|
|
843
|
+
const stateContent = gitShow(memDir, branch, 'state.md');
|
|
844
|
+
if (stateContent) {
|
|
845
|
+
const { frontmatter } = parseFrontmatter(stateContent);
|
|
846
|
+
status = frontmatter.status || 'active';
|
|
847
|
+
provider = frontmatter.provider || 'claude';
|
|
848
|
+
}
|
|
849
|
+
} catch {}
|
|
850
|
+
|
|
851
|
+
try {
|
|
852
|
+
const goalContent = gitShow(memDir, branch, 'goal.md');
|
|
853
|
+
if (goalContent) {
|
|
854
|
+
const progressMatch = goalContent.match(/Progress:\s*(\d+)%/i);
|
|
855
|
+
if (progressMatch) progress = `${progressMatch[1]}%`;
|
|
856
|
+
|
|
857
|
+
const goalMatch = goalContent.match(/^#\s*Goal\s*\n+([^\n#]+)/m);
|
|
858
|
+
if (goalMatch) goal = goalMatch[1].trim().slice(0, 50);
|
|
859
|
+
}
|
|
860
|
+
} catch {}
|
|
861
|
+
|
|
862
|
+
return {
|
|
863
|
+
branch,
|
|
864
|
+
taskName,
|
|
865
|
+
status,
|
|
866
|
+
progress,
|
|
867
|
+
goal,
|
|
868
|
+
provider,
|
|
869
|
+
projectDir: branchToDir[branch] || null,
|
|
870
|
+
isCurrent: branch === currentBranch
|
|
871
|
+
};
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
// JSON mode - output structured data for agx
|
|
875
|
+
if (jsonMode) {
|
|
876
|
+
console.log(JSON.stringify({ tasks }));
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Non-interactive if not TTY
|
|
881
|
+
if (!process.stdin.isTTY) {
|
|
882
|
+
console.log(`${c.bold}Tasks${c.reset}\n`);
|
|
883
|
+
tasks.forEach(t => {
|
|
884
|
+
const marker = t.isCurrent ? `${c.green}→${c.reset}` : ' ';
|
|
885
|
+
const statusIcon = t.status === 'done' ? `${c.dim}✓${c.reset}`
|
|
886
|
+
: t.status === 'blocked' ? `${c.yellow}○${c.reset}`
|
|
887
|
+
: `${c.green}●${c.reset}`;
|
|
888
|
+
console.log(`${marker} ${statusIcon} ${t.taskName} ${c.dim}${t.progress}${c.reset}`);
|
|
889
|
+
});
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// Interactive mode
|
|
894
|
+
let selectedIdx = tasks.findIndex(t => t.isCurrent);
|
|
895
|
+
if (selectedIdx < 0) selectedIdx = 0;
|
|
896
|
+
let inDetailView = false;
|
|
897
|
+
|
|
898
|
+
const clearScreen = () => process.stdout.write('\x1b[2J\x1b[H');
|
|
899
|
+
const hideCursor = () => process.stdout.write('\x1b[?25l');
|
|
900
|
+
const showCursor = () => process.stdout.write('\x1b[?25h');
|
|
901
|
+
|
|
902
|
+
const renderList = () => {
|
|
903
|
+
clearScreen();
|
|
904
|
+
console.log(`${c.bold}Tasks${c.reset}\n`);
|
|
905
|
+
|
|
906
|
+
tasks.forEach((task, idx) => {
|
|
907
|
+
const selected = idx === selectedIdx;
|
|
908
|
+
const prefix = selected ? `${c.cyan}❯${c.reset}` : ' ';
|
|
909
|
+
const statusIcon = task.status === 'done' ? `${c.dim}✓${c.reset}`
|
|
910
|
+
: task.status === 'blocked' ? `${c.yellow}○${c.reset}`
|
|
911
|
+
: `${c.green}●${c.reset}`;
|
|
912
|
+
const current = task.isCurrent ? ` ${c.green}(current)${c.reset}` : '';
|
|
913
|
+
const name = selected ? `${c.bold}${task.taskName}${c.reset}` : task.taskName;
|
|
914
|
+
const progressText = task.progress !== '—' ? ` ${c.green}${task.progress}${c.reset}` : '';
|
|
915
|
+
|
|
916
|
+
console.log(`${prefix} ${statusIcon} ${name}${progressText}${current}`);
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
console.log(`\n${c.dim}↑/↓ select · enter view · s switch · d done · x delete · q quit${c.reset}`);
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
const renderDetail = () => {
|
|
923
|
+
clearScreen();
|
|
924
|
+
const task = tasks[selectedIdx];
|
|
925
|
+
const statusColor = task.status === 'done' ? c.dim
|
|
926
|
+
: task.status === 'blocked' ? c.yellow
|
|
927
|
+
: c.green;
|
|
928
|
+
|
|
929
|
+
console.log(`${c.bold}${c.cyan}${task.taskName}${c.reset}${task.isCurrent ? ` ${c.green}(current)${c.reset}` : ''}\n`);
|
|
930
|
+
console.log(` ${c.dim}Status:${c.reset} ${statusColor}${task.status}${c.reset}`);
|
|
931
|
+
console.log(` ${c.dim}Progress:${c.reset} ${task.progress !== '—' ? c.green + task.progress + c.reset : c.dim + '—' + c.reset}`);
|
|
932
|
+
if (task.goal) console.log(` ${c.dim}Goal:${c.reset} ${task.goal}`);
|
|
933
|
+
|
|
934
|
+
console.log(`\n${c.dim}esc back · s switch · d done · x delete · q quit${c.reset}`);
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
const render = () => inDetailView ? renderDetail() : renderList();
|
|
938
|
+
|
|
939
|
+
const doAction = (action) => {
|
|
940
|
+
const task = tasks[selectedIdx];
|
|
941
|
+
showCursor();
|
|
942
|
+
clearScreen();
|
|
943
|
+
|
|
944
|
+
if (action === 'switch') {
|
|
945
|
+
try {
|
|
946
|
+
git(memDir, 'checkout', task.branch);
|
|
947
|
+
console.log(`${c.green}✓${c.reset} Switched to: ${c.bold}${task.taskName}${c.reset}`);
|
|
948
|
+
} catch (err) {
|
|
949
|
+
console.log(`${c.red}Error:${c.reset} ${err.message}`);
|
|
950
|
+
}
|
|
951
|
+
process.exit(0);
|
|
952
|
+
} else if (action === 'done') {
|
|
953
|
+
try {
|
|
954
|
+
git(memDir, 'checkout', task.branch);
|
|
955
|
+
const stateFile = path.join(memDir, 'state.md');
|
|
956
|
+
if (fs.existsSync(stateFile)) {
|
|
957
|
+
let state = fs.readFileSync(stateFile, 'utf8');
|
|
958
|
+
state = state.replace(/^status:\s*.+$/m, 'status: done');
|
|
959
|
+
fs.writeFileSync(stateFile, state);
|
|
960
|
+
git(memDir, 'add', 'state.md');
|
|
961
|
+
git(memDir, 'commit', '-m', 'done: marked complete');
|
|
962
|
+
}
|
|
963
|
+
console.log(`${c.green}✓${c.reset} Marked ${c.bold}${task.taskName}${c.reset} done`);
|
|
964
|
+
} catch (err) {
|
|
965
|
+
console.log(`${c.red}Error:${c.reset} ${err.message}`);
|
|
966
|
+
}
|
|
967
|
+
process.exit(0);
|
|
968
|
+
} else if (action === 'delete') {
|
|
969
|
+
try {
|
|
970
|
+
if (task.isCurrent) {
|
|
971
|
+
git(memDir, 'checkout', 'main');
|
|
972
|
+
}
|
|
973
|
+
git(memDir, 'branch', '-D', task.branch);
|
|
974
|
+
console.log(`${c.red}✗${c.reset} Deleted ${c.bold}${task.taskName}${c.reset}`);
|
|
975
|
+
} catch (err) {
|
|
976
|
+
console.log(`${c.red}Error:${c.reset} ${err.message}`);
|
|
977
|
+
}
|
|
978
|
+
process.exit(0);
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
|
|
982
|
+
process.stdin.setRawMode(true);
|
|
983
|
+
process.stdin.resume();
|
|
984
|
+
hideCursor();
|
|
985
|
+
render();
|
|
986
|
+
|
|
987
|
+
process.stdin.on('data', (key) => {
|
|
988
|
+
const k = key.toString();
|
|
989
|
+
|
|
990
|
+
if (k === 'q' || k === '\x03') {
|
|
991
|
+
showCursor();
|
|
992
|
+
clearScreen();
|
|
993
|
+
process.exit(0);
|
|
994
|
+
} else if (k === '\x1b[A') { // up
|
|
995
|
+
selectedIdx = Math.max(0, selectedIdx - 1);
|
|
996
|
+
render();
|
|
997
|
+
} else if (k === '\x1b[B') { // down
|
|
998
|
+
selectedIdx = Math.min(tasks.length - 1, selectedIdx + 1);
|
|
999
|
+
render();
|
|
1000
|
+
} else if (k === '\r' || k === '\n') { // enter
|
|
1001
|
+
inDetailView = true;
|
|
1002
|
+
render();
|
|
1003
|
+
} else if (k === '\x1b' || k === '\x1b[D') { // esc or left
|
|
1004
|
+
inDetailView = false;
|
|
1005
|
+
render();
|
|
1006
|
+
} else if (k === 's') {
|
|
1007
|
+
doAction('switch');
|
|
1008
|
+
} else if (k === 'd') {
|
|
1009
|
+
doAction('done');
|
|
1010
|
+
} else if (k === 'x') {
|
|
1011
|
+
doAction('delete');
|
|
1012
|
+
}
|
|
779
1013
|
});
|
|
780
|
-
|
|
781
|
-
console.log('');
|
|
782
1014
|
}
|
|
783
1015
|
|
|
784
1016
|
// Switch task
|
|
@@ -1299,6 +1531,202 @@ function cmdCriteria(args, memDir) {
|
|
|
1299
1531
|
|
|
1300
1532
|
// ==================== PRIMITIVES ====================
|
|
1301
1533
|
|
|
1534
|
+
function cmdBranch(args, memDir) {
|
|
1535
|
+
const name = args[0];
|
|
1536
|
+
|
|
1537
|
+
// Initialize central mem if needed
|
|
1538
|
+
if (!fs.existsSync(CENTRAL_MEM)) {
|
|
1539
|
+
fs.mkdirSync(CENTRAL_MEM, { recursive: true });
|
|
1540
|
+
execSync('git init', { cwd: CENTRAL_MEM, stdio: 'ignore' });
|
|
1541
|
+
writeMemFile(CENTRAL_MEM, 'playbook.md', '# Playbook\n\n');
|
|
1542
|
+
git(CENTRAL_MEM, 'add', '-A');
|
|
1543
|
+
git(CENTRAL_MEM, 'commit', '-m', 'init: central memory');
|
|
1544
|
+
console.log(`${c.green}✓${c.reset} Initialized central memory: ${c.dim}${CENTRAL_MEM}${c.reset}`);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
const targetDir = memDir || CENTRAL_MEM;
|
|
1548
|
+
|
|
1549
|
+
// No args: list branches
|
|
1550
|
+
if (!name) {
|
|
1551
|
+
const branches = git(targetDir, 'branch', '--list');
|
|
1552
|
+
console.log(branches || `${c.dim}No branches${c.reset}`);
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
// Check if branch exists
|
|
1557
|
+
const branches = git(targetDir, 'branch', '--list').split('\n').map(b => b.replace('*', '').trim());
|
|
1558
|
+
const branchExists = branches.includes(name) || branches.includes(`task/${name}`);
|
|
1559
|
+
|
|
1560
|
+
try {
|
|
1561
|
+
if (branchExists) {
|
|
1562
|
+
// Switch to existing branch
|
|
1563
|
+
const branchName = branches.includes(name) ? name : `task/${name}`;
|
|
1564
|
+
git(targetDir, 'checkout', branchName);
|
|
1565
|
+
console.log(`${c.green}✓${c.reset} Switched to: ${c.cyan}${branchName}${c.reset}`);
|
|
1566
|
+
} else {
|
|
1567
|
+
// Create new branch
|
|
1568
|
+
const branchName = name.startsWith('task/') ? name : `task/${name}`;
|
|
1569
|
+
git(targetDir, 'checkout', '-b', branchName);
|
|
1570
|
+
console.log(`${c.green}✓${c.reset} Created branch: ${c.cyan}${branchName}${c.reset}`);
|
|
1571
|
+
}
|
|
1572
|
+
} catch (err) {
|
|
1573
|
+
console.log(`${c.red}Error:${c.reset} ${err.message}`);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
function cmdCommit(args, memDir) {
|
|
1578
|
+
if (!memDir) {
|
|
1579
|
+
// Use central mem
|
|
1580
|
+
memDir = CENTRAL_MEM;
|
|
1581
|
+
if (!fs.existsSync(memDir)) {
|
|
1582
|
+
console.log(`${c.yellow}No .mem repo found.${c.reset} Run ${c.cyan}mem branch <name>${c.reset} first.`);
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
const msg = args.join(' ') || 'checkpoint';
|
|
1588
|
+
|
|
1589
|
+
try {
|
|
1590
|
+
// Check for changes
|
|
1591
|
+
const status = git(memDir, 'status', '--porcelain');
|
|
1592
|
+
if (!status.trim()) {
|
|
1593
|
+
console.log(`${c.dim}No changes to commit${c.reset}`);
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
// Add and commit
|
|
1598
|
+
git(memDir, 'add', '-A');
|
|
1599
|
+
git(memDir, 'commit', '-m', msg);
|
|
1600
|
+
console.log(`${c.green}✓${c.reset} Committed: ${c.dim}${msg}${c.reset}`);
|
|
1601
|
+
} catch (err) {
|
|
1602
|
+
console.log(`${c.red}Error:${c.reset} ${err.message}`);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
// ============================================================
|
|
1607
|
+
// cmdNew: Create a new task with all necessary files
|
|
1608
|
+
// This is the ONLY way to create tasks - agx calls this
|
|
1609
|
+
// ============================================================
|
|
1610
|
+
function cmdNew(args, memDir) {
|
|
1611
|
+
// Parse flags
|
|
1612
|
+
const jsonMode = args.includes('--json');
|
|
1613
|
+
|
|
1614
|
+
// Parse --provider / -P flag
|
|
1615
|
+
let provider = 'claude';
|
|
1616
|
+
const providerIdx = args.findIndex(a => a === '--provider' || a === '-P');
|
|
1617
|
+
if (providerIdx !== -1 && args[providerIdx + 1]) {
|
|
1618
|
+
provider = args[providerIdx + 1].toLowerCase();
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
// Parse --dir flag for project directory mapping
|
|
1622
|
+
let projectDir = process.cwd();
|
|
1623
|
+
const dirIdx = args.findIndex(a => a === '--dir');
|
|
1624
|
+
if (dirIdx !== -1 && args[dirIdx + 1]) {
|
|
1625
|
+
projectDir = args[dirIdx + 1];
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// Extract goal text - filter out all flags and their values
|
|
1629
|
+
const flagsWithValues = ['--provider', '-P', '--dir'];
|
|
1630
|
+
const flagsWithoutValues = ['--json'];
|
|
1631
|
+
const goalParts = [];
|
|
1632
|
+
for (let i = 0; i < args.length; i++) {
|
|
1633
|
+
if (flagsWithValues.includes(args[i])) {
|
|
1634
|
+
i++; // skip the flag's value too
|
|
1635
|
+
continue;
|
|
1636
|
+
}
|
|
1637
|
+
if (flagsWithoutValues.includes(args[i])) {
|
|
1638
|
+
continue;
|
|
1639
|
+
}
|
|
1640
|
+
goalParts.push(args[i]);
|
|
1641
|
+
}
|
|
1642
|
+
const goalText = goalParts.join(' ');
|
|
1643
|
+
if (!goalText) {
|
|
1644
|
+
if (jsonMode) {
|
|
1645
|
+
console.log(JSON.stringify({ error: 'missing_goal', usage: 'mem new "<goal>" [--provider c] [--dir /path]' }));
|
|
1646
|
+
} else {
|
|
1647
|
+
console.log(`${c.red}Usage:${c.reset} mem new "<goal>" [--provider c|g|o] [--dir /path]`);
|
|
1648
|
+
}
|
|
1649
|
+
process.exit(1);
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// Generate task name from goal
|
|
1653
|
+
const taskName = goalText
|
|
1654
|
+
.toLowerCase()
|
|
1655
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
1656
|
+
.split(/\s+/)
|
|
1657
|
+
.slice(0, 3)
|
|
1658
|
+
.join('-');
|
|
1659
|
+
|
|
1660
|
+
const branch = `task/${taskName}`;
|
|
1661
|
+
const today = new Date().toISOString().split('T')[0];
|
|
1662
|
+
|
|
1663
|
+
try {
|
|
1664
|
+
// Create branch (also initializes central mem if needed)
|
|
1665
|
+
cmdBranch([taskName], memDir);
|
|
1666
|
+
|
|
1667
|
+
// Write goal.md
|
|
1668
|
+
const goalMd = `---
|
|
1669
|
+
task: ${taskName}
|
|
1670
|
+
created: ${today}
|
|
1671
|
+
---
|
|
1672
|
+
|
|
1673
|
+
# Goal
|
|
1674
|
+
|
|
1675
|
+
${goalText}
|
|
1676
|
+
|
|
1677
|
+
## Criteria
|
|
1678
|
+
|
|
1679
|
+
- [ ] Define success criteria
|
|
1680
|
+
|
|
1681
|
+
## Progress: 0%`;
|
|
1682
|
+
writeMemFile(CENTRAL_MEM, 'goal.md', goalMd);
|
|
1683
|
+
|
|
1684
|
+
// Write state.md with provider
|
|
1685
|
+
const stateMd = `---
|
|
1686
|
+
status: active
|
|
1687
|
+
provider: ${provider}
|
|
1688
|
+
---
|
|
1689
|
+
|
|
1690
|
+
# State
|
|
1691
|
+
|
|
1692
|
+
## Next Step
|
|
1693
|
+
|
|
1694
|
+
Begin work
|
|
1695
|
+
|
|
1696
|
+
## Checkpoints
|
|
1697
|
+
|
|
1698
|
+
- [ ] Started`;
|
|
1699
|
+
writeMemFile(CENTRAL_MEM, 'state.md', stateMd);
|
|
1700
|
+
|
|
1701
|
+
// Write memory.md
|
|
1702
|
+
writeMemFile(CENTRAL_MEM, 'memory.md', '# Learnings\n\n');
|
|
1703
|
+
|
|
1704
|
+
// Commit
|
|
1705
|
+
git(CENTRAL_MEM, 'add', '-A');
|
|
1706
|
+
git(CENTRAL_MEM, 'commit', '-m', `new: ${taskName}`);
|
|
1707
|
+
|
|
1708
|
+
// Update index mapping (projectDir -> branch)
|
|
1709
|
+
const index = loadIndex();
|
|
1710
|
+
index[projectDir] = branch;
|
|
1711
|
+
saveIndex(index);
|
|
1712
|
+
|
|
1713
|
+
if (jsonMode) {
|
|
1714
|
+
console.log(JSON.stringify({ taskName, branch, projectDir, created: today, goal: goalText, provider }));
|
|
1715
|
+
} else {
|
|
1716
|
+
console.log(`${c.green}✓${c.reset} Created task: ${c.bold}${taskName}${c.reset}`);
|
|
1717
|
+
console.log(`${c.green}✓${c.reset} Provider: ${c.cyan}${provider}${c.reset}`);
|
|
1718
|
+
console.log(`${c.green}✓${c.reset} Mapped: ${c.dim}${projectDir} → ${branch}${c.reset}`);
|
|
1719
|
+
}
|
|
1720
|
+
} catch (err) {
|
|
1721
|
+
if (jsonMode) {
|
|
1722
|
+
console.log(JSON.stringify({ error: err.message }));
|
|
1723
|
+
} else {
|
|
1724
|
+
console.log(`${c.red}Error:${c.reset} ${err.message}`);
|
|
1725
|
+
}
|
|
1726
|
+
process.exit(1);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1302
1730
|
function cmdSet(args, memDir) {
|
|
1303
1731
|
if (!memDir) {
|
|
1304
1732
|
console.log(`${c.yellow}No .mem repo found.${c.reset}`);
|
|
@@ -1402,6 +1830,73 @@ function cmdLog(memDir) {
|
|
|
1402
1830
|
console.log(log);
|
|
1403
1831
|
}
|
|
1404
1832
|
|
|
1833
|
+
// Live watch memory activity
|
|
1834
|
+
function cmdWatch(memDir) {
|
|
1835
|
+
if (!memDir) {
|
|
1836
|
+
console.log(`${c.yellow}No .mem repo found.${c.reset}`);
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
const clearScreen = () => process.stdout.write('\x1b[2J\x1b[H');
|
|
1841
|
+
|
|
1842
|
+
const render = () => {
|
|
1843
|
+
clearScreen();
|
|
1844
|
+
console.log('═══════════════════════════════════════════════════════');
|
|
1845
|
+
console.log(' 📝 Memory Activity');
|
|
1846
|
+
console.log('═══════════════════════════════════════════════════════');
|
|
1847
|
+
console.log('');
|
|
1848
|
+
|
|
1849
|
+
// Show main branch
|
|
1850
|
+
console.log(` ${c.yellow}📌 main${c.reset}`);
|
|
1851
|
+
try {
|
|
1852
|
+
const mainLog = git(memDir, 'log', 'main', '--format=%C(dim)%ar%C(reset) %C(cyan)%s%C(reset)', '-6');
|
|
1853
|
+
mainLog.split('\n').filter(l => l).forEach(line => console.log(` ${line}`));
|
|
1854
|
+
} catch (e) {
|
|
1855
|
+
console.log(` ${c.dim}(no commits)${c.reset}`);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
// Show task branches
|
|
1859
|
+
try {
|
|
1860
|
+
const branches = git(memDir, 'branch', '--format=%(refname:short)')
|
|
1861
|
+
.split('\n')
|
|
1862
|
+
.filter(b => b.startsWith('task/'))
|
|
1863
|
+
.slice(0, 3);
|
|
1864
|
+
|
|
1865
|
+
for (const branch of branches) {
|
|
1866
|
+
const taskName = branch.replace('task/', '');
|
|
1867
|
+
console.log('');
|
|
1868
|
+
console.log(` ${c.yellow}📌 ${taskName}${c.reset}`);
|
|
1869
|
+
try {
|
|
1870
|
+
const branchLog = git(memDir, 'log', branch, '--format=%C(dim)%ar%C(reset) %C(cyan)%s%C(reset)', '-3');
|
|
1871
|
+
branchLog.split('\n').filter(l => l).forEach(line => console.log(` ${line}`));
|
|
1872
|
+
} catch (e) {
|
|
1873
|
+
console.log(` ${c.dim}(no commits)${c.reset}`);
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
} catch (e) {
|
|
1877
|
+
// No task branches
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
console.log('');
|
|
1881
|
+
console.log('═══════════════════════════════════════════════════════');
|
|
1882
|
+
const now = new Date().toLocaleTimeString('en-US', { hour12: false });
|
|
1883
|
+
console.log(` ${c.dim}${now} • ctrl+c to exit${c.reset}`);
|
|
1884
|
+
};
|
|
1885
|
+
|
|
1886
|
+
// Initial render
|
|
1887
|
+
render();
|
|
1888
|
+
|
|
1889
|
+
// Update every 2 seconds
|
|
1890
|
+
const interval = setInterval(render, 2000);
|
|
1891
|
+
|
|
1892
|
+
// Handle exit
|
|
1893
|
+
process.on('SIGINT', () => {
|
|
1894
|
+
clearInterval(interval);
|
|
1895
|
+
clearScreen();
|
|
1896
|
+
process.exit(0);
|
|
1897
|
+
});
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1405
1900
|
// ==================== SKILL ====================
|
|
1406
1901
|
|
|
1407
1902
|
function isSkillInstalled(provider) {
|
|
@@ -1501,171 +1996,6 @@ function handleSkillCommand(args) {
|
|
|
1501
1996
|
console.log('');
|
|
1502
1997
|
}
|
|
1503
1998
|
|
|
1504
|
-
// ==================== WAKE ====================
|
|
1505
|
-
|
|
1506
|
-
// Parse wake pattern to cron expression
|
|
1507
|
-
function parseWakeToCron(pattern) {
|
|
1508
|
-
pattern = pattern.toLowerCase().trim();
|
|
1509
|
-
|
|
1510
|
-
// Handle intervals: every Xm, every Xh
|
|
1511
|
-
const intervalMatch = pattern.match(/^every\s+(\d+)\s*(m|min|minutes?|h|hr|hours?)$/);
|
|
1512
|
-
if (intervalMatch) {
|
|
1513
|
-
const num = parseInt(intervalMatch[1]);
|
|
1514
|
-
const unit = intervalMatch[2][0];
|
|
1515
|
-
if (unit === 'm') {
|
|
1516
|
-
return `*/${num} * * * *`;
|
|
1517
|
-
} else if (unit === 'h') {
|
|
1518
|
-
return `0 */${num} * * *`;
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
// Handle daily: Xam daily, Xpm daily, every day at X
|
|
1523
|
-
const dailyMatch = pattern.match(/^(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\s*(?:daily|every\s*day)?$/);
|
|
1524
|
-
if (dailyMatch) {
|
|
1525
|
-
let hour = parseInt(dailyMatch[1]);
|
|
1526
|
-
const min = dailyMatch[2] ? parseInt(dailyMatch[2]) : 0;
|
|
1527
|
-
const ampm = dailyMatch[3];
|
|
1528
|
-
if (ampm === 'pm' && hour < 12) hour += 12;
|
|
1529
|
-
if (ampm === 'am' && hour === 12) hour = 0;
|
|
1530
|
-
return `${min} ${hour} * * *`;
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
|
-
// Handle weekly: monday 9am, every tuesday at 3pm
|
|
1534
|
-
const weeklyMatch = pattern.match(/^(?:every\s+)?(mon|tue|wed|thu|fri|sat|sun)[a-z]*\s+(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)?$/);
|
|
1535
|
-
if (weeklyMatch) {
|
|
1536
|
-
const days = { sun: 0, mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6 };
|
|
1537
|
-
const day = days[weeklyMatch[1]];
|
|
1538
|
-
let hour = parseInt(weeklyMatch[2]);
|
|
1539
|
-
const min = weeklyMatch[3] ? parseInt(weeklyMatch[3]) : 0;
|
|
1540
|
-
const ampm = weeklyMatch[4];
|
|
1541
|
-
if (ampm === 'pm' && hour < 12) hour += 12;
|
|
1542
|
-
if (ampm === 'am' && hour === 12) hour = 0;
|
|
1543
|
-
return `${min} ${hour} * * ${day}`;
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
// Already cron format? Pass through
|
|
1547
|
-
if (pattern.match(/^[\d\*\/\-\,]+\s+[\d\*\/\-\,]+\s+[\d\*\/\-\,]+\s+[\d\*\/\-\,]+\s+[\d\*\/\-\,]+$/)) {
|
|
1548
|
-
return pattern;
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
return null;
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
// Set/get/clear wake
|
|
1555
|
-
function cmdWake(args, memDir) {
|
|
1556
|
-
if (!memDir) {
|
|
1557
|
-
console.log(`${c.yellow}No .mem repo found.${c.reset}`);
|
|
1558
|
-
return;
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
const state = readMemFile(memDir, 'state.md') || '';
|
|
1562
|
-
const { frontmatter, body } = parseFrontmatter(state);
|
|
1563
|
-
|
|
1564
|
-
// No args: show current wake
|
|
1565
|
-
if (args.length === 0) {
|
|
1566
|
-
if (frontmatter.wake) {
|
|
1567
|
-
console.log(`${c.bold}Wake:${c.reset} ${frontmatter.wake}`);
|
|
1568
|
-
if (frontmatter.wake_command) {
|
|
1569
|
-
console.log(`${c.bold}Command:${c.reset} ${frontmatter.wake_command}`);
|
|
1570
|
-
}
|
|
1571
|
-
const cron = parseWakeToCron(frontmatter.wake);
|
|
1572
|
-
if (cron) {
|
|
1573
|
-
console.log(`${c.dim}Cron: ${cron}${c.reset}`);
|
|
1574
|
-
}
|
|
1575
|
-
} else {
|
|
1576
|
-
console.log(`${c.dim}No wake set${c.reset}`);
|
|
1577
|
-
console.log(`${c.dim}Usage: mem wake "every 15m" [--run "command"]${c.reset}`);
|
|
1578
|
-
}
|
|
1579
|
-
return;
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
// Clear wake
|
|
1583
|
-
if (args[0] === 'clear') {
|
|
1584
|
-
delete frontmatter.wake;
|
|
1585
|
-
delete frontmatter.wake_command;
|
|
1586
|
-
writeMemFile(memDir, 'state.md', serializeFrontmatter(frontmatter, body));
|
|
1587
|
-
git(memDir, 'add', 'state.md');
|
|
1588
|
-
git(memDir, 'commit', '-m', 'wake: clear');
|
|
1589
|
-
console.log(`${c.green}✓${c.reset} Wake cleared`);
|
|
1590
|
-
return;
|
|
1591
|
-
}
|
|
1592
|
-
|
|
1593
|
-
// Set wake
|
|
1594
|
-
let pattern = '';
|
|
1595
|
-
let command = '';
|
|
1596
|
-
|
|
1597
|
-
// Parse args
|
|
1598
|
-
for (let i = 0; i < args.length; i++) {
|
|
1599
|
-
if (args[i] === '--run' || args[i] === '-r') {
|
|
1600
|
-
command = args.slice(i + 1).join(' ');
|
|
1601
|
-
break;
|
|
1602
|
-
}
|
|
1603
|
-
pattern += (pattern ? ' ' : '') + args[i];
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
// Validate pattern
|
|
1607
|
-
const cron = parseWakeToCron(pattern);
|
|
1608
|
-
if (!cron) {
|
|
1609
|
-
console.log(`${c.red}Could not parse wake pattern:${c.reset} ${pattern}`);
|
|
1610
|
-
console.log(`\n${c.dim}Examples:${c.reset}`);
|
|
1611
|
-
console.log(` mem wake "every 15m"`);
|
|
1612
|
-
console.log(` mem wake "every 2h"`);
|
|
1613
|
-
console.log(` mem wake "8am daily"`);
|
|
1614
|
-
console.log(` mem wake "monday 9am"`);
|
|
1615
|
-
console.log(` mem wake "*/30 * * * *" ${c.dim}(raw cron)${c.reset}`);
|
|
1616
|
-
return;
|
|
1617
|
-
}
|
|
1618
|
-
|
|
1619
|
-
frontmatter.wake = pattern;
|
|
1620
|
-
if (command) {
|
|
1621
|
-
frontmatter.wake_command = command;
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
writeMemFile(memDir, 'state.md', serializeFrontmatter(frontmatter, body));
|
|
1625
|
-
git(memDir, 'add', 'state.md');
|
|
1626
|
-
git(memDir, 'commit', '-m', `wake: ${pattern}`);
|
|
1627
|
-
|
|
1628
|
-
console.log(`${c.green}✓${c.reset} Wake set: ${c.bold}${pattern}${c.reset}`);
|
|
1629
|
-
console.log(`${c.dim}Cron: ${cron}${c.reset}`);
|
|
1630
|
-
if (command) {
|
|
1631
|
-
console.log(`${c.dim}Command: ${command}${c.reset}`);
|
|
1632
|
-
}
|
|
1633
|
-
console.log(`\n${c.dim}Export with: ${c.reset}mem cron export`);
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
// Export to cron format
|
|
1637
|
-
function cmdCronExport(memDir) {
|
|
1638
|
-
if (!memDir) {
|
|
1639
|
-
console.log(`${c.yellow}No .mem repo found.${c.reset}`);
|
|
1640
|
-
return;
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
const state = readMemFile(memDir, 'state.md') || '';
|
|
1644
|
-
const { frontmatter } = parseFrontmatter(state);
|
|
1645
|
-
|
|
1646
|
-
if (!frontmatter.wake) {
|
|
1647
|
-
console.log(`${c.yellow}No wake set.${c.reset} Use ${c.cyan}mem wake "pattern"${c.reset} first.`);
|
|
1648
|
-
return;
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
const cron = parseWakeToCron(frontmatter.wake);
|
|
1652
|
-
if (!cron) {
|
|
1653
|
-
console.log(`${c.red}Could not parse wake pattern:${c.reset} ${frontmatter.wake}`);
|
|
1654
|
-
return;
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
|
-
// Detect PATH from current environment for cron (which has minimal PATH)
|
|
1658
|
-
const nodeBin = path.dirname(process.execPath);
|
|
1659
|
-
const pathDirs = [nodeBin, '/usr/local/bin', '/usr/bin', '/bin'];
|
|
1660
|
-
const pathPrefix = `PATH=${pathDirs.join(':')}`;
|
|
1661
|
-
|
|
1662
|
-
const command = frontmatter.wake_command || `cd ${memDir} && mem context`;
|
|
1663
|
-
const entry = `${cron} ${pathPrefix} && ${command}`;
|
|
1664
|
-
|
|
1665
|
-
// Just output the cron line (can be piped/appended)
|
|
1666
|
-
console.log(entry);
|
|
1667
|
-
}
|
|
1668
|
-
|
|
1669
1999
|
// ==================== MCP ====================
|
|
1670
2000
|
|
|
1671
2001
|
function startMCPServer(args) {
|
|
@@ -1756,7 +2086,7 @@ ${c.bold}PROGRESS${c.reset}
|
|
|
1756
2086
|
criteria <n> Mark criterion #n complete
|
|
1757
2087
|
|
|
1758
2088
|
${c.bold}QUERY${c.reset}
|
|
1759
|
-
context Full
|
|
2089
|
+
context Full context for agent
|
|
1760
2090
|
history Task progression
|
|
1761
2091
|
query "<search>" Search all memory
|
|
1762
2092
|
|
|
@@ -1772,12 +2102,8 @@ ${c.bold}PRIMITIVES${c.reset}
|
|
|
1772
2102
|
get <key> Get a value
|
|
1773
2103
|
append <list> <item> Append to list
|
|
1774
2104
|
log Raw git log
|
|
2105
|
+
watch Live tail of memory activity
|
|
1775
2106
|
|
|
1776
|
-
${c.bold}WAKE${c.reset}
|
|
1777
|
-
wake "<pattern>" Set wake schedule (every 15m, 8am daily, etc.)
|
|
1778
|
-
wake --run "<cmd>" Set wake with custom command
|
|
1779
|
-
wake clear Clear wake schedule
|
|
1780
|
-
cron export Export wake as crontab entry
|
|
1781
2107
|
|
|
1782
2108
|
${c.bold}INTEGRATION${c.reset}
|
|
1783
2109
|
skill View LLM skill
|
|
@@ -1799,42 +2125,45 @@ ${c.dim}Docs: https://github.com/ramarlina/memx${c.reset}
|
|
|
1799
2125
|
// ==================== MAIN ====================
|
|
1800
2126
|
|
|
1801
2127
|
async function main() {
|
|
2128
|
+
// ============================================================
|
|
2129
|
+
// mem: Git-backed key-value store for AI agents
|
|
2130
|
+
//
|
|
2131
|
+
// Architecture:
|
|
2132
|
+
// - All data stored in central ~/.mem git repo
|
|
2133
|
+
// - Each task is a branch (task/<name>)
|
|
2134
|
+
// - index.json maps project directories to task branches
|
|
2135
|
+
// - agx orchestrator uses mem primitives, never accesses ~/.mem directly
|
|
2136
|
+
// ============================================================
|
|
2137
|
+
|
|
1802
2138
|
const args = process.argv.slice(2);
|
|
1803
2139
|
const cmd = args[0];
|
|
1804
2140
|
const cmdArgs = args.slice(1);
|
|
1805
|
-
|
|
1806
|
-
//
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
if
|
|
1811
|
-
|
|
1812
|
-
//
|
|
1813
|
-
if (!
|
|
1814
|
-
ensureTaskBranch(memDir, memInfo.taskBranch);
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
// No command and no .mem? Start interactive onboarding
|
|
1819
|
-
if (!cmd && !memDir) {
|
|
1820
|
-
await interactiveInit();
|
|
1821
|
-
return;
|
|
1822
|
-
}
|
|
1823
|
-
|
|
1824
|
-
// No command but has central mem with no mapping for this dir?
|
|
1825
|
-
if (!cmd && memInfo && memInfo.unmapped) {
|
|
1826
|
-
console.log(`${c.dim}No task mapped for this directory.${c.reset}`);
|
|
1827
|
-
const create = await prompt(`Create a new task? [Y/n]: `);
|
|
1828
|
-
if (create.toLowerCase() !== 'n') {
|
|
2141
|
+
|
|
2142
|
+
// Always use central ~/.mem repository
|
|
2143
|
+
// This is the single source of truth for all task data
|
|
2144
|
+
const memDir = CENTRAL_MEM;
|
|
2145
|
+
|
|
2146
|
+
// Initialize central mem if it doesn't exist
|
|
2147
|
+
if (!fs.existsSync(memDir)) {
|
|
2148
|
+
// No command? Start interactive onboarding
|
|
2149
|
+
if (!cmd) {
|
|
1829
2150
|
await interactiveInit();
|
|
2151
|
+
return;
|
|
1830
2152
|
}
|
|
1831
|
-
|
|
2153
|
+
// For commands like 'branch', let them handle initialization
|
|
1832
2154
|
}
|
|
1833
|
-
|
|
1834
|
-
// No command
|
|
1835
|
-
if (!cmd
|
|
1836
|
-
|
|
1837
|
-
|
|
2155
|
+
|
|
2156
|
+
// No command? Show status for current directory's mapped task
|
|
2157
|
+
if (!cmd) {
|
|
2158
|
+
const index = loadIndex();
|
|
2159
|
+
const taskBranch = index[process.cwd()];
|
|
2160
|
+
if (taskBranch) {
|
|
2161
|
+
ensureTaskBranch(memDir, taskBranch);
|
|
2162
|
+
cmdStatus(memDir);
|
|
2163
|
+
} else {
|
|
2164
|
+
console.log(`${c.dim}No task mapped for this directory.${c.reset}`);
|
|
2165
|
+
console.log(`${c.dim}Run ${c.reset}mem branch <name>${c.dim} to create one.${c.reset}`);
|
|
2166
|
+
}
|
|
1838
2167
|
return;
|
|
1839
2168
|
}
|
|
1840
2169
|
|
|
@@ -1848,6 +2177,11 @@ async function main() {
|
|
|
1848
2177
|
case 'init':
|
|
1849
2178
|
await cmdInit(cmdArgs, memDir);
|
|
1850
2179
|
break;
|
|
2180
|
+
case 'new':
|
|
2181
|
+
// Create a new task - this is the primary entry point
|
|
2182
|
+
// agx calls: mem new "goal" --provider claude --dir /project/path
|
|
2183
|
+
cmdNew(cmdArgs, memDir);
|
|
2184
|
+
break;
|
|
1851
2185
|
case 'status':
|
|
1852
2186
|
cmdStatus(memDir);
|
|
1853
2187
|
break;
|
|
@@ -1918,7 +2252,7 @@ async function main() {
|
|
|
1918
2252
|
// Tasks
|
|
1919
2253
|
case 'tasks':
|
|
1920
2254
|
case 'ls':
|
|
1921
|
-
cmdTasks(memDir);
|
|
2255
|
+
cmdTasks(cmdArgs, memDir);
|
|
1922
2256
|
break;
|
|
1923
2257
|
case 'switch':
|
|
1924
2258
|
case 'sw':
|
|
@@ -1931,6 +2265,14 @@ async function main() {
|
|
|
1931
2265
|
break;
|
|
1932
2266
|
|
|
1933
2267
|
// Primitives
|
|
2268
|
+
case 'branch':
|
|
2269
|
+
case 'br':
|
|
2270
|
+
cmdBranch(cmdArgs, memDir);
|
|
2271
|
+
break;
|
|
2272
|
+
case 'commit':
|
|
2273
|
+
case 'ci':
|
|
2274
|
+
cmdCommit(cmdArgs, memDir);
|
|
2275
|
+
break;
|
|
1934
2276
|
case 'set':
|
|
1935
2277
|
cmdSet(cmdArgs, memDir);
|
|
1936
2278
|
break;
|
|
@@ -1943,24 +2285,16 @@ async function main() {
|
|
|
1943
2285
|
case 'log':
|
|
1944
2286
|
cmdLog(memDir);
|
|
1945
2287
|
break;
|
|
1946
|
-
|
|
2288
|
+
case 'watch':
|
|
2289
|
+
case 'tail':
|
|
2290
|
+
cmdWatch(memDir);
|
|
2291
|
+
break;
|
|
2292
|
+
|
|
1947
2293
|
// Skill
|
|
1948
2294
|
case 'skill':
|
|
1949
2295
|
handleSkillCommand(args);
|
|
1950
2296
|
break;
|
|
1951
|
-
|
|
1952
|
-
// Wake
|
|
1953
|
-
case 'wake':
|
|
1954
|
-
cmdWake(cmdArgs, memDir);
|
|
1955
|
-
break;
|
|
1956
|
-
case 'cron':
|
|
1957
|
-
if (cmdArgs[0] === 'export') {
|
|
1958
|
-
cmdCronExport(memDir);
|
|
1959
|
-
} else {
|
|
1960
|
-
console.log(`${c.dim}Usage: mem cron export${c.reset}`);
|
|
1961
|
-
}
|
|
1962
|
-
break;
|
|
1963
|
-
|
|
2297
|
+
|
|
1964
2298
|
// MCP
|
|
1965
2299
|
case 'mcp':
|
|
1966
2300
|
if (cmdArgs[0] === 'config') {
|
|
@@ -1976,7 +2310,81 @@ async function main() {
|
|
|
1976
2310
|
}
|
|
1977
2311
|
}
|
|
1978
2312
|
|
|
1979
|
-
main(
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
});
|
|
2313
|
+
// Only run main if executed directly (not when required for tests)
|
|
2314
|
+
if (require.main === module) {
|
|
2315
|
+
main().catch(err => {
|
|
2316
|
+
console.error(`${c.red}Error:${c.reset}`, err.message);
|
|
2317
|
+
process.exit(1);
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
// Export for testing
|
|
2322
|
+
module.exports = {
|
|
2323
|
+
// Constants
|
|
2324
|
+
CONFIG_DIR,
|
|
2325
|
+
CONFIG_FILE,
|
|
2326
|
+
CENTRAL_MEM,
|
|
2327
|
+
INDEX_FILE,
|
|
2328
|
+
MEM_SKILL,
|
|
2329
|
+
c,
|
|
2330
|
+
|
|
2331
|
+
// Utility functions
|
|
2332
|
+
loadConfig,
|
|
2333
|
+
saveConfig,
|
|
2334
|
+
prompt,
|
|
2335
|
+
loadIndex,
|
|
2336
|
+
saveIndex,
|
|
2337
|
+
findMemDir,
|
|
2338
|
+
ensureTaskBranch,
|
|
2339
|
+
git,
|
|
2340
|
+
getCurrentBranch,
|
|
2341
|
+
readMemFile,
|
|
2342
|
+
writeMemFile,
|
|
2343
|
+
parseFrontmatter,
|
|
2344
|
+
serializeFrontmatter,
|
|
2345
|
+
|
|
2346
|
+
// Command functions
|
|
2347
|
+
interactiveInit,
|
|
2348
|
+
setupRemote,
|
|
2349
|
+
cmdInit,
|
|
2350
|
+
cmdStatus,
|
|
2351
|
+
cmdGoal,
|
|
2352
|
+
cmdNext,
|
|
2353
|
+
cmdCheckpoint,
|
|
2354
|
+
cmdLearn,
|
|
2355
|
+
cmdContext,
|
|
2356
|
+
cmdTasks,
|
|
2357
|
+
cmdSwitch,
|
|
2358
|
+
cmdSync,
|
|
2359
|
+
cmdHistory,
|
|
2360
|
+
cmdDone,
|
|
2361
|
+
cmdStuck,
|
|
2362
|
+
cmdQuery,
|
|
2363
|
+
cmdPlaybook,
|
|
2364
|
+
cmdLearnings,
|
|
2365
|
+
cmdPromote,
|
|
2366
|
+
cmdConstraint,
|
|
2367
|
+
cmdProgress,
|
|
2368
|
+
cmdCriteria,
|
|
2369
|
+
cmdBranch,
|
|
2370
|
+
cmdCommit,
|
|
2371
|
+
cmdSet,
|
|
2372
|
+
cmdGet,
|
|
2373
|
+
cmdAppend,
|
|
2374
|
+
cmdLog,
|
|
2375
|
+
cmdWatch,
|
|
2376
|
+
cmdNew,
|
|
2377
|
+
|
|
2378
|
+
// Skill functions
|
|
2379
|
+
isSkillInstalled,
|
|
2380
|
+
installSkillTo,
|
|
2381
|
+
handleSkillCommand,
|
|
2382
|
+
|
|
2383
|
+
// MCP functions
|
|
2384
|
+
startMCPServer,
|
|
2385
|
+
showMCPConfig,
|
|
2386
|
+
|
|
2387
|
+
// Main
|
|
2388
|
+
showHelp,
|
|
2389
|
+
main
|
|
2390
|
+
};
|