@tonycasey/lisa 0.5.13
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 +42 -0
- package/dist/cli.js +390 -0
- package/dist/lib/interfaces/IDockerClient.js +2 -0
- package/dist/lib/interfaces/IMcpClient.js +2 -0
- package/dist/lib/interfaces/IServices.js +2 -0
- package/dist/lib/interfaces/ITemplateCopier.js +2 -0
- package/dist/lib/mcp.js +35 -0
- package/dist/lib/services.js +57 -0
- package/dist/package.json +36 -0
- package/dist/templates/agents/.sample.env +12 -0
- package/dist/templates/agents/docs/STORAGE_SETUP.md +161 -0
- package/dist/templates/agents/skills/common/group-id.js +193 -0
- package/dist/templates/agents/skills/init-review/SKILL.md +119 -0
- package/dist/templates/agents/skills/init-review/scripts/ai-enrich.js +258 -0
- package/dist/templates/agents/skills/init-review/scripts/init-review.js +769 -0
- package/dist/templates/agents/skills/lisa/SKILL.md +92 -0
- package/dist/templates/agents/skills/lisa/cache/.gitkeep +0 -0
- package/dist/templates/agents/skills/lisa/scripts/storage.js +374 -0
- package/dist/templates/agents/skills/memory/SKILL.md +31 -0
- package/dist/templates/agents/skills/memory/scripts/memory.js +533 -0
- package/dist/templates/agents/skills/prompt/SKILL.md +19 -0
- package/dist/templates/agents/skills/prompt/scripts/prompt.js +184 -0
- package/dist/templates/agents/skills/tasks/SKILL.md +31 -0
- package/dist/templates/agents/skills/tasks/scripts/tasks.js +489 -0
- package/dist/templates/claude/config.js +40 -0
- package/dist/templates/claude/hooks/README.md +158 -0
- package/dist/templates/claude/hooks/common/complexity-rater.js +290 -0
- package/dist/templates/claude/hooks/common/context.js +263 -0
- package/dist/templates/claude/hooks/common/group-id.js +188 -0
- package/dist/templates/claude/hooks/common/mcp-client.js +131 -0
- package/dist/templates/claude/hooks/common/transcript-parser.js +256 -0
- package/dist/templates/claude/hooks/common/zep-client.js +175 -0
- package/dist/templates/claude/hooks/session-start.js +401 -0
- package/dist/templates/claude/hooks/session-stop-worker.js +341 -0
- package/dist/templates/claude/hooks/session-stop.js +122 -0
- package/dist/templates/claude/hooks/user-prompt-submit.js +256 -0
- package/dist/templates/claude/settings.json +46 -0
- package/dist/templates/docker/.env.lisa.example +17 -0
- package/dist/templates/docker/docker-compose.graphiti.yml +45 -0
- package/dist/templates/rules/shared/clean-architecture.md +333 -0
- package/dist/templates/rules/shared/code-quality-rules.md +469 -0
- package/dist/templates/rules/shared/git-rules.md +64 -0
- package/dist/templates/rules/shared/testing-principles.md +469 -0
- package/dist/templates/rules/typescript/coding-standards.md +751 -0
- package/dist/templates/rules/typescript/testing.md +629 -0
- package/dist/templates/rules/typescript/typescript-config-guide.md +465 -0
- package/package.json +64 -0
- package/scripts/postinstall.js +710 -0
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Lisa – Long Term Memory for Claude
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
### **#ClaudeNeedsLisa**
|
|
7
|
+
|
|
8
|
+
> *Named after Lisa Simpson - the overachiever who never forgets a fact, a slight, or a saxophone lesson.*
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## To Install
|
|
13
|
+
|
|
14
|
+
### From your, console
|
|
15
|
+
```bash
|
|
16
|
+
install -g @tonycasey/lisa
|
|
17
|
+
```
|
|
18
|
+
### then in any folder you want to work with, open your console and type
|
|
19
|
+
```bash
|
|
20
|
+
lisa setup
|
|
21
|
+
```
|
|
22
|
+
## Using Lisa
|
|
23
|
+
|
|
24
|
+
Once installed, Lisa works automatically. Your AI assistant will:
|
|
25
|
+
|
|
26
|
+
1. **Load context at session start** - Previous memories and project context
|
|
27
|
+
2. **Capture important info during coding** - Decisions, patterns, etc.
|
|
28
|
+
3. **Remember explicitly when asked** - Say "remember that..." to save important notes
|
|
29
|
+
|
|
30
|
+
### Explicit Memory Commands
|
|
31
|
+
|
|
32
|
+
During a Claude Code session:
|
|
33
|
+
|
|
34
|
+
- "remember that we decided to use Redux for state management"
|
|
35
|
+
- "hey lisa, what do you know about the authentication system?"
|
|
36
|
+
- "lisa, show me recent memories"
|
|
37
|
+
- "lisa, what tasks are we working on?"
|
|
38
|
+
|
|
39
|
+
See the [Getting Started Guide](./docs/getting-started.md)
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
[Contributing](./CONTRIBUTING.md) | [Changelog](./CHANGELOG.md)
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.TEMPLATE_ROOT = exports.DEFAULT_GROUP = exports.DEFAULT_ENDPOINT = exports.createDefaultServices = void 0;
|
|
8
|
+
exports.initCommand = initCommand;
|
|
9
|
+
exports.doctorCommand = doctorCommand;
|
|
10
|
+
exports.upCommand = upCommand;
|
|
11
|
+
exports.downCommand = downCommand;
|
|
12
|
+
const commander_1 = require("commander");
|
|
13
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
16
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
17
|
+
const services_1 = require("./lib/services");
|
|
18
|
+
Object.defineProperty(exports, "createDefaultServices", { enumerable: true, get: function () { return services_1.createDefaultServices; } });
|
|
19
|
+
// Templates are copied into dist/templates by postbuild; resolve relative to compiled file.
|
|
20
|
+
const TEMPLATE_ROOT = path_1.default.join(__dirname, 'templates');
|
|
21
|
+
exports.TEMPLATE_ROOT = TEMPLATE_ROOT;
|
|
22
|
+
const DEFAULT_ENDPOINT = 'http://localhost:8010/mcp/';
|
|
23
|
+
exports.DEFAULT_ENDPOINT = DEFAULT_ENDPOINT;
|
|
24
|
+
const ZEP_CLOUD_ENDPOINT = 'https://api.getzep.com/mcp/';
|
|
25
|
+
/**
|
|
26
|
+
* Get project name from package.json or directory name.
|
|
27
|
+
* Used as the default group ID for memory storage.
|
|
28
|
+
*/
|
|
29
|
+
function getProjectName() {
|
|
30
|
+
try {
|
|
31
|
+
const pkgPath = path_1.default.join(process.cwd(), 'package.json');
|
|
32
|
+
if (fs_extra_1.default.existsSync(pkgPath)) {
|
|
33
|
+
const pkg = fs_extra_1.default.readJsonSync(pkgPath);
|
|
34
|
+
if (pkg.name) {
|
|
35
|
+
// Remove scope prefix if present (e.g., @tonycasey/lisa -> lisa)
|
|
36
|
+
return pkg.name.replace(/^@[^/]+\//, '');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Ignore errors reading package.json
|
|
42
|
+
}
|
|
43
|
+
// Fall back to directory name
|
|
44
|
+
return path_1.default.basename(process.cwd());
|
|
45
|
+
}
|
|
46
|
+
const DEFAULT_GROUP = getProjectName();
|
|
47
|
+
exports.DEFAULT_GROUP = DEFAULT_GROUP;
|
|
48
|
+
// Interactive prompt functions
|
|
49
|
+
async function promptDeploymentMode() {
|
|
50
|
+
return await (0, prompts_1.select)({
|
|
51
|
+
message: 'How would you like to configure storage?',
|
|
52
|
+
choices: [
|
|
53
|
+
{
|
|
54
|
+
name: 'Local Docker (runs Neo4j + MCP server locally)',
|
|
55
|
+
value: 'local',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'Zep Cloud (managed storage service)',
|
|
59
|
+
value: 'zep-cloud',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'Set up later (scaffold project, configure storage later)',
|
|
63
|
+
value: 'skip',
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async function promptZepCloudConfig() {
|
|
69
|
+
const zepApiKey = await (0, prompts_1.password)({
|
|
70
|
+
message: 'Zep API Key:',
|
|
71
|
+
validate: (val) => val.length > 0 || 'API key is required',
|
|
72
|
+
});
|
|
73
|
+
const zepProjectId = await (0, prompts_1.input)({
|
|
74
|
+
message: 'Zep Project ID:',
|
|
75
|
+
validate: (val) => val.length > 0 || 'Project ID is required',
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
zepApiKey,
|
|
79
|
+
zepProjectId,
|
|
80
|
+
endpoint: ZEP_CLOUD_ENDPOINT,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function promptGroupId() {
|
|
84
|
+
const projectName = path_1.default.basename(process.cwd());
|
|
85
|
+
return await (0, prompts_1.input)({
|
|
86
|
+
message: 'Group ID:',
|
|
87
|
+
default: projectName,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async function initCommand(opts, services) {
|
|
91
|
+
const force = Boolean(opts.force);
|
|
92
|
+
const cwd = opts.cwd;
|
|
93
|
+
let config;
|
|
94
|
+
// Determine if we need interactive prompts
|
|
95
|
+
const hasExplicitMode = opts.mode !== undefined;
|
|
96
|
+
const skipPrompts = opts.yes || hasExplicitMode;
|
|
97
|
+
if (skipPrompts) {
|
|
98
|
+
// Non-interactive mode - use provided options or defaults
|
|
99
|
+
const mode = opts.mode || 'local';
|
|
100
|
+
config = {
|
|
101
|
+
mode,
|
|
102
|
+
endpoint: opts.endpoint || (mode === 'zep-cloud' ? ZEP_CLOUD_ENDPOINT : DEFAULT_ENDPOINT),
|
|
103
|
+
groupId: opts.group || process.env.GRAPHITI_GROUP_ID || DEFAULT_GROUP,
|
|
104
|
+
zepApiKey: opts.zepApiKey,
|
|
105
|
+
zepProjectId: opts.zepProjectId,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// Interactive mode - prompt user
|
|
110
|
+
const mode = await promptDeploymentMode();
|
|
111
|
+
let modeConfig = {};
|
|
112
|
+
if (mode === 'zep-cloud') {
|
|
113
|
+
modeConfig = await promptZepCloudConfig();
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
modeConfig = { endpoint: DEFAULT_ENDPOINT };
|
|
117
|
+
}
|
|
118
|
+
const groupId = await promptGroupId();
|
|
119
|
+
config = {
|
|
120
|
+
mode,
|
|
121
|
+
endpoint: modeConfig.endpoint || DEFAULT_ENDPOINT,
|
|
122
|
+
groupId,
|
|
123
|
+
...modeConfig,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const includeDocker = opts.includeDocker !== false && config.mode !== 'zep-cloud' && config.mode !== 'skip';
|
|
127
|
+
const replacements = {
|
|
128
|
+
GRAPHITI_ENDPOINT: config.endpoint,
|
|
129
|
+
GRAPHITI_GROUP: config.groupId,
|
|
130
|
+
PROJECT_NAME: config.groupId,
|
|
131
|
+
};
|
|
132
|
+
const agentsDir = path_1.default.join(cwd, '.agents');
|
|
133
|
+
const skillsDir = path_1.default.join(agentsDir, 'skills');
|
|
134
|
+
const rulesDir = path_1.default.join(agentsDir, 'rules');
|
|
135
|
+
const claudeDir = path_1.default.join(cwd, '.claude');
|
|
136
|
+
const composeDest = path_1.default.join(cwd, 'docker-compose.graphiti.yml');
|
|
137
|
+
const envDest = path_1.default.join(cwd, '.env.lisa.example');
|
|
138
|
+
const agentsEnvDest = path_1.default.join(agentsDir, '.env');
|
|
139
|
+
const copies = [];
|
|
140
|
+
// Skill scaffolding (model-neutral)
|
|
141
|
+
copies.push(services.templateCopier.copy('agents/skills/memory/SKILL.md', path_1.default.join(skillsDir, 'memory', 'SKILL.md'), replacements, force));
|
|
142
|
+
copies.push(services.templateCopier.copy('agents/skills/tasks/SKILL.md', path_1.default.join(skillsDir, 'tasks', 'SKILL.md'), replacements, force));
|
|
143
|
+
// Rules scaffolding (shared)
|
|
144
|
+
copies.push(services.templateCopier.copy('rules/shared/clean-architecture.md', path_1.default.join(rulesDir, 'shared', 'clean-architecture.md'), replacements, force));
|
|
145
|
+
copies.push(services.templateCopier.copy('rules/shared/code-quality-rules.md', path_1.default.join(rulesDir, 'shared', 'code-quality-rules.md'), replacements, force));
|
|
146
|
+
copies.push(services.templateCopier.copy('rules/shared/testing-principles.md', path_1.default.join(rulesDir, 'shared', 'testing-principles.md'), replacements, force));
|
|
147
|
+
// Rules scaffolding (typescript)
|
|
148
|
+
copies.push(services.templateCopier.copy('rules/typescript/coding-standards.md', path_1.default.join(rulesDir, 'typescript', 'coding-standards.md'), replacements, force));
|
|
149
|
+
copies.push(services.templateCopier.copy('rules/typescript/testing.md', path_1.default.join(rulesDir, 'typescript', 'testing.md'), replacements, force));
|
|
150
|
+
copies.push(services.templateCopier.copy('rules/typescript/typescript-config-guide.md', path_1.default.join(rulesDir, 'typescript', 'typescript-config-guide.md'), replacements, force));
|
|
151
|
+
// Claude Code scaffolding (hooks and settings)
|
|
152
|
+
copies.push(services.templateCopier.copy('claude/settings.json', path_1.default.join(claudeDir, 'settings.json'), replacements, force));
|
|
153
|
+
copies.push(services.templateCopier.copy('claude/config.js', path_1.default.join(claudeDir, 'config.js'), replacements, force));
|
|
154
|
+
copies.push(services.templateCopier.copy('claude/hooks/user-prompt-submit.js', path_1.default.join(claudeDir, 'hooks', 'user-prompt-submit.js'), replacements, force));
|
|
155
|
+
// Storage setup documentation
|
|
156
|
+
const docsDir = path_1.default.join(agentsDir, 'docs');
|
|
157
|
+
copies.push(services.templateCopier.copy('agents/docs/STORAGE_SETUP.md', path_1.default.join(docsDir, 'STORAGE_SETUP.md'), replacements, force));
|
|
158
|
+
// Copy .env template with replacements
|
|
159
|
+
copies.push(services.templateCopier.copy('agents/.sample.env', agentsEnvDest, replacements, force));
|
|
160
|
+
if (includeDocker) {
|
|
161
|
+
// Choose compose file based on mode
|
|
162
|
+
const composeTemplate = 'docker/docker-compose.graphiti.yml';
|
|
163
|
+
copies.push(services.templateCopier.copy(composeTemplate, composeDest, replacements, force));
|
|
164
|
+
copies.push(services.templateCopier.copy('docker/.env.lisa.example', envDest, replacements, force));
|
|
165
|
+
}
|
|
166
|
+
await Promise.all(copies);
|
|
167
|
+
console.log(chalk_1.default.green(`Scaffolded .agents, .claude${includeDocker ? ', and Docker assets' : ''} into ${cwd}`));
|
|
168
|
+
console.log(`Mode: ${config.mode}`);
|
|
169
|
+
console.log(`Endpoint: ${config.endpoint}`);
|
|
170
|
+
console.log(`Group ID: ${config.groupId}`);
|
|
171
|
+
// Show skip mode instructions
|
|
172
|
+
if (config.mode === 'skip') {
|
|
173
|
+
console.log('');
|
|
174
|
+
console.log(chalk_1.default.cyan('To configure storage later:'));
|
|
175
|
+
console.log(chalk_1.default.cyan(' 1. Read .agents/docs/STORAGE_SETUP.md'));
|
|
176
|
+
console.log(chalk_1.default.cyan(' 2. Edit .agents/.env with your configuration'));
|
|
177
|
+
console.log(chalk_1.default.cyan(' 3. Start a new terminal session'));
|
|
178
|
+
console.log(chalk_1.default.cyan(' 4. Run `lisa doctor` to verify connection'));
|
|
179
|
+
}
|
|
180
|
+
// Isolated mode: create .claude/lib structure for non-npm projects
|
|
181
|
+
if (opts.isolated) {
|
|
182
|
+
const libDir = path_1.default.join(claudeDir, 'lib');
|
|
183
|
+
await fs_extra_1.default.ensureDir(libDir);
|
|
184
|
+
// Create minimal package.json in .claude/lib
|
|
185
|
+
const libPackageJson = {
|
|
186
|
+
name: 'claude-lib',
|
|
187
|
+
version: '1.0.0',
|
|
188
|
+
private: true,
|
|
189
|
+
description: 'Lisa support files for Claude Code',
|
|
190
|
+
};
|
|
191
|
+
const libPackagePath = path_1.default.join(libDir, 'package.json');
|
|
192
|
+
if (!await fs_extra_1.default.pathExists(libPackagePath) || force) {
|
|
193
|
+
await fs_extra_1.default.writeJson(libPackagePath, libPackageJson, { spaces: 2 });
|
|
194
|
+
console.log(chalk_1.default.green('Created .claude/lib/package.json'));
|
|
195
|
+
}
|
|
196
|
+
// Add .claude/lib to .gitignore if not already there
|
|
197
|
+
const gitignorePath = path_1.default.join(cwd, '.gitignore');
|
|
198
|
+
if (await fs_extra_1.default.pathExists(gitignorePath)) {
|
|
199
|
+
let gitignore = await fs_extra_1.default.readFile(gitignorePath, 'utf8');
|
|
200
|
+
if (!gitignore.includes('.claude/lib/node_modules')) {
|
|
201
|
+
gitignore += '\n# Lisa support files\n.claude/lib/node_modules/\n';
|
|
202
|
+
await fs_extra_1.default.writeFile(gitignorePath, gitignore);
|
|
203
|
+
console.log(chalk_1.default.green('Added .claude/lib/node_modules to .gitignore'));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
console.log('');
|
|
207
|
+
console.log(chalk_1.default.cyan('Isolated mode: Lisa installed to .claude/lib/'));
|
|
208
|
+
console.log(chalk_1.default.cyan('Your project root stays clean (no package.json or node_modules).'));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async function loadConfig(cwd) {
|
|
212
|
+
const agentsEnv = path_1.default.join(cwd, '.agents', '.env');
|
|
213
|
+
if (!(await fs_extra_1.default.pathExists(agentsEnv)))
|
|
214
|
+
return null;
|
|
215
|
+
const raw = await fs_extra_1.default.readFile(agentsEnv, 'utf8');
|
|
216
|
+
const map = {};
|
|
217
|
+
raw.split(/\r?\n/).forEach((line) => {
|
|
218
|
+
if (!line || line.startsWith('#'))
|
|
219
|
+
return;
|
|
220
|
+
const idx = line.indexOf('=');
|
|
221
|
+
if (idx === -1)
|
|
222
|
+
return;
|
|
223
|
+
const key = line.slice(0, idx).trim();
|
|
224
|
+
const val = line.slice(idx + 1).trim();
|
|
225
|
+
map[key] = val;
|
|
226
|
+
});
|
|
227
|
+
return {
|
|
228
|
+
endpoint: map.GRAPHITI_ENDPOINT || DEFAULT_ENDPOINT,
|
|
229
|
+
group: map.GRAPHITI_GROUP_ID || DEFAULT_GROUP,
|
|
230
|
+
mode: map.STORAGE_MODE || 'local',
|
|
231
|
+
zepApiKey: map.ZEP_API_KEY,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
async function doctorCommand(opts, services) {
|
|
235
|
+
const cwd = opts.cwd;
|
|
236
|
+
const composeFile = opts.compose || path_1.default.join(cwd, 'docker-compose.graphiti.yml');
|
|
237
|
+
const config = (await loadConfig(cwd)) ?? { endpoint: undefined, group: undefined, mode: 'local' };
|
|
238
|
+
const endpoint = opts.endpoint || config.endpoint || DEFAULT_ENDPOINT;
|
|
239
|
+
const mode = config.mode || 'local';
|
|
240
|
+
const results = [];
|
|
241
|
+
// Show current mode
|
|
242
|
+
results.push(chalk_1.default.cyan(`Mode: ${mode}`));
|
|
243
|
+
results.push(chalk_1.default.cyan(`Group: ${config.group || DEFAULT_GROUP}`));
|
|
244
|
+
results.push('');
|
|
245
|
+
// Mode-specific checks
|
|
246
|
+
if (mode === 'zep-cloud') {
|
|
247
|
+
// Zep Cloud mode - no local Docker needed
|
|
248
|
+
results.push(chalk_1.default.yellow('Zep Cloud mode - no local Docker required'));
|
|
249
|
+
results.push('');
|
|
250
|
+
// Get API key from config or environment for Zep Cloud authentication
|
|
251
|
+
const zepApiKey = config.zepApiKey || process.env.ZEP_API_KEY;
|
|
252
|
+
if (!zepApiKey) {
|
|
253
|
+
results.push(chalk_1.default.yellow('Warning: ZEP_API_KEY not configured (required for Zep Cloud)'));
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
await services.mcp.ping(endpoint, { apiKey: zepApiKey });
|
|
257
|
+
results.push(chalk_1.default.green(`Zep MCP reachable at ${endpoint}`));
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
results.push(chalk_1.default.red(`Zep MCP check failed at ${endpoint}: ${err.message}`));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
// Local mode - Docker is needed
|
|
265
|
+
try {
|
|
266
|
+
const stdout = await services.docker.version();
|
|
267
|
+
results.push(chalk_1.default.green(`Docker OK: ${stdout}`));
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
results.push(chalk_1.default.red(`Docker missing or not running: ${err.message}`));
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
const stdout = await services.docker.composeVersion();
|
|
274
|
+
results.push(chalk_1.default.green(`Docker Compose OK: ${stdout}`));
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
results.push(chalk_1.default.red(`Docker Compose missing: ${err.message}`));
|
|
278
|
+
}
|
|
279
|
+
if (await fs_extra_1.default.pathExists(composeFile)) {
|
|
280
|
+
results.push(chalk_1.default.green(`Compose file found: ${composeFile}`));
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
results.push(chalk_1.default.red(`Compose file not found: ${composeFile}`));
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
await services.mcp.ping(endpoint);
|
|
287
|
+
results.push(chalk_1.default.green(`MCP reachable at ${endpoint}`));
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
results.push(chalk_1.default.red(`MCP check failed at ${endpoint}: ${err.message}`));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
console.log(results.join('\n'));
|
|
294
|
+
}
|
|
295
|
+
async function upCommand(opts, services) {
|
|
296
|
+
await services.docker.compose(opts.composeFile, ['up', '-d']);
|
|
297
|
+
}
|
|
298
|
+
async function downCommand(opts, services) {
|
|
299
|
+
await services.docker.compose(opts.composeFile, ['down']);
|
|
300
|
+
}
|
|
301
|
+
const program = new commander_1.Command();
|
|
302
|
+
program
|
|
303
|
+
.name('lisa')
|
|
304
|
+
.description('Lisa remembers everything. Memory for Claude Code and AI assistants.')
|
|
305
|
+
.version('0.5.0');
|
|
306
|
+
program
|
|
307
|
+
.command('init')
|
|
308
|
+
.description('Scaffold .agents, .claude, and Docker assets')
|
|
309
|
+
.option('-e, --endpoint <url>', 'MCP endpoint')
|
|
310
|
+
.option('-g, --group <id>', 'Default group id')
|
|
311
|
+
.option('-f, --force', 'Overwrite existing files')
|
|
312
|
+
.option('-m, --mode <mode>', 'Deployment mode: local or zep-cloud')
|
|
313
|
+
.option('--zep-api-key <key>', 'Zep API key (for zep-cloud mode)')
|
|
314
|
+
.option('--zep-project-id <id>', 'Zep project ID (for zep-cloud mode)')
|
|
315
|
+
.option('-y, --yes', 'Skip prompts, use defaults')
|
|
316
|
+
.option('--isolated', 'Install to .claude/lib for non-npm projects (Python, Go, etc.)')
|
|
317
|
+
.action(async (cmd) => {
|
|
318
|
+
const services = (0, services_1.createDefaultServices)(TEMPLATE_ROOT);
|
|
319
|
+
await initCommand({
|
|
320
|
+
endpoint: cmd.endpoint,
|
|
321
|
+
group: cmd.group,
|
|
322
|
+
force: cmd.force,
|
|
323
|
+
cwd: process.cwd(),
|
|
324
|
+
includeDocker: true,
|
|
325
|
+
mode: cmd.mode,
|
|
326
|
+
zepApiKey: cmd.zepApiKey,
|
|
327
|
+
zepProjectId: cmd.zepProjectId,
|
|
328
|
+
yes: cmd.yes,
|
|
329
|
+
isolated: cmd.isolated,
|
|
330
|
+
}, services);
|
|
331
|
+
});
|
|
332
|
+
program
|
|
333
|
+
.command('setup')
|
|
334
|
+
.description('Scaffold .agents and .claude only (no Docker assets)')
|
|
335
|
+
.option('-e, --endpoint <url>', 'MCP endpoint')
|
|
336
|
+
.option('-g, --group <id>', 'Default group id')
|
|
337
|
+
.option('-f, --force', 'Overwrite existing files')
|
|
338
|
+
.option('-m, --mode <mode>', 'Deployment mode: local or zep-cloud')
|
|
339
|
+
.option('--zep-api-key <key>', 'Zep API key (for zep-cloud mode)')
|
|
340
|
+
.option('--zep-project-id <id>', 'Zep project ID (for zep-cloud mode)')
|
|
341
|
+
.option('-y, --yes', 'Skip prompts, use defaults')
|
|
342
|
+
.option('--isolated', 'Install to .claude/lib for non-npm projects (Python, Go, etc.)')
|
|
343
|
+
.action(async (cmd) => {
|
|
344
|
+
const services = (0, services_1.createDefaultServices)(TEMPLATE_ROOT);
|
|
345
|
+
await initCommand({
|
|
346
|
+
endpoint: cmd.endpoint,
|
|
347
|
+
group: cmd.group,
|
|
348
|
+
force: cmd.force,
|
|
349
|
+
cwd: process.cwd(),
|
|
350
|
+
includeDocker: false,
|
|
351
|
+
mode: cmd.mode,
|
|
352
|
+
zepApiKey: cmd.zepApiKey,
|
|
353
|
+
zepProjectId: cmd.zepProjectId,
|
|
354
|
+
yes: cmd.yes,
|
|
355
|
+
isolated: cmd.isolated,
|
|
356
|
+
}, services);
|
|
357
|
+
});
|
|
358
|
+
program
|
|
359
|
+
.command('up')
|
|
360
|
+
.description('Start Neo4j/Graph/graphiti-mcp via docker compose')
|
|
361
|
+
.option('-c, --compose <file>', 'Compose file', 'docker-compose.graphiti.yml')
|
|
362
|
+
.action(async (cmd) => {
|
|
363
|
+
const composeFile = path_1.default.resolve(process.cwd(), cmd.compose);
|
|
364
|
+
const services = (0, services_1.createDefaultServices)(TEMPLATE_ROOT);
|
|
365
|
+
await upCommand({ composeFile }, services);
|
|
366
|
+
});
|
|
367
|
+
program
|
|
368
|
+
.command('down')
|
|
369
|
+
.description('Stop Neo4j/Graph/graphiti-mcp via docker compose')
|
|
370
|
+
.option('-c, --compose <file>', 'Compose file', 'docker-compose.graphiti.yml')
|
|
371
|
+
.action(async (cmd) => {
|
|
372
|
+
const composeFile = path_1.default.resolve(process.cwd(), cmd.compose);
|
|
373
|
+
const services = (0, services_1.createDefaultServices)(TEMPLATE_ROOT);
|
|
374
|
+
await downCommand({ composeFile }, services);
|
|
375
|
+
});
|
|
376
|
+
program
|
|
377
|
+
.command('doctor')
|
|
378
|
+
.description('Validate Docker and MCP connectivity')
|
|
379
|
+
.option('-c, --compose <file>', 'Compose file', 'docker-compose.graphiti.yml')
|
|
380
|
+
.option('-e, --endpoint <url>', 'MCP endpoint override')
|
|
381
|
+
.action(async (cmd) => {
|
|
382
|
+
const services = (0, services_1.createDefaultServices)(TEMPLATE_ROOT);
|
|
383
|
+
await doctorCommand({ cwd: process.cwd(), compose: cmd.compose, endpoint: cmd.endpoint }, services);
|
|
384
|
+
});
|
|
385
|
+
if (require.main === module) {
|
|
386
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
387
|
+
console.error(chalk_1.default.red(err.message));
|
|
388
|
+
process.exit(1);
|
|
389
|
+
});
|
|
390
|
+
}
|
package/dist/lib/mcp.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pingMcp = pingMcp;
|
|
4
|
+
const DEFAULT_BODY = {
|
|
5
|
+
jsonrpc: '2.0',
|
|
6
|
+
id: 'init',
|
|
7
|
+
method: 'initialize',
|
|
8
|
+
params: {
|
|
9
|
+
protocolVersion: '2024-11-05',
|
|
10
|
+
capabilities: {},
|
|
11
|
+
clientInfo: { name: 'lisa', version: '0.5.0' },
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
async function pingMcp(endpoint, options) {
|
|
15
|
+
const headers = {
|
|
16
|
+
'Content-Type': 'application/json',
|
|
17
|
+
Accept: 'application/json, text/event-stream',
|
|
18
|
+
};
|
|
19
|
+
// Add Authorization header for Zep Cloud mode
|
|
20
|
+
if (options?.apiKey) {
|
|
21
|
+
headers['Authorization'] = `Api-Key ${options.apiKey}`;
|
|
22
|
+
}
|
|
23
|
+
const resp = await fetch(endpoint, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers,
|
|
26
|
+
body: JSON.stringify(DEFAULT_BODY),
|
|
27
|
+
});
|
|
28
|
+
if (!resp.ok) {
|
|
29
|
+
throw new Error(`HTTP ${resp.status}`);
|
|
30
|
+
}
|
|
31
|
+
const session = resp.headers.get('mcp-session-id');
|
|
32
|
+
if (!session) {
|
|
33
|
+
throw new Error('No mcp-session-id header returned');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createDefaultServices = createDefaultServices;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const execa_1 = require("execa");
|
|
10
|
+
const mcp_1 = require("./mcp");
|
|
11
|
+
class DefaultTemplateCopier {
|
|
12
|
+
constructor(templateRoot) {
|
|
13
|
+
this.templateRoot = templateRoot;
|
|
14
|
+
}
|
|
15
|
+
async copy(templateRel, destAbs, replacements, force = false) {
|
|
16
|
+
const src = path_1.default.join(this.templateRoot, templateRel);
|
|
17
|
+
if (!(await fs_extra_1.default.pathExists(src))) {
|
|
18
|
+
throw new Error(`Missing template ${templateRel}`);
|
|
19
|
+
}
|
|
20
|
+
if (await fs_extra_1.default.pathExists(destAbs) && !force) {
|
|
21
|
+
return { skipped: true };
|
|
22
|
+
}
|
|
23
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(destAbs));
|
|
24
|
+
let content = await fs_extra_1.default.readFile(src, 'utf8');
|
|
25
|
+
Object.entries(replacements).forEach(([key, value]) => {
|
|
26
|
+
content = content.replace(new RegExp(`{{${key}}}`, 'g'), value);
|
|
27
|
+
});
|
|
28
|
+
await fs_extra_1.default.writeFile(destAbs, content, 'utf8');
|
|
29
|
+
return { skipped: false };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
class DefaultDockerClient {
|
|
33
|
+
async version() {
|
|
34
|
+
const { stdout } = await (0, execa_1.execa)('docker', ['--version']);
|
|
35
|
+
return stdout;
|
|
36
|
+
}
|
|
37
|
+
async composeVersion() {
|
|
38
|
+
const { stdout } = await (0, execa_1.execa)('docker', ['compose', 'version']);
|
|
39
|
+
return stdout;
|
|
40
|
+
}
|
|
41
|
+
async compose(composeFile, args, stdio = 'inherit') {
|
|
42
|
+
const composeArgs = ['compose', '-f', composeFile, ...args];
|
|
43
|
+
await (0, execa_1.execa)('docker', composeArgs, { stdio });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
class DefaultMcpClient {
|
|
47
|
+
async ping(endpoint, options) {
|
|
48
|
+
await (0, mcp_1.pingMcp)(endpoint, options);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function createDefaultServices(templateRoot) {
|
|
52
|
+
return {
|
|
53
|
+
templateCopier: new DefaultTemplateCopier(templateRoot),
|
|
54
|
+
docker: new DefaultDockerClient(),
|
|
55
|
+
mcp: new DefaultMcpClient(),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tonycasey/lisa",
|
|
3
|
+
"version": "0.5.13",
|
|
4
|
+
"description": "Claude NEEDS Lisa. Plug-and-play memory for Claude Code and AI coding assistants.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"remember": "cli.js",
|
|
7
|
+
"lisa": "cli.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "commonjs",
|
|
10
|
+
"main": "cli.js",
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=18.0.0"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"**/*"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"graphiti",
|
|
19
|
+
"mcp",
|
|
20
|
+
"memory",
|
|
21
|
+
"hooks",
|
|
22
|
+
"cli"
|
|
23
|
+
],
|
|
24
|
+
"author": "Tony Casey",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@anthropic-ai/claude-code": "^2.1.3",
|
|
28
|
+
"@inquirer/prompts": "^7.0.0",
|
|
29
|
+
"chalk": "^5.3.0",
|
|
30
|
+
"cli-table3": "^0.6.5",
|
|
31
|
+
"commander": "^11.1.0",
|
|
32
|
+
"execa": "^8.0.1",
|
|
33
|
+
"fs-extra": "^11.2.0",
|
|
34
|
+
"yaml": "^2.4.2"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
PROJECT_NAME={{PROJECT_NAME}}
|
|
2
|
+
#PROJECT_ALIASES= previous names of your project
|
|
3
|
+
|
|
4
|
+
# STORAGE SETTINGS
|
|
5
|
+
#STORAGE_MODE=local
|
|
6
|
+
#GRAPHITI_ENDPOINT=http://localhost:8010/mcp/
|
|
7
|
+
#GRAPHITI_GROUP_ID={{GRAPHITI_GROUP}}
|
|
8
|
+
|
|
9
|
+
# Zep Cloud (no Docker required)
|
|
10
|
+
# Add your ZEP_API_KEY to enable "lisa, switch to zep-cloud"
|
|
11
|
+
#ZEP_API_KEY=z_xxxxx
|
|
12
|
+
|