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.
- package/CHANGELOG.md +27 -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 +562 -0
- package/dist/commands/deactivate.d.ts +10 -0
- package/dist/commands/deactivate.js +95 -0
- package/dist/commands/migrate-local.d.ts +53 -0
- package/dist/commands/migrate-local.js +177 -0
- package/dist/hooks/format-utils.js +4 -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 +33 -2
- package/dist/services/analytics.d.ts +22 -0
- package/dist/services/analytics.js +68 -0
- package/dist/services/doc-chunker.d.ts +45 -0
- package/dist/services/doc-chunker.js +208 -0
- package/dist/services/doc-index.d.ts +88 -0
- package/dist/services/doc-index.js +328 -0
- 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/tools/definitions.d.ts +688 -0
- package/dist/tools/definitions.js +87 -0
- package/dist/tools/index-docs.d.ts +30 -0
- package/dist/tools/index-docs.js +163 -0
- package/dist/tools/prepare-context.js +7 -0
- package/dist/tools/recall.js +25 -4
- package/dist/tools/search-docs.d.ts +38 -0
- package/dist/tools/search-docs.js +94 -0
- package/dist/tools/search.js +11 -1
- package/dist/tools/session-close.js +76 -7
- package/dist/tools/session-start.js +57 -5
- package/package.json +1 -1
- 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
|
|
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
|
|
144
|
+
| **A/B testing analytics** | Measure which scar phrasings actually change agent behavior |
|
|
140
145
|
|
|
141
|
-
|
|
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
|
-
[
|
|
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
|