codexgpt 1.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 (654) hide show
  1. package/AGENTS.example.md +20 -0
  2. package/CHANGELOG.md +342 -0
  3. package/CHATGPT_PROMPT.md +12 -0
  4. package/CLOUDFLARED_VERIFIED_INSTALL.md +94 -0
  5. package/CODEX_PROMPT.md +12 -0
  6. package/CONTRIBUTING.md +62 -0
  7. package/DOMAIN_SETUP.md +275 -0
  8. package/FAQ.md +366 -0
  9. package/FAQ_ZH.md +329 -0
  10. package/LICENSE +21 -0
  11. package/PUBLIC_LAUNCH_CHECKLIST.md +111 -0
  12. package/README.md +490 -0
  13. package/README_ZH.md +747 -0
  14. package/SECURITY.md +295 -0
  15. package/config.example.env +103 -0
  16. package/dist/access/fullAccessLease.js +148 -0
  17. package/dist/access/fullAccessLease.js.map +1 -0
  18. package/dist/access/protectedRoots.js +122 -0
  19. package/dist/access/protectedRoots.js.map +1 -0
  20. package/dist/access/rootAdmission.js +347 -0
  21. package/dist/access/rootAdmission.js.map +1 -0
  22. package/dist/analysis/cache.js +27 -0
  23. package/dist/analysis/cache.js.map +1 -0
  24. package/dist/analysis/classify.js +102 -0
  25. package/dist/analysis/classify.js.map +1 -0
  26. package/dist/analysis/extract.js +139 -0
  27. package/dist/analysis/extract.js.map +1 -0
  28. package/dist/analysis/graph.js +22 -0
  29. package/dist/analysis/graph.js.map +1 -0
  30. package/dist/analysis/impact.js +167 -0
  31. package/dist/analysis/impact.js.map +1 -0
  32. package/dist/analysis/index.js +215 -0
  33. package/dist/analysis/index.js.map +1 -0
  34. package/dist/analysis/inventory.js +51 -0
  35. package/dist/analysis/inventory.js.map +1 -0
  36. package/dist/analysis/providers.js +24 -0
  37. package/dist/analysis/providers.js.map +1 -0
  38. package/dist/analysis/rank.js +27 -0
  39. package/dist/analysis/rank.js.map +1 -0
  40. package/dist/analysis/types.js +8 -0
  41. package/dist/analysis/types.js.map +1 -0
  42. package/dist/audit/canonicalJson.js +79 -0
  43. package/dist/audit/canonicalJson.js.map +1 -0
  44. package/dist/audit/diagnostics.js +128 -0
  45. package/dist/audit/diagnostics.js.map +1 -0
  46. package/dist/audit/index.js +12 -0
  47. package/dist/audit/index.js.map +1 -0
  48. package/dist/audit/lifecycleV3.js +53 -0
  49. package/dist/audit/lifecycleV3.js.map +1 -0
  50. package/dist/audit/lifecycleV4.js +70 -0
  51. package/dist/audit/lifecycleV4.js.map +1 -0
  52. package/dist/audit/lock.js +195 -0
  53. package/dist/audit/lock.js.map +1 -0
  54. package/dist/audit/queryTool.js +120 -0
  55. package/dist/audit/queryTool.js.map +1 -0
  56. package/dist/audit/runtime.js +48 -0
  57. package/dist/audit/runtime.js.map +1 -0
  58. package/dist/audit/schemas.js +640 -0
  59. package/dist/audit/schemas.js.map +1 -0
  60. package/dist/audit/store.js +1309 -0
  61. package/dist/audit/store.js.map +1 -0
  62. package/dist/audit/transactionParticipant.js +45 -0
  63. package/dist/audit/transactionParticipant.js.map +1 -0
  64. package/dist/audit/types.js +11 -0
  65. package/dist/audit/types.js.map +1 -0
  66. package/dist/auth/audit.js +44 -0
  67. package/dist/auth/audit.js.map +1 -0
  68. package/dist/auth/authorizationStore.js +333 -0
  69. package/dist/auth/authorizationStore.js.map +1 -0
  70. package/dist/auth/challenges.js +25 -0
  71. package/dist/auth/challenges.js.map +1 -0
  72. package/dist/auth/clientStore.js +469 -0
  73. package/dist/auth/clientStore.js.map +1 -0
  74. package/dist/auth/cloudflareConfig.js +112 -0
  75. package/dist/auth/cloudflareConfig.js.map +1 -0
  76. package/dist/auth/configuration.js +167 -0
  77. package/dist/auth/configuration.js.map +1 -0
  78. package/dist/auth/credentialStore.js +65 -0
  79. package/dist/auth/credentialStore.js.map +1 -0
  80. package/dist/auth/deploymentLock.js +220 -0
  81. package/dist/auth/deploymentLock.js.map +1 -0
  82. package/dist/auth/deploymentRegistry.js +149 -0
  83. package/dist/auth/deploymentRegistry.js.map +1 -0
  84. package/dist/auth/errors.js +26 -0
  85. package/dist/auth/errors.js.map +1 -0
  86. package/dist/auth/grantStore.js +307 -0
  87. package/dist/auth/grantStore.js.map +1 -0
  88. package/dist/auth/index.js +39 -0
  89. package/dist/auth/index.js.map +1 -0
  90. package/dist/auth/keyManager.js +148 -0
  91. package/dist/auth/keyManager.js.map +1 -0
  92. package/dist/auth/localAdminSession.js +173 -0
  93. package/dist/auth/localAdminSession.js.map +1 -0
  94. package/dist/auth/metadata.js +69 -0
  95. package/dist/auth/metadata.js.map +1 -0
  96. package/dist/auth/oauthProvider.js +589 -0
  97. package/dist/auth/oauthProvider.js.map +1 -0
  98. package/dist/auth/ownerApproval.js +51 -0
  99. package/dist/auth/ownerApproval.js.map +1 -0
  100. package/dist/auth/policyIdentity.js +189 -0
  101. package/dist/auth/policyIdentity.js.map +1 -0
  102. package/dist/auth/rateLimits.js +66 -0
  103. package/dist/auth/rateLimits.js.map +1 -0
  104. package/dist/auth/recovery.js +275 -0
  105. package/dist/auth/recovery.js.map +1 -0
  106. package/dist/auth/requestContext.js +34 -0
  107. package/dist/auth/requestContext.js.map +1 -0
  108. package/dist/auth/resourceMiddleware.js +147 -0
  109. package/dist/auth/resourceMiddleware.js.map +1 -0
  110. package/dist/auth/runtimeStatus.js +51 -0
  111. package/dist/auth/runtimeStatus.js.map +1 -0
  112. package/dist/auth/schemas.js +84 -0
  113. package/dist/auth/schemas.js.map +1 -0
  114. package/dist/auth/stateStore.js +468 -0
  115. package/dist/auth/stateStore.js.map +1 -0
  116. package/dist/auth/tokenService.js +631 -0
  117. package/dist/auth/tokenService.js.map +1 -0
  118. package/dist/auth/toolSecurity.js +108 -0
  119. package/dist/auth/toolSecurity.js.map +1 -0
  120. package/dist/auth/types.js +6 -0
  121. package/dist/auth/types.js.map +1 -0
  122. package/dist/auth/windowsDpapi.js +193 -0
  123. package/dist/auth/windowsDpapi.js.map +1 -0
  124. package/dist/bashOps.js +292 -0
  125. package/dist/bashOps.js.map +1 -0
  126. package/dist/capabilitiesOps.js +412 -0
  127. package/dist/capabilitiesOps.js.map +1 -0
  128. package/dist/changesets/crypto.js +92 -0
  129. package/dist/changesets/crypto.js.map +1 -0
  130. package/dist/changesets/index.js +9 -0
  131. package/dist/changesets/index.js.map +1 -0
  132. package/dist/changesets/moveStore.js +237 -0
  133. package/dist/changesets/moveStore.js.map +1 -0
  134. package/dist/changesets/moveUndo.js +386 -0
  135. package/dist/changesets/moveUndo.js.map +1 -0
  136. package/dist/changesets/schemas.js +234 -0
  137. package/dist/changesets/schemas.js.map +1 -0
  138. package/dist/changesets/store.js +573 -0
  139. package/dist/changesets/store.js.map +1 -0
  140. package/dist/changesets/types.js +9 -0
  141. package/dist/changesets/types.js.map +1 -0
  142. package/dist/changesets/undo.js +525 -0
  143. package/dist/changesets/undo.js.map +1 -0
  144. package/dist/changesets/unifiedUndo.js +31 -0
  145. package/dist/changesets/unifiedUndo.js.map +1 -0
  146. package/dist/cliEnvironment.js +85 -0
  147. package/dist/cliEnvironment.js.map +1 -0
  148. package/dist/codexSessions.js +733 -0
  149. package/dist/codexSessions.js.map +1 -0
  150. package/dist/codexgptSupertool.js +216 -0
  151. package/dist/codexgptSupertool.js.map +1 -0
  152. package/dist/codexproSupertool.js +204 -0
  153. package/dist/codexproSupertool.js.map +1 -0
  154. package/dist/config.js +733 -0
  155. package/dist/config.js.map +1 -0
  156. package/dist/control/localApprovalClient.js +287 -0
  157. package/dist/control/localApprovalClient.js.map +1 -0
  158. package/dist/control/localApprovalServer.js +466 -0
  159. package/dist/control/localApprovalServer.js.map +1 -0
  160. package/dist/control/runtime.js +178 -0
  161. package/dist/control/runtime.js.map +1 -0
  162. package/dist/control/schemas.js +184 -0
  163. package/dist/control/schemas.js.map +1 -0
  164. package/dist/control/windowsLocalControl.js +348 -0
  165. package/dist/control/windowsLocalControl.js.map +1 -0
  166. package/dist/fsOps.js +541 -0
  167. package/dist/fsOps.js.map +1 -0
  168. package/dist/git/admission.js +26 -0
  169. package/dist/git/admission.js.map +1 -0
  170. package/dist/git/branchService.js +51 -0
  171. package/dist/git/branchService.js.map +1 -0
  172. package/dist/git/capabilities.js +143 -0
  173. package/dist/git/capabilities.js.map +1 -0
  174. package/dist/git/commitService.js +344 -0
  175. package/dist/git/commitService.js.map +1 -0
  176. package/dist/git/durableState.js +151 -0
  177. package/dist/git/durableState.js.map +1 -0
  178. package/dist/git/execution.js +451 -0
  179. package/dist/git/execution.js.map +1 -0
  180. package/dist/git/fileTransaction.js +50 -0
  181. package/dist/git/fileTransaction.js.map +1 -0
  182. package/dist/git/indexService.js +421 -0
  183. package/dist/git/indexService.js.map +1 -0
  184. package/dist/git/integrations.js +653 -0
  185. package/dist/git/integrations.js.map +1 -0
  186. package/dist/git/locks.js +287 -0
  187. package/dist/git/locks.js.map +1 -0
  188. package/dist/git/mutationContext.js +92 -0
  189. package/dist/git/mutationContext.js.map +1 -0
  190. package/dist/git/mutationJournal.js +91 -0
  191. package/dist/git/mutationJournal.js.map +1 -0
  192. package/dist/git/mutationService.js +352 -0
  193. package/dist/git/mutationService.js.map +1 -0
  194. package/dist/git/normalization.js +57 -0
  195. package/dist/git/normalization.js.map +1 -0
  196. package/dist/git/objectQuarantine.js +243 -0
  197. package/dist/git/objectQuarantine.js.map +1 -0
  198. package/dist/git/opaqueRecordStore.js +187 -0
  199. package/dist/git/opaqueRecordStore.js.map +1 -0
  200. package/dist/git/operationStore.js +287 -0
  201. package/dist/git/operationStore.js.map +1 -0
  202. package/dist/git/parsers.js +311 -0
  203. package/dist/git/parsers.js.map +1 -0
  204. package/dist/git/privateIndex.js +79 -0
  205. package/dist/git/privateIndex.js.map +1 -0
  206. package/dist/git/productionBootstrap.js +39 -0
  207. package/dist/git/productionBootstrap.js.map +1 -0
  208. package/dist/git/readService.js +846 -0
  209. package/dist/git/readService.js.map +1 -0
  210. package/dist/git/recovery.js +417 -0
  211. package/dist/git/recovery.js.map +1 -0
  212. package/dist/git/repositoryIdentity.js +611 -0
  213. package/dist/git/repositoryIdentity.js.map +1 -0
  214. package/dist/git/repositoryStore.js +237 -0
  215. package/dist/git/repositoryStore.js.map +1 -0
  216. package/dist/git/resources.js +113 -0
  217. package/dist/git/resources.js.map +1 -0
  218. package/dist/git/restoreService.js +178 -0
  219. package/dist/git/restoreService.js.map +1 -0
  220. package/dist/git/reviewToken.js +86 -0
  221. package/dist/git/reviewToken.js.map +1 -0
  222. package/dist/git/stashService.js +566 -0
  223. package/dist/git/stashService.js.map +1 -0
  224. package/dist/git/stateToken.js +139 -0
  225. package/dist/git/stateToken.js.map +1 -0
  226. package/dist/gitOps.js +147 -0
  227. package/dist/gitOps.js.map +1 -0
  228. package/dist/guard.js +608 -0
  229. package/dist/guard.js.map +1 -0
  230. package/dist/guidance/diagnostics.js +14 -0
  231. package/dist/guidance/diagnostics.js.map +1 -0
  232. package/dist/guidance/instructions.js +167 -0
  233. package/dist/guidance/instructions.js.map +1 -0
  234. package/dist/guidance/mode.js +18 -0
  235. package/dist/guidance/mode.js.map +1 -0
  236. package/dist/guidance/openaiSkillMetadata.js +29 -0
  237. package/dist/guidance/openaiSkillMetadata.js.map +1 -0
  238. package/dist/guidance/safeTextReader.js +171 -0
  239. package/dist/guidance/safeTextReader.js.map +1 -0
  240. package/dist/guidance/skillCatalog.js +70 -0
  241. package/dist/guidance/skillCatalog.js.map +1 -0
  242. package/dist/guidance/skillDiscovery.js +353 -0
  243. package/dist/guidance/skillDiscovery.js.map +1 -0
  244. package/dist/guidance/skillMetadata.js +115 -0
  245. package/dist/guidance/skillMetadata.js.map +1 -0
  246. package/dist/guidance/skillResources.js +146 -0
  247. package/dist/guidance/skillResources.js.map +1 -0
  248. package/dist/handoffOps.js +449 -0
  249. package/dist/handoffOps.js.map +1 -0
  250. package/dist/http/localAdminApp.js +203 -0
  251. package/dist/http/localAdminApp.js.map +1 -0
  252. package/dist/http/oauthMcpRuntime.js +294 -0
  253. package/dist/http/oauthMcpRuntime.js.map +1 -0
  254. package/dist/http/publicApp.js +350 -0
  255. package/dist/http/publicApp.js.map +1 -0
  256. package/dist/http/securityHeaders.js +11 -0
  257. package/dist/http/securityHeaders.js.map +1 -0
  258. package/dist/http.js +2076 -0
  259. package/dist/http.js.map +1 -0
  260. package/dist/moves/engine.js +1079 -0
  261. package/dist/moves/engine.js.map +1 -0
  262. package/dist/moves/index.js +6 -0
  263. package/dist/moves/index.js.map +1 -0
  264. package/dist/moves/planner.js +270 -0
  265. package/dist/moves/planner.js.map +1 -0
  266. package/dist/moves/recovery.js +557 -0
  267. package/dist/moves/recovery.js.map +1 -0
  268. package/dist/moves/service.js +300 -0
  269. package/dist/moves/service.js.map +1 -0
  270. package/dist/moves/types.js +11 -0
  271. package/dist/moves/types.js.map +1 -0
  272. package/dist/mutations/index.js +4 -0
  273. package/dist/mutations/index.js.map +1 -0
  274. package/dist/mutations/lifecycle.js +165 -0
  275. package/dist/mutations/lifecycle.js.map +1 -0
  276. package/dist/mutations/localService.js +163 -0
  277. package/dist/mutations/localService.js.map +1 -0
  278. package/dist/mutations/runtime.js +322 -0
  279. package/dist/mutations/runtime.js.map +1 -0
  280. package/dist/mutations/types.js +2 -0
  281. package/dist/mutations/types.js.map +1 -0
  282. package/dist/mutations/writers.js +360 -0
  283. package/dist/mutations/writers.js.map +1 -0
  284. package/dist/patchOps.js +420 -0
  285. package/dist/patchOps.js.map +1 -0
  286. package/dist/policy/approval.js +306 -0
  287. package/dist/policy/approval.js.map +1 -0
  288. package/dist/policy/audit.js +88 -0
  289. package/dist/policy/audit.js.map +1 -0
  290. package/dist/policy/authorizationFacts.js +187 -0
  291. package/dist/policy/authorizationFacts.js.map +1 -0
  292. package/dist/policy/compat.js +104 -0
  293. package/dist/policy/compat.js.map +1 -0
  294. package/dist/policy/context.js +31 -0
  295. package/dist/policy/context.js.map +1 -0
  296. package/dist/policy/enforcement.js +46 -0
  297. package/dist/policy/enforcement.js.map +1 -0
  298. package/dist/policy/evaluator.js +338 -0
  299. package/dist/policy/evaluator.js.map +1 -0
  300. package/dist/policy/executionCapabilities.js +80 -0
  301. package/dist/policy/executionCapabilities.js.map +1 -0
  302. package/dist/policy/executionResources.js +192 -0
  303. package/dist/policy/executionResources.js.map +1 -0
  304. package/dist/policy/fullAccessResources.js +23 -0
  305. package/dist/policy/fullAccessResources.js.map +1 -0
  306. package/dist/policy/hardPolicy.js +50 -0
  307. package/dist/policy/hardPolicy.js.map +1 -0
  308. package/dist/policy/identity.js +142 -0
  309. package/dist/policy/identity.js.map +1 -0
  310. package/dist/policy/integration.js +331 -0
  311. package/dist/policy/integration.js.map +1 -0
  312. package/dist/policy/pendingApprovals.js +301 -0
  313. package/dist/policy/pendingApprovals.js.map +1 -0
  314. package/dist/policy/profileStore.js +367 -0
  315. package/dist/policy/profileStore.js.map +1 -0
  316. package/dist/policy/resources.js +249 -0
  317. package/dist/policy/resources.js.map +1 -0
  318. package/dist/policy/runtime.js +713 -0
  319. package/dist/policy/runtime.js.map +1 -0
  320. package/dist/policy/schemas.js +625 -0
  321. package/dist/policy/schemas.js.map +1 -0
  322. package/dist/policy/toolPolicy.js +141 -0
  323. package/dist/policy/toolPolicy.js.map +1 -0
  324. package/dist/policy/transport.js +10 -0
  325. package/dist/policy/transport.js.map +1 -0
  326. package/dist/policy/types.js +31 -0
  327. package/dist/policy/types.js.map +1 -0
  328. package/dist/proContext.js +675 -0
  329. package/dist/proContext.js.map +1 -0
  330. package/dist/process/authority.js +18 -0
  331. package/dist/process/authority.js.map +1 -0
  332. package/dist/process/backendDiscovery.js +83 -0
  333. package/dist/process/backendDiscovery.js.map +1 -0
  334. package/dist/process/commandCompiler.js +71 -0
  335. package/dist/process/commandCompiler.js.map +1 -0
  336. package/dist/process/outputCursor.js +67 -0
  337. package/dist/process/outputCursor.js.map +1 -0
  338. package/dist/process/outputQuota.js +94 -0
  339. package/dist/process/outputQuota.js.map +1 -0
  340. package/dist/process/outputRing.js +138 -0
  341. package/dist/process/outputRing.js.map +1 -0
  342. package/dist/process/processAuditCoordinator.js +52 -0
  343. package/dist/process/processAuditCoordinator.js.map +1 -0
  344. package/dist/process/processManager.js +311 -0
  345. package/dist/process/processManager.js.map +1 -0
  346. package/dist/process/runCommand.js +332 -0
  347. package/dist/process/runCommand.js.map +1 -0
  348. package/dist/process/streamingRedactor.js +105 -0
  349. package/dist/process/streamingRedactor.js.map +1 -0
  350. package/dist/process/types.js +2 -0
  351. package/dist/process/types.js.map +1 -0
  352. package/dist/process/windowsHostClient.js +604 -0
  353. package/dist/process/windowsHostClient.js.map +1 -0
  354. package/dist/process/windowsHostProtocol.js +270 -0
  355. package/dist/process/windowsHostProtocol.js.map +1 -0
  356. package/dist/process/windowsPersistentBackend.js +263 -0
  357. package/dist/process/windowsPersistentBackend.js.map +1 -0
  358. package/dist/productionRuntime.js +907 -0
  359. package/dist/productionRuntime.js.map +1 -0
  360. package/dist/profileStore.js +231 -0
  361. package/dist/profileStore.js.map +1 -0
  362. package/dist/redact.js +124 -0
  363. package/dist/redact.js.map +1 -0
  364. package/dist/searchOps.js +186 -0
  365. package/dist/searchOps.js.map +1 -0
  366. package/dist/selfTestOps.js +786 -0
  367. package/dist/selfTestOps.js.map +1 -0
  368. package/dist/semantic/budgets.js +12 -0
  369. package/dist/semantic/budgets.js.map +1 -0
  370. package/dist/semantic/builtin/typescriptAssets.js +68 -0
  371. package/dist/semantic/builtin/typescriptAssets.js.map +1 -0
  372. package/dist/semantic/builtin/typescriptProvider.js +211 -0
  373. package/dist/semantic/builtin/typescriptProvider.js.map +1 -0
  374. package/dist/semantic/builtin/typescriptWorker.js +365 -0
  375. package/dist/semantic/builtin/typescriptWorker.js.map +1 -0
  376. package/dist/semantic/diffPreview.js +36 -0
  377. package/dist/semantic/diffPreview.js.map +1 -0
  378. package/dist/semantic/index.js +11 -0
  379. package/dist/semantic/index.js.map +1 -0
  380. package/dist/semantic/manager.js +522 -0
  381. package/dist/semantic/manager.js.map +1 -0
  382. package/dist/semantic/positions.js +86 -0
  383. package/dist/semantic/positions.js.map +1 -0
  384. package/dist/semantic/previewStore.js +276 -0
  385. package/dist/semantic/previewStore.js.map +1 -0
  386. package/dist/semantic/projectResolver.js +309 -0
  387. package/dist/semantic/projectResolver.js.map +1 -0
  388. package/dist/semantic/sourceSnapshot.js +54 -0
  389. package/dist/semantic/sourceSnapshot.js.map +1 -0
  390. package/dist/semantic/status.js +46 -0
  391. package/dist/semantic/status.js.map +1 -0
  392. package/dist/semantic/types.js +2 -0
  393. package/dist/semantic/types.js.map +1 -0
  394. package/dist/server.js +8676 -0
  395. package/dist/server.js.map +1 -0
  396. package/dist/stdio.js +64 -0
  397. package/dist/stdio.js.map +1 -0
  398. package/dist/toolCardWidget.js +2000 -0
  399. package/dist/toolCardWidget.js.map +1 -0
  400. package/dist/tools/contracts/catalog.js +111 -0
  401. package/dist/tools/contracts/catalog.js.map +1 -0
  402. package/dist/tools/contracts/index.js +8 -0
  403. package/dist/tools/contracts/index.js.map +1 -0
  404. package/dist/tools/contracts/registration.js +27 -0
  405. package/dist/tools/contracts/registration.js.map +1 -0
  406. package/dist/tools/contracts/schemas.js +11 -0
  407. package/dist/tools/contracts/schemas.js.map +1 -0
  408. package/dist/tools/contracts/types.js +2 -0
  409. package/dist/tools/contracts/types.js.map +1 -0
  410. package/dist/tools/contracts/v3.js +76 -0
  411. package/dist/tools/contracts/v3.js.map +1 -0
  412. package/dist/tools/contracts/v4.js +39 -0
  413. package/dist/tools/contracts/v4.js.map +1 -0
  414. package/dist/tools/contracts/v5.js +20 -0
  415. package/dist/tools/contracts/v5.js.map +1 -0
  416. package/dist/tools/phase3dServer.js +350 -0
  417. package/dist/tools/phase3dServer.js.map +1 -0
  418. package/dist/tools/schemas/applyPatch.js +388 -0
  419. package/dist/tools/schemas/applyPatch.js.map +1 -0
  420. package/dist/tools/schemas/bash.js +202 -0
  421. package/dist/tools/schemas/bash.js.map +1 -0
  422. package/dist/tools/schemas/closeWorkspace.js +105 -0
  423. package/dist/tools/schemas/closeWorkspace.js.map +1 -0
  424. package/dist/tools/schemas/codexContext.js +402 -0
  425. package/dist/tools/schemas/codexContext.js.map +1 -0
  426. package/dist/tools/schemas/codexSessions.js +328 -0
  427. package/dist/tools/schemas/codexSessions.js.map +1 -0
  428. package/dist/tools/schemas/codexgpt.js +1068 -0
  429. package/dist/tools/schemas/codexgpt.js.map +1 -0
  430. package/dist/tools/schemas/codexgptInventory.js +359 -0
  431. package/dist/tools/schemas/codexgptInventory.js.map +1 -0
  432. package/dist/tools/schemas/codexgptSelfTest.js +563 -0
  433. package/dist/tools/schemas/codexgptSelfTest.js.map +1 -0
  434. package/dist/tools/schemas/codexpro.js +917 -0
  435. package/dist/tools/schemas/codexpro.js.map +1 -0
  436. package/dist/tools/schemas/codexproInventory.js +352 -0
  437. package/dist/tools/schemas/codexproInventory.js.map +1 -0
  438. package/dist/tools/schemas/codexproSelfTest.js +546 -0
  439. package/dist/tools/schemas/codexproSelfTest.js.map +1 -0
  440. package/dist/tools/schemas/common.js +21 -0
  441. package/dist/tools/schemas/common.js.map +1 -0
  442. package/dist/tools/schemas/createTaskWorktree.js +51 -0
  443. package/dist/tools/schemas/createTaskWorktree.js.map +1 -0
  444. package/dist/tools/schemas/edit.js +321 -0
  445. package/dist/tools/schemas/edit.js.map +1 -0
  446. package/dist/tools/schemas/execution.js +330 -0
  447. package/dist/tools/schemas/execution.js.map +1 -0
  448. package/dist/tools/schemas/exportProContext.js +370 -0
  449. package/dist/tools/schemas/exportProContext.js.map +1 -0
  450. package/dist/tools/schemas/getTaskWorktree.js +19 -0
  451. package/dist/tools/schemas/getTaskWorktree.js.map +1 -0
  452. package/dist/tools/schemas/gitBranch.js +42 -0
  453. package/dist/tools/schemas/gitBranch.js.map +1 -0
  454. package/dist/tools/schemas/gitCommit.js +40 -0
  455. package/dist/tools/schemas/gitCommit.js.map +1 -0
  456. package/dist/tools/schemas/gitCreateBranch.js +22 -0
  457. package/dist/tools/schemas/gitCreateBranch.js.map +1 -0
  458. package/dist/tools/schemas/gitDiff.js +222 -0
  459. package/dist/tools/schemas/gitDiff.js.map +1 -0
  460. package/dist/tools/schemas/gitLog.js +41 -0
  461. package/dist/tools/schemas/gitLog.js.map +1 -0
  462. package/dist/tools/schemas/gitRestore.js +47 -0
  463. package/dist/tools/schemas/gitRestore.js.map +1 -0
  464. package/dist/tools/schemas/gitStage.js +33 -0
  465. package/dist/tools/schemas/gitStage.js.map +1 -0
  466. package/dist/tools/schemas/gitStash.js +102 -0
  467. package/dist/tools/schemas/gitStash.js.map +1 -0
  468. package/dist/tools/schemas/gitStatus.js +226 -0
  469. package/dist/tools/schemas/gitStatus.js.map +1 -0
  470. package/dist/tools/schemas/gitV4Common.js +205 -0
  471. package/dist/tools/schemas/gitV4Common.js.map +1 -0
  472. package/dist/tools/schemas/guidance.js +40 -0
  473. package/dist/tools/schemas/guidance.js.map +1 -0
  474. package/dist/tools/schemas/handoffToAgent.js +276 -0
  475. package/dist/tools/schemas/handoffToAgent.js.map +1 -0
  476. package/dist/tools/schemas/handoffToCodex.js +137 -0
  477. package/dist/tools/schemas/handoffToCodex.js.map +1 -0
  478. package/dist/tools/schemas/inspectWorkspace.js +379 -0
  479. package/dist/tools/schemas/inspectWorkspace.js.map +1 -0
  480. package/dist/tools/schemas/listTaskWorktrees.js +21 -0
  481. package/dist/tools/schemas/listTaskWorktrees.js.map +1 -0
  482. package/dist/tools/schemas/listWorkspaces.js +134 -0
  483. package/dist/tools/schemas/listWorkspaces.js.map +1 -0
  484. package/dist/tools/schemas/loadSkill.js +619 -0
  485. package/dist/tools/schemas/loadSkill.js.map +1 -0
  486. package/dist/tools/schemas/mergeTaskWorktree.js +105 -0
  487. package/dist/tools/schemas/mergeTaskWorktree.js.map +1 -0
  488. package/dist/tools/schemas/movePaths.js +175 -0
  489. package/dist/tools/schemas/movePaths.js.map +1 -0
  490. package/dist/tools/schemas/openCurrentWorkspace.js +174 -0
  491. package/dist/tools/schemas/openCurrentWorkspace.js.map +1 -0
  492. package/dist/tools/schemas/openFullAccessWorkspace.js +81 -0
  493. package/dist/tools/schemas/openFullAccessWorkspace.js.map +1 -0
  494. package/dist/tools/schemas/openWorkspace.js +193 -0
  495. package/dist/tools/schemas/openWorkspace.js.map +1 -0
  496. package/dist/tools/schemas/queryAuditEvents.js +193 -0
  497. package/dist/tools/schemas/queryAuditEvents.js.map +1 -0
  498. package/dist/tools/schemas/read.js +199 -0
  499. package/dist/tools/schemas/read.js.map +1 -0
  500. package/dist/tools/schemas/readCodexSession.js +353 -0
  501. package/dist/tools/schemas/readCodexSession.js.map +1 -0
  502. package/dist/tools/schemas/readHandoff.js +446 -0
  503. package/dist/tools/schemas/readHandoff.js.map +1 -0
  504. package/dist/tools/schemas/removeTaskWorktree.js +40 -0
  505. package/dist/tools/schemas/removeTaskWorktree.js.map +1 -0
  506. package/dist/tools/schemas/search.js +235 -0
  507. package/dist/tools/schemas/search.js.map +1 -0
  508. package/dist/tools/schemas/semantic.js +195 -0
  509. package/dist/tools/schemas/semantic.js.map +1 -0
  510. package/dist/tools/schemas/serverConfig.js +157 -0
  511. package/dist/tools/schemas/serverConfig.js.map +1 -0
  512. package/dist/tools/schemas/showChanges.js +283 -0
  513. package/dist/tools/schemas/showChanges.js.map +1 -0
  514. package/dist/tools/schemas/transactionResult.js +11 -0
  515. package/dist/tools/schemas/transactionResult.js.map +1 -0
  516. package/dist/tools/schemas/tree.js +136 -0
  517. package/dist/tools/schemas/tree.js.map +1 -0
  518. package/dist/tools/schemas/undoChangeSet.js +152 -0
  519. package/dist/tools/schemas/undoChangeSet.js.map +1 -0
  520. package/dist/tools/schemas/waitForHandoff.js +462 -0
  521. package/dist/tools/schemas/waitForHandoff.js.map +1 -0
  522. package/dist/tools/schemas/workspaceSnapshot.js +145 -0
  523. package/dist/tools/schemas/workspaceSnapshot.js.map +1 -0
  524. package/dist/tools/schemas/write.js +285 -0
  525. package/dist/tools/schemas/write.js.map +1 -0
  526. package/dist/transactions/atomicFs.js +438 -0
  527. package/dist/transactions/atomicFs.js.map +1 -0
  528. package/dist/transactions/atomicStateFile.js +190 -0
  529. package/dist/transactions/atomicStateFile.js.map +1 -0
  530. package/dist/transactions/engine.js +574 -0
  531. package/dist/transactions/engine.js.map +1 -0
  532. package/dist/transactions/index.js +12 -0
  533. package/dist/transactions/index.js.map +1 -0
  534. package/dist/transactions/installation.js +158 -0
  535. package/dist/transactions/installation.js.map +1 -0
  536. package/dist/transactions/manifestV2Store.js +96 -0
  537. package/dist/transactions/manifestV2Store.js.map +1 -0
  538. package/dist/transactions/participantRecovery.js +98 -0
  539. package/dist/transactions/participantRecovery.js.map +1 -0
  540. package/dist/transactions/recovery.js +513 -0
  541. package/dist/transactions/recovery.js.map +1 -0
  542. package/dist/transactions/schemas.js +365 -0
  543. package/dist/transactions/schemas.js.map +1 -0
  544. package/dist/transactions/stateRoot.js +63 -0
  545. package/dist/transactions/stateRoot.js.map +1 -0
  546. package/dist/transactions/types.js +11 -0
  547. package/dist/transactions/types.js.map +1 -0
  548. package/dist/transactions/workspaceLock.js +201 -0
  549. package/dist/transactions/workspaceLock.js.map +1 -0
  550. package/dist/workspaceOps.js +924 -0
  551. package/dist/workspaceOps.js.map +1 -0
  552. package/dist/worktrees/candidateWorkspace.js +566 -0
  553. package/dist/worktrees/candidateWorkspace.js.map +1 -0
  554. package/dist/worktrees/manager.js +258 -0
  555. package/dist/worktrees/manager.js.map +1 -0
  556. package/dist/worktrees/materializer.js +60 -0
  557. package/dist/worktrees/materializer.js.map +1 -0
  558. package/dist/worktrees/mergeExecute.js +498 -0
  559. package/dist/worktrees/mergeExecute.js.map +1 -0
  560. package/dist/worktrees/mergePlanStore.js +247 -0
  561. package/dist/worktrees/mergePlanStore.js.map +1 -0
  562. package/dist/worktrees/mergePrepare.js +709 -0
  563. package/dist/worktrees/mergePrepare.js.map +1 -0
  564. package/dist/worktrees/recovery.js +294 -0
  565. package/dist/worktrees/recovery.js.map +1 -0
  566. package/dist/worktrees/remove.js +296 -0
  567. package/dist/worktrees/remove.js.map +1 -0
  568. package/dist/worktrees/remover.js +57 -0
  569. package/dist/worktrees/remover.js.map +1 -0
  570. package/dist/worktrees/root.js +47 -0
  571. package/dist/worktrees/root.js.map +1 -0
  572. package/dist/worktrees/service.js +245 -0
  573. package/dist/worktrees/service.js.map +1 -0
  574. package/dist/worktrees/store.js +156 -0
  575. package/dist/worktrees/store.js.map +1 -0
  576. package/dist/worktrees/treeManifest.js +106 -0
  577. package/dist/worktrees/treeManifest.js.map +1 -0
  578. package/dist/worktrees/verificationReceipts.js +215 -0
  579. package/dist/worktrees/verificationReceipts.js.map +1 -0
  580. package/dist/worktrees/verificationTerminal.js +23 -0
  581. package/dist/worktrees/verificationTerminal.js.map +1 -0
  582. package/dist/worktrees/workspaceAuthority.js +64 -0
  583. package/dist/worktrees/workspaceAuthority.js.map +1 -0
  584. package/docs/favicon.svg +5 -0
  585. package/docs/index.html +624 -0
  586. package/docs/og.svg +52 -0
  587. package/docs/script.js +33 -0
  588. package/docs/star.svg +11 -0
  589. package/docs/styles.css +1571 -0
  590. package/docs/zh.html +428 -0
  591. package/package.json +121 -0
  592. package/scripts/analysis-cli-smoke.mjs +81 -0
  593. package/scripts/analysis-smoke.mjs +179 -0
  594. package/scripts/atomic-file.mjs +137 -0
  595. package/scripts/ci-change-classifier.mjs +59 -0
  596. package/scripts/ci-failure-summary.mjs +92 -0
  597. package/scripts/cloudflared-installer.mjs +215 -0
  598. package/scripts/cloudflared-release.mjs +63 -0
  599. package/scripts/codexgpt-entry.mjs +235 -0
  600. package/scripts/codexgpt.mjs +4987 -0
  601. package/scripts/connector-auth-output-shim.cjs +66 -0
  602. package/scripts/doctor-smoke.mjs +90 -0
  603. package/scripts/doctor.mjs +165 -0
  604. package/scripts/exact-head-ci.mjs +126 -0
  605. package/scripts/execute-handoff-smoke-platform-compat.mjs +38 -0
  606. package/scripts/execute-handoff-smoke.mjs +1117 -0
  607. package/scripts/g8-u-live-probe.mjs +208 -0
  608. package/scripts/http-smoke-compat.mjs +151 -0
  609. package/scripts/http-smoke.mjs +776 -0
  610. package/scripts/long-task-runner.mjs +1037 -0
  611. package/scripts/oauth-admin.mjs +1608 -0
  612. package/scripts/output-bounds.mjs +29 -0
  613. package/scripts/owned-temp-root.d.mts +50 -0
  614. package/scripts/owned-temp-root.mjs +567 -0
  615. package/scripts/phase-4a-smoke.mjs +36 -0
  616. package/scripts/policy-windows-spike.mjs +135 -0
  617. package/scripts/pro-apply.mjs +152 -0
  618. package/scripts/pro-bundle.mjs +121 -0
  619. package/scripts/pro-smoke.mjs +97 -0
  620. package/scripts/process-identity.mjs +67 -0
  621. package/scripts/repository-policy.mjs +166 -0
  622. package/scripts/run-and-summarize.mjs +134 -0
  623. package/scripts/run-smoke.mjs +52 -0
  624. package/scripts/run-with-cleanup.mjs +98 -0
  625. package/scripts/settings-smoke-platform-compat.mjs +85 -0
  626. package/scripts/settings-smoke.mjs +769 -0
  627. package/scripts/smoke-platform-compat.mjs +302 -0
  628. package/scripts/smoke.mjs +1180 -0
  629. package/scripts/stress-contract-compat.mjs +94 -0
  630. package/scripts/stress.mjs +882 -0
  631. package/scripts/test-domains.mjs +247 -0
  632. package/scripts/test-execution-profile-manifest.mjs +273 -0
  633. package/scripts/test-execution-profiles.mjs +147 -0
  634. package/scripts/test-performance-reporter.mjs +291 -0
  635. package/scripts/toolchain-manager.mjs +266 -0
  636. package/scripts/toolchains.json +19 -0
  637. package/scripts/windows-conpty-probe-child.mjs +82 -0
  638. package/scripts/windows-conpty-worker.ps1 +24 -0
  639. package/scripts/windows-credential-host-manifest.json +16 -0
  640. package/scripts/windows-credential-host-protocol-v1.json +16 -0
  641. package/scripts/windows-credential-host.cs +256 -0
  642. package/scripts/windows-credential-host.ps1 +26 -0
  643. package/scripts/windows-local-control-manifest.json +23 -0
  644. package/scripts/windows-local-control-spike.cs +738 -0
  645. package/scripts/windows-local-control-spike.mjs +415 -0
  646. package/scripts/windows-local-control-spike.ps1 +35 -0
  647. package/scripts/windows-local-control.cs +738 -0
  648. package/scripts/windows-local-control.ps1 +35 -0
  649. package/scripts/windows-native-api-inventory-v1.json +90 -0
  650. package/scripts/windows-process-host-manifest.json +20 -0
  651. package/scripts/windows-process-host-protocol-v1.json +59 -0
  652. package/scripts/windows-process-host.cs +2385 -0
  653. package/scripts/windows-process-host.ps1 +59 -0
  654. package/scripts/windows-realpath-shim.cjs +78 -0
