oc-codex-multi-auth 6.0.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.
Files changed (248) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +162 -0
  3. package/assets/opencode-logo-ornate-dark.svg +18 -0
  4. package/assets/readme-hero.svg +31 -0
  5. package/config/README.md +96 -0
  6. package/config/minimal-opencode.json +13 -0
  7. package/config/opencode-legacy.json +871 -0
  8. package/config/opencode-modern.json +358 -0
  9. package/dist/index.d.ts +45 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +5008 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/lib/accounts/rate-limits.d.ts +22 -0
  14. package/dist/lib/accounts/rate-limits.d.ts.map +1 -0
  15. package/dist/lib/accounts/rate-limits.js +63 -0
  16. package/dist/lib/accounts/rate-limits.js.map +1 -0
  17. package/dist/lib/accounts.d.ts +134 -0
  18. package/dist/lib/accounts.d.ts.map +1 -0
  19. package/dist/lib/accounts.js +795 -0
  20. package/dist/lib/accounts.js.map +1 -0
  21. package/dist/lib/audit.d.ts +45 -0
  22. package/dist/lib/audit.d.ts.map +1 -0
  23. package/dist/lib/audit.js +154 -0
  24. package/dist/lib/audit.js.map +1 -0
  25. package/dist/lib/auth/auth.d.ts +51 -0
  26. package/dist/lib/auth/auth.d.ts.map +1 -0
  27. package/dist/lib/auth/auth.js +191 -0
  28. package/dist/lib/auth/auth.js.map +1 -0
  29. package/dist/lib/auth/browser.d.ts +17 -0
  30. package/dist/lib/auth/browser.d.ts.map +1 -0
  31. package/dist/lib/auth/browser.js +90 -0
  32. package/dist/lib/auth/browser.js.map +1 -0
  33. package/dist/lib/auth/device-code.d.ts +28 -0
  34. package/dist/lib/auth/device-code.d.ts.map +1 -0
  35. package/dist/lib/auth/device-code.js +215 -0
  36. package/dist/lib/auth/device-code.js.map +1 -0
  37. package/dist/lib/auth/login-runner.d.ts +55 -0
  38. package/dist/lib/auth/login-runner.d.ts.map +1 -0
  39. package/dist/lib/auth/login-runner.js +631 -0
  40. package/dist/lib/auth/login-runner.js.map +1 -0
  41. package/dist/lib/auth/server.d.ts +10 -0
  42. package/dist/lib/auth/server.d.ts.map +1 -0
  43. package/dist/lib/auth/server.js +98 -0
  44. package/dist/lib/auth/server.js.map +1 -0
  45. package/dist/lib/auth/token-utils.d.ts +60 -0
  46. package/dist/lib/auth/token-utils.d.ts.map +1 -0
  47. package/dist/lib/auth/token-utils.js +416 -0
  48. package/dist/lib/auth/token-utils.js.map +1 -0
  49. package/dist/lib/auth-rate-limit.d.ts +20 -0
  50. package/dist/lib/auth-rate-limit.d.ts.map +1 -0
  51. package/dist/lib/auth-rate-limit.js +91 -0
  52. package/dist/lib/auth-rate-limit.js.map +1 -0
  53. package/dist/lib/auto-update-checker.d.ts +10 -0
  54. package/dist/lib/auto-update-checker.d.ts.map +1 -0
  55. package/dist/lib/auto-update-checker.js +129 -0
  56. package/dist/lib/auto-update-checker.js.map +1 -0
  57. package/dist/lib/circuit-breaker.d.ts +34 -0
  58. package/dist/lib/circuit-breaker.d.ts.map +1 -0
  59. package/dist/lib/circuit-breaker.js +124 -0
  60. package/dist/lib/circuit-breaker.js.map +1 -0
  61. package/dist/lib/cli.d.ts +46 -0
  62. package/dist/lib/cli.d.ts.map +1 -0
  63. package/dist/lib/cli.js +192 -0
  64. package/dist/lib/cli.js.map +1 -0
  65. package/dist/lib/config.d.ts +42 -0
  66. package/dist/lib/config.d.ts.map +1 -0
  67. package/dist/lib/config.js +314 -0
  68. package/dist/lib/config.js.map +1 -0
  69. package/dist/lib/constants.d.ts +90 -0
  70. package/dist/lib/constants.d.ts.map +1 -0
  71. package/dist/lib/constants.js +90 -0
  72. package/dist/lib/constants.js.map +1 -0
  73. package/dist/lib/context-overflow.d.ts +27 -0
  74. package/dist/lib/context-overflow.d.ts.map +1 -0
  75. package/dist/lib/context-overflow.js +124 -0
  76. package/dist/lib/context-overflow.js.map +1 -0
  77. package/dist/lib/errors.d.ts +113 -0
  78. package/dist/lib/errors.d.ts.map +1 -0
  79. package/dist/lib/errors.js +103 -0
  80. package/dist/lib/errors.js.map +1 -0
  81. package/dist/lib/health.d.ts +33 -0
  82. package/dist/lib/health.d.ts.map +1 -0
  83. package/dist/lib/health.js +70 -0
  84. package/dist/lib/health.js.map +1 -0
  85. package/dist/lib/index.d.ts +20 -0
  86. package/dist/lib/index.d.ts.map +1 -0
  87. package/dist/lib/index.js +20 -0
  88. package/dist/lib/index.js.map +1 -0
  89. package/dist/lib/logger.d.ts +40 -0
  90. package/dist/lib/logger.d.ts.map +1 -0
  91. package/dist/lib/logger.js +337 -0
  92. package/dist/lib/logger.js.map +1 -0
  93. package/dist/lib/oauth-success.html +712 -0
  94. package/dist/lib/parallel-probe.d.ts +28 -0
  95. package/dist/lib/parallel-probe.d.ts.map +1 -0
  96. package/dist/lib/parallel-probe.js +97 -0
  97. package/dist/lib/parallel-probe.js.map +1 -0
  98. package/dist/lib/proactive-refresh.d.ts +67 -0
  99. package/dist/lib/proactive-refresh.d.ts.map +1 -0
  100. package/dist/lib/proactive-refresh.js +161 -0
  101. package/dist/lib/proactive-refresh.js.map +1 -0
  102. package/dist/lib/prompts/codex-opencode-bridge.d.ts +20 -0
  103. package/dist/lib/prompts/codex-opencode-bridge.d.ts.map +1 -0
  104. package/dist/lib/prompts/codex-opencode-bridge.js +126 -0
  105. package/dist/lib/prompts/codex-opencode-bridge.js.map +1 -0
  106. package/dist/lib/prompts/codex.d.ts +41 -0
  107. package/dist/lib/prompts/codex.d.ts.map +1 -0
  108. package/dist/lib/prompts/codex.js +400 -0
  109. package/dist/lib/prompts/codex.js.map +1 -0
  110. package/dist/lib/prompts/opencode-codex.d.ts +25 -0
  111. package/dist/lib/prompts/opencode-codex.d.ts.map +1 -0
  112. package/dist/lib/prompts/opencode-codex.js +223 -0
  113. package/dist/lib/prompts/opencode-codex.js.map +1 -0
  114. package/dist/lib/recovery/constants.d.ts +12 -0
  115. package/dist/lib/recovery/constants.d.ts.map +1 -0
  116. package/dist/lib/recovery/constants.js +25 -0
  117. package/dist/lib/recovery/constants.js.map +1 -0
  118. package/dist/lib/recovery/index.d.ts +12 -0
  119. package/dist/lib/recovery/index.d.ts.map +1 -0
  120. package/dist/lib/recovery/index.js +12 -0
  121. package/dist/lib/recovery/index.js.map +1 -0
  122. package/dist/lib/recovery/storage.d.ts +24 -0
  123. package/dist/lib/recovery/storage.d.ts.map +1 -0
  124. package/dist/lib/recovery/storage.js +362 -0
  125. package/dist/lib/recovery/storage.js.map +1 -0
  126. package/dist/lib/recovery/types.d.ts +116 -0
  127. package/dist/lib/recovery/types.d.ts.map +1 -0
  128. package/dist/lib/recovery/types.js +7 -0
  129. package/dist/lib/recovery/types.js.map +1 -0
  130. package/dist/lib/recovery.d.ts +31 -0
  131. package/dist/lib/recovery.d.ts.map +1 -0
  132. package/dist/lib/recovery.js +313 -0
  133. package/dist/lib/recovery.js.map +1 -0
  134. package/dist/lib/refresh-queue.d.ts +131 -0
  135. package/dist/lib/refresh-queue.d.ts.map +1 -0
  136. package/dist/lib/refresh-queue.js +297 -0
  137. package/dist/lib/refresh-queue.js.map +1 -0
  138. package/dist/lib/request/fetch-helpers.d.ts +142 -0
  139. package/dist/lib/request/fetch-helpers.d.ts.map +1 -0
  140. package/dist/lib/request/fetch-helpers.js +749 -0
  141. package/dist/lib/request/fetch-helpers.js.map +1 -0
  142. package/dist/lib/request/helpers/input-utils.d.ts +7 -0
  143. package/dist/lib/request/helpers/input-utils.d.ts.map +1 -0
  144. package/dist/lib/request/helpers/input-utils.js +214 -0
  145. package/dist/lib/request/helpers/input-utils.js.map +1 -0
  146. package/dist/lib/request/helpers/model-map.d.ts +22 -0
  147. package/dist/lib/request/helpers/model-map.d.ts.map +1 -0
  148. package/dist/lib/request/helpers/model-map.js +183 -0
  149. package/dist/lib/request/helpers/model-map.js.map +1 -0
  150. package/dist/lib/request/helpers/tool-utils.d.ts +29 -0
  151. package/dist/lib/request/helpers/tool-utils.d.ts.map +1 -0
  152. package/dist/lib/request/helpers/tool-utils.js +117 -0
  153. package/dist/lib/request/helpers/tool-utils.js.map +1 -0
  154. package/dist/lib/request/rate-limit-backoff.d.ts +17 -0
  155. package/dist/lib/request/rate-limit-backoff.d.ts.map +1 -0
  156. package/dist/lib/request/rate-limit-backoff.js +83 -0
  157. package/dist/lib/request/rate-limit-backoff.js.map +1 -0
  158. package/dist/lib/request/request-transformer.d.ts +107 -0
  159. package/dist/lib/request/request-transformer.d.ts.map +1 -0
  160. package/dist/lib/request/request-transformer.js +930 -0
  161. package/dist/lib/request/request-transformer.js.map +1 -0
  162. package/dist/lib/request/response-handler.d.ts +23 -0
  163. package/dist/lib/request/response-handler.d.ts.map +1 -0
  164. package/dist/lib/request/response-handler.js +245 -0
  165. package/dist/lib/request/response-handler.js.map +1 -0
  166. package/dist/lib/request/retry-budget.d.ts +19 -0
  167. package/dist/lib/request/retry-budget.d.ts.map +1 -0
  168. package/dist/lib/request/retry-budget.js +99 -0
  169. package/dist/lib/request/retry-budget.js.map +1 -0
  170. package/dist/lib/rotation.d.ts +130 -0
  171. package/dist/lib/rotation.d.ts.map +1 -0
  172. package/dist/lib/rotation.js +301 -0
  173. package/dist/lib/rotation.js.map +1 -0
  174. package/dist/lib/runtime-contracts.d.ts +16 -0
  175. package/dist/lib/runtime-contracts.d.ts.map +1 -0
  176. package/dist/lib/runtime-contracts.js +24 -0
  177. package/dist/lib/runtime-contracts.js.map +1 -0
  178. package/dist/lib/schemas.d.ts +457 -0
  179. package/dist/lib/schemas.d.ts.map +1 -0
  180. package/dist/lib/schemas.js +283 -0
  181. package/dist/lib/schemas.js.map +1 -0
  182. package/dist/lib/shutdown.d.ts +7 -0
  183. package/dist/lib/shutdown.d.ts.map +1 -0
  184. package/dist/lib/shutdown.js +43 -0
  185. package/dist/lib/shutdown.js.map +1 -0
  186. package/dist/lib/storage/migrations.d.ts +65 -0
  187. package/dist/lib/storage/migrations.d.ts.map +1 -0
  188. package/dist/lib/storage/migrations.js +44 -0
  189. package/dist/lib/storage/migrations.js.map +1 -0
  190. package/dist/lib/storage/paths.d.ts +16 -0
  191. package/dist/lib/storage/paths.d.ts.map +1 -0
  192. package/dist/lib/storage/paths.js +92 -0
  193. package/dist/lib/storage/paths.js.map +1 -0
  194. package/dist/lib/storage.d.ts +157 -0
  195. package/dist/lib/storage.d.ts.map +1 -0
  196. package/dist/lib/storage.js +1169 -0
  197. package/dist/lib/storage.js.map +1 -0
  198. package/dist/lib/table-formatter.d.ts +32 -0
  199. package/dist/lib/table-formatter.d.ts.map +1 -0
  200. package/dist/lib/table-formatter.js +44 -0
  201. package/dist/lib/table-formatter.js.map +1 -0
  202. package/dist/lib/types.d.ts +131 -0
  203. package/dist/lib/types.d.ts.map +1 -0
  204. package/dist/lib/types.js +2 -0
  205. package/dist/lib/types.js.map +1 -0
  206. package/dist/lib/ui/ansi.d.ts +22 -0
  207. package/dist/lib/ui/ansi.d.ts.map +1 -0
  208. package/dist/lib/ui/ansi.js +38 -0
  209. package/dist/lib/ui/ansi.js.map +1 -0
  210. package/dist/lib/ui/auth-menu.d.ts +39 -0
  211. package/dist/lib/ui/auth-menu.d.ts.map +1 -0
  212. package/dist/lib/ui/auth-menu.js +186 -0
  213. package/dist/lib/ui/auth-menu.js.map +1 -0
  214. package/dist/lib/ui/beginner.d.ts +62 -0
  215. package/dist/lib/ui/beginner.d.ts.map +1 -0
  216. package/dist/lib/ui/beginner.js +259 -0
  217. package/dist/lib/ui/beginner.js.map +1 -0
  218. package/dist/lib/ui/confirm.d.ts +2 -0
  219. package/dist/lib/ui/confirm.d.ts.map +1 -0
  220. package/dist/lib/ui/confirm.js +21 -0
  221. package/dist/lib/ui/confirm.js.map +1 -0
  222. package/dist/lib/ui/format.d.ts +9 -0
  223. package/dist/lib/ui/format.d.ts.map +1 -0
  224. package/dist/lib/ui/format.js +49 -0
  225. package/dist/lib/ui/format.js.map +1 -0
  226. package/dist/lib/ui/runtime.d.ts +11 -0
  227. package/dist/lib/ui/runtime.d.ts.map +1 -0
  228. package/dist/lib/ui/runtime.js +31 -0
  229. package/dist/lib/ui/runtime.js.map +1 -0
  230. package/dist/lib/ui/select.d.ts +20 -0
  231. package/dist/lib/ui/select.d.ts.map +1 -0
  232. package/dist/lib/ui/select.js +343 -0
  233. package/dist/lib/ui/select.js.map +1 -0
  234. package/dist/lib/ui/theme.d.ts +34 -0
  235. package/dist/lib/ui/theme.d.ts.map +1 -0
  236. package/dist/lib/ui/theme.js +84 -0
  237. package/dist/lib/ui/theme.js.map +1 -0
  238. package/dist/lib/utils.d.ts +29 -0
  239. package/dist/lib/utils.d.ts.map +1 -0
  240. package/dist/lib/utils.js +54 -0
  241. package/dist/lib/utils.js.map +1 -0
  242. package/package.json +113 -0
  243. package/scripts/audit-dev-allowlist.js +114 -0
  244. package/scripts/copy-oauth-success.js +37 -0
  245. package/scripts/install-oc-codex-multi-auth-core.js +381 -0
  246. package/scripts/install-oc-codex-multi-auth.js +15 -0
  247. package/scripts/test-all-models.sh +260 -0
  248. package/scripts/validate-model-map.sh +97 -0
