cortex-sync 0.4.1 → 0.4.3
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/README.md +43 -0
- package/dist/cli.js +193 -31
- package/package.json +4 -2
- package/scripts/postinstall.cjs +29 -0
package/README.md
CHANGED
|
@@ -65,6 +65,49 @@ Open any project on Machine B — Claude Code shows your full session history.
|
|
|
65
65
|
|
|
66
66
|
---
|
|
67
67
|
|
|
68
|
+
## Team context
|
|
69
|
+
|
|
70
|
+
Share skills, CLAUDE.md, plugins, and chat sessions with your team via a shared GitHub repo.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Tech Lead — one-time setup
|
|
74
|
+
cortex team init --repo https://github.com/your-org/claude-config
|
|
75
|
+
|
|
76
|
+
# Push your local .claude/ context + sessions (if opted in)
|
|
77
|
+
cortex team push
|
|
78
|
+
|
|
79
|
+
# Dev — pull team context + sessions
|
|
80
|
+
cortex team pull
|
|
81
|
+
|
|
82
|
+
# New dev — first-time install
|
|
83
|
+
cortex install --repo https://github.com/your-org/claude-config
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### What gets shared
|
|
87
|
+
|
|
88
|
+
| What | Source | Destination |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| Skills | `.claude/skills/*.md` | `skills/` in team repo |
|
|
91
|
+
| CLAUDE.md | `.claude/CLAUDE.md` | `CLAUDE.md` in team repo |
|
|
92
|
+
| Plugins | Installed Claude plugins | `cortex.json → plugins[]` |
|
|
93
|
+
| Sessions (opt-in) | `~/.claude/projects/<project>/` | `sessions/<email>/<project-id>/` |
|
|
94
|
+
|
|
95
|
+
### Session sharing
|
|
96
|
+
|
|
97
|
+
During `cortex team init` you are asked once whether to share your Claude Code chat sessions with the team. If you accept:
|
|
98
|
+
|
|
99
|
+
- Sessions are uploaded on every `cortex team push`
|
|
100
|
+
- Teammates get your sessions (paths remapped to their machine) on `cortex team pull`
|
|
101
|
+
- Claude Code shows all team sessions natively — no extra steps
|
|
102
|
+
|
|
103
|
+
You can choose to encrypt sessions with a shared team passphrase (AES-256-GCM). Share the passphrase with your team via a password manager — it is never stored by cortex.
|
|
104
|
+
|
|
105
|
+
**Two machines, same GitHub user:** Each machine generates unique session IDs, so pushing from both machines never creates duplicates.
|
|
106
|
+
|
|
107
|
+
> **Privacy:** Sessions may contain source code, API calls, and sensitive context. Only opt in if your team has a shared understanding that sessions are visible to all members.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
68
111
|
## Claude Code MCP integration
|
|
69
112
|
|
|
70
113
|
Use `sync`, `pull`, `status`, `convert`, and `init` directly from the Claude Code chat — one command does everything:
|
package/dist/cli.js
CHANGED
|
@@ -1341,9 +1341,9 @@ Make sure Claude Code CLI is installed and "claude" is in your PATH.`
|
|
|
1341
1341
|
}
|
|
1342
1342
|
|
|
1343
1343
|
// src/commands/team/init.ts
|
|
1344
|
-
import { input as input3 } from "@inquirer/prompts";
|
|
1345
|
-
import { writeFile as
|
|
1346
|
-
import { join as
|
|
1344
|
+
import { input as input3, confirm as confirm3, select as select2, password as password5 } from "@inquirer/prompts";
|
|
1345
|
+
import { writeFile as writeFile12, mkdir as mkdir11 } from "fs/promises";
|
|
1346
|
+
import { join as join17 } from "path";
|
|
1347
1347
|
|
|
1348
1348
|
// src/lib/team-repo.ts
|
|
1349
1349
|
import { access as access3, mkdir as mkdir8, rm } from "fs/promises";
|
|
@@ -1486,6 +1486,107 @@ async function writeProjectConfig(updates, cwd = process.cwd()) {
|
|
|
1486
1486
|
await writeFile10(join15(cwd, "cortex.json"), JSON.stringify(merged, null, 2), "utf-8");
|
|
1487
1487
|
}
|
|
1488
1488
|
|
|
1489
|
+
// src/lib/team-sessions.ts
|
|
1490
|
+
import { readdir as readdir4, readFile as readFile13, writeFile as writeFile11, mkdir as mkdir10 } from "fs/promises";
|
|
1491
|
+
import { homedir as homedir5 } from "os";
|
|
1492
|
+
import { join as join16 } from "path";
|
|
1493
|
+
function localSessionsDir(cwd) {
|
|
1494
|
+
return join16(homedir5(), ".claude", "projects", encodeProjectPath(cwd));
|
|
1495
|
+
}
|
|
1496
|
+
function teamSessionsDir(base, email, projectId) {
|
|
1497
|
+
return join16(base, "sessions", email, projectId);
|
|
1498
|
+
}
|
|
1499
|
+
async function readSessionFiles(dir) {
|
|
1500
|
+
const result = /* @__PURE__ */ new Map();
|
|
1501
|
+
try {
|
|
1502
|
+
const entries = await readdir4(dir);
|
|
1503
|
+
for (const entry of entries) {
|
|
1504
|
+
if (entry.endsWith(".jsonl")) {
|
|
1505
|
+
result.set(entry, await readFile13(join16(dir, entry)));
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
} catch {
|
|
1509
|
+
}
|
|
1510
|
+
return result;
|
|
1511
|
+
}
|
|
1512
|
+
async function copySessionsToTeamDir(srcDir, destDir, derived) {
|
|
1513
|
+
const sessions = await readSessionFiles(srcDir);
|
|
1514
|
+
if (sessions.size === 0) return 0;
|
|
1515
|
+
await mkdir10(destDir, { recursive: true });
|
|
1516
|
+
for (const [filename, content] of sessions) {
|
|
1517
|
+
if (derived) {
|
|
1518
|
+
await writeFile11(join16(destDir, `${filename}.enc`), encrypt(content, derived));
|
|
1519
|
+
} else {
|
|
1520
|
+
await writeFile11(join16(destDir, filename), content);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
return sessions.size;
|
|
1524
|
+
}
|
|
1525
|
+
async function copySessionsFromRepo(sessionsRoot, projectId, destDir, localCwd, derived) {
|
|
1526
|
+
let devFolders;
|
|
1527
|
+
try {
|
|
1528
|
+
devFolders = await readdir4(sessionsRoot);
|
|
1529
|
+
} catch {
|
|
1530
|
+
return 0;
|
|
1531
|
+
}
|
|
1532
|
+
await mkdir10(destDir, { recursive: true });
|
|
1533
|
+
let count = 0;
|
|
1534
|
+
for (const email of devFolders) {
|
|
1535
|
+
const projDir = join16(sessionsRoot, email, projectId);
|
|
1536
|
+
let files;
|
|
1537
|
+
try {
|
|
1538
|
+
files = await readdir4(projDir);
|
|
1539
|
+
} catch {
|
|
1540
|
+
continue;
|
|
1541
|
+
}
|
|
1542
|
+
for (const file of files) {
|
|
1543
|
+
try {
|
|
1544
|
+
const isEnc = file.endsWith(".jsonl.enc");
|
|
1545
|
+
const isPlain = file.endsWith(".jsonl") && !isEnc;
|
|
1546
|
+
if (!isEnc && !isPlain) continue;
|
|
1547
|
+
if (isEnc && !derived) continue;
|
|
1548
|
+
let data = await readFile13(join16(projDir, file));
|
|
1549
|
+
if (isEnc && derived) {
|
|
1550
|
+
data = decrypt(data, derived);
|
|
1551
|
+
}
|
|
1552
|
+
const remoteCwd = extractCwdFromJsonl(data);
|
|
1553
|
+
if (remoteCwd && remoteCwd !== localCwd) {
|
|
1554
|
+
data = remapJsonlBuffer(data, remoteCwd, localCwd);
|
|
1555
|
+
}
|
|
1556
|
+
const destFilename = isEnc ? file.slice(0, -".enc".length) : file;
|
|
1557
|
+
const destPath = join16(destDir, destFilename);
|
|
1558
|
+
let finalPath = destPath;
|
|
1559
|
+
try {
|
|
1560
|
+
await readFile13(destPath);
|
|
1561
|
+
const initials = email.split("@")[0].slice(0, 3);
|
|
1562
|
+
const base = destFilename.slice(0, -".jsonl".length);
|
|
1563
|
+
finalPath = join16(destDir, `${base}.${initials}.jsonl`);
|
|
1564
|
+
} catch {
|
|
1565
|
+
}
|
|
1566
|
+
await writeFile11(finalPath, data);
|
|
1567
|
+
count++;
|
|
1568
|
+
} catch {
|
|
1569
|
+
continue;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
return count;
|
|
1574
|
+
}
|
|
1575
|
+
async function pushSessions(email, cwd, derived) {
|
|
1576
|
+
const projectInfo = identifyProject(cwd);
|
|
1577
|
+
if (!projectInfo) return 0;
|
|
1578
|
+
const srcDir = localSessionsDir(cwd);
|
|
1579
|
+
const destDir = teamSessionsDir(TEAM_DIR, email, projectInfo.projectId);
|
|
1580
|
+
return copySessionsToTeamDir(srcDir, destDir, derived);
|
|
1581
|
+
}
|
|
1582
|
+
async function pullSessions(cwd, derived) {
|
|
1583
|
+
const projectInfo = identifyProject(cwd);
|
|
1584
|
+
if (!projectInfo) return 0;
|
|
1585
|
+
const sessionsRoot = join16(TEAM_DIR, "sessions");
|
|
1586
|
+
const destDir = localSessionsDir(cwd);
|
|
1587
|
+
return copySessionsFromRepo(sessionsRoot, projectInfo.projectId, destDir, cwd, derived);
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1489
1590
|
// src/commands/team/init.ts
|
|
1490
1591
|
async function teamInitCommand(opts) {
|
|
1491
1592
|
const config = await loadConfig();
|
|
@@ -1500,33 +1601,67 @@ Cloning ${repoUrl} \u2192 ~/.cortex/team/`);
|
|
|
1500
1601
|
await cloneTeamRepo(repoUrl, token);
|
|
1501
1602
|
const skills = await readSkillsFromDir(LOCAL_SKILLS_DIR);
|
|
1502
1603
|
if (skills.size > 0) {
|
|
1503
|
-
await
|
|
1604
|
+
await mkdir11(join17(TEAM_DIR, "skills"), { recursive: true });
|
|
1504
1605
|
for (const [filename, content] of skills) {
|
|
1505
|
-
await
|
|
1606
|
+
await writeFile12(join17(TEAM_DIR, "skills", filename), content, "utf-8");
|
|
1506
1607
|
}
|
|
1507
1608
|
console.log(` Copied ${skills.size} skills from .claude/skills/`);
|
|
1508
1609
|
}
|
|
1509
1610
|
const claudeMd = await readFileFromPath(LOCAL_CLAUDE_MD);
|
|
1510
1611
|
if (claudeMd) {
|
|
1511
|
-
await
|
|
1612
|
+
await writeFile12(join17(TEAM_DIR, "CLAUDE.md"), claudeMd, "utf-8");
|
|
1512
1613
|
console.log(" Copied .claude/CLAUDE.md");
|
|
1513
1614
|
}
|
|
1514
1615
|
const plugins = await getInstalledPluginIds();
|
|
1515
1616
|
const cortexJson = { version: "1", plugins };
|
|
1516
|
-
await
|
|
1617
|
+
await writeFile12(join17(TEAM_DIR, "cortex.json"), JSON.stringify(cortexJson, null, 2), "utf-8");
|
|
1517
1618
|
console.log(` Generated cortex.json (${plugins.length} plugins)`);
|
|
1518
1619
|
commitAndPush(repoUrl, token, "feat: initial team Claude Code context");
|
|
1519
|
-
|
|
1620
|
+
console.log("\n\u26A0 Compartir sesiones de chat");
|
|
1621
|
+
console.log(" Tus sesiones de Claude Code para este proyecto se subir\xEDan");
|
|
1622
|
+
console.log(" al repo de equipo y ser\xEDan visibles por todos los miembros.");
|
|
1623
|
+
console.log(" Las sesiones pueden contener c\xF3digo privado o informaci\xF3n sensible.");
|
|
1624
|
+
const shareSession = await confirm3({
|
|
1625
|
+
message: "\xBFCompartir sesiones con el equipo?",
|
|
1626
|
+
default: false
|
|
1627
|
+
});
|
|
1628
|
+
let encryptSessions = false;
|
|
1629
|
+
if (shareSession) {
|
|
1630
|
+
const encChoice = await select2({
|
|
1631
|
+
message: "\xBFEncriptar las sesiones?",
|
|
1632
|
+
choices: [
|
|
1633
|
+
{ name: "S\xED \u2014 encriptadas con passphrase del equipo (recomendado)", value: true },
|
|
1634
|
+
{ name: "No \u2014 JSONL plano (legible directo en GitHub)", value: false }
|
|
1635
|
+
]
|
|
1636
|
+
});
|
|
1637
|
+
encryptSessions = encChoice;
|
|
1638
|
+
let derived;
|
|
1639
|
+
if (encryptSessions) {
|
|
1640
|
+
const teamPassphrase = await password5({
|
|
1641
|
+
message: "Team passphrase (shared with all devs, min 12 chars):",
|
|
1642
|
+
mask: "*",
|
|
1643
|
+
validate: (v) => v.length >= 12 || "Minimum 12 characters"
|
|
1644
|
+
});
|
|
1645
|
+
derived = deriveKey(teamPassphrase, repoUrl);
|
|
1646
|
+
}
|
|
1647
|
+
const count = await pushSessions(config.email, process.cwd(), derived);
|
|
1648
|
+
if (count > 0) {
|
|
1649
|
+
commitAndPush(repoUrl, token, `feat: share ${count} team sessions`);
|
|
1650
|
+
console.log(` Uploaded ${count} sessions`);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
await writeProjectConfig({ repo: repoUrl, shareSession, encryptSessions });
|
|
1520
1654
|
console.log("\n\u2713 Team repo initialized and pushed.");
|
|
1521
1655
|
console.log(` Devs can now run: cortex install --repo ${repoUrl}`);
|
|
1522
1656
|
}
|
|
1523
1657
|
|
|
1524
1658
|
// src/commands/team/push.ts
|
|
1525
|
-
import { writeFile as
|
|
1526
|
-
import { join as
|
|
1659
|
+
import { writeFile as writeFile13, mkdir as mkdir12 } from "fs/promises";
|
|
1660
|
+
import { join as join18 } from "path";
|
|
1661
|
+
import { password as password6 } from "@inquirer/prompts";
|
|
1527
1662
|
async function teamPushCommand() {
|
|
1528
1663
|
const config = await loadConfig();
|
|
1529
|
-
const { repo: repoUrl } = await readProjectConfig();
|
|
1664
|
+
const { repo: repoUrl, shareSession, encryptSessions } = await readProjectConfig();
|
|
1530
1665
|
const token = config.githubToken;
|
|
1531
1666
|
if (!repoUrl) throw new Error('No team repo configured. Run "cortex team init --repo <url>" first.');
|
|
1532
1667
|
if (!token) throw new Error('No GitHub token found. Run "cortex init" first.');
|
|
@@ -1535,31 +1670,45 @@ async function teamPushCommand() {
|
|
|
1535
1670
|
pullTeamRepo();
|
|
1536
1671
|
const skills = await readSkillsFromDir(LOCAL_SKILLS_DIR);
|
|
1537
1672
|
if (skills.size > 0) {
|
|
1538
|
-
await
|
|
1673
|
+
await mkdir12(join18(TEAM_DIR, "skills"), { recursive: true });
|
|
1539
1674
|
for (const [filename, content] of skills) {
|
|
1540
|
-
await
|
|
1675
|
+
await writeFile13(join18(TEAM_DIR, "skills", filename), content, "utf-8");
|
|
1541
1676
|
}
|
|
1542
1677
|
}
|
|
1543
1678
|
const claudeMd = await readFileFromPath(LOCAL_CLAUDE_MD);
|
|
1544
1679
|
if (claudeMd) {
|
|
1545
|
-
await
|
|
1680
|
+
await writeFile13(join18(TEAM_DIR, "CLAUDE.md"), claudeMd, "utf-8");
|
|
1546
1681
|
}
|
|
1547
1682
|
const plugins = await getInstalledPluginIds();
|
|
1548
|
-
await
|
|
1549
|
-
|
|
1683
|
+
await writeFile13(
|
|
1684
|
+
join18(TEAM_DIR, "cortex.json"),
|
|
1550
1685
|
JSON.stringify({ version: "1", plugins }, null, 2),
|
|
1551
1686
|
"utf-8"
|
|
1552
1687
|
);
|
|
1688
|
+
if (shareSession) {
|
|
1689
|
+
let derived;
|
|
1690
|
+
if (encryptSessions) {
|
|
1691
|
+
const teamPassphrase = await password6({
|
|
1692
|
+
message: "Team passphrase:",
|
|
1693
|
+
mask: "*",
|
|
1694
|
+
validate: (v) => v.length >= 12 || "Minimum 12 characters"
|
|
1695
|
+
});
|
|
1696
|
+
derived = deriveKey(teamPassphrase, repoUrl);
|
|
1697
|
+
}
|
|
1698
|
+
const count = await pushSessions(config.email, process.cwd(), derived);
|
|
1699
|
+
if (count > 0) console.log(` Uploaded ${count} sessions`);
|
|
1700
|
+
}
|
|
1553
1701
|
commitAndPush(repoUrl, token, "chore: update team Claude Code context");
|
|
1554
1702
|
console.log("\n\u2713 Pushed to team repo.");
|
|
1555
1703
|
}
|
|
1556
1704
|
|
|
1557
1705
|
// src/commands/team/pull.ts
|
|
1558
|
-
import { readFile as
|
|
1559
|
-
import { join as
|
|
1706
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
1707
|
+
import { join as join19 } from "path";
|
|
1708
|
+
import { password as password7 } from "@inquirer/prompts";
|
|
1560
1709
|
|
|
1561
1710
|
// src/lib/conflict.ts
|
|
1562
|
-
import { select as
|
|
1711
|
+
import { select as select3 } from "@inquirer/prompts";
|
|
1563
1712
|
function hasConflict(local, remote) {
|
|
1564
1713
|
return local.trim() !== remote.trim();
|
|
1565
1714
|
}
|
|
@@ -1593,7 +1742,7 @@ function displayDiff(filename, local, remote) {
|
|
|
1593
1742
|
}
|
|
1594
1743
|
async function promptConflict(filename, local, remote) {
|
|
1595
1744
|
displayDiff(filename, local, remote);
|
|
1596
|
-
return
|
|
1745
|
+
return select3({
|
|
1597
1746
|
message: "How to resolve?",
|
|
1598
1747
|
choices: [
|
|
1599
1748
|
{ name: "[M] Merge \u2014 keep both versions", value: "merge" },
|
|
@@ -1606,12 +1755,12 @@ async function promptConflict(filename, local, remote) {
|
|
|
1606
1755
|
// src/commands/team/pull.ts
|
|
1607
1756
|
async function teamPullCommand() {
|
|
1608
1757
|
await loadConfig();
|
|
1609
|
-
const { repo: repoUrl } = await readProjectConfig();
|
|
1758
|
+
const { repo: repoUrl, encryptSessions } = await readProjectConfig();
|
|
1610
1759
|
if (!repoUrl) throw new Error('No team repo configured. Run "cortex team init --repo <url>" first.');
|
|
1611
1760
|
if (!await hasLocalClone()) throw new Error('No local team clone found. Run "cortex install" first.');
|
|
1612
1761
|
console.log("Pulling from team repo\u2026");
|
|
1613
1762
|
pullTeamRepo();
|
|
1614
|
-
const remoteSkills = await readSkillsFromDir(
|
|
1763
|
+
const remoteSkills = await readSkillsFromDir(join19(TEAM_DIR, "skills"));
|
|
1615
1764
|
const localSkills = await readSkillsFromDir(LOCAL_SKILLS_DIR);
|
|
1616
1765
|
for (const [filename, remoteContent] of remoteSkills) {
|
|
1617
1766
|
const localContent = localSkills.get(filename) ?? null;
|
|
@@ -1632,7 +1781,7 @@ async function teamPullCommand() {
|
|
|
1632
1781
|
console.log(` ~ ${filename} skipped`);
|
|
1633
1782
|
}
|
|
1634
1783
|
}
|
|
1635
|
-
const remoteMd = await readFileFromPath(
|
|
1784
|
+
const remoteMd = await readFileFromPath(join19(TEAM_DIR, "CLAUDE.md"));
|
|
1636
1785
|
if (remoteMd) {
|
|
1637
1786
|
const localMd = await readFileFromPath(LOCAL_CLAUDE_MD);
|
|
1638
1787
|
if (!localMd) {
|
|
@@ -1651,24 +1800,37 @@ async function teamPullCommand() {
|
|
|
1651
1800
|
}
|
|
1652
1801
|
}
|
|
1653
1802
|
}
|
|
1654
|
-
let
|
|
1803
|
+
let cortexManifest = {};
|
|
1655
1804
|
try {
|
|
1656
|
-
|
|
1805
|
+
cortexManifest = JSON.parse(await readFile14(join19(TEAM_DIR, "cortex.json"), "utf-8"));
|
|
1657
1806
|
} catch {
|
|
1658
1807
|
}
|
|
1659
|
-
for (const pluginId of
|
|
1808
|
+
for (const pluginId of cortexManifest.plugins ?? []) {
|
|
1660
1809
|
try {
|
|
1661
1810
|
installPlugin(pluginId);
|
|
1662
1811
|
} catch (e) {
|
|
1663
1812
|
console.warn(` \u26A0 Could not install ${pluginId}: ${e.message}`);
|
|
1664
1813
|
}
|
|
1665
1814
|
}
|
|
1815
|
+
let derived;
|
|
1816
|
+
if (encryptSessions) {
|
|
1817
|
+
const teamPassphrase = await password7({
|
|
1818
|
+
message: "Team passphrase (to decrypt sessions):",
|
|
1819
|
+
mask: "*",
|
|
1820
|
+
validate: (v) => v.length >= 12 || "Minimum 12 characters"
|
|
1821
|
+
});
|
|
1822
|
+
derived = deriveKey(teamPassphrase, repoUrl);
|
|
1823
|
+
}
|
|
1824
|
+
const sessionCount = await pullSessions(process.cwd(), derived);
|
|
1825
|
+
if (sessionCount > 0) {
|
|
1826
|
+
console.log(` + ${sessionCount} sessions installed (restart Claude Code to see them)`);
|
|
1827
|
+
}
|
|
1666
1828
|
console.log("\n\u2713 Team context applied.");
|
|
1667
1829
|
}
|
|
1668
1830
|
|
|
1669
1831
|
// src/commands/install.ts
|
|
1670
|
-
import { readFile as
|
|
1671
|
-
import { join as
|
|
1832
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
1833
|
+
import { join as join20 } from "path";
|
|
1672
1834
|
async function installCommand(opts) {
|
|
1673
1835
|
let config;
|
|
1674
1836
|
try {
|
|
@@ -1685,19 +1847,19 @@ async function installCommand(opts) {
|
|
|
1685
1847
|
if (!token) throw new Error('No GitHub token found. Run "cortex init" first.');
|
|
1686
1848
|
console.log(`Installing from ${repoUrl} into ${process.cwd()}/.claude/`);
|
|
1687
1849
|
await cloneTeamRepo(repoUrl, token);
|
|
1688
|
-
const skills = await readSkillsFromDir(
|
|
1850
|
+
const skills = await readSkillsFromDir(join20(TEAM_DIR, "skills"));
|
|
1689
1851
|
for (const [filename, content] of skills) {
|
|
1690
1852
|
await writeSkillToDir(LOCAL_SKILLS_DIR, filename, content);
|
|
1691
1853
|
console.log(` + .claude/skills/${filename}`);
|
|
1692
1854
|
}
|
|
1693
|
-
const claudeMd = await readFileFromPath(
|
|
1855
|
+
const claudeMd = await readFileFromPath(join20(TEAM_DIR, "CLAUDE.md"));
|
|
1694
1856
|
if (claudeMd) {
|
|
1695
1857
|
await writeFileToPath(LOCAL_CLAUDE_MD, claudeMd);
|
|
1696
1858
|
console.log(" + .claude/CLAUDE.md");
|
|
1697
1859
|
}
|
|
1698
1860
|
let cortexJson = {};
|
|
1699
1861
|
try {
|
|
1700
|
-
cortexJson = JSON.parse(await
|
|
1862
|
+
cortexJson = JSON.parse(await readFile15(join20(TEAM_DIR, "cortex.json"), "utf-8"));
|
|
1701
1863
|
} catch {
|
|
1702
1864
|
}
|
|
1703
1865
|
for (const pluginId of cortexJson.plugins ?? []) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cortex-sync",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
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",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
+
"scripts",
|
|
12
13
|
"README.md",
|
|
13
14
|
"CHANGELOG.md",
|
|
14
15
|
"LICENSE"
|
|
@@ -31,7 +32,8 @@
|
|
|
31
32
|
"build": "tsup",
|
|
32
33
|
"dev": "tsup --watch",
|
|
33
34
|
"test": "vitest run",
|
|
34
|
-
"typecheck": "tsc --noEmit"
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"postinstall": "node scripts/postinstall.cjs"
|
|
35
37
|
},
|
|
36
38
|
"dependencies": {
|
|
37
39
|
"@anthropic-ai/sdk": "^0.95.2",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
const { join } = require('path');
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const prefix = execSync('npm config get prefix', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
9
|
+
const binDir = join(prefix, 'bin');
|
|
10
|
+
const pathDirs = (process.env.PATH || '').split(':');
|
|
11
|
+
const inPath = pathDirs.some((d) => d === binDir);
|
|
12
|
+
|
|
13
|
+
if (!inPath) {
|
|
14
|
+
console.log('\n╔════════════════════════════════════════════════╗');
|
|
15
|
+
console.log('║ cortex installed — but needs a PATH fix ║');
|
|
16
|
+
console.log('╚════════════════════════════════════════════════╝');
|
|
17
|
+
console.log(`\n Binary location: ${join(binDir, 'cortex')}`);
|
|
18
|
+
console.log(` Missing from PATH: ${binDir}\n`);
|
|
19
|
+
console.log(' Fix (add ONE of these to ~/.zshrc or ~/.bashrc):\n');
|
|
20
|
+
console.log(` export PATH="${binDir}:$PATH"\n`);
|
|
21
|
+
console.log(' Then reload your shell:');
|
|
22
|
+
console.log(' source ~/.zshrc (zsh)');
|
|
23
|
+
console.log(' source ~/.bashrc (bash)\n');
|
|
24
|
+
} else {
|
|
25
|
+
console.log(`\n ✓ cortex installed. Run: cortex --version\n`);
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
// silently ignore — postinstall must never break the install
|
|
29
|
+
}
|