codexport 0.1.6 → 0.1.8

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.
Files changed (2) hide show
  1. package/dist/index.js +77 -13
  2. 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.1.6";
14
+ const VERSION = "0.1.8";
15
15
  const DEFAULT_PORT = 17342;
16
16
  const DEFAULT_TIMEOUT_MS = 5_000;
17
17
  const CODEXPORT_DIR = ".codexport";
@@ -415,19 +415,21 @@ function rewritePortableTableKeys(table, sourceRoot, sourceHome) {
415
415
  function rewritePortableMcpServer(_name, server, sourceRoot, sourceHome) {
416
416
  if (typeof server.url === "string")
417
417
  return;
418
- if (typeof server.command === "string" && Array.isArray(server.args)) {
419
- const nodePackage = nodePackageFromServer(server.command, server.args);
420
- if (nodePackage) {
421
- server.command = "npx";
422
- server.args = ["-y", nodePackage.packageName, ...nodePackage.remainingArgs.map((arg) => rewritePortablePath(arg, sourceRoot, sourceHome))];
423
- return;
424
- }
418
+ const command = typeof server.command === "string" ? server.command : undefined;
419
+ const args = Array.isArray(server.args) ? server.args : [];
420
+ const launcher = command && mcpHasRequiredPortableEnv(_name, command, server) ? 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;
425
427
  }
426
- if (typeof server.command === "string") {
427
- server.command = rewritePortableCommand(server.command, sourceRoot);
428
+ else if (command) {
429
+ server.command = rewritePortableCommand(command, sourceRoot);
428
430
  }
429
- if (Array.isArray(server.args)) {
430
- server.args = server.args.map((arg) => typeof arg === "string" ? rewritePortablePath(arg, sourceRoot, sourceHome) : arg);
431
+ if (!launcher && args.length) {
432
+ server.args = args.map((arg) => typeof arg === "string" ? rewritePortablePath(arg, sourceRoot, sourceHome) : arg);
431
433
  }
432
434
  if (server.env && typeof server.env === "object" && !Array.isArray(server.env)) {
433
435
  for (const [key, value] of Object.entries(server.env)) {
@@ -436,6 +438,50 @@ function rewritePortableMcpServer(_name, server, sourceRoot, sourceHome) {
436
438
  }
437
439
  }
438
440
  }
441
+ if (typeof server.command === "string" && !server.url) {
442
+ ensurePortablePathEnv(server);
443
+ }
444
+ }
445
+ function portableMcpLauncher(name, command, args, sourceHome) {
446
+ const commandName = basenameAnyPlatform(command);
447
+ if (commandName === "npx" || commandName === "bunx" || commandName === "uvx") {
448
+ return allStrings(args) ? { command: commandName, args: args } : undefined;
449
+ }
450
+ const nodePackage = nodePackageFromServer(command, args) ?? workspacePackageFromServer(command, args, sourceHome);
451
+ if (nodePackage) {
452
+ return { command: "npx", args: ["-y", nodePackage.packageName, ...nodePackage.remainingArgs] };
453
+ }
454
+ const npmPackage = npmPackageForPortableMcp(name, commandName);
455
+ if (npmPackage) {
456
+ const remainingArgs = allStrings(args) ? args : [];
457
+ return { command: "npx", args: ["-y", npmPackage, ...remainingArgs] };
458
+ }
459
+ return undefined;
460
+ }
461
+ function mcpHasRequiredPortableEnv(name, command, server) {
462
+ if (name !== "kagi-mcp" && basenameAnyPlatform(command) !== "kagi-mcp")
463
+ return true;
464
+ 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;
466
+ }
467
+ function ensurePortablePathEnv(server) {
468
+ const env = server.env && typeof server.env === "object" && !Array.isArray(server.env) ? server.env : {};
469
+ const existingPath = typeof env.PATH === "string" ? env.PATH : undefined;
470
+ const portableBins = ["${home}/.bun/bin", "${home}/.local/bin", "${home}/.cargo/bin", "${home}/go/bin"];
471
+ env.PATH = [...portableBins, existingPath ?? process.env.PATH ?? ""].filter(Boolean).join(path.delimiter);
472
+ server.env = env;
473
+ }
474
+ function npmPackageForPortableMcp(name, commandName) {
475
+ const knownPackages = {
476
+ "dora": "@butttons/dora",
477
+ "kagi-mcp": "kagi-mcp",
478
+ "opensrc-mcp": "opensrc-mcp",
479
+ "opensrc-mcp-stdio": "opensrc-mcp",
480
+ "perplexity-webui": "perplexity-webui-mcp",
481
+ "perplexity-webui-mcp": "perplexity-webui-mcp",
482
+ "reddit-mcp-buddy": "reddit-mcp-buddy"
483
+ };
484
+ return knownPackages[name] ?? knownPackages[commandName];
439
485
  }
440
486
  function rewritePortableCommand(command, sourceRoot) {
441
487
  const sourceRelative = rewriteSourceRootPath(command, sourceRoot);
@@ -457,13 +503,31 @@ function nodePackageFromServer(command, args) {
457
503
  const [entrypoint, ...remainingArgs] = args;
458
504
  if (typeof entrypoint !== "string" || !isAbsoluteAnyPlatform(entrypoint))
459
505
  return undefined;
460
- if (!remainingArgs.every((arg) => typeof arg === "string"))
506
+ if (!allStrings(remainingArgs))
461
507
  return undefined;
462
508
  const packageName = packageNameFromNodeModulesPath(entrypoint);
463
509
  if (!packageName)
464
510
  return undefined;
465
511
  return { packageName, remainingArgs: remainingArgs };
466
512
  }
513
+ function workspacePackageFromServer(command, args, sourceHome) {
514
+ if (basenameAnyPlatform(command) !== "node")
515
+ return undefined;
516
+ const [entrypoint, ...remainingArgs] = args;
517
+ if (!sourceHome || typeof entrypoint !== "string" || !isAbsoluteAnyPlatform(entrypoint) || !allStrings(remainingArgs))
518
+ return undefined;
519
+ const normalizedEntry = normalizePathForCompare(entrypoint);
520
+ const workspacePrefix = `${normalizePathForCompare(sourceHome)}/workspace/`;
521
+ if (!normalizedEntry.startsWith(workspacePrefix))
522
+ return undefined;
523
+ const packageName = normalizedEntry.slice(workspacePrefix.length).split("/")[0];
524
+ if (!packageName)
525
+ return undefined;
526
+ return { packageName, remainingArgs: remainingArgs };
527
+ }
528
+ function allStrings(values) {
529
+ return values.every((value) => typeof value === "string");
530
+ }
467
531
  function packageNameFromNodeModulesPath(value) {
468
532
  const parts = normalizePathForCompare(value).split("/");
469
533
  const nodeModulesIndex = parts.lastIndexOf("node_modules");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexport",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
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",