skill-codex 0.7.1 → 0.8.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.
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // bin/skill-codex.ts
4
- import fs8 from "fs";
5
- import path10 from "path";
4
+ import fs11 from "fs";
5
+ import path13 from "path";
6
6
  import { fileURLToPath } from "url";
7
7
 
8
8
  // setup/setup.ts
@@ -479,14 +479,1180 @@ async function runUninstall() {
479
479
  log("[ok]", "Uninstall complete. Restart Claude Code to apply changes.");
480
480
  }
481
481
 
482
+ // src/server.ts
483
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
484
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
485
+ import {
486
+ CallToolRequestSchema,
487
+ ListToolsRequestSchema
488
+ } from "@modelcontextprotocol/sdk/types.js";
489
+
490
+ // src/tools/codex-exec.ts
491
+ import fs10 from "fs";
492
+ import path12 from "path";
493
+ import { z } from "zod";
494
+
495
+ // src/errors/errors.ts
496
+ var BridgeError = class extends Error {
497
+ code;
498
+ retryable;
499
+ constructor(message, code, retryable) {
500
+ super(message);
501
+ this.name = "BridgeError";
502
+ this.code = code;
503
+ this.retryable = retryable;
504
+ }
505
+ };
506
+ var CliNotFoundError = class extends BridgeError {
507
+ constructor(binary = "codex") {
508
+ super(
509
+ `${binary} CLI not found on PATH. Install it with: npm i -g @openai/codex`,
510
+ "CLI_NOT_FOUND",
511
+ false
512
+ );
513
+ this.name = "CliNotFoundError";
514
+ }
515
+ };
516
+ var AuthExpiredError = class extends BridgeError {
517
+ constructor() {
518
+ super(
519
+ "Codex authentication expired or not found. Run `codex login` to re-authenticate.",
520
+ "AUTH_EXPIRED",
521
+ false
522
+ );
523
+ this.name = "AuthExpiredError";
524
+ }
525
+ };
526
+ var RecursionLimitError = class extends BridgeError {
527
+ constructor(depth, max) {
528
+ super(
529
+ `Maximum bridge nesting depth reached (${depth} >= ${max}). This prevents infinite recursion between Claude and Codex.`,
530
+ "RECURSION_LIMIT",
531
+ false
532
+ );
533
+ this.name = "RecursionLimitError";
534
+ }
535
+ };
536
+ var LockConflictError = class extends BridgeError {
537
+ constructor(pid) {
538
+ super(
539
+ `Another skill-codex instance is running (PID ${pid}). Wait for it to finish or delete the lock file.`,
540
+ "LOCK_CONFLICT",
541
+ false
542
+ );
543
+ this.name = "LockConflictError";
544
+ }
545
+ };
546
+ var TimeoutError = class extends BridgeError {
547
+ constructor(timeoutMs) {
548
+ super(
549
+ `Codex timed out after ${Math.round(timeoutMs / 1e3)}s. Increase SKILL_CODEX_TIMEOUT_MS if needed.`,
550
+ "TIMEOUT",
551
+ true
552
+ );
553
+ this.name = "TimeoutError";
554
+ }
555
+ };
556
+ var RateLimitError = class extends BridgeError {
557
+ constructor() {
558
+ super(
559
+ "Codex rate limited (429). Will retry with backoff.",
560
+ "RATE_LIMIT",
561
+ true
562
+ );
563
+ this.name = "RateLimitError";
564
+ }
565
+ };
566
+ var ServerError = class extends BridgeError {
567
+ constructor(detail = "") {
568
+ super(
569
+ `Codex server error${detail ? `: ${detail}` : ""}. Will retry.`,
570
+ "SERVER_ERROR",
571
+ true
572
+ );
573
+ this.name = "ServerError";
574
+ }
575
+ };
576
+ var NetworkError = class extends BridgeError {
577
+ constructor(detail = "") {
578
+ super(
579
+ `Network error connecting to Codex${detail ? `: ${detail}` : ""}. Check your connection.`,
580
+ "NETWORK_ERROR",
581
+ true
582
+ );
583
+ this.name = "NetworkError";
584
+ }
585
+ };
586
+ var EmptyOutputError = class extends BridgeError {
587
+ constructor() {
588
+ super(
589
+ "Codex returned empty output. This may be a transient issue.",
590
+ "EMPTY_OUTPUT",
591
+ true
592
+ );
593
+ this.name = "EmptyOutputError";
594
+ }
595
+ };
596
+ var NotGitRepoError = class extends BridgeError {
597
+ constructor(cwd) {
598
+ super(
599
+ `Not a git repository: ${cwd}. This operation requires a git repo.`,
600
+ "NOT_GIT_REPO",
601
+ false
602
+ );
603
+ this.name = "NotGitRepoError";
604
+ }
605
+ };
606
+
607
+ // src/config/constants.ts
608
+ var MAX_BRIDGE_DEPTH = 2;
609
+ var BRIDGE_DEPTH_ENV = "SKILL_CODEX_DEPTH";
610
+ var DEFAULT_TIMEOUT_MS = 3e5;
611
+ var TIMEOUT_ENV = "SKILL_CODEX_TIMEOUT_MS";
612
+ var KILL_GRACE_MS = 5e3;
613
+ var HEARTBEAT_INTERVAL_MS = 1e4;
614
+ var MAX_RETRIES = 3;
615
+ var MAX_RETRIES_ENV = "SKILL_CODEX_MAX_RETRIES";
616
+ var RETRY_DELAYS_MS = [1e3, 2e3, 4e3];
617
+ var RETRY_CAP_MS = 1e4;
618
+ var MAX_RESPONSE_CHARS = 8e4;
619
+ var LOCK_STALE_MS = 9e5;
620
+ var LOCK_FILENAME = ".skill-codex.lock";
621
+ var LOG_ENV = "SKILL_CODEX_LOG";
622
+ var WINDOWS_SANDBOX_ENV = "SKILL_CODEX_WINDOWS_SANDBOX";
623
+ var WINDOWS_SANDBOX_DEFAULT = "unelevated";
624
+
625
+ // src/guards/check-recursion.ts
626
+ function getCurrentDepth() {
627
+ return parseInt(process.env[BRIDGE_DEPTH_ENV] ?? "0", 10);
628
+ }
629
+ function getNextDepth() {
630
+ return getCurrentDepth() + 1;
631
+ }
632
+ function checkRecursion() {
633
+ const depth = getCurrentDepth();
634
+ if (depth >= MAX_BRIDGE_DEPTH) {
635
+ throw new RecursionLimitError(depth, MAX_BRIDGE_DEPTH);
636
+ }
637
+ }
638
+
639
+ // src/guards/check-binary.ts
640
+ import which2 from "which";
641
+ var cachedBinaryPath = null;
642
+ function getCachedBinaryPath() {
643
+ return cachedBinaryPath;
644
+ }
645
+ async function checkBinary(binary = "codex") {
646
+ if (cachedBinaryPath !== null) {
647
+ return { found: true, path: cachedBinaryPath };
648
+ }
649
+ try {
650
+ const resolved = await which2(binary);
651
+ cachedBinaryPath = resolved;
652
+ return { found: true, path: resolved };
653
+ } catch {
654
+ throw new CliNotFoundError(binary);
655
+ }
656
+ }
657
+
658
+ // src/guards/check-auth.ts
659
+ import { execFile } from "child_process";
660
+
661
+ // src/runner/sandbox-args.ts
662
+ function getSandboxConfigArgs() {
663
+ if (process.platform !== "win32") return [];
664
+ const raw = process.env[WINDOWS_SANDBOX_ENV]?.trim() || WINDOWS_SANDBOX_DEFAULT;
665
+ const mode = /^[a-z-]+$/.test(raw) ? raw : WINDOWS_SANDBOX_DEFAULT;
666
+ return ["-c", `windows.sandbox=${mode}`];
667
+ }
668
+
669
+ // src/guards/check-auth.ts
670
+ var AUTH_CACHE_TTL_MS = 6e4;
671
+ var authCachedAt = null;
672
+ async function checkAuth() {
673
+ const now = Date.now();
674
+ if (authCachedAt !== null && now - authCachedAt < AUTH_CACHE_TTL_MS) {
675
+ return;
676
+ }
677
+ const binary = getCachedBinaryPath() ?? "codex";
678
+ return new Promise((resolve, reject) => {
679
+ const child = execFile(
680
+ binary,
681
+ ["exec", "--sandbox", "read-only", ...getSandboxConfigArgs(), "--skip-git-repo-check", "--ephemeral", "echo ok"],
682
+ { timeout: 3e4, shell: process.platform === "win32" },
683
+ (error, _stdout, stderr) => {
684
+ if (!error) {
685
+ authCachedAt = Date.now();
686
+ resolve();
687
+ return;
688
+ }
689
+ const lower = (stderr ?? error.message ?? "").toLowerCase();
690
+ if (error.code === "ENOENT" || error.code === "ENOENT") {
691
+ reject(new CliNotFoundError());
692
+ return;
693
+ }
694
+ if (error.killed) {
695
+ reject(new NetworkError("Auth check timed out \u2014 check your network connection"));
696
+ return;
697
+ }
698
+ if (["econnrefused", "econnreset", "etimedout", "network error", "fetch failed"].some((p) => lower.includes(p))) {
699
+ reject(new NetworkError("Network error during auth check"));
700
+ return;
701
+ }
702
+ reject(new AuthExpiredError());
703
+ }
704
+ );
705
+ child.stdin?.end();
706
+ });
707
+ }
708
+
709
+ // src/lock/lock-file.ts
710
+ import fs8 from "fs";
711
+ import path10 from "path";
712
+ import os2 from "os";
713
+ function getLockPath(cwd) {
714
+ return path10.join(cwd, LOCK_FILENAME);
715
+ }
716
+ function isProcessAlive(pid) {
717
+ try {
718
+ process.kill(pid, 0);
719
+ return true;
720
+ } catch {
721
+ return false;
722
+ }
723
+ }
724
+ function isLockStale(data) {
725
+ const age = Date.now() - data.timestamp;
726
+ if (age > LOCK_STALE_MS) return true;
727
+ if (!isProcessAlive(data.pid)) return true;
728
+ return false;
729
+ }
730
+ function tryRemoveStaleLock(lockPath) {
731
+ try {
732
+ const raw = fs8.readFileSync(lockPath, "utf-8");
733
+ const data = JSON.parse(raw);
734
+ if (isLockStale(data)) {
735
+ fs8.unlinkSync(lockPath);
736
+ return true;
737
+ }
738
+ throw new LockConflictError(data.pid);
739
+ } catch (err) {
740
+ if (err instanceof LockConflictError) throw err;
741
+ try {
742
+ fs8.unlinkSync(lockPath);
743
+ } catch {
744
+ }
745
+ return true;
746
+ }
747
+ }
748
+ function acquireLock(cwd) {
749
+ const lockPath = getLockPath(cwd);
750
+ const lockData = {
751
+ pid: process.pid,
752
+ timestamp: Date.now(),
753
+ hostname: os2.hostname()
754
+ };
755
+ const content = JSON.stringify(lockData, null, 2);
756
+ try {
757
+ fs8.writeFileSync(lockPath, content, { flag: "wx" });
758
+ } catch (err) {
759
+ const fsErr = err;
760
+ if (fsErr.code === "EEXIST") {
761
+ tryRemoveStaleLock(lockPath);
762
+ try {
763
+ fs8.writeFileSync(lockPath, content, { flag: "wx" });
764
+ } catch (retryErr) {
765
+ const retryFsErr = retryErr;
766
+ if (retryFsErr.code === "EEXIST") {
767
+ throw new LockConflictError(0);
768
+ }
769
+ throw retryErr;
770
+ }
771
+ } else {
772
+ throw err;
773
+ }
774
+ }
775
+ const onExit = () => {
776
+ try {
777
+ fs8.unlinkSync(lockPath);
778
+ } catch {
779
+ }
780
+ };
781
+ process.on("exit", onExit);
782
+ process.on("SIGINT", onExit);
783
+ process.on("SIGTERM", onExit);
784
+ const release = () => {
785
+ process.removeListener("exit", onExit);
786
+ process.removeListener("SIGINT", onExit);
787
+ process.removeListener("SIGTERM", onExit);
788
+ try {
789
+ fs8.unlinkSync(lockPath);
790
+ } catch {
791
+ }
792
+ };
793
+ return { release };
794
+ }
795
+
796
+ // src/guards/check-lock.ts
797
+ function checkLock(cwd) {
798
+ return acquireLock(cwd);
799
+ }
800
+
801
+ // src/guards/check-git.ts
802
+ import { execFileSync } from "child_process";
803
+ function checkGit(cwd) {
804
+ try {
805
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
806
+ cwd,
807
+ stdio: "pipe",
808
+ timeout: 5e3
809
+ });
810
+ return { isGitRepo: true };
811
+ } catch {
812
+ return { isGitRepo: false };
813
+ }
814
+ }
815
+
816
+ // src/guards/preflight.ts
817
+ async function runPreflight(options) {
818
+ checkRecursion();
819
+ await checkBinary();
820
+ if (!options.skipAuth && process.platform !== "win32") {
821
+ await checkAuth();
822
+ }
823
+ let lockHandle = null;
824
+ if (!options.skipLock) {
825
+ lockHandle = checkLock(options.cwd);
826
+ }
827
+ if (options.requireGit) {
828
+ const { isGitRepo } = checkGit(options.cwd);
829
+ if (!isGitRepo) {
830
+ lockHandle?.release();
831
+ throw new NotGitRepoError(options.cwd);
832
+ }
833
+ }
834
+ return { lockHandle };
835
+ }
836
+
837
+ // src/runner/exec-runner.ts
838
+ import { spawn } from "child_process";
839
+ import { StringDecoder } from "string_decoder";
840
+
841
+ // src/runner/timeout.ts
842
+ function setupTimeout(child, timeoutMs) {
843
+ let timer = null;
844
+ let graceTimer = null;
845
+ const promise = new Promise((_resolve, reject) => {
846
+ timer = setTimeout(() => {
847
+ if (isWindows()) {
848
+ child.kill();
849
+ } else {
850
+ child.kill("SIGTERM");
851
+ graceTimer = setTimeout(() => {
852
+ try {
853
+ if (!child.killed) {
854
+ child.kill("SIGKILL");
855
+ }
856
+ } catch {
857
+ }
858
+ }, KILL_GRACE_MS);
859
+ }
860
+ reject(new TimeoutError(timeoutMs));
861
+ }, timeoutMs);
862
+ });
863
+ const clear = () => {
864
+ if (timer) clearTimeout(timer);
865
+ if (graceTimer) clearTimeout(graceTimer);
866
+ };
867
+ return { clear, promise };
868
+ }
869
+
870
+ // src/util/truncate.ts
871
+ function truncateResponse(text, maxChars = MAX_RESPONSE_CHARS) {
872
+ if (text.length <= maxChars) return text;
873
+ const omitted = text.length - maxChars;
874
+ return text.slice(0, maxChars) + `
875
+
876
+ [Response truncated at ${maxChars} characters. ${omitted} characters omitted.]`;
877
+ }
878
+
879
+ // src/runner/output-parser.ts
880
+ function parseCodexOutput(raw) {
881
+ if (!raw.trim()) {
882
+ throw new EmptyOutputError();
883
+ }
884
+ const lines = raw.split("\n").filter((line) => line.trim());
885
+ const messages = [];
886
+ const activity = [];
887
+ let resultContent = null;
888
+ let sessionId;
889
+ let usage = null;
890
+ for (const line of lines) {
891
+ try {
892
+ const parsed = JSON.parse(line);
893
+ if (parsed.type === "thread.started" && typeof parsed.thread_id === "string") {
894
+ sessionId = parsed.thread_id;
895
+ continue;
896
+ }
897
+ if (parsed.type === "turn.completed" && parsed.usage) {
898
+ usage = {
899
+ input_tokens: parsed.usage.input_tokens ?? 0,
900
+ cached_input_tokens: parsed.usage.cached_input_tokens ?? 0,
901
+ output_tokens: parsed.usage.output_tokens ?? 0,
902
+ reasoning_output_tokens: parsed.usage.reasoning_output_tokens ?? 0
903
+ };
904
+ continue;
905
+ }
906
+ if (parsed.type === "result" && typeof parsed.content === "string") {
907
+ resultContent = parsed.content;
908
+ continue;
909
+ }
910
+ if (parsed.type === "message" && typeof parsed.content === "string") {
911
+ messages.push(parsed.content);
912
+ continue;
913
+ }
914
+ if (parsed.item?.type === "command_execution") {
915
+ const cmd = parsed.item;
916
+ const shortCmd = cmd.command?.length > 80 ? cmd.command.slice(0, 77) + "..." : cmd.command;
917
+ const statusIcon = cmd.status === "declined" ? "\u2718" : cmd.exit_code === 0 ? "\u2714" : cmd.exit_code !== null ? "\u2718" : "\u25B6";
918
+ const statusLabel = cmd.status === "declined" ? "blocked" : cmd.status === "in_progress" ? "running" : cmd.exit_code === 0 ? "ok" : `exit ${cmd.exit_code}`;
919
+ if (cmd.status !== "in_progress") {
920
+ activity.push({
921
+ type: "exec",
922
+ command: shortCmd,
923
+ icon: statusIcon,
924
+ status: statusLabel
925
+ });
926
+ }
927
+ continue;
928
+ }
929
+ if (parsed.item?.type === "agent_message" && typeof parsed.item.text === "string" && parsed.type !== "item.started" && parsed.type !== "item.updated") {
930
+ messages.push(parsed.item.text);
931
+ continue;
932
+ }
933
+ if (parsed.itemType === "agent_message" && typeof parsed.text === "string") {
934
+ messages.push(parsed.text);
935
+ continue;
936
+ }
937
+ if (parsed.item?.type === "file_read") {
938
+ activity.push({
939
+ type: "read",
940
+ path: parsed.item.path || "file",
941
+ icon: "\u25B6",
942
+ status: "read"
943
+ });
944
+ continue;
945
+ }
946
+ if (parsed.item?.type === "file_write" || parsed.item?.type === "file_edit") {
947
+ activity.push({
948
+ type: "write",
949
+ path: parsed.item.path || "file",
950
+ icon: "\u270E",
951
+ status: "write"
952
+ });
953
+ continue;
954
+ }
955
+ if (parsed.item?.type === "file_change") {
956
+ const changes = Array.isArray(parsed.item.changes) ? parsed.item.changes : null;
957
+ if (changes && changes.length > 0) {
958
+ for (const change of changes) {
959
+ activity.push({
960
+ type: "write",
961
+ path: change?.path || "file",
962
+ icon: "\u270E",
963
+ status: change?.kind || "write"
964
+ });
965
+ }
966
+ } else {
967
+ activity.push({
968
+ type: "write",
969
+ path: parsed.item.path || "file",
970
+ icon: "\u270E",
971
+ status: "write"
972
+ });
973
+ }
974
+ continue;
975
+ }
976
+ } catch {
977
+ }
978
+ }
979
+ let agentMessage;
980
+ if (resultContent !== null) {
981
+ agentMessage = resultContent;
982
+ } else if (messages.length > 0) {
983
+ agentMessage = messages.join("\n\n");
984
+ } else {
985
+ const substantiveLines = lines.filter(
986
+ (line) => !line.startsWith("OpenAI Codex") && !line.startsWith("---") && !line.startsWith("tokens used")
987
+ );
988
+ agentMessage = substantiveLines.join("\n").trim();
989
+ }
990
+ if (!agentMessage) {
991
+ throw new EmptyOutputError();
992
+ }
993
+ return {
994
+ content: truncateResponse(agentMessage),
995
+ activity,
996
+ usage,
997
+ raw,
998
+ sessionId
999
+ };
1000
+ }
1001
+
1002
+ // src/util/text.ts
1003
+ function oneLine(text, max) {
1004
+ const collapsed = text.replace(/\s+/g, " ").trim();
1005
+ return collapsed.length > max ? collapsed.slice(0, max - 1) + "\u2026" : collapsed;
1006
+ }
1007
+ function baseName(p) {
1008
+ if (!p) return "file";
1009
+ const parts = p.split(/[\\/]/).filter(Boolean);
1010
+ return parts.length > 0 ? parts[parts.length - 1] : p;
1011
+ }
1012
+
1013
+ // src/runner/progress.ts
1014
+ function formatProgressMessage(evt) {
1015
+ if (!evt || typeof evt !== "object") return null;
1016
+ const item = evt.item;
1017
+ if (item?.type === "command_execution") {
1018
+ const cmd = oneLine(String(item.command ?? ""), 60);
1019
+ if (item.status === "in_progress") return `running: ${cmd}`;
1020
+ if (item.status === "declined") return `blocked: ${cmd}`;
1021
+ if (item.exit_code === 0) return `ran: ${cmd}`;
1022
+ return `failed (exit ${String(item.exit_code)}): ${cmd}`;
1023
+ }
1024
+ if (item?.type === "file_read") return `reading ${baseName(String(item.path ?? "file"))}`;
1025
+ if (item?.type === "file_write" || item?.type === "file_edit") {
1026
+ return `editing ${baseName(String(item.path ?? "file"))}`;
1027
+ }
1028
+ if (item?.type === "file_change") {
1029
+ const changes = Array.isArray(item.changes) ? item.changes : null;
1030
+ if (changes && changes.length > 0) {
1031
+ return changes.length === 1 ? `editing ${baseName(String(changes[0]?.path ?? "file"))}` : `editing ${changes.length} files`;
1032
+ }
1033
+ return `editing ${baseName(String(item.path ?? "file"))}`;
1034
+ }
1035
+ if (item?.type === "reasoning" || evt.type === "turn.started") return "thinking\u2026";
1036
+ if (item?.type === "agent_message" && typeof item.text === "string" && evt.type !== "item.started" && evt.type !== "item.updated") {
1037
+ return "writing response\u2026";
1038
+ }
1039
+ return null;
1040
+ }
1041
+
1042
+ // src/util/live-logger.ts
1043
+ import fs9 from "fs";
1044
+ import os3 from "os";
1045
+ import path11 from "path";
1046
+ import { createHash } from "crypto";
1047
+ function resolveLogPath(cwd) {
1048
+ const override = process.env[LOG_ENV];
1049
+ if (override && override.trim()) return path11.resolve(override.trim());
1050
+ const base = path11.basename(cwd).replace(/[^a-zA-Z0-9._-]/g, "_") || "run";
1051
+ const hash = createHash("sha1").update(cwd).digest("hex").slice(0, 8);
1052
+ return path11.join(os3.tmpdir(), "skill-codex", `${base}-${hash}.log`);
1053
+ }
1054
+ function formatLogLines(evt) {
1055
+ if (!evt || typeof evt !== "object") return [];
1056
+ const item = evt.item;
1057
+ if (item?.type === "command_execution") {
1058
+ if (item.status === "in_progress") return [` $ ${oneLine(String(item.command ?? ""), 120)}`];
1059
+ if (item.status === "declined") return [" \u2718 blocked"];
1060
+ if (item.exit_code === 0) return [" \u2714 ok"];
1061
+ return [` \u2718 exit ${String(item.exit_code)}`];
1062
+ }
1063
+ if (item?.type === "file_read") return [` read ${String(item.path ?? "file")}`];
1064
+ if (item?.type === "file_write" || item?.type === "file_edit") {
1065
+ return [` write ${String(item.path ?? "file")}`];
1066
+ }
1067
+ if (item?.type === "file_change") {
1068
+ const changes = Array.isArray(item.changes) ? item.changes : null;
1069
+ if (changes && changes.length > 0) {
1070
+ return changes.map(
1071
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1072
+ (change) => ` write ${String(change?.path ?? "file")} (${String(change?.kind ?? "write")})`
1073
+ );
1074
+ }
1075
+ return [` write ${String(item.path ?? "file")}`];
1076
+ }
1077
+ if (item?.type === "agent_message" && typeof item.text === "string" && evt.type !== "item.started" && evt.type !== "item.updated") {
1078
+ return [` msg ${oneLine(item.text, 400)}`];
1079
+ }
1080
+ if (evt.type === "message" && typeof evt.content === "string") {
1081
+ return [` msg ${oneLine(evt.content, 400)}`];
1082
+ }
1083
+ if (evt.type === "turn.completed" && evt.usage) {
1084
+ const u = evt.usage;
1085
+ const reasoning = u.reasoning_output_tokens ?? 0;
1086
+ return [
1087
+ ` tokens: ${u.input_tokens ?? 0} in \u2192 ${u.output_tokens ?? 0} out${reasoning > 0 ? ` (+${reasoning} reasoning)` : ""}`
1088
+ ];
1089
+ }
1090
+ return [];
1091
+ }
1092
+ function createLiveLogger(opts) {
1093
+ const logPath = resolveLogPath(opts.cwd);
1094
+ let stream = null;
1095
+ try {
1096
+ fs9.mkdirSync(path11.dirname(logPath), { recursive: true });
1097
+ stream = fs9.createWriteStream(logPath, { flags: "a" });
1098
+ } catch {
1099
+ stream = null;
1100
+ }
1101
+ const line = (text) => {
1102
+ try {
1103
+ stream?.write(text + "\n");
1104
+ } catch {
1105
+ }
1106
+ };
1107
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1108
+ line("");
1109
+ line("=".repeat(60));
1110
+ line(`> codex ${opts.mode} ${startedAt}`);
1111
+ line(` cwd: ${opts.cwd}`);
1112
+ line(` task: ${oneLine(opts.prompt, 200)}`);
1113
+ line("-".repeat(60));
1114
+ const handleEvent = (raw) => {
1115
+ const trimmed = raw.trim();
1116
+ if (!trimmed) return;
1117
+ let evt;
1118
+ try {
1119
+ evt = JSON.parse(trimmed);
1120
+ } catch {
1121
+ return;
1122
+ }
1123
+ for (const l of formatLogLines(evt)) line(l);
1124
+ };
1125
+ let buffer = "";
1126
+ return {
1127
+ path: logPath,
1128
+ write(fragment) {
1129
+ buffer += fragment;
1130
+ let idx;
1131
+ while ((idx = buffer.indexOf("\n")) >= 0) {
1132
+ const lineStr = buffer.slice(0, idx);
1133
+ buffer = buffer.slice(idx + 1);
1134
+ handleEvent(lineStr);
1135
+ }
1136
+ },
1137
+ finish(summary) {
1138
+ if (buffer.trim()) {
1139
+ handleEvent(buffer);
1140
+ buffer = "";
1141
+ }
1142
+ line("-".repeat(60));
1143
+ line(`# done ${(/* @__PURE__ */ new Date()).toISOString()} ${summary}`);
1144
+ try {
1145
+ stream?.end();
1146
+ } catch {
1147
+ }
1148
+ }
1149
+ };
1150
+ }
1151
+
1152
+ // src/runner/exec-runner.ts
1153
+ function getTimeout(override) {
1154
+ if (override !== void 0) return override;
1155
+ const envVal = process.env[TIMEOUT_ENV];
1156
+ if (envVal) {
1157
+ const parsed = parseInt(envVal, 10);
1158
+ if (!isNaN(parsed) && parsed > 0) return parsed;
1159
+ }
1160
+ return DEFAULT_TIMEOUT_MS;
1161
+ }
1162
+ function classifyError(exitCode, stderr) {
1163
+ const lower = stderr.toLowerCase();
1164
+ if (lower.includes("unauthorized") || lower.includes("401") || lower.includes("api key")) {
1165
+ return new AuthExpiredError();
1166
+ }
1167
+ if (lower.includes("rate limit") || lower.includes("429") || lower.includes("too many requests")) {
1168
+ return new RateLimitError();
1169
+ }
1170
+ if (["500", "502", "503", "504", "internal server error", "bad gateway", "service unavailable"].some((p) => lower.includes(p))) {
1171
+ return new ServerError(stderr.slice(0, 200));
1172
+ }
1173
+ if (["econnreset", "econnrefused", "etimedout", "network error", "fetch failed", "socket hang up"].some((p) => lower.includes(p))) {
1174
+ return new NetworkError(stderr.slice(0, 200));
1175
+ }
1176
+ return new BridgeError(
1177
+ `Codex exited with code ${exitCode}: ${stderr.slice(0, 300)}`,
1178
+ "EXEC_FAILED",
1179
+ false
1180
+ );
1181
+ }
1182
+ async function execCodex(params) {
1183
+ const codexPath = getCachedBinaryPath();
1184
+ if (codexPath === null) {
1185
+ throw new CliNotFoundError();
1186
+ }
1187
+ return new Promise((resolve, reject) => {
1188
+ const timeoutMs = getTimeout(params.timeoutMs);
1189
+ let args2;
1190
+ let sendStdinPrompt;
1191
+ if (params.review) {
1192
+ args2 = ["exec", "review", "--json", "--skip-git-repo-check"];
1193
+ if (params.reviewBase) {
1194
+ args2.push("--base", params.reviewBase);
1195
+ sendStdinPrompt = false;
1196
+ } else if (params.reviewCommit) {
1197
+ args2.push("--commit", params.reviewCommit);
1198
+ sendStdinPrompt = false;
1199
+ } else if (params.prompt?.trim()) {
1200
+ sendStdinPrompt = true;
1201
+ } else {
1202
+ args2.push("--uncommitted");
1203
+ sendStdinPrompt = false;
1204
+ }
1205
+ } else if (params.sessionId) {
1206
+ args2 = ["exec", "resume", params.sessionId, "--json", "--skip-git-repo-check"];
1207
+ sendStdinPrompt = true;
1208
+ } else {
1209
+ const sandbox = params.sandbox ?? (params.mode === "full-auto" ? "workspace-write" : "read-only");
1210
+ args2 = ["exec", "--json", "--skip-git-repo-check", "--sandbox", sandbox];
1211
+ sendStdinPrompt = true;
1212
+ }
1213
+ if (params.model) {
1214
+ args2.push("-m", params.model);
1215
+ }
1216
+ if (params.reasoningEffort) {
1217
+ args2.push("-c", `model_reasoning_effort=${params.reasoningEffort}`);
1218
+ }
1219
+ args2.push(...getSandboxConfigArgs());
1220
+ const stdinPrompt = params.prompt ?? "";
1221
+ if (sendStdinPrompt) {
1222
+ args2.push("-");
1223
+ }
1224
+ const env = {
1225
+ ...process.env,
1226
+ [BRIDGE_DEPTH_ENV]: String(getNextDepth())
1227
+ };
1228
+ const child = spawn(codexPath, args2, {
1229
+ cwd: params.cwd,
1230
+ env,
1231
+ stdio: ["pipe", "pipe", "pipe"],
1232
+ shell: process.platform === "win32",
1233
+ windowsHide: true
1234
+ });
1235
+ const { clear: clearTimeout_, promise: timeoutPromise } = setupTimeout(child, timeoutMs);
1236
+ const startedAt = Date.now();
1237
+ const logger = createLiveLogger({
1238
+ cwd: params.cwd,
1239
+ mode: params.mode,
1240
+ prompt: params.prompt
1241
+ });
1242
+ process.stderr.write(`[skill-codex] live log: ${logger.path}
1243
+ `);
1244
+ let heartbeat = null;
1245
+ if (params.onProgress) {
1246
+ heartbeat = setInterval(() => {
1247
+ const secs = Math.round((Date.now() - startedAt) / 1e3);
1248
+ params.onProgress?.(`Codex working\u2026 ${secs}s elapsed`);
1249
+ }, HEARTBEAT_INTERVAL_MS);
1250
+ if (typeof heartbeat.unref === "function") heartbeat.unref();
1251
+ }
1252
+ const stopHeartbeat = () => {
1253
+ if (heartbeat) {
1254
+ clearInterval(heartbeat);
1255
+ heartbeat = null;
1256
+ }
1257
+ };
1258
+ const decoder = new StringDecoder("utf8");
1259
+ let progressBuf = "";
1260
+ const consumeForProgress = (text) => {
1261
+ if (!params.onProgress) return;
1262
+ progressBuf += text;
1263
+ let idx;
1264
+ while ((idx = progressBuf.indexOf("\n")) >= 0) {
1265
+ const lineStr = progressBuf.slice(0, idx).trim();
1266
+ progressBuf = progressBuf.slice(idx + 1);
1267
+ if (!lineStr) continue;
1268
+ try {
1269
+ const msg = formatProgressMessage(JSON.parse(lineStr));
1270
+ if (msg) params.onProgress(msg);
1271
+ } catch {
1272
+ }
1273
+ }
1274
+ };
1275
+ let logFinished = false;
1276
+ const finishLog = (summary) => {
1277
+ stopHeartbeat();
1278
+ if (logFinished) return;
1279
+ logFinished = true;
1280
+ try {
1281
+ logger.write(decoder.end());
1282
+ logger.finish(summary);
1283
+ } catch {
1284
+ }
1285
+ };
1286
+ const stdoutChunks = [];
1287
+ let stderr = "";
1288
+ child.stdout?.on("data", (chunk) => {
1289
+ stdoutChunks.push(chunk);
1290
+ const text = decoder.write(chunk);
1291
+ try {
1292
+ logger.write(text);
1293
+ } catch {
1294
+ }
1295
+ consumeForProgress(text);
1296
+ });
1297
+ child.stderr?.on("data", (chunk) => {
1298
+ stderr += chunk.toString();
1299
+ });
1300
+ if (sendStdinPrompt) {
1301
+ child.stdin?.write(stdinPrompt);
1302
+ }
1303
+ child.stdin?.end();
1304
+ const onClose = (exitCode) => {
1305
+ clearTimeout_();
1306
+ finishLog(exitCode === 0 || exitCode === null ? "ok" : `exit ${exitCode}`);
1307
+ const stdout = Buffer.concat(stdoutChunks).toString();
1308
+ if (exitCode === 0 || exitCode === null) {
1309
+ try {
1310
+ const result = parseCodexOutput(stdout);
1311
+ resolve({ ...result, logPath: logger.path, durationMs: Date.now() - startedAt });
1312
+ } catch (err) {
1313
+ reject(err);
1314
+ }
1315
+ return;
1316
+ }
1317
+ reject(classifyError(exitCode, stderr));
1318
+ };
1319
+ child.on("close", onClose);
1320
+ child.on("error", (err) => {
1321
+ clearTimeout_();
1322
+ finishLog("spawn error");
1323
+ if (err.code === "ENOENT") {
1324
+ reject(new CliNotFoundError());
1325
+ } else {
1326
+ reject(new BridgeError(`Failed to spawn codex: ${err.message}`, "SPAWN_ERROR", false));
1327
+ }
1328
+ });
1329
+ timeoutPromise.catch((err) => {
1330
+ finishLog("timeout");
1331
+ reject(err);
1332
+ });
1333
+ });
1334
+ }
1335
+
1336
+ // src/runner/retry.ts
1337
+ function getMaxRetries(override) {
1338
+ if (override !== void 0) return override;
1339
+ const envVal = process.env[MAX_RETRIES_ENV];
1340
+ if (envVal) {
1341
+ const parsed = parseInt(envVal, 10);
1342
+ if (!isNaN(parsed) && parsed >= 0) return parsed;
1343
+ }
1344
+ return MAX_RETRIES;
1345
+ }
1346
+ function getDelay(attempt) {
1347
+ const base = RETRY_DELAYS_MS[attempt] ?? RETRY_CAP_MS;
1348
+ const capped = Math.min(base, RETRY_CAP_MS);
1349
+ const jitter = 0.5 + Math.random();
1350
+ return Math.round(capped * jitter);
1351
+ }
1352
+ function defaultShouldRetry(err) {
1353
+ return err instanceof BridgeError && err.retryable;
1354
+ }
1355
+ function sleep(ms) {
1356
+ return new Promise((resolve) => setTimeout(resolve, ms));
1357
+ }
1358
+ async function withRetry(fn, options = {}) {
1359
+ const maxRetries = getMaxRetries(options.maxRetries);
1360
+ const shouldRetry = options.shouldRetry ?? defaultShouldRetry;
1361
+ for (let attempt = 0; ; attempt++) {
1362
+ try {
1363
+ return await fn();
1364
+ } catch (err) {
1365
+ const isRetryable = err instanceof Error && shouldRetry(err);
1366
+ if (attempt < maxRetries && isRetryable) {
1367
+ const delay = getDelay(attempt);
1368
+ const errorName = err instanceof Error ? err.constructor.name : "UnknownError";
1369
+ process.stderr.write(
1370
+ `[skill-codex] ${errorName} (attempt ${attempt + 1}/${maxRetries}), retrying in ${delay}ms...
1371
+ `
1372
+ );
1373
+ await sleep(delay);
1374
+ continue;
1375
+ }
1376
+ throw err;
1377
+ }
1378
+ }
1379
+ }
1380
+
1381
+ // src/tools/codex-exec.ts
1382
+ var TOOL_NAME = "codex_exec";
1383
+ var TOOL_DESCRIPTION = "Execute a task using OpenAI Codex CLI. Use for code review, implementation tasks, or getting a second opinion. Codex output is a SUGGESTION \u2014 evaluate it critically before applying.";
1384
+ var inputSchema = z.object({
1385
+ prompt: z.string().optional().describe("The task description for Codex"),
1386
+ mode: z.enum(["exec", "full-auto"]).default("exec").describe("exec = read-only with confirmation, full-auto = can write files"),
1387
+ sandbox: z.enum(["read-only", "workspace-write", "danger-full-access"]).optional().describe(
1388
+ "Explicit Codex sandbox policy; overrides mode. read-only = no writes, workspace-write = write within cwd, danger-full-access = unrestricted (use with care)."
1389
+ ),
1390
+ sessionId: z.string().regex(/^[A-Za-z0-9_-]{1,128}$/, "sessionId must be a Codex thread id (letters, digits, '-', '_')").optional().describe(
1391
+ "Resume a prior Codex session by its thread id (returned in a previous response) so Codex retains context across calls."
1392
+ ),
1393
+ model: z.string().regex(/^[A-Za-z0-9._-]{1,64}$/).optional().describe(
1394
+ "Codex model to use (e.g. gpt-5.5, gpt-5.4, gpt-5.4-mini). Omit to use Codex's configured default."
1395
+ ),
1396
+ reasoningEffort: z.enum(["minimal", "low", "medium", "high", "xhigh"]).optional().describe("How much reasoning effort Codex spends. Omit for the model's default."),
1397
+ review: z.boolean().optional().describe(
1398
+ "Run Codex's native diff-scoped review (`codex exec review`) instead of a freeform prompt. The prompt becomes optional custom review instructions."
1399
+ ),
1400
+ reviewBase: z.string().regex(/^[A-Za-z0-9._\/-]{1,128}$/).optional().describe("With review: diff against this base branch (default: uncommitted changes)."),
1401
+ reviewCommit: z.string().regex(/^[0-9a-fA-F]{4,64}$/).optional().describe("With review: review the changes introduced by this commit SHA."),
1402
+ cwd: z.string().optional().describe("Working directory (defaults to server cwd)"),
1403
+ timeoutMs: z.number().optional().describe("Override default timeout in milliseconds"),
1404
+ requireGit: z.boolean().default(false).describe("Fail if not inside a git repository")
1405
+ });
1406
+ var TOOL_INPUT_JSON_SCHEMA = {
1407
+ type: "object",
1408
+ properties: {
1409
+ prompt: { type: "string", description: "The task description for Codex" },
1410
+ mode: {
1411
+ type: "string",
1412
+ enum: ["exec", "full-auto"],
1413
+ default: "exec",
1414
+ description: "exec = read-only, full-auto = can write files"
1415
+ },
1416
+ sandbox: {
1417
+ type: "string",
1418
+ enum: ["read-only", "workspace-write", "danger-full-access"],
1419
+ description: "Explicit Codex sandbox policy; overrides mode. read-only = no writes, workspace-write = write within cwd, danger-full-access = unrestricted (use with care)."
1420
+ },
1421
+ sessionId: {
1422
+ type: "string",
1423
+ pattern: "^[A-Za-z0-9_-]{1,128}$",
1424
+ description: "Resume a prior Codex session by its thread id (returned in a previous response) so Codex retains context across calls."
1425
+ },
1426
+ model: {
1427
+ type: "string",
1428
+ pattern: "^[A-Za-z0-9._-]{1,64}$",
1429
+ description: "Codex model to use (e.g. gpt-5.5, gpt-5.4, gpt-5.4-mini). Omit to use Codex's configured default."
1430
+ },
1431
+ reasoningEffort: {
1432
+ type: "string",
1433
+ enum: ["minimal", "low", "medium", "high", "xhigh"],
1434
+ description: "How much reasoning effort Codex spends. Omit for the model's default."
1435
+ },
1436
+ review: {
1437
+ type: "boolean",
1438
+ description: "Run Codex's native diff-scoped review (`codex exec review`) instead of a freeform prompt. The prompt becomes optional custom review instructions."
1439
+ },
1440
+ reviewBase: {
1441
+ type: "string",
1442
+ pattern: "^[A-Za-z0-9._\\/-]{1,128}$",
1443
+ description: "With review: diff against this base branch (default: uncommitted changes)."
1444
+ },
1445
+ reviewCommit: {
1446
+ type: "string",
1447
+ pattern: "^[0-9a-fA-F]{4,64}$",
1448
+ description: "With review: review the changes introduced by this commit SHA."
1449
+ },
1450
+ cwd: { type: "string", description: "Working directory (defaults to server cwd)" },
1451
+ timeoutMs: { type: "number", description: "Override default timeout in milliseconds" },
1452
+ requireGit: {
1453
+ type: "boolean",
1454
+ default: false,
1455
+ description: "Fail if not inside a git repository"
1456
+ }
1457
+ },
1458
+ required: []
1459
+ };
1460
+ function formatError(err) {
1461
+ if (err instanceof BridgeError) {
1462
+ return `[skill-codex error: ${err.code}] ${err.message}`;
1463
+ }
1464
+ if (err instanceof Error) {
1465
+ return `[skill-codex error] ${err.message}`;
1466
+ }
1467
+ return `[skill-codex error] Unknown error: ${String(err)}`;
1468
+ }
1469
+ function formatRichResponse(result, input, cwd) {
1470
+ const lines = [];
1471
+ const sandboxLabel = input.sandbox ?? (input.mode === "full-auto" ? "workspace-write" : "read-only");
1472
+ const label = input.review ? "review" : input.sessionId ? "resumed" : sandboxLabel;
1473
+ const metaParts = [label];
1474
+ if (input.model) {
1475
+ metaParts.push(input.model);
1476
+ }
1477
+ if (input.reasoningEffort) {
1478
+ metaParts.push(`effort:${input.reasoningEffort}`);
1479
+ }
1480
+ metaParts.push(cwd);
1481
+ if (typeof result.durationMs === "number") {
1482
+ metaParts.push(`${(result.durationMs / 1e3).toFixed(1)}s`);
1483
+ }
1484
+ if (result.usage) {
1485
+ const {
1486
+ input_tokens: inp,
1487
+ output_tokens: out,
1488
+ cached_input_tokens: cached,
1489
+ reasoning_output_tokens: reasoning
1490
+ } = result.usage;
1491
+ metaParts.push(
1492
+ `${inp} tok in${cached > 0 ? ` (${cached} cached)` : ""} \u2192 ${out} out${reasoning > 0 ? ` (+${reasoning} reasoning)` : ""}`
1493
+ );
1494
+ }
1495
+ lines.push(`[${metaParts.join(" \u2502 ")}]`);
1496
+ if (result.sessionId) {
1497
+ lines.push(` session: ${result.sessionId} (pass as sessionId to continue this conversation)`);
1498
+ }
1499
+ if (result.logPath) {
1500
+ lines.push(` live log: ${result.logPath}`);
1501
+ }
1502
+ if (result.activity.length > 0) {
1503
+ for (const a of result.activity) {
1504
+ if (a.type === "exec") {
1505
+ lines.push(` ${a.icon} exec: ${a.command} (${a.status})`);
1506
+ } else if (a.type === "read") {
1507
+ lines.push(` \u25B6 read: ${a.path}`);
1508
+ } else if (a.type === "write") {
1509
+ lines.push(` \u270E write: ${a.path}`);
1510
+ }
1511
+ }
1512
+ }
1513
+ lines.push("");
1514
+ lines.push(result.content);
1515
+ return lines.join("\n");
1516
+ }
1517
+ async function handleCodexExec(input, serverCwd, onProgress) {
1518
+ const rawCwd = input.cwd ?? serverCwd;
1519
+ const cwd = path12.resolve(rawCwd);
1520
+ if (!fs10.existsSync(cwd) || !fs10.statSync(cwd).isDirectory()) {
1521
+ return {
1522
+ content: [{ type: "text", text: `[skill-codex error: INVALID_CWD] cwd is not an existing directory: ${cwd}` }],
1523
+ isError: true
1524
+ };
1525
+ }
1526
+ const optError = (msg) => ({
1527
+ content: [{ type: "text", text: `[skill-codex error: INVALID_OPTIONS] ${msg}` }],
1528
+ isError: true
1529
+ });
1530
+ if (input.review && input.sessionId) return optError("review and sessionId are mutually exclusive");
1531
+ if (input.reviewBase && input.reviewCommit) return optError("reviewBase and reviewCommit are mutually exclusive");
1532
+ if ((input.reviewBase ?? input.reviewCommit) && !input.review) {
1533
+ return optError("reviewBase/reviewCommit require review: true");
1534
+ }
1535
+ if (input.review && (input.reviewBase ?? input.reviewCommit) && input.prompt?.trim()) {
1536
+ return optError(
1537
+ "a review target (reviewBase/reviewCommit) can't be combined with a prompt \u2014 Codex review takes a target OR instructions, not both"
1538
+ );
1539
+ }
1540
+ if (!input.review && !input.prompt?.trim()) {
1541
+ return {
1542
+ content: [{ type: "text", text: "[skill-codex error: MISSING_PROMPT] prompt is required unless review is set" }],
1543
+ isError: true
1544
+ };
1545
+ }
1546
+ let lockRelease = null;
1547
+ try {
1548
+ const { lockHandle } = await runPreflight({
1549
+ cwd,
1550
+ requireGit: input.requireGit
1551
+ });
1552
+ lockRelease = lockHandle?.release ?? null;
1553
+ const result = await withRetry(
1554
+ () => execCodex({
1555
+ prompt: input.prompt ?? "",
1556
+ cwd,
1557
+ mode: input.mode,
1558
+ sandbox: input.sandbox,
1559
+ sessionId: input.sessionId,
1560
+ model: input.model,
1561
+ reasoningEffort: input.reasoningEffort,
1562
+ review: input.review,
1563
+ reviewBase: input.reviewBase,
1564
+ reviewCommit: input.reviewCommit,
1565
+ timeoutMs: input.timeoutMs,
1566
+ onProgress
1567
+ })
1568
+ );
1569
+ const formatted = formatRichResponse(result, input, cwd);
1570
+ return {
1571
+ content: [{ type: "text", text: formatted }]
1572
+ };
1573
+ } catch (err) {
1574
+ return {
1575
+ content: [{ type: "text", text: formatError(err) }],
1576
+ isError: true
1577
+ };
1578
+ } finally {
1579
+ lockRelease?.();
1580
+ }
1581
+ }
1582
+
1583
+ // src/server.ts
1584
+ function createServer(cwd) {
1585
+ const server = new Server(
1586
+ { name: "skill-codex", version: "0.8.0" },
1587
+ { capabilities: { tools: {} } }
1588
+ );
1589
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
1590
+ tools: [
1591
+ {
1592
+ name: TOOL_NAME,
1593
+ description: TOOL_DESCRIPTION,
1594
+ inputSchema: TOOL_INPUT_JSON_SCHEMA
1595
+ }
1596
+ ]
1597
+ }));
1598
+ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
1599
+ if (request.params.name !== TOOL_NAME) {
1600
+ return {
1601
+ content: [{ type: "text", text: `Unknown tool: ${request.params.name}` }],
1602
+ isError: true
1603
+ };
1604
+ }
1605
+ const parsed = inputSchema.safeParse(request.params.arguments);
1606
+ if (!parsed.success) {
1607
+ return {
1608
+ content: [
1609
+ {
1610
+ type: "text",
1611
+ text: `Invalid input: ${parsed.error.issues.map((i) => i.message).join(", ")}`
1612
+ }
1613
+ ],
1614
+ isError: true
1615
+ };
1616
+ }
1617
+ const progressToken = request.params._meta?.progressToken;
1618
+ let progressCounter = 0;
1619
+ const onProgress = progressToken === void 0 ? void 0 : (message) => {
1620
+ progressCounter += 1;
1621
+ void extra.sendNotification({
1622
+ method: "notifications/progress",
1623
+ params: { progressToken, progress: progressCounter, message }
1624
+ }).catch(() => {
1625
+ });
1626
+ };
1627
+ return handleCodexExec(parsed.data, cwd, onProgress);
1628
+ });
1629
+ return server;
1630
+ }
1631
+ async function startServer() {
1632
+ const cwd = process.cwd();
1633
+ const server = createServer(cwd);
1634
+ const transport = new StdioServerTransport();
1635
+ process.stderr.write("[skill-codex] MCP server starting...\n");
1636
+ await server.connect(transport);
1637
+ process.stderr.write("[skill-codex] MCP server connected via stdio\n");
1638
+ process.on("uncaughtException", (err) => {
1639
+ process.stderr.write(`[skill-codex] Uncaught exception: ${err.message}
1640
+ `);
1641
+ });
1642
+ process.on("unhandledRejection", (reason) => {
1643
+ process.stderr.write(`[skill-codex] Unhandled rejection: ${String(reason)}
1644
+ `);
1645
+ });
1646
+ }
1647
+
482
1648
  // bin/skill-codex.ts
483
1649
  var args = process.argv.slice(2);
484
1650
  var command = args[0];
485
1651
  function getVersion() {
486
- const __dirname = path10.dirname(fileURLToPath(import.meta.url));
487
- const pkgPath = path10.resolve(__dirname, "..", "package.json");
1652
+ const __dirname = path13.dirname(fileURLToPath(import.meta.url));
1653
+ const pkgPath = path13.resolve(__dirname, "..", "package.json");
488
1654
  try {
489
- const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
1655
+ const pkg = JSON.parse(fs11.readFileSync(pkgPath, "utf-8"));
490
1656
  return pkg.version ?? "0.0.0";
491
1657
  } catch {
492
1658
  return "0.0.0";
@@ -535,6 +1701,11 @@ Usage:
535
1701
  await runUninstall();
536
1702
  break;
537
1703
  }
1704
+ case "mcp":
1705
+ case "serve": {
1706
+ await startServer();
1707
+ break;
1708
+ }
538
1709
  default: {
539
1710
  const version = getVersion();
540
1711
  process.stdout.write(`skill-codex v${version}