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.
- {codex_partner-0.0.1 → codex_partner-0.0.2}/PKG-INFO +20 -7
- {codex_partner-0.0.1 → codex_partner-0.0.2}/README.md +19 -6
- {codex_partner-0.0.1 → codex_partner-0.0.2}/app.py +112 -15
- {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner/__init__.py +1 -1
- {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner/schemas.py +5 -0
- codex_partner-0.0.2/codex_partner/ssh_auth.py +123 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner.egg-info/PKG-INFO +20 -7
- {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner.egg-info/SOURCES.txt +1 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/pyproject.toml +1 -1
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/app.js +2 -1
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/conversation.js +51 -28
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/core.js +80 -12
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/index.html +5 -4
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/styles.css +27 -8
- {codex_partner-0.0.1 → codex_partner-0.0.2}/tests/test_running_state.py +50 -1
- {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner/__main__.py +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner/app_server.py +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner/cli.py +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner/commands.py +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner/database.py +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner/discovery.py +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner.egg-info/dependency_links.txt +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner.egg-info/entry_points.txt +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner.egg-info/requires.txt +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/codex_partner.egg-info/top_level.txt +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/setup.cfg +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/__init__.py +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/chat-worker.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/editor.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/mascot-dance.css +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/mascot-dance.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/settings.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/LICENSE +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/ace.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/ext-modelist.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-abap.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-abc.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-actionscript.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-ada.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-alda.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-apache_conf.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-apex.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-applescript.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-aql.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-asciidoc.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-asl.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-assembly_arm32.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-assembly_x86.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-astro.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-autohotkey.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-basic.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-batchfile.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-bibtex.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-c9search.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-c_cpp.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-cedar.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-cedarschema.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-cirru.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-clojure.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-clue.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-cobol.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-coffee.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-coldfusion.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-crystal.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-csharp.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-csound_document.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-csound_orchestra.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-csound_score.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-csp.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-css.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-csv.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-curly.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-cuttlefish.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-d.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-dart.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-diff.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-django.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-dockerfile.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-dot.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-drools.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-edifact.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-eiffel.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-ejs.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-elixir.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-elm.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-erlang.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-flix.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-forth.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-fortran.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-fsharp.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-fsl.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-ftl.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-gcode.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-gherkin.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-gitignore.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-glsl.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-gobstones.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-golang.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-graphqlschema.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-groovy.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-groq.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-haml.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-handlebars.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-haskell.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-haskell_cabal.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-haxe.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-hjson.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-html.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-html_elixir.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-html_ruby.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-ini.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-io.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-ion.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-jack.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-jade.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-java.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-javascript.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-jexl.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-json.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-json5.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-jsoniq.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-jsp.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-jssm.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-jsx.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-julia.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-kotlin.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-latex.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-latte.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-less.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-liquid.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-lisp.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-livescript.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-logiql.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-logtalk.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-lsl.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-lua.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-luapage.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-lucene.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-makefile.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-mariadb.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-markdown.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-mask.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-matlab.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-maze.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-mediawiki.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-mel.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-mips.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-mixal.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-mushcode.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-mysql.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-nasal.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-nginx.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-nim.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-nix.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-nsis.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-nunjucks.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-objectivec.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-ocaml.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-odin.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-partiql.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-pascal.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-perl.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-pgsql.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-php.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-php_laravel_blade.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-pig.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-plain_text.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-plsql.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-powershell.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-praat.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-prisma.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-prolog.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-properties.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-protobuf.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-prql.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-puppet.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-python.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-qml.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-r.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-raku.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-razor.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-rdoc.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-red.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-redshift.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-rhtml.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-robot.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-rst.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-ruby.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-rust.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-sac.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-sass.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-scad.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-scala.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-scheme.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-scrypt.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-scss.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-sh.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-sjs.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-slim.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-smarty.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-smithy.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-snippets.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-soy_template.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-space.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-sparql.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-sql.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-sqlserver.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-stylus.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-svg.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-swift.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-tcl.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-terraform.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-tex.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-text.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-textile.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-toml.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-tsv.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-tsx.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-turtle.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-twig.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-typescript.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-vala.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-vbscript.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-velocity.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-verilog.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-vhdl.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-visualforce.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-vue.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-wollok.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-xml.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-xquery.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-yaml.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-zeek.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/mode-zig.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/ace/theme-tomorrow_night_eighties.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/i18next/LICENSE +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/i18next/i18next.min.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/toastui-editor/LICENSE +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/toastui-editor/toastui-editor-dark.css +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/toastui-editor/toastui-editor.css +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/toastui-editor/toastui-editor.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/toastui-editor/zh-cn.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/xterm/LICENSE +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/xterm/addons/fit.LICENSE +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/xterm/addons/fit.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/xterm/addons/unicode11.LICENSE +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/xterm/addons/unicode11.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/xterm/addons/web-links.LICENSE +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/xterm/addons/web-links.js +0 -0
- {codex_partner-0.0.1 → codex_partner-0.0.2}/static/vendor/xterm/xterm.css +0 -0
- {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.
|
|
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
|
|
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
|
|
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.
|
|
81
|
+
docker build -t codex-partner:0.0.2 .
|
|
71
82
|
docker run -d --name codex-partner \
|
|
72
83
|
-p 8787:8787 \
|
|
73
|
-
-
|
|
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.
|
|
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
|
|
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
|
|
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.
|
|
56
|
+
docker build -t codex-partner:0.0.2 .
|
|
46
57
|
docker run -d --name codex-partner \
|
|
47
58
|
-p 8787:8787 \
|
|
48
|
-
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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,)):
|
|
@@ -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.
|
|
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
|
|
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
|
|
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.
|
|
81
|
+
docker build -t codex-partner:0.0.2 .
|
|
71
82
|
docker run -d --name codex-partner \
|
|
72
83
|
-p 8787:8787 \
|
|
73
|
-
-
|
|
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.
|
|
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
|
|
@@ -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);
|