codexport 0.3.4 → 0.3.6
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 +3 -3
- package/dist/index.js +48 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
the master serves a content-hashed bundle from its `~/.codex` directory. followers pin the master's fingerprint on join, fetch updates over a Tailscale-reachable HTTP address, and apply updates at Codex `SessionStart` through a short best-effort hook.
|
|
14
14
|
|
|
15
|
-
MCPs are exported as full definitions, including env needed by supported tools. command-based MCPs are written through a managed launcher, so followers run `
|
|
15
|
+
MCPs are exported as full definitions, including env needed by supported tools. command-based MCPs are written through a quiet local managed launcher, so followers run `node ~/.codexport/bin/codexport-mcp-run.mjs mcp run <name>` and let `codexport` translate master-local paths into repairable package, Python tool, or source-built launchers.
|
|
16
16
|
|
|
17
17
|
[npm](https://www.npmjs.com/package/codexport) | [github](https://github.com/Microck/codexport)
|
|
18
18
|
|
|
@@ -123,8 +123,8 @@ all master MCP definitions are exported into `~/.codexport/mcp-manifest.json` on
|
|
|
123
123
|
|
|
124
124
|
```toml
|
|
125
125
|
[mcp_servers.example]
|
|
126
|
-
command = "
|
|
127
|
-
args = [ "-
|
|
126
|
+
command = "node"
|
|
127
|
+
args = [ "~/.codexport/bin/codexport-mcp-run.mjs", "mcp", "run", "example" ]
|
|
128
128
|
```
|
|
129
129
|
|
|
130
130
|
when Codex starts an MCP, `codexport mcp run` reads the original manifest entry, restores transferred environment values, rewrites master paths to follower paths, and chooses a runnable target:
|
package/dist/index.js
CHANGED
|
@@ -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.3.
|
|
14
|
+
const VERSION = "0.3.6";
|
|
15
15
|
const DEFAULT_PORT = 17342;
|
|
16
16
|
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
17
17
|
const CODEXPORT_DIR = ".codexport";
|
|
@@ -22,7 +22,7 @@ const LAST_BUNDLE_FILE = "last-bundle.json";
|
|
|
22
22
|
const CACHE_BUNDLE_FILE = "bundle.json";
|
|
23
23
|
const APPLIED_FILES_FILE = "applied-files.json";
|
|
24
24
|
const MCP_MANIFEST_FILE = "mcp-manifest.json";
|
|
25
|
-
const
|
|
25
|
+
const MANAGED_MCP_RUNNER_FILE = "codexport-mcp-run.mjs";
|
|
26
26
|
const INCLUDE_ROOTS = [
|
|
27
27
|
"AGENTS.md",
|
|
28
28
|
"RTK.md",
|
|
@@ -465,9 +465,8 @@ function rewritePortableTableKeys(table, sourceRoot, sourceHome) {
|
|
|
465
465
|
function rewriteManagedMcpServer(name, server, sourceRoot, sourceHome) {
|
|
466
466
|
if (typeof server.url === "string")
|
|
467
467
|
return;
|
|
468
|
-
|
|
469
|
-
server.
|
|
470
|
-
server.args = ["-y", MANAGED_MCP_PACKAGE, "mcp", "run", name];
|
|
468
|
+
server.command = "${node}";
|
|
469
|
+
server.args = ["${codexportMcpRunner}", "mcp", "run", name];
|
|
471
470
|
if (server.env && typeof server.env === "object" && !Array.isArray(server.env)) {
|
|
472
471
|
for (const [key, value] of Object.entries(server.env)) {
|
|
473
472
|
if (typeof value === "string") {
|
|
@@ -499,7 +498,15 @@ function portableMcpLauncher(name, command, args, sourceHome, server) {
|
|
|
499
498
|
}
|
|
500
499
|
if (name === "discord-py-self" || commandName === "discord-py-self-mcp") {
|
|
501
500
|
const remainingArgs = allStrings(args) ? args : [];
|
|
502
|
-
return {
|
|
501
|
+
return {
|
|
502
|
+
command: "uvx",
|
|
503
|
+
args: ["--from", "git+https://github.com/Microck/discord.py-self-mcp.git", "discord-py-self-mcp", ...remainingArgs],
|
|
504
|
+
repair: {
|
|
505
|
+
whenMissing: "uvx",
|
|
506
|
+
command: "__codexport_install_uv",
|
|
507
|
+
args: []
|
|
508
|
+
}
|
|
509
|
+
};
|
|
503
510
|
}
|
|
504
511
|
if (name === "qmd" || commandName === "qmd") {
|
|
505
512
|
const remainingArgs = allStrings(args) ? args : [];
|
|
@@ -752,6 +759,7 @@ async function applyBundle(ctx, bundle) {
|
|
|
752
759
|
const localConfig = await readLocalConfig(ctx);
|
|
753
760
|
await assertSkillConflicts(ctx, bundle, localConfig);
|
|
754
761
|
await ensureDir(ctx.codexDir);
|
|
762
|
+
const managedMcpRunner = await writeManagedMcpRunner(ctx);
|
|
755
763
|
const nextFiles = new Set(bundle.files.map((file) => file.path));
|
|
756
764
|
const previousFiles = await readJsonIfExists(path.join(ctx.stateDir, APPLIED_FILES_FILE)) ?? [];
|
|
757
765
|
for (const previousFile of previousFiles) {
|
|
@@ -777,7 +785,15 @@ async function applyBundle(ctx, bundle) {
|
|
|
777
785
|
if (manifest) {
|
|
778
786
|
await writeJsonAtomic(path.join(ctx.stateDir, MCP_MANIFEST_FILE), manifest);
|
|
779
787
|
}
|
|
780
|
-
const generated = mergeTomlText(canonicalConfig, localMcpText, {
|
|
788
|
+
const generated = mergeTomlText(canonicalConfig, localMcpText, {
|
|
789
|
+
...localConfig,
|
|
790
|
+
codexDir: ctx.codexDir,
|
|
791
|
+
pathVariables: {
|
|
792
|
+
...(localConfig.pathVariables ?? {}),
|
|
793
|
+
node: process.execPath,
|
|
794
|
+
codexportMcpRunner: managedMcpRunner
|
|
795
|
+
}
|
|
796
|
+
}, bundle.sourceRoot, bundle.sourceEnv);
|
|
781
797
|
const configPath = path.join(ctx.codexDir, "config.toml");
|
|
782
798
|
if (await pathExists(configPath)) {
|
|
783
799
|
const backupPath = `${configPath}.codexport-backup-${new Date().toISOString().replace(/[:.]/g, "-")}`;
|
|
@@ -794,6 +810,15 @@ async function applyBundle(ctx, bundle) {
|
|
|
794
810
|
await writeLocalConfig(ctx, { ...localConfig, lastRevision: bundle.revision, codexDir: ctx.codexDir });
|
|
795
811
|
await writeJsonAtomic(path.join(ctx.stateDir, APPLIED_FILES_FILE), bundle.files.map((file) => file.path));
|
|
796
812
|
}
|
|
813
|
+
async function writeManagedMcpRunner(ctx) {
|
|
814
|
+
const binDir = path.join(ctx.stateDir, "bin");
|
|
815
|
+
const runnerPath = path.join(binDir, MANAGED_MCP_RUNNER_FILE);
|
|
816
|
+
await ensureDir(binDir);
|
|
817
|
+
await writeFileReplacingExisting(runnerPath, await readFile(fileURLToPath(import.meta.url)), { mode: 0o755 });
|
|
818
|
+
if (platform() !== "win32")
|
|
819
|
+
await chmod(runnerPath, 0o755);
|
|
820
|
+
return runnerPath;
|
|
821
|
+
}
|
|
797
822
|
async function commandMcpRun(ctx, name) {
|
|
798
823
|
const manifest = await readJsonIfExists(path.join(ctx.stateDir, MCP_MANIFEST_FILE));
|
|
799
824
|
if (!manifest?.servers?.[name]) {
|
|
@@ -997,7 +1022,7 @@ async function installHook(ctx, timeoutMs) {
|
|
|
997
1022
|
const existingText = await readTextIfExists(hooksPath);
|
|
998
1023
|
const existing = existingText ? JSON.parse(existingText) : {};
|
|
999
1024
|
const hooks = existing.SessionStart && Array.isArray(existing.SessionStart) ? existing.SessionStart : [];
|
|
1000
|
-
const command = `codexport sync --
|
|
1025
|
+
const command = `codexport hook sync --timeout-ms ${timeoutMs} --no-input`;
|
|
1001
1026
|
const filtered = hooks.filter((hook) => !(hook && typeof hook === "object" && hook.name === "codexport-sync"));
|
|
1002
1027
|
filtered.push({ name: "codexport-sync", command, timeoutMs });
|
|
1003
1028
|
await writeJsonAtomic(hooksPath, { ...existing, SessionStart: filtered });
|
|
@@ -1228,6 +1253,16 @@ async function commandSync(ctx, options) {
|
|
|
1228
1253
|
print(ctx, { status: "staged", revision: bundle.revision, files: bundle.files.length });
|
|
1229
1254
|
}
|
|
1230
1255
|
}
|
|
1256
|
+
async function commandHookSync(ctx, options) {
|
|
1257
|
+
try {
|
|
1258
|
+
await commandSync(ctx, { apply: true, timeoutMs: options.timeoutMs });
|
|
1259
|
+
}
|
|
1260
|
+
catch (error) {
|
|
1261
|
+
if (!ctx.quiet) {
|
|
1262
|
+
process.stderr.write(`codexport hook sync skipped: ${asError(error).message}\n`);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1231
1266
|
async function commandApply(ctx) {
|
|
1232
1267
|
const bundle = await readCachedBundle(ctx);
|
|
1233
1268
|
await applyBundle(ctx, bundle);
|
|
@@ -1328,8 +1363,12 @@ async function main(argv) {
|
|
|
1328
1363
|
.action(async (options, command) => {
|
|
1329
1364
|
const ctx = contextFromCommand(command);
|
|
1330
1365
|
await installHook(ctx, options.timeoutMs);
|
|
1331
|
-
print(ctx, { installed: true, hook: "SessionStart", command: `codexport sync --
|
|
1366
|
+
print(ctx, { installed: true, hook: "SessionStart", command: `codexport hook sync --timeout-ms ${options.timeoutMs} --no-input` });
|
|
1332
1367
|
});
|
|
1368
|
+
hook.command("sync")
|
|
1369
|
+
.description("Best-effort follower sync for Codex SessionStart hooks.")
|
|
1370
|
+
.option("--timeout-ms <ms>", "hook sync timeout", parsePositiveInt, 3_000)
|
|
1371
|
+
.action(async (options, command) => commandHookSync(contextFromCommand(command), options));
|
|
1333
1372
|
program.command("status")
|
|
1334
1373
|
.description("Show local enrollment state and remote revision reachability.")
|
|
1335
1374
|
.option("--timeout-ms <ms>", "network timeout", parsePositiveInt, 1_500)
|