memax-cli 0.0.1 → 0.1.0-alpha.10
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/.vscode/mcp.json +8 -0
- package/dist/commands/auth.d.ts +6 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +62 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/capture.d.ts +17 -0
- package/dist/commands/capture.d.ts.map +1 -0
- package/dist/commands/capture.js +61 -0
- package/dist/commands/capture.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +24 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/delete.d.ts +4 -0
- package/dist/commands/delete.d.ts.map +1 -0
- package/dist/commands/delete.js +45 -0
- package/dist/commands/delete.js.map +1 -0
- package/dist/commands/hook.d.ts +2 -0
- package/dist/commands/hook.d.ts.map +1 -0
- package/dist/commands/hook.js +189 -0
- package/dist/commands/hook.js.map +1 -0
- package/dist/commands/list.d.ts +8 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +23 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/login.d.ts +4 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +131 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +384 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/push.d.ts +11 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +98 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/recall.d.ts +12 -0
- package/dist/commands/recall.d.ts.map +1 -0
- package/dist/commands/recall.js +107 -0
- package/dist/commands/recall.js.map +1 -0
- package/dist/commands/setup.d.ts +16 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +869 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/show.d.ts +2 -0
- package/dist/commands/show.d.ts.map +1 -0
- package/dist/commands/show.js +29 -0
- package/dist/commands/show.js.map +1 -0
- package/dist/commands/sync.d.ts +12 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +414 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +168 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api.d.ts +4 -0
- package/dist/lib/api.d.ts.map +1 -0
- package/dist/lib/api.js +95 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/config.d.ts +10 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +49 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/credentials.d.ts +11 -0
- package/dist/lib/credentials.d.ts.map +1 -0
- package/dist/lib/credentials.js +36 -0
- package/dist/lib/credentials.js.map +1 -0
- package/package.json +39 -4
- package/src/commands/auth.ts +92 -0
- package/src/commands/capture.ts +86 -0
- package/src/commands/config.ts +27 -0
- package/src/commands/delete.ts +58 -0
- package/src/commands/hook.ts +243 -0
- package/src/commands/list.ts +38 -0
- package/src/commands/login.ts +164 -0
- package/src/commands/mcp.ts +490 -0
- package/src/commands/push.ts +137 -0
- package/src/commands/recall.ts +163 -0
- package/src/commands/setup.ts +1129 -0
- package/src/commands/show.ts +35 -0
- package/src/commands/sync.ts +506 -0
- package/src/index.ts +223 -0
- package/src/lib/api.ts +110 -0
- package/src/lib/config.ts +61 -0
- package/src/lib/credentials.ts +42 -0
- package/tsconfig.json +9 -0
- package/LICENSE +0 -24
- package/README.md +0 -2
- package/bin/memax.js +0 -13
package/src/index.ts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { join, dirname } from "node:path";
|
|
6
|
+
import { pushCommand } from "./commands/push.js";
|
|
7
|
+
import { recallCommand } from "./commands/recall.js";
|
|
8
|
+
import { listCommand } from "./commands/list.js";
|
|
9
|
+
import { showCommand } from "./commands/show.js";
|
|
10
|
+
import { deleteCommand } from "./commands/delete.js";
|
|
11
|
+
import { syncCommand, syncAgentMemoryCommand } from "./commands/sync.js";
|
|
12
|
+
import { hookCommand } from "./commands/hook.js";
|
|
13
|
+
import { configGetCommand, configSetCommand } from "./commands/config.js";
|
|
14
|
+
import {
|
|
15
|
+
loginCommand,
|
|
16
|
+
logoutCommand,
|
|
17
|
+
whoamiCommand,
|
|
18
|
+
} from "./commands/login.js";
|
|
19
|
+
import {
|
|
20
|
+
createKeyCommand,
|
|
21
|
+
listKeysCommand,
|
|
22
|
+
revokeKeyCommand,
|
|
23
|
+
} from "./commands/auth.js";
|
|
24
|
+
import { registerMcpCommand } from "./commands/mcp.js";
|
|
25
|
+
import { setupCommand, teardownCommand } from "./commands/setup.js";
|
|
26
|
+
import { captureSessionCommand } from "./commands/capture.js";
|
|
27
|
+
|
|
28
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const pkg = JSON.parse(
|
|
30
|
+
readFileSync(join(__dirname, "..", "package.json"), "utf-8"),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const program = new Command();
|
|
34
|
+
|
|
35
|
+
program
|
|
36
|
+
.name("memax")
|
|
37
|
+
.description("Universal context & memory hub for AI agents")
|
|
38
|
+
.version(pkg.version);
|
|
39
|
+
|
|
40
|
+
// --- Core commands ---
|
|
41
|
+
|
|
42
|
+
program
|
|
43
|
+
.command("push [content]")
|
|
44
|
+
.description("Save knowledge to your Memax workspace")
|
|
45
|
+
.option("-f, --file <path>", "File to push")
|
|
46
|
+
.option("-c, --category <category>", "Category (auto-detected if omitted)")
|
|
47
|
+
.option("-t, --tags <tags>", "Comma-separated tags")
|
|
48
|
+
.option("--title <title>", "Note title")
|
|
49
|
+
.option("--ttl <duration>", "Auto-archive after duration (e.g., 7d, 30d)")
|
|
50
|
+
.option("--stdin", "Read content from stdin")
|
|
51
|
+
.action(pushCommand);
|
|
52
|
+
|
|
53
|
+
program
|
|
54
|
+
.command("recall [query]")
|
|
55
|
+
.description("Ask your knowledge a question")
|
|
56
|
+
.option("-c, --category <category>", "Filter by category")
|
|
57
|
+
.option("-t, --tags <tags>", "Filter by tags")
|
|
58
|
+
.option("-l, --limit <n>", "Max results", "5")
|
|
59
|
+
.option("--format <format>", "Output format: text, json", "text")
|
|
60
|
+
.option("--hook", "Output in agent-injectable format")
|
|
61
|
+
.option("--max-tokens <number>", "Maximum tokens to output (approximate)")
|
|
62
|
+
.option("--include-archived", "Include archived notes")
|
|
63
|
+
.action(recallCommand);
|
|
64
|
+
|
|
65
|
+
program
|
|
66
|
+
.command("list")
|
|
67
|
+
.description("List your knowledge notes")
|
|
68
|
+
.option("-c, --category <category>", "Filter by category")
|
|
69
|
+
.option("--state <state>", "Filter by state: active, cooling, archived")
|
|
70
|
+
.option("-l, --limit <n>", "Max results", "20")
|
|
71
|
+
.action(listCommand);
|
|
72
|
+
|
|
73
|
+
program
|
|
74
|
+
.command("show <id>")
|
|
75
|
+
.description("Show a specific note")
|
|
76
|
+
.action(showCommand);
|
|
77
|
+
|
|
78
|
+
program
|
|
79
|
+
.command("delete <id>")
|
|
80
|
+
.description("Delete a note")
|
|
81
|
+
.option("-y, --yes", "Skip confirmation")
|
|
82
|
+
.action(deleteCommand);
|
|
83
|
+
|
|
84
|
+
// Aliases
|
|
85
|
+
program
|
|
86
|
+
.command("remember [content]")
|
|
87
|
+
.description("Alias for push — save knowledge to your Memax workspace")
|
|
88
|
+
.option("-f, --file <path>", "File to push")
|
|
89
|
+
.option("-c, --category <category>", "Category (auto-detected if omitted)")
|
|
90
|
+
.option("-t, --tags <tags>", "Comma-separated tags")
|
|
91
|
+
.option("--title <title>", "Note title")
|
|
92
|
+
.option("--stdin", "Read content from stdin")
|
|
93
|
+
.action(pushCommand);
|
|
94
|
+
|
|
95
|
+
program
|
|
96
|
+
.command("forget <id>")
|
|
97
|
+
.description("Alias for delete — remove a note from your workspace")
|
|
98
|
+
.option("-y, --yes", "Skip confirmation")
|
|
99
|
+
.action(deleteCommand);
|
|
100
|
+
|
|
101
|
+
// --- Sync ---
|
|
102
|
+
|
|
103
|
+
const syncCmd = program
|
|
104
|
+
.command("sync [directory]")
|
|
105
|
+
.description("Sync a directory or agent memory to your Memax workspace")
|
|
106
|
+
.option("-w, --watch", "Watch for changes (coming soon)")
|
|
107
|
+
.option(
|
|
108
|
+
"-b, --boundary <level>",
|
|
109
|
+
"Visibility level: private, team, org",
|
|
110
|
+
"private",
|
|
111
|
+
)
|
|
112
|
+
.option(
|
|
113
|
+
"-c, --category <category>",
|
|
114
|
+
"Default category (auto-detected if omitted)",
|
|
115
|
+
)
|
|
116
|
+
.option("--ignore <patterns>", "Comma-separated directories to ignore")
|
|
117
|
+
.option(
|
|
118
|
+
"--agent-memory",
|
|
119
|
+
"Sync native AI agent memory files (Claude Code, Cursor, Codex)",
|
|
120
|
+
)
|
|
121
|
+
.option("-y, --yes", "Skip confirmation for large syncs")
|
|
122
|
+
.action(syncCommand);
|
|
123
|
+
|
|
124
|
+
syncCmd
|
|
125
|
+
.command("agents")
|
|
126
|
+
.description("Sync native AI agent memory files to Memax")
|
|
127
|
+
.action(syncAgentMemoryCommand);
|
|
128
|
+
|
|
129
|
+
// --- Session capture ---
|
|
130
|
+
|
|
131
|
+
program
|
|
132
|
+
.command("capture-session")
|
|
133
|
+
.description("Capture an agent session — extract decisions and learnings")
|
|
134
|
+
.option("--summary <text>", "Session summary text (alternative to stdin)")
|
|
135
|
+
.option("--agent <name>", "Agent name (claude-code, gemini, etc.)")
|
|
136
|
+
.action(captureSessionCommand);
|
|
137
|
+
|
|
138
|
+
// --- Agent integration setup ---
|
|
139
|
+
|
|
140
|
+
program
|
|
141
|
+
.command("setup")
|
|
142
|
+
.description("Set up AI agent integrations (auto-detects installed agents)")
|
|
143
|
+
.option("--mcp", "Enable MCP server (agent tools)")
|
|
144
|
+
.option("--hooks", "Enable context injection hooks")
|
|
145
|
+
.option("--instructions", "Inject memax instructions into agent config files")
|
|
146
|
+
.option("--all", "Enable MCP + hooks + instructions")
|
|
147
|
+
.option("--local", "Use local stdio MCP instead of remote server")
|
|
148
|
+
.option("--print", "Print MCP config JSON to copy/paste (no changes made)")
|
|
149
|
+
.option("--only <agents>", "Only configure these agents (comma-separated)")
|
|
150
|
+
.option("--skip <agents>", "Skip these agents (comma-separated)")
|
|
151
|
+
.action(setupCommand);
|
|
152
|
+
|
|
153
|
+
program
|
|
154
|
+
.command("teardown")
|
|
155
|
+
.description("Remove Memax integrations from agents")
|
|
156
|
+
.option("--only <agents>", "Only remove from these agents (comma-separated)")
|
|
157
|
+
.action(teardownCommand);
|
|
158
|
+
|
|
159
|
+
program
|
|
160
|
+
.command("hook <action> <agent>")
|
|
161
|
+
.description("Manage agent hooks (install/uninstall claude-code)")
|
|
162
|
+
.action(hookCommand);
|
|
163
|
+
|
|
164
|
+
// --- Auth ---
|
|
165
|
+
|
|
166
|
+
program
|
|
167
|
+
.command("login")
|
|
168
|
+
.description("Log in to Memax via GitHub")
|
|
169
|
+
.action(loginCommand);
|
|
170
|
+
|
|
171
|
+
program
|
|
172
|
+
.command("logout")
|
|
173
|
+
.description("Clear saved credentials")
|
|
174
|
+
.action(logoutCommand);
|
|
175
|
+
|
|
176
|
+
program
|
|
177
|
+
.command("whoami")
|
|
178
|
+
.description("Show current user")
|
|
179
|
+
.action(whoamiCommand);
|
|
180
|
+
|
|
181
|
+
// --- API Keys ---
|
|
182
|
+
|
|
183
|
+
const authCmd = program
|
|
184
|
+
.command("auth")
|
|
185
|
+
.description("Manage authentication and API keys");
|
|
186
|
+
|
|
187
|
+
authCmd
|
|
188
|
+
.command("create-key <name>")
|
|
189
|
+
.description("Create an API key for CI/CD or non-interactive use")
|
|
190
|
+
.option("--expires <days>", "Expire after N days (default: never)")
|
|
191
|
+
.action(createKeyCommand);
|
|
192
|
+
|
|
193
|
+
authCmd
|
|
194
|
+
.command("list-keys")
|
|
195
|
+
.description("List your API keys")
|
|
196
|
+
.action(listKeysCommand);
|
|
197
|
+
|
|
198
|
+
authCmd
|
|
199
|
+
.command("revoke-key <id>")
|
|
200
|
+
.description("Revoke an API key")
|
|
201
|
+
.action(revokeKeyCommand);
|
|
202
|
+
|
|
203
|
+
// --- MCP Server ---
|
|
204
|
+
|
|
205
|
+
registerMcpCommand(program);
|
|
206
|
+
|
|
207
|
+
// --- Config ---
|
|
208
|
+
|
|
209
|
+
const configCmd = program
|
|
210
|
+
.command("config")
|
|
211
|
+
.description("Manage Memax configuration");
|
|
212
|
+
|
|
213
|
+
configCmd
|
|
214
|
+
.command("get [key]")
|
|
215
|
+
.description("Get config value (or all values)")
|
|
216
|
+
.action(configGetCommand);
|
|
217
|
+
|
|
218
|
+
configCmd
|
|
219
|
+
.command("set <key> <value>")
|
|
220
|
+
.description("Set a config value")
|
|
221
|
+
.action(configSetCommand);
|
|
222
|
+
|
|
223
|
+
program.parse();
|
package/src/lib/api.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { loadConfig } from "./config.js";
|
|
2
|
+
import {
|
|
3
|
+
loadCredentials,
|
|
4
|
+
saveCredentials,
|
|
5
|
+
isTokenExpired,
|
|
6
|
+
} from "./credentials.js";
|
|
7
|
+
|
|
8
|
+
interface ApiEnvelope {
|
|
9
|
+
data?: unknown;
|
|
10
|
+
error?: { code: string; message: string };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getApiUrl(): string {
|
|
14
|
+
return loadConfig().api_url;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function authHeaders(): Promise<Record<string, string>> {
|
|
18
|
+
// 1. MEMAX_API_KEY env var takes priority (CI/CD, non-interactive)
|
|
19
|
+
const envKey = process.env.MEMAX_API_KEY;
|
|
20
|
+
if (envKey) {
|
|
21
|
+
return { Authorization: `Bearer ${envKey}` };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 2. Stored credentials with auto-refresh
|
|
25
|
+
const creds = loadCredentials();
|
|
26
|
+
if (!creds?.access_token) return {};
|
|
27
|
+
|
|
28
|
+
if (isTokenExpired() && creds.refresh_token) {
|
|
29
|
+
// Attempt to refresh the access token
|
|
30
|
+
try {
|
|
31
|
+
const url = `${getApiUrl()}/v1/auth/refresh`;
|
|
32
|
+
const res = await fetch(url, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: { "Content-Type": "application/json" },
|
|
35
|
+
body: JSON.stringify({ refresh_token: creds.refresh_token }),
|
|
36
|
+
});
|
|
37
|
+
const json = (await res.json()) as ApiEnvelope;
|
|
38
|
+
if (json.data) {
|
|
39
|
+
const tokens = json.data as {
|
|
40
|
+
access_token: string;
|
|
41
|
+
refresh_token: string;
|
|
42
|
+
expires_in: number;
|
|
43
|
+
};
|
|
44
|
+
saveCredentials({
|
|
45
|
+
access_token: tokens.access_token,
|
|
46
|
+
refresh_token: tokens.refresh_token,
|
|
47
|
+
expires_at: Date.now() + tokens.expires_in * 1000,
|
|
48
|
+
});
|
|
49
|
+
return { Authorization: `Bearer ${tokens.access_token}` };
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
// Refresh failed — fall through to use stale token (server will reject)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { Authorization: `Bearer ${creds.access_token}` };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function apiPost<T>(path: string, body: unknown): Promise<T> {
|
|
60
|
+
const url = `${getApiUrl()}${path}`;
|
|
61
|
+
let res: Response;
|
|
62
|
+
try {
|
|
63
|
+
res = await fetch(url, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: { "Content-Type": "application/json", ...(await authHeaders()) },
|
|
66
|
+
body: JSON.stringify(body),
|
|
67
|
+
});
|
|
68
|
+
} catch {
|
|
69
|
+
throw new Error(`Cannot reach API at ${url} — is the server running?`);
|
|
70
|
+
}
|
|
71
|
+
const json = (await res.json()) as ApiEnvelope;
|
|
72
|
+
if (json.error) {
|
|
73
|
+
throw new Error(json.error.message);
|
|
74
|
+
}
|
|
75
|
+
return json.data as T;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function apiGet<T>(path: string): Promise<T> {
|
|
79
|
+
const url = `${getApiUrl()}${path}`;
|
|
80
|
+
let res: Response;
|
|
81
|
+
try {
|
|
82
|
+
res = await fetch(url, {
|
|
83
|
+
headers: { ...(await authHeaders()) },
|
|
84
|
+
});
|
|
85
|
+
} catch {
|
|
86
|
+
throw new Error(`Cannot reach API at ${url} — is the server running?`);
|
|
87
|
+
}
|
|
88
|
+
const json = (await res.json()) as ApiEnvelope;
|
|
89
|
+
if (json.error) {
|
|
90
|
+
throw new Error(json.error.message);
|
|
91
|
+
}
|
|
92
|
+
return json.data as T;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function apiDelete(path: string): Promise<void> {
|
|
96
|
+
const url = `${getApiUrl()}${path}`;
|
|
97
|
+
let res: Response;
|
|
98
|
+
try {
|
|
99
|
+
res = await fetch(url, {
|
|
100
|
+
method: "DELETE",
|
|
101
|
+
headers: { ...(await authHeaders()) },
|
|
102
|
+
});
|
|
103
|
+
} catch {
|
|
104
|
+
throw new Error(`Cannot reach API at ${url} — is the server running?`);
|
|
105
|
+
}
|
|
106
|
+
const json = (await res.json()) as ApiEnvelope;
|
|
107
|
+
if (json.error) {
|
|
108
|
+
throw new Error(json.error.message);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = join(homedir(), ".memax");
|
|
6
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
7
|
+
|
|
8
|
+
export interface MemaxConfig {
|
|
9
|
+
api_url: string;
|
|
10
|
+
default_hub: string;
|
|
11
|
+
default_boundary: string;
|
|
12
|
+
auto_categorize: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const DEFAULT_CONFIG: MemaxConfig = {
|
|
16
|
+
api_url: "http://localhost:8080",
|
|
17
|
+
default_hub: "default",
|
|
18
|
+
default_boundary: "private",
|
|
19
|
+
auto_categorize: true,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function getConfigDir(): string {
|
|
23
|
+
return CONFIG_DIR;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function loadConfig(): MemaxConfig {
|
|
27
|
+
// Env var overrides everything
|
|
28
|
+
const envUrl = process.env.MEMAX_API_URL;
|
|
29
|
+
|
|
30
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
31
|
+
return envUrl ? { ...DEFAULT_CONFIG, api_url: envUrl } : DEFAULT_CONFIG;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
36
|
+
const config = { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
|
|
37
|
+
if (envUrl) config.api_url = envUrl;
|
|
38
|
+
return config;
|
|
39
|
+
} catch {
|
|
40
|
+
return envUrl ? { ...DEFAULT_CONFIG, api_url: envUrl } : DEFAULT_CONFIG;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function saveConfig(config: Partial<MemaxConfig>): void {
|
|
45
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
46
|
+
|
|
47
|
+
let existing = DEFAULT_CONFIG;
|
|
48
|
+
if (existsSync(CONFIG_FILE)) {
|
|
49
|
+
try {
|
|
50
|
+
existing = {
|
|
51
|
+
...DEFAULT_CONFIG,
|
|
52
|
+
...JSON.parse(readFileSync(CONFIG_FILE, "utf-8")),
|
|
53
|
+
};
|
|
54
|
+
} catch {
|
|
55
|
+
// ignore parse errors
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const merged = { ...existing, ...config };
|
|
60
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n");
|
|
61
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getConfigDir } from "./config.js";
|
|
4
|
+
|
|
5
|
+
const CRED_FILE = join(getConfigDir(), "credentials.json");
|
|
6
|
+
|
|
7
|
+
interface Credentials {
|
|
8
|
+
access_token: string;
|
|
9
|
+
refresh_token: string;
|
|
10
|
+
expires_at?: number; // unix timestamp (ms)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function loadCredentials(): Credentials | null {
|
|
14
|
+
if (!existsSync(CRED_FILE)) return null;
|
|
15
|
+
try {
|
|
16
|
+
const creds = JSON.parse(readFileSync(CRED_FILE, "utf-8")) as Credentials;
|
|
17
|
+
if (!creds.access_token) return null;
|
|
18
|
+
return creds;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function saveCredentials(creds: Credentials): void {
|
|
25
|
+
mkdirSync(getConfigDir(), { recursive: true });
|
|
26
|
+
writeFileSync(CRED_FILE, JSON.stringify(creds, null, 2) + "\n", {
|
|
27
|
+
mode: 0o600,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isTokenExpired(): boolean {
|
|
32
|
+
const creds = loadCredentials();
|
|
33
|
+
if (!creds?.expires_at) return false; // no expiry info → assume valid
|
|
34
|
+
// Treat as expired 5 minutes early to avoid edge cases
|
|
35
|
+
return Date.now() >= creds.expires_at - 5 * 60 * 1000;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function clearCredentials(): void {
|
|
39
|
+
if (existsSync(CRED_FILE)) {
|
|
40
|
+
writeFileSync(CRED_FILE, "{}\n", { mode: 0o600 });
|
|
41
|
+
}
|
|
42
|
+
}
|
package/tsconfig.json
ADDED
package/LICENSE
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
This is free and unencumbered software released into the public domain.
|
|
2
|
-
|
|
3
|
-
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
-
distribute this software, either in source code form or as a compiled
|
|
5
|
-
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
-
means.
|
|
7
|
-
|
|
8
|
-
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
-
of this software dedicate any and all copyright interest in the
|
|
10
|
-
software to the public domain. We make this dedication for the benefit
|
|
11
|
-
of the public at large and to the detriment of our heirs and
|
|
12
|
-
successors. We intend this dedication to be an overt act of
|
|
13
|
-
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
-
software under copyright law.
|
|
15
|
-
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
-
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
-
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
-
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
-
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
-
|
|
24
|
-
For more information, please refer to <https://unlicense.org>
|
package/README.md
DELETED
package/bin/memax.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const message = [
|
|
4
|
-
"",
|
|
5
|
-
" ===============================",
|
|
6
|
-
" memax CLI",
|
|
7
|
-
" ===============================",
|
|
8
|
-
" We're polishing the experience.",
|
|
9
|
-
" Stay tuned for the first release.",
|
|
10
|
-
""
|
|
11
|
-
].join("\n");
|
|
12
|
-
|
|
13
|
-
console.log(message);
|