cortex-sync 0.1.0 → 0.2.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 +31 -2
- package/dist/cli.js +244 -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
|
|
@@ -62,6 +62,35 @@ Open any project on Machine B — Claude Code shows your full session history.
|
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
65
|
+
## Claude Code MCP integration
|
|
66
|
+
|
|
67
|
+
Install cortex as a Claude Code MCP server to use `sync`, `pull`, `status`, `convert`, and `init` directly from the chat:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
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
|
+
```
|
|
75
|
+
|
|
76
|
+
| Tool | What it does |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `sync` | Encrypt and upload `~/.claude/` |
|
|
79
|
+
| `pull` | Download, decrypt, remap paths |
|
|
80
|
+
| `status` | Show what's out of sync |
|
|
81
|
+
| `convert` | Convert a skill to Antigravity or Cursor |
|
|
82
|
+
| `init` | Configure storage (non-interactive) |
|
|
83
|
+
|
|
84
|
+
**Environment variables used by the MCP server:**
|
|
85
|
+
|
|
86
|
+
| Variable | Required for |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `CORTEX_PASSPHRASE` | `sync`, `pull`, `status` |
|
|
89
|
+
| `ANTHROPIC_API_KEY` | `convert` |
|
|
90
|
+
| `CORTEX_GITHUB_TOKEN` | `init` with GitHub storage |
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
65
94
|
## Storage backends
|
|
66
95
|
|
|
67
96
|
### GitHub private repo (recommended)
|
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,173 @@ 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
|
+
|
|
1075
1305
|
// src/cli.ts
|
|
1306
|
+
var require2 = createRequire(import.meta.url);
|
|
1307
|
+
var { version } = require2("../package.json");
|
|
1076
1308
|
var program = new Command();
|
|
1077
|
-
program.name("cortex").description("Sync Claude Code context between machines with path remapping").version(
|
|
1309
|
+
program.name("cortex").description("Sync Claude Code context between machines with path remapping").version(version);
|
|
1078
1310
|
program.command("init").description("Configure Cortex: pick storage, set passphrase, detect tools").action(initCommand);
|
|
1079
1311
|
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);
|
|
1312
|
+
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
1313
|
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
1314
|
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
1315
|
const validTargets = ["antigravity", "cursor", "all"];
|
|
@@ -1087,4 +1319,5 @@ program.command("convert <skill-file>").description("Convert a Claude Code skill
|
|
|
1087
1319
|
}
|
|
1088
1320
|
return convertCommand(skillFile, { to: opts.to, outputDir: opts.outputDir });
|
|
1089
1321
|
});
|
|
1322
|
+
program.command("mcp").description("Start the MCP server (for use with claude mcp add cortex -- cortex mcp)").action(mcpCommand);
|
|
1090
1323
|
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.2.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",
|