@seqyuan/annodex 0.1.53 → 0.1.54

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 (71) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +6 -6
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/required-server-files.js +3 -1
  6. package/.next/required-server-files.json +3 -1
  7. package/.next/server/app/_global-error.html +1 -1
  8. package/.next/server/app/_global-error.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/server/app/_not-found.html +1 -1
  16. package/.next/server/app/_not-found.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  21. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  23. package/.next/server/app/api/internal/runtime/route.js +1 -1
  24. package/.next/server/app/api/version/route.js +1 -1
  25. package/.next/server/app/docs/changelog.html +2 -2
  26. package/.next/server/app/docs/changelog.rsc +1 -1
  27. package/.next/server/app/docs/changelog.segments/_full.segment.rsc +1 -1
  28. package/.next/server/app/docs/changelog.segments/_head.segment.rsc +1 -1
  29. package/.next/server/app/docs/changelog.segments/_index.segment.rsc +1 -1
  30. package/.next/server/app/docs/changelog.segments/_tree.segment.rsc +1 -1
  31. package/.next/server/app/docs/changelog.segments/docs/changelog/__PAGE__.segment.rsc +1 -1
  32. package/.next/server/app/docs/changelog.segments/docs/changelog.segment.rsc +1 -1
  33. package/.next/server/app/docs/changelog.segments/docs.segment.rsc +1 -1
  34. package/.next/server/app/index.html +1 -1
  35. package/.next/server/app/index.rsc +1 -1
  36. package/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  37. package/.next/server/app/index.segments/_full.segment.rsc +1 -1
  38. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  39. package/.next/server/app/index.segments/_index.segment.rsc +1 -1
  40. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  41. package/.next/server/app/login.html +1 -1
  42. package/.next/server/app/login.rsc +1 -1
  43. package/.next/server/app/login.segments/_full.segment.rsc +1 -1
  44. package/.next/server/app/login.segments/_head.segment.rsc +1 -1
  45. package/.next/server/app/login.segments/_index.segment.rsc +1 -1
  46. package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
  47. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +1 -1
  48. package/.next/server/app/login.segments/login.segment.rsc +1 -1
  49. package/.next/server/app/workspace/page.js +3 -3
  50. package/.next/server/app/workspace/page_client-reference-manifest.js +1 -1
  51. package/.next/server/app/workspace.html +1 -1
  52. package/.next/server/app/workspace.rsc +2 -2
  53. package/.next/server/app/workspace.segments/_full.segment.rsc +2 -2
  54. package/.next/server/app/workspace.segments/_head.segment.rsc +1 -1
  55. package/.next/server/app/workspace.segments/_index.segment.rsc +1 -1
  56. package/.next/server/app/workspace.segments/_tree.segment.rsc +1 -1
  57. package/.next/server/app/workspace.segments/workspace/__PAGE__.segment.rsc +2 -2
  58. package/.next/server/app/workspace.segments/workspace.segment.rsc +1 -1
  59. package/.next/server/app-paths-manifest.json +6 -6
  60. package/.next/server/chunks/6983.js +12 -5
  61. package/.next/server/middleware-build-manifest.js +1 -1
  62. package/.next/server/pages/404.html +1 -1
  63. package/.next/server/pages/500.html +1 -1
  64. package/.next/server/server-reference-manifest.json +1 -1
  65. package/.next/static/chunks/app/workspace/{page-d1366e4f9b7a7a15.js → page-cccacfa255ff7d01.js} +3 -3
  66. package/bin/annodex.js +299 -0
  67. package/lib/macos-codex-security.js +316 -0
  68. package/next.config.ts +1 -1
  69. package/package.json +2 -1
  70. /package/.next/static/{ZR0UxSLWFSPEwsYD3WfeI → CSz6uFOkLtpKpHvCD1S5n}/_buildManifest.js +0 -0
  71. /package/.next/static/{ZR0UxSLWFSPEwsYD3WfeI → CSz6uFOkLtpKpHvCD1S5n}/_ssgManifest.js +0 -0
package/bin/annodex.js CHANGED
@@ -27,6 +27,7 @@ const https = require("https");
27
27
  const { randomBytes } = require("crypto");
28
28
  // eslint-disable-next-line @typescript-eslint/no-require-imports
29
29
  const { readAppSettings, ensureAppSettings } = require("../lib/app-settings.js");
30
+ const macosCodexSecurity = require("../lib/macos-codex-security.js");
30
31
 
31
32
  const pkgDir = path.join(__dirname, "..");
32
33
  const nextDir = path.join(pkgDir, ".next");