package/dist/http.js ADDED
@@ -0,0 +1,2076 @@
1
+ #!/usr/bin/env node
2
+ import { randomUUID } from "node:crypto";
3
+ import { timingSafeEqual } from "node:crypto";
4
+ import path from "node:path";
5
+ import express from "express";
6
+ import cors from "cors";
7
+ import { z } from "zod";
8
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
9
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
10
+ import { expandHome, loadConfig } from "./config.js";
11
+ import { contractIncludesV4, contractIncludesV5 } from "./tools/contracts/index.js";
12
+ import { createHttpPolicySessionSource, loadOrCreateIdentityKey } from "./policy/identity.js";
13
+ import { policyIdentityScopes } from "./policy/runtime.js";
14
+ import { acceptedAuthenticationMode } from "./policy/transport.js";
15
+ import { profilePathForRoot, readRuntimeConnection, readWorkspaceProfile, sanitizeWorkspaceProfile, saveWorkspaceProfile } from "./profileStore.js";
16
+ import { redactSensitiveText, redactStructured } from "./redact.js";
17
+ import { connectProductionCodexGPTServer, createProductionCodexGPTServer, disposeProductionCodexGPTServer } from "./productionRuntime.js";
18
+ import { createProductionGitBootstrapV4 } from "./git/productionBootstrap.js";
19
+ import { resolveTransactionStateRoot } from "./transactions/stateRoot.js";
20
+ import { ProcessInstanceRegistry } from "./transactions/workspaceLock.js";
21
+ import { PersistentAuditStore } from "./audit/index.js";
22
+ import { LocalApprovalRuntimeV3 } from "./control/runtime.js";
23
+ import { SemanticPreviewStore } from "./semantic/previewStore.js";
24
+ import { createSemanticWorkerHealthRegistry } from "./semantic/builtin/typescriptProvider.js";
25
+ import { resolveOAuthDeploymentConfiguration } from "./auth/configuration.js";
26
+ import { oauthScopesForDeployment } from "./auth/policyIdentity.js";
27
+ import { createOAuthDeploymentIdentity } from "./auth/schemas.js";
28
+ import { AuthDeploymentCoordinator, AuthKeyManager, AuthProcessInstanceRegistry, AuthStateLock, AuthStateStore, AuthorizationStore, DeploymentRegistry, LocalAdminSessionManager, OAuthClientStore, OAuthGrantStore, OAuthOwnerApprovalService, OAuthOwnerClientService, OAuthOwnerGrantService, OAuthTokenService, PersistentAuthStateAuditAppender, createProductionCredentialStore, removeOAuthRuntimeStatus, writeOAuthRuntimeStatus } from "./auth/index.js";
29
+ import { profileIdForRoot } from "./profileStore.js";
30
+ import { createPublicOAuthApp } from "./http/publicApp.js";
31
+ import { OAuthReadOnlyMcpRuntime } from "./http/oauthMcpRuntime.js";
32
+ import { createLocalAdminApp, createLocalControlOwnerAdminService } from "./http/localAdminApp.js";
33
+ function escapeHtml(value) {
34
+ return String(value ?? "")
35
+ .replaceAll("&", "&")
36
+ .replaceAll("<", "&lt;")
37
+ .replaceAll(">", "&gt;")
38
+ .replaceAll('"', "&quot;");
39
+ }
40
+ function shellQuote(value) {
41
+ return `'${value.replaceAll("'", "'\\''")}'`;
42
+ }
43
+ function copyCommand(title, description, command, displayCommand = command, copyKind = "") {
44
+ const copyAttrs = copyKind
45
+ ? `data-copy-kind="${escapeHtml(copyKind)}" data-copy-base="${escapeHtml(command)}"`
46
+ : `data-copy="${escapeHtml(command)}"`;
47
+ return `<div class="control">
48
+ <div>
49
+ <strong>${escapeHtml(title)}</strong>
50
+ <p>${escapeHtml(description)}</p>
51
+ <code>${escapeHtml(displayCommand)}</code>
52
+ </div>
53
+ <button type="button" class="copy-mini" ${copyAttrs}>Copy</button>
54
+ </div>`;
55
+ }
56
+ const TUNNELS = ["cloudflare", "ngrok", "cloudflare-named", "tailscale", "none"];
57
+ const MODES = ["agent", "handoff", "pro"];
58
+ const BASH_MODES = ["safe", "off", "full"];
59
+ const BASH_TRANSCRIPTS = ["compact", "full"];
60
+ const CODEX_SESSIONS = ["off", "metadata", "read"];
61
+ const WRITE_MODES = ["workspace", "handoff", "off"];
62
+ const TOOL_MODES = ["standard", "minimal", "full"];
63
+ const POLICY_ENGINE_MODES = ["legacy", "shadow", "enforce"];
64
+ const textField = (max) => z.preprocess((value) => (typeof value === "string" ? value.trim() : value), z.string().max(max).optional());
65
+ const AdminProfilePatch = z.object({
66
+ tunnel: z.enum(TUNNELS).optional(),
67
+ hostname: textField(253),
68
+ port: z.coerce.number().int().min(1).max(65535).optional(),
69
+ mode: z.enum(MODES).optional(),
70
+ bash: z.enum(BASH_MODES).optional(),
71
+ bashTranscript: z.enum(BASH_TRANSCRIPTS).optional(),
72
+ codexSessions: z.enum(CODEX_SESSIONS).optional(),
73
+ codexDir: textField(4096),
74
+ bashSession: textField(64).refine((value) => !value || /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(value), "bashSession must be 1-64 characters using letters, numbers, dot, underscore, or dash, and must start with a letter or number."),
75
+ requireBashSession: z.boolean().optional(),
76
+ write: z.enum(WRITE_MODES).optional(),
77
+ toolMode: z.enum(TOOL_MODES).optional(),
78
+ policyEngine: z.enum(POLICY_ENGINE_MODES).optional(),
79
+ permissionProfile: textField(64).refine((value) => !value || /^[a-z0-9][a-z0-9._-]{0,63}$/.test(value), "permissionProfile must be 1-64 lowercase characters using letters, numbers, dot, underscore, or dash."),
80
+ toolCards: z.boolean().optional(),
81
+ widgetDomain: textField(2048),
82
+ tunnelName: textField(128),
83
+ ngrokConfig: textField(4096),
84
+ cloudflareConfig: textField(4096),
85
+ cloudflareTokenFile: textField(4096),
86
+ noInstallCloudflared: z.boolean().optional()
87
+ }).strict();
88
+ function oneOf(value, values, fallback) {
89
+ return typeof value === "string" && values.includes(value) ? value : fallback;
90
+ }
91
+ function runtimeTunnelFallback() {
92
+ if (process.env.CODEXGPT_TUNNEL && TUNNELS.includes(process.env.CODEXGPT_TUNNEL)) {
93
+ return process.env.CODEXGPT_TUNNEL;
94
+ }
95
+ return process.env.CODEXGPT_TUNNEL_MODE === "0" ? "none" : "cloudflare";
96
+ }
97
+ function normalizePublicHostname(value) {
98
+ const raw = value?.trim().replace(/\/+$/, "") ?? "";
99
+ if (!raw)
100
+ return "";
101
+ const url = new URL(raw.includes("://") ? raw : `https://${raw}`);
102
+ if (url.protocol !== "https:")
103
+ throw new Error("hostname must use https when a scheme is provided.");
104
+ if (url.search || url.hash)
105
+ throw new Error("hostname must not include query strings or fragments.");
106
+ if (url.pathname !== "/" && url.pathname !== "/mcp")
107
+ throw new Error("hostname must be a host, URL root, or /mcp URL.");
108
+ return url.host;
109
+ }
110
+ function normalizeWidgetDomain(value) {
111
+ const raw = value?.trim() ?? "";
112
+ if (!raw)
113
+ return "";
114
+ const url = new URL(raw);
115
+ if (url.protocol !== "https:")
116
+ throw new Error("widgetDomain must use https.");
117
+ if (url.pathname !== "/" || url.search || url.hash) {
118
+ throw new Error("widgetDomain must be an origin only, for example https://widgets.example.com.");
119
+ }
120
+ return url.origin;
121
+ }
122
+ function effectiveWriteMode(mode, write) {
123
+ if (mode === "agent")
124
+ return write;
125
+ return write === "off" ? "off" : "handoff";
126
+ }
127
+ function normalizeProfilePath(root, value) {
128
+ const raw = value?.trim() ?? "";
129
+ if (!raw)
130
+ return "";
131
+ const expanded = expandHome(raw);
132
+ return path.isAbsolute(expanded) || path.win32.isAbsolute(expanded)
133
+ ? path.resolve(expanded)
134
+ : path.resolve(root, expanded);
135
+ }
136
+ function profileValues(config, profile = readWorkspaceProfile(config.defaultRoot)) {
137
+ const hostname = profile.hostname ??
138
+ process.env.CODEXGPT_PUBLIC_HOSTNAME ??
139
+ process.env.CODEXGPT_HOSTNAME ??
140
+ process.env.NGROK_DOMAIN ??
141
+ "";
142
+ const mode = oneOf(profile.mode ?? process.env.CODEXGPT_MODE, MODES, "agent");
143
+ const write = effectiveWriteMode(mode, oneOf(profile.write ?? config.writeMode, WRITE_MODES, config.writeMode));
144
+ return {
145
+ port: String(profile.port ?? config.port),
146
+ mode,
147
+ tunnel: oneOf(profile.tunnel, TUNNELS, runtimeTunnelFallback()),
148
+ hostname: String(hostname),
149
+ tunnelName: String(profile.tunnelName ?? ""),
150
+ ngrokConfig: String(profile.ngrokConfig ?? ""),
151
+ cloudflareConfig: String(profile.cloudflareConfig ?? ""),
152
+ cloudflareTokenFile: String(profile.cloudflareTokenFile ?? ""),
153
+ bash: oneOf(profile.bash ?? config.bashMode, BASH_MODES, config.bashMode),
154
+ bashTranscript: oneOf(profile.bashTranscript ?? config.bashTranscript, BASH_TRANSCRIPTS, config.bashTranscript),
155
+ codexSessions: oneOf(profile.codexSessions ?? config.codexSessions, CODEX_SESSIONS, config.codexSessions),
156
+ codexDir: String(profile.codexDir ?? config.codexDir),
157
+ bashSession: String(profile.bashSession ?? config.bashSessionId ?? ""),
158
+ requireBashSession: Boolean(profile.requireBashSession ?? config.requireBashSession),
159
+ write,
160
+ toolMode: oneOf(profile.toolMode ?? config.toolMode, TOOL_MODES, config.toolMode),
161
+ policyEngine: oneOf(profile.policyEngine ?? config.policyEngineMode, POLICY_ENGINE_MODES, config.policyEngineMode),
162
+ permissionProfile: String(profile.permissionProfile ?? config.permissionProfileId ?? ""),
163
+ toolCards: Boolean(profile.toolCards ?? config.toolCards),
164
+ widgetDomain: String(profile.widgetDomain ?? config.widgetDomain),
165
+ noInstallCloudflared: Boolean(profile.noInstallCloudflared)
166
+ };
167
+ }
168
+ const OPTION_LABELS = {
169
+ cloudflare: "Cloudflare quick tunnel",
170
+ ngrok: "ngrok stable URL",
171
+ "cloudflare-named": "Cloudflare named tunnel",
172
+ tailscale: "Tailscale Funnel",
173
+ none: "Local only",
174
+ agent: "Agent",
175
+ handoff: "Handoff",
176
+ pro: "Pro bundle",
177
+ safe: "Safe",
178
+ off: "Off",
179
+ full: "Full",
180
+ compact: "Compact",
181
+ metadata: "Metadata",
182
+ read: "Read",
183
+ workspace: "Workspace",
184
+ minimal: "Minimal",
185
+ standard: "Standard",
186
+ legacy: "Legacy",
187
+ shadow: "Shadow",
188
+ enforce: "Enforce"
189
+ };
190
+ function optionLabel(value) {
191
+ return OPTION_LABELS[value] ?? value;
192
+ }
193
+ function selectOptions(values, current) {
194
+ return values
195
+ .map((value) => `<option value="${escapeHtml(value)}"${value === current ? " selected" : ""}>${escapeHtml(optionLabel(value))}</option>`)
196
+ .join("");
197
+ }
198
+ function serverUrlDisplay(endpoint, authEnabled) {
199
+ if (!endpoint)
200
+ return "";
201
+ const safeEndpoint = redactSensitiveText(endpoint);
202
+ if (!authEnabled)
203
+ return safeEndpoint;
204
+ const glue = safeEndpoint.includes("?") ? "&" : "?";
205
+ return `${safeEndpoint}${glue}codexgpt_token=<redacted>`;
206
+ }
207
+ function currentTunnelMessage(tunnel, endpoint) {
208
+ if (endpoint) {
209
+ if (tunnel === "cloudflare")
210
+ return "Cloudflare generated this URL for the current run. Quick tunnel URLs change after restart.";
211
+ if (tunnel === "ngrok")
212
+ return "ngrok is using the saved public hostname for this run.";
213
+ if (tunnel === "cloudflare-named")
214
+ return "Cloudflare named tunnel is using the saved public hostname for this run.";
215
+ if (tunnel === "tailscale")
216
+ return "Tailscale Funnel is using the saved ts.net hostname for this run.";
217
+ return "Local-only endpoint for clients that can reach this machine.";
218
+ }
219
+ if (tunnel === "cloudflare")
220
+ return "Cloudflare quick tunnels print a generated URL after the tunnel opens.";
221
+ if (tunnel === "ngrok")
222
+ return "Enter your reserved ngrok domain, or set NGROK_DOMAIN before starting CodexGPT.";
223
+ if (tunnel === "cloudflare-named")
224
+ return "Enter the Cloudflare hostname routed to your named tunnel.";
225
+ if (tunnel === "tailscale")
226
+ return "Enter the Tailscale Funnel hostname for this device, for example machine.tailnet.ts.net.";
227
+ return "No public tunnel is saved; local MCP clients can use the local URL.";
228
+ }
229
+ function profileForm(config) {
230
+ const profile = readWorkspaceProfile(config.defaultRoot);
231
+ const values = profileValues(config, profile);
232
+ const runtime = readRuntimeConnection(config.defaultRoot);
233
+ const profilePath = profile.profilePath ?? profilePathForRoot(config.defaultRoot);
234
+ const savedLabel = profile.profilePath ? "saved" : "not saved yet";
235
+ const runtimeEndpoint = typeof runtime.endpoint === "string" ? runtime.endpoint : "";
236
+ const runtimeTunnel = oneOf(runtime.tunnel ?? values.tunnel, TUNNELS, values.tunnel);
237
+ const runtimeUrl = serverUrlDisplay(runtimeEndpoint, Boolean(config.authToken));
238
+ const savedEndpoint = values.hostname ? `https://${values.hostname}/mcp` : "";
239
+ const savedUrl = serverUrlDisplay(savedEndpoint, Boolean(config.authToken));
240
+ const ngrokHostname = process.env.NGROK_DOMAIN ?? (values.tunnel === "ngrok" ? values.hostname : "");
241
+ const cloudflareHostname = process.env.CODEXGPT_PUBLIC_HOSTNAME ??
242
+ process.env.CODEXGPT_HOSTNAME ??
243
+ (values.tunnel === "cloudflare-named" ? values.hostname : "");
244
+ const currentUrlBlock = runtimeUrl
245
+ ? `<div class="current-url">
246
+ <div>
247
+ <span>Current Server URL</span>
248
+ <code>${escapeHtml(runtimeUrl)}</code>
249
+ <p>${escapeHtml(currentTunnelMessage(runtimeTunnel, runtimeEndpoint))}</p>
250
+ </div>
251
+ <button type="button" class="copy-mini" data-copy-kind="server-url" data-copy-base="${escapeHtml(redactSensitiveText(runtimeEndpoint))}">Copy</button>
252
+ </div>`
253
+ : `<div class="current-url idle">
254
+ <div>
255
+ <span>${savedUrl ? "Saved Server URL preview" : "Current Server URL"}</span>
256
+ <code>${savedUrl ? escapeHtml(savedUrl) : "No public URL detected for this run"}</code>
257
+ <p>${escapeHtml(savedUrl ? "This is based on the saved hostname. It becomes current after the launcher starts that tunnel." : currentTunnelMessage(values.tunnel, ""))}</p>
258
+ </div>
259
+ ${savedEndpoint ? `<button type="button" class="copy-mini" data-copy-kind="server-url" data-copy-base="${escapeHtml(redactSensitiveText(savedEndpoint))}">Copy</button>` : ""}
260
+ </div>`;
261
+ return `<section class="panel profile-panel" id="profile">
262
+ <div class="section-head">
263
+ <div>
264
+ <h2>Connection profile</h2>
265
+ <p>Use this for the next start. Current tunnel URLs appear here when the launcher knows them.</p>
266
+ </div>
267
+ <span class="pill ${profile.profilePath ? "" : "warn"}">${escapeHtml(savedLabel)}</span>
268
+ </div>
269
+ <form class="profile-form" data-profile-form>
270
+ ${currentUrlBlock}
271
+ <fieldset class="profile-group">
272
+ <legend>Connection</legend>
273
+ <p>Choose how ChatGPT reaches this local MCP server. Stable providers use the saved hostname; quick Cloudflare generates the URL at launch.</p>
274
+ <div class="form-grid">
275
+ <label><span>Tunnel</span><select name="tunnel" data-tunnel-select data-ngrok-hostname="${escapeHtml(ngrokHostname)}" data-cloudflare-hostname="${escapeHtml(cloudflareHostname)}">${selectOptions(TUNNELS, values.tunnel)}</select></label>
276
+ <label><span>Public hostname</span><input name="hostname" value="${escapeHtml(values.hostname)}" data-hostname-input data-autofilled="0"></label>
277
+ <label><span>Port</span><input name="port" type="number" min="1" max="65535" value="${escapeHtml(values.port)}"></label>
278
+ <label><span>Mode</span><select name="mode">${selectOptions(MODES, values.mode)}</select></label>
279
+ <label><span>Cloudflare tunnel name</span><input name="tunnelName" value="${escapeHtml(values.tunnelName)}"></label>
280
+ <label><span>ngrok config file</span><input name="ngrokConfig" value="${escapeHtml(values.ngrokConfig)}"></label>
281
+ <label><span>Cloudflare config file</span><input name="cloudflareConfig" value="${escapeHtml(values.cloudflareConfig)}"></label>
282
+ <label><span>Cloudflare token file</span><input name="cloudflareTokenFile" value="${escapeHtml(values.cloudflareTokenFile)}"></label>
283
+ </div>
284
+ <p class="field-help" data-hostname-help>${escapeHtml(currentTunnelMessage(values.tunnel, runtimeEndpoint))}</p>
285
+ <label class="check-row"><input name="noInstallCloudflared" type="checkbox" value="true"${values.noInstallCloudflared ? " checked" : ""}><span>Do not auto-install cloudflared</span></label>
286
+ </fieldset>
287
+ <fieldset class="profile-group">
288
+ <legend>Runtime policy</legend>
289
+ <p>Save the default access level for the next launch. These settings do not mutate the process that is already running.</p>
290
+ <div class="form-grid">
291
+ <label><span>Bash</span><select name="bash">${selectOptions(BASH_MODES, values.bash)}</select></label>
292
+ <label><span>Write mode</span><select name="write">${selectOptions(WRITE_MODES, values.write)}</select></label>
293
+ <label><span>Tool mode</span><select name="toolMode">${selectOptions(TOOL_MODES, values.toolMode)}</select></label>
294
+ <label><span>Policy engine</span><select name="policyEngine">${selectOptions(POLICY_ENGINE_MODES, values.policyEngine)}</select></label>
295
+ <label><span>Permission profile</span><input name="permissionProfile" value="${escapeHtml(values.permissionProfile)}" placeholder="compat-v1"></label>
296
+ <label><span>Codex sessions</span><select name="codexSessions">${selectOptions(CODEX_SESSIONS, values.codexSessions)}</select></label>
297
+ <label><span>Codex directory</span><input name="codexDir" value="${escapeHtml(values.codexDir)}"></label>
298
+ <label><span>Bash session</span><input name="bashSession" value="${escapeHtml(values.bashSession)}"></label>
299
+ </div>
300
+ <label class="check-row"><input name="toolCards" type="checkbox" value="true"${values.toolCards ? " checked" : ""}><span>Enable ChatGPT tool cards</span></label>
301
+ <label class="check-row"><input name="requireBashSession" type="checkbox" value="true"${values.requireBashSession ? " checked" : ""}><span>Require matching bash session id</span></label>
302
+ </fieldset>
303
+ <fieldset class="profile-group readonly-group">
304
+ <legend>Read-only this run</legend>
305
+ <div class="readonly-grid">
306
+ <div><span>Bash transcript</span><code>${escapeHtml(values.bashTranscript)}</code></div>
307
+ <div><span>Widget origin</span><code>${escapeHtml(values.widgetDomain)}</code></div>
308
+ </div>
309
+ </fieldset>
310
+ <div class="actions">
311
+ <button type="submit" class="primary">Save profile</button>
312
+ <span class="mono">${escapeHtml(profilePath)}</span>
313
+ </div>
314
+ <p class="note" data-profile-status>Tokens stay hidden. Restart CodexGPT for saved profile changes to apply.</p>
315
+ </form>
316
+ </section>`;
317
+ }
318
+ function buildProfilePayload(config, existing, input) {
319
+ const current = profileValues(config, existing);
320
+ const next = {
321
+ ...current,
322
+ ...input,
323
+ port: input.port ? String(input.port) : current.port,
324
+ requireBashSession: input.requireBashSession ?? current.requireBashSession,
325
+ noInstallCloudflared: input.noInstallCloudflared ?? current.noInstallCloudflared
326
+ };
327
+ next.hostname = normalizePublicHostname(next.hostname);
328
+ if (next.tunnel !== "ngrok" && next.tunnel !== "cloudflare-named" && next.tunnel !== "tailscale")
329
+ next.hostname = "";
330
+ next.widgetDomain = normalizeWidgetDomain(next.widgetDomain);
331
+ if ((next.tunnel === "ngrok" || next.tunnel === "cloudflare-named" || next.tunnel === "tailscale") && !next.hostname) {
332
+ throw new Error("hostname is required for ngrok, cloudflare-named, and tailscale profiles.");
333
+ }
334
+ if (next.requireBashSession && !next.bashSession) {
335
+ throw new Error("requireBashSession requires a bashSession value.");
336
+ }
337
+ const token = typeof existing.token === "string" && existing.token ? existing.token : config.authToken ?? "";
338
+ const cloudflareToken = next.tunnel === "cloudflare-named" && typeof existing.cloudflareToken === "string" && existing.cloudflareToken ? existing.cloudflareToken : "";
339
+ const write = effectiveWriteMode(next.mode, next.write);
340
+ const tunnelName = next.tunnel === "cloudflare-named" ? next.tunnelName : "";
341
+ const ngrokConfig = next.tunnel === "ngrok" ? normalizeProfilePath(config.defaultRoot, next.ngrokConfig) : "";
342
+ const cloudflareConfig = next.tunnel === "cloudflare-named" ? normalizeProfilePath(config.defaultRoot, next.cloudflareConfig) : "";
343
+ const cloudflareTokenFile = next.tunnel === "cloudflare-named" ? normalizeProfilePath(config.defaultRoot, next.cloudflareTokenFile) : "";
344
+ return {
345
+ port: next.port,
346
+ mode: next.mode,
347
+ tunnel: next.tunnel,
348
+ ...(next.hostname ? { hostname: next.hostname } : {}),
349
+ ...(tunnelName ? { tunnelName } : {}),
350
+ ...(ngrokConfig ? { ngrokConfig } : {}),
351
+ ...(cloudflareConfig ? { cloudflareConfig } : {}),
352
+ ...(cloudflareTokenFile ? { cloudflareTokenFile } : {}),
353
+ ...(token ? { token } : {}),
354
+ ...(cloudflareToken ? { cloudflareToken } : {}),
355
+ bash: next.bash,
356
+ ...(next.bashTranscript !== "compact" ? { bashTranscript: next.bashTranscript } : {}),
357
+ ...(next.codexSessions !== "off" ? { codexSessions: next.codexSessions } : {}),
358
+ ...(next.codexDir ? { codexDir: next.codexDir } : {}),
359
+ ...(next.bashSession ? { bashSession: next.bashSession } : {}),
360
+ ...(next.requireBashSession ? { requireBashSession: true } : {}),
361
+ write,
362
+ toolMode: next.toolMode,
363
+ policyEngine: next.policyEngine,
364
+ ...(next.permissionProfile ? { permissionProfile: next.permissionProfile } : {}),
365
+ toolCards: next.toolCards,
366
+ ...(next.widgetDomain ? { widgetDomain: next.widgetDomain } : {}),
367
+ ...(next.noInstallCloudflared ? { noInstallCloudflared: true } : {})
368
+ };
369
+ }
370
+ function profileResponse(config) {
371
+ const profile = readWorkspaceProfile(config.defaultRoot);
372
+ const runtime = readRuntimeConnection(config.defaultRoot);
373
+ return redactStructured({
374
+ ok: true,
375
+ profile_path: profile.profilePath ?? profilePathForRoot(config.defaultRoot),
376
+ exists: Boolean(profile.profilePath),
377
+ profile: sanitizeWorkspaceProfile(profile),
378
+ effective: profileValues(config, profile),
379
+ runtime_connection: runtime,
380
+ runtime: {
381
+ defaultRoot: config.defaultRoot,
382
+ port: config.port,
383
+ bashMode: config.bashMode,
384
+ bashTranscript: config.bashTranscript,
385
+ codexSessions: config.codexSessions,
386
+ writeMode: config.writeMode,
387
+ toolMode: config.toolMode,
388
+ policyEngineMode: config.policyEngineMode,
389
+ permissionProfileId: config.permissionProfileId ?? null,
390
+ toolCards: config.toolCards,
391
+ widgetDomain: config.widgetDomain,
392
+ authEnabled: Boolean(config.authToken)
393
+ }
394
+ });
395
+ }
396
+ function jsonError(res, status, code, message, issues) {
397
+ res.status(status).json({
398
+ ok: false,
399
+ error: { code, message, ...(issues ? { issues } : {}) }
400
+ });
401
+ }
402
+ const LOCAL_FAVICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
403
+ <rect width="64" height="64" rx="16" fill="#2563eb"/>
404
+ <rect x="8" y="8" width="48" height="48" rx="12" fill="#ffffff" fill-opacity=".12" stroke="#ffffff" stroke-opacity=".38"/>
405
+ <path d="M38.4 40.3c-1.8 1.1-3.9 1.7-6.3 1.7-6.1 0-10.3-4.2-10.3-10s4.2-10 10.4-10c2.4 0 4.5.6 6.2 1.7l-2.1 4.1c-1.1-.7-2.3-1-3.8-1-2.9 0-4.9 2.1-4.9 5.2s2 5.2 4.9 5.2c1.5 0 2.8-.4 3.9-1.1l2 4.2Z" fill="#ffffff"/>
406
+ </svg>`;
407
+ const CODEXGPT_VERSION = "1.0.0";
408
+ function printHelp() {
409
+ console.log(`CodexGPT MCP HTTP server
410
+
411
+ Usage:
412
+ codexgpt-mcp-http --root /path/to/repo --port 8787
413
+ codexgpt-mcp-http --version
414
+ codexgpt-mcp-http --help
415
+
416
+ Set CODEXGPT_HTTP_TOKEN for public/tunnel use.
417
+ For trusted local-only testing, set CODEXGPT_ALLOW_NO_HTTP_TOKEN=1.
418
+ Most users should run: codexgpt start`);
419
+ }
420
+ function onboardingPage(config) {
421
+ const localMcp = `http://${config.host}:${config.port}/mcp`;
422
+ const localMcpDisplay = config.authToken ? `${localMcp}?codexgpt_token=<redacted>` : localMcp;
423
+ const allowedRoots = config.allowedRoots.map((root) => `<li>${escapeHtml(root)}</li>`).join("");
424
+ const authLabel = config.authToken ? "Token protected" : "Disabled";
425
+ const writeTone = config.writeMode === "workspace" ? "agent" : config.writeMode;
426
+ const rootArg = shellQuote(config.defaultRoot);
427
+ const sessionArg = shellQuote(config.bashSessionId || "main");
428
+ const githubUrl = "https://github.com/chatGPT-10/codexgpt";
429
+ const npmUrl = "https://www.npmjs.com/package/codexgpt";
430
+ const docsUrl = "https://rebel0789.github.io/codexgpt/";
431
+ const chatgptUrl = "https://chatgpt.com/#settings/Connectors";
432
+ const controls = [
433
+ copyCommand("Re-run setup wizard", "Use the CLI for broader profile edits that are intentionally not exposed here.", "codexgpt setup"),
434
+ copyCommand("Copy local MCP URL", "Useful for a local MCP client. ChatGPT usually needs the public tunnel URL copied by the terminal.", localMcp, localMcpDisplay, "local-mcp"),
435
+ copyCommand("Start without bash", "Restart with file tools but no ChatGPT-triggered bash tool.", `codexgpt start --root ${rootArg} --no-bash`),
436
+ copyCommand("Require explicit bash target", "Restart so bash calls must include this matching session_id.", `codexgpt start --root ${rootArg} --bash-session ${sessionArg} --require-bash-session`),
437
+ copyCommand("Show Codex session list", "Restart with read-only local Codex session metadata in full tool mode.", `codexgpt start --root ${rootArg} --tool-mode full --codex-sessions metadata`),
438
+ copyCommand("Read Codex transcripts", "Restart with bounded local transcript reads from Codex JSONL history.", `codexgpt start --root ${rootArg} --tool-mode full --codex-sessions read`),
439
+ copyCommand("Use full bash transcript", "Restart with the raw stdout/stderr transcript instead of compact tool cards.", `codexgpt start --root ${rootArg} --bash-transcript full`)
440
+ ].join("");
441
+ return `<!doctype html>
442
+ <html lang="en">
443
+ <head>
444
+ <meta charset="utf-8">
445
+ <meta name="viewport" content="width=device-width, initial-scale=1">
446
+ <link rel="icon" href="/favicon.ico">
447
+ <title>CodexGPT Local Control - ChatGPT Workspace Agent</title>
448
+ <style>
449
+ /* Hallmark · pre-emit critique: P5 H5 E5 S5 R5 V5 */
450
+ /* Hallmark · macrostructure: Workbench · genre: modern-minimal · theme: CC Switch-inspired light manager · tone: technical admin · nav: section switcher · footer: Ft2 · contrast: pass (40-41) · mobile: pass (34, 49, 50-57) */
451
+ :root {
452
+ color-scheme: light;
453
+ --font-display: "Geist", "Aptos", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
454
+ --font-body: "Geist", "Aptos", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
455
+ --font-mono: "Fira Code", "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
456
+ --color-paper: oklch(98.5% 0.004 250);
457
+ --color-surface: oklch(100% 0 0);
458
+ --color-panel: oklch(100% 0 0);
459
+ --color-panel-2: oklch(96.5% 0.008 252);
460
+ --color-row: oklch(99.2% 0.004 250);
461
+ --color-rule: oklch(88% 0.012 250);
462
+ --color-rule-strong: oklch(78% 0.018 250);
463
+ --color-ink: oklch(23% 0.026 255);
464
+ --color-soft: oklch(39% 0.026 255);
465
+ --color-muted: oklch(53% 0.022 255);
466
+ --color-subtle: oklch(64% 0.018 255);
467
+ --color-accent: oklch(58% 0.19 256);
468
+ --color-accent-strong: oklch(50% 0.22 256);
469
+ --color-accent-ink: oklch(99% 0.004 250);
470
+ --color-action: var(--color-accent);
471
+ --color-action-strong: var(--color-accent-strong);
472
+ --color-good: oklch(56% 0.14 154);
473
+ --color-warn: oklch(54% 0.17 256);
474
+ --color-focus: oklch(61% 0.2 256);
475
+ --surface-accent: oklch(95% 0.036 256);
476
+ --surface-good: oklch(94.5% 0.035 154);
477
+ --surface-warn: oklch(95% 0.036 256);
478
+ --surface-hover: oklch(93.5% 0.025 256);
479
+ --shadow-panel: 0 18px 50px oklch(24% 0.03 255 / 0.10);
480
+ --shadow-row: 0 8px 18px oklch(24% 0.03 255 / 0.06);
481
+ --space-1: 0.25rem;
482
+ --space-2: 0.5rem;
483
+ --space-3: 0.75rem;
484
+ --space-4: 1rem;
485
+ --space-5: 1.25rem;
486
+ --space-6: 1.5rem;
487
+ --space-7: 2rem;
488
+ --space-8: 2.5rem;
489
+ --space-9: 3rem;
490
+ --radius-1: 6px;
491
+ --radius-2: 8px;
492
+ --dur-micro: 120ms;
493
+ --dur-short: 180ms;
494
+ --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
495
+ --ease-in: cubic-bezier(0.7, 0, 0.84, 0);
496
+ }
497
+ * { box-sizing: border-box; }
498
+ html,
499
+ body {
500
+ overflow-x: clip;
501
+ }
502
+ body {
503
+ margin: 0;
504
+ min-height: 100vh;
505
+ background: var(--color-paper);
506
+ color: var(--color-ink);
507
+ font: 14px/1.55 var(--font-body);
508
+ letter-spacing: 0;
509
+ }
510
+ a {
511
+ color: inherit;
512
+ text-decoration: none;
513
+ }
514
+ button,
515
+ input,
516
+ select {
517
+ font: inherit;
518
+ }
519
+ main {
520
+ width: min(1240px, calc(100% - (var(--space-4) * 2)));
521
+ margin: 0 auto;
522
+ padding: var(--space-5) 0 var(--space-8);
523
+ }
524
+ .topbar {
525
+ display: flex;
526
+ align-items: center;
527
+ justify-content: space-between;
528
+ gap: var(--space-4);
529
+ margin-bottom: var(--space-3);
530
+ padding: var(--space-2) 0 var(--space-3);
531
+ }
532
+ .brand {
533
+ display: flex;
534
+ align-items: center;
535
+ gap: var(--space-3);
536
+ min-width: 0;
537
+ }
538
+ .logo {
539
+ display: inline-grid;
540
+ place-items: center;
541
+ width: 42px;
542
+ height: 42px;
543
+ flex: 0 0 auto;
544
+ border: 1px solid var(--color-accent);
545
+ border-radius: 12px;
546
+ background: var(--color-accent);
547
+ color: var(--color-accent-ink);
548
+ box-shadow: var(--shadow-row);
549
+ font: 900 15px/1 var(--font-mono);
550
+ }
551
+ .logo img {
552
+ display: block;
553
+ width: 100%;
554
+ height: 100%;
555
+ border-radius: inherit;
556
+ }
557
+ .brand-kicker {
558
+ display: block;
559
+ color: var(--color-muted);
560
+ font-size: 12px;
561
+ font-weight: 800;
562
+ line-height: 1.1;
563
+ text-transform: uppercase;
564
+ }
565
+ .brand-title {
566
+ display: block;
567
+ overflow-wrap: anywhere;
568
+ color: var(--color-accent);
569
+ font: 900 28px/1 var(--font-display);
570
+ }
571
+ .quick-links {
572
+ display: flex;
573
+ align-items: center;
574
+ justify-content: flex-end;
575
+ gap: var(--space-2);
576
+ flex-wrap: wrap;
577
+ }
578
+ .resource-link,
579
+ .action-link,
580
+ .copy-mini,
581
+ .primary {
582
+ min-height: 44px;
583
+ display: inline-flex;
584
+ align-items: center;
585
+ justify-content: center;
586
+ border-radius: var(--radius-2);
587
+ white-space: nowrap;
588
+ transition: background-color var(--dur-short) var(--ease-out),
589
+ border-color var(--dur-short) var(--ease-out),
590
+ color var(--dur-short) var(--ease-out),
591
+ transform var(--dur-micro) var(--ease-out);
592
+ }
593
+ .resource-link,
594
+ .action-link {
595
+ border: 1px solid var(--color-rule);
596
+ background: var(--color-surface);
597
+ color: var(--color-soft);
598
+ font-size: 12px;
599
+ font-weight: 800;
600
+ padding: 0 var(--space-3);
601
+ box-shadow: var(--shadow-row);
602
+ }
603
+ .action-link.primary-link {
604
+ border-color: var(--color-action);
605
+ background: var(--color-action);
606
+ color: var(--color-accent-ink);
607
+ }
608
+ .section-tabs {
609
+ display: flex;
610
+ align-items: center;
611
+ justify-content: center;
612
+ gap: var(--space-1);
613
+ flex-wrap: wrap;
614
+ margin: 0 0 var(--space-5);
615
+ padding: var(--space-1);
616
+ border: 1px solid var(--color-rule);
617
+ border-radius: 16px;
618
+ background: var(--color-surface);
619
+ box-shadow: var(--shadow-panel);
620
+ }
621
+ .section-tabs a {
622
+ min-height: 40px;
623
+ display: inline-flex;
624
+ align-items: center;
625
+ justify-content: center;
626
+ border-radius: 12px;
627
+ color: var(--color-muted);
628
+ font-size: 13px;
629
+ font-weight: 900;
630
+ white-space: nowrap;
631
+ padding: 0 var(--space-4);
632
+ }
633
+ .section-tabs a[aria-current="page"] {
634
+ background: var(--color-accent);
635
+ color: var(--color-accent-ink);
636
+ box-shadow: var(--shadow-row);
637
+ }
638
+ .overview {
639
+ display: grid;
640
+ grid-template-columns: minmax(0, 1.08fr) minmax(330px, 0.52fr);
641
+ gap: var(--space-5);
642
+ align-items: start;
643
+ margin-bottom: var(--space-5);
644
+ }
645
+ .intro-stack {
646
+ display: grid;
647
+ gap: var(--space-5);
648
+ min-width: 0;
649
+ }
650
+ .intro {
651
+ min-width: 0;
652
+ padding: var(--space-6);
653
+ border: 1px solid var(--color-rule);
654
+ border-radius: 18px;
655
+ background: var(--color-surface);
656
+ box-shadow: var(--shadow-panel);
657
+ }
658
+ h1 {
659
+ margin: 0;
660
+ max-width: 18ch;
661
+ font: 900 2rem/1.05 var(--font-display);
662
+ letter-spacing: 0;
663
+ overflow-wrap: anywhere;
664
+ }
665
+ .lead {
666
+ max-width: 64ch;
667
+ margin: var(--space-4) 0 0;
668
+ color: var(--color-soft);
669
+ font-size: 15px;
670
+ }
671
+ .scope-note {
672
+ margin-top: var(--space-5);
673
+ padding-top: var(--space-4);
674
+ border-top: 1px solid var(--color-rule);
675
+ color: var(--color-muted);
676
+ font-size: 13px;
677
+ }
678
+ .run-card,
679
+ .panel {
680
+ min-width: 0;
681
+ border: 1px solid var(--color-rule);
682
+ border-radius: 18px;
683
+ background: var(--color-panel);
684
+ box-shadow: var(--shadow-panel);
685
+ }
686
+ .run-card,
687
+ .panel {
688
+ padding: var(--space-5);
689
+ }
690
+ .run-card h2,
691
+ .panel h2 {
692
+ margin: 0;
693
+ color: var(--color-ink);
694
+ font: 700 16px/1.25 var(--font-display);
695
+ letter-spacing: 0;
696
+ }
697
+ .workspace-grid {
698
+ display: grid;
699
+ grid-template-columns: minmax(0, 1.08fr) minmax(310px, 0.58fr);
700
+ gap: var(--space-5);
701
+ align-items: start;
702
+ }
703
+ .side-stack {
704
+ display: grid;
705
+ gap: var(--space-5);
706
+ }
707
+ .section-head {
708
+ display: flex;
709
+ align-items: start;
710
+ justify-content: space-between;
711
+ gap: var(--space-4);
712
+ margin-bottom: var(--space-4);
713
+ }
714
+ .section-head p,
715
+ .panel > p {
716
+ margin: var(--space-1) 0 0;
717
+ color: var(--color-muted);
718
+ font-size: 13px;
719
+ }
720
+ .status {
721
+ display: grid;
722
+ }
723
+ .row {
724
+ display: grid;
725
+ grid-template-columns: 132px minmax(0, 1fr);
726
+ gap: var(--space-3);
727
+ padding: var(--space-3) 0;
728
+ border-bottom: 1px solid var(--color-rule);
729
+ }
730
+ .row:last-child { border-bottom: 0; }
731
+ .label {
732
+ color: var(--color-subtle);
733
+ font-size: 11px;
734
+ font-weight: 800;
735
+ text-transform: uppercase;
736
+ }
737
+ code,
738
+ .mono {
739
+ font-family: var(--font-mono);
740
+ color: var(--color-soft);
741
+ overflow-wrap: anywhere;
742
+ font-variant-numeric: tabular-nums;
743
+ }
744
+ .pill {
745
+ display: inline-flex;
746
+ width: fit-content;
747
+ align-items: center;
748
+ min-height: 26px;
749
+ padding: 0 var(--space-2);
750
+ border: 1px solid var(--color-good);
751
+ border-radius: 999px;
752
+ background: var(--surface-good);
753
+ color: var(--color-good);
754
+ font-size: 12px;
755
+ font-weight: 800;
756
+ line-height: 1;
757
+ white-space: nowrap;
758
+ }
759
+ .warn {
760
+ border-color: var(--color-warn);
761
+ background: var(--surface-warn);
762
+ color: var(--color-warn);
763
+ }
764
+ .controls {
765
+ display: grid;
766
+ grid-template-columns: repeat(2, minmax(0, 1fr));
767
+ gap: var(--space-3);
768
+ }
769
+ .control {
770
+ display: grid;
771
+ grid-template-columns: minmax(0, 1fr) auto;
772
+ gap: var(--space-3);
773
+ align-items: center;
774
+ min-width: 0;
775
+ min-height: 112px;
776
+ padding: var(--space-4);
777
+ border: 1px solid var(--color-rule);
778
+ border-radius: 18px;
779
+ background: var(--color-row);
780
+ box-shadow: var(--shadow-row);
781
+ }
782
+ .control strong {
783
+ display: block;
784
+ margin-bottom: var(--space-1);
785
+ color: var(--color-ink);
786
+ font-size: 13px;
787
+ }
788
+ .control p {
789
+ margin: 0 0 var(--space-2);
790
+ color: var(--color-muted);
791
+ font-size: 12px;
792
+ }
793
+ .control code {
794
+ display: block;
795
+ padding: var(--space-2);
796
+ border: 1px solid var(--color-rule);
797
+ border-radius: 10px;
798
+ background: var(--color-panel-2);
799
+ line-height: 1.45;
800
+ }
801
+ .steps {
802
+ display: grid;
803
+ gap: var(--space-3);
804
+ margin: 0;
805
+ padding: 0;
806
+ list-style: none;
807
+ }
808
+ .guide-list {
809
+ display: grid;
810
+ gap: var(--space-3);
811
+ }
812
+ .guide-item {
813
+ display: grid;
814
+ grid-template-columns: 28px minmax(0, 1fr);
815
+ gap: var(--space-3);
816
+ padding: var(--space-3);
817
+ border: 1px solid var(--color-rule);
818
+ border-radius: 14px;
819
+ background: var(--color-row);
820
+ }
821
+ .guide-item strong {
822
+ display: block;
823
+ color: var(--color-ink);
824
+ font-size: 13px;
825
+ }
826
+ .guide-item p {
827
+ margin: var(--space-1) 0 0;
828
+ color: var(--color-muted);
829
+ font-size: 12px;
830
+ }
831
+ .steps li {
832
+ display: grid;
833
+ grid-template-columns: 28px minmax(0, 1fr);
834
+ gap: var(--space-3);
835
+ align-items: start;
836
+ color: var(--color-soft);
837
+ }
838
+ .num {
839
+ display: inline-grid;
840
+ place-items: center;
841
+ width: 26px;
842
+ height: 26px;
843
+ border: 1px solid var(--color-accent);
844
+ border-radius: 999px;
845
+ background: var(--color-accent);
846
+ color: var(--color-accent-ink);
847
+ font: 800 11px/1 var(--font-mono);
848
+ }
849
+ .roots {
850
+ margin: var(--space-3) 0 0;
851
+ padding-left: var(--space-5);
852
+ color: var(--color-muted);
853
+ }
854
+ .profile-form {
855
+ display: grid;
856
+ gap: var(--space-4);
857
+ }
858
+ .current-url {
859
+ display: grid;
860
+ grid-template-columns: minmax(0, 1fr) auto;
861
+ gap: var(--space-3);
862
+ align-items: center;
863
+ min-width: 0;
864
+ padding: var(--space-4);
865
+ border: 1px solid var(--color-accent);
866
+ border-radius: 18px;
867
+ background: var(--surface-accent);
868
+ }
869
+ .current-url.idle {
870
+ border-color: var(--color-rule);
871
+ background: var(--color-row);
872
+ }
873
+ .current-url span,
874
+ .readonly-grid span {
875
+ display: block;
876
+ margin-bottom: var(--space-1);
877
+ color: var(--color-muted);
878
+ font-size: 11px;
879
+ font-weight: 900;
880
+ text-transform: uppercase;
881
+ }
882
+ .current-url code {
883
+ display: block;
884
+ color: var(--color-ink);
885
+ font-size: 12px;
886
+ line-height: 1.45;
887
+ }
888
+ .current-url p {
889
+ margin: var(--space-2) 0 0;
890
+ color: var(--color-soft);
891
+ font-size: 12px;
892
+ }
893
+ .profile-group {
894
+ min-width: 0;
895
+ margin: 0;
896
+ padding: var(--space-4);
897
+ border: 1px solid var(--color-rule);
898
+ border-radius: 18px;
899
+ background: var(--color-row);
900
+ box-shadow: var(--shadow-row);
901
+ }
902
+ .profile-group legend {
903
+ padding: 0 var(--space-2);
904
+ color: var(--color-ink);
905
+ font: 900 13px/1 var(--font-display);
906
+ }
907
+ .profile-group p {
908
+ margin: 0 0 var(--space-3);
909
+ color: var(--color-muted);
910
+ font-size: 12px;
911
+ }
912
+ .form-grid {
913
+ display: grid;
914
+ grid-template-columns: repeat(2, minmax(0, 1fr));
915
+ gap: var(--space-3);
916
+ }
917
+ .profile-form label {
918
+ display: grid;
919
+ gap: var(--space-2);
920
+ color: var(--color-soft);
921
+ font-size: 12px;
922
+ font-weight: 800;
923
+ }
924
+ .profile-form label span {
925
+ color: var(--color-muted);
926
+ text-transform: uppercase;
927
+ }
928
+ .profile-form input,
929
+ .profile-form select {
930
+ width: 100%;
931
+ min-height: 44px;
932
+ border: 1px solid var(--color-rule);
933
+ border-radius: 12px;
934
+ outline: 2px solid transparent;
935
+ outline-offset: 1px;
936
+ background: var(--color-surface);
937
+ color: var(--color-ink);
938
+ font: 13px/1.25 var(--font-body);
939
+ padding: 0 var(--space-3);
940
+ }
941
+ .field-help {
942
+ min-height: 1lh;
943
+ margin: var(--space-3) 0 0 !important;
944
+ color: var(--color-soft) !important;
945
+ font-size: 12px !important;
946
+ }
947
+ .check-row {
948
+ width: fit-content;
949
+ display: inline-flex !important;
950
+ grid-template-columns: none !important;
951
+ align-items: center;
952
+ gap: var(--space-2) !important;
953
+ margin-top: var(--space-3);
954
+ padding: var(--space-2) var(--space-3);
955
+ border: 1px solid var(--color-rule);
956
+ border-radius: 999px;
957
+ background: var(--color-surface);
958
+ color: var(--color-soft);
959
+ }
960
+ .check-row input {
961
+ width: 18px;
962
+ min-height: 18px;
963
+ height: 18px;
964
+ padding: 0;
965
+ accent-color: var(--color-accent);
966
+ }
967
+ .check-row span {
968
+ color: var(--color-soft) !important;
969
+ text-transform: none !important;
970
+ }
971
+ .readonly-grid {
972
+ display: grid;
973
+ grid-template-columns: repeat(2, minmax(0, 1fr));
974
+ gap: var(--space-3);
975
+ }
976
+ .readonly-grid > div {
977
+ min-width: 0;
978
+ padding: var(--space-3);
979
+ border: 1px solid var(--color-rule);
980
+ border-radius: 14px;
981
+ background: var(--color-surface);
982
+ }
983
+ .readonly-grid code {
984
+ display: block;
985
+ font-size: 12px;
986
+ line-height: 1.45;
987
+ }
988
+ .actions {
989
+ display: flex;
990
+ align-items: center;
991
+ gap: var(--space-3);
992
+ min-width: 0;
993
+ flex-wrap: wrap;
994
+ }
995
+ .primary {
996
+ border: 1px solid var(--color-accent);
997
+ background: var(--color-accent);
998
+ color: var(--color-accent-ink);
999
+ cursor: pointer;
1000
+ font: 800 13px/1 var(--font-body);
1001
+ padding: 0 var(--space-4);
1002
+ box-shadow: var(--shadow-row);
1003
+ }
1004
+ .copy-mini {
1005
+ border: 1px solid var(--color-action);
1006
+ background: var(--color-action);
1007
+ color: var(--color-accent-ink);
1008
+ cursor: pointer;
1009
+ font: 800 12px/1 var(--font-body);
1010
+ padding: 0 var(--space-3);
1011
+ }
1012
+ .note {
1013
+ min-height: 1lh;
1014
+ margin: var(--space-1) 0 0;
1015
+ color: var(--color-subtle);
1016
+ font-size: 12px;
1017
+ }
1018
+ .scope-list {
1019
+ display: grid;
1020
+ gap: var(--space-2);
1021
+ margin: var(--space-3) 0 0;
1022
+ padding: 0;
1023
+ list-style: none;
1024
+ }
1025
+ .scope-list li {
1026
+ display: grid;
1027
+ grid-template-columns: 112px minmax(0, 1fr);
1028
+ gap: var(--space-3);
1029
+ padding-block: var(--space-2);
1030
+ border-bottom: 1px solid var(--color-rule);
1031
+ color: var(--color-muted);
1032
+ }
1033
+ .scope-list li:last-child {
1034
+ border-bottom: 0;
1035
+ }
1036
+ .scope-list strong {
1037
+ color: var(--color-soft);
1038
+ font-family: var(--font-mono);
1039
+ font-size: 12px;
1040
+ }
1041
+ .details-panel {
1042
+ margin-top: var(--space-5);
1043
+ }
1044
+ details summary {
1045
+ min-height: 44px;
1046
+ display: flex;
1047
+ align-items: center;
1048
+ justify-content: space-between;
1049
+ gap: var(--space-3);
1050
+ cursor: pointer;
1051
+ color: var(--color-ink);
1052
+ font: 800 14px/1 var(--font-body);
1053
+ list-style: none;
1054
+ }
1055
+ details summary::-webkit-details-marker {
1056
+ display: none;
1057
+ }
1058
+ details summary::after {
1059
+ content: "+";
1060
+ color: var(--color-accent);
1061
+ font: 800 18px/1 var(--font-mono);
1062
+ }
1063
+ details[open] summary {
1064
+ margin-bottom: var(--space-4);
1065
+ }
1066
+ details[open] summary::after {
1067
+ content: "-";
1068
+ }
1069
+ .foot {
1070
+ margin-top: var(--space-6);
1071
+ padding-top: var(--space-4);
1072
+ border-top: 1px solid var(--color-rule);
1073
+ color: var(--color-subtle);
1074
+ font-size: 12px;
1075
+ }
1076
+ :focus {
1077
+ outline: none;
1078
+ }
1079
+ :focus-visible {
1080
+ outline: 2px solid var(--color-focus);
1081
+ outline-offset: 2px;
1082
+ }
1083
+ .profile-form input:focus-visible,
1084
+ .profile-form select:focus-visible {
1085
+ outline: 2px solid var(--color-focus);
1086
+ outline-offset: 1px;
1087
+ }
1088
+ .resource-link:active,
1089
+ .action-link:active,
1090
+ .copy-mini:active,
1091
+ .primary:active {
1092
+ transform: translateY(1px);
1093
+ }
1094
+ .resource-link[aria-disabled="true"],
1095
+ .action-link[aria-disabled="true"],
1096
+ .copy-mini:disabled,
1097
+ .primary:disabled {
1098
+ cursor: not-allowed;
1099
+ opacity: 0.55;
1100
+ }
1101
+ @media (hover: hover) and (pointer: fine) {
1102
+ .resource-link:hover,
1103
+ .action-link:hover,
1104
+ .copy-mini:hover,
1105
+ .primary:hover {
1106
+ border-color: var(--color-accent);
1107
+ background: var(--surface-hover);
1108
+ color: var(--color-accent-strong);
1109
+ transform: translateY(-1px);
1110
+ }
1111
+ .action-link.primary-link:hover,
1112
+ .copy-mini:hover,
1113
+ .primary:hover {
1114
+ border-color: var(--color-action-strong);
1115
+ background: var(--color-action-strong);
1116
+ color: var(--color-accent-ink);
1117
+ }
1118
+ .section-tabs a:hover {
1119
+ background: var(--surface-hover);
1120
+ color: var(--color-accent-strong);
1121
+ }
1122
+ .profile-form input:hover,
1123
+ .profile-form select:hover {
1124
+ border-color: var(--color-rule-strong);
1125
+ background: var(--color-panel-2);
1126
+ }
1127
+ }
1128
+ @media (min-width: 52rem) {
1129
+ h1 {
1130
+ font-size: 2.45rem;
1131
+ }
1132
+ }
1133
+ @media (max-width: 58rem) {
1134
+ .overview,
1135
+ .workspace-grid {
1136
+ grid-template-columns: 1fr;
1137
+ }
1138
+ .topbar {
1139
+ align-items: flex-start;
1140
+ flex-direction: column;
1141
+ }
1142
+ .quick-links {
1143
+ justify-content: flex-start;
1144
+ }
1145
+ .section-tabs {
1146
+ justify-content: flex-start;
1147
+ }
1148
+ }
1149
+ @media (max-width: 42rem) {
1150
+ main {
1151
+ width: min(100% - (var(--space-3) * 2), 1180px);
1152
+ padding-block: var(--space-4) var(--space-7);
1153
+ }
1154
+ .intro,
1155
+ .run-card,
1156
+ .panel {
1157
+ padding: var(--space-4);
1158
+ }
1159
+ .section-head {
1160
+ align-items: start;
1161
+ flex-direction: column;
1162
+ }
1163
+ h1 {
1164
+ font-size: 1.85rem;
1165
+ }
1166
+ .row,
1167
+ .scope-list li {
1168
+ grid-template-columns: 1fr;
1169
+ gap: var(--space-1);
1170
+ }
1171
+ .controls,
1172
+ .form-grid {
1173
+ grid-template-columns: 1fr;
1174
+ }
1175
+ .control {
1176
+ grid-template-columns: 1fr;
1177
+ }
1178
+ .current-url,
1179
+ .readonly-grid {
1180
+ grid-template-columns: 1fr;
1181
+ }
1182
+ .actions {
1183
+ align-items: stretch;
1184
+ flex-direction: column;
1185
+ }
1186
+ .primary {
1187
+ width: 100%;
1188
+ }
1189
+ }
1190
+ @media (prefers-reduced-motion: reduce) {
1191
+ *,
1192
+ *::before,
1193
+ *::after {
1194
+ animation-duration: 150ms !important;
1195
+ animation-iteration-count: 1 !important;
1196
+ transition-duration: 150ms !important;
1197
+ }
1198
+ }
1199
+ </style>
1200
+ </head>
1201
+ <body>
1202
+ <main>
1203
+ <header class="topbar">
1204
+ <div class="brand">
1205
+ <span class="logo" aria-hidden="true"><img src="/favicon.ico" alt=""></span>
1206
+ <span>
1207
+ <span class="brand-kicker">Workspace control</span>
1208
+ <span class="brand-title">CodexGPT</span>
1209
+ </span>
1210
+ </div>
1211
+ <nav class="quick-links" aria-label="CodexGPT resources">
1212
+ <a class="action-link primary-link" href="${chatgptUrl}" target="_blank" rel="noreferrer">Open ChatGPT settings</a>
1213
+ <a class="resource-link" href="${githubUrl}" target="_blank" rel="noreferrer">Open GitHub</a>
1214
+ <a class="resource-link" href="${npmUrl}" target="_blank" rel="noreferrer">NPM</a>
1215
+ <a class="resource-link" href="${docsUrl}" target="_blank" rel="noreferrer">Docs</a>
1216
+ </nav>
1217
+ </header>
1218
+ <nav class="section-tabs" aria-label="Admin sections">
1219
+ <a href="#profile" aria-current="page">Profile</a>
1220
+ <a href="#status">Status</a>
1221
+ <a href="#connect">ChatGPT</a>
1222
+ <a href="#access">Access</a>
1223
+ <a href="#cli">CLI</a>
1224
+ </nav>
1225
+ <section class="overview">
1226
+ ${profileForm(config)}
1227
+ <aside class="side-stack">
1228
+ <section class="panel guide-panel" id="guide">
1229
+ <div class="section-head">
1230
+ <div>
1231
+ <h2>Quick path</h2>
1232
+ <p>Use ChatGPT like a coding agent for this workspace without widening the local trust boundary.</p>
1233
+ </div>
1234
+ </div>
1235
+ <div class="guide-list">
1236
+ <div class="guide-item"><span class="num">1</span><span><strong>Review the profile</strong><p>Choose the tunnel, port, mode, bash, write, tool, Codex session, and workspace defaults for the next launch.</p></span></div>
1237
+ <div class="guide-item"><span class="num">2</span><span><strong>Copy the Server URL</strong><p>Use the current public URL shown in the profile when available, or the one printed by the terminal after launch.</p></span></div>
1238
+ <div class="guide-item"><span class="num">3</span><span><strong>Create the ChatGPT app</strong><p>Choose Server URL, paste the copied URL, and use no extra authentication. The private token is already in the URL.</p></span></div>
1239
+ <div class="guide-item"><span class="num">4</span><span><strong>Restart for policy changes</strong><p>Saved profile changes apply when CodexGPT starts again. The live server does not mutate under an active ChatGPT session.</p></span></div>
1240
+ </div>
1241
+ </section>
1242
+ <article class="run-card" id="status" aria-label="Current runtime">
1243
+ <h2>Runtime guardrails</h2>
1244
+ <div class="status">
1245
+ <div class="row"><span class="label">Workspace</span><span class="mono">${escapeHtml(config.defaultRoot)}</span></div>
1246
+ <div class="row"><span class="label">Local MCP</span><span class="mono">${escapeHtml(localMcp)}</span></div>
1247
+ <div class="row"><span class="label">Write mode</span><span class="pill ${config.writeMode === "workspace" ? "" : "warn"}">${escapeHtml(writeTone)}</span></div>
1248
+ <div class="row"><span class="label">Tool mode</span><span class="pill ${config.toolMode === "standard" ? "" : "warn"}">${escapeHtml(config.toolMode)}</span></div>
1249
+ <div class="row"><span class="label">Bash mode</span><span class="pill ${config.bashMode === "safe" ? "" : "warn"}">${escapeHtml(config.bashMode)}</span></div>
1250
+ <div class="row"><span class="label">Transcript</span><span class="pill ${config.bashTranscript === "compact" ? "" : "warn"}">${escapeHtml(config.bashTranscript)}</span></div>
1251
+ <div class="row"><span class="label">Bash session</span><span class="pill ${config.requireBashSession ? "warn" : ""}">${escapeHtml(config.bashSessionId ? `${config.bashSessionId}${config.requireBashSession ? " required" : ""}` : "not set")}</span></div>
1252
+ <div class="row"><span class="label">Codex sessions</span><span class="pill ${config.codexSessions === "off" ? "" : "warn"}">${escapeHtml(config.codexSessions)}</span></div>
1253
+ <div class="row"><span class="label">Widget domain</span><span class="mono">${escapeHtml(config.widgetDomain)}</span></div>
1254
+ <div class="row"><span class="label">Auth</span><span class="pill">${escapeHtml(authLabel)}</span></div>
1255
+ </div>
1256
+ </article>
1257
+ </aside>
1258
+ </section>
1259
+ <section class="workspace-grid">
1260
+ <section class="panel" id="connect">
1261
+ <div class="section-head">
1262
+ <div>
1263
+ <h2>Connect ChatGPT</h2>
1264
+ <p>Create an app connection that points at the public Server URL copied by the terminal.</p>
1265
+ </div>
1266
+ </div>
1267
+ <ol class="steps">
1268
+ <li><span class="num">1</span><span>Open ChatGPT settings and create an app connection.</span></li>
1269
+ <li><span class="num">2</span><span>Set Connection to <code>Server URL</code>.</span></li>
1270
+ <li><span class="num">3</span><span>Paste the public CodexGPT URL from the terminal.</span></li>
1271
+ <li><span class="num">4</span><span>Use <code>No Authentication / None</code>; the private token is already in the copied URL.</span></li>
1272
+ </ol>
1273
+ <p class="note"><a class="action-link" href="${chatgptUrl}" target="_blank" rel="noreferrer">Open ChatGPT settings</a></p>
1274
+ </section>
1275
+ <aside class="side-stack">
1276
+ <section class="panel" id="access">
1277
+ <h2>Admin boundary</h2>
1278
+ <ul class="scope-list">
1279
+ <li><strong>/setup</strong><span>this setup and settings page</span></li>
1280
+ <li><strong>/admin/profile</strong><span>saved workspace profile API</span></li>
1281
+ <li><strong>/healthz</strong><span>authenticated status check</span></li>
1282
+ <li><strong>/mcp</strong><span>MCP endpoint for ChatGPT and local clients</span></li>
1283
+ </ul>
1284
+ </section>
1285
+ </aside>
1286
+ </section>
1287
+ <section class="panel cli-panel details-panel" id="cli">
1288
+ <div class="section-head">
1289
+ <div>
1290
+ <h2>CLI controls</h2>
1291
+ <p>Copy these when you need to restart with a different runtime policy. They do not mutate the running process from the browser.</p>
1292
+ </div>
1293
+ </div>
1294
+ <div class="controls">${controls}</div>
1295
+ </section>
1296
+ <details class="panel details-panel">
1297
+ <summary>Allowed roots</summary>
1298
+ <ul class="roots">${allowedRoots}</ul>
1299
+ <p class="note">CodexGPT rejects workspace access outside these roots.</p>
1300
+ </details>
1301
+ <footer class="foot">Token-protected local control surface for this workspace. Public sharing still happens only through your chosen tunnel.</footer>
1302
+ </main>
1303
+ <script>
1304
+ document.querySelectorAll("[data-copy], [data-copy-kind]").forEach((button) => {
1305
+ button.addEventListener("click", async () => {
1306
+ let value = button.getAttribute("data-copy") || "";
1307
+ if (button.getAttribute("data-copy-kind") === "local-mcp") {
1308
+ const base = button.getAttribute("data-copy-base") || value;
1309
+ const params = new URLSearchParams(window.location.search);
1310
+ const token = params.get("codexgpt_token") || params.get("token") || "";
1311
+ value = token ? base + "?codexgpt_token=" + encodeURIComponent(token) : base;
1312
+ } else if (button.getAttribute("data-copy-kind") === "server-url") {
1313
+ const base = button.getAttribute("data-copy-base") || value;
1314
+ const params = new URLSearchParams(window.location.search);
1315
+ const token = params.get("codexgpt_token") || params.get("token") || "";
1316
+ value = token ? base + "?codexgpt_token=" + encodeURIComponent(token) : base;
1317
+ }
1318
+ try {
1319
+ await navigator.clipboard.writeText(value);
1320
+ button.textContent = "Copied";
1321
+ setTimeout(() => { button.textContent = "Copy"; }, 1400);
1322
+ } catch {
1323
+ button.textContent = "Select";
1324
+ }
1325
+ });
1326
+ });
1327
+ const profileForm = document.querySelector("[data-profile-form]");
1328
+ const tunnelSelect = document.querySelector("[data-tunnel-select]");
1329
+ const hostnameInput = document.querySelector("[data-hostname-input]");
1330
+ const hostnameHelp = document.querySelector("[data-hostname-help]");
1331
+ const tokenEnabled = ${config.authToken ? "true" : "false"};
1332
+ function serverPreviewFor(hostname) {
1333
+ const clean = String(hostname || "").trim().replace(/^https?:\\/\\//, "").replace(/\\/mcp\\/?$/, "").replace(/\\/+$/, "");
1334
+ if (!clean) return "";
1335
+ return "https://" + clean + "/mcp" + (tokenEnabled ? "?codexgpt_token=<redacted>" : "");
1336
+ }
1337
+ function updateTunnelHelp() {
1338
+ if (!tunnelSelect || !hostnameInput || !hostnameHelp) return;
1339
+ const tunnel = tunnelSelect.value;
1340
+ const ngrokHost = tunnelSelect.getAttribute("data-ngrok-hostname") || "";
1341
+ const cloudflareHost = tunnelSelect.getAttribute("data-cloudflare-hostname") || "";
1342
+ if (tunnel === "ngrok" && !hostnameInput.value && ngrokHost) {
1343
+ hostnameInput.value = ngrokHost;
1344
+ hostnameInput.setAttribute("data-autofilled", "1");
1345
+ }
1346
+ if (tunnel === "cloudflare-named" && !hostnameInput.value && cloudflareHost) {
1347
+ hostnameInput.value = cloudflareHost;
1348
+ hostnameInput.setAttribute("data-autofilled", "1");
1349
+ }
1350
+ if ((tunnel === "cloudflare" || tunnel === "none") && hostnameInput.getAttribute("data-autofilled") === "1") {
1351
+ hostnameInput.value = "";
1352
+ hostnameInput.setAttribute("data-autofilled", "0");
1353
+ }
1354
+ const preview = serverPreviewFor(hostnameInput.value);
1355
+ if (tunnel === "cloudflare") {
1356
+ hostnameHelp.textContent = "Cloudflare quick tunnel generates the public URL at launch and this page shows it when the launcher reports it.";
1357
+ } else if (tunnel === "ngrok") {
1358
+ hostnameHelp.textContent = preview ? "Next Server URL preview: " + preview : "Enter the reserved ngrok domain from your local ngrok setup.";
1359
+ } else if (tunnel === "cloudflare-named") {
1360
+ hostnameHelp.textContent = preview ? "Next Server URL preview: " + preview : "Enter the hostname routed to your Cloudflare named tunnel.";
1361
+ } else if (tunnel === "tailscale") {
1362
+ hostnameHelp.textContent = preview ? "Next Server URL preview: " + preview : "Enter the Tailscale Funnel hostname for this device.";
1363
+ } else {
1364
+ hostnameHelp.textContent = "Local-only mode does not expose a public ChatGPT Server URL.";
1365
+ }
1366
+ }
1367
+ tunnelSelect?.addEventListener("change", updateTunnelHelp);
1368
+ hostnameInput?.addEventListener("input", () => {
1369
+ hostnameInput.setAttribute("data-autofilled", "0");
1370
+ updateTunnelHelp();
1371
+ });
1372
+ updateTunnelHelp();
1373
+ if (profileForm) {
1374
+ profileForm.addEventListener("submit", async (event) => {
1375
+ event.preventDefault();
1376
+ const form = event.currentTarget;
1377
+ const status = document.querySelector("[data-profile-status]");
1378
+ const data = Object.fromEntries(new FormData(form).entries());
1379
+ const payload = {
1380
+ tunnel: data.tunnel,
1381
+ hostname: data.hostname,
1382
+ tunnelName: data.tunnelName,
1383
+ ngrokConfig: data.ngrokConfig,
1384
+ cloudflareConfig: data.cloudflareConfig,
1385
+ cloudflareTokenFile: data.cloudflareTokenFile,
1386
+ port: Number(data.port),
1387
+ mode: data.mode,
1388
+ bash: data.bash,
1389
+ write: data.write,
1390
+ toolMode: data.toolMode,
1391
+ toolCards: Boolean(form.elements.toolCards?.checked),
1392
+ codexSessions: data.codexSessions,
1393
+ codexDir: data.codexDir,
1394
+ bashSession: data.bashSession,
1395
+ requireBashSession: Boolean(form.elements.requireBashSession?.checked),
1396
+ noInstallCloudflared: Boolean(form.elements.noInstallCloudflared?.checked)
1397
+ };
1398
+ if (status) status.textContent = "Saving...";
1399
+ try {
1400
+ const response = await fetch("/admin/profile" + window.location.search, {
1401
+ method: "POST",
1402
+ headers: { "content-type": "application/json" },
1403
+ body: JSON.stringify(payload)
1404
+ });
1405
+ const result = await response.json().catch(() => ({}));
1406
+ if (!response.ok) throw new Error(result.error?.message || "Save failed");
1407
+ if (status) status.textContent = "Saved. Restart CodexGPT for these profile settings to apply.";
1408
+ } catch (error) {
1409
+ if (status) status.textContent = error instanceof Error ? error.message : "Save failed";
1410
+ }
1411
+ });
1412
+ }
1413
+ </script>
1414
+ </body>
1415
+ </html>`;
1416
+ }
1417
+ async function startLocalApprovalRuntime(auditStore, stateBaseRoot) {
1418
+ return LocalApprovalRuntimeV3.start({ auditStore, stateBaseRoot });
1419
+ }
1420
+ async function main() {
1421
+ const argv = process.argv.slice(2);
1422
+ if (argv.includes("--version") || argv.includes("-v") || argv[0] === "version") {
1423
+ console.log(CODEXGPT_VERSION);
1424
+ return;
1425
+ }
1426
+ if (argv.includes("--help") || argv[0] === "help") {
1427
+ printHelp();
1428
+ return;
1429
+ }
1430
+ const config = loadConfig();
1431
+ if (config.authMode === "legacy" && config.requireHttpToken && !config.authToken) {
1432
+ throw new Error("CODEXGPT_HTTP_TOKEN is required for this HTTP binding. " +
1433
+ "Set CODEXGPT_HTTP_TOKEN, use `codexgpt start` to generate one, " +
1434
+ "or set CODEXGPT_ALLOW_NO_HTTP_TOKEN=1 only for a trusted local-only setup.");
1435
+ }
1436
+ if (config.allowQueryToken) {
1437
+ console.error("[CodexGPT] Warning: personal query-token compatibility is enabled. Treat the complete URL as a secret; it may leak through browser history, clipboard contents, screenshots, logs, and copied links.");
1438
+ }
1439
+ const semanticPreviewStoreV5 = contractIncludesV5(config.toolContractVersion)
1440
+ ? new SemanticPreviewStore()
1441
+ : undefined;
1442
+ const semanticWorkerHealthV5 = contractIncludesV5(config.toolContractVersion)
1443
+ ? createSemanticWorkerHealthRegistry()
1444
+ : undefined;
1445
+ if (config.authMode === "oauth") {
1446
+ const profile = readWorkspaceProfile(config.defaultRoot);
1447
+ const deployment = resolveOAuthDeploymentConfiguration({
1448
+ canonicalRoot: config.defaultRoot,
1449
+ profileId: profileIdForRoot(config.defaultRoot),
1450
+ hostname: profile.hostname ?? "",
1451
+ issuer: profile.oauthIssuer,
1452
+ resource: profile.oauthResource,
1453
+ tunnel: profile.tunnel ?? "",
1454
+ tunnelName: profile.tunnelName ?? "",
1455
+ tunnelOwner: profile.tunnelOwner ?? "",
1456
+ publicHost: config.host,
1457
+ publicPort: config.port,
1458
+ localAdminHost: "127.0.0.1",
1459
+ localAdminPort: Number(profile.localAdminPort ?? 8788)
1460
+ });
1461
+ const stateRoot = resolveTransactionStateRoot();
1462
+ const oauthStateRoot = path.join(stateRoot, "oauth");
1463
+ const auditRegistry = new ProcessInstanceRegistry(stateRoot);
1464
+ const auditStore = PersistentAuditStore.open({
1465
+ stateRoot,
1466
+ registry: auditRegistry,
1467
+ retention: config.auditRetention
1468
+ });
1469
+ const credentialStore = createProductionCredentialStore();
1470
+ const authInstance = new AuthProcessInstanceRegistry(oauthStateRoot);
1471
+ let localApprovalRuntime;
1472
+ let oauthTokenService;
1473
+ let oauthMcpRuntime;
1474
+ let publicServer;
1475
+ let localAdminServer;
1476
+ let runtimeStatusIdentity;
1477
+ try {
1478
+ await credentialStore.probe();
1479
+ const authAudit = new PersistentAuthStateAuditAppender(auditStore);
1480
+ const authStore = new AuthStateStore(oauthStateRoot, credentialStore, {
1481
+ audit: authAudit
1482
+ });
1483
+ const authLocks = new AuthStateLock(oauthStateRoot, authInstance);
1484
+ const authRegistry = new DeploymentRegistry(authStore);
1485
+ const authCoordinator = new AuthDeploymentCoordinator(authStore, new AuthKeyManager(credentialStore), authRegistry, authLocks);
1486
+ const initialized = await authCoordinator.initialize(deployment);
1487
+ const state = initialized.state;
1488
+ const identity = createOAuthDeploymentIdentity({
1489
+ issuer: state.issuer,
1490
+ resource: state.resource,
1491
+ hostname: state.hostname,
1492
+ profileId: state.profileId,
1493
+ bindingId: state.bindingId,
1494
+ incarnationId: state.incarnationId,
1495
+ recoveryEpoch: state.recoveryEpoch
1496
+ });
1497
+ const enabledScopes = oauthScopesForDeployment(config);
1498
+ localApprovalRuntime = await startLocalApprovalRuntime(auditStore, stateRoot);
1499
+ const oauthClients = new OAuthClientStore({
1500
+ store: authStore,
1501
+ locks: authLocks,
1502
+ bindingId: state.bindingId,
1503
+ incarnationId: state.incarnationId
1504
+ });
1505
+ const oauthAuthorizations = new AuthorizationStore({
1506
+ identity,
1507
+ canonicalRoot: state.canonicalRoot,
1508
+ enabledScopes,
1509
+ clients: oauthClients,
1510
+ audit: authAudit
1511
+ });
1512
+ const oauthGrants = new OAuthGrantStore({
1513
+ store: authStore,
1514
+ locks: authLocks,
1515
+ bindingId: state.bindingId,
1516
+ incarnationId: state.incarnationId,
1517
+ ownerRef: state.ownerRef,
1518
+ resource: state.resource
1519
+ });
1520
+ oauthTokenService = await OAuthTokenService.create({
1521
+ identity,
1522
+ ownerSubject: initialized.ownerSubject,
1523
+ ownerRef: state.ownerRef,
1524
+ state,
1525
+ store: authStore,
1526
+ keyManager: authCoordinator.keyManager,
1527
+ grants: oauthGrants
1528
+ });
1529
+ oauthMcpRuntime = new OAuthReadOnlyMcpRuntime({
1530
+ config,
1531
+ identity,
1532
+ enabledScopes,
1533
+ localApprovalRuntimeV3: localApprovalRuntime,
1534
+ semanticPreviewStoreV5,
1535
+ semanticWorkerHealthV5
1536
+ });
1537
+ const localAdminOrigin = `http://${deployment.listeners.localAdminHost}:${deployment.listeners.localAdminPort}`;
1538
+ const localAdminSessions = new LocalAdminSessionManager();
1539
+ const ownerAdminService = createLocalControlOwnerAdminService(localApprovalRuntime, localAdminSessions, localAdminOrigin);
1540
+ localApprovalRuntime.setOAuthAdminBootstrapControl({
1541
+ issue: () => ownerAdminService.issueBootstrap()
1542
+ });
1543
+ localApprovalRuntime.setOAuthBackupControl({
1544
+ create: () => authCoordinator.createBackup(deployment.identityKey)
1545
+ });
1546
+ localApprovalRuntime.setOAuthSigningKeyControl({
1547
+ rotate: async () => {
1548
+ await authCoordinator.rotateSigningKey(deployment.identityKey, { createBackup: false });
1549
+ }
1550
+ });
1551
+ localApprovalRuntime.setOAuthAuthorizationControl(new OAuthOwnerApprovalService(oauthAuthorizations));
1552
+ localApprovalRuntime.setOAuthClientControl(new OAuthOwnerClientService(oauthClients, oauthGrants));
1553
+ localApprovalRuntime.setOAuthGrantControl(new OAuthOwnerGrantService(oauthGrants));
1554
+ console.error(`[CodexGPT] OAuth owner-control server ${localApprovalRuntime.serverId}`);
1555
+ const publicApp = createPublicOAuthApp({
1556
+ identity,
1557
+ enabledScopes,
1558
+ publicJwks: [state.activePublicJwk, ...state.previousPublicJwks],
1559
+ allowedOrigins: config.allowedOrigins,
1560
+ oauthRuntime: {
1561
+ clients: oauthClients,
1562
+ authorizations: oauthAuthorizations,
1563
+ tokens: oauthTokenService,
1564
+ mcp: oauthMcpRuntime
1565
+ }
1566
+ });
1567
+ const localAdminApp = createLocalAdminApp({
1568
+ ownerAdminService,
1569
+ sessions: localAdminSessions,
1570
+ origin: localAdminOrigin
1571
+ });
1572
+ const listen = (app, port, host, label) => new Promise((resolve, reject) => {
1573
+ const server = app.listen(port, host);
1574
+ const onError = (error) => {
1575
+ server.off("listening", onListening);
1576
+ reject(error);
1577
+ };
1578
+ const onListening = () => {
1579
+ server.off("error", onError);
1580
+ console.error(`[CodexGPT] ${label} on http://${host}:${port}`);
1581
+ resolve(server);
1582
+ };
1583
+ server.once("error", onError);
1584
+ server.once("listening", onListening);
1585
+ });
1586
+ publicServer = await listen(publicApp, deployment.listeners.publicPort, deployment.listeners.publicHost, "OAuth public listener");
1587
+ try {
1588
+ localAdminServer = await listen(localAdminApp, deployment.listeners.localAdminPort, deployment.listeners.localAdminHost, "OAuth local-admin listener");
1589
+ const nativeControlReady = localApprovalRuntime.nativeControl().ready;
1590
+ writeOAuthRuntimeStatus(oauthStateRoot, {
1591
+ schemaVersion: 1,
1592
+ profileId: state.profileId,
1593
+ canonicalRoot: state.canonicalRoot,
1594
+ bindingId: state.bindingId,
1595
+ incarnationId: state.incarnationId,
1596
+ serverId: localApprovalRuntime.serverId,
1597
+ pid: nativeControlReady.pid,
1598
+ processCreationTime: nativeControlReady.processCreationTime,
1599
+ localAdminOrigin,
1600
+ startedAt: new Date().toISOString()
1601
+ });
1602
+ runtimeStatusIdentity = {
1603
+ profileId: state.profileId,
1604
+ pid: process.pid,
1605
+ serverId: localApprovalRuntime.serverId
1606
+ };
1607
+ }
1608
+ catch (error) {
1609
+ await new Promise((resolve) => publicServer.close(() => resolve()));
1610
+ publicServer = undefined;
1611
+ throw error;
1612
+ }
1613
+ }
1614
+ catch (error) {
1615
+ await oauthMcpRuntime?.close().catch(() => undefined);
1616
+ oauthTokenService?.dispose();
1617
+ await localApprovalRuntime?.close().catch(() => undefined);
1618
+ authInstance.dispose();
1619
+ auditStore.dispose();
1620
+ auditRegistry.dispose();
1621
+ throw error;
1622
+ }
1623
+ const closeServer = (server) => server
1624
+ ? new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()))
1625
+ : Promise.resolve();
1626
+ let shutdownPromise;
1627
+ const shutdown = () => {
1628
+ if (shutdownPromise)
1629
+ return shutdownPromise;
1630
+ shutdownPromise = (async () => {
1631
+ if (runtimeStatusIdentity) {
1632
+ removeOAuthRuntimeStatus(oauthStateRoot, runtimeStatusIdentity.profileId, runtimeStatusIdentity);
1633
+ runtimeStatusIdentity = undefined;
1634
+ }
1635
+ await Promise.all([closeServer(publicServer), closeServer(localAdminServer)]);
1636
+ await oauthMcpRuntime?.close();
1637
+ oauthTokenService?.dispose();
1638
+ await localApprovalRuntime?.close();
1639
+ authInstance.dispose();
1640
+ auditStore.dispose();
1641
+ auditRegistry.dispose();
1642
+ })();
1643
+ return shutdownPromise;
1644
+ };
1645
+ const requestShutdown = () => {
1646
+ void shutdown().catch((error) => {
1647
+ console.error(error instanceof Error ? error.stack ?? error.message : String(error));
1648
+ process.exitCode = 1;
1649
+ });
1650
+ };
1651
+ process.once("SIGINT", requestShutdown);
1652
+ process.once("SIGTERM", requestShutdown);
1653
+ return;
1654
+ }
1655
+ const sharedApprovalStateRootV5 = contractIncludesV5(config.toolContractVersion) &&
1656
+ config.policyEngineMode === "enforce" &&
1657
+ config.auditMode !== "off" &&
1658
+ config.executionProfile === "off" &&
1659
+ config.localFileAccess !== "confirmed_roots"
1660
+ ? resolveTransactionStateRoot()
1661
+ : undefined;
1662
+ const sharedApprovalRegistryV5 = sharedApprovalStateRootV5
1663
+ ? new ProcessInstanceRegistry(sharedApprovalStateRootV5)
1664
+ : undefined;
1665
+ const sharedApprovalAuditStoreV5 = sharedApprovalStateRootV5 && sharedApprovalRegistryV5
1666
+ ? PersistentAuditStore.open({
1667
+ stateRoot: sharedApprovalStateRootV5,
1668
+ registry: sharedApprovalRegistryV5,
1669
+ retention: config.auditRetention
1670
+ })
1671
+ : undefined;
1672
+ const sharedLocalApprovalRuntimeV5 = sharedApprovalStateRootV5 && sharedApprovalAuditStoreV5
1673
+ ? await startLocalApprovalRuntime(sharedApprovalAuditStoreV5, sharedApprovalStateRootV5)
1674
+ : undefined;
1675
+ const app = express();
1676
+ const logRequests = process.env.CODEXGPT_LOG_REQUESTS === "1";
1677
+ function tokenMatches(value) {
1678
+ if (!config.authToken || typeof value !== "string")
1679
+ return false;
1680
+ const expected = Buffer.from(config.authToken);
1681
+ const actual = Buffer.from(value);
1682
+ return expected.length === actual.length && timingSafeEqual(expected, actual);
1683
+ }
1684
+ function requestHostName(req) {
1685
+ const raw = req.headers.host?.trim().toLowerCase();
1686
+ if (!raw)
1687
+ return undefined;
1688
+ if (raw.startsWith("[")) {
1689
+ const closing = raw.indexOf("]");
1690
+ return closing > 1 ? raw.slice(1, closing) : undefined;
1691
+ }
1692
+ const colon = raw.indexOf(":");
1693
+ return colon >= 0 ? raw.slice(0, colon) : raw;
1694
+ }
1695
+ function originAllowed(req, origin) {
1696
+ let parsed;
1697
+ try {
1698
+ parsed = new URL(origin);
1699
+ }
1700
+ catch {
1701
+ return false;
1702
+ }
1703
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
1704
+ return false;
1705
+ const requestHost = req.headers.host?.trim().toLowerCase();
1706
+ if (requestHost && parsed.host.toLowerCase() === requestHost)
1707
+ return true;
1708
+ return config.allowedOrigins.includes(parsed.origin);
1709
+ }
1710
+ const adminRateWindow = new Map();
1711
+ function adminRateLimit(req, res, next) {
1712
+ const now = Date.now();
1713
+ const key = req.ip || req.socket.remoteAddress || "local";
1714
+ const current = adminRateWindow.get(key);
1715
+ if (!current || current.resetAt <= now) {
1716
+ adminRateWindow.set(key, { count: 1, resetAt: now + 60_000 });
1717
+ next();
1718
+ return;
1719
+ }
1720
+ current.count += 1;
1721
+ if (current.count > 30) {
1722
+ jsonError(res, 429, "rate_limited", "Too many profile save attempts. Try again in a minute.");
1723
+ return;
1724
+ }
1725
+ next();
1726
+ }
1727
+ function adminBodyLimit(req, res, next) {
1728
+ const length = Number(req.headers["content-length"] ?? 0);
1729
+ if (Number.isFinite(length) && length > 32_768) {
1730
+ jsonError(res, 413, "payload_too_large", "Profile request body is too large.");
1731
+ return;
1732
+ }
1733
+ next();
1734
+ }
1735
+ app.use((req, res, next) => {
1736
+ if (!logRequests) {
1737
+ next();
1738
+ return;
1739
+ }
1740
+ const started = Date.now();
1741
+ console.error(`[CodexGPT] ${req.method} ${req.path} received`);
1742
+ res.on("finish", () => {
1743
+ console.error(`[CodexGPT] ${req.method} ${req.path} -> ${res.statusCode} ${Date.now() - started}ms`);
1744
+ });
1745
+ next();
1746
+ });
1747
+ app.use((req, res, next) => {
1748
+ res.setHeader("X-Content-Type-Options", "nosniff");
1749
+ res.setHeader("Referrer-Policy", "no-referrer");
1750
+ res.setHeader("X-Frame-Options", "DENY");
1751
+ res.setHeader("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
1752
+ const host = requestHostName(req);
1753
+ if (!host || !config.allowedHosts.includes(host)) {
1754
+ res.status(403).send("Forbidden: Host is not allowed");
1755
+ return;
1756
+ }
1757
+ const origin = typeof req.headers.origin === "string" ? req.headers.origin : undefined;
1758
+ if (origin && !originAllowed(req, origin)) {
1759
+ res.status(403).send("Forbidden: Origin is not allowed");
1760
+ return;
1761
+ }
1762
+ next();
1763
+ });
1764
+ app.use(cors({ origin: true, exposedHeaders: ["Mcp-Session-Id"] }));
1765
+ app.get("/favicon.ico", (_req, res) => {
1766
+ res.setHeader("Cache-Control", "public, max-age=86400");
1767
+ res.type("image/svg+xml").send(LOCAL_FAVICON);
1768
+ });
1769
+ app.use((req, res, next) => {
1770
+ if (!config.allowQueryToken && req.url.includes("?")) {
1771
+ const parsed = new URL(req.url, "http://localhost");
1772
+ const hasQueryCredential = parsed.searchParams.has("codexgpt_" + "token") || parsed.searchParams.has("token");
1773
+ if (hasQueryCredential) {
1774
+ const bearer = req.headers.authorization?.match(/^Bearer\s+(.+)$/i)?.[1]?.trim();
1775
+ if (!tokenMatches(bearer)) {
1776
+ res.status(401).send("Unauthorized");
1777
+ return;
1778
+ }
1779
+ }
1780
+ }
1781
+ next();
1782
+ });
1783
+ app.use((req, res, next) => {
1784
+ if (!config.authToken) {
1785
+ res.locals.codexgptAuthenticationMode = "loopback_none";
1786
+ next();
1787
+ return;
1788
+ }
1789
+ const bearer = req.headers.authorization?.match(/^Bearer\s+(.+)$/i)?.[1]?.trim();
1790
+ const queryToken = typeof req.query.codexgpt_token === "string"
1791
+ ? req.query.codexgpt_token
1792
+ : typeof req.query.token === "string"
1793
+ ? req.query.token
1794
+ : undefined;
1795
+ const bearerMatched = tokenMatches(bearer);
1796
+ const queryMatched = tokenMatches(queryToken);
1797
+ if (!bearerMatched && !queryMatched) {
1798
+ res.status(401).send("Unauthorized");
1799
+ return;
1800
+ }
1801
+ res.locals.codexgptAuthenticationMode = acceptedAuthenticationMode({
1802
+ authConfigured: true,
1803
+ bearerMatched,
1804
+ queryMatched
1805
+ });
1806
+ next();
1807
+ });
1808
+ const transports = new Map();
1809
+ const sessionIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1810
+ function requestSessionId(req) {
1811
+ const value = req.headers["mcp-session-id"];
1812
+ return Array.isArray(value) ? value[0] : value;
1813
+ }
1814
+ function sendSessionError(res, sessionId) {
1815
+ const missing = !sessionId;
1816
+ const malformed = Boolean(sessionId && !sessionIdPattern.test(sessionId));
1817
+ res.status(missing || malformed ? 400 : 404).json({
1818
+ jsonrpc: "2.0",
1819
+ error: missing
1820
+ ? { code: -32000, message: "Bad Request: Mcp-Session-Id header is required" }
1821
+ : malformed
1822
+ ? { code: -32000, message: "Bad Request: invalid MCP session id" }
1823
+ : { code: -32001, message: "Session not found" },
1824
+ id: null
1825
+ });
1826
+ }
1827
+ function closeTransport(record) {
1828
+ void record.transport.close?.();
1829
+ }
1830
+ function pruneTransports() {
1831
+ const now = Date.now();
1832
+ for (const [sessionId, record] of transports) {
1833
+ if (now - record.lastSeenAt > config.httpSessionTtlMs) {
1834
+ transports.delete(sessionId);
1835
+ closeTransport(record);
1836
+ }
1837
+ }
1838
+ while (transports.size > config.maxHttpSessions) {
1839
+ const oldest = [...transports.entries()].sort((a, b) => a[1].lastSeenAt - b[1].lastSeenAt)[0];
1840
+ if (!oldest)
1841
+ break;
1842
+ transports.delete(oldest[0]);
1843
+ closeTransport(oldest[1]);
1844
+ }
1845
+ }
1846
+ function getTransport(sessionId) {
1847
+ if (!sessionId || !sessionIdPattern.test(sessionId))
1848
+ return undefined;
1849
+ pruneTransports();
1850
+ const record = transports.get(sessionId);
1851
+ if (!record)
1852
+ return undefined;
1853
+ record.lastSeenAt = Date.now();
1854
+ return record.transport;
1855
+ }
1856
+ const pruneTimer = setInterval(pruneTransports, Math.min(config.httpSessionTtlMs, 60_000));
1857
+ pruneTimer.unref();
1858
+ app.get("/", (_req, res) => {
1859
+ res.type("html").send(onboardingPage(config));
1860
+ });
1861
+ app.get("/setup", (_req, res) => {
1862
+ res.type("html").send(onboardingPage(config));
1863
+ });
1864
+ app.get("/healthz", (_req, res) => {
1865
+ res.json({
1866
+ ok: true,
1867
+ name: "CodexGPT",
1868
+ defaultRoot: config.defaultRoot,
1869
+ allowedRoots: config.allowedRoots,
1870
+ bashMode: config.bashMode,
1871
+ bashTranscript: config.bashTranscript,
1872
+ bashSessionId: config.bashSessionId ?? null,
1873
+ requireBashSession: config.requireBashSession,
1874
+ codexSessions: config.codexSessions,
1875
+ writeMode: config.writeMode,
1876
+ toolMode: config.toolMode,
1877
+ widgetDomain: config.widgetDomain,
1878
+ contextDir: config.contextDir,
1879
+ authEnabled: Boolean(config.authToken),
1880
+ authRequired: Boolean(config.authToken)
1881
+ });
1882
+ });
1883
+ app.get("/admin/profile", (_req, res) => {
1884
+ res.json(profileResponse(config));
1885
+ });
1886
+ app.post("/admin/profile", adminRateLimit, adminBodyLimit, express.json({ limit: "32kb" }), (req, res) => {
1887
+ const parsed = AdminProfilePatch.safeParse(req.body ?? {});
1888
+ if (!parsed.success) {
1889
+ jsonError(res, 400, "invalid_profile", "Invalid profile settings.", parsed.error.flatten());
1890
+ return;
1891
+ }
1892
+ try {
1893
+ const existing = readWorkspaceProfile(config.defaultRoot);
1894
+ const payload = buildProfilePayload(config, existing, parsed.data);
1895
+ const profilePath = saveWorkspaceProfile(config.defaultRoot, payload);
1896
+ res.json({
1897
+ ...profileResponse(config),
1898
+ saved: true,
1899
+ profile_path: profilePath,
1900
+ message: "Saved. Restart CodexGPT for these profile settings to apply."
1901
+ });
1902
+ }
1903
+ catch (error) {
1904
+ jsonError(res, 400, "invalid_profile", error instanceof Error ? error.message : String(error));
1905
+ }
1906
+ });
1907
+ app.all("/admin/profile", (_req, res) => {
1908
+ jsonError(res, 405, "method_not_allowed", "Use GET or POST for /admin/profile.");
1909
+ });
1910
+ app.post("/mcp", express.json({ limit: "20mb" }), async (req, res) => {
1911
+ try {
1912
+ const sessionId = requestSessionId(req);
1913
+ let transport;
1914
+ const existingTransport = getTransport(sessionId);
1915
+ if (existingTransport) {
1916
+ transport = existingTransport;
1917
+ }
1918
+ else if (!sessionId && isInitializeRequest(req.body)) {
1919
+ transport = new StreamableHTTPServerTransport({
1920
+ sessionIdGenerator: () => randomUUID(),
1921
+ onsessioninitialized: (newSessionId) => {
1922
+ pruneTransports();
1923
+ transports.set(newSessionId, {
1924
+ transport,
1925
+ createdAt: Date.now(),
1926
+ lastSeenAt: Date.now()
1927
+ });
1928
+ pruneTransports();
1929
+ }
1930
+ });
1931
+ let sessionServer;
1932
+ transport.onclose = () => {
1933
+ const closedSessionId = transport.sessionId;
1934
+ if (closedSessionId)
1935
+ transports.delete(closedSessionId);
1936
+ if (sessionServer)
1937
+ disposeProductionCodexGPTServer(sessionServer);
1938
+ };
1939
+ const policyEngineMode = config.policyEngineMode ?? "legacy";
1940
+ const needsSessionContext = contractIncludesV4(config.toolContractVersion) ||
1941
+ policyEngineMode !== "legacy" ||
1942
+ (config.fileTransactions === "atomic" && config.writeMode !== "off");
1943
+ const authenticationMode = (res.locals.codexgptAuthenticationMode ?? "loopback_none");
1944
+ const policySessionContextSource = needsSessionContext
1945
+ ? createHttpPolicySessionSource({
1946
+ authenticationMode,
1947
+ configuredCredential: config.authToken,
1948
+ key: authenticationMode === "loopback_none" ? Buffer.alloc(32) : loadOrCreateIdentityKey(),
1949
+ transportSessionId: () => String(transport.sessionId ?? "pending"),
1950
+ scopes: policyIdentityScopes(config)
1951
+ })
1952
+ : undefined;
1953
+ const gitBootstrapV4 = await createProductionGitBootstrapV4(config, {
1954
+ stateRoot: resolveTransactionStateRoot()
1955
+ });
1956
+ try {
1957
+ sessionServer = createProductionCodexGPTServer(config, {
1958
+ policySessionContextSource,
1959
+ gitBootstrapV4: gitBootstrapV4 ?? undefined,
1960
+ semanticPreviewStoreV5,
1961
+ semanticWorkerHealthV5,
1962
+ localApprovalRuntimeV3: sharedLocalApprovalRuntimeV5
1963
+ });
1964
+ }
1965
+ catch (error) {
1966
+ await gitBootstrapV4?.dispose();
1967
+ throw error;
1968
+ }
1969
+ await connectProductionCodexGPTServer(sessionServer, transport);
1970
+ }
1971
+ else {
1972
+ sendSessionError(res, sessionId);
1973
+ return;
1974
+ }
1975
+ await transport.handleRequest(req, res, req.body);
1976
+ }
1977
+ catch (error) {
1978
+ console.error(error instanceof Error ? error.stack ?? error.message : String(error));
1979
+ if (!res.headersSent) {
1980
+ res.status(500).json({
1981
+ jsonrpc: "2.0",
1982
+ error: { code: -32603, message: "Internal CodexGPT MCP error. Check the local terminal for details." },
1983
+ id: null
1984
+ });
1985
+ }
1986
+ }
1987
+ });
1988
+ const handleSessionRequest = async (req, res) => {
1989
+ const sessionId = requestSessionId(req);
1990
+ const transport = getTransport(sessionId);
1991
+ if (!transport) {
1992
+ sendSessionError(res, sessionId);
1993
+ return;
1994
+ }
1995
+ await transport.handleRequest(req, res);
1996
+ };
1997
+ app.get("/mcp", handleSessionRequest);
1998
+ app.delete("/mcp", handleSessionRequest);
1999
+ app.use((error, req, res, next) => {
2000
+ if (!error || typeof error !== "object" || !("type" in error)) {
2001
+ next(error);
2002
+ return;
2003
+ }
2004
+ const type = String(error.type ?? "");
2005
+ if (type !== "entity.parse.failed" && type !== "entity.too.large") {
2006
+ next(error);
2007
+ return;
2008
+ }
2009
+ const status = type === "entity.too.large" ? 413 : 400;
2010
+ if (req.path === "/mcp") {
2011
+ res.status(status).json({
2012
+ jsonrpc: "2.0",
2013
+ error: {
2014
+ code: type === "entity.too.large" ? -32000 : -32700,
2015
+ message: type === "entity.too.large" ? "Payload too large." : "Parse error."
2016
+ },
2017
+ id: null
2018
+ });
2019
+ return;
2020
+ }
2021
+ if (req.path === "/admin/profile") {
2022
+ jsonError(res, status, type === "entity.too.large" ? "payload_too_large" : "invalid_json", type === "entity.too.large" ? "Request body is too large." : "Request body must be valid JSON.");
2023
+ return;
2024
+ }
2025
+ next(error);
2026
+ });
2027
+ const httpServer = app.listen(config.port, config.host, () => {
2028
+ console.error(`[CodexGPT] HTTP MCP listening on http://${config.host}:${config.port}/mcp`);
2029
+ console.error(`[CodexGPT] defaultRoot=${config.defaultRoot}`);
2030
+ console.error(`[CodexGPT] allowedRoots=${config.allowedRoots.join(", ")}`);
2031
+ console.error(`[CodexGPT] bashMode=${config.bashMode}`);
2032
+ console.error(`[CodexGPT] writeMode=${config.writeMode}`);
2033
+ console.error(`[CodexGPT] widgetDomain=${config.widgetDomain}`);
2034
+ });
2035
+ let shutdownPromise = null;
2036
+ const shutdown = () => {
2037
+ if (shutdownPromise)
2038
+ return shutdownPromise;
2039
+ shutdownPromise = (async () => {
2040
+ clearInterval(pruneTimer);
2041
+ const closingTransports = [...transports.values()].map((record) => Promise.resolve(record.transport.close?.()));
2042
+ transports.clear();
2043
+ await Promise.allSettled(closingTransports);
2044
+ await new Promise((resolve, reject) => {
2045
+ httpServer.close((error) => error ? reject(error) : resolve());
2046
+ });
2047
+ let approvalFailure;
2048
+ try {
2049
+ await sharedLocalApprovalRuntimeV5?.close();
2050
+ }
2051
+ catch (error) {
2052
+ approvalFailure = error;
2053
+ }
2054
+ finally {
2055
+ sharedApprovalAuditStoreV5?.dispose();
2056
+ sharedApprovalRegistryV5?.dispose();
2057
+ }
2058
+ if (approvalFailure)
2059
+ throw approvalFailure;
2060
+ })();
2061
+ return shutdownPromise;
2062
+ };
2063
+ const requestShutdown = () => {
2064
+ void shutdown().catch((error) => {
2065
+ console.error(error instanceof Error ? error.stack ?? error.message : String(error));
2066
+ process.exitCode = 1;
2067
+ });
2068
+ };
2069
+ process.once("SIGINT", requestShutdown);
2070
+ process.once("SIGTERM", requestShutdown);
2071
+ }
2072
+ main().catch((error) => {
2073
+ console.error(error instanceof Error ? error.stack ?? error.message : String(error));
2074
+ process.exit(1);
2075
+ });
2076
+ //# sourceMappingURL=http.js.map