codexport 0.1.9 → 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/dist/index.js +50 -13
- package/package.json +1 -1
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.
|
|
14
|
+
const VERSION = "0.2.0";
|
|
15
15
|
const DEFAULT_PORT = 17342;
|
|
16
16
|
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
17
17
|
const CODEXPORT_DIR = ".codexport";
|
|
@@ -50,6 +50,11 @@ const EXCLUDE_PARTS = new Set([
|
|
|
50
50
|
".sqlite",
|
|
51
51
|
".sqlite3"
|
|
52
52
|
]);
|
|
53
|
+
const MCP_ENV_EXPORT_NAMES = [
|
|
54
|
+
"KAGI_API_KEY",
|
|
55
|
+
"KAGI_SESSION_TOKEN",
|
|
56
|
+
"KAGI_CLI_PROFILE"
|
|
57
|
+
];
|
|
53
58
|
class CliError extends Error {
|
|
54
59
|
exitCode;
|
|
55
60
|
details;
|
|
@@ -232,26 +237,37 @@ async function walkIncluded(root, absolute, files) {
|
|
|
232
237
|
});
|
|
233
238
|
}
|
|
234
239
|
}
|
|
235
|
-
function computeRevision(files) {
|
|
240
|
+
function computeRevision(files, sourceEnv = {}) {
|
|
236
241
|
const normalized = files.map((file) => ({
|
|
237
242
|
path: file.path,
|
|
238
243
|
mode: file.mode,
|
|
239
244
|
kind: file.kind,
|
|
240
245
|
contentHash: sha256(Buffer.from(file.content, "base64"))
|
|
241
246
|
}));
|
|
242
|
-
return sha256(JSON.stringify(normalized));
|
|
247
|
+
return sha256(JSON.stringify({ files: normalized, sourceEnv }));
|
|
243
248
|
}
|
|
244
249
|
async function buildBundle(codexDir) {
|
|
245
250
|
const files = await collectFiles(codexDir);
|
|
246
|
-
const
|
|
251
|
+
const sourceEnv = collectSourceEnv();
|
|
252
|
+
const revision = computeRevision(files, sourceEnv);
|
|
247
253
|
return {
|
|
248
254
|
version: 1,
|
|
249
255
|
builtAt: new Date().toISOString(),
|
|
250
256
|
sourceRoot: codexDir,
|
|
251
257
|
revision,
|
|
252
|
-
files
|
|
258
|
+
files,
|
|
259
|
+
sourceEnv
|
|
253
260
|
};
|
|
254
261
|
}
|
|
262
|
+
function collectSourceEnv() {
|
|
263
|
+
const sourceEnv = {};
|
|
264
|
+
for (const name of MCP_ENV_EXPORT_NAMES) {
|
|
265
|
+
const value = process.env[name];
|
|
266
|
+
if (value)
|
|
267
|
+
sourceEnv[name] = value;
|
|
268
|
+
}
|
|
269
|
+
return sourceEnv;
|
|
270
|
+
}
|
|
255
271
|
async function saveMasterBundle(ctx, bundle) {
|
|
256
272
|
await writeJsonAtomic(path.join(ctx.stateDir, LAST_BUNDLE_FILE), bundle);
|
|
257
273
|
}
|
|
@@ -327,7 +343,7 @@ function verifyBundle(bundle) {
|
|
|
327
343
|
if (bundle.version !== 1 || !Array.isArray(bundle.files)) {
|
|
328
344
|
throw new CliError("Bundle has an unsupported format.", 1);
|
|
329
345
|
}
|
|
330
|
-
const actualRevision = computeRevision(bundle.files);
|
|
346
|
+
const actualRevision = computeRevision(bundle.files, bundle.sourceEnv ?? {});
|
|
331
347
|
if (bundle.revision !== actualRevision) {
|
|
332
348
|
throw new CliError(`Bundle revision mismatch. Expected ${bundle.revision}, computed ${actualRevision}.`, 1);
|
|
333
349
|
}
|
|
@@ -361,8 +377,8 @@ function extractTomlTableNames(text, prefix) {
|
|
|
361
377
|
}
|
|
362
378
|
return names;
|
|
363
379
|
}
|
|
364
|
-
function mergeTomlText(canonical, localMcpText, localConfig, sourceRoot) {
|
|
365
|
-
const expandedCanonical = expandPathVariables(rewritePortableConfig(canonical, sourceRoot), localConfig);
|
|
380
|
+
function mergeTomlText(canonical, localMcpText, localConfig, sourceRoot, sourceEnv = {}) {
|
|
381
|
+
const expandedCanonical = expandPathVariables(rewritePortableConfig(canonical, sourceRoot, sourceEnv), localConfig);
|
|
366
382
|
if (!localMcpText?.trim())
|
|
367
383
|
return expandedCanonical;
|
|
368
384
|
const canonicalMcps = extractTomlTableNames(canonical, "mcp_servers");
|
|
@@ -374,7 +390,7 @@ function mergeTomlText(canonical, localMcpText, localConfig, sourceRoot) {
|
|
|
374
390
|
}
|
|
375
391
|
return `${expandedCanonical.trimEnd()}\n\n# Follower-local MCP overlay from ~/.codexport/mcps.local.toml\n${localMcpText.trim()}\n`;
|
|
376
392
|
}
|
|
377
|
-
function rewritePortableConfig(canonical, sourceRoot) {
|
|
393
|
+
function rewritePortableConfig(canonical, sourceRoot, sourceEnv = {}) {
|
|
378
394
|
let parsed;
|
|
379
395
|
try {
|
|
380
396
|
parsed = parseTomlObject(canonical, "canonical config.toml");
|
|
@@ -393,10 +409,21 @@ function rewritePortableConfig(canonical, sourceRoot) {
|
|
|
393
409
|
if (!rawServer || typeof rawServer !== "object" || Array.isArray(rawServer))
|
|
394
410
|
continue;
|
|
395
411
|
const server = rawServer;
|
|
412
|
+
mergeSourceEnvForMcp(name, server, sourceEnv);
|
|
396
413
|
rewritePortableMcpServer(name, server, sourceRoot, sourceHome);
|
|
397
414
|
}
|
|
398
415
|
return stringifyToml(parsed);
|
|
399
416
|
}
|
|
417
|
+
function mergeSourceEnvForMcp(name, server, sourceEnv) {
|
|
418
|
+
if (name !== "kagi-mcp")
|
|
419
|
+
return;
|
|
420
|
+
const env = server.env && typeof server.env === "object" && !Array.isArray(server.env) ? server.env : {};
|
|
421
|
+
for (const key of ["KAGI_API_KEY", "KAGI_SESSION_TOKEN", "KAGI_CLI_PROFILE"]) {
|
|
422
|
+
if (typeof env[key] !== "string" && sourceEnv[key])
|
|
423
|
+
env[key] = sourceEnv[key];
|
|
424
|
+
}
|
|
425
|
+
server.env = env;
|
|
426
|
+
}
|
|
400
427
|
function rewritePortableTableKeys(table, sourceRoot, sourceHome) {
|
|
401
428
|
const rewritten = {};
|
|
402
429
|
for (const [key, value] of Object.entries(table)) {
|
|
@@ -417,7 +444,7 @@ function rewritePortableMcpServer(_name, server, sourceRoot, sourceHome) {
|
|
|
417
444
|
return;
|
|
418
445
|
const command = typeof server.command === "string" ? server.command : undefined;
|
|
419
446
|
const args = Array.isArray(server.args) ? server.args : [];
|
|
420
|
-
const launcher = command && mcpHasRequiredPortableEnv(_name, command, server) ? portableMcpLauncher(_name, command, args, sourceHome) : undefined;
|
|
447
|
+
const launcher = command && mcpHasRequiredPortableEnv(_name, command, server) ? portableMcpLauncher(_name, command, args, sourceHome, server) : undefined;
|
|
421
448
|
if (launcher) {
|
|
422
449
|
server.command = launcher.command;
|
|
423
450
|
server.args = launcher.args.map((arg) => rewritePortablePath(arg, sourceRoot, sourceHome));
|
|
@@ -442,11 +469,20 @@ function rewritePortableMcpServer(_name, server, sourceRoot, sourceHome) {
|
|
|
442
469
|
ensurePortablePathEnv(server);
|
|
443
470
|
}
|
|
444
471
|
}
|
|
445
|
-
function portableMcpLauncher(name, command, args, sourceHome) {
|
|
472
|
+
function portableMcpLauncher(name, command, args, sourceHome, server) {
|
|
446
473
|
const commandName = basenameAnyPlatform(command);
|
|
447
474
|
if (commandName === "npx" || commandName === "bunx" || commandName === "uvx") {
|
|
448
475
|
return allStrings(args) ? { command: commandName, args: args } : undefined;
|
|
449
476
|
}
|
|
477
|
+
if (name === "kagi-mcp" || commandName === "kagi-mcp") {
|
|
478
|
+
const env = server.env && typeof server.env === "object" && !Array.isArray(server.env) ? server.env : {};
|
|
479
|
+
if (typeof env.KAGI_API_KEY === "string" && env.KAGI_API_KEY.length > 0) {
|
|
480
|
+
return { command: "npx", args: ["-y", "kagi-mcp"] };
|
|
481
|
+
}
|
|
482
|
+
if (typeof env.KAGI_SESSION_TOKEN === "string" && env.KAGI_SESSION_TOKEN.length > 0) {
|
|
483
|
+
return { command: "npx", args: ["-y", "kagi-cli", "mcp"] };
|
|
484
|
+
}
|
|
485
|
+
}
|
|
450
486
|
const nodePackage = nodePackageFromServer(command, args) ?? workspacePackageFromServer(command, args, sourceHome);
|
|
451
487
|
if (nodePackage) {
|
|
452
488
|
return { command: "npx", args: ["-y", nodePackage.packageName, ...nodePackage.remainingArgs] };
|
|
@@ -462,7 +498,8 @@ function mcpHasRequiredPortableEnv(name, command, server) {
|
|
|
462
498
|
if (name !== "kagi-mcp" && basenameAnyPlatform(command) !== "kagi-mcp")
|
|
463
499
|
return true;
|
|
464
500
|
const env = server.env && typeof server.env === "object" && !Array.isArray(server.env) ? server.env : undefined;
|
|
465
|
-
return typeof env?.KAGI_API_KEY === "string" && env.KAGI_API_KEY.length > 0
|
|
501
|
+
return (typeof env?.KAGI_API_KEY === "string" && env.KAGI_API_KEY.length > 0)
|
|
502
|
+
|| (typeof env?.KAGI_SESSION_TOKEN === "string" && env.KAGI_SESSION_TOKEN.length > 0);
|
|
466
503
|
}
|
|
467
504
|
function ensurePortablePathEnv(server) {
|
|
468
505
|
const env = server.env && typeof server.env === "object" && !Array.isArray(server.env) ? server.env : {};
|
|
@@ -654,7 +691,7 @@ async function applyBundle(ctx, bundle) {
|
|
|
654
691
|
if (configEntry) {
|
|
655
692
|
const canonicalConfig = decodeFile(configEntry).toString("utf8");
|
|
656
693
|
const localMcpText = await readTextIfExists(path.join(ctx.stateDir, MCPS_LOCAL_FILE));
|
|
657
|
-
const generated = mergeTomlText(canonicalConfig, localMcpText, { ...localConfig, codexDir: ctx.codexDir }, bundle.sourceRoot);
|
|
694
|
+
const generated = mergeTomlText(canonicalConfig, localMcpText, { ...localConfig, codexDir: ctx.codexDir }, bundle.sourceRoot, bundle.sourceEnv);
|
|
658
695
|
const configPath = path.join(ctx.codexDir, "config.toml");
|
|
659
696
|
if (await pathExists(configPath)) {
|
|
660
697
|
const backupPath = `${configPath}.codexport-backup-${new Date().toISOString().replace(/[:.]/g, "-")}`;
|