chattool 7.0.3__tar.gz → 7.0.5__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.
- {chattool-7.0.3/src/chattool.egg-info → chattool-7.0.5}/PKG-INFO +8 -7
- {chattool-7.0.3 → chattool-7.0.5}/README.md +4 -3
- {chattool-7.0.3 → chattool-7.0.5}/pyproject.toml +3 -4
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/__init__.py +1 -1
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/client/main.py +0 -1
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/__init__.py +0 -2
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/elements.py +0 -2
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/openai.py +25 -1
- chattool-7.0.5/src/chattool/explore/__init__.py +5 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/serve/__init__.py +4 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/serve/cli.py +8 -0
- chattool-7.0.5/src/chattool/serve/oauth.py +249 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/alias.py +0 -1
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/elements.py +37 -1
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/workspace/cli.py +42 -8
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/workspace/options.py +77 -112
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/workspace/render.py +32 -17
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/workspace/templates/default/en/AGENTS.md +7 -5
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/workspace/templates/default/en/projects/README.md +1 -1
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/workspace/templates/default/zh/AGENTS.md +7 -5
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/workspace/templates/default/zh/projects/README.md +1 -1
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/__init__.py +0 -2
- chattool-7.0.5/src/chattool/tools/crs/__init__.py +19 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/crs/cli.py +92 -0
- chattool-7.0.5/src/chattool/tools/crs/openai_oauth.py +138 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/image/__init__.py +6 -1
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/image/cli.py +113 -9
- chattool-7.0.5/src/chattool/tools/image/codex.py +393 -0
- chattool-7.0.5/src/chattool/tools/image/helpers.py +96 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/pypi/main.py +1 -0
- {chattool-7.0.3 → chattool-7.0.5/src/chattool.egg-info}/PKG-INFO +8 -7
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool.egg-info/SOURCES.txt +3 -11
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool.egg-info/requires.txt +3 -4
- chattool-7.0.3/src/chattool/config/github.py +0 -27
- chattool-7.0.3/src/chattool/explore/__init__.py +0 -5
- chattool-7.0.3/src/chattool/explore/github/__init__.py +0 -6
- chattool-7.0.3/src/chattool/explore/github/client.py +0 -106
- chattool-7.0.3/src/chattool/explore/github/models.py +0 -42
- chattool-7.0.3/src/chattool/tools/crs/__init__.py +0 -5
- chattool-7.0.3/src/chattool/tools/github/__init__.py +0 -11
- chattool-7.0.3/src/chattool/tools/github/api.py +0 -368
- chattool-7.0.3/src/chattool/tools/github/cli.py +0 -448
- chattool-7.0.3/src/chattool/tools/github/client.py +0 -153
- chattool-7.0.3/src/chattool/tools/github/commands.py +0 -250
- chattool-7.0.3/src/chattool/tools/github/render.py +0 -212
- chattool-7.0.3/src/chattool/tools/github/requests.py +0 -295
- chattool-7.0.3/src/chattool/tools/image/helpers.py +0 -28
- {chattool-7.0.3 → chattool-7.0.5}/LICENSE +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/MANIFEST.in +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/scripts/migrate_chattool_envs_to_chatarch.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/setup.cfg +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/_all.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/client/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/client/cert_client.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/client/svg2gif_client.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/aliyun.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/azure.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/base.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/browser.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/crs.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/feishu.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/huggingface.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/liblib.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/pollinations.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/siliconflow.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/skills.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/tencent.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/tongyi.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/tplink.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/config/zulip.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/const.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/docker/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/docker/cli.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/docker/elements.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/docker/main.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/explore/arxiv/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/explore/arxiv/cli.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/explore/arxiv/client.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/explore/arxiv/daily.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/explore/arxiv/harvest.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/explore/arxiv/models.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/explore/arxiv/presets.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/explore/arxiv/query.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/explore/cli.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/explore/wordpress/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/explore/wordpress/client.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/explore/wordpress/models.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/interaction/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/interaction/command_schema.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/interaction/policy.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/interaction/warnings.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/llm/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/llm/chattype.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/llm/response.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/mcp/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/mcp/catalog.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/mcp/cli.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/mcp/server.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/serve/capture.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/serve/cert_server.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/serve/lark_serve.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/serve/local.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/serve/svg2gif.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/assets/hermes/install.sh +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/assets/nvm.sh +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/assets/opencode_chatloop/README.md +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/assets/opencode_chatloop/commands/chatloop-help.md +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/assets/opencode_chatloop/commands/chatloop-project.md +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/assets/opencode_chatloop/commands/chatloop-status.md +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/assets/opencode_chatloop/commands/chatloop-stop.md +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/assets/opencode_chatloop/commands/chatloop.md +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/assets/opencode_chatloop/plugins/chatloop/index.ts +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/assets/opencode_chatloop/plugins/chatloop/package.json +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/cc_connect.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/chrome.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/claude.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/cli.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/codex.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/docker.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/frp.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/hermes.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/lark_cli.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/mode_prompt.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/nodejs.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/opencode.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/opencode_chatloop.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/workspace/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/workspace/core.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/workspace.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/setup/zsh.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/skill/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/skill/cli.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/skill/interaction.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/skill/platforms.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/skill/validation.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/browser/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/browser/base.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/browser/cli.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/browser/playwright_impl.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/browser/selenium.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/browser/test_browser.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/browser/test_module.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/browser/test_remote.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/browser/xhs_qrcode.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/cc/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/cc/cli.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/cert/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/cert/acme_dns_tiny.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/cert/cert_server.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/cert/cert_updater.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/cert/cli.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/cert/email.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/crs/api.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/dns/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/dns/aliyun.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/dns/base.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/dns/cli.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/dns/domain_utils.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/dns/ip_updater.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/dns/mcp.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/dns/tencent.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/dns/utils.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/image/base.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/image/huggingface.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/image/liblib.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/image/pollinations.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/image/siliconflow.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/image/tongyi.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/interact.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/lark/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/lark/bot.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/lark/cli.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/lark/context.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/lark/docx_blocks.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/lark/elements.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/lark/markdown_blocks.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/lark/session.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/network/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/network/cli.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/network/link_check.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/network/mcp.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/network/scanner.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/network/service_urls.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/nginx/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/nginx/cli.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/nginx/templates.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/pypi/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/pypi/cli.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/tplogin.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/tplogin_cli.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/zulip/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/zulip/cli.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/zulip/client.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/zulip/legacy.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/tools/zulip/mcp.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/utils/__init__.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/utils/basic.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/utils/custom_logger.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/utils/fastobj.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/utils/httpclient.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/utils/pathing.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/utils/test_helpers.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool/utils/urltool.py +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool.egg-info/dependency_links.txt +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool.egg-info/entry_points.txt +0 -0
- {chattool-7.0.3 → chattool-7.0.5}/src/chattool.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chattool
|
|
3
|
-
Version: 7.0.
|
|
3
|
+
Version: 7.0.5
|
|
4
4
|
Summary: Toolkit for Chat API
|
|
5
5
|
Author-email: Rex Wang <1073853456@qq.com>
|
|
6
6
|
License: MIT license
|
|
@@ -17,6 +17,7 @@ Requires-Python: >=3.10
|
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: Click>=7.0
|
|
20
|
+
Requires-Dist: chatgh>=0.2.4
|
|
20
21
|
Requires-Dist: chatstyle>=0.1.0
|
|
21
22
|
Requires-Dist: chatenv>=0.1.1
|
|
22
23
|
Requires-Dist: requests>=2.20
|
|
@@ -58,8 +59,6 @@ Requires-Dist: uvicorn; extra == "serve"
|
|
|
58
59
|
Requires-Dist: pydantic>=2.0.0; extra == "serve"
|
|
59
60
|
Provides-Extra: mcp
|
|
60
61
|
Requires-Dist: fastmcp; python_version >= "3.10" and extra == "mcp"
|
|
61
|
-
Provides-Extra: github
|
|
62
|
-
Requires-Dist: PyGithub; extra == "github"
|
|
63
62
|
Provides-Extra: client
|
|
64
63
|
Requires-Dist: gitpython; extra == "client"
|
|
65
64
|
Requires-Dist: rich; extra == "client"
|
|
@@ -67,7 +66,7 @@ Provides-Extra: pypi
|
|
|
67
66
|
Requires-Dist: build; extra == "pypi"
|
|
68
67
|
Requires-Dist: twine; extra == "pypi"
|
|
69
68
|
Provides-Extra: tools
|
|
70
|
-
Requires-Dist: chattool[batch,browser,client,dns,
|
|
69
|
+
Requires-Dist: chattool[batch,browser,client,dns,images,interactive,lark,mcp,screenshot,serve]; extra == "tools"
|
|
71
70
|
Provides-Extra: tests
|
|
72
71
|
Requires-Dist: coverage; extra == "tests"
|
|
73
72
|
Requires-Dist: pytest>=3; extra == "tests"
|
|
@@ -81,6 +80,7 @@ Requires-Dist: mkdocs-static-i18n>=0.53; extra == "docs"
|
|
|
81
80
|
Requires-Dist: mkdocs-awesome-pages-plugin>=2.8.0; extra == "docs"
|
|
82
81
|
Requires-Dist: mkdocs-redirects>=1.2.0; extra == "docs"
|
|
83
82
|
Requires-Dist: mike>=1.1.2; extra == "docs"
|
|
83
|
+
Requires-Dist: pyparsing>=3.0; extra == "docs"
|
|
84
84
|
Provides-Extra: dev
|
|
85
85
|
Requires-Dist: wheel; extra == "dev"
|
|
86
86
|
Requires-Dist: build; extra == "dev"
|
|
@@ -231,10 +231,11 @@ chattool nginx -i # 交互式选择模板并逐项填写
|
|
|
231
231
|
```bash
|
|
232
232
|
chattool image pollinations generate "a cat in space" -o cat.png
|
|
233
233
|
chattool image siliconflow generate "a cute dog" -o dog.png
|
|
234
|
+
chattool image codex generate "a watercolor fox"
|
|
234
235
|
chattool image huggingface generate
|
|
235
236
|
```
|
|
236
237
|
|
|
237
|
-
交互终端里,各家 `generate` 命令缺少 `prompt` 时会自动补问;`huggingface generate`
|
|
238
|
+
交互终端里,各家 `generate` 命令缺少 `prompt` 时会自动补问;`huggingface generate` 与 `codex generate` 未传 `-o` 时会默认写到当前目录下的 `generated/image_<provider>_<model>_<timestamp>.png`。显式传 `-I` 时保持直接报错。
|
|
238
239
|
|
|
239
240
|
### 数据探索 (`chattool explore`)
|
|
240
241
|
|
|
@@ -267,7 +268,7 @@ chattool serve local . --html index.html --dry-run
|
|
|
267
268
|
| PyPI 工具 | `chattool pypi` | 创建、构建、校验、上传与探测 Python 包 |
|
|
268
269
|
| MCP 服务 | `chattool mcp start` | 标准 MCP Server,供 Claude/Cursor 调用 |
|
|
269
270
|
| 环境安装 | `chattool setup zsh/codex/claude/opencode/hermes/lark-cli/docker` | 配置 zsh,安装或检查常用 Agent CLI / WebUI / Docker 环境,并在确认后执行建议的系统命令;`setup hermes` 封装 Hermes 官方 installer,WebUI 需显式启用;`setup opencode/codex/claude/hermes` 支持 `--install-only` 纯安装/升级,`setup opencode` 也支持 `--plugin auto-loop` 追加写入 `opencode-auto-loop` |
|
|
270
|
-
| Workspace | `chattool setup workspace` | 初始化围绕核心项目的人类-AI 协作工作区骨架;当前默认使用 `projects/` 作为实际工作的执行容器,workspace 根目录则保留 general-use 协议与上下文;可选 `--with-
|
|
271
|
+
| Workspace | `chattool setup workspace` | 初始化围绕核心项目的人类-AI 协作工作区骨架;当前默认使用 `projects/` 作为实际工作的执行容器,workspace 根目录则保留 general-use 协议与上下文;可选 `--with-chattool`、`--with-chatblog` 分别挂载 ChatTool 和 ChatBlog |
|
|
271
272
|
| Skills | `chattool skill install` | 安装 ChatTool skills 到 Codex / Claude / OpenCode |
|
|
272
273
|
| CC-Connect | `chattool cc` | cc-connect 快速配置与启动 |
|
|
273
274
|
| CRS 查询 | `chattool crs` | 查询 Claude Relay Service API key 用量、模型统计和只读 admin 信息 |
|
|
@@ -297,7 +298,7 @@ chatpypi mychat
|
|
|
297
298
|
|
|
298
299
|
默认模板生成的 `pyproject.toml` 会写入 `requires-python = ">=3.9"`;`chatarch` 模板依赖 `chatstyle>=0.1.0` 与 `chatenv>=0.1.1`,默认写入 `requires-python = ">=3.10"`。
|
|
299
300
|
|
|
300
|
-
`chatarch` 模板会额外生成 `DEVELOP.md`、`CHANGELOG.md`、`AGENTS.md`、`README.en.md`、`mkdocs.yml`、`docs/`、`tests/cli-tests/`、`tests/mock-cli-tests/`、`tests/code-tests/` 与 `.github/workflows/` 骨架,并默认依赖 `chatstyle>=0.1.0` 与 `chatenv>=0.1.1` 作为 ChatArch CLI/env 运行时;其中发布 workflow
|
|
301
|
+
`chatarch` 模板会额外生成 `DEVELOP.md`、`CHANGELOG.md`、`AGENTS.md`、`README.en.md`、`mkdocs.yml`、`docs/`、`tests/cli-tests/`、`tests/mock-cli-tests/`、`tests/code-tests/` 与 `.github/workflows/` 骨架,并默认依赖 `chatstyle>=0.1.0` 与 `chatenv>=0.1.1` 作为 ChatArch CLI/env 运行时;其中发布 workflow 由显式 `v*` tag 或 `workflow_dispatch` 触发,校验 tag 与包内 `__version__` 一致,并通过 PyPI Trusted Publishing 的 `environment: pypi` 发布尚未存在的版本,不依赖仓库级 PyPI token secret。
|
|
301
302
|
|
|
302
303
|
如只想生成代码与测试骨架,可以用 `--without-mkdocs` 跳过 mkdocs/docs,用 `--without-workflows` 跳过 `.github/workflows/`。
|
|
303
304
|
|
|
@@ -141,10 +141,11 @@ chattool nginx -i # 交互式选择模板并逐项填写
|
|
|
141
141
|
```bash
|
|
142
142
|
chattool image pollinations generate "a cat in space" -o cat.png
|
|
143
143
|
chattool image siliconflow generate "a cute dog" -o dog.png
|
|
144
|
+
chattool image codex generate "a watercolor fox"
|
|
144
145
|
chattool image huggingface generate
|
|
145
146
|
```
|
|
146
147
|
|
|
147
|
-
交互终端里,各家 `generate` 命令缺少 `prompt` 时会自动补问;`huggingface generate`
|
|
148
|
+
交互终端里,各家 `generate` 命令缺少 `prompt` 时会自动补问;`huggingface generate` 与 `codex generate` 未传 `-o` 时会默认写到当前目录下的 `generated/image_<provider>_<model>_<timestamp>.png`。显式传 `-I` 时保持直接报错。
|
|
148
149
|
|
|
149
150
|
### 数据探索 (`chattool explore`)
|
|
150
151
|
|
|
@@ -177,7 +178,7 @@ chattool serve local . --html index.html --dry-run
|
|
|
177
178
|
| PyPI 工具 | `chattool pypi` | 创建、构建、校验、上传与探测 Python 包 |
|
|
178
179
|
| MCP 服务 | `chattool mcp start` | 标准 MCP Server,供 Claude/Cursor 调用 |
|
|
179
180
|
| 环境安装 | `chattool setup zsh/codex/claude/opencode/hermes/lark-cli/docker` | 配置 zsh,安装或检查常用 Agent CLI / WebUI / Docker 环境,并在确认后执行建议的系统命令;`setup hermes` 封装 Hermes 官方 installer,WebUI 需显式启用;`setup opencode/codex/claude/hermes` 支持 `--install-only` 纯安装/升级,`setup opencode` 也支持 `--plugin auto-loop` 追加写入 `opencode-auto-loop` |
|
|
180
|
-
| Workspace | `chattool setup workspace` | 初始化围绕核心项目的人类-AI 协作工作区骨架;当前默认使用 `projects/` 作为实际工作的执行容器,workspace 根目录则保留 general-use 协议与上下文;可选 `--with-
|
|
181
|
+
| Workspace | `chattool setup workspace` | 初始化围绕核心项目的人类-AI 协作工作区骨架;当前默认使用 `projects/` 作为实际工作的执行容器,workspace 根目录则保留 general-use 协议与上下文;可选 `--with-chattool`、`--with-chatblog` 分别挂载 ChatTool 和 ChatBlog |
|
|
181
182
|
| Skills | `chattool skill install` | 安装 ChatTool skills 到 Codex / Claude / OpenCode |
|
|
182
183
|
| CC-Connect | `chattool cc` | cc-connect 快速配置与启动 |
|
|
183
184
|
| CRS 查询 | `chattool crs` | 查询 Claude Relay Service API key 用量、模型统计和只读 admin 信息 |
|
|
@@ -207,7 +208,7 @@ chatpypi mychat
|
|
|
207
208
|
|
|
208
209
|
默认模板生成的 `pyproject.toml` 会写入 `requires-python = ">=3.9"`;`chatarch` 模板依赖 `chatstyle>=0.1.0` 与 `chatenv>=0.1.1`,默认写入 `requires-python = ">=3.10"`。
|
|
209
210
|
|
|
210
|
-
`chatarch` 模板会额外生成 `DEVELOP.md`、`CHANGELOG.md`、`AGENTS.md`、`README.en.md`、`mkdocs.yml`、`docs/`、`tests/cli-tests/`、`tests/mock-cli-tests/`、`tests/code-tests/` 与 `.github/workflows/` 骨架,并默认依赖 `chatstyle>=0.1.0` 与 `chatenv>=0.1.1` 作为 ChatArch CLI/env 运行时;其中发布 workflow
|
|
211
|
+
`chatarch` 模板会额外生成 `DEVELOP.md`、`CHANGELOG.md`、`AGENTS.md`、`README.en.md`、`mkdocs.yml`、`docs/`、`tests/cli-tests/`、`tests/mock-cli-tests/`、`tests/code-tests/` 与 `.github/workflows/` 骨架,并默认依赖 `chatstyle>=0.1.0` 与 `chatenv>=0.1.1` 作为 ChatArch CLI/env 运行时;其中发布 workflow 由显式 `v*` tag 或 `workflow_dispatch` 触发,校验 tag 与包内 `__version__` 一致,并通过 PyPI Trusted Publishing 的 `environment: pypi` 发布尚未存在的版本,不依赖仓库级 PyPI token secret。
|
|
211
212
|
|
|
212
213
|
如只想生成代码与测试骨架,可以用 `--without-mkdocs` 跳过 mkdocs/docs,用 `--without-workflows` 跳过 `.github/workflows/`。
|
|
213
214
|
|
|
@@ -23,6 +23,7 @@ keywords = ["chattool"]
|
|
|
23
23
|
license = {text = "MIT license"}
|
|
24
24
|
dependencies = [
|
|
25
25
|
"Click>=7.0",
|
|
26
|
+
"chatgh>=0.2.4",
|
|
26
27
|
"chatstyle>=0.1.0",
|
|
27
28
|
"chatenv>=0.1.1",
|
|
28
29
|
"requests>=2.20",
|
|
@@ -76,9 +77,6 @@ serve = [
|
|
|
76
77
|
mcp = [
|
|
77
78
|
"fastmcp; python_version >= '3.10'",
|
|
78
79
|
]
|
|
79
|
-
github = [
|
|
80
|
-
"PyGithub",
|
|
81
|
-
]
|
|
82
80
|
client = [
|
|
83
81
|
"gitpython",
|
|
84
82
|
"rich",
|
|
@@ -88,7 +86,7 @@ pypi = [
|
|
|
88
86
|
"twine",
|
|
89
87
|
]
|
|
90
88
|
tools = [
|
|
91
|
-
"chattool[batch,browser,client,dns,
|
|
89
|
+
"chattool[batch,browser,client,dns,images,interactive,lark,mcp,serve,screenshot]",
|
|
92
90
|
]
|
|
93
91
|
tests = [
|
|
94
92
|
"coverage",
|
|
@@ -104,6 +102,7 @@ docs = [
|
|
|
104
102
|
"mkdocs-awesome-pages-plugin>=2.8.0",
|
|
105
103
|
"mkdocs-redirects>=1.2.0",
|
|
106
104
|
"mike>=1.1.2",
|
|
105
|
+
"pyparsing>=3.0",
|
|
107
106
|
]
|
|
108
107
|
dev = [
|
|
109
108
|
"wheel",
|
|
@@ -98,7 +98,6 @@ cli._lazy_commands.update(
|
|
|
98
98
|
"lark": lambda: _load_attr("chattool.tools.lark.cli", "cli"),
|
|
99
99
|
"image": lambda: _load_attr("chattool.tools.image.cli", "cli"),
|
|
100
100
|
"tplogin": lambda: _load_attr("chattool.tools.tplogin_cli", "cli"),
|
|
101
|
-
"gh": lambda: _load_attr("chattool.tools.github.cli", "cli"),
|
|
102
101
|
"browser": lambda: _load_attr("chattool.tools.browser.cli", "cli"),
|
|
103
102
|
"zulip": lambda: _load_attr("chattool.tools.zulip.cli", "cli"),
|
|
104
103
|
"skill": lambda: _load_attr("chattool.skill.cli", "skill_cli"),
|
|
@@ -14,7 +14,6 @@ from .liblib import LiblibConfig
|
|
|
14
14
|
from .siliconflow import SiliconFlowConfig
|
|
15
15
|
from .tplink import TPLinkConfig
|
|
16
16
|
from .browser import BrowserConfig
|
|
17
|
-
from .github import GitHubConfig
|
|
18
17
|
|
|
19
18
|
__all__ = [
|
|
20
19
|
"EnvField",
|
|
@@ -32,7 +31,6 @@ __all__ = [
|
|
|
32
31
|
"PollinationsConfig",
|
|
33
32
|
"LiblibConfig",
|
|
34
33
|
"SiliconFlowConfig",
|
|
35
|
-
"GitHubConfig",
|
|
36
34
|
"BrowserConfig",
|
|
37
35
|
"TPLinkConfig",
|
|
38
36
|
"SkillsConfig",
|
|
@@ -16,7 +16,6 @@ from .liblib import LiblibConfig
|
|
|
16
16
|
from .siliconflow import SiliconFlowConfig
|
|
17
17
|
from .tplink import TPLinkConfig
|
|
18
18
|
from .browser import BrowserConfig
|
|
19
|
-
from .github import GitHubConfig
|
|
20
19
|
|
|
21
20
|
__all__ = [
|
|
22
21
|
"EnvField",
|
|
@@ -34,7 +33,6 @@ __all__ = [
|
|
|
34
33
|
"PollinationsConfig",
|
|
35
34
|
"LiblibConfig",
|
|
36
35
|
"SiliconFlowConfig",
|
|
37
|
-
"GitHubConfig",
|
|
38
36
|
"BrowserConfig",
|
|
39
37
|
"TPLinkConfig",
|
|
40
38
|
"SkillsConfig",
|
|
@@ -11,7 +11,31 @@ class OpenAIConfig(BaseEnvConfig):
|
|
|
11
11
|
|
|
12
12
|
OPENAI_API_BASE = EnvField("OPENAI_API_BASE", desc="The base url of the API (with suffix /v1).")
|
|
13
13
|
OPENAI_API_KEY = EnvField("OPENAI_API_KEY", desc="Your API key", is_sensitive=True)
|
|
14
|
-
OPENAI_API_MODEL = EnvField("OPENAI_API_MODEL", default="gpt-
|
|
14
|
+
OPENAI_API_MODEL = EnvField("OPENAI_API_MODEL", default="gpt-5.5", desc="The default model name")
|
|
15
|
+
OPENAI_ACCESS_TOKEN = EnvField(
|
|
16
|
+
"OPENAI_ACCESS_TOKEN",
|
|
17
|
+
desc="OpenAI OAuth access token for OAuth-backed capabilities.",
|
|
18
|
+
is_sensitive=True,
|
|
19
|
+
)
|
|
20
|
+
OPENAI_REFRESH_TOKEN = EnvField(
|
|
21
|
+
"OPENAI_REFRESH_TOKEN",
|
|
22
|
+
desc="OpenAI OAuth refresh token for OAuth-backed capabilities.",
|
|
23
|
+
is_sensitive=True,
|
|
24
|
+
)
|
|
25
|
+
OPENAI_OAUTH_BASE_URL = EnvField(
|
|
26
|
+
"OPENAI_OAUTH_BASE_URL",
|
|
27
|
+
default="https://auth.openai.com",
|
|
28
|
+
desc="OpenAI OAuth auth server base URL used to refresh access tokens.",
|
|
29
|
+
)
|
|
30
|
+
OPENAI_ACCESS_TOKEN_EXPIRES_AT = EnvField(
|
|
31
|
+
"OPENAI_ACCESS_TOKEN_EXPIRES_AT",
|
|
32
|
+
desc="UTC ISO timestamp when the OpenAI OAuth access token expires.",
|
|
33
|
+
)
|
|
34
|
+
OPENAI_IMAGE_MODEL = EnvField(
|
|
35
|
+
"OPENAI_IMAGE_MODEL",
|
|
36
|
+
default="gpt-image-2-medium",
|
|
37
|
+
desc="Default OpenAI image model preset.",
|
|
38
|
+
)
|
|
15
39
|
|
|
16
40
|
@classmethod
|
|
17
41
|
def _parse_responses_stream_event(cls, line: str):
|
|
@@ -7,6 +7,8 @@ _LAZY_ATTRS = {
|
|
|
7
7
|
"cert_app": ("chattool.serve.cert_server", "cert_app"),
|
|
8
8
|
"serve_cli": ("chattool.serve.cli", "serve_cli"),
|
|
9
9
|
"lark_serve_cli": ("chattool.serve.lark_serve", "cli"),
|
|
10
|
+
"oauth_app": ("chattool.serve.oauth", "app"),
|
|
11
|
+
"oauth_serve_cli": ("chattool.serve.oauth", "oauth"),
|
|
10
12
|
"svg2gif_app": ("chattool.serve.svg2gif", "svg2gif_app"),
|
|
11
13
|
}
|
|
12
14
|
|
|
@@ -26,5 +28,7 @@ __all__ = [
|
|
|
26
28
|
"cert_app",
|
|
27
29
|
"serve_cli",
|
|
28
30
|
"lark_serve_cli",
|
|
31
|
+
"oauth_app",
|
|
32
|
+
"oauth_serve_cli",
|
|
29
33
|
"svg2gif_app",
|
|
30
34
|
]
|
|
@@ -33,6 +33,12 @@ def _load_local():
|
|
|
33
33
|
return command
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
def _load_oauth():
|
|
37
|
+
command = _load_attr("chattool.serve.oauth", "oauth")
|
|
38
|
+
command.short_help = "Run the local OAuth token service."
|
|
39
|
+
return command
|
|
40
|
+
|
|
41
|
+
|
|
36
42
|
def _load_svg2gif():
|
|
37
43
|
command = _load_attr("chattool.serve.svg2gif", "svg2gif_app")
|
|
38
44
|
command.short_help = "Run the SVG-to-GIF conversion service."
|
|
@@ -51,6 +57,7 @@ serve_cli._lazy_commands.update(
|
|
|
51
57
|
"cert": _load_cert,
|
|
52
58
|
"lark": _load_lark,
|
|
53
59
|
"local": _load_local,
|
|
60
|
+
"oauth": _load_oauth,
|
|
54
61
|
"svg2gif": _load_svg2gif,
|
|
55
62
|
}
|
|
56
63
|
)
|
|
@@ -60,6 +67,7 @@ serve_cli._lazy_command_help.update(
|
|
|
60
67
|
"cert": "Run the certificate service.",
|
|
61
68
|
"lark": "Run the Lark webhook service.",
|
|
62
69
|
"local": "Serve a local directory or HTML file.",
|
|
70
|
+
"oauth": "Run the local OAuth token service.",
|
|
63
71
|
"svg2gif": "Run the SVG-to-GIF conversion service.",
|
|
64
72
|
}
|
|
65
73
|
)
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import secrets
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
from fastapi import Depends, FastAPI, Header, HTTPException
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
|
|
13
|
+
from chattool.config import BaseEnvConfig, OpenAIConfig # type: ignore[attr-defined]
|
|
14
|
+
from chattool.const import CHATARCH_ENV_DIR
|
|
15
|
+
from chattool.tools.crs.openai_oauth import (
|
|
16
|
+
build_openai_oauth_refresh_result,
|
|
17
|
+
build_openai_oauth_status,
|
|
18
|
+
refresh_openai_oauth_token,
|
|
19
|
+
save_openai_oauth_token_data,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
config: dict[str, Any] = {
|
|
23
|
+
"token": None,
|
|
24
|
+
"env_dir": CHATARCH_ENV_DIR,
|
|
25
|
+
"oauth_base_url": None,
|
|
26
|
+
"timeout_seconds": 20.0,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
app = FastAPI(
|
|
30
|
+
title="ChatTool OAuth Token Service",
|
|
31
|
+
description="Local auth service for OAuth token status, refresh, and access-token retrieval.",
|
|
32
|
+
version="0.1.0",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class OAuthConfigRequest(BaseModel):
|
|
37
|
+
access_token: str | None = Field(default=None)
|
|
38
|
+
refresh_token: str | None = Field(default=None)
|
|
39
|
+
oauth_base_url: str | None = Field(default=None)
|
|
40
|
+
access_token_expires_at: str | None = Field(default=None)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class OAuthRefreshRequest(BaseModel):
|
|
44
|
+
refresh_token: str | None = Field(default=None)
|
|
45
|
+
oauth_base_url: str | None = Field(default=None)
|
|
46
|
+
timeout_seconds: float | None = Field(default=None)
|
|
47
|
+
save: bool = Field(default=True)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _env_dir() -> Path:
|
|
51
|
+
return Path(config.get("env_dir") or CHATARCH_ENV_DIR)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _active_openai_env_file() -> Path:
|
|
55
|
+
return OpenAIConfig.get_active_env_file(_env_dir())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _load_openai_env() -> None:
|
|
59
|
+
if _active_openai_env_file().exists():
|
|
60
|
+
BaseEnvConfig.load_all(_env_dir())
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _configured_oauth_base_url(override: str | None = None) -> str:
|
|
64
|
+
value = (
|
|
65
|
+
override
|
|
66
|
+
or config.get("oauth_base_url")
|
|
67
|
+
or OpenAIConfig.OPENAI_OAUTH_BASE_URL.value
|
|
68
|
+
or "https://auth.openai.com"
|
|
69
|
+
)
|
|
70
|
+
return str(value).strip().rstrip("/")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _service_token() -> str | None:
|
|
74
|
+
value = config.get("token") or os.getenv("CHATTOOL_OAUTH_SERVICE_TOKEN")
|
|
75
|
+
return str(value) if value else None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
async def require_service_token(
|
|
79
|
+
x_chattool_token: str | None = Header(default=None),
|
|
80
|
+
authorization: str | None = Header(default=None),
|
|
81
|
+
) -> str:
|
|
82
|
+
expected = _service_token()
|
|
83
|
+
if not expected:
|
|
84
|
+
raise HTTPException(status_code=403, detail="Service token is not configured")
|
|
85
|
+
provided = x_chattool_token
|
|
86
|
+
if not provided and authorization:
|
|
87
|
+
prefix = "Bearer "
|
|
88
|
+
if authorization.startswith(prefix):
|
|
89
|
+
provided = authorization[len(prefix):]
|
|
90
|
+
if not provided or not secrets.compare_digest(str(provided), expected):
|
|
91
|
+
raise HTTPException(status_code=403, detail="Invalid or missing service token")
|
|
92
|
+
return str(provided)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _parse_iso(value: str | None) -> datetime | None:
|
|
96
|
+
if not value:
|
|
97
|
+
return None
|
|
98
|
+
text = value.strip()
|
|
99
|
+
if not text:
|
|
100
|
+
return None
|
|
101
|
+
if text.endswith("Z"):
|
|
102
|
+
text = text[:-1] + "+00:00"
|
|
103
|
+
try:
|
|
104
|
+
parsed = datetime.fromisoformat(text)
|
|
105
|
+
except ValueError:
|
|
106
|
+
return None
|
|
107
|
+
if parsed.tzinfo is None:
|
|
108
|
+
parsed = parsed.replace(tzinfo=timezone.utc)
|
|
109
|
+
return parsed.astimezone(timezone.utc)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _is_expired_or_missing(expires_at: str | None) -> bool:
|
|
113
|
+
parsed = _parse_iso(expires_at)
|
|
114
|
+
if parsed is None:
|
|
115
|
+
return True
|
|
116
|
+
return parsed <= datetime.now(timezone.utc)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _save_current_openai_values(target_path: Path) -> None:
|
|
120
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
121
|
+
target_path.write_text(OpenAIConfig.render_env_file(), encoding="utf-8")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@app.get("/health")
|
|
125
|
+
def health() -> dict[str, str]:
|
|
126
|
+
return {"status": "ok"}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@app.get("/oauth/status")
|
|
130
|
+
def oauth_status(_: str = Depends(require_service_token)) -> dict[str, str]:
|
|
131
|
+
_load_openai_env()
|
|
132
|
+
return build_openai_oauth_status(env_file=_active_openai_env_file())
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@app.post("/oauth/config")
|
|
136
|
+
def oauth_config(
|
|
137
|
+
request: OAuthConfigRequest,
|
|
138
|
+
_: str = Depends(require_service_token),
|
|
139
|
+
) -> dict[str, str]:
|
|
140
|
+
_load_openai_env()
|
|
141
|
+
if request.access_token is not None:
|
|
142
|
+
OpenAIConfig.OPENAI_ACCESS_TOKEN.value = request.access_token
|
|
143
|
+
if request.refresh_token is not None:
|
|
144
|
+
OpenAIConfig.OPENAI_REFRESH_TOKEN.value = request.refresh_token
|
|
145
|
+
if request.oauth_base_url is not None:
|
|
146
|
+
OpenAIConfig.OPENAI_OAUTH_BASE_URL.value = request.oauth_base_url.strip().rstrip("/")
|
|
147
|
+
elif config.get("oauth_base_url"):
|
|
148
|
+
OpenAIConfig.OPENAI_OAUTH_BASE_URL.value = _configured_oauth_base_url()
|
|
149
|
+
if request.access_token_expires_at is not None:
|
|
150
|
+
OpenAIConfig.OPENAI_ACCESS_TOKEN_EXPIRES_AT.value = request.access_token_expires_at
|
|
151
|
+
target_path = _active_openai_env_file()
|
|
152
|
+
_save_current_openai_values(target_path)
|
|
153
|
+
return build_openai_oauth_status(env_file=target_path)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@app.post("/oauth/refresh")
|
|
157
|
+
def oauth_refresh(
|
|
158
|
+
request: OAuthRefreshRequest | None = None,
|
|
159
|
+
_: str = Depends(require_service_token),
|
|
160
|
+
) -> dict[str, Any]:
|
|
161
|
+
_load_openai_env()
|
|
162
|
+
request = request or OAuthRefreshRequest()
|
|
163
|
+
refresh_token = (request.refresh_token or OpenAIConfig.OPENAI_REFRESH_TOKEN.value or "").strip()
|
|
164
|
+
if not refresh_token:
|
|
165
|
+
raise HTTPException(status_code=400, detail="OPENAI_REFRESH_TOKEN is required")
|
|
166
|
+
oauth_base_url = _configured_oauth_base_url(request.oauth_base_url)
|
|
167
|
+
timeout_seconds = request.timeout_seconds or float(config.get("timeout_seconds") or 20.0)
|
|
168
|
+
try:
|
|
169
|
+
token_data = refresh_openai_oauth_token(
|
|
170
|
+
refresh_token,
|
|
171
|
+
base_url=oauth_base_url,
|
|
172
|
+
timeout_seconds=timeout_seconds,
|
|
173
|
+
)
|
|
174
|
+
except (RuntimeError, ValueError) as exc:
|
|
175
|
+
raise HTTPException(status_code=502, detail=str(exc)) from exc
|
|
176
|
+
target_path = _active_openai_env_file()
|
|
177
|
+
if request.save:
|
|
178
|
+
save_openai_oauth_token_data(
|
|
179
|
+
token_data=token_data,
|
|
180
|
+
oauth_base_url=oauth_base_url,
|
|
181
|
+
target_path=target_path,
|
|
182
|
+
)
|
|
183
|
+
return build_openai_oauth_refresh_result(
|
|
184
|
+
token_data=token_data,
|
|
185
|
+
oauth_base_url=oauth_base_url,
|
|
186
|
+
saved=request.save,
|
|
187
|
+
env_file=target_path if request.save else None,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@app.get("/oauth/access-token")
|
|
192
|
+
def oauth_access_token(_: str = Depends(require_service_token)) -> dict[str, Any]:
|
|
193
|
+
_load_openai_env()
|
|
194
|
+
access_token = (OpenAIConfig.OPENAI_ACCESS_TOKEN.value or "").strip()
|
|
195
|
+
expires_at = OpenAIConfig.OPENAI_ACCESS_TOKEN_EXPIRES_AT.value
|
|
196
|
+
refreshed = False
|
|
197
|
+
if not access_token or _is_expired_or_missing(expires_at):
|
|
198
|
+
refresh_token = (OpenAIConfig.OPENAI_REFRESH_TOKEN.value or "").strip()
|
|
199
|
+
if not refresh_token:
|
|
200
|
+
raise HTTPException(status_code=400, detail="No valid access token and no refresh token configured")
|
|
201
|
+
oauth_base_url = _configured_oauth_base_url()
|
|
202
|
+
try:
|
|
203
|
+
token_data = refresh_openai_oauth_token(
|
|
204
|
+
refresh_token,
|
|
205
|
+
base_url=oauth_base_url,
|
|
206
|
+
timeout_seconds=float(config.get("timeout_seconds") or 20.0),
|
|
207
|
+
)
|
|
208
|
+
except (RuntimeError, ValueError) as exc:
|
|
209
|
+
raise HTTPException(status_code=502, detail=str(exc)) from exc
|
|
210
|
+
save_openai_oauth_token_data(
|
|
211
|
+
token_data=token_data,
|
|
212
|
+
oauth_base_url=oauth_base_url,
|
|
213
|
+
target_path=_active_openai_env_file(),
|
|
214
|
+
)
|
|
215
|
+
access_token = str(token_data.get("access_token") or "")
|
|
216
|
+
expires_at = str(token_data.get("access_token_expires_at") or "")
|
|
217
|
+
refreshed = True
|
|
218
|
+
return {
|
|
219
|
+
"access_token": access_token,
|
|
220
|
+
"access_token_expires_at": expires_at,
|
|
221
|
+
"refreshed": refreshed,
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@click.command(name="oauth")
|
|
226
|
+
@click.option("--host", default="127.0.0.1", show_default=True, help="Bind host.")
|
|
227
|
+
@click.option("--port", default=8787, show_default=True, type=int, help="Bind port.")
|
|
228
|
+
@click.option("--token", default=None, help="Service token required by protected endpoints. Defaults to CHATTOOL_OAUTH_SERVICE_TOKEN.")
|
|
229
|
+
@click.option("--env-dir", default=None, help="ChatArch env directory. Defaults to the active ChatTool env dir.")
|
|
230
|
+
@click.option("--oauth-base-url", default=None, help="Default OAuth auth server base URL.")
|
|
231
|
+
@click.option("--timeout", "timeout_seconds", default=20.0, type=float, show_default=True)
|
|
232
|
+
@click.option("--dry-run", is_flag=True, help="Print resolved server settings without starting uvicorn.")
|
|
233
|
+
def oauth(host: str, port: int, token: str | None, env_dir: str | None, oauth_base_url: str | None, timeout_seconds: float, dry_run: bool) -> None:
|
|
234
|
+
"""Run the local OAuth token service."""
|
|
235
|
+
config["token"] = token
|
|
236
|
+
config["env_dir"] = Path(env_dir).expanduser() if env_dir else CHATARCH_ENV_DIR
|
|
237
|
+
config["oauth_base_url"] = oauth_base_url
|
|
238
|
+
config["timeout_seconds"] = timeout_seconds
|
|
239
|
+
click.echo(f"OAuth service: http://{host}:{port}")
|
|
240
|
+
click.echo(f"Env dir: {config['env_dir']}")
|
|
241
|
+
click.echo(f"OAuth base: {_configured_oauth_base_url()}")
|
|
242
|
+
click.echo(f"Service token: {'configured' if _service_token() else 'missing'}")
|
|
243
|
+
if dry_run:
|
|
244
|
+
return
|
|
245
|
+
if not _service_token():
|
|
246
|
+
raise click.ClickException("--token or CHATTOOL_OAUTH_SERVICE_TOKEN is required unless --dry-run is used.")
|
|
247
|
+
import uvicorn
|
|
248
|
+
|
|
249
|
+
uvicorn.run(app, host=host, port=port)
|
|
@@ -194,6 +194,10 @@ def workspace_setup(
|
|
|
194
194
|
dry_run,
|
|
195
195
|
with_chattool,
|
|
196
196
|
chattool_source,
|
|
197
|
+
with_chatblog,
|
|
198
|
+
chatblog_source,
|
|
199
|
+
with_memory,
|
|
200
|
+
memory_source,
|
|
197
201
|
):
|
|
198
202
|
setup_workspace(
|
|
199
203
|
profile_name=profile,
|
|
@@ -204,6 +208,10 @@ def workspace_setup(
|
|
|
204
208
|
dry_run=dry_run,
|
|
205
209
|
with_chattool=with_chattool,
|
|
206
210
|
chattool_source=chattool_source,
|
|
211
|
+
with_chatblog=with_chatblog,
|
|
212
|
+
chatblog_source=chatblog_source,
|
|
213
|
+
with_memory=with_memory,
|
|
214
|
+
memory_source=memory_source,
|
|
207
215
|
)
|
|
208
216
|
|
|
209
217
|
|
|
@@ -678,7 +686,7 @@ SETUP_COMMAND_ELEMENTS = (
|
|
|
678
686
|
param_decls=("--with-chattool/--no-chattool",),
|
|
679
687
|
kwargs={
|
|
680
688
|
"default": False,
|
|
681
|
-
"help": "Optionally clone/update ChatTool
|
|
689
|
+
"help": "Optionally clone/update ChatTool into core/ChatTool and sync its skills into workspace skills/.",
|
|
682
690
|
},
|
|
683
691
|
),
|
|
684
692
|
SetupOptionElement(
|
|
@@ -688,6 +696,34 @@ SETUP_COMMAND_ELEMENTS = (
|
|
|
688
696
|
"help": "Git URL or local ChatTool repo path used when --with-chattool is enabled.",
|
|
689
697
|
},
|
|
690
698
|
),
|
|
699
|
+
SetupOptionElement(
|
|
700
|
+
param_decls=("--with-chatblog/--no-chatblog",),
|
|
701
|
+
kwargs={
|
|
702
|
+
"default": False,
|
|
703
|
+
"help": "Optionally clone/update ChatBlog and link its posts into public/chatblog.",
|
|
704
|
+
},
|
|
705
|
+
),
|
|
706
|
+
SetupOptionElement(
|
|
707
|
+
param_decls=("--chatblog-source",),
|
|
708
|
+
kwargs={
|
|
709
|
+
"default": None,
|
|
710
|
+
"help": "Git URL or local ChatBlog repo path used when --with-chatblog is enabled.",
|
|
711
|
+
},
|
|
712
|
+
),
|
|
713
|
+
SetupOptionElement(
|
|
714
|
+
param_decls=("--with-memory/--no-memory",),
|
|
715
|
+
kwargs={
|
|
716
|
+
"default": False,
|
|
717
|
+
"help": "Optionally clone/update ChatMemory, link shared groups chatarch/common/agents, and create local skills.",
|
|
718
|
+
},
|
|
719
|
+
),
|
|
720
|
+
SetupOptionElement(
|
|
721
|
+
param_decls=("--memory-source",),
|
|
722
|
+
kwargs={
|
|
723
|
+
"default": None,
|
|
724
|
+
"help": "Git URL or local ChatMemory repo path used when --with-memory is enabled.",
|
|
725
|
+
},
|
|
726
|
+
),
|
|
691
727
|
),
|
|
692
728
|
),
|
|
693
729
|
)
|