@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
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lisa
|
|
3
|
+
description: "Lisa - intelligent assistant for memory and tasks. Triggers on 'lisa', 'hey lisa', or addressing lisa directly."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
Primary interface for project memory, tasks, and knowledge. Routes natural language requests to appropriate capabilities.
|
|
8
|
+
|
|
9
|
+
## Triggers
|
|
10
|
+
Use when the user addresses "lisa" directly:
|
|
11
|
+
- "hey lisa, ..."
|
|
12
|
+
- "lisa, ..."
|
|
13
|
+
- "ask lisa ..."
|
|
14
|
+
- "lisa knows ..."
|
|
15
|
+
|
|
16
|
+
## Capabilities
|
|
17
|
+
|
|
18
|
+
### Memory Operations
|
|
19
|
+
- "lisa, show me recent memories" → Load recent facts
|
|
20
|
+
- "lisa, what do you know about X" → Search memories for topic X
|
|
21
|
+
- "lisa, remember that X" → Store a memory
|
|
22
|
+
- "lisa, recall X" → Search with specific query
|
|
23
|
+
|
|
24
|
+
### Task Operations
|
|
25
|
+
- "lisa, what tasks are we working on" → List tasks
|
|
26
|
+
- "lisa, add task X" → Create new task
|
|
27
|
+
- "lisa, task status" → Show task overview
|
|
28
|
+
|
|
29
|
+
### Storage Operations
|
|
30
|
+
- "lisa, what storage are we using" → Show current storage mode
|
|
31
|
+
- "lisa, storage status" → Show mode and connection status
|
|
32
|
+
- "lisa, switch to local" → Switch to local Docker mode
|
|
33
|
+
- "lisa, switch to zep-cloud" → Switch to Zep Cloud mode
|
|
34
|
+
- "lisa, use docker" → Switch to local mode
|
|
35
|
+
- "lisa, use cloud storage" → Switch to Zep Cloud mode
|
|
36
|
+
|
|
37
|
+
## How to use
|
|
38
|
+
1) Parse user intent from "lisa" request
|
|
39
|
+
2) Route to appropriate underlying command:
|
|
40
|
+
- Memory recall: `node .agents/skills/memory/scripts/memory.js load --cache`
|
|
41
|
+
- Memory search: `node .agents/skills/memory/scripts/memory.js load --cache --query "<topic>"`
|
|
42
|
+
- Memory add: `node .agents/skills/memory/scripts/memory.js add "<text>" --cache`
|
|
43
|
+
- Task list: `node .agents/skills/tasks/scripts/tasks.js list --cache`
|
|
44
|
+
- Task add: `node .agents/skills/tasks/scripts/tasks.js add "<text>" --cache`
|
|
45
|
+
- Storage status: `node .agents/skills/lisa/scripts/storage.js status --cache`
|
|
46
|
+
- Storage switch: `node .agents/skills/lisa/scripts/storage.js switch <mode> --cache`
|
|
47
|
+
3) Summarize results conversationally
|
|
48
|
+
|
|
49
|
+
## Intent Mapping
|
|
50
|
+
|
|
51
|
+
| User Says | Intent | Route To |
|
|
52
|
+
|-----------|--------|----------|
|
|
53
|
+
| "show memories", "recent memories", "what's stored" | recall | memory load |
|
|
54
|
+
| "what do you know about X", "recall X", "search X" | search | memory load --query X |
|
|
55
|
+
| "remember that X", "save this", "note that X" | remember | memory add X |
|
|
56
|
+
| "tasks", "what are we working on", "todo" | list tasks | tasks list |
|
|
57
|
+
| "add task X", "new task X", "create task X" | add task | tasks add X |
|
|
58
|
+
| "what storage", "current mode", "storage status" | storage status | storage status |
|
|
59
|
+
| "switch to local", "use docker", "local mode" | switch local | storage switch local |
|
|
60
|
+
| "switch to zep", "use cloud", "zep-cloud mode" | switch zep-cloud | storage switch zep-cloud |
|
|
61
|
+
|
|
62
|
+
## Personality Guidelines
|
|
63
|
+
- Lisa is helpful and knowledgeable
|
|
64
|
+
- Responses are conversational but concise
|
|
65
|
+
- Acknowledges when memory is empty or query returns nothing
|
|
66
|
+
- Suggests related queries when appropriate
|
|
67
|
+
|
|
68
|
+
## Output Formatting
|
|
69
|
+
- Always prefix Lisa's responses with the 👧 emoji followed by a space
|
|
70
|
+
- Use the emoji at the start of section headers when presenting data:
|
|
71
|
+
- `👧 Recent Memories:` for memory listings
|
|
72
|
+
- `👧 Tasks:` for task listings
|
|
73
|
+
- `👧 Lisa says:` for conversational responses
|
|
74
|
+
- Example format:
|
|
75
|
+
```
|
|
76
|
+
👧 Recent Memories:
|
|
77
|
+
1. **Memory title** (date)
|
|
78
|
+
- Details here
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## I/O Contract
|
|
82
|
+
Underlying scripts return JSON:
|
|
83
|
+
- Memory recall: `{ status: "ok", action: "load", facts: [...] }`
|
|
84
|
+
- Memory add: `{ status: "ok", action: "add", text: "..." }`
|
|
85
|
+
- Task list: `{ status: "ok", action: "list", tasks: [...] }`
|
|
86
|
+
- Task add: `{ status: "ok", action: "add", task: {...} }`
|
|
87
|
+
- Storage status: `{ status: "ok", action: "status", mode: "local|zep-cloud", isConnected: true|false }`
|
|
88
|
+
- Storage switch: `{ status: "ok", action: "switch", previousMode: "...", newMode: "...", verified: true|false }`
|
|
89
|
+
|
|
90
|
+
## Cross-model checklist
|
|
91
|
+
- Claude: Keep instructions concise; conversational output format
|
|
92
|
+
- Gemini: Use explicit commands; avoid model-specific tokens
|
|
File without changes
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
// Lisa storage mode management script.
|
|
4
|
+
// Commands:
|
|
5
|
+
// node storage.js status [--cache] Show current storage mode and connection status
|
|
6
|
+
// node storage.js switch <mode> [--cache] Switch to local or zep-cloud mode
|
|
7
|
+
// Options:
|
|
8
|
+
// --cache Write successful responses to cache and use as fallback on errors.
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { execSync } = require('child_process');
|
|
13
|
+
// Constants
|
|
14
|
+
const ZEP_BASE_URL = 'https://api.getzep.com/api/v2';
|
|
15
|
+
const DEFAULT_LOCAL_ENDPOINT = 'http://localhost:8010/mcp/';
|
|
16
|
+
const DEFAULT_ZEP_ENDPOINT = 'https://api.getzep.com/mcp/';
|
|
17
|
+
// Read environment config from .agents/skills/.env
|
|
18
|
+
function readEnvConfig() {
|
|
19
|
+
// Path: .agents/skills/lisa/scripts/ -> .agents/skills/.env (2 levels up)
|
|
20
|
+
const envPath = path.join(__dirname, '..', '..', '.env');
|
|
21
|
+
const out = {};
|
|
22
|
+
try {
|
|
23
|
+
const raw = fs.readFileSync(envPath, 'utf8');
|
|
24
|
+
raw.split(/\r?\n/).forEach((line) => {
|
|
25
|
+
if (!line || line.startsWith('#'))
|
|
26
|
+
return;
|
|
27
|
+
const idx = line.indexOf('=');
|
|
28
|
+
if (idx === -1)
|
|
29
|
+
return;
|
|
30
|
+
const key = line.slice(0, idx).trim();
|
|
31
|
+
const val = line.slice(idx + 1).trim();
|
|
32
|
+
out[key] = val;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (_) {
|
|
36
|
+
// optional .env; ignore if missing
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
// Update STORAGE_MODE in .agents/skills/.env (preserves comments and other vars)
|
|
41
|
+
function updateEnvStorageMode(newMode) {
|
|
42
|
+
const envPath = path.join(__dirname, '..', '..', '.env');
|
|
43
|
+
let content;
|
|
44
|
+
try {
|
|
45
|
+
content = fs.readFileSync(envPath, 'utf8');
|
|
46
|
+
}
|
|
47
|
+
catch (_) {
|
|
48
|
+
// Create new .env if it doesn't exist
|
|
49
|
+
content = '';
|
|
50
|
+
}
|
|
51
|
+
const lines = content.split(/\r?\n/);
|
|
52
|
+
let foundMode = false;
|
|
53
|
+
let foundEndpoint = false;
|
|
54
|
+
const newEndpoint = newMode === 'zep-cloud' ? DEFAULT_ZEP_ENDPOINT : DEFAULT_LOCAL_ENDPOINT;
|
|
55
|
+
const updatedLines = lines.map((line) => {
|
|
56
|
+
// Update STORAGE_MODE
|
|
57
|
+
if (line.startsWith('STORAGE_MODE=') || line.startsWith('STORAGE_MODE =')) {
|
|
58
|
+
foundMode = true;
|
|
59
|
+
return `STORAGE_MODE=${newMode}`;
|
|
60
|
+
}
|
|
61
|
+
// Update GRAPHITI_ENDPOINT
|
|
62
|
+
if (line.startsWith('GRAPHITI_ENDPOINT=') || line.startsWith('GRAPHITI_ENDPOINT =')) {
|
|
63
|
+
foundEndpoint = true;
|
|
64
|
+
return `GRAPHITI_ENDPOINT=${newEndpoint}`;
|
|
65
|
+
}
|
|
66
|
+
return line;
|
|
67
|
+
});
|
|
68
|
+
// Add STORAGE_MODE if not found
|
|
69
|
+
if (!foundMode) {
|
|
70
|
+
// Find a good place to insert (after GRAPHITI_GROUP_ID or at end)
|
|
71
|
+
const groupIdx = updatedLines.findIndex((l) => l.startsWith('GRAPHITI_GROUP_ID'));
|
|
72
|
+
if (groupIdx !== -1) {
|
|
73
|
+
updatedLines.splice(groupIdx + 1, 0, `STORAGE_MODE=${newMode}`);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
updatedLines.push(`STORAGE_MODE=${newMode}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Add GRAPHITI_ENDPOINT if not found
|
|
80
|
+
if (!foundEndpoint) {
|
|
81
|
+
const modeIdx = updatedLines.findIndex((l) => l.startsWith('STORAGE_MODE'));
|
|
82
|
+
if (modeIdx !== -1) {
|
|
83
|
+
updatedLines.splice(modeIdx + 1, 0, `GRAPHITI_ENDPOINT=${newEndpoint}`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
updatedLines.push(`GRAPHITI_ENDPOINT=${newEndpoint}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Write back
|
|
90
|
+
fs.writeFileSync(envPath, updatedLines.join('\n'), 'utf8');
|
|
91
|
+
}
|
|
92
|
+
// Check if Docker daemon is running
|
|
93
|
+
function checkDockerRunning() {
|
|
94
|
+
try {
|
|
95
|
+
execSync('docker info', { stdio: 'pipe', timeout: 5000 });
|
|
96
|
+
return { running: true };
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
100
|
+
if (message.includes('Cannot connect') || message.includes('Is the docker daemon running')) {
|
|
101
|
+
return { running: false, error: 'Docker daemon is not running. Start Docker Desktop or run `dockerd`.' };
|
|
102
|
+
}
|
|
103
|
+
if (message.includes('command not found') || message.includes('not recognized')) {
|
|
104
|
+
return { running: false, error: 'Docker is not installed. Install Docker from https://docker.com' };
|
|
105
|
+
}
|
|
106
|
+
return { running: false, error: `Docker check failed: ${message}` };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Check if ZEP_API_KEY exists
|
|
110
|
+
function checkZepApiKeyExists(env) {
|
|
111
|
+
const apiKey = env.ZEP_API_KEY || process.env.ZEP_API_KEY;
|
|
112
|
+
if (apiKey && apiKey.length > 0 && !apiKey.startsWith('${')) {
|
|
113
|
+
return { exists: true };
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
exists: false,
|
|
117
|
+
error: 'ZEP_API_KEY not configured. Add ZEP_API_KEY to .agents/.env or set it as an environment variable.'
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// Ping local MCP endpoint
|
|
121
|
+
async function pingLocalMcp(endpoint) {
|
|
122
|
+
try {
|
|
123
|
+
const body = {
|
|
124
|
+
jsonrpc: '2.0',
|
|
125
|
+
id: 'ping',
|
|
126
|
+
method: 'initialize',
|
|
127
|
+
params: {
|
|
128
|
+
protocolVersion: '2024-11-05',
|
|
129
|
+
capabilities: {},
|
|
130
|
+
clientInfo: { name: 'storage-skill', version: '0.1.0' },
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
const resp = await fetch(endpoint, {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: {
|
|
136
|
+
'Content-Type': 'application/json',
|
|
137
|
+
Accept: 'application/json, text/event-stream',
|
|
138
|
+
},
|
|
139
|
+
body: JSON.stringify(body),
|
|
140
|
+
signal: AbortSignal.timeout(10000),
|
|
141
|
+
});
|
|
142
|
+
if (resp.ok) {
|
|
143
|
+
return { reachable: true };
|
|
144
|
+
}
|
|
145
|
+
return { reachable: false, error: `HTTP ${resp.status}` };
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
149
|
+
return { reachable: false, error: message };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Ping Zep Cloud API
|
|
153
|
+
async function pingZepCloud(apiKey) {
|
|
154
|
+
try {
|
|
155
|
+
const resp = await fetch(`${ZEP_BASE_URL}/users`, {
|
|
156
|
+
method: 'GET',
|
|
157
|
+
headers: {
|
|
158
|
+
'Content-Type': 'application/json',
|
|
159
|
+
Authorization: `Api-Key ${apiKey}`,
|
|
160
|
+
},
|
|
161
|
+
signal: AbortSignal.timeout(10000),
|
|
162
|
+
});
|
|
163
|
+
// Even 404 or 400 means the API is reachable and auth worked
|
|
164
|
+
if (resp.ok || resp.status === 404 || resp.status === 400) {
|
|
165
|
+
return { reachable: true };
|
|
166
|
+
}
|
|
167
|
+
if (resp.status === 401 || resp.status === 403) {
|
|
168
|
+
return { reachable: false, error: 'Invalid API key' };
|
|
169
|
+
}
|
|
170
|
+
return { reachable: false, error: `HTTP ${resp.status}` };
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
174
|
+
return { reachable: false, error: message };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Argument parsing
|
|
178
|
+
const args = process.argv.slice(2);
|
|
179
|
+
function popFlag(name, fallback) {
|
|
180
|
+
const idx = args.indexOf(name);
|
|
181
|
+
if (idx === -1)
|
|
182
|
+
return fallback;
|
|
183
|
+
const val = args[idx + 1];
|
|
184
|
+
args.splice(idx, 2);
|
|
185
|
+
return val ?? fallback;
|
|
186
|
+
}
|
|
187
|
+
function hasFlag(name) {
|
|
188
|
+
const idx = args.indexOf(name);
|
|
189
|
+
if (idx === -1)
|
|
190
|
+
return false;
|
|
191
|
+
args.splice(idx, 1);
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
const command = args.shift() ?? '';
|
|
195
|
+
const useCache = hasFlag('--cache');
|
|
196
|
+
const targetMode = args.shift() ?? '';
|
|
197
|
+
const cacheFile = path.join(__dirname, '..', 'cache', 'storage.log');
|
|
198
|
+
function writeCache(obj) {
|
|
199
|
+
try {
|
|
200
|
+
const cacheDir = path.dirname(cacheFile);
|
|
201
|
+
if (!fs.existsSync(cacheDir)) {
|
|
202
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
203
|
+
}
|
|
204
|
+
const line = JSON.stringify({ ts: new Date().toISOString(), ...obj });
|
|
205
|
+
fs.appendFileSync(cacheFile, `${line}\n`, 'utf8');
|
|
206
|
+
}
|
|
207
|
+
catch (_) {
|
|
208
|
+
// cache failures should not crash command
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function readCacheFallback() {
|
|
212
|
+
try {
|
|
213
|
+
const data = fs.readFileSync(cacheFile, 'utf8').trim().split('\n').filter(Boolean);
|
|
214
|
+
if (!data.length)
|
|
215
|
+
return null;
|
|
216
|
+
return data.slice(-1).map((l) => JSON.parse(l))[0];
|
|
217
|
+
}
|
|
218
|
+
catch (_) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Status command
|
|
223
|
+
async function statusCommand() {
|
|
224
|
+
const env = readEnvConfig();
|
|
225
|
+
const mode = env.STORAGE_MODE || process.env.STORAGE_MODE || 'local';
|
|
226
|
+
const endpoint = env.GRAPHITI_ENDPOINT || process.env.GRAPHITI_ENDPOINT || DEFAULT_LOCAL_ENDPOINT;
|
|
227
|
+
const groupId = env.GRAPHITI_GROUP_ID || process.env.GRAPHITI_GROUP_ID || 'lisa';
|
|
228
|
+
let isConnected = false;
|
|
229
|
+
let connectionError;
|
|
230
|
+
if (mode === 'zep-cloud') {
|
|
231
|
+
const apiKey = env.ZEP_API_KEY || process.env.ZEP_API_KEY || '';
|
|
232
|
+
if (apiKey && !apiKey.startsWith('${')) {
|
|
233
|
+
const result = await pingZepCloud(apiKey);
|
|
234
|
+
isConnected = result.reachable;
|
|
235
|
+
connectionError = result.error;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
connectionError = 'ZEP_API_KEY not configured';
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else if (mode === 'local') {
|
|
242
|
+
const dockerCheck = checkDockerRunning();
|
|
243
|
+
if (!dockerCheck.running) {
|
|
244
|
+
connectionError = dockerCheck.error;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
const result = await pingLocalMcp(endpoint);
|
|
248
|
+
isConnected = result.reachable;
|
|
249
|
+
connectionError = result.error;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
connectionError = `Unknown mode: ${mode}`;
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
status: 'ok',
|
|
257
|
+
action: 'status',
|
|
258
|
+
mode,
|
|
259
|
+
endpoint,
|
|
260
|
+
groupId,
|
|
261
|
+
isConnected,
|
|
262
|
+
...(connectionError ? { connectionError } : {}),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
// Switch command
|
|
266
|
+
async function switchCommand(newMode) {
|
|
267
|
+
const validModes = ['local', 'zep-cloud'];
|
|
268
|
+
if (!validModes.includes(newMode)) {
|
|
269
|
+
return {
|
|
270
|
+
status: 'error',
|
|
271
|
+
action: 'switch',
|
|
272
|
+
message: `Invalid mode: ${newMode}. Valid modes are: ${validModes.join(', ')}`,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const env = readEnvConfig();
|
|
276
|
+
const previousMode = env.STORAGE_MODE || process.env.STORAGE_MODE || 'local';
|
|
277
|
+
// Same mode check
|
|
278
|
+
if (previousMode === newMode) {
|
|
279
|
+
return {
|
|
280
|
+
status: 'ok',
|
|
281
|
+
action: 'switch',
|
|
282
|
+
previousMode,
|
|
283
|
+
newMode,
|
|
284
|
+
verified: true,
|
|
285
|
+
message: `Already using ${newMode} mode.`,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
// Validation before switching
|
|
289
|
+
if (newMode === 'local') {
|
|
290
|
+
const dockerCheck = checkDockerRunning();
|
|
291
|
+
if (!dockerCheck.running) {
|
|
292
|
+
return {
|
|
293
|
+
status: 'error',
|
|
294
|
+
action: 'switch',
|
|
295
|
+
message: `Cannot switch to local: ${dockerCheck.error}`,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
else if (newMode === 'zep-cloud') {
|
|
300
|
+
const zepCheck = checkZepApiKeyExists(env);
|
|
301
|
+
if (!zepCheck.exists) {
|
|
302
|
+
return {
|
|
303
|
+
status: 'error',
|
|
304
|
+
action: 'switch',
|
|
305
|
+
message: `Cannot switch to zep-cloud: ${zepCheck.error}`,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Update the .env file
|
|
310
|
+
updateEnvStorageMode(newMode);
|
|
311
|
+
// Re-read config to get updated endpoint
|
|
312
|
+
const updatedEnv = readEnvConfig();
|
|
313
|
+
const newEndpoint = updatedEnv.GRAPHITI_ENDPOINT || (newMode === 'zep-cloud' ? DEFAULT_ZEP_ENDPOINT : DEFAULT_LOCAL_ENDPOINT);
|
|
314
|
+
// Verify connection after switch
|
|
315
|
+
let verified = false;
|
|
316
|
+
let verifyError;
|
|
317
|
+
if (newMode === 'zep-cloud') {
|
|
318
|
+
const apiKey = updatedEnv.ZEP_API_KEY || process.env.ZEP_API_KEY || '';
|
|
319
|
+
const result = await pingZepCloud(apiKey);
|
|
320
|
+
verified = result.reachable;
|
|
321
|
+
verifyError = result.error;
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
const result = await pingLocalMcp(newEndpoint);
|
|
325
|
+
verified = result.reachable;
|
|
326
|
+
verifyError = result.error;
|
|
327
|
+
}
|
|
328
|
+
const message = verified
|
|
329
|
+
? `Successfully switched from ${previousMode} to ${newMode}. Connection verified.`
|
|
330
|
+
: `Switched from ${previousMode} to ${newMode}, but connection verification failed: ${verifyError}`;
|
|
331
|
+
return {
|
|
332
|
+
status: 'ok',
|
|
333
|
+
action: 'switch',
|
|
334
|
+
previousMode,
|
|
335
|
+
newMode,
|
|
336
|
+
endpoint: newEndpoint,
|
|
337
|
+
verified,
|
|
338
|
+
message,
|
|
339
|
+
...(verifyError && !verified ? { verifyError } : {}),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
async function main() {
|
|
343
|
+
try {
|
|
344
|
+
if (!['status', 'switch'].includes(command)) {
|
|
345
|
+
throw new Error('command must be status|switch');
|
|
346
|
+
}
|
|
347
|
+
let out;
|
|
348
|
+
if (command === 'status') {
|
|
349
|
+
out = await statusCommand();
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
// switch
|
|
353
|
+
if (!targetMode) {
|
|
354
|
+
throw new Error('switch requires a mode argument (local or zep-cloud)');
|
|
355
|
+
}
|
|
356
|
+
out = await switchCommand(targetMode);
|
|
357
|
+
}
|
|
358
|
+
if (useCache && out.status === 'ok') {
|
|
359
|
+
writeCache(out);
|
|
360
|
+
}
|
|
361
|
+
console.log(JSON.stringify(out, null, 2));
|
|
362
|
+
}
|
|
363
|
+
catch (err) {
|
|
364
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
365
|
+
const fallback = useCache ? readCacheFallback() : null;
|
|
366
|
+
if (fallback) {
|
|
367
|
+
console.log(JSON.stringify({ status: 'fallback', error: message, fallback }, null, 2));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
console.error(message);
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
main();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: memory
|
|
3
|
+
description: "Load or remember project memory via Graphiti MCP; triggers on 'load memory', 'recall', or 'remember', usable by any model (Claude, Gemini)."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
Reusable memory helper that routes remember/recall requests to Graphiti MCP while staying model-neutral and providing cache fallback.
|
|
8
|
+
|
|
9
|
+
## Triggers
|
|
10
|
+
Use when the user says things like: "load memory", "recall notes", "remember", "pull saved context", "fetch past tasks".
|
|
11
|
+
|
|
12
|
+
## How to use
|
|
13
|
+
1) For recall: run `scripts/memory.js load --cache [--query <q>] [--limit 10] [--group <id>]`. Reads Graphiti facts and prints JSON. Uses cache if MCP is down.
|
|
14
|
+
2) For remember: run `scripts/memory.js add "<text>" --cache [--group <id>] [--tag foo] [--source <src>]` to append an episode.
|
|
15
|
+
3) Endpoint/group: reads ${GRAPHITI_ENDPOINT} / ${GRAPHITI_GROUP_ID} from `.agents/.env` (written by init); see root `AGENTS.md` for canonical defaults.
|
|
16
|
+
4) Cache fallback: stored at `cache/memory.log` inside this skill. On failure, last cached result is returned with `status: "fallback"`.
|
|
17
|
+
5) Keep prompts model-neutral; avoid role tokens. Models just orchestrate the script and summarize results to the user.
|
|
18
|
+
|
|
19
|
+
## I/O contract (examples)
|
|
20
|
+
- Recall: output JSON `{ status: "ok", action: "load", group, query, facts: [...] }`.
|
|
21
|
+
- Remember: JSON `{ status: "ok", action: "add", group, text }`.
|
|
22
|
+
- Fallback: JSON `{ status: "fallback", error, fallback: <last cached object> }`.
|
|
23
|
+
|
|
24
|
+
## Cross-model checklist
|
|
25
|
+
- Claude: confirm concise trigger phrasing; keep under system limits; avoid markdown-heavy instructions.
|
|
26
|
+
- Gemini: ensure commands are explicit; avoid model-specific tokens; keep JSON small.
|
|
27
|
+
|
|
28
|
+
## Notes
|
|
29
|
+
- Script is Node.js; relies on global `fetch` (Node ≥18). If older runtime, `node --experimental-fetch`.
|
|
30
|
+
- Facts query defaults to `*` with `max_facts=10`; tune via `--limit` and `--query`.
|
|
31
|
+
- Safe to relocate: skill lives in `.agents/skills/memory` to remain decoupled from model-specific bindings.
|