cortex-sync 0.4.0 → 0.4.2
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 +223 -48
- package/package.json +1 -1
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 {
|
|
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";
|
|
@@ -1410,10 +1410,9 @@ function commitAndPush(repoUrl, token, message, dir = TEAM_DIR) {
|
|
|
1410
1410
|
|
|
1411
1411
|
// src/lib/claude-skills.ts
|
|
1412
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
1413
|
import { join as join13, dirname as dirname7 } from "path";
|
|
1415
|
-
var LOCAL_SKILLS_DIR = join13(
|
|
1416
|
-
var LOCAL_CLAUDE_MD = join13(
|
|
1414
|
+
var LOCAL_SKILLS_DIR = join13(process.cwd(), ".claude", "skills");
|
|
1415
|
+
var LOCAL_CLAUDE_MD = join13(process.cwd(), ".claude", "CLAUDE.md");
|
|
1417
1416
|
async function readSkillsFromDir(dir) {
|
|
1418
1417
|
const map = /* @__PURE__ */ new Map();
|
|
1419
1418
|
let entries;
|
|
@@ -1448,10 +1447,10 @@ async function writeFileToPath(filePath, content) {
|
|
|
1448
1447
|
|
|
1449
1448
|
// src/lib/claude-plugins.ts
|
|
1450
1449
|
import { readFile as readFile11 } from "fs/promises";
|
|
1451
|
-
import { homedir as
|
|
1450
|
+
import { homedir as homedir4 } from "os";
|
|
1452
1451
|
import { join as join14 } from "path";
|
|
1453
1452
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
1454
|
-
var INSTALLED_PLUGINS_PATH = join14(
|
|
1453
|
+
var INSTALLED_PLUGINS_PATH = join14(homedir4(), ".claude", "plugins", "installed_plugins.json");
|
|
1455
1454
|
function parseInstalledPlugins(data) {
|
|
1456
1455
|
return Object.keys(data.plugins);
|
|
1457
1456
|
}
|
|
@@ -1470,6 +1469,124 @@ function installPlugin(pluginId) {
|
|
|
1470
1469
|
if (result.status !== 0) throw new Error(`Plugin install failed for ${pluginId}`);
|
|
1471
1470
|
}
|
|
1472
1471
|
|
|
1472
|
+
// src/lib/project-config.ts
|
|
1473
|
+
import { readFile as readFile12, writeFile as writeFile10 } from "fs/promises";
|
|
1474
|
+
import { join as join15 } from "path";
|
|
1475
|
+
async function readProjectConfig(cwd = process.cwd()) {
|
|
1476
|
+
try {
|
|
1477
|
+
const content = await readFile12(join15(cwd, "cortex.json"), "utf-8");
|
|
1478
|
+
return JSON.parse(content);
|
|
1479
|
+
} catch {
|
|
1480
|
+
return {};
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
async function writeProjectConfig(updates, cwd = process.cwd()) {
|
|
1484
|
+
const existing = await readProjectConfig(cwd);
|
|
1485
|
+
const merged = { ...existing, ...updates };
|
|
1486
|
+
await writeFile10(join15(cwd, "cortex.json"), JSON.stringify(merged, null, 2), "utf-8");
|
|
1487
|
+
}
|
|
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
|
+
|
|
1473
1590
|
// src/commands/team/init.ts
|
|
1474
1591
|
async function teamInitCommand(opts) {
|
|
1475
1592
|
const config = await loadConfig();
|
|
@@ -1484,35 +1601,67 @@ Cloning ${repoUrl} \u2192 ~/.cortex/team/`);
|
|
|
1484
1601
|
await cloneTeamRepo(repoUrl, token);
|
|
1485
1602
|
const skills = await readSkillsFromDir(LOCAL_SKILLS_DIR);
|
|
1486
1603
|
if (skills.size > 0) {
|
|
1487
|
-
await
|
|
1604
|
+
await mkdir11(join17(TEAM_DIR, "skills"), { recursive: true });
|
|
1488
1605
|
for (const [filename, content] of skills) {
|
|
1489
|
-
await
|
|
1606
|
+
await writeFile12(join17(TEAM_DIR, "skills", filename), content, "utf-8");
|
|
1490
1607
|
}
|
|
1491
|
-
console.log(` Copied ${skills.size} skills
|
|
1608
|
+
console.log(` Copied ${skills.size} skills from .claude/skills/`);
|
|
1492
1609
|
}
|
|
1493
1610
|
const claudeMd = await readFileFromPath(LOCAL_CLAUDE_MD);
|
|
1494
1611
|
if (claudeMd) {
|
|
1495
|
-
await
|
|
1496
|
-
console.log(" Copied CLAUDE.md");
|
|
1612
|
+
await writeFile12(join17(TEAM_DIR, "CLAUDE.md"), claudeMd, "utf-8");
|
|
1613
|
+
console.log(" Copied .claude/CLAUDE.md");
|
|
1497
1614
|
}
|
|
1498
1615
|
const plugins = await getInstalledPluginIds();
|
|
1499
1616
|
const cortexJson = { version: "1", plugins };
|
|
1500
|
-
await
|
|
1617
|
+
await writeFile12(join17(TEAM_DIR, "cortex.json"), JSON.stringify(cortexJson, null, 2), "utf-8");
|
|
1501
1618
|
console.log(` Generated cortex.json (${plugins.length} plugins)`);
|
|
1502
1619
|
commitAndPush(repoUrl, token, "feat: initial team Claude Code context");
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
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 });
|
|
1506
1654
|
console.log("\n\u2713 Team repo initialized and pushed.");
|
|
1507
1655
|
console.log(` Devs can now run: cortex install --repo ${repoUrl}`);
|
|
1508
1656
|
}
|
|
1509
1657
|
|
|
1510
1658
|
// src/commands/team/push.ts
|
|
1511
|
-
import { writeFile as
|
|
1512
|
-
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";
|
|
1513
1662
|
async function teamPushCommand() {
|
|
1514
1663
|
const config = await loadConfig();
|
|
1515
|
-
const repoUrl =
|
|
1664
|
+
const { repo: repoUrl, shareSession, encryptSessions } = await readProjectConfig();
|
|
1516
1665
|
const token = config.githubToken;
|
|
1517
1666
|
if (!repoUrl) throw new Error('No team repo configured. Run "cortex team init --repo <url>" first.');
|
|
1518
1667
|
if (!token) throw new Error('No GitHub token found. Run "cortex init" first.');
|
|
@@ -1521,31 +1670,45 @@ async function teamPushCommand() {
|
|
|
1521
1670
|
pullTeamRepo();
|
|
1522
1671
|
const skills = await readSkillsFromDir(LOCAL_SKILLS_DIR);
|
|
1523
1672
|
if (skills.size > 0) {
|
|
1524
|
-
await
|
|
1673
|
+
await mkdir12(join18(TEAM_DIR, "skills"), { recursive: true });
|
|
1525
1674
|
for (const [filename, content] of skills) {
|
|
1526
|
-
await
|
|
1675
|
+
await writeFile13(join18(TEAM_DIR, "skills", filename), content, "utf-8");
|
|
1527
1676
|
}
|
|
1528
1677
|
}
|
|
1529
1678
|
const claudeMd = await readFileFromPath(LOCAL_CLAUDE_MD);
|
|
1530
1679
|
if (claudeMd) {
|
|
1531
|
-
await
|
|
1680
|
+
await writeFile13(join18(TEAM_DIR, "CLAUDE.md"), claudeMd, "utf-8");
|
|
1532
1681
|
}
|
|
1533
1682
|
const plugins = await getInstalledPluginIds();
|
|
1534
|
-
await
|
|
1535
|
-
|
|
1683
|
+
await writeFile13(
|
|
1684
|
+
join18(TEAM_DIR, "cortex.json"),
|
|
1536
1685
|
JSON.stringify({ version: "1", plugins }, null, 2),
|
|
1537
1686
|
"utf-8"
|
|
1538
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
|
+
}
|
|
1539
1701
|
commitAndPush(repoUrl, token, "chore: update team Claude Code context");
|
|
1540
1702
|
console.log("\n\u2713 Pushed to team repo.");
|
|
1541
1703
|
}
|
|
1542
1704
|
|
|
1543
1705
|
// src/commands/team/pull.ts
|
|
1544
|
-
import { readFile as
|
|
1545
|
-
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";
|
|
1546
1709
|
|
|
1547
1710
|
// src/lib/conflict.ts
|
|
1548
|
-
import { select as
|
|
1711
|
+
import { select as select3 } from "@inquirer/prompts";
|
|
1549
1712
|
function hasConflict(local, remote) {
|
|
1550
1713
|
return local.trim() !== remote.trim();
|
|
1551
1714
|
}
|
|
@@ -1579,7 +1742,7 @@ function displayDiff(filename, local, remote) {
|
|
|
1579
1742
|
}
|
|
1580
1743
|
async function promptConflict(filename, local, remote) {
|
|
1581
1744
|
displayDiff(filename, local, remote);
|
|
1582
|
-
return
|
|
1745
|
+
return select3({
|
|
1583
1746
|
message: "How to resolve?",
|
|
1584
1747
|
choices: [
|
|
1585
1748
|
{ name: "[M] Merge \u2014 keep both versions", value: "merge" },
|
|
@@ -1591,13 +1754,13 @@ async function promptConflict(filename, local, remote) {
|
|
|
1591
1754
|
|
|
1592
1755
|
// src/commands/team/pull.ts
|
|
1593
1756
|
async function teamPullCommand() {
|
|
1594
|
-
|
|
1595
|
-
const repoUrl =
|
|
1757
|
+
await loadConfig();
|
|
1758
|
+
const { repo: repoUrl, encryptSessions } = await readProjectConfig();
|
|
1596
1759
|
if (!repoUrl) throw new Error('No team repo configured. Run "cortex team init --repo <url>" first.');
|
|
1597
1760
|
if (!await hasLocalClone()) throw new Error('No local team clone found. Run "cortex install" first.');
|
|
1598
1761
|
console.log("Pulling from team repo\u2026");
|
|
1599
1762
|
pullTeamRepo();
|
|
1600
|
-
const remoteSkills = await readSkillsFromDir(
|
|
1763
|
+
const remoteSkills = await readSkillsFromDir(join19(TEAM_DIR, "skills"));
|
|
1601
1764
|
const localSkills = await readSkillsFromDir(LOCAL_SKILLS_DIR);
|
|
1602
1765
|
for (const [filename, remoteContent] of remoteSkills) {
|
|
1603
1766
|
const localContent = localSkills.get(filename) ?? null;
|
|
@@ -1618,7 +1781,7 @@ async function teamPullCommand() {
|
|
|
1618
1781
|
console.log(` ~ ${filename} skipped`);
|
|
1619
1782
|
}
|
|
1620
1783
|
}
|
|
1621
|
-
const remoteMd = await readFileFromPath(
|
|
1784
|
+
const remoteMd = await readFileFromPath(join19(TEAM_DIR, "CLAUDE.md"));
|
|
1622
1785
|
if (remoteMd) {
|
|
1623
1786
|
const localMd = await readFileFromPath(LOCAL_CLAUDE_MD);
|
|
1624
1787
|
if (!localMd) {
|
|
@@ -1637,24 +1800,37 @@ async function teamPullCommand() {
|
|
|
1637
1800
|
}
|
|
1638
1801
|
}
|
|
1639
1802
|
}
|
|
1640
|
-
let
|
|
1803
|
+
let cortexManifest = {};
|
|
1641
1804
|
try {
|
|
1642
|
-
|
|
1805
|
+
cortexManifest = JSON.parse(await readFile14(join19(TEAM_DIR, "cortex.json"), "utf-8"));
|
|
1643
1806
|
} catch {
|
|
1644
1807
|
}
|
|
1645
|
-
for (const pluginId of
|
|
1808
|
+
for (const pluginId of cortexManifest.plugins ?? []) {
|
|
1646
1809
|
try {
|
|
1647
1810
|
installPlugin(pluginId);
|
|
1648
1811
|
} catch (e) {
|
|
1649
1812
|
console.warn(` \u26A0 Could not install ${pluginId}: ${e.message}`);
|
|
1650
1813
|
}
|
|
1651
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
|
+
}
|
|
1652
1828
|
console.log("\n\u2713 Team context applied.");
|
|
1653
1829
|
}
|
|
1654
1830
|
|
|
1655
1831
|
// src/commands/install.ts
|
|
1656
|
-
import { readFile as
|
|
1657
|
-
import { join as
|
|
1832
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
1833
|
+
import { join as join20 } from "path";
|
|
1658
1834
|
async function installCommand(opts) {
|
|
1659
1835
|
let config;
|
|
1660
1836
|
try {
|
|
@@ -1662,27 +1838,28 @@ async function installCommand(opts) {
|
|
|
1662
1838
|
} catch {
|
|
1663
1839
|
throw new Error('Run "cortex init" first to configure your GitHub token.');
|
|
1664
1840
|
}
|
|
1665
|
-
const
|
|
1841
|
+
const projectConfig = await readProjectConfig();
|
|
1842
|
+
const repoUrl = opts.repo ?? projectConfig.repo;
|
|
1666
1843
|
if (!repoUrl) {
|
|
1667
1844
|
throw new Error('No team repo URL. Pass --repo <url> or run "cortex team init" first.');
|
|
1668
1845
|
}
|
|
1669
1846
|
const token = config.githubToken;
|
|
1670
1847
|
if (!token) throw new Error('No GitHub token found. Run "cortex init" first.');
|
|
1671
|
-
console.log(`Installing from ${repoUrl}
|
|
1848
|
+
console.log(`Installing from ${repoUrl} into ${process.cwd()}/.claude/`);
|
|
1672
1849
|
await cloneTeamRepo(repoUrl, token);
|
|
1673
|
-
const skills = await readSkillsFromDir(
|
|
1850
|
+
const skills = await readSkillsFromDir(join20(TEAM_DIR, "skills"));
|
|
1674
1851
|
for (const [filename, content] of skills) {
|
|
1675
1852
|
await writeSkillToDir(LOCAL_SKILLS_DIR, filename, content);
|
|
1676
|
-
console.log(` +
|
|
1853
|
+
console.log(` + .claude/skills/${filename}`);
|
|
1677
1854
|
}
|
|
1678
|
-
const claudeMd = await readFileFromPath(
|
|
1855
|
+
const claudeMd = await readFileFromPath(join20(TEAM_DIR, "CLAUDE.md"));
|
|
1679
1856
|
if (claudeMd) {
|
|
1680
1857
|
await writeFileToPath(LOCAL_CLAUDE_MD, claudeMd);
|
|
1681
|
-
console.log(" + CLAUDE.md");
|
|
1858
|
+
console.log(" + .claude/CLAUDE.md");
|
|
1682
1859
|
}
|
|
1683
1860
|
let cortexJson = {};
|
|
1684
1861
|
try {
|
|
1685
|
-
cortexJson = JSON.parse(await
|
|
1862
|
+
cortexJson = JSON.parse(await readFile15(join20(TEAM_DIR, "cortex.json"), "utf-8"));
|
|
1686
1863
|
} catch {
|
|
1687
1864
|
}
|
|
1688
1865
|
for (const pluginId of cortexJson.plugins ?? []) {
|
|
@@ -1692,9 +1869,7 @@ async function installCommand(opts) {
|
|
|
1692
1869
|
console.warn(` \u26A0 Could not install ${pluginId}: ${e.message}`);
|
|
1693
1870
|
}
|
|
1694
1871
|
}
|
|
1695
|
-
|
|
1696
|
-
raw.teamRepo = repoUrl;
|
|
1697
|
-
await writeFile13(CONFIG_PATH, JSON.stringify(raw, null, 2), "utf-8");
|
|
1872
|
+
await writeProjectConfig({ repo: repoUrl });
|
|
1698
1873
|
console.log("\n\u2713 Team context installed. Restart Claude Code to activate new skills and plugins.");
|
|
1699
1874
|
}
|
|
1700
1875
|
|