gitmem-mcp 1.4.4 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +21 -4
  3. package/bin/gitmem.js +10 -0
  4. package/dist/commands/activate.d.ts +20 -0
  5. package/dist/commands/activate.js +562 -0
  6. package/dist/commands/deactivate.d.ts +10 -0
  7. package/dist/commands/deactivate.js +95 -0
  8. package/dist/commands/migrate-local.d.ts +53 -0
  9. package/dist/commands/migrate-local.js +177 -0
  10. package/dist/hooks/format-utils.js +4 -0
  11. package/dist/schemas/log.d.ts +2 -2
  12. package/dist/schemas/search.d.ts +2 -2
  13. package/dist/schemas/session-close.d.ts +12 -12
  14. package/dist/server.js +33 -2
  15. package/dist/services/analytics.d.ts +22 -0
  16. package/dist/services/analytics.js +68 -0
  17. package/dist/services/doc-chunker.d.ts +45 -0
  18. package/dist/services/doc-chunker.js +208 -0
  19. package/dist/services/doc-index.d.ts +88 -0
  20. package/dist/services/doc-index.js +328 -0
  21. package/dist/services/license.d.ts +57 -0
  22. package/dist/services/license.js +200 -0
  23. package/dist/services/supabase-client.d.ts +6 -0
  24. package/dist/services/supabase-client.js +75 -22
  25. package/dist/services/tier.d.ts +13 -3
  26. package/dist/services/tier.js +38 -7
  27. package/dist/tools/definitions.d.ts +688 -0
  28. package/dist/tools/definitions.js +87 -0
  29. package/dist/tools/index-docs.d.ts +30 -0
  30. package/dist/tools/index-docs.js +163 -0
  31. package/dist/tools/prepare-context.js +7 -0
  32. package/dist/tools/recall.js +25 -4
  33. package/dist/tools/search-docs.d.ts +38 -0
  34. package/dist/tools/search-docs.js +94 -0
  35. package/dist/tools/search.js +11 -1
  36. package/dist/tools/session-close.js +76 -7
  37. package/dist/tools/session-start.js +57 -5
  38. package/package.json +1 -1
  39. package/schema/setup.sql +489 -25
package/CHANGELOG.md CHANGED
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.6.0] - 2026-05-25
11
+
12
+ ### Added
13
+ - **Free→Pro migration**: Running `activate` with existing local `.gitmem/` data automatically migrates learnings, sessions, decisions, and scar usage to Supabase. Local files are archived with `.pre-migration` suffix.
14
+ - **Schema auto-apply via DATABASE_URL**: `activate` now falls back to direct Postgres connection when `SUPABASE_ACCESS_TOKEN` is unavailable, using `DATABASE_URL` from env, config, or interactive prompt.
15
+
16
+ ### Fixed
17
+ - **Idempotent schema SQL**: `setup.sql` now uses `CREATE TABLE IF NOT EXISTS`, `CREATE OR REPLACE`, and `DO $$ ... END $$` guards throughout — safe to re-run for upgrades without errors.
18
+
19
+ ## [1.5.1] - 2026-05-13
20
+
21
+ ### Fixed
22
+ - **CI smoke test tool count**: Updated `EXPECTED_TOOL_COUNTS` to reflect `index_docs` and `search_docs` additions (+2 per tier). The 1.5.0 release failed to publish because the smoke test expected 23 free-tier tools but found 25.
23
+
24
+ ## [1.5.0] - 2026-05-11
25
+
26
+ ### Added
27
+ - **`index_docs` tool**: Scan a directory of markdown files, chunk them, and store in a local doc index for semantic search. Supports incremental indexing (only re-processes changed files), force re-index, and project-scoped indexes. Aliases: `gitmem-idx`.
28
+ - **`search_docs` tool**: Search indexed repository documentation using semantic similarity (pro tier) or BM25 keyword search (free tier). Returns relevant chunks with file paths for targeted reading. Aliases: `gitmem-sd`.
29
+ - **Citation protocol**: `recall`, `search`, and `prepare_context` now include a citation rule instructing agents to cite record IDs when referencing facts from institutional memory.
30
+ - **Low confidence tagging**: Recall and search results with similarity below 0.55 are tagged `[low confidence]` — these matches have a 66% N/A rate historically.
31
+ - **Session duration on resume**: `session_start` now shows elapsed session time and loaded scar count when resuming or refreshing an existing session.
32
+
33
+ ### Changed
34
+ - **Quick close hard gate**: `session_close` with `close_type: "quick"` now rejects sessions over 30 minutes, requiring standard close instead.
35
+ - **Standard close recall gate**: `session_close` with `close_type: "standard"` now requires at least one `recall()` call during the session (exemptions: quick close, autonomous agents, sessions with inline reflection).
36
+
10
37
  ## [1.4.4] - 2026-03-31
