@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.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +6 -6
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +3 -3
- package/.next/required-server-files.js +3 -1
- package/.next/required-server-files.json +3 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/internal/runtime/route.js +1 -1
- package/.next/server/app/api/version/route.js +1 -1
- package/.next/server/app/docs/changelog.html +2 -2
- package/.next/server/app/docs/changelog.rsc +1 -1
- package/.next/server/app/docs/changelog.segments/_full.segment.rsc +1 -1
- package/.next/server/app/docs/changelog.segments/_head.segment.rsc +1 -1
- package/.next/server/app/docs/changelog.segments/_index.segment.rsc +1 -1
- package/.next/server/app/docs/changelog.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/docs/changelog.segments/docs/changelog/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/docs/changelog.segments/docs/changelog.segment.rsc +1 -1
- package/.next/server/app/docs/changelog.segments/docs.segment.rsc +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +1 -1
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.rsc +1 -1
- package/.next/server/app/login.segments/_full.segment.rsc +1 -1
- package/.next/server/app/login.segments/_head.segment.rsc +1 -1
- package/.next/server/app/login.segments/_index.segment.rsc +1 -1
- package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/login.segments/login.segment.rsc +1 -1
- package/.next/server/app/workspace/page.js +3 -3
- package/.next/server/app/workspace/page_client-reference-manifest.js +1 -1
- package/.next/server/app/workspace.html +1 -1
- package/.next/server/app/workspace.rsc +2 -2
- package/.next/server/app/workspace.segments/_full.segment.rsc +2 -2
- package/.next/server/app/workspace.segments/_head.segment.rsc +1 -1
- package/.next/server/app/workspace.segments/_index.segment.rsc +1 -1
- package/.next/server/app/workspace.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/workspace.segments/workspace/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/workspace.segments/workspace.segment.rsc +1 -1
- package/.next/server/app-paths-manifest.json +6 -6
- package/.next/server/chunks/6983.js +12 -5
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/app/workspace/{page-d1366e4f9b7a7a15.js → page-cccacfa255ff7d01.js} +3 -3
- package/bin/annodex.js +299 -0
- package/lib/macos-codex-security.js +316 -0
- package/next.config.ts +1 -1
- package/package.json +2 -1
- /package/.next/static/{ZR0UxSLWFSPEwsYD3WfeI → CSz6uFOkLtpKpHvCD1S5n}/_buildManifest.js +0 -0
- /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: [
|
|
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.
|
|
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",
|
|
File without changes
|
|
File without changes
|