mcp-context-sync 1.0.11 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/cli.js +253 -49
  2. package/dist/cli.js.map +1 -1
  3. package/dist/db/connection.d.ts +10 -1
  4. package/dist/db/connection.js +59 -23
  5. package/dist/db/connection.js.map +1 -1
  6. package/dist/db/json-adapter.d.ts +47 -0
  7. package/dist/db/json-adapter.js +235 -0
  8. package/dist/db/json-adapter.js.map +1 -0
  9. package/dist/db/sqlite-adapter.d.ts +37 -0
  10. package/dist/db/sqlite-adapter.js +78 -0
  11. package/dist/db/sqlite-adapter.js.map +1 -0
  12. package/dist/db/storage.d.ts +41 -0
  13. package/dist/db/storage.js +9 -0
  14. package/dist/db/storage.js.map +1 -0
  15. package/dist/index.js +4 -4
  16. package/dist/index.js.map +1 -1
  17. package/dist/resources/project-decisions.d.ts +2 -2
  18. package/dist/resources/project-decisions.js +3 -4
  19. package/dist/resources/project-decisions.js.map +1 -1
  20. package/dist/resources/project-snapshot.d.ts +2 -2
  21. package/dist/resources/project-snapshot.js +3 -4
  22. package/dist/resources/project-snapshot.js.map +1 -1
  23. package/dist/resources/status.d.ts +2 -2
  24. package/dist/resources/status.js +1 -2
  25. package/dist/resources/status.js.map +1 -1
  26. package/dist/tools/amend-snapshot.d.ts +2 -2
  27. package/dist/tools/amend-snapshot.js +9 -10
  28. package/dist/tools/amend-snapshot.js.map +1 -1
  29. package/dist/tools/get-history.d.ts +2 -2
  30. package/dist/tools/get-history.js +3 -4
  31. package/dist/tools/get-history.js.map +1 -1
  32. package/dist/tools/list-projects.d.ts +2 -2
  33. package/dist/tools/list-projects.js +1 -2
  34. package/dist/tools/list-projects.js.map +1 -1
  35. package/dist/tools/log-decision.d.ts +2 -2
  36. package/dist/tools/log-decision.js +2 -3
  37. package/dist/tools/log-decision.js.map +1 -1
  38. package/dist/tools/resume.d.ts +2 -2
  39. package/dist/tools/resume.js +7 -8
  40. package/dist/tools/resume.js.map +1 -1
  41. package/dist/tools/search-snapshots.d.ts +2 -2
  42. package/dist/tools/search-snapshots.js +2 -3
  43. package/dist/tools/search-snapshots.js.map +1 -1
  44. package/dist/tools/sync.d.ts +2 -2
  45. package/dist/tools/sync.js +5 -6
  46. package/dist/tools/sync.js.map +1 -1
  47. package/package.json +4 -2
  48. package/scripts/postinstall.cjs +14 -34
  49. package/scripts/preinstall.cjs +13 -86
package/dist/cli.js CHANGED
@@ -15,8 +15,7 @@ import { execSync } from 'node:child_process';
15
15
  import { dirname, join } from 'node:path';
16
16
  import { fileURLToPath } from 'node:url';
17
17
  import { homedir, platform } from 'node:os';
18
- import { getDatabase, closeDatabase } from './db/connection.js';
19
- import { upsertProject, getProject, getNextSequenceNumber, insertSnapshot, insertDecision, insertHandoff, updateProjectSyncStats, getLatestSnapshot, getProjectAgents, listProjects, getHandoffsByProject, } from './db/queries.js';
18
+ import { getStorage, closeStorage } from './db/connection.js';
20
19
  import { projectIdFromPath, normalizePath, displayNameFromPath, } from './lib/project-id.js';
21
20
  import { generateId } from './lib/uuid.js';
22
21
  import { renderSnapshotMarkdown, renderSnapshotBrief } from './lib/snapshot-renderer.js';
