gitmem-mcp 1.5.1 → 1.6.1
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/CHANGELOG.md +21 -0
- package/README.md +21 -4
- package/bin/gitmem.js +10 -0
- package/dist/commands/activate.d.ts +20 -0
- package/dist/commands/activate.js +606 -0
- package/dist/commands/deactivate.d.ts +10 -0
- package/dist/commands/deactivate.js +95 -0
- package/dist/commands/migrate-local.d.ts +71 -0
- package/dist/commands/migrate-local.js +317 -0
- package/dist/schemas/log.d.ts +2 -2
- package/dist/schemas/search.d.ts +2 -2
- package/dist/schemas/session-close.d.ts +12 -12
- package/dist/server.js +20 -2
- package/dist/services/analytics.d.ts +22 -0
- package/dist/services/analytics.js +68 -0
- package/dist/services/embedding.d.ts +7 -1
- package/dist/services/embedding.js +23 -5
- package/dist/services/license.d.ts +57 -0
- package/dist/services/license.js +200 -0
- package/dist/services/supabase-client.d.ts +6 -0
- package/dist/services/supabase-client.js +75 -22
- package/dist/services/tier.d.ts +13 -3
- package/dist/services/tier.js +38 -7
- package/dist/services/transcript-chunker.js +3 -2
- package/dist/services/variant-generation.js +3 -2
- package/dist/tools/recall.js +16 -4
- package/dist/tools/session-close.js +31 -5
- package/dist/tools/session-start.js +43 -5
- package/package.json +1 -1
- package/schema/setup.sql +489 -25
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitMem Pro Activation Wizard
|
|
3
|
+
*
|
|
4
|
+
* Usage: npx gitmem-mcp activate [license-key]
|
|
5
|
+
*
|
|
6
|
+
* Credential resolution (priority order):
|
|
7
|
+
* 1. Environment variables (SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, OPENROUTER_API_KEY)
|
|
8
|
+
* 2. Existing values in .gitmem/config.json (re-activation)
|
|
9
|
+
* 3. Interactive prompt (TTY only)
|
|
10
|
+
*
|
|
11
|
+
* Steps:
|
|
12
|
+
* 1. Accept key as argument or prompt for it
|
|
13
|
+
* 2. Validate key against our endpoint (register device)
|
|
14
|
+
* 3. Resolve Supabase URL + service role key (env → config → prompt)
|
|
15
|
+
* 4. Test Supabase connection (verify tables exist)
|
|
16
|
+
* 5. Resolve OpenRouter API key (env → config → prompt)
|
|
17
|
+
* 6. Write everything to ~/.gitmem/config.json
|
|
18
|
+
*/
|
|
19
|
+
import * as fs from "fs";
|
|
20
|
+
import * as path from "path";
|
|
21
|
+
import * as readline from "readline";
|
|
22
|
+
import { fileURLToPath } from "url";
|
|
23
|
+
import { getGitmemDir, getInstallId } from "../services/gitmem-dir.js";
|
|
24
|
+
import { validateLicense, clearLicenseCache } from "../services/license.js";
|
|
25
|
+
import { hasLocalData, hasPreMigrationData, migrateLocalToSupabase, reimportFromBackups, archiveLocalData } from "./migrate-local.js";
|
|
26
|
+
function createReadline() {
|
|
27
|
+
return readline.createInterface({
|
|
28
|
+
input: process.stdin,
|
|
29
|
+
output: process.stdout,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function ask(rl, question) {
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a credential value using the priority chain:
|
|
39
|
+
* 1. Environment variable
|
|
40
|
+
* 2. Existing config value
|
|
41
|
+
* 3. Interactive prompt (if TTY available)
|
|
42
|
+
*
|
|
43
|
+
* Returns the resolved value or empty string.
|
|
44
|
+
*/
|
|
45
|
+
async function resolveCredential(opts) {
|
|
46
|
+
// 1. Environment variable
|
|
47
|
+
const envValue = process.env[opts.envVar];
|
|
48
|
+
if (envValue) {
|
|
49
|
+
return { value: envValue, source: "env" };
|
|
50
|
+
}
|
|
51
|
+
// 2. Existing config value
|
|
52
|
+
if (opts.configValue) {
|
|
53
|
+
return { value: opts.configValue, source: "config" };
|
|
54
|
+
}
|
|
55
|
+
// 3. Interactive prompt
|
|
56
|
+
if (opts.rl) {
|
|
57
|
+
const prompt = opts.existingHint
|
|
58
|
+
? ` ${opts.promptLabel} [${opts.existingHint}]: `
|
|
59
|
+
: ` ${opts.promptLabel}: `;
|
|
60
|
+
const input = await ask(opts.rl, prompt);
|
|
61
|
+
if (input) {
|
|
62
|
+
return { value: input, source: "prompt" };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { value: "", source: "none" };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Test basic Supabase connectivity via REST API
|
|
69
|
+
*/
|
|
70
|
+
async function testSupabaseConnection(url, key) {
|
|
71
|
+
try {
|
|
72
|
+
const response = await fetch(`${url}/rest/v1/`, {
|
|
73
|
+
method: "GET",
|
|
74
|
+
headers: {
|
|
75
|
+
apikey: key,
|
|
76
|
+
Authorization: `Bearer ${key}`,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
return response.ok || response.status === 200;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check if gitmem tables exist in the user's Supabase
|
|
87
|
+
* Returns list of missing tables (empty = all present)
|
|
88
|
+
*/
|
|
89
|
+
async function checkSchemaExists(url, key) {
|
|
90
|
+
const requiredTables = ["gitmem_learnings", "gitmem_sessions", "gitmem_decisions", "gitmem_scar_usage"];
|
|
91
|
+
const missing = [];
|
|
92
|
+
for (const table of requiredTables) {
|
|
93
|
+
try {
|
|
94
|
+
const response = await fetch(`${url}/rest/v1/${table}?select=id&limit=0`, {
|
|
95
|
+
method: "GET",
|
|
96
|
+
headers: {
|
|
97
|
+
apikey: key,
|
|
98
|
+
Authorization: `Bearer ${key}`,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
missing.push(table);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
missing.push(table);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return missing;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get the Supabase access token for Management API
|
|
113
|
+
* Priority: SUPABASE_ACCESS_TOKEN env var → ~/.supabase/access-token file
|
|
114
|
+
*/
|
|
115
|
+
function getSupabaseAccessToken() {
|
|
116
|
+
const envToken = process.env.SUPABASE_ACCESS_TOKEN;
|
|
117
|
+
if (envToken)
|
|
118
|
+
return envToken;
|
|
119
|
+
try {
|
|
120
|
+
const tokenPath = path.join(process.env.HOME || process.env.USERPROFILE || "", ".supabase", "access-token");
|
|
121
|
+
if (fs.existsSync(tokenPath)) {
|
|
122
|
+
return fs.readFileSync(tokenPath, "utf-8").trim();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// No stored token
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Extract project ref from Supabase URL
|
|
132
|
+
* e.g., "https://abcdef.supabase.co" → "abcdef"
|
|
133
|
+
*/
|
|
134
|
+
function extractProjectRef(url) {
|
|
135
|
+
const match = url.match(/https?:\/\/([^.]+)\.supabase\.co/);
|
|
136
|
+
return match ? match[1] : null;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Load the setup SQL from the schema file bundled with the package
|
|
140
|
+
* Strips the license management section (not for user's Supabase)
|
|
141
|
+
*/
|
|
142
|
+
function loadSetupSql() {
|
|
143
|
+
try {
|
|
144
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
145
|
+
const sqlPath = path.join(__dirname, "..", "..", "schema", "setup.sql");
|
|
146
|
+
if (!fs.existsSync(sqlPath))
|
|
147
|
+
return null;
|
|
148
|
+
let sql = fs.readFileSync(sqlPath, "utf-8");
|
|
149
|
+
// Strip license management tables (they belong on our infra, not user's)
|
|
150
|
+
const licenseIdx = sql.indexOf("-- License Management Tables");
|
|
151
|
+
if (licenseIdx > 0) {
|
|
152
|
+
sql = sql.substring(0, licenseIdx);
|
|
153
|
+
}
|
|
154
|
+
return sql;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Apply schema SQL via direct Postgres connection (pg client)
|
|
162
|
+
* Uses DATABASE_URL connection string from env/config/prompt
|
|
163
|
+
*/
|
|
164
|
+
async function applySchemaViaPg(databaseUrl, sql) {
|
|
165
|
+
try {
|
|
166
|
+
const pg = await import("pg");
|
|
167
|
+
const client = new pg.default.Client({
|
|
168
|
+
connectionString: databaseUrl,
|
|
169
|
+
ssl: { rejectUnauthorized: false },
|
|
170
|
+
connectionTimeoutMillis: 15_000,
|
|
171
|
+
statement_timeout: 30_000,
|
|
172
|
+
});
|
|
173
|
+
await client.connect();
|
|
174
|
+
await client.query(sql);
|
|
175
|
+
// Reload PostgREST schema cache
|
|
176
|
+
await client.query("NOTIFY pgrst, 'reload schema'");
|
|
177
|
+
await client.end();
|
|
178
|
+
return { success: true };
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
182
|
+
return { success: false, error: message };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Apply schema SQL to user's Supabase via Management API
|
|
187
|
+
*/
|
|
188
|
+
async function applySchema(projectRef, accessToken, sql) {
|
|
189
|
+
try {
|
|
190
|
+
const response = await fetch(`https://api.supabase.com/v1/projects/${projectRef}/database/query`, {
|
|
191
|
+
method: "POST",
|
|
192
|
+
headers: {
|
|
193
|
+
"Content-Type": "application/json",
|
|
194
|
+
Authorization: `Bearer ${accessToken}`,
|
|
195
|
+
},
|
|
196
|
+
body: JSON.stringify({ query: sql }),
|
|
197
|
+
signal: AbortSignal.timeout(30_000),
|
|
198
|
+
});
|
|
199
|
+
if (!response.ok) {
|
|
200
|
+
const text = await response.text();
|
|
201
|
+
return { success: false, error: `HTTP ${response.status}: ${text.slice(0, 200)}` };
|
|
202
|
+
}
|
|
203
|
+
return { success: true };
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
207
|
+
return { success: false, error: message };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
export async function main(args) {
|
|
211
|
+
console.log("");
|
|
212
|
+
console.log("GitMem Pro Activation");
|
|
213
|
+
console.log("─────────────────────");
|
|
214
|
+
console.log("");
|
|
215
|
+
const gitmemDir = getGitmemDir();
|
|
216
|
+
const configPath = path.join(gitmemDir, "config.json");
|
|
217
|
+
// Load existing config
|
|
218
|
+
let config = {};
|
|
219
|
+
try {
|
|
220
|
+
if (fs.existsSync(configPath)) {
|
|
221
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// Start fresh
|
|
226
|
+
}
|
|
227
|
+
// Ensure install_id exists
|
|
228
|
+
let installId = config.install_id || getInstallId();
|
|
229
|
+
if (!installId) {
|
|
230
|
+
const { randomUUID } = await import("crypto");
|
|
231
|
+
installId = randomUUID();
|
|
232
|
+
config.install_id = installId;
|
|
233
|
+
}
|
|
234
|
+
// Step 1: Get license key (arg → env → prompt)
|
|
235
|
+
let apiKey = args[0] || process.env.GITMEM_API_KEY || config.api_key || "";
|
|
236
|
+
if (!apiKey) {
|
|
237
|
+
if (!process.stdin.isTTY) {
|
|
238
|
+
console.error("Error: License key required. Usage: npx gitmem-mcp activate <key>");
|
|
239
|
+
console.error(" Or set GITMEM_API_KEY environment variable.");
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
const rl = createReadline();
|
|
243
|
+
apiKey = await ask(rl, "License key: ");
|
|
244
|
+
rl.close();
|
|
245
|
+
if (!apiKey) {
|
|
246
|
+
console.error("Error: License key is required.");
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Validate key format
|
|
251
|
+
if (!apiKey.startsWith("gitmem_pro_") && !apiKey.startsWith("gitmem_dev_")) {
|
|
252
|
+
console.error("Error: Invalid key format. Keys start with 'gitmem_pro_' or 'gitmem_dev_'.");
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
// Step 2: Validate key against endpoint
|
|
256
|
+
console.log("Validating license key...");
|
|
257
|
+
const result = await validateLicense(apiKey, installId);
|
|
258
|
+
if (!result.valid) {
|
|
259
|
+
console.error(`\nError: ${result.message}`);
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
console.log(`✓ Key validated (${result.tier} tier)`);
|
|
263
|
+
console.log("");
|
|
264
|
+
// Create readline only if TTY is available
|
|
265
|
+
const rl = process.stdin.isTTY ? createReadline() : null;
|
|
266
|
+
// Step 3: Resolve Supabase credentials (env → config → prompt)
|
|
267
|
+
const existingUrl = config.supabase_url;
|
|
268
|
+
const existingKey = config.supabase_key;
|
|
269
|
+
if (rl) {
|
|
270
|
+
console.log("Supabase Setup");
|
|
271
|
+
console.log(" (Create a free project at https://database.new)");
|
|
272
|
+
if (existingUrl) {
|
|
273
|
+
console.log(` Current: ${existingUrl}`);
|
|
274
|
+
}
|
|
275
|
+
console.log("");
|
|
276
|
+
}
|
|
277
|
+
const supabaseUrlResult = await resolveCredential({
|
|
278
|
+
envVar: "SUPABASE_URL",
|
|
279
|
+
configValue: existingUrl,
|
|
280
|
+
promptLabel: "Project URL",
|
|
281
|
+
required: true,
|
|
282
|
+
rl,
|
|
283
|
+
});
|
|
284
|
+
const supabaseUrl = supabaseUrlResult.value;
|
|
285
|
+
// Re-activation safety: warn if changing URL interactively
|
|
286
|
+
if (rl && existingUrl && supabaseUrl !== existingUrl && supabaseUrlResult.source === "prompt") {
|
|
287
|
+
console.log("");
|
|
288
|
+
console.log(" ⚠ WARNING: You are changing your Supabase URL.");
|
|
289
|
+
console.log(` Old: ${existingUrl}`);
|
|
290
|
+
console.log(` New: ${supabaseUrl}`);
|
|
291
|
+
console.log(" Your existing data in the old project will NOT be migrated.");
|
|
292
|
+
console.log("");
|
|
293
|
+
const confirm = await ask(rl, " Continue with new URL? (y/N): ");
|
|
294
|
+
if (confirm.toLowerCase() !== "y" && confirm.toLowerCase() !== "yes") {
|
|
295
|
+
console.log(" Keeping existing URL.");
|
|
296
|
+
// Fall back handled below by using existingUrl
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// Resolve service role key — if same URL, offer to keep existing
|
|
300
|
+
const supabaseKeyResult = await resolveCredential({
|
|
301
|
+
envVar: "SUPABASE_SERVICE_ROLE_KEY",
|
|
302
|
+
configValue: (supabaseUrl === existingUrl) ? existingKey : undefined,
|
|
303
|
+
promptLabel: "Service Role Key",
|
|
304
|
+
required: true,
|
|
305
|
+
rl,
|
|
306
|
+
existingHint: (existingKey && supabaseUrl === existingUrl) ? "keep existing" : undefined,
|
|
307
|
+
});
|
|
308
|
+
const supabaseKey = supabaseKeyResult.value;
|
|
309
|
+
// Step 4: Test connection if we have credentials
|
|
310
|
+
let missingTables = [];
|
|
311
|
+
let connectionFailed = false;
|
|
312
|
+
if (supabaseUrl && supabaseKey) {
|
|
313
|
+
if (supabaseUrlResult.source !== "config" || supabaseKeyResult.source !== "config") {
|
|
314
|
+
// Only test if credentials are new (not just re-read from config)
|
|
315
|
+
console.log(" Testing connection...");
|
|
316
|
+
const connected = await testSupabaseConnection(supabaseUrl, supabaseKey);
|
|
317
|
+
if (!connected) {
|
|
318
|
+
connectionFailed = true;
|
|
319
|
+
if (rl) {
|
|
320
|
+
// Interactive: hard failure — user can re-enter
|
|
321
|
+
console.error(" ✗ Could not connect to Supabase. Check your URL and key.");
|
|
322
|
+
rl.close();
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
// Non-interactive: warn but save credentials anyway
|
|
327
|
+
console.log(" ⚠ Could not connect to Supabase (credentials saved anyway).");
|
|
328
|
+
console.log(" Verify your SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are correct.");
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
console.log(" ✓ Connected to Supabase");
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (!connectionFailed) {
|
|
336
|
+
missingTables = await checkSchemaExists(supabaseUrl, supabaseKey);
|
|
337
|
+
if (missingTables.length > 0) {
|
|
338
|
+
console.log(" Setting up schema...");
|
|
339
|
+
const projectRef = extractProjectRef(supabaseUrl);
|
|
340
|
+
const accessToken = getSupabaseAccessToken();
|
|
341
|
+
const setupSql = loadSetupSql();
|
|
342
|
+
if (projectRef && accessToken && setupSql) {
|
|
343
|
+
// Auto-apply schema via Management API
|
|
344
|
+
const result = await applySchema(projectRef, accessToken, setupSql);
|
|
345
|
+
if (result.success) {
|
|
346
|
+
// Reload PostgREST schema cache so new tables are visible via REST API
|
|
347
|
+
await applySchema(projectRef, accessToken, "NOTIFY pgrst, 'reload schema'");
|
|
348
|
+
// Brief wait for PostgREST to pick up the notification
|
|
349
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
350
|
+
// Verify tables now exist
|
|
351
|
+
const stillMissing = await checkSchemaExists(supabaseUrl, supabaseKey);
|
|
352
|
+
if (stillMissing.length === 0) {
|
|
353
|
+
console.log(" ✓ Schema applied automatically");
|
|
354
|
+
missingTables = [];
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
console.log(" ⚠ Schema applied but some tables still missing: " + stillMissing.join(", "));
|
|
358
|
+
console.log(" PostgREST may need a moment to reload. Try: npx gitmem-mcp check");
|
|
359
|
+
missingTables = stillMissing;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
console.log(" ⚠ Auto-schema failed: " + result.error);
|
|
364
|
+
console.log(" Apply manually:");
|
|
365
|
+
console.log("");
|
|
366
|
+
console.log(" npx gitmem-mcp setup | pbcopy (macOS — copies SQL to clipboard)");
|
|
367
|
+
console.log(" npx gitmem-mcp setup (prints SQL to paste manually)");
|
|
368
|
+
console.log("");
|
|
369
|
+
console.log(" Then: Supabase Dashboard → SQL Editor → New query → Paste → Run");
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
else if (!setupSql) {
|
|
373
|
+
console.log(" ⚠ Could not load schema SQL file");
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
// No access token — try DATABASE_URL as fallback
|
|
377
|
+
const dbUrlResult = await resolveCredential({
|
|
378
|
+
envVar: "DATABASE_URL",
|
|
379
|
+
configValue: config.database_url,
|
|
380
|
+
promptLabel: "Database URL (from Supabase Connect panel)",
|
|
381
|
+
required: false,
|
|
382
|
+
rl,
|
|
383
|
+
});
|
|
384
|
+
if (dbUrlResult.value) {
|
|
385
|
+
console.log(" Applying schema via direct connection...");
|
|
386
|
+
const pgResult = await applySchemaViaPg(dbUrlResult.value, setupSql);
|
|
387
|
+
if (pgResult.success) {
|
|
388
|
+
// Save DATABASE_URL to config for future use
|
|
389
|
+
config.database_url = dbUrlResult.value;
|
|
390
|
+
// Wait for PostgREST cache reload
|
|
391
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
392
|
+
const stillMissing = await checkSchemaExists(supabaseUrl, supabaseKey);
|
|
393
|
+
if (stillMissing.length === 0) {
|
|
394
|
+
console.log(" ✓ Schema applied via direct connection");
|
|
395
|
+
missingTables = [];
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
console.log(" ⚠ Schema applied but PostgREST cache may be stale");
|
|
399
|
+
console.log(" Try: npx gitmem-mcp check");
|
|
400
|
+
missingTables = stillMissing;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
console.log(" ⚠ Direct connection failed: " + pgResult.error);
|
|
405
|
+
console.log(" Apply manually:");
|
|
406
|
+
console.log(" npx gitmem-mcp setup | pbcopy (macOS)");
|
|
407
|
+
console.log(" Then: Supabase Dashboard → SQL Editor → Paste → Run");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
// No access token AND no DATABASE_URL
|
|
412
|
+
console.log(" ⚠ Missing tables: " + missingTables.join(", "));
|
|
413
|
+
console.log("");
|
|
414
|
+
console.log(" To apply automatically, provide one of:");
|
|
415
|
+
console.log(" - DATABASE_URL (from Supabase Dashboard → Connect)");
|
|
416
|
+
console.log(" - SUPABASE_ACCESS_TOKEN (from: npx supabase login)");
|
|
417
|
+
console.log(" Then re-run: npx gitmem-mcp activate");
|
|
418
|
+
console.log("");
|
|
419
|
+
console.log(" Or apply manually:");
|
|
420
|
+
console.log(" npx gitmem-mcp setup | pbcopy (macOS)");
|
|
421
|
+
console.log(" Then: Supabase Dashboard → SQL Editor → Paste → Run");
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
console.log(" ✓ Schema verified (all tables present)");
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
console.log("");
|
|
430
|
+
}
|
|
431
|
+
else if (!supabaseUrl || !supabaseKey) {
|
|
432
|
+
console.log(" ⚠ Supabase credentials not provided.");
|
|
433
|
+
console.log(" Pro features require Supabase. Set via:");
|
|
434
|
+
console.log(" - Environment: SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY");
|
|
435
|
+
console.log(" - Config: edit .gitmem/config.json (supabase_url, supabase_key)");
|
|
436
|
+
console.log(" - Re-run: npx gitmem-mcp activate (interactive)");
|
|
437
|
+
console.log("");
|
|
438
|
+
}
|
|
439
|
+
// Step 5: Resolve OpenRouter key (env → config → prompt)
|
|
440
|
+
if (rl) {
|
|
441
|
+
console.log("OpenRouter Setup");
|
|
442
|
+
console.log(" (Get a key at https://openrouter.ai/keys)");
|
|
443
|
+
const existingOpenRouter = config.openrouter_key;
|
|
444
|
+
if (existingOpenRouter) {
|
|
445
|
+
console.log(` Current: ${existingOpenRouter.substring(0, 12)}...`);
|
|
446
|
+
}
|
|
447
|
+
console.log("");
|
|
448
|
+
}
|
|
449
|
+
const openrouterResult = await resolveCredential({
|
|
450
|
+
envVar: "OPENROUTER_API_KEY",
|
|
451
|
+
configValue: config.openrouter_key,
|
|
452
|
+
promptLabel: "API Key",
|
|
453
|
+
required: false,
|
|
454
|
+
rl,
|
|
455
|
+
existingHint: config.openrouter_key ? "keep existing" : undefined,
|
|
456
|
+
});
|
|
457
|
+
const openrouterKey = openrouterResult.value;
|
|
458
|
+
if (openrouterKey) {
|
|
459
|
+
if (openrouterResult.source === "env") {
|
|
460
|
+
console.log(" ✓ OpenRouter configured (from env)");
|
|
461
|
+
}
|
|
462
|
+
else if (openrouterResult.source === "config") {
|
|
463
|
+
// Silent — already configured
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
console.log(" ✓ OpenRouter configured");
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
else if (rl) {
|
|
470
|
+
console.log(" ⚠ Skipped (semantic search will not work without embeddings)");
|
|
471
|
+
}
|
|
472
|
+
if (rl)
|
|
473
|
+
rl.close();
|
|
474
|
+
// Step 6: Write config (preserves existing fields like project, install_id, feedback_enabled)
|
|
475
|
+
config.api_key = apiKey;
|
|
476
|
+
if (supabaseUrl)
|
|
477
|
+
config.supabase_url = supabaseUrl;
|
|
478
|
+
if (supabaseKey)
|
|
479
|
+
config.supabase_key = supabaseKey;
|
|
480
|
+
if (openrouterKey)
|
|
481
|
+
config.openrouter_key = openrouterKey;
|
|
482
|
+
if (!fs.existsSync(gitmemDir)) {
|
|
483
|
+
fs.mkdirSync(gitmemDir, { recursive: true });
|
|
484
|
+
}
|
|
485
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
486
|
+
// Clear any stale license cache
|
|
487
|
+
clearLicenseCache();
|
|
488
|
+
// Ensure .gitmem/ is in .gitignore (prevents credential exposure)
|
|
489
|
+
try {
|
|
490
|
+
const projectRoot = process.cwd();
|
|
491
|
+
const gitignorePath = path.join(projectRoot, ".gitignore");
|
|
492
|
+
if (fs.existsSync(path.join(projectRoot, ".git"))) {
|
|
493
|
+
let gitignore = "";
|
|
494
|
+
if (fs.existsSync(gitignorePath)) {
|
|
495
|
+
gitignore = fs.readFileSync(gitignorePath, "utf-8");
|
|
496
|
+
}
|
|
497
|
+
if (!gitignore.split("\n").some(line => line.trim() === ".gitmem/" || line.trim() === ".gitmem")) {
|
|
498
|
+
const separator = gitignore.length > 0 && !gitignore.endsWith("\n") ? "\n" : "";
|
|
499
|
+
fs.appendFileSync(gitignorePath, `${separator}\n# GitMem local data (contains credentials)\n.gitmem/\n`);
|
|
500
|
+
console.log(" ✓ Added .gitmem/ to .gitignore");
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
// Non-fatal — warn but don't block activation
|
|
506
|
+
console.log(" ⚠ Could not update .gitignore — manually add .gitmem/ to prevent credential exposure");
|
|
507
|
+
}
|
|
508
|
+
// Step 7: Migrate local data to Supabase (free → pro upgrade)
|
|
509
|
+
// Handles three scenarios:
|
|
510
|
+
// A) Fresh upgrade: .json files exist → migrate and archive
|
|
511
|
+
// B) Re-activation after failed migration: .pre-migration backups exist → reimport
|
|
512
|
+
// C) Already migrated: neither exists → skip
|
|
513
|
+
if (supabaseUrl && supabaseKey && missingTables.length === 0) {
|
|
514
|
+
const hasLive = hasLocalData(gitmemDir);
|
|
515
|
+
const hasBackups = hasPreMigrationData(gitmemDir);
|
|
516
|
+
if (hasLive || hasBackups) {
|
|
517
|
+
if (hasLive) {
|
|
518
|
+
console.log("Migrating Local Data");
|
|
519
|
+
console.log(" Found existing local data from free tier...");
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
console.log("Re-importing From Backups");
|
|
523
|
+
console.log(" Found .pre-migration backup files from a previous upgrade...");
|
|
524
|
+
}
|
|
525
|
+
console.log("");
|
|
526
|
+
const migrationResult = hasLive
|
|
527
|
+
? await migrateLocalToSupabase({
|
|
528
|
+
supabaseUrl,
|
|
529
|
+
supabaseKey,
|
|
530
|
+
gitmemDir,
|
|
531
|
+
onProgress: (msg) => console.log(msg),
|
|
532
|
+
})
|
|
533
|
+
: await reimportFromBackups({
|
|
534
|
+
supabaseUrl,
|
|
535
|
+
supabaseKey,
|
|
536
|
+
gitmemDir,
|
|
537
|
+
onProgress: (msg) => console.log(msg),
|
|
538
|
+
});
|
|
539
|
+
// Report results
|
|
540
|
+
const collections = Object.keys(migrationResult.migrated);
|
|
541
|
+
let totalMigrated = 0;
|
|
542
|
+
let totalSkipped = 0;
|
|
543
|
+
let totalErrors = 0;
|
|
544
|
+
for (const col of collections) {
|
|
545
|
+
const m = migrationResult.migrated[col];
|
|
546
|
+
const s = migrationResult.skipped[col];
|
|
547
|
+
const e = migrationResult.errors[col]?.length || 0;
|
|
548
|
+
totalMigrated += m;
|
|
549
|
+
totalSkipped += s;
|
|
550
|
+
totalErrors += e;
|
|
551
|
+
if (m > 0 || s > 0) {
|
|
552
|
+
console.log(` ${m > 0 ? "✓" : "⚠"} ${col}: ${m} migrated${s > 0 ? `, ${s} failed` : ""}`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// Show ALL errors
|
|
556
|
+
if (totalErrors > 0) {
|
|
557
|
+
console.log("");
|
|
558
|
+
for (const col of collections) {
|
|
559
|
+
for (const err of migrationResult.errors[col] || []) {
|
|
560
|
+
console.log(` ⚠ ${col}: ${err}`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (totalMigrated > 0) {
|
|
565
|
+
console.log("");
|
|
566
|
+
console.log(` ✓ Migrated ${totalMigrated} records to Supabase`);
|
|
567
|
+
if (hasLive) {
|
|
568
|
+
// Archive local files so they aren't re-read
|
|
569
|
+
const archived = archiveLocalData(gitmemDir);
|
|
570
|
+
if (archived.length > 0) {
|
|
571
|
+
console.log(` ✓ Local files archived (${archived.join(", ")}.json → .pre-migration)`);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
else if (migrationResult.hasLocalData) {
|
|
576
|
+
console.log(" ⚠ Migration encountered errors. Local data preserved.");
|
|
577
|
+
console.log(" Check .gitmem/migration.log for details.");
|
|
578
|
+
console.log(" Fix issues and re-run: npx gitmem-mcp activate");
|
|
579
|
+
}
|
|
580
|
+
console.log("");
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
// Summary
|
|
584
|
+
console.log("");
|
|
585
|
+
console.log("─────────────────────");
|
|
586
|
+
const sources = [];
|
|
587
|
+
if (supabaseUrl)
|
|
588
|
+
sources.push(`Supabase (${supabaseUrlResult.source})`);
|
|
589
|
+
if (openrouterKey)
|
|
590
|
+
sources.push(`OpenRouter (${openrouterResult.source})`);
|
|
591
|
+
if (!supabaseUrl) {
|
|
592
|
+
console.log("License key activated. Supabase credentials still needed for Pro features.");
|
|
593
|
+
}
|
|
594
|
+
else if (missingTables.length > 0) {
|
|
595
|
+
console.log("Pro tier activated! Run schema setup, then restart your editor.");
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
console.log("Pro tier activated! Restart your editor to apply.");
|
|
599
|
+
}
|
|
600
|
+
if (sources.length > 0) {
|
|
601
|
+
console.log(` Credentials: ${sources.join(", ")}`);
|
|
602
|
+
}
|
|
603
|
+
console.log(` Config: ${configPath}`);
|
|
604
|
+
console.log("");
|
|
605
|
+
}
|
|
606
|
+
//# sourceMappingURL=activate.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitMem Pro Deactivation
|
|
3
|
+
*
|
|
4
|
+
* 1. Calls gitmem_deactivate_device RPC to remove this device server-side
|
|
5
|
+
* 2. Removes api_key, supabase_url, supabase_key, openrouter_key from config.json
|
|
6
|
+
* 3. Deletes license-cache.json
|
|
7
|
+
* Does NOT remove .gitmem/ directory or local data.
|
|
8
|
+
*/
|
|
9
|
+
export declare function main(_args: string[]): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=deactivate.d.ts.map
|