oh-memos-mcp 1.0.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/.env.example +27 -0
- package/README.md +71 -0
- package/dist/api-client.d.ts +22 -0
- package/dist/api-client.js +127 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.js +163 -0
- package/dist/cube-manager.d.ts +16 -0
- package/dist/cube-manager.js +357 -0
- package/dist/formatters.d.ts +16 -0
- package/dist/formatters.js +174 -0
- package/dist/handlers/admin.d.ts +13 -0
- package/dist/handlers/admin.js +331 -0
- package/dist/handlers/calendar.d.ts +8 -0
- package/dist/handlers/calendar.js +281 -0
- package/dist/handlers/graph.d.ts +11 -0
- package/dist/handlers/graph.js +502 -0
- package/dist/handlers/index.d.ts +7 -0
- package/dist/handlers/index.js +71 -0
- package/dist/handlers/memory.d.ts +12 -0
- package/dist/handlers/memory.js +270 -0
- package/dist/handlers/search.d.ts +11 -0
- package/dist/handlers/search.js +334 -0
- package/dist/handlers/utils.d.ts +22 -0
- package/dist/handlers/utils.js +75 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +18 -0
- package/dist/keyword-enhancer.d.ts +18 -0
- package/dist/keyword-enhancer.js +335 -0
- package/dist/memory-analysis.d.ts +7 -0
- package/dist/memory-analysis.js +45 -0
- package/dist/models.d.ts +21 -0
- package/dist/models.js +101 -0
- package/dist/query-processing.d.ts +20 -0
- package/dist/query-processing.js +265 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +114 -0
- package/dist/tools-registry.d.ts +303 -0
- package/dist/tools-registry.js +369 -0
- package/dist/types.d.ts +118 -0
- package/dist/types.js +5 -0
- package/package.json +53 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Handlers
|
|
3
|
+
*
|
|
4
|
+
* memos_list_cubes, memos_register_cube, memos_create_user,
|
|
5
|
+
* memos_validate_cubes, memos_delete
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import { MEMOS_URL, MEMOS_USER, MEMOS_DEFAULT_CUBE, MEMOS_CUBES_DIR, MEMOS_ENABLE_DELETE, MEMOS_TIMEOUT_TOOL, registeredCubes, } from "../config.js";
|
|
10
|
+
import { fetchWithTimeout } from "../api-client.js";
|
|
11
|
+
import { ensureCubeRegistered, getCubePath, getCubesBaseDir, getLocalCubesBaseDir, listAvailableCubes, validateAndFixCubeConfig, verifyCubeLoaded, } from "../cube-manager.js";
|
|
12
|
+
import { ERR_CUBE_NOT_FOUND, ERR_DELETE_DISABLED, ERR_PARAM_MISSING, apiErrorResponse, cubeRegistrationError, errorResponse, getCubeIdFromArgs, } from "./utils.js";
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// memos_list_cubes
|
|
15
|
+
// ============================================================================
|
|
16
|
+
export async function handleMemosListCubes(arguments_) {
|
|
17
|
+
const includeStatus = arguments_.include_status === true;
|
|
18
|
+
const available = listAvailableCubes();
|
|
19
|
+
if (available.length === 0) {
|
|
20
|
+
const cubesDir = MEMOS_CUBES_DIR.endsWith(MEMOS_DEFAULT_CUBE)
|
|
21
|
+
? path.dirname(MEMOS_CUBES_DIR)
|
|
22
|
+
: MEMOS_CUBES_DIR;
|
|
23
|
+
return [{ type: "text", text: [
|
|
24
|
+
"## No cubes found",
|
|
25
|
+
"",
|
|
26
|
+
`Cubes directory: \`${cubesDir}\``,
|
|
27
|
+
"",
|
|
28
|
+
`To create a new cube, use the MemOS web interface at ${MEMOS_URL}/docs`,
|
|
29
|
+
"or create a cube directory with a config.json file.",
|
|
30
|
+
].join("\n") }];
|
|
31
|
+
}
|
|
32
|
+
const result = ["## Available Memory Cubes\n"];
|
|
33
|
+
result.push(`Cubes directory: \`${MEMOS_CUBES_DIR}\`\n`);
|
|
34
|
+
for (const cube of available) {
|
|
35
|
+
if (includeStatus) {
|
|
36
|
+
const isLoaded = await verifyCubeLoaded(cube.id);
|
|
37
|
+
const status = isLoaded ? "loaded" : "not loaded";
|
|
38
|
+
result.push(`- **${cube.id}**: \`${cube.path}\` (${status})`);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
result.push(`- **${cube.id}**: \`${cube.path}\``);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
result.push(`\n**Default cube**: \`${MEMOS_DEFAULT_CUBE}\``);
|
|
45
|
+
result.push("\n*Use `memos_list_cubes(include_status=True)` to check registration status.*");
|
|
46
|
+
return [{ type: "text", text: result.join("\n") }];
|
|
47
|
+
}
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// memos_register_cube
|
|
50
|
+
// ============================================================================
|
|
51
|
+
export async function handleMemosRegisterCube(arguments_) {
|
|
52
|
+
const cubeId = String(arguments_.cube_id ?? "");
|
|
53
|
+
let cubePath = arguments_.cube_path ? String(arguments_.cube_path) : undefined;
|
|
54
|
+
if (!cubeId) {
|
|
55
|
+
return errorResponse("cube_id parameter is required", ERR_PARAM_MISSING, [
|
|
56
|
+
"Use memos_list_cubes() to find available cubes",
|
|
57
|
+
'`memos_register_cube(cube_id="dev_cube")`',
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
if (!cubePath) {
|
|
61
|
+
// Try to find cube path (returns Windows/original path for API)
|
|
62
|
+
const localPath = getCubePath(cubeId);
|
|
63
|
+
if (!localPath) {
|
|
64
|
+
const available = listAvailableCubes();
|
|
65
|
+
const hint = available.length > 0
|
|
66
|
+
? `\n\n**Available cubes:**\n${available.map((c) => `- \`${c.id}\``).join("\n")}`
|
|
67
|
+
: "";
|
|
68
|
+
return [{ type: "text", text: `❌ Cube \`${cubeId}\` not found in cubes directory.\n\nCubes directory: \`${getCubesBaseDir()}\`${hint}` }];
|
|
69
|
+
}
|
|
70
|
+
// Convert back to Windows path if needed
|
|
71
|
+
const cubesDir = MEMOS_CUBES_DIR;
|
|
72
|
+
const isWindowsPath = /^[A-Za-z]:/.test(cubesDir);
|
|
73
|
+
if (isWindowsPath) {
|
|
74
|
+
const match = localPath.match(/^\/mnt\/([a-z])\/(.*)/);
|
|
75
|
+
if (match) {
|
|
76
|
+
cubePath = `${match[1].toUpperCase()}:\\${match[2].replace(/\//g, "\\")}`;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
cubePath = localPath;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
cubePath = localPath;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const response = await fetchWithTimeout(`${MEMOS_URL}/mem_cubes`, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: { "Content-Type": "application/json" },
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
user_id: MEMOS_USER,
|
|
92
|
+
mem_cube_name_or_path: cubePath,
|
|
93
|
+
mem_cube_id: cubeId,
|
|
94
|
+
}),
|
|
95
|
+
timeoutMs: MEMOS_TIMEOUT_TOOL,
|
|
96
|
+
});
|
|
97
|
+
if (response.ok) {
|
|
98
|
+
const data = await response.json();
|
|
99
|
+
if (data.code === 200) {
|
|
100
|
+
registeredCubes.add(cubeId);
|
|
101
|
+
return [{ type: "text", text: [
|
|
102
|
+
"✅ **Cube registered successfully!**",
|
|
103
|
+
"",
|
|
104
|
+
`- Cube ID: \`${cubeId}\``,
|
|
105
|
+
`- Path: \`${cubePath}\``,
|
|
106
|
+
"",
|
|
107
|
+
"You can now use this cube with other memos_* tools.",
|
|
108
|
+
].join("\n") }];
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const errorMsg = String(data.message ?? "Unknown error");
|
|
112
|
+
let hint = "";
|
|
113
|
+
if (errorMsg.toLowerCase().includes("reranker")) {
|
|
114
|
+
hint = "Edit the cube's config.json and change `reranker.backend` to `http_bge` or `noop`.";
|
|
115
|
+
}
|
|
116
|
+
else if (errorMsg.toLowerCase().includes("user")) {
|
|
117
|
+
hint = "Use `memos_create_user` tool to create the user first.";
|
|
118
|
+
}
|
|
119
|
+
return errorResponse(`Registration failed: ${errorMsg}`, errorMsg.toLowerCase().includes("not found") ? ERR_CUBE_NOT_FOUND : undefined, hint ? [hint] : []);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
return apiErrorResponse("Registration", `HTTP ${response.status}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
return apiErrorResponse("Registration", String(err));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// memos_create_user
|
|
132
|
+
// ============================================================================
|
|
133
|
+
export async function handleMemosCreateUser(arguments_) {
|
|
134
|
+
const userId = String(arguments_.user_id ?? MEMOS_USER);
|
|
135
|
+
const userName = String(arguments_.user_name ?? userId);
|
|
136
|
+
try {
|
|
137
|
+
const response = await fetchWithTimeout(`${MEMOS_URL}/users`, {
|
|
138
|
+
method: "POST",
|
|
139
|
+
headers: { "Content-Type": "application/json" },
|
|
140
|
+
body: JSON.stringify({
|
|
141
|
+
user_name: userName,
|
|
142
|
+
user_id: userId,
|
|
143
|
+
role: "USER",
|
|
144
|
+
}),
|
|
145
|
+
timeoutMs: MEMOS_TIMEOUT_TOOL,
|
|
146
|
+
});
|
|
147
|
+
if (response.ok) {
|
|
148
|
+
const data = await response.json();
|
|
149
|
+
if (data.code === 200) {
|
|
150
|
+
return [{ type: "text", text: [
|
|
151
|
+
"✅ **User created successfully!**",
|
|
152
|
+
"",
|
|
153
|
+
`- User ID: \`${userId}\``,
|
|
154
|
+
`- User Name: \`${userName}\``,
|
|
155
|
+
"",
|
|
156
|
+
"You can now register cubes and store memories.",
|
|
157
|
+
].join("\n") }];
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
const errorMsg = String(data.message ?? "Unknown error");
|
|
161
|
+
if (errorMsg.toLowerCase().includes("exist")) {
|
|
162
|
+
return [{ type: "text", text: `ℹ️ User \`${userId}\` already exists. You can proceed with cube registration.` }];
|
|
163
|
+
}
|
|
164
|
+
return errorResponse(`User creation failed: ${errorMsg}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
return apiErrorResponse("User creation", `HTTP ${response.status}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
return apiErrorResponse("User creation", String(err));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// ============================================================================
|
|
176
|
+
// memos_validate_cubes
|
|
177
|
+
// ============================================================================
|
|
178
|
+
export async function handleMemosValidateCubes(arguments_) {
|
|
179
|
+
const fixIssues = arguments_.fix !== false; // Default true
|
|
180
|
+
const localCubesDir = getLocalCubesBaseDir();
|
|
181
|
+
if (!fs.existsSync(localCubesDir) || !fs.statSync(localCubesDir).isDirectory()) {
|
|
182
|
+
return errorResponse(`Cubes directory not found: ${localCubesDir}`, ERR_CUBE_NOT_FOUND, [
|
|
183
|
+
"Set MEMOS_CUBES_DIR in .env",
|
|
184
|
+
"Verify the directory exists on disk",
|
|
185
|
+
]);
|
|
186
|
+
}
|
|
187
|
+
const results = ["## 🔍 Cube Configuration Validation\n"];
|
|
188
|
+
let fixedCount = 0;
|
|
189
|
+
let errorCount = 0;
|
|
190
|
+
let okCount = 0;
|
|
191
|
+
try {
|
|
192
|
+
const items = fs.readdirSync(localCubesDir);
|
|
193
|
+
for (const cubeName of items) {
|
|
194
|
+
const cubePath = path.join(localCubesDir, cubeName);
|
|
195
|
+
const configPath = path.join(cubePath, "config.json");
|
|
196
|
+
if (!fs.existsSync(cubePath) || !fs.statSync(cubePath).isDirectory() || !fs.existsSync(configPath)) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
201
|
+
const issues = [];
|
|
202
|
+
if (config.cube_id !== cubeName) {
|
|
203
|
+
issues.push(`cube_id: \`${config.cube_id}\` → \`${cubeName}\``);
|
|
204
|
+
}
|
|
205
|
+
const textCfg = config.text_mem?.config;
|
|
206
|
+
const graphCfg = textCfg?.graph_db?.config;
|
|
207
|
+
const currentUserName = graphCfg?.user_name;
|
|
208
|
+
if (currentUserName && currentUserName !== cubeName) {
|
|
209
|
+
issues.push(`user_name: \`${currentUserName}\` → \`${cubeName}\``);
|
|
210
|
+
}
|
|
211
|
+
if (issues.length > 0) {
|
|
212
|
+
if (fixIssues) {
|
|
213
|
+
const [wasFixes, fixError] = validateAndFixCubeConfig(cubeName, configPath);
|
|
214
|
+
if (wasFixes) {
|
|
215
|
+
results.push(`- **${cubeName}**: ✅ Fixed - ${issues.join(", ")}`);
|
|
216
|
+
fixedCount++;
|
|
217
|
+
}
|
|
218
|
+
else if (fixError) {
|
|
219
|
+
results.push(`- **${cubeName}**: ❌ Error - ${fixError}`);
|
|
220
|
+
errorCount++;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
results.push(`- **${cubeName}**: ⚠️ Issues - ${issues.join(", ")}`);
|
|
225
|
+
errorCount++;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
results.push(`- **${cubeName}**: ✅ OK`);
|
|
230
|
+
okCount++;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
results.push(`- **${cubeName}**: ❌ Error reading config - ${err}`);
|
|
235
|
+
errorCount++;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
results.push("\n### Summary");
|
|
239
|
+
results.push(`- ✅ OK: ${okCount}`);
|
|
240
|
+
if (fixedCount > 0)
|
|
241
|
+
results.push(`- 🔧 Fixed: ${fixedCount}`);
|
|
242
|
+
if (errorCount > 0)
|
|
243
|
+
results.push(`- ⚠️ Issues: ${errorCount}`);
|
|
244
|
+
if (fixedCount > 0)
|
|
245
|
+
results.push("\n**Note:** Fixed cubes need API restart to take effect.");
|
|
246
|
+
return [{ type: "text", text: results.join("\n") }];
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
return apiErrorResponse("Validation", String(err));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// ============================================================================
|
|
253
|
+
// memos_delete
|
|
254
|
+
// ============================================================================
|
|
255
|
+
export async function handleMemosDelete(arguments_) {
|
|
256
|
+
if (!MEMOS_ENABLE_DELETE) {
|
|
257
|
+
return errorResponse("Delete functionality is disabled", ERR_DELETE_DISABLED, [
|
|
258
|
+
"Set MEMOS_ENABLE_DELETE=true in environment to enable",
|
|
259
|
+
"This is a safety feature to prevent accidental data loss",
|
|
260
|
+
]);
|
|
261
|
+
}
|
|
262
|
+
const cubeId = getCubeIdFromArgs(arguments_);
|
|
263
|
+
const memoryId = arguments_.memory_id ? String(arguments_.memory_id) : undefined;
|
|
264
|
+
const memoryIds = Array.isArray(arguments_.memory_ids)
|
|
265
|
+
? arguments_.memory_ids.map(String)
|
|
266
|
+
: [];
|
|
267
|
+
const deleteAll = arguments_.delete_all === true;
|
|
268
|
+
const [regSuccess, regError] = await ensureCubeRegistered(cubeId);
|
|
269
|
+
if (!regSuccess)
|
|
270
|
+
return cubeRegistrationError(cubeId, regError);
|
|
271
|
+
if (deleteAll) {
|
|
272
|
+
const response = await fetchWithTimeout(`${MEMOS_URL}/memories/${cubeId}?user_id=${encodeURIComponent(MEMOS_USER)}`, { method: "DELETE" });
|
|
273
|
+
if (response.ok) {
|
|
274
|
+
const data = await response.json();
|
|
275
|
+
if (data.code === 200) {
|
|
276
|
+
return [{ type: "text", text: `⚠️ **ALL memories deleted** from cube: \`${cubeId}\`` }];
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
return errorResponse(`❌ **Delete all failed**: ${data.message ?? "Unknown error"}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
return errorResponse(`❌ **API error** during delete all: ${response.status}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const idsToDelete = [];
|
|
287
|
+
if (memoryId)
|
|
288
|
+
idsToDelete.push(memoryId);
|
|
289
|
+
idsToDelete.push(...memoryIds);
|
|
290
|
+
if (idsToDelete.length === 0) {
|
|
291
|
+
return errorResponse("No target specified for deletion", ERR_PARAM_MISSING, [
|
|
292
|
+
"Provide `memory_id` for single delete",
|
|
293
|
+
"Provide `memory_ids` for batch delete",
|
|
294
|
+
"Set `delete_all=true` to delete all memories (dangerous)",
|
|
295
|
+
]);
|
|
296
|
+
}
|
|
297
|
+
const results = [];
|
|
298
|
+
for (const mid of idsToDelete) {
|
|
299
|
+
let memContent = "*(Unknown content)*";
|
|
300
|
+
try {
|
|
301
|
+
const getResp = await fetchWithTimeout(`${MEMOS_URL}/memories/${cubeId}/${mid}?user_id=${encodeURIComponent(MEMOS_USER)}`, { method: "GET" });
|
|
302
|
+
if (getResp.ok) {
|
|
303
|
+
const gData = await getResp.json();
|
|
304
|
+
if (gData.code === 200 && gData.data) {
|
|
305
|
+
const memNode = gData.data;
|
|
306
|
+
if (typeof memNode === "object") {
|
|
307
|
+
memContent = String(memNode.memory ?? memContent);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
memContent = "*(Fetch failed)*";
|
|
314
|
+
}
|
|
315
|
+
const deleteResp = await fetchWithTimeout(`${MEMOS_URL}/memories/${cubeId}/${mid}?user_id=${encodeURIComponent(MEMOS_USER)}`, { method: "DELETE" });
|
|
316
|
+
if (deleteResp.ok) {
|
|
317
|
+
const data = await deleteResp.json();
|
|
318
|
+
if (data.code === 200) {
|
|
319
|
+
results.push(`✅ Deleted: \`${mid}\`\n > ${memContent.slice(0, 150)}...`);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
results.push(`❌ Failed: \`${mid}\` (${data.message ?? "Unknown error"})`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
results.push(`❌ API Error: \`${mid}\` (Status: ${deleteResp.status})`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return [{ type: "text", text: results.join("\n") }];
|
|
330
|
+
}
|
|
331
|
+
//# sourceMappingURL=admin.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar Handler
|
|
3
|
+
*
|
|
4
|
+
* memos_calendar - project mode and student mode
|
|
5
|
+
*/
|
|
6
|
+
import type { TextContent } from "../types.js";
|
|
7
|
+
export declare function handleMemosCalendar(arguments_: Record<string, unknown>): Promise<TextContent[]>;
|
|
8
|
+
//# sourceMappingURL=calendar.d.ts.map
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar Handler
|
|
3
|
+
*
|
|
4
|
+
* memos_calendar - project mode and student mode
|
|
5
|
+
*/
|
|
6
|
+
import { MEMOS_URL, MEMOS_USER } from "../config.js";
|
|
7
|
+
import { apiCallWithRetry } from "../api-client.js";
|
|
8
|
+
import { ensureCubeRegistered } from "../cube-manager.js";
|
|
9
|
+
import { apiErrorResponse, cubeRegistrationError, getCubeIdFromArgs } from "./utils.js";
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Date Utilities
|
|
12
|
+
// ============================================================================
|
|
13
|
+
function getSemesterDates(semester) {
|
|
14
|
+
const now = new Date();
|
|
15
|
+
if (semester === "current") {
|
|
16
|
+
const year = now.getFullYear();
|
|
17
|
+
const month = now.getMonth() + 1; // 1-based
|
|
18
|
+
if (month >= 9) {
|
|
19
|
+
return [new Date(year, 8, 1), new Date(year, 11, 31)];
|
|
20
|
+
}
|
|
21
|
+
else if (month >= 2) {
|
|
22
|
+
return [new Date(year, 1, 1), new Date(year, 5, 30)];
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
return [new Date(year - 1, 8, 1), new Date(year - 1, 11, 31)];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const parts = semester.split("-");
|
|
29
|
+
if (parts.length === 2) {
|
|
30
|
+
const year = parseInt(parts[0]);
|
|
31
|
+
const season = parts[1].toLowerCase();
|
|
32
|
+
if (!isNaN(year)) {
|
|
33
|
+
if (season === "spring")
|
|
34
|
+
return [new Date(year, 1, 1), new Date(year, 5, 30)];
|
|
35
|
+
if (season === "fall")
|
|
36
|
+
return [new Date(year, 8, 1), new Date(year, 11, 31)];
|
|
37
|
+
if (season === "summer")
|
|
38
|
+
return [new Date(year, 6, 1), new Date(year, 7, 31)];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return [new Date(now.getFullYear(), now.getMonth(), 1), now];
|
|
42
|
+
}
|
|
43
|
+
function getWeekDates(semesterStart, week) {
|
|
44
|
+
const weekStart = new Date(semesterStart.getTime() + (week - 1) * 7 * 24 * 60 * 60 * 1000);
|
|
45
|
+
const dayOfWeek = weekStart.getDay();
|
|
46
|
+
const monday = new Date(weekStart.getTime() - ((dayOfWeek + 6) % 7) * 24 * 60 * 60 * 1000);
|
|
47
|
+
const sunday = new Date(monday.getTime() + 6 * 24 * 60 * 60 * 1000);
|
|
48
|
+
return [monday, sunday];
|
|
49
|
+
}
|
|
50
|
+
// ============================================================================
|
|
51
|
+
// Project Timeline Formatter
|
|
52
|
+
// ============================================================================
|
|
53
|
+
function formatProjectTimeline(memories, cubeId) {
|
|
54
|
+
const lines = [];
|
|
55
|
+
lines.push(`## 🗓️ Project Timeline — ${cubeId}`, "");
|
|
56
|
+
const projectTypes = new Set(["MILESTONE", "DECISION", "FEATURE", "BUGFIX", "ERROR_PATTERN", "GOTCHA"]);
|
|
57
|
+
const projectMems = memories.filter((m) => {
|
|
58
|
+
const t = String(m.memory_type ?? m.type ?? "").toUpperCase();
|
|
59
|
+
return projectTypes.has(t);
|
|
60
|
+
});
|
|
61
|
+
if (projectMems.length === 0) {
|
|
62
|
+
lines.push("_No project milestones or decisions found._", "");
|
|
63
|
+
lines.push('Save memories with: memos_save(..., memory_type="MILESTONE")');
|
|
64
|
+
return lines.join("\n");
|
|
65
|
+
}
|
|
66
|
+
// Group by year-month
|
|
67
|
+
const byMonth = {};
|
|
68
|
+
for (const mem of projectMems) {
|
|
69
|
+
const created = String(mem.created_at ?? mem.timestamp ?? "");
|
|
70
|
+
const monthStr = created.length >= 7 ? created.slice(0, 7) : "unknown";
|
|
71
|
+
if (!byMonth[monthStr])
|
|
72
|
+
byMonth[monthStr] = [];
|
|
73
|
+
byMonth[monthStr].push(mem);
|
|
74
|
+
}
|
|
75
|
+
const sortedMonths = Object.keys(byMonth).sort().reverse();
|
|
76
|
+
for (const monthStr of sortedMonths) {
|
|
77
|
+
const mems = byMonth[monthStr];
|
|
78
|
+
lines.push(`**${monthStr}**`);
|
|
79
|
+
for (let i = 0; i < mems.length; i++) {
|
|
80
|
+
const mem = mems[i];
|
|
81
|
+
const memType = String(mem.memory_type ?? mem.type ?? "NOTE").toUpperCase();
|
|
82
|
+
const content = String(mem.content ?? mem.key ?? "");
|
|
83
|
+
const summary = content.split("\n")[0].slice(0, 80);
|
|
84
|
+
const connector = i === mems.length - 1 ? "└──" : "├──";
|
|
85
|
+
lines.push(` ${connector} [${memType}] ${summary}`);
|
|
86
|
+
}
|
|
87
|
+
lines.push("");
|
|
88
|
+
}
|
|
89
|
+
const typeCounts = {};
|
|
90
|
+
for (const mem of projectMems) {
|
|
91
|
+
const t = String(mem.memory_type ?? "OTHER").toUpperCase();
|
|
92
|
+
typeCounts[t] = (typeCounts[t] ?? 0) + 1;
|
|
93
|
+
}
|
|
94
|
+
lines.push(`📊 Total: ${projectMems.length} entries across ${sortedMonths.length} month(s)`);
|
|
95
|
+
const typeStr = Object.entries(typeCounts).sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k}:${v}`).join(", ");
|
|
96
|
+
lines.push(` Types: ${typeStr}`);
|
|
97
|
+
return lines.join("\n");
|
|
98
|
+
}
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// Calendar View Formatter
|
|
101
|
+
// ============================================================================
|
|
102
|
+
function formatCalendarOutput(memories, semester, course, week, view) {
|
|
103
|
+
const lines = [];
|
|
104
|
+
if (course) {
|
|
105
|
+
lines.push(`## 📅 ${semester} - ${course}`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
lines.push(`## 📅 ${semester} 学期笔记`);
|
|
109
|
+
}
|
|
110
|
+
lines.push("");
|
|
111
|
+
if (memories.length === 0) {
|
|
112
|
+
lines.push("_暂无笔记_");
|
|
113
|
+
return lines.join("\n");
|
|
114
|
+
}
|
|
115
|
+
// Group by date
|
|
116
|
+
const byDate = {};
|
|
117
|
+
for (const mem of memories) {
|
|
118
|
+
const created = String(mem.created_at ?? mem.timestamp ?? "");
|
|
119
|
+
const dateStr = created ? created.slice(0, 10) : new Date().toISOString().slice(0, 10);
|
|
120
|
+
if (!byDate[dateStr])
|
|
121
|
+
byDate[dateStr] = [];
|
|
122
|
+
byDate[dateStr].push(mem);
|
|
123
|
+
}
|
|
124
|
+
const sortedDates = Object.keys(byDate).sort().reverse();
|
|
125
|
+
if (view === "list") {
|
|
126
|
+
lines.push("| 日期 | 类型 | 内容摘要 |");
|
|
127
|
+
lines.push("|------|------|---------|");
|
|
128
|
+
for (const dateStr of sortedDates.slice(0, 20)) {
|
|
129
|
+
for (const mem of byDate[dateStr]) {
|
|
130
|
+
const memType = String(mem.memory_type ?? mem.type ?? "NOTE");
|
|
131
|
+
let content = String(mem.content ?? mem.key ?? "").slice(0, 50);
|
|
132
|
+
if (content.length === 50)
|
|
133
|
+
content += "...";
|
|
134
|
+
content = content.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
135
|
+
lines.push(`| ${dateStr} | ${memType} | ${content} |`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else if (view === "week") {
|
|
140
|
+
if (week)
|
|
141
|
+
lines.push(`### 第 ${week} 周`);
|
|
142
|
+
lines.push("");
|
|
143
|
+
const weekdays = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
|
|
144
|
+
for (const dateStr of sortedDates.slice(0, 7)) {
|
|
145
|
+
try {
|
|
146
|
+
const dt = new Date(dateStr);
|
|
147
|
+
const weekday = weekdays[(dt.getDay() + 6) % 7];
|
|
148
|
+
lines.push(`**${dateStr} ${weekday}**`);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
lines.push(`**${dateStr}**`);
|
|
152
|
+
}
|
|
153
|
+
for (const mem of byDate[dateStr]) {
|
|
154
|
+
const memType = String(mem.memory_type ?? mem.type ?? "NOTE");
|
|
155
|
+
const content = String(mem.content ?? mem.key ?? "").slice(0, 60).replace(/\n/g, " ");
|
|
156
|
+
lines.push(`- [${memType}] ${content}`);
|
|
157
|
+
}
|
|
158
|
+
lines.push("");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
// month view
|
|
163
|
+
lines.push("| 日期 | 笔记数 | 类型分布 |");
|
|
164
|
+
lines.push("|------|--------|---------|");
|
|
165
|
+
for (const dateStr of sortedDates.slice(0, 30)) {
|
|
166
|
+
const mems = byDate[dateStr];
|
|
167
|
+
const types = {};
|
|
168
|
+
for (const m of mems) {
|
|
169
|
+
const t = String(m.memory_type ?? "OTHER");
|
|
170
|
+
types[t] = (types[t] ?? 0) + 1;
|
|
171
|
+
}
|
|
172
|
+
const typeStr = Object.entries(types).map(([k, v]) => `${k}:${v}`).join(", ");
|
|
173
|
+
lines.push(`| ${dateStr} | ${mems.length} | ${typeStr} |`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const total = memories.length;
|
|
177
|
+
const typeCounts = {};
|
|
178
|
+
for (const mem of memories) {
|
|
179
|
+
const t = String(mem.memory_type ?? "OTHER");
|
|
180
|
+
typeCounts[t] = (typeCounts[t] ?? 0) + 1;
|
|
181
|
+
}
|
|
182
|
+
lines.push("");
|
|
183
|
+
lines.push(`📊 总计: ${total} 条笔记`);
|
|
184
|
+
if (Object.keys(typeCounts).length > 0) {
|
|
185
|
+
const typeStr = Object.entries(typeCounts).sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k}:${v}`).join(", ");
|
|
186
|
+
lines.push(` 类型: ${typeStr}`);
|
|
187
|
+
}
|
|
188
|
+
return lines.join("\n");
|
|
189
|
+
}
|
|
190
|
+
// ============================================================================
|
|
191
|
+
// memos_calendar
|
|
192
|
+
// ============================================================================
|
|
193
|
+
export async function handleMemosCalendar(arguments_) {
|
|
194
|
+
const cubeId = getCubeIdFromArgs(arguments_);
|
|
195
|
+
const mode = String(arguments_.mode ?? "student");
|
|
196
|
+
const semester = String(arguments_.semester ?? "current");
|
|
197
|
+
const course = arguments_.course ? String(arguments_.course) : undefined;
|
|
198
|
+
const week = arguments_.week !== undefined ? Number(arguments_.week) : undefined;
|
|
199
|
+
const view = String(arguments_.view ?? "list");
|
|
200
|
+
const [regSuccess, regError] = await ensureCubeRegistered(cubeId);
|
|
201
|
+
if (!regSuccess)
|
|
202
|
+
return cubeRegistrationError(cubeId, regError);
|
|
203
|
+
if (mode === "project") {
|
|
204
|
+
const result = await apiCallWithRetry("GET", `${MEMOS_URL}/memories`, cubeId, { params: { user_id: MEMOS_USER, mem_cube_id: cubeId, limit: 200 } }, ensureCubeRegistered);
|
|
205
|
+
if (result.success && result.data) {
|
|
206
|
+
const resultData = result.data.data ?? {};
|
|
207
|
+
const memories = [];
|
|
208
|
+
for (const cubeData of resultData.text_mem ?? []) {
|
|
209
|
+
const memData = cubeData.memories;
|
|
210
|
+
if (memData && !Array.isArray(memData) && memData.nodes) {
|
|
211
|
+
memories.push(...memData.nodes);
|
|
212
|
+
}
|
|
213
|
+
else if (Array.isArray(memData)) {
|
|
214
|
+
memories.push(...memData);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const output = formatProjectTimeline(memories, cubeId);
|
|
218
|
+
return [{ type: "text", text: output }];
|
|
219
|
+
}
|
|
220
|
+
else if (result.data) {
|
|
221
|
+
return apiErrorResponse("Calendar", String(result.data.message ?? "Unknown error"));
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
return apiErrorResponse("Calendar", `HTTP ${result.status}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Student mode
|
|
228
|
+
let [startDate, endDate] = getSemesterDates(semester);
|
|
229
|
+
if (week !== undefined) {
|
|
230
|
+
[startDate, endDate] = getWeekDates(startDate, week);
|
|
231
|
+
}
|
|
232
|
+
const result = await apiCallWithRetry("GET", `${MEMOS_URL}/memories`, cubeId, { params: { user_id: MEMOS_USER, mem_cube_id: cubeId, limit: 100 } }, ensureCubeRegistered);
|
|
233
|
+
if (result.success && result.data) {
|
|
234
|
+
const resultData = result.data.data ?? {};
|
|
235
|
+
let memories = [];
|
|
236
|
+
for (const cubeData of resultData.text_mem ?? []) {
|
|
237
|
+
const memData = cubeData.memories;
|
|
238
|
+
if (memData && !Array.isArray(memData) && memData.nodes) {
|
|
239
|
+
memories.push(...memData.nodes);
|
|
240
|
+
}
|
|
241
|
+
else if (Array.isArray(memData)) {
|
|
242
|
+
memories.push(...memData);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// Filter by course
|
|
246
|
+
if (course) {
|
|
247
|
+
const courseLower = course.toLowerCase();
|
|
248
|
+
memories = memories.filter((m) => String(m.content ?? "").toLowerCase().includes(courseLower) ||
|
|
249
|
+
String(m.tags ?? "").toLowerCase().includes(courseLower));
|
|
250
|
+
}
|
|
251
|
+
// Filter by date range
|
|
252
|
+
const filtered = [];
|
|
253
|
+
for (const mem of memories) {
|
|
254
|
+
const created = String(mem.created_at ?? "");
|
|
255
|
+
if (created) {
|
|
256
|
+
try {
|
|
257
|
+
const dt = new Date(created.replace("Z", "+00:00"));
|
|
258
|
+
const dtNoTz = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate());
|
|
259
|
+
if (dtNoTz >= startDate && dtNoTz <= endDate) {
|
|
260
|
+
filtered.push(mem);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
filtered.push(mem);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
filtered.push(mem);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const output = formatCalendarOutput(filtered, semester, course, week, view);
|
|
272
|
+
return [{ type: "text", text: output }];
|
|
273
|
+
}
|
|
274
|
+
else if (result.data) {
|
|
275
|
+
return apiErrorResponse("Calendar", String(result.data.message ?? "Unknown error"));
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
return apiErrorResponse("Calendar", `HTTP ${result.status}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=calendar.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph Handlers
|
|
3
|
+
*
|
|
4
|
+
* memos_trace_path, memos_get_graph, memos_export_schema, memos_impact
|
|
5
|
+
*/
|
|
6
|
+
import type { TextContent } from "../types.js";
|
|
7
|
+
export declare function handleMemosTracePath(arguments_: Record<string, unknown>): Promise<TextContent[]>;
|
|
8
|
+
export declare function handleMemosGetGraph(arguments_: Record<string, unknown>): Promise<TextContent[]>;
|
|
9
|
+
export declare function handleMemosExportSchema(arguments_: Record<string, unknown>): Promise<TextContent[]>;
|
|
10
|
+
export declare function handleMemosImpact(arguments_: Record<string, unknown>): Promise<TextContent[]>;
|
|
11
|
+
//# sourceMappingURL=graph.d.ts.map
|