11
38
 
12
39
  ### Fixed
package/README.md CHANGED
@@ -124,11 +124,16 @@ Add this to your MCP client's config file:
124
124
  | `npx gitmem-mcp init --client <name>` | Setup for specific client (`claude`, `cursor`, `vscode`, `windsurf`, `generic`) |
125
125
  | `npx gitmem-mcp init --yes` | Non-interactive setup |
126
126
  | `npx gitmem-mcp init --dry-run` | Preview changes |
127
+ | `npx gitmem-mcp activate <key>` | Activate Pro tier (auto-applies schema) |
128
+ | `npx gitmem-mcp deactivate` | Remove Pro credentials, free device slot |
129
+ | `npx gitmem-mcp setup` | Output schema SQL (for manual Supabase setup) |
127
130
  | `npx gitmem-mcp uninstall` | Clean removal (preserves `.gitmem/` data) |
128
131
  | `npx gitmem-mcp uninstall --all` | Full removal including data |
129
132
  | `npx gitmem-mcp check` | Diagnostic health check |
130
133
 
131
- ## Pro Tier — Coming Soon
134
+ ## Pro Tier
135
+
136
+ Self-hosted on your own Supabase. You bring the infrastructure, gitmem sets it up.
132
137
 
133
138
  | What you get | Why your agent cares |
134
139
  |-------------|---------------------|
@@ -136,11 +141,23 @@ Add this to your MCP client's config file:
136
141
  | **Session analytics** | Spot patterns in what keeps going wrong |
137
142
  | **Sub-agent briefing** | Hand institutional context to sub-agents automatically |
138
143
  | **Cloud persistence** | Memory survives machine changes, shareable across team |
139
- | **A/B testing analytics** | Measure which scar phrasings actually change agent behavior (free tier includes `GITMEM_NUDGE_VARIANT` for manual testing) |
144
+ | **A/B testing analytics** | Measure which scar phrasings actually change agent behavior |
140
145
 
141
- The free tier gives you everything for solo projects. Pro makes recall smarter and memory portable.
146
+ ### Quick start
147
+
148
+ ```bash
149
+ npx supabase login # one time
150
+ export SUPABASE_URL="https://yourproject.supabase.co"
151
+ export SUPABASE_SERVICE_ROLE_KEY="eyJ..."
152
+ export OPENROUTER_API_KEY="sk-or-v1-..."
153
+ npx gitmem-mcp activate <your-license-key>
154
+ ```
155
+
156
+ The activate command creates all tables, views, RPC functions, and indexes automatically. No manual SQL needed.
142
157
 
