codex-partner 0.0.1__tar.gz → 0.0.2__tar.gz

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 (251) hide show
  1. {codex_partner-0.0.1 → codex_partner-0.0.2}/PKG-INFO +20 -7
  2. {codex_partner-0.0.1 → codex_partner-0.0.2}/README.md +19 -6
  3. {codex_partner-0.0.1 → codex_partner-0.0.2}/app.py +112 -15
  4. {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner/__init__.py +1 -1
  5. {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner/schemas.py +5 -0
  6. codex_partner-0.0.2/codex_partner/ssh_auth.py +123 -0
  7. {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner.egg-info/PKG-INFO +20 -7
  8. {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner.egg-info/SOURCES.txt +1 -0
  9. {codex_partner-0.0.1 → codex_partner-0.0.2}/pyproject.toml +1 -1
  10. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/app.js +2 -1
  11. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/conversation.js +51 -28
  12. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/core.js +80 -12
  13. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/index.html +5 -4
  14. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/styles.css +27 -8
  15. {codex_partner-0.0.1 → codex_partner-0.0.2}/tests/test_running_state.py +50 -1
  16. {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner/__main__.py +0 -0
  17. {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner/app_server.py +0 -0
  18. {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner/cli.py +0 -0
  19. {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner/commands.py +0 -0
  20. {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner/database.py +0 -0
  21. {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner/discovery.py +0 -0
  22. {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner.egg-info/dependency_links.txt +0 -0
  23. {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner.egg-info/entry_points.txt +0 -0
  24. {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner.egg-info/requires.txt +0 -0
  25. {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner.egg-info/top_level.txt +0 -0
  26. {codex_partner-0.0.1 → codex_partner-0.0.2}/setup.cfg +0 -0
  27. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/__init__.py +0 -0
  28. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/chat-worker.js +0 -0
  29. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/editor.js +0 -0
  30. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/mascot-dance.css +0 -0
  31. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/mascot-dance.js +0 -0
  32. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/settings.js +0 -0
  33. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/LICENSE +0 -0
  34. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/ace.js +0 -0
  35. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/ext-modelist.js +0 -0
  36. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-abap.js +0 -0
  37. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-abc.js +0 -0
  38. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-actionscript.js +0 -0
  39. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-ada.js +0 -0
  40. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-alda.js +0 -0
  41. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-apache_conf.js +0 -0
  42. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-apex.js +0 -0
  43. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-applescript.js +0 -0
  44. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-aql.js +0 -0
  45. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-asciidoc.js +0 -0
  46. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-asl.js +0 -0
  47. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-assembly_arm32.js +0 -0
  48. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-assembly_x86.js +0 -0
  49. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-astro.js +0 -0
  50. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-autohotkey.js +0 -0
  51. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-basic.js +0 -0
  52. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-batchfile.js +0 -0
  53. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-bibtex.js +0 -0
  54. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-c9search.js +0 -0
  55. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-c_cpp.js +0 -0
  56. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-cedar.js +0 -0
  57. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-cedarschema.js +0 -0
  58. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-cirru.js +0 -0
  59. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-clojure.js +0 -0
  60. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-clue.js +0 -0
  61. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-cobol.js +0 -0
  62. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-coffee.js +0 -0
  63. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-coldfusion.js +0 -0
  64. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-crystal.js +0 -0
  65. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-csharp.js +0 -0
  66. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-csound_document.js +0 -0
  67. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-csound_orchestra.js +0 -0
  68. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-csound_score.js +0 -0
  69. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-csp.js +0 -0
  70. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-css.js +0 -0
  71. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-csv.js +0 -0
  72. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-curly.js +0 -0
  73. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-cuttlefish.js +0 -0
  74. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-d.js +0 -0
  75. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-dart.js +0 -0
  76. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-diff.js +0 -0
  77. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-django.js +0 -0
  78. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-dockerfile.js +0 -0
  79. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-dot.js +0 -0
  80. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-drools.js +0 -0
  81. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-edifact.js +0 -0
  82. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-eiffel.js +0 -0
  83. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-ejs.js +0 -0
  84. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-elixir.js +0 -0
  85. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-elm.js +0 -0
  86. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-erlang.js +0 -0
  87. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-flix.js +0 -0
  88. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-forth.js +0 -0
  89. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-fortran.js +0 -0
  90. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-fsharp.js +0 -0
  91. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-fsl.js +0 -0
  92. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-ftl.js +0 -0
  93. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-gcode.js +0 -0
  94. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-gherkin.js +0 -0
  95. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-gitignore.js +0 -0
  96. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-glsl.js +0 -0
  97. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-gobstones.js +0 -0
  98. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-golang.js +0 -0
  99. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-graphqlschema.js +0 -0
  100. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-groovy.js +0 -0
  101. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-groq.js +0 -0
  102. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-haml.js +0 -0
  103. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-handlebars.js +0 -0
  104. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-haskell.js +0 -0
  105. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-haskell_cabal.js +0 -0
  106. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-haxe.js +0 -0
  107. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-hjson.js +0 -0
  108. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-html.js +0 -0
  109. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-html_elixir.js +0 -0
  110. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-html_ruby.js +0 -0
  111. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-ini.js +0 -0
  112. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-io.js +0 -0
  113. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-ion.js +0 -0
  114. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-jack.js +0 -0
  115. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-jade.js +0 -0
  116. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-java.js +0 -0
  117. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-javascript.js +0 -0
  118. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-jexl.js +0 -0
  119. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-json.js +0 -0
  120. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-json5.js +0 -0
  121. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-jsoniq.js +0 -0
  122. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-jsp.js +0 -0
  123. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-jssm.js +0 -0
  124. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-jsx.js +0 -0
  125. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-julia.js +0 -0
  126. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-kotlin.js +0 -0
  127. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-latex.js +0 -0
  128. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-latte.js +0 -0
  129. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-less.js +0 -0
  130. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-liquid.js +0 -0
  131. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-lisp.js +0 -0
  132. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-livescript.js +0 -0
  133. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-logiql.js +0 -0
  134. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-logtalk.js +0 -0
  135. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-lsl.js +0 -0
  136. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-lua.js +0 -0
  137. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-luapage.js +0 -0
  138. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-lucene.js +0 -0
  139. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-makefile.js +0 -0
  140. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-mariadb.js +0 -0
  141. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-markdown.js +0 -0
  142. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-mask.js +0 -0
  143. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-matlab.js +0 -0
  144. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-maze.js +0 -0
  145. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-mediawiki.js +0 -0
  146. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-mel.js +0 -0
  147. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-mips.js +0 -0
  148. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-mixal.js +0 -0
  149. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-mushcode.js +0 -0
  150. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-mysql.js +0 -0
  151. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-nasal.js +0 -0
  152. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-nginx.js +0 -0
  153. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-nim.js +0 -0
  154. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-nix.js +0 -0
  155. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-nsis.js +0 -0
  156. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-nunjucks.js +0 -0
  157. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-objectivec.js +0 -0
  158. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-ocaml.js +0 -0
  159. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-odin.js +0 -0
  160. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-partiql.js +0 -0
  161. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-pascal.js +0 -0
  162. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-perl.js +0 -0
  163. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-pgsql.js +0 -0
  164. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-php.js +0 -0
  165. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-php_laravel_blade.js +0 -0
  166. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-pig.js +0 -0
  167. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-plain_text.js +0 -0
  168. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-plsql.js +0 -0
  169. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-powershell.js +0 -0
  170. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-praat.js +0 -0
  171. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-prisma.js +0 -0
  172. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-prolog.js +0 -0
  173. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-properties.js +0 -0
  174. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-protobuf.js +0 -0
  175. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-prql.js +0 -0
  176. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-puppet.js +0 -0
  177. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-python.js +0 -0
  178. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-qml.js +0 -0
  179. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-r.js +0 -0
  180. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-raku.js +0 -0
  181. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-razor.js +0 -0
  182. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-rdoc.js +0 -0
  183. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-red.js +0 -0
  184. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-redshift.js +0 -0
  185. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-rhtml.js +0 -0
  186. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-robot.js +0 -0
  187. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-rst.js +0 -0
  188. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-ruby.js +0 -0
  189. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-rust.js +0 -0
  190. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-sac.js +0 -0
  191. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-sass.js +0 -0
  192. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-scad.js +0 -0
  193. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-scala.js +0 -0
  194. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-scheme.js +0 -0
  195. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-scrypt.js +0 -0
  196. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-scss.js +0 -0
  197. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-sh.js +0 -0
  198. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-sjs.js +0 -0
  199. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-slim.js +0 -0
  200. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-smarty.js +0 -0
  201. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-smithy.js +0 -0
  202. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-snippets.js +0 -0
  203. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-soy_template.js +0 -0
  204. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-space.js +0 -0
  205. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-sparql.js +0 -0
  206. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-sql.js +0 -0
  207. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-sqlserver.js +0 -0
  208. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-stylus.js +0 -0
  209. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-svg.js +0 -0
  210. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-swift.js +0 -0
  211. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-tcl.js +0 -0
  212. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-terraform.js +0 -0
  213. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-tex.js +0 -0
  214. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-text.js +0 -0
  215. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-textile.js +0 -0
  216. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-toml.js +0 -0
  217. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-tsv.js +0 -0
  218. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-tsx.js +0 -0
  219. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-turtle.js +0 -0
  220. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-twig.js +0 -0
  221. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-typescript.js +0 -0
  222. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-vala.js +0 -0
  223. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-vbscript.js +0 -0
  224. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-velocity.js +0 -0
  225. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-verilog.js +0 -0
  226. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-vhdl.js +0 -0
  227. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-visualforce.js +0 -0
  228. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-vue.js +0 -0
  229. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-wollok.js +0 -0
  230. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-xml.js +0 -0
  231. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-xquery.js +0 -0
  232. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-yaml.js +0 -0
  233. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-zeek.js +0 -0
  234. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-zig.js +0 -0
  235. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/theme-tomorrow_night_eighties.js +0 -0
  236. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/i18next/LICENSE +0 -0
  237. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/i18next/i18next.min.js +0 -0
  238. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/toastui-editor/LICENSE +0 -0
  239. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/toastui-editor/toastui-editor-dark.css +0 -0
  240. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/toastui-editor/toastui-editor.css +0 -0
  241. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/toastui-editor/toastui-editor.js +0 -0
  242. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/toastui-editor/zh-cn.js +0 -0
  243. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/xterm/LICENSE +0 -0
  244. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/xterm/addons/fit.LICENSE +0 -0
  245. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/xterm/addons/fit.js +0 -0
  246. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/xterm/addons/unicode11.LICENSE +0 -0
  247. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/xterm/addons/unicode11.js +0 -0
  248. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/xterm/addons/web-links.LICENSE +0 -0
  249. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/xterm/addons/web-links.js +0 -0
  250. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/xterm/xterm.css +0 -0
  251. {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/xterm/xterm.js +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-partner
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: A self-hosted web interface for managing persistent Codex tasks.
5
5
  Project-URL: Homepage, https://github.com/froststeam/Codex-Partner
6
6
  Project-URL: Repository, https://github.com/froststeam/Codex-Partner.git
@@ -50,8 +50,19 @@ Codex Partner is a self-hosted control panel for long-running Codex work. It kee
50
50
 
51
51
  Requires Python 3.10+ and an authenticated [Codex CLI](https://github.com/openai/codex).
52
52
 
53
+ ### PyPI
54
+
55
+ ```bash
56
+ python3 -m venv .venv
57
+ . .venv/bin/activate
58
+ pip install codex-partner
59
+ codex-partner
60
+ ```
61
+
62
+ ### From source
63
+
53
64
  ```bash
54
- git clone git@github.com:froststeam/Codex-Partner.git
65
+ git clone https://github.com/froststeam/Codex-Partner.git
55
66
  cd Codex-Partner
56
67
  python3 -m venv .venv
57
68
  . .venv/bin/activate
@@ -62,25 +73,27 @@ codex-partner
62
73
 
63
74
  Open <http://127.0.0.1:8787>.
64
75
 
65
- For access from other devices, set `CODEX_DASHBOARD_HOST=0.0.0.0` and a strong `CODEX_DASHBOARD_TOKEN` in `.env`.
76
+ For access from other devices, set `CODEX_DASHBOARD_HOST=0.0.0.0`. Codex Partner signs browsers in with a username and password accepted by this server's SSH service; the password is verified by OpenSSH and is never stored. Put HTTPS in front of the service before entering an SSH password over an untrusted network.
66
77
 
67
78
  ## Docker
68
79
 
69
80
  ```bash
70
- docker build -t codex-partner:0.0.1 .
81
+ docker build -t codex-partner:0.0.2 .
71
82
  docker run -d --name codex-partner \
72
83
  -p 8787:8787 \
73
- -e CODEX_DASHBOARD_TOKEN=replace-with-a-strong-random-token \
84
+ --add-host=host.docker.internal:host-gateway \
85
+ -e CODEX_DASHBOARD_AUTH=ssh \
86
+ -e CODEX_DASHBOARD_AUTH_SSH_HOST=host.docker.internal \
74
87
  -v codex-partner-data:/var/lib/codex-partner \
75
88
  -v codex-home:/home/codex/.codex \
76
89
  -v codex-workspace:/workspace \
77
- codex-partner:0.0.1
90
+ codex-partner:0.0.2
78
91
  docker exec -it codex-partner codex login
79
92
  ```
80
93
 
81
94
  ## systemd
82
95
 
83
- After completing the installation above, stop the foreground process and install the user service:
96
+ After completing the source installation above, stop the foreground process and install the user service:
84
97
 
85
98
  ```bash
86
99
  mkdir -p ~/.config/systemd/user
@@ -25,8 +25,19 @@ Codex Partner is a self-hosted control panel for long-running Codex work. It kee
25
25
 
26
26
  Requires Python 3.10+ and an authenticated [Codex CLI](https://github.com/openai/codex).
27
27
 
28
+ ### PyPI
29
+
30
+ ```bash
31
+ python3 -m venv .venv
32
+ . .venv/bin/activate
33
+ pip install codex-partner
34
+ codex-partner
35
+ ```
36
+
37
+ ### From source
38
+
28
39
  ```bash
29
- git clone git@github.com:froststeam/Codex-Partner.git
40
+ git clone https://github.com/froststeam/Codex-Partner.git
30
41
  cd Codex-Partner
31
42
  python3 -m venv .venv
32
43
  . .venv/bin/activate
@@ -37,25 +48,27 @@ codex-partner
37
48
 
38
49
  Open <http://127.0.0.1:8787>.
39
50
 
40
- For access from other devices, set `CODEX_DASHBOARD_HOST=0.0.0.0` and a strong `CODEX_DASHBOARD_TOKEN` in `.env`.
51
+ For access from other devices, set `CODEX_DASHBOARD_HOST=0.0.0.0`. Codex Partner signs browsers in with a username and password accepted by this server's SSH service; the password is verified by OpenSSH and is never stored. Put HTTPS in front of the service before entering an SSH password over an untrusted network.
41
52
 
42
53
  ## Docker
43
54
 
44
55
  ```bash
45
- docker build -t codex-partner:0.0.1 .
56
+ docker build -t codex-partner:0.0.2 .
46
57
  docker run -d --name codex-partner \
47
58
  -p 8787:8787 \
48
- -e CODEX_DASHBOARD_TOKEN=replace-with-a-strong-random-token \
59
+ --add-host=host.docker.internal:host-gateway \
60
+ -e CODEX_DASHBOARD_AUTH=ssh \
61
+ -e CODEX_DASHBOARD_AUTH_SSH_HOST=host.docker.internal \
49
62
  -v codex-partner-data:/var/lib/codex-partner \
50
63
  -v codex-home:/home/codex/.codex \
51
64
  -v codex-workspace:/workspace \
52
- codex-partner:0.0.1
65
+ codex-partner:0.0.2
53
66
  docker exec -it codex-partner codex login
54
67
  ```
55
68
 
56
69
  ## systemd
57
70
 
58
- After completing the installation above, stop the foreground process and install the user service:
71
+ After completing the source installation above, stop the foreground process and install the user service:
59
72
 
60
73
  ```bash
61
74
  mkdir -p ~/.config/systemd/user
@@ -17,6 +17,7 @@ import pty
17
17
  import platform
18
18
  import re
19
19
  import signal
20
+ import secrets
20
21
  import shlex
21
22
  import shutil
22
23
  import sqlite3
@@ -34,9 +35,8 @@ from datetime import datetime, timezone
34
35
  from pathlib import Path, PurePosixPath
35
36
  from typing import Any, Optional
36
37
 
37
- from fastapi import Depends, FastAPI, HTTPException, Request, WebSocket, WebSocketDisconnect
38
- from fastapi.responses import FileResponse, StreamingResponse
39
- from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
38
+ from fastapi import Depends, FastAPI, HTTPException, Request, Response, WebSocket, WebSocketDisconnect
39
+ from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
40
40
  from starlette.middleware.gzip import GZipMiddleware
41
41
  from dotenv import load_dotenv
42
42
  import yaml
@@ -65,6 +65,7 @@ from codex_partner.schemas import (
65
65
  ProviderVerifyIn,
66
66
  QuickTaskCreate,
67
67
  SSHConnectIn,
68
+ SSHLoginIn,
68
69
  SkillIn,
69
70
  SlashCommandIn,
70
71
  TaskCreate,
@@ -73,6 +74,7 @@ from codex_partner.schemas import (
73
74
  TaskPatch,
74
75
  WorkspaceFileUpdate,
75
76
  )
77
+ from codex_partner.ssh_auth import LoginThrottle, verify_ssh_password
76
78
 
77
79
  try:
78
80
  import tomllib
@@ -88,7 +90,12 @@ DATA_DIR.mkdir(parents=True, exist_ok=True)
88
90
  DB_PATH = DATA_DIR / "dashboard.sqlite3"
89
91
  CODEX_HOME = Path(os.getenv("CODEX_HOME", str(Path.home() / ".codex"))).expanduser().resolve()
90
92
  CODEX_MEMORY_DIR = CODEX_HOME / "memories"
91
- TOKEN = os.getenv("CODEX_DASHBOARD_TOKEN", "").strip()
93
+ AUTH_MODE = os.getenv("CODEX_DASHBOARD_AUTH", "ssh").strip().lower()
94
+ if AUTH_MODE not in {"ssh", "none"}:
95
+ raise RuntimeError("CODEX_DASHBOARD_AUTH must be 'ssh' or 'none'")
96
+ AUTH_COOKIE = "codex_partner_session"
97
+ AUTH_SESSION_TTL = max(300, int(os.getenv("CODEX_DASHBOARD_AUTH_TTL", "43200")))
98
+ AUTH_SSH_HOST = os.getenv("CODEX_DASHBOARD_AUTH_SSH_HOST", "127.0.0.1").strip() or "127.0.0.1"
92
99
  CODEX_APP_SERVER_ENABLED = os.getenv("CODEX_APP_SERVER", "1").lower() not in {"0", "false", "no"}
93
100
  AUTO_RESUME = os.getenv("CODEX_DASHBOARD_AUTO_RESUME", "1").lower() not in {"0", "false", "no"}
94
101
  app_shutting_down = False
@@ -108,6 +115,7 @@ def configured_port(name: str, default: int) -> int:
108
115
 
109
116
  DASHBOARD_HOST = os.getenv("CODEX_DASHBOARD_HOST", "127.0.0.1").strip() or "127.0.0.1"
110
117
  DASHBOARD_PORT = configured_port("CODEX_DASHBOARD_PORT", 8787)
118
+ AUTH_SSH_PORT = configured_port("CODEX_DASHBOARD_AUTH_SSH_PORT", 22)
111
119
  SSH_BIN = shutil.which("ssh") or "ssh"
112
120
  SSH_CONFIG = Path.home() / ".ssh/config"
113
121
  SSH_FIXED_HOST = os.getenv("CODEX_DASHBOARD_SSH_HOST", "").strip()
@@ -372,7 +380,8 @@ async def lifespan(_: FastAPI):
372
380
 
373
381
  app = FastAPI(title=APP_NAME, version=APP_VERSION, lifespan=lifespan)
374
382
  app.add_middleware(GZipMiddleware, minimum_size=1024, compresslevel=5)
375
- bearer = HTTPBearer(auto_error=False)
383
+ auth_sessions: dict[str, dict[str, Any]] = {}
384
+ login_throttle = LoginThrottle()
376
385
  running: dict[str, asyncio.subprocess.Process] = {}
377
386
  running_lock = asyncio.Lock()
378
387
  task_clients: dict[str, set[WebSocket]] = {}
@@ -2356,9 +2365,33 @@ async def supervise_appserver_turn(
2356
2365
  await drain_task_messages(task["id"])
2357
2366
 
2358
2367
 
2359
- async def auth(credentials: HTTPAuthorizationCredentials = Depends(bearer)):
2360
- if TOKEN and (not credentials or credentials.credentials != TOKEN):
2361
- raise HTTPException(401, "Bearer token required")
2368
+ def active_auth_session(token: str) -> Optional[dict[str, Any]]:
2369
+ session = auth_sessions.get(token)
2370
+ if not session:
2371
+ return None
2372
+ if float(session.get("expires_at", 0)) <= time.time():
2373
+ auth_sessions.pop(token, None)
2374
+ return None
2375
+ return session
2376
+
2377
+
2378
+ def request_auth_session(request: Request) -> Optional[dict[str, Any]]:
2379
+ if AUTH_MODE == "none":
2380
+ return {"username": getpass.getuser(), "expires_at": 0}
2381
+ return active_auth_session(request.cookies.get(AUTH_COOKIE, ""))
2382
+
2383
+
2384
+ def websocket_auth_session(websocket: WebSocket) -> Optional[dict[str, Any]]:
2385
+ if AUTH_MODE == "none":
2386
+ return {"username": getpass.getuser(), "expires_at": 0}
2387
+ return active_auth_session(websocket.cookies.get(AUTH_COOKIE, ""))
2388
+
2389
+
2390
+ async def auth(request: Request):
2391
+ session = request_auth_session(request)
2392
+ if not session:
2393
+ raise HTTPException(401, "SSH login required")
2394
+ return session
2362
2395
 
2363
2396
 
2364
2397
  def task_or_404(task_id: str) -> dict:
@@ -2890,8 +2923,7 @@ def set_terminal_size(fd: int, cols: int, rows: int) -> None:
2890
2923
 
2891
2924
  @app.websocket("/ws/terminal/{task_id}")
2892
2925
  async def terminal_socket(websocket: WebSocket, task_id: str):
2893
- token = websocket.query_params.get("token", "") or websocket.headers.get("authorization", "").removeprefix("Bearer ")
2894
- if TOKEN and token != TOKEN:
2926
+ if not websocket_auth_session(websocket):
2895
2927
  await websocket.close(code=4401)
2896
2928
  return
2897
2929
  task = db.one("SELECT id,workspace,ssh_host FROM tasks WHERE id=?", (task_id,))
@@ -3202,6 +3234,73 @@ def markdown_rows(rows: list[dict], fields: list[tuple[str, str]], empty: str) -
3202
3234
  return "\n".join(result)
3203
3235
 
3204
3236
 
3237
+ def secure_request_cookie(request: Request) -> bool:
3238
+ forwarded = request.headers.get("x-forwarded-proto", "").split(",", 1)[0].strip().lower()
3239
+ return request.url.scheme == "https" or forwarded == "https"
3240
+
3241
+
3242
+ @app.get("/api/auth/status")
3243
+ async def auth_status(request: Request):
3244
+ session = request_auth_session(request)
3245
+ return {
3246
+ "mode": AUTH_MODE,
3247
+ "authenticated": bool(session),
3248
+ "username": (session or {}).get("username", ""),
3249
+ "server_hostname": socket.gethostname(),
3250
+ "ssh_host": AUTH_SSH_HOST,
3251
+ "ssh_port": AUTH_SSH_PORT,
3252
+ }
3253
+
3254
+
3255
+ @app.get("/api/live")
3256
+ async def live():
3257
+ return {"ok": True, "version": app.version}
3258
+
3259
+
3260
+ @app.post("/api/auth/login")
3261
+ async def ssh_login(payload: SSHLoginIn, request: Request):
3262
+ if AUTH_MODE == "none":
3263
+ return {"authenticated": True, "username": getpass.getuser()}
3264
+ client = request.client.host if request.client else "unknown"
3265
+ if not login_throttle.allowed(client):
3266
+ raise HTTPException(429, "Too many failed SSH login attempts; try again later")
3267
+ username = payload.username.strip()
3268
+ valid, _detail = await asyncio.to_thread(
3269
+ verify_ssh_password,
3270
+ username,
3271
+ payload.password.get_secret_value(),
3272
+ host=AUTH_SSH_HOST,
3273
+ port=AUTH_SSH_PORT,
3274
+ known_hosts=DATA_DIR / "ssh-login-known-hosts",
3275
+ )
3276
+ if not valid:
3277
+ login_throttle.fail(client)
3278
+ raise HTTPException(401, "SSH username or password is incorrect")
3279
+ login_throttle.clear(client)
3280
+ token = secrets.token_urlsafe(32)
3281
+ auth_sessions[token] = {"username": username, "expires_at": time.time() + AUTH_SESSION_TTL}
3282
+ response = JSONResponse({"authenticated": True, "username": username})
3283
+ response.set_cookie(
3284
+ AUTH_COOKIE,
3285
+ token,
3286
+ max_age=AUTH_SESSION_TTL,
3287
+ httponly=True,
3288
+ secure=secure_request_cookie(request),
3289
+ samesite="strict",
3290
+ path="/",
3291
+ )
3292
+ return response
3293
+
3294
+
3295
+ @app.post("/api/auth/logout")
3296
+ async def ssh_logout(request: Request, response: Response):
3297
+ token = request.cookies.get(AUTH_COOKIE, "")
3298
+ if token:
3299
+ auth_sessions.pop(token, None)
3300
+ response.delete_cookie(AUTH_COOKIE, path="/", samesite="strict")
3301
+ return {"authenticated": False}
3302
+
3303
+
3205
3304
  @app.get("/api/commands")
3206
3305
  async def list_slash_commands(_: Any = Depends(auth)):
3207
3306
  return {"version": app.version, "commands": SLASH_COMMANDS}
@@ -3216,7 +3315,7 @@ async def health(_: Any = Depends(auth)):
3216
3315
  "codex_available": CODEX_AVAILABLE,
3217
3316
  "codex_bin_source": CODEX_DISCOVERY.get("source", "missing"),
3218
3317
  "codex_install": None if CODEX_AVAILABLE else codex_install_plan(),
3219
- "token_required": bool(TOKEN),
3318
+ "auth_mode": AUTH_MODE,
3220
3319
  "running": active,
3221
3320
  "server_user": getpass.getuser(),
3222
3321
  "server_hostname": socket.gethostname(),
@@ -4535,8 +4634,7 @@ async def steer_task_message(task_id: str, payload: TaskMessageIn, _: Any = Depe
4535
4634
 
4536
4635
  @app.websocket("/ws/overview")
4537
4636
  async def overview_socket(websocket: WebSocket):
4538
- token = websocket.query_params.get("token", "") or websocket.headers.get("authorization", "").removeprefix("Bearer ")
4539
- if TOKEN and token != TOKEN:
4637
+ if not websocket_auth_session(websocket):
4540
4638
  await websocket.close(code=4401)
4541
4639
  return
4542
4640
  await websocket.accept()
@@ -4555,8 +4653,7 @@ async def overview_socket(websocket: WebSocket):
4555
4653
 
4556
4654
  @app.websocket("/ws/tasks/{task_id}")
4557
4655
  async def task_socket(websocket: WebSocket, task_id: str):
4558
- token = websocket.query_params.get("token", "") or websocket.headers.get("authorization", "").removeprefix("Bearer ")
4559
- if TOKEN and token != TOKEN:
4656
+ if not websocket_auth_session(websocket):
4560
4657
  await websocket.close(code=4401)
4561
4658
  return
4562
4659
  if not db.one("SELECT id FROM tasks WHERE id=?", (task_id,)):
@@ -1,4 +1,4 @@
1
1
  """Codex Partner application package."""
2
2
 
3
3
  APP_NAME = "Codex Partner"
4
- APP_VERSION = "0.0.1"
4
+ APP_VERSION = "0.0.2"
@@ -74,6 +74,11 @@ class SSHConnectIn(BaseModel):
74
74
  password: Optional[SecretStr] = None
75
75
 
76
76
 
77
+ class SSHLoginIn(BaseModel):
78
+ username: str = Field(min_length=1, max_length=64)
79
+ password: SecretStr
80
+
81
+
77
82
  class SkillIn(BaseModel):
78
83
  name: str = Field(min_length=1, max_length=120)
79
84
  description: str = ""
@@ -0,0 +1,123 @@
1
+ """SSH-backed browser authentication for Codex Partner."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import re
7
+ import time
8
+ from pathlib import Path
9
+
10
+ import pexpect
11
+
12
+
13
+ USERNAME_PATTERN = re.compile(r"^[a-z_][a-z0-9_.-]{0,63}$", re.IGNORECASE)
14
+
15
+
16
+ def valid_ssh_username(username: str) -> bool:
17
+ """Reject values that cannot be a plain OpenSSH user name."""
18
+
19
+ return bool(USERNAME_PATTERN.fullmatch(username.strip()))
20
+
21
+
22
+ def verify_ssh_password(
23
+ username: str,
24
+ password: str,
25
+ *,
26
+ host: str,
27
+ port: int,
28
+ known_hosts: Path,
29
+ timeout: int = 12,
30
+ ) -> tuple[bool, str]:
31
+ """Verify credentials with OpenSSH without retaining the password."""
32
+
33
+ username = username.strip()
34
+ if not valid_ssh_username(username) or not password:
35
+ return False, "Invalid SSH username or password"
36
+
37
+ known_hosts.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
38
+ args = [
39
+ "ssh",
40
+ "-F",
41
+ "/dev/null",
42
+ "-o",
43
+ "BatchMode=no",
44
+ "-o",
45
+ "PreferredAuthentications=password,keyboard-interactive",
46
+ "-o",
47
+ "PubkeyAuthentication=no",
48
+ "-o",
49
+ "NumberOfPasswordPrompts=1",
50
+ "-o",
51
+ "StrictHostKeyChecking=accept-new",
52
+ "-o",
53
+ f"UserKnownHostsFile={known_hosts}",
54
+ "-o",
55
+ f"ConnectTimeout={min(max(timeout, 3), 30)}",
56
+ "-p",
57
+ str(port),
58
+ f"{username}@{host}",
59
+ "true",
60
+ ]
61
+ child = pexpect.spawn(
62
+ args[0],
63
+ args[1:],
64
+ encoding="utf-8",
65
+ timeout=timeout,
66
+ env={**os.environ, "LC_ALL": "C"},
67
+ )
68
+ password_sent = False
69
+ output: list[str] = []
70
+ try:
71
+ while True:
72
+ matched = child.expect(
73
+ [
74
+ r"(?i)(?:password|passphrase).*:",
75
+ r"(?i)are you sure you want to continue connecting",
76
+ pexpect.EOF,
77
+ pexpect.TIMEOUT,
78
+ ]
79
+ )
80
+ output.append(child.before or "")
81
+ if matched == 0:
82
+ if password_sent:
83
+ return False, "SSH authentication failed"
84
+ child.sendline(password)
85
+ password_sent = True
86
+ continue
87
+ if matched == 1:
88
+ child.sendline("yes")
89
+ continue
90
+ if matched == 3:
91
+ return False, "SSH authentication timed out"
92
+ break
93
+ child.close()
94
+ if child.exitstatus == 0:
95
+ return True, ""
96
+ detail = " ".join(" ".join(output).split()).lower()
97
+ if "connection refused" in detail or "no route to host" in detail:
98
+ return False, "Local SSH service is unavailable"
99
+ return False, "SSH authentication failed"
100
+ finally:
101
+ if child.isalive():
102
+ child.close(force=True)
103
+
104
+
105
+ class LoginThrottle:
106
+ """Small in-memory limiter for repeated SSH password failures."""
107
+
108
+ def __init__(self, attempts: int = 5, window_seconds: int = 300):
109
+ self.attempts = attempts
110
+ self.window_seconds = window_seconds
111
+ self.failures: dict[str, list[float]] = {}
112
+
113
+ def allowed(self, key: str) -> bool:
114
+ cutoff = time.monotonic() - self.window_seconds
115
+ recent = [stamp for stamp in self.failures.get(key, []) if stamp >= cutoff]
116
+ self.failures[key] = recent
117
+ return len(recent) < self.attempts
118
+
119
+ def fail(self, key: str) -> None:
120
+ self.failures.setdefault(key, []).append(time.monotonic())
121
+
122
+ def clear(self, key: str) -> None:
123
+ self.failures.pop(key, None)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-partner
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: A self-hosted web interface for managing persistent Codex tasks.
5
5
  Project-URL: Homepage, https://github.com/froststeam/Codex-Partner
6
6
  Project-URL: Repository, https://github.com/froststeam/Codex-Partner.git
@@ -50,8 +50,19 @@ Codex Partner is a self-hosted control panel for long-running Codex work. It kee
50
50
 
51
51
  Requires Python 3.10+ and an authenticated [Codex CLI](https://github.com/openai/codex).
52
52
 
53
+ ### PyPI
54
+
55
+ ```bash
56
+ python3 -m venv .venv
57
+ . .venv/bin/activate
58
+ pip install codex-partner
59
+ codex-partner
60
+ ```
61
+
62
+ ### From source
63
+
53
64
  ```bash
54
- git clone git@github.com:froststeam/Codex-Partner.git
65
+ git clone https://github.com/froststeam/Codex-Partner.git
55
66
  cd Codex-Partner
56
67
  python3 -m venv .venv
57
68
  . .venv/bin/activate
@@ -62,25 +73,27 @@ codex-partner
62
73
 
63
74
  Open <http://127.0.0.1:8787>.
64
75
 
65
- For access from other devices, set `CODEX_DASHBOARD_HOST=0.0.0.0` and a strong `CODEX_DASHBOARD_TOKEN` in `.env`.
76
+ For access from other devices, set `CODEX_DASHBOARD_HOST=0.0.0.0`. Codex Partner signs browsers in with a username and password accepted by this server's SSH service; the password is verified by OpenSSH and is never stored. Put HTTPS in front of the service before entering an SSH password over an untrusted network.
66
77
 
67
78
  ## Docker
68
79
 
69
80
  ```bash
70
- docker build -t codex-partner:0.0.1 .
81
+ docker build -t codex-partner:0.0.2 .
71
82
  docker run -d --name codex-partner \
72
83
  -p 8787:8787 \
73
- -e CODEX_DASHBOARD_TOKEN=replace-with-a-strong-random-token \
84
+ --add-host=host.docker.internal:host-gateway \
85
+ -e CODEX_DASHBOARD_AUTH=ssh \
86
+ -e CODEX_DASHBOARD_AUTH_SSH_HOST=host.docker.internal \
74
87
  -v codex-partner-data:/var/lib/codex-partner \
75
88
  -v codex-home:/home/codex/.codex \
76
89
  -v codex-workspace:/workspace \
77
- codex-partner:0.0.1
90
+ codex-partner:0.0.2
78
91
  docker exec -it codex-partner codex login
79
92
  ```
80
93
 
81
94
  ## systemd
82
95
 
83
- After completing the installation above, stop the foreground process and install the user service:
96
+ After completing the source installation above, stop the foreground process and install the user service:
84
97
 
85
98
  ```bash
86
99
  mkdir -p ~/.config/systemd/user
@@ -9,6 +9,7 @@ codex_partner/commands.py
9
9
  codex_partner/database.py
10
10
  codex_partner/discovery.py
11
11
  codex_partner/schemas.py
12
+ codex_partner/ssh_auth.py
12
13
  codex_partner.egg-info/PKG-INFO
13
14
  codex_partner.egg-info/SOURCES.txt
14
15
  codex_partner.egg-info/dependency_links.txt
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codex-partner"
7
- version = "0.0.1"
7
+ version = "0.0.2"
8
8
  description = "A self-hosted web interface for managing persistent Codex tasks."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -487,7 +487,7 @@ document.addEventListener("keydown", event => {
487
487
  mascotDanceIndex = 0;
488
488
  triggerMascotDance();
489
489
  });
490
- document.addEventListener("visibilitychange", () => { if (document.visibilityState === "visible") { connectOverviewSocket(); if (state.selectedId && !socketPaused && (!socket || socket.readyState === WebSocket.CLOSED)) connectSocket(state.selectedId, true); if (terminalTaskId && terminalShouldReconnect && !terminalSocket) connectTerminal(terminalTaskId, true); } });
490
+ document.addEventListener("visibilitychange", () => { if (document.visibilityState === "visible") { connectOverviewSocket(); if (state.selectedId && !socketPaused && (!socket || socket.readyState === WebSocket.CLOSED)) connectSocket(state.selectedId, true); if (terminalTaskId && terminalShouldReconnect && !terminalSocket) connectTerminal(terminalTaskId, true); reconcileSelectedTaskStatus(); } });
491
491
  window.addEventListener("offline", renderConnectionStatus);
492
492
  window.addEventListener("online", () => {
493
493
  setRealtimeChannel("overview", "reconnecting");
@@ -500,6 +500,7 @@ window.addEventListener("online", () => {
500
500
  });
501
501
  setTheme(localStorage.getItem("codex-dashboard-theme") || "dark");
502
502
  setInterval(updateTurnElapsed, 1000);
503
+ setInterval(reconcileSelectedTaskStatus, 5000);
503
504
  setInterval(heartbeatRealtime, 10_000);
504
505
  renderConnectionStatus();
505
506
  showEmptyConversation(); refresh(false);