total-agent-memory 0.1.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/LICENSE +21 -0
- package/README.md +78 -0
- package/bin/cli.js +97 -0
- package/lib/args.js +72 -0
- package/lib/commands.js +168 -0
- package/lib/doctor.js +85 -0
- package/lib/ide.js +151 -0
- package/lib/index.js +22 -0
- package/lib/install.js +135 -0
- package/lib/paths.js +44 -0
- package/lib/sh.js +66 -0
- package/lib/style.js +43 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vitalii Cherepanov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# total-agent-memory
|
|
2
|
+
|
|
3
|
+
> Persistent memory for Claude Code, Codex CLI, Cursor, Cline and any MCP client.
|
|
4
|
+
> Local-first. Hits **#5 on LoCoMo** and **96.2% R@5 on LongMemEval**.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npx -y total-agent-memory
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
That's it. One command bootstraps a Python virtual env in `~/.claude-memory/`,
|
|
11
|
+
installs the MCP server, and prints next-step instructions for wiring your IDE.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Onboard (install + show next steps)
|
|
17
|
+
npx -y total-agent-memory
|
|
18
|
+
|
|
19
|
+
# Or explicit
|
|
20
|
+
npx -y total-agent-memory install
|
|
21
|
+
npx -y total-agent-memory connect claude-code
|
|
22
|
+
npx -y total-agent-memory connect cursor
|
|
23
|
+
npx -y total-agent-memory connect codex
|
|
24
|
+
npx -y total-agent-memory status
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Alias: `tam` (same binary).
|
|
28
|
+
|
|
29
|
+
## Commands
|
|
30
|
+
|
|
31
|
+
| Command | What it does |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `install` | Create venv, install Python server. Idempotent. |
|
|
34
|
+
| `start` | Run the MCP server (stdin/stdout). |
|
|
35
|
+
| `connect <ide>` | Wire an IDE config. `claude-code` `codex` `cursor` `cline` `continue` `windsurf` `gemini-cli` `opencode` `aider` |
|
|
36
|
+
| `demo` | Health-check end-to-end. |
|
|
37
|
+
| `status` | Print install paths, DB size. |
|
|
38
|
+
| `doctor` | Diagnose Python/uv/git on PATH. |
|
|
39
|
+
| `upgrade` | Pull the latest server version. |
|
|
40
|
+
| `uninstall` | Remove `~/.claude-memory` (needs `--yes`). |
|
|
41
|
+
|
|
42
|
+
## Options
|
|
43
|
+
|
|
44
|
+
| Flag | Meaning |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `--server-version 11.2.2` | Pin a specific server release. |
|
|
47
|
+
| `--source pypi\|git` | Force install source. Default: PyPI with GitHub fallback. |
|
|
48
|
+
| `--memory-dir /path` | Override install dir (default `~/.claude-memory`). |
|
|
49
|
+
| `--yes` | Skip prompts (needed for `uninstall`). |
|
|
50
|
+
| `--no-color` | Disable ANSI. |
|
|
51
|
+
| `--verbose` | Print every shell command. |
|
|
52
|
+
|
|
53
|
+
## Requirements
|
|
54
|
+
|
|
55
|
+
- **Node ≥ 18** (you already have it if you're reading this).
|
|
56
|
+
- **Python ≥ 3.10** on PATH. The wrapper will tell you how to install it if missing.
|
|
57
|
+
- Optional: **[uv](https://docs.astral.sh/uv/)** — 10× faster installs, auto-detected.
|
|
58
|
+
|
|
59
|
+
## What gets installed
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
~/.claude-memory/
|
|
63
|
+
├── .venv/ # isolated Python env
|
|
64
|
+
│ └── bin/claude-total-memory # MCP server entry-point
|
|
65
|
+
└── memory.db # SQLite — your memory lives here
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Nothing global is touched. Uninstall with `tam uninstall --yes`.
|
|
69
|
+
|
|
70
|
+
## Links
|
|
71
|
+
|
|
72
|
+
- Site: <https://totalmemory.dev>
|
|
73
|
+
- Server source: <https://github.com/vbcherepanov/total-agent-memory>
|
|
74
|
+
- PyPI package: `claude-total-memory`
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// total-agent-memory CLI — Node wrapper around the Python MCP server.
|
|
3
|
+
// Zero runtime dependencies, fast cold start, works on macOS / Linux / Windows.
|
|
4
|
+
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const { parseArgs } = require('../lib/args');
|
|
8
|
+
const { c, banner, dim, bold } = require('../lib/style');
|
|
9
|
+
const cmds = require('../lib/commands');
|
|
10
|
+
|
|
11
|
+
const VERSION = require('../package.json').version;
|
|
12
|
+
|
|
13
|
+
const HELP = `${banner()}
|
|
14
|
+
|
|
15
|
+
${bold('Usage:')} total-agent-memory <command> [options]
|
|
16
|
+
${dim('alias:')} tam <command> [options]
|
|
17
|
+
|
|
18
|
+
${bold('Commands:')}
|
|
19
|
+
install Install Python server into ~/.claude-memory (idempotent)
|
|
20
|
+
start Start the MCP server (default if no command)
|
|
21
|
+
connect <ide> Wire memory into an IDE config
|
|
22
|
+
${dim('claude-code | codex | cursor | cline | continue')}
|
|
23
|
+
${dim('aider | windsurf | gemini-cli | opencode')}
|
|
24
|
+
demo Seed demo session + run a recall to prove it works
|
|
25
|
+
status Health check + tool count + DB stats
|
|
26
|
+
doctor Diagnose environment (python, uv, ports, configs)
|
|
27
|
+
upgrade Upgrade to the latest server version
|
|
28
|
+
uninstall Remove ~/.claude-memory and IDE wirings
|
|
29
|
+
version Print version
|
|
30
|
+
help Show this message
|
|
31
|
+
|
|
32
|
+
${bold('Options:')}
|
|
33
|
+
--version, -v Print npm wrapper version
|
|
34
|
+
--server-version <v> Pin server version (default: latest)
|
|
35
|
+
--source <pypi|git> Where to install from (default: auto)
|
|
36
|
+
--memory-dir <path> Override install dir (default: ~/.claude-memory)
|
|
37
|
+
--yes, -y Skip prompts
|
|
38
|
+
--no-color Disable ANSI colors
|
|
39
|
+
--verbose Print every shell command
|
|
40
|
+
|
|
41
|
+
${bold('Examples:')}
|
|
42
|
+
${c.dim('# Install and start, then wire Claude Code:')}
|
|
43
|
+
npx -y total-agent-memory install
|
|
44
|
+
npx -y total-agent-memory connect claude-code
|
|
45
|
+
|
|
46
|
+
${c.dim('# All-in-one onboarding:')}
|
|
47
|
+
npx -y total-agent-memory
|
|
48
|
+
|
|
49
|
+
${c.dim('# Pin a specific server version:')}
|
|
50
|
+
npx -y total-agent-memory install --server-version 11.2.2
|
|
51
|
+
|
|
52
|
+
${bold('Docs:')} https://totalmemory.dev
|
|
53
|
+
${bold('Repo:')} https://github.com/vbcherepanov/total-agent-memory
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
async function main() {
|
|
57
|
+
const argv = process.argv.slice(2);
|
|
58
|
+
const parsed = parseArgs(argv);
|
|
59
|
+
|
|
60
|
+
if (parsed.flags.version || parsed.flags.v) {
|
|
61
|
+
process.stdout.write(`${VERSION}\n`);
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
if (parsed.command === 'version') {
|
|
65
|
+
process.stdout.write(`total-agent-memory v${VERSION}\n`);
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
if (parsed.command === 'help' || parsed.flags.help || parsed.flags.h) {
|
|
69
|
+
process.stdout.write(HELP);
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const ctx = {
|
|
74
|
+
flags: parsed.flags,
|
|
75
|
+
positional: parsed.positional,
|
|
76
|
+
wrapperVersion: VERSION,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const cmd = parsed.command || 'onboard';
|
|
80
|
+
const fn = cmds[cmd];
|
|
81
|
+
if (!fn) {
|
|
82
|
+
process.stderr.write(`${c.red('error')}: unknown command "${cmd}"\n`);
|
|
83
|
+
process.stderr.write(HELP);
|
|
84
|
+
return 2;
|
|
85
|
+
}
|
|
86
|
+
return await fn(ctx);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
main()
|
|
90
|
+
.then((code) => process.exit(code || 0))
|
|
91
|
+
.catch((err) => {
|
|
92
|
+
process.stderr.write(`${c.red('fatal')}: ${err && err.message ? err.message : err}\n`);
|
|
93
|
+
if (process.env.TAM_DEBUG || process.argv.includes('--verbose')) {
|
|
94
|
+
process.stderr.write(`${err && err.stack ? err.stack : ''}\n`);
|
|
95
|
+
}
|
|
96
|
+
process.exit(1);
|
|
97
|
+
});
|
package/lib/args.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Minimal CLI parser. Supports:
|
|
4
|
+
// <command> <positional...>
|
|
5
|
+
// --flag, --flag=value, --flag value
|
|
6
|
+
// -shortflag, -shortflag value
|
|
7
|
+
// bare positionals collected in order.
|
|
8
|
+
|
|
9
|
+
const FLAGS_WITH_VALUE = new Set([
|
|
10
|
+
'--server-version',
|
|
11
|
+
'--source',
|
|
12
|
+
'--memory-dir',
|
|
13
|
+
'--ide',
|
|
14
|
+
'--port',
|
|
15
|
+
'--log-level',
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
function normalize(name) {
|
|
19
|
+
return name.replace(/^-+/, '');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseArgs(argv) {
|
|
23
|
+
const flags = {};
|
|
24
|
+
const positional = [];
|
|
25
|
+
let command = null;
|
|
26
|
+
|
|
27
|
+
for (let i = 0; i < argv.length; i++) {
|
|
28
|
+
const tok = argv[i];
|
|
29
|
+
if (!tok) continue;
|
|
30
|
+
if (tok === '--') {
|
|
31
|
+
positional.push(...argv.slice(i + 1));
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
if (tok.startsWith('--')) {
|
|
35
|
+
const eq = tok.indexOf('=');
|
|
36
|
+
let key, val;
|
|
37
|
+
if (eq !== -1) {
|
|
38
|
+
key = tok.slice(0, eq);
|
|
39
|
+
val = tok.slice(eq + 1);
|
|
40
|
+
} else {
|
|
41
|
+
key = tok;
|
|
42
|
+
if (FLAGS_WITH_VALUE.has(key) && argv[i + 1] && !argv[i + 1].startsWith('-')) {
|
|
43
|
+
val = argv[++i];
|
|
44
|
+
} else {
|
|
45
|
+
val = true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
flags[normalize(key)] = val;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (tok.startsWith('-') && tok.length > 1) {
|
|
52
|
+
// Short flag(s). We treat each separately and they take no value
|
|
53
|
+
// except the well-known -v/--version mapping.
|
|
54
|
+
const short = tok.slice(1);
|
|
55
|
+
flags[short] = true;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (command === null) {
|
|
59
|
+
command = tok;
|
|
60
|
+
} else {
|
|
61
|
+
positional.push(tok);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Normalize aliases.
|
|
66
|
+
if (flags.y) flags.yes = true;
|
|
67
|
+
if (flags.h) flags.help = true;
|
|
68
|
+
|
|
69
|
+
return { command, positional, flags };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { parseArgs };
|
package/lib/commands.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
const { install, venvBin, pinned } = require('./install');
|
|
7
|
+
const { connect, SUPPORTED } = require('./ide');
|
|
8
|
+
const { diagnose, printReport, printRemediation } = require('./doctor');
|
|
9
|
+
const { memoryDir, venvDir, exists, home } = require('./paths');
|
|
10
|
+
const { run, runOrThrow } = require('./sh');
|
|
11
|
+
const { c, bold, ok, fail, warn, info, banner, step } = require('./style');
|
|
12
|
+
|
|
13
|
+
async function ensureInstalled(ctx) {
|
|
14
|
+
const bin = venvBin(ctx.flags, 'claude-total-memory');
|
|
15
|
+
if (exists(bin)) return bin;
|
|
16
|
+
process.stdout.write(`${info('Server not installed yet — installing now…')}\n`);
|
|
17
|
+
const code = await install(ctx);
|
|
18
|
+
if (code !== 0) throw new Error('install failed');
|
|
19
|
+
return bin;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
async function cmdInstall(ctx) {
|
|
25
|
+
process.stdout.write(banner());
|
|
26
|
+
return await install(ctx);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function cmdStart(ctx) {
|
|
30
|
+
const bin = await ensureInstalled(ctx);
|
|
31
|
+
process.stdout.write(`${info(`Starting MCP server: ${c.dim(bin)}`)}\n`);
|
|
32
|
+
// Replace this Node process with the server (transparent pass-through).
|
|
33
|
+
const child = spawn(bin, [], {
|
|
34
|
+
stdio: 'inherit',
|
|
35
|
+
env: { ...process.env, MEMORY_MODE: process.env.MEMORY_MODE || 'fast' },
|
|
36
|
+
});
|
|
37
|
+
return await new Promise((resolve) => child.on('close', (code) => resolve(code || 0)));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function cmdConnect(ctx) {
|
|
41
|
+
const ide = ctx.positional[0];
|
|
42
|
+
if (!ide) {
|
|
43
|
+
process.stderr.write(`${fail('connect requires an IDE name')}\n`);
|
|
44
|
+
process.stderr.write(`Supported: ${SUPPORTED.join(', ')}\n`);
|
|
45
|
+
return 2;
|
|
46
|
+
}
|
|
47
|
+
await ensureInstalled(ctx);
|
|
48
|
+
try {
|
|
49
|
+
const { file, manual } = connect(ide, ctx.flags);
|
|
50
|
+
if (manual) {
|
|
51
|
+
process.stdout.write(`${warn(`Manual wiring required for ${ide}: ${file}`)}\n`);
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
process.stdout.write(banner());
|
|
55
|
+
process.stdout.write(`${ok(`Wired ${c.bold(ide)} → ${c.dim(file)}`)}\n`);
|
|
56
|
+
process.stdout.write(`${info('Restart your IDE for the change to take effect.')}\n`);
|
|
57
|
+
return 0;
|
|
58
|
+
} catch (e) {
|
|
59
|
+
process.stderr.write(`${fail(e.message)}\n`);
|
|
60
|
+
return 2;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function cmdDemo(ctx) {
|
|
65
|
+
const bin = await ensureInstalled(ctx);
|
|
66
|
+
process.stdout.write(banner());
|
|
67
|
+
process.stdout.write(`${info('Demo: seed a session + recall it.')}\n`);
|
|
68
|
+
// We use the lookup-memory CLI shipped in the same venv to drive a
|
|
69
|
+
// round-trip without needing an MCP client.
|
|
70
|
+
const cli = path.join(path.dirname(bin), process.platform === 'win32' ? 'lookup-memory.exe' : 'lookup-memory');
|
|
71
|
+
if (!exists(cli)) {
|
|
72
|
+
process.stdout.write(`${warn('lookup-memory CLI not found in venv — open an MCP client to drive the server.')}\n`);
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
process.stdout.write(`${c.dim('$ lookup-memory --help')}\n`);
|
|
76
|
+
await run(cli, ['--help']);
|
|
77
|
+
process.stdout.write(`\n${ok('Server is healthy. Connect your IDE next:')}\n`);
|
|
78
|
+
process.stdout.write(` ${c.cyan('total-agent-memory connect claude-code')}\n`);
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function cmdStatus(ctx) {
|
|
83
|
+
const env = await diagnose();
|
|
84
|
+
process.stdout.write(banner());
|
|
85
|
+
printReport(env);
|
|
86
|
+
const dir = memoryDir(ctx.flags);
|
|
87
|
+
const venv = venvDir(ctx.flags);
|
|
88
|
+
const bin = venvBin(ctx.flags, 'claude-total-memory');
|
|
89
|
+
const db = path.join(dir, 'memory.db');
|
|
90
|
+
process.stdout.write(`\n${bold('Install')}\n`);
|
|
91
|
+
process.stdout.write(` ${exists(dir) ? ok(`memory dir : ${dir}`) : warn(`memory dir : ${dir} (missing)`)}\n`);
|
|
92
|
+
process.stdout.write(` ${exists(venv) ? ok(`venv : ${venv}`) : warn(`venv : ${venv} (missing)`)}\n`);
|
|
93
|
+
process.stdout.write(` ${exists(bin) ? ok(`server bin : ${bin}`) : warn(`server bin : ${bin} (missing)`)}\n`);
|
|
94
|
+
if (exists(db)) {
|
|
95
|
+
const sz = (fs.statSync(db).size / 1024 / 1024).toFixed(2);
|
|
96
|
+
process.stdout.write(` ${ok(`memory.db : ${sz} MB`)}\n`);
|
|
97
|
+
} else {
|
|
98
|
+
process.stdout.write(` ${warn(`memory.db : ${db} (will be created on first save)`)}\n`);
|
|
99
|
+
}
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function cmdDoctor(ctx) {
|
|
104
|
+
const env = await diagnose();
|
|
105
|
+
process.stdout.write(banner());
|
|
106
|
+
printReport(env);
|
|
107
|
+
printRemediation(env);
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function cmdUpgrade(ctx) {
|
|
112
|
+
process.stdout.write(banner());
|
|
113
|
+
process.stdout.write(`${info('Upgrading server to the latest version…')}\n`);
|
|
114
|
+
// Force install with --server-version=latest.
|
|
115
|
+
const upgradeCtx = { ...ctx, flags: { ...ctx.flags, 'server-version': 'latest' } };
|
|
116
|
+
return await install(upgradeCtx);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function cmdUninstall(ctx) {
|
|
120
|
+
process.stdout.write(banner());
|
|
121
|
+
const dir = memoryDir(ctx.flags);
|
|
122
|
+
if (!exists(dir)) {
|
|
123
|
+
process.stdout.write(`${ok('Nothing to remove — directory does not exist.')}\n`);
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
process.stdout.write(`${warn(`About to remove ${dir}.`)}\n`);
|
|
127
|
+
if (!ctx.flags.yes) {
|
|
128
|
+
process.stdout.write(`${info('Re-run with --yes to confirm.')}\n`);
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
132
|
+
process.stdout.write(`${ok('Removed.')}\n`);
|
|
133
|
+
process.stdout.write(`${info('IDE configs were left intact. Edit ~/.claude.json / ~/.cursor/mcp.json / etc. manually to drop the MCP entry.')}\n`);
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// First-run onboarding: install + suggest a connect step.
|
|
138
|
+
async function cmdOnboard(ctx) {
|
|
139
|
+
process.stdout.write(banner());
|
|
140
|
+
const env = await diagnose();
|
|
141
|
+
printReport(env);
|
|
142
|
+
if (!env.python3) {
|
|
143
|
+
printRemediation(env);
|
|
144
|
+
return 1;
|
|
145
|
+
}
|
|
146
|
+
process.stdout.write(`\n${bold('Quick setup')}\n`);
|
|
147
|
+
process.stdout.write(` ${step(1, 2, 'install server')}\n`);
|
|
148
|
+
const code = await install(ctx);
|
|
149
|
+
if (code !== 0) return code;
|
|
150
|
+
process.stdout.write(` ${step(2, 2, 'next: wire an IDE')}\n\n`);
|
|
151
|
+
process.stdout.write(` ${c.cyan('total-agent-memory connect claude-code')}\n`);
|
|
152
|
+
process.stdout.write(` ${c.cyan('total-agent-memory connect cursor')}\n`);
|
|
153
|
+
process.stdout.write(` ${c.cyan('total-agent-memory connect codex')}\n`);
|
|
154
|
+
process.stdout.write(`\n${c.dim('Docs: https://totalmemory.dev')}\n`);
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
install: cmdInstall,
|
|
160
|
+
start: cmdStart,
|
|
161
|
+
connect: cmdConnect,
|
|
162
|
+
demo: cmdDemo,
|
|
163
|
+
status: cmdStatus,
|
|
164
|
+
doctor: cmdDoctor,
|
|
165
|
+
upgrade: cmdUpgrade,
|
|
166
|
+
uninstall: cmdUninstall,
|
|
167
|
+
onboard: cmdOnboard,
|
|
168
|
+
};
|
package/lib/doctor.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { probe, which } = require('./sh');
|
|
4
|
+
const { exists } = require('./paths');
|
|
5
|
+
const { c, bold, ok, fail, warn, info } = require('./style');
|
|
6
|
+
|
|
7
|
+
// Returns environment summary.
|
|
8
|
+
async function diagnose() {
|
|
9
|
+
const env = {
|
|
10
|
+
platform: process.platform,
|
|
11
|
+
arch: process.arch,
|
|
12
|
+
node: process.version,
|
|
13
|
+
python: null,
|
|
14
|
+
python3: null,
|
|
15
|
+
uv: null,
|
|
16
|
+
pip: null,
|
|
17
|
+
pipx: null,
|
|
18
|
+
git: null,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
for (const bin of ['python3', 'python', 'uv', 'pipx', 'git']) {
|
|
22
|
+
const r = probe(bin, ['--version']);
|
|
23
|
+
if (r.ok) {
|
|
24
|
+
env[bin] = { path: r.path, version: r.out };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (env.python3 && env.python3.version) {
|
|
28
|
+
const m = env.python3.version.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
29
|
+
if (m) {
|
|
30
|
+
env.pythonMajor = +m[1];
|
|
31
|
+
env.pythonMinor = +m[2];
|
|
32
|
+
env.pythonPatch = +m[3];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return env;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function pickInstaller(env) {
|
|
39
|
+
// Prefer uv (fastest, most reliable). Fall back to venv+pip.
|
|
40
|
+
if (env.uv) return 'uv';
|
|
41
|
+
if (env.python3 && env.pythonMajor >= 3 && env.pythonMinor >= 10) return 'venv-pip';
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function printReport(env) {
|
|
46
|
+
process.stdout.write(`\n${bold('Environment')}\n`);
|
|
47
|
+
process.stdout.write(` ${info(`platform : ${env.platform}/${env.arch}`)}\n`);
|
|
48
|
+
process.stdout.write(` ${info(`node : ${env.node}`)}\n`);
|
|
49
|
+
if (env.python3) {
|
|
50
|
+
process.stdout.write(` ${ok(`python3 : ${env.python3.version} ${c.dim('(' + env.python3.path + ')')}`)}\n`);
|
|
51
|
+
} else {
|
|
52
|
+
process.stdout.write(` ${fail('python3 : not found')}\n`);
|
|
53
|
+
}
|
|
54
|
+
if (env.uv) {
|
|
55
|
+
process.stdout.write(` ${ok(`uv : ${env.uv.version} ${c.dim('(' + env.uv.path + ')')}`)}\n`);
|
|
56
|
+
} else {
|
|
57
|
+
process.stdout.write(` ${warn('uv : not found (optional but recommended — 10× faster installs)')}\n`);
|
|
58
|
+
}
|
|
59
|
+
if (env.git) {
|
|
60
|
+
process.stdout.write(` ${ok(`git : ${env.git.version}`)}\n`);
|
|
61
|
+
} else {
|
|
62
|
+
process.stdout.write(` ${warn('git : not found (needed only for --source git fallback)')}\n`);
|
|
63
|
+
}
|
|
64
|
+
return env;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function printRemediation(env) {
|
|
68
|
+
if (!env.python3) {
|
|
69
|
+
process.stdout.write(`\n${bold('Install Python 3.10+:')}\n`);
|
|
70
|
+
if (process.platform === 'darwin') {
|
|
71
|
+
process.stdout.write(` ${c.cyan('brew install python@3.12')}\n`);
|
|
72
|
+
} else if (process.platform === 'linux') {
|
|
73
|
+
process.stdout.write(` ${c.cyan('sudo apt install -y python3 python3-venv python3-pip')} ${c.dim('# Debian/Ubuntu')}\n`);
|
|
74
|
+
process.stdout.write(` ${c.cyan('sudo dnf install -y python3 python3-pip')} ${c.dim('# Fedora/RHEL')}\n`);
|
|
75
|
+
} else if (process.platform === 'win32') {
|
|
76
|
+
process.stdout.write(` ${c.cyan('winget install Python.Python.3.12')}\n`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (env.python3 && !env.uv) {
|
|
80
|
+
process.stdout.write(`\n${bold('Optional — install uv for 10× faster installs:')}\n`);
|
|
81
|
+
process.stdout.write(` ${c.cyan('curl -LsSf https://astral.sh/uv/install.sh | sh')}\n`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { diagnose, pickInstaller, printReport, printRemediation };
|
package/lib/ide.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const { home, memoryDir, venvBin, exists, readJSONSafe, writeJSON, ensureDir } = require('./paths');
|
|
6
|
+
const { c, ok, info, warn } = require('./style');
|
|
7
|
+
|
|
8
|
+
const SUPPORTED = [
|
|
9
|
+
'claude-code',
|
|
10
|
+
'codex',
|
|
11
|
+
'cursor',
|
|
12
|
+
'cline',
|
|
13
|
+
'continue',
|
|
14
|
+
'aider',
|
|
15
|
+
'windsurf',
|
|
16
|
+
'gemini-cli',
|
|
17
|
+
'opencode',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
function mcpEntry(flags) {
|
|
21
|
+
const bin = venvBin(flags, 'claude-total-memory');
|
|
22
|
+
return {
|
|
23
|
+
command: bin,
|
|
24
|
+
args: [],
|
|
25
|
+
env: {
|
|
26
|
+
MEMORY_MODE: 'fast',
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function claudeCodePath() {
|
|
32
|
+
return path.join(home(), '.claude.json');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function cursorPath() {
|
|
36
|
+
return path.join(home(), '.cursor', 'mcp.json');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function clinePath() {
|
|
40
|
+
// Cline reads VSCode global state, but for portability we use its public
|
|
41
|
+
// config file location when present.
|
|
42
|
+
return path.join(home(), '.cline', 'mcp.json');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function continuePath() {
|
|
46
|
+
return path.join(home(), '.continue', 'config.json');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function codexPath() {
|
|
50
|
+
return path.join(home(), '.codex', 'config.toml');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function geminiPath() {
|
|
54
|
+
return path.join(home(), '.gemini', 'settings.json');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function windsurfPath() {
|
|
58
|
+
return path.join(home(), '.codeium', 'windsurf', 'mcp_config.json');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function aiderPath() {
|
|
62
|
+
return path.join(home(), '.aider.conf.yml');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function opencodePath() {
|
|
66
|
+
return path.join(home(), '.config', 'opencode', 'config.json');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function mergeMcpServers(file, name, entry) {
|
|
70
|
+
const cfg = readJSONSafe(file) || {};
|
|
71
|
+
cfg.mcpServers = cfg.mcpServers || {};
|
|
72
|
+
cfg.mcpServers[name] = entry;
|
|
73
|
+
writeJSON(file, cfg);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function wireClaudeCode(flags) {
|
|
77
|
+
mergeMcpServers(claudeCodePath(), 'total-agent-memory', mcpEntry(flags));
|
|
78
|
+
return claudeCodePath();
|
|
79
|
+
}
|
|
80
|
+
function wireCursor(flags) {
|
|
81
|
+
ensureDir(path.dirname(cursorPath()));
|
|
82
|
+
mergeMcpServers(cursorPath(), 'total-agent-memory', mcpEntry(flags));
|
|
83
|
+
return cursorPath();
|
|
84
|
+
}
|
|
85
|
+
function wireCline(flags) {
|
|
86
|
+
ensureDir(path.dirname(clinePath()));
|
|
87
|
+
mergeMcpServers(clinePath(), 'total-agent-memory', mcpEntry(flags));
|
|
88
|
+
return clinePath();
|
|
89
|
+
}
|
|
90
|
+
function wireContinue(flags) {
|
|
91
|
+
ensureDir(path.dirname(continuePath()));
|
|
92
|
+
const cfg = readJSONSafe(continuePath()) || {};
|
|
93
|
+
cfg.mcpServers = cfg.mcpServers || [];
|
|
94
|
+
cfg.mcpServers = cfg.mcpServers.filter((s) => s.name !== 'total-agent-memory');
|
|
95
|
+
cfg.mcpServers.push({ name: 'total-agent-memory', ...mcpEntry(flags) });
|
|
96
|
+
writeJSON(continuePath(), cfg);
|
|
97
|
+
return continuePath();
|
|
98
|
+
}
|
|
99
|
+
function wireWindsurf(flags) {
|
|
100
|
+
ensureDir(path.dirname(windsurfPath()));
|
|
101
|
+
mergeMcpServers(windsurfPath(), 'total-agent-memory', mcpEntry(flags));
|
|
102
|
+
return windsurfPath();
|
|
103
|
+
}
|
|
104
|
+
function wireGeminiCLI(flags) {
|
|
105
|
+
ensureDir(path.dirname(geminiPath()));
|
|
106
|
+
mergeMcpServers(geminiPath(), 'total-agent-memory', mcpEntry(flags));
|
|
107
|
+
return geminiPath();
|
|
108
|
+
}
|
|
109
|
+
function wireOpenCode(flags) {
|
|
110
|
+
ensureDir(path.dirname(opencodePath()));
|
|
111
|
+
mergeMcpServers(opencodePath(), 'total-agent-memory', mcpEntry(flags));
|
|
112
|
+
return opencodePath();
|
|
113
|
+
}
|
|
114
|
+
function wireCodex(flags) {
|
|
115
|
+
ensureDir(path.dirname(codexPath()));
|
|
116
|
+
const bin = venvBin(flags, 'claude-total-memory');
|
|
117
|
+
const block = `
|
|
118
|
+
|
|
119
|
+
[mcp_servers.total-agent-memory]
|
|
120
|
+
command = ${JSON.stringify(bin)}
|
|
121
|
+
args = []
|
|
122
|
+
env = { MEMORY_MODE = "fast" }
|
|
123
|
+
`;
|
|
124
|
+
let prev = '';
|
|
125
|
+
try { prev = fs.readFileSync(codexPath(), 'utf8'); } catch (_) { /* fresh */ }
|
|
126
|
+
if (prev.includes('[mcp_servers.total-agent-memory]')) {
|
|
127
|
+
return codexPath(); // already wired
|
|
128
|
+
}
|
|
129
|
+
fs.writeFileSync(codexPath(), prev + block);
|
|
130
|
+
return codexPath();
|
|
131
|
+
}
|
|
132
|
+
function wireAider() {
|
|
133
|
+
return aiderPath() + ' (manual: aider does not yet support MCP; use lookup-memory CLI instead)';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function connect(ide, flags) {
|
|
137
|
+
switch (ide) {
|
|
138
|
+
case 'claude-code': return { file: wireClaudeCode(flags) };
|
|
139
|
+
case 'codex': return { file: wireCodex(flags) };
|
|
140
|
+
case 'cursor': return { file: wireCursor(flags) };
|
|
141
|
+
case 'cline': return { file: wireCline(flags) };
|
|
142
|
+
case 'continue': return { file: wireContinue(flags) };
|
|
143
|
+
case 'windsurf': return { file: wireWindsurf(flags) };
|
|
144
|
+
case 'gemini-cli': return { file: wireGeminiCLI(flags) };
|
|
145
|
+
case 'opencode': return { file: wireOpenCode(flags) };
|
|
146
|
+
case 'aider': return { file: wireAider(flags), manual: true };
|
|
147
|
+
default: throw new Error(`unsupported IDE: ${ide}. Supported: ${SUPPORTED.join(', ')}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = { SUPPORTED, connect, mcpEntry };
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Library facade so users can `require('total-agent-memory')` and drive
|
|
4
|
+
// the wrapper from JS as well. Useful for postinstall scripts and CI.
|
|
5
|
+
|
|
6
|
+
const cmds = require('./commands');
|
|
7
|
+
const { diagnose } = require('./doctor');
|
|
8
|
+
const { mcpEntry, SUPPORTED } = require('./ide');
|
|
9
|
+
const { memoryDir, venvDir, venvBin } = require('./paths');
|
|
10
|
+
const { install, pinned } = require('./install');
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
...cmds,
|
|
14
|
+
diagnose,
|
|
15
|
+
mcpEntry,
|
|
16
|
+
SUPPORTED_IDES: SUPPORTED,
|
|
17
|
+
memoryDir,
|
|
18
|
+
venvDir,
|
|
19
|
+
venvBin,
|
|
20
|
+
installServer: install,
|
|
21
|
+
pinnedVersion: pinned,
|
|
22
|
+
};
|
package/lib/install.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { runOrThrow, run, which } = require('./sh');
|
|
6
|
+
const { memoryDir, venvDir, venvBin, ensureDir, exists } = require('./paths');
|
|
7
|
+
const { diagnose, pickInstaller, printReport, printRemediation } = require('./doctor');
|
|
8
|
+
const { c, bold, ok, fail, warn, info, step } = require('./style');
|
|
9
|
+
|
|
10
|
+
const PYPI_PACKAGE = 'claude-total-memory';
|
|
11
|
+
// Pinned to the latest stable release at the time of the wrapper release.
|
|
12
|
+
// Override via `--server-version` or env TAM_SERVER_VERSION.
|
|
13
|
+
const DEFAULT_SERVER_VERSION = '11.2.2';
|
|
14
|
+
const GIT_URL = 'https://github.com/vbcherepanov/total-agent-memory.git';
|
|
15
|
+
|
|
16
|
+
function pinned(flags) {
|
|
17
|
+
return (
|
|
18
|
+
(flags && flags['server-version']) ||
|
|
19
|
+
process.env.TAM_SERVER_VERSION ||
|
|
20
|
+
DEFAULT_SERVER_VERSION
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function specPyPI(flags) {
|
|
25
|
+
const v = pinned(flags);
|
|
26
|
+
return v && v !== 'latest' ? `${PYPI_PACKAGE}==${v}` : PYPI_PACKAGE;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function specGit(flags) {
|
|
30
|
+
const v = pinned(flags);
|
|
31
|
+
const tag = v && v !== 'latest' ? `@v${v}` : '';
|
|
32
|
+
return `git+${GIT_URL}${tag}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function pypiHas(version) {
|
|
36
|
+
// We can't import https here cheaply — use built-in for HEAD.
|
|
37
|
+
return await new Promise((resolve) => {
|
|
38
|
+
const https = require('https');
|
|
39
|
+
const url = version
|
|
40
|
+
? `https://pypi.org/pypi/${PYPI_PACKAGE}/${version}/json`
|
|
41
|
+
: `https://pypi.org/pypi/${PYPI_PACKAGE}/json`;
|
|
42
|
+
const req = https.get(url, (res) => {
|
|
43
|
+
// Drain.
|
|
44
|
+
res.on('data', () => {});
|
|
45
|
+
res.on('end', () => resolve(res.statusCode === 200));
|
|
46
|
+
});
|
|
47
|
+
req.on('error', () => resolve(false));
|
|
48
|
+
req.setTimeout(4000, () => { req.destroy(); resolve(false); });
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function pickSource(flags) {
|
|
53
|
+
const explicit = flags && flags.source;
|
|
54
|
+
if (explicit === 'pypi' || explicit === 'git') return explicit;
|
|
55
|
+
// Auto: try PyPI first, fall back to git.
|
|
56
|
+
if (await pypiHas(pinned(flags))) return 'pypi';
|
|
57
|
+
return 'git';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function createVenv(flags, env) {
|
|
61
|
+
const venv = venvDir(flags);
|
|
62
|
+
if (exists(path.join(venv, process.platform === 'win32' ? 'Scripts' : 'bin'))) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
ensureDir(memoryDir(flags));
|
|
66
|
+
|
|
67
|
+
if (env.uv) {
|
|
68
|
+
await runOrThrow('uv', ['venv', venv, '--python', '3.12'], { silent: true });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
await runOrThrow('python3', ['-m', 'venv', venv], { silent: true });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function pipInstall(flags, env, source) {
|
|
75
|
+
const spec = source === 'pypi' ? specPyPI(flags) : specGit(flags);
|
|
76
|
+
process.stdout.write(`${info(`Installing ${c.bold(spec)} (source: ${source})…`)}\n`);
|
|
77
|
+
|
|
78
|
+
// Dev-only: skip the actual pip step so CI / smoke tests can validate the
|
|
79
|
+
// wrapper end-to-end without downloading ~1.5GB of ML deps.
|
|
80
|
+
if (process.env.TAM_INSTALL_DRY === '1') {
|
|
81
|
+
process.stdout.write(` ${warn('TAM_INSTALL_DRY=1 — skipping real pip install')}\n`);
|
|
82
|
+
// Drop a marker file so subsequent steps don't barf on missing bin.
|
|
83
|
+
const fs = require('fs');
|
|
84
|
+
const markerDir = path.dirname(venvBin(flags, 'claude-total-memory'));
|
|
85
|
+
fs.mkdirSync(markerDir, { recursive: true });
|
|
86
|
+
fs.writeFileSync(venvBin(flags, 'claude-total-memory'), '#!/bin/sh\necho "dry-run shim"\n');
|
|
87
|
+
fs.chmodSync(venvBin(flags, 'claude-total-memory'), 0o755);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (env.uv) {
|
|
92
|
+
// uv pip is dramatically faster.
|
|
93
|
+
const pyExe = venvBin(flags, 'python');
|
|
94
|
+
await runOrThrow('uv', ['pip', 'install', '--python', pyExe, '--upgrade', spec]);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const pipExe = venvBin(flags, 'pip');
|
|
98
|
+
await runOrThrow(pipExe, ['install', '--upgrade', '--disable-pip-version-check', spec]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function install(ctx) {
|
|
102
|
+
const flags = ctx.flags;
|
|
103
|
+
const env = await diagnose();
|
|
104
|
+
printReport(env);
|
|
105
|
+
|
|
106
|
+
const picker = pickInstaller(env);
|
|
107
|
+
if (!picker) {
|
|
108
|
+
process.stderr.write(`\n${fail('cannot proceed: missing python3 ≥ 3.10')}\n`);
|
|
109
|
+
printRemediation(env);
|
|
110
|
+
return 1;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
process.stdout.write(`\n${bold('Installing server…')}\n`);
|
|
114
|
+
process.stdout.write(` ${step(1, 3, `venv → ${c.dim(venvDir(flags))}`)}\n`);
|
|
115
|
+
await createVenv(flags, env);
|
|
116
|
+
process.stdout.write(` ${ok('venv ready')}\n`);
|
|
117
|
+
|
|
118
|
+
process.stdout.write(` ${step(2, 3, 'resolving source (PyPI → GitHub fallback)…')}\n`);
|
|
119
|
+
const source = await pickSource(flags);
|
|
120
|
+
|
|
121
|
+
process.stdout.write(` ${step(3, 3, 'pip install…')}\n`);
|
|
122
|
+
await pipInstall(flags, env, source);
|
|
123
|
+
|
|
124
|
+
// Verify entry-point present.
|
|
125
|
+
const bin = venvBin(flags, 'claude-total-memory');
|
|
126
|
+
if (!exists(bin)) {
|
|
127
|
+
process.stderr.write(`\n${fail(`entry-point ${bin} not found after install`)}\n`);
|
|
128
|
+
return 1;
|
|
129
|
+
}
|
|
130
|
+
process.stdout.write(`\n${ok(c.bold('Installation complete.'))}\n`);
|
|
131
|
+
process.stdout.write(`${info(`Binary: ${c.dim(bin)}`)}\n`);
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = { install, venvBin, pinned, DEFAULT_SERVER_VERSION, PYPI_PACKAGE };
|
package/lib/paths.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
function home() {
|
|
8
|
+
return process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function memoryDir(flags) {
|
|
12
|
+
if (flags && typeof flags['memory-dir'] === 'string') return flags['memory-dir'];
|
|
13
|
+
if (process.env.CLAUDE_MEMORY_DIR) return process.env.CLAUDE_MEMORY_DIR;
|
|
14
|
+
return path.join(home(), '.claude-memory');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function venvDir(flags) {
|
|
18
|
+
return path.join(memoryDir(flags), '.venv');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function venvBin(flags, name) {
|
|
22
|
+
const sub = process.platform === 'win32' ? 'Scripts' : 'bin';
|
|
23
|
+
const ext = process.platform === 'win32' && !name.endsWith('.exe') ? '.exe' : '';
|
|
24
|
+
return path.join(venvDir(flags), sub, name + ext);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function ensureDir(p) {
|
|
28
|
+
fs.mkdirSync(p, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function exists(p) {
|
|
32
|
+
try { fs.accessSync(p); return true; } catch (_) { return false; }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readJSONSafe(p) {
|
|
36
|
+
try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch (_) { return null; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function writeJSON(p, obj) {
|
|
40
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
41
|
+
fs.writeFileSync(p, JSON.stringify(obj, null, 2));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { home, memoryDir, venvDir, venvBin, ensureDir, exists, readJSONSafe, writeJSON };
|
package/lib/sh.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawn, spawnSync } = require('child_process');
|
|
4
|
+
const { c, dim } = require('./style');
|
|
5
|
+
|
|
6
|
+
function isVerbose() {
|
|
7
|
+
return process.argv.includes('--verbose') || process.env.TAM_DEBUG === '1';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function which(bin) {
|
|
11
|
+
const cmd = process.platform === 'win32' ? 'where' : 'which';
|
|
12
|
+
const r = spawnSync(cmd, [bin], { encoding: 'utf8' });
|
|
13
|
+
if (r.status === 0) {
|
|
14
|
+
const path = (r.stdout || '').split(/\r?\n/).filter(Boolean)[0];
|
|
15
|
+
return path || null;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function probe(bin, args = ['--version']) {
|
|
21
|
+
const path = which(bin);
|
|
22
|
+
if (!path) return { ok: false };
|
|
23
|
+
const r = spawnSync(path, args, { encoding: 'utf8' });
|
|
24
|
+
return {
|
|
25
|
+
ok: r.status === 0,
|
|
26
|
+
path,
|
|
27
|
+
out: (r.stdout || '').trim() || (r.stderr || '').trim(),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function run(cmd, args, opts = {}) {
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
if (isVerbose()) {
|
|
34
|
+
process.stderr.write(
|
|
35
|
+
`${dim('$')} ${[cmd, ...args].map((a) => (/\s/.test(a) ? JSON.stringify(a) : a)).join(' ')}\n`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const child = spawn(cmd, args, {
|
|
39
|
+
stdio: opts.silent ? ['ignore', 'pipe', 'pipe'] : 'inherit',
|
|
40
|
+
env: { ...process.env, ...(opts.env || {}) },
|
|
41
|
+
cwd: opts.cwd || process.cwd(),
|
|
42
|
+
});
|
|
43
|
+
let stdout = '';
|
|
44
|
+
let stderr = '';
|
|
45
|
+
if (opts.silent) {
|
|
46
|
+
child.stdout.on('data', (d) => (stdout += d.toString()));
|
|
47
|
+
child.stderr.on('data', (d) => (stderr += d.toString()));
|
|
48
|
+
}
|
|
49
|
+
child.on('close', (code) => resolve({ code, stdout, stderr }));
|
|
50
|
+
child.on('error', (err) => resolve({ code: 1, stdout, stderr: String(err) }));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function runOrThrow(cmd, args, opts = {}) {
|
|
55
|
+
const r = await run(cmd, args, opts);
|
|
56
|
+
if (r.code !== 0) {
|
|
57
|
+
const err = new Error(`${cmd} ${args.join(' ')} exited with code ${r.code}`);
|
|
58
|
+
err.stdout = r.stdout;
|
|
59
|
+
err.stderr = r.stderr;
|
|
60
|
+
err.code = r.code;
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
return r;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { which, probe, run, runOrThrow, isVerbose };
|
package/lib/style.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const noColor =
|
|
4
|
+
process.argv.includes('--no-color') ||
|
|
5
|
+
process.env.NO_COLOR === '1' ||
|
|
6
|
+
process.env.TERM === 'dumb' ||
|
|
7
|
+
!process.stdout.isTTY;
|
|
8
|
+
|
|
9
|
+
function wrap(open, close) {
|
|
10
|
+
return (s) => (noColor ? String(s) : `\x1b[${open}m${s}\x1b[${close}m`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const c = {
|
|
14
|
+
bold: wrap(1, 22),
|
|
15
|
+
dim: wrap(2, 22),
|
|
16
|
+
red: wrap(31, 39),
|
|
17
|
+
green: wrap(32, 39),
|
|
18
|
+
yellow: wrap(33, 39),
|
|
19
|
+
blue: wrap(34, 39),
|
|
20
|
+
magenta: wrap(35, 39),
|
|
21
|
+
cyan: wrap(36, 39),
|
|
22
|
+
gray: wrap(90, 39),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function bold(s) { return c.bold(s); }
|
|
26
|
+
function dim(s) { return c.dim(s); }
|
|
27
|
+
|
|
28
|
+
function banner() {
|
|
29
|
+
const v = require('../package.json').version;
|
|
30
|
+
const line = c.cyan('total-agent-memory');
|
|
31
|
+
return `\n${c.bold(line)} ${c.dim(`v${v}`)} ${c.dim('— persistent memory for AI coding agents')}\n`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function step(n, total, msg) {
|
|
35
|
+
return `${c.dim(`[${n}/${total}]`)} ${msg}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function ok(msg) { return `${c.green('✓')} ${msg}`; }
|
|
39
|
+
function fail(msg) { return `${c.red('✗')} ${msg}`; }
|
|
40
|
+
function warn(msg) { return `${c.yellow('!')} ${msg}`; }
|
|
41
|
+
function info(msg) { return `${c.cyan('›')} ${msg}`; }
|
|
42
|
+
|
|
43
|
+
module.exports = { c, bold, dim, banner, step, ok, fail, warn, info };
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "total-agent-memory",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Persistent memory for Claude Code, Codex CLI, Cursor and any MCP client. Local-first, hits #5 on LoCoMo. One command to install.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"memory",
|
|
8
|
+
"claude",
|
|
9
|
+
"claude-code",
|
|
10
|
+
"codex",
|
|
11
|
+
"cursor",
|
|
12
|
+
"cline",
|
|
13
|
+
"agent",
|
|
14
|
+
"ai",
|
|
15
|
+
"llm",
|
|
16
|
+
"knowledge-graph",
|
|
17
|
+
"rag",
|
|
18
|
+
"persistent-memory",
|
|
19
|
+
"local-first"
|
|
20
|
+
],
|
|
21
|
+
"homepage": "https://totalmemory.dev",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://git.infra.vbcherepanov.com/vbcherepanov/total-memory-npm/issues"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://git.infra.vbcherepanov.com/vbcherepanov/total-memory-npm.git"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"author": "Vitalii Cherepanov <vbcherepanov@gmail.com> (https://vbcherepanov.com)",
|
|
31
|
+
"type": "commonjs",
|
|
32
|
+
"bin": {
|
|
33
|
+
"total-agent-memory": "bin/cli.js",
|
|
34
|
+
"tam": "bin/cli.js"
|
|
35
|
+
},
|
|
36
|
+
"main": "lib/index.js",
|
|
37
|
+
"files": [
|
|
38
|
+
"bin/",
|
|
39
|
+
"lib/",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE"
|
|
42
|
+
],
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"test": "node tests/smoke.test.js",
|
|
48
|
+
"lint": "node --check bin/cli.js && node --check lib/*.js"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {},
|
|
51
|
+
"devDependencies": {},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
}
|
|
55
|
+
}
|