cortex-sync 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +15 -5
  2. package/dist/cli.js +415 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -8,6 +8,8 @@ Claude Code stores your session history in `~/.claude/projects/` using absolute
8
8
  npm install -g cortex-sync
9
9
  ```
10
10
 
11
+ ![cortex demo](demo/demo.gif)
12
+
11
13
  ---
12
14
 
13
15
  ## How it works
@@ -59,20 +61,28 @@ Open any project on Machine B — Claude Code shows your full session history.
59
61
  | `cortex pull` | Download, decrypt, remap paths |
60
62
  | `cortex status` | Show what's out of sync (no download) |
61
63
  | `cortex convert <file> --to <target>` | Convert a Claude Code skill |
64
+ | `cortex setup-mcp` | Register cortex as a Claude Code MCP server |
62
65
 
63
66
  ---
64
67
 
65
68
  ## Claude Code MCP integration
66
69
 
67
- Install cortex as a Claude Code MCP server to use `sync`, `pull`, `status`, `convert`, and `init` directly from the chat:
70
+ Use `sync`, `pull`, `status`, `convert`, and `init` directly from the Claude Code chat — one command does everything:
68
71
 
69
72
  ```bash
70
73
  npm install -g cortex-sync
71
- cortex init # configure once from terminal
72
- export CORTEX_PASSPHRASE="..." # add to ~/.zshrc
73
- claude mcp add cortex -- cortex mcp # register in Claude Code
74
+ cortex init # configure storage and passphrase
75
+ cortex setup-mcp # registers cortex in Claude Code automatically
74
76
  ```
75
77
 
78
+ That's it. Restart Claude Code and you're done.
79
+
80
+ ### What setup-mcp does
81
+
82
+ `cortex setup-mcp` detects the binary path, prompts for your passphrase once, and runs `claude mcp add` with the correct arguments — no manual PATH configuration needed.
83
+
84
+ ### Available MCP tools
85
+
76
86
  | Tool | What it does |
77
87
  |---|---|
78
88
  | `sync` | Encrypt and upload `~/.claude/` |
@@ -81,7 +91,7 @@ claude mcp add cortex -- cortex mcp # register in Claude Code
81
91
  | `convert` | Convert a skill to Antigravity or Cursor |
82
92
  | `init` | Configure storage (non-interactive) |
83
93
 
84
- **Environment variables used by the MCP server:**
94
+ ### Environment variables (advanced / manual setup)
85
95
 
86
96
  | Variable | Required for |
87
97
  |---|---|
package/dist/cli.js CHANGED
@@ -788,9 +788,9 @@ function remapLine(parsed, oldPath, newPath) {
788
788
  const content = msg["content"];
789
789
  if (Array.isArray(content)) {
790
790
  for (const item of content) {
791
- const input3 = item["input"];
792
- if (input3 !== null && typeof input3 === "object") {
793
- const inp = input3;
791
+ const input4 = item["input"];
792
+ if (input4 !== null && typeof input4 === "object") {
793
+ const inp = input4;
794
794
  if ("file_path" in inp) {
795
795
  inp["file_path"] = remapPath(inp["file_path"], oldPath, newPath);
796
796
  }
@@ -799,22 +799,22 @@ function remapLine(parsed, oldPath, newPath) {
799
799
  }
800
800
  }
801
801
  }
802
- function remapJsonlBuffer(input3, oldPath, newPath) {
803
- if (oldPath === newPath) return input3;
802
+ function remapJsonlBuffer(input4, oldPath, newPath) {
803
+ if (oldPath === newPath) return input4;
804
804
  const parts = [];
805
805
  let lineStart = 0;
806
- for (let i = 0; i <= input3.length; i++) {
807
- const atEnd = i === input3.length;
808
- const byte = atEnd ? void 0 : input3[i];
806
+ for (let i = 0; i <= input4.length; i++) {
807
+ const atEnd = i === input4.length;
808
+ const byte = atEnd ? void 0 : input4[i];
809
809
  const isLF = byte === 10;
810
810
  if (!isLF && !atEnd) continue;
811
811
  const lineEnd = i;
812
812
  let contentEnd = lineEnd;
813
- if (contentEnd > lineStart && input3[contentEnd - 1] === 13) {
813
+ if (contentEnd > lineStart && input4[contentEnd - 1] === 13) {
814
814
  contentEnd--;
815
815
  }
816
- const lineBytes = input3.slice(lineStart, contentEnd);
817
- const eolBytes = input3.slice(contentEnd, isLF ? lineEnd + 1 : lineEnd);
816
+ const lineBytes = input4.slice(lineStart, contentEnd);
817
+ const eolBytes = input4.slice(contentEnd, isLF ? lineEnd + 1 : lineEnd);
818
818
  const lineStr = lineBytes.toString("utf-8").trim();
819
819
  if (lineStr.length > 0) {
820
820
  try {
@@ -832,8 +832,8 @@ function remapJsonlBuffer(input3, oldPath, newPath) {
832
832
  }
833
833
  return Buffer.concat(parts);
834
834
  }
835
- function extractCwdFromJsonl(input3) {
836
- const text = input3.toString("utf-8");
835
+ function extractCwdFromJsonl(input4) {
836
+ const text = input4.toString("utf-8");
837
837
  for (const line of text.split("\n")) {
838
838
  const trimmed = line.trim();
839
839
  if (!trimmed) continue;
@@ -1302,6 +1302,402 @@ ${lines.join("\n")}`
1302
1302
  });
