codexport 0.3.5 → 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 +39 -8
- 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") {
|
|
@@ -760,6 +759,7 @@ async function applyBundle(ctx, bundle) {
|
|
|
760
759
|
const localConfig = await readLocalConfig(ctx);
|
|
761
760
|
await assertSkillConflicts(ctx, bundle, localConfig);
|
|
762
761
|
await ensureDir(ctx.codexDir);
|
|
762
|
+
const managedMcpRunner = await writeManagedMcpRunner(ctx);
|
|
763
763
|
const nextFiles = new Set(bundle.files.map((file) => file.path));
|
|
764
764
|
const previousFiles = await readJsonIfExists(path.join(ctx.stateDir, APPLIED_FILES_FILE)) ?? [];
|
|
765
765
|
for (const previousFile of previousFiles) {
|
|
@@ -785,7 +785,15 @@ async function applyBundle(ctx, bundle) {
|
|
|
785
785
|
if (manifest) {
|
|
786
786
|
await writeJsonAtomic(path.join(ctx.stateDir, MCP_MANIFEST_FILE), manifest);
|
|
787
787
|
}
|
|
788
|
-
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);
|
|
789
797
|
const configPath = path.join(ctx.codexDir, "config.toml");
|
|
790
798
|
if (await pathExists(configPath)) {
|
|
791
799
|
const backupPath = `${configPath}.codexport-backup-${new Date().toISOString().replace(/[:.]/g, "-")}`;
|
|
@@ -802,6 +810,15 @@ async function applyBundle(ctx, bundle) {
|
|
|
802
810
|
await writeLocalConfig(ctx, { ...localConfig, lastRevision: bundle.revision, codexDir: ctx.codexDir });
|
|
803
811
|
await writeJsonAtomic(path.join(ctx.stateDir, APPLIED_FILES_FILE), bundle.files.map((file) => file.path));
|
|
804
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
|
+
}
|
|
805
822
|
async function commandMcpRun(ctx, name) {
|
|
806
823
|
const manifest = await readJsonIfExists(path.join(ctx.stateDir, MCP_MANIFEST_FILE));
|
|
807
824
|
if (!manifest?.servers?.[name]) {
|
|
@@ -1005,7 +1022,7 @@ async function installHook(ctx, timeoutMs) {
|
|
|
1005
1022
|
const existingText = await readTextIfExists(hooksPath);
|
|
1006
1023
|
const existing = existingText ? JSON.parse(existingText) : {};
|
|
1007
1024
|
const hooks = existing.SessionStart && Array.isArray(existing.SessionStart) ? existing.SessionStart : [];
|
|
1008
|
-
const command = `codexport sync --
|
|
1025
|
+
const command = `codexport hook sync --timeout-ms ${timeoutMs} --no-input`;
|
|
1009
1026
|
const filtered = hooks.filter((hook) => !(hook && typeof hook === "object" && hook.name === "codexport-sync"));
|
|
1010
1027
|
filtered.push({ name: "codexport-sync", command, timeoutMs });
|
|
1011
1028
|
await writeJsonAtomic(hooksPath, { ...existing, SessionStart: filtered });
|
|
@@ -1236,6 +1253,16 @@ async function commandSync(ctx, options) {
|
|
|
1236
1253
|
print(ctx, { status: "staged", revision: bundle.revision, files: bundle.files.length });
|
|
1237
1254
|
}
|
|
1238
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
|
+
}
|
|
1239
1266
|
async function commandApply(ctx) {
|
|
1240
1267
|
const bundle = await readCachedBundle(ctx);
|
|
1241
1268
|
await applyBundle(ctx, bundle);
|
|
@@ -1336,8 +1363,12 @@ async function main(argv) {
|
|
|
1336
1363
|
.action(async (options, command) => {
|
|
1337
1364
|
const ctx = contextFromCommand(command);
|
|
1338
1365
|
await installHook(ctx, options.timeoutMs);
|
|
1339
|
-
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` });
|
|
1340
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));
|
|
1341
1372
|
program.command("status")
|
|
1342
1373
|
.description("Show local enrollment state and remote revision reachability.")
|
|
1343
1374
|
.option("--timeout-ms <ms>", "network timeout", parsePositiveInt, 1_500)
|