codexport 0.1.5 → 0.1.7
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/dist/index.js +90 -17
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { Command, Option } from "commander";
|
|
|
3
3
|
import chokidar from "chokidar";
|
|
4
4
|
import { createHash, randomBytes } from "node:crypto";
|
|
5
5
|
import { createServer, request } from "node:http";
|
|
6
|
-
import { lstat, mkdir, readFile, readdir, readlink, rename, rm, stat, symlink, writeFile } from "node:fs/promises";
|
|
6
|
+
import { chmod, lstat, mkdir, readFile, readdir, readlink, rename, rm, stat, symlink, writeFile } from "node:fs/promises";
|
|
7
7
|
import { existsSync } from "node:fs";
|
|
8
8
|
import { realpathSync } from "node:fs";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
@@ -11,7 +11,7 @@ import { homedir, platform } from "node:os";
|
|
|
11
11
|
import path from "node:path";
|
|
12
12
|
import { spawn } from "node:child_process";
|
|
13
13
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
14
|
-
const VERSION = "0.1.
|
|
14
|
+
const VERSION = "0.1.7";
|
|
15
15
|
const DEFAULT_PORT = 17342;
|
|
16
16
|
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
17
17
|
const CODEXPORT_DIR = ".codexport";
|
|
@@ -114,6 +114,31 @@ async function writeJsonAtomic(filePath, value) {
|
|
|
114
114
|
await writeFile(tmpPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
115
115
|
await rename(tmpPath, filePath);
|
|
116
116
|
}
|
|
117
|
+
async function writeFileReplacingExisting(filePath, content, options) {
|
|
118
|
+
try {
|
|
119
|
+
await writeFile(filePath, content, options);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (!isPermissionError(error))
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
if (await pathExists(filePath)) {
|
|
127
|
+
try {
|
|
128
|
+
await chmod(filePath, 0o666);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (!isPermissionError(error))
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
await rm(filePath, { force: true });
|
|
135
|
+
}
|
|
136
|
+
await writeFile(filePath, content, options);
|
|
137
|
+
}
|
|
138
|
+
function isPermissionError(error) {
|
|
139
|
+
const code = error.code;
|
|
140
|
+
return code === "EACCES" || code === "EPERM";
|
|
141
|
+
}
|
|
117
142
|
function parseTomlObject(text, filePath) {
|
|
118
143
|
try {
|
|
119
144
|
const parsed = parseToml(text);
|
|
@@ -390,19 +415,21 @@ function rewritePortableTableKeys(table, sourceRoot, sourceHome) {
|
|
|
390
415
|
function rewritePortableMcpServer(_name, server, sourceRoot, sourceHome) {
|
|
391
416
|
if (typeof server.url === "string")
|
|
392
417
|
return;
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
418
|
+
const command = typeof server.command === "string" ? server.command : undefined;
|
|
419
|
+
const args = Array.isArray(server.args) ? server.args : [];
|
|
420
|
+
const launcher = command ? portableMcpLauncher(_name, command, args, sourceHome) : undefined;
|
|
421
|
+
if (launcher) {
|
|
422
|
+
server.command = launcher.command;
|
|
423
|
+
server.args = launcher.args.map((arg) => rewritePortablePath(arg, sourceRoot, sourceHome));
|
|
424
|
+
}
|
|
425
|
+
else if (command && isAbsoluteAnyPlatform(command)) {
|
|
426
|
+
server.enabled = false;
|
|
400
427
|
}
|
|
401
|
-
if (
|
|
402
|
-
server.command = rewritePortableCommand(
|
|
428
|
+
else if (command) {
|
|
429
|
+
server.command = rewritePortableCommand(command, sourceRoot);
|
|
403
430
|
}
|
|
404
|
-
if (
|
|
405
|
-
server.args =
|
|
431
|
+
if (!launcher && args.length) {
|
|
432
|
+
server.args = args.map((arg) => typeof arg === "string" ? rewritePortablePath(arg, sourceRoot, sourceHome) : arg);
|
|
406
433
|
}
|
|
407
434
|
if (server.env && typeof server.env === "object" && !Array.isArray(server.env)) {
|
|
408
435
|
for (const [key, value] of Object.entries(server.env)) {
|
|
@@ -412,6 +439,34 @@ function rewritePortableMcpServer(_name, server, sourceRoot, sourceHome) {
|
|
|
412
439
|
}
|
|
413
440
|
}
|
|
414
441
|
}
|
|
442
|
+
function portableMcpLauncher(name, command, args, sourceHome) {
|
|
443
|
+
const commandName = basenameAnyPlatform(command);
|
|
444
|
+
if (commandName === "npx" || commandName === "bunx" || commandName === "uvx") {
|
|
445
|
+
return allStrings(args) ? { command: commandName, args: args } : undefined;
|
|
446
|
+
}
|
|
447
|
+
const nodePackage = nodePackageFromServer(command, args) ?? workspacePackageFromServer(command, args, sourceHome);
|
|
448
|
+
if (nodePackage) {
|
|
449
|
+
return { command: "npx", args: ["-y", nodePackage.packageName, ...nodePackage.remainingArgs] };
|
|
450
|
+
}
|
|
451
|
+
const npmPackage = npmPackageForPortableMcp(name, commandName);
|
|
452
|
+
if (npmPackage) {
|
|
453
|
+
const remainingArgs = allStrings(args) ? args : [];
|
|
454
|
+
return { command: "npx", args: ["-y", npmPackage, ...remainingArgs] };
|
|
455
|
+
}
|
|
456
|
+
return undefined;
|
|
457
|
+
}
|
|
458
|
+
function npmPackageForPortableMcp(name, commandName) {
|
|
459
|
+
const knownPackages = {
|
|
460
|
+
"dora": "dora",
|
|
461
|
+
"kagi-mcp": "kagi-mcp",
|
|
462
|
+
"opensrc-mcp": "opensrc-mcp",
|
|
463
|
+
"opensrc-mcp-stdio": "opensrc-mcp",
|
|
464
|
+
"perplexity-webui": "perplexity-webui-mcp",
|
|
465
|
+
"perplexity-webui-mcp": "perplexity-webui-mcp",
|
|
466
|
+
"reddit-mcp-buddy": "reddit-mcp-buddy"
|
|
467
|
+
};
|
|
468
|
+
return knownPackages[name] ?? knownPackages[commandName];
|
|
469
|
+
}
|
|
415
470
|
function rewritePortableCommand(command, sourceRoot) {
|
|
416
471
|
const sourceRelative = rewriteSourceRootPath(command, sourceRoot);
|
|
417
472
|
if (sourceRelative !== command)
|
|
@@ -432,13 +487,31 @@ function nodePackageFromServer(command, args) {
|
|
|
432
487
|
const [entrypoint, ...remainingArgs] = args;
|
|
433
488
|
if (typeof entrypoint !== "string" || !isAbsoluteAnyPlatform(entrypoint))
|
|
434
489
|
return undefined;
|
|
435
|
-
if (!remainingArgs
|
|
490
|
+
if (!allStrings(remainingArgs))
|
|
436
491
|
return undefined;
|
|
437
492
|
const packageName = packageNameFromNodeModulesPath(entrypoint);
|
|
438
493
|
if (!packageName)
|
|
439
494
|
return undefined;
|
|
440
495
|
return { packageName, remainingArgs: remainingArgs };
|
|
441
496
|
}
|
|
497
|
+
function workspacePackageFromServer(command, args, sourceHome) {
|
|
498
|
+
if (basenameAnyPlatform(command) !== "node")
|
|
499
|
+
return undefined;
|
|
500
|
+
const [entrypoint, ...remainingArgs] = args;
|
|
501
|
+
if (!sourceHome || typeof entrypoint !== "string" || !isAbsoluteAnyPlatform(entrypoint) || !allStrings(remainingArgs))
|
|
502
|
+
return undefined;
|
|
503
|
+
const normalizedEntry = normalizePathForCompare(entrypoint);
|
|
504
|
+
const workspacePrefix = `${normalizePathForCompare(sourceHome)}/workspace/`;
|
|
505
|
+
if (!normalizedEntry.startsWith(workspacePrefix))
|
|
506
|
+
return undefined;
|
|
507
|
+
const packageName = normalizedEntry.slice(workspacePrefix.length).split("/")[0];
|
|
508
|
+
if (!packageName)
|
|
509
|
+
return undefined;
|
|
510
|
+
return { packageName, remainingArgs: remainingArgs };
|
|
511
|
+
}
|
|
512
|
+
function allStrings(values) {
|
|
513
|
+
return values.every((value) => typeof value === "string");
|
|
514
|
+
}
|
|
442
515
|
function packageNameFromNodeModulesPath(value) {
|
|
443
516
|
const parts = normalizePathForCompare(value).split("/");
|
|
444
517
|
const nodeModulesIndex = parts.lastIndexOf("node_modules");
|
|
@@ -556,7 +629,7 @@ async function applyBundle(ctx, bundle) {
|
|
|
556
629
|
await ensureDir(path.dirname(target));
|
|
557
630
|
if (file.path === "config.toml")
|
|
558
631
|
continue;
|
|
559
|
-
await
|
|
632
|
+
await writeFileReplacingExisting(target, decodeFile(file), { mode: file.mode });
|
|
560
633
|
}
|
|
561
634
|
const configEntry = bundle.files.find((file) => file.path === "config.toml");
|
|
562
635
|
if (configEntry) {
|
|
@@ -568,7 +641,7 @@ async function applyBundle(ctx, bundle) {
|
|
|
568
641
|
const backupPath = `${configPath}.codexport-backup-${new Date().toISOString().replace(/[:.]/g, "-")}`;
|
|
569
642
|
await writeFile(backupPath, await readFile(configPath));
|
|
570
643
|
}
|
|
571
|
-
await
|
|
644
|
+
await writeFileReplacingExisting(configPath, generated, "utf8");
|
|
572
645
|
}
|
|
573
646
|
const localSkillsDir = path.join(ctx.stateDir, "skills");
|
|
574
647
|
if (await pathExists(localSkillsDir)) {
|
|
@@ -589,7 +662,7 @@ async function copyDirectory(source, target) {
|
|
|
589
662
|
}
|
|
590
663
|
else if (entry.isFile()) {
|
|
591
664
|
await ensureDir(path.dirname(targetPath));
|
|
592
|
-
await
|
|
665
|
+
await writeFileReplacingExisting(targetPath, await readFile(sourcePath));
|
|
593
666
|
}
|
|
594
667
|
else if (entry.isSymbolicLink()) {
|
|
595
668
|
const linkTarget = await readlink(sourcePath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codexport",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "sync a canonical Codex setup from one master machine to follower machines",
|
|
5
5
|
"author": "Microck <contact@micr.dev>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"automation"
|
|
25
25
|
],
|
|
26
26
|
"bin": {
|
|
27
|
-
"codexport": "
|
|
27
|
+
"codexport": "dist/index.js"
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
30
|
"dist",
|