package/package.json ADDED
@@ -0,0 +1,113 @@
1
+ {
2
+ "name": "oc-codex-multi-auth",
3
+ "version": "6.0.0",
4
+ "description": "OpenCode plugin for Codex-first GPT-5 workflows with ChatGPT Plus/Pro OAuth, multi-account rotation, and guided setup",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "type": "module",
8
+ "license": "MIT",
9
+ "author": "Numman Ali",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/ndycode/oc-codex-multi-auth.git"
13
+ },
14
+ "keywords": [
15
+ "typescript",
16
+ "nodejs",
17
+ "opencode",
18
+ "opencode-plugin",
19
+ "openai",
20
+ "chatgpt",
21
+ "chatgpt-plus",
22
+ "chatgpt-pro",
23
+ "oauth",
24
+ "oauth2",
25
+ "pkce",
26
+ "gpt-5",
27
+ "codex",
28
+ "multi-account",
29
+ "developer-tools",
30
+ "terminal"
31
+ ],
32
+ "homepage": "https://github.com/ndycode/oc-codex-multi-auth#readme",
33
+ "bugs": {
34
+ "url": "https://github.com/ndycode/oc-codex-multi-auth/issues"
35
+ },
36
+ "scripts": {
37
+ "build": "tsc && node scripts/copy-oauth-success.js",
38
+ "typecheck": "tsc --noEmit",
39
+ "lint": "npm run lint:ts && npm run lint:scripts",
40
+ "lint:ts": "eslint . --ext .ts",
41
+ "lint:scripts": "eslint scripts --ext .js",
42
+ "lint:fix": "npm run lint:ts:fix && npm run lint:scripts:fix",
43
+ "lint:ts:fix": "eslint . --ext .ts --fix",
44
+ "lint:scripts:fix": "eslint scripts --ext .js --fix",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest",
47
+ "test:ui": "vitest --ui",
48
+ "test:coverage": "vitest run --coverage",
49
+ "coverage": "vitest run --coverage",
50
+ "audit:prod": "npm audit --omit=dev --audit-level=high",
51
+ "audit:all": "npm audit --audit-level=high",
52
+ "audit:dev:allowlist": "node scripts/audit-dev-allowlist.js",
53
+ "audit:ci": "npm run audit:prod && npm run audit:dev:allowlist",
54
+ "prepublishOnly": "npm run build",
55
+ "prepare": "husky"
56
+ },
57
+ "bin": {
58
+ "oc-codex-multi-auth": "scripts/install-oc-codex-multi-auth.js"
59
+ },
60
+ "files": [
61
+ "dist/",
62
+ "assets/",
63
+ "config/",
64
+ "scripts/",
65
+ "README.md",
66
+ "LICENSE"
67
+ ],
68
+ "lint-staged": {
69
+ "*.ts": [
70
+ "eslint --max-warnings=0 --fix --no-warn-ignored"
71
+ ],
72
+ "scripts/**/*.js": [
73
+ "eslint --max-warnings=0 --fix --no-warn-ignored"
74
+ ]
75
+ },
76
+ "engines": {
77
+ "node": ">=18.0.0"
78
+ },
79
+ "peerDependencies": {
80
+ "typescript": "^5"
81
+ },
82
+ "devDependencies": {
83
+ "@fast-check/vitest": "^0.2.4",
84
+ "@opencode-ai/sdk": "^1.2.10",
85
+ "@types/node": "^25.3.0",
86
+ "@typescript-eslint/eslint-plugin": "^8.56.0",
87
+ "@typescript-eslint/parser": "^8.56.0",
88
+ "@vitest/coverage-v8": "^4.0.18",
89
+ "@vitest/ui": "^4.0.18",
90
+ "eslint": "^10.0.0",
91
+ "fast-check": "^4.5.3",
92
+ "husky": "^9.1.7",
93
+ "lint-staged": "^16.2.7",
94
+ "typescript": "^5.9.3",
95
+ "typescript-language-server": "^5.1.3",
96
+ "vitest": "^4.0.18"
97
+ },
98
+ "dependencies": {
99
+ "@openauthjs/openauth": "^0.4.3",
100
+ "@opencode-ai/plugin": "^1.2.9",
101
+ "hono": "4.12.8",
102
+ "zod": "^4.3.6"
103
+ },
104
+ "overrides": {
105
+ "flatted": "3.4.2",
106
+ "hono": "4.12.8",
107
+ "rollup": "4.60.0",
108
+ "vite": "^7.3.1",
109
+ "@typescript-eslint/typescript-estree": {
110
+ "minimatch": "^9.0.5"
111
+ }
112
+ }
113
+ }
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execSync } from "node:child_process";
4
+
5
+ const ALLOWED_HIGH_OR_CRITICAL_PACKAGES = new Set([
6
+ "eslint",
7
+ "ajv",
8
+ "@eslint-community/eslint-utils",
9
+ "@typescript-eslint/eslint-plugin",
10
+ "@typescript-eslint/parser",
11
+ "@typescript-eslint/type-utils",
12
+ "@typescript-eslint/typescript-estree",
13
+ "@typescript-eslint/utils",
14
+ "minimatch",
15
+ ]);
16
+
17
+ function summarizeVia(via) {
18
+ if (!Array.isArray(via)) return [];
19
+ return via
20
+ .map((item) => {
21
+ if (typeof item === "string") return item;
22
+ if (!item || typeof item !== "object") return "unknown";
23
+ const name = typeof item.name === "string" ? item.name : "unknown";
24
+ const range = typeof item.range === "string" ? item.range : "";
25
+ return range ? `${name}:${range}` : name;
26
+ })
27
+ .slice(0, 5);
28
+ }
29
+
30
+ let rawAuditOutput = "";
31
+ try {
32
+ rawAuditOutput = execSync("npm audit --json", {
33
+ encoding: "utf8",
34
+ stdio: ["ignore", "pipe", "pipe"],
35
+ }).trim();
36
+ } catch (error) {
37
+ const execError = error;
38
+ const stdout =
39
+ execError &&
40
+ typeof execError === "object" &&
41
+ "stdout" in execError &&
42
+ typeof execError.stdout === "string"
43
+ ? execError.stdout
44
+ : "";
45
+ const stderr =
46
+ execError &&
47
+ typeof execError === "object" &&
48
+ "stderr" in execError &&
49
+ typeof execError.stderr === "string"
50
+ ? execError.stderr
51
+ : "";
52
+ rawAuditOutput = stdout.trim() || stderr.trim();
53
+ }
54
+
55
+ if (!rawAuditOutput) {
56
+ console.error("Failed to read npm audit JSON output.");
57
+ process.exit(1);
58
+ }
59
+
60
+ let auditJson;
61
+ try {
62
+ auditJson = JSON.parse(rawAuditOutput.replace(/^\uFEFF/, ""));
63
+ } catch (error) {
64
+ console.error("Failed to parse npm audit JSON output.");
65
+ console.error(error instanceof Error ? error.message : String(error));
66
+ process.exit(1);
67
+ }
68
+
69
+ const vulnerabilities =
70
+ auditJson && typeof auditJson === "object" && auditJson.vulnerabilities && typeof auditJson.vulnerabilities === "object"
71
+ ? auditJson.vulnerabilities
72
+ : {};
73
+
74
+ const unexpected = [];
75
+ const allowlisted = [];
76
+
77
+ for (const [name, details] of Object.entries(vulnerabilities)) {
78
+ if (!details || typeof details !== "object") continue;
79
+ const severity = typeof details.severity === "string" ? details.severity : "unknown";
80
+ if (severity !== "high" && severity !== "critical") continue;
81
+
82
+ const entry = {
83
+ name,
84
+ severity,
85
+ via: summarizeVia(details.via),
86
+ fixAvailable: details.fixAvailable ?? false,
87
+ };
88
+
89
+ if (ALLOWED_HIGH_OR_CRITICAL_PACKAGES.has(name)) {
90
+ allowlisted.push(entry);
91
+ continue;
92
+ }
93
+ unexpected.push(entry);
94
+ }
95
+
96
+ if (unexpected.length > 0) {
97
+ console.error("Unexpected high/critical vulnerabilities detected in dev dependency audit:");
98
+ for (const entry of unexpected) {
99
+ console.error(
100
+ `- ${entry.name} (${entry.severity}) via ${entry.via.join(", ") || "unknown"} fixAvailable=${String(entry.fixAvailable)}`,
101
+ );
102
+ }
103
+ process.exit(1);
104
+ }
105
+
106
+ if (allowlisted.length > 0) {
107
+ console.warn("Allowlisted high/critical dev vulnerabilities detected:");
108
+ for (const entry of allowlisted) {
109
+ console.warn(
110
+ `- ${entry.name} (${entry.severity}) via ${entry.via.join(", ") || "unknown"} fixAvailable=${String(entry.fixAvailable)}`,
111
+ );
112
+ }
113
+ console.warn("No unexpected high/critical vulnerabilities found.");
114
+ }
@@ -0,0 +1,37 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+
8
+ function normalizePathForCompare(path) {
9
+ const resolved = resolve(path);
10
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
11
+ }
12
+
13
+ function getDefaultPaths() {
14
+ const src = join(__dirname, "..", "lib", "oauth-success.html");
15
+ const dest = join(__dirname, "..", "dist", "lib", "oauth-success.html");
16
+ return { src, dest };
17
+ }
18
+
19
+ export async function copyOAuthSuccessHtml(options = {}) {
20
+ const defaults = getDefaultPaths();
21
+ const src = options.src ?? defaults.src;
22
+ const dest = options.dest ?? defaults.dest;
23
+
24
+ await fs.mkdir(dirname(dest), { recursive: true });
25
+ await fs.copyFile(src, dest);
26
+
27
+ return { src, dest };
28
+ }
29
+
30
+ const isDirectRun = (() => {
31
+ if (!process.argv[1]) return false;
32
+ return normalizePathForCompare(process.argv[1]) === normalizePathForCompare(__filename);
33
+ })();
34
+
35
+ if (isDirectRun) {
36
+ await copyOAuthSuccessHtml();
37
+ }
@@ -0,0 +1,381 @@
1
+ import { existsSync } from "node:fs";
2
+ import { copyFile, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const PACKAGE_NAME = "oc-codex-multi-auth";
8
+ const LEGACY_PACKAGE_NAMES = ["oc-chatgpt-multi-auth"];
9
+ const WINDOWS_RENAME_RETRY_ATTEMPTS = 5;
10
+ const WINDOWS_RENAME_RETRY_BASE_DELAY_MS = 10;
11
+
12
+ function getManagedPackageNames() {
13
+ return [PACKAGE_NAME, ...LEGACY_PACKAGE_NAMES];
14
+ }
15
+
16
+ function printHelp() {
17
+ console.log(`Usage: ${PACKAGE_NAME} [--modern|--legacy] [--dry-run] [--no-cache-clear]\n\n` +
18
+ "Default behavior:\n" +
19
+ " - Installs/updates global config at ~/.config/opencode/opencode.json\n" +
20
+ " - Uses full catalog config by default (9 base models + 34 explicit presets)\n" +
21
+ " - Ensures plugin is unpinned (latest)\n" +
22
+ " - Clears OpenCode plugin cache\n\n" +
23
+ "Options:\n" +
24
+ " --modern Force compact modern config (9 base models + --variant presets)\n" +
25
+ " --legacy Force explicit legacy config (34 preset model entries)\n" +
26
+ " --dry-run Show actions without writing\n" +
27
+ " --no-cache-clear Skip clearing OpenCode cache\n"
28
+ );
29
+ }
30
+
31
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
32
+ const repoRoot = resolve(scriptDir, "..");
33
+ const modernTemplatePath = join(repoRoot, "config", "opencode-modern.json");
34
+ const legacyTemplatePath = join(repoRoot, "config", "opencode-legacy.json");
35
+
36
+ function log(message) {
37
+ console.log(message);
38
+ }
39
+
40
+ function delay(ms) {
41
+ return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
42
+ }
43
+
44
+ function isWindowsLockError(error) {
45
+ const code = error?.code;
46
+ return code === "EPERM" || code === "EBUSY";
47
+ }
48
+
49
+ function formatErrorForLog(error) {
50
+ if (error instanceof Error) {
51
+ return error.message;
52
+ }
53
+ return String(error);
54
+ }
55
+
56
+ function resolveHomeDirectory(env = process.env) {
57
+ return env.HOME || env.USERPROFILE || homedir();
58
+ }
59
+
60
+ function buildPaths(homeDir) {
61
+ const configDir = join(homeDir, ".config", "opencode");
62
+ const cacheDir = join(homeDir, ".cache", "opencode");
63
+ return {
64
+ configDir,
65
+ configPath: join(configDir, "opencode.json"),
66
+ cacheDir,
67
+ cacheNodeModulesPaths: getManagedPackageNames().map((name) => join(cacheDir, "node_modules", name)),
68
+ cacheBunLock: join(cacheDir, "bun.lock"),
69
+ cachePackageJson: join(cacheDir, "package.json"),
70
+ modernTemplatePath,
71
+ legacyTemplatePath,
72
+ };
73
+ }
74
+
75
+ function parseCliArgs(argv = process.argv.slice(2)) {
76
+ const args = new Set(argv);
77
+ if (args.has("--help") || args.has("-h")) {
78
+ return {
79
+ wantsHelp: true,
80
+ };
81
+ }
82
+
83
+ const requestedModern = args.has("--modern");
84
+ const requestedLegacy = args.has("--legacy");
85
+
86
+ if (requestedModern && requestedLegacy) {
87
+ throw new Error("Choose only one of --modern or --legacy.");
88
+ }
89
+
90
+ return {
91
+ wantsHelp: false,
92
+ dryRun: args.has("--dry-run"),
93
+ skipCacheClear: args.has("--no-cache-clear"),
94
+ configMode: requestedModern ? "modern" : requestedLegacy ? "legacy" : "full",
95
+ };
96
+ }
97
+
98
+ function normalizePluginList(list) {
99
+ const entries = Array.isArray(list) ? list.filter(Boolean) : [];
100
+ const managedNames = getManagedPackageNames();
101
+ const filtered = entries.filter((entry) => {
102
+ if (typeof entry !== "string") return true;
103
+ return !managedNames.some((name) => entry === name || entry.startsWith(`${name}@`));
104
+ });
105
+ return [...filtered, PACKAGE_NAME];
106
+ }
107
+
108
+ function formatJson(obj) {
109
+ return `${JSON.stringify(obj, null, 2)}\n`;
110
+ }
111
+
112
+ function mergeFullTemplate(modernTemplate, legacyTemplate) {
113
+ const modernModels = modernTemplate.provider?.openai?.models ?? {};
114
+ const legacyModels = legacyTemplate.provider?.openai?.models ?? {};
115
+ const overlappingKeys = Object.keys(modernModels).filter((key) => Object.hasOwn(legacyModels, key));
116
+
117
+ if (overlappingKeys.length > 0) {
118
+ throw new Error(`Full config template collision for model keys: ${overlappingKeys.join(", ")}`);
119
+ }
120
+
121
+ return {
122
+ ...modernTemplate,
123
+ provider: {
124
+ ...(modernTemplate.provider ?? {}),
125
+ openai: {
126
+ ...(modernTemplate.provider?.openai ?? {}),
127
+ models: {
128
+ ...modernModels,
129
+ ...legacyModels,
130
+ },
131
+ },
132
+ },
133
+ };
134
+ }
135
+
136
+ async function readJson(filePath) {
137
+ const content = await readFile(filePath, "utf-8");
138
+ return JSON.parse(content);
139
+ }
140
+
141
+ async function renameWithWindowsRetry(sourcePath, destinationPath) {
142
+ let lastError = null;
143
+
144
+ for (let attempt = 0; attempt < WINDOWS_RENAME_RETRY_ATTEMPTS; attempt += 1) {
145
+ try {
146
+ await rename(sourcePath, destinationPath);
147
+ return;
148
+ } catch (error) {
149
+ if (isWindowsLockError(error)) {
150
+ lastError = error;
151
+ await delay(WINDOWS_RENAME_RETRY_BASE_DELAY_MS * 2 ** attempt);
152
+ continue;
153
+ }
154
+ throw error;
155
+ }
156
+ }
157
+
158
+ if (lastError) {
159
+ throw lastError;
160
+ }
161
+ }
162
+
163
+ async function writeFileAtomic(filePath, content) {
164
+ const uniqueSuffix = `${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
165
+ const tempPath = `${filePath}.${uniqueSuffix}.tmp`;
166
+
167
+ try {
168
+ await mkdir(dirname(filePath), { recursive: true });
169
+ await writeFile(tempPath, content, { encoding: "utf-8", mode: 0o600 });
170
+ await renameWithWindowsRetry(tempPath, filePath);
171
+ } catch (error) {
172
+ await rm(tempPath, { force: true }).catch(() => {});
173
+ throw error;
174
+ }
175
+ }
176
+
177
+ async function loadTemplate(mode, paths) {
178
+ if (mode === "modern") {
179
+ return readJson(paths.modernTemplatePath);
180
+ }
181
+ if (mode === "legacy") {
182
+ return readJson(paths.legacyTemplatePath);
183
+ }
184
+
185
+ const [modernTemplate, legacyTemplate] = await Promise.all([
186
+ readJson(paths.modernTemplatePath),
187
+ readJson(paths.legacyTemplatePath),
188
+ ]);
189
+
190
+ return mergeFullTemplate(modernTemplate, legacyTemplate);
191
+ }
192
+
193
+ async function copyFileWithWindowsRetry(sourcePath, destinationPath) {
194
+ let lastError = null;
195
+
196
+ for (let attempt = 0; attempt < WINDOWS_RENAME_RETRY_ATTEMPTS; attempt += 1) {
197
+ try {
198
+ await copyFile(sourcePath, destinationPath);
199
+ return;
200
+ } catch (error) {
201
+ if (isWindowsLockError(error)) {
202
+ lastError = error;
203
+ await delay(WINDOWS_RENAME_RETRY_BASE_DELAY_MS * 2 ** attempt);
204
+ continue;
205
+ }
206
+ throw error;
207
+ }
208
+ }
209
+
210
+ if (lastError) {
211
+ throw lastError;
212
+ }
213
+ }
214
+
215
+ async function backupConfig(sourcePath, dryRun) {
216
+ const timestamp = new Date()
217
+ .toISOString()
218
+ .replace(/[:.]/g, "-")
219
+ .replace("T", "_")
220
+ .replace("Z", "");
221
+ const backupPath = `${sourcePath}.bak-${timestamp}`;
222
+ if (!dryRun) {
223
+ await copyFileWithWindowsRetry(sourcePath, backupPath);
224
+ }
225
+ return backupPath;
226
+ }
227
+
228
+ async function removePluginFromCachePackage(paths, dryRun) {
229
+ if (!existsSync(paths.cachePackageJson)) {
230
+ return;
231
+ }
232
+
233
+ let cacheData;
234
+ try {
235
+ cacheData = await readJson(paths.cachePackageJson);
236
+ } catch (error) {
237
+ log(`Warning: Could not parse ${paths.cachePackageJson} (${formatErrorForLog(error)}). Skipping.`);
238
+ return;
239
+ }
240
+
241
+ const sections = [
242
+ "dependencies",
243
+ "devDependencies",
244
+ "peerDependencies",
245
+ "optionalDependencies",
246
+ ];
247
+
248
+ let changed = false;
249
+ for (const section of sections) {
250
+ const deps = cacheData?.[section];
251
+ if (deps && typeof deps === "object") {
252
+ for (const name of getManagedPackageNames()) {
253
+ if (name in deps) {
254
+ delete deps[name];
255
+ changed = true;
256
+ }
257
+ }
258
+ }
259
+ }
260
+
261
+ if (!changed) {
262
+ return;
263
+ }
264
+
265
+ if (dryRun) {
266
+ log(`[dry-run] Would update ${paths.cachePackageJson} to remove ${getManagedPackageNames().join(", ")}`);
267
+ return;
268
+ }
269
+
270
+ await writeFileAtomic(paths.cachePackageJson, formatJson(cacheData));
271
+ }
272
+
273
+ async function clearCache(paths, dryRun, skipCacheClear) {
274
+ if (skipCacheClear) {
275
+ log("Skipping cache clear (--no-cache-clear).");
276
+ await removePluginFromCachePackage(paths, dryRun);
277
+ return;
278
+ }
279
+
280
+ if (dryRun) {
281
+ for (const cacheNodeModulesPath of paths.cacheNodeModulesPaths) {
282
+ log(`[dry-run] Would remove ${cacheNodeModulesPath}`);
283
+ }
284
+ log(`[dry-run] Would remove ${paths.cacheBunLock}`);
285
+ } else {
286
+ for (const cacheNodeModulesPath of paths.cacheNodeModulesPaths) {
287
+ await rm(cacheNodeModulesPath, { recursive: true, force: true });
288
+ }
289
+ await rm(paths.cacheBunLock, { force: true });
290
+ }
291
+
292
+ await removePluginFromCachePackage(paths, dryRun);
293
+ }
294
+
295
+ export async function runInstaller(argv = process.argv.slice(2), options = {}) {
296
+ const parsed = parseCliArgs(argv);
297
+ if (parsed.wantsHelp) {
298
+ printHelp();
299
+ return { exitCode: 0, action: "help" };
300
+ }
301
+
302
+ const { env = process.env } = options;
303
+ const { configMode, dryRun, skipCacheClear } = parsed;
304
+ const paths = buildPaths(resolveHomeDirectory(env));
305
+ const requiredTemplatePaths = configMode === "modern"
306
+ ? [paths.modernTemplatePath]
307
+ : configMode === "legacy"
308
+ ? [paths.legacyTemplatePath]
309
+ : [paths.modernTemplatePath, paths.legacyTemplatePath];
310
+
311
+ for (const templatePath of requiredTemplatePaths) {
312
+ if (!existsSync(templatePath)) {
313
+ throw new Error(`Config template not found at ${templatePath}`);
314
+ }
315
+ }
316
+
317
+ const template = await loadTemplate(configMode, paths);
318
+ template.plugin = [PACKAGE_NAME];
319
+
320
+ let nextConfig = template;
321
+ if (existsSync(paths.configPath)) {
322
+ const backupPath = await backupConfig(paths.configPath, dryRun);
323
+ log(`${dryRun ? "[dry-run] Would create backup" : "Backup created"}: ${backupPath}`);
324
+
325
+ try {
326
+ const existing = await readJson(paths.configPath);
327
+ const merged = { ...existing };
328
+ merged.plugin = normalizePluginList(existing.plugin);
329
+ const provider = (existing.provider && typeof existing.provider === "object")
330
+ ? { ...existing.provider }
331
+ : {};
332
+ provider.openai = template.provider.openai;
333
+ merged.provider = provider;
334
+ nextConfig = merged;
335
+ } catch (error) {
336
+ log(`Warning: Could not parse existing config (${formatErrorForLog(error)}). Replacing with template.`);
337
+ nextConfig = template;
338
+ }
339
+ } else {
340
+ log("No existing config found. Creating new global config.");
341
+ }
342
+
343
+ if (dryRun) {
344
+ log(`[dry-run] Would write ${paths.configPath} using ${configMode} config`);
345
+ } else {
346
+ await writeFileAtomic(paths.configPath, formatJson(nextConfig));
347
+ log(`Wrote ${paths.configPath} (${configMode} config)`);
348
+ }
349
+
350
+ await clearCache(paths, dryRun, skipCacheClear);
351
+
352
+ log("\nDone. Restart OpenCode to (re)install the plugin.");
353
+ log("Example: opencode");
354
+ if (configMode === "modern") {
355
+ log("Note: Modern config intentionally shows 9 base model entries; use --variant to access all 34 shipped presets.");
356
+ }
357
+ if (configMode === "legacy") {
358
+ log("Note: Legacy config writes 34 explicit preset entries and is also safe for older OpenCode versions.");
359
+ }
360
+ if (configMode === "full") {
361
+ log("Note: Full config installs both modern base models and explicit preset entries so the full shipped catalog is visible by default.");
362
+ }
363
+
364
+ return {
365
+ exitCode: 0,
366
+ action: "install",
367
+ configMode,
368
+ configPath: paths.configPath,
369
+ };
370
+ }
371
+
372
+ export const __test = {
373
+ buildPaths,
374
+ backupConfig,
375
+ copyFileWithWindowsRetry,
376
+ mergeFullTemplate,
377
+ parseCliArgs,
378
+ writeFileAtomic,
379
+ renameWithWindowsRetry,
380
+ resolveHomeDirectory,
381
+ };
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { runInstaller } from "./install-oc-codex-multi-auth-core.js";
6
+
7
+ export * from "./install-oc-codex-multi-auth-core.js";
8
+
9
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
10
+ runInstaller().catch((error) => {
11
+ const message = error instanceof Error ? error.message : String(error);
12
+ console.error(`Installer failed: ${message}`);
13
+ process.exit(1);
14
+ });
15
+ }