@@ -82,6 +83,7 @@ const { values: cliArgs, positionals } = parseArgs({
82
83
  hostname: { type: "string", short: "H" },
83
84
  json: { type: "boolean" },
84
85
  follow: { type: "boolean", short: "f" },
86
+ repair: { type: "boolean" },
85
87
  version: { type: "boolean", short: "v" },
86
88
  help: { type: "boolean", short: "h" },
87
89
  },
@@ -719,6 +721,298 @@ function spawnDetachedServer(extraEnv = {}, overridePort, overrideHost) {
719
721
  return child;
720
722
  }
721
723
 
724
+
725
+ function commandOutput(command, args, options = {}) {
726
+ try {
727
+ const result = spawnSync(command, args, {
728
+ encoding: "utf8",
729
+ timeout: options.timeout ?? 5000,
730
+ windowsHide: true,
731
+ ...options,
732
+ });
733
+ return {
734
+ ok: result.status === 0,
735
+ status: result.status,
736
+ signal: result.signal,
737
+ stdout: (result.stdout || "").trim(),
738
+ stderr: (result.stderr || "").trim(),
739
+ error: result.error ? result.error.message : null,
740
+ };
741
+ } catch (error) {
742
+ return { ok: false, status: null, signal: null, stdout: "", stderr: "", error: error instanceof Error ? error.message : String(error) };
743
+ }
744
+ }
745
+
746
+ function getNpmPrefix() {
747
+ const npm = process.platform === "win32" ? "npm.cmd" : "npm";
748
+ const result = commandOutput(npm, ["prefix", "-g"], { timeout: 3000 });
749
+ return result.ok && result.stdout ? result.stdout.split(/\r?\n/)[0].trim() : null;
750
+ }
751
+
752
+ function codexPackageInfo() {
753
+ const platform = process.platform;
754
+ const arch = process.arch;
755
+ let pkgName = null;
756
+ let triple = null;
757
+ if (platform === "darwin") {
758
+ if (arch === "arm64") { pkgName = "codex-darwin-arm64"; triple = "aarch64-apple-darwin"; }
759
+ else if (arch === "x64") { pkgName = "codex-darwin-x64"; triple = "x86_64-apple-darwin"; }
760
+ } else if (platform === "linux") {
761
+ if (arch === "arm64") { pkgName = "codex-linux-arm64"; triple = "aarch64-unknown-linux-musl"; }
762
+ else if (arch === "x64") { pkgName = "codex-linux-x64"; triple = "x86_64-unknown-linux-musl"; }
763
+ } else if (platform === "win32") {
764
+ if (arch === "arm64") { pkgName = "codex-win32-arm64"; triple = "aarch64-pc-windows-msvc"; }
765
+ else if (arch === "x64") { pkgName = "codex-win32-x64"; triple = "x86_64-pc-windows-msvc"; }
766
+ }
767
+ return { pkgName, triple, binaryName: platform === "win32" ? "codex.exe" : "codex" };
768
+ }
769
+
770
+ function resolveCodexNativeCandidates() {
771
+ const { pkgName, triple, binaryName } = codexPackageInfo();
772
+ if (!pkgName || !triple) return [];
773
+ const roots = [
774
+ path.join(process.cwd(), "node_modules"),
775
+ path.join(pkgDir, "node_modules"),
776
+ ];
777
+ const npmPrefix = getNpmPrefix();
778
+ if (npmPrefix) roots.push(path.join(npmPrefix, "lib", "node_modules"));
779
+ const installedDir = getInstalledPackageDir();
780
+ roots.push(path.join(installedDir, "node_modules"));
781
+ roots.push(path.join(installedDir, "..", "..", "node_modules"));
782
+ const seen = new Set();
783
+ const out = [];
784
+ const subPaths = [
785
+ path.join("vendor", triple, "bin", binaryName),
786
+ path.join("vendor", triple, "codex", binaryName),
787
+ ];
788
+ for (const root of roots) {
789
+ for (const layout of ["flat", "nested"]) {
790
+ const pkgRoot = layout === "flat"
791
+ ? path.join(root, "@openai", pkgName)
792
+ : path.join(root, "@openai", "codex", "node_modules", "@openai", pkgName);
793
+ for (const sub of subPaths) {
794
+ const candidate = path.join(pkgRoot, sub);
795
+ if (seen.has(candidate)) continue;
796
+ seen.add(candidate);
797
+ if (fs.existsSync(candidate)) out.push(candidate);
798
+ }
799
+ }
800
+ }
801
+ return out;
802
+ }
803
+
804
+ function resolveCodexShimCandidates() {
805
+ const names = process.platform === "win32" ? ["codex.cmd", "codex.exe", "codex"] : ["codex"];
806
+ const dirs = [path.join(process.cwd(), "node_modules", ".bin"), path.join(pkgDir, "node_modules", ".bin")];
807
+ const npmPrefix = getNpmPrefix();
808
+ if (npmPrefix) dirs.push(process.platform === "win32" ? npmPrefix : path.join(npmPrefix, "bin"));
809
+ const installedDir = getInstalledPackageDir();
810
+ dirs.push(path.join(installedDir, "node_modules", ".bin"));
811
+ dirs.push(path.join(installedDir, "..", "..", ".bin"));
812
+ const seen = new Set();
813
+ const out = [];
814
+ for (const dir of dirs) {
815
+ for (const name of names) {
816
+ const candidate = path.join(dir, name);
817
+ if (!seen.has(candidate) && fs.existsSync(candidate)) {
818
+ seen.add(candidate);
819
+ out.push(candidate);
820
+ }
821
+ }
822
+ }
823
+ const which = commandOutput(process.platform === "win32" ? "where" : "which", ["codex"], { timeout: 3000 });
824
+ if (which.ok && which.stdout) {
825
+ for (const line of which.stdout.split(/\r?\n/).map((v) => v.trim()).filter(Boolean)) {
826
+ if (!seen.has(line) && fs.existsSync(line)) {
827
+ seen.add(line);
828
+ out.push(line);
829
+ }
830
+ }
831
+ }
832
+ return out;
833
+ }
834
+
835
+ function xattrInfo(target) {
836
+ if (process.platform !== "darwin" || !target) return null;
837
+ const result = commandOutput("xattr", [target], { timeout: 3000 });
838
+ return {
839
+ ok: result.ok,
840
+ hasQuarantine: /com\.apple\.quarantine/.test(result.stdout),
841
+ attributes: result.stdout ? result.stdout.split(/\r?\n/).filter(Boolean) : [],
842
+ error: result.ok ? null : (result.stderr || result.error),
843
+ };
844
+ }
845
+
846
+ function spctlAssess(target) {
847
+ if (process.platform !== "darwin" || !target) return null;
848
+ const result = commandOutput("spctl", ["--assess", "--verbose", target], { timeout: 5000 });
849
+ return {
850
+ accepted: result.ok || /accepted/i.test(`${result.stdout}\n${result.stderr}`),
851
+ status: result.status,
852
+ output: `${result.stdout}${result.stderr ? `\n${result.stderr}` : ""}`.trim(),
853
+ error: result.error,
854
+ };
855
+ }
856
+
857
+ function selectedCodexPath(nativeCandidates, shimCandidates) {
858
+ const envPath = process.env.ANNODEX_CODEX_PATH?.trim();
859
+ if (envPath) return { path: envPath, source: "ANNODEX_CODEX_PATH", exists: fs.existsSync(envPath) };
860
+ if (nativeCandidates[0]) return { path: nativeCandidates[0], source: "native-candidate", exists: true };
861
+ if (shimCandidates[0]) return { path: shimCandidates[0], source: "shim-candidate", exists: true };
862
+ return { path: process.platform === "win32" ? "codex.cmd" : "codex", source: "PATH fallback", exists: null };
863
+ }
864
+
865
+ function detectMacOSVersion() {
866
+ if (process.platform !== "darwin") return null;
867
+ const result = commandOutput("sw_vers", ["-productVersion"], { timeout: 3000 });
868
+ return result.ok ? result.stdout : null;
869
+ }
870
+
871
+ function macOSProtectedLocationInfo(targetPath) {
872
+ if (process.platform !== "darwin" || !targetPath) return null;
873
+ const real = safeRealpath(targetPath);
874
+ const home = os.homedir();
875
+ const protectedRoots = [
876
+ { label: "Desktop", path: path.join(home, "Desktop") },
877
+ { label: "Documents", path: path.join(home, "Documents") },
878
+ { label: "Downloads", path: path.join(home, "Downloads") },
879
+ { label: "iCloud Drive", path: path.join(home, "Library", "Mobile Documents") },
880
+ ];
881
+ const match = protectedRoots.find((root) => isPathInside(real, root.path));
882
+ return {
883
+ path: real,
884
+ inProtectedLocation: Boolean(match),
885
+ root: match?.label ?? null,
886
+ };
887
+ }
888
+
889
+ function runDoctor(json = false, repair = false) {
890
+ const nativeCandidates = resolveCodexNativeCandidates();
891
+ const shimCandidates = resolveCodexShimCandidates();
892
+ const selected = selectedCodexPath(nativeCandidates, shimCandidates);
893
+ const selectedReal = selected.exists ? safeRealpath(selected.path) : selected.path;
894
+
895
+ let repairResults = null;
896
+ if (repair && process.platform === "darwin") {
897
+ macosCodexSecurity.clearMacOSQuarantine(process.cwd());
898
+ const repairPaths = [...new Set([
899
+ ...nativeCandidates,
900
+ selected.exists ? selected.path : null,
901
+ ].filter(Boolean))];
902
+ repairResults = macosCodexSecurity.repairMacOSCodexPaths(repairPaths);
903
+ if (!json) {
904
+ console.log("repair:");
905
+ for (const item of repairResults) {
906
+ if (item.skipped) {
907
+ console.log(`- ${item.path}: skipped (no revoked cert)`);
908
+ } else {
909
+ console.log(`- ${item.path}: ${item.repair?.ok ? "repaired" : "failed"}`);
910
+ }
911
+ }
912
+ console.log("");
913
+ }
914
+ }
915
+
916
+ const transport = (process.env.ANNODEX_CODEX_TRANSPORT || "auto").toLowerCase();
917
+ const resolvedTransport = transport === "ws" || transport === "stdio" ? transport : (process.platform === "linux" ? "ws" : "stdio");
918
+ const versionProbe = selected.path
919
+ ? commandOutput(selected.path, ["--version"], { timeout: 15000 })
920
+ : null;
921
+ const report = {
922
+ annodex: { version: VERSION, packageName: PKG_NAME, packageDir: pkgDir, installedPackageDir: getInstalledPackageDir() },
923
+ platform: { platform: process.platform, arch: process.arch, macOSVersion: detectMacOSVersion() },
924
+ node: { version: process.version, execPath: process.execPath, cwd: process.cwd() },
925
+ appBundle: { enabled: process.env.ANNODEX_APP_BUNDLE === "1", env: process.env.ANNODEX_APP_BUNDLE || null },
926
+ workspace: {
927
+ packageDir: safeRealpath(pkgDir),
928
+ cwd: safeRealpath(process.cwd()),
929
+ packageDirMacOSProtection: macOSProtectedLocationInfo(pkgDir),
930
+ cwdMacOSProtection: macOSProtectedLocationInfo(process.cwd()),
931
+ },
932
+ config: { configDir: agentDir, logFile, npmPrefix: getNpmPrefix() },
933
+ transport: { requested: process.env.ANNODEX_CODEX_TRANSPORT || "auto", resolved: resolvedTransport },
934
+ codex: {
935
+ envPath: process.env.ANNODEX_CODEX_PATH || null,
936
+ selected: { ...selected, realpath: selectedReal },
937
+ nativeCandidates,
938
+ shimCandidates,
939
+ selectedXattr: xattrInfo(selected.exists ? selected.path : null),
940
+ selectedCodesign: selected.exists
941
+ ? macosCodexSecurity.codesignVerify(selected.path)
942
+ : null,
943
+ selectedSpctl: spctlAssess(selected.exists ? selected.path : null),
944
+ versionProbe: versionProbe ? {
945
+ ok: versionProbe.ok,
946
+ status: versionProbe.status,
947
+ signal: versionProbe.signal,
948
+ stdout: versionProbe.stdout,
949
+ stderr: versionProbe.stderr,
950
+ error: versionProbe.error,
951
+ } : null,
952
+ },
953
+ recommendations: [],
954
+ repair: repairResults,
955
+ };
956
+ if (process.platform === "darwin") {
957
+ if (!report.appBundle.enabled) report.recommendations.push("macOS: not launched from Annodex.app; CLI mode is diagnostic/advanced until the launcher PoC is validated.");
958
+ if (report.appBundle.enabled && report.workspace.packageDirMacOSProtection?.inProtectedLocation) {
959
+ report.recommendations.push(`macOS: Annodex.app is reading source/runtime files from ${report.workspace.packageDirMacOSProtection.root}. Finder-launched apps may hit TCC EPERM there. Move the repo/runtime outside protected folders or bundle the runtime into Annodex.app before evaluating launcher viability.`);
960
+ }
961
+ if (report.codex.selectedXattr?.hasQuarantine) report.recommendations.push("macOS: selected codex path has com.apple.quarantine. Run `annodex doctor --repair` or restart annodex (xattr is cleared automatically on codex spawn).");
962
+ if (report.codex.selectedCodesign?.revoked || (report.codex.selectedSpctl?.output && macosCodexSecurity.needsRevokedCertRepair(report.codex.selectedSpctl.output))) {
963
+ report.recommendations.push("macOS: codex binary has a revoked Developer ID signature (CSSMERR_TP_CERT_REVOKED). Run `annodex doctor --repair` to strip and apply ad-hoc signing, then retry codex --version.");
964
+ } else if (report.codex.selectedSpctl && !report.codex.selectedSpctl.accepted && versionProbe && !versionProbe.ok) {
965
+ report.recommendations.push("macOS: spctl assessment did not accept the selected codex binary and codex --version failed. Try `annodex doctor --repair` for ad-hoc signing.");
966
+ }
967
+ }
968
+ if (report.codex.selected.exists === false) report.recommendations.push("ANNODEX_CODEX_PATH is set but does not exist. Fix or unset it.");
969
+ if (versionProbe && !versionProbe.ok) report.recommendations.push("codex --version probe failed. Check the selected codex path and macOS security dialogs/logs.");
970
+
971
+ if (json) {
972
+ console.log(JSON.stringify(report, null, 2));
973
+ return versionProbe && !versionProbe.ok ? 1 : 0;
974
+ }
975
+ console.log(`annodex doctor v${VERSION}`);
976
+ console.log(`platform: ${process.platform}/${process.arch}${report.platform.macOSVersion ? ` macOS ${report.platform.macOSVersion}` : ""}`);
977
+ console.log(`node: ${process.version} ${process.execPath}`);
978
+ console.log(`app bundle: ${report.appBundle.enabled ? "yes" : "no"}`);
979
+ if (report.workspace.packageDirMacOSProtection?.inProtectedLocation) {
980
+ console.log(`package dir protection: ${report.workspace.packageDirMacOSProtection.root}`);
981
+ }
982
+ if (report.workspace.cwdMacOSProtection?.inProtectedLocation) {
983
+ console.log(`cwd protection: ${report.workspace.cwdMacOSProtection.root}`);
984
+ }
985
+ console.log(`config dir: ${agentDir}`);
986
+ console.log(`transport: requested=${report.transport.requested} resolved=${report.transport.resolved}`);
987
+ console.log(`codex selected: ${selected.path} (${selected.source}, exists=${selected.exists})`);
988
+ if (selectedReal && selectedReal !== selected.path) console.log(`codex realpath: ${selectedReal}`);
989
+ console.log(`native candidates: ${nativeCandidates.length ? nativeCandidates.join("; ") : "none"}`);
990
+ console.log(`shim candidates: ${shimCandidates.length ? shimCandidates.join("; ") : "none"}`);
991
+ if (report.codex.selectedXattr) {
992
+ console.log(`xattr quarantine: ${report.codex.selectedXattr.hasQuarantine ? "yes" : "no"}`);
993
+ if (report.codex.selectedXattr.attributes.length) console.log(`xattr attributes: ${report.codex.selectedXattr.attributes.join(", ")}`);
994
+ }
995
+ if (report.codex.selectedCodesign) {
996
+ console.log(`codesign verify: ${report.codex.selectedCodesign.ok ? "ok" : "failed"}${report.codex.selectedCodesign.revoked ? " (revoked cert)" : ""}`);
997
+ if (report.codex.selectedCodesign.output) console.log(`codesign output: ${report.codex.selectedCodesign.output}`);
998
+ }
999
+ if (report.codex.selectedSpctl) {
1000
+ console.log(`spctl accepted: ${report.codex.selectedSpctl.accepted ? "yes" : "no"}`);
1001
+ if (report.codex.selectedSpctl.output) console.log(`spctl output: ${report.codex.selectedSpctl.output}`);
1002
+ }
1003
+ if (versionProbe) {
1004
+ console.log(`codex --version: ${versionProbe.ok ? "ok" : "failed"}${versionProbe.signal ? ` signal=${versionProbe.signal}` : ""}`);
1005
+ if (versionProbe.stdout) console.log(` stdout: ${versionProbe.stdout}`);
1006
+ if (versionProbe.stderr) console.log(` stderr: ${versionProbe.stderr}`);
1007
+ if (versionProbe.error) console.log(` error: ${versionProbe.error}`);
1008
+ }
1009
+ if (report.recommendations.length) {
1010
+ console.log("recommendations:");
1011
+ for (const item of report.recommendations) console.log(`- ${item}`);
1012
+ }
1013
+ return versionProbe && !versionProbe.ok ? 1 : 0;
1014
+ }
1015
+
722
1016
  if (cliArgs.version) {
723
1017
  console.log(VERSION);
724
1018
  process.exit(0);
@@ -734,6 +1028,7 @@ Usage:
734
1028
  annodex stop Stop background annodex
735
1029
  annodex status [--json] Show background server status
736
1030
  annodex logs [-f] Show background server logs
1031
+ annodex doctor [--json] [--repair] Diagnose local codex/runtime setup
737
1032
  annodex passwd Set or change password (empty = disable auth)
738
1033
  annodex update Update to the latest version
739
1034
 
@@ -749,6 +1044,10 @@ Options:
749
1044
  }
750
1045
 
751
1046
  const firstPos = positionals[0];
1047
+ if (firstPos === "doctor") {
1048
+ process.exit(runDoctor(cliArgs.json, cliArgs.repair));
1049
+ }
1050
+
752
1051
  if (firstPos === "status") {
753
1052
  const json = cliArgs.json;
754
1053
  staleStateRemoved();
@@ -0,0 +1,316 @@
1
+ "use strict";
2
+ /* eslint-disable @typescript-eslint/no-require-imports */
3
+
4
+ /**
5
+ * macOS Gatekeeper helpers for the vendored @openai/codex native binary.
6
+ *
7
+ * npm-installed codex may carry com.apple.quarantine and/or a Developer ID
8
+ * signature whose certificate chain was revoked. The latter can cause macOS
9
+ * to SIGKILL the process immediately (CSSMERR_TP_CERT_REVOKED). Stripping the
10
+ * broken signature and applying ad-hoc signing (--sign -) downgrades the
11
+ * failure to a first-run Gatekeeper dialog (right-click → Open), matching the
12
+ * hermes-agent Electron recovery pattern.
13
+ */
14
+
15
+ const { spawnSync, execSync } = require("child_process");
16
+ const fs = require("fs");
17
+ const path = require("path");
18
+
19
+ const CODESIGN = "/usr/bin/codesign";
20
+ const SPCTL = "/usr/sbin/spctl";
21
+
22
+ /** Avoid repeating signature checks on every codex spawn in one process. */
23
+ function getPreparedRegistry() {
24
+ if (!globalThis.__annodexCodexPreparedBinaries) {
25
+ globalThis.__annodexCodexPreparedBinaries = new Map();
26
+ }
27
+ return globalThis.__annodexCodexPreparedBinaries;
28
+ }
29
+
30
+ const preparingBinaries = new Set();
31
+
32
+ function isBinaryPrepared(binaryPath) {
33
+ const mtime = binaryMtimeMs(binaryPath);
34
+ return mtime !== null && getPreparedRegistry().get(binaryPath) === mtime;
35
+ }
36
+
37
+ function markBinaryPrepared(binaryPath) {
38
+ const mtime = binaryMtimeMs(binaryPath);
39
+ if (mtime !== null) getPreparedRegistry().set(binaryPath, mtime);
40
+ }
41
+
42
+ let quarantineClearedForCwd = null;
43
+
44
+ function isDarwin() {
45
+ return process.platform === "darwin";
46
+ }
47
+
48
+ function codesignOutput(result) {
49
+ return `${result.stdout || ""}${result.stderr || ""}`.trim();
50
+ }
51
+
52
+ function needsRevokedCertRepair(text) {
53
+ return /CSSMERR_TP_CERT_REVOKED/i.test(text || "");
54
+ }
55
+
56
+ function binaryMtimeMs(binaryPath) {
57
+ try {
58
+ return fs.statSync(binaryPath).mtimeMs;
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ function spctlAssess(binaryPath, timeoutMs = 10_000) {
65
+ if (!isDarwin() || !binaryPath || !fs.existsSync(binaryPath)) {
66
+ return { accepted: false, output: "", error: "missing binary" };
67
+ }
68
+ const result = spawnSync(SPCTL, ["--assess", "--verbose", binaryPath], {
69
+ encoding: "utf8",
70
+ timeout: timeoutMs,
71
+ });
72
+ const output = codesignOutput(result);
73
+ const timedOut = result.error && /timed out/i.test(result.error.message || "");
74
+ return {
75
+ accepted: result.status === 0 || /accepted/i.test(output),
76
+ revoked: needsRevokedCertRepair(output),
77
+ timedOut,
78
+ output,
79
+ status: result.status,
80
+ error: result.error ? result.error.message : null,
81
+ };
82
+ }
83
+
84
+ function codesignVerify(binaryPath, { timeoutMs = 5_000 } = {}) {
85
+ if (!isDarwin() || !binaryPath || !fs.existsSync(binaryPath)) {
86
+ return { ok: false, revoked: false, output: "", error: "missing binary" };
87
+ }
88
+ const result = spawnSync(CODESIGN, ["--verify", "--strict", "--verbose=4", binaryPath], {
89
+ encoding: "utf8",
90
+ timeout: timeoutMs,
91
+ });
92
+ const output = codesignOutput(result);
93
+ return {
94
+ ok: result.status === 0,
95
+ revoked: needsRevokedCertRepair(output),
96
+ output,
97
+ status: result.status,
98
+ error: result.error ? result.error.message : null,
99
+ };
100
+ }
101
+
102
+ /** Fast spawn-path check — only detect revoked certs (not full strict verify). */
103
+ function needsRepairBeforeSpawn(binaryPath) {
104
+ const result = spawnSync(CODESIGN, ["--verify", binaryPath], {
105
+ encoding: "utf8",
106
+ timeout: 3_000,
107
+ });
108
+ return needsRevokedCertRepair(codesignOutput(result));
109
+ }
110
+
111
+ function shouldRepairCodexBinary(binaryPath) {
112
+ const verify = codesignVerify(binaryPath);
113
+ if (verify.revoked || !verify.ok) return true;
114
+ const spctl = spctlAssess(binaryPath, 10_000);
115
+ return spctl.revoked;
116
+ }
117
+
118
+ function repairCodexBinaryAdHoc(binaryPath) {
119
+ if (!isDarwin() || !binaryPath || !fs.existsSync(binaryPath)) {
120
+ return { ok: false, output: "", error: "missing binary" };
121
+ }
122
+ spawnSync(CODESIGN, ["--remove-signature", binaryPath], {
123
+ encoding: "utf8",
124
+ timeout: 10_000,
125
+ });
126
+ const sign = spawnSync(CODESIGN, ["--force", "--sign", "-", binaryPath], {
127
+ encoding: "utf8",
128
+ timeout: 10_000,
129
+ });
130
+ const output = codesignOutput(sign);
131
+ return {
132
+ ok: sign.status === 0,
133
+ output,
134
+ error: sign.error ? sign.error.message : null,
135
+ };
136
+ }
137
+
138
+ function collectCodexXattrTargets(cwd) {
139
+ const targets = [];
140
+ const workDir = cwd || process.cwd();
141
+
142
+ const localPkg = path.join(workDir, "node_modules", "@openai", "codex");
143
+ if (fs.existsSync(localPkg)) targets.push(localPkg);
144
+
145
+ let annodexRoot = null;
146
+ try {
147
+ annodexRoot = path.join(__dirname, "..");
148
+ } catch {
149
+ // bundled build
150
+ }
151
+
152
+ try {
153
+ const npmPrefix = execSync("npm prefix -g", { encoding: "utf8", timeout: 3000 }).trim();
154
+ if (npmPrefix) {
155
+ const globalPkg = path.join(npmPrefix, "lib", "node_modules", "@openai", "codex");
156
+ if (fs.existsSync(globalPkg)) targets.push(globalPkg);
157
+ const binShim = path.join(npmPrefix, "bin", "codex");
158
+ if (fs.existsSync(binShim)) targets.push(binShim);
159
+ }
160
+ } catch {
161
+ // npm not available
162
+ }
163
+
164
+ if (annodexRoot) {
165
+ const annodexPkg = path.join(annodexRoot, "..", "..", "node_modules", "@openai", "codex");
166
+ if (fs.existsSync(annodexPkg) && !targets.includes(annodexPkg)) targets.push(annodexPkg);
167
+ }
168
+
169
+ return targets;
170
+ }
171
+
172
+ function clearMacOSQuarantineDeep(cwd) {
173
+ if (!isDarwin()) return { cleared: 0, paths: [], skipped: true };
174
+ const targets = collectCodexXattrTargets(cwd);
175
+ const clearedPaths = [];
176
+ for (const target of targets) {
177
+ try {
178
+ execSync(`xattr -cr "${target}" 2>/dev/null || true`, {
179
+ shell: "/bin/bash",
180
+ timeout: 5000,
181
+ });
182
+ clearedPaths.push(target);
183
+ } catch {
184
+ // read-only volumes, etc.
185
+ }
186
+ }
187
+ return { cleared: clearedPaths.length, paths: clearedPaths, skipped: false };
188
+ }
189
+
190
+ function clearMacOSQuarantine(cwd) {
191
+ return clearMacOSQuarantineDeep(cwd);
192
+ }
193
+
194
+ /** Fast quarantine clear for codex spawn — target only the native binary. */
195
+ function clearMacOSQuarantineForSpawn(executablePath, cwd) {
196
+ if (!isDarwin()) return { cleared: 0, paths: [], skipped: true };
197
+ const key = cwd || process.cwd();
198
+ if (quarantineClearedForCwd === key) {
199
+ return { cleared: 0, paths: [], skipped: true };
200
+ }
201
+ const clearedPaths = [];
202
+ if (executablePath && fs.existsSync(executablePath)) {
203
+ try {
204
+ execSync(`xattr -c "${executablePath}" 2>/dev/null || true`, {
205
+ shell: "/bin/bash",
206
+ timeout: 2000,
207
+ });
208
+ clearedPaths.push(executablePath);
209
+ } catch {
210
+ // ignore
211
+ }
212
+ }
213
+ quarantineClearedForCwd = key;
214
+ return { cleared: clearedPaths.length, paths: clearedPaths, skipped: false };
215
+ }
216
+
217
+ function looksLikeNativeCodexBinary(filePath) {
218
+ if (!filePath) return false;
219
+ const base = path.basename(filePath);
220
+ return base === "codex" || base === "codex.exe";
221
+ }
222
+
223
+ function repairMacOSCodexPaths(binaryPaths, { force = false, mode = "doctor" } = {}) {
224
+ if (!isDarwin()) return [];
225
+ const seen = new Set();
226
+ const results = [];
227
+ for (const binaryPath of binaryPaths) {
228
+ if (!binaryPath || seen.has(binaryPath) || !fs.existsSync(binaryPath)) continue;
229
+ if (!looksLikeNativeCodexBinary(binaryPath)) continue;
230
+ seen.add(binaryPath);
231
+
232
+ const beforeVerify = mode === "doctor" ? codesignVerify(binaryPath) : null;
233
+ const beforeSpctl = mode === "doctor" ? spctlAssess(binaryPath, 10_000) : null;
234
+ const needsRepair = force
235
+ || (beforeVerify?.revoked ?? false)
236
+ || (beforeSpctl?.revoked ?? false)
237
+ || (mode === "spawn" && needsRepairBeforeSpawn(binaryPath));
238
+ if (!needsRepair) {
239
+ markBinaryPrepared(binaryPath);
240
+ results.push({
241
+ path: binaryPath,
242
+ skipped: true,
243
+ before: beforeVerify,
244
+ beforeSpctl,
245
+ });
246
+ continue;
247
+ }
248
+
249
+ const repair = repairCodexBinaryAdHoc(binaryPath);
250
+ const after = codesignVerify(binaryPath);
251
+ const afterSpctl = mode === "doctor" ? spctlAssess(binaryPath, 10_000) : null;
252
+ if (repair.ok) markBinaryPrepared(binaryPath);
253
+ results.push({
254
+ path: binaryPath,
255
+ skipped: false,
256
+ before: beforeVerify,
257
+ beforeSpctl,
258
+ repair,
259
+ after,
260
+ afterSpctl,
261
+ });
262
+ }
263
+ return results;
264
+ }
265
+
266
+ /**
267
+ * Best-effort macOS prep before spawning codex: replace revoked signatures with
268
+ * ad-hoc signing. Skips xattr on the hot path — clearing quarantine on trees
269
+ * under macOS protected folders (Documents, etc.) can take 20s+ per spawn.
270
+ * Use `annodex doctor --repair` for deep quarantine cleanup.
271
+ */
272
+ function prepareMacOSCodexForSpawn(executablePath, cwd) {
273
+ if (!isDarwin()) return { quarantine: { cleared: 0, paths: [] }, repairs: [] };
274
+
275
+ const quarantine = { cleared: 0, paths: [], skipped: true };
276
+
277
+ if (!executablePath || !looksLikeNativeCodexBinary(executablePath)) {
278
+ return { quarantine, repairs: [] };
279
+ }
280
+ if (isBinaryPrepared(executablePath) || preparingBinaries.has(executablePath)) {
281
+ return { quarantine, repairs: [], skipped: true };
282
+ }
283
+
284
+ preparingBinaries.add(executablePath);
285
+ let repairs = [];
286
+ try {
287
+ repairs = repairMacOSCodexPaths([executablePath], { mode: "spawn" });
288
+ } finally {
289
+ preparingBinaries.delete(executablePath);
290
+ }
291
+ for (const item of repairs) {
292
+ if (item.skipped) continue;
293
+ if (item.repair?.ok) {
294
+ console.log(
295
+ `[codex-server] Repaired codex signature with ad-hoc signing: ${item.path}`,
296
+ );
297
+ } else if (item.before?.revoked || item.beforeSpctl?.revoked) {
298
+ console.warn(
299
+ `[codex-server] Failed to repair revoked codex signature at ${item.path}: ${item.repair?.output || item.repair?.error || "unknown error"}`,
300
+ );
301
+ }
302
+ }
303
+
304
+ return { quarantine, repairs };
305
+ }
306
+
307
+ module.exports = {
308
+ codesignVerify,
309
+ spctlAssess,
310
+ shouldRepairCodexBinary,
311
+ repairCodexBinaryAdHoc,
312
+ needsRevokedCertRepair,
313
+ clearMacOSQuarantine,
314
+ repairMacOSCodexPaths,
315
+ prepareMacOSCodexForSpawn,
316
+ };
package/next.config.ts CHANGED
@@ -6,7 +6,7 @@ const { version } = JSON.parse(readFileSync(join(__dirname, "package.json"), "ut
6
6
 
7
7
  const nextConfig: NextConfig = {
8
8
  serverExternalPackages: ["ws", "@openai/codex"],
9
- allowedDevOrigins: ['192.168.*.*'],
9
+ allowedDevOrigins: ["127.0.0.1", "localhost", "192.168.*.*"],
10
10
  env: {
11
11
  NEXT_PUBLIC_APP_VERSION: version,
12
12
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seqyuan/annodex",
3
- "version": "0.1.53",
3
+ "version": "0.1.54",
4
4
  "description": "AI-native bioinformatics workspace by Annoroad",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -16,6 +16,7 @@
16
16
  "!.next/trace",
17
17
  "!.next/trace-build",
18
18
  "lib/app-settings.js",
19
+ "lib/macos-codex-security.js",
19
20
  "lib/default-SOUL.md",
20
21
  "lib/default-HARNESS.md",
21
22
  "public",