memax-cli 0.1.0-alpha.2 → 0.1.0-alpha.21
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/assets/skills/memax-memory/SKILL.md +154 -0
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +14 -7
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/capture.d.ts +17 -0
- package/dist/commands/capture.d.ts.map +1 -0
- package/dist/commands/capture.js +60 -0
- package/dist/commands/capture.js.map +1 -0
- package/dist/commands/delete.d.ts +1 -1
- package/dist/commands/delete.d.ts.map +1 -1
- package/dist/commands/delete.js +22 -5
- package/dist/commands/delete.js.map +1 -1
- package/dist/commands/hub.d.ts +4 -0
- package/dist/commands/hub.d.ts.map +1 -0
- package/dist/commands/hub.js +53 -0
- package/dist/commands/hub.js.map +1 -0
- package/dist/commands/list.d.ts +2 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +35 -8
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +32 -7
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +226 -26
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/push.d.ts +2 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +49 -11
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/recall.d.ts +1 -0
- package/dist/commands/recall.d.ts.map +1 -1
- package/dist/commands/recall.js +30 -27
- package/dist/commands/recall.js.map +1 -1
- package/dist/commands/setup-hooks.d.ts +12 -0
- package/dist/commands/setup-hooks.d.ts.map +1 -0
- package/dist/commands/setup-hooks.js +184 -0
- package/dist/commands/setup-hooks.js.map +1 -0
- package/dist/commands/setup-instructions.d.ts +21 -0
- package/dist/commands/setup-instructions.d.ts.map +1 -0
- package/dist/commands/setup-instructions.js +172 -0
- package/dist/commands/setup-instructions.js.map +1 -0
- package/dist/commands/setup-mcp.d.ts +14 -0
- package/dist/commands/setup-mcp.d.ts.map +1 -0
- package/dist/commands/setup-mcp.js +276 -0
- package/dist/commands/setup-mcp.js.map +1 -0
- package/dist/commands/setup-types.d.ts +20 -0
- package/dist/commands/setup-types.d.ts.map +1 -0
- package/dist/commands/setup-types.js +60 -0
- package/dist/commands/setup-types.js.map +1 -0
- package/dist/commands/setup.d.ts +18 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +371 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/show.d.ts.map +1 -1
- package/dist/commands/show.js +10 -13
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/sync.d.ts +7 -2
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +614 -107
- package/dist/commands/sync.js.map +1 -1
- package/dist/index.js +85 -14
- package/dist/index.js.map +1 -1
- package/dist/lib/client.d.ts +6 -0
- package/dist/lib/client.d.ts.map +1 -0
- package/dist/lib/client.js +69 -0
- package/dist/lib/client.js.map +1 -0
- package/dist/lib/project-context.d.ts +40 -0
- package/dist/lib/project-context.d.ts.map +1 -0
- package/dist/lib/project-context.js +157 -0
- package/dist/lib/project-context.js.map +1 -0
- package/dist/lib/prompt.d.ts +7 -0
- package/dist/lib/prompt.d.ts.map +1 -0
- package/dist/lib/prompt.js +41 -0
- package/dist/lib/prompt.js.map +1 -0
- package/package.json +17 -13
- package/dist/lib/api.d.ts +0 -4
- package/dist/lib/api.d.ts.map +0 -1
- package/dist/lib/api.js +0 -95
- package/dist/lib/api.js.map +0 -1
- package/src/commands/auth.ts +0 -92
- package/src/commands/config.ts +0 -27
- package/src/commands/delete.ts +0 -20
- package/src/commands/hook.ts +0 -243
- package/src/commands/list.ts +0 -38
- package/src/commands/login.ts +0 -159
- package/src/commands/mcp.ts +0 -282
- package/src/commands/push.ts +0 -82
- package/src/commands/recall.ts +0 -160
- package/src/commands/show.ts +0 -35
- package/src/commands/sync.ts +0 -403
- package/src/index.ts +0 -167
- package/src/lib/api.ts +0 -110
- package/src/lib/config.ts +0 -61
- package/src/lib/credentials.ts +0 -42
- package/tsconfig.json +0 -9
package/src/commands/login.ts
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { createServer } from "node:http";
|
|
2
|
-
import { loadConfig } from "../lib/config.js";
|
|
3
|
-
import { saveCredentials } from "../lib/credentials.js";
|
|
4
|
-
|
|
5
|
-
interface TokenPair {
|
|
6
|
-
access_token: string;
|
|
7
|
-
refresh_token: string;
|
|
8
|
-
expires_in: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export async function loginCommand(): Promise<void> {
|
|
12
|
-
const config = loadConfig();
|
|
13
|
-
const apiUrl = config.api_url;
|
|
14
|
-
|
|
15
|
-
// Start a temporary local server to receive the OAuth callback
|
|
16
|
-
const port = await findFreePort();
|
|
17
|
-
const callbackUrl = `http://localhost:${port}/callback`;
|
|
18
|
-
|
|
19
|
-
const tokenPromise = new Promise<TokenPair>((resolve, reject) => {
|
|
20
|
-
const server = createServer(async (req, res) => {
|
|
21
|
-
const url = new URL(req.url!, `http://localhost:${port}`);
|
|
22
|
-
|
|
23
|
-
if (url.pathname === "/callback") {
|
|
24
|
-
const code = url.searchParams.get("code");
|
|
25
|
-
|
|
26
|
-
if (code) {
|
|
27
|
-
// Exchange the one-time code for tokens via POST
|
|
28
|
-
try {
|
|
29
|
-
const exchangeRes = await fetch(`${apiUrl}/v1/auth/exchange`, {
|
|
30
|
-
method: "POST",
|
|
31
|
-
headers: { "Content-Type": "application/json" },
|
|
32
|
-
body: JSON.stringify({ code }),
|
|
33
|
-
});
|
|
34
|
-
const json = (await exchangeRes.json()) as {
|
|
35
|
-
data?: TokenPair;
|
|
36
|
-
error?: { message: string };
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
if (json.data) {
|
|
40
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
41
|
-
res.end(`
|
|
42
|
-
<html><body style="font-family: system-ui; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0;">
|
|
43
|
-
<div style="text-align: center;">
|
|
44
|
-
<h2 style="font-weight: 600;">Logged in to Memax</h2>
|
|
45
|
-
<p style="color: #64748b;">You can close this tab and return to your terminal.</p>
|
|
46
|
-
</div>
|
|
47
|
-
</body></html>
|
|
48
|
-
`);
|
|
49
|
-
resolve(json.data);
|
|
50
|
-
} else {
|
|
51
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
52
|
-
res.end(
|
|
53
|
-
`<html><body><h2>Login failed</h2><p>${json.error?.message ?? "Token exchange failed."}</p></body></html>`,
|
|
54
|
-
);
|
|
55
|
-
reject(
|
|
56
|
-
new Error(json.error?.message ?? "Token exchange failed."),
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
} catch (err) {
|
|
60
|
-
res.writeHead(500, { "Content-Type": "text/html" });
|
|
61
|
-
res.end(
|
|
62
|
-
"<html><body><h2>Login failed</h2><p>Could not reach Memax API.</p></body></html>",
|
|
63
|
-
);
|
|
64
|
-
reject(new Error("Could not reach Memax API for token exchange."));
|
|
65
|
-
}
|
|
66
|
-
} else {
|
|
67
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
68
|
-
res.end(
|
|
69
|
-
"<html><body><h2>Login failed</h2><p>No authorization code received.</p></body></html>",
|
|
70
|
-
);
|
|
71
|
-
reject(new Error("No authorization code received from callback."));
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Close the server after handling the callback
|
|
75
|
-
setTimeout(() => server.close(), 500);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
server.listen(port);
|
|
80
|
-
|
|
81
|
-
// Timeout after 2 minutes
|
|
82
|
-
setTimeout(() => {
|
|
83
|
-
server.close();
|
|
84
|
-
reject(
|
|
85
|
-
new Error("Login timed out — no callback received within 2 minutes."),
|
|
86
|
-
);
|
|
87
|
-
}, 120_000);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Build the GitHub OAuth URL with our local callback as the redirect
|
|
91
|
-
const authUrl = `${apiUrl}/v1/auth/github?redirect_uri=${encodeURIComponent(callbackUrl)}`;
|
|
92
|
-
|
|
93
|
-
console.log("\n Opening browser for GitHub login...\n");
|
|
94
|
-
console.log(` If the browser doesn't open, visit:\n ${authUrl}\n`);
|
|
95
|
-
|
|
96
|
-
// Try to open the browser
|
|
97
|
-
try {
|
|
98
|
-
const { exec } = await import("node:child_process");
|
|
99
|
-
const cmd =
|
|
100
|
-
process.platform === "darwin"
|
|
101
|
-
? `open "${authUrl}"`
|
|
102
|
-
: process.platform === "win32"
|
|
103
|
-
? `start "${authUrl}"`
|
|
104
|
-
: `xdg-open "${authUrl}"`;
|
|
105
|
-
exec(cmd);
|
|
106
|
-
} catch {
|
|
107
|
-
// Browser open failed — user can copy the URL
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
const tokens = await tokenPromise;
|
|
112
|
-
saveCredentials({
|
|
113
|
-
access_token: tokens.access_token,
|
|
114
|
-
refresh_token: tokens.refresh_token,
|
|
115
|
-
expires_at: Date.now() + tokens.expires_in * 1000,
|
|
116
|
-
});
|
|
117
|
-
console.log(
|
|
118
|
-
" Logged in successfully. Credentials saved to ~/.memax/credentials.json\n",
|
|
119
|
-
);
|
|
120
|
-
} catch (err) {
|
|
121
|
-
console.error(` Login failed: ${(err as Error).message}\n`);
|
|
122
|
-
process.exit(1);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export async function logoutCommand(): Promise<void> {
|
|
127
|
-
const { clearCredentials } = await import("../lib/credentials.js");
|
|
128
|
-
clearCredentials();
|
|
129
|
-
console.log(" Logged out. Credentials cleared.\n");
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export async function whoamiCommand(): Promise<void> {
|
|
133
|
-
const { loadCredentials } = await import("../lib/credentials.js");
|
|
134
|
-
const { apiGet } = await import("../lib/api.js");
|
|
135
|
-
|
|
136
|
-
const creds = loadCredentials();
|
|
137
|
-
if (!creds?.access_token) {
|
|
138
|
-
console.log(" Not logged in. Run: memax login\n");
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
const user = await apiGet<{ name: string; email: string }>("/v1/auth/me");
|
|
144
|
-
console.log(` ${user.name} (${user.email})\n`);
|
|
145
|
-
} catch {
|
|
146
|
-
console.log(" Session expired or invalid. Run: memax login\n");
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async function findFreePort(): Promise<number> {
|
|
151
|
-
return new Promise((resolve) => {
|
|
152
|
-
const server = createServer();
|
|
153
|
-
server.listen(0, () => {
|
|
154
|
-
const addr = server.address();
|
|
155
|
-
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
156
|
-
server.close(() => resolve(port));
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
}
|
package/src/commands/mcp.ts
DELETED
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import {
|
|
5
|
-
CallToolRequestSchema,
|
|
6
|
-
ListToolsRequestSchema,
|
|
7
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
-
import { apiGet, apiPost } from "../lib/api.js";
|
|
9
|
-
|
|
10
|
-
interface RecallResult {
|
|
11
|
-
notes: Array<{
|
|
12
|
-
id: string;
|
|
13
|
-
title: string;
|
|
14
|
-
chunk_content: string;
|
|
15
|
-
heading_chain: string;
|
|
16
|
-
relevance_score: number;
|
|
17
|
-
category: string;
|
|
18
|
-
source: string;
|
|
19
|
-
age: string;
|
|
20
|
-
}>;
|
|
21
|
-
query_metadata: {
|
|
22
|
-
intent: string;
|
|
23
|
-
total_candidates: number;
|
|
24
|
-
latency_ms: number;
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface NoteResult {
|
|
29
|
-
id: string;
|
|
30
|
-
title: string;
|
|
31
|
-
category: string;
|
|
32
|
-
source: string;
|
|
33
|
-
tags: string[];
|
|
34
|
-
summary: string;
|
|
35
|
-
created_at: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function createServer(): Server {
|
|
39
|
-
const server = new Server(
|
|
40
|
-
{ name: "memax", version: "0.0.1" },
|
|
41
|
-
{ capabilities: { tools: {} } },
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
45
|
-
tools: [
|
|
46
|
-
{
|
|
47
|
-
name: "memax_recall",
|
|
48
|
-
description:
|
|
49
|
-
"Search the user's Memax knowledge base with a natural language query. " +
|
|
50
|
-
"Returns relevant memories ranked by relevance. Use this when you need " +
|
|
51
|
-
"background information about the project, team processes, or past decisions.",
|
|
52
|
-
inputSchema: {
|
|
53
|
-
type: "object" as const,
|
|
54
|
-
properties: {
|
|
55
|
-
query: {
|
|
56
|
-
type: "string",
|
|
57
|
-
description: "Natural language search query",
|
|
58
|
-
},
|
|
59
|
-
limit: {
|
|
60
|
-
type: "number",
|
|
61
|
-
description: "Max results to return (default 5)",
|
|
62
|
-
},
|
|
63
|
-
category: {
|
|
64
|
-
type: "string",
|
|
65
|
-
description:
|
|
66
|
-
"Filter by category prefix (e.g. 'core', 'decisions', 'process')",
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
required: ["query"],
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: "memax_push",
|
|
74
|
-
description:
|
|
75
|
-
"Save a piece of knowledge to the user's Memax knowledge base. " +
|
|
76
|
-
"Auto-categorized by AI if no category is provided.",
|
|
77
|
-
inputSchema: {
|
|
78
|
-
type: "object" as const,
|
|
79
|
-
properties: {
|
|
80
|
-
content: {
|
|
81
|
-
type: "string",
|
|
82
|
-
description: "The knowledge content to save",
|
|
83
|
-
},
|
|
84
|
-
title: {
|
|
85
|
-
type: "string",
|
|
86
|
-
description: "Optional title (auto-generated if omitted)",
|
|
87
|
-
},
|
|
88
|
-
category: {
|
|
89
|
-
type: "string",
|
|
90
|
-
description:
|
|
91
|
-
"Category (e.g. 'decisions/adr', 'core/architecture'). Auto-detected if omitted.",
|
|
92
|
-
},
|
|
93
|
-
tags: {
|
|
94
|
-
type: "array",
|
|
95
|
-
items: { type: "string" },
|
|
96
|
-
description: "Tags for the memory",
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
required: ["content"],
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
name: "memax_search",
|
|
104
|
-
description:
|
|
105
|
-
"List notes filtered by category or tags. " +
|
|
106
|
-
"For browsing and structured lookups, not semantic search. " +
|
|
107
|
-
"Use memax_recall for natural language queries instead.",
|
|
108
|
-
inputSchema: {
|
|
109
|
-
type: "object" as const,
|
|
110
|
-
properties: {
|
|
111
|
-
category: {
|
|
112
|
-
type: "string",
|
|
113
|
-
description: "Filter by category prefix",
|
|
114
|
-
},
|
|
115
|
-
limit: {
|
|
116
|
-
type: "number",
|
|
117
|
-
description: "Max results to return (default 10)",
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
],
|
|
123
|
-
}));
|
|
124
|
-
|
|
125
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
126
|
-
const { name, arguments: args } = request.params;
|
|
127
|
-
|
|
128
|
-
switch (name) {
|
|
129
|
-
case "memax_recall": {
|
|
130
|
-
const typedArgs = args as {
|
|
131
|
-
query: string;
|
|
132
|
-
limit?: number;
|
|
133
|
-
category?: string;
|
|
134
|
-
};
|
|
135
|
-
try {
|
|
136
|
-
const result = await apiPost<RecallResult>("/v1/recall", {
|
|
137
|
-
query: typedArgs.query,
|
|
138
|
-
limit: typedArgs.limit ?? 5,
|
|
139
|
-
category: typedArgs.category ?? "",
|
|
140
|
-
source: "mcp",
|
|
141
|
-
working_dir: process.cwd(),
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
const notes = result.notes ?? [];
|
|
145
|
-
if (notes.length === 0) {
|
|
146
|
-
return {
|
|
147
|
-
content: [{ type: "text" as const, text: "No results found." }],
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const formatted = notes
|
|
152
|
-
.map((n, i) => {
|
|
153
|
-
const score = (n.relevance_score * 100).toFixed(0);
|
|
154
|
-
const heading = n.heading_chain ? ` — ${n.heading_chain}` : "";
|
|
155
|
-
return (
|
|
156
|
-
`[${i + 1}] ${n.title} [${n.category}, ${score}%, ${n.age}]${heading}\n` +
|
|
157
|
-
n.chunk_content
|
|
158
|
-
);
|
|
159
|
-
})
|
|
160
|
-
.join("\n\n");
|
|
161
|
-
|
|
162
|
-
return { content: [{ type: "text" as const, text: formatted }] };
|
|
163
|
-
} catch (err) {
|
|
164
|
-
return {
|
|
165
|
-
content: [
|
|
166
|
-
{
|
|
167
|
-
type: "text" as const,
|
|
168
|
-
text: `Recall failed: ${(err as Error).message}`,
|
|
169
|
-
},
|
|
170
|
-
],
|
|
171
|
-
isError: true,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
case "memax_push": {
|
|
177
|
-
const typedArgs = args as {
|
|
178
|
-
content: string;
|
|
179
|
-
title?: string;
|
|
180
|
-
category?: string;
|
|
181
|
-
tags?: string[];
|
|
182
|
-
};
|
|
183
|
-
try {
|
|
184
|
-
const note = await apiPost<NoteResult>("/v1/notes", {
|
|
185
|
-
content: typedArgs.content,
|
|
186
|
-
title: typedArgs.title ?? "",
|
|
187
|
-
category: typedArgs.category ?? "",
|
|
188
|
-
tags: typedArgs.tags ?? [],
|
|
189
|
-
source: "mcp",
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
content: [
|
|
194
|
-
{
|
|
195
|
-
type: "text" as const,
|
|
196
|
-
text: `Saved: ${note.title} (id: ${note.id})`,
|
|
197
|
-
},
|
|
198
|
-
],
|
|
199
|
-
};
|
|
200
|
-
} catch (err) {
|
|
201
|
-
return {
|
|
202
|
-
content: [
|
|
203
|
-
{
|
|
204
|
-
type: "text" as const,
|
|
205
|
-
text: `Push failed: ${(err as Error).message}`,
|
|
206
|
-
},
|
|
207
|
-
],
|
|
208
|
-
isError: true,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
case "memax_search": {
|
|
214
|
-
const typedArgs = args as {
|
|
215
|
-
category?: string;
|
|
216
|
-
limit?: number;
|
|
217
|
-
};
|
|
218
|
-
try {
|
|
219
|
-
const notes = (await apiGet<NoteResult[]>("/v1/notes")) ?? [];
|
|
220
|
-
|
|
221
|
-
let filtered = notes;
|
|
222
|
-
if (typedArgs.category) {
|
|
223
|
-
filtered = notes.filter((n) =>
|
|
224
|
-
n.category.startsWith(typedArgs.category!),
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const limited = filtered.slice(0, typedArgs.limit ?? 10);
|
|
229
|
-
|
|
230
|
-
if (limited.length === 0) {
|
|
231
|
-
return {
|
|
232
|
-
content: [{ type: "text" as const, text: "No notes found." }],
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const formatted = limited
|
|
237
|
-
.map(
|
|
238
|
-
(n) => `- ${n.title} [${n.category}] — ${n.source} (id: ${n.id})`,
|
|
239
|
-
)
|
|
240
|
-
.join("\n");
|
|
241
|
-
|
|
242
|
-
return { content: [{ type: "text" as const, text: formatted }] };
|
|
243
|
-
} catch (err) {
|
|
244
|
-
return {
|
|
245
|
-
content: [
|
|
246
|
-
{
|
|
247
|
-
type: "text" as const,
|
|
248
|
-
text: `Search failed: ${(err as Error).message}`,
|
|
249
|
-
},
|
|
250
|
-
],
|
|
251
|
-
isError: true,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
default:
|
|
257
|
-
return {
|
|
258
|
-
content: [{ type: "text" as const, text: `Unknown tool: ${name}` }],
|
|
259
|
-
isError: true,
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
return server;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
async function mcpServeCommand(): Promise<void> {
|
|
268
|
-
const server = createServer();
|
|
269
|
-
const transport = new StdioServerTransport();
|
|
270
|
-
await server.connect(transport);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
export function registerMcpCommand(program: Command): void {
|
|
274
|
-
const mcp = program
|
|
275
|
-
.command("mcp")
|
|
276
|
-
.description("Model Context Protocol server for AI agents");
|
|
277
|
-
|
|
278
|
-
mcp
|
|
279
|
-
.command("serve")
|
|
280
|
-
.description("Start MCP server on stdio")
|
|
281
|
-
.action(mcpServeCommand);
|
|
282
|
-
}
|
package/src/commands/push.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { basename } from "node:path";
|
|
4
|
-
import { apiPost } from "../lib/api.js";
|
|
5
|
-
import type { Note } from "@memaxlabs/shared";
|
|
6
|
-
|
|
7
|
-
interface PushOptions {
|
|
8
|
-
file?: string;
|
|
9
|
-
category?: string;
|
|
10
|
-
tags?: string;
|
|
11
|
-
ttl?: string;
|
|
12
|
-
stdin?: boolean;
|
|
13
|
-
title?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function pushCommand(options: PushOptions): Promise<void> {
|
|
17
|
-
let content: string;
|
|
18
|
-
let title = options.title ?? "";
|
|
19
|
-
let sourcePath = "";
|
|
20
|
-
|
|
21
|
-
if (options.file) {
|
|
22
|
-
try {
|
|
23
|
-
content = readFileSync(options.file, "utf-8");
|
|
24
|
-
sourcePath = options.file;
|
|
25
|
-
if (!title) {
|
|
26
|
-
title = basename(options.file);
|
|
27
|
-
}
|
|
28
|
-
} catch (err) {
|
|
29
|
-
console.error(chalk.red(`Error reading file: ${options.file}`));
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
} else if (options.stdin || !process.stdin.isTTY) {
|
|
33
|
-
const chunks: Buffer[] = [];
|
|
34
|
-
for await (const chunk of process.stdin) {
|
|
35
|
-
chunks.push(chunk);
|
|
36
|
-
}
|
|
37
|
-
content = Buffer.concat(chunks).toString("utf-8");
|
|
38
|
-
} else {
|
|
39
|
-
console.error(chalk.red("Provide --file <path> or pipe content via stdin"));
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (!content.trim()) {
|
|
44
|
-
console.error(chalk.red("No content to push"));
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const tags = options.tags ? options.tags.split(",").map((t) => t.trim()) : [];
|
|
49
|
-
|
|
50
|
-
// Auto-detect URL content: single-line http(s) URL -> content_type "link"
|
|
51
|
-
const trimmed = content.trim();
|
|
52
|
-
const isURL =
|
|
53
|
-
(trimmed.startsWith("http://") || trimmed.startsWith("https://")) &&
|
|
54
|
-
!trimmed.includes("\n");
|
|
55
|
-
|
|
56
|
-
let contentType = sourcePath.endsWith(".md") ? "markdown" : "text";
|
|
57
|
-
if (isURL) {
|
|
58
|
-
contentType = "link";
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
const note = await apiPost<Note>("/v1/notes", {
|
|
63
|
-
content,
|
|
64
|
-
title,
|
|
65
|
-
category: options.category ?? "",
|
|
66
|
-
tags,
|
|
67
|
-
source: "cli",
|
|
68
|
-
source_path: sourcePath,
|
|
69
|
-
content_type: contentType,
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
console.log(chalk.green("Saved"), chalk.bold(note.title));
|
|
73
|
-
console.log(
|
|
74
|
-
chalk.gray(
|
|
75
|
-
` id: ${note.id} category: ${note.category} source: ${note.source}`,
|
|
76
|
-
),
|
|
77
|
-
);
|
|
78
|
-
} catch (err) {
|
|
79
|
-
console.error(chalk.red(`Push failed: ${(err as Error).message}`));
|
|
80
|
-
process.exit(1);
|
|
81
|
-
}
|
|
82
|
-
}
|
package/src/commands/recall.ts
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { apiPost } from "../lib/api.js";
|
|
3
|
-
|
|
4
|
-
interface RecallResult {
|
|
5
|
-
notes: Array<{
|
|
6
|
-
id: string;
|
|
7
|
-
title: string;
|
|
8
|
-
chunk_content: string;
|
|
9
|
-
heading_chain: string;
|
|
10
|
-
relevance_score: number;
|
|
11
|
-
category: string;
|
|
12
|
-
source: string;
|
|
13
|
-
age: string;
|
|
14
|
-
}>;
|
|
15
|
-
query_metadata: {
|
|
16
|
-
intent: string;
|
|
17
|
-
total_candidates: number;
|
|
18
|
-
latency_ms: number;
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface RecallOptions {
|
|
23
|
-
category?: string;
|
|
24
|
-
tags?: string;
|
|
25
|
-
limit?: string;
|
|
26
|
-
format?: string;
|
|
27
|
-
includeArchived?: boolean;
|
|
28
|
-
hook?: boolean;
|
|
29
|
-
maxTokens?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export async function recallCommand(
|
|
33
|
-
query: string | undefined,
|
|
34
|
-
options: RecallOptions,
|
|
35
|
-
): Promise<void> {
|
|
36
|
-
if (!query) {
|
|
37
|
-
// Read from stdin if no query argument
|
|
38
|
-
if (!process.stdin.isTTY) {
|
|
39
|
-
const chunks: Buffer[] = [];
|
|
40
|
-
for await (const chunk of process.stdin) {
|
|
41
|
-
chunks.push(chunk);
|
|
42
|
-
}
|
|
43
|
-
query = Buffer.concat(chunks).toString("utf-8").trim();
|
|
44
|
-
}
|
|
45
|
-
if (!query) {
|
|
46
|
-
console.error(chalk.red('Provide a query: memax recall "your question"'));
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const limit = parseInt(options.limit ?? "5", 10);
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
const result = await apiPost<RecallResult>("/v1/recall", {
|
|
55
|
-
query,
|
|
56
|
-
category: options.category ?? "",
|
|
57
|
-
limit,
|
|
58
|
-
include_archived: options.includeArchived ?? false,
|
|
59
|
-
source: options.hook ? "hook" : "cli",
|
|
60
|
-
working_dir: process.cwd(),
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const notes = result.notes ?? [];
|
|
64
|
-
|
|
65
|
-
if (options.format === "json") {
|
|
66
|
-
console.log(JSON.stringify({ ...result, notes }, null, 2));
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Hook mode: output clean context for agent injection
|
|
71
|
-
if (options.hook) {
|
|
72
|
-
if (notes.length === 0) return;
|
|
73
|
-
let output = "<memax-context>\n";
|
|
74
|
-
output += "## Relevant Context (from Memax)\n\n";
|
|
75
|
-
for (const note of notes) {
|
|
76
|
-
const heading = note.heading_chain ? ` — ${note.heading_chain}` : "";
|
|
77
|
-
output += `### ${note.title} [${note.category}, ${note.age}]${heading}\n`;
|
|
78
|
-
output += note.chunk_content + "\n\n";
|
|
79
|
-
}
|
|
80
|
-
output += "</memax-context>";
|
|
81
|
-
|
|
82
|
-
// Truncate to fit within token budget (1 token ≈ 4 characters)
|
|
83
|
-
const maxTokens = options.maxTokens
|
|
84
|
-
? parseInt(options.maxTokens, 10)
|
|
85
|
-
: undefined;
|
|
86
|
-
if (maxTokens) {
|
|
87
|
-
const maxChars = maxTokens * 4;
|
|
88
|
-
if (output.length > maxChars) {
|
|
89
|
-
output =
|
|
90
|
-
output.substring(0, maxChars - 50) +
|
|
91
|
-
"\n\n[truncated to fit token budget]\n</memax-context>";
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
console.log(output);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Pipe-friendly plain text output when stdout is not a TTY
|
|
100
|
-
if (!process.stdout.isTTY) {
|
|
101
|
-
if (notes.length === 0) {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
for (const note of notes) {
|
|
105
|
-
const score = (note.relevance_score * 100).toFixed(0);
|
|
106
|
-
console.log(`${note.title} [${note.category}] ${score}% · ${note.age}`);
|
|
107
|
-
if (note.heading_chain) {
|
|
108
|
-
console.log(` ${note.heading_chain}`);
|
|
109
|
-
}
|
|
110
|
-
const lines = note.chunk_content.split("\n").slice(0, 3);
|
|
111
|
-
for (const line of lines) {
|
|
112
|
-
console.log(` ${line}`);
|
|
113
|
-
}
|
|
114
|
-
console.log();
|
|
115
|
-
}
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Human-readable output
|
|
120
|
-
if (notes.length === 0) {
|
|
121
|
-
console.log(chalk.yellow("No results found."));
|
|
122
|
-
console.log(
|
|
123
|
-
chalk.gray(
|
|
124
|
-
` Searched ${result.query_metadata.total_candidates} chunks in ${result.query_metadata.latency_ms}ms`,
|
|
125
|
-
),
|
|
126
|
-
);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
console.log(
|
|
131
|
-
chalk.blue(`${notes.length} result${notes.length > 1 ? "s" : ""}`),
|
|
132
|
-
chalk.gray(
|
|
133
|
-
`(${result.query_metadata.total_candidates} chunks searched, ${result.query_metadata.latency_ms}ms)`,
|
|
134
|
-
),
|
|
135
|
-
);
|
|
136
|
-
console.log();
|
|
137
|
-
|
|
138
|
-
for (const note of notes) {
|
|
139
|
-
const score = (note.relevance_score * 100).toFixed(0);
|
|
140
|
-
console.log(
|
|
141
|
-
chalk.bold(note.title),
|
|
142
|
-
chalk.gray(`[${note.category}]`),
|
|
143
|
-
chalk.cyan(`${score}%`),
|
|
144
|
-
chalk.gray(`· ${note.age}`),
|
|
145
|
-
);
|
|
146
|
-
if (note.heading_chain) {
|
|
147
|
-
console.log(chalk.gray(` ${note.heading_chain}`));
|
|
148
|
-
}
|
|
149
|
-
// Show first 3 lines of chunk content
|
|
150
|
-
const lines = note.chunk_content.split("\n").slice(0, 3);
|
|
151
|
-
for (const line of lines) {
|
|
152
|
-
console.log(chalk.white(` ${line}`));
|
|
153
|
-
}
|
|
154
|
-
console.log();
|
|
155
|
-
}
|
|
156
|
-
} catch (err) {
|
|
157
|
-
console.error(chalk.red(`Recall failed: ${(err as Error).message}`));
|
|
158
|
-
process.exit(1);
|
|
159
|
-
}
|
|
160
|
-
}
|