cortex-sync 0.1.0 → 0.3.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.
- package/README.md +39 -2
- package/dist/cli.js +283 -11
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
Claude Code stores your session history in `~/.claude/projects/` using absolute paths. Switch from your Mac to a Linux server and those sessions are gone — the paths don't match. `cortex` fixes that.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install -g cortex-
|
|
8
|
+
npm install -g cortex-sync
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
---
|
|
@@ -31,7 +31,7 @@ Machine A (Mac) Machine B (Linux)
|
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
# 1. Install
|
|
34
|
-
npm install -g cortex-
|
|
34
|
+
npm install -g cortex-sync
|
|
35
35
|
|
|
36
36
|
# 2. Configure on Machine A
|
|
37
37
|
cortex init
|
|
@@ -59,6 +59,43 @@ Open any project on Machine B — Claude Code shows your full session history.
|
|
|
59
59
|
| `cortex pull` | Download, decrypt, remap paths |
|
|
60
60
|
| `cortex status` | Show what's out of sync (no download) |
|
|
61
61
|
| `cortex convert <file> --to <target>` | Convert a Claude Code skill |
|
|
62
|
+
| `cortex setup-mcp` | Register cortex as a Claude Code MCP server |
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Claude Code MCP integration
|
|
67
|
+
|
|
68
|
+
Use `sync`, `pull`, `status`, `convert`, and `init` directly from the Claude Code chat — one command does everything:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm install -g cortex-sync
|
|
72
|
+
cortex init # configure storage and passphrase
|
|
73
|
+
cortex setup-mcp # registers cortex in Claude Code automatically
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
That's it. Restart Claude Code and you're done.
|
|
77
|
+
|
|
78
|
+
### What setup-mcp does
|
|
79
|
+
|
|
80
|
+
`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.
|
|
81
|
+
|
|
82
|
+
### Available MCP tools
|
|
83
|
+
|
|
84
|
+
| Tool | What it does |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `sync` | Encrypt and upload `~/.claude/` |
|
|
87
|
+
| `pull` | Download, decrypt, remap paths |
|
|
88
|
+
| `status` | Show what's out of sync |
|
|
89
|
+
| `convert` | Convert a skill to Antigravity or Cursor |
|
|
90
|
+
| `init` | Configure storage (non-interactive) |
|
|
91
|
+
|
|
92
|
+
### Environment variables (advanced / manual setup)
|
|
93
|
+
|
|
94
|
+
| Variable | Required for |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `CORTEX_PASSPHRASE` | `sync`, `pull`, `status` |
|
|
97
|
+
| `ANTHROPIC_API_KEY` | `convert` |
|
|
98
|
+
| `CORTEX_GITHUB_TOKEN` | `init` with GitHub storage |
|
|
62
99
|
|
|
63
100
|
---
|
|
64
101
|
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
+
import { createRequire } from "module";
|
|
4
5
|
import { Command } from "commander";
|
|
5
6
|
|
|
6
7
|
// src/commands/convert.ts
|
|
@@ -58,8 +59,8 @@ function decrypt(blob, derived) {
|
|
|
58
59
|
if (!blob.subarray(0, MAGIC.length).equals(MAGIC)) {
|
|
59
60
|
throw new Error("cortex blob has bad magic bytes");
|
|
60
61
|
}
|
|
61
|
-
const
|
|
62
|
-
if (
|
|
62
|
+
const version2 = blob[MAGIC.length];
|
|
63
|
+
if (version2 !== VERSION) throw new Error(`cortex blob version ${version2} not supported`);
|
|
63
64
|
const ivStart = MAGIC.length + 1;
|
|
64
65
|
const iv = blob.subarray(ivStart, ivStart + IV_LEN);
|
|
65
66
|
const tag = blob.subarray(ivStart + IV_LEN, ivStart + IV_LEN + TAG_LEN);
|
|
@@ -85,7 +86,7 @@ async function readPassphrase() {
|
|
|
85
86
|
|
|
86
87
|
// src/lib/api-key.ts
|
|
87
88
|
var API_KEY_PATH = join2(CORTEX_DIR, "api-key.enc");
|
|
88
|
-
async function loadApiKey() {
|
|
89
|
+
async function loadApiKey(opts = {}) {
|
|
89
90
|
if (process.env.ANTHROPIC_API_KEY) return process.env.ANTHROPIC_API_KEY;
|
|
90
91
|
if (existsSync(API_KEY_PATH)) {
|
|
91
92
|
const config = await loadConfig();
|
|
@@ -94,6 +95,11 @@ async function loadApiKey() {
|
|
|
94
95
|
const enc = await readFile2(API_KEY_PATH);
|
|
95
96
|
return decrypt(enc, derived).toString("utf-8").trim();
|
|
96
97
|
}
|
|
98
|
+
if (opts.nonInteractive) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
'ANTHROPIC_API_KEY environment variable is not set and no encrypted key found.\nSet it with: export ANTHROPIC_API_KEY="sk-ant-..."'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
97
103
|
const key = await password2({
|
|
98
104
|
message: "Anthropic API key (sk-ant-...):",
|
|
99
105
|
mask: "*",
|
|
@@ -257,10 +263,10 @@ async function convertCommand(skillPath, opts) {
|
|
|
257
263
|
const skill = parseSkillMeta(source, fallbackName);
|
|
258
264
|
console.log(`Converting "${skill.name}" \u2192 ${opts.to}
|
|
259
265
|
`);
|
|
260
|
-
const apiKey = await loadApiKey();
|
|
266
|
+
const apiKey = opts.apiKey ?? await loadApiKey();
|
|
261
267
|
const targets = opts.to === "all" ? ["antigravity", "cursor"] : [opts.to];
|
|
262
268
|
for (const target of targets) {
|
|
263
|
-
|
|
269
|
+
console.log(` ${target}\u2026`);
|
|
264
270
|
let outPath;
|
|
265
271
|
if (target === "antigravity") {
|
|
266
272
|
outPath = await convertToAntigravity(skill, apiKey, outputDir);
|
|
@@ -465,7 +471,7 @@ async function initCommand() {
|
|
|
465
471
|
mask: "*",
|
|
466
472
|
validate: (v) => v.trim().startsWith("gh") || "Token should start with gh"
|
|
467
473
|
});
|
|
468
|
-
|
|
474
|
+
console.log("Validating token\u2026");
|
|
469
475
|
githubOwner = await fetchGitHubUser(githubToken.trim());
|
|
470
476
|
console.log(`\u2713 Authenticated as ${githubOwner}`);
|
|
471
477
|
githubRepo = await input({
|
|
@@ -473,7 +479,7 @@ async function initCommand() {
|
|
|
473
479
|
default: "cortex-backup",
|
|
474
480
|
validate: (v) => /^[a-zA-Z0-9_.-]+$/.test(v.trim()) || "Invalid repo name"
|
|
475
481
|
});
|
|
476
|
-
|
|
482
|
+
console.log(`Creating private repo ${githubOwner}/${githubRepo}\u2026`);
|
|
477
483
|
await ensureGitHubRepo(githubToken.trim(), githubRepo.trim());
|
|
478
484
|
console.log("\u2713 Ready");
|
|
479
485
|
githubToken = githubToken.trim();
|
|
@@ -516,6 +522,50 @@ Detected tools: ${detected.length ? detected.join(", ") : "none"}`);
|
|
|
516
522
|
console.log('Next step: Google Drive backend is not yet implemented \u2014 use --target <path> with "cortex sync" for now.');
|
|
517
523
|
}
|
|
518
524
|
}
|
|
525
|
+
async function initNonInteractive(opts) {
|
|
526
|
+
if (!opts.email) throw new Error("email is required");
|
|
527
|
+
if (!opts.storage) throw new Error('storage is required: "github" or "local"');
|
|
528
|
+
if (opts.storage === "local" && !opts.target) {
|
|
529
|
+
throw new Error('target path is required when storage is "local"');
|
|
530
|
+
}
|
|
531
|
+
let githubToken;
|
|
532
|
+
let githubOwner;
|
|
533
|
+
let githubRepo;
|
|
534
|
+
if (opts.storage === "github") {
|
|
535
|
+
githubToken = process.env.CORTEX_GITHUB_TOKEN;
|
|
536
|
+
if (!githubToken) {
|
|
537
|
+
throw new Error(
|
|
538
|
+
'CORTEX_GITHUB_TOKEN environment variable is not set.\nCreate a PAT at: https://github.com/settings/tokens/new?scopes=repo\nThen set: export CORTEX_GITHUB_TOKEN="ghp_..."'
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
githubOwner = await fetchGitHubUser(githubToken);
|
|
542
|
+
console.log(`\u2713 Authenticated as ${githubOwner}`);
|
|
543
|
+
githubRepo = opts.githubRepo ?? "cortex-backup";
|
|
544
|
+
await ensureGitHubRepo(githubToken, githubRepo);
|
|
545
|
+
console.log(`\u2713 Repo ${githubOwner}/${githubRepo} ready`);
|
|
546
|
+
}
|
|
547
|
+
const detected = await detectInstalledTools();
|
|
548
|
+
await mkdir3(CORTEX_DIR2, { recursive: true });
|
|
549
|
+
const config = {
|
|
550
|
+
version: 1,
|
|
551
|
+
storage: opts.storage,
|
|
552
|
+
email: opts.email,
|
|
553
|
+
target: opts.target,
|
|
554
|
+
githubToken,
|
|
555
|
+
githubOwner,
|
|
556
|
+
githubRepo,
|
|
557
|
+
tools: detected,
|
|
558
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
559
|
+
};
|
|
560
|
+
await writeFile4(CONFIG_PATH2, JSON.stringify(config, null, 2), { mode: 384 });
|
|
561
|
+
await chmod2(CONFIG_PATH2, 384);
|
|
562
|
+
console.log(`\u2713 Configuration saved to ${CONFIG_PATH2}`);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// src/commands/mcp.ts
|
|
566
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
567
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
568
|
+
import { z } from "zod";
|
|
519
569
|
|
|
520
570
|
// src/commands/pull.ts
|
|
521
571
|
import { input as input2 } from "@inquirer/prompts";
|
|
@@ -800,11 +850,14 @@ function extractCwdFromJsonl(input3) {
|
|
|
800
850
|
}
|
|
801
851
|
|
|
802
852
|
// src/commands/pull.ts
|
|
803
|
-
async function resolveLocalPath(projectId, originalPath, mappings) {
|
|
853
|
+
async function resolveLocalPath(projectId, originalPath, mappings, projectMappings, nonInteractive) {
|
|
804
854
|
const key = projectId ?? originalPath;
|
|
855
|
+
if (projectMappings?.[key]) return projectMappings[key];
|
|
856
|
+
if (projectId && projectMappings?.[originalPath]) return projectMappings[originalPath];
|
|
805
857
|
if (mappings[key]) return mappings[key];
|
|
806
858
|
if (projectId && mappings[originalPath]) return mappings[originalPath];
|
|
807
859
|
if (existsSync5(originalPath)) return originalPath;
|
|
860
|
+
if (nonInteractive) return null;
|
|
808
861
|
console.log(`
|
|
809
862
|
Project not found on this machine:`);
|
|
810
863
|
console.log(` Original path: ${originalPath}`);
|
|
@@ -836,6 +889,7 @@ async function pullCommand(opts = {}) {
|
|
|
836
889
|
);
|
|
837
890
|
const mappings = await loadMappings();
|
|
838
891
|
const dirRemap = /* @__PURE__ */ new Map();
|
|
892
|
+
const pendingMappings = [];
|
|
839
893
|
if (remote.projects) {
|
|
840
894
|
let mappingsDirty = false;
|
|
841
895
|
for (const [encodedDir, meta] of Object.entries(remote.projects)) {
|
|
@@ -844,8 +898,21 @@ async function pullCommand(opts = {}) {
|
|
|
844
898
|
dirRemap.set(encodedDir, null);
|
|
845
899
|
continue;
|
|
846
900
|
}
|
|
847
|
-
const localPath = await resolveLocalPath(
|
|
901
|
+
const localPath = await resolveLocalPath(
|
|
902
|
+
meta.projectId,
|
|
903
|
+
meta.originalPath,
|
|
904
|
+
mappings,
|
|
905
|
+
opts.projectMappings,
|
|
906
|
+
opts.nonInteractive
|
|
907
|
+
);
|
|
848
908
|
if (localPath === null) {
|
|
909
|
+
if (opts.nonInteractive) {
|
|
910
|
+
pendingMappings.push({
|
|
911
|
+
encodedDir,
|
|
912
|
+
projectId: meta.projectId,
|
|
913
|
+
originalPath: meta.originalPath
|
|
914
|
+
});
|
|
915
|
+
}
|
|
849
916
|
dirRemap.set(encodedDir, null);
|
|
850
917
|
continue;
|
|
851
918
|
}
|
|
@@ -893,6 +960,10 @@ async function pullCommand(opts = {}) {
|
|
|
893
960
|
await saveManifest(MANIFEST_PATH, remote);
|
|
894
961
|
console.log(`
|
|
895
962
|
\u2713 Pull complete \u2014 ${toPull.length} files restored.`);
|
|
963
|
+
return {
|
|
964
|
+
filesRestored: toPull.length,
|
|
965
|
+
pendingMappings: pendingMappings.length > 0 ? pendingMappings : void 0
|
|
966
|
+
};
|
|
896
967
|
}
|
|
897
968
|
|
|
898
969
|
// src/commands/status.ts
|
|
@@ -1072,12 +1143,211 @@ async function syncCommand(opts = {}) {
|
|
|
1072
1143
|
);
|
|
1073
1144
|
}
|
|
1074
1145
|
|
|
1146
|
+
// src/commands/mcp.ts
|
|
1147
|
+
var logToStderr = (...args) => process.stderr.write(args.map(String).join(" ") + "\n");
|
|
1148
|
+
console.log = logToStderr;
|
|
1149
|
+
function requirePassphrase() {
|
|
1150
|
+
if (!process.env.CORTEX_PASSPHRASE) {
|
|
1151
|
+
throw new Error(
|
|
1152
|
+
'CORTEX_PASSPHRASE environment variable is not set.\nRun "cortex init" from your terminal first, then set:\n export CORTEX_PASSPHRASE="your-passphrase"'
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
async function captureOutput(fn) {
|
|
1157
|
+
const lines = [];
|
|
1158
|
+
const prev = console.log;
|
|
1159
|
+
console.log = (...args) => lines.push(args.map(String).join(" "));
|
|
1160
|
+
try {
|
|
1161
|
+
const result = await fn();
|
|
1162
|
+
console.log = prev;
|
|
1163
|
+
return { result, output: lines.join("\n") };
|
|
1164
|
+
} catch (e) {
|
|
1165
|
+
console.log = prev;
|
|
1166
|
+
throw e;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
function ok(text) {
|
|
1170
|
+
return { content: [{ type: "text", text }] };
|
|
1171
|
+
}
|
|
1172
|
+
function toolErr(e) {
|
|
1173
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1174
|
+
return { content: [{ type: "text", text: msg }], isError: true };
|
|
1175
|
+
}
|
|
1176
|
+
async function mcpCommand() {
|
|
1177
|
+
const server = new McpServer({ name: "cortex", version: "0.1.0" });
|
|
1178
|
+
server.registerTool(
|
|
1179
|
+
"sync",
|
|
1180
|
+
{
|
|
1181
|
+
description: "Encrypt ~/.claude/ and upload to configured storage. Requires CORTEX_PASSPHRASE env var.",
|
|
1182
|
+
inputSchema: z.object({
|
|
1183
|
+
skipSecretsCheck: z.boolean().optional().describe("Skip the API key detection warning before encrypting"),
|
|
1184
|
+
target: z.string().optional().describe("Override storage to a local folder path")
|
|
1185
|
+
})
|
|
1186
|
+
},
|
|
1187
|
+
async ({ skipSecretsCheck, target }) => {
|
|
1188
|
+
try {
|
|
1189
|
+
requirePassphrase();
|
|
1190
|
+
const { output } = await captureOutput(() => syncCommand({ skipSecretsCheck, target }));
|
|
1191
|
+
return ok(output);
|
|
1192
|
+
} catch (e) {
|
|
1193
|
+
return toolErr(e);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
);
|
|
1197
|
+
server.registerTool(
|
|
1198
|
+
"pull",
|
|
1199
|
+
{
|
|
1200
|
+
description: "Download from storage, decrypt, and remap paths into ~/.claude/. If pendingMappings is returned, call pull again with projectMappings populated.",
|
|
1201
|
+
inputSchema: z.object({
|
|
1202
|
+
target: z.string().optional().describe("Override storage to a local folder path"),
|
|
1203
|
+
projectMappings: z.record(z.string(), z.string()).optional().describe("Map of projectId or originalPath to local path on this machine")
|
|
1204
|
+
})
|
|
1205
|
+
},
|
|
1206
|
+
async ({ target, projectMappings }) => {
|
|
1207
|
+
try {
|
|
1208
|
+
requirePassphrase();
|
|
1209
|
+
const result = await pullCommand({ target, projectMappings, nonInteractive: true });
|
|
1210
|
+
if (result.pendingMappings?.length) {
|
|
1211
|
+
const lines = result.pendingMappings.map(
|
|
1212
|
+
(p) => ` "${p.originalPath}" (projectId: ${p.projectId ?? "none"})`
|
|
1213
|
+
);
|
|
1214
|
+
return ok(
|
|
1215
|
+
`Restored ${result.filesRestored} files.
|
|
1216
|
+
|
|
1217
|
+
These projects need a local path mapping.
|
|
1218
|
+
Call pull again with projectMappings, e.g.:
|
|
1219
|
+
{ "${result.pendingMappings[0].projectId ?? result.pendingMappings[0].originalPath}": "/your/local/path" }
|
|
1220
|
+
|
|
1221
|
+
Pending projects:
|
|
1222
|
+
${lines.join("\n")}`
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
return ok(`Pull complete. ${result.filesRestored} files restored.`);
|
|
1226
|
+
} catch (e) {
|
|
1227
|
+
return toolErr(e);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
);
|
|
1231
|
+
server.registerTool(
|
|
1232
|
+
"status",
|
|
1233
|
+
{
|
|
1234
|
+
description: "Show what is out of sync between local ~/.claude/ files and storage.",
|
|
1235
|
+
inputSchema: z.object({
|
|
1236
|
+
target: z.string().optional().describe("Override storage to a local folder path")
|
|
1237
|
+
})
|
|
1238
|
+
},
|
|
1239
|
+
async ({ target }) => {
|
|
1240
|
+
try {
|
|
1241
|
+
requirePassphrase();
|
|
1242
|
+
const { output } = await captureOutput(() => statusCommand({ target }));
|
|
1243
|
+
return ok(output);
|
|
1244
|
+
} catch (e) {
|
|
1245
|
+
return toolErr(e);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
);
|
|
1249
|
+
server.registerTool(
|
|
1250
|
+
"convert",
|
|
1251
|
+
{
|
|
1252
|
+
description: "Convert a Claude Code skill to Antigravity or Cursor format using the Anthropic API. Requires ANTHROPIC_API_KEY env var (or ~/.cortex/api-key.enc).",
|
|
1253
|
+
inputSchema: z.object({
|
|
1254
|
+
skillPath: z.string().describe("Absolute path to the Claude Code skill .md file"),
|
|
1255
|
+
to: z.enum(["antigravity", "cursor", "all"]).describe("Target format"),
|
|
1256
|
+
outputDir: z.string().optional().describe("Project root where output files are written (default: cwd)")
|
|
1257
|
+
})
|
|
1258
|
+
},
|
|
1259
|
+
async ({ skillPath, to, outputDir }) => {
|
|
1260
|
+
try {
|
|
1261
|
+
let apiKey;
|
|
1262
|
+
try {
|
|
1263
|
+
apiKey = await loadApiKey({ nonInteractive: true });
|
|
1264
|
+
} catch (e) {
|
|
1265
|
+
return toolErr(e);
|
|
1266
|
+
}
|
|
1267
|
+
const { output } = await captureOutput(
|
|
1268
|
+
() => convertCommand(skillPath, { to, outputDir, apiKey })
|
|
1269
|
+
);
|
|
1270
|
+
return ok(output);
|
|
1271
|
+
} catch (e) {
|
|
1272
|
+
return toolErr(e);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
);
|
|
1276
|
+
server.registerTool(
|
|
1277
|
+
"init",
|
|
1278
|
+
{
|
|
1279
|
+
description: "Configure cortex storage. For GitHub, requires CORTEX_GITHUB_TOKEN env var.",
|
|
1280
|
+
inputSchema: z.object({
|
|
1281
|
+
email: z.string().describe("Email used as salt for key derivation"),
|
|
1282
|
+
storage: z.enum(["github", "local"]).describe("Storage backend"),
|
|
1283
|
+
githubRepo: z.string().optional().describe("GitHub repo name for backup (default: cortex-backup)"),
|
|
1284
|
+
target: z.string().optional().describe('Local folder path \u2014 required when storage is "local"')
|
|
1285
|
+
})
|
|
1286
|
+
},
|
|
1287
|
+
async (params) => {
|
|
1288
|
+
try {
|
|
1289
|
+
const { output } = await captureOutput(() => initNonInteractive(params));
|
|
1290
|
+
return ok(output);
|
|
1291
|
+
} catch (e) {
|
|
1292
|
+
return toolErr(e);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
);
|
|
1296
|
+
const transport = new StdioServerTransport();
|
|
1297
|
+
await server.connect(transport);
|
|
1298
|
+
process.stderr.write("cortex MCP server running on stdio\n");
|
|
1299
|
+
process.on("SIGINT", async () => {
|
|
1300
|
+
await server.close();
|
|
1301
|
+
process.exit(0);
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
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
|
+
|
|
1075
1343
|
// src/cli.ts
|
|
1344
|
+
var require2 = createRequire(import.meta.url);
|
|
1345
|
+
var { version } = require2("../package.json");
|
|
1076
1346
|
var program = new Command();
|
|
1077
|
-
program.name("cortex").description("Sync Claude Code context between machines with path remapping").version(
|
|
1347
|
+
program.name("cortex").description("Sync Claude Code context between machines with path remapping").version(version);
|
|
1078
1348
|
program.command("init").description("Configure Cortex: pick storage, set passphrase, detect tools").action(initCommand);
|
|
1079
1349
|
program.command("sync").description("Encrypt local files and upload to the configured storage").option("--target <path>", "Override storage to a local folder (overrides config)").option("--skip-secrets-check", "Skip the regex scan for API keys before encrypting").action(syncCommand);
|
|
1080
|
-
program.command("pull").description("Download from storage and restore into ~/.claude/").option("--target <path>", "Override storage to a local folder (overrides config)").action(pullCommand);
|
|
1350
|
+
program.command("pull").description("Download from storage and restore into ~/.claude/").option("--target <path>", "Override storage to a local folder (overrides config)").action((opts) => void pullCommand(opts));
|
|
1081
1351
|
program.command("status").description("Show what is out of sync between local files and storage").option("--target <path>", "Override storage to a local folder (overrides config)").action(statusCommand);
|
|
1082
1352
|
program.command("convert <skill-file>").description("Convert a Claude Code skill to Antigravity or Cursor format").requiredOption("--to <target>", "Target format: antigravity | cursor | all").option("--output-dir <path>", "Project root where output files are written (default: cwd)").action((skillFile, opts) => {
|
|
1083
1353
|
const validTargets = ["antigravity", "cursor", "all"];
|
|
@@ -1087,4 +1357,6 @@ program.command("convert <skill-file>").description("Convert a Claude Code skill
|
|
|
1087
1357
|
}
|
|
1088
1358
|
return convertCommand(skillFile, { to: opts.to, outputDir: opts.outputDir });
|
|
1089
1359
|
});
|
|
1360
|
+
program.command("mcp").description("Start the MCP server (for use with claude mcp add cortex -- cortex mcp)").action(mcpCommand);
|
|
1361
|
+
program.command("setup-mcp").description("Register cortex as a Claude Code MCP server automatically").action(setupMcpCommand);
|
|
1090
1362
|
await program.parseAsync(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cortex-sync",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|
|
@@ -36,7 +36,9 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@anthropic-ai/sdk": "^0.95.2",
|
|
38
38
|
"@inquirer/prompts": "^7.0.0",
|
|
39
|
-
"
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
40
|
+
"commander": "^12.1.0",
|
|
41
|
+
"zod": "^4.4.3"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
42
44
|
"@types/node": "^22.0.0",
|