@@ -60,7 +59,7 @@ function readJsonInput() {
60
59
  * Returns the matched normalized path, or null if none found.
61
60
  */
62
61
  function resolveProjectDir(db, startDir) {
63
- const projects = listProjects(db);
62
+ const projects = db.listProjects();
64
63
  if (projects.length === 0)
65
64
  return null;
66
65
  const knownPaths = new Set(projects.map((p) => p.normalized_path));
@@ -121,14 +120,14 @@ function cmdResume() {
121
120
  const wantBrief = argv.includes('--brief');
122
121
  // --json and --brief imply --peek (no handoff registration)
123
122
  const peek = argv.includes('--peek') || wantJson || wantBrief;
124
- const db = getDatabase();
123
+ const db = getStorage();
125
124
  const projectDir = getEffectiveProjectDir(db);
126
125
  const projectId = projectIdFromPath(projectDir);
127
126
  const displayName = displayNameFromPath(projectDir);
128
- const project = getProject(db, projectId);
127
+ const project = db.getProject(projectId);
129
128
  if (!project) {
130
129
  if (!peek) {
131
- const all = listProjects(db);
130
+ const all = db.listProjects();
132
131
  console.error(`No data found for "${displayName}" (${normalizePath(projectDir)})`);
133
132
  if (all.length > 0) {
134
133
  console.error('\nAvailable projects:');
@@ -136,20 +135,20 @@ function cmdResume() {
136
135
  console.error(` ${p.normalized_path} (${p.snapshot_count} snapshots)`);
137
136
  }
138
137
  }
139
- closeDatabase();
138
+ closeStorage();
140
139
  process.exit(peek ? 0 : 1);
141
140
  }
142
- const snapshot = getLatestSnapshot(db, projectId);
141
+ const snapshot = db.getLatestSnapshot(projectId);
143
142
  if (!snapshot) {
144
143
  if (!peek)
145
144
  console.error(`Project "${displayName}" exists but has no snapshots.`);
146
- closeDatabase();
145
+ closeStorage();
147
146
  process.exit(peek ? 0 : 1);
148
147
  }
149
148
  if (!peek) {
150
149
  const now = new Date().toISOString();
151
150
  db.transaction(() => {
152
- insertHandoff(db, {
151
+ db.insertHandoff({
153
152
  id: generateId(),
154
153
  project_id: projectId,
155
154
  snapshot_id: snapshot.id,
@@ -159,7 +158,7 @@ function cmdResume() {
159
158
  to_timestamp: now,
160
159
  created_at: now,
161
160
  });
162
- updateProjectSyncStats(db, projectId);
161
+ db.updateProjectSyncStats(projectId);
163
162
  })();
164
163
  }
165
164
  // Output priority: --json > --brief > default (full markdown)
@@ -170,14 +169,14 @@ function cmdResume() {
170
169
  console.log(renderSnapshotBrief(snapshot, displayName));
171
170
  }
172
171
  else {
173
- const agents = getProjectAgents(db, projectId);
172
+ const agents = db.getProjectAgents(projectId);
174
173
  console.log(renderSnapshotMarkdown(snapshot, displayName, {
175
174
  totalSnapshots: project.snapshot_count,
176
175
  totalHandoffs: project.handoff_count + (peek ? 0 : 1),
177
176
  agents,
178
177
  }));
179
178
  }
180
- closeDatabase();
179
+ closeStorage();
181
180
  }
182
181
  // ---------------------------------------------------------------------------
183
182
  // sync
@@ -186,16 +185,16 @@ function cmdResume() {
186
185
  * Save the snapshot to the database (shared by cmdSync and cmdSyncInteractive).
187
186
  */
188
187
  function saveSnapshot(projectDir, agent, summary, tasksCompleted, tasksRemaining, decisions, filesModified, blockers, nextSteps, tags) {
189
- const db = getDatabase();
188
+ const db = getStorage();
190
189
  const projectId = projectIdFromPath(projectDir);
191
190
  const normalized = normalizePath(projectDir);
192
191
  const displayName = displayNameFromPath(projectDir);
193
192
  const now = new Date().toISOString();
194
193
  const { snapshotId, seqNum } = db.transaction(() => {
195
- upsertProject(db, projectId, normalized, displayName);
196
- const seqNum = getNextSequenceNumber(db, projectId);
194
+ db.upsertProject(projectId, normalized, displayName);
195
+ const seqNum = db.getNextSequenceNumber(projectId);
197
196
  const snapshotId = generateId();
198
- insertSnapshot(db, {
197
+ db.insertSnapshot({
199
198
  id: snapshotId,
200
199
  project_id: projectId,
201
200
  sequence_number: seqNum,
@@ -211,7 +210,7 @@ function saveSnapshot(projectDir, agent, summary, tasksCompleted, tasksRemaining
211
210
  created_at: now,
212
211
  });
213
212
  for (const d of decisions) {
214
- insertDecision(db, {
213
+ db.insertDecision({
215
214
  id: generateId(),
216
215
  project_id: projectId,
217
216
  snapshot_id: snapshotId,
@@ -224,10 +223,10 @@ function saveSnapshot(projectDir, agent, summary, tasksCompleted, tasksRemaining
224
223
  created_at: now,
225
224
  });
226
225
  }
227
- updateProjectSyncStats(db, projectId);
226
+ db.updateProjectSyncStats(projectId);
228
227
  return { snapshotId, seqNum };
229
228
  })();
230
- closeDatabase();
229
+ closeStorage();
231
230
  return { snapshotId, seqNum, displayName, normalized };
232
231
  }
233
232
  function cmdSync() {
@@ -296,9 +295,9 @@ function cmdSyncInteractive() {
296
295
  return input.split(',').map((s) => s.trim()).filter(Boolean);
297
296
  }
298
297
  async function run() {
299
- const db = getDatabase();
298
+ const db = getStorage();
300
299
  const projectDir = getEffectiveProjectDir(db);
301
- closeDatabase();
300
+ closeStorage();
302
301
  const agent = getFlag('agent') ?? 'claude-code';
303
302
  const displayName = displayNameFromPath(projectDir);
304
303
  console.log('');
@@ -349,8 +348,8 @@ function cmdSyncInteractive() {
349
348
  // list
350
349
  // ---------------------------------------------------------------------------
351
350
  function cmdList() {
352
- const db = getDatabase();
353
- const projects = listProjects(db);
351
+ const db = getStorage();
352
+ const projects = db.listProjects();
354
353
  if (projects.length === 0) {
355
354
  console.log('No projects synced yet.');
356
355
  }
@@ -363,17 +362,17 @@ function cmdList() {
363
362
  console.log();
364
363
  }
365
364
  }
366
- closeDatabase();
365
+ closeStorage();
367
366
  }
368
367
  // ---------------------------------------------------------------------------
369
368
  // history
370
369
  // ---------------------------------------------------------------------------
371
370
  function cmdHistory() {
372
371
  const limit = parseInt(getFlag('limit') ?? '10', 10);
373
- const db = getDatabase();
372
+ const db = getStorage();
374
373
  const projectDir = getEffectiveProjectDir(db);
375
374
  const projectId = projectIdFromPath(projectDir);
376
- const handoffs = getHandoffsByProject(db, projectId, limit);
375
+ const handoffs = db.getHandoffsByProject(projectId, limit);
377
376
  if (handoffs.length === 0) {
378
377
  console.log('No handoff history for this project.');
379
378
  }
@@ -382,7 +381,7 @@ function cmdHistory() {
382
381
  console.log(`${h.from_agent} -> ${h.to_agent} (${h.created_at})`);
383
382
  }
384
383
  }
385
- closeDatabase();
384
+ closeStorage();
386
385
  }
387
386
  // ---------------------------------------------------------------------------
388
387
  // auto-sync
@@ -391,7 +390,7 @@ function cmdAutoSync() {
391
390
  const agent = getFlag('agent') ?? 'claude-code';
392
391
  let db;
393
392
  try {
394
- db = getDatabase();
393
+ db = getStorage();
395
394
  }
396
395
  catch {
397
396
  // DB not accessible — exit silently
@@ -399,13 +398,13 @@ function cmdAutoSync() {
399
398
  }
400
399
  const projectDir = getEffectiveProjectDir(db);
401
400
  const projectId = projectIdFromPath(projectDir);
402
- const project = getProject(db, projectId);
401
+ const project = db.getProject(projectId);
403
402
  if (!project) {
404
403
  // No existing project — exit silently, don't create one
405
- closeDatabase();
404
+ closeStorage();
406
405
  process.exit(0);
407
406
  }
408
- const latestSnapshot = getLatestSnapshot(db, projectId);
407
+ const latestSnapshot = db.getLatestSnapshot(projectId);
409
408
  // Carry over remaining tasks from the latest snapshot
410
409
  let tasksRemaining = '[]';
411
410
  if (latestSnapshot) {
@@ -414,9 +413,9 @@ function cmdAutoSync() {
414
413
  const now = new Date().toISOString();
415
414
  try {
416
415
  db.transaction(() => {
417
- const seqNum = getNextSequenceNumber(db, projectId);
416
+ const seqNum = db.getNextSequenceNumber(projectId);
418
417
  const snapshotId = generateId();
419
- insertSnapshot(db, {
418
+ db.insertSnapshot({
420
419
  id: snapshotId,
421
420
  project_id: projectId,
422
421
  sequence_number: seqNum,
@@ -431,13 +430,13 @@ function cmdAutoSync() {
431
430
  tags: '["auto-sync"]',
432
431
  created_at: now,
433
432
  });
434
- updateProjectSyncStats(db, projectId);
433
+ db.updateProjectSyncStats(projectId);
435
434
  })();
436
435
  }
437
436
  catch {
438
437
  // Swallow errors — must not fail on session exit
439
438
  }
440
- closeDatabase();
439
+ closeStorage();
441
440
  }
442
441
  // ---------------------------------------------------------------------------
443
442
  // doctor
@@ -472,19 +471,28 @@ function cmdDoctor() {
472
471
  catch {
473
472
  console.log('Node.js: unknown \u26A0');
474
473
  }
475
- // 2. better-sqlite3 — test by opening the database
476
- let sqliteOk = false;
474
+ // 2. Storage backend
475
+ let storageOk = false;
476
+ let backendName = 'unknown';
477
477
  try {
478
- const db = getDatabase();
479
- closeDatabase();
480
- void db;
481
- sqliteOk = true;
482
- console.log('better-sqlite3: loaded \u2713');
478
+ const db = getStorage();
479
+ backendName = db.backend;
480
+ storageOk = true;
481
+ if (backendName === 'sqlite') {
482
+ console.log('Storage backend: SQLite (better-sqlite3) \u2713');
483
+ }
484
+ else {
485
+ console.log('Storage backend: JSON files (fallback) \u26A0');
486
+ issues.push('\u26A0 Using JSON fallback storage. better-sqlite3 is not available.\n' +
487
+ ' SQLite is faster and supports FTS search. To enable it:\n' +
488
+ ' Fix: Reinstall with Node 20-22 LTS, or install Visual Studio Build Tools on Windows.');
489
+ }
490
+ closeStorage();
483
491
  }
484
492
  catch (e) {
485
493
  const msg = e instanceof Error ? e.message : String(e);
486
- console.log('better-sqlite3: FAILED \u2717');
487
- issues.push(`\u2717 better-sqlite3 failed to load: ${msg}\n Fix: npm i -g mcp-context-sync (with Node 20-22), or install Visual Studio Build Tools on Windows.`);
494
+ console.log('Storage backend: FAILED \u2717');
495
+ issues.push(`\u2717 Storage failed to initialize: ${msg}`);
488
496
  }
489
497
  // 3. SQLite DB
490
498
  const dbPath = join(homedir(), '.agents', 'context-sync', 'data', 'context-sync.sqlite');
@@ -650,14 +658,14 @@ function cmdDoctor() {
650
658
  }
651
659
  // 7. Projects synced
652
660
  console.log('\nData:');
653
- if (sqliteOk) {
661
+ if (storageOk) {
654
662
  try {
655
- const db = getDatabase();
656
- const projects = listProjects(db);
663
+ const db = getStorage();
664
+ const projects = db.listProjects();
657
665
  const totalSnapshots = projects.reduce((sum, p) => sum + p.snapshot_count, 0);
658
666
  const totalHandoffs = projects.reduce((sum, p) => sum + p.handoff_count, 0);
659
667
  console.log(` Projects: ${projects.length} | Snapshots: ${totalSnapshots} | Handoffs: ${totalHandoffs}`);
660
- closeDatabase();
668
+ closeStorage();
661
669
  }
662
670
  catch {
663
671
  console.log(' (unable to read database)');
@@ -690,17 +698,201 @@ function hasMcpServer(obj, serverName) {
690
698
  const servers = obj.mcpServers;
691
699
  return !!servers && serverName in servers;
692
700
  }
701
+ // ---------------------------------------------------------------------------
702
+ // Skill templates for /resume-project and /sync-project
703
+ // ---------------------------------------------------------------------------
704
+ function getResumeSkill(agent, mcpStyle) {
705
+ if (mcpStyle === 'direct') {
706
+ // Claude Code — can call MCP tools directly
707
+ return `---
708
+ name: resume-project
709
+ description: Load previous work context from context-sync. Use when starting a session, switching from another agent, or wanting to see what was previously worked on.
710
+ disable-model-invocation: true
711
+ ---
712
+
713
+ Load previous work context so you can continue where another agent left off.
714
+
715
+ ## Steps
716
+
717
+ 1. **Detect environment:**
718
+ - \`projectDir\`: Primary working directory (absolute path to project root)
719
+ - \`agent\`: \`"${agent}"\`
720
+
721
+ 2. **Call \`mcp__context-sync__resume\`** with \`{ "projectDir": "<path>", "agent": "${agent}" }\`
722
+
723
+ 3. **Present results conversationally:**
724
+ - Short header: project name, previous agent, date, snapshot number
725
+ - Top 5 completed tasks (mention total if more)
726
+ - Remaining tasks (top 5)
727
+ - 1-2 recent decisions
728
+ - \`next_steps\` as the suggested starting point
729
+
730
+ 4. **If error** (project not found): relay the error directly — it already lists available projects and suggestions.
731
+
732
+ 5. **If MCP tool fails** (connection error): fall back to CLI: \`context-sync resume --agent ${agent}\`
733
+ `;
734
+ }
735
+ // Codex/Gemini — MCP + CLI fallback
736
+ return `---
737
+ name: resume-project
738
+ description: Load previous work context from context-sync. Use when starting a session, switching from another agent, or wanting to see what was previously worked on.
739
+ metadata:
740
+ short-description: Resume context from previous session
741
+ ---
742
+
743
+ # Resume Project
744
+
745
+ Load previous work context so you can continue where another agent left off.
746
+
747
+ ## Method 1: MCP Tool (preferred)
748
+
749
+ Call the MCP tool named **\`resume\`** from the **\`context-sync\`** server:
750
+ \`\`\`json
751
+ { "projectDir": "<absolute path to project root>", "agent": "${agent}" }
752
+ \`\`\`
753
+
754
+ ## Method 2: CLI (fallback if MCP tool call fails)
755
+
756
+ \`\`\`bash
757
+ context-sync resume --project "<absolute path to project root>" --agent ${agent}
758
+ \`\`\`
759
+
760
+ If \`--project\` is omitted, the current working directory is used automatically.
761
+
762
+ ## After loading
763
+
764
+ Present results conversationally:
765
+ - Project name, previous agent, date, snapshot number
766
+ - Completed and remaining tasks (top 5 each)
767
+ - Key decisions
768
+ - \`next_steps\` as suggested starting point
769
+
770
+ If project not found, the output will list available projects.
771
+ `;
772
+ }
773
+ function getSyncSkill(agent, mcpStyle) {
774
+ if (mcpStyle === 'direct') {
775
+ // Claude Code — can call MCP tools directly
776
+ return `---
777
+ name: sync-project
778
+ description: Save a structured context snapshot for handoff to another agent. Use when finishing a session, switching agents, or reaching a checkpoint.
779
+ disable-model-invocation: true
780
+ ---
781
+
782
+ Analyze the current session and save a structured snapshot for another agent to resume from.
783
+
784
+ ## Steps
785
+
786
+ 1. **Detect environment:**
787
+ - \`projectDir\`: Primary working directory (absolute path to project root, not a subdirectory)
788
+ - \`agent\`: \`"${agent}"\`
789
+
790
+ 2. **Analyze conversation and extract:**
791
+ - \`summary\`: 1-2 sentences on what was accomplished
792
+ - \`tasksCompleted\`: Specific tasks finished (be precise)
793
+ - \`tasksRemaining\`: Pending work, TODOs, known issues
794
+ - \`decisions\`: Technical choices with \`decision\`, \`reasoning\`, \`alternatives\`
795
+ - \`filesModified\`: Files touched with \`path\`, \`action\` (created/modified/deleted), \`description\`
796
+ - \`blockers\`: Unresolved issues (empty array if none)
797
+ - \`nextSteps\`: Specific actionable instructions for the next agent
798
+ - \`tags\`: Lowercase categories (e.g., \`feature\`, \`backend\`, \`typescript\`)
799
+
800
+ 3. **Show compact draft and ask for confirmation:**
801
+ \`\`\`
802
+ Ready to save snapshot for: [project]
803
+ Summary: [summary]
804
+ Completed: [count] | Remaining: [count] | Decisions: [count] | Files: [count]
805
+ Next steps: [brief]
806
+ Save? (yes / no / edit)
807
+ \`\`\`
808
+
809
+ 4. **Once confirmed, call \`mcp__context-sync__sync\`** with all extracted data.
810
+
811
+ 5. **Confirm** snapshot number, timestamp, and that another agent can use \`/resume-project\` to continue.
812
+ `;
813
+ }
814
+ // Codex/Gemini — MCP + CLI fallback
815
+ return `---
816
+ name: sync-project
817
+ description: Save a structured context snapshot for handoff to another agent. Use when finishing a session, switching agents, or reaching a checkpoint.
818
+ metadata:
819
+ short-description: Save context for agent handoff
820
+ ---
821
+
822
+ # Sync Project
823
+
824
+ Analyze the current session and save a structured snapshot for another agent to resume from.
825
+
826
+ ## Step 1: Gather data
827
+
828
+ - \`projectDir\`: Current working directory (absolute path to project **root**, not a subdirectory)
829
+ - \`agent\`: \`"${agent}"\`
830
+ - \`summary\`: 1-2 sentences on what was accomplished
831
+ - \`tasksCompleted\`: Specific finished tasks
832
+ - \`tasksRemaining\`: Pending work, TODOs, known issues
833
+ - \`decisions\`: Technical choices with \`decision\`, \`reasoning\`, \`alternatives\`
834
+ - \`filesModified\`: Files touched with \`path\`, \`action\` (created/modified/deleted), \`description\`
835
+ - \`blockers\`: Unresolved issues (empty array if none)
836
+ - \`nextSteps\`: Specific actionable instructions for the next agent
837
+ - \`tags\`: Lowercase categories (e.g., \`feature\`, \`backend\`, \`typescript\`)
838
+
839
+ Show a compact draft and ask for confirmation before saving.
840
+
841
+ ## Step 2: Save (pick one method)
842
+
843
+ ### Method 1: MCP Tool (preferred)
844
+
845
+ Call the MCP tool named **\`sync\`** from the **\`context-sync\`** server with the JSON above.
846
+
847
+ ### Method 2: CLI (fallback if MCP tool call fails)
848
+
849
+ Write the JSON to a temp file first:
850
+ \`\`\`bash
851
+ cat > /tmp/sync.json << 'EOF'
852
+ {"projectDir":"<path>","agent":"${agent}","summary":"...","tasksCompleted":[...],"tasksRemaining":[...],"nextSteps":"..."}
853
+ EOF
854
+
855
+ context-sync sync --file /tmp/sync.json
856
+ \`\`\`
857
+
858
+ ## Step 3: Confirm
859
+
860
+ Report the snapshot number and confirm another agent can resume.
861
+ `;
862
+ }
863
+ /**
864
+ * Install skill files for an agent. Returns paths created.
865
+ */
866
+ function installSkills(skillsDir, agent, mcpStyle) {
867
+ const created = [];
868
+ const skills = [
869
+ { name: 'resume-project', content: getResumeSkill(agent, mcpStyle) },
870
+ { name: 'sync-project', content: getSyncSkill(agent, mcpStyle) },
871
+ ];
872
+ for (const skill of skills) {
873
+ const dir = join(skillsDir, skill.name);
874
+ const file = join(dir, 'SKILL.md');
875
+ if (existsSync(file))
876
+ continue; // don't overwrite
877
+ ensureDir(dir);
878
+ writeFileSync(file, skill.content, 'utf-8');
879
+ created.push(file);
880
+ }
881
+ return created;
882
+ }
693
883
  function installClaude() {
694
884
  const label = 'Claude Code';
695
885
  // Claude Code can store MCP config in ~/.claude.json OR ~/.claude/settings.json
696
886
  const claudeJson = join(homedir(), '.claude.json');
697
887
  const settingsJson = join(homedir(), '.claude', 'settings.json');
888
+ const skillsDir = join(homedir(), '.claude', 'skills');
698
889
  // Check if already configured in either location
699
890
  for (const p of [claudeJson, settingsJson]) {
700
891
  try {
701
892
  if (existsSync(p)) {
702
893
  const cfg = readJsonFile(p);
703
894
  if (hasMcpServer(cfg, 'context-sync')) {
895
+ installSkills(skillsDir, 'claude-code', 'direct'); // ensure skills even if MCP skipped
704
896
  return { label, path: p, status: 'skipped' };
705
897
  }
706
898
  }
@@ -720,6 +912,9 @@ function installClaude() {
720
912
  env: {},
721
913
  };
722
914
  writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
915
+ // Install skills
916
+ const skillsDir = join(homedir(), '.claude', 'skills');
917
+ installSkills(skillsDir, 'claude-code', 'direct');
723
918
  return { label, path: configPath, status: 'configured' };
724
919
  }
725
920
  catch (err) {
@@ -730,12 +925,15 @@ function installCodex() {
730
925
  const codexDir = join(homedir(), '.codex');
731
926
  const mcpJsonPath = join(codexDir, 'mcp.json');
732
927
  const configTomlPath = join(codexDir, 'config.toml');
928
+ const skillsDir = join(codexDir, 'skills');
733
929
  const label = 'Codex';
734
930
  try {
931
+ const doSkills = () => installSkills(skillsDir, 'codex', 'cli');
735
932
  // Check mcp.json first
736
933
  if (existsSync(mcpJsonPath)) {
737
934
  const config = readJsonFile(mcpJsonPath);
738
935
  if (hasMcpServer(config, 'context-sync')) {
936
+ doSkills(); // ensure skills even if MCP already configured
739
937
  return { label, path: mcpJsonPath, status: 'skipped' };
740
938
  }
741
939
  if (!config.mcpServers)
@@ -744,16 +942,19 @@ function installCodex() {
744
942
  command: 'mcp-context-sync',
745
943
  };
746
944
  writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
945
+ doSkills();
747
946
  return { label, path: mcpJsonPath, status: 'configured' };
748
947
  }
749
948
  // Check config.toml
750
949
  if (existsSync(configTomlPath)) {
751
950
  const tomlContent = readFileSync(configTomlPath, 'utf-8');
752
951
  if (tomlContent.includes('[mcp_servers.context-sync]')) {
952
+ doSkills();
753
953
  return { label, path: configTomlPath, status: 'skipped' };
754
954
  }
755
955
  const appendBlock = '\n[mcp_servers.context-sync]\ncommand = "mcp-context-sync"\n';
756
956
  writeFileSync(configTomlPath, tomlContent + appendBlock, 'utf-8');
957
+ doSkills();
757
958
  return { label, path: configTomlPath, status: 'configured' };
758
959
  }
759
960
  // Neither exists — create mcp.json
@@ -764,6 +965,7 @@ function installCodex() {
764
965
  },
765
966
  };
766
967
  writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
968
+ doSkills();
767
969
  return { label, path: mcpJsonPath, status: 'configured' };
768
970
  }
769
971
  catch (err) {
@@ -772,9 +974,11 @@ function installCodex() {
772
974
  }
773
975
  function installGemini() {
774
976
  const configPath = join(homedir(), '.gemini', 'antigravity', 'mcp_config.json');
977
+ const skillsDir = join(homedir(), '.gemini', 'skills');
775
978
  const label = 'Gemini';
776
979
  try {
777
980
  ensureDir(dirname(configPath));
981
+ installSkills(skillsDir, 'gemini-cli', 'cli');
778
982
  const config = existsSync(configPath) ? readJsonFile(configPath) : {};
779
983
  if (hasMcpServer(config, 'context-sync')) {
780
984
  return { label, path: configPath, status: 'skipped' };