143
- [Join the mailing list](https://gitmem.ai) to get notified.
158
+ See **[docs/pro-setup-guide.md](docs/pro-setup-guide.md)** for the full guide.
159
+
160
+ The free tier gives you everything for solo projects. Pro makes recall smarter and memory portable.
144
161
 
145
162
  ## GitMem + MEMORY.md
146
163
 
package/bin/gitmem.js CHANGED
@@ -44,6 +44,10 @@ Usage:
44
44
  npx gitmem-mcp uninstall Clean removal of gitmem from project
45
45
  npx gitmem-mcp uninstall --all Also delete .gitmem/ data directory
46
46
 
47
+ Pro Tier Activation:
48
+ npx gitmem-mcp activate Activate Pro tier (license key + credentials wizard)
49
+ npx gitmem-mcp deactivate Remove Pro credentials from config
50
+
47
51
  Other commands:
48
52
  npx gitmem-mcp setup Output SQL for Supabase schema setup (pro/dev tier)
49
53
  npx gitmem-mcp configure Generate .mcp.json config for Claude Code / Cursor
@@ -876,6 +880,12 @@ switch (command) {
876
880
  // New interactive wizard (replaces old cmdInit for CLI usage)
877
881
  import("./init-wizard.js");
878
882
  break;
883
+ case "activate":
884
+ import("../dist/commands/activate.js").then((m) => m.main(process.argv.slice(3)));
885
+ break;
886
+ case "deactivate":
887
+ import("../dist/commands/deactivate.js").then((m) => m.main(process.argv.slice(3)));
888
+ break;
879
889
  case "uninstall":
880
890
  import("./uninstall.js");
881
891
  break;
@@ -0,0 +1,20 @@
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
+ export declare function main(args: string[]): Promise<void>;
20
+ //# sourceMappingURL=activate.d.ts.map
@@ -0,0 +1,562 @@
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, migrateLocalToSupabase, 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
+ // Step 7: Migrate local data to Supabase (free → pro upgrade)
489
+ if (supabaseUrl && supabaseKey && missingTables.length === 0 && hasLocalData(gitmemDir)) {
490
+ console.log("Migrating Local Data");
491
+ console.log(" Found existing local data from free tier...");
492
+ console.log("");
493
+ const migrationResult = await migrateLocalToSupabase({
494
+ supabaseUrl,
495
+ supabaseKey,
496
+ gitmemDir,
497
+ onProgress: (msg) => console.log(msg),
498
+ });
499
+ // Report results
500
+ const collections = Object.keys(migrationResult.migrated);
501
+ let totalMigrated = 0;
502
+ let totalSkipped = 0;
503
+ let totalErrors = 0;
504
+ for (const col of collections) {
505
+ const m = migrationResult.migrated[col];
506
+ const s = migrationResult.skipped[col];
507
+ const e = migrationResult.errors[col]?.length || 0;
508
+ totalMigrated += m;
509
+ totalSkipped += s;
510
+ totalErrors += e;
511
+ if (m > 0) {
512
+ console.log(` ✓ ${col}: ${m} records migrated${s > 0 ? ` (${s} skipped)` : ""}`);
513
+ }
514
+ }
515
+ // Show errors if any
516
+ if (totalErrors > 0) {
517
+ console.log("");
518
+ for (const col of collections) {
519
+ for (const err of migrationResult.errors[col] || []) {
520
+ console.log(` ⚠ ${col}: ${err}`);
521
+ }
522
+ }
523
+ }
524
+ if (totalMigrated > 0) {
525
+ console.log("");
526
+ console.log(` ✓ Migrated ${totalMigrated} records to Supabase`);
527
+ // Archive local files so they aren't re-read
528
+ const archived = archiveLocalData(gitmemDir);
529
+ if (archived.length > 0) {
530
+ console.log(` ✓ Local files archived (${archived.join(", ")}.json → .pre-migration)`);
531
+ }
532
+ }
533
+ else if (migrationResult.hasLocalData) {
534
+ console.log(" ⚠ Migration encountered errors. Local data preserved.");
535
+ console.log(" Re-run activate after resolving issues.");
536
+ }
537
+ console.log("");
538
+ }
539
+ // Summary
540
+ console.log("");
541
+ console.log("─────────────────────");
542
+ const sources = [];
543
+ if (supabaseUrl)
544
+ sources.push(`Supabase (${supabaseUrlResult.source})`);
545
+ if (openrouterKey)
546
+ sources.push(`OpenRouter (${openrouterResult.source})`);
547
+ if (!supabaseUrl) {
548
+ console.log("License key activated. Supabase credentials still needed for Pro features.");
549
+ }
550
+ else if (missingTables.length > 0) {
551
+ console.log("Pro tier activated! Run schema setup, then restart your editor.");
552
+ }
553
+ else {
554
+ console.log("Pro tier activated! Restart your editor to apply.");
555
+ }
556
+ if (sources.length > 0) {
557
+ console.log(` Credentials: ${sources.join(", ")}`);
558
+ }
559
+ console.log(` Config: ${configPath}`);
560
+ console.log("");
561
+ }
562
+ //# sourceMappingURL=activate.js.map