@mycontxt/cli 0.1.7 → 0.1.8
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/dist/contxt.js +702 -423
- package/dist/contxt.js.map +1 -1
- package/package.json +2 -2
package/dist/contxt.js
CHANGED
|
@@ -10,8 +10,10 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
10
10
|
import { Command } from "commander";
|
|
11
11
|
|
|
12
12
|
// src/commands/init.ts
|
|
13
|
-
import { mkdirSync } from "fs";
|
|
14
|
-
import { basename } from "path";
|
|
13
|
+
import { mkdirSync, existsSync as existsSync3, writeFileSync, readFileSync as readFileSync2, chmodSync } from "fs";
|
|
14
|
+
import { basename, join as join3 } from "path";
|
|
15
|
+
import { homedir as homedir2 } from "os";
|
|
16
|
+
import { spawn } from "child_process";
|
|
15
17
|
import { SQLiteDatabase as SQLiteDatabase2 } from "@mycontxt/adapters/sqlite";
|
|
16
18
|
|
|
17
19
|
// src/utils/project.ts
|
|
@@ -85,6 +87,9 @@ function error(message) {
|
|
|
85
87
|
function info(message) {
|
|
86
88
|
console.log(chalk.blue("\u2139"), message);
|
|
87
89
|
}
|
|
90
|
+
function warn(message) {
|
|
91
|
+
console.log(chalk.yellow("\u26A0"), message);
|
|
92
|
+
}
|
|
88
93
|
function formatEntry(entry) {
|
|
89
94
|
const lines = [];
|
|
90
95
|
lines.push(chalk.bold(entry.title));
|
|
@@ -119,11 +124,236 @@ function formatEntryList(entries) {
|
|
|
119
124
|
return ` ${id} ${title} ${time}`;
|
|
120
125
|
}).join("\n");
|
|
121
126
|
}
|
|
127
|
+
function loading(message) {
|
|
128
|
+
console.log(chalk.cyan("~"), message);
|
|
129
|
+
}
|
|
130
|
+
function dryRun(message) {
|
|
131
|
+
console.log(chalk.dim("\u2192"), chalk.dim(message));
|
|
132
|
+
}
|
|
133
|
+
function conflict(message) {
|
|
134
|
+
console.log(chalk.yellow("!"), message);
|
|
135
|
+
}
|
|
136
|
+
function header(title) {
|
|
137
|
+
console.log(chalk.bold.cyan("\u203A"), chalk.bold(title));
|
|
138
|
+
}
|
|
122
139
|
function section(title) {
|
|
123
140
|
return chalk.bold.underline(title);
|
|
124
141
|
}
|
|
125
142
|
|
|
143
|
+
// src/utils/usage-gate.ts
|
|
144
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
145
|
+
import { homedir } from "os";
|
|
146
|
+
import { join as join2 } from "path";
|
|
147
|
+
import chalk2 from "chalk";
|
|
148
|
+
import { UsageGate } from "@mycontxt/core/engine/usage";
|
|
149
|
+
import { resolveUserPlan } from "@mycontxt/core/engine/plan-resolver";
|
|
150
|
+
import { SupabaseDatabase } from "@mycontxt/adapters/supabase";
|
|
151
|
+
|
|
152
|
+
// src/config.ts
|
|
153
|
+
var SUPABASE_URL = "";
|
|
154
|
+
var SUPABASE_ANON_KEY = "";
|
|
155
|
+
function getSupabaseConfig() {
|
|
156
|
+
if (!SUPABASE_URL || !SUPABASE_ANON_KEY) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
"Contxt is not configured for cloud sync.\nIf you installed via npm, please reinstall the latest version.\nIf building locally, set CONTXT_SUPABASE_URL and CONTXT_SUPABASE_ANON_KEY before running `pnpm build`."
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
return { url: SUPABASE_URL, anonKey: SUPABASE_ANON_KEY };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/utils/usage-gate.ts
|
|
165
|
+
var AUTH_FILE = join2(homedir(), ".contxt", "auth.json");
|
|
166
|
+
function getCurrentUserId() {
|
|
167
|
+
if (!existsSync2(AUTH_FILE)) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const content = readFileSync(AUTH_FILE, "utf-8");
|
|
172
|
+
const auth2 = JSON.parse(content);
|
|
173
|
+
return auth2.userId;
|
|
174
|
+
} catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function getRemoteDb() {
|
|
179
|
+
if (!existsSync2(AUTH_FILE)) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const content = readFileSync(AUTH_FILE, "utf-8");
|
|
184
|
+
const auth2 = JSON.parse(content);
|
|
185
|
+
const config = getSupabaseConfig();
|
|
186
|
+
return new SupabaseDatabase({
|
|
187
|
+
url: config.url,
|
|
188
|
+
anonKey: config.anonKey,
|
|
189
|
+
accessToken: auth2.accessToken
|
|
190
|
+
});
|
|
191
|
+
} catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function getUsageCounts(localDb, userId, projectId) {
|
|
196
|
+
const projectCountQuery = "SELECT COUNT(*) as count FROM projects";
|
|
197
|
+
const projectRow = localDb.db.prepare(projectCountQuery).get();
|
|
198
|
+
const totalProjects = projectRow.count;
|
|
199
|
+
const totalEntriesQuery = "SELECT COUNT(*) as count FROM memory_entries WHERE is_archived = 0";
|
|
200
|
+
const totalEntriesRow = localDb.db.prepare(totalEntriesQuery).get();
|
|
201
|
+
const totalEntries = totalEntriesRow.count;
|
|
202
|
+
let entriesInProject = 0;
|
|
203
|
+
if (projectId) {
|
|
204
|
+
const projectEntriesQuery = "SELECT COUNT(*) as count FROM memory_entries WHERE project_id = ? AND is_archived = 0";
|
|
205
|
+
const projectEntriesRow = localDb.db.prepare(projectEntriesQuery).get(projectId);
|
|
206
|
+
entriesInProject = projectEntriesRow.count;
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
totalProjects,
|
|
210
|
+
totalEntries,
|
|
211
|
+
entriesInProject,
|
|
212
|
+
totalSeats: 1
|
|
213
|
+
// Single user for now
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
async function createUsageGate(localDb, projectId) {
|
|
217
|
+
const userId = getCurrentUserId();
|
|
218
|
+
const remoteDb = getRemoteDb();
|
|
219
|
+
const planId = await resolveUserPlan(localDb, remoteDb, userId);
|
|
220
|
+
return new UsageGate(planId, () => getUsageCounts(localDb, userId, projectId));
|
|
221
|
+
}
|
|
222
|
+
function enforceGate(result) {
|
|
223
|
+
if (!result.allowed) {
|
|
224
|
+
console.error(chalk2.red(`
|
|
225
|
+
\u2716 ${result.reason}`));
|
|
226
|
+
console.error(chalk2.dim(` Current: ${result.current} / Limit: ${result.limit === null ? "\u221E" : result.limit}`));
|
|
227
|
+
console.error(chalk2.cyan(`
|
|
228
|
+
${result.upgradeHint}
|
|
229
|
+
`));
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
126
234
|
// src/commands/init.ts
|
|
235
|
+
var CLAUDE_MD_TEMPLATE = `# Contxt \u2014 Active Context Capture
|
|
236
|
+
|
|
237
|
+
This project uses [Contxt](https://mycontxt.co) (MCP) to automatically capture technical decisions and patterns from your conversations.
|
|
238
|
+
|
|
239
|
+
## Your Role
|
|
240
|
+
|
|
241
|
+
During every conversation in this project, actively capture knowledge using the Contxt MCP tools:
|
|
242
|
+
|
|
243
|
+
- When the developer makes an architectural or technical decision \u2192 call \`contxt_auto_capture_decision\` or \`contxt_capture_discussion\`
|
|
244
|
+
- When a reusable code pattern or convention is established \u2192 call \`contxt_auto_capture_pattern\`
|
|
245
|
+
- At the end of each conversation \u2192 call \`contxt_update_session\` with a summary of what was accomplished
|
|
246
|
+
|
|
247
|
+
Use a consistent \`conversationId\` (a UUID you generate at the start of the session) on every tool call so entries from the same conversation are linked.
|
|
248
|
+
|
|
249
|
+
## When to Capture a Decision
|
|
250
|
+
|
|
251
|
+
Capture a **decision** when the developer (or you together):
|
|
252
|
+
- Chooses one technology, library, or approach over alternatives
|
|
253
|
+
- Establishes a project structure, naming convention, or architectural boundary
|
|
254
|
+
- Resolves a tradeoff (e.g. performance vs. simplicity, SQL vs. NoSQL)
|
|
255
|
+
- Says "let's use X", "we'll go with Y", or "we decided to Z"
|
|
256
|
+
|
|
257
|
+
Use \`contxt_capture_discussion\` when the decision came out of back-and-forth so you can capture the context and what was rejected.
|
|
258
|
+
|
|
259
|
+
## When to Capture a Pattern
|
|
260
|
+
|
|
261
|
+
Capture a **pattern** when:
|
|
262
|
+
- A code shape is established that will be repeated (component structure, API format, hook convention, etc.)
|
|
263
|
+
- A rule is set about how something should always be done in this project
|
|
264
|
+
|
|
265
|
+
## What NOT to Capture
|
|
266
|
+
|
|
267
|
+
- One-off debugging steps or experiments
|
|
268
|
+
- Trivial implementation details (e.g. "added a console.log")
|
|
269
|
+
- Things the developer explicitly says to ignore
|
|
270
|
+
|
|
271
|
+
## After Capture
|
|
272
|
+
|
|
273
|
+
All entries are saved as **drafts**. The developer reviews and confirms them with:
|
|
274
|
+
\`\`\`
|
|
275
|
+
contxt review
|
|
276
|
+
\`\`\`
|
|
277
|
+
`;
|
|
278
|
+
var MCP_CONFIG = {
|
|
279
|
+
mcpServers: {
|
|
280
|
+
contxt: {
|
|
281
|
+
command: "contxt",
|
|
282
|
+
args: ["mcp"],
|
|
283
|
+
env: {}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
var CONTXT_BLOCK_START = "# --- contxt hook start ---";
|
|
288
|
+
var CONTXT_BLOCK_END = "# --- contxt hook end ---";
|
|
289
|
+
var ALL_HOOKS = ["post-commit", "pre-push", "post-checkout", "prepare-commit-msg"];
|
|
290
|
+
function writeMcpConfigs(cwd) {
|
|
291
|
+
const mcpJson = JSON.stringify(MCP_CONFIG, null, 2);
|
|
292
|
+
writeFileSync(join3(cwd, ".mcp.json"), mcpJson, "utf-8");
|
|
293
|
+
const cursorDir = join3(cwd, ".cursor");
|
|
294
|
+
mkdirSync(cursorDir, { recursive: true });
|
|
295
|
+
writeFileSync(join3(cursorDir, "mcp.json"), mcpJson, "utf-8");
|
|
296
|
+
}
|
|
297
|
+
function installGitHooks(cwd) {
|
|
298
|
+
const gitDir = join3(cwd, ".git");
|
|
299
|
+
if (!existsSync3(gitDir)) return false;
|
|
300
|
+
const gitHooksDir = join3(gitDir, "hooks");
|
|
301
|
+
mkdirSync(gitHooksDir, { recursive: true });
|
|
302
|
+
for (const hookName of ALL_HOOKS) {
|
|
303
|
+
const hookPath = join3(gitHooksDir, hookName);
|
|
304
|
+
const contxtBlock = `${CONTXT_BLOCK_START}
|
|
305
|
+
contxt hook run ${hookName} "$@"
|
|
306
|
+
${CONTXT_BLOCK_END}`;
|
|
307
|
+
if (existsSync3(hookPath)) {
|
|
308
|
+
const content = readFileSync2(hookPath, "utf-8");
|
|
309
|
+
if (!content.includes(CONTXT_BLOCK_START)) {
|
|
310
|
+
writeFileSync(hookPath, content.trimEnd() + "\n\n" + contxtBlock + "\n", "utf-8");
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
writeFileSync(hookPath, `#!/bin/sh
|
|
314
|
+
|
|
315
|
+
${contxtBlock}
|
|
316
|
+
`, "utf-8");
|
|
317
|
+
}
|
|
318
|
+
chmodSync(hookPath, "755");
|
|
319
|
+
}
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
function startWatchDaemon(cwd) {
|
|
323
|
+
const pidFile = join3(cwd, ".contxt", ".watch.pid");
|
|
324
|
+
if (existsSync3(pidFile)) return;
|
|
325
|
+
const child = spawn("contxt", ["watch", "--daemon"], {
|
|
326
|
+
detached: true,
|
|
327
|
+
stdio: "ignore",
|
|
328
|
+
cwd
|
|
329
|
+
});
|
|
330
|
+
child.unref();
|
|
331
|
+
}
|
|
332
|
+
function registerClaudeCodeHook() {
|
|
333
|
+
const claudeDir = join3(homedir2(), ".claude");
|
|
334
|
+
const settingsPath = join3(claudeDir, "settings.json");
|
|
335
|
+
const hookCommand2 = `bash -c 'PROMPT=$(cat | jq -r ".prompt // empty" 2>/dev/null | head -c 300); [ -n "$PROMPT" ] && contxt load --task "$PROMPT" 2>/dev/null || true'`;
|
|
336
|
+
let settings = {};
|
|
337
|
+
if (existsSync3(settingsPath)) {
|
|
338
|
+
try {
|
|
339
|
+
settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
|
|
340
|
+
} catch {
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (!settings.hooks) settings.hooks = {};
|
|
344
|
+
if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
|
|
345
|
+
const alreadyRegistered = settings.hooks.UserPromptSubmit.some(
|
|
346
|
+
(h) => h.hooks?.some((hh) => typeof hh.command === "string" && hh.command.includes("contxt load"))
|
|
347
|
+
);
|
|
348
|
+
if (!alreadyRegistered) {
|
|
349
|
+
settings.hooks.UserPromptSubmit.push({
|
|
350
|
+
matcher: "",
|
|
351
|
+
hooks: [{ type: "command", command: hookCommand2 }]
|
|
352
|
+
});
|
|
353
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
354
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
355
|
+
}
|
|
356
|
+
}
|
|
127
357
|
async function initCommand(options) {
|
|
128
358
|
try {
|
|
129
359
|
const cwd = process.cwd();
|
|
@@ -133,25 +363,34 @@ async function initCommand(options) {
|
|
|
133
363
|
}
|
|
134
364
|
const contxtDir = getContxtDir(cwd);
|
|
135
365
|
mkdirSync(contxtDir, { recursive: true });
|
|
366
|
+
const claudeMdPath = join3(contxtDir, "CLAUDE.md");
|
|
367
|
+
writeFileSync(claudeMdPath, CLAUDE_MD_TEMPLATE, "utf-8");
|
|
136
368
|
const dbPath = getDbPath(cwd);
|
|
137
369
|
const db = new SQLiteDatabase2(dbPath);
|
|
138
370
|
await db.initialize();
|
|
371
|
+
const gate = await createUsageGate(db);
|
|
372
|
+
const result = await gate.checkProjectCreate();
|
|
373
|
+
enforceGate(result);
|
|
139
374
|
const projectName = options.name || basename(cwd);
|
|
140
375
|
const project = await db.initProject({
|
|
141
376
|
name: projectName,
|
|
142
377
|
path: cwd,
|
|
143
|
-
stack: []
|
|
144
|
-
|
|
378
|
+
stack: [],
|
|
379
|
+
config: { autoSync: true }
|
|
145
380
|
});
|
|
381
|
+
await db.close();
|
|
382
|
+
writeMcpConfigs(cwd);
|
|
383
|
+
const hooksInstalled = installGitHooks(cwd);
|
|
384
|
+
startWatchDaemon(cwd);
|
|
385
|
+
registerClaudeCodeHook();
|
|
146
386
|
success(`Initialized Contxt project: ${project.name}`);
|
|
147
|
-
info(`Project ID: ${project.id}`);
|
|
148
|
-
info(`Database: ${dbPath}`);
|
|
149
387
|
console.log();
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
388
|
+
info("\u2713 MCP server configured (.mcp.json + .cursor/mcp.json)");
|
|
389
|
+
if (hooksInstalled) info("\u2713 Git hooks installed (post-commit, pre-push, post-checkout)");
|
|
390
|
+
info("\u2713 Watch daemon started (auto-sync enabled)");
|
|
391
|
+
info("\u2713 Claude Code context hook registered");
|
|
392
|
+
console.log();
|
|
393
|
+
console.log("Contxt is running. You won't need to think about it again.");
|
|
155
394
|
} catch (err) {
|
|
156
395
|
error(`Failed to initialize project: ${err.message}`);
|
|
157
396
|
process.exit(1);
|
|
@@ -162,6 +401,9 @@ async function initCommand(options) {
|
|
|
162
401
|
async function add(options) {
|
|
163
402
|
try {
|
|
164
403
|
const { engine, projectId, db } = await loadProject();
|
|
404
|
+
const gate = await createUsageGate(db, projectId);
|
|
405
|
+
const result = await gate.checkEntryCreate();
|
|
406
|
+
enforceGate(result);
|
|
165
407
|
const input = {
|
|
166
408
|
title: options.title,
|
|
167
409
|
rationale: options.rationale,
|
|
@@ -212,6 +454,9 @@ var decisionCommand = {
|
|
|
212
454
|
async function add2(options) {
|
|
213
455
|
try {
|
|
214
456
|
const { engine, projectId, db } = await loadProject();
|
|
457
|
+
const gate = await createUsageGate(db, projectId);
|
|
458
|
+
const result = await gate.checkEntryCreate();
|
|
459
|
+
enforceGate(result);
|
|
215
460
|
const input = {
|
|
216
461
|
title: options.title,
|
|
217
462
|
content: options.content,
|
|
@@ -314,7 +559,7 @@ var contextCommand = {
|
|
|
314
559
|
};
|
|
315
560
|
|
|
316
561
|
// src/commands/status.ts
|
|
317
|
-
import
|
|
562
|
+
import chalk3 from "chalk";
|
|
318
563
|
async function statusCommand() {
|
|
319
564
|
try {
|
|
320
565
|
const { engine, projectId, db } = await loadProject();
|
|
@@ -333,14 +578,14 @@ async function statusCommand() {
|
|
|
333
578
|
console.log();
|
|
334
579
|
console.log(section("Project Status"));
|
|
335
580
|
console.log();
|
|
336
|
-
console.log(
|
|
337
|
-
console.log(
|
|
338
|
-
console.log(
|
|
581
|
+
console.log(chalk3.bold("Name:"), project.name);
|
|
582
|
+
console.log(chalk3.bold("Path:"), project.path);
|
|
583
|
+
console.log(chalk3.bold("ID:"), project.id);
|
|
339
584
|
console.log();
|
|
340
585
|
console.log(section("Branches"));
|
|
341
586
|
console.log();
|
|
342
587
|
for (const branch2 of branches) {
|
|
343
|
-
const marker = branch2.name === activeBranch ?
|
|
588
|
+
const marker = branch2.name === activeBranch ? chalk3.green("\u25CF") : " ";
|
|
344
589
|
console.log(` ${marker} ${branch2.name}`);
|
|
345
590
|
}
|
|
346
591
|
console.log();
|
|
@@ -356,21 +601,21 @@ async function statusCommand() {
|
|
|
356
601
|
console.log(section("Current Context"));
|
|
357
602
|
console.log();
|
|
358
603
|
if (context2.metadata.feature) {
|
|
359
|
-
console.log(
|
|
604
|
+
console.log(chalk3.bold("Feature:"), context2.metadata.feature);
|
|
360
605
|
}
|
|
361
606
|
if (context2.metadata.blockers?.length > 0) {
|
|
362
|
-
console.log(
|
|
607
|
+
console.log(chalk3.bold("Blockers:"));
|
|
363
608
|
context2.metadata.blockers.forEach((b) => console.log(` - ${b}`));
|
|
364
609
|
}
|
|
365
610
|
if (context2.metadata.nextSteps?.length > 0) {
|
|
366
|
-
console.log(
|
|
611
|
+
console.log(chalk3.bold("Next Steps:"));
|
|
367
612
|
context2.metadata.nextSteps.forEach((s) => console.log(` - ${s}`));
|
|
368
613
|
}
|
|
369
614
|
console.log();
|
|
370
615
|
}
|
|
371
616
|
if (unsynced.length > 0) {
|
|
372
|
-
console.log(
|
|
373
|
-
console.log(
|
|
617
|
+
console.log(chalk3.yellow(`\u26A0 ${unsynced.length} unsynced entries`));
|
|
618
|
+
console.log(chalk3.dim(' Run "contxt push" to sync'));
|
|
374
619
|
console.log();
|
|
375
620
|
}
|
|
376
621
|
await db.close();
|
|
@@ -381,14 +626,14 @@ async function statusCommand() {
|
|
|
381
626
|
}
|
|
382
627
|
|
|
383
628
|
// src/commands/doc.ts
|
|
384
|
-
import { readFileSync } from "fs";
|
|
629
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
385
630
|
async function add3(options) {
|
|
386
631
|
try {
|
|
387
632
|
const { engine, projectId, db } = await loadProject();
|
|
388
633
|
let content = options.content || "";
|
|
389
634
|
if (options.file) {
|
|
390
635
|
try {
|
|
391
|
-
content =
|
|
636
|
+
content = readFileSync3(options.file, "utf-8");
|
|
392
637
|
} catch (err) {
|
|
393
638
|
error(`Failed to read file: ${err.message}`);
|
|
394
639
|
process.exit(1);
|
|
@@ -530,7 +775,7 @@ async function searchCommand(query, options) {
|
|
|
530
775
|
}
|
|
531
776
|
|
|
532
777
|
// src/commands/export.ts
|
|
533
|
-
import { writeFileSync, readFileSync as
|
|
778
|
+
import { writeFileSync as writeFileSync2, readFileSync as readFileSync4 } from "fs";
|
|
534
779
|
async function exportCommand(options) {
|
|
535
780
|
try {
|
|
536
781
|
const { engine, projectId, db } = await loadProject();
|
|
@@ -557,7 +802,7 @@ async function exportCommand(options) {
|
|
|
557
802
|
};
|
|
558
803
|
const json = JSON.stringify(exportData, null, 2);
|
|
559
804
|
if (options.output) {
|
|
560
|
-
|
|
805
|
+
writeFileSync2(options.output, json, "utf-8");
|
|
561
806
|
success(`Exported ${entries.length} entries to ${options.output}`);
|
|
562
807
|
} else {
|
|
563
808
|
console.log(json);
|
|
@@ -573,7 +818,7 @@ async function importCommand(options) {
|
|
|
573
818
|
const { engine, projectId, db } = await loadProject();
|
|
574
819
|
let data;
|
|
575
820
|
try {
|
|
576
|
-
const json =
|
|
821
|
+
const json = readFileSync4(options.file, "utf-8");
|
|
577
822
|
data = JSON.parse(json);
|
|
578
823
|
} catch (err) {
|
|
579
824
|
error(`Failed to read import file: ${err.message}`);
|
|
@@ -610,37 +855,23 @@ async function importCommand(options) {
|
|
|
610
855
|
|
|
611
856
|
// src/commands/auth.ts
|
|
612
857
|
import { SupabaseAuth } from "@mycontxt/adapters/supabase-auth";
|
|
613
|
-
import { existsSync as
|
|
614
|
-
import { homedir } from "os";
|
|
615
|
-
import { join as
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
var SUPABASE_URL = "https://nreejrbrgbpcteliqqxm.supabase.co";
|
|
619
|
-
var SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5yZWVqcmJyZ2JwY3RlbGlxcXhtIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzEyODU3MDcsImV4cCI6MjA4Njg2MTcwN30.1oMu3WWwPQj1PU9jkAeWt7dbzaS4LN99pMkc299RWhY";
|
|
620
|
-
function getSupabaseConfig() {
|
|
621
|
-
if (!SUPABASE_URL || !SUPABASE_ANON_KEY) {
|
|
622
|
-
throw new Error(
|
|
623
|
-
"Contxt is not configured for cloud sync.\nIf you installed via npm, please reinstall the latest version.\nIf building locally, set CONTXT_SUPABASE_URL and CONTXT_SUPABASE_ANON_KEY before running `pnpm build`."
|
|
624
|
-
);
|
|
625
|
-
}
|
|
626
|
-
return { url: SUPABASE_URL, anonKey: SUPABASE_ANON_KEY };
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// src/commands/auth.ts
|
|
630
|
-
var CONFIG_DIR = join2(homedir(), ".contxt");
|
|
631
|
-
var AUTH_FILE = join2(CONFIG_DIR, "auth.json");
|
|
858
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync5 } from "fs";
|
|
859
|
+
import { homedir as homedir3 } from "os";
|
|
860
|
+
import { join as join4 } from "path";
|
|
861
|
+
var CONFIG_DIR = join4(homedir3(), ".contxt");
|
|
862
|
+
var AUTH_FILE2 = join4(CONFIG_DIR, "auth.json");
|
|
632
863
|
function saveAuthData(data) {
|
|
633
|
-
if (!
|
|
864
|
+
if (!existsSync4(CONFIG_DIR)) {
|
|
634
865
|
mkdirSync2(CONFIG_DIR, { recursive: true });
|
|
635
866
|
}
|
|
636
|
-
|
|
867
|
+
writeFileSync3(AUTH_FILE2, JSON.stringify(data, null, 2), "utf-8");
|
|
637
868
|
}
|
|
638
869
|
function loadAuthData() {
|
|
639
|
-
if (!
|
|
870
|
+
if (!existsSync4(AUTH_FILE2)) {
|
|
640
871
|
return null;
|
|
641
872
|
}
|
|
642
873
|
try {
|
|
643
|
-
const content =
|
|
874
|
+
const content = readFileSync5(AUTH_FILE2, "utf-8");
|
|
644
875
|
return JSON.parse(content);
|
|
645
876
|
} catch {
|
|
646
877
|
return null;
|
|
@@ -651,10 +882,12 @@ var authCommand = {
|
|
|
651
882
|
try {
|
|
652
883
|
const config = getSupabaseConfig();
|
|
653
884
|
const auth2 = new SupabaseAuth(config);
|
|
654
|
-
|
|
885
|
+
header("Contxt Authentication");
|
|
886
|
+
console.log("");
|
|
655
887
|
if (options.email) {
|
|
656
888
|
await auth2.loginWithMagicLink(options.email);
|
|
657
|
-
console.log("
|
|
889
|
+
console.log("");
|
|
890
|
+
success("Magic link sent! Check your email and click the link.");
|
|
658
891
|
console.log(" Then run `contxt auth status` to verify.");
|
|
659
892
|
} else {
|
|
660
893
|
console.log("Opening browser for GitHub authentication...\n");
|
|
@@ -665,18 +898,16 @@ var authCommand = {
|
|
|
665
898
|
email: result.user.email,
|
|
666
899
|
githubUsername: result.user.githubUsername
|
|
667
900
|
});
|
|
668
|
-
console.log("
|
|
901
|
+
console.log("");
|
|
902
|
+
success("Successfully authenticated!");
|
|
669
903
|
console.log(` Email: ${result.user.email}`);
|
|
670
904
|
if (result.user.githubUsername) {
|
|
671
905
|
console.log(` GitHub: @${result.user.githubUsername}`);
|
|
672
906
|
}
|
|
673
907
|
console.log("\nYou can now use `contxt push` and `contxt pull` to sync your memory.");
|
|
674
908
|
}
|
|
675
|
-
} catch (
|
|
676
|
-
|
|
677
|
-
"\u274C Authentication failed:",
|
|
678
|
-
error2 instanceof Error ? error2.message : error2
|
|
679
|
-
);
|
|
909
|
+
} catch (err) {
|
|
910
|
+
error(`Authentication failed: ${err instanceof Error ? err.message : err}`);
|
|
680
911
|
process.exit(1);
|
|
681
912
|
}
|
|
682
913
|
},
|
|
@@ -685,16 +916,13 @@ var authCommand = {
|
|
|
685
916
|
const config = getSupabaseConfig();
|
|
686
917
|
const auth2 = new SupabaseAuth(config);
|
|
687
918
|
await auth2.logout();
|
|
688
|
-
if (
|
|
919
|
+
if (existsSync4(AUTH_FILE2)) {
|
|
689
920
|
const fs = await import("fs/promises");
|
|
690
|
-
await fs.unlink(
|
|
921
|
+
await fs.unlink(AUTH_FILE2);
|
|
691
922
|
}
|
|
692
|
-
|
|
693
|
-
} catch (
|
|
694
|
-
|
|
695
|
-
"\u274C Logout failed:",
|
|
696
|
-
error2 instanceof Error ? error2.message : error2
|
|
697
|
-
);
|
|
923
|
+
success("Logged out successfully");
|
|
924
|
+
} catch (err) {
|
|
925
|
+
error(`Logout failed: ${err instanceof Error ? err.message : err}`);
|
|
698
926
|
process.exit(1);
|
|
699
927
|
}
|
|
700
928
|
},
|
|
@@ -702,11 +930,11 @@ var authCommand = {
|
|
|
702
930
|
try {
|
|
703
931
|
const authData = loadAuthData();
|
|
704
932
|
if (!authData) {
|
|
705
|
-
|
|
933
|
+
error("Not authenticated");
|
|
706
934
|
console.log("\nRun `contxt auth login` to authenticate.");
|
|
707
935
|
process.exit(1);
|
|
708
936
|
}
|
|
709
|
-
|
|
937
|
+
success("Authenticated");
|
|
710
938
|
console.log(` Email: ${authData.email}`);
|
|
711
939
|
if (authData.githubUsername) {
|
|
712
940
|
console.log(` GitHub: @${authData.githubUsername}`);
|
|
@@ -716,15 +944,14 @@ var authCommand = {
|
|
|
716
944
|
const auth2 = new SupabaseAuth(config);
|
|
717
945
|
try {
|
|
718
946
|
await auth2.refreshSession();
|
|
719
|
-
console.log("
|
|
947
|
+
console.log("");
|
|
948
|
+
success("Session is valid");
|
|
720
949
|
} catch {
|
|
721
|
-
console.log("
|
|
950
|
+
console.log("");
|
|
951
|
+
warn("Session expired. Run `contxt auth login` to re-authenticate.");
|
|
722
952
|
}
|
|
723
|
-
} catch (
|
|
724
|
-
|
|
725
|
-
"\u274C Status check failed:",
|
|
726
|
-
error2 instanceof Error ? error2.message : error2
|
|
727
|
-
);
|
|
953
|
+
} catch (err) {
|
|
954
|
+
error(`Status check failed: ${err instanceof Error ? err.message : err}`);
|
|
728
955
|
process.exit(1);
|
|
729
956
|
}
|
|
730
957
|
}
|
|
@@ -736,7 +963,7 @@ function getAccessToken() {
|
|
|
736
963
|
|
|
737
964
|
// src/commands/sync.ts
|
|
738
965
|
import { SQLiteDatabase as SQLiteDatabase3 } from "@mycontxt/adapters/sqlite";
|
|
739
|
-
import { SupabaseDatabase } from "@mycontxt/adapters/supabase";
|
|
966
|
+
import { SupabaseDatabase as SupabaseDatabase2 } from "@mycontxt/adapters/supabase";
|
|
740
967
|
import { SyncEngine } from "@mycontxt/core";
|
|
741
968
|
var syncCommand = {
|
|
742
969
|
/**
|
|
@@ -746,7 +973,7 @@ var syncCommand = {
|
|
|
746
973
|
try {
|
|
747
974
|
const accessToken = getAccessToken();
|
|
748
975
|
if (!accessToken) {
|
|
749
|
-
|
|
976
|
+
error("Not authenticated. Run `contxt auth login` first.");
|
|
750
977
|
process.exit(1);
|
|
751
978
|
}
|
|
752
979
|
const dbPath = getDbPath();
|
|
@@ -756,45 +983,41 @@ var syncCommand = {
|
|
|
756
983
|
const cwd = process.cwd();
|
|
757
984
|
const project = await localDb.getProjectByPath(cwd);
|
|
758
985
|
if (!project) {
|
|
759
|
-
|
|
986
|
+
error("No Contxt project found. Run `contxt init` first.");
|
|
760
987
|
process.exit(1);
|
|
761
988
|
}
|
|
762
989
|
const supabaseConfig = getSupabaseConfig();
|
|
763
|
-
const remoteDb = new
|
|
990
|
+
const remoteDb = new SupabaseDatabase2({
|
|
764
991
|
...supabaseConfig,
|
|
765
992
|
accessToken
|
|
766
993
|
});
|
|
767
994
|
await remoteDb.initialize();
|
|
768
995
|
const syncEngine = new SyncEngine(localDb, remoteDb);
|
|
769
|
-
|
|
996
|
+
loading("Pushing local changes to cloud...");
|
|
997
|
+
console.log("");
|
|
770
998
|
const result = await syncEngine.push(project.id, {
|
|
771
999
|
force: options.force,
|
|
772
1000
|
dryRun: options.dryRun
|
|
773
1001
|
});
|
|
774
1002
|
if (result.errors.length > 0) {
|
|
775
|
-
|
|
1003
|
+
error("Push failed:");
|
|
776
1004
|
result.errors.forEach((err) => console.error(` ${err}`));
|
|
777
1005
|
process.exit(1);
|
|
778
1006
|
}
|
|
779
1007
|
if (options.dryRun) {
|
|
780
|
-
|
|
1008
|
+
dryRun(`Dry run \u2014 would push ${result.pushed} entries`);
|
|
781
1009
|
} else {
|
|
782
|
-
|
|
1010
|
+
success(`Pushed ${result.pushed} entries`);
|
|
783
1011
|
}
|
|
784
1012
|
if (result.conflicts > 0) {
|
|
785
|
-
|
|
786
|
-
`\u26A0\uFE0F ${result.conflicts} conflict(s) detected. Use --force to override.`
|
|
787
|
-
);
|
|
1013
|
+
conflict(`${result.conflicts} conflict(s) detected. Use --force to override.`);
|
|
788
1014
|
}
|
|
789
1015
|
await remoteDb.close();
|
|
790
1016
|
} finally {
|
|
791
1017
|
await localDb.close();
|
|
792
1018
|
}
|
|
793
|
-
} catch (
|
|
794
|
-
|
|
795
|
-
"\u274C Push failed:",
|
|
796
|
-
error2 instanceof Error ? error2.message : error2
|
|
797
|
-
);
|
|
1019
|
+
} catch (err) {
|
|
1020
|
+
error(`Push failed: ${err instanceof Error ? err.message : err}`);
|
|
798
1021
|
process.exit(1);
|
|
799
1022
|
}
|
|
800
1023
|
},
|
|
@@ -805,7 +1028,7 @@ var syncCommand = {
|
|
|
805
1028
|
try {
|
|
806
1029
|
const accessToken = getAccessToken();
|
|
807
1030
|
if (!accessToken) {
|
|
808
|
-
|
|
1031
|
+
error("Not authenticated. Run `contxt auth login` first.");
|
|
809
1032
|
process.exit(1);
|
|
810
1033
|
}
|
|
811
1034
|
const dbPath = getDbPath();
|
|
@@ -815,45 +1038,41 @@ var syncCommand = {
|
|
|
815
1038
|
const cwd = process.cwd();
|
|
816
1039
|
const project = await localDb.getProjectByPath(cwd);
|
|
817
1040
|
if (!project) {
|
|
818
|
-
|
|
1041
|
+
error("No Contxt project found. Run `contxt init` first.");
|
|
819
1042
|
process.exit(1);
|
|
820
1043
|
}
|
|
821
1044
|
const supabaseConfig = getSupabaseConfig();
|
|
822
|
-
const remoteDb = new
|
|
1045
|
+
const remoteDb = new SupabaseDatabase2({
|
|
823
1046
|
...supabaseConfig,
|
|
824
1047
|
accessToken
|
|
825
1048
|
});
|
|
826
1049
|
await remoteDb.initialize();
|
|
827
1050
|
const syncEngine = new SyncEngine(localDb, remoteDb);
|
|
828
|
-
|
|
1051
|
+
loading("Pulling remote changes to local...");
|
|
1052
|
+
console.log("");
|
|
829
1053
|
const result = await syncEngine.pull(project.id, {
|
|
830
1054
|
force: options.force,
|
|
831
1055
|
dryRun: options.dryRun
|
|
832
1056
|
});
|
|
833
1057
|
if (result.errors.length > 0) {
|
|
834
|
-
|
|
1058
|
+
error("Pull failed:");
|
|
835
1059
|
result.errors.forEach((err) => console.error(` ${err}`));
|
|
836
1060
|
process.exit(1);
|
|
837
1061
|
}
|
|
838
1062
|
if (options.dryRun) {
|
|
839
|
-
|
|
1063
|
+
dryRun(`Dry run \u2014 would pull ${result.pulled} entries`);
|
|
840
1064
|
} else {
|
|
841
|
-
|
|
1065
|
+
success(`Pulled ${result.pulled} entries`);
|
|
842
1066
|
}
|
|
843
1067
|
if (result.conflicts > 0) {
|
|
844
|
-
|
|
845
|
-
`\u26A0\uFE0F ${result.conflicts} conflict(s) detected. Use --force to override.`
|
|
846
|
-
);
|
|
1068
|
+
conflict(`${result.conflicts} conflict(s) detected. Use --force to override.`);
|
|
847
1069
|
}
|
|
848
1070
|
await remoteDb.close();
|
|
849
1071
|
} finally {
|
|
850
1072
|
await localDb.close();
|
|
851
1073
|
}
|
|
852
|
-
} catch (
|
|
853
|
-
|
|
854
|
-
"\u274C Pull failed:",
|
|
855
|
-
error2 instanceof Error ? error2.message : error2
|
|
856
|
-
);
|
|
1074
|
+
} catch (err) {
|
|
1075
|
+
error(`Pull failed: ${err instanceof Error ? err.message : err}`);
|
|
857
1076
|
process.exit(1);
|
|
858
1077
|
}
|
|
859
1078
|
},
|
|
@@ -864,7 +1083,7 @@ var syncCommand = {
|
|
|
864
1083
|
try {
|
|
865
1084
|
const accessToken = getAccessToken();
|
|
866
1085
|
if (!accessToken) {
|
|
867
|
-
|
|
1086
|
+
error("Not authenticated. Run `contxt auth login` first.");
|
|
868
1087
|
process.exit(1);
|
|
869
1088
|
}
|
|
870
1089
|
const dbPath = getDbPath();
|
|
@@ -874,49 +1093,43 @@ var syncCommand = {
|
|
|
874
1093
|
const cwd = process.cwd();
|
|
875
1094
|
const project = await localDb.getProjectByPath(cwd);
|
|
876
1095
|
if (!project) {
|
|
877
|
-
|
|
1096
|
+
error("No Contxt project found. Run `contxt init` first.");
|
|
878
1097
|
process.exit(1);
|
|
879
1098
|
}
|
|
880
1099
|
const supabaseConfig = getSupabaseConfig();
|
|
881
|
-
const remoteDb = new
|
|
1100
|
+
const remoteDb = new SupabaseDatabase2({
|
|
882
1101
|
...supabaseConfig,
|
|
883
1102
|
accessToken
|
|
884
1103
|
});
|
|
885
1104
|
await remoteDb.initialize();
|
|
886
1105
|
const syncEngine = new SyncEngine(localDb, remoteDb);
|
|
887
|
-
|
|
1106
|
+
loading("Syncing with cloud (pull + push)...");
|
|
1107
|
+
console.log("");
|
|
888
1108
|
const result = await syncEngine.sync(project.id, {
|
|
889
1109
|
force: options.force,
|
|
890
1110
|
dryRun: options.dryRun
|
|
891
1111
|
});
|
|
892
1112
|
if (result.errors.length > 0) {
|
|
893
|
-
|
|
1113
|
+
error("Sync failed:");
|
|
894
1114
|
result.errors.forEach((err) => console.error(` ${err}`));
|
|
895
1115
|
process.exit(1);
|
|
896
1116
|
}
|
|
897
1117
|
if (options.dryRun) {
|
|
898
|
-
|
|
899
|
-
`\u{1F4CB} Dry run - would pull ${result.pulled} and push ${result.pushed} entries`
|
|
900
|
-
);
|
|
1118
|
+
dryRun(`Dry run \u2014 would pull ${result.pulled} and push ${result.pushed} entries`);
|
|
901
1119
|
} else {
|
|
902
|
-
|
|
1120
|
+
success("Synced successfully");
|
|
903
1121
|
console.log(` Pulled: ${result.pulled} entries`);
|
|
904
1122
|
console.log(` Pushed: ${result.pushed} entries`);
|
|
905
1123
|
}
|
|
906
1124
|
if (result.conflicts > 0) {
|
|
907
|
-
|
|
908
|
-
`\u26A0\uFE0F ${result.conflicts} conflict(s) detected. Use --force to override.`
|
|
909
|
-
);
|
|
1125
|
+
conflict(`${result.conflicts} conflict(s) detected. Use --force to override.`);
|
|
910
1126
|
}
|
|
911
1127
|
await remoteDb.close();
|
|
912
1128
|
} finally {
|
|
913
1129
|
await localDb.close();
|
|
914
1130
|
}
|
|
915
|
-
} catch (
|
|
916
|
-
|
|
917
|
-
"\u274C Sync failed:",
|
|
918
|
-
error2 instanceof Error ? error2.message : error2
|
|
919
|
-
);
|
|
1131
|
+
} catch (err) {
|
|
1132
|
+
error(`Sync failed: ${err instanceof Error ? err.message : err}`);
|
|
920
1133
|
process.exit(1);
|
|
921
1134
|
}
|
|
922
1135
|
}
|
|
@@ -937,20 +1150,20 @@ var branchCommand = {
|
|
|
937
1150
|
const cwd = process.cwd();
|
|
938
1151
|
const project = await db.getProjectByPath(cwd);
|
|
939
1152
|
if (!project) {
|
|
940
|
-
|
|
1153
|
+
error("No Contxt project found. Run `contxt init` first.");
|
|
941
1154
|
process.exit(1);
|
|
942
1155
|
}
|
|
1156
|
+
const gate = await createUsageGate(db, project.id);
|
|
1157
|
+
const result = gate.checkFeature("branchingEnabled");
|
|
1158
|
+
enforceGate(result);
|
|
943
1159
|
const fromBranch = options.from || await db.getActiveBranch(project.id);
|
|
944
1160
|
await db.createBranch(project.id, name, fromBranch);
|
|
945
|
-
|
|
1161
|
+
success(`Created branch '${name}' from '${fromBranch}'`);
|
|
946
1162
|
} finally {
|
|
947
1163
|
await db.close();
|
|
948
1164
|
}
|
|
949
|
-
} catch (
|
|
950
|
-
|
|
951
|
-
"\u274C Branch create failed:",
|
|
952
|
-
error2 instanceof Error ? error2.message : error2
|
|
953
|
-
);
|
|
1165
|
+
} catch (err) {
|
|
1166
|
+
error(`Branch create failed: ${err instanceof Error ? err.message : err}`);
|
|
954
1167
|
process.exit(1);
|
|
955
1168
|
}
|
|
956
1169
|
},
|
|
@@ -966,7 +1179,7 @@ var branchCommand = {
|
|
|
966
1179
|
const cwd = process.cwd();
|
|
967
1180
|
const project = await db.getProjectByPath(cwd);
|
|
968
1181
|
if (!project) {
|
|
969
|
-
|
|
1182
|
+
error("No Contxt project found. Run `contxt init` first.");
|
|
970
1183
|
process.exit(1);
|
|
971
1184
|
}
|
|
972
1185
|
const branches = await db.listBranches(project.id);
|
|
@@ -980,11 +1193,8 @@ var branchCommand = {
|
|
|
980
1193
|
} finally {
|
|
981
1194
|
await db.close();
|
|
982
1195
|
}
|
|
983
|
-
} catch (
|
|
984
|
-
|
|
985
|
-
"\u274C Branch list failed:",
|
|
986
|
-
error2 instanceof Error ? error2.message : error2
|
|
987
|
-
);
|
|
1196
|
+
} catch (err) {
|
|
1197
|
+
error(`Branch list failed: ${err instanceof Error ? err.message : err}`);
|
|
988
1198
|
process.exit(1);
|
|
989
1199
|
}
|
|
990
1200
|
},
|
|
@@ -1000,19 +1210,19 @@ var branchCommand = {
|
|
|
1000
1210
|
const cwd = process.cwd();
|
|
1001
1211
|
const project = await db.getProjectByPath(cwd);
|
|
1002
1212
|
if (!project) {
|
|
1003
|
-
|
|
1213
|
+
error("No Contxt project found. Run `contxt init` first.");
|
|
1004
1214
|
process.exit(1);
|
|
1005
1215
|
}
|
|
1216
|
+
const gate = await createUsageGate(db, project.id);
|
|
1217
|
+
const result = gate.checkFeature("branchingEnabled");
|
|
1218
|
+
enforceGate(result);
|
|
1006
1219
|
await db.switchBranch(project.id, name);
|
|
1007
|
-
|
|
1220
|
+
success(`Switched to branch '${name}'`);
|
|
1008
1221
|
} finally {
|
|
1009
1222
|
await db.close();
|
|
1010
1223
|
}
|
|
1011
|
-
} catch (
|
|
1012
|
-
|
|
1013
|
-
"\u274C Branch switch failed:",
|
|
1014
|
-
error2 instanceof Error ? error2.message : error2
|
|
1015
|
-
);
|
|
1224
|
+
} catch (err) {
|
|
1225
|
+
error(`Branch switch failed: ${err instanceof Error ? err.message : err}`);
|
|
1016
1226
|
process.exit(1);
|
|
1017
1227
|
}
|
|
1018
1228
|
},
|
|
@@ -1028,28 +1238,25 @@ var branchCommand = {
|
|
|
1028
1238
|
const cwd = process.cwd();
|
|
1029
1239
|
const project = await db.getProjectByPath(cwd);
|
|
1030
1240
|
if (!project) {
|
|
1031
|
-
|
|
1241
|
+
error("No Contxt project found. Run `contxt init` first.");
|
|
1032
1242
|
process.exit(1);
|
|
1033
1243
|
}
|
|
1034
1244
|
const activeBranch = await db.getActiveBranch(project.id);
|
|
1035
1245
|
if (name === activeBranch) {
|
|
1036
|
-
|
|
1246
|
+
error("Cannot delete active branch. Switch to another branch first.");
|
|
1037
1247
|
process.exit(1);
|
|
1038
1248
|
}
|
|
1039
1249
|
if (name === "main") {
|
|
1040
|
-
|
|
1250
|
+
error("Cannot delete main branch.");
|
|
1041
1251
|
process.exit(1);
|
|
1042
1252
|
}
|
|
1043
1253
|
await db.deleteBranch(project.id, name);
|
|
1044
|
-
|
|
1254
|
+
success(`Deleted branch '${name}'`);
|
|
1045
1255
|
} finally {
|
|
1046
1256
|
await db.close();
|
|
1047
1257
|
}
|
|
1048
|
-
} catch (
|
|
1049
|
-
|
|
1050
|
-
"\u274C Branch delete failed:",
|
|
1051
|
-
error2 instanceof Error ? error2.message : error2
|
|
1052
|
-
);
|
|
1258
|
+
} catch (err) {
|
|
1259
|
+
error(`Branch delete failed: ${err instanceof Error ? err.message : err}`);
|
|
1053
1260
|
process.exit(1);
|
|
1054
1261
|
}
|
|
1055
1262
|
},
|
|
@@ -1065,12 +1272,12 @@ var branchCommand = {
|
|
|
1065
1272
|
const cwd = process.cwd();
|
|
1066
1273
|
const project = await db.getProjectByPath(cwd);
|
|
1067
1274
|
if (!project) {
|
|
1068
|
-
|
|
1275
|
+
error("No Contxt project found. Run `contxt init` first.");
|
|
1069
1276
|
process.exit(1);
|
|
1070
1277
|
}
|
|
1071
1278
|
const targetBranch = await db.getActiveBranch(project.id);
|
|
1072
1279
|
if (sourceBranch === targetBranch) {
|
|
1073
|
-
|
|
1280
|
+
error("Cannot merge a branch into itself.");
|
|
1074
1281
|
process.exit(1);
|
|
1075
1282
|
}
|
|
1076
1283
|
const sourceEntries = await db.listEntries({
|
|
@@ -1106,17 +1313,12 @@ var branchCommand = {
|
|
|
1106
1313
|
merged++;
|
|
1107
1314
|
}
|
|
1108
1315
|
}
|
|
1109
|
-
|
|
1110
|
-
`\u2705 Merged ${merged} entries from '${sourceBranch}' into '${targetBranch}'`
|
|
1111
|
-
);
|
|
1316
|
+
success(`Merged ${merged} entries from '${sourceBranch}' into '${targetBranch}'`);
|
|
1112
1317
|
} finally {
|
|
1113
1318
|
await db.close();
|
|
1114
1319
|
}
|
|
1115
|
-
} catch (
|
|
1116
|
-
|
|
1117
|
-
"\u274C Branch merge failed:",
|
|
1118
|
-
error2 instanceof Error ? error2.message : error2
|
|
1119
|
-
);
|
|
1320
|
+
} catch (err) {
|
|
1321
|
+
error(`Branch merge failed: ${err instanceof Error ? err.message : err}`);
|
|
1120
1322
|
process.exit(1);
|
|
1121
1323
|
}
|
|
1122
1324
|
}
|
|
@@ -1136,17 +1338,17 @@ var historyCommand = {
|
|
|
1136
1338
|
try {
|
|
1137
1339
|
const entry = await db.getEntry(entryId);
|
|
1138
1340
|
if (!entry) {
|
|
1139
|
-
|
|
1341
|
+
error("Entry not found.");
|
|
1140
1342
|
process.exit(1);
|
|
1141
1343
|
}
|
|
1142
1344
|
const versions = await db.getVersionHistory(entryId);
|
|
1143
|
-
console.log(
|
|
1144
|
-
|
|
1345
|
+
console.log(section(`Version History: ${entry.title}`));
|
|
1346
|
+
console.log("");
|
|
1145
1347
|
console.log(`Current (v${entry.version}):`);
|
|
1146
1348
|
console.log(` Updated: ${entry.updatedAt.toLocaleString()}`);
|
|
1147
1349
|
console.log(` Title: ${entry.title}`);
|
|
1148
|
-
console.log(` Content: ${entry.content.substring(0, 100)}${entry.content.length > 100 ? "..." : ""}
|
|
1149
|
-
|
|
1350
|
+
console.log(` Content: ${entry.content.substring(0, 100)}${entry.content.length > 100 ? "..." : ""}`);
|
|
1351
|
+
console.log("");
|
|
1150
1352
|
if (versions.length > 0) {
|
|
1151
1353
|
console.log("Previous versions:");
|
|
1152
1354
|
for (const version of versions) {
|
|
@@ -1164,11 +1366,8 @@ v${version.version}:`);
|
|
|
1164
1366
|
} finally {
|
|
1165
1367
|
await db.close();
|
|
1166
1368
|
}
|
|
1167
|
-
} catch (
|
|
1168
|
-
|
|
1169
|
-
"\u274C History failed:",
|
|
1170
|
-
error2 instanceof Error ? error2.message : error2
|
|
1171
|
-
);
|
|
1369
|
+
} catch (err) {
|
|
1370
|
+
error(`History failed: ${err instanceof Error ? err.message : err}`);
|
|
1172
1371
|
process.exit(1);
|
|
1173
1372
|
}
|
|
1174
1373
|
},
|
|
@@ -1182,16 +1381,13 @@ v${version.version}:`);
|
|
|
1182
1381
|
await db.initialize();
|
|
1183
1382
|
try {
|
|
1184
1383
|
const restored = await db.restoreVersion(entryId, options.version);
|
|
1185
|
-
|
|
1384
|
+
success(`Restored '${restored.title}' to version ${options.version}`);
|
|
1186
1385
|
console.log(` Current version is now: v${restored.version}`);
|
|
1187
1386
|
} finally {
|
|
1188
1387
|
await db.close();
|
|
1189
1388
|
}
|
|
1190
|
-
} catch (
|
|
1191
|
-
|
|
1192
|
-
"\u274C Restore failed:",
|
|
1193
|
-
error2 instanceof Error ? error2.message : error2
|
|
1194
|
-
);
|
|
1389
|
+
} catch (err) {
|
|
1390
|
+
error(`Restore failed: ${err instanceof Error ? err.message : err}`);
|
|
1195
1391
|
process.exit(1);
|
|
1196
1392
|
}
|
|
1197
1393
|
}
|
|
@@ -1199,7 +1395,36 @@ v${version.version}:`);
|
|
|
1199
1395
|
|
|
1200
1396
|
// src/commands/load.ts
|
|
1201
1397
|
import { SQLiteDatabase as SQLiteDatabase6 } from "@mycontxt/adapters/sqlite";
|
|
1398
|
+
import { SupabaseDatabase as SupabaseDatabase3 } from "@mycontxt/adapters/supabase";
|
|
1202
1399
|
import { buildContextPayload, buildContextSummary } from "@mycontxt/core";
|
|
1400
|
+
import chalk4 from "chalk";
|
|
1401
|
+
var MODEL_COSTS_PER_1K = {
|
|
1402
|
+
"claude-sonnet": 3e-3,
|
|
1403
|
+
"claude-haiku": 25e-5,
|
|
1404
|
+
"claude-opus": 0.015,
|
|
1405
|
+
"gpt-4o": 25e-4,
|
|
1406
|
+
"gpt-4": 0.03,
|
|
1407
|
+
"gpt-3.5": 5e-4
|
|
1408
|
+
};
|
|
1409
|
+
var DEFAULT_MODEL = "claude-sonnet";
|
|
1410
|
+
function formatCost(tokens, model) {
|
|
1411
|
+
const costPerK = MODEL_COSTS_PER_1K[model] ?? MODEL_COSTS_PER_1K[DEFAULT_MODEL];
|
|
1412
|
+
const cost = tokens / 1e3 * costPerK;
|
|
1413
|
+
return `$${cost.toFixed(4)}`;
|
|
1414
|
+
}
|
|
1415
|
+
async function generateQueryEmbedding(text, apiKey) {
|
|
1416
|
+
try {
|
|
1417
|
+
const res = await fetch("https://api.openai.com/v1/embeddings", {
|
|
1418
|
+
method: "POST",
|
|
1419
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1420
|
+
body: JSON.stringify({ model: "text-embedding-3-small", input: text })
|
|
1421
|
+
});
|
|
1422
|
+
const data = await res.json();
|
|
1423
|
+
return data.data?.[0]?.embedding ?? null;
|
|
1424
|
+
} catch {
|
|
1425
|
+
return null;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1203
1428
|
async function loadCommand(options) {
|
|
1204
1429
|
try {
|
|
1205
1430
|
const dbPath = getDbPath();
|
|
@@ -1209,7 +1434,7 @@ async function loadCommand(options) {
|
|
|
1209
1434
|
const cwd = process.cwd();
|
|
1210
1435
|
const project = await db.getProjectByPath(cwd);
|
|
1211
1436
|
if (!project) {
|
|
1212
|
-
|
|
1437
|
+
error("No Contxt project found. Run `contxt init` first.");
|
|
1213
1438
|
process.exit(1);
|
|
1214
1439
|
}
|
|
1215
1440
|
const branch2 = await db.getActiveBranch(project.id);
|
|
@@ -1224,11 +1449,11 @@ async function loadCommand(options) {
|
|
|
1224
1449
|
}
|
|
1225
1450
|
if (options.summary) {
|
|
1226
1451
|
const summary = buildContextSummary(entries);
|
|
1227
|
-
console.log(
|
|
1228
|
-
|
|
1452
|
+
console.log(section("Context Summary"));
|
|
1453
|
+
console.log("");
|
|
1229
1454
|
console.log(`Total entries: ${summary.totalEntries}`);
|
|
1230
|
-
console.log(`Branch: ${branch2}
|
|
1231
|
-
|
|
1455
|
+
console.log(`Branch: ${branch2}`);
|
|
1456
|
+
console.log("");
|
|
1232
1457
|
console.log("By type:");
|
|
1233
1458
|
for (const [type, count] of Object.entries(summary.byType)) {
|
|
1234
1459
|
console.log(` ${type}: ${count}`);
|
|
@@ -1256,7 +1481,30 @@ Date range: ${summary.oldestEntry.toLocaleDateString()} - ${summary.newestEntry.
|
|
|
1256
1481
|
} else if (options.all) {
|
|
1257
1482
|
mode = "all";
|
|
1258
1483
|
}
|
|
1259
|
-
const
|
|
1484
|
+
const totalEntryCount = entries.length;
|
|
1485
|
+
let resolvedEntries = entries;
|
|
1486
|
+
if (mode === "task" && options.task) {
|
|
1487
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
1488
|
+
if (apiKey) {
|
|
1489
|
+
try {
|
|
1490
|
+
const config = getSupabaseConfig();
|
|
1491
|
+
const remoteDb = new SupabaseDatabase3(config);
|
|
1492
|
+
const embedding = await generateQueryEmbedding(options.task, apiKey);
|
|
1493
|
+
if (embedding) {
|
|
1494
|
+
const semanticResults = await remoteDb.semanticSearch(project.id, embedding, {
|
|
1495
|
+
branch: branch2,
|
|
1496
|
+
limit: 20,
|
|
1497
|
+
minSimilarity: 0.65
|
|
1498
|
+
}).catch(() => []);
|
|
1499
|
+
if (semanticResults.length > 0) {
|
|
1500
|
+
resolvedEntries = semanticResults;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
} catch {
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
const result = buildContextPayload(resolvedEntries, {
|
|
1260
1508
|
projectId: project.id,
|
|
1261
1509
|
type: mode,
|
|
1262
1510
|
taskDescription: options.task,
|
|
@@ -1265,26 +1513,36 @@ Date range: ${summary.oldestEntry.toLocaleDateString()} - ${summary.newestEntry.
|
|
|
1265
1513
|
includeTypes: options.type ? [options.type] : void 0
|
|
1266
1514
|
});
|
|
1267
1515
|
console.log(result.context);
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1516
|
+
const model = process.env.CONTXT_MODEL ?? DEFAULT_MODEL;
|
|
1517
|
+
const totalFiltered = totalEntryCount - result.entriesIncluded;
|
|
1518
|
+
if (totalFiltered > 0 && result.tokensSaved > 0) {
|
|
1519
|
+
const filteredCost = formatCost(result.tokensUsed, model);
|
|
1520
|
+
const fullCost = formatCost(result.tokensUsed + result.tokensSaved, model);
|
|
1521
|
+
console.error(
|
|
1522
|
+
chalk4.dim(
|
|
1523
|
+
`
|
|
1524
|
+
\u2192 ${result.entriesIncluded} entries loaded \xB7 ${result.tokensUsed} tokens \xB7 saved ${result.tokensSaved.toLocaleString()} tokens (${totalFiltered} filtered) \xB7 ~${filteredCost} vs ${fullCost} full load`
|
|
1525
|
+
)
|
|
1526
|
+
);
|
|
1527
|
+
} else {
|
|
1528
|
+
console.error(
|
|
1529
|
+
chalk4.dim(`
|
|
1530
|
+
\u2192 ${result.entriesIncluded} entries loaded \xB7 ${result.tokensUsed}/${result.budget} tokens`)
|
|
1531
|
+
);
|
|
1532
|
+
}
|
|
1272
1533
|
} finally {
|
|
1273
1534
|
await db.close();
|
|
1274
1535
|
}
|
|
1275
|
-
} catch (
|
|
1276
|
-
|
|
1277
|
-
"\u274C Load failed:",
|
|
1278
|
-
error2 instanceof Error ? error2.message : error2
|
|
1279
|
-
);
|
|
1536
|
+
} catch (err) {
|
|
1537
|
+
error(`Load failed: ${err instanceof Error ? err.message : err}`);
|
|
1280
1538
|
process.exit(1);
|
|
1281
1539
|
}
|
|
1282
1540
|
}
|
|
1283
1541
|
|
|
1284
1542
|
// src/commands/scan.ts
|
|
1285
|
-
import { readFileSync as
|
|
1286
|
-
import { join as
|
|
1287
|
-
import
|
|
1543
|
+
import { readFileSync as readFileSync6, existsSync as existsSync5 } from "fs";
|
|
1544
|
+
import { join as join5, relative } from "path";
|
|
1545
|
+
import chalk5 from "chalk";
|
|
1288
1546
|
import ora from "ora";
|
|
1289
1547
|
import { parseFile, scanCommentToEntry } from "@mycontxt/core";
|
|
1290
1548
|
import { glob } from "glob";
|
|
@@ -1319,9 +1577,9 @@ async function scanCommand(options = {}) {
|
|
|
1319
1577
|
"**/*.lock",
|
|
1320
1578
|
".contxt/**"
|
|
1321
1579
|
];
|
|
1322
|
-
const contxtIgnorePath =
|
|
1323
|
-
if (
|
|
1324
|
-
const contxtIgnore =
|
|
1580
|
+
const contxtIgnorePath = join5(process.cwd(), ".contxtignore");
|
|
1581
|
+
if (existsSync5(contxtIgnorePath)) {
|
|
1582
|
+
const contxtIgnore = readFileSync6(contxtIgnorePath, "utf-8").split("\n").filter((line) => line.trim() && !line.startsWith("#"));
|
|
1325
1583
|
ignore.push(...contxtIgnore);
|
|
1326
1584
|
}
|
|
1327
1585
|
const searchPath = options.path || process.cwd();
|
|
@@ -1334,13 +1592,13 @@ async function scanCommand(options = {}) {
|
|
|
1334
1592
|
spinner.text = `Scanning ${files.length} files...`;
|
|
1335
1593
|
const allComments = [];
|
|
1336
1594
|
for (const file of files) {
|
|
1337
|
-
const content =
|
|
1595
|
+
const content = readFileSync6(file, "utf-8");
|
|
1338
1596
|
const comments = parseFile(content, relative(process.cwd(), file));
|
|
1339
1597
|
allComments.push(...comments);
|
|
1340
1598
|
}
|
|
1341
1599
|
spinner.succeed(`Found ${allComments.length} tagged comments across ${files.length} files`);
|
|
1342
1600
|
if (allComments.length === 0) {
|
|
1343
|
-
console.log(
|
|
1601
|
+
console.log(chalk5.gray("\nNo tagged comments found. Try adding @decision, @pattern, or @context tags to your code."));
|
|
1344
1602
|
return;
|
|
1345
1603
|
}
|
|
1346
1604
|
const existingEntries = await db.listEntries({
|
|
@@ -1370,50 +1628,50 @@ async function scanCommand(options = {}) {
|
|
|
1370
1628
|
const staleEntries = Array.from(existingHashes.values());
|
|
1371
1629
|
console.log("");
|
|
1372
1630
|
if (newComments.length > 0) {
|
|
1373
|
-
console.log(
|
|
1631
|
+
console.log(chalk5.bold("NEW"));
|
|
1374
1632
|
for (const comment of newComments) {
|
|
1375
1633
|
const icon = getTypeIcon(comment.tag);
|
|
1376
1634
|
console.log(
|
|
1377
|
-
` ${
|
|
1635
|
+
` ${chalk5.green("+")} ${icon} ${chalk5.bold(comment.title.substring(0, 50))} ${chalk5.gray(comment.file + ":" + comment.line)}`
|
|
1378
1636
|
);
|
|
1379
1637
|
}
|
|
1380
1638
|
console.log("");
|
|
1381
1639
|
}
|
|
1382
1640
|
if (updatedComments.length > 0) {
|
|
1383
|
-
console.log(
|
|
1641
|
+
console.log(chalk5.bold("UPDATED"));
|
|
1384
1642
|
for (const comment of updatedComments) {
|
|
1385
1643
|
const icon = getTypeIcon(comment.tag);
|
|
1386
1644
|
console.log(
|
|
1387
|
-
` ${
|
|
1645
|
+
` ${chalk5.yellow("~")} ${icon} ${chalk5.bold(comment.title.substring(0, 50))} ${chalk5.gray(comment.file + ":" + comment.line)}`
|
|
1388
1646
|
);
|
|
1389
1647
|
}
|
|
1390
1648
|
console.log("");
|
|
1391
1649
|
}
|
|
1392
1650
|
if (unchangedComments.length > 0) {
|
|
1393
|
-
console.log(
|
|
1651
|
+
console.log(chalk5.bold("UNCHANGED"));
|
|
1394
1652
|
for (const comment of unchangedComments.slice(0, 3)) {
|
|
1395
1653
|
const icon = getTypeIcon(comment.tag);
|
|
1396
1654
|
console.log(
|
|
1397
|
-
` ${
|
|
1655
|
+
` ${chalk5.gray("\xB7")} ${icon} ${chalk5.gray(comment.title.substring(0, 50))} ${chalk5.gray(comment.file + ":" + comment.line)}`
|
|
1398
1656
|
);
|
|
1399
1657
|
}
|
|
1400
1658
|
if (unchangedComments.length > 3) {
|
|
1401
|
-
console.log(
|
|
1659
|
+
console.log(chalk5.gray(` ... and ${unchangedComments.length - 3} more`));
|
|
1402
1660
|
}
|
|
1403
1661
|
console.log("");
|
|
1404
1662
|
}
|
|
1405
1663
|
if (staleEntries.length > 0) {
|
|
1406
|
-
console.log(
|
|
1664
|
+
console.log(chalk5.bold("STALE (source comment removed)"));
|
|
1407
1665
|
for (const entry of staleEntries) {
|
|
1408
1666
|
const icon = getTypeIcon(entry.type);
|
|
1409
1667
|
console.log(
|
|
1410
|
-
` ${
|
|
1668
|
+
` ${chalk5.red("?")} ${icon} ${chalk5.gray(entry.title.substring(0, 50))} ${chalk5.gray("was: " + entry.metadata.file + ":" + entry.metadata.line)}`
|
|
1411
1669
|
);
|
|
1412
1670
|
}
|
|
1413
1671
|
console.log("");
|
|
1414
1672
|
}
|
|
1415
1673
|
if (options.dryRun) {
|
|
1416
|
-
console.log(
|
|
1674
|
+
console.log(chalk5.yellow("Dry run - no changes saved."));
|
|
1417
1675
|
return;
|
|
1418
1676
|
}
|
|
1419
1677
|
const toSave = [...newComments, ...updatedComments];
|
|
@@ -1447,30 +1705,30 @@ async function scanCommand(options = {}) {
|
|
|
1447
1705
|
for (const entry of staleEntries) {
|
|
1448
1706
|
await db.updateEntry(entry.id, { status: "stale" });
|
|
1449
1707
|
}
|
|
1450
|
-
console.log(
|
|
1708
|
+
console.log(chalk5.gray(`Marked ${staleEntries.length} entries as stale.`));
|
|
1451
1709
|
}
|
|
1452
1710
|
if (!options.autoConfirm && toSave.length > 0) {
|
|
1453
1711
|
console.log("");
|
|
1454
|
-
console.log(
|
|
1712
|
+
console.log(chalk5.cyan(`Run ${chalk5.bold("contxt review")} to confirm drafts.`));
|
|
1455
1713
|
}
|
|
1456
1714
|
await db.close();
|
|
1457
1715
|
} catch (error2) {
|
|
1458
1716
|
spinner.fail("Scan failed");
|
|
1459
|
-
console.error(
|
|
1717
|
+
console.error(chalk5.red(error2 instanceof Error ? error2.message : String(error2)));
|
|
1460
1718
|
process.exit(1);
|
|
1461
1719
|
}
|
|
1462
1720
|
}
|
|
1463
1721
|
function getTypeIcon(type) {
|
|
1464
1722
|
const icons = {
|
|
1465
|
-
decision:
|
|
1466
|
-
pattern:
|
|
1467
|
-
context:
|
|
1723
|
+
decision: chalk5.blue("DECISION"),
|
|
1724
|
+
pattern: chalk5.magenta("PATTERN"),
|
|
1725
|
+
context: chalk5.green("CONTEXT")
|
|
1468
1726
|
};
|
|
1469
1727
|
return icons[type] || type.toUpperCase();
|
|
1470
1728
|
}
|
|
1471
1729
|
|
|
1472
1730
|
// src/commands/review.ts
|
|
1473
|
-
import
|
|
1731
|
+
import chalk6 from "chalk";
|
|
1474
1732
|
import inquirer from "inquirer";
|
|
1475
1733
|
import ora2 from "ora";
|
|
1476
1734
|
async function reviewCommand(options = {}) {
|
|
@@ -1478,7 +1736,7 @@ async function reviewCommand(options = {}) {
|
|
|
1478
1736
|
const db = await getProjectDb();
|
|
1479
1737
|
const project = await db.getProjectByPath(process.cwd());
|
|
1480
1738
|
if (!project) {
|
|
1481
|
-
console.log(
|
|
1739
|
+
console.log(chalk6.red("Not a Contxt project. Run `contxt init` first."));
|
|
1482
1740
|
return;
|
|
1483
1741
|
}
|
|
1484
1742
|
let drafts = await db.listEntries({
|
|
@@ -1493,14 +1751,14 @@ async function reviewCommand(options = {}) {
|
|
|
1493
1751
|
});
|
|
1494
1752
|
}
|
|
1495
1753
|
if (drafts.length === 0) {
|
|
1496
|
-
console.log(
|
|
1754
|
+
console.log(chalk6.green("\u2713 No drafts pending review"));
|
|
1497
1755
|
return;
|
|
1498
1756
|
}
|
|
1499
1757
|
if (options.count) {
|
|
1500
1758
|
console.log(`${drafts.length} drafts pending review`);
|
|
1501
1759
|
return;
|
|
1502
1760
|
}
|
|
1503
|
-
console.log(
|
|
1761
|
+
console.log(chalk6.bold(`
|
|
1504
1762
|
${drafts.length} drafts pending review
|
|
1505
1763
|
`));
|
|
1506
1764
|
if (options.confirmAll) {
|
|
@@ -1535,7 +1793,7 @@ ${drafts.length} drafts pending review
|
|
|
1535
1793
|
let discarded = 0;
|
|
1536
1794
|
let skipped = 0;
|
|
1537
1795
|
for (const draft of drafts) {
|
|
1538
|
-
console.log(
|
|
1796
|
+
console.log(chalk6.gray("\u2500".repeat(50)));
|
|
1539
1797
|
console.log("");
|
|
1540
1798
|
displayDraft(draft);
|
|
1541
1799
|
console.log("");
|
|
@@ -1554,7 +1812,7 @@ ${drafts.length} drafts pending review
|
|
|
1554
1812
|
]);
|
|
1555
1813
|
if (action === "confirm") {
|
|
1556
1814
|
await db.updateEntry(draft.id, { status: "active" });
|
|
1557
|
-
console.log(
|
|
1815
|
+
console.log(chalk6.green("\u2713 Confirmed"));
|
|
1558
1816
|
confirmed++;
|
|
1559
1817
|
} else if (action === "edit") {
|
|
1560
1818
|
const edited = await editDraft(draft);
|
|
@@ -1564,28 +1822,28 @@ ${drafts.length} drafts pending review
|
|
|
1564
1822
|
metadata: edited.metadata,
|
|
1565
1823
|
status: "active"
|
|
1566
1824
|
});
|
|
1567
|
-
console.log(
|
|
1825
|
+
console.log(chalk6.green("\u2713 Edited and confirmed"));
|
|
1568
1826
|
confirmed++;
|
|
1569
1827
|
} else if (action === "discard") {
|
|
1570
1828
|
await db.deleteEntry(draft.id);
|
|
1571
|
-
console.log(
|
|
1829
|
+
console.log(chalk6.red("\u2717 Discarded"));
|
|
1572
1830
|
discarded++;
|
|
1573
1831
|
} else {
|
|
1574
|
-
console.log(
|
|
1832
|
+
console.log(chalk6.gray("\u25CB Skipped"));
|
|
1575
1833
|
skipped++;
|
|
1576
1834
|
}
|
|
1577
1835
|
console.log("");
|
|
1578
1836
|
}
|
|
1579
|
-
console.log(
|
|
1837
|
+
console.log(chalk6.gray("\u2500".repeat(50)));
|
|
1580
1838
|
console.log("");
|
|
1581
|
-
console.log(
|
|
1582
|
-
console.log(` ${
|
|
1583
|
-
console.log(` ${
|
|
1584
|
-
console.log(` ${
|
|
1839
|
+
console.log(chalk6.bold("Review complete"));
|
|
1840
|
+
console.log(` ${chalk6.green(confirmed)} confirmed`);
|
|
1841
|
+
console.log(` ${chalk6.red(discarded)} discarded`);
|
|
1842
|
+
console.log(` ${chalk6.gray(skipped)} skipped`);
|
|
1585
1843
|
console.log("");
|
|
1586
1844
|
await db.close();
|
|
1587
1845
|
} catch (error2) {
|
|
1588
|
-
console.error(
|
|
1846
|
+
console.error(chalk6.red(error2 instanceof Error ? error2.message : String(error2)));
|
|
1589
1847
|
process.exit(1);
|
|
1590
1848
|
}
|
|
1591
1849
|
}
|
|
@@ -1594,28 +1852,28 @@ function displayDraft(draft) {
|
|
|
1594
1852
|
const source = draft.metadata.source || "unknown";
|
|
1595
1853
|
const file = draft.metadata.file ? ` \xB7 ${draft.metadata.file}:${draft.metadata.line}` : "";
|
|
1596
1854
|
const timeAgo = getTimeAgo(draft.createdAt);
|
|
1597
|
-
console.log(` ${icon} ${
|
|
1598
|
-
console.log(` ${
|
|
1855
|
+
console.log(` ${icon} ${chalk6.bold(draft.title)}`);
|
|
1856
|
+
console.log(` ${chalk6.gray(`Source: ${source}${file} \xB7 ${timeAgo}`)}`);
|
|
1599
1857
|
const contentPreview = draft.content.split("\n")[0].substring(0, 80);
|
|
1600
1858
|
if (contentPreview) {
|
|
1601
|
-
console.log(` ${
|
|
1859
|
+
console.log(` ${chalk6.gray(contentPreview)}${draft.content.length > 80 ? "..." : ""}`);
|
|
1602
1860
|
}
|
|
1603
1861
|
if (draft.type === "decision") {
|
|
1604
1862
|
if (draft.metadata.rationale) {
|
|
1605
|
-
console.log(` ${
|
|
1863
|
+
console.log(` ${chalk6.dim("Rationale:")} ${draft.metadata.rationale.substring(0, 60)}...`);
|
|
1606
1864
|
}
|
|
1607
1865
|
if (draft.metadata.alternatives) {
|
|
1608
|
-
console.log(` ${
|
|
1866
|
+
console.log(` ${chalk6.dim("Alternatives:")} ${draft.metadata.alternatives}`);
|
|
1609
1867
|
}
|
|
1610
1868
|
}
|
|
1611
1869
|
if (draft.type === "pattern") {
|
|
1612
1870
|
if (draft.metadata.when) {
|
|
1613
|
-
console.log(` ${
|
|
1871
|
+
console.log(` ${chalk6.dim("When:")} ${draft.metadata.when}`);
|
|
1614
1872
|
}
|
|
1615
1873
|
}
|
|
1616
1874
|
}
|
|
1617
1875
|
async function editDraft(draft) {
|
|
1618
|
-
console.log(
|
|
1876
|
+
console.log(chalk6.cyan("\nEdit mode (press Enter to keep current value):\n"));
|
|
1619
1877
|
const { title, content } = await inquirer.prompt([
|
|
1620
1878
|
{
|
|
1621
1879
|
type: "input",
|
|
@@ -1638,11 +1896,11 @@ async function editDraft(draft) {
|
|
|
1638
1896
|
}
|
|
1639
1897
|
function getTypeIcon2(type) {
|
|
1640
1898
|
const icons = {
|
|
1641
|
-
decision:
|
|
1642
|
-
pattern:
|
|
1643
|
-
context:
|
|
1644
|
-
document:
|
|
1645
|
-
session:
|
|
1899
|
+
decision: chalk6.blue("DECISION"),
|
|
1900
|
+
pattern: chalk6.magenta("PATTERN"),
|
|
1901
|
+
context: chalk6.green("CONTEXT"),
|
|
1902
|
+
document: chalk6.yellow("DOCUMENT"),
|
|
1903
|
+
session: chalk6.cyan("SESSION")
|
|
1646
1904
|
};
|
|
1647
1905
|
return icons[type] || type.toUpperCase();
|
|
1648
1906
|
}
|
|
@@ -1655,9 +1913,9 @@ function getTimeAgo(date) {
|
|
|
1655
1913
|
}
|
|
1656
1914
|
|
|
1657
1915
|
// src/commands/rules.ts
|
|
1658
|
-
import { readFileSync as
|
|
1659
|
-
import { join as
|
|
1660
|
-
import
|
|
1916
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "fs";
|
|
1917
|
+
import { join as join6 } from "path";
|
|
1918
|
+
import chalk7 from "chalk";
|
|
1661
1919
|
import ora3 from "ora";
|
|
1662
1920
|
import { parseRulesFile, generateRulesFile } from "@mycontxt/core";
|
|
1663
1921
|
async function syncCommand2(options = {}) {
|
|
@@ -1669,12 +1927,12 @@ async function syncCommand2(options = {}) {
|
|
|
1669
1927
|
spinner.fail("Not a Contxt project. Run `contxt init` first.");
|
|
1670
1928
|
return;
|
|
1671
1929
|
}
|
|
1672
|
-
const rulesPath =
|
|
1673
|
-
if (!
|
|
1930
|
+
const rulesPath = join6(process.cwd(), ".contxt", "rules.md");
|
|
1931
|
+
if (!existsSync6(rulesPath)) {
|
|
1674
1932
|
spinner.fail("No rules.md file found. Run `contxt rules generate` to create one.");
|
|
1675
1933
|
return;
|
|
1676
1934
|
}
|
|
1677
|
-
const content =
|
|
1935
|
+
const content = readFileSync7(rulesPath, "utf-8");
|
|
1678
1936
|
const parsed = parseRulesFile(content);
|
|
1679
1937
|
spinner.text = "Processing entries...";
|
|
1680
1938
|
let added = 0;
|
|
@@ -1801,7 +2059,7 @@ async function syncCommand2(options = {}) {
|
|
|
1801
2059
|
await db.close();
|
|
1802
2060
|
} catch (error2) {
|
|
1803
2061
|
spinner.fail("Sync failed");
|
|
1804
|
-
console.error(
|
|
2062
|
+
console.error(chalk7.red(error2 instanceof Error ? error2.message : String(error2)));
|
|
1805
2063
|
process.exit(1);
|
|
1806
2064
|
}
|
|
1807
2065
|
}
|
|
@@ -1814,8 +2072,8 @@ async function generateCommand(options = {}) {
|
|
|
1814
2072
|
spinner.fail("Not a Contxt project. Run `contxt init` first.");
|
|
1815
2073
|
return;
|
|
1816
2074
|
}
|
|
1817
|
-
const rulesPath =
|
|
1818
|
-
if (
|
|
2075
|
+
const rulesPath = join6(process.cwd(), ".contxt", "rules.md");
|
|
2076
|
+
if (existsSync6(rulesPath) && !options.force) {
|
|
1819
2077
|
spinner.fail("rules.md already exists. Use --force to overwrite.");
|
|
1820
2078
|
return;
|
|
1821
2079
|
}
|
|
@@ -1852,15 +2110,15 @@ async function generateCommand(options = {}) {
|
|
|
1852
2110
|
if (options.dryRun) {
|
|
1853
2111
|
spinner.succeed("Generated rules.md (dry run):");
|
|
1854
2112
|
console.log("");
|
|
1855
|
-
console.log(
|
|
2113
|
+
console.log(chalk7.gray(rulesContent));
|
|
1856
2114
|
} else {
|
|
1857
|
-
|
|
2115
|
+
writeFileSync4(rulesPath, rulesContent, "utf-8");
|
|
1858
2116
|
spinner.succeed(`Generated ${rulesPath}`);
|
|
1859
2117
|
}
|
|
1860
2118
|
await db.close();
|
|
1861
2119
|
} catch (error2) {
|
|
1862
2120
|
spinner.fail("Generate failed");
|
|
1863
|
-
console.error(
|
|
2121
|
+
console.error(chalk7.red(error2 instanceof Error ? error2.message : String(error2)));
|
|
1864
2122
|
process.exit(1);
|
|
1865
2123
|
}
|
|
1866
2124
|
}
|
|
@@ -1873,12 +2131,12 @@ async function diffCommand() {
|
|
|
1873
2131
|
spinner.fail("Not a Contxt project. Run `contxt init` first.");
|
|
1874
2132
|
return;
|
|
1875
2133
|
}
|
|
1876
|
-
const rulesPath =
|
|
1877
|
-
if (!
|
|
2134
|
+
const rulesPath = join6(process.cwd(), ".contxt", "rules.md");
|
|
2135
|
+
if (!existsSync6(rulesPath)) {
|
|
1878
2136
|
spinner.fail("No rules.md file found.");
|
|
1879
2137
|
return;
|
|
1880
2138
|
}
|
|
1881
|
-
const content =
|
|
2139
|
+
const content = readFileSync7(rulesPath, "utf-8");
|
|
1882
2140
|
const parsed = parseRulesFile(content);
|
|
1883
2141
|
const branch2 = await db.getActiveBranch(project.id);
|
|
1884
2142
|
const allEntries = await db.listEntries({
|
|
@@ -1895,67 +2153,67 @@ async function diffCommand() {
|
|
|
1895
2153
|
for (const decision2 of parsed.decisions) {
|
|
1896
2154
|
const existing = existingByTitle.get(decision2.title);
|
|
1897
2155
|
if (!existing) {
|
|
1898
|
-
toAdd.push(`${
|
|
2156
|
+
toAdd.push(`${chalk7.blue("DECISION")} ${decision2.title}`);
|
|
1899
2157
|
} else if (existing.content !== decision2.content) {
|
|
1900
|
-
toUpdate.push(`${
|
|
2158
|
+
toUpdate.push(`${chalk7.blue("DECISION")} ${decision2.title}`);
|
|
1901
2159
|
} else {
|
|
1902
|
-
inSync.push(`${
|
|
2160
|
+
inSync.push(`${chalk7.blue("DECISION")} ${decision2.title}`);
|
|
1903
2161
|
}
|
|
1904
2162
|
}
|
|
1905
2163
|
for (const pattern2 of parsed.patterns) {
|
|
1906
2164
|
const existing = existingByTitle.get(pattern2.title);
|
|
1907
2165
|
if (!existing) {
|
|
1908
|
-
toAdd.push(`${
|
|
2166
|
+
toAdd.push(`${chalk7.magenta("PATTERN")} ${pattern2.title}`);
|
|
1909
2167
|
} else if (existing.content !== pattern2.content) {
|
|
1910
|
-
toUpdate.push(`${
|
|
2168
|
+
toUpdate.push(`${chalk7.magenta("PATTERN")} ${pattern2.title}`);
|
|
1911
2169
|
} else {
|
|
1912
|
-
inSync.push(`${
|
|
2170
|
+
inSync.push(`${chalk7.magenta("PATTERN")} ${pattern2.title}`);
|
|
1913
2171
|
}
|
|
1914
2172
|
}
|
|
1915
2173
|
for (const doc2 of parsed.documents) {
|
|
1916
2174
|
const existing = existingByTitle.get(doc2.title);
|
|
1917
2175
|
if (!existing) {
|
|
1918
|
-
toAdd.push(`${
|
|
2176
|
+
toAdd.push(`${chalk7.cyan("DOCUMENT")} ${doc2.title}`);
|
|
1919
2177
|
} else if (existing.content !== doc2.content) {
|
|
1920
|
-
toUpdate.push(`${
|
|
2178
|
+
toUpdate.push(`${chalk7.cyan("DOCUMENT")} ${doc2.title}`);
|
|
1921
2179
|
} else {
|
|
1922
|
-
inSync.push(`${
|
|
2180
|
+
inSync.push(`${chalk7.cyan("DOCUMENT")} ${doc2.title}`);
|
|
1923
2181
|
}
|
|
1924
2182
|
}
|
|
1925
2183
|
console.log("");
|
|
1926
2184
|
if (toAdd.length > 0) {
|
|
1927
|
-
console.log(
|
|
2185
|
+
console.log(chalk7.bold("TO ADD (in rules.md, not in memory):"));
|
|
1928
2186
|
for (const item of toAdd) {
|
|
1929
|
-
console.log(` ${
|
|
2187
|
+
console.log(` ${chalk7.green("+")} ${item}`);
|
|
1930
2188
|
}
|
|
1931
2189
|
console.log("");
|
|
1932
2190
|
}
|
|
1933
2191
|
if (toUpdate.length > 0) {
|
|
1934
|
-
console.log(
|
|
2192
|
+
console.log(chalk7.bold("TO UPDATE (content differs):"));
|
|
1935
2193
|
for (const item of toUpdate) {
|
|
1936
|
-
console.log(` ${
|
|
2194
|
+
console.log(` ${chalk7.yellow("~")} ${item}`);
|
|
1937
2195
|
}
|
|
1938
2196
|
console.log("");
|
|
1939
2197
|
}
|
|
1940
2198
|
if (inSync.length > 0) {
|
|
1941
|
-
console.log(
|
|
2199
|
+
console.log(chalk7.bold("IN SYNC:"));
|
|
1942
2200
|
for (const item of inSync.slice(0, 5)) {
|
|
1943
|
-
console.log(` ${
|
|
2201
|
+
console.log(` ${chalk7.gray("\xB7")} ${chalk7.gray(item)}`);
|
|
1944
2202
|
}
|
|
1945
2203
|
if (inSync.length > 5) {
|
|
1946
|
-
console.log(
|
|
2204
|
+
console.log(chalk7.gray(` ... and ${inSync.length - 5} more`));
|
|
1947
2205
|
}
|
|
1948
2206
|
console.log("");
|
|
1949
2207
|
}
|
|
1950
2208
|
if (toAdd.length > 0 || toUpdate.length > 0) {
|
|
1951
|
-
console.log(
|
|
2209
|
+
console.log(chalk7.cyan(`Run ${chalk7.bold("contxt rules sync")} to apply changes.`));
|
|
1952
2210
|
} else {
|
|
1953
|
-
console.log(
|
|
2211
|
+
console.log(chalk7.green("\u2713 Everything in sync!"));
|
|
1954
2212
|
}
|
|
1955
2213
|
await db.close();
|
|
1956
2214
|
} catch (error2) {
|
|
1957
2215
|
spinner.fail("Diff failed");
|
|
1958
|
-
console.error(
|
|
2216
|
+
console.error(chalk7.red(error2 instanceof Error ? error2.message : String(error2)));
|
|
1959
2217
|
process.exit(1);
|
|
1960
2218
|
}
|
|
1961
2219
|
}
|
|
@@ -1966,9 +2224,9 @@ var rulesCommand = {
|
|
|
1966
2224
|
};
|
|
1967
2225
|
|
|
1968
2226
|
// src/commands/capture.ts
|
|
1969
|
-
import { readFileSync as
|
|
1970
|
-
import { join as
|
|
1971
|
-
import
|
|
2227
|
+
import { readFileSync as readFileSync8, existsSync as existsSync7, readdirSync } from "fs";
|
|
2228
|
+
import { join as join7, basename as basename2 } from "path";
|
|
2229
|
+
import chalk8 from "chalk";
|
|
1972
2230
|
import ora4 from "ora";
|
|
1973
2231
|
async function captureCommand(options = {}) {
|
|
1974
2232
|
const spinner = ora4("Scanning project files...").start();
|
|
@@ -2006,24 +2264,24 @@ async function captureCommand(options = {}) {
|
|
|
2006
2264
|
}
|
|
2007
2265
|
spinner.succeed(`Found ${entries.length} entries across ${sources.length} source(s)`);
|
|
2008
2266
|
if (entries.length === 0) {
|
|
2009
|
-
console.log(
|
|
2267
|
+
console.log(chalk8.gray("\\nNo entries found to import."));
|
|
2010
2268
|
return;
|
|
2011
2269
|
}
|
|
2012
2270
|
console.log("");
|
|
2013
2271
|
const grouped = groupBy(entries, "source");
|
|
2014
2272
|
for (const [source, items] of Object.entries(grouped)) {
|
|
2015
|
-
console.log(
|
|
2273
|
+
console.log(chalk8.bold(`${source.toUpperCase()} (${items.length})`));
|
|
2016
2274
|
for (const item of items.slice(0, 3)) {
|
|
2017
2275
|
const icon = getTypeIcon3(item.type);
|
|
2018
|
-
console.log(` ${icon} ${
|
|
2276
|
+
console.log(` ${icon} ${chalk8.bold(item.title.substring(0, 60))}`);
|
|
2019
2277
|
}
|
|
2020
2278
|
if (items.length > 3) {
|
|
2021
|
-
console.log(
|
|
2279
|
+
console.log(chalk8.gray(` ... and ${items.length - 3} more`));
|
|
2022
2280
|
}
|
|
2023
2281
|
console.log("");
|
|
2024
2282
|
}
|
|
2025
2283
|
if (options.dryRun) {
|
|
2026
|
-
console.log(
|
|
2284
|
+
console.log(chalk8.yellow("Dry run - no entries saved."));
|
|
2027
2285
|
return;
|
|
2028
2286
|
}
|
|
2029
2287
|
const saveSpinner = ora4("Saving entries...").start();
|
|
@@ -2047,20 +2305,20 @@ async function captureCommand(options = {}) {
|
|
|
2047
2305
|
);
|
|
2048
2306
|
if (!options.autoConfirm) {
|
|
2049
2307
|
console.log("");
|
|
2050
|
-
console.log(
|
|
2308
|
+
console.log(chalk8.cyan(`Run ${chalk8.bold("contxt review")} to confirm drafts.`));
|
|
2051
2309
|
}
|
|
2052
2310
|
await db.close();
|
|
2053
2311
|
} catch (error2) {
|
|
2054
2312
|
spinner.fail("Import failed");
|
|
2055
|
-
console.error(
|
|
2313
|
+
console.error(chalk8.red(error2 instanceof Error ? error2.message : String(error2)));
|
|
2056
2314
|
process.exit(1);
|
|
2057
2315
|
}
|
|
2058
2316
|
}
|
|
2059
2317
|
function importReadme() {
|
|
2060
|
-
const readmePath =
|
|
2061
|
-
if (!
|
|
2318
|
+
const readmePath = join7(process.cwd(), "README.md");
|
|
2319
|
+
if (!existsSync7(readmePath)) return [];
|
|
2062
2320
|
try {
|
|
2063
|
-
const content =
|
|
2321
|
+
const content = readFileSync8(readmePath, "utf-8");
|
|
2064
2322
|
const lines = content.split("\\n");
|
|
2065
2323
|
const entries = [];
|
|
2066
2324
|
let currentSection = null;
|
|
@@ -2107,10 +2365,10 @@ function importReadme() {
|
|
|
2107
2365
|
}
|
|
2108
2366
|
function importCursor() {
|
|
2109
2367
|
const entries = [];
|
|
2110
|
-
const cursorrulesPath =
|
|
2111
|
-
if (
|
|
2368
|
+
const cursorrulesPath = join7(process.cwd(), ".cursorrules");
|
|
2369
|
+
if (existsSync7(cursorrulesPath)) {
|
|
2112
2370
|
try {
|
|
2113
|
-
const content =
|
|
2371
|
+
const content = readFileSync8(cursorrulesPath, "utf-8").trim();
|
|
2114
2372
|
if (content.length > 0) {
|
|
2115
2373
|
entries.push({
|
|
2116
2374
|
type: "document",
|
|
@@ -2123,10 +2381,10 @@ function importCursor() {
|
|
|
2123
2381
|
} catch {
|
|
2124
2382
|
}
|
|
2125
2383
|
}
|
|
2126
|
-
const cursorRulesPath =
|
|
2127
|
-
if (
|
|
2384
|
+
const cursorRulesPath = join7(process.cwd(), ".cursor", "rules");
|
|
2385
|
+
if (existsSync7(cursorRulesPath)) {
|
|
2128
2386
|
try {
|
|
2129
|
-
const content =
|
|
2387
|
+
const content = readFileSync8(cursorRulesPath, "utf-8").trim();
|
|
2130
2388
|
if (content.length > 0) {
|
|
2131
2389
|
entries.push({
|
|
2132
2390
|
type: "document",
|
|
@@ -2143,10 +2401,10 @@ function importCursor() {
|
|
|
2143
2401
|
}
|
|
2144
2402
|
function importClaude() {
|
|
2145
2403
|
const entries = [];
|
|
2146
|
-
const claudePath =
|
|
2147
|
-
if (
|
|
2404
|
+
const claudePath = join7(process.cwd(), ".claude", "CLAUDE.md");
|
|
2405
|
+
if (existsSync7(claudePath)) {
|
|
2148
2406
|
try {
|
|
2149
|
-
const content =
|
|
2407
|
+
const content = readFileSync8(claudePath, "utf-8");
|
|
2150
2408
|
const lines = content.split("\\n");
|
|
2151
2409
|
let currentSection = null;
|
|
2152
2410
|
let currentContent = [];
|
|
@@ -2193,17 +2451,17 @@ function importClaude() {
|
|
|
2193
2451
|
function importADR() {
|
|
2194
2452
|
const entries = [];
|
|
2195
2453
|
const adrDirs = [
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2454
|
+
join7(process.cwd(), "docs", "adr"),
|
|
2455
|
+
join7(process.cwd(), "docs", "architecture"),
|
|
2456
|
+
join7(process.cwd(), "adr")
|
|
2199
2457
|
];
|
|
2200
2458
|
for (const adrDir of adrDirs) {
|
|
2201
|
-
if (!
|
|
2459
|
+
if (!existsSync7(adrDir)) continue;
|
|
2202
2460
|
try {
|
|
2203
|
-
const files = readdirSync(adrDir).filter((f) => f.endsWith(".md")).map((f) =>
|
|
2461
|
+
const files = readdirSync(adrDir).filter((f) => f.endsWith(".md")).map((f) => join7(adrDir, f));
|
|
2204
2462
|
for (const file of files) {
|
|
2205
2463
|
try {
|
|
2206
|
-
const content =
|
|
2464
|
+
const content = readFileSync8(file, "utf-8");
|
|
2207
2465
|
const title = extractADRTitle(content, basename2(file, ".md"));
|
|
2208
2466
|
entries.push({
|
|
2209
2467
|
type: "decision",
|
|
@@ -2265,10 +2523,10 @@ function importCommits(limit) {
|
|
|
2265
2523
|
}
|
|
2266
2524
|
function importPackageFiles() {
|
|
2267
2525
|
const entries = [];
|
|
2268
|
-
const packagePath =
|
|
2269
|
-
if (
|
|
2526
|
+
const packagePath = join7(process.cwd(), "package.json");
|
|
2527
|
+
if (existsSync7(packagePath)) {
|
|
2270
2528
|
try {
|
|
2271
|
-
const pkg = JSON.parse(
|
|
2529
|
+
const pkg = JSON.parse(readFileSync8(packagePath, "utf-8"));
|
|
2272
2530
|
if (pkg.description) {
|
|
2273
2531
|
entries.push({
|
|
2274
2532
|
type: "document",
|
|
@@ -2294,10 +2552,10 @@ function importPackageFiles() {
|
|
|
2294
2552
|
} catch {
|
|
2295
2553
|
}
|
|
2296
2554
|
}
|
|
2297
|
-
const requirementsPath =
|
|
2298
|
-
if (
|
|
2555
|
+
const requirementsPath = join7(process.cwd(), "requirements.txt");
|
|
2556
|
+
if (existsSync7(requirementsPath)) {
|
|
2299
2557
|
try {
|
|
2300
|
-
const content =
|
|
2558
|
+
const content = readFileSync8(requirementsPath, "utf-8");
|
|
2301
2559
|
const packages = content.split("\\n").filter((line) => line.trim() && !line.startsWith("#")).map((line) => line.split("==")[0].split(">=")[0].trim());
|
|
2302
2560
|
if (packages.length > 0) {
|
|
2303
2561
|
entries.push({
|
|
@@ -2311,10 +2569,10 @@ function importPackageFiles() {
|
|
|
2311
2569
|
} catch {
|
|
2312
2570
|
}
|
|
2313
2571
|
}
|
|
2314
|
-
const cargoPath =
|
|
2315
|
-
if (
|
|
2572
|
+
const cargoPath = join7(process.cwd(), "Cargo.toml");
|
|
2573
|
+
if (existsSync7(cargoPath)) {
|
|
2316
2574
|
try {
|
|
2317
|
-
const content =
|
|
2575
|
+
const content = readFileSync8(cargoPath, "utf-8");
|
|
2318
2576
|
const nameMatch = content.match(/name\\s*=\\s*"([^"]+)"/);
|
|
2319
2577
|
const descMatch = content.match(/description\\s*=\\s*"([^"]+)"/);
|
|
2320
2578
|
if (descMatch) {
|
|
@@ -2341,19 +2599,19 @@ function groupBy(arr, key) {
|
|
|
2341
2599
|
}
|
|
2342
2600
|
function getTypeIcon3(type) {
|
|
2343
2601
|
const icons = {
|
|
2344
|
-
decision:
|
|
2345
|
-
pattern:
|
|
2346
|
-
context:
|
|
2347
|
-
document:
|
|
2348
|
-
session:
|
|
2602
|
+
decision: chalk8.blue("DECISION"),
|
|
2603
|
+
pattern: chalk8.magenta("PATTERN"),
|
|
2604
|
+
context: chalk8.green("CONTEXT"),
|
|
2605
|
+
document: chalk8.cyan("DOCUMENT"),
|
|
2606
|
+
session: chalk8.yellow("SESSION")
|
|
2349
2607
|
};
|
|
2350
2608
|
return icons[type] || type.toUpperCase();
|
|
2351
2609
|
}
|
|
2352
2610
|
|
|
2353
2611
|
// src/commands/hook.ts
|
|
2354
|
-
import { readFileSync as
|
|
2355
|
-
import { join as
|
|
2356
|
-
import
|
|
2612
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, existsSync as existsSync9, chmodSync as chmodSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
2613
|
+
import { join as join9 } from "path";
|
|
2614
|
+
import chalk9 from "chalk";
|
|
2357
2615
|
|
|
2358
2616
|
// src/hooks/post-commit.ts
|
|
2359
2617
|
import { execSync } from "child_process";
|
|
@@ -2441,8 +2699,8 @@ function stripConventionalPrefix(msg) {
|
|
|
2441
2699
|
|
|
2442
2700
|
// src/hooks/pre-push.ts
|
|
2443
2701
|
import { execSync as execSync2 } from "child_process";
|
|
2444
|
-
import { readFileSync as
|
|
2445
|
-
import { join as
|
|
2702
|
+
import { readFileSync as readFileSync9, existsSync as existsSync8 } from "fs";
|
|
2703
|
+
import { join as join8 } from "path";
|
|
2446
2704
|
async function runPrePush() {
|
|
2447
2705
|
try {
|
|
2448
2706
|
const db = await getProjectDb();
|
|
@@ -2494,10 +2752,10 @@ async function runPrePush() {
|
|
|
2494
2752
|
`contxt: session updated \u2014 ${commitCount} commit${commitCount !== 1 ? "s" : ""}, ${changedFiles.length} files changed
|
|
2495
2753
|
`
|
|
2496
2754
|
);
|
|
2497
|
-
const configPath =
|
|
2498
|
-
if (
|
|
2755
|
+
const configPath = join8(process.cwd(), ".contxt", "config.json");
|
|
2756
|
+
if (existsSync8(configPath)) {
|
|
2499
2757
|
try {
|
|
2500
|
-
const config = JSON.parse(
|
|
2758
|
+
const config = JSON.parse(readFileSync9(configPath, "utf-8"));
|
|
2501
2759
|
if (config.hooks?.auto_push_on_push) {
|
|
2502
2760
|
process.stdout.write("contxt: syncing to cloud...\n");
|
|
2503
2761
|
execSync2("contxt push --quiet 2>/dev/null", {
|
|
@@ -2545,7 +2803,7 @@ async function runPostCheckout() {
|
|
|
2545
2803
|
}
|
|
2546
2804
|
|
|
2547
2805
|
// src/hooks/prepare-commit-msg.ts
|
|
2548
|
-
import { readFileSync as
|
|
2806
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
2549
2807
|
async function runPrepareCommitMsg() {
|
|
2550
2808
|
try {
|
|
2551
2809
|
const commitMsgFile = process.argv[4];
|
|
@@ -2586,45 +2844,45 @@ async function runPrepareCommitMsg() {
|
|
|
2586
2844
|
lines.push(`# ${draftCount} drafts pending \u2014 run \`contxt review\``);
|
|
2587
2845
|
}
|
|
2588
2846
|
lines.push("#");
|
|
2589
|
-
const existing =
|
|
2590
|
-
|
|
2847
|
+
const existing = readFileSync10(commitMsgFile, "utf-8");
|
|
2848
|
+
writeFileSync5(commitMsgFile, existing + lines.join("\n") + "\n", "utf-8");
|
|
2591
2849
|
await db.close();
|
|
2592
2850
|
} catch {
|
|
2593
2851
|
}
|
|
2594
2852
|
}
|
|
2595
2853
|
|
|
2596
2854
|
// src/commands/hook.ts
|
|
2597
|
-
var
|
|
2598
|
-
var
|
|
2599
|
-
var
|
|
2855
|
+
var CONTXT_BLOCK_START2 = "# --- contxt hook start ---";
|
|
2856
|
+
var CONTXT_BLOCK_END2 = "# --- contxt hook end ---";
|
|
2857
|
+
var ALL_HOOKS2 = ["post-commit", "pre-push", "post-checkout", "prepare-commit-msg"];
|
|
2600
2858
|
async function installCommand(options = {}) {
|
|
2601
|
-
const gitHooksDir =
|
|
2602
|
-
if (!
|
|
2603
|
-
console.error(
|
|
2859
|
+
const gitHooksDir = join9(process.cwd(), ".git", "hooks");
|
|
2860
|
+
if (!existsSync9(join9(process.cwd(), ".git"))) {
|
|
2861
|
+
console.error(chalk9.red("Not a git repository."));
|
|
2604
2862
|
process.exit(1);
|
|
2605
2863
|
}
|
|
2606
2864
|
mkdirSync3(gitHooksDir, { recursive: true });
|
|
2607
|
-
const hooksToInstall = options.hooks ? options.hooks.split(",").map((h) => h.trim()) : [...
|
|
2865
|
+
const hooksToInstall = options.hooks ? options.hooks.split(",").map((h) => h.trim()) : [...ALL_HOOKS2];
|
|
2608
2866
|
let installed = 0;
|
|
2609
2867
|
let updated = 0;
|
|
2610
2868
|
for (const hookName of hooksToInstall) {
|
|
2611
|
-
const hookPath =
|
|
2869
|
+
const hookPath = join9(gitHooksDir, hookName);
|
|
2612
2870
|
const contxtLine = `contxt hook run ${hookName} "$@"`;
|
|
2613
|
-
const contxtBlock = `${
|
|
2871
|
+
const contxtBlock = `${CONTXT_BLOCK_START2}
|
|
2614
2872
|
${contxtLine}
|
|
2615
|
-
${
|
|
2616
|
-
if (
|
|
2617
|
-
const content =
|
|
2618
|
-
if (content.includes(
|
|
2873
|
+
${CONTXT_BLOCK_END2}`;
|
|
2874
|
+
if (existsSync9(hookPath)) {
|
|
2875
|
+
const content = readFileSync11(hookPath, "utf-8");
|
|
2876
|
+
if (content.includes(CONTXT_BLOCK_START2)) {
|
|
2619
2877
|
const updated_content = content.replace(
|
|
2620
|
-
new RegExp(`${escapeRegex(
|
|
2878
|
+
new RegExp(`${escapeRegex(CONTXT_BLOCK_START2)}[\\s\\S]*?${escapeRegex(CONTXT_BLOCK_END2)}`),
|
|
2621
2879
|
contxtBlock
|
|
2622
2880
|
);
|
|
2623
|
-
|
|
2881
|
+
writeFileSync6(hookPath, updated_content, "utf-8");
|
|
2624
2882
|
updated++;
|
|
2625
2883
|
} else {
|
|
2626
2884
|
const newContent = content.trimEnd() + "\n\n" + contxtBlock + "\n";
|
|
2627
|
-
|
|
2885
|
+
writeFileSync6(hookPath, newContent, "utf-8");
|
|
2628
2886
|
installed++;
|
|
2629
2887
|
}
|
|
2630
2888
|
} else {
|
|
@@ -2632,79 +2890,79 @@ ${CONTXT_BLOCK_END}`;
|
|
|
2632
2890
|
|
|
2633
2891
|
${contxtBlock}
|
|
2634
2892
|
`;
|
|
2635
|
-
|
|
2893
|
+
writeFileSync6(hookPath, newContent, "utf-8");
|
|
2636
2894
|
installed++;
|
|
2637
2895
|
}
|
|
2638
|
-
|
|
2639
|
-
console.log(
|
|
2896
|
+
chmodSync2(hookPath, "755");
|
|
2897
|
+
console.log(chalk9.green("\u2713"), `${hookName}`);
|
|
2640
2898
|
}
|
|
2641
2899
|
console.log("");
|
|
2642
|
-
if (installed > 0) console.log(
|
|
2643
|
-
if (updated > 0) console.log(
|
|
2900
|
+
if (installed > 0) console.log(chalk9.green(`Installed ${installed} hook${installed !== 1 ? "s" : ""}.`));
|
|
2901
|
+
if (updated > 0) console.log(chalk9.yellow(`Updated ${updated} existing hook${updated !== 1 ? "s" : ""}.`));
|
|
2644
2902
|
console.log("");
|
|
2645
|
-
console.log(
|
|
2903
|
+
console.log(chalk9.gray("Hooks will capture context from your git workflow automatically."));
|
|
2646
2904
|
}
|
|
2647
2905
|
async function uninstallCommand(options = {}) {
|
|
2648
|
-
const gitHooksDir =
|
|
2649
|
-
if (!
|
|
2650
|
-
console.error(
|
|
2906
|
+
const gitHooksDir = join9(process.cwd(), ".git", "hooks");
|
|
2907
|
+
if (!existsSync9(join9(process.cwd(), ".git"))) {
|
|
2908
|
+
console.error(chalk9.red("Not a git repository."));
|
|
2651
2909
|
process.exit(1);
|
|
2652
2910
|
}
|
|
2653
|
-
const hooksToRemove = options.hooks ? options.hooks.split(",").map((h) => h.trim()) : [...
|
|
2911
|
+
const hooksToRemove = options.hooks ? options.hooks.split(",").map((h) => h.trim()) : [...ALL_HOOKS2];
|
|
2654
2912
|
let removed = 0;
|
|
2655
2913
|
for (const hookName of hooksToRemove) {
|
|
2656
|
-
const hookPath =
|
|
2657
|
-
if (!
|
|
2658
|
-
const content =
|
|
2659
|
-
if (!content.includes(
|
|
2914
|
+
const hookPath = join9(gitHooksDir, hookName);
|
|
2915
|
+
if (!existsSync9(hookPath)) continue;
|
|
2916
|
+
const content = readFileSync11(hookPath, "utf-8");
|
|
2917
|
+
if (!content.includes(CONTXT_BLOCK_START2)) continue;
|
|
2660
2918
|
const cleaned = content.replace(
|
|
2661
|
-
new RegExp(`\\n*${escapeRegex(
|
|
2919
|
+
new RegExp(`\\n*${escapeRegex(CONTXT_BLOCK_START2)}[\\s\\S]*?${escapeRegex(CONTXT_BLOCK_END2)}\\n*`),
|
|
2662
2920
|
"\n"
|
|
2663
2921
|
).trim();
|
|
2664
2922
|
if (cleaned === "#!/bin/sh" || cleaned === "") {
|
|
2665
|
-
|
|
2923
|
+
writeFileSync6(hookPath, "#!/bin/sh\n", "utf-8");
|
|
2666
2924
|
} else {
|
|
2667
|
-
|
|
2925
|
+
writeFileSync6(hookPath, cleaned + "\n", "utf-8");
|
|
2668
2926
|
}
|
|
2669
2927
|
removed++;
|
|
2670
|
-
console.log(
|
|
2928
|
+
console.log(chalk9.gray("\u2717"), hookName);
|
|
2671
2929
|
}
|
|
2672
2930
|
if (removed > 0) {
|
|
2673
2931
|
console.log("");
|
|
2674
|
-
console.log(
|
|
2932
|
+
console.log(chalk9.green(`Removed ${removed} hook${removed !== 1 ? "s" : ""}.`));
|
|
2675
2933
|
} else {
|
|
2676
|
-
console.log(
|
|
2934
|
+
console.log(chalk9.gray("No Contxt hooks found to remove."));
|
|
2677
2935
|
}
|
|
2678
2936
|
}
|
|
2679
2937
|
async function statusCommand2() {
|
|
2680
|
-
const gitHooksDir =
|
|
2681
|
-
if (!
|
|
2682
|
-
console.error(
|
|
2938
|
+
const gitHooksDir = join9(process.cwd(), ".git", "hooks");
|
|
2939
|
+
if (!existsSync9(join9(process.cwd(), ".git"))) {
|
|
2940
|
+
console.error(chalk9.red("Not a git repository."));
|
|
2683
2941
|
process.exit(1);
|
|
2684
2942
|
}
|
|
2685
2943
|
console.log("");
|
|
2686
|
-
console.log(
|
|
2944
|
+
console.log(chalk9.bold("Git Hook Status"));
|
|
2687
2945
|
console.log("");
|
|
2688
|
-
for (const hookName of
|
|
2689
|
-
const hookPath =
|
|
2690
|
-
const fileExists =
|
|
2946
|
+
for (const hookName of ALL_HOOKS2) {
|
|
2947
|
+
const hookPath = join9(gitHooksDir, hookName);
|
|
2948
|
+
const fileExists = existsSync9(hookPath);
|
|
2691
2949
|
let isInstalled = false;
|
|
2692
2950
|
if (fileExists) {
|
|
2693
|
-
const content =
|
|
2694
|
-
isInstalled = content.includes(
|
|
2951
|
+
const content = readFileSync11(hookPath, "utf-8");
|
|
2952
|
+
isInstalled = content.includes(CONTXT_BLOCK_START2);
|
|
2695
2953
|
}
|
|
2696
|
-
const statusIcon = isInstalled ?
|
|
2697
|
-
const label = isInstalled ?
|
|
2698
|
-
const note = !fileExists ?
|
|
2954
|
+
const statusIcon = isInstalled ? chalk9.green("\u2713") : chalk9.gray("\u25CB");
|
|
2955
|
+
const label = isInstalled ? chalk9.green(hookName) : chalk9.gray(hookName);
|
|
2956
|
+
const note = !fileExists ? chalk9.gray(" (no hook file)") : isInstalled ? chalk9.gray(" installed") : chalk9.yellow(" not installed");
|
|
2699
2957
|
console.log(` ${statusIcon} ${label}${note}`);
|
|
2700
2958
|
}
|
|
2701
2959
|
console.log("");
|
|
2702
|
-
const installedCount =
|
|
2703
|
-
const hookPath =
|
|
2704
|
-
return
|
|
2960
|
+
const installedCount = ALL_HOOKS2.filter((h) => {
|
|
2961
|
+
const hookPath = join9(gitHooksDir, h);
|
|
2962
|
+
return existsSync9(hookPath) && readFileSync11(hookPath, "utf-8").includes(CONTXT_BLOCK_START2);
|
|
2705
2963
|
}).length;
|
|
2706
2964
|
if (installedCount === 0) {
|
|
2707
|
-
console.log(
|
|
2965
|
+
console.log(chalk9.cyan(`Run ${chalk9.bold("contxt hook install")} to enable automatic context capture.`));
|
|
2708
2966
|
}
|
|
2709
2967
|
}
|
|
2710
2968
|
async function runCommand(hookName) {
|
|
@@ -2735,12 +2993,14 @@ var hookCommand = {
|
|
|
2735
2993
|
};
|
|
2736
2994
|
|
|
2737
2995
|
// src/commands/watch.ts
|
|
2738
|
-
import { readFileSync as
|
|
2739
|
-
import { join as
|
|
2740
|
-
import { spawn } from "child_process";
|
|
2741
|
-
import
|
|
2996
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync10, unlinkSync } from "fs";
|
|
2997
|
+
import { join as join10, relative as relative2 } from "path";
|
|
2998
|
+
import { spawn as spawn2 } from "child_process";
|
|
2999
|
+
import chalk10 from "chalk";
|
|
2742
3000
|
import chokidar from "chokidar";
|
|
2743
|
-
import { parseFile as parseFile2, scanCommentToEntry as scanCommentToEntry2 } from "@mycontxt/core";
|
|
3001
|
+
import { parseFile as parseFile2, scanCommentToEntry as scanCommentToEntry2, SyncEngine as SyncEngine2 } from "@mycontxt/core";
|
|
3002
|
+
import { SQLiteDatabase as SQLiteDatabase7 } from "@mycontxt/adapters/sqlite";
|
|
3003
|
+
import { SupabaseDatabase as SupabaseDatabase4 } from "@mycontxt/adapters/supabase";
|
|
2744
3004
|
var PID_FILE = ".contxt/.watch.pid";
|
|
2745
3005
|
var LOG_FILE = ".contxt/watch.log";
|
|
2746
3006
|
var SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
@@ -2752,59 +3012,59 @@ async function startCommand(options = {}) {
|
|
|
2752
3012
|
return runWatcher();
|
|
2753
3013
|
}
|
|
2754
3014
|
async function stopCommand() {
|
|
2755
|
-
const pidFile =
|
|
2756
|
-
if (!
|
|
2757
|
-
console.log(
|
|
3015
|
+
const pidFile = join10(process.cwd(), PID_FILE);
|
|
3016
|
+
if (!existsSync10(pidFile)) {
|
|
3017
|
+
console.log(chalk10.gray("No watch daemon running."));
|
|
2758
3018
|
return;
|
|
2759
3019
|
}
|
|
2760
|
-
const pid = parseInt(
|
|
3020
|
+
const pid = parseInt(readFileSync12(pidFile, "utf-8").trim(), 10);
|
|
2761
3021
|
try {
|
|
2762
3022
|
process.kill(pid, "SIGTERM");
|
|
2763
3023
|
unlinkSync(pidFile);
|
|
2764
|
-
console.log(
|
|
3024
|
+
console.log(chalk10.green("Watch daemon stopped."));
|
|
2765
3025
|
} catch {
|
|
2766
|
-
console.log(
|
|
3026
|
+
console.log(chalk10.yellow("Daemon not found \u2014 removing stale PID file."));
|
|
2767
3027
|
unlinkSync(pidFile);
|
|
2768
3028
|
}
|
|
2769
3029
|
}
|
|
2770
3030
|
async function statusCommand3() {
|
|
2771
|
-
const pidFile =
|
|
2772
|
-
if (!
|
|
2773
|
-
console.log(
|
|
3031
|
+
const pidFile = join10(process.cwd(), PID_FILE);
|
|
3032
|
+
if (!existsSync10(pidFile)) {
|
|
3033
|
+
console.log(chalk10.gray("Watch daemon: not running"));
|
|
2774
3034
|
return;
|
|
2775
3035
|
}
|
|
2776
|
-
const pid = parseInt(
|
|
3036
|
+
const pid = parseInt(readFileSync12(pidFile, "utf-8").trim(), 10);
|
|
2777
3037
|
try {
|
|
2778
3038
|
process.kill(pid, 0);
|
|
2779
|
-
console.log(
|
|
3039
|
+
console.log(chalk10.green(`Watch daemon: running (PID ${pid})`));
|
|
2780
3040
|
} catch {
|
|
2781
|
-
console.log(
|
|
3041
|
+
console.log(chalk10.yellow("Watch daemon: stale PID file (process not found)"));
|
|
2782
3042
|
unlinkSync(pidFile);
|
|
2783
3043
|
}
|
|
2784
3044
|
}
|
|
2785
3045
|
function startDaemon() {
|
|
2786
|
-
const pidFile =
|
|
2787
|
-
if (
|
|
2788
|
-
const pid = parseInt(
|
|
3046
|
+
const pidFile = join10(process.cwd(), PID_FILE);
|
|
3047
|
+
if (existsSync10(pidFile)) {
|
|
3048
|
+
const pid = parseInt(readFileSync12(pidFile, "utf-8").trim(), 10);
|
|
2789
3049
|
try {
|
|
2790
3050
|
process.kill(pid, 0);
|
|
2791
|
-
console.log(
|
|
3051
|
+
console.log(chalk10.yellow(`Watch daemon already running (PID ${pid}).`));
|
|
2792
3052
|
return;
|
|
2793
3053
|
} catch {
|
|
2794
3054
|
}
|
|
2795
3055
|
}
|
|
2796
|
-
const logPath =
|
|
3056
|
+
const logPath = join10(process.cwd(), LOG_FILE);
|
|
2797
3057
|
const logStream = __require("fs").openSync(logPath, "a");
|
|
2798
|
-
const child =
|
|
3058
|
+
const child = spawn2(process.execPath, [process.argv[1], "watch"], {
|
|
2799
3059
|
env: { ...process.env, CONTXT_WATCH_DAEMON: "1" },
|
|
2800
3060
|
detached: true,
|
|
2801
3061
|
stdio: ["ignore", logStream, logStream],
|
|
2802
3062
|
cwd: process.cwd()
|
|
2803
3063
|
});
|
|
2804
3064
|
child.unref();
|
|
2805
|
-
|
|
2806
|
-
console.log(
|
|
2807
|
-
console.log(
|
|
3065
|
+
writeFileSync7(pidFile, String(child.pid), "utf-8");
|
|
3066
|
+
console.log(chalk10.green(`Watch daemon started (PID ${child.pid}).`));
|
|
3067
|
+
console.log(chalk10.gray(`Logs: ${logPath}`));
|
|
2808
3068
|
}
|
|
2809
3069
|
async function runWatcher() {
|
|
2810
3070
|
const cwd = process.cwd();
|
|
@@ -2812,12 +3072,12 @@ async function runWatcher() {
|
|
|
2812
3072
|
const db = await getProjectDb(cwd);
|
|
2813
3073
|
const project = await db.getProjectByPath(cwd);
|
|
2814
3074
|
if (!project) {
|
|
2815
|
-
if (!isDaemon) console.error(
|
|
3075
|
+
if (!isDaemon) console.error(chalk10.red("Not a Contxt project."));
|
|
2816
3076
|
return;
|
|
2817
3077
|
}
|
|
2818
3078
|
const branch2 = await db.getActiveBranch(project.id);
|
|
2819
3079
|
if (!isDaemon) {
|
|
2820
|
-
console.log(
|
|
3080
|
+
console.log(chalk10.bold(`contxt watch`) + ` \u2014 monitoring ${project.name} (${branch2})`);
|
|
2821
3081
|
console.log("");
|
|
2822
3082
|
}
|
|
2823
3083
|
const pendingFiles = /* @__PURE__ */ new Set();
|
|
@@ -2854,9 +3114,9 @@ async function runWatcher() {
|
|
|
2854
3114
|
usePolling: false,
|
|
2855
3115
|
interval: 1e3
|
|
2856
3116
|
});
|
|
2857
|
-
const rulesPath =
|
|
3117
|
+
const rulesPath = join10(cwd, ".contxt", "rules.md");
|
|
2858
3118
|
const rulesWatcher = chokidar.watch(rulesPath, { ignoreInitial: true });
|
|
2859
|
-
const gitHeadPath =
|
|
3119
|
+
const gitHeadPath = join10(cwd, ".git", "HEAD");
|
|
2860
3120
|
const gitWatcher = chokidar.watch(gitHeadPath, { ignoreInitial: true });
|
|
2861
3121
|
watcher.on("change", (filePath) => {
|
|
2862
3122
|
pendingFiles.add(filePath);
|
|
@@ -2872,7 +3132,7 @@ async function runWatcher() {
|
|
|
2872
3132
|
log("rules", "rules.md changed \u2014 syncing...");
|
|
2873
3133
|
try {
|
|
2874
3134
|
const { parseRulesFile: parseRulesFile2 } = await import("@mycontxt/core");
|
|
2875
|
-
const content =
|
|
3135
|
+
const content = readFileSync12(rulesPath, "utf-8");
|
|
2876
3136
|
const parsed = parseRulesFile2(content);
|
|
2877
3137
|
let synced = 0;
|
|
2878
3138
|
const existing = await db.listEntries({ projectId: project.id, branch: branch2 });
|
|
@@ -2894,7 +3154,7 @@ async function runWatcher() {
|
|
|
2894
3154
|
});
|
|
2895
3155
|
gitWatcher.on("change", async () => {
|
|
2896
3156
|
try {
|
|
2897
|
-
const headContent =
|
|
3157
|
+
const headContent = readFileSync12(gitHeadPath, "utf-8").trim();
|
|
2898
3158
|
const branchMatch = headContent.match(/^ref: refs\/heads\/(.+)$/);
|
|
2899
3159
|
if (!branchMatch) return;
|
|
2900
3160
|
const newBranch = branchMatch[1];
|
|
@@ -2919,7 +3179,7 @@ async function runWatcher() {
|
|
|
2919
3179
|
const activeCtx = entries.find((e) => e.status === "active");
|
|
2920
3180
|
if (activeCtx) {
|
|
2921
3181
|
const currentFiles = activeCtx.metadata.files || [];
|
|
2922
|
-
const relFiles = files.map((f) => relative2(cwd,
|
|
3182
|
+
const relFiles = files.map((f) => relative2(cwd, join10(cwd, f)));
|
|
2923
3183
|
const merged = Array.from(/* @__PURE__ */ new Set([...currentFiles, ...relFiles])).slice(0, 30);
|
|
2924
3184
|
await db.updateEntry(activeCtx.id, { metadata: { ...activeCtx.metadata, files: merged } });
|
|
2925
3185
|
}
|
|
@@ -2928,9 +3188,9 @@ async function runWatcher() {
|
|
|
2928
3188
|
let newDrafts = 0;
|
|
2929
3189
|
for (const file of files) {
|
|
2930
3190
|
try {
|
|
2931
|
-
const absPath =
|
|
2932
|
-
if (!
|
|
2933
|
-
const content =
|
|
3191
|
+
const absPath = join10(cwd, file);
|
|
3192
|
+
if (!existsSync10(absPath)) continue;
|
|
3193
|
+
const content = readFileSync12(absPath, "utf-8");
|
|
2934
3194
|
const comments = parseFile2(content, file);
|
|
2935
3195
|
if (comments.length === 0) continue;
|
|
2936
3196
|
const existing = await db.listEntries({ projectId: project.id, branch: branch2 });
|
|
@@ -2946,6 +3206,25 @@ async function runWatcher() {
|
|
|
2946
3206
|
}
|
|
2947
3207
|
}
|
|
2948
3208
|
log("files", `${files.length} file${files.length !== 1 ? "s" : ""} ${newDrafts > 0 ? `\xB7 +${newDrafts} draft${newDrafts !== 1 ? "s" : ""}` : ""}`);
|
|
3209
|
+
try {
|
|
3210
|
+
const freshProject = await db.getProjectByPath(cwd);
|
|
3211
|
+
if (freshProject?.config.autoSync) {
|
|
3212
|
+
const accessToken = getAccessToken();
|
|
3213
|
+
if (accessToken) {
|
|
3214
|
+
const supabaseConfig = getSupabaseConfig();
|
|
3215
|
+
const dbPath = getDbPath();
|
|
3216
|
+
const localDb = new SQLiteDatabase7(dbPath);
|
|
3217
|
+
await localDb.initialize();
|
|
3218
|
+
const remoteDb = new SupabaseDatabase4({ ...supabaseConfig, accessToken });
|
|
3219
|
+
await remoteDb.initialize();
|
|
3220
|
+
const syncEngine = new SyncEngine2(localDb, remoteDb);
|
|
3221
|
+
const result = await syncEngine.push(freshProject.id, {});
|
|
3222
|
+
await localDb.close();
|
|
3223
|
+
log("sync", `pushed ${result.pushed} entr${result.pushed !== 1 ? "ies" : "y"} to cloud`);
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
} catch {
|
|
3227
|
+
}
|
|
2949
3228
|
}
|
|
2950
3229
|
function scheduleFlush() {
|
|
2951
3230
|
if (flushTimer) clearTimeout(flushTimer);
|
|
@@ -2972,10 +3251,10 @@ async function runWatcher() {
|
|
|
2972
3251
|
const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" });
|
|
2973
3252
|
const line = `${time} ${type.padEnd(8)} ${message}`;
|
|
2974
3253
|
if (!isDaemon) {
|
|
2975
|
-
console.log(` ${
|
|
3254
|
+
console.log(` ${chalk10.gray(time)} ${chalk10.cyan(type.padEnd(8))} ${message}`);
|
|
2976
3255
|
} else {
|
|
2977
3256
|
try {
|
|
2978
|
-
__require("fs").appendFileSync(
|
|
3257
|
+
__require("fs").appendFileSync(join10(cwd, LOG_FILE), line + "\n");
|
|
2979
3258
|
} catch {
|
|
2980
3259
|
}
|
|
2981
3260
|
}
|
|
@@ -2987,14 +3266,14 @@ async function runWatcher() {
|
|
|
2987
3266
|
rulesWatcher.close();
|
|
2988
3267
|
gitWatcher.close();
|
|
2989
3268
|
await db.close();
|
|
2990
|
-
const pidFile =
|
|
2991
|
-
if (
|
|
3269
|
+
const pidFile = join10(cwd, PID_FILE);
|
|
3270
|
+
if (existsSync10(pidFile)) unlinkSync(pidFile);
|
|
2992
3271
|
process.exit(0);
|
|
2993
3272
|
};
|
|
2994
3273
|
process.on("SIGTERM", shutdown);
|
|
2995
3274
|
process.on("SIGINT", shutdown);
|
|
2996
3275
|
if (!isDaemon) {
|
|
2997
|
-
console.log(
|
|
3276
|
+
console.log(chalk10.gray("Watching for file changes. Ctrl+C to stop.\n"));
|
|
2998
3277
|
}
|
|
2999
3278
|
}
|
|
3000
3279
|
var watchCommand = {
|