1303
1303
  }
1304
1304
 
1305
+ // src/commands/setup-mcp.ts
1306
+ import { password as password4 } from "@inquirer/prompts";
1307
+ import { existsSync as existsSync7 } from "fs";
1308
+ import { spawnSync } from "child_process";
1309
+ import { dirname as dirname6, join as join11 } from "path";
1310
+ async function setupMcpCommand() {
1311
+ const cortexBin = join11(dirname6(process.execPath), "cortex");
1312
+ if (!existsSync7(cortexBin)) {
1313
+ throw new Error(
1314
+ `cortex binary not found at ${cortexBin}.
1315
+ Make sure cortex-sync is installed globally: npm install -g cortex-sync`
1316
+ );
1317
+ }
1318
+ console.log(`Found cortex at: ${cortexBin}`);
1319
+ const passphrase = await password4({
1320
+ message: "Encryption passphrase (same one used in cortex init):",
1321
+ mask: "*",
1322
+ validate: (v) => v.length >= 12 || "Minimum 12 characters"
1323
+ });
1324
+ const result = spawnSync(
1325
+ "claude",
1326
+ ["mcp", "add", "cortex", "-e", `CORTEX_PASSPHRASE=${passphrase}`, "--", cortexBin, "mcp"],
1327
+ { stdio: "inherit" }
1328
+ );
1329
+ if (result.error) {
1330
+ throw new Error(
1331
+ `Failed to run "claude" CLI: ${result.error.message}
1332
+ Make sure Claude Code CLI is installed and "claude" is in your PATH.`
1333
+ );
1334
+ }
1335
+ if (result.status !== 0) {
1336
+ throw new Error('"claude mcp add" failed. Check the output above for details.');
1337
+ }
1338
+ console.log("\n\u2713 cortex MCP server registered in Claude Code.");
1339
+ console.log("Restart Claude Code (or open a new session) to activate it.");
1340
+ console.log("\nTo verify: claude mcp list");
1341
+ }
1342
+
1343
+ // src/commands/team/init.ts
1344
+ import { input as input3 } from "@inquirer/prompts";
1345
+ import { readFile as readFile12, writeFile as writeFile10, mkdir as mkdir10 } from "fs/promises";
1346
+ import { join as join15 } from "path";
1347
+
1348
+ // src/lib/team-repo.ts
1349
+ import { access as access3, mkdir as mkdir8, rm } from "fs/promises";
1350
+ import { join as join12 } from "path";
1351
+ import { spawnSync as spawnSync2 } from "child_process";
1352
+ var TEAM_DIR = join12(CORTEX_DIR, "team");
1353
+ function authUrl(repoUrl, token) {
1354
+ return repoUrl.replace("https://", `https://${token}@`);
1355
+ }
1356
+ async function hasLocalClone(dir = TEAM_DIR) {
1357
+ try {
1358
+ await access3(join12(dir, ".git"));
1359
+ return true;
1360
+ } catch {
1361
+ return false;
1362
+ }
1363
+ }
1364
+ async function cloneTeamRepo(repoUrl, token) {
1365
+ await rm(TEAM_DIR, { recursive: true, force: true });
1366
+ await mkdir8(CORTEX_DIR, { recursive: true });
1367
+ const result = spawnSync2(
1368
+ "git",
1369
+ ["clone", authUrl(repoUrl, token), TEAM_DIR],
1370
+ { stdio: "pipe", env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } }
1371
+ );
1372
+ if (result.status !== 0) {
1373
+ throw new Error(`git clone failed: ${result.stderr?.toString().trim()}`);
1374
+ }
1375
+ }
1376
+ function configureGitUser(dir = TEAM_DIR) {
1377
+ spawnSync2("git", ["-C", dir, "config", "user.email", "cortex@local"], { stdio: "pipe" });
1378
+ spawnSync2("git", ["-C", dir, "config", "user.name", "cortex"], { stdio: "pipe" });
1379
+ }
1380
+ function pullTeamRepo(dir = TEAM_DIR) {
1381
+ const result = spawnSync2("git", ["-C", dir, "pull", "--ff-only"], {
1382
+ stdio: "pipe",
1383
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
1384
+ });
1385
+ if (result.status !== 0) {
1386
+ throw new Error(`git pull failed: ${result.stderr?.toString().trim()}`);
1387
+ }
1388
+ }
1389
+ function hasPendingChanges(dir = TEAM_DIR) {
1390
+ const result = spawnSync2("git", ["-C", dir, "status", "--porcelain"], { stdio: "pipe" });
1391
+ return (result.stdout?.toString().trim() ?? "") !== "";
1392
+ }
1393
+ function commitAndPush(repoUrl, token, message, dir = TEAM_DIR) {
1394
+ configureGitUser(dir);
1395
+ spawnSync2("git", ["-C", dir, "add", "-A"], { stdio: "pipe" });
1396
+ if (!hasPendingChanges(dir)) {
1397
+ console.log("Nothing to push \u2014 team repo is already up to date.");
1398
+ return;
1399
+ }
1400
+ const commit = spawnSync2("git", ["-C", dir, "commit", "-m", message], { stdio: "pipe" });
1401
+ if (commit.status !== 0) throw new Error("git commit failed");
1402
+ const push = spawnSync2("git", ["-C", dir, "push", authUrl(repoUrl, token)], {
1403
+ stdio: "pipe",
1404
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
1405
+ });
1406
+ if (push.status !== 0) {
1407
+ throw new Error(`git push failed: ${push.stderr?.toString().trim()}`);
1408
+ }
1409
+ }
1410
+
1411
+ // src/lib/claude-skills.ts
1412
+ import { readdir as readdir3, readFile as readFile10, mkdir as mkdir9, writeFile as writeFile9 } from "fs/promises";
1413
+ import { homedir as homedir4 } from "os";
1414
+ import { join as join13, dirname as dirname7 } from "path";
1415
+ var LOCAL_SKILLS_DIR = join13(homedir4(), ".claude", "skills");
1416
+ var LOCAL_CLAUDE_MD = join13(homedir4(), ".claude", "CLAUDE.md");
1417
+ async function readSkillsFromDir(dir) {
1418
+ const map = /* @__PURE__ */ new Map();
1419
+ let entries;
1420
+ try {
1421
+ entries = await readdir3(dir, { withFileTypes: true });
1422
+ } catch {
1423
+ return map;
1424
+ }
1425
+ for (const e of entries) {
1426
+ if (e.isFile() && e.name.endsWith(".md")) {
1427
+ const content = await readFile10(join13(dir, e.name), "utf-8");
1428
+ map.set(e.name, content);
1429
+ }
1430
+ }
1431
+ return map;
1432
+ }
1433
+ async function writeSkillToDir(dir, filename, content) {
1434
+ await mkdir9(dir, { recursive: true });
1435
+ await writeFile9(join13(dir, filename), content, "utf-8");
1436
+ }
1437
+ async function readFileFromPath(filePath) {
1438
+ try {
1439
+ return await readFile10(filePath, "utf-8");
1440
+ } catch {
1441
+ return null;
1442
+ }
1443
+ }
1444
+ async function writeFileToPath(filePath, content) {
1445
+ await mkdir9(dirname7(filePath), { recursive: true });
1446
+ await writeFile9(filePath, content, "utf-8");
1447
+ }
1448
+
1449
+ // src/lib/claude-plugins.ts
1450
+ import { readFile as readFile11 } from "fs/promises";
1451
+ import { homedir as homedir5 } from "os";
1452
+ import { join as join14 } from "path";
1453
+ import { spawnSync as spawnSync3 } from "child_process";
1454
+ var INSTALLED_PLUGINS_PATH = join14(homedir5(), ".claude", "plugins", "installed_plugins.json");
1455
+ function parseInstalledPlugins(data) {
1456
+ return Object.keys(data.plugins);
1457
+ }
1458
+ async function getInstalledPluginIds() {
1459
+ try {
1460
+ const buf = await readFile11(INSTALLED_PLUGINS_PATH, "utf-8");
1461
+ return parseInstalledPlugins(JSON.parse(buf));
1462
+ } catch {
1463
+ return [];
1464
+ }
1465
+ }
1466
+ function installPlugin(pluginId) {
1467
+ console.log(`Installing plugin: ${pluginId}`);
1468
+ const result = spawnSync3("claude", ["plugin", "install", pluginId], { stdio: "inherit" });
1469
+ if (result.error) throw new Error(`Failed to run claude: ${result.error.message}`);
1470
+ if (result.status !== 0) throw new Error(`Plugin install failed for ${pluginId}`);
1471
+ }
1472
+
1473
+ // src/commands/team/init.ts
1474
+ async function teamInitCommand(opts) {
1475
+ const config = await loadConfig();
1476
+ const repoUrl = opts.repo ?? await input3({
1477
+ message: "Team config repo URL (https://github.com/user/claude-config):",
1478
+ validate: (v) => v.startsWith("https://") || "Must be an https URL"
1479
+ });
1480
+ const token = config.githubToken;
1481
+ if (!token) throw new Error('No GitHub token found. Run "cortex init" first with GitHub storage.');
1482
+ console.log(`
1483
+ Cloning ${repoUrl} \u2192 ~/.cortex/team/`);
1484
+ await cloneTeamRepo(repoUrl, token);
1485
+ const skills = await readSkillsFromDir(LOCAL_SKILLS_DIR);
1486
+ if (skills.size > 0) {
1487
+ await mkdir10(join15(TEAM_DIR, "skills"), { recursive: true });
1488
+ for (const [filename, content] of skills) {
1489
+ await writeFile10(join15(TEAM_DIR, "skills", filename), content, "utf-8");
1490
+ }
1491
+ console.log(` Copied ${skills.size} skills`);
1492
+ }
1493
+ const claudeMd = await readFileFromPath(LOCAL_CLAUDE_MD);
1494
+ if (claudeMd) {
1495
+ await writeFile10(join15(TEAM_DIR, "CLAUDE.md"), claudeMd, "utf-8");
1496
+ console.log(" Copied CLAUDE.md");
1497
+ }
1498
+ const plugins = await getInstalledPluginIds();
1499
+ const cortexJson = { version: "1", plugins };
1500
+ await writeFile10(join15(TEAM_DIR, "cortex.json"), JSON.stringify(cortexJson, null, 2), "utf-8");
1501
+ console.log(` Generated cortex.json (${plugins.length} plugins)`);
1502
+ commitAndPush(repoUrl, token, "feat: initial team Claude Code context");
1503
+ const raw = JSON.parse(await readFile12(CONFIG_PATH, "utf-8"));
1504
+ raw.teamRepo = repoUrl;
1505
+ await writeFile10(CONFIG_PATH, JSON.stringify(raw, null, 2), "utf-8");
1506
+ console.log("\n\u2713 Team repo initialized and pushed.");
1507
+ console.log(` Devs can now run: cortex install --repo ${repoUrl}`);
1508
+ }
1509
+
1510
+ // src/commands/team/push.ts
1511
+ import { writeFile as writeFile11, mkdir as mkdir11 } from "fs/promises";
1512
+ import { join as join16 } from "path";
1513
+ async function teamPushCommand() {
1514
+ const config = await loadConfig();
1515
+ const repoUrl = config.teamRepo;
1516
+ const token = config.githubToken;
1517
+ if (!repoUrl) throw new Error('No team repo configured. Run "cortex team init --repo <url>" first.');
1518
+ if (!token) throw new Error('No GitHub token found. Run "cortex init" first.');
1519
+ if (!await hasLocalClone()) throw new Error('No local team clone found. Run "cortex team init" first.');
1520
+ console.log("Pulling latest from team repo\u2026");
1521
+ pullTeamRepo();
1522
+ const skills = await readSkillsFromDir(LOCAL_SKILLS_DIR);
1523
+ if (skills.size > 0) {
1524
+ await mkdir11(join16(TEAM_DIR, "skills"), { recursive: true });
1525
+ for (const [filename, content] of skills) {
1526
+ await writeFile11(join16(TEAM_DIR, "skills", filename), content, "utf-8");
1527
+ }
1528
+ }
1529
+ const claudeMd = await readFileFromPath(LOCAL_CLAUDE_MD);
1530
+ if (claudeMd) {
1531
+ await writeFile11(join16(TEAM_DIR, "CLAUDE.md"), claudeMd, "utf-8");
1532
+ }
1533
+ const plugins = await getInstalledPluginIds();
1534
+ await writeFile11(
1535
+ join16(TEAM_DIR, "cortex.json"),
1536
+ JSON.stringify({ version: "1", plugins }, null, 2),
1537
+ "utf-8"
1538
+ );
1539
+ commitAndPush(repoUrl, token, "chore: update team Claude Code context");
1540
+ console.log("\n\u2713 Pushed to team repo.");
1541
+ }
1542
+
1543
+ // src/commands/team/pull.ts
1544
+ import { readFile as readFile13 } from "fs/promises";
1545
+ import { join as join17 } from "path";
1546
+
1547
+ // src/lib/conflict.ts
1548
+ import { select as select2 } from "@inquirer/prompts";
1549
+ function hasConflict(local, remote) {
1550
+ return local.trim() !== remote.trim();
1551
+ }
1552
+ function buildDiffLines(local, remote) {
1553
+ const localLines = local.split("\n");
1554
+ const remoteLines = remote.split("\n");
1555
+ const localSet = new Set(localLines);
1556
+ const remoteSet = new Set(remoteLines);
1557
+ const all = [.../* @__PURE__ */ new Set([...localLines, ...remoteLines])];
1558
+ return all.map((line) => ({
1559
+ type: localSet.has(line) && remoteSet.has(line) ? "same" : localSet.has(line) ? "local" : "remote",
1560
+ line
1561
+ }));
1562
+ }
1563
+ function mergeContent(local, remote) {
1564
+ const localSet = new Set(local.split("\n"));
1565
+ const additions = remote.split("\n").filter((l) => !localSet.has(l) && l.trim() !== "");
1566
+ if (additions.length === 0) return local;
1567
+ return local.trimEnd() + "\n\n" + additions.join("\n");
1568
+ }
1569
+ function displayDiff(filename, local, remote) {
1570
+ const diff = buildDiffLines(local, remote);
1571
+ console.log(`
1572
+ \u26A0 Conflict: ${filename}`);
1573
+ console.log("\u2500".repeat(50));
1574
+ for (const d of diff) {
1575
+ if (d.type === "local") console.log(` - ${d.line}`);
1576
+ else if (d.type === "remote") console.log(` + ${d.line}`);
1577
+ }
1578
+ console.log("\u2500".repeat(50));
1579
+ }
1580
+ async function promptConflict(filename, local, remote) {
1581
+ displayDiff(filename, local, remote);
1582
+ return select2({
1583
+ message: "How to resolve?",
1584
+ choices: [
1585
+ { name: "[M] Merge \u2014 keep both versions", value: "merge" },
1586
+ { name: "[O] Overwrite \u2014 use team version", value: "overwrite" },
1587
+ { name: "[S] Skip \u2014 keep local version", value: "skip" }
1588
+ ]
1589
+ });
1590
+ }
1591
+
1592
+ // src/commands/team/pull.ts
1593
+ async function teamPullCommand() {
1594
+ const config = await loadConfig();
1595
+ const repoUrl = config.teamRepo;
1596
+ if (!repoUrl) throw new Error('No team repo configured. Run "cortex team init --repo <url>" first.');
1597
+ if (!await hasLocalClone()) throw new Error('No local team clone found. Run "cortex install" first.');
1598
+ console.log("Pulling from team repo\u2026");
1599
+ pullTeamRepo();
1600
+ const remoteSkills = await readSkillsFromDir(join17(TEAM_DIR, "skills"));
1601
+ const localSkills = await readSkillsFromDir(LOCAL_SKILLS_DIR);
1602
+ for (const [filename, remoteContent] of remoteSkills) {
1603
+ const localContent = localSkills.get(filename) ?? null;
1604
+ if (!localContent) {
1605
+ await writeSkillToDir(LOCAL_SKILLS_DIR, filename, remoteContent);
1606
+ console.log(` + ${filename} (new)`);
1607
+ continue;
1608
+ }
1609
+ if (!hasConflict(localContent, remoteContent)) continue;
1610
+ const resolution = await promptConflict(filename, localContent, remoteContent);
1611
+ if (resolution === "overwrite") {
1612
+ await writeSkillToDir(LOCAL_SKILLS_DIR, filename, remoteContent);
1613
+ console.log(` \u2713 ${filename} overwritten`);
1614
+ } else if (resolution === "merge") {
1615
+ await writeSkillToDir(LOCAL_SKILLS_DIR, filename, mergeContent(localContent, remoteContent));
1616
+ console.log(` \u2713 ${filename} merged`);
1617
+ } else {
1618
+ console.log(` ~ ${filename} skipped`);
1619
+ }
1620
+ }
1621
+ const remoteMd = await readFileFromPath(join17(TEAM_DIR, "CLAUDE.md"));
1622
+ if (remoteMd) {
1623
+ const localMd = await readFileFromPath(LOCAL_CLAUDE_MD);
1624
+ if (!localMd) {
1625
+ await writeFileToPath(LOCAL_CLAUDE_MD, remoteMd);
1626
+ console.log(" + CLAUDE.md (new)");
1627
+ } else if (hasConflict(localMd, remoteMd)) {
1628
+ const resolution = await promptConflict("CLAUDE.md", localMd, remoteMd);
1629
+ if (resolution === "overwrite") {
1630
+ await writeFileToPath(LOCAL_CLAUDE_MD, remoteMd);
1631
+ console.log(" \u2713 CLAUDE.md overwritten");
1632
+ } else if (resolution === "merge") {
1633
+ await writeFileToPath(LOCAL_CLAUDE_MD, mergeContent(localMd, remoteMd));
1634
+ console.log(" \u2713 CLAUDE.md merged");
1635
+ } else {
1636
+ console.log(" ~ CLAUDE.md skipped");
1637
+ }
1638
+ }
1639
+ }
1640
+ let cortexJson = {};
1641
+ try {
1642
+ cortexJson = JSON.parse(await readFile13(join17(TEAM_DIR, "cortex.json"), "utf-8"));
1643
+ } catch {
1644
+ }
1645
+ for (const pluginId of cortexJson.plugins ?? []) {
1646
+ try {
1647
+ installPlugin(pluginId);
1648
+ } catch (e) {
1649
+ console.warn(` \u26A0 Could not install ${pluginId}: ${e.message}`);
1650
+ }
1651
+ }
1652
+ console.log("\n\u2713 Team context applied.");
1653
+ }
1654
+
1655
+ // src/commands/install.ts
1656
+ import { readFile as readFile14, writeFile as writeFile13 } from "fs/promises";
1657
+ import { join as join18 } from "path";
1658
+ async function installCommand(opts) {
1659
+ let config;
1660
+ try {
1661
+ config = await loadConfig();
1662
+ } catch {
1663
+ throw new Error('Run "cortex init" first to configure your GitHub token.');
1664
+ }
1665
+ const repoUrl = opts.repo ?? config.teamRepo;
1666
+ if (!repoUrl) {
1667
+ throw new Error('No team repo URL. Pass --repo <url> or run "cortex team init" first.');
1668
+ }
1669
+ const token = config.githubToken;
1670
+ if (!token) throw new Error('No GitHub token found. Run "cortex init" first.');
1671
+ console.log(`Installing from ${repoUrl}\u2026`);
1672
+ await cloneTeamRepo(repoUrl, token);
1673
+ const skills = await readSkillsFromDir(join18(TEAM_DIR, "skills"));
1674
+ for (const [filename, content] of skills) {
1675
+ await writeSkillToDir(LOCAL_SKILLS_DIR, filename, content);
1676
+ console.log(` + ${filename}`);
1677
+ }
1678
+ const claudeMd = await readFileFromPath(join18(TEAM_DIR, "CLAUDE.md"));
1679
+ if (claudeMd) {
1680
+ await writeFileToPath(LOCAL_CLAUDE_MD, claudeMd);
1681
+ console.log(" + CLAUDE.md");
1682
+ }
1683
+ let cortexJson = {};
1684
+ try {
1685
+ cortexJson = JSON.parse(await readFile14(join18(TEAM_DIR, "cortex.json"), "utf-8"));
1686
+ } catch {
1687
+ }
1688
+ for (const pluginId of cortexJson.plugins ?? []) {
1689
+ try {
1690
+ installPlugin(pluginId);
1691
+ } catch (e) {
1692
+ console.warn(` \u26A0 Could not install ${pluginId}: ${e.message}`);
1693
+ }
1694
+ }
1695
+ const raw = JSON.parse(await readFile14(CONFIG_PATH, "utf-8"));
1696
+ raw.teamRepo = repoUrl;
1697
+ await writeFile13(CONFIG_PATH, JSON.stringify(raw, null, 2), "utf-8");
1698
+ console.log("\n\u2713 Team context installed. Restart Claude Code to activate new skills and plugins.");
1699
+ }
1700
+
1305
1701
  // src/cli.ts
1306
1702
  var require2 = createRequire(import.meta.url);
1307
1703
  var { version } = require2("../package.json");
@@ -1320,4 +1716,10 @@ program.command("convert <skill-file>").description("Convert a Claude Code skill
1320
1716
  return convertCommand(skillFile, { to: opts.to, outputDir: opts.outputDir });
1321
1717
  });
1322
1718
  program.command("mcp").description("Start the MCP server (for use with claude mcp add cortex -- cortex mcp)").action(mcpCommand);
1719
+ program.command("setup-mcp").description("Register cortex as a Claude Code MCP server automatically").action(setupMcpCommand);
1720
+ var team = program.command("team").description("Share Claude Code context with your team");
1721
+ team.command("init").description("Publish your Claude setup to a shared GitHub repo").option("--repo <url>", "Team config repo URL (https://github.com/user/claude-config)").action((opts) => void teamInitCommand(opts));
1722
+ team.command("push").description("Push local skill/CLAUDE.md/plugin changes to the team repo").action(() => void teamPushCommand());
1723
+ team.command("pull").description("Pull team context updates and install with conflict resolution").action(() => void teamPullCommand());
1724
+ program.command("install").description("First-time install of team Claude context from shared repo").option("--repo <url>", "Team config repo URL (overrides stored config)").action((opts) => void installCommand(opts));
1323
1725
  await program.parseAsync(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cortex-sync",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Sync Claude Code sessions between machines with automatic path remapping and skill conversion",
5
5
  "license": "AGPL-3.0",
6
6
  "type": "module",