memax-cli 0.1.0-alpha.12 → 0.1.0-alpha.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.
Files changed (66) hide show
  1. package/assets/memax-memory-skill.md +154 -0
  2. package/dist/commands/auth.d.ts +1 -0
  3. package/dist/commands/auth.d.ts.map +1 -1
  4. package/dist/commands/auth.js +9 -2
  5. package/dist/commands/auth.js.map +1 -1
  6. package/dist/commands/capture.d.ts +1 -1
  7. package/dist/commands/capture.d.ts.map +1 -1
  8. package/dist/commands/capture.js +3 -3
  9. package/dist/commands/capture.js.map +1 -1
  10. package/dist/commands/delete.js +5 -5
  11. package/dist/commands/delete.js.map +1 -1
  12. package/dist/commands/hub.d.ts +4 -0
  13. package/dist/commands/hub.d.ts.map +1 -0
  14. package/dist/commands/hub.js +52 -0
  15. package/dist/commands/hub.js.map +1 -0
  16. package/dist/commands/list.d.ts.map +1 -1
  17. package/dist/commands/list.js +14 -14
  18. package/dist/commands/list.js.map +1 -1
  19. package/dist/commands/login.d.ts.map +1 -1
  20. package/dist/commands/login.js +22 -2
  21. package/dist/commands/login.js.map +1 -1
  22. package/dist/commands/mcp.d.ts.map +1 -1
  23. package/dist/commands/mcp.js +37 -35
  24. package/dist/commands/mcp.js.map +1 -1
  25. package/dist/commands/push.d.ts +1 -0
  26. package/dist/commands/push.d.ts.map +1 -1
  27. package/dist/commands/push.js +69 -6
  28. package/dist/commands/push.js.map +1 -1
  29. package/dist/commands/recall.d.ts +1 -0
  30. package/dist/commands/recall.d.ts.map +1 -1
  31. package/dist/commands/recall.js +62 -24
  32. package/dist/commands/recall.js.map +1 -1
  33. package/dist/commands/setup.d.ts +1 -0
  34. package/dist/commands/setup.d.ts.map +1 -1
  35. package/dist/commands/setup.js +76 -4
  36. package/dist/commands/setup.js.map +1 -1
  37. package/dist/commands/show.js +9 -9
  38. package/dist/commands/show.js.map +1 -1
  39. package/dist/commands/sync.js +3 -3
  40. package/dist/commands/sync.js.map +1 -1
  41. package/dist/index.js +20 -4
  42. package/dist/index.js.map +1 -1
  43. package/dist/lib/api.d.ts +4 -3
  44. package/dist/lib/api.d.ts.map +1 -1
  45. package/dist/lib/api.js +83 -40
  46. package/dist/lib/api.js.map +1 -1
  47. package/package.json +6 -1
  48. package/.vscode/mcp.json +0 -8
  49. package/src/commands/auth.ts +0 -92
  50. package/src/commands/capture.ts +0 -86
  51. package/src/commands/config.ts +0 -27
  52. package/src/commands/delete.ts +0 -58
  53. package/src/commands/hook.ts +0 -243
  54. package/src/commands/list.ts +0 -92
  55. package/src/commands/login.ts +0 -164
  56. package/src/commands/mcp.ts +0 -528
  57. package/src/commands/push.ts +0 -137
  58. package/src/commands/recall.ts +0 -163
  59. package/src/commands/setup.ts +0 -1129
  60. package/src/commands/show.ts +0 -35
  61. package/src/commands/sync.ts +0 -506
  62. package/src/index.ts +0 -210
  63. package/src/lib/api.ts +0 -110
  64. package/src/lib/config.ts +0 -61
  65. package/src/lib/credentials.ts +0 -42
  66. package/tsconfig.json +0 -9
@@ -1,243 +0,0 @@
1
- import chalk from "chalk";
2
- import {
3
- readFileSync,
4
- writeFileSync,
5
- mkdirSync,
6
- existsSync,
7
- chmodSync,
8
- } from "node:fs";
9
- import { join } from "node:path";
10
- import { homedir } from "node:os";
11
-
12
- type Agent = "claude-code";
13
-
14
- const SUPPORTED_AGENTS: Agent[] = ["claude-code"];
15
-
16
- export function hookCommand(action: string, agent: string): void {
17
- if (!SUPPORTED_AGENTS.includes(agent as Agent)) {
18
- console.error(chalk.red(`Unsupported agent: ${agent}`));
19
- console.error(chalk.gray(`Supported: ${SUPPORTED_AGENTS.join(", ")}`));
20
- process.exit(1);
21
- }
22
-
23
- switch (action) {
24
- case "install":
25
- installHook(agent as Agent);
26
- break;
27
- case "uninstall":
28
- uninstallHook(agent as Agent);
29
- break;
30
- default:
31
- console.error(
32
- chalk.red(`Unknown action: ${action}. Use install or uninstall.`),
33
- );
34
- process.exit(1);
35
- }
36
- }
37
-
38
- function installHook(agent: Agent): void {
39
- switch (agent) {
40
- case "claude-code":
41
- installClaudeCodeHook();
42
- break;
43
- }
44
- }
45
-
46
- function uninstallHook(agent: Agent): void {
47
- switch (agent) {
48
- case "claude-code":
49
- uninstallClaudeCodeHook();
50
- break;
51
- }
52
- }
53
-
54
- function installClaudeCodeHook(): void {
55
- // Write the hook script to ~/.memax/hooks/
56
- const hooksDir = join(homedir(), ".memax", "hooks");
57
- mkdirSync(hooksDir, { recursive: true });
58
-
59
- const scriptPath = join(hooksDir, "claude-code-recall.sh");
60
- writeFileSync(scriptPath, CLAUDE_CODE_HOOK_SCRIPT);
61
- chmodSync(scriptPath, 0o755);
62
-
63
- // Update Claude Code settings (~/.claude/settings.json)
64
- const claudeSettingsDir = join(homedir(), ".claude");
65
- const claudeSettingsFile = join(claudeSettingsDir, "settings.json");
66
- mkdirSync(claudeSettingsDir, { recursive: true });
67
-
68
- let settings: Record<string, unknown> = {};
69
- if (existsSync(claudeSettingsFile)) {
70
- try {
71
- settings = JSON.parse(readFileSync(claudeSettingsFile, "utf-8"));
72
- } catch {
73
- // Start fresh if parse fails
74
- }
75
- }
76
-
77
- // Add the hook
78
- const hooks = (settings.hooks ?? {}) as Record<string, unknown[]>;
79
- const hookEntry = {
80
- matcher: "",
81
- hooks: [
82
- {
83
- type: "command",
84
- command: scriptPath,
85
- timeout: 30,
86
- },
87
- ],
88
- };
89
-
90
- // Check if already installed
91
- const existing = hooks["UserPromptSubmit"] as
92
- | Array<{ hooks?: Array<{ command?: string }> }>
93
- | undefined;
94
- if (
95
- existing?.some((h) => h.hooks?.some((hh) => hh.command?.includes("memax")))
96
- ) {
97
- console.log(
98
- chalk.yellow("Memax hook is already installed for Claude Code."),
99
- );
100
- return;
101
- }
102
-
103
- hooks["UserPromptSubmit"] = [...(hooks["UserPromptSubmit"] ?? []), hookEntry];
104
- settings.hooks = hooks;
105
-
106
- writeFileSync(claudeSettingsFile, JSON.stringify(settings, null, 2) + "\n");
107
-
108
- console.log(chalk.green("Memax hook installed for Claude Code"));
109
- console.log();
110
- console.log(chalk.gray(" Hook script:"), scriptPath);
111
- console.log(chalk.gray(" Settings:"), claudeSettingsFile);
112
- console.log();
113
- console.log(
114
- chalk.gray(
115
- " Every prompt you type in Claude Code will now recall relevant\n" +
116
- " knowledge from Memax and inject it as context automatically.",
117
- ),
118
- );
119
- console.log();
120
- console.log(chalk.gray(" Make sure the Memax API server is running:"));
121
- console.log(chalk.gray(" cd packages/server && go run ./cmd/server"));
122
- console.log();
123
- console.log(
124
- chalk.gray(" To also enable MCP tools, add to your Claude Code settings:"),
125
- );
126
- console.log(
127
- chalk.gray(
128
- ` {
129
- "mcpServers": {
130
- "memax": {
131
- "command": "memax",
132
- "args": ["mcp", "serve"]
133
- }
134
- }
135
- }`,
136
- ),
137
- );
138
- }
139
-
140
- function uninstallClaudeCodeHook(): void {
141
- const claudeSettingsFile = join(homedir(), ".claude", "settings.json");
142
-
143
- if (!existsSync(claudeSettingsFile)) {
144
- console.log(
145
- chalk.yellow("No Claude Code settings found. Nothing to uninstall."),
146
- );
147
- return;
148
- }
149
-
150
- try {
151
- const settings = JSON.parse(readFileSync(claudeSettingsFile, "utf-8"));
152
- const hooks = settings.hooks as Record<string, unknown[]> | undefined;
153
-
154
- if (hooks?.["UserPromptSubmit"]) {
155
- hooks["UserPromptSubmit"] = (
156
- hooks["UserPromptSubmit"] as Array<{
157
- hooks?: Array<{ command?: string }>;
158
- }>
159
- ).filter((h) => !h.hooks?.some((hh) => hh.command?.includes("memax")));
160
-
161
- if ((hooks["UserPromptSubmit"] as unknown[]).length === 0) {
162
- delete hooks["UserPromptSubmit"];
163
- }
164
-
165
- settings.hooks = hooks;
166
- writeFileSync(
167
- claudeSettingsFile,
168
- JSON.stringify(settings, null, 2) + "\n",
169
- );
170
- }
171
-
172
- console.log(chalk.green("Memax hook uninstalled from Claude Code."));
173
- } catch {
174
- console.error(chalk.red("Failed to read Claude Code settings."));
175
- process.exit(1);
176
- }
177
- }
178
-
179
- const CLAUDE_CODE_HOOK_SCRIPT = `#!/bin/bash
180
- # Memax context injection hook for Claude Code
181
- # Installed by: memax hook install claude-code
182
- # This runs on every prompt, recalls relevant knowledge, and injects it.
183
-
184
- set -e
185
-
186
- # Read hook input from stdin
187
- INPUT=$(cat)
188
- PROMPT=$(echo "$INPUT" | jq -r '.prompt // empty')
189
- CWD=$(echo "$INPUT" | jq -r '.cwd // empty')
190
-
191
- # Skip if no prompt
192
- if [ -z "$PROMPT" ]; then
193
- exit 0
194
- fi
195
-
196
- # Skip trivial prompts (less than 10 chars, or just "yes"/"no"/etc.)
197
- if [ \${#PROMPT} -lt 10 ]; then
198
- exit 0
199
- fi
200
-
201
- # Skip if prompt looks like a simple confirmation
202
- case "$PROMPT" in
203
- [Yy]|[Yy]es|[Nn]|[Nn]o|ok|OK|Ok|sure|Sure|thanks|Thanks|y|n)
204
- exit 0
205
- ;;
206
- esac
207
-
208
- # Change to the session's working directory
209
- if [ -n "$CWD" ]; then
210
- cd "$CWD" 2>/dev/null || true
211
- fi
212
-
213
- # Find memax CLI — check common locations
214
- MEMAX=""
215
- if command -v memax &>/dev/null; then
216
- MEMAX="memax"
217
- elif [ -x "$HOME/.memax/bin/memax" ]; then
218
- MEMAX="$HOME/.memax/bin/memax"
219
- else
220
- # Try to find it via node
221
- for dir in "$CWD" "$HOME"; do
222
- if [ -f "$dir/packages/cli/dist/index.js" ]; then
223
- MEMAX="node $dir/packages/cli/dist/index.js"
224
- break
225
- fi
226
- done
227
- fi
228
-
229
- # Gracefully exit if memax not found
230
- if [ -z "$MEMAX" ]; then
231
- exit 0
232
- fi
233
-
234
- # Recall context and output it (stdout is injected into Claude's context)
235
- CONTEXT=$($MEMAX recall "$PROMPT" --hook --limit 5 --max-tokens 3000 2>/dev/null) || exit 0
236
-
237
- # Only output if we got results
238
- if [ -n "$CONTEXT" ] && [ "$CONTEXT" != "<memax-context>" ]; then
239
- echo "$CONTEXT"
240
- fi
241
-
242
- exit 0
243
- `;
@@ -1,92 +0,0 @@
1
- import chalk from "chalk";
2
- import { apiGet } from "../lib/api.js";
3
- import type { Note } from "@memaxlabs/shared";
4
-
5
- interface ListResponse {
6
- notes: Note[];
7
- next_cursor: string;
8
- has_more: boolean;
9
- total: number;
10
- }
11
-
12
- interface ListOptions {
13
- category?: string;
14
- sort?: string;
15
- limit?: string;
16
- all?: boolean;
17
- }
18
-
19
- export async function listCommand(options: ListOptions): Promise<void> {
20
- try {
21
- const limit = options.limit ? parseInt(options.limit, 10) : 20;
22
- const sort = options.sort ?? "newest";
23
- let allNotes: Note[] = [];
24
- let cursor = "";
25
- let total = 0;
26
-
27
- // Fetch pages
28
- do {
29
- const params = new URLSearchParams();
30
- params.set("limit", String(limit));
31
- params.set("sort", sort);
32
- if (cursor) params.set("cursor", cursor);
33
-
34
- const res = await apiGet<ListResponse | Note[]>(
35
- `/v1/notes?${params.toString()}`,
36
- );
37
-
38
- if (Array.isArray(res)) {
39
- allNotes = res;
40
- total = res.length;
41
- break;
42
- }
43
-
44
- allNotes.push(...(res.notes ?? []));
45
- cursor = res.next_cursor;
46
- total = res.total;
47
-
48
- // If not --all, just show first page
49
- if (!options.all) break;
50
- } while (cursor);
51
-
52
- // Filter by category if specified
53
- let notes = allNotes;
54
- if (options.category) {
55
- notes = notes.filter((n) =>
56
- n.category.toLowerCase().startsWith(options.category!.toLowerCase()),
57
- );
58
- }
59
-
60
- if (notes.length === 0) {
61
- console.log(chalk.yellow("No notes yet. Push your first one:"));
62
- console.log(chalk.gray(" memax push --file ./README.md"));
63
- return;
64
- }
65
-
66
- console.log(
67
- chalk.blue(`${notes.length} note${notes.length > 1 ? "s" : ""}`),
68
- chalk.gray(`(${total} total)`),
69
- );
70
- console.log();
71
-
72
- for (const note of notes) {
73
- console.log(
74
- chalk.bold(note.title),
75
- chalk.gray(`[${note.category}]`),
76
- chalk.gray(`· ${note.source}`),
77
- );
78
- console.log(chalk.gray(` id: ${note.id}`));
79
- }
80
-
81
- if (!options.all && allNotes.length < total) {
82
- console.log(
83
- chalk.gray(
84
- `\n Showing ${allNotes.length} of ${total}. Use --all to fetch everything.`,
85
- ),
86
- );
87
- }
88
- } catch (err) {
89
- console.error(chalk.red(`List failed: ${(err as Error).message}`));
90
- process.exit(1);
91
- }
92
- }
@@ -1,164 +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 and force-kill connections so the process exits
75
- setTimeout(() => {
76
- server.close();
77
- server.closeAllConnections();
78
- }, 500);
79
- }
80
- });
81
-
82
- server.listen(port);
83
-
84
- // Timeout after 2 minutes — use unref() so it doesn't keep the process alive
85
- const timeout = setTimeout(() => {
86
- server.close();
87
- server.closeAllConnections();
88
- reject(
89
- new Error("Login timed out — no callback received within 2 minutes."),
90
- );
91
- }, 120_000);
92
- timeout.unref();
93
- });
94
-
95
- // Build the GitHub OAuth URL with our local callback as the redirect
96
- const authUrl = `${apiUrl}/v1/auth/github?redirect_uri=${encodeURIComponent(callbackUrl)}`;
97
-
98
- console.log("\n Opening browser for GitHub login...\n");
99
- console.log(` If the browser doesn't open, visit:\n ${authUrl}\n`);
100
-
101
- // Try to open the browser
102
- try {
103
- const { exec } = await import("node:child_process");
104
- const cmd =
105
- process.platform === "darwin"
106
- ? `open "${authUrl}"`
107
- : process.platform === "win32"
108
- ? `start "${authUrl}"`
109
- : `xdg-open "${authUrl}"`;
110
- exec(cmd);
111
- } catch {
112
- // Browser open failed — user can copy the URL
113
- }
114
-
115
- try {
116
- const tokens = await tokenPromise;
117
- saveCredentials({
118
- access_token: tokens.access_token,
119
- refresh_token: tokens.refresh_token,
120
- expires_at: Date.now() + tokens.expires_in * 1000,
121
- });
122
- console.log(
123
- " Logged in successfully. Credentials saved to ~/.memax/credentials.json\n",
124
- );
125
- } catch (err) {
126
- console.error(` Login failed: ${(err as Error).message}\n`);
127
- process.exit(1);
128
- }
129
- }
130
-
131
- export async function logoutCommand(): Promise<void> {
132
- const { clearCredentials } = await import("../lib/credentials.js");
133
- clearCredentials();
134
- console.log(" Logged out. Credentials cleared.\n");
135
- }
136
-
137
- export async function whoamiCommand(): Promise<void> {
138
- const { loadCredentials } = await import("../lib/credentials.js");
139
- const { apiGet } = await import("../lib/api.js");
140
-
141
- const creds = loadCredentials();
142
- if (!creds?.access_token) {
143
- console.log(" Not logged in. Run: memax login\n");
144
- return;
145
- }
146
-
147
- try {
148
- const user = await apiGet<{ name: string; email: string }>("/v1/auth/me");
149
- console.log(` ${user.name} (${user.email})\n`);
150
- } catch {
151
- console.log(" Session expired or invalid. Run: memax login\n");
152
- }
153
- }
154
-
155
- async function findFreePort(): Promise<number> {
156
- return new Promise((resolve) => {
157
- const server = createServer();
158
- server.listen(0, () => {
159
- const addr = server.address();
160
- const port = typeof addr === "object" && addr ? addr.port : 0;
161
- server.close(() => resolve(port));
162
- });
163
- });
164
- }