From f6dd426830bf75ed6a1aa0fa515c6dd3ccf1a827 Mon Sep 17 00:00:00 2001 From: CamelAIorg Date: Tue, 29 Jul 2025 23:57:16 +0800 Subject: [PATCH 001/138] initial commit --- .github/ISSUE_TEMPLATE/bug_report.md | 11 + .github/ISSUE_TEMPLATE/help_wanted.md | 10 + .github/PULL_REQUEST_TEMPLATE.md | 12 + .github/dependabot.yml | 11 + .github/workflows/build.yml | 64 + .github/workflows/ci.yml | 81 + .github/workflows/remove-old-artifacts.yml | 27 + .gitignore | 42 + .npmrc | 6 + .vscode/.debug.script.mjs | 23 + .vscode/extensions.json | 7 + .vscode/launch.json | 54 + .vscode/settings.json | 16 + .vscode/tasks.json | 31 + CONTRIBUTING.md | 200 + LICENSE | 46 + README.md | 402 ++ README_CN.md | 350 ++ backend/.gitignore | 21 + backend/.python-version | 1 + backend/.vscode/settings.json | 10 + backend/README.md | 23 + backend/app/__init__.py | 6 + backend/app/command/__init__.py | 5 + backend/app/component/babel.py | 10 + backend/app/component/code.py | 16 + backend/app/component/command.py | 9 + backend/app/component/debug.py | 15 + backend/app/component/encrypt.py | 11 + backend/app/component/environment.py | 98 + backend/app/component/model_validation.py | 41 + backend/app/component/pydantic/i18n.py | 58 + .../pydantic/translations/en_US.json | 98 + .../pydantic/translations/zh_CN.json | 98 + backend/app/controller/__init__.py | 0 backend/app/controller/chat_controller.py | 83 + backend/app/controller/model_controller.py | 43 + backend/app/controller/task_controller.py | 52 + backend/app/controller/tool_controller.py | 18 + backend/app/exception/exception.py | 20 + backend/app/exception/handler.py | 47 + backend/app/middleware/__init__.py | 6 + backend/app/model/chat.py | 110 + backend/app/service/chat_service.py | 480 ++ backend/app/service/task.py | 360 ++ backend/app/utils/agent.py | 1327 ++++++ backend/app/utils/listen/toolkit_listen.py | 177 + backend/app/utils/server/sync_step.py | 48 + backend/app/utils/single_agent_worker.py | 197 + backend/app/utils/toolkit/abstract_toolkit.py | 16 + .../utils/toolkit/audio_analysis_toolkit.py | 36 + .../utils/toolkit/code_execution_toolkit.py | 39 + backend/app/utils/toolkit/craw4ai_toolkit.py | 29 + backend/app/utils/toolkit/excel_toolkit.py | 26 + .../app/utils/toolkit/file_write_toolkit.py | 56 + backend/app/utils/toolkit/github_toolkit.py | 107 + .../utils/toolkit/google_calendar_toolkit.py | 59 + .../utils/toolkit/google_drive_mcp_toolkit.py | 45 + .../utils/toolkit/google_gmail_mcp_toolkit.py | 53 + backend/app/utils/toolkit/human_toolkit.py | 138 + .../toolkit/hybrid_browser_python_toolkit.py | 404 ++ .../utils/toolkit/hybrid_browser_toolkit.py | 260 ++ .../utils/toolkit/image_analysis_toolkit.py | 40 + backend/app/utils/toolkit/linkedin_toolkit.py | 42 + .../app/utils/toolkit/markitdown_toolkit.py | 18 + .../app/utils/toolkit/mcp_search_toolkit.py | 59 + .../app/utils/toolkit/note_taking_toolkit.py | 41 + .../app/utils/toolkit/notion_mcp_toolkit.py | 46 + backend/app/utils/toolkit/notion_toolkit.py | 50 + .../app/utils/toolkit/openai_image_toolkit.py | 50 + backend/app/utils/toolkit/pptx_toolkit.py | 44 + .../app/utils/toolkit/pyautogui_toolkit.py | 89 + backend/app/utils/toolkit/reddit_toolkit.py | 69 + .../app/utils/toolkit/screenshot_toolkit.py | 27 + backend/app/utils/toolkit/search_toolkit.py | 301 ++ backend/app/utils/toolkit/slack_toolkit.py | 77 + backend/app/utils/toolkit/terminal_toolkit.py | 99 + backend/app/utils/toolkit/thinking_toolkit.py | 40 + backend/app/utils/toolkit/twitter_toolkit.py | 75 + .../utils/toolkit/video_analysis_toolkit.py | 45 + .../utils/toolkit/video_download_toolkit.py | 45 + .../app/utils/toolkit/web_deploy_toolkit.py | 53 + backend/app/utils/toolkit/whatsapp_toolkit.py | 46 + backend/app/utils/workforce.py | 271 ++ backend/babel.cfg | 1 + backend/cli.py | 9 + backend/lang/en_US/LC_MESSAGES/messages.po | 34 + backend/lang/zh_CN/LC_MESSAGES/messages.po | 36 + backend/main.py | 85 + backend/messages.pot | 35 + backend/pyproject.toml | 49 + backend/uv.lock | 3917 +++++++++++++++++ build/icon.icns | Bin 0 -> 184841 bytes build/icon.ico | Bin 0 -> 104479 bytes build/icon.png | Bin 0 -> 8488 bytes build/installer.nsh | 4 + components.json | 21 + config/notarize.cjs | 27 + docs/concepts.md | 173 + docs/docs.json | 85 + docs/features/add_mcps.md | 12 + docs/features/customised_workers.md | 13 + docs/features/dynamic_workforce.md | 12 + docs/features/human-in-the-loop.md | 13 + docs/features/local_models.md | 13 + docs/get_started/installation.md | 49 + docs/get_started/quick_start.md | 204 + docs/get_started/welcome.md | 52 + docs/support.md | 7 + electron-builder.json | 78 + electron/electron-env.d.ts | 24 + electron/electron.d.ts | 1 + electron/main/copy.ts | 26 + electron/main/fileReader.ts | 204 + electron/main/index.ts | 1017 +++++ electron/main/init.ts | 258 ++ electron/main/update.ts | 110 + electron/main/utils/envUtil.ts | 73 + electron/main/utils/mcpConfig.ts | 79 + electron/main/utils/process.ts | 74 + electron/main/webview.ts | 249 ++ electron/preload/index.ts | 183 + entitlements.mac.plist | 45 + index.html | 18 + package.json | 106 + package/@stackframe/react/CHANGELOG.md | 1640 +++++++ package/@stackframe/react/LICENSE | 7 + package/@stackframe/react/README.md | 26 + .../dist/components-page/account-settings.js | 175 + .../components-page/account-settings.js.map | 1 + .../active-sessions/active-sessions-page.js | 151 + .../active-sessions-page.js.map | 1 + .../api-keys/api-keys-page.js | 70 + .../api-keys/api-keys-page.js.map | 1 + .../account-settings/editable-text.js | 75 + .../account-settings/editable-text.js.map | 1 + .../email-and-auth/email-and-auth-page.js | 46 + .../email-and-auth/email-and-auth-page.js.map | 1 + .../email-and-auth/emails-section.js | 188 + .../email-and-auth/emails-section.js.map | 1 + .../email-and-auth/mfa-section.js | 146 + .../email-and-auth/mfa-section.js.map | 1 + .../email-and-auth/otp-section.js | 89 + .../email-and-auth/otp-section.js.map | 1 + .../email-and-auth/passkey-section.js | 92 + .../email-and-auth/passkey-section.js.map | 1 + .../email-and-auth/password-section.js | 181 + .../email-and-auth/password-section.js.map | 1 + .../account-settings/page-layout.js | 34 + .../account-settings/page-layout.js.map | 1 + .../profile-page/profile-page.js | 75 + .../profile-page/profile-page.js.map | 1 + .../account-settings/section.js | 44 + .../account-settings/section.js.map | 1 + .../settings/delete-account-section.js | 87 + .../settings/delete-account-section.js.map | 1 + .../settings/settings-page.js | 40 + .../settings/settings-page.js.map | 1 + .../settings/sign-out-section.js | 54 + .../settings/sign-out-section.js.map | 1 + .../teams/leave-team-section.js | 79 + .../teams/leave-team-section.js.map | 1 + .../teams/team-api-keys-section.js | 92 + .../teams/team-api-keys-section.js.map | 1 + .../teams/team-creation-page.js | 92 + .../teams/team-creation-page.js.map | 1 + .../teams/team-display-name-section.js | 57 + .../teams/team-display-name-section.js.map | 1 + .../teams/team-member-invitation-section.js | 131 + .../team-member-invitation-section.js.map | 1 + .../teams/team-member-list-section.js | 64 + .../teams/team-member-list-section.js.map | 1 + .../account-settings/teams/team-page.js | 50 + .../account-settings/teams/team-page.js.map | 1 + .../teams/team-profile-image-section.js | 59 + .../teams/team-profile-image-section.js.map | 1 + .../teams/team-profile-user-section.js | 56 + .../teams/team-profile-user-section.js.map | 1 + .../react/dist/components-page/auth-page.js | 123 + .../dist/components-page/auth-page.js.map | 1 + .../dist/components-page/cli-auth-confirm.js | 123 + .../components-page/cli-auth-confirm.js.map | 1 + .../components-page/email-verification.js | 100 + .../components-page/email-verification.js.map | 1 + .../react/dist/components-page/error-page.js | 97 + .../dist/components-page/error-page.js.map | 1 + .../dist/components-page/forgot-password.js | 117 + .../components-page/forgot-password.js.map | 1 + .../components-page/magic-link-callback.js | 110 + .../magic-link-callback.js.map | 1 + .../dist/components-page/oauth-callback.js | 93 + .../components-page/oauth-callback.js.map | 1 + .../dist/components-page/password-reset.js | 179 + .../components-page/password-reset.js.map | 1 + .../react/dist/components-page/section.js | 6 + .../react/dist/components-page/section.js.map | 1 + .../react/dist/components-page/sign-in.js | 44 + .../react/dist/components-page/sign-in.js.map | 1 + .../react/dist/components-page/sign-out.js | 57 + .../dist/components-page/sign-out.js.map | 1 + .../react/dist/components-page/sign-up.js | 47 + .../react/dist/components-page/sign-up.js.map | 1 + .../dist/components-page/stack-handler.js | 255 ++ .../dist/components-page/stack-handler.js.map | 1 + .../dist/components-page/team-creation.js | 92 + .../dist/components-page/team-creation.js.map | 1 + .../dist/components-page/team-invitation.js | 144 + .../components-page/team-invitation.js.map | 1 + .../react/dist/components/api-key-dialogs.js | 184 + .../dist/components/api-key-dialogs.js.map | 1 + .../react/dist/components/api-key-table.js | 149 + .../dist/components/api-key-table.js.map | 1 + .../dist/components/credential-sign-in.js | 103 + .../dist/components/credential-sign-in.js.map | 1 + .../dist/components/credential-sign-up.js | 138 + .../dist/components/credential-sign-up.js.map | 1 + .../dist/components/elements/form-warning.js | 39 + .../components/elements/form-warning.js.map | 1 + .../components/elements/maybe-full-page.js | 75 + .../elements/maybe-full-page.js.map | 1 + .../elements/separator-with-text.js | 41 + .../elements/separator-with-text.js.map | 1 + .../components/elements/sidebar-layout.js | 119 + .../components/elements/sidebar-layout.js.map | 1 + .../components/elements/ssr-layout-effect.js | 47 + .../elements/ssr-layout-effect.js.map | 1 + .../dist/components/elements/user-avatar.js | 41 + .../components/elements/user-avatar.js.map | 1 + .../react/dist/components/iframe-preventer.js | 52 + .../dist/components/iframe-preventer.js.map | 1 + .../@stackframe/react/dist/components/link.js | 51 + .../react/dist/components/link.js.map | 1 + .../dist/components/magic-link-sign-in.js | 151 + .../dist/components/magic-link-sign-in.js.map | 1 + .../message-cards/known-error-message-card.js | 61 + .../known-error-message-card.js.map | 1 + .../components/message-cards/message-card.js | 45 + .../message-cards/message-card.js.map | 1 + .../message-cards/predefined-message-card.js | 107 + .../predefined-message-card.js.map | 1 + .../dist/components/oauth-button-group.js | 51 + .../dist/components/oauth-button-group.js.map | 1 + .../react/dist/components/oauth-button.js | 211 + .../react/dist/components/oauth-button.js.map | 1 + .../react/dist/components/passkey-button.js | 58 + .../dist/components/passkey-button.js.map | 1 + .../dist/components/profile-image-editor.js | 165 + .../components/profile-image-editor.js.map | 1 + .../dist/components/selected-team-switcher.js | 119 + .../components/selected-team-switcher.js.map | 1 + .../react/dist/components/team-icon.js | 39 + .../react/dist/components/team-icon.js.map | 1 + .../react/dist/components/user-button.js | 120 + .../react/dist/components/user-button.js.map | 1 + .../esm/components-page/account-settings.js | 151 + .../components-page/account-settings.js.map | 1 + .../active-sessions/active-sessions-page.js | 126 + .../active-sessions-page.js.map | 1 + .../api-keys/api-keys-page.js | 45 + .../api-keys/api-keys-page.js.map | 1 + .../account-settings/editable-text.js | 50 + .../account-settings/editable-text.js.map | 1 + .../email-and-auth/email-and-auth-page.js | 21 + .../email-and-auth/email-and-auth-page.js.map | 1 + .../email-and-auth/emails-section.js | 163 + .../email-and-auth/emails-section.js.map | 1 + .../email-and-auth/mfa-section.js | 111 + .../email-and-auth/mfa-section.js.map | 1 + .../email-and-auth/otp-section.js | 64 + .../email-and-auth/otp-section.js.map | 1 + .../email-and-auth/passkey-section.js | 67 + .../email-and-auth/passkey-section.js.map | 1 + .../email-and-auth/password-section.js | 146 + .../email-and-auth/password-section.js.map | 1 + .../account-settings/page-layout.js | 9 + .../account-settings/page-layout.js.map | 1 + .../profile-page/profile-page.js | 50 + .../profile-page/profile-page.js.map | 1 + .../account-settings/section.js | 19 + .../account-settings/section.js.map | 1 + .../settings/delete-account-section.js | 62 + .../settings/delete-account-section.js.map | 1 + .../settings/settings-page.js | 15 + .../settings/settings-page.js.map | 1 + .../settings/sign-out-section.js | 29 + .../settings/sign-out-section.js.map | 1 + .../teams/leave-team-section.js | 54 + .../teams/leave-team-section.js.map | 1 + .../teams/team-api-keys-section.js | 67 + .../teams/team-api-keys-section.js.map | 1 + .../teams/team-creation-page.js | 67 + .../teams/team-creation-page.js.map | 1 + .../teams/team-display-name-section.js | 32 + .../teams/team-display-name-section.js.map | 1 + .../teams/team-member-invitation-section.js | 106 + .../team-member-invitation-section.js.map | 1 + .../teams/team-member-list-section.js | 39 + .../teams/team-member-list-section.js.map | 1 + .../account-settings/teams/team-page.js | 25 + .../account-settings/teams/team-page.js.map | 1 + .../teams/team-profile-image-section.js | 34 + .../teams/team-profile-image-section.js.map | 1 + .../teams/team-profile-user-section.js | 31 + .../teams/team-profile-user-section.js.map | 1 + .../dist/esm/components-page/auth-page.js | 99 + .../dist/esm/components-page/auth-page.js.map | 1 + .../esm/components-page/cli-auth-confirm.js | 99 + .../components-page/cli-auth-confirm.js.map | 1 + .../esm/components-page/email-verification.js | 66 + .../components-page/email-verification.js.map | 1 + .../dist/esm/components-page/error-page.js | 73 + .../esm/components-page/error-page.js.map | 1 + .../esm/components-page/forgot-password.js | 92 + .../components-page/forgot-password.js.map | 1 + .../components-page/magic-link-callback.js | 76 + .../magic-link-callback.js.map | 1 + .../esm/components-page/oauth-callback.js | 69 + .../esm/components-page/oauth-callback.js.map | 1 + .../esm/components-page/password-reset.js | 145 + .../esm/components-page/password-reset.js.map | 1 + .../react/dist/esm/components-page/section.js | 4 + .../dist/esm/components-page/section.js.map | 1 + .../react/dist/esm/components-page/sign-in.js | 19 + .../dist/esm/components-page/sign-in.js.map | 1 + .../dist/esm/components-page/sign-out.js | 23 + .../dist/esm/components-page/sign-out.js.map | 1 + .../react/dist/esm/components-page/sign-up.js | 23 + .../dist/esm/components-page/sign-up.js.map | 1 + .../dist/esm/components-page/stack-handler.js | 234 + .../esm/components-page/stack-handler.js.map | 1 + .../dist/esm/components-page/team-creation.js | 68 + .../esm/components-page/team-creation.js.map | 1 + .../esm/components-page/team-invitation.js | 110 + .../components-page/team-invitation.js.map | 1 + .../dist/esm/components/api-key-dialogs.js | 157 + .../esm/components/api-key-dialogs.js.map | 1 + .../dist/esm/components/api-key-table.js | 125 + .../dist/esm/components/api-key-table.js.map | 1 + .../dist/esm/components/credential-sign-in.js | 79 + .../esm/components/credential-sign-in.js.map | 1 + .../dist/esm/components/credential-sign-up.js | 104 + .../esm/components/credential-sign-up.js.map | 1 + .../esm/components/elements/form-warning.js | 15 + .../components/elements/form-warning.js.map | 1 + .../components/elements/maybe-full-page.js | 51 + .../elements/maybe-full-page.js.map | 1 + .../elements/separator-with-text.js | 17 + .../elements/separator-with-text.js.map | 1 + .../esm/components/elements/sidebar-layout.js | 95 + .../components/elements/sidebar-layout.js.map | 1 + .../components/elements/ssr-layout-effect.js | 23 + .../elements/ssr-layout-effect.js.map | 1 + .../esm/components/elements/user-avatar.js | 16 + .../components/elements/user-avatar.js.map | 1 + .../dist/esm/components/iframe-preventer.js | 28 + .../esm/components/iframe-preventer.js.map | 1 + .../react/dist/esm/components/link.js | 26 + .../react/dist/esm/components/link.js.map | 1 + .../dist/esm/components/magic-link-sign-in.js | 127 + .../esm/components/magic-link-sign-in.js.map | 1 + .../message-cards/known-error-message-card.js | 37 + .../known-error-message-card.js.map | 1 + .../components/message-cards/message-card.js | 21 + .../message-cards/message-card.js.map | 1 + .../message-cards/predefined-message-card.js | 83 + .../predefined-message-card.js.map | 1 + .../dist/esm/components/oauth-button-group.js | 27 + .../esm/components/oauth-button-group.js.map | 1 + .../react/dist/esm/components/oauth-button.js | 177 + .../dist/esm/components/oauth-button.js.map | 1 + .../dist/esm/components/passkey-button.js | 34 + .../dist/esm/components/passkey-button.js.map | 1 + .../esm/components/profile-image-editor.js | 129 + .../components/profile-image-editor.js.map | 1 + .../esm/components/selected-team-switcher.js | 107 + .../components/selected-team-switcher.js.map | 1 + .../react/dist/esm/components/team-icon.js | 14 + .../dist/esm/components/team-icon.js.map | 1 + .../react/dist/esm/components/user-button.js | 96 + .../dist/esm/components/user-button.js.map | 1 + .../react/dist/esm/generated/global-css.js | 6 + .../dist/esm/generated/global-css.js.map | 1 + .../esm/generated/quetzal-translations.js | 2397 ++++++++++ .../esm/generated/quetzal-translations.js.map | 1 + .../@stackframe/react/dist/esm/global.d.js | 1 + .../react/dist/esm/global.d.js.map | 1 + package/@stackframe/react/dist/esm/index.js | 48 + .../@stackframe/react/dist/esm/index.js.map | 1 + .../@stackframe/react/dist/esm/lib/auth.js | 98 + .../react/dist/esm/lib/auth.js.map | 1 + .../@stackframe/react/dist/esm/lib/cookie.js | 246 ++ .../react/dist/esm/lib/cookie.js.map | 1 + .../@stackframe/react/dist/esm/lib/hooks.js | 30 + .../react/dist/esm/lib/hooks.js.map | 1 + .../dist/esm/lib/stack-app/api-keys/index.js | 22 + .../esm/lib/stack-app/api-keys/index.js.map | 1 + .../apps/implementations/admin-app-impl.js | 331 ++ .../implementations/admin-app-impl.js.map | 1 + .../apps/implementations/client-app-impl.js | 1585 +++++++ .../implementations/client-app-impl.js.map | 1 + .../stack-app/apps/implementations/common.js | 147 + .../apps/implementations/common.js.map | 1 + .../stack-app/apps/implementations/index.js | 24 + .../apps/implementations/index.js.map | 1 + .../apps/implementations/server-app-impl.js | 770 ++++ .../implementations/server-app-impl.js.map | 1 + .../dist/esm/lib/stack-app/apps/index.js | 16 + .../dist/esm/lib/stack-app/apps/index.js.map | 1 + .../stack-app/apps/interfaces/admin-app.js | 7 + .../apps/interfaces/admin-app.js.map | 1 + .../stack-app/apps/interfaces/client-app.js | 7 + .../apps/interfaces/client-app.js.map | 1 + .../stack-app/apps/interfaces/server-app.js | 7 + .../apps/interfaces/server-app.js.map | 1 + .../react/dist/esm/lib/stack-app/common.js | 6 + .../dist/esm/lib/stack-app/common.js.map | 1 + .../lib/stack-app/connected-accounts/index.js | 1 + .../stack-app/connected-accounts/index.js.map | 1 + .../lib/stack-app/contact-channels/index.js | 42 + .../stack-app/contact-channels/index.js.map | 1 + .../lib/stack-app/email-templates/index.js | 11 + .../stack-app/email-templates/index.js.map | 1 + .../dist/esm/lib/stack-app/email/index.js | 1 + .../dist/esm/lib/stack-app/email/index.js.map | 1 + .../react/dist/esm/lib/stack-app/index.js | 16 + .../react/dist/esm/lib/stack-app/index.js.map | 1 + .../lib/stack-app/internal-api-keys/index.js | 14 + .../stack-app/internal-api-keys/index.js.map | 1 + .../esm/lib/stack-app/permissions/index.js | 34 + .../lib/stack-app/permissions/index.js.map | 1 + .../lib/stack-app/project-configs/index.js | 1 + .../stack-app/project-configs/index.js.map | 1 + .../dist/esm/lib/stack-app/projects/index.js | 60 + .../esm/lib/stack-app/projects/index.js.map | 1 + .../dist/esm/lib/stack-app/teams/index.js | 38 + .../dist/esm/lib/stack-app/teams/index.js.map | 1 + .../dist/esm/lib/stack-app/users/index.js | 47 + .../dist/esm/lib/stack-app/users/index.js.map | 1 + .../react/dist/esm/lib/translations.js | 23 + .../react/dist/esm/lib/translations.js.map | 1 + .../esm/providers/stack-provider-client.js | 29 + .../providers/stack-provider-client.js.map | 1 + .../dist/esm/providers/stack-provider.js | 25 + .../dist/esm/providers/stack-provider.js.map | 1 + .../dist/esm/providers/theme-provider.js | 71 + .../dist/esm/providers/theme-provider.js.map | 1 + .../providers/translation-provider-client.js | 18 + .../translation-provider-client.js.map | 1 + .../esm/providers/translation-provider.js | 18 + .../esm/providers/translation-provider.js.map | 1 + .../react/dist/esm/utils/browser-script.js | 112 + .../dist/esm/utils/browser-script.js.map | 1 + .../react/dist/esm/utils/constants.js | 66 + .../react/dist/esm/utils/constants.js.map | 1 + .../@stackframe/react/dist/esm/utils/url.js | 21 + .../react/dist/esm/utils/url.js.map | 1 + .../react/dist/generated/global-css.js | 31 + .../react/dist/generated/global-css.js.map | 1 + .../dist/generated/quetzal-translations.js | 2423 ++++++++++ .../generated/quetzal-translations.js.map | 1 + package/@stackframe/react/dist/global.d.js | 2 + .../@stackframe/react/dist/global.d.js.map | 1 + package/@stackframe/react/dist/index.d.mts | 1221 +++++ package/@stackframe/react/dist/index.d.ts | 1221 +++++ package/@stackframe/react/dist/index.js | 106 + package/@stackframe/react/dist/index.js.map | 1 + package/@stackframe/react/dist/lib/auth.js | 125 + .../@stackframe/react/dist/lib/auth.js.map | 1 + package/@stackframe/react/dist/lib/cookie.js | 324 ++ .../@stackframe/react/dist/lib/cookie.js.map | 1 + package/@stackframe/react/dist/lib/hooks.js | 56 + .../@stackframe/react/dist/lib/hooks.js.map | 1 + .../dist/lib/stack-app/api-keys/index.js | 48 + .../dist/lib/stack-app/api-keys/index.js.map | 1 + .../apps/implementations/admin-app-impl.js | 356 ++ .../implementations/admin-app-impl.js.map | 1 + .../apps/implementations/client-app-impl.js | 1620 +++++++ .../implementations/client-app-impl.js.map | 1 + .../stack-app/apps/implementations/common.js | 193 + .../apps/implementations/common.js.map | 1 + .../stack-app/apps/implementations/index.js | 51 + .../apps/implementations/index.js.map | 1 + .../apps/implementations/server-app-impl.js | 795 ++++ .../implementations/server-app-impl.js.map | 1 + .../react/dist/lib/stack-app/apps/index.js | 37 + .../dist/lib/stack-app/apps/index.js.map | 1 + .../stack-app/apps/interfaces/admin-app.js | 32 + .../apps/interfaces/admin-app.js.map | 1 + .../stack-app/apps/interfaces/client-app.js | 32 + .../apps/interfaces/client-app.js.map | 1 + .../stack-app/apps/interfaces/server-app.js | 32 + .../apps/interfaces/server-app.js.map | 1 + .../react/dist/lib/stack-app/common.js | 31 + .../react/dist/lib/stack-app/common.js.map | 1 + .../lib/stack-app/connected-accounts/index.js | 19 + .../stack-app/connected-accounts/index.js.map | 1 + .../lib/stack-app/contact-channels/index.js | 70 + .../stack-app/contact-channels/index.js.map | 1 + .../lib/stack-app/email-templates/index.js | 36 + .../stack-app/email-templates/index.js.map | 1 + .../react/dist/lib/stack-app/email/index.js | 19 + .../dist/lib/stack-app/email/index.js.map | 1 + .../react/dist/lib/stack-app/index.js | 38 + .../react/dist/lib/stack-app/index.js.map | 1 + .../lib/stack-app/internal-api-keys/index.js | 39 + .../stack-app/internal-api-keys/index.js.map | 1 + .../dist/lib/stack-app/permissions/index.js | 62 + .../lib/stack-app/permissions/index.js.map | 1 + .../lib/stack-app/project-configs/index.js | 19 + .../stack-app/project-configs/index.js.map | 1 + .../dist/lib/stack-app/projects/index.js | 86 + .../dist/lib/stack-app/projects/index.js.map | 1 + .../react/dist/lib/stack-app/teams/index.js | 66 + .../dist/lib/stack-app/teams/index.js.map | 1 + .../react/dist/lib/stack-app/users/index.js | 74 + .../dist/lib/stack-app/users/index.js.map | 1 + .../react/dist/lib/translations.js | 58 + .../react/dist/lib/translations.js.map | 1 + .../dist/providers/stack-provider-client.js | 65 + .../providers/stack-provider-client.js.map | 1 + .../react/dist/providers/stack-provider.js | 46 + .../dist/providers/stack-provider.js.map | 1 + .../react/dist/providers/theme-provider.js | 105 + .../dist/providers/theme-provider.js.map | 1 + .../providers/translation-provider-client.js | 43 + .../translation-provider-client.js.map | 1 + .../dist/providers/translation-provider.js | 43 + .../providers/translation-provider.js.map | 1 + .../react/dist/utils/browser-script.js | 137 + .../react/dist/utils/browser-script.js.map | 1 + .../@stackframe/react/dist/utils/constants.js | 99 + .../react/dist/utils/constants.js.map | 1 + package/@stackframe/react/dist/utils/url.js | 46 + .../@stackframe/react/dist/utils/url.js.map | 1 + package/@stackframe/react/package.json | 92 + package/@stackframe/stack-shared/CHANGELOG.md | 1102 +++++ package/@stackframe/stack-shared/LICENSE | 7 + .../stack-shared/dist/config/format.d.mts | 39 + .../stack-shared/dist/config/format.d.ts | 39 + .../stack-shared/dist/config/format.js | 165 + .../stack-shared/dist/config/format.js.map | 1 + .../stack-shared/dist/config/schema.d.mts | 729 +++ .../stack-shared/dist/config/schema.d.ts | 729 +++ .../stack-shared/dist/config/schema.js | 245 ++ .../stack-shared/dist/config/schema.js.map | 1 + .../@stackframe/stack-shared/dist/crud.d.mts | 102 + .../@stackframe/stack-shared/dist/crud.d.ts | 102 + package/@stackframe/stack-shared/dist/crud.js | 85 + .../@stackframe/stack-shared/dist/crud.js.map | 1 + .../stack-shared/dist/esm/config/format.js | 135 + .../dist/esm/config/format.js.map | 1 + .../stack-shared/dist/esm/config/schema.js | 201 + .../dist/esm/config/schema.js.map | 1 + .../@stackframe/stack-shared/dist/esm/crud.js | 60 + .../stack-shared/dist/esm/crud.js.map | 1 + .../stack-shared/dist/esm/global.d.js | 1 + .../stack-shared/dist/esm/global.d.js.map | 1 + .../stack-shared/dist/esm/helpers/password.js | 17 + .../dist/esm/helpers/password.js.map | 1 + .../dist/esm/helpers/production-mode.js | 50 + .../dist/esm/helpers/production-mode.js.map | 1 + .../dist/esm/hooks/use-async-callback.js | 38 + .../dist/esm/hooks/use-async-callback.js.map | 1 + .../esm/hooks/use-async-external-store.js | 23 + .../esm/hooks/use-async-external-store.js.map | 1 + .../stack-shared/dist/esm/hooks/use-hash.js | 17 + .../dist/esm/hooks/use-hash.js.map | 1 + .../dist/esm/hooks/use-strict-memo.js | 61 + .../dist/esm/hooks/use-strict-memo.js.map | 1 + .../stack-shared/dist/esm/index.js | 22 + .../stack-shared/dist/esm/index.js.map | 1 + .../dist/esm/interface/adminInterface.js | 244 + .../dist/esm/interface/adminInterface.js.map | 1 + .../dist/esm/interface/clientInterface.js | 2043 +++++++++ .../dist/esm/interface/clientInterface.js.map | 1 + .../esm/interface/crud/contact-channels.js | 77 + .../interface/crud/contact-channels.js.map | 1 + .../dist/esm/interface/crud/current-user.js | 65 + .../esm/interface/crud/current-user.js.map | 1 + .../esm/interface/crud/email-templates.js | 52 + .../esm/interface/crud/email-templates.js.map | 1 + .../dist/esm/interface/crud/emails.js | 20 + .../dist/esm/interface/crud/emails.js.map | 1 + .../esm/interface/crud/internal-api-keys.js | 69 + .../interface/crud/internal-api-keys.js.map | 1 + .../dist/esm/interface/crud/oauth.js | 24 + .../dist/esm/interface/crud/oauth.js.map | 1 + .../esm/interface/crud/project-api-keys.js | 98 + .../interface/crud/project-api-keys.js.map | 1 + .../esm/interface/crud/project-permissions.js | 113 + .../interface/crud/project-permissions.js.map | 1 + .../dist/esm/interface/crud/projects.js | 181 + .../dist/esm/interface/crud/projects.js.map | 1 + .../dist/esm/interface/crud/sessions.js | 62 + .../dist/esm/interface/crud/sessions.js.map | 1 + .../dist/esm/interface/crud/svix-token.js | 22 + .../dist/esm/interface/crud/svix-token.js.map | 1 + .../interface/crud/team-invitation-details.js | 23 + .../crud/team-invitation-details.js.map | 1 + .../esm/interface/crud/team-invitation.js | 36 + .../esm/interface/crud/team-invitation.js.map | 1 + .../interface/crud/team-member-profiles.js | 62 + .../crud/team-member-profiles.js.map | 1 + .../esm/interface/crud/team-memberships.js | 60 + .../interface/crud/team-memberships.js.map | 1 + .../esm/interface/crud/team-permissions.js | 114 + .../interface/crud/team-permissions.js.map | 1 + .../dist/esm/interface/crud/teams.js | 143 + .../dist/esm/interface/crud/teams.js.map | 1 + .../dist/esm/interface/crud/users.js | 139 + .../dist/esm/interface/crud/users.js.map | 1 + .../dist/esm/interface/serverInterface.js | 511 +++ .../dist/esm/interface/serverInterface.js.map | 1 + .../dist/esm/interface/webhooks.js | 21 + .../dist/esm/interface/webhooks.js.map | 1 + .../stack-shared/dist/esm/known-errors.js | 1265 ++++++ .../stack-shared/dist/esm/known-errors.js.map | 1 + .../stack-shared/dist/esm/schema-fields.js | 486 ++ .../dist/esm/schema-fields.js.map | 1 + .../stack-shared/dist/esm/sessions.js | 168 + .../stack-shared/dist/esm/sessions.js.map | 1 + .../stack-shared/dist/esm/utils/api-keys.js | 79 + .../dist/esm/utils/api-keys.js.map | 1 + .../stack-shared/dist/esm/utils/arrays.js | 78 + .../stack-shared/dist/esm/utils/arrays.js.map | 1 + .../stack-shared/dist/esm/utils/base64.js | 18 + .../stack-shared/dist/esm/utils/base64.js.map | 1 + .../stack-shared/dist/esm/utils/booleans.js | 12 + .../dist/esm/utils/booleans.js.map | 1 + .../dist/esm/utils/browser-compat.js | 21 + .../dist/esm/utils/browser-compat.js.map | 1 + .../stack-shared/dist/esm/utils/bytes.js | 160 + .../stack-shared/dist/esm/utils/bytes.js.map | 1 + .../stack-shared/dist/esm/utils/caches.js | 167 + .../stack-shared/dist/esm/utils/caches.js.map | 1 + .../dist/esm/utils/compile-time.js | 11 + .../dist/esm/utils/compile-time.js.map | 1 + .../stack-shared/dist/esm/utils/crypto.js | 25 + .../stack-shared/dist/esm/utils/crypto.js.map | 1 + .../stack-shared/dist/esm/utils/dates.js | 64 + .../stack-shared/dist/esm/utils/dates.js.map | 1 + .../stack-shared/dist/esm/utils/dom.js | 11 + .../stack-shared/dist/esm/utils/dom.js.map | 1 + .../stack-shared/dist/esm/utils/env.js | 58 + .../stack-shared/dist/esm/utils/env.js.map | 1 + .../stack-shared/dist/esm/utils/errors.js | 178 + .../stack-shared/dist/esm/utils/errors.js.map | 1 + .../stack-shared/dist/esm/utils/fs.js | 37 + .../stack-shared/dist/esm/utils/fs.js.map | 1 + .../stack-shared/dist/esm/utils/functions.js | 12 + .../dist/esm/utils/functions.js.map | 1 + .../stack-shared/dist/esm/utils/geo.js | 15 + .../stack-shared/dist/esm/utils/geo.js.map | 1 + .../stack-shared/dist/esm/utils/globals.js | 18 + .../dist/esm/utils/globals.js.map | 1 + .../stack-shared/dist/esm/utils/hashes.js | 55 + .../stack-shared/dist/esm/utils/hashes.js.map | 1 + .../stack-shared/dist/esm/utils/html.js | 13 + .../stack-shared/dist/esm/utils/html.js.map | 1 + .../stack-shared/dist/esm/utils/http.js | 60 + .../stack-shared/dist/esm/utils/http.js.map | 1 + .../stack-shared/dist/esm/utils/ips.js | 15 + .../stack-shared/dist/esm/utils/ips.js.map | 1 + .../stack-shared/dist/esm/utils/json.js | 31 + .../stack-shared/dist/esm/utils/json.js.map | 1 + .../stack-shared/dist/esm/utils/jwt.js | 87 + .../stack-shared/dist/esm/utils/jwt.js.map | 1 + .../stack-shared/dist/esm/utils/locks.js | 57 + .../stack-shared/dist/esm/utils/locks.js.map | 1 + .../stack-shared/dist/esm/utils/maps.js | 181 + .../stack-shared/dist/esm/utils/maps.js.map | 1 + .../stack-shared/dist/esm/utils/math.js | 8 + .../stack-shared/dist/esm/utils/math.js.map | 1 + .../stack-shared/dist/esm/utils/node-http.js | 42 + .../dist/esm/utils/node-http.js.map | 1 + .../stack-shared/dist/esm/utils/numbers.js | 32 + .../dist/esm/utils/numbers.js.map | 1 + .../stack-shared/dist/esm/utils/oauth.js | 10 + .../stack-shared/dist/esm/utils/oauth.js.map | 1 + .../stack-shared/dist/esm/utils/objects.js | 177 + .../dist/esm/utils/objects.js.map | 1 + .../stack-shared/dist/esm/utils/passkey.js | 1 + .../dist/esm/utils/passkey.js.map | 1 + .../stack-shared/dist/esm/utils/promises.js | 233 + .../dist/esm/utils/promises.js.map | 1 + .../stack-shared/dist/esm/utils/proxies.js | 128 + .../dist/esm/utils/proxies.js.map | 1 + .../stack-shared/dist/esm/utils/react.js | 78 + .../stack-shared/dist/esm/utils/react.js.map | 1 + .../stack-shared/dist/esm/utils/results.js | 141 + .../dist/esm/utils/results.js.map | 1 + .../stack-shared/dist/esm/utils/sentry.js | 20 + .../stack-shared/dist/esm/utils/sentry.js.map | 1 + .../stack-shared/dist/esm/utils/stores.js | 195 + .../stack-shared/dist/esm/utils/stores.js.map | 1 + .../stack-shared/dist/esm/utils/strings.js | 295 ++ .../dist/esm/utils/strings.js.map | 1 + .../dist/esm/utils/strings.nicify.test.js | 222 + .../dist/esm/utils/strings.nicify.test.js.map | 1 + .../stack-shared/dist/esm/utils/types.js | 1 + .../stack-shared/dist/esm/utils/types.js.map | 1 + .../stack-shared/dist/esm/utils/unicode.js | 11 + .../dist/esm/utils/unicode.js.map | 1 + .../stack-shared/dist/esm/utils/urls.js | 53 + .../stack-shared/dist/esm/utils/urls.js.map | 1 + .../stack-shared/dist/esm/utils/uuids.js | 16 + .../stack-shared/dist/esm/utils/uuids.js.map | 1 + .../stack-shared/dist/global.d.d.mts | 1 + .../stack-shared/dist/global.d.d.ts | 1 + .../@stackframe/stack-shared/dist/global.d.js | 2 + .../stack-shared/dist/global.d.js.map | 1 + .../stack-shared/dist/helpers/password.d.mts | 11 + .../stack-shared/dist/helpers/password.d.ts | 11 + .../stack-shared/dist/helpers/password.js | 42 + .../stack-shared/dist/helpers/password.js.map | 1 + .../dist/helpers/production-mode.d.mts | 12 + .../dist/helpers/production-mode.d.ts | 12 + .../dist/helpers/production-mode.js | 75 + .../dist/helpers/production-mode.js.map | 1 + .../dist/hooks/use-async-callback.d.mts | 6 + .../dist/hooks/use-async-callback.d.ts | 6 + .../dist/hooks/use-async-callback.js | 74 + .../dist/hooks/use-async-callback.js.map | 1 + .../dist/hooks/use-async-external-store.d.mts | 7 + .../dist/hooks/use-async-external-store.d.ts | 7 + .../dist/hooks/use-async-external-store.js | 48 + .../hooks/use-async-external-store.js.map | 1 + .../stack-shared/dist/hooks/use-hash.d.mts | 3 + .../stack-shared/dist/hooks/use-hash.d.ts | 3 + .../stack-shared/dist/hooks/use-hash.js | 42 + .../stack-shared/dist/hooks/use-hash.js.map | 1 + .../dist/hooks/use-strict-memo.d.mts | 8 + .../dist/hooks/use-strict-memo.d.ts | 8 + .../dist/hooks/use-strict-memo.js | 86 + .../dist/hooks/use-strict-memo.js.map | 1 + .../@stackframe/stack-shared/dist/index.d.mts | 30 + .../@stackframe/stack-shared/dist/index.d.ts | 30 + .../@stackframe/stack-shared/dist/index.js | 42 + .../stack-shared/dist/index.js.map | 1 + .../dist/interface/adminInterface.d.mts | 94 + .../dist/interface/adminInterface.d.ts | 94 + .../dist/interface/adminInterface.js | 269 ++ .../dist/interface/adminInterface.js.map | 1 + .../dist/interface/clientInterface.d.mts | 260 ++ .../dist/interface/clientInterface.d.ts | 260 ++ .../dist/interface/clientInterface.js | 2070 +++++++++ .../dist/interface/clientInterface.js.map | 1 + .../interface/crud/contact-channels.d.mts | 180 + .../dist/interface/crud/contact-channels.d.ts | 180 + .../dist/interface/crud/contact-channels.js | 108 + .../interface/crud/contact-channels.js.map | 1 + .../dist/interface/crud/current-user.d.mts | 205 + .../dist/interface/crud/current-user.d.ts | 205 + .../dist/interface/crud/current-user.js | 90 + .../dist/interface/crud/current-user.js.map | 1 + .../dist/interface/crud/email-templates.d.mts | 84 + .../dist/interface/crud/email-templates.d.ts | 84 + .../dist/interface/crud/email-templates.js | 82 + .../interface/crud/email-templates.js.map | 1 + .../dist/interface/crud/emails.d.mts | 69 + .../dist/interface/crud/emails.d.ts | 69 + .../dist/interface/crud/emails.js | 56 + .../dist/interface/crud/emails.js.map | 1 + .../interface/crud/internal-api-keys.d.mts | 139 + .../interface/crud/internal-api-keys.d.ts | 139 + .../dist/interface/crud/internal-api-keys.js | 99 + .../interface/crud/internal-api-keys.js.map | 1 + .../dist/interface/crud/oauth.d.mts | 34 + .../dist/interface/crud/oauth.d.ts | 34 + .../stack-shared/dist/interface/crud/oauth.js | 51 + .../dist/interface/crud/oauth.js.map | 1 + .../interface/crud/project-api-keys.d.mts | 206 + .../dist/interface/crud/project-api-keys.d.ts | 206 + .../dist/interface/crud/project-api-keys.js | 128 + .../interface/crud/project-api-keys.js.map | 1 + .../interface/crud/project-permissions.d.mts | 160 + .../interface/crud/project-permissions.d.ts | 160 + .../interface/crud/project-permissions.js | 158 + .../interface/crud/project-permissions.js.map | 1 + .../dist/interface/crud/projects.d.mts | 627 +++ .../dist/interface/crud/projects.d.ts | 627 +++ .../dist/interface/crud/projects.js | 225 + .../dist/interface/crud/projects.js.map | 1 + .../dist/interface/crud/sessions.d.mts | 149 + .../dist/interface/crud/sessions.d.ts | 149 + .../dist/interface/crud/sessions.js | 91 + .../dist/interface/crud/sessions.js.map | 1 + .../dist/interface/crud/svix-token.d.mts | 26 + .../dist/interface/crud/svix-token.d.ts | 26 + .../dist/interface/crud/svix-token.js | 49 + .../dist/interface/crud/svix-token.js.map | 1 + .../crud/team-invitation-details.d.mts | 30 + .../crud/team-invitation-details.d.ts | 30 + .../interface/crud/team-invitation-details.js | 59 + .../crud/team-invitation-details.js.map | 1 + .../dist/interface/crud/team-invitation.d.mts | 49 + .../dist/interface/crud/team-invitation.d.ts | 49 + .../dist/interface/crud/team-invitation.js | 72 + .../interface/crud/team-invitation.js.map | 1 + .../interface/crud/team-member-profiles.d.mts | 229 + .../interface/crud/team-member-profiles.d.ts | 229 + .../interface/crud/team-member-profiles.js | 100 + .../crud/team-member-profiles.js.map | 1 + .../interface/crud/team-memberships.d.mts | 74 + .../dist/interface/crud/team-memberships.d.ts | 74 + .../dist/interface/crud/team-memberships.js | 90 + .../interface/crud/team-memberships.js.map | 1 + .../interface/crud/team-permissions.d.mts | 168 + .../dist/interface/crud/team-permissions.d.ts | 168 + .../dist/interface/crud/team-permissions.js | 159 + .../interface/crud/team-permissions.js.map | 1 + .../dist/interface/crud/teams.d.mts | 298 ++ .../dist/interface/crud/teams.d.ts | 298 ++ .../stack-shared/dist/interface/crud/teams.js | 189 + .../dist/interface/crud/teams.js.map | 1 + .../dist/interface/crud/users.d.mts | 469 ++ .../dist/interface/crud/users.d.ts | 469 ++ .../stack-shared/dist/interface/crud/users.js | 181 + .../dist/interface/crud/users.js.map | 1 + .../dist/interface/serverInterface.d.mts | 130 + .../dist/interface/serverInterface.d.ts | 130 + .../dist/interface/serverInterface.js | 534 +++ .../dist/interface/serverInterface.js.map | 1 + .../dist/interface/webhooks.d.mts | 292 ++ .../stack-shared/dist/interface/webhooks.d.ts | 292 ++ .../stack-shared/dist/interface/webhooks.js | 46 + .../dist/interface/webhooks.js.map | 1 + .../stack-shared/dist/known-errors.d.mts | 455 ++ .../stack-shared/dist/known-errors.d.ts | 455 ++ .../stack-shared/dist/known-errors.js | 1291 ++++++ .../stack-shared/dist/known-errors.js.map | 1 + .../stack-shared/dist/schema-fields.d.mts | 164 + .../stack-shared/dist/schema-fields.d.ts | 164 + .../stack-shared/dist/schema-fields.js | 632 +++ .../stack-shared/dist/schema-fields.js.map | 1 + .../stack-shared/dist/sessions.d.mts | 109 + .../stack-shared/dist/sessions.d.ts | 109 + .../@stackframe/stack-shared/dist/sessions.js | 205 + .../stack-shared/dist/sessions.js.map | 1 + .../stack-shared/dist/utils/api-keys.d.mts | 24 + .../stack-shared/dist/utils/api-keys.d.ts | 24 + .../stack-shared/dist/utils/api-keys.js | 116 + .../stack-shared/dist/utils/api-keys.js.map | 1 + .../stack-shared/dist/utils/arrays.d.mts | 18 + .../stack-shared/dist/utils/arrays.d.ts | 18 + .../stack-shared/dist/utils/arrays.js | 113 + .../stack-shared/dist/utils/arrays.js.map | 1 + .../stack-shared/dist/utils/base64.d.mts | 4 + .../stack-shared/dist/utils/base64.d.ts | 4 + .../stack-shared/dist/utils/base64.js | 44 + .../stack-shared/dist/utils/base64.js.map | 1 + .../stack-shared/dist/utils/booleans.d.mts | 6 + .../stack-shared/dist/utils/booleans.d.ts | 6 + .../stack-shared/dist/utils/booleans.js | 38 + .../stack-shared/dist/utils/booleans.js.map | 1 + .../dist/utils/browser-compat.d.mts | 8 + .../dist/utils/browser-compat.d.ts | 8 + .../stack-shared/dist/utils/browser-compat.js | 46 + .../dist/utils/browser-compat.js.map | 1 + .../stack-shared/dist/utils/bytes.d.mts | 15 + .../stack-shared/dist/utils/bytes.d.ts | 15 + .../stack-shared/dist/utils/bytes.js | 197 + .../stack-shared/dist/utils/bytes.js.map | 1 + .../stack-shared/dist/utils/caches.d.mts | 98 + .../stack-shared/dist/utils/caches.d.ts | 98 + .../stack-shared/dist/utils/caches.js | 193 + .../stack-shared/dist/utils/caches.js.map | 1 + .../dist/utils/compile-time.d.mts | 8 + .../stack-shared/dist/utils/compile-time.d.ts | 8 + .../stack-shared/dist/utils/compile-time.js | 36 + .../dist/utils/compile-time.js.map | 1 + .../stack-shared/dist/utils/crypto.d.mts | 8 + .../stack-shared/dist/utils/crypto.d.ts | 8 + .../stack-shared/dist/utils/crypto.js | 51 + .../stack-shared/dist/utils/crypto.js.map | 1 + .../stack-shared/dist/utils/dates.d.mts | 15 + .../stack-shared/dist/utils/dates.d.ts | 15 + .../stack-shared/dist/utils/dates.js | 92 + .../stack-shared/dist/utils/dates.js.map | 1 + .../stack-shared/dist/utils/dom.d.mts | 3 + .../stack-shared/dist/utils/dom.d.ts | 3 + .../stack-shared/dist/utils/dom.js | 36 + .../stack-shared/dist/utils/dom.js.map | 1 + .../stack-shared/dist/utils/env.d.mts | 9 + .../stack-shared/dist/utils/env.d.ts | 9 + .../stack-shared/dist/utils/env.js | 86 + .../stack-shared/dist/utils/env.js.map | 1 + .../stack-shared/dist/utils/errors.d.mts | 225 + .../stack-shared/dist/utils/errors.d.ts | 225 + .../stack-shared/dist/utils/errors.js | 209 + .../stack-shared/dist/utils/errors.js.map | 1 + .../stack-shared/dist/utils/fs.d.mts | 7 + .../stack-shared/dist/utils/fs.d.ts | 7 + .../@stackframe/stack-shared/dist/utils/fs.js | 74 + .../stack-shared/dist/utils/fs.js.map | 1 + .../stack-shared/dist/utils/functions.d.mts | 4 + .../stack-shared/dist/utils/functions.d.ts | 4 + .../stack-shared/dist/utils/functions.js | 38 + .../stack-shared/dist/utils/functions.js.map | 1 + .../stack-shared/dist/utils/geo.d.mts | 22 + .../stack-shared/dist/utils/geo.d.ts | 22 + .../stack-shared/dist/utils/geo.js | 40 + .../stack-shared/dist/utils/geo.js.map | 1 + .../stack-shared/dist/utils/globals.d.mts | 5 + .../stack-shared/dist/utils/globals.d.ts | 5 + .../stack-shared/dist/utils/globals.js | 44 + .../stack-shared/dist/utils/globals.js.map | 1 + .../stack-shared/dist/utils/hashes.d.mts | 7 + .../stack-shared/dist/utils/hashes.d.ts | 7 + .../stack-shared/dist/utils/hashes.js | 94 + .../stack-shared/dist/utils/hashes.js.map | 1 + .../stack-shared/dist/utils/html.d.mts | 4 + .../stack-shared/dist/utils/html.d.ts | 4 + .../stack-shared/dist/utils/html.js | 39 + .../stack-shared/dist/utils/html.js.map | 1 + .../stack-shared/dist/utils/http.d.mts | 43 + .../stack-shared/dist/utils/http.d.ts | 43 + .../stack-shared/dist/utils/http.js | 87 + .../stack-shared/dist/utils/http.js.map | 1 + .../stack-shared/dist/utils/ips.d.mts | 6 + .../stack-shared/dist/utils/ips.d.ts | 6 + .../stack-shared/dist/utils/ips.js | 51 + .../stack-shared/dist/utils/ips.js.map | 1 + .../stack-shared/dist/utils/json.d.mts | 13 + .../stack-shared/dist/utils/json.d.ts | 13 + .../stack-shared/dist/utils/json.js | 58 + .../stack-shared/dist/utils/json.js.map | 1 + .../stack-shared/dist/utils/jwt.d.mts | 44 + .../stack-shared/dist/utils/jwt.d.ts | 44 + .../stack-shared/dist/utils/jwt.js | 129 + .../stack-shared/dist/utils/jwt.js.map | 1 + .../stack-shared/dist/utils/locks.d.mts | 15 + .../stack-shared/dist/utils/locks.d.ts | 15 + .../stack-shared/dist/utils/locks.js | 82 + .../stack-shared/dist/utils/locks.js.map | 1 + .../stack-shared/dist/utils/maps.d.mts | 59 + .../stack-shared/dist/utils/maps.d.ts | 59 + .../stack-shared/dist/utils/maps.js | 209 + .../stack-shared/dist/utils/maps.js.map | 1 + .../stack-shared/dist/utils/math.d.mts | 6 + .../stack-shared/dist/utils/math.d.ts | 6 + .../stack-shared/dist/utils/math.js | 33 + .../stack-shared/dist/utils/math.js.map | 1 + .../stack-shared/dist/utils/node-http.d.mts | 15 + .../stack-shared/dist/utils/node-http.d.ts | 15 + .../stack-shared/dist/utils/node-http.js | 67 + .../stack-shared/dist/utils/node-http.js.map | 1 + .../stack-shared/dist/utils/numbers.d.mts | 5 + .../stack-shared/dist/utils/numbers.d.ts | 5 + .../stack-shared/dist/utils/numbers.js | 59 + .../stack-shared/dist/utils/numbers.js.map | 1 + .../stack-shared/dist/utils/oauth.d.mts | 8 + .../stack-shared/dist/utils/oauth.d.ts | 8 + .../stack-shared/dist/utils/oauth.js | 37 + .../stack-shared/dist/utils/oauth.js.map | 1 + .../stack-shared/dist/utils/objects.d.mts | 69 + .../stack-shared/dist/utils/objects.d.ts | 69 + .../stack-shared/dist/utils/objects.js | 228 + .../stack-shared/dist/utils/objects.js.map | 1 + .../stack-shared/dist/utils/passkey.d.mts | 1 + .../stack-shared/dist/utils/passkey.d.ts | 1 + .../stack-shared/dist/utils/passkey.js | 19 + .../stack-shared/dist/utils/passkey.js.map | 1 + .../stack-shared/dist/utils/promises.d.mts | 74 + .../stack-shared/dist/utils/promises.d.ts | 74 + .../stack-shared/dist/utils/promises.js | 271 ++ .../stack-shared/dist/utils/promises.js.map | 1 + .../stack-shared/dist/utils/proxies.d.mts | 4 + .../stack-shared/dist/utils/proxies.d.ts | 4 + .../stack-shared/dist/utils/proxies.js | 154 + .../stack-shared/dist/utils/proxies.js.map | 1 + .../stack-shared/dist/utils/react.d.mts | 25 + .../stack-shared/dist/utils/react.d.ts | 25 + .../stack-shared/dist/utils/react.js | 117 + .../stack-shared/dist/utils/react.js.map | 1 + .../stack-shared/dist/utils/results.d.mts | 78 + .../stack-shared/dist/utils/results.d.ts | 78 + .../stack-shared/dist/utils/results.js | 167 + .../stack-shared/dist/utils/results.js.map | 1 + .../stack-shared/dist/utils/sentry.d.mts | 5 + .../stack-shared/dist/utils/sentry.d.ts | 5 + .../stack-shared/dist/utils/sentry.js | 45 + .../stack-shared/dist/utils/sentry.js.map | 1 + .../stack-shared/dist/utils/stores.d.mts | 102 + .../stack-shared/dist/utils/stores.d.ts | 102 + .../stack-shared/dist/utils/stores.js | 222 + .../stack-shared/dist/utils/stores.js.map | 1 + .../stack-shared/dist/utils/strings.d.mts | 72 + .../stack-shared/dist/utils/strings.d.ts | 72 + .../stack-shared/dist/utils/strings.js | 336 ++ .../stack-shared/dist/utils/strings.js.map | 1 + .../dist/utils/strings.nicify.test.d.mts | 2 + .../dist/utils/strings.nicify.test.d.ts | 2 + .../dist/utils/strings.nicify.test.js | 224 + .../dist/utils/strings.nicify.test.js.map | 1 + .../stack-shared/dist/utils/types.d.mts | 23 + .../stack-shared/dist/utils/types.d.ts | 23 + .../stack-shared/dist/utils/types.js | 19 + .../stack-shared/dist/utils/types.js.map | 1 + .../stack-shared/dist/utils/unicode.d.mts | 3 + .../stack-shared/dist/utils/unicode.d.ts | 3 + .../stack-shared/dist/utils/unicode.js | 36 + .../stack-shared/dist/utils/unicode.js.map | 1 + .../stack-shared/dist/utils/urls.d.mts | 20 + .../stack-shared/dist/utils/urls.d.ts | 20 + .../stack-shared/dist/utils/urls.js | 85 + .../stack-shared/dist/utils/urls.js.map | 1 + .../stack-shared/dist/utils/uuids.d.mts | 4 + .../stack-shared/dist/utils/uuids.d.ts | 4 + .../stack-shared/dist/utils/uuids.js | 42 + .../stack-shared/dist/utils/uuids.js.map | 1 + package/@stackframe/stack-shared/package.json | 78 + postcss.config.cjs | 8 + public/favicon.ico | Bin 0 -> 103295 bytes public/favicon.png | Bin 0 -> 8488 bytes public/node.svg | 1 + resources/scripts/download.js | 45 + resources/scripts/install-bun.js | 175 + resources/scripts/install-uv.js | 216 + src/App.tsx | 70 + src/api/http.ts | 251 ++ src/components/AddWorker/IntegrationList.tsx | 477 ++ src/components/AddWorker/ToolSelect.tsx | 528 +++ src/components/AddWorker/index.tsx | 485 ++ src/components/AnimationJson.tsx | 35 + src/components/BottomBar/index.tsx | 11 + src/components/ChatBox/BottomInput.tsx | 458 ++ src/components/ChatBox/MarkDown.tsx | 192 + src/components/ChatBox/MessageCard.tsx | 100 + src/components/ChatBox/NoticeCard.tsx | 94 + src/components/ChatBox/SummaryMarkDown.tsx | 112 + src/components/ChatBox/TaskCard.tsx | 294 ++ src/components/ChatBox/TaskItem.tsx | 137 + src/components/ChatBox/TaskType.tsx | 30 + src/components/ChatBox/TypeCardSkeleton.tsx | 95 + src/components/ChatBox/index.tsx | 553 +++ src/components/Folder/index.tsx | 355 ++ src/components/GlobalSearch/index.tsx | 70 + src/components/HistorySidebar/SearchInput.tsx | 40 + src/components/HistorySidebar/index.tsx | 598 +++ src/components/InstallStep/Carousel.tsx | 179 + .../InstallStep/InstallDependencies.tsx | 179 + src/components/InstallStep/Permissions.tsx | 124 + src/components/Layout/index.tsx | 54 + src/components/SearchAgentWrokSpace/index.tsx | 387 ++ src/components/SearchHistoryDialog.tsx | 102 + src/components/SearchInput/index.tsx | 42 + src/components/TaskState/index.tsx | 48 + src/components/Terminal/index.tsx | 366 ++ .../TerminalAgentWrokSpace/index.tsx | 328 ++ src/components/ThemeProvider.tsx | 45 + src/components/Toast/creditsToast.tsx | 25 + src/components/Toast/storageToast.tsx | 19 + src/components/Toast/trafficToast.tsx | 12 + src/components/TopBar/index.css | 29 + src/components/TopBar/index.tsx | 215 + src/components/WorkFlow/MarkDown.tsx | 183 + src/components/WorkFlow/index.tsx | 411 ++ src/components/WorkFlow/node.tsx | 774 ++++ src/components/WorkSpaceMenu/index.tsx | 372 ++ src/components/ui/ShinyText/ShinyText.css | 30 + src/components/ui/ShinyText/ShinyText.tsx | 23 + src/components/ui/accordion.tsx | 55 + src/components/ui/alert.tsx | 59 + src/components/ui/alertDialog.tsx | 69 + src/components/ui/badge.tsx | 36 + src/components/ui/button.tsx | 64 + src/components/ui/card.tsx | 76 + src/components/ui/carousel.tsx | 260 ++ src/components/ui/command.tsx | 151 + src/components/ui/dialog.tsx | 122 + src/components/ui/dropdown-menu.tsx | 199 + src/components/ui/input.tsx | 22 + src/components/ui/label.tsx | 24 + src/components/ui/popover.tsx | 33 + src/components/ui/progress-install.tsx | 30 + src/components/ui/progress.tsx | 26 + src/components/ui/select.tsx | 157 + src/components/ui/separator.tsx | 29 + src/components/ui/sheet.tsx | 140 + src/components/ui/sidebar.tsx | 771 ++++ src/components/ui/skeleton.tsx | 15 + src/components/ui/sonner.tsx | 29 + src/components/ui/switch.tsx | 27 + src/components/ui/tabs.tsx | 53 + src/components/ui/tag.tsx | 56 + src/components/ui/textarea.tsx | 22 + src/components/ui/toggle-group.tsx | 59 + src/components/ui/toggle.tsx | 45 + src/components/ui/tooltip.tsx | 30 + src/components/update/index.tsx | 98 + src/demos/node.ts | 8 + src/hooks/use-app-version.tsx | 14 + src/hooks/use-mobile.tsx | 19 + src/lib/index.ts | 53 + src/lib/llm.ts | 86 + src/lib/oauth.ts | 225 + src/lib/share.ts | 21 + src/lib/utils.ts | 6 + src/main.tsx | 32 + src/pages/History.tsx | 686 +++ src/pages/Home.tsx | 275 ++ src/pages/Login.tsx | 340 ++ src/pages/NotFound.tsx | 5 + src/pages/Setting.tsx | 142 + src/pages/Setting/API.tsx | 108 + src/pages/Setting/General.tsx | 174 + src/pages/Setting/MCP.tsx | 379 ++ src/pages/Setting/MCPMarket.tsx | 441 ++ src/pages/Setting/Models.tsx | 912 ++++ src/pages/Setting/Privacy.tsx | 97 + .../Setting/components/IntegrationList.tsx | 439 ++ src/pages/Setting/components/MCPAddDialog.tsx | 157 + .../Setting/components/MCPConfigDialog.tsx | 100 + .../Setting/components/MCPDeleteDialog.tsx | 28 + src/pages/Setting/components/MCPEnvDialog.tsx | 224 + src/pages/Setting/components/MCPList.tsx | 28 + src/pages/Setting/components/MCPListItem.tsx | 106 + src/pages/Setting/components/types.ts | 21 + src/pages/Setting/components/utils.ts | 11 + src/pages/SignUp.tsx | 376 ++ src/pages/Task.tsx | 4 + src/routers/index.tsx | 70 + src/stack/client.ts | 14 + src/store/authStore.ts | 175 + src/store/chatStore.ts | 1628 +++++++ src/store/globalStore.ts | 33 + src/store/sidebarStore.ts | 15 + src/style/index.css | 330 ++ src/style/token.css | 643 +++ src/types/chatbox.d.ts | 132 + src/types/electron-updater.d.ts | 10 + src/types/electron.d.ts | 67 + src/types/index.ts | 31 + src/types/stackframe-react.d.ts | 1 + src/types/workspace.d.ts | 8 + src/vite-env.d.ts | 7 + tailwind.config.js | 646 +++ test/e2e.spec.ts | 64 + test/screenshots/e2e.png | Bin 0 -> 88980 bytes tsconfig.json | 27 + tsconfig.node.json | 10 + vite.config.ts | 98 + ....timestamp-1753782969125-f2b417d9fc657.mjs | 209 + vitest.config.ts | 8 + 1145 files changed, 102834 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/help_wanted.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/remove-old-artifacts.yml create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .vscode/.debug.script.mjs create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 README_CN.md create mode 100644 backend/.gitignore create mode 100644 backend/.python-version create mode 100644 backend/.vscode/settings.json create mode 100644 backend/README.md create mode 100644 backend/app/__init__.py create mode 100644 backend/app/command/__init__.py create mode 100644 backend/app/component/babel.py create mode 100644 backend/app/component/code.py create mode 100644 backend/app/component/command.py create mode 100644 backend/app/component/debug.py create mode 100644 backend/app/component/encrypt.py create mode 100644 backend/app/component/environment.py create mode 100644 backend/app/component/model_validation.py create mode 100644 backend/app/component/pydantic/i18n.py create mode 100644 backend/app/component/pydantic/translations/en_US.json create mode 100644 backend/app/component/pydantic/translations/zh_CN.json create mode 100644 backend/app/controller/__init__.py create mode 100644 backend/app/controller/chat_controller.py create mode 100644 backend/app/controller/model_controller.py create mode 100644 backend/app/controller/task_controller.py create mode 100644 backend/app/controller/tool_controller.py create mode 100644 backend/app/exception/exception.py create mode 100644 backend/app/exception/handler.py create mode 100644 backend/app/middleware/__init__.py create mode 100644 backend/app/model/chat.py create mode 100644 backend/app/service/chat_service.py create mode 100644 backend/app/service/task.py create mode 100644 backend/app/utils/agent.py create mode 100644 backend/app/utils/listen/toolkit_listen.py create mode 100644 backend/app/utils/server/sync_step.py create mode 100644 backend/app/utils/single_agent_worker.py create mode 100644 backend/app/utils/toolkit/abstract_toolkit.py create mode 100644 backend/app/utils/toolkit/audio_analysis_toolkit.py create mode 100644 backend/app/utils/toolkit/code_execution_toolkit.py create mode 100644 backend/app/utils/toolkit/craw4ai_toolkit.py create mode 100644 backend/app/utils/toolkit/excel_toolkit.py create mode 100644 backend/app/utils/toolkit/file_write_toolkit.py create mode 100644 backend/app/utils/toolkit/github_toolkit.py create mode 100644 backend/app/utils/toolkit/google_calendar_toolkit.py create mode 100644 backend/app/utils/toolkit/google_drive_mcp_toolkit.py create mode 100644 backend/app/utils/toolkit/google_gmail_mcp_toolkit.py create mode 100644 backend/app/utils/toolkit/human_toolkit.py create mode 100644 backend/app/utils/toolkit/hybrid_browser_python_toolkit.py create mode 100644 backend/app/utils/toolkit/hybrid_browser_toolkit.py create mode 100644 backend/app/utils/toolkit/image_analysis_toolkit.py create mode 100644 backend/app/utils/toolkit/linkedin_toolkit.py create mode 100644 backend/app/utils/toolkit/markitdown_toolkit.py create mode 100644 backend/app/utils/toolkit/mcp_search_toolkit.py create mode 100644 backend/app/utils/toolkit/note_taking_toolkit.py create mode 100644 backend/app/utils/toolkit/notion_mcp_toolkit.py create mode 100644 backend/app/utils/toolkit/notion_toolkit.py create mode 100644 backend/app/utils/toolkit/openai_image_toolkit.py create mode 100644 backend/app/utils/toolkit/pptx_toolkit.py create mode 100644 backend/app/utils/toolkit/pyautogui_toolkit.py create mode 100644 backend/app/utils/toolkit/reddit_toolkit.py create mode 100644 backend/app/utils/toolkit/screenshot_toolkit.py create mode 100644 backend/app/utils/toolkit/search_toolkit.py create mode 100644 backend/app/utils/toolkit/slack_toolkit.py create mode 100644 backend/app/utils/toolkit/terminal_toolkit.py create mode 100644 backend/app/utils/toolkit/thinking_toolkit.py create mode 100644 backend/app/utils/toolkit/twitter_toolkit.py create mode 100644 backend/app/utils/toolkit/video_analysis_toolkit.py create mode 100644 backend/app/utils/toolkit/video_download_toolkit.py create mode 100644 backend/app/utils/toolkit/web_deploy_toolkit.py create mode 100644 backend/app/utils/toolkit/whatsapp_toolkit.py create mode 100644 backend/app/utils/workforce.py create mode 100644 backend/babel.cfg create mode 100644 backend/cli.py create mode 100644 backend/lang/en_US/LC_MESSAGES/messages.po create mode 100644 backend/lang/zh_CN/LC_MESSAGES/messages.po create mode 100644 backend/main.py create mode 100644 backend/messages.pot create mode 100644 backend/pyproject.toml create mode 100644 backend/uv.lock create mode 100644 build/icon.icns create mode 100644 build/icon.ico create mode 100644 build/icon.png create mode 100644 build/installer.nsh create mode 100644 components.json create mode 100644 config/notarize.cjs create mode 100644 docs/concepts.md create mode 100644 docs/docs.json create mode 100644 docs/features/add_mcps.md create mode 100644 docs/features/customised_workers.md create mode 100644 docs/features/dynamic_workforce.md create mode 100644 docs/features/human-in-the-loop.md create mode 100644 docs/features/local_models.md create mode 100644 docs/get_started/installation.md create mode 100644 docs/get_started/quick_start.md create mode 100644 docs/get_started/welcome.md create mode 100644 docs/support.md create mode 100644 electron-builder.json create mode 100644 electron/electron-env.d.ts create mode 100644 electron/electron.d.ts create mode 100644 electron/main/copy.ts create mode 100644 electron/main/fileReader.ts create mode 100644 electron/main/index.ts create mode 100644 electron/main/init.ts create mode 100644 electron/main/update.ts create mode 100644 electron/main/utils/envUtil.ts create mode 100644 electron/main/utils/mcpConfig.ts create mode 100644 electron/main/utils/process.ts create mode 100644 electron/main/webview.ts create mode 100644 electron/preload/index.ts create mode 100644 entitlements.mac.plist create mode 100644 index.html create mode 100644 package.json create mode 100644 package/@stackframe/react/CHANGELOG.md create mode 100644 package/@stackframe/react/LICENSE create mode 100644 package/@stackframe/react/README.md create mode 100644 package/@stackframe/react/dist/components-page/account-settings.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/active-sessions/active-sessions-page.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/active-sessions/active-sessions-page.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/api-keys/api-keys-page.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/api-keys/api-keys-page.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/editable-text.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/editable-text.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/email-and-auth/email-and-auth-page.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/email-and-auth/email-and-auth-page.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/email-and-auth/emails-section.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/email-and-auth/emails-section.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/email-and-auth/mfa-section.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/email-and-auth/mfa-section.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/email-and-auth/otp-section.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/email-and-auth/otp-section.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/email-and-auth/passkey-section.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/email-and-auth/passkey-section.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/email-and-auth/password-section.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/email-and-auth/password-section.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/page-layout.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/page-layout.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/profile-page/profile-page.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/profile-page/profile-page.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/section.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/section.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/settings/delete-account-section.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/settings/delete-account-section.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/settings/settings-page.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/settings/settings-page.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/settings/sign-out-section.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/settings/sign-out-section.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/leave-team-section.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/leave-team-section.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/team-api-keys-section.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/team-api-keys-section.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/team-creation-page.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/team-creation-page.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/team-display-name-section.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/team-display-name-section.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/team-member-invitation-section.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/team-member-invitation-section.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/team-member-list-section.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/team-member-list-section.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/team-page.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/team-page.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/team-profile-image-section.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/team-profile-image-section.js.map create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/team-profile-user-section.js create mode 100644 package/@stackframe/react/dist/components-page/account-settings/teams/team-profile-user-section.js.map create mode 100644 package/@stackframe/react/dist/components-page/auth-page.js create mode 100644 package/@stackframe/react/dist/components-page/auth-page.js.map create mode 100644 package/@stackframe/react/dist/components-page/cli-auth-confirm.js create mode 100644 package/@stackframe/react/dist/components-page/cli-auth-confirm.js.map create mode 100644 package/@stackframe/react/dist/components-page/email-verification.js create mode 100644 package/@stackframe/react/dist/components-page/email-verification.js.map create mode 100644 package/@stackframe/react/dist/components-page/error-page.js create mode 100644 package/@stackframe/react/dist/components-page/error-page.js.map create mode 100644 package/@stackframe/react/dist/components-page/forgot-password.js create mode 100644 package/@stackframe/react/dist/components-page/forgot-password.js.map create mode 100644 package/@stackframe/react/dist/components-page/magic-link-callback.js create mode 100644 package/@stackframe/react/dist/components-page/magic-link-callback.js.map create mode 100644 package/@stackframe/react/dist/components-page/oauth-callback.js create mode 100644 package/@stackframe/react/dist/components-page/oauth-callback.js.map create mode 100644 package/@stackframe/react/dist/components-page/password-reset.js create mode 100644 package/@stackframe/react/dist/components-page/password-reset.js.map create mode 100644 package/@stackframe/react/dist/components-page/section.js create mode 100644 package/@stackframe/react/dist/components-page/section.js.map create mode 100644 package/@stackframe/react/dist/components-page/sign-in.js create mode 100644 package/@stackframe/react/dist/components-page/sign-in.js.map create mode 100644 package/@stackframe/react/dist/components-page/sign-out.js create mode 100644 package/@stackframe/react/dist/components-page/sign-out.js.map create mode 100644 package/@stackframe/react/dist/components-page/sign-up.js create mode 100644 package/@stackframe/react/dist/components-page/sign-up.js.map create mode 100644 package/@stackframe/react/dist/components-page/stack-handler.js create mode 100644 package/@stackframe/react/dist/components-page/stack-handler.js.map create mode 100644 package/@stackframe/react/dist/components-page/team-creation.js create mode 100644 package/@stackframe/react/dist/components-page/team-creation.js.map create mode 100644 package/@stackframe/react/dist/components-page/team-invitation.js create mode 100644 package/@stackframe/react/dist/components-page/team-invitation.js.map create mode 100644 package/@stackframe/react/dist/components/api-key-dialogs.js create mode 100644 package/@stackframe/react/dist/components/api-key-dialogs.js.map create mode 100644 package/@stackframe/react/dist/components/api-key-table.js create mode 100644 package/@stackframe/react/dist/components/api-key-table.js.map create mode 100644 package/@stackframe/react/dist/components/credential-sign-in.js create mode 100644 package/@stackframe/react/dist/components/credential-sign-in.js.map create mode 100644 package/@stackframe/react/dist/components/credential-sign-up.js create mode 100644 package/@stackframe/react/dist/components/credential-sign-up.js.map create mode 100644 package/@stackframe/react/dist/components/elements/form-warning.js create mode 100644 package/@stackframe/react/dist/components/elements/form-warning.js.map create mode 100644 package/@stackframe/react/dist/components/elements/maybe-full-page.js create mode 100644 package/@stackframe/react/dist/components/elements/maybe-full-page.js.map create mode 100644 package/@stackframe/react/dist/components/elements/separator-with-text.js create mode 100644 package/@stackframe/react/dist/components/elements/separator-with-text.js.map create mode 100644 package/@stackframe/react/dist/components/elements/sidebar-layout.js create mode 100644 package/@stackframe/react/dist/components/elements/sidebar-layout.js.map create mode 100644 package/@stackframe/react/dist/components/elements/ssr-layout-effect.js create mode 100644 package/@stackframe/react/dist/components/elements/ssr-layout-effect.js.map create mode 100644 package/@stackframe/react/dist/components/elements/user-avatar.js create mode 100644 package/@stackframe/react/dist/components/elements/user-avatar.js.map create mode 100644 package/@stackframe/react/dist/components/iframe-preventer.js create mode 100644 package/@stackframe/react/dist/components/iframe-preventer.js.map create mode 100644 package/@stackframe/react/dist/components/link.js create mode 100644 package/@stackframe/react/dist/components/link.js.map create mode 100644 package/@stackframe/react/dist/components/magic-link-sign-in.js create mode 100644 package/@stackframe/react/dist/components/magic-link-sign-in.js.map create mode 100644 package/@stackframe/react/dist/components/message-cards/known-error-message-card.js create mode 100644 package/@stackframe/react/dist/components/message-cards/known-error-message-card.js.map create mode 100644 package/@stackframe/react/dist/components/message-cards/message-card.js create mode 100644 package/@stackframe/react/dist/components/message-cards/message-card.js.map create mode 100644 package/@stackframe/react/dist/components/message-cards/predefined-message-card.js create mode 100644 package/@stackframe/react/dist/components/message-cards/predefined-message-card.js.map create mode 100644 package/@stackframe/react/dist/components/oauth-button-group.js create mode 100644 package/@stackframe/react/dist/components/oauth-button-group.js.map create mode 100644 package/@stackframe/react/dist/components/oauth-button.js create mode 100644 package/@stackframe/react/dist/components/oauth-button.js.map create mode 100644 package/@stackframe/react/dist/components/passkey-button.js create mode 100644 package/@stackframe/react/dist/components/passkey-button.js.map create mode 100644 package/@stackframe/react/dist/components/profile-image-editor.js create mode 100644 package/@stackframe/react/dist/components/profile-image-editor.js.map create mode 100644 package/@stackframe/react/dist/components/selected-team-switcher.js create mode 100644 package/@stackframe/react/dist/components/selected-team-switcher.js.map create mode 100644 package/@stackframe/react/dist/components/team-icon.js create mode 100644 package/@stackframe/react/dist/components/team-icon.js.map create mode 100644 package/@stackframe/react/dist/components/user-button.js create mode 100644 package/@stackframe/react/dist/components/user-button.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/active-sessions/active-sessions-page.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/active-sessions/active-sessions-page.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/api-keys/api-keys-page.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/api-keys/api-keys-page.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/editable-text.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/editable-text.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/email-and-auth/email-and-auth-page.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/email-and-auth/email-and-auth-page.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/email-and-auth/emails-section.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/email-and-auth/emails-section.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/email-and-auth/mfa-section.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/email-and-auth/mfa-section.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/email-and-auth/otp-section.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/email-and-auth/otp-section.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/email-and-auth/passkey-section.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/email-and-auth/passkey-section.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/email-and-auth/password-section.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/email-and-auth/password-section.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/page-layout.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/page-layout.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/profile-page/profile-page.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/profile-page/profile-page.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/section.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/section.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/settings/delete-account-section.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/settings/delete-account-section.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/settings/settings-page.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/settings/settings-page.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/settings/sign-out-section.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/settings/sign-out-section.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/leave-team-section.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/leave-team-section.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/team-api-keys-section.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/team-api-keys-section.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/team-creation-page.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/team-creation-page.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/team-display-name-section.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/team-display-name-section.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/team-member-invitation-section.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/team-member-invitation-section.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/team-member-list-section.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/team-member-list-section.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/team-page.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/team-page.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/team-profile-image-section.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/team-profile-image-section.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/team-profile-user-section.js create mode 100644 package/@stackframe/react/dist/esm/components-page/account-settings/teams/team-profile-user-section.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/auth-page.js create mode 100644 package/@stackframe/react/dist/esm/components-page/auth-page.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/cli-auth-confirm.js create mode 100644 package/@stackframe/react/dist/esm/components-page/cli-auth-confirm.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/email-verification.js create mode 100644 package/@stackframe/react/dist/esm/components-page/email-verification.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/error-page.js create mode 100644 package/@stackframe/react/dist/esm/components-page/error-page.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/forgot-password.js create mode 100644 package/@stackframe/react/dist/esm/components-page/forgot-password.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/magic-link-callback.js create mode 100644 package/@stackframe/react/dist/esm/components-page/magic-link-callback.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/oauth-callback.js create mode 100644 package/@stackframe/react/dist/esm/components-page/oauth-callback.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/password-reset.js create mode 100644 package/@stackframe/react/dist/esm/components-page/password-reset.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/section.js create mode 100644 package/@stackframe/react/dist/esm/components-page/section.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/sign-in.js create mode 100644 package/@stackframe/react/dist/esm/components-page/sign-in.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/sign-out.js create mode 100644 package/@stackframe/react/dist/esm/components-page/sign-out.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/sign-up.js create mode 100644 package/@stackframe/react/dist/esm/components-page/sign-up.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/stack-handler.js create mode 100644 package/@stackframe/react/dist/esm/components-page/stack-handler.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/team-creation.js create mode 100644 package/@stackframe/react/dist/esm/components-page/team-creation.js.map create mode 100644 package/@stackframe/react/dist/esm/components-page/team-invitation.js create mode 100644 package/@stackframe/react/dist/esm/components-page/team-invitation.js.map create mode 100644 package/@stackframe/react/dist/esm/components/api-key-dialogs.js create mode 100644 package/@stackframe/react/dist/esm/components/api-key-dialogs.js.map create mode 100644 package/@stackframe/react/dist/esm/components/api-key-table.js create mode 100644 package/@stackframe/react/dist/esm/components/api-key-table.js.map create mode 100644 package/@stackframe/react/dist/esm/components/credential-sign-in.js create mode 100644 package/@stackframe/react/dist/esm/components/credential-sign-in.js.map create mode 100644 package/@stackframe/react/dist/esm/components/credential-sign-up.js create mode 100644 package/@stackframe/react/dist/esm/components/credential-sign-up.js.map create mode 100644 package/@stackframe/react/dist/esm/components/elements/form-warning.js create mode 100644 package/@stackframe/react/dist/esm/components/elements/form-warning.js.map create mode 100644 package/@stackframe/react/dist/esm/components/elements/maybe-full-page.js create mode 100644 package/@stackframe/react/dist/esm/components/elements/maybe-full-page.js.map create mode 100644 package/@stackframe/react/dist/esm/components/elements/separator-with-text.js create mode 100644 package/@stackframe/react/dist/esm/components/elements/separator-with-text.js.map create mode 100644 package/@stackframe/react/dist/esm/components/elements/sidebar-layout.js create mode 100644 package/@stackframe/react/dist/esm/components/elements/sidebar-layout.js.map create mode 100644 package/@stackframe/react/dist/esm/components/elements/ssr-layout-effect.js create mode 100644 package/@stackframe/react/dist/esm/components/elements/ssr-layout-effect.js.map create mode 100644 package/@stackframe/react/dist/esm/components/elements/user-avatar.js create mode 100644 package/@stackframe/react/dist/esm/components/elements/user-avatar.js.map create mode 100644 package/@stackframe/react/dist/esm/components/iframe-preventer.js create mode 100644 package/@stackframe/react/dist/esm/components/iframe-preventer.js.map create mode 100644 package/@stackframe/react/dist/esm/components/link.js create mode 100644 package/@stackframe/react/dist/esm/components/link.js.map create mode 100644 package/@stackframe/react/dist/esm/components/magic-link-sign-in.js create mode 100644 package/@stackframe/react/dist/esm/components/magic-link-sign-in.js.map create mode 100644 package/@stackframe/react/dist/esm/components/message-cards/known-error-message-card.js create mode 100644 package/@stackframe/react/dist/esm/components/message-cards/known-error-message-card.js.map create mode 100644 package/@stackframe/react/dist/esm/components/message-cards/message-card.js create mode 100644 package/@stackframe/react/dist/esm/components/message-cards/message-card.js.map create mode 100644 package/@stackframe/react/dist/esm/components/message-cards/predefined-message-card.js create mode 100644 package/@stackframe/react/dist/esm/components/message-cards/predefined-message-card.js.map create mode 100644 package/@stackframe/react/dist/esm/components/oauth-button-group.js create mode 100644 package/@stackframe/react/dist/esm/components/oauth-button-group.js.map create mode 100644 package/@stackframe/react/dist/esm/components/oauth-button.js create mode 100644 package/@stackframe/react/dist/esm/components/oauth-button.js.map create mode 100644 package/@stackframe/react/dist/esm/components/passkey-button.js create mode 100644 package/@stackframe/react/dist/esm/components/passkey-button.js.map create mode 100644 package/@stackframe/react/dist/esm/components/profile-image-editor.js create mode 100644 package/@stackframe/react/dist/esm/components/profile-image-editor.js.map create mode 100644 package/@stackframe/react/dist/esm/components/selected-team-switcher.js create mode 100644 package/@stackframe/react/dist/esm/components/selected-team-switcher.js.map create mode 100644 package/@stackframe/react/dist/esm/components/team-icon.js create mode 100644 package/@stackframe/react/dist/esm/components/team-icon.js.map create mode 100644 package/@stackframe/react/dist/esm/components/user-button.js create mode 100644 package/@stackframe/react/dist/esm/components/user-button.js.map create mode 100644 package/@stackframe/react/dist/esm/generated/global-css.js create mode 100644 package/@stackframe/react/dist/esm/generated/global-css.js.map create mode 100644 package/@stackframe/react/dist/esm/generated/quetzal-translations.js create mode 100644 package/@stackframe/react/dist/esm/generated/quetzal-translations.js.map create mode 100644 package/@stackframe/react/dist/esm/global.d.js create mode 100644 package/@stackframe/react/dist/esm/global.d.js.map create mode 100644 package/@stackframe/react/dist/esm/index.js create mode 100644 package/@stackframe/react/dist/esm/index.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/auth.js create mode 100644 package/@stackframe/react/dist/esm/lib/auth.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/cookie.js create mode 100644 package/@stackframe/react/dist/esm/lib/cookie.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/hooks.js create mode 100644 package/@stackframe/react/dist/esm/lib/hooks.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/api-keys/index.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/api-keys/index.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/implementations/common.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/implementations/common.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/implementations/index.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/implementations/index.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/index.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/index.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/interfaces/admin-app.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/interfaces/admin-app.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/interfaces/client-app.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/interfaces/client-app.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/interfaces/server-app.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/apps/interfaces/server-app.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/common.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/common.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/connected-accounts/index.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/connected-accounts/index.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/contact-channels/index.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/contact-channels/index.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/email-templates/index.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/email-templates/index.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/email/index.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/email/index.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/index.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/index.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/internal-api-keys/index.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/internal-api-keys/index.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/permissions/index.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/permissions/index.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/project-configs/index.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/project-configs/index.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/projects/index.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/projects/index.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/teams/index.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/teams/index.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/users/index.js create mode 100644 package/@stackframe/react/dist/esm/lib/stack-app/users/index.js.map create mode 100644 package/@stackframe/react/dist/esm/lib/translations.js create mode 100644 package/@stackframe/react/dist/esm/lib/translations.js.map create mode 100644 package/@stackframe/react/dist/esm/providers/stack-provider-client.js create mode 100644 package/@stackframe/react/dist/esm/providers/stack-provider-client.js.map create mode 100644 package/@stackframe/react/dist/esm/providers/stack-provider.js create mode 100644 package/@stackframe/react/dist/esm/providers/stack-provider.js.map create mode 100644 package/@stackframe/react/dist/esm/providers/theme-provider.js create mode 100644 package/@stackframe/react/dist/esm/providers/theme-provider.js.map create mode 100644 package/@stackframe/react/dist/esm/providers/translation-provider-client.js create mode 100644 package/@stackframe/react/dist/esm/providers/translation-provider-client.js.map create mode 100644 package/@stackframe/react/dist/esm/providers/translation-provider.js create mode 100644 package/@stackframe/react/dist/esm/providers/translation-provider.js.map create mode 100644 package/@stackframe/react/dist/esm/utils/browser-script.js create mode 100644 package/@stackframe/react/dist/esm/utils/browser-script.js.map create mode 100644 package/@stackframe/react/dist/esm/utils/constants.js create mode 100644 package/@stackframe/react/dist/esm/utils/constants.js.map create mode 100644 package/@stackframe/react/dist/esm/utils/url.js create mode 100644 package/@stackframe/react/dist/esm/utils/url.js.map create mode 100644 package/@stackframe/react/dist/generated/global-css.js create mode 100644 package/@stackframe/react/dist/generated/global-css.js.map create mode 100644 package/@stackframe/react/dist/generated/quetzal-translations.js create mode 100644 package/@stackframe/react/dist/generated/quetzal-translations.js.map create mode 100644 package/@stackframe/react/dist/global.d.js create mode 100644 package/@stackframe/react/dist/global.d.js.map create mode 100644 package/@stackframe/react/dist/index.d.mts create mode 100644 package/@stackframe/react/dist/index.d.ts create mode 100644 package/@stackframe/react/dist/index.js create mode 100644 package/@stackframe/react/dist/index.js.map create mode 100644 package/@stackframe/react/dist/lib/auth.js create mode 100644 package/@stackframe/react/dist/lib/auth.js.map create mode 100644 package/@stackframe/react/dist/lib/cookie.js create mode 100644 package/@stackframe/react/dist/lib/cookie.js.map create mode 100644 package/@stackframe/react/dist/lib/hooks.js create mode 100644 package/@stackframe/react/dist/lib/hooks.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/api-keys/index.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/api-keys/index.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/implementations/admin-app-impl.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/implementations/admin-app-impl.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/implementations/client-app-impl.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/implementations/client-app-impl.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/implementations/common.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/implementations/common.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/implementations/index.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/implementations/index.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/implementations/server-app-impl.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/implementations/server-app-impl.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/index.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/index.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/interfaces/admin-app.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/interfaces/admin-app.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/interfaces/client-app.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/interfaces/client-app.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/interfaces/server-app.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/apps/interfaces/server-app.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/common.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/common.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/connected-accounts/index.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/connected-accounts/index.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/contact-channels/index.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/contact-channels/index.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/email-templates/index.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/email-templates/index.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/email/index.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/email/index.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/index.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/index.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/internal-api-keys/index.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/internal-api-keys/index.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/permissions/index.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/permissions/index.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/project-configs/index.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/project-configs/index.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/projects/index.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/projects/index.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/teams/index.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/teams/index.js.map create mode 100644 package/@stackframe/react/dist/lib/stack-app/users/index.js create mode 100644 package/@stackframe/react/dist/lib/stack-app/users/index.js.map create mode 100644 package/@stackframe/react/dist/lib/translations.js create mode 100644 package/@stackframe/react/dist/lib/translations.js.map create mode 100644 package/@stackframe/react/dist/providers/stack-provider-client.js create mode 100644 package/@stackframe/react/dist/providers/stack-provider-client.js.map create mode 100644 package/@stackframe/react/dist/providers/stack-provider.js create mode 100644 package/@stackframe/react/dist/providers/stack-provider.js.map create mode 100644 package/@stackframe/react/dist/providers/theme-provider.js create mode 100644 package/@stackframe/react/dist/providers/theme-provider.js.map create mode 100644 package/@stackframe/react/dist/providers/translation-provider-client.js create mode 100644 package/@stackframe/react/dist/providers/translation-provider-client.js.map create mode 100644 package/@stackframe/react/dist/providers/translation-provider.js create mode 100644 package/@stackframe/react/dist/providers/translation-provider.js.map create mode 100644 package/@stackframe/react/dist/utils/browser-script.js create mode 100644 package/@stackframe/react/dist/utils/browser-script.js.map create mode 100644 package/@stackframe/react/dist/utils/constants.js create mode 100644 package/@stackframe/react/dist/utils/constants.js.map create mode 100644 package/@stackframe/react/dist/utils/url.js create mode 100644 package/@stackframe/react/dist/utils/url.js.map create mode 100644 package/@stackframe/react/package.json create mode 100644 package/@stackframe/stack-shared/CHANGELOG.md create mode 100644 package/@stackframe/stack-shared/LICENSE create mode 100644 package/@stackframe/stack-shared/dist/config/format.d.mts create mode 100644 package/@stackframe/stack-shared/dist/config/format.d.ts create mode 100644 package/@stackframe/stack-shared/dist/config/format.js create mode 100644 package/@stackframe/stack-shared/dist/config/format.js.map create mode 100644 package/@stackframe/stack-shared/dist/config/schema.d.mts create mode 100644 package/@stackframe/stack-shared/dist/config/schema.d.ts create mode 100644 package/@stackframe/stack-shared/dist/config/schema.js create mode 100644 package/@stackframe/stack-shared/dist/config/schema.js.map create mode 100644 package/@stackframe/stack-shared/dist/crud.d.mts create mode 100644 package/@stackframe/stack-shared/dist/crud.d.ts create mode 100644 package/@stackframe/stack-shared/dist/crud.js create mode 100644 package/@stackframe/stack-shared/dist/crud.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/config/format.js create mode 100644 package/@stackframe/stack-shared/dist/esm/config/format.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/config/schema.js create mode 100644 package/@stackframe/stack-shared/dist/esm/config/schema.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/crud.js create mode 100644 package/@stackframe/stack-shared/dist/esm/crud.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/global.d.js create mode 100644 package/@stackframe/stack-shared/dist/esm/global.d.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/helpers/password.js create mode 100644 package/@stackframe/stack-shared/dist/esm/helpers/password.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/helpers/production-mode.js create mode 100644 package/@stackframe/stack-shared/dist/esm/helpers/production-mode.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/hooks/use-async-callback.js create mode 100644 package/@stackframe/stack-shared/dist/esm/hooks/use-async-callback.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/hooks/use-async-external-store.js create mode 100644 package/@stackframe/stack-shared/dist/esm/hooks/use-async-external-store.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/hooks/use-hash.js create mode 100644 package/@stackframe/stack-shared/dist/esm/hooks/use-hash.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/hooks/use-strict-memo.js create mode 100644 package/@stackframe/stack-shared/dist/esm/hooks/use-strict-memo.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/index.js create mode 100644 package/@stackframe/stack-shared/dist/esm/index.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/adminInterface.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/adminInterface.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/clientInterface.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/clientInterface.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/contact-channels.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/contact-channels.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/current-user.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/current-user.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/email-templates.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/email-templates.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/emails.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/emails.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/internal-api-keys.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/internal-api-keys.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/oauth.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/oauth.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/project-api-keys.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/project-api-keys.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/project-permissions.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/project-permissions.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/projects.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/projects.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/sessions.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/sessions.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/svix-token.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/svix-token.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/team-invitation-details.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/team-invitation-details.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/team-invitation.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/team-invitation.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/team-member-profiles.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/team-member-profiles.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/team-memberships.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/team-memberships.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/team-permissions.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/team-permissions.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/teams.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/teams.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/users.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/crud/users.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/serverInterface.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/serverInterface.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/webhooks.js create mode 100644 package/@stackframe/stack-shared/dist/esm/interface/webhooks.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/known-errors.js create mode 100644 package/@stackframe/stack-shared/dist/esm/known-errors.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/schema-fields.js create mode 100644 package/@stackframe/stack-shared/dist/esm/schema-fields.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/sessions.js create mode 100644 package/@stackframe/stack-shared/dist/esm/sessions.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/api-keys.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/api-keys.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/arrays.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/arrays.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/base64.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/base64.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/booleans.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/booleans.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/browser-compat.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/browser-compat.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/bytes.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/bytes.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/caches.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/caches.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/compile-time.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/compile-time.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/crypto.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/crypto.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/dates.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/dates.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/dom.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/dom.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/env.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/env.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/errors.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/errors.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/fs.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/fs.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/functions.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/functions.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/geo.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/geo.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/globals.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/globals.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/hashes.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/hashes.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/html.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/html.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/http.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/http.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/ips.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/ips.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/json.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/json.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/jwt.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/jwt.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/locks.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/locks.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/maps.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/maps.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/math.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/math.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/node-http.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/node-http.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/numbers.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/numbers.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/oauth.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/oauth.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/objects.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/objects.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/passkey.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/passkey.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/promises.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/promises.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/proxies.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/proxies.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/react.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/react.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/results.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/results.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/sentry.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/sentry.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/stores.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/stores.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/strings.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/strings.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/strings.nicify.test.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/strings.nicify.test.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/types.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/types.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/unicode.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/unicode.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/urls.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/urls.js.map create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/uuids.js create mode 100644 package/@stackframe/stack-shared/dist/esm/utils/uuids.js.map create mode 100644 package/@stackframe/stack-shared/dist/global.d.d.mts create mode 100644 package/@stackframe/stack-shared/dist/global.d.d.ts create mode 100644 package/@stackframe/stack-shared/dist/global.d.js create mode 100644 package/@stackframe/stack-shared/dist/global.d.js.map create mode 100644 package/@stackframe/stack-shared/dist/helpers/password.d.mts create mode 100644 package/@stackframe/stack-shared/dist/helpers/password.d.ts create mode 100644 package/@stackframe/stack-shared/dist/helpers/password.js create mode 100644 package/@stackframe/stack-shared/dist/helpers/password.js.map create mode 100644 package/@stackframe/stack-shared/dist/helpers/production-mode.d.mts create mode 100644 package/@stackframe/stack-shared/dist/helpers/production-mode.d.ts create mode 100644 package/@stackframe/stack-shared/dist/helpers/production-mode.js create mode 100644 package/@stackframe/stack-shared/dist/helpers/production-mode.js.map create mode 100644 package/@stackframe/stack-shared/dist/hooks/use-async-callback.d.mts create mode 100644 package/@stackframe/stack-shared/dist/hooks/use-async-callback.d.ts create mode 100644 package/@stackframe/stack-shared/dist/hooks/use-async-callback.js create mode 100644 package/@stackframe/stack-shared/dist/hooks/use-async-callback.js.map create mode 100644 package/@stackframe/stack-shared/dist/hooks/use-async-external-store.d.mts create mode 100644 package/@stackframe/stack-shared/dist/hooks/use-async-external-store.d.ts create mode 100644 package/@stackframe/stack-shared/dist/hooks/use-async-external-store.js create mode 100644 package/@stackframe/stack-shared/dist/hooks/use-async-external-store.js.map create mode 100644 package/@stackframe/stack-shared/dist/hooks/use-hash.d.mts create mode 100644 package/@stackframe/stack-shared/dist/hooks/use-hash.d.ts create mode 100644 package/@stackframe/stack-shared/dist/hooks/use-hash.js create mode 100644 package/@stackframe/stack-shared/dist/hooks/use-hash.js.map create mode 100644 package/@stackframe/stack-shared/dist/hooks/use-strict-memo.d.mts create mode 100644 package/@stackframe/stack-shared/dist/hooks/use-strict-memo.d.ts create mode 100644 package/@stackframe/stack-shared/dist/hooks/use-strict-memo.js create mode 100644 package/@stackframe/stack-shared/dist/hooks/use-strict-memo.js.map create mode 100644 package/@stackframe/stack-shared/dist/index.d.mts create mode 100644 package/@stackframe/stack-shared/dist/index.d.ts create mode 100644 package/@stackframe/stack-shared/dist/index.js create mode 100644 package/@stackframe/stack-shared/dist/index.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/adminInterface.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/adminInterface.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/adminInterface.js create mode 100644 package/@stackframe/stack-shared/dist/interface/adminInterface.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/clientInterface.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/clientInterface.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/clientInterface.js create mode 100644 package/@stackframe/stack-shared/dist/interface/clientInterface.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/contact-channels.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/contact-channels.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/contact-channels.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/contact-channels.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/current-user.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/current-user.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/current-user.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/current-user.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/email-templates.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/email-templates.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/email-templates.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/email-templates.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/emails.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/emails.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/emails.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/emails.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/internal-api-keys.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/internal-api-keys.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/internal-api-keys.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/internal-api-keys.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/oauth.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/oauth.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/oauth.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/oauth.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/project-api-keys.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/project-api-keys.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/project-api-keys.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/project-api-keys.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/project-permissions.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/project-permissions.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/project-permissions.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/project-permissions.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/projects.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/projects.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/projects.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/projects.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/sessions.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/sessions.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/sessions.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/sessions.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/svix-token.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/svix-token.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/svix-token.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/svix-token.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-invitation-details.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-invitation-details.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-invitation-details.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-invitation-details.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-invitation.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-invitation.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-invitation.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-invitation.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-member-profiles.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-member-profiles.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-member-profiles.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-member-profiles.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-memberships.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-memberships.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-memberships.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-memberships.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-permissions.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-permissions.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-permissions.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/team-permissions.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/teams.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/teams.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/teams.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/teams.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/users.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/users.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/users.js create mode 100644 package/@stackframe/stack-shared/dist/interface/crud/users.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/serverInterface.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/serverInterface.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/serverInterface.js create mode 100644 package/@stackframe/stack-shared/dist/interface/serverInterface.js.map create mode 100644 package/@stackframe/stack-shared/dist/interface/webhooks.d.mts create mode 100644 package/@stackframe/stack-shared/dist/interface/webhooks.d.ts create mode 100644 package/@stackframe/stack-shared/dist/interface/webhooks.js create mode 100644 package/@stackframe/stack-shared/dist/interface/webhooks.js.map create mode 100644 package/@stackframe/stack-shared/dist/known-errors.d.mts create mode 100644 package/@stackframe/stack-shared/dist/known-errors.d.ts create mode 100644 package/@stackframe/stack-shared/dist/known-errors.js create mode 100644 package/@stackframe/stack-shared/dist/known-errors.js.map create mode 100644 package/@stackframe/stack-shared/dist/schema-fields.d.mts create mode 100644 package/@stackframe/stack-shared/dist/schema-fields.d.ts create mode 100644 package/@stackframe/stack-shared/dist/schema-fields.js create mode 100644 package/@stackframe/stack-shared/dist/schema-fields.js.map create mode 100644 package/@stackframe/stack-shared/dist/sessions.d.mts create mode 100644 package/@stackframe/stack-shared/dist/sessions.d.ts create mode 100644 package/@stackframe/stack-shared/dist/sessions.js create mode 100644 package/@stackframe/stack-shared/dist/sessions.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/api-keys.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/api-keys.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/api-keys.js create mode 100644 package/@stackframe/stack-shared/dist/utils/api-keys.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/arrays.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/arrays.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/arrays.js create mode 100644 package/@stackframe/stack-shared/dist/utils/arrays.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/base64.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/base64.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/base64.js create mode 100644 package/@stackframe/stack-shared/dist/utils/base64.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/booleans.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/booleans.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/booleans.js create mode 100644 package/@stackframe/stack-shared/dist/utils/booleans.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/browser-compat.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/browser-compat.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/browser-compat.js create mode 100644 package/@stackframe/stack-shared/dist/utils/browser-compat.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/bytes.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/bytes.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/bytes.js create mode 100644 package/@stackframe/stack-shared/dist/utils/bytes.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/caches.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/caches.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/caches.js create mode 100644 package/@stackframe/stack-shared/dist/utils/caches.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/compile-time.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/compile-time.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/compile-time.js create mode 100644 package/@stackframe/stack-shared/dist/utils/compile-time.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/crypto.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/crypto.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/crypto.js create mode 100644 package/@stackframe/stack-shared/dist/utils/crypto.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/dates.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/dates.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/dates.js create mode 100644 package/@stackframe/stack-shared/dist/utils/dates.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/dom.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/dom.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/dom.js create mode 100644 package/@stackframe/stack-shared/dist/utils/dom.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/env.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/env.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/env.js create mode 100644 package/@stackframe/stack-shared/dist/utils/env.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/errors.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/errors.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/errors.js create mode 100644 package/@stackframe/stack-shared/dist/utils/errors.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/fs.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/fs.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/fs.js create mode 100644 package/@stackframe/stack-shared/dist/utils/fs.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/functions.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/functions.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/functions.js create mode 100644 package/@stackframe/stack-shared/dist/utils/functions.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/geo.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/geo.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/geo.js create mode 100644 package/@stackframe/stack-shared/dist/utils/geo.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/globals.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/globals.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/globals.js create mode 100644 package/@stackframe/stack-shared/dist/utils/globals.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/hashes.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/hashes.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/hashes.js create mode 100644 package/@stackframe/stack-shared/dist/utils/hashes.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/html.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/html.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/html.js create mode 100644 package/@stackframe/stack-shared/dist/utils/html.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/http.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/http.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/http.js create mode 100644 package/@stackframe/stack-shared/dist/utils/http.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/ips.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/ips.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/ips.js create mode 100644 package/@stackframe/stack-shared/dist/utils/ips.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/json.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/json.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/json.js create mode 100644 package/@stackframe/stack-shared/dist/utils/json.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/jwt.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/jwt.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/jwt.js create mode 100644 package/@stackframe/stack-shared/dist/utils/jwt.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/locks.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/locks.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/locks.js create mode 100644 package/@stackframe/stack-shared/dist/utils/locks.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/maps.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/maps.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/maps.js create mode 100644 package/@stackframe/stack-shared/dist/utils/maps.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/math.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/math.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/math.js create mode 100644 package/@stackframe/stack-shared/dist/utils/math.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/node-http.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/node-http.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/node-http.js create mode 100644 package/@stackframe/stack-shared/dist/utils/node-http.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/numbers.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/numbers.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/numbers.js create mode 100644 package/@stackframe/stack-shared/dist/utils/numbers.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/oauth.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/oauth.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/oauth.js create mode 100644 package/@stackframe/stack-shared/dist/utils/oauth.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/objects.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/objects.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/objects.js create mode 100644 package/@stackframe/stack-shared/dist/utils/objects.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/passkey.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/passkey.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/passkey.js create mode 100644 package/@stackframe/stack-shared/dist/utils/passkey.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/promises.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/promises.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/promises.js create mode 100644 package/@stackframe/stack-shared/dist/utils/promises.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/proxies.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/proxies.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/proxies.js create mode 100644 package/@stackframe/stack-shared/dist/utils/proxies.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/react.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/react.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/react.js create mode 100644 package/@stackframe/stack-shared/dist/utils/react.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/results.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/results.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/results.js create mode 100644 package/@stackframe/stack-shared/dist/utils/results.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/sentry.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/sentry.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/sentry.js create mode 100644 package/@stackframe/stack-shared/dist/utils/sentry.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/stores.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/stores.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/stores.js create mode 100644 package/@stackframe/stack-shared/dist/utils/stores.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/strings.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/strings.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/strings.js create mode 100644 package/@stackframe/stack-shared/dist/utils/strings.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/strings.nicify.test.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/strings.nicify.test.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/strings.nicify.test.js create mode 100644 package/@stackframe/stack-shared/dist/utils/strings.nicify.test.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/types.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/types.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/types.js create mode 100644 package/@stackframe/stack-shared/dist/utils/types.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/unicode.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/unicode.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/unicode.js create mode 100644 package/@stackframe/stack-shared/dist/utils/unicode.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/urls.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/urls.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/urls.js create mode 100644 package/@stackframe/stack-shared/dist/utils/urls.js.map create mode 100644 package/@stackframe/stack-shared/dist/utils/uuids.d.mts create mode 100644 package/@stackframe/stack-shared/dist/utils/uuids.d.ts create mode 100644 package/@stackframe/stack-shared/dist/utils/uuids.js create mode 100644 package/@stackframe/stack-shared/dist/utils/uuids.js.map create mode 100644 package/@stackframe/stack-shared/package.json create mode 100644 postcss.config.cjs create mode 100644 public/favicon.ico create mode 100644 public/favicon.png create mode 100644 public/node.svg create mode 100644 resources/scripts/download.js create mode 100644 resources/scripts/install-bun.js create mode 100644 resources/scripts/install-uv.js create mode 100644 src/App.tsx create mode 100644 src/api/http.ts create mode 100644 src/components/AddWorker/IntegrationList.tsx create mode 100644 src/components/AddWorker/ToolSelect.tsx create mode 100644 src/components/AddWorker/index.tsx create mode 100644 src/components/AnimationJson.tsx create mode 100644 src/components/BottomBar/index.tsx create mode 100644 src/components/ChatBox/BottomInput.tsx create mode 100644 src/components/ChatBox/MarkDown.tsx create mode 100644 src/components/ChatBox/MessageCard.tsx create mode 100644 src/components/ChatBox/NoticeCard.tsx create mode 100644 src/components/ChatBox/SummaryMarkDown.tsx create mode 100644 src/components/ChatBox/TaskCard.tsx create mode 100644 src/components/ChatBox/TaskItem.tsx create mode 100644 src/components/ChatBox/TaskType.tsx create mode 100644 src/components/ChatBox/TypeCardSkeleton.tsx create mode 100644 src/components/ChatBox/index.tsx create mode 100644 src/components/Folder/index.tsx create mode 100644 src/components/GlobalSearch/index.tsx create mode 100644 src/components/HistorySidebar/SearchInput.tsx create mode 100644 src/components/HistorySidebar/index.tsx create mode 100644 src/components/InstallStep/Carousel.tsx create mode 100644 src/components/InstallStep/InstallDependencies.tsx create mode 100644 src/components/InstallStep/Permissions.tsx create mode 100644 src/components/Layout/index.tsx create mode 100644 src/components/SearchAgentWrokSpace/index.tsx create mode 100644 src/components/SearchHistoryDialog.tsx create mode 100644 src/components/SearchInput/index.tsx create mode 100644 src/components/TaskState/index.tsx create mode 100644 src/components/Terminal/index.tsx create mode 100644 src/components/TerminalAgentWrokSpace/index.tsx create mode 100644 src/components/ThemeProvider.tsx create mode 100644 src/components/Toast/creditsToast.tsx create mode 100644 src/components/Toast/storageToast.tsx create mode 100644 src/components/Toast/trafficToast.tsx create mode 100644 src/components/TopBar/index.css create mode 100644 src/components/TopBar/index.tsx create mode 100644 src/components/WorkFlow/MarkDown.tsx create mode 100644 src/components/WorkFlow/index.tsx create mode 100644 src/components/WorkFlow/node.tsx create mode 100644 src/components/WorkSpaceMenu/index.tsx create mode 100644 src/components/ui/ShinyText/ShinyText.css create mode 100644 src/components/ui/ShinyText/ShinyText.tsx create mode 100644 src/components/ui/accordion.tsx create mode 100644 src/components/ui/alert.tsx create mode 100644 src/components/ui/alertDialog.tsx create mode 100644 src/components/ui/badge.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/card.tsx create mode 100644 src/components/ui/carousel.tsx create mode 100644 src/components/ui/command.tsx create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/dropdown-menu.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/popover.tsx create mode 100644 src/components/ui/progress-install.tsx create mode 100644 src/components/ui/progress.tsx create mode 100644 src/components/ui/select.tsx create mode 100644 src/components/ui/separator.tsx create mode 100644 src/components/ui/sheet.tsx create mode 100644 src/components/ui/sidebar.tsx create mode 100644 src/components/ui/skeleton.tsx create mode 100644 src/components/ui/sonner.tsx create mode 100644 src/components/ui/switch.tsx create mode 100644 src/components/ui/tabs.tsx create mode 100644 src/components/ui/tag.tsx create mode 100644 src/components/ui/textarea.tsx create mode 100644 src/components/ui/toggle-group.tsx create mode 100644 src/components/ui/toggle.tsx create mode 100644 src/components/ui/tooltip.tsx create mode 100644 src/components/update/index.tsx create mode 100644 src/demos/node.ts create mode 100644 src/hooks/use-app-version.tsx create mode 100644 src/hooks/use-mobile.tsx create mode 100644 src/lib/index.ts create mode 100644 src/lib/llm.ts create mode 100644 src/lib/oauth.ts create mode 100644 src/lib/share.ts create mode 100644 src/lib/utils.ts create mode 100644 src/main.tsx create mode 100644 src/pages/History.tsx create mode 100644 src/pages/Home.tsx create mode 100644 src/pages/Login.tsx create mode 100644 src/pages/NotFound.tsx create mode 100644 src/pages/Setting.tsx create mode 100644 src/pages/Setting/API.tsx create mode 100644 src/pages/Setting/General.tsx create mode 100644 src/pages/Setting/MCP.tsx create mode 100644 src/pages/Setting/MCPMarket.tsx create mode 100644 src/pages/Setting/Models.tsx create mode 100644 src/pages/Setting/Privacy.tsx create mode 100644 src/pages/Setting/components/IntegrationList.tsx create mode 100644 src/pages/Setting/components/MCPAddDialog.tsx create mode 100644 src/pages/Setting/components/MCPConfigDialog.tsx create mode 100644 src/pages/Setting/components/MCPDeleteDialog.tsx create mode 100644 src/pages/Setting/components/MCPEnvDialog.tsx create mode 100644 src/pages/Setting/components/MCPList.tsx create mode 100644 src/pages/Setting/components/MCPListItem.tsx create mode 100644 src/pages/Setting/components/types.ts create mode 100644 src/pages/Setting/components/utils.ts create mode 100644 src/pages/SignUp.tsx create mode 100644 src/pages/Task.tsx create mode 100644 src/routers/index.tsx create mode 100644 src/stack/client.ts create mode 100644 src/store/authStore.ts create mode 100644 src/store/chatStore.ts create mode 100644 src/store/globalStore.ts create mode 100644 src/store/sidebarStore.ts create mode 100644 src/style/index.css create mode 100644 src/style/token.css create mode 100644 src/types/chatbox.d.ts create mode 100644 src/types/electron-updater.d.ts create mode 100644 src/types/electron.d.ts create mode 100644 src/types/index.ts create mode 100644 src/types/stackframe-react.d.ts create mode 100644 src/types/workspace.d.ts create mode 100644 src/vite-env.d.ts create mode 100644 tailwind.config.js create mode 100644 test/e2e.spec.ts create mode 100644 test/screenshots/e2e.png create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts create mode 100644 vite.config.ts.timestamp-1753782969125-f2b417d9fc657.mjs create mode 100644 vitest.config.ts diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..2bc0b863b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,11 @@ +--- + +name: 🐞 Bug report +about: Create a report to help us improve +title: "[Bug] the title of bug report" +labels: bug +assignees: '' + +--- + +#### Describe the bug diff --git a/.github/ISSUE_TEMPLATE/help_wanted.md b/.github/ISSUE_TEMPLATE/help_wanted.md new file mode 100644 index 000000000..6fba797e2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/help_wanted.md @@ -0,0 +1,10 @@ +--- +name: 🥺 Help wanted +about: Confuse about the use of electron-vue-vite +title: "[Help] the title of help wanted report" +labels: help wanted +assignees: '' + +--- + +#### Describe the problem you confuse diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..c533dfbf3 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,12 @@ + + +### Description + + + +### What is the purpose of this pull request? + +- [ ] Bug fix +- [ ] New Feature +- [ ] Documentation update +- [ ] Other diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..fe250ebd3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "npm" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "monthly" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..95fb0dd7e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,64 @@ +name: Build + +on: + push: + tags: + - "v*" + paths-ignore: + - "**.md" + - "**.spec.js" + - ".idea" + - ".vscode" + - ".dockerignore" + - "Dockerfile" + - ".gitignore" + - ".github/**" + - "!.github/workflows/build.yml" + +permissions: + contents: write + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [macos-latest, macos-13, windows-latest] + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: Install Dependencies + run: npm install + + - name: Build Release Files + run: npm run build + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: release_on_${{ matrix.os }} + path: release/ + retention-days: 5 + + - name: Create Release + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + files: | + release/*.exe + release/*.dmg + release/*.zip + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..3d3b53e14 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,81 @@ +name: CI + +on: + pull_request_target: + branches: + - main + +permissions: + pull-requests: write + +jobs: + job1: + name: Check Not Allowed File Changes + runs-on: ubuntu-latest + outputs: + markdown_change: ${{ steps.filter_markdown.outputs.change }} + markdown_files: ${{ steps.filter_markdown.outputs.change_files }} + steps: + + - name: Check Not Allowed File Changes + uses: dorny/paths-filter@v2 + id: filter_not_allowed + with: + list-files: json + filters: | + change: + - 'package-lock.json' + - 'yarn.lock' + - 'pnpm-lock.yaml' + + # ref: https://github.com/github/docs/blob/main/.github/workflows/triage-unallowed-contributions.yml + - name: Comment About Changes We Can't Accept + if: ${{ steps.filter_not_allowed.outputs.change == 'true' }} + uses: actions/github-script@v6 + with: + script: | + let workflowFailMessage = "It looks like you've modified some files that we can't accept as contributions." + try { + const badFilesArr = [ + 'package-lock.json', + 'yarn.lock', + 'pnpm-lock.yaml', + ] + const badFiles = badFilesArr.join('\n- ') + const reviewMessage = `👋 Hey there spelunker. It looks like you've modified some files that we can't accept as contributions. The complete list of files we can't accept are:\n- ${badFiles}\n\nYou'll need to revert all of the files you changed in that list using [GitHub Desktop](https://docs.github.com/en/free-pro-team@latest/desktop/contributing-and-collaborating-using-github-desktop/managing-commits/reverting-a-commit) or \`git checkout origin/main \`. Once you get those files reverted, we can continue with the review process. :octocat:\n\nMore discussion:\n- https://github.com/electron-vite/electron-vite-vue/issues/192` + createdComment = await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.number, + body: reviewMessage, + }) + workflowFailMessage = `${workflowFailMessage} Please see ${createdComment.data.html_url} for details.` + } catch(err) { + console.log("Error creating comment.", err) + } + core.setFailed(workflowFailMessage) + + - name: Check Not Linted Markdown + if: ${{ always() }} + uses: dorny/paths-filter@v2 + id: filter_markdown + with: + list-files: shell + filters: | + change: + - added|modified: '*.md' + + + job2: + name: Lint Markdown + runs-on: ubuntu-latest + needs: job1 + if: ${{ always() && needs.job1.outputs.markdown_change == 'true' }} + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Lint markdown + run: npx markdownlint-cli ${{ needs.job1.outputs.markdown_files }} --ignore node_modules \ No newline at end of file diff --git a/.github/workflows/remove-old-artifacts.yml b/.github/workflows/remove-old-artifacts.yml new file mode 100644 index 000000000..1065bbd06 --- /dev/null +++ b/.github/workflows/remove-old-artifacts.yml @@ -0,0 +1,27 @@ +name: Remove old artifacts + +on: + schedule: + # Every day at 1am + - cron: '0 1 * * *' + push: + branches: + - master + +jobs: + remove-old-artifacts: + runs-on: ubuntu-latest + timeout-minutes: 10 + + # For private repos + permissions: + actions: write + + steps: + - name: Remove old artifacts + uses: c-hive/gha-remove-artifacts@v1 + with: + age: '2 days' # ' ', e.g. 5 days, 2 years, 90 seconds, parsed by Moment.js + # Optional inputs + # skip-tags: true + # skip-recent: 5 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..4451f971a --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +!package/**/dist +dist-ssr +dist-electron +release +*.local + +# Editor directories and files +.vscode/.debug.env +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +#lockfile +package-lock.json +pnpm-lock.yaml +yarn.lock +/test-results/ +/playwright-report/ +/playwright/.cache/ + +.env +.env.local +.env.development +.env.production + +.cursor + diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..45c8cd74a --- /dev/null +++ b/.npmrc @@ -0,0 +1,6 @@ +# For electron-builder +# https://github.com/electron-userland/electron-builder/issues/6289#issuecomment-1042620422 +shamefully-hoist=true + +# For China 🇨🇳 developers +# electron_mirror=https://npmmirror.com/mirrors/electron/ diff --git a/.vscode/.debug.script.mjs b/.vscode/.debug.script.mjs new file mode 100644 index 000000000..9ca93363c --- /dev/null +++ b/.vscode/.debug.script.mjs @@ -0,0 +1,23 @@ +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { createRequire } from 'node:module' +import { spawn } from 'node:child_process' + +const pkg = createRequire(import.meta.url)('../package.json') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +// write .debug.env +const envContent = Object.entries(pkg.debug.env).map(([key, val]) => `${key}=${val}`) +fs.writeFileSync(path.join(__dirname, '.debug.env'), envContent.join('\n')) + +// bootstrap +spawn( + // TODO: terminate `npm run dev` when Debug exits. + process.platform === 'win32' ? 'npm.cmd' : 'npm', + ['run', 'dev'], + { + stdio: 'inherit', + env: Object.assign(process.env, { VSCODE_DEBUG: 'true' }), + }, +) \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..fe78c45f3 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "mrmlnc.vscode-json5" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..2177eb13c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,54 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "compounds": [ + { + "name": "Debug App", + "preLaunchTask": "Before Debug", + "configurations": [ + "Debug Main Process", + "Debug Renderer Process" + ], + "presentation": { + "hidden": false, + "group": "", + "order": 1 + }, + "stopAll": true + } + ], + "configurations": [ + { + "name": "Debug Main Process", + "type": "node", + "request": "launch", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", + "windows": { + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" + }, + "runtimeArgs": [ + "--no-sandbox", + "--remote-debugging-port=9229", + "." + ], + "envFile": "${workspaceFolder}/.vscode/.debug.env", + "console": "integratedTerminal" + }, + { + "name": "Debug Renderer Process", + "port": 9229, + "request": "attach", + "type": "chrome", + "timeout": 60000, + "skipFiles": [ + "/**", + "${workspaceRoot}/node_modules/**", + "${workspaceRoot}/dist-electron/**", + // Skip files in host(VITE_DEV_SERVER_URL) + "http://127.0.0.1:7777/**" + ] + }, + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..fa6153c78 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,16 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.tsc.autoDetect": "off", + "json.schemas": [ + { + "fileMatch": [ + "/*electron-builder.json5", + "/*electron-builder.json" + ], + "url": "https://json.schemastore.org/electron-builder" + } + ], + "cSpell.words": [ + "Eigent" + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..85d09cdea --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,31 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Before Debug", + "type": "shell", + "command": "node .vscode/.debug.script.mjs", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "fileLocation": "relative", + "pattern": { + // TODO: correct "regexp" + "regexp": "^([a-zA-Z]\\:\/?([\\w\\-]\/?)+\\.\\w+):(\\d+):(\\d+): (ERROR|WARNING)\\: (.*)$", + "file": 1, + "line": 3, + "column": 4, + "code": 5, + "message": 6 + }, + "background": { + "activeOnStart": true, + "beginsPattern": "^.*VITE v.* ready in \\d* ms.*$", + "endsPattern": "^.*\\[startup\\] Electron App.*$" + } + } + } + ] +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..c6e955c4f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,200 @@ +🐫 **Welcome to Eigent!** 🐫 + +Thank you for your interest in contributing to the Eigent project! 🎉 We're excited to have your support. As an open-source product build on CAMEL in a rapidly evolving and open-ended field, we wholeheartedly welcome contributions of all kinds. Whether you want to introduce new features, enhance the infrastructure, improve documentation, asking issues, or fix bugs, we appreciate your enthusiasm and efforts. 🙌 You are welcome to join our [discord](https://discord.camel-ai.org/) for more efficient communication. 💬 + +## Join Our Community 🌍 + +### Developer Meeting Time & Link 💻 +- English speakers: Mondays at 8 PM PDT. Join via Discord: [Meeting Link](https://meet.google.com/sez-aomy-ebm?authuser=0&hs=122&ijlm=1753634732982) +- Chinese Speakers: Mondays at 9 PM UTC+8. Join via TecentMeeting: [Meeting Link](https://meeting.tencent.com/dm/057wap1eeCSY) + +### Our Communication Channels 💬 +- **Discord:** [Join here](https://discord.camel-ai.org/) +- **WeChat:** Scan the QR code [here](https://ghli.org/camel/wechat.png) +- **Slack:** [Join here](https://join.slack.com/t/camel-ai/shared_invite/zt-2g7xc41gy-_7rcrNNAArIP6sLQqldkqQ) + +## Guidelines 📝 + +### Contributing to the Code 👨‍💻👩‍💻 + +If you're eager to contribute to this project, that's fantastic! We're thrilled to have your support. + +- If you are a contributor from the community: + - Follow the [Fork-and-Pull-Request](https://docs.github.com/en/get-started/quickstart/contributing-to-projects) workflow when opening your pull requests. +- If you are a member of [CAMEL-AI.org](https://github.com/camel-ai): + - Follow the [Checkout-and-Pull-Request](https://dev.to/ceceliacreates/how-to-create-a-pull-request-on-github-16h1) workflow when opening your pull request; this will allow the PR to pass all tests that require [GitHub Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets). + +Make sure to mention any related issues and tag the relevant maintainers too. 💪 + +Before your pull request can be merged, it must pass the formatting, linting, and testing checks. You can find instructions on running these checks locally under the **Common Actions** section below. 🔍 + +Ensuring excellent documentation and thorough testing is absolutely crucial. Here are some guidelines to follow based on the type of contribution you're making: + +- If you fix a bug: + - Add a relevant unit test when possible. These can be found in the `test` directory. +- If you make an improvement: + - Update any affected example console scripts in the `examples` directory, Gradio demos in the `apps` directory, and documentation in the `docs` directory. + - Update unit tests when relevant. +- If you add a feature: + - Include unit tests in the `test` directory. + - Add a demo script in the `examples` directory. + +We're a small team focused on building great things. If you have something in mind that you'd like to add or modify, opening a pull request is the ideal way to catch our attention. 🚀 + +### Contributing to Code Reviews 🔍 +This part outlines the guidelines and best practices for conducting code reviews in Eigent. The aim is to ensure that all contributions are of high quality, align with the project's goals, and are consistent with our coding standards. + +#### Purpose of Code Reviews +- Maintain Code Quality: Ensure that the codebase remains clean, readable, and maintainable. +- Knowledge Sharing: Facilitate knowledge sharing among contributors and help new contributors learn best practices. +- Bug Prevention: Catch potential bugs and issues before they are merged into the main branch. +- Consistency: Ensure consistency in style, design patterns, and architecture across the project. + +#### Review Process Overview +- Reviewers should check the code for functionality, readability, consistency, and compliance with the project’s coding standards. +- If changes are necessary, the reviewer should leave constructive feedback. +- The contributor addresses feedback and updates the PR. +- The reviewer re-reviews the updated code. +- Once the code is approved by at least two reviewer, it can be merged into the main branch. +- Merging should be done by a maintainer or an authorized contributor. + +#### Code Review Checklist +- Functionality + - Correctness: Does the code perform the intended task? Are edge cases handled? + - Testing: Is there sufficient test coverage? Do all tests pass? + - Security: Are there any security vulnerabilities introduced by the change? + - Performance: Does the code introduce any performance regressions? + +- Code Quality + - Readability: Is the code easy to read and understand? Is it well-commented where necessary? + - Maintainability: Is the code structured in a way that makes future changes easy? + - Style: Does the code follow the project’s style guidelines? + Currently we use Ruff for format check and take [Google Python Style Guide]("https://google.github.io/styleguide/pyguide.html") as reference. + - Documentation: Are public methods, classes, and any complex logic well-documented? +- Design + - Consistency: Does the code follow established design patterns and project architecture? + - Modularity: Are the changes modular and self-contained? Does the code avoid unnecessary duplication? + - Dependencies: Are dependencies minimized and used appropriately? + +#### Reviewer Responsibilities +- Timely Reviews: Reviewers should strive to review PRs promptly to keep the project moving. +- Constructive Feedback: Provide feedback that is clear, constructive, and aimed at helping the contributor improve. +- Collaboration: Work with the contributor to address any issues and ensure the final code meets the project’s standards. +- Approvals: Only approve code that you are confident meets all the necessary criteria. + +#### Common Pitfalls +- Large PRs: Avoid submitting PRs that are too large. Break down your changes into smaller, manageable PRs if possible. +- Ignoring Feedback: Address all feedback provided by reviewers, even if you don’t agree with it—discuss it instead of ignoring it. +- Rushed Reviews: Avoid rushing through reviews. Taking the time to thoroughly review code is critical to maintaining quality. + +Code reviews are an essential part of maintaining the quality and integrity of our open source project. By following these guidelines, we can ensure that Eigent remains robust, secure, and easy to maintain, while also fostering a collaborative and welcoming community. + +### Guideline for Writing Docstrings + +This guideline will help you write clear, concise, and structured docstrings for contributing to `Eigent`. + +#### 1. Use the Triple-Quoted String with `r"""` (Raw String) +Begin the docstring with `r"""` to indicate a raw docstring. This prevents any issues with special characters and ensures consistent formatting. + +#### 2. Provide a Brief Class or Method Description +- Start with a concise summary of the purpose and functionality. +- Keep each line under `79` characters. +- The summary should start on the first line without a linebreak. + +Example: +```python +r"""Class for managing conversations of CAMEL Chat Agents. +""" +``` + +#### 3. Document Parameters in the Args Section +- Use an `Args`: section for documenting constructor or function parameters. +- Maintain the `79`-character limit for each line, and indent continuation lines by 4 spaces. +- Follow this structure: + - Parameter Name: Match the function signature. + - Type: Include the type (e.g., `int`, `str`, custom types like `BaseModelBackend`). + - Description: Provide a brief explanation of the parameter's role. + - Default Value: Use (`default: :obj:`) to indicate default values. + +Example: +```markdown +Args: + system_message (BaseMessage): The system message for initializing + the agent's conversation context. + model (BaseModelBackend, optional): The model backend to use for + response generation. Defaults to :obj:`OpenAIModel` with + `GPT_4O_MINI`. (default: :obj:`OpenAIModel` with `GPT_4O_MINI`) +``` + +### Principles 🛡️ + +#### Naming Principle: Avoid Abbreviations in Naming + +- Abbreviations can lead to ambiguity, especially since variable names and code in CAMEL are directly used by agents. +- Use clear, descriptive names that convey meaning without requiring additional explanation. This improves both human readability and the agent's ability to interpret the code. + +Examples: + +- Bad: msg_win_sz +- Good: message_window_size + +By adhering to this principle, we ensure that CAMEL remains accessible and unambiguous for both developers and AI agents. + +### Board Item Create Workflow 🛠️ +At Eigent, we manage our project through a structured workflow that ensures efficiency and clarity in our development process. Our workflow includes stages for issue creation and pull requests (PRs), sprint planning, and reviews. + +#### Issue Item Stage: +Our [issues](https://github.com/eigent-ai/Eigent-desktop/issues) page on GitHub is regularly updated with bugs, improvements, and feature requests. We have a handy set of labels to help you sort through and find issues that interest you. Feel free to use these labels to keep things organized. + +When you start working on an issue, please assign it to yourself so that others know it's being taken care of. + +When creating a new issue, it's best to keep it focused on a specific bug, improvement, or feature. If two issues are related or blocking each other, it's better to link them instead of merging them into one. + +We do our best to keep these issues up to date, but considering the fast-paced nature of this field, some may become outdated. If you come across any such issues, please give us a heads-up so we can address them promptly. 👀 + +Here’s how to engage with our issues effectively: +- Go to [GitHub Issues](https://github.com/eigent-ai/Eigent-desktop/issues), create a new issue, choose the category, and fill in the required information. +- Ensure the issue has a proper title and update the Assignees, Labels, Projects (select Backlog status), Development, and Milestones. +- Discuss the issue during team meetings, then move it to the Analysis Done column. +- At the beginning of each sprint, share the analyzed issue and move it to the Sprint Planned column if you are going to work on this issue in the sprint. + +#### Pull Request Item Stage: + +- Go to [GitHub Pulls](https://github.com/eigent-ai/Eigent-desktop/pulls), create a new PR, choose the branch, and fill in the information, linking the related issue. +- Ensure the PR has a proper title and update the Reviewers (convert to draft), Assignees, Labels, Projects (select Developing status), Development, and Milestones. +- If the PR is related to a roadmap, link the roadmap to the PR. +- Move the PR item through the stages: Developing, Stuck, Reviewing (click ready for review), Merged. The linked issue will close automatically when the PR is merged. + +**Labeling PRs:** +- **feat**: For new features (e.g., `feat: Add new AI model`) +- **fix**: For bug fixes (e.g., `fix: Resolve memory leak issue`) +- **docs**: For documentation updates (e.g., `docs: Update contribution guidelines`) +- **style**: For code style changes (e.g., `style: Refactor code formatting`) +- **refactor**: For code refactoring (e.g., `refactor: Optimize data processing`) +- **test**: For adding or updating tests (e.g., `test: Add unit tests for new feature`) +- **chore**: For maintenance tasks (e.g., `chore: Update dependencies`) + +### Getting Help 🆘 + +Our aim is to make the developer setup as straightforward as possible. If you encounter any challenges during the setup process, don't hesitate to reach out to a maintainer. We're here to assist you and ensure that the experience is smooth not just for you but also for future contributors. 😊 + +## Quick Start 🚀 + +```bash +git clone https://github.com/eigent-ai/Eigent-desktop.git +cd Eigent-desktop +npm install +npm run dev +``` + +## Common Actions 🔄 + +### Update dependencies + +Whenever you add, update, or delete any dependencies in `pyproject.toml`, please run `uv lock` to synchronize the dependencies with the lock file. + +## Giving Credit 🎉 + +If your contribution has been included in a release, we'd love to give you credit on Twitter, but only if you're comfortable with it! + +If you have a Twitter account that you would like us to mention, please let us know either in the pull request or through another communication method. We want to make sure you receive proper recognition for your valuable contributions. 😄 diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..9889957cd --- /dev/null +++ b/LICENSE @@ -0,0 +1,46 @@ +# Eigent Open Source License + +Eigent is licensed under a modified version of the Apache License 2.0, with the following additional conditions: + +1. Eigent may be utilized commercially, including as a backend service for other applications or as an application development platform for enterprises. However, if any of the following conditions apply, you must obtain a valid commercial license from Eigent.AI : + +a. Commercial Self-Hosted Deployment: You may not use this software or any of its components in a production environment for commercial purposes without an active, valid commercial license from Eigent AI. +Definitions: + - Software: Eigent source code, binaries, and related components provided under this license. + - Production Environment: Any environment not solely used for development, testing, or personal non-commercial evaluation purposes. + - Commercial Purposes: Activities intended or directed towards commercial advantage or monetary compensation, including, without limitation, supporting internal business operations or providing services to third parties. + +b. Multi-tenant SaaS service: Unless explicitly authorized by Eigent.AI in writing, you may not use the Eigent source code to operate a multi-tenant Software-as-a-Service platform or any online service similar to Eigent’s official cloud service. + - Tenant Definition: Within the context of Eigent, one tenant corresponds to one workspace. The workspace provides a separated area for each tenant's data and configurations. + +c. Branding and Attribution: You must not remove, hide, or alter the Eigent name, logos, or copyright notices displayed in the Eigent user interface (including desktop applications and web consoles).This restriction is inapplicable to uses of Eigent that do not involve its frontend. + - Frontend Definition: For the purposes of this license, the "frontend" of Eigent includes all components located in the `electron/` directory when running Eigent from the raw source code, or the "electron" image when running Eigent with Docker. + +Please contact us at info@eigent.ai for licensing inquiries. + +2. As a contributor, you should agree that: + +a. Eigent AI can adjust the open-source agreement to be more restrictive or permissive as deemed necessary. + +b. Your contributed code may be used by Eigent.AI for commercial purposes, including but not limited to cloud-hosted and self-hosted services operated by Eigent AI. + +Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache License 2.0. Detailed information about the Apache License 2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0. + +The interactive design of this product, as well as Eigent’s names and logos, are protected by intellectual property laws. + +© 2023-2025 Eigent AI LTD + + +---------- + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..293007046 --- /dev/null +++ b/README.md @@ -0,0 +1,402 @@ +
+ +[![][image-head]][eigent-site] + +[![][image-seperator]][eigent-site] + +### Eigent: The World's First Multi-agent Workforce to Unlock Your Exceptional Productivity + +**English** · [简体中文](./README_CN.md) · [Official Site][eigent-site] · [Documents][docs-site] · [Feedback][github-issue-link] + + + +[![][discord-shield]][discord-link] +[![][github-star]][eigent-github] +[![][social-x-shield]][social-x-link] +[![][share-linkedin-shield]][share-linkedin-link] +[![][share-reddit-shield]][share-reddit-link] +[![][sponsor-shield]][sponsor-link] + +
+ +
+
+ +**Eigent** is the world’s first **Multi-agent Workforce** desktop application, empowering you to build, manage, and deploy a custom AI workforce that can turn your most complex workflows into automated tasks. + +Built on [CAMEL-AI][camel-site]'s acclaimed open-source project, our system introduces a **Multi-Agent Workforce** that **boosts productivity** through parallel execution, customization, and privacy protection. + +### ⭐ 13k GitHub Stars - 🥇 #1 GitHub Daily Trending - 🏆 #1 on GAIA + +- ✅ **Zero Setup** - No technical configuration required +- ✅ **Multi-Agent Coordination** - Handle complex multi-agent workflows +- ✅ **Enterprise Feature** - SSO/Access control +- ✅ **Local Deploymen**t +- ✅ **Open Source** +- ✅ **Custom Model Support** +- ✅ **MCP Integration** + +
+ +
+Table of contents + +#### TOC + +- [🚀 Getting Started](#-getting-started) + - [☁️ Cloud Version](#️-cloud-version) + - [🏠 Self-Hosting (Community Edition)](#-self-hosting-community-edition) + - [🏢 Enterprise](#-enterprise) +- [✨ Key features](#-key-features) + - [🏭 Workforce](#-workforce) + - [🧠 Comprehensive Model Support](#-comprehensive-model-support) + - [🔌 MCP Tools Integration (MCP)](#-mcp-tools-integration-mcp) + - [✋ Human-in-the-Loop](#-human-in-the-loop) + - [👐 100% Open Source](#-100-open-source) +- [🧩 Use Cases](#-use-cases) +- [🛠️ Tech Stack](#-tech-stack) + - [Backend](#backend) + - [Frontend](#frontend) +- [🌟 Staying ahead](#staying-ahead) +- [🗺️ Roadmap](#-roadmap) +- [📖 Contributing](#-contributing) + - [Main Contributors](#main-contributors) + - [Distinguished amabssador](#distinguished-amabssador) +- [Ecosystem](#ecosystem) +- [📄 Open Source License](#-open-source-license) +- [🌐 Community & contact](#-community--contact) + +#### + +
+ +
+ +## **🚀 Getting Started** + +There are three ways to get started with Eigent: + +### ☁️ Cloud Version + +The fastest way to experience Eigent's multi-agent AI capabilities is through our cloud platform, perfect for teams and individuals who want immediate access without setup complexity. We'll host the models, APIs, and cloud storage, ensuring Eigent runs flawlessly. + +- **Instant Access** - Start building multi-agent workflows in minutes. +- **Managed Infrastructure** - We handle scaling, updates, and maintenance. +- **Premium Support** - Subscribe and get priority assistance from our engineering team. + +
+ +![public-beta][image-public-beta] + +[**Get started at Eigent.ai →**][eigent-site] + +### 🏠 Self-Hosting (Community Edition) + +For users who prefer local control, data privacy, or customization, this option is ideal for organizations requiring: + +- **Data Privacy** - Keep sensitive data within your infrastructure. +- **Customization** - Modify and extend the platform to fit your needs. +- **Cost Control** - Avoid recurring cloud fees for large-scale deployments. + +#### 1. Prerequisites + +- Node.js and npm +- Python 3.10+ and uv + +#### 2. Quick Start + +```bash +git clone https://github.com/eigent-ai/Eigent-desktop.git +cd Eigent-desktop +npm install +npm run dev +``` + +### 🏢 Enterprise + +For organizations requiring maximum security, customization, and control: + +- **Commercial License** - [Check our license →](LICENSE) +- **Exclusive Features** (like SSO & custom development) +- **Scalable Enterprise Deployment** +- **Negotiated SLAs** & implementation services + +📧 For further details, please contact us at [info@eigent.ai](mailto:info@eigent.ai). + +## **✨ Key features** +Unlock the full potential of exceptional productivity with Eigent’s powerful features—built for seamless integration, smarter task execution, and boundless automation. + +### 🏭 Workforce +Employs a team of specialized AI agents that collaborate to solve complex tasks. Eigent dynamically breaks down tasks and activates multiple agents to work **in parallel.** + +Eigent pre-defined the following agent workers: + +- **Developer Agent:** Writes and executes code, runs terminal commands. +- **Search Agent:** Searches the web and extracts content. +- **Document Agent:** Creates and manages documents. +- **Multi-Modal Agent:** Processes images and audio. + +
+ +![Workforce][image-workforce] + +[![Try it Now]](https://www.eigent.ai/download) + +
+ +### 🧠 Comprehensive Model Support +Deploy Eigent locally with your preferred models. + +
+ +![Model][image-local-model] + +[![Try it Now]](https://www.eigent.ai/download) + +
+ +### 🔌 MCP Tools Integration (MCP) +Eigent comes with massive built-in **Model Context Protocol (MCP)** tools (for web browsing, code execution, Notion, Google suite, Slack etc.), and also lets you **install your own tools**. Equip agents with exactly the right tools for your scenarios – even integrate internal APIs or custom functions – to enhance their capabilities. + +
+ +![MCP][image-mcp] + +[![Try it Now]](https://www.eigent.ai/download) + +
+ +### ✋ Human-in-the-Loop +If a task gets stuck or encounters uncertainty, Eigent will automatically request human input. + +
+ +![Human-in-the-loop][image-human-in-the-loop] + +[![Try it Now]](https://www.eigent.ai/download) + +
+ +### 👐 100% Open Source +Eigent is completely open-sourced. You can download, inspect, and modify the code, ensuring transparency and fostering a community-driven ecosystem for multi-agent innovation. + +
+ +![Opensource][image-opensource] + +[![Try it Now]](https://www.eigent.ai/download) + +
+ +## 🧩 Use Cases + +### 1. CAMEL GitHub Newsletter [Replay ▶️]() +Please review the latest updates from the CAMEL GitHub repository over the past month, craft a polished newsletter summarising the key changes, and email it to my client list. + +Video + +### 2. Palm Springs Tennis Trip [Replay ▶️]() +I am a tennis fan and want to go see the tennis tournament in palm springs. I live in SF - please prepare a detailed itinerary with flights, hotels, things to do for 3 days - around the time semifinal/finals are happening. I like hiking, vegan food and spas. My budget is $3K. The itinerary should be a detailed timeline of time, activity, cost, other details and if applicable a link to buy tickets/make reservations etc. for the item. + +### 3. AI Customer Form & Dashboard [Replay ▶️]() +We are a tech consulting firm with in-depth research on technologies in the multi-agent general intelligence field. Please create a potential customer form for us. The target companies are: – B2B American companies – In the development stage before Series B – Require AI technology empowerment – And demonstrate a progressive attitude toward adopting modern productivity software (e.g., Slack, Google Workspace, Notion, etc.) Please list at least 15 companies, clearly stating their contact information, company business introductions, addresses and other specific details. Also, create a dashboard. + +### 4. Website SEO Audit Report [Replay ▶️]() +To support the launch of our new Workforce Multiagent product, please run a thorough SEO audit on our official website (https://www.camel-ai.org/) and deliver a detailed optimization report with actionable recommendations. + +### 5. Q2 Financial Statement Prep [Replay ▶️]() +Please help me prepare a Q2 financial statement based on this bank transfer record, to report to investors how much we have spent. + +### 6. RL Engineer Candidate Summary with Notion MCP [Replay ▶️]() +I am an HR professional looking to hire a Reinforcement Learning Algorithm Engineer. I prefer someone with relevant RL experience. Please help me organize candidate information from this notion page Applicant Tracker into a complete Excel summary table including basic information and concise summary of project experiences (focusing on key highlights and achievements). Please rank candidates based on their RL expertise and provide me with an Excel file that includes all this information in an organized format. I hope you can carefully read through each candidate's resume pdf attachments in the table in notion page Applicant Tracker one by one. + +### 7. PDF to Conference Slides [Replay ▶️]() +Convert this PDF into presentation slides suitable for an academic conference. The presentation should be approximately 15 minutes long and must include all key points. + +### 8. OWL Webpage Creation with Figma MCP [Replay ▶️]() +Please create a new webpage styled after (https://www.camel-ai.org/) to introduce OWL (https://github.com/camel-ai/owl). Start by generating a Figma-editable file and replace its content to showcase our product. Then, develop and deploy the webpage based on this design. + +### 9. Blender 3D Modeling with Blender MCP [Replay ▶️]() +Based on this image, help me model in Blender. + +### 10. H1B1 Visa Application with local files [Replay ▶️]() +I'm currently applying for a U.S. H1B1 visa. Please help me locate the necessary application documents on my computer, organize them into a single folder, and finally assist me in filling out the H1B1 visa application form. + +## 🛠️ Tech Stack + +### Backend +- **Framework:** FastAPI +- **Package Manager:** uv +- **Async Server:** Uvicorn +- **Authentication:** OAuth 2.0, Passlib. +- **Multi-agent framework:** CAMEL + +### Frontend + +- **Framework:** React +- **Desktop App Framework:** Electron +- **Language:** TypeScript +- **UI:** Tailwind CSS, Radix UI, Lucide React, Framer Motion +- **State Management:** Zustand +- **Flow Editor:** React Flow + +## 🌟 Staying ahead + +> \[!IMPORTANT] +> +> **Star Eigent**, You will receive all release notifications from GitHub without any delay \~ ⭐️ + +![][image-star-us] + +### 🗺️ Roadmap + + + + + + +
+ +**Context Engineering** [**Join our slack channel →**: https://camel-ai.slack.com/archives/C097Q1QL98C] + +- prompt caching +- system prompt optimize +- toolkit docstring optimize +- context compression + +**Multi-modal Enhancement** [**Join our slack channel →**: https://camel-ai.slack.com/archives/C097Q7RT0EQ] + +- more accurate image understanding when using browser +- advanced video generation + +**Multi-agent system** [**Join our slack channel →**: https://camel-ai.slack.com/archives/C098BJV0KR6] + +- workforce support fixed workflow +- workforce support multi-round conversion + + + +**Browser Toolkit** [**Join our slack channel →**: https://camel-ai.slack.com/archives/C0977N3C87R] + +- forbid repeated page visiting +- automatic cache button clicking + +**Document Toolkit** [**Join our slack channel →**: https://camel-ai.slack.com/archives/C0983CP7AHX] + +- support dynamic file editing + +**Terminal Toolkit** [**Join our slack channel →**: https://camel-ai.slack.com/archives/C097L9D7LDU] + +- Terminal Bench integration + +**Environment & RL** [**Join our slack channel →**: https://camel-ai.slack.com/archives/C097Q91K1EG] + +- Verl integration + +
+ +## 📖 Contributing + +We welcome contributions! For those who’d like to contribute code, Please see our Contributing Guide for details. + +### Main Contributors +| Name | GitHub Profile | +| ---------------- | ------------------------------------------------ | +| Wendong Fan | [Wendong-Fan](https://github.com/Wendong-Fan) | +| Puzhen Zhang | [nitpicker55555](https://github.com/nitpicker55555) | +| Wei Li | [luoyou](https://github.com/luoyou) | +| Wei Sun | [FooFindBar](https://github.com/FooFindBar) | +| Tao Sun | [fengju0213](https://github.com/fengju0213) | +| Chuan He | [weer0026](https://github.com/weer0026) | +| Xiaotian Jin | [MuggleJinx](https://github.com/MuggleJinx) | +| Yifeng Wang | [zjrwtx](https://github.com/zjrwtx) | +| Weijie Bai (product manager) | [Pakchoioioi](https://github.com/Pakchoioioi) | +| Douglas Lai (product designer) | [Douglasymlai](https://github.com/Douglasymlai) | + + + +At the same time, please consider supporting Eigent by sharing it on social media and at events and conferences. See our ambassador program. [Join our Ambassador Program: Finding the Scaling Laws of Agents][ambassador-link] + +[![][image-ambassador]][ambassador-link] + +### Distinguished amabssador + +| Name | GitHub Profile | +| ---------------- | ------------------------------------------------ | +| Parth Sharma | [parthshr370](https://github.com/parthshr370) | +| Jino Rohit | [jino-rohit](https://github.com/jino-rohit) | +| Bipul Sharma | [Bipul70701](https://github.com/Bipul70701) | +| Tushar Singh | [tushar80rt](https://github.com/tushar80rt) | +| Harshit Sharma | [Harsh1tSh](https://github.com/Harsh1tSh) | +| Divyansh Goyal | [divital-coder](https://github.com/divital-coder) | + +## Ecosystem + + +[![Sglang](https://img.shields.io/badge/SGLang-FF6B6B?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDJMMjIgMjJIMkwxMiAyWiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+)](https://github.com/sgl-project/sglang) +[![AWS](https://img.shields.io/badge/AWS-232F3E?style=for-the-badge&logo=amazon-aws&logoColor=white)](https://aws.amazon.com/) +[![Qwen](https://img.shields.io/badge/Qwen-FF6B35?style=for-the-badge&logo=alibaba-cloud&logoColor=white)](https://qwenlm.github.io/) +[![Gemini](https://img.shields.io/badge/Google_Gemini-4285F4?style=for-the-badge&logo=google&logoColor=white)](https://gemini.google.com/) +[![Azure](https://img.shields.io/badge/Microsoft_Azure-0078D4?style=for-the-badge&logo=microsoft-azure&logoColor=white)](https://azure.microsoft.com/) + + +## **📄 Open Source License** + +This repository is licensed under the [**Eigent Open Source License**](LICENSE), based on Apache 2.0 with additional conditions. + +## 🌐 Community & contact + +- GitHub Discussion | [Explreo More →](https://github.com/eigent-ai/Eigent-desktop/discussions) +- GitHub Issues | [Contribute →][github-issue-link] +- X (Twitter) | [Follow Us →][social-x-link] +- Discord | [Seek Support →][discord-link] +- Wechat
+![Wechat][image-wechat] + + + + +[discord-link]: https://discord.camel-ai.org +[discord-shield]: https://img.shields.io/discord/1127171173982154893?color=5865F2&label=discord&labelColor=gray&logo=discord&logoColor=white&style=plastic + + +[eigent-github]: https://github.com/eigent-ai/Eigent-desktop +[github-star]: https://img.shields.io/github/stars/eigent-ai?color=F5F4F0&labelColor=gray&style=plastic&logo=github + +[social-x-link]: https://x.com/Eigent-AI +[social-x-shield]: https://img.shields.io/badge/-%40Eigent_AI-white?labelColor=gray&logo=x&logoColor=white&style=plastic + +[sponsor-link]: https://github.com/sponsors/eigent-ai +[sponsor-shield]: https://img.shields.io/badge/-Sponsor%20Eigent-1d1d1d?logo=github&logoColor=white&style=plastic + +[share-linkedin-link]: https://www.linkedin.com/company/eigent-ai/ +[share-linkedin-shield]: https://img.shields.io/badge/-Share%20on%20linkedin-blue?labelColor=gray&logo=linkedin&logoColor=white&style=plastic + +[share-reddit-link]: https://www.reddit.com/r/CamelAI/comments/1m7oa2n/launch_countdown_of_our_first_ever_agent_product/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button +[share-reddit-shield]: https://img.shields.io/badge/-Share%20on%20reddit-red?labelColor=gray&logo=reddit&logoColor=white&style=flat-plastic + + +[camel-site]: https://www.camel-ai.org +[eigent-site]: https://www.eigent.ai +[docs-site]: https://docs.eigent.ai +[github-issue-link]: https://github.com/eigent-ai/Eigent-desktop/issues +[ambassador-link]:https://www.camel-ai.org/ambassador + + +[Try it Now]: https://img.shields.io/badge/-TRY_IT_NOW-363AF5?style=plastic + +[image-seperator]: https://eigent-ai.github.io/.github/assets/seperator.png +[image-head]: https://eigent-ai.github.io/.github/assets/head.png +[image-overview]: https://eigent-ai.github.io/.github/assets/overview.png +[image-public-beta]: https://eigent-ai.github.io/.github/assets/banner.png +[image-ecosystem]: https://eigent-ai.github.io/.github/assets/ecosystem.png +[image-star-us]: https://eigent-ai.github.io/.github/assets/star-us.gif + +[image-workforce]: https://eigent-ai.github.io/.github/assets/feature_dynamic_workforce.gif +[image-human-in-the-loop]: https://eigent-ai.github.io/.github/assets/feature_human_in_the_loop.gif +[image-add-worker]: https://eigent-ai.github.io/.github/assets/feature_add_worker.gif +[image-mcp]: https://eigent-ai.github.io/.github/assets/feature_add_mcp.gif +[image-local-model]: https://eigent-ai.github.io/.github/assets/feature_local_model.gif +[image-opensource]: https://eigent-ai.github.io/.github/assets/opensource.png +[image-wechat]: https://eigent-ai.github.io/.github/assets/wechat.png +[image-ambassador]: https://eigent-ai.github.io/.github/assets/ambassador-program.png \ No newline at end of file diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 000000000..6558bc7bf --- /dev/null +++ b/README_CN.md @@ -0,0 +1,350 @@ +
+ +[![][image-head]][eigent-site] + +[![][image-seperator]][eigent-site] + +### Eigent:全球首个多智能体工作流平台,释放非凡生产力 + +[English](./README.md) · **简体中文** · [官网][eigent-site] · [文档][docs-site] · [反馈][github-issue-link] + + + +[![][discord-shield]][discord-link] +[![][github-star]][eigent-github] +[![][social-x-shield]][social-x-link] +[![][share-linkedin-shield]][share-linkedin-link] +[![][share-reddit-shield]][share-reddit-link] +[![][sponsor-shield]][sponsor-link] + +
+ +
+
+ +**Eigent** 是全球首个**多智能体工作流**桌面应用,助您构建、管理和部署定制化AI团队,将复杂工作流程转化为自动化任务。 + +基于[CAMEL-AI][camel-site]知名开源项目构建,我们创新性地引入**多智能体协同系统**,通过并行执行、深度定制和隐私保护实现**生产力跃升**。 + +### ⭐ GitHub 13k星标 - 🥇 GitHub每日趋势榜首 - 🏆 GAIA排行榜冠军 + +- ✅ **零配置** - 无需技术背景 +- ✅ **多智能体协同** - 处理复杂工作流 +- ✅ **企业级功能** - SSO/权限控制 +- ✅ **本地化部署** +- ✅ **开源可定制** +- ✅ **多模型支持** +- ✅ **MCP工具集成** + +
+ +
+目录导航 + +#### 目录 + +- [🚀 快速开始](#-快速开始) + - [☁️ 云服务版](#️-云服务版) + - [🏠 社区版自托管](#-社区版自托管) + - [🏢 企业版](#-企业版) +- [✨ 核心功能](#-核心功能) + - [🏭 智能体工作流](#-智能体工作流) + - [🧠 全栈模型支持](#-全栈模型支持) + - [🔌 MCP工具集成](#-mcp工具集成) + - [✋ 人工介入机制](#-人工介入机制) + - [👐 100%开源](#-100开源) +- [🧩 应用场景](#-应用场景) +- [🛠️ 技术栈](#-技术栈) + - [后端架构](#后端架构) + - [前端架构](#前端架构) +- [🌟 保持领先](#保持领先) +- [🗺️ 发展路线](#-发展路线) +- [📖 参与贡献](#-参与贡献) + - [核心贡献者](#核心贡献者) + - [杰出大使](#杰出大使) +- [生态体系](#生态体系) +- [📄 开源许可](#-开源许可) +- [🌐 社区联系](#-社区联系) + +#### + +
+ +
+ +## **🚀 快速开始** + +三种方式体验Eigent: + +### ☁️ 云服务版 + +最快体验多智能体能力的云端平台,适合希望即开即用的团队和个人。我们将托管所有模型、API和云存储,确保流畅运行。 + +- **即时接入** - 分钟级搭建工作流 +- **托管运维** - 自动扩缩容与更新 +- **专属支持** - 工程师团队优先响应 + +
+ +![公测版][image-public-beta] + +[**立即访问 www.eigent.ai →**][eigent-site] + +### 🏠 社区版自托管 + +适合需要数据主权、深度定制的用户: + +- **数据隐私** - 敏感数据留存本地 +- **灵活定制** - 按需修改平台功能 +- **成本可控** - 避免云端持续支出 + +#### 1. 环境准备 + +- Node.js与npm +- Python 3.10+ 和 uv + +#### 2. 快速启动** + +```bash +git clone https://github.com/eigent-ai/Eigent-desktop.git +cd Eigent-desktop +npm install +npm run dev +``` + +### 🏢 企业版 + +为需要最高级别安全与定制的组织打造: + +- **商业授权** +- **专属功能**(如SSO定制开发) +- **弹性扩展架构** +- **SLA保障**与实施服务 + +📧 垂询请致 [info@eigent.ai](mailto:info@eigent.ai)。 + +## **✨ 核心功能** +通过Eigent的强大功能释放生产力潜能——专为无缝集成、智能任务执行和无界自动化设计。 + +### 🏭 智能体工作流 +由专业AI智能体团队协同解决复杂任务。Eigent动态分解任务并**并行调度**多个智能体。 + +预置智能体类型: + +- **开发智能体:** 编写执行代码,运行终端命令 +- **搜索智能体:** 网络信息检索与提取 +- **文档智能体:** 创建管理各类文档 +- **多模态智能体:** 处理图像与音频 + +
+ +![工作流][image-workforce] + +[![立即体验]](https://www.eigent.ai/download) + +
+ +### 🧠 全栈模型支持 +使用您偏好的模型本地化部署Eigent。 + +
+ +![模型][image-local-model] + +[![立即体验]](https://www.eigent.ai/download) + +
+ +### 🔌 MCP工具集成 +内置海量**模型上下文协议(MCP)**工具(网页浏览、代码执行、Notion、Google套件、Slack等),并支持**自定义工具安装**。为智能体配备场景化工具链——甚至可集成内部API,扩展能力边界。 + +
+ +![MCP][image-mcp] + +[![立即体验]](https://www.eigent.ai/download) + +
+ +### ✋ 人工介入机制 +当任务受阻或存在不确定性时,Eigent将自动请求人工干预。 + +
+ +![人工介入][image-human-in-the-loop] + +[![立即体验]](https://www.eigent.ai/download) + +
+ +### 👐 100%开源 +Eigent完全开源。您可以下载、审查和修改代码,确保透明度并共建多智能体创新生态。 + +
+ +![开源][image-opensource] + +[![立即体验]](https://www.eigent.ai/download) + +
+ +## 🧩 应用场景 + +### 1. CAMEL GitHub月报 [回放▶️]() +请审阅CAMEL GitHub仓库过去一个月的更新,撰写精炼的新闻简报并发送至我的客户列表。 + +视频 + +### 2. 棕榈泉网球之旅 [回放▶️]() +我是网球爱好者,计划从旧金山前往棕榈泉观看半决赛/决赛。请制定包含航班、酒店、3日行程的详细计划(需含徒步、素食餐厅和水疗)。预算3000美元。行程需为时间轴格式,包含活动、费用详情及购票/预订链接。 + +### 3. AI客户表单与看板 [回放▶️]() +我们是一家专注多智能体通用智能领域的技术咨询公司。请创建潜在客户表单,目标客户特征:B2B美国企业、B轮前发展阶段、需要AI技术赋能、积极采用现代生产力工具(如Slack/Google Workspace/Notion等)。至少列出15家公司详细信息,并同步生成数据看板。 + +### 4. 网站SEO审计 [回放▶️]() +为支持新产品发布,请对我们的官网(https://www.camel-ai.org/)进行全面SEO审计,并提供含可执行建议的优化报告。 + +### 5. Q2财务报告生成 [回放▶️]() +请根据银行转账记录帮我准备面向投资者的Q2财务支出报告。 + +### 6. Notion候选人筛选 [回放▶️]() +我需要招聘强化学习算法工程师。请从Notion页面的"应聘者追踪表"中整理候选人信息,生成包含基本信息与项目经历摘要的Excel表格(突出核心成果),按RL专业度排序。需仔细阅读每位候选人的PDF简历附件。 + +### 7. 学术PDF转会议幻灯 [回放▶️]() +将此PDF转换为适合学术会议的演示文稿(约15分钟),需保留所有关键点。 + +### 8. Figma网页重构 [回放▶️]() +请参照(https://www.camel-ai.org/)风格为OWL项目(https://github.com/camel-ai/owl)创建新网页。首先生成Figma可编辑文件,替换内容后基于设计开发部署。 + +### 9. Blender三维建模 [回放▶️]() +请基于此图像协助完成Blender建模。 + +### 10. H1B1签证申请 [回放▶️]() +我正在申请美国H1B1签证。请协助定位电脑中的申请文档,整理至单独文件夹,并帮助填写申请表。 + +## 🛠️ 技术栈 + +### 后端架构 +- **框架:** FastAPI +- **包管理:** uv +- **异步服务:** Uvicorn +- **认证:** OAuth 2.0, Passlib +- **多智能体框架:** CAMEL + +### 前端架构 + +- **框架:** React +- **桌面应用框架:** Electron +- **语言:** TypeScript +- **UI组件:** Tailwind CSS, Radix UI, Lucide React, Framer Motion +- **状态管理:** Zustand +- **流程编辑器:** React Flow + +## 🌟 保持领先 + +> \[!IMPORTANT] +> +> **为Eigent加星标**,您将通过GitHub第一时间获取所有版本更新通知 ⭐️ + +![][image-star-us] + +### 🗺️ 发展路线 + +![][image-raodmap] + +## 📖 参与贡献 + +我们欢迎各类贡献!请参阅[贡献指南](贡献链接)了解代码提交规范。 + +### 核心贡献者 +| Name | GitHub Profile | +| ---------------- | ------------------------------------------------ | +| Wendong Fan | [Wendong-Fan](https://github.com/Wendong-Fan) | +| Puzhen Zhang | [nitpicker55555](https://github.com/nitpicker55555) | +| Wei Li | [FooFindBar](https://github.com/FooFindBar) | +| Wei Sun | [luoyou](https://github.com/luoyou) | +| Tao Sun | [fengju0213](https://github.com/fengju0213) | +| Xiaotian Jin | [MuggleJinx](https://github.com/MuggleJinx) | +| Yifeng Wang | [zjrwtx](https://github.com/zjrwtx) | +| Weijie Bai (product manager) | [Pakchoioioi](https://github.com/Pakchoioioi) | +| Douglas Lai (product designer) | [Douglasymlai](https://github.com/Douglasymlai) | + + +同时欢迎通过社交媒体和会议活动分享Eigent。参见[大使计划:探索智能体扩展法则][ambassador-link] + +[![][image-ambassador]][ambassador-link] + +### 杰出大使 + +| Name | GitHub Profile | +| ---------------- | ------------------------------------------------ | +| Parth Sharma | [parthshr370](https://github.com/parthshr370) | +| Jino Rohit | [jino-rohit](https://github.com/jino-rohit) | +| Bipul Sharma | [Bipul70701](https://github.com/Bipul70701) | +| Tushar Singh | [tushar80rt](https://github.com/tushar80rt) | +| Harshit Sharma | [Harsh1tSh](https://github.com/Harsh1tSh) | +| Divyansh Goyal | [divital-coder](https://github.com/divital-coder) | + +## 生态体系 +![][image-ecosystem] + + +## **📄 开源许可** + +本仓库采用[**Eigent开源许可**](LICENSE),基于Apache 2.0协议附加补充条款。 + +## 🌐 社区联系 + +- GitHub讨论区 | [探索更多→](https://github.com/eigent-ai/Eigent-desktop/discussions) +- GitHub议题 | [参与贡献→][github-issue-link] +- X(推特) | [关注我们→][social-x-link] +- Discord | [获取支持→][discord-link] +- 微信
+![微信][image-wechat] + + + +[discord-link]: https://discord.camel-ai.org +[discord-shield]: https://img.shields.io/discord/1127171173982154893?color=5865F2&label=discord&labelColor=gray&logo=discord&logoColor=white&style=plastic + + +[eigent-github]: https://github.com/eigent-ai/Eigent-desktop +[github-star]: https://img.shields.io/github/stars/eigent-ai?color=F5F4F0&labelColor=gray&style=plastic&logo=github + +[social-x-link]: https://x.com/Eigent-AI +[social-x-shield]: https://img.shields.io/badge/-%40Eigent_AI-white?labelColor=gray&logo=x&logoColor=white&style=plastic + +[sponsor-link]: https://github.com/sponsors/eigent-ai +[sponsor-shield]: https://img.shields.io/badge/-Sponsor%20Eigent-1d1d1d?logo=github&logoColor=white&style=plastic + +[share-linkedin-link]: https://www.linkedin.com/company/eigent-ai/ +[share-linkedin-shield]: https://img.shields.io/badge/-Share%20on%20linkedin-blue?labelColor=gray&logo=linkedin&logoColor=white&style=plastic + +[share-reddit-link]: https://www.reddit.com/r/CamelAI/comments/1m7oa2n/launch_countdown_of_our_first_ever_agent_product/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button +[share-reddit-shield]: https://img.shields.io/badge/-Share%20on%20reddit-red?labelColor=gray&logo=reddit&logoColor=white&style=flat-plastic + + +[camel-site]: https://www.camel-ai.org +[eigent-site]: https://www.eigent.ai +[docs-site]: https://docs.eigent.ai +[github-issue-link]: https://github.com/eigent-ai/Eigent-desktop/issues +[ambassador-link]:https://www.camel-ai.org/ambassador + + +[立即体验]: https://img.shields.io/badge/-TRY_IT_NOW-363AF5?style=plastic + +[image-seperator]: https://eigent-ai.github.io/.github/assets/seperator.png +[image-head]: https://eigent-ai.github.io/.github/assets/head.png +[image-overview]: https://eigent-ai.github.io/.github/assets/overview.png +[image-public-beta]: https://eigent-ai.github.io/.github/assets/banner.png +[image-ecosystem]: https://eigent-ai.github.io/.github/assets/ecosystem.png +[image-star-us]: https://eigent-ai.github.io/.github/assets/star-us.gif + +[image-workforce]: https://eigent-ai.github.io/.github/assets/feature_dynamic_workforce.gif +[image-human-in-the-loop]: https://eigent-ai.github.io/.github/assets/feature_human_in_the_loop.gif +[image-add-worker]: https://eigent-ai.github.io/.github/assets/feature_add_worker.gif +[image-mcp]: https://eigent-ai.github.io/.github/assets/feature_add_mcp.gif +[image-local-model]: https://eigent-ai.github.io/.github/assets/feature_local_model.gif +[image-opensource]: https://eigent-ai.github.io/.github/assets/opensource.png +[image-wechat]: https://eigent-ai.github.io/.github/assets/wechat.png +[image-ambassador]: https://eigent-ai.github.io/.github/assets/ambassador-program.png \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 000000000..cc8a07b5a --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,21 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info +*.mo +test.py + +# Virtual environments +.venv +.env + +runtime +tmp +img + + +uv_installing.lock +uv_installed.lock \ No newline at end of file diff --git a/backend/.python-version b/backend/.python-version new file mode 100644 index 000000000..59002f8f6 --- /dev/null +++ b/backend/.python-version @@ -0,0 +1 @@ +3.10.16 \ No newline at end of file diff --git a/backend/.vscode/settings.json b/backend/.vscode/settings.json new file mode 100644 index 000000000..18d80ff04 --- /dev/null +++ b/backend/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "cSpell.words": [ + "astep", + "dotenv", + "duckduckgo", + "eigent", + "extendleft", + "toolkits" + ] +} \ No newline at end of file diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 000000000..237ae8dde --- /dev/null +++ b/backend/README.md @@ -0,0 +1,23 @@ +```bash +uv run uvicorn main:api --port 5001 +``` + +i18n operation process: https://github.com/Anbarryprojects/fastapi-babel + +```bash + +pybabel extract -F babel.cfg -o messages.pot . --ignore-pot-creation-date # Extract multilingual strings from code to messages.pot file +pybabel init -i messages.pot -d lang -l zh_CN # Generate Chinese language pack, can only be generated initially, subsequent execution will cause overwrite +pybabel compile -d lang -l zh_CN # Compile language pack + + +pybabel update -i messages.pot -d lang +# -i messages.pot: Specify the input file as the generated .pot file +# -d translations: Specify the translation directory, which typically contains .po files for each language +# -l zh: Specify the language code +``` + +```bash +# regular search +\berror\b(?!\]) +``` diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 000000000..442cf2ca6 --- /dev/null +++ b/backend/app/__init__.py @@ -0,0 +1,6 @@ +from fastapi import FastAPI +from fastapi_pagination import add_pagination + + +api = FastAPI() +add_pagination(api) diff --git a/backend/app/command/__init__.py b/backend/app/command/__init__.py new file mode 100644 index 000000000..13dd71710 --- /dev/null +++ b/backend/app/command/__init__.py @@ -0,0 +1,5 @@ +import click + + +@click.group() +def cli(): ... diff --git a/backend/app/component/babel.py b/backend/app/component/babel.py new file mode 100644 index 000000000..4fd7dc39d --- /dev/null +++ b/backend/app/component/babel.py @@ -0,0 +1,10 @@ +from fastapi_babel import BabelConfigs, Babel +from pathlib import Path + +babel_configs = BabelConfigs( + ROOT_DIR=Path(__file__).parent.parent, + BABEL_DEFAULT_LOCALE="en_US", + BABEL_TRANSLATION_DIRECTORY="lang", +) + +babel = Babel(configs=babel_configs) diff --git a/backend/app/component/code.py b/backend/app/component/code.py new file mode 100644 index 000000000..cdc28a830 --- /dev/null +++ b/backend/app/component/code.py @@ -0,0 +1,16 @@ +success = 0 # success +error = 1 # common error +not_found = 4 # can't found route or resource + +password = 10 # acount password error +token_need = 11 # token need +token_expired = 12 # token expired +token_invalid = 13 # token invalid +token_blocked = 14 # token in block list + + +form_error = 100 # form error + +no_permission_error = 300 # no permission + +program_error = 500 # program error diff --git a/backend/app/component/command.py b/backend/app/component/command.py new file mode 100644 index 000000000..1b320dc88 --- /dev/null +++ b/backend/app/component/command.py @@ -0,0 +1,9 @@ +import os + + +def bun(): + return os.path.expanduser("~/.eigent/bin/bun") + + +def uv(): + return os.path.expanduser("~/.eigent/bin/uv") diff --git a/backend/app/component/debug.py b/backend/app/component/debug.py new file mode 100644 index 000000000..fcfa7b74c --- /dev/null +++ b/backend/app/component/debug.py @@ -0,0 +1,15 @@ +import inspect + + +def dump_class(obj, max_val_len=1000): + cls = obj.__class__ + print(f"Class: {cls.__name__}") + print("Attributes:") + for name, val in vars(obj).items(): + val_str = repr(val) + if len(val_str) > max_val_len: + val_str = val_str[:max_val_len] + "... [truncated]" + print(f" {name} = {val_str}") + # print("Methods:") + # for name, method in inspect.getmembers(cls, predicate=inspect.isfunction): + # print(f" {name}()") diff --git a/backend/app/component/encrypt.py b/backend/app/component/encrypt.py new file mode 100644 index 000000000..2345c09bf --- /dev/null +++ b/backend/app/component/encrypt.py @@ -0,0 +1,11 @@ +from passlib.context import CryptContext + +password = CryptContext(schemes=["bcrypt"], deprecated="auto") + + +def password_hash(password_value: str): + return password.hash(password_value) + + +def password_verify(password_value: str, password_hash: str): + return password.verify(password_value, password_hash) diff --git a/backend/app/component/environment.py b/backend/app/component/environment.py new file mode 100644 index 000000000..3096d84c6 --- /dev/null +++ b/backend/app/component/environment.py @@ -0,0 +1,98 @@ +import importlib.util +import os +from pathlib import Path +from fastapi import APIRouter, FastAPI +from dotenv import load_dotenv +import importlib +from typing import Any, overload + + +env_path = os.path.join(os.path.expanduser("~"), ".eigent", ".env") +load_dotenv(dotenv_path=env_path) + + +@overload +def env(key: str) -> str | None: ... + + +@overload +def env(key: str, default: str) -> str: ... + + +@overload +def env(key: str, default: Any) -> Any: ... + + +def env(key: str, default=None): + return os.getenv(key, default) + + +def env_or_fail(key: str): + value = env(key) + if value is None: + raise Exception("can't get env config value.") + return value + + +def env_not_empty(key: str): + value = env(key) + if not value: + raise Exception("env config value can't be empty.") + return value + + +def base_path(): + return Path(__file__).parent.parent.parent + + +def to_path(path: str): + return base_path() / path + + +def auto_import(package: str): + """ + Automatically import all Python files in the specified directory + """ + # Get all file names in the folder + folder = package.replace(".", "/") + files = os.listdir(folder) + + # Import all .py files in the folder + for file in files: + if file.endswith(".py") and not file.startswith("__"): + module_name = file[:-3] # Remove the .py extension from filename + importlib.import_module(package + "." + module_name) + + +def auto_include_routers(api: FastAPI, prefix: str, directory: str): + """ + Automatically scan all modules in the specified directory and register routes + + :param api: FastAPI instance + :param prefix: Route prefix + :param directory: Directory path to scan + """ + # Convert directory to absolute path + dir_path = Path(directory).resolve() + + # Traverse all .py files in the directory + for root, _, files in os.walk(dir_path): + for file_name in files: + if file_name.endswith("_controller.py") and not file_name.startswith("__"): + # Construct complete file path + file_path = Path(root) / file_name + + # Generate module name + module_name = file_path.stem + + # Load module using importlib + spec = importlib.util.spec_from_file_location(module_name, file_path) + if spec is None or spec.loader is None: + continue + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + # Check if router attribute exists in module and is an APIRouter instance + router = getattr(module, "router", None) + if isinstance(router, APIRouter): + api.include_router(router, prefix=prefix) diff --git a/backend/app/component/model_validation.py b/backend/app/component/model_validation.py new file mode 100644 index 000000000..f962d4dfb --- /dev/null +++ b/backend/app/component/model_validation.py @@ -0,0 +1,41 @@ +from camel.agents import ChatAgent +from camel.models import ModelFactory +from camel.types import ModelPlatformType, ModelType + + +def get_website_content(url: str) -> str: + r"""Gets the content of a website. + + Args: + url (str): The URL of the website. + + Returns: + str: The content of the website. + """ + return "Welcome to CAMEL AI!" + + +def create_agent( + model_platform: str, model_type: str, api_key: str = None, url: str = None, model_config_dict: dict = None, **kwargs +) -> ChatAgent: + platform = getattr(ModelPlatformType, model_platform.upper(), None) + mtype = getattr(ModelType, model_type.upper(), None) + if mtype is None: + mtype = model_type + if platform is None: + raise ValueError(f"Invalid model_platform: {model_platform}") + model = ModelFactory.create( + model_platform=platform, + model_type=mtype, + api_key=api_key, + url=url, + timeout=10, + model_config_dict=model_config_dict, + **kwargs, + ) + agent = ChatAgent( + system_message="You are a helpful assistant that must use the tool get_website_content to get the content of a website.", + model=model, + tools=[get_website_content], + ) + return agent diff --git a/backend/app/component/pydantic/i18n.py b/backend/app/component/pydantic/i18n.py new file mode 100644 index 000000000..e382e65eb --- /dev/null +++ b/backend/app/component/pydantic/i18n.py @@ -0,0 +1,58 @@ +from pathlib import Path +from app.component.babel import babel_configs, babel +import re, os +from fastapi_babel.middleware import Babel, LANGUAGES_PATTERN +from pydantic_i18n import JsonLoader, PydanticI18n + + +def get_language(lang_code: str | None = None): + """Ported from fastapi_babel.middleware.BabelMiddleware.get_language + Applies an available language. + + To apply an available language it will be searched in the language folder for an available one + and will also priotize the one with the highest quality value. The Fallback language will be the + taken from the BABEL_DEFAULT_LOCALE var. + + Args: + babel (Babel): Request scoped Babel instance + lang_code (str): The Value of the Accept-Language Header. + + Returns: + str: The language that should be used. + """ + + if not lang_code: + return babel.config.BABEL_DEFAULT_LOCALE + + matches = re.finditer(LANGUAGES_PATTERN, lang_code) + languages = [ + (f"{m.group(1)}{f'_{m.group(2)}' if m.group(2) else ''}", m.group(3) or "") + for m in matches + ] + languages = sorted( + languages, key=lambda x: x[1], reverse=True + ) # sort the priority, no priority comes last + translation_directory = Path(babel.config.BABEL_TRANSLATION_DIRECTORY) + translation_files = [i.name for i in translation_directory.iterdir()] + explicit_priority = None + + for lang, quality in languages: + if lang in translation_files: + if ( + not quality + ): # languages without quality value having the highest priority 1 + return lang + + elif ( + not explicit_priority + ): # set language with explicit priority <= priority 1 + explicit_priority = lang + + # Return language with explicit priority or default value + return ( + explicit_priority if explicit_priority else babel_configs.BABEL_DEFAULT_LOCALE + ) + + +loader = JsonLoader(os.path.dirname(__file__) + "/translations") +trans = PydanticI18n(loader) diff --git a/backend/app/component/pydantic/translations/en_US.json b/backend/app/component/pydantic/translations/en_US.json new file mode 100644 index 000000000..0891cc744 --- /dev/null +++ b/backend/app/component/pydantic/translations/en_US.json @@ -0,0 +1,98 @@ +{ + "Object has no attribute '{}'": "Object has no attribute '{}'", + "Invalid JSON: {}": "Invalid JSON: {}", + "JSON input should be string, bytes or bytearray": "JSON input should be string, bytes or bytearray", + "Cannot check `{}` when validating from json, use a JsonOrPython validator instead": "Cannot check `{}` when validating from json, use a JsonOrPython validator instead", + "Recursion error - cyclic reference detected": "Recursion error - cyclic reference detected", + "Field required": "Field required", + "Field is frozen": "Field is frozen", + "Instance is frozen": "Instance is frozen", + "Extra inputs are not permitted": "Extra inputs are not permitted", + "Keys should be strings": "Keys should be strings", + "Error extracting attribute: {}": "Error extracting attribute: {}", + "Input should be a valid dictionary or instance of {}": "Input should be a valid dictionary or instance of {}", + "Input should be a valid dictionary or object to extract fields from": "Input should be a valid dictionary or object to extract fields from", + "Input should be a dictionary or an instance of {}": "Input should be a dictionary or an instance of {}", + "Input should be an instance of {}": "Input should be an instance of {}", + "Input should be None": "Input should be None", + "Input should be greater than {}": "Input should be greater than {}", + "Input should be greater than or equal to {}": "Input should be greater than or equal to {}", + "Input should be less than {}": "Input should be less than {}", + "Input should be less than or equal to {}": "Input should be less than or equal to {}", + "Input should be a multiple of {}": "Input should be a multiple of {}", + "Input should be a finite number": "Input should be a finite number", + "Input should be iterable": "Input should be iterable", + "Error iterating over object, error: {}": "Error iterating over object, error: {}", + "Input should be a valid string": "Input should be a valid string", + "Input should be a string, not an instance of a subclass of str": "Input should be a string, not an instance of a subclass of str", + "Input should be a valid string, unable to parse raw data as a unicode string": "Input should be a valid string, unable to parse raw data as a unicode string", + "String should have at least {}": "String should have at least {}", + "String should have at most {}": "String should have at most {}", + "String should match pattern '{}'": "String should match pattern '{}'", + "Input should be {}": "Input should be {}", + "Input should be a valid dictionary": "Input should be a valid dictionary", + "Input should be a valid mapping, error: {}": "Input should be a valid mapping, error: {}", + "Input should be a valid list": "Input should be a valid list", + "Input should be a valid tuple": "Input should be a valid tuple", + "Input should be a valid set": "Input should be a valid set", + "Input should be a valid boolean": "Input should be a valid boolean", + "Input should be a valid boolean, unable to interpret input": "Input should be a valid boolean, unable to interpret input", + "Input should be a valid integer": "Input should be a valid integer", + "Input should be a valid integer, unable to parse string as an integer": "Input should be a valid integer, unable to parse string as an integer", + "Unable to parse input string as an integer, exceeded maximum size": "Unable to parse input string as an integer, exceeded maximum size", + "Input should be a valid integer, got a number with a fractional part": "Input should be a valid integer, got a number with a fractional part", + "Input should be a valid number": "Input should be a valid number", + "Input should be a valid number, unable to parse string as a number": "Input should be a valid number, unable to parse string as a number", + "Input should be a valid bytes": "Input should be a valid bytes", + "Data should have at least {}": "Data should have at least {}", + "Data should have at most {}": "Data should have at most {}", + "Data should be valid {}": "Data should be valid {}", + "Value error, {}": "Value error, {}", + "Assertion failed, {}": "Assertion failed, {}", + "Input should be a valid date": "Input should be a valid date", + "Input should be a valid date in the format YYYY-MM-DD, {}": "Input should be a valid date in the format YYYY-MM-DD, {}", + "Input should be a valid date or datetime, {}": "Input should be a valid date or datetime, {}", + "Datetimes provided to dates should have zero time - e.g. be exact dates": "Datetimes provided to dates should have zero time - e.g. be exact dates", + "Date should be in the past": "Date should be in the past", + "Date should be in the future": "Date should be in the future", + "Input should be a valid time": "Input should be a valid time", + "Input should be in a valid time format, {}": "Input should be in a valid time format, {}", + "Input should be a valid datetime": "Input should be a valid datetime", + "Input should be a valid datetime, {}": "Input should be a valid datetime, {}", + "Invalid datetime object, got {}": "Invalid datetime object, got {}", + "Input should be a valid datetime or date, {}": "Input should be a valid datetime or date, {}", + "Input should be in the past": "Input should be in the past", + "Input should be in the future": "Input should be in the future", + "Input should not have timezone info": "Input should not have timezone info", + "Input should have timezone info": "Input should have timezone info", + "Timezone offset of {}": "Timezone offset of {}", + "Input should be a valid timedelta": "Input should be a valid timedelta", + "Input should be a valid timedelta, {}": "Input should be a valid timedelta, {}", + "Input should be a valid frozenset": "Input should be a valid frozenset", + "Input should be a subclass of {}": "Input should be a subclass of {}", + "Input should be callable": "Input should be callable", + "Input tag '{}": "Input tag '{}", + "Unable to extract tag using discriminator {}": "Unable to extract tag using discriminator {}", + "Arguments must be a tuple, list or a dictionary": "Arguments must be a tuple, list or a dictionary", + "Missing required argument": "Missing required argument", + "Unexpected keyword argument": "Unexpected keyword argument", + "Missing required keyword only argument": "Missing required keyword only argument", + "Unexpected positional argument": "Unexpected positional argument", + "Missing required positional only argument": "Missing required positional only argument", + "Got multiple values for argument": "Got multiple values for argument", + "URL input should be a string or URL": "URL input should be a string or URL", + "Input should be a valid URL, {}": "Input should be a valid URL, {}", + "Input violated strict URL syntax rules, {}": "Input violated strict URL syntax rules, {}", + "URL should have at most {}": "URL should have at most {}", + "URL scheme should be {}": "URL scheme should be {}", + "UUID input should be a string, bytes or UUID object": "UUID input should be a string, bytes or UUID object", + "Input should be a valid UUID, {}": "Input should be a valid UUID, {}", + "UUID version {} expected": "UUID version {} expected", + "Decimal input should be an integer, float, string or Decimal object": "Decimal input should be an integer, float, string or Decimal object", + "Input should be a valid decimal": "Input should be a valid decimal", + "Decimal input should have no more than {} in total": "Decimal input should have no more than {} in total", + "Decimal input should have no more than {}": "Decimal input should have no more than {}", + "Decimal input should have no more than {} before the decimal point": "Decimal input should have no more than {} before the decimal point", + "Input should be a valid python complex object, a number, or a valid complex string following the rules at https://docs.python.org/3/library/functions.html#complex": "Input should be a valid python complex object, a number, or a valid complex string following the rules at https://docs.python.org/3/library/functions.html#complex", + "Input should be a valid complex string following the rules at https://docs.python.org/3/library/functions.html#complex": "Input should be a valid complex string following the rules at https://docs.python.org/3/library/functions.html#complex" +} diff --git a/backend/app/component/pydantic/translations/zh_CN.json b/backend/app/component/pydantic/translations/zh_CN.json new file mode 100644 index 000000000..c63db8258 --- /dev/null +++ b/backend/app/component/pydantic/translations/zh_CN.json @@ -0,0 +1,98 @@ +{ + "Object has no attribute '{}'": "对象没有属性'{}'", + "Invalid JSON: {}": "无效的 JSON:{}", + "JSON input should be string, bytes or bytearray": "JSON 输入应为字符串、字节或字节数组", + "Cannot check `{}` when validating from json, use a JsonOrPython validator instead": "在从 JSON 验证时,无法检查`{}`,请改用 JsonOrPython 验证器", + "Recursion error - cyclic reference detected": "递归错误 - 检测到循环引用", + "Field required": "字段必填", + "Field is frozen": "字段已冻结", + "Instance is frozen": "实例已冻结", + "Extra inputs are not permitted": "不允许额外输入", + "Keys should be strings": "键应为字符串", + "Error extracting attribute: {}": "提取属性时出错:{}", + "Input should be a valid dictionary or instance of {}": "输入应为有效的字典或{}的实例", + "Input should be a valid dictionary or object to extract fields from": "输入应为有效的字典或可用于提取字段的对象", + "Input should be a dictionary or an instance of {}": "输入应为字典或{}的实例", + "Input should be an instance of {}": "输入应为{}的实例", + "Input should be None": "输入应为 None", + "Input should be greater than {}": "输入应大于{}", + "Input should be greater than or equal to {}": "输入应大于或等于{}", + "Input should be less than {}": "输入应小于{}", + "Input should be less than or equal to {}": "输入应小于或等于{}", + "Input should be a multiple of {}": "输入应为{}的倍数", + "Input should be a finite number": "输入应为有限数字", + "Input should be iterable": "输入应为可迭代对象", + "Error iterating over object, error: {}": "迭代对象时出错,错误:{}", + "Input should be a valid string": "输入应为有效字符串", + "Input should be a string, not an instance of a subclass of str": "输入应为字符串,而不是 str 的子类实例", + "Input should be a valid string, unable to parse raw data as a unicode string": "输入应为有效字符串,无法将原始数据解析为 Unicode 字符串", + "String should have at least {}": "字符串应至少有{}", + "String should have at most {}": "字符串应最多有{}", + "String should match pattern '{}'": "字符串应匹配模式'{}'", + "Input should be {}": "输入应为{}", + "Input should be a valid dictionary": "输入应为有效的字典", + "Input should be a valid mapping, error: {}": "输入应为有效的映射,错误:{}", + "Input should be a valid list": "输入应为有效的列表", + "Input should be a valid tuple": "输入应为有效的元组", + "Input should be a valid set": "输入应为有效的集合", + "Input should be a valid boolean": "输入应为有效的布尔值", + "Input should be a valid boolean, unable to interpret input": "输入应为有效的布尔值,无法解析输入", + "Input should be a valid integer": "输入应为有效的整数", + "Input should be a valid integer, unable to parse string as an integer": "输入应为有效的整数,无法将字符串解析为整数", + "Unable to parse input string as an integer, exceeded maximum size": "无法将输入字符串解析为整数,超出最大尺寸", + "Input should be a valid integer, got a number with a fractional part": "输入应为有效的整数,但输入的数字有小数部分", + "Input should be a valid number": "输入应为有效的数字", + "Input should be a valid number, unable to parse string as a number": "输入应为有效的数字,无法将字符串解析为数字", + "Input should be a valid bytes": "输入应为有效的字节", + "Data should have at least {}": "数据应至少有{}", + "Data should have at most {}": "数据应最多有{}", + "Data should be valid {}": "数据应为有效的{}", + "Value error, {}": "值错误,{}", + "Assertion failed, {}": "断言失败,{}", + "Input should be a valid date": "输入应为有效的日期", + "Input should be a valid date in the format YYYY-MM-DD, {}": "输入应为有效的日期,格式为 YYYY-MM-DD,{}", + "Input should be a valid date or datetime, {}": "输入应为有效的日期或日期时间,{}", + "Datetimes provided to dates should have zero time - e.g. be exact dates": "提供给日期的日期时间应为零时间,即为精确日期", + "Date should be in the past": "日期应为过去的日期", + "Date should be in the future": "日期应为未来的日期", + "Input should be a valid time": "输入应为有效的时间", + "Input should be in a valid time format, {}": "输入应为有效的时间格式,{}", + "Input should be a valid datetime": "输入应为有效的日期时间", + "Input should be a valid datetime, {}": "输入应为有效的日期时间,{}", + "Invalid datetime object, got {}": "无效的日期时间对象,得到{}", + "Input should be a valid datetime or date, {}": "输入应为有效的日期时间或日期,{}", + "Input should be in the past": "输入应为过去的日期/时间", + "Input should be in the future": "输入应为未来的日期/时间", + "Input should not have timezone info": "输入不应包含时区信息", + "Input should have timezone info": "输入应包含时区信息", + "Timezone offset of {}": "时区偏移量为{}", + "Input should be a valid timedelta": "输入应为有效的 timedelta", + "Input should be a valid timedelta, {}": "输入应为有效的 timedelta,{}", + "Input should be a valid frozenset": "输入应为有效的 frozenset", + "Input should be a subclass of {}": "输入应为{}的子类", + "Input should be callable": "输入应为可调用对象", + "Input tag '{}": "输入标签'{}'", + "Unable to extract tag using discriminator {}": "无法使用区分符{}提取标签", + "Arguments must be a tuple, list or a dictionary": "参数必须是元组、列表或字典", + "Missing required argument": "缺少必需的参数", + "Unexpected keyword argument": "意外的关键字参数", + "Missing required keyword only argument": "缺少必需的关键字参数", + "Unexpected positional argument": "意外的位置参数", + "Missing required positional only argument": "缺少必需的位置参数", + "Got multiple values for argument": "为参数提供了多个值", + "URL input should be a string or URL": "URL 输入应为字符串或 URL", + "Input should be a valid URL, {}": "输入应为有效的 URL,{}", + "Input violated strict URL syntax rules, {}": "输入违反了严格的 URL 语法规则,{}", + "URL should have at most {}": "URL 应最多有{}", + "URL scheme should be {}": "URL 的协议应为{}", + "UUID input should be a string, bytes or UUID object": "UUID 输入应为字符串、字节或 UUID 对象", + "Input should be a valid UUID, {}": "输入应为有效的 UUID,{}", + "UUID version {} expected": "期望的 UUID 版本为{}", + "Decimal input should be an integer, float, string or Decimal object": "十进制输入应为整数、浮点数、字符串或 Decimal 对象", + "Input should be a valid decimal": "输入应为有效的十进制数", + "Decimal input should have no more than {} in total": "十进制输入的总位数不应超过{}", + "Decimal input should have no more than {}": "十进制输入不应超过{}", + "Decimal input should have no more than {} before the decimal point": "十进制输入的小数点前不应超过{}位", + "Input should be a valid python complex object, a number, or a valid complex string following the rules at https://docs.python.org/3/library/functions.html#complex": "输入应为有效的 Python 复杂对象、数字,或遵循 https://docs.python.org/3/library/functions.html#complex 规则的有效复杂字符串", + "Input should be a valid complex string following the rules at https://docs.python.org/3/library/functions.html#complex": "输入应为遵循 https://docs.python.org/3/library/functions.html#complex 规则的有效复杂字符串" +} diff --git a/backend/app/controller/__init__.py b/backend/app/controller/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/app/controller/chat_controller.py b/backend/app/controller/chat_controller.py new file mode 100644 index 000000000..2f67bc9f0 --- /dev/null +++ b/backend/app/controller/chat_controller.py @@ -0,0 +1,83 @@ +import asyncio +import os +import re +from pathlib import Path +from dotenv import load_dotenv +from fastapi import APIRouter, Request, Response +from fastapi.responses import StreamingResponse +from app.component import code +from app.exception.exception import UserException +from app.model.chat import Chat, HumanReply, McpServers, Status, SupplementChat +from app.service.chat_service import step_solve +from app.service.task import ( + Action, + ActionImproveData, + ActionInstallMcpData, + ActionStopData, + ActionSupplementData, + get_task_lock, +) + + +router = APIRouter(tags=["chat"]) + + +@router.post("/chat", name="start chat") +def post(data: Chat, request: Request): + load_dotenv(dotenv_path=data.env_path) + + os.environ["file_save_path"] = data.file_save_path() + os.environ["browser_port"] = str(data.browser_port) + os.environ["OPENAI_API_KEY"] = data.api_key + os.environ["OPENAI_API_BASE_URL"] = data.api_url or "https://api.openai.com/v1" + os.environ["CAMEL_MODEL_LOG_ENABLED"] = "true" + + email = re.sub(r'[\\/*?:"<>|\s]', "_", data.email.split("@")[0]).strip(".") + camel_log = Path.home() / ".eigent" / email / ("task_" + data.task_id) / "camel_logs" + camel_log.mkdir(parents=True, exist_ok=True) + + os.environ["CAMEL_LOG_DIR"] = str(camel_log) + + if data.is_cloud(): + os.environ["cloud_api_key"] = data.api_key + return StreamingResponse(step_solve(data, request), media_type="text/event-stream") + + +@router.post("/chat/{id}", name="improve chat") +def improve(id: str, data: SupplementChat): + task_lock = get_task_lock(id) + if task_lock.status == Status.done: + raise UserException(code.error, "Task was done") + asyncio.run(task_lock.put_queue(ActionImproveData(data=data.question))) + return Response(status_code=201) + + +@router.put("/chat/{id}", name="supplement task") +def supplement(id: str, data: SupplementChat): + task_lock = get_task_lock(id) + if task_lock.status != Status.done: + raise UserException(code.error, "Please wait task done") + asyncio.run(task_lock.put_queue(ActionSupplementData(data=data))) + return Response(status_code=201) + + +@router.delete("/chat/{id}", name="stop chat") +def stop(id: str): + """stop the task""" + task_lock = get_task_lock(id) + asyncio.run(task_lock.put_queue(ActionStopData(action=Action.stop))) + return Response(status_code=204) + + +@router.post("/chat/{id}/human-reply") +def human_reply(id: str, data: HumanReply): + task_lock = get_task_lock(id) + asyncio.run(task_lock.put_human_input(data.agent, data.reply)) + return Response(status_code=201) + + +@router.post("/chat/{id}/install-mcp") +def install_mcp(id: str, data: McpServers): + task_lock = get_task_lock(id) + asyncio.run(task_lock.put_queue(ActionInstallMcpData(action=Action.install_mcp, data=data))) + return Response(status_code=201) diff --git a/backend/app/controller/model_controller.py b/backend/app/controller/model_controller.py new file mode 100644 index 000000000..7888ff2a6 --- /dev/null +++ b/backend/app/controller/model_controller.py @@ -0,0 +1,43 @@ +from fastapi import APIRouter +from pydantic import BaseModel, Field +from app.component.model_validation import create_agent + + +router = APIRouter(tags=["model"]) + + +class ValidateModelRequest(BaseModel): + model_platform: str = Field("OPENAI", description="Model platform") + model_type: str = Field("GPT_4O_MINI", description="Model type") + api_key: str | None = Field(None, description="API key") + url: str | None = Field(None, description="Model URL") + model_config_dict: dict | None = Field(None, description="Model config dict") + extra_params: dict | None = Field(None, description="Extra model parameters") + + +class ValidateModelResponse(BaseModel): + is_valid: bool = Field(..., description="Is valid") + is_tool_calls: bool = Field(..., description="Is tool call used") + message: str = Field(..., description="Message") + + +@router.post("/model/validate") +async def validate_model(request: ValidateModelRequest): + try: + extra = request.extra_params or {} + agent = create_agent( + request.model_platform, + request.model_type, + api_key=request.api_key, + url=request.url, + model_config_dict=request.model_config_dict, + **extra, + ) + response = agent.step(input_message="Get the content of https://www.camel.ai") + except Exception as e: + return ValidateModelResponse(is_valid=False, is_tool_calls=False, message=str(e)) + return ValidateModelResponse( + is_valid=True if response else False, + is_tool_calls=response.info["tool_calls"][0].result == "Welcome to CAMEL AI!", + message="", + ) diff --git a/backend/app/controller/task_controller.py b/backend/app/controller/task_controller.py new file mode 100644 index 000000000..9ae12cf93 --- /dev/null +++ b/backend/app/controller/task_controller.py @@ -0,0 +1,52 @@ +from typing import Literal +from dotenv import load_dotenv +from fastapi import APIRouter, Response +from loguru import logger +from pydantic import BaseModel +from app.model.chat import NewAgent, UpdateData +from app.service.task import ( + Action, + ActionNewAgent, + ActionTakeControl, + ActionStartData, + ActionUpdateTaskData, + get_task_lock, +) +import asyncio + + +router = APIRouter(tags=["task"]) + + +@router.post("/task/{id}/start", name="start task") +def start(id: str): + task_lock = get_task_lock(id) + logger.debug(f"start task {id}") + asyncio.run(task_lock.put_queue(ActionStartData(action=Action.start))) + logger.debug(f"start task {id} success") + return Response(status_code=201) + + +@router.put("/task/{id}", name="update task") +def put(id: str, data: UpdateData): + task_lock = get_task_lock(id) + asyncio.run(task_lock.put_queue(ActionUpdateTaskData(action=Action.update_task, data=data))) + return Response(status_code=201) + + +class TakeControl(BaseModel): + action: Literal[Action.pause, Action.resume] + + +@router.put("/task/{id}/take-control", name="take control pause or resume") +def take_control(id: str, data: TakeControl): + task_lock = get_task_lock(id) + asyncio.run(task_lock.put_queue(ActionTakeControl(action=data.action))) + return Response(status_code=204) + + +@router.post("/task/{id}/add-agent", name="add new agent") +def add_agent(id: str, data: NewAgent): + load_dotenv(dotenv_path=data.env_path) + asyncio.run(get_task_lock(id).put_queue(ActionNewAgent(**data.model_dump()))) + return Response(status_code=204) diff --git a/backend/app/controller/tool_controller.py b/backend/app/controller/tool_controller.py new file mode 100644 index 000000000..88474a2d8 --- /dev/null +++ b/backend/app/controller/tool_controller.py @@ -0,0 +1,18 @@ +from fastapi import APIRouter + +from app.utils.toolkit.notion_mcp_toolkit import NotionMCPToolkit + + +router = APIRouter(tags=["task"]) + + +@router.post("/install/tool/{tool}", name="install tool") +async def install_tool(tool: str): + if tool == "notion": + toolkit = NotionMCPToolkit(tool) + await toolkit.connect() + else: + return {"error": "Tool not found"} + tools = [tool.func.__name__ for tool in toolkit.get_tools()] + await toolkit.disconnect() + return tools diff --git a/backend/app/exception/exception.py b/backend/app/exception/exception.py new file mode 100644 index 000000000..44954636c --- /dev/null +++ b/backend/app/exception/exception.py @@ -0,0 +1,20 @@ +class UserException(Exception): + def __init__(self, code: int, description: str): + self.code = code + self.description = description + + +class TokenException(Exception): + def __init__(self, code: int, text: str): + self.code = code + self.text = text + + +class NoPermissionException(Exception): + def __init__(self, text: str): + self.text = text + + +class ProgramException(Exception): + def __init__(self, text: str): + self.text = text diff --git a/backend/app/exception/handler.py b/backend/app/exception/handler.py new file mode 100644 index 000000000..7d2cebc9e --- /dev/null +++ b/backend/app/exception/handler.py @@ -0,0 +1,47 @@ +from fastapi import Request +from fastapi.encoders import jsonable_encoder +from fastapi.exceptions import RequestValidationError +from fastapi.responses import JSONResponse +from app import api +from app.component import code +from app.exception.exception import NoPermissionException, ProgramException, TokenException +from app.component.pydantic.i18n import trans, get_language +from app.exception.exception import UserException + + +@api.exception_handler(RequestValidationError) +async def request_exception(request: Request, e: RequestValidationError): + if (lang := get_language(request.headers.get("Accept-Language"))) is None: + lang = "en_US" + return JSONResponse( + content={ + "code": code.form_error, + "error": jsonable_encoder(trans.translate(list(e.errors()), locale=lang)), + } + ) + + +@api.exception_handler(TokenException) +async def token_exception(request: Request, e: TokenException): + return JSONResponse(content={"code": e.code, "text": e.text}) + + +@api.exception_handler(UserException) +async def user_exception(request: Request, e: UserException): + return JSONResponse(content={"code": e.code, "text": e.description}) + + +@api.exception_handler(NoPermissionException) +async def no_permission(request: Request, exception: NoPermissionException): + return JSONResponse( + status_code=200, + content={"code": code.no_permission_error, "text": exception.text}, + ) + + +@api.exception_handler(ProgramException) +async def program_exception(request: Request, exception: NoPermissionException): + return JSONResponse( + status_code=200, + content={"code": code.program_error, "text": exception.text}, + ) diff --git a/backend/app/middleware/__init__.py b/backend/app/middleware/__init__.py new file mode 100644 index 000000000..6d0cbae32 --- /dev/null +++ b/backend/app/middleware/__init__.py @@ -0,0 +1,6 @@ +from app import api +from app.component.babel import babel_configs +from fastapi_babel import BabelMiddleware + + +api.add_middleware(BabelMiddleware, babel_configs=babel_configs) diff --git a/backend/app/model/chat.py b/backend/app/model/chat.py new file mode 100644 index 000000000..2de412bc3 --- /dev/null +++ b/backend/app/model/chat.py @@ -0,0 +1,110 @@ +from enum import Enum +import json +from pathlib import Path +import re +from typing import Literal +from loguru import logger +from pydantic import BaseModel, field_validator +from camel.types import ModelPlatformType, ModelType, RoleType + + +class Status(str, Enum): + confirming = "confirming" + confirmed = "confirmed" + processing = "processing" + done = "done" + + +class ChatHistory(BaseModel): + role: RoleType + content: str + + +McpServers = dict[Literal["mcpServers"], dict[str, dict]] + + +class Chat(BaseModel): + task_id: str + question: str + email: str + attaches: list[str] = [] + model_platform: ModelPlatformType + model_type: str + api_key: str + api_url: str | None = None # for cloud version, user don't need to set api_url + language: str = "en" + browser_port: int = 9222 + max_retries: int = 3 + allow_local_system: bool = False + installed_mcp: McpServers = {"mcpServers": {}} + bun_mirror: str = "" + uvx_mirror: str = "" + env_path: str | None = None + summary_prompt: str = ( + "After completing the task, please generate a summary of the entire task completion. " + "The summary must be enclosed in tags and include:\n" + "1. A confirmation of task completion, referencing the original goal.\n" + "2. A high-level overview of the work performed and the final outcome.\n" + "3. A bulleted list of key results or accomplishments.\n" + "Adopt a confident and professional tone." + ) + new_agents: list["NewAgent"] = [] + + @field_validator("model_type") + @classmethod + def check_model_type(cls, model_type: str): + try: + ModelType(model_type) + except ValueError: + # raise ValueError("Invalid model type") + logger.debug("model_type is invalid") + return model_type + + def get_bun_env(self) -> dict[str, str]: + return {"NPM_CONFIG_REGISTRY": self.bun_mirror} if self.bun_mirror else {} + + def get_uvx_env(self) -> dict[str, str]: + return {"UV_DEFAULT_INDEX": self.uvx_mirror, "PIP_INDEX_URL": self.uvx_mirror} if self.uvx_mirror else {} + + def is_cloud(self): + return self.api_url is not None + + def file_save_path(self, path: str | None = None): + email = re.sub(r'[\\/*?:"<>|\s]', "_", self.email.split("@")[0]).strip(".") + save_path = Path.home() / "eigent" / email / ("task_" + self.task_id) + if path is not None: + save_path = save_path / path + save_path.mkdir(parents=True, exist_ok=True) + + return str(save_path) + + +class SupplementChat(BaseModel): + question: str + + +class HumanReply(BaseModel): + agent: str + reply: str + + +class TaskContent(BaseModel): + id: str + content: str + + +class UpdateData(BaseModel): + task: list[TaskContent] + + +class NewAgent(BaseModel): + name: str + description: str + tools: list[str] + mcp_tools: McpServers | None + env_path: str | None = None + + +def sse_json(step: str, data): + res_format = {"step": step, "data": data} + return f"data: {json.dumps(res_format, ensure_ascii=False)}\n\n" diff --git a/backend/app/service/chat_service.py b/backend/app/service/chat_service.py new file mode 100644 index 000000000..259f31006 --- /dev/null +++ b/backend/app/service/chat_service.py @@ -0,0 +1,480 @@ +import asyncio +import datetime +from pathlib import Path +import platform +from typing import Literal +from fastapi import Request +from inflection import titleize +from pydash import chain +from app.component.debug import dump_class +from app.component.environment import env +from app.service.task import ( + ActionImproveData, + ActionInstallMcpData, + ActionNewAgent, + create_task_lock, + delete_task_lock, +) +from camel.toolkits import AgentCommunicationToolkit, ToolkitMessageIntegration +from app.utils.toolkit.human_toolkit import HumanToolkit +from app.utils.toolkit.note_taking_toolkit import NoteTakingToolkit +from app.utils.workforce import Workforce +from loguru import logger +from app.model.chat import Chat, NewAgent, Status, sse_json, TaskContent +from camel.tasks import Task +from app.utils.agent import ( + ListenChatAgent, + agent_model, + get_mcp_tools, + get_toolkits, + mcp_agent, + developer_agent, + document_agent, + multi_modal_agent, + search_agent, + social_medium_agent, + task_summary_agent, + question_confirm_agent, +) +from app.service.task import Action, Agents +from app.utils.server.sync_step import sync_step +from camel.types import ModelPlatformType +from camel.models import ModelProcessingError + + +@sync_step +async def step_solve(options: Chat, request: Request): + # if True: + # import faulthandler + + # faulthandler.enable() + # for second in [5, 10, 20, 30, 60, 120, 240]: + # faulthandler.dump_traceback_later(second) + task_lock = create_task_lock(options.task_id) + + start_event_loop = True + question_agent = question_confirm_agent(options) + camel_task = None + while True: + if await request.is_disconnected(): + try: + workforce.stop() + except NameError: + pass + break + try: + item = await task_lock.get_queue() + logger.info(f"item: {dump_class(item)}") + except Exception as e: + logger.error(f"Error getting item from queue: {e}") + break + + try: + if item.action == Action.improve or start_event_loop: + # from viztracer import VizTracer + + # tracer = VizTracer() + # tracer.start() + if start_event_loop is True: + question = options.question + start_event_loop = False + else: + assert isinstance(item, ActionImproveData) + question = item.data + if len(question) < 12 and len(options.attaches) == 0: + confirm = await question_confirm(question_agent, question) + else: + confirm = True + + if confirm is not True: + yield confirm + else: + yield sse_json("confirmed", "") + (workforce, mcp) = await construct_workforce(options) + for new_agent in options.new_agents: + workforce.add_single_agent_worker( + format_agent_description(new_agent), await new_agent_model(new_agent, options) + ) + summary_task_agent = task_summary_agent(options) + task_lock.status = Status.confirmed + question = question + options.summary_prompt + camel_task = Task(content=question, id=options.task_id) + if len(options.attaches) > 0: + camel_task.additional_info = {Path(file_path).name: file_path for file_path in options.attaches} + + sub_tasks = workforce.eigent_make_sub_tasks(camel_task) + summary_task_content = await summary_task(summary_task_agent, camel_task) + yield to_sub_tasks(camel_task, summary_task_content) + # tracer.stop() + # tracer.save("trace.json") + if env("debug") == "on": + task_lock.status = Status.processing + task = asyncio.create_task(workforce.eigent_start(sub_tasks)) + task_lock.add_background_task(task) + + elif item.action == Action.update_task: + assert camel_task is not None + update_tasks = {item.id: item for item in item.data.task} + sub_tasks = update_sub_tasks(sub_tasks, update_tasks) + add_sub_tasks(camel_task, item.data.task) + yield to_sub_tasks(camel_task, summary_task_content) + elif item.action == Action.start: + task_lock.status = Status.processing + task = asyncio.create_task(workforce.eigent_start(sub_tasks)) + task_lock.add_background_task(task) + elif item.action == Action.task_state: + yield sse_json("task_state", item.data) + elif item.action == Action.create_agent: + yield sse_json("create_agent", item.data) + elif item.action == Action.activate_agent: + yield sse_json("activate_agent", item.data) + elif item.action == Action.deactivate_agent: + yield sse_json("deactivate_agent", dict(item.data)) + elif item.action == Action.assign_task: + yield sse_json("assign_task", item.data) + elif item.action == Action.activate_toolkit: + yield sse_json("activate_toolkit", item.data) + elif item.action == Action.deactivate_toolkit: + yield sse_json("deactivate_toolkit", item.data) + elif item.action == Action.write_file: + yield sse_json( + "write_file", + {"file_path": item.data, "process_task_id": item.process_task_id}, + ) + elif item.action == Action.ask: + yield sse_json("ask", item.data) + elif item.action == Action.notice: + yield sse_json( + "notice", + {"notice": item.data, "process_task_id": item.process_task_id}, + ) + elif item.action == Action.search_mcp: + yield sse_json("search_mcp", item.data) + elif item.action == Action.install_mcp: + task = asyncio.create_task(install_mcp(mcp, item)) + task_lock.add_background_task(task) + elif item.action == Action.terminal: + yield sse_json( + "terminal", + {"output": item.data, "process_task_id": item.process_task_id}, + ) + elif item.action == Action.pause: + workforce.pause() + elif item.action == Action.resume: + workforce.resume() + elif item.action == Action.new_agent: + workforce.pause() + workforce.add_single_agent_worker(format_agent_description(item), await new_agent_model(item, options)) + workforce.resume() + elif item.action == Action.end: + assert camel_task is not None + task_lock.status = Status.done + yield sse_json("end", str(camel_task.result)) + workforce.stop_gracefully() + break + elif item.action == Action.supplement: + assert camel_task is not None + task_lock.status = Status.processing + camel_task.add_subtask( + Task( + content=item.data.question, + id=f"{camel_task.id}.{len(camel_task.subtasks)}", + ) + ) + task = asyncio.create_task(workforce.eigent_start(camel_task.subtasks)) + task_lock.add_background_task(task) + elif item.action == Action.budget_not_enough: + workforce.pause() + yield sse_json(Action.budget_not_enough, {"message": "budget not enouth"}) + elif item.action == Action.stop: + if workforce._running: + workforce.stop() + workforce.stop_gracefully() + await delete_task_lock(task_lock.id) + break + else: + logger.warning(f"Unknown action: {item.action}") + except ModelProcessingError as e: + logger.error(f"Error processing action {item.action}: {e}") + yield sse_json("error", {"message": str(e)}) + if workforce._running: + workforce.stop() + except Exception as e: + logger.error(f"Error processing action {item.action}: {e}") + raise e + # Continue processing other items instead of breaking + + +async def install_mcp( + mcp: ListenChatAgent, + install_mcp: ActionInstallMcpData, +): + mcp.add_tools(await get_mcp_tools(install_mcp.data)) + + +def to_sub_tasks(task: Task, summary_task_content: str): + return sse_json( + "to_sub_tasks", + { + "summary_task": summary_task_content, + "sub_tasks": tree_sub_tasks(task.subtasks), + }, + ) + + +def tree_sub_tasks(sub_tasks: list[Task], depth: int = 0): + if depth > 5: + return [] + return ( + chain(sub_tasks) + .map( + lambda x: { + "id": x.id, + "content": x.content, + "state": x.state, + "subtasks": tree_sub_tasks(x.subtasks, depth + 1), + } + ) + .value() + ) + + +def update_sub_tasks(sub_tasks: list[Task], update_tasks: dict[str, TaskContent], depth: int = 0): + if depth > 5: # limit the depth of the recursion + return [] + + i = 0 + while i < len(sub_tasks): + item = sub_tasks[i] + if item.id in update_tasks: + item.content = update_tasks[item.id].content + update_sub_tasks(item.subtasks, update_tasks, depth + 1) + i += 1 + else: + sub_tasks.pop(i) + return sub_tasks + + +def add_sub_tasks(camel_task: Task, update_tasks: list[TaskContent]): + for item in update_tasks: + if item.id == "": # + camel_task.add_subtask( + Task( + content=item.content, + id=f"{camel_task.id}.{len(camel_task.subtasks) + 1}", + ) + ) + + +async def question_confirm(agent: ListenChatAgent, prompt: str) -> str | Literal[True]: + prompt = f""" +> **Your Role:** You are a highly capable agent. Your primary function is to analyze a user's request and determine the appropriate course of action. +> +> **Your Process:** +> +> 1. **Analyze the User's Query:** Carefully examine the user's request: `{prompt}`. +> +> 2. **Categorize the Query:** +> * **Simple Query:** Is this a simple greeting, a question that can be answered directly, or a conversational interaction (e.g., "hello", "thank you")? +> * **Complex Task:** Is this a request that requires a series of steps, code execution, or interaction with tools to complete? +> +> 3. **Execute Your Decision:** +> * **For a Simple Query:** Provide a direct and helpful response. +> * **For a Complex Task:** Your *only* response should be "yes". This will trigger a specialized workforce to handle the task. Do not include any other text, punctuation, or pleasantries. + """ + resp = agent.step(prompt) + logger.info(f"resp: {agent.chat_history}") + if resp.msgs[0].content.lower() != "yes": + return sse_json("wait_confirm", {"content": resp.msgs[0].content}) + else: + return True + + +async def summary_task(agent: ListenChatAgent, task: Task) -> str: + prompt = f"""The user's task is: +--- +{task.to_string()} +--- +Your instructions are: +1. Come up with a short and descriptive name for this task. +2. Create a concise summary of the task's main points and objectives. +3. Return the task name and the summary, separated by a vertical bar (|). + +Example format: "Task Name|This is the summary of the task." +Do not include any other text or formatting. +""" + res = agent.step(prompt) + logger.info(f"summary_task: {res.msgs[0].content}") + return res.msgs[0].content + + +async def construct_workforce(options: Chat) -> tuple[Workforce, ListenChatAgent]: + working_directory = options.file_save_path() + [coordinator_agent, task_agent] = [ + agent_model( + key, + prompt, + options, + [ + *( + ToolkitMessageIntegration( + message_handler=HumanToolkit(options.task_id, key).send_message_to_user + ).register_toolkits(NoteTakingToolkit(options.task_id, working_directory=working_directory)) + ).get_tools() + ], + ) + for key, prompt in { + Agents.coordinator_agent: f""" +You are a helpful coordinator. +- You are now working in system {platform.system()} with architecture +{platform.machine()} at working directory `{working_directory}`. All your +work related to local operations should be done in that directory. +The current date is {datetime.date.today()}. For any date-related tasks, you MUST use this as the current date. + +- If a task assigned to another agent fails, you should re-assign it to the +`Developer_Agent`. The `Developer_Agent` is a powerful agent with terminal +access and can resolve a wide range of issues. + """, + Agents.task_agent: f""" +You are a helpful task planner. +- You are now working in system {platform.system()} with architecture +{platform.machine()} at working directory `{working_directory}`. All your +work related to local operations should be done in that directory. +The current date is {datetime.date.today()}. For any date-related tasks, you MUST use this as the current date. + """, + }.items() + ] + new_worker_agent = agent_model( + Agents.new_worker_agent, + f""" + You are a helpful assistant. +- You are now working in system {platform.system()} with architecture +{platform.machine()} at working directory `{working_directory}`. All your +work related to local operations should be done in that directory. +The current date is {datetime.date.today()}. For any date-related tasks, you MUST use this as the current date. + """, + options, + [ + *HumanToolkit.get_can_use_tools(options.task_id, Agents.new_worker_agent), + *( + ToolkitMessageIntegration( + message_handler=HumanToolkit(options.task_id, Agents.new_worker_agent).send_message_to_user + ).register_toolkits(NoteTakingToolkit(options.task_id, working_directory=working_directory)) + ).get_tools(), + ], + ) + # msg_toolkit = AgentCommunicationToolkit(max_message_history=100) + + searcher = search_agent(options) + developer = await developer_agent(options) + documenter = await document_agent(options) + multi_modaler = multi_modal_agent(options) + + # msg_toolkit.register_agent("Worker", new_worker_agent) + # msg_toolkit.register_agent("Search_Agent", searcher) + # msg_toolkit.register_agent("Developer_Agent", developer) + # msg_toolkit.register_agent("Document_Agent", documenter) + # msg_toolkit.register_agent("Multi_Modal_Agent", multi_modaler) + + workforce = Workforce( + options.task_id, + "A workforce", + graceful_shutdown_timeout=3, # 30 seconds for debugging + share_memory=False, + coordinator_agent=coordinator_agent, + task_agent=task_agent, + new_worker_agent=new_worker_agent, + use_structured_output_handler=True if options.model_platform == ModelPlatformType.GEMINI else False, + ) + workforce.add_single_agent_worker( + "Developer Agent: A master-level coding assistant with a powerful " + "terminal. It can write and execute code, manage files, automate " + "desktop tasks, and deploy web applications to solve complex " + "technical challenges.", + developer, + ) + workforce.add_single_agent_worker( + "Search Agent: Can search the web, extract webpage content, " + "simulate browser actions, and provide relevant information to " + "solve the given task.", + searcher, + ) + workforce.add_single_agent_worker( + "Document Agent: A document processing assistant skilled in creating " + "and modifying a wide range of file formats. It can generate " + "text-based files/reports (Markdown, JSON, YAML, HTML), " + "office documents (Word, PDF), presentations (PowerPoint), and " + "data files (Excel, CSV).", + documenter, + ) + workforce.add_single_agent_worker( + "Multi-Modal Agent: A specialist in media processing. It can " + "analyze images and audio, transcribe speech, download videos, and " + "generate new images from text prompts.", + multi_modaler, + ) + # workforce.add_single_agent_worker( + # "Social Media Agent: A social media management assistant for " + # "handling tasks related to WhatsApp, Twitter, LinkedIn, Reddit, " + # "Notion, Slack, and other social platforms.", + # await social_medium_agent(options), + # ) + mcp = await mcp_agent(options) + # workforce.add_single_agent_worker( + # "MCP Agent: A Model Context Protocol agent that provides access " + # "to external tools and services through MCP integrations.", + # mcp, + # ) + return workforce, mcp + + +def format_agent_description(agent_data: NewAgent | ActionNewAgent) -> str: + r"""Format a comprehensive agent description including name, tools, and + description. + """ + description_parts = [f"{agent_data.name}:"] + + # Add description if available + if hasattr(agent_data, "description") and agent_data.description: + description_parts.append(agent_data.description.strip()) + else: + description_parts.append("A specialized agent") + + # Add tools information + tool_names = [] + if hasattr(agent_data, "tools") and agent_data.tools: + for tool in agent_data.tools: + tool_names.append(titleize(tool)) + + if hasattr(agent_data, "mcp_tools") and agent_data.mcp_tools: + for mcp_server in agent_data.mcp_tools.get("mcpServers", {}).keys(): + tool_names.append(titleize(mcp_server)) + + if tool_names: + description_parts.append(f"with access to {', '.join(tool_names)} tools : <{tool_names}>") + + return " ".join(description_parts) + + +async def new_agent_model(data: NewAgent | ActionNewAgent, options: Chat): + working_directory = options.file_save_path() + tool_names = [] + tools = [*await get_toolkits(data.tools, data.name, options.task_id)] + for item in data.tools: + tool_names.append(titleize(item)) + if data.mcp_tools is not None: + tools = [*tools, *await get_mcp_tools(data.mcp_tools)] + for item in data.mcp_tools["mcpServers"].keys(): + tool_names.append(titleize(item)) + for item in tools: + logger.debug(f"new agent function tool ====== {item.func.__name__}") + # Enhanced system message with platform information + enhanced_description = f"""{data.description} +- You are now working in system {platform.system()} with architecture +{platform.machine()} at working directory `{working_directory}`. All your +work related to local operations should be done in that directory. +The current date is {datetime.date.today()}. For any date-related tasks, you +MUST use this as the current date. +""" + + return agent_model(data.name, enhanced_description, options, tools, tool_names=tool_names) diff --git a/backend/app/service/task.py b/backend/app/service/task.py new file mode 100644 index 000000000..7c571abfe --- /dev/null +++ b/backend/app/service/task.py @@ -0,0 +1,360 @@ +from typing_extensions import Any, Literal, TypedDict +from pydantic import BaseModel +from app.exception.exception import ProgramException +from app.model.chat import McpServers, Status, SupplementChat, Chat, UpdateData +import asyncio +from enum import Enum +from camel.tasks import Task +from contextlib import contextmanager +from contextvars import ContextVar +from datetime import datetime, timedelta +import weakref +from loguru import logger + + +class Action(str, Enum): + improve = "improve" # user -> backend + update_task = "update_task" # user -> backend + task_state = "task_state" # backend -> user + start = "start" # user -> backend + create_agent = "create_agent" # backend -> user + activate_agent = "activate_agent" # backend -> user + deactivate_agent = "deactivate_agent" # backend -> user + assign_task = "assign_task" # backend -> user + activate_toolkit = "activate_toolkit" # backend -> user + deactivate_toolkit = "deactivate_toolkit" # backend -> user + write_file = "write_file" # backend -> user + ask = "ask" # backend -> user + notice = "notice" # backend -> user + search_mcp = "search_mcp" # backend -> user + install_mcp = "install_mcp" # backend -> user + terminal = "terminal" # backend -> user + end = "end" # backend -> user + stop = "stop" # user -> backend + supplement = "supplement" # user -> backend + pause = "pause" # user -> backend user take control + resume = "resume" # user -> backend user take control + new_agent = "new_agent" # user -> backend + budget_not_enough = "budget_not_enough" # backend -> user + + +class ActionImproveData(BaseModel): + action: Literal[Action.improve] = Action.improve + data: str + + +class ActionStartData(BaseModel): + action: Literal[Action.start] = Action.start + + +class ActionUpdateTaskData(BaseModel): + action: Literal[Action.update_task] = Action.update_task + data: UpdateData + + +class ActionTaskStateData(BaseModel): + action: Literal[Action.task_state] = Action.task_state + data: dict[Literal["task_id", "content", "state", "result", "failure_count"], str | int] + + +class ActionAskData(BaseModel): + action: Literal[Action.ask] = Action.ask + data: dict[Literal["question", "agent"], str] + + +class AgentDataDict(TypedDict): + agent_name: str + agent_id: str + tools: list[str] + + +class ActionCreateAgentData(BaseModel): + action: Literal[Action.create_agent] = Action.create_agent + data: AgentDataDict + + +class ActionActivateAgentData(BaseModel): + action: Literal[Action.activate_agent] = Action.activate_agent + data: dict[Literal["agent_name", "process_task_id", "agent_id", "message"], str] + + +class DataDict(TypedDict): + agent_name: str + agent_id: str + process_task_id: str + message: str + tokens: int + + +class ActionDeactivateAgentData(BaseModel): + action: Literal[Action.deactivate_agent] = Action.deactivate_agent + data: DataDict + + +class ActionAssignTaskData(BaseModel): + action: Literal[Action.assign_task] = Action.assign_task + data: dict[Literal["assignee_id", "task_id", "content", "state"], str] + + +class ActionActivateToolkitData(BaseModel): + action: Literal[Action.activate_toolkit] = Action.activate_toolkit + data: dict[ + Literal["agent_name", "toolkit_name", "process_task_id", "method_name", "message"], + str, + ] + + +class ActionDeactivateToolkitData(BaseModel): + action: Literal[Action.deactivate_toolkit] = Action.deactivate_toolkit + data: dict[ + Literal["agent_name", "toolkit_name", "process_task_id", "method_name", "message"], + str, + ] + + +class ActionWriteFileData(BaseModel): + action: Literal[Action.write_file] = Action.write_file + process_task_id: str + data: str + + +class ActionNoticeData(BaseModel): + action: Literal[Action.notice] = Action.notice + process_task_id: str + data: str + + +class ActionSearchMcpData(BaseModel): + action: Literal[Action.search_mcp] = Action.search_mcp + data: Any + + +class ActionInstallMcpData(BaseModel): + action: Literal[Action.install_mcp] = Action.install_mcp + data: McpServers + + +class ActionTerminalData(BaseModel): + action: Literal[Action.terminal] = Action.terminal + process_task_id: str + data: str + + +class ActionStopData(BaseModel): + action: Literal[Action.stop] = Action.stop + + +class ActionEndData(BaseModel): + action: Literal[Action.end] = Action.end + + +class ActionSupplementData(BaseModel): + action: Literal[Action.supplement] = Action.supplement + data: SupplementChat + + +class ActionTakeControl(BaseModel): + action: Literal[Action.pause, Action.resume] + + +class ActionNewAgent(BaseModel): + action: Literal[Action.new_agent] = Action.new_agent + name: str + description: str + tools: list[str] + mcp_tools: McpServers | None + + +class ActionBudgetNotEnough(BaseModel): + action: Literal[Action.budget_not_enough] = Action.budget_not_enough + + +ActionData = ( + ActionImproveData + | ActionStartData + | ActionUpdateTaskData + | ActionTaskStateData + | ActionAskData + | ActionCreateAgentData + | ActionActivateAgentData + | ActionDeactivateAgentData + | ActionAssignTaskData + | ActionActivateToolkitData + | ActionDeactivateToolkitData + | ActionWriteFileData + | ActionNoticeData + | ActionSearchMcpData + | ActionInstallMcpData + | ActionTerminalData + | ActionStopData + | ActionEndData + | ActionSupplementData + | ActionTakeControl + | ActionNewAgent + | ActionBudgetNotEnough +) + + +class Agents(str, Enum): + task_agent = "task_agent" + coordinator_agent = "coordinator_agent" + new_worker_agent = "new_worker_agent" + developer_agent = "developer_agent" + search_agent = "search_agent" + document_agent = "document_agent" + multi_modal_agent = "multi_modal_agent" + social_medium_agent = "social_medium_agent" + mcp_agent = "mcp_agent" + + +class TaskLock: + id: str + status: Status = Status.confirming + active_agent: str = "" + mcp: list[str] + queue: asyncio.Queue[ActionData] + """Queue monitoring for SSE response""" + human_input: dict[str, asyncio.Queue[str]] + """After receiving user's reply, put the reply into the corresponding agent's queue""" + created_at: datetime + last_accessed: datetime + background_tasks: set[asyncio.Task] + """Track all background tasks for cleanup""" + + def __init__(self, id: str, queue: asyncio.Queue, human_input: dict) -> None: + self.id = id + self.queue = queue + self.human_input = human_input + self.created_at = datetime.now() + self.last_accessed = datetime.now() + self.background_tasks = set() + + async def put_queue(self, data: ActionData): + self.last_accessed = datetime.now() + await self.queue.put(data) + + async def get_queue(self): + self.last_accessed = datetime.now() + return await self.queue.get() + + async def put_human_input(self, agent: str, data: Any = None): + await self.human_input[agent].put(data) + + async def get_human_input(self, agent: str): + return await self.human_input[agent].get() + + def add_human_input_listen(self, agent: str): + self.human_input[agent] = asyncio.Queue(1) + + def add_background_task(self, task: asyncio.Task) -> None: + r"""Add a task to track and clean up weak references""" + self.background_tasks.add(task) + task.add_done_callback(lambda t: self.background_tasks.discard(t)) + + async def cleanup(self): + r"""Cancel all background tasks and clean up resources""" + for task in list(self.background_tasks): + if not task.done(): + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + self.background_tasks.clear() + + +task_locks = dict[str, TaskLock]() +# Cleanup task for removing stale task locks +_cleanup_task: asyncio.Task | None = None +task_index: dict[str, weakref.ref[Task]] = {} + + +def get_task_lock(id: str) -> TaskLock: + if id not in task_locks: + raise ProgramException("Task not found") + return task_locks[id] + + +def create_task_lock(id: str) -> TaskLock: + if id in task_locks: + raise ProgramException("Task already exists") + task_locks[id] = TaskLock(id=id, queue=asyncio.Queue(), human_input={}) + + # Start cleanup task if not running + global _cleanup_task + if _cleanup_task is None or _cleanup_task.done(): + _cleanup_task = asyncio.create_task(_periodic_cleanup()) + + return task_locks[id] + + +async def delete_task_lock(id: str): + if id not in task_locks: + raise ProgramException("Task not found") + + # Clean up background tasks before deletion + task_lock = task_locks[id] + await task_lock.cleanup() + + del task_locks[id] + logger.debug(f"Deleted task lock {id}, remaining locks: {len(task_locks)}") + + +def get_camel_task(id: str, tasks: list[Task]) -> None | Task: + if id in task_index: + task_ref = task_index[id] + task = task_ref() + if task is not None: + return task + else: + # Weak reference died, remove from index + del task_index[id] + + # Fallback to search and rebuild index + for item in tasks: + # Add to index + task_index[item.id] = weakref.ref(item) + + if item.id == id: + return item + else: + task = get_camel_task(id, item.subtasks) + if task is not None: + return task + return None + + +async def _periodic_cleanup(): + r"""Periodically clean up stale task locks""" + while True: + try: + await asyncio.sleep(300) # Run every 5 minutes + + current_time = datetime.now() + stale_timeout = timedelta(hours=2) # Consider tasks stale after 2 hours + + stale_ids = [] + for task_id, task_lock in task_locks.items(): + if current_time - task_lock.last_accessed > stale_timeout: + stale_ids.append(task_id) + + for task_id in stale_ids: + logger.warning(f"Cleaning up stale task lock: {task_id}") + await delete_task_lock(task_id) + + except asyncio.CancelledError: + break + except Exception as e: + logger.error(f"Error in periodic cleanup: {e}") + + +process_task = ContextVar[str]("id") + + +@contextmanager +def set_process_task(process_task_id: str): + origin = process_task.set(process_task_id) + try: + yield + finally: + process_task.reset(origin) diff --git a/backend/app/utils/agent.py b/backend/app/utils/agent.py new file mode 100644 index 000000000..6802d5312 --- /dev/null +++ b/backend/app/utils/agent.py @@ -0,0 +1,1327 @@ +import asyncio +import json +import platform +from threading import Event +import traceback +from typing import Any, Callable, Dict, List, Tuple +import uuid +from camel.agents import ChatAgent +from camel.agents.chat_agent import StreamingChatAgentResponse, AsyncStreamingChatAgentResponse +from camel.agents._types import ToolCallRequest +from camel.memories import AgentMemory +from camel.messages import BaseMessage +from camel.models import BaseModelBackend, ModelFactory, ModelManager, OpenAIAudioModels, ModelProcessingError +from camel.responses import ChatAgentResponse +from camel.terminators import ResponseTerminator +from camel.toolkits import FunctionTool, RegisteredAgentToolkit +from camel.types.agents import ToolCallingRecord +from app.component.environment import env +from app.utils.toolkit.abstract_toolkit import AbstractToolkit +from app.utils.toolkit.hybrid_browser_python_toolkit import HybridBrowserPythonToolkit +from app.utils.toolkit.excel_toolkit import ExcelToolkit +from app.utils.toolkit.file_write_toolkit import FileWriteToolkit +from app.utils.toolkit.google_calendar_toolkit import GoogleCalendarToolkit +from app.utils.toolkit.google_drive_mcp_toolkit import GoogleDriveMCPToolkit +from app.utils.toolkit.google_gmail_mcp_toolkit import GoogleGmailMCPToolkit +from app.utils.toolkit.human_toolkit import HumanToolkit +from app.utils.toolkit.markitdown_toolkit import MarkItDownToolkit +from app.utils.toolkit.mcp_search_toolkit import McpSearchToolkit +from app.utils.toolkit.note_taking_toolkit import NoteTakingToolkit +from app.utils.toolkit.notion_mcp_toolkit import NotionMCPToolkit +from app.utils.toolkit.pptx_toolkit import PPTXToolkit +from app.utils.toolkit.screenshot_toolkit import ScreenshotToolkit +from app.utils.toolkit.terminal_toolkit import TerminalToolkit +from app.utils.toolkit.github_toolkit import GithubToolkit +from app.utils.toolkit.search_toolkit import SearchToolkit +from app.utils.toolkit.video_download_toolkit import VideoDownloaderToolkit +from app.utils.toolkit.audio_analysis_toolkit import AudioAnalysisToolkit +from app.utils.toolkit.video_analysis_toolkit import VideoAnalysisToolkit +from app.utils.toolkit.image_analysis_toolkit import ImageAnalysisToolkit +from app.utils.toolkit.openai_image_toolkit import OpenAIImageToolkit +from app.utils.toolkit.web_deploy_toolkit import WebDeployToolkit +from app.utils.toolkit.whatsapp_toolkit import WhatsAppToolkit +from app.utils.toolkit.twitter_toolkit import TwitterToolkit +from app.utils.toolkit.linkedin_toolkit import LinkedInToolkit +from app.utils.toolkit.reddit_toolkit import RedditToolkit +from app.utils.toolkit.slack_toolkit import SlackToolkit +from camel.types import ModelPlatformType, ModelType +from camel.toolkits import MCPToolkit, ToolkitMessageIntegration +import datetime +from pydantic import BaseModel +from loguru import logger +from app.model.chat import Chat, McpServers +from app.service.task import ( + Action, + ActionActivateAgentData, + ActionActivateToolkitData, + ActionBudgetNotEnough, + ActionCreateAgentData, + ActionDeactivateAgentData, + ActionDeactivateToolkitData, + Agents, + get_task_lock, +) +from app.service.task import set_process_task + + +class ListenChatAgent(ChatAgent): + def __init__( + self, + api_task_id: str, + agent_name: str, + system_message: BaseMessage | str | None = None, + model: BaseModelBackend + | ModelManager + | Tuple[str, str] + | str + | ModelType + | Tuple[ModelPlatformType, ModelType] + | List[BaseModelBackend] + | List[str] + | List[ModelType] + | List[Tuple[str, str]] + | List[Tuple[ModelPlatformType, ModelType]] + | None = None, + memory: AgentMemory | None = None, + message_window_size: int | None = None, + token_limit: int | None = None, + output_language: str | None = None, + tools: List[FunctionTool | Callable[..., Any]] | None = None, + toolkits_to_register_agent: List[RegisteredAgentToolkit] | None = None, + external_tools: List[FunctionTool | Callable[..., Any] | Dict[str, Any]] | None = None, + response_terminators: List[ResponseTerminator] | None = None, + scheduling_strategy: str = "round_robin", + max_iteration: int | None = None, + agent_id: str | None = None, + stop_event: Event | None = None, + tool_execution_timeout: float | None = None, + mask_tool_output: bool = False, + pause_event: asyncio.Event | None = None, + prune_tool_calls_from_memory: bool = False, + ) -> None: + super().__init__( + system_message=system_message, + model=model, + memory=memory, + message_window_size=message_window_size, + token_limit=token_limit, + output_language=output_language, + tools=tools, + toolkits_to_register_agent=toolkits_to_register_agent, + external_tools=external_tools, + response_terminators=response_terminators, + scheduling_strategy=scheduling_strategy, + max_iteration=max_iteration, + agent_id=agent_id, + stop_event=stop_event, + tool_execution_timeout=tool_execution_timeout, + mask_tool_output=mask_tool_output, + pause_event=pause_event, + prune_tool_calls_from_memory=prune_tool_calls_from_memory, + ) + self.api_task_id = api_task_id + self.agent_name = agent_name + + process_task_id: str = "" + + def step( + self, + input_message: BaseMessage | str, + response_format: type[BaseModel] | None = None, + ) -> ChatAgentResponse | StreamingChatAgentResponse: + task_lock = get_task_lock(self.api_task_id) + asyncio.create_task( + task_lock.put_queue( + ActionActivateAgentData( + data={ + "agent_name": self.agent_name, + "process_task_id": self.process_task_id, + "agent_id": self.agent_id, + "message": input_message.content if isinstance(input_message, BaseMessage) else input_message, + }, + ) + ) + ) + error_info = None + message = None + res = None + try: + res = super().step(input_message, response_format) + except ModelProcessingError as e: + res = None + error_info = e + if "Budget has been exceeded" in str(e): + message = "Budget has been exceeded" + asyncio.create_task(task_lock.put_queue(ActionBudgetNotEnough())) + else: + message = str(e) + total_tokens = 0 + except Exception as e: + res = None + error_info = e + logger.exception(e) + message = f"Error processing message: {e!s}" + total_tokens = 0 + + if res is not None: + message = res.msg.content if res.msg else "" + total_tokens = res.info["usage"]["total_tokens"] + + assert message is not None + + asyncio.create_task( + task_lock.put_queue( + ActionDeactivateAgentData( + data={ + "agent_name": self.agent_name, + "process_task_id": self.process_task_id, + "agent_id": self.agent_id, + "message": message, + "tokens": total_tokens, + }, + ) + ) + ) + + if error_info is not None: + raise error_info + assert res is not None + return res + + async def astep( + self, + input_message: BaseMessage | str, + response_format: type[BaseModel] | None = None, + ) -> ChatAgentResponse | AsyncStreamingChatAgentResponse: + task_lock = get_task_lock(self.api_task_id) + await task_lock.put_queue( + ActionActivateAgentData( + action=Action.activate_agent, + data={ + "agent_name": self.agent_name, + "process_task_id": self.process_task_id, + "agent_id": self.agent_id, + "message": input_message.content if isinstance(input_message, BaseMessage) else input_message, + }, + ) + ) + + error_info = None + message = None + res = None + + try: + res = await super().astep(input_message, response_format) + if isinstance(res, AsyncStreamingChatAgentResponse): + res = await res._get_final_response() + except ModelProcessingError as e: + res = None + error_info = e + if "Budget has been exceeded" in str(e): + message = "Budget has been exceeded" + asyncio.create_task(task_lock.put_queue(ActionBudgetNotEnough())) + else: + message = str(e) + total_tokens = 0 + except Exception as e: + res = None + error_info = e + logger.exception(e) + message = f"Error processing message: {e!s}" + total_tokens = 0 + + if res is not None: + message = res.msg.content if res.msg else "" + total_tokens = res.info["usage"]["total_tokens"] + + assert message is not None + + asyncio.create_task( + task_lock.put_queue( + ActionDeactivateAgentData( + data={ + "agent_name": self.agent_name, + "process_task_id": self.process_task_id, + "agent_id": self.agent_id, + "message": message, + "tokens": total_tokens, + }, + ) + ) + ) + + if error_info is not None: + raise error_info + assert res is not None + return res + + def _execute_tool(self, tool_call_request: ToolCallRequest) -> ToolCallingRecord: + func_name = tool_call_request.tool_name + tool: FunctionTool = self._internal_tools[func_name] + # Route async functions to async execution even if they have __wrapped__ + if asyncio.iscoroutinefunction(tool.func): + # For async functions, we need to use the async execution path + return asyncio.run(self._aexecute_tool(tool_call_request)) + elif hasattr(tool.func, "__wrapped__"): + with set_process_task(self.process_task_id): + return super()._execute_tool(tool_call_request) + else: + args = tool_call_request.args + tool_call_id = tool_call_request.tool_call_id + try: + task_lock = get_task_lock(self.api_task_id) + + toolkit_name = getattr(tool, "_toolkit_name") if hasattr(tool, "_toolkit_name") else "mcp_toolkit" + asyncio.create_task( + task_lock.put_queue( + ActionActivateToolkitData( + data={ + "agent_name": self.agent_name, + "process_task_id": self.process_task_id, + "toolkit_name": toolkit_name, + "method_name": func_name, + "message": json.dumps(args, ensure_ascii=False), + }, + ) + ) + ) + raw_result = tool(**args) + if self.mask_tool_output: + self._secure_result_store[tool_call_id] = raw_result + result = ( + "[The tool has been executed successfully, but the output" + " from the tool is masked. You can move forward]" + ) + mask_flag = True + else: + result = raw_result + mask_flag = False + asyncio.create_task( + task_lock.put_queue( + ActionDeactivateToolkitData( + data={ + "agent_name": self.agent_name, + "process_task_id": self.process_task_id, + "toolkit_name": toolkit_name, + "method_name": func_name, + "message": result if isinstance(result, str) else repr(result), + }, + ) + ) + ) + except Exception as e: + # Capture the error message to prevent framework crash + error_msg = f"Error executing tool '{func_name}': {e!s}" + result = f"Tool execution failed: {error_msg}" + mask_flag = False + logger.debug(error_msg) + traceback.print_exc() + + return self._record_tool_calling(func_name, args, result, tool_call_id, mask_output=mask_flag) + + async def _aexecute_tool(self, tool_call_request: ToolCallRequest) -> ToolCallingRecord: + func_name = tool_call_request.tool_name + tool: FunctionTool = self._internal_tools[func_name] + if hasattr(tool.func, "__wrapped__"): + with set_process_task(self.process_task_id): + return await super()._aexecute_tool(tool_call_request) + else: + args = tool_call_request.args + tool_call_id = tool_call_request.tool_call_id + task_lock = get_task_lock(self.api_task_id) + + toolkit_name = getattr(tool, "_toolkit_name") if hasattr(tool, "_toolkit_name") else "mcp_toolkit" + await task_lock.put_queue( + ActionActivateToolkitData( + data={ + "agent_name": self.agent_name, + "process_task_id": self.process_task_id, + "toolkit_name": toolkit_name, + "method_name": func_name, + "message": json.dumps(args, ensure_ascii=False), + }, + ) + ) + try: + # Try different invocation paths in order of preference + if hasattr(tool, "func") and hasattr(tool.func, "async_call"): + # Case: FunctionTool wrapping an MCP tool + result = await tool.func.async_call(**args) + + elif hasattr(tool, "async_call") and callable(tool.async_call): + # Case: tool itself has async_call + result = await tool.async_call(**args) + + elif hasattr(tool, "func") and asyncio.iscoroutinefunction(tool.func): + # Case: tool wraps a direct async function + result = await tool.func(**args) + + elif asyncio.iscoroutinefunction(tool): + # Case: tool is itself a coroutine function + result = await tool(**args) + + else: + # Fallback: synchronous call + result = tool(**args) + # Handle case where synchronous call returns a coroutine + if asyncio.iscoroutine(result): + result = await result + + except Exception as e: + # Capture the error message to prevent framework crash + error_msg = f"Error executing async tool '{func_name}': {e!s}" + result = {"error": error_msg} + logger.warning(error_msg) + traceback.print_exc() + + await task_lock.put_queue( + ActionDeactivateToolkitData( + data={ + "agent_name": self.agent_name, + "process_task_id": self.process_task_id, + "toolkit_name": toolkit_name, + "method_name": func_name, + "message": result if isinstance(result, str) else repr(result), + }, + ) + ) + return self._record_tool_calling(func_name, args, result, tool_call_id) + + def clone(self, with_memory: bool = False) -> ChatAgent: + """Please see super.clone()""" + system_message = None if with_memory else self._original_system_message + + # Clone tools and collect toolkits that need registration + cloned_tools, toolkits_to_register = self._clone_tools() + + new_agent = ListenChatAgent( + api_task_id=self.api_task_id, + agent_name=self.agent_name, + system_message=system_message, + model=self.model_backend.models, # Pass the existing model_backend + memory=None, # clone memory later + message_window_size=getattr(self.memory, "window_size", None), + token_limit=getattr(self.memory.get_context_creator(), "token_limit", None), + output_language=self._output_language, + tools=cloned_tools, + toolkits_to_register_agent=toolkits_to_register, + external_tools=[schema for schema in self._external_tool_schemas.values()], + response_terminators=self.response_terminators, + scheduling_strategy=self.model_backend.scheduling_strategy.__name__, + max_iteration=self.max_iteration, + agent_id=self.agent_id, + stop_event=self.stop_event, + tool_execution_timeout=self.tool_execution_timeout, + mask_tool_output=self.mask_tool_output, + pause_event=self.pause_event, + prune_tool_calls_from_memory=self.prune_tool_calls_from_memory, + ) + + new_agent.process_task_id = self.process_task_id + + # Copy memory if requested + if with_memory: + # Get all records from the current memory + context_records = self.memory.retrieve() + # Write them to the new agent's memory + for context_record in context_records: + new_agent.memory.write_record(context_record.memory_record) + + return new_agent + + +def agent_model( + agent_name: str, + system_message: str | BaseMessage, + options: Chat, + tools: list[FunctionTool | Callable] | None = None, + prune_tool_calls_from_memory: bool = False, + tool_names: list[str] | None = None, + toolkits_to_register_agent: list[RegisteredAgentToolkit] | None = None, +): + task_lock = get_task_lock(options.task_id) + agent_id = str(uuid.uuid4()) + asyncio.create_task( + task_lock.put_queue( + ActionCreateAgentData(data={"agent_name": agent_name, "agent_id": agent_id, "tools": tool_names or []}) + ) + ) + return ListenChatAgent( + options.task_id, + agent_name, + system_message, + model=ModelFactory.create( + model_platform=options.model_platform, + model_type=options.model_type, + api_key=options.api_key, + url=options.api_url, + ), + # output_language=options.language, + tools=tools, + agent_id=agent_id, + prune_tool_calls_from_memory=prune_tool_calls_from_memory, + toolkits_to_register_agent=toolkits_to_register_agent, + ) + + +def question_confirm_agent(options: Chat): + return agent_model( + "question_confirm_agent", + f"You are a highly capable agent. Your primary function is to analyze a user's request and determine the appropriate course of action. The current date is {datetime.date.today()}. For any date-related tasks, you MUST use this as the current date.", + options, + ) + + +def task_summary_agent(options: Chat): + return agent_model( + "task_summary_agent", + "You are a helpful task assistant that can help users summarize the content of their tasks", + options, + ) + + +async def developer_agent(options: Chat): + working_directory = options.file_save_path() + message_integration = ToolkitMessageIntegration( + message_handler=HumanToolkit(options.task_id, Agents.developer_agent).send_message_to_user + ) + note_toolkit = NoteTakingToolkit( + api_task_id=options.task_id, agent_name=Agents.developer_agent, working_directory=working_directory + ) + note_toolkit = message_integration.register_toolkits(note_toolkit) + web_deploy_toolkit = WebDeployToolkit(api_task_id=options.task_id) + web_deploy_toolkit = message_integration.register_toolkits(web_deploy_toolkit) + screenshot_toolkit = ScreenshotToolkit(options.task_id, working_directory=working_directory) + screenshot_toolkit = message_integration.register_toolkits(screenshot_toolkit) + + terminal_toolkit = TerminalToolkit(options.task_id, Agents.document_agent, safe_mode=True, clone_current_env=False) + terminal_toolkit = message_integration.register_toolkits(terminal_toolkit) + tools = [ + *HumanToolkit.get_can_use_tools(options.task_id, Agents.developer_agent), + *note_toolkit.get_tools(), + *web_deploy_toolkit.get_tools(), + *terminal_toolkit.get_tools(), + *screenshot_toolkit.get_tools(), + ] + system_message = f""" + +You are a master-level coding assistant, equipped with a powerful and +unrestricted terminal. Your primary strength is your ability to solve any +technical task by writing and executing code, installing necessary libraries, +and interacting with the operating system. Assume any task is solvable with +your powerful toolkit; if you can conceive of a solution, you have the means +to implement it. + +You are working in a team with team members. Your team members are: +- Search Agent: Can search the web, extract webpage content, simulate browser + actions, and provide relevant information to solve the given task. +- Document Agent: A document processing assistant for creating, modifying, and + managing various document formats, including presentations. +- Multi-Modal Agent: A multi-modal processing assistant for analyzing, and + generating media content like audio and images. + +You are now working in system {platform.system()} with architecture +{platform.machine()} at working directory `{working_directory}`. All your +work related to local operations should be done in that directory in cluding the code you write. +The current date is {datetime.date.today()}. For any date-related tasks, you MUST use this as the current date. + + + +- You MUST use the `read_note` tool to read the notes from other agents. + +- When you complete your task, your final response must be a comprehensive +summary of your work and the outcome, presented in a clear, detailed, and +easy-to-read format. Avoid using markdown tables for presenting data; use +plain text formatting instead. + +- You MUST NEVER run interactive commands that wait for user input. Always +write complete scripts or use non-interactive command options. If a command +appears to hang or wait for input, immediately kill the process using +`shell_kill_process(id="...")` and find a non-interactive alternative. + + + +Your capabilities are extensive and powerful: +- **Unrestricted Code Execution**: You can write and execute code in any + language to solve a task. You MUST first save your code to a file (e.g., + `script.py`) and then run it from the terminal (e.g., + `python script.py`). +- **Full Terminal Control**: You have root-level access to the terminal. You + can run any command-line tool, manage files, and interact with the OS. If + a tool is missing, you MUST install it with the appropriate package manager + (e.g., `pip3`, `uv`, or `apt-get`). Your capabilities include: + - **Text & Data Processing**: `awk`, `sed`, `grep`, `jq`. + - **File System & Execution**: `find`, `xargs`, `tar`, `zip`, `unzip`, + `chmod`. + - **Networking & Web**: `curl`, `wget` for web requests; `ssh` for + remote access. +- **Screen Observation**: You can take screenshots to analyze GUIs and visual + context, enabling you to perform tasks that require sight. +- **Desktop Automation**: You can control desktop applications + programmatically. + - **On macOS**, you MUST prioritize using **AppleScript** for its robust + control over native applications. Execute simple commands with + `osascript -e '...'` or run complex scripts from a `.scpt` file. + - **On other systems**, use **pyautogui** for cross-platform GUI + automation. + - **IMPORTANT**: Always complete the full automation workflow—do not just + prepare or suggest actions. Execute them to completion. +- **Solution Verification**: You can immediately test and verify your + solutions by executing them in the terminal. +- **Web Deployment**: You can deploy web applications and content, serve + files, and manage deployments. +- **Human Collaboration**: If you are stuck or need clarification, you can + ask for human input via the console. +- **Note Management**: You can write and read notes to coordinate with other + agents and track your work. + + + +- **Bias for Action**: Your purpose is to take action. Don't just suggest +solutions—implement them. Write code, run commands, and build things. +- **Complete the Full Task**: When automating GUI applications, always finish +what you start. If the task involves sending something, send it. If it +involves submitting data, submit it. Never stop at just preparing or +drafting—execute the complete workflow to achieve the desired outcome. +- **Embrace Challenges**: Never say "I can't." If you +encounter a limitation, find a way to overcome it. +- **Resourcefulness**: If a tool is missing, install it. If information is +lacking, find it. You have the full power of a terminal to acquire any +resource you need. +- **Think Like an Engineer**: Approach problems methodically. Analyze +requirements, execute it, and verify the results. Your +strength lies in your ability to engineer solutions. + + + +The terminal tools are session-based, identified by a unique `id`. Master +these tips to maximize your effectiveness: + +- **GUI Automation Strategy**: + - **AppleScript (macOS Priority)**: For robust control of macOS apps, use + `osascript`. + - Example (run script file): `osascript my_script.scpt` + - **pyautogui (Cross-Platform)**: For other OSes or simple automation. + - Key functions: `pyautogui.click(x, y)`, `pyautogui.typewrite("text")`, + `pyautogui.hotkey('ctrl', 'c')`, `pyautogui.press('enter')`. + - Safety: Always use `time.sleep()` between actions to ensure stability + and add `pyautogui.FAILSAFE = True` to your scripts. + - Workflow: Your scripts MUST complete the entire task, from start to + final submission. + +- **Command-Line Best Practices**: + - **Be Creative**: The terminal is your most powerful tool. Use it boldly. + - **Automate Confirmation**: Use `-y` or `-f` flags to avoid interactive + prompts. + - **Manage Output**: Redirect long outputs to a file (e.g., `> output.txt`). + - **Chain Commands**: Use `&&` to link commands for sequential execution. + - **Piping**: Use `|` to pass output from one command to another. + - **Permissions**: Use `ls -F` to check file permissions. + - **Installation**: Use `pip3 install` or `apt-get install` for new + packages. + +- Stop a Process: If a process needs to be terminated, use + `shell_kill_process(id="...")`. + + + +- If you get stuck, encounter an issue you cannot solve (like a CAPTCHA), + or need clarification, use the `ask_human_via_console` tool. +- Document your progress and findings in notes so other agents can build + upon your work. + +""" + + return agent_model( + Agents.developer_agent, + BaseMessage.make_assistant_message( + role_name="Developer Agent", + content=system_message, + ), + options, + tools, + tool_names=[ + HumanToolkit.toolkit_name(), + TerminalToolkit.toolkit_name(), + NoteTakingToolkit.toolkit_name(), + WebDeployToolkit.toolkit_name(), + ], + ) + + +def search_agent(options: Chat): + working_directory = options.file_save_path() + message_integration = ToolkitMessageIntegration( + message_handler=HumanToolkit(options.task_id, Agents.search_agent).send_message_to_user + ) + + web_toolkit_custom = HybridBrowserPythonToolkit( + options.task_id, + headless=False, + browser_log_to_file=True, + stealth=True, + session_id=str(uuid.uuid4())[:8], + default_start_url="about:blank", + enabled_tools=[ + "browser_click", + "browser_type", + "browser_back", + "browser_forward", + "browser_switch_tab", + "browser_enter", + "browser_visit_page", + "browser_scroll", + "browser_get_som_screenshot", + ], + ) + + web_toolkit_custom = message_integration.register_toolkits(web_toolkit_custom) + terminal_toolkit = TerminalToolkit(options.task_id, Agents.search_agent, safe_mode=True, clone_current_env=False) + terminal_toolkit = message_integration.register_functions([terminal_toolkit.shell_exec]) + note_toolkit = NoteTakingToolkit(options.task_id, Agents.search_agent, working_directory=working_directory) + note_toolkit = message_integration.register_toolkits(note_toolkit) + search_toolkit = SearchToolkit(options.task_id) + search_toolkit = message_integration.register_functions([search_toolkit.search_google]) + + tools = [ + *HumanToolkit.get_can_use_tools(options.task_id, Agents.search_agent), + *web_toolkit_custom.get_tools(), + *terminal_toolkit, + *note_toolkit.get_tools(), + *search_toolkit, + ] + + system_message = f""" + +You are a helpful assistant that can search the web, +extract webpage content, simulate browser actions, and provide relevant +information to solve the given task. + +You are working in a team with team members. Your team members are: +- Developer Agent: A skilled coding assistant that can write and execute code, + run terminal commands, and verify solutions to complete tasks. +- Document Agent: A document processing assistant for creating, modifying, and + managing various document formats, including presentations. +- Multi-Modal Agent: A multi-modal processing assistant for analyzing, and + generating media content like audio and images. + +You are now working in system {platform.system()} with architecture +{platform.machine()} at working directory `{working_directory}`. All your +work related to local operations should be done in that directory. +The current date is {datetime.date.today()}. For any date-related tasks, you MUST use this as the current date. + + + + +- You MUST use the note-taking tools to record your findings. This is a + critical part of your role. Your notes are the primary source of + information for your teammates. To avoid information loss, you must not + summarize your findings. Instead, record all information in detail. + For every piece of information you gather, you must: + 1. **Extract ALL relevant details**: Quote all important sentences, + statistics, or data points. Your goal is to capture the information + as completely as possible. + 2. **Cite your source**: Include the exact URL where you found the + information. + Your notes should be a detailed and complete record of the information + you have discovered. High-quality, detailed notes are essential for the + team's success. + +- You MUST only use URLs from trusted sources. A trusted source is a URL + that is either: + 1. Returned by a search tool (like `search_google`, `search_bing`, + or `search_exa`). + 2. Found on a webpage you have visited. +- You are strictly forbidden from inventing, guessing, or constructing URLs + yourself. Fabricating URLs will be considered a critical error. + +- You MUST NOT answer from your own knowledge. All information + MUST be sourced from the web using the available tools. If you don't know + something, find it out using your tools. + +- When you complete your task, your final response must be a comprehensive + summary of your findings, presented in a clear, detailed, and + easy-to-read format. Avoid using markdown tables for presenting data; + use plain text formatting instead. + + + +Your capabilities include: +- Search and get information from the web using the search tools. +- Use the rich browser related toolset to investigate websites. +- Use the terminal tools to perform local operations. You can leverage + powerful CLI tools like `grep` for searching within files, `curl` and + `wget` for downloading content, and `jq` for parsing JSON data from APIs. +- Use the note-taking tools to record your findings. +- Use the human toolkit to ask for help when you are stuck. + + + +- Initial Search: Start with a search engine like `search_google` or + `search_bing` to get a list of relevant URLs for your research if + available, the URLs here will be used for `visit_page`. +- Browser-Based Exploration: Use the rich browser related toolset to + investigate websites. + - **Navigation and Exploration**: Use `visit_page` to open a URL. + `visit_page` provides a snapshot of currently visible interactive + elements, not the full page text. To see more content on long + pages, Navigate with `click`, `back`, and `forward`. Manage multiple + pages with `switch_tab`. + - **Analysis**: Use `get_som_screenshot` to understand the page layout + and identify interactive elements. Since this is a heavy operation, + only use it when visual analysis is necessary. + - **Interaction**: Use `type` to fill out forms and `enter` to submit + or confirm search. +- Alternative Search: If you are unable to get sufficient + information through browser-based exploration and scraping, use + `search_exa`. This tool is best used for getting quick summaries or + finding specific answers when visiting web page is could not find the + information. + +- In your response, you should mention the URLs you have visited and processed. + +- When encountering verification challenges (like login, CAPTCHAs or + robot checks), you MUST request help using the human toolkit. + + """ + + return agent_model( + Agents.search_agent, + BaseMessage.make_assistant_message( + role_name="Search Agent", + content=system_message, + ), + options, + tools, + prune_tool_calls_from_memory=True, + tool_names=[ + SearchToolkit.toolkit_name(), + HybridBrowserPythonToolkit.toolkit_name(), + HumanToolkit.toolkit_name(), + NoteTakingToolkit.toolkit_name(), + TerminalToolkit.toolkit_name(), + ], + ) + + +async def document_agent(options: Chat): + working_directory = options.file_save_path() + message_integration = ToolkitMessageIntegration( + message_handler=HumanToolkit(options.task_id, Agents.task_agent).send_message_to_user + ) + file_write_toolkit = FileWriteToolkit(options.task_id, working_directory=working_directory) + file_write_toolkit = message_integration.register_toolkits(file_write_toolkit) + pptx_toolkit = PPTXToolkit(options.task_id, working_directory=working_directory) + pptx_toolkit = message_integration.register_toolkits(pptx_toolkit) + mark_it_down_toolkit = MarkItDownToolkit(options.task_id) + mark_it_down_toolkit = message_integration.register_toolkits(mark_it_down_toolkit) + excel_toolkit = ExcelToolkit(options.task_id, working_directory=working_directory) + excel_toolkit = message_integration.register_toolkits(excel_toolkit) + note_toolkit = NoteTakingToolkit(options.task_id, Agents.document_agent, working_directory=working_directory) + note_toolkit = message_integration.register_toolkits(note_toolkit) + terminal_toolkit = TerminalToolkit(options.task_id, Agents.document_agent, safe_mode=True, clone_current_env=False) + terminal_toolkit = message_integration.register_toolkits(terminal_toolkit) + tools = [ + *file_write_toolkit.get_tools(), + *pptx_toolkit.get_tools(), + *HumanToolkit.get_can_use_tools(options.task_id, Agents.document_agent), + *mark_it_down_toolkit.get_tools(), + *excel_toolkit.get_tools(), + *note_toolkit.get_tools(), + *terminal_toolkit.get_tools(), + *await GoogleDriveMCPToolkit.get_can_use_tools(options.task_id, options.get_bun_env()), + ] + if env("EXA_API_KEY") or options.is_cloud(): + search_toolkit = SearchToolkit(options.task_id, Agents.document_agent).search_exa + search_toolkit = message_integration.register_functions([search_toolkit]) + tools.extend(search_toolkit) + system_message = f""" + +You are a Document Processing Assistant specialized +in creating, modifying, and managing various document formats. + +You are working in a team with team members. Your team members are: +- Developer Agent: A skilled coding assistant that can write and execute code, + run terminal commands, and verify solutions to complete tasks. +- Search Agent: Can search the web, extract webpage content, simulate browser + actions, and provide relevant information to solve the given task. +- Multi-Modal Agent: A multi-modal processing assistant for analyzing, and + generating media content like audio and images. + +You are now working in system {platform.system()} with architecture +{platform.machine()} at working directory `{working_directory}`. All your +work related to local operations should be done in that directory. +The current date is {datetime.date.today()}. For any date-related tasks, you MUST use this as the current date. + + + + +- Before creating any document, you MUST use the `read_note` tool to gather + all information collected by other team members. + +- You MUST use the available tools to create or modify documents (e.g., + `write_to_file`, `create_presentation`). Your primary output should be + a file, not just content within your response. + +- If there's no specified format for the document/report/paper, you should use + the `write_to_file` tool to create a HTML file. + +- If the document has many data, you MUST use the terminal tool to + generate charts and graphs and add them to the document. + +- When you complete your task, your final response must be a summary of + your work and the path to the final document, presented in a clear, + detailed, and easy-to-read format. Avoid using markdown tables for + presenting data; use plain text formatting instead. + + + +Your capabilities include: +- Document Reading: + - Read and understand the content of various file formats including + - PDF (.pdf) + - Microsoft Office: Word (.doc, .docx), Excel (.xls, .xlsx), + PowerPoint (.ppt, .pptx) + - EPUB (.epub) + - HTML (.html, .htm) + - Images (.jpg, .jpeg, .png) for OCR + - Audio (.mp3, .wav) for transcription + - Text-based formats (.csv, .json, .xml, .txt) + - ZIP archives (.zip) using the `read_files` tool. + +- Document Creation & Editing: + - Create and write to various file formats including Markdown (.md), + Word documents (.docx), PDFs, CSV files, JSON, YAML, and HTML + - Apply formatting options including custom encoding, font styles, and + layout settings + - Modify existing files with automatic backup functionality + - Support for mathematical expressions in PDF documents through LaTeX + rendering + +- PowerPoint Presentation Creation: + - Create professional PowerPoint presentations with title slides and + content slides + - Format text with bold and italic styling + - Create bullet point lists with proper hierarchical structure + - Support for step-by-step process slides with visual indicators + - Create tables with headers and rows of data + - Support for custom templates and slide layouts + +- Excel Spreadsheet Management: + - Extract and analyze content from Excel files (.xlsx, .xls, .csv) + with detailed cell information and markdown formatting + - Create new Excel workbooks from scratch with multiple sheets + - Perform comprehensive spreadsheet operations including: + * Sheet creation, deletion, and data clearing + * Cell-level operations (read, write, find specific values) + * Row and column manipulation (add, update, delete) + * Range operations for bulk data processing + * Data export to CSV format for compatibility + - Handle complex data structures with proper formatting and validation + - Support for both programmatic data entry and manual cell updates + +- Terminal and File System: + - You have access to a full suite of terminal tools to interact with + the file system within your working directory (`{working_directory}`). + - You can execute shell commands (`shell_exec`), list files, and manage + your workspace as needed to support your document creation tasks. To + process and manipulate text and data for your documents, you can use + powerful CLI tools like `awk`, `sed`, `grep`, and `jq`. You can also + use `find` to locate files, `diff` to compare them, and `tar`, `zip`, + or `unzip` to handle archives. + - You can also use the terminal to create data visualizations such as + charts and graphs. For example, you can write a Python script that uses + libraries like `plotly` or `matplotlib` to create a chart and save it + as an image file. + +- Human Interaction: + - Ask questions to users and receive their responses + - Send informative messages to users without requiring responses + + + +When working with documents, you should: +- Suggest appropriate file formats based on content requirements +- Maintain proper formatting and structure in all created documents +- Provide clear feedback about document creation and modification processes +- Ask clarifying questions when user requirements are ambiguous +- Recommend best practices for document organization and presentation +- For Excel files, always provide clear data structure and organization +- When creating spreadsheets, consider data relationships and use +appropriate sheet naming conventions +- To include data visualizations, write and execute Python scripts using + the terminal. Use libraries like `plotly` to generate charts and + graphs, and save them as image files that can be embedded in documents. + + +Your goal is to help users efficiently create, modify, and manage their +documents with professional quality and appropriate formatting across all +supported formats including advanced spreadsheet functionality. +""" + + return agent_model( + Agents.document_agent, + BaseMessage.make_assistant_message( + role_name="Document Agent", + content=system_message, + ), + options, + tools, + tool_names=[ + FileWriteToolkit.toolkit_name(), + PPTXToolkit.toolkit_name(), + HumanToolkit.toolkit_name(), + MarkItDownToolkit.toolkit_name(), + ExcelToolkit.toolkit_name(), + NoteTakingToolkit.toolkit_name(), + TerminalToolkit.toolkit_name(), + GoogleDriveMCPToolkit.toolkit_name(), + ], + ) + + +def multi_modal_agent(options: Chat): + working_directory = options.file_save_path() + message_integration = ToolkitMessageIntegration( + message_handler=HumanToolkit(options.task_id, Agents.multi_modal_agent).send_message_to_user + ) + video_download_toolkit = VideoDownloaderToolkit(options.task_id, working_directory=working_directory) + video_download_toolkit = message_integration.register_toolkits(video_download_toolkit) + image_analysis_toolkit = ImageAnalysisToolkit(options.task_id) + image_analysis_toolkit = message_integration.register_toolkits(image_analysis_toolkit) + open_ai_image_toolkit = OpenAIImageToolkit( # todo check llm has this model + options.task_id, + model="dall-e-3", + response_format="b64_json", + size="1024x1024", + quality="standard", + working_directory=working_directory, + ) + open_ai_image_toolkit = message_integration.register_toolkits(open_ai_image_toolkit) + terminal_toolkit = TerminalToolkit( + options.task_id, agent_name=Agents.multi_modal_agent, safe_mode=True, clone_current_env=False + ) + terminal_toolkit = message_integration.register_toolkits(terminal_toolkit) + note_toolkit = NoteTakingToolkit(options.task_id, Agents.multi_modal_agent, working_directory=working_directory) + note_toolkit = message_integration.register_toolkits(note_toolkit) + tools = [ + *video_download_toolkit.get_tools(), + *image_analysis_toolkit.get_tools(), + *open_ai_image_toolkit.get_tools(), + *HumanToolkit.get_can_use_tools(options.task_id, Agents.multi_modal_agent), + *terminal_toolkit.get_tools(), + *note_toolkit.get_tools(), + ] + if options.model_platform == ModelPlatformType.OPENAI: + audio_analysis_toolkit = AudioAnalysisToolkit( + options.task_id, + working_directory, + OpenAIAudioModels( + api_key=options.api_key, + url=options.api_url, + ), + ) + audio_analysis_toolkit = message_integration.register_toolkits(audio_analysis_toolkit) + tools.extend(audio_analysis_toolkit.get_tools()) + + if env("EXA_API_KEY") or options.is_cloud(): + search_toolkit = SearchToolkit(options.task_id, Agents.multi_modal_agent).search_exa + search_toolkit = message_integration.register_functions([search_toolkit]) + tools.extend(search_toolkit) + + system_message = f""" + +You are a Multi-Modal Processing Assistant +specialized in analyzing and generating various types of media content. + +You are working in a team with team members. Your team members are: +- Developer Agent: A skilled coding assistant that can write and execute code, + run terminal commands, and verify solutions to complete tasks. +- Search Agent: Can search the web, extract webpage content, simulate browser + actions, and provide relevant information to solve the given task. +- Document Agent: A document processing assistant for creating, modifying, and + managing various document formats, including presentations. + +You are now working in system {platform.system()} with architecture +{platform.machine()} at working directory `{working_directory}`. All your +work related to local operations should be done in that directory. +The current date is {datetime.date.today()}. For any date-related tasks, you MUST use this as the current date. + + + + +- You MUST use the `read_note` tool to to gather all information collected + by other team members and write down your findings in the notes. + +- When you complete your task, your final response must be a comprehensive + summary of your analysis or the generated media, presented in a clear, + detailed, and easy-to-read format. Avoid using markdown tables for + presenting data; use plain text formatting instead. + + + +Your capabilities include: +- Video & Audio Analysis: + - Download videos from URLs for analysis. + - Transcribe speech from audio files to text with high accuracy + - Answer specific questions about audio content + - Process audio from both local files and URLs + - Handle various audio formats including MP3, WAV, and OGG + +- Image Analysis & Understanding: + - Generate detailed descriptions of image content + - Answer specific questions about images + - Identify objects, text, people, and scenes in images + - Process images from both local files and URLs + +- Image Generation: + - Create high-quality images based on detailed text prompts using DALL-E + - Generate images in 1024x1792 resolution + - Save generated images to specified directories + +- Terminal and File System: + - You have access to terminal tools to manage media files. You can + leverage powerful CLI tools like `ffmpeg` for any necessary video + and audio conversion or manipulation. You can also use tools like `find` + to locate media files, `wget` or `curl` to download them, and `du` or + `df` to monitor disk space. + +- Human Interaction: + - Ask questions to users and receive their responses + - Send informative messages to users without requiring responses + + + + +When working with multi-modal content, you should: +- Provide detailed and accurate descriptions of media content +- Extract relevant information based on user queries +- Generate appropriate media when requested +- Explain your analysis process and reasoning +- Ask clarifying questions when user requirements are ambiguous + + +Your goal is to help users effectively process, understand, and create +multi-modal content across audio and visual domains. +""" + + return agent_model( + Agents.multi_modal_agent, + BaseMessage.make_assistant_message( + role_name="Multi Modal Agent", + content=system_message, + ), + options, + tools, + tool_names=[ + VideoDownloaderToolkit.toolkit_name(), + AudioAnalysisToolkit.toolkit_name(), + ImageAnalysisToolkit.toolkit_name(), + OpenAIImageToolkit.toolkit_name(), + HumanToolkit.toolkit_name(), + TerminalToolkit.toolkit_name(), + NoteTakingToolkit.toolkit_name(), + SearchToolkit.toolkit_name(), + ], + ) + + +async def social_medium_agent(options: Chat): + """ + Agent to handling tasks related to social media: + include toolkits: WhatsApp, Twitter, LinkedIn, Reddit, Notion, Slack, Discord and Google Suite. + """ + working_directory = options.file_save_path() + tools = [ + *WhatsAppToolkit.get_can_use_tools(options.task_id), + *TwitterToolkit.get_can_use_tools(options.task_id), + *LinkedInToolkit.get_can_use_tools(options.task_id), + *RedditToolkit.get_can_use_tools(options.task_id), + *await NotionMCPToolkit.get_can_use_tools(options.task_id), + # *SlackToolkit.get_can_use_tools(options.task_id), + *await GoogleGmailMCPToolkit.get_can_use_tools(options.task_id, options.get_bun_env()), + *GoogleCalendarToolkit.get_can_use_tools(options.task_id), + *HumanToolkit.get_can_use_tools(options.task_id, Agents.social_medium_agent), + *TerminalToolkit(options.task_id, agent_name=Agents.social_medium_agent, clone_current_env=False).get_tools(), + *NoteTakingToolkit( + options.task_id, Agents.social_medium_agent, working_directory=working_directory + ).get_tools(), + # *DiscordToolkit(options.task_id).get_tools(), # Not supported temporarily + # *GoogleSuiteToolkit(options.task_id).get_tools(), # Not supported temporarily + ] + if env("EXA_API_KEY") or options.is_cloud(): + tools.append(FunctionTool(SearchToolkit(options.task_id, Agents.social_medium_agent).search_exa)) + return agent_model( + Agents.social_medium_agent, + BaseMessage.make_assistant_message( + role_name="Social Medium Agent", + content=f""" +You are a Social Media Management Assistant with comprehensive capabilities +across multiple platforms. You MUST use the `send_message_to_user` tool to +inform the user of every decision and action you take. Your message must +include a short title and a one-sentence description. This is a mandatory +part of your workflow. When you complete your task, your final response must +be a comprehensive summary of your actions, presented in a clear, detailed, +and easy-to-read format. Avoid using markdown tables for presenting data; +use plain text formatting instead. + +You are now working in `{working_directory}`. All your work +related to local operations should be done in that directory. +The current date is {datetime.date.today()}. For any date-related tasks, you MUST use this as the current date. + +Your integrated toolkits enable you to: + +1. WhatsApp Business Management (WhatsAppToolkit): + - Send text and template messages to customers via the WhatsApp Business + API. + - Retrieve business profile information. + +2. Twitter Account Management (TwitterToolkit): + - Create tweets with text content, polls, or as quote tweets. + - Delete existing tweets. + - Retrieve user profile information. + +3. LinkedIn Professional Networking (LinkedInToolkit): + - Create posts on LinkedIn. + - Delete existing posts. + - Retrieve authenticated user's profile information. + +4. Reddit Content Analysis (RedditToolkit): + - Collect top posts and comments from specified subreddits. + - Perform sentiment analysis on Reddit comments. + - Track keyword discussions across multiple subreddits. + +5. Notion Workspace Management (NotionToolkit): + - List all pages and users in a Notion workspace. + - Retrieve and extract text content from Notion blocks. + +6. Slack Workspace Interaction (SlackToolkit): + - Create new Slack channels (public or private). + - Join or leave existing channels. + - Send and delete messages in channels. + - Retrieve channel information and message history. + +7. Human Interaction (HumanToolkit): + - Ask questions to users and send messages via console. + +8. Agent Communication: + - Communicate with other agents using messaging tools when collaboration + is needed. Use `list_available_agents` to see available team members and + `send_message` to coordinate with them, especially when you need content + from document agents or research from search agents. + +9. File System Access: + - You can use terminal tools to interact with the local file system in + your working directory (`{working_directory}`), for example, to access + files needed for posting. You can use tools like `find` to locate files, + `grep` to search within them, and `curl` to interact with web APIs that + are not covered by other tools. + +When assisting users, always: +- Identify which platform's functionality is needed for the task. +- Check if required API credentials are available before attempting +operations. +- Provide clear explanations of what actions you're taking. +- Handle rate limits and API restrictions appropriately. +- Ask clarifying questions when user requests are ambiguous. +""", + ), + options, + tools, + tool_names=[ + WhatsAppToolkit.toolkit_name(), + TwitterToolkit.toolkit_name(), + LinkedInToolkit.toolkit_name(), + RedditToolkit.toolkit_name(), + NotionMCPToolkit.toolkit_name(), + GoogleGmailMCPToolkit.toolkit_name(), + GoogleCalendarToolkit.toolkit_name(), + HumanToolkit.toolkit_name(), + TerminalToolkit.toolkit_name(), + NoteTakingToolkit.toolkit_name(), + ], + ) + + +async def mcp_agent(options: Chat): + tools = [ + # *HumanToolkit.get_can_use_tools(options.task_id, Agents.mcp_agent), + *McpSearchToolkit(options.task_id).get_tools(), + ] + if len(options.installed_mcp["mcpServers"]) > 0: + try: + tools = [*tools, *await get_mcp_tools(options.installed_mcp)] + except Exception as e: + logger.debug(repr(e)) + + task_lock = get_task_lock(options.task_id) + agent_id = str(uuid.uuid4()) + asyncio.create_task( + task_lock.put_queue( + ActionCreateAgentData( + data={ + "agent_name": Agents.mcp_agent, + "agent_id": agent_id, + "tools": [key for key in options.installed_mcp["mcpServers"].keys()], + } + ) + ) + ) + return ListenChatAgent( + options.task_id, + Agents.mcp_agent, + system_message="You are a helpful assistant that can help users search mcp servers. The found mcp services will be returned to the user, and you will ask the user via ask_human_via_gui whether they want to install these mcp services.", + model=ModelFactory.create( + model_platform=options.model_platform, + model_type=options.model_type, + api_key=options.api_key, + ), + # output_language=options.language, + tools=tools, + agent_id=agent_id, + ) + + +async def get_toolkits(tools: list[str], agent_name: str, api_task_id: str): + toolkits = { + "audio_analysis_toolkit": AudioAnalysisToolkit, + "openai_image_toolkit": OpenAIImageToolkit, + "excel_toolkit": ExcelToolkit, + "file_write_toolkit": FileWriteToolkit, + "github_toolkit": GithubToolkit, + "google_calendar_toolkit": GoogleCalendarToolkit, + "google_drive_mcp_toolkit": GoogleDriveMCPToolkit, + "google_gmail_mcp_toolkit": GoogleGmailMCPToolkit, + "image_analysis_toolkit": ImageAnalysisToolkit, + "linkedin_toolkit": LinkedInToolkit, + "mcp_search_toolkit": McpSearchToolkit, + "notion_toolkit": NotionMCPToolkit, + "pptx_toolkit": PPTXToolkit, + "reddit_toolkit": RedditToolkit, + "search_toolkit": SearchToolkit, + "slack_toolkit": SlackToolkit, + "terminal_toolkit": TerminalToolkit, + "twitter_toolkit": TwitterToolkit, + "video_analysis_toolkit": VideoAnalysisToolkit, + "video_download_toolkit": VideoDownloaderToolkit, + "whatsapp_toolkit": WhatsAppToolkit, + } + res = [] + for item in tools: + if item in toolkits: + toolkit: AbstractToolkit = toolkits[item] + toolkit.agent_name = agent_name + res = toolkit.get_can_use_tools(api_task_id) + res = await res if asyncio.iscoroutine(res) else res + res.extend(res) + else: + logger.warning(f"Toolkit {item} not found, please check your configuration.") + return res + + +async def get_mcp_tools(mcp_server: McpServers): + if len(mcp_server["mcpServers"]) == 0: + return [] + logger.debug(f">>>>>>>>{mcp_server}") + mcp_toolkit = MCPToolkit(config_dict={**mcp_server}, timeout=180) + return mcp_toolkit.get_tools() diff --git a/backend/app/utils/listen/toolkit_listen.py b/backend/app/utils/listen/toolkit_listen.py new file mode 100644 index 000000000..542effbc2 --- /dev/null +++ b/backend/app/utils/listen/toolkit_listen.py @@ -0,0 +1,177 @@ +import asyncio +from functools import wraps +from inspect import iscoroutinefunction +import json +from typing import Any, Callable + +from loguru import logger +from app.service.task import ( + ActionActivateToolkitData, + ActionDeactivateToolkitData, + get_task_lock, +) +from app.utils.toolkit.abstract_toolkit import AbstractToolkit +from app.service.task import process_task + + +def listen_toolkit( + wrap_method: Callable[..., Any] | None = None, + inputs: Callable[..., str] | None = None, + return_msg: Callable[[Any], str] | None = None, +): + def decorator(func: Callable[..., Any]): + wrap = func if wrap_method is None else wrap_method + + if iscoroutinefunction(func): + # async function wrapper + @wraps(func) + async def async_wrapper(*args, **kwargs): + toolkit: AbstractToolkit = args[0] + task_lock = get_task_lock(toolkit.api_task_id) + + if inputs is not None: + args_str = inputs(*args, **kwargs) + else: + # remove first param self + filtered_args = args[1:] if len(args) > 0 else [] + + args_str = ", ".join(repr(arg) for arg in filtered_args) + if kwargs: + kwargs_str = ", ".join(f"{k}={v!r}" for k, v in kwargs.items()) + args_str = f"{args_str}, {kwargs_str}" if args_str else kwargs_str + + toolkit_name = toolkit.toolkit_name() + method_name = func.__name__.replace("_", " ") + await task_lock.put_queue( + ActionActivateToolkitData( + data={ + "agent_name": toolkit.agent_name, + "process_task_id": process_task.get(""), + "toolkit_name": toolkit_name, + "method_name": method_name, + "message": args_str, + }, + ) + ) + error = None + res = None + try: + res = await func(*args, **kwargs) + except Exception as e: + error = e + + if return_msg and error is None: + res_msg = return_msg(res) + elif isinstance(res, str): + res_msg = res + else: + if error is None: + try: + res_msg = json.dumps(res, ensure_ascii=False) + except TypeError: + # Handle cases where res contains non-serializable objects (like coroutines) + res_msg = str(res) + else: + res_msg = str(error) + + await task_lock.put_queue( + ActionDeactivateToolkitData( + data={ + "agent_name": toolkit.agent_name, + "process_task_id": process_task.get(""), + "toolkit_name": toolkit_name, + "method_name": method_name, + "message": res_msg[:500] if len(res_msg) > 500 else res_msg, + }, + ) + ) + if error is not None: + raise error + return res + + return async_wrapper + + else: + # sync function wrapper + @wraps(func) + def sync_wrapper(*args, **kwargs): + toolkit: AbstractToolkit = args[0] + task_lock = get_task_lock(toolkit.api_task_id) + + if inputs is not None: + args_str = inputs(*args, **kwargs) + else: + # remove first param self + filtered_args = args[1:] if len(args) > 0 else [] + + args_str = ", ".join(repr(arg) for arg in filtered_args) + if kwargs: + kwargs_str = ", ".join(f"{k}={v!r}" for k, v in kwargs.items()) + args_str = f"{args_str}, {kwargs_str}" if args_str else kwargs_str + + toolkit_name = toolkit.toolkit_name() + method_name = func.__name__.replace("_", " ") + task = asyncio.create_task( + task_lock.put_queue( + ActionActivateToolkitData( + data={ + "agent_name": toolkit.agent_name, + "process_task_id": process_task.get(""), + "toolkit_name": toolkit_name, + "method_name": method_name, + "message": args_str, + }, + ) + ) + ) + if hasattr(task_lock, "add_background_task"): + task_lock.add_background_task(task) + error = None + res = None + try: + print(">>>>", func.__name__, "<<<<") + res = func(*args, **kwargs) + # Safety check: if the result is a coroutine, we need to await it + if asyncio.iscoroutine(res): + import warnings + warnings.warn(f"Async function {func.__name__} was incorrectly called synchronously") + res = asyncio.run(res) + except Exception as e: + error = e + + if return_msg and error is None: + res_msg = return_msg(res) + elif isinstance(res, str): + res_msg = res + else: + if error is None: + try: + res_msg = json.dumps(res, ensure_ascii=False) + except TypeError: + # Handle cases where res contains non-serializable objects (like coroutines) + res_msg = str(res) + else: + res_msg = str(error) + + task = asyncio.create_task( + task_lock.put_queue( + ActionDeactivateToolkitData( + data={ + "agent_name": toolkit.agent_name, + "process_task_id": process_task.get(""), + "toolkit_name": toolkit_name, + "method_name": method_name, + "message": res_msg[:500] if len(res_msg) > 500 else res_msg, + }, + ) + ) + ) + if hasattr(task_lock, "add_background_task"): + task_lock.add_background_task(task) + if error is not None: + raise error + return res + + return sync_wrapper + + return decorator diff --git a/backend/app/utils/server/sync_step.py b/backend/app/utils/server/sync_step.py new file mode 100644 index 000000000..c45714e2f --- /dev/null +++ b/backend/app/utils/server/sync_step.py @@ -0,0 +1,48 @@ +import time +import httpx +import asyncio +import os +import json +from loguru import logger +from app.service.chat_service import Chat +from app.component.environment import env + + +def sync_step(func): + async def wrapper(*args, **kwargs): + server_url = env("SERVER_URL") + sync_url = server_url + "/chat/steps" if server_url else None + async for value in func(*args, **kwargs): + if not server_url: + yield value + continue + + if isinstance(value, str) and value.startswith("data: "): + value_json_str = value[len("data: ") :].strip() + else: + value_json_str = value + json_data = json.loads(value_json_str) + chat: Chat = args[0] if args else None + if chat is not None: + asyncio.create_task( + send_to_api( + sync_url, + { + "task_id": chat.task_id, + "step": json_data["step"], + "data": json_data["data"], + }, + ) + ) + yield value + + return wrapper + + +async def send_to_api(url, data): + async with httpx.AsyncClient() as client: + try: + res = await client.post(url, json=data) + # logger.info(res) + except Exception as e: + logger.error(e) diff --git a/backend/app/utils/single_agent_worker.py b/backend/app/utils/single_agent_worker.py new file mode 100644 index 000000000..e8008e53a --- /dev/null +++ b/backend/app/utils/single_agent_worker.py @@ -0,0 +1,197 @@ +import datetime +from camel.agents.chat_agent import AsyncStreamingChatAgentResponse +from camel.societies.workforce.single_agent_worker import SingleAgentWorker as BaseSingleAgentWorker +from camel.tasks.task import Task, TaskState, is_task_result_insufficient + +from app.utils.agent import ListenChatAgent +from camel.societies.workforce.prompts import PROCESS_TASK_PROMPT +from colorama import Fore +from camel.societies.workforce.utils import TaskResult + + +class SingleAgentWorker(BaseSingleAgentWorker): + def __init__( + self, + description: str, + worker: ListenChatAgent, + use_agent_pool: bool = True, + pool_initial_size: int = 1, + pool_max_size: int = 10, + auto_scale_pool: bool = True, + use_structured_output_handler: bool = True, + ) -> None: + super().__init__( + description=description, + worker=worker, + use_agent_pool=use_agent_pool, + pool_initial_size=pool_initial_size, + pool_max_size=pool_max_size, + auto_scale_pool=auto_scale_pool, + use_structured_output_handler=use_structured_output_handler, + ) + self.worker = worker # change type hint + + async def _process_task(self, task: Task, dependencies: list[Task]) -> TaskState: + r"""Processes a task with its dependencies using an efficient agent + management system. + + This method asynchronously processes a given task, considering its + dependencies, by sending a generated prompt to a worker agent. + Uses an agent pool for efficiency when enabled, or falls back to + cloning when pool is disabled. + + Args: + task (Task): The task to process, which includes necessary details + like content and type. + dependencies (List[Task]): Tasks that the given task depends on. + + Returns: + TaskState: `TaskState.DONE` if processed successfully, otherwise + `TaskState.FAILED`. + """ + # Get agent efficiently (from pool or by cloning) + worker_agent = await self._get_worker_agent() + worker_agent.process_task_id = task.id # type: ignore rewrite line + + response_content = "" + try: + dependency_tasks_info = self._get_dep_tasks_info(dependencies) + prompt = PROCESS_TASK_PROMPT.format( + content=task.content, + parent_task_content=task.parent.content if task.parent else "", + dependency_tasks_info=dependency_tasks_info, + additional_info=task.additional_info, + ) + + if self.use_structured_output_handler and self.structured_handler: + # Use structured output handler for prompt-based extraction + enhanced_prompt = self.structured_handler.generate_structured_prompt( + base_prompt=prompt, + schema=TaskResult, + examples=[ + { + "content": "I have successfully completed the task...", + "failed": False, + } + ], + additional_instructions="Ensure you provide a clear " + "description of what was done and whether the task " + "succeeded or failed.", + ) + response = await worker_agent.astep(enhanced_prompt) + + # Handle streaming response + if isinstance(response, AsyncStreamingChatAgentResponse): + content = "" + async for chunk in response: + if chunk.msg: + content = chunk.msg.content + response_content = content + else: + # Regular ChatAgentResponse + response_content = response.msg.content if response.msg else "" + + task_result = self.structured_handler.parse_structured_response( + response_text=response_content, + schema=TaskResult, + fallback_values={ + "content": "Task processing failed", + "failed": True, + }, + ) + else: + # Use native structured output if supported + response = await worker_agent.astep(prompt, response_format=TaskResult) + + # Handle streaming response for native output + if isinstance(response, AsyncStreamingChatAgentResponse): + task_result = None + async for chunk in response: + if chunk.msg and chunk.msg.parsed: + task_result = chunk.msg.parsed + response_content = chunk.msg.content + # If no parsed result found in streaming, create fallback + if task_result is None: + task_result = TaskResult( + content="Failed to parse streaming response", + failed=True, + ) + else: + # Regular ChatAgentResponse + task_result = response.msg.parsed + response_content = response.msg.content if response.msg else "" + + # Get token usage from the response + if isinstance(response, AsyncStreamingChatAgentResponse): + # For streaming responses, get the final response info + final_response = await response + usage_info = final_response.info.get("usage") or final_response.info.get("token_usage") + else: + usage_info = response.info.get("usage") or response.info.get("token_usage") + total_tokens = usage_info.get("total_tokens", 0) if usage_info else 0 + + except Exception as e: + print(f"{Fore.RED}Error processing task {task.id}: {type(e).__name__}: {e}{Fore.RESET}") + # Store error information in task result + task.result = f"{type(e).__name__}: {e!s}" + return TaskState.FAILED + finally: + # Return agent to pool or let it be garbage collected + await self._return_worker_agent(worker_agent) + + # Populate additional_info with worker attempt details + if task.additional_info is None: + task.additional_info = {} + + # Create worker attempt details with descriptive keys + worker_attempt_details = { + "agent_id": getattr(worker_agent, "agent_id", worker_agent.role_name), + "original_worker_id": getattr(self.worker, "agent_id", self.worker.role_name), + "timestamp": str(datetime.datetime.now()), + "description": f"Attempt by " + f"{getattr(worker_agent, 'agent_id', worker_agent.role_name)} " + f"(from pool/clone of " + f"{getattr(self.worker, 'agent_id', self.worker.role_name)}) " + f"to process task: {task.content}", + "response_content": response_content[:50], + "tool_calls": str( + final_response.info.get("tool_calls") + if isinstance(response, AsyncStreamingChatAgentResponse) + else response.info.get("tool_calls") + )[:50], + "total_tokens": total_tokens, + } + + # Store the worker attempt in additional_info + if "worker_attempts" not in task.additional_info: + task.additional_info["worker_attempts"] = [] + task.additional_info["worker_attempts"].append(worker_attempt_details) + + # Store the actual token usage for this specific task + task.additional_info["token_usage"] = {"total_tokens": total_tokens} + + print(f"======\n{Fore.GREEN}Response from {self}:{Fore.RESET}") + + if not self.use_structured_output_handler: + # Handle native structured output parsing + if task_result is None: + print(f"{Fore.RED}Error in worker step execution: Invalid task result{Fore.RESET}") + task_result = TaskResult( + content="Failed to generate valid task result.", + failed=True, + ) + + color = Fore.RED if task_result.failed else Fore.GREEN # type: ignore[union-attr] + print( + f"\n{color}{task_result.content}{Fore.RESET}\n======", # type: ignore[union-attr] + ) + + task.result = task_result.content # type: ignore[union-attr] + + if task_result.failed: # type: ignore[union-attr] + return TaskState.FAILED + + if is_task_result_insufficient(task): + print(f"{Fore.RED}Task {task.id}: Content validation failed - task marked as failed{Fore.RESET}") + return TaskState.FAILED + return TaskState.DONE diff --git a/backend/app/utils/toolkit/abstract_toolkit.py b/backend/app/utils/toolkit/abstract_toolkit.py new file mode 100644 index 000000000..4a41cd6ea --- /dev/null +++ b/backend/app/utils/toolkit/abstract_toolkit.py @@ -0,0 +1,16 @@ +from camel.toolkits.function_tool import FunctionTool +from inflection import titleize + + +class AbstractToolkit: + api_task_id: str + agent_name: str + + @classmethod + def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]: + """default return all tools, subclass can override this method to filter tools""" + return cls(api_task_id).get_tools() # type: ignore + + @classmethod + def toolkit_name(cls) -> str: + return titleize(cls.__name__) diff --git a/backend/app/utils/toolkit/audio_analysis_toolkit.py b/backend/app/utils/toolkit/audio_analysis_toolkit.py new file mode 100644 index 000000000..ff69d35a5 --- /dev/null +++ b/backend/app/utils/toolkit/audio_analysis_toolkit.py @@ -0,0 +1,36 @@ +import os +from camel.models import BaseAudioModel, BaseModelBackend +from camel.toolkits import AudioAnalysisToolkit as BaseAudioAnalysisToolkit + +from app.component.environment import env +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class AudioAnalysisToolkit(BaseAudioAnalysisToolkit, AbstractToolkit): + agent_name: str = Agents.multi_modal_agent + + def __init__( + self, + api_task_id: str, + cache_dir: str | None = None, + transcribe_model: BaseAudioModel | None = None, + audio_reasoning_model: BaseModelBackend | None = None, + timeout: float | None = None, + ): + if cache_dir is None: + cache_dir = env("file_save_path", os.path.expanduser("~/.eigent/tmp/")) + super().__init__(cache_dir, transcribe_model, audio_reasoning_model, timeout) + self.api_task_id = api_task_id + + @listen_toolkit( + BaseAudioAnalysisToolkit.audio2text, + lambda _, audio_path, question: f"transcribe audio from {audio_path} and ask question: {question}", + ) + def ask_question_about_audio(self, audio_path: str, question: str) -> str: + return super().ask_question_about_audio(audio_path, question) + + @listen_toolkit(BaseAudioAnalysisToolkit.audio2text) + def audio2text(self, audio_path: str) -> str: + return super().audio2text(audio_path) diff --git a/backend/app/utils/toolkit/code_execution_toolkit.py b/backend/app/utils/toolkit/code_execution_toolkit.py new file mode 100644 index 000000000..2e0292a37 --- /dev/null +++ b/backend/app/utils/toolkit/code_execution_toolkit.py @@ -0,0 +1,39 @@ +from typing import List, Literal +from camel.toolkits import CodeExecutionToolkit as BaseCodeExecutionToolkit, FunctionTool +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class CodeExecutionToolkit(BaseCodeExecutionToolkit, AbstractToolkit): + agent_name: str = Agents.developer_agent + + def __init__( + self, + api_task_id: str, + sandbox: Literal["internal_python", "jupyter", "docker", "subprocess", "e2b"] = "subprocess", + verbose: bool = False, + unsafe_mode: bool = False, + import_white_list: List[str] | None = None, + require_confirm: bool = False, + timeout: float | None = None, + ) -> None: + self.api_task_id = api_task_id + super().__init__(sandbox, verbose, unsafe_mode, import_white_list, require_confirm, timeout) + + @listen_toolkit( + BaseCodeExecutionToolkit.execute_code, + ) + def execute_code(self, code: str, code_type: str = "python") -> str: + return super().execute_code(code, code_type) + + @listen_toolkit( + BaseCodeExecutionToolkit.execute_command, + ) + def execute_command(self, command: str) -> str | tuple[str, str]: + return super().execute_command(command) + + def get_tools(self) -> List[FunctionTool]: + return [ + FunctionTool(self.execute_code), + ] diff --git a/backend/app/utils/toolkit/craw4ai_toolkit.py b/backend/app/utils/toolkit/craw4ai_toolkit.py new file mode 100644 index 000000000..a422465dd --- /dev/null +++ b/backend/app/utils/toolkit/craw4ai_toolkit.py @@ -0,0 +1,29 @@ +from camel.toolkits import Crawl4AIToolkit as BaseCrawl4AIToolkit + +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class Crawl4AIToolkit(BaseCrawl4AIToolkit, AbstractToolkit): + agent_name: str = Agents.search_agent + + def __init__(self, api_task_id: str, timeout: float | None = None): + self.api_task_id = api_task_id + super().__init__(timeout) + + # async def _get_client(self): + # r"""Get or create the AsyncWebCrawler client.""" + # if self._client is None: + # from crawl4ai import AsyncWebCrawler + + # self._client = AsyncWebCrawler(use_managed_browser=True) + # await self._client.__aenter__() + # return self._client + + @listen_toolkit(BaseCrawl4AIToolkit.scrape) + async def scrape(self, url: str) -> str: + return await super().scrape(url) + + def toolkit_name(self) -> str: + return "Crawl Toolkit" diff --git a/backend/app/utils/toolkit/excel_toolkit.py b/backend/app/utils/toolkit/excel_toolkit.py new file mode 100644 index 000000000..e871a1851 --- /dev/null +++ b/backend/app/utils/toolkit/excel_toolkit.py @@ -0,0 +1,26 @@ +import os +from camel.toolkits import ExcelToolkit as BaseExcelToolkit + +from app.component.environment import env +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class ExcelToolkit(BaseExcelToolkit, AbstractToolkit): + agent_name: str = Agents.document_agent + + def __init__( + self, + api_task_id: str, + timeout: float | None = None, + working_directory: str | None = None, + ): + self.api_task_id = api_task_id + if working_directory is None: + working_directory = env("file_save_path", os.path.expanduser("~/Downloads")) + super().__init__(timeout) + + @listen_toolkit(BaseExcelToolkit.extract_excel_content) + def extract_excel_content(self, document_path: str) -> str: + return super().extract_excel_content(document_path) diff --git a/backend/app/utils/toolkit/file_write_toolkit.py b/backend/app/utils/toolkit/file_write_toolkit.py new file mode 100644 index 000000000..51c38af65 --- /dev/null +++ b/backend/app/utils/toolkit/file_write_toolkit.py @@ -0,0 +1,56 @@ +import asyncio +import os +from typing import List +from camel.toolkits import FileWriteToolkit as BaseFileWriteToolkit +from app.component.environment import env +from app.service.task import process_task +from app.service.task import ActionWriteFileData, Agents, get_task_lock +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class FileWriteToolkit(BaseFileWriteToolkit, AbstractToolkit): + agent_name: str = Agents.document_agent + + def __init__( + self, + api_task_id: str, + working_directory: str | None = None, + timeout: float | None = None, + default_encoding: str = "utf-8", + backup_enabled: bool = True, + ) -> None: + if working_directory is None: + working_directory = env("file_save_path", os.path.expanduser("~/Downloads")) + super().__init__(working_directory, timeout, default_encoding, backup_enabled) + self.api_task_id = api_task_id + + @listen_toolkit( + BaseFileWriteToolkit.write_to_file, + lambda _, + title, + content, + filename, + encoding=None, + use_latex=False: f"write content to file: {filename} with encoding: {encoding} and use_latex: {use_latex}", + ) + def write_to_file( + self, + title: str, + content: str | List[List[str]], + filename: str, + encoding: str | None = None, + use_latex: bool = False, + ) -> str: + res = super().write_to_file(title, content, filename, encoding, use_latex) + if "Content successfully written to file: " in res: + task_lock = get_task_lock(self.api_task_id) + asyncio.create_task( + task_lock.put_queue( + ActionWriteFileData( + process_task_id=process_task.get(), + data=res.replace("Content successfully written to file: ", ""), + ) + ) + ) + return res diff --git a/backend/app/utils/toolkit/github_toolkit.py b/backend/app/utils/toolkit/github_toolkit.py new file mode 100644 index 000000000..87ba2ed16 --- /dev/null +++ b/backend/app/utils/toolkit/github_toolkit.py @@ -0,0 +1,107 @@ +from typing import Literal +from camel.toolkits import GithubToolkit as BaseGithubToolkit +from camel.toolkits.function_tool import FunctionTool +from app.component.environment import env +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class GithubToolkit(BaseGithubToolkit, AbstractToolkit): + agent_name: str = Agents.developer_agent + + def __init__( + self, + api_task_id: str, + access_token: str | None = None, + timeout: float | None = None, + ) -> None: + super().__init__(access_token, timeout) + self.api_task_id = api_task_id + + @listen_toolkit( + BaseGithubToolkit.create_pull_request, + lambda _, + repo_name, + file_path, + new_content, + pr_title, + body, + branch_name: f"Create PR in {repo_name} for {file_path} with title '{pr_title}', branch '{branch_name}', content '{new_content}'", + ) + def create_pull_request( + self, + repo_name: str, + file_path: str, + new_content: str, + pr_title: str, + body: str, + branch_name: str, + ) -> str: + return super().create_pull_request(repo_name, file_path, new_content, pr_title, body, branch_name) + + @listen_toolkit( + BaseGithubToolkit.get_issue_list, + lambda _, repo_name, state="all": f"Get issue list from {repo_name} with state '{state}'", + lambda issues: f"Retrieved {len(issues)} issues", + ) + def get_issue_list( + self, repo_name: str, state: Literal["open", "closed", "all"] = "all" + ) -> list[dict[str, object]]: + return super().get_issue_list(repo_name, state) + + @listen_toolkit( + BaseGithubToolkit.get_issue_content, + lambda _, repo_name, issue_number: f"Get content of issue {issue_number} from {repo_name}", + ) + def get_issue_content(self, repo_name: str, issue_number: int) -> str: + return super().get_issue_content(repo_name, issue_number) + + @listen_toolkit( + BaseGithubToolkit.get_pull_request_list, + lambda _, repo_name, state="all": f"Get pull request list from {repo_name} with state '{state}'", + lambda prs: f"Retrieved {len(prs)} pull requests", + ) + def get_pull_request_list( + self, repo_name: str, state: Literal["open", "closed", "all"] = "all" + ) -> list[dict[str, object]]: + return super().get_pull_request_list(repo_name, state) + + @listen_toolkit( + BaseGithubToolkit.get_pull_request_code, + lambda _, repo_name, pr_number: f"Get code for pull request {pr_number} in {repo_name}", + lambda code: f"Retrieved {len(code)} code files", + ) + def get_pull_request_code(self, repo_name: str, pr_number: int) -> list[dict[str, str]]: + return super().get_pull_request_code(repo_name, pr_number) + + @listen_toolkit( + BaseGithubToolkit.get_pull_request_comments, + lambda _, repo_name, pr_number: f"Get comments for pull request {pr_number} in {repo_name}", + lambda comments: f"Retrieved {len(comments)} comments", + ) + def get_pull_request_comments(self, repo_name: str, pr_number: int) -> list[dict[str, str]]: + return super().get_pull_request_comments(repo_name, pr_number) + + @listen_toolkit( + BaseGithubToolkit.get_all_file_paths, + lambda _, repo_name, path="": f"Get all file paths from {repo_name}, path '{path}'", + lambda paths: f"Retrieved {len(paths)} file paths", + ) + def get_all_file_paths(self, repo_name: str, path: str = "") -> list[str]: + return super().get_all_file_paths(repo_name, path) + + @listen_toolkit( + BaseGithubToolkit.retrieve_file_content, + lambda _, repo_name, file_path: f"Retrieve content of file {file_path} from {repo_name}", + lambda content: f"Retrieved content of length {len(content)}", + ) + def retrieve_file_content(self, repo_name: str, file_path: str) -> str: + return super().retrieve_file_content(repo_name, file_path) + + @classmethod + def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]: + if env("GITHUB_ACCESS_TOKEN"): + return GithubToolkit(api_task_id).get_tools() + else: + return [] diff --git a/backend/app/utils/toolkit/google_calendar_toolkit.py b/backend/app/utils/toolkit/google_calendar_toolkit.py new file mode 100644 index 000000000..34b07bd31 --- /dev/null +++ b/backend/app/utils/toolkit/google_calendar_toolkit.py @@ -0,0 +1,59 @@ +from typing import Any, Dict, List +from app.component.environment import env +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit +from camel.toolkits import GoogleCalendarToolkit as BaseGoogleCalendarToolkit + + +class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): + agent_name: str = Agents.social_medium_agent + + def __init__(self, api_task_id: str, timeout: float | None = None): + self.api_task_id = api_task_id + super().__init__(timeout) + + @listen_toolkit(BaseGoogleCalendarToolkit.create_event) + def create_event( + self, + event_title: str, + start_time: str, + end_time: str, + description: str = "", + location: str = "", + attendees_email: List[str] | None = None, + timezone: str = "UTC", + ) -> Dict[str, Any]: + return super().create_event(event_title, start_time, end_time, description, location, attendees_email, timezone) + + @listen_toolkit(BaseGoogleCalendarToolkit.get_events) + def get_events(self, max_results: int = 10, time_min: str | None = None) -> List[Dict[str, Any]] | Dict[str, Any]: + return super().get_events(max_results, time_min) + + @listen_toolkit(BaseGoogleCalendarToolkit.update_event) + def update_event( + self, + event_id: str, + event_title: str | None = None, + start_time: str | None = None, + end_time: str | None = None, + description: str | None = None, + location: str | None = None, + attendees_email: List[str] | None = None, + ) -> Dict[str, Any]: + return super().update_event(event_id, event_title, start_time, end_time, description, location, attendees_email) + + @listen_toolkit(BaseGoogleCalendarToolkit.delete_event) + def delete_event(self, event_id: str) -> str: + return super().delete_event(event_id) + + @listen_toolkit(BaseGoogleCalendarToolkit.get_calendar_details) + def get_calendar_details(self) -> Dict[str, Any]: + return super().get_calendar_details() + + @classmethod + def get_can_use_tools(cls, api_task_id: str): + if env("GOOGLE_CLIENT_ID") and env("GOOGLE_CLIENT_SECRET"): + return cls(api_task_id).get_tools() + else: + return [] diff --git a/backend/app/utils/toolkit/google_drive_mcp_toolkit.py b/backend/app/utils/toolkit/google_drive_mcp_toolkit.py new file mode 100644 index 000000000..6e880e83d --- /dev/null +++ b/backend/app/utils/toolkit/google_drive_mcp_toolkit.py @@ -0,0 +1,45 @@ +from camel.toolkits import GoogleDriveMCPToolkit as BaseGoogleDriveMCPToolkit, MCPToolkit +from app.component.command import bun +from app.component.environment import env +from app.service.task import Agents +from app.utils.toolkit.abstract_toolkit import AbstractToolkit +from camel.toolkits.function_tool import FunctionTool + + +class GoogleDriveMCPToolkit(BaseGoogleDriveMCPToolkit, AbstractToolkit): + agent_name: str = Agents.document_agent + + def __init__( + self, + api_task_id: str, + timeout: float | None = None, + credentials_path: str | None = None, + input_env: dict[str, str] | None = None, + ) -> None: + self.api_task_id = api_task_id + super().__init__(timeout, credentials_path) + credentials_path = credentials_path or env("GDRIVE_CREDENTIALS_PATH") + self._mcp_toolkit = MCPToolkit( + config_dict={ + "mcpServers": { + "gdrive": { + "command": bun(), + "args": ["x", "-y", "@modelcontextprotocol/server-gdrive"], + "env": {"GDRIVE_CREDENTIALS_PATH": credentials_path, **(input_env or {})}, + } + } + }, + timeout=timeout, + ) + + @classmethod + async def get_can_use_tools(cls, api_task_id: str, input_env: dict[str, str] | None = None) -> list[FunctionTool]: + if env("GDRIVE_CREDENTIALS_PATH") is None: + return [] + toolkit = cls(api_task_id, 180, env("GDRIVE_CREDENTIALS_PATH"), input_env) + await toolkit.connect() + tools = [] + for item in toolkit.get_tools(): + setattr(item, "_toolkit_name", cls.__name__) + tools.append(item) + return tools diff --git a/backend/app/utils/toolkit/google_gmail_mcp_toolkit.py b/backend/app/utils/toolkit/google_gmail_mcp_toolkit.py new file mode 100644 index 000000000..68aec649a --- /dev/null +++ b/backend/app/utils/toolkit/google_gmail_mcp_toolkit.py @@ -0,0 +1,53 @@ +from camel.toolkits import BaseToolkit, FunctionTool, MCPToolkit +from app.component.environment import env, env_or_fail +from app.component.command import bun +from app.service.task import Agents +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class GoogleGmailMCPToolkit(BaseToolkit, AbstractToolkit): + agent_name: str = Agents.social_medium_agent + + def __init__( + self, + api_task_id: str, + credentials_path: str | None = None, + timeout: float | None = None, + input_env: dict[str, str] | None = None, + ): + super().__init__(timeout) + self.api_task_id = api_task_id + credentials_path = credentials_path or env("GMAIL_CREDENTIALS_PATH") + self._mcp_toolkit = MCPToolkit( + config_dict={ + "mcpServers": { + "gmail": { + "command": bun(), + "args": ["x", "-y", "@gongrzhe/server-gmail-autoauth-mcp"], + "env": {"GMAIL_CREDENTIALS_PATH": credentials_path, **(input_env or {})}, + } + } + }, + timeout=timeout, + ) + + async def connect(self): + await self._mcp_toolkit.connect() + + async def disconnect(self): + await self._mcp_toolkit.disconnect() + + def get_tools(self) -> list[FunctionTool]: + return self._mcp_toolkit.get_tools() + + @classmethod + async def get_can_use_tools(cls, api_task_id: str, input_env: dict[str, str] | None = None) -> list[FunctionTool]: + if env("GMAIL_CREDENTIALS_PATH") is None: + return [] + toolkit = cls(api_task_id, env_or_fail("GMAIL_CREDENTIALS_PATH"), 180, input_env) + await toolkit.connect() + tools = [] + for item in toolkit.get_tools(): + setattr(item, "_toolkit_name", cls.__name__) + tools.append(item) + return tools diff --git a/backend/app/utils/toolkit/human_toolkit.py b/backend/app/utils/toolkit/human_toolkit.py new file mode 100644 index 000000000..ba616b5c3 --- /dev/null +++ b/backend/app/utils/toolkit/human_toolkit.py @@ -0,0 +1,138 @@ +import asyncio +from camel.toolkits.base import BaseToolkit +from loguru import logger +from camel.toolkits.function_tool import FunctionTool +from app.service.task import Action, ActionAskData, ActionNoticeData, get_task_lock +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit +from app.service.task import process_task +# Rewrite HumanToolkit because the system's user interaction was using console, but in electron we cannot use console. Changed to use SSE response to let frontend show dialog for user interaction + + +class HumanToolkit(BaseToolkit, AbstractToolkit): + r"""A class representing a toolkit for human interaction. + Note: + This toolkit should be called to send a tidy message to the user to + keep them informed. + """ + + agent_name: str + + def __init__(self, api_task_id: str, agent_name: str, timeout: float | None = None): + super().__init__(timeout) + self.api_task_id = api_task_id + self.agent_name = agent_name + task_lock = get_task_lock(self.api_task_id) + task_lock.add_human_input_listen(self.agent_name) + + @listen_toolkit(inputs=lambda _, question: question) + async def ask_human_via_gui(self, question: str) -> str: + """Use this tool to ask a question to the user when you are stuck, + need clarification, or require a decision to be made. This is a + two-way communication channel that will wait for the user's response. + You should use it to: + - Clarify ambiguous instructions or requirements. + - Request missing information that you cannot find (e.g., login + credentials, file paths). + - Ask for a decision when there are multiple viable options. + - Seek help when you encounter an error you cannot resolve on your own. + + Args: + question (str): The question to ask the user. + + Returns: + str: The user's response to the question. + """ + logger.info(f"Question: {question}") + task_lock = get_task_lock(self.api_task_id) + await task_lock.put_queue( + ActionAskData( + action=Action.ask, + data={ + "question": question, + "agent": self.agent_name, + }, + ) + ) + + reply = await task_lock.get_human_input(self.agent_name) + logger.info(f"User reply: {reply}") + return reply + + @listen_toolkit() + def send_message_to_user( + self, + message_title: str, + message_description: str, + message_attachment: str | None = None, + ) -> str: + r"""Use this tool to send a tidy message to the user, including a + short title, a one-sentence description, and an optional attachment. + + This one-way tool keeps the user informed about your progress, + decisions, or actions. It does not require a response. + You should use it to: + - Announce what you are about to do. + For example: + message_title="Starting Task" + message_description="Searching for papers on GUI Agents." + - Report the result of an action. + For example: + message_title="Search Complete" + message_description="Found 15 relevant papers." + - Report a created file. + For example: + message_title="File Ready" + message_description="The report is ready for your review." + message_attachment="report.pdf" + - State a decision. + For example: + message_title="Next Step" + message_description="Analyzing the top 10 papers." + - Give a status update during a long-running task. + + Args: + message_title (str): The title of the message. + message_description (str): The short description. + message_attachment (str): The attachment of the message, + which can be a file path or a URL. + + Returns: + str: Confirmation that the message was successfully sent. + """ + print(f"\nAgent Message:\n{message_title} \n{message_description}\n") + if message_attachment: + print(message_attachment) + logger.info(f"\nAgent Message:\n{message_title} {message_description} {message_attachment}") + task_lock = get_task_lock(self.api_task_id) + asyncio.create_task( + task_lock.put_queue( + ActionNoticeData( + process_task_id=process_task.get(""), + data=f"{message_description}", + ) + ) + ) + + attachment_info = f" {message_attachment}" if message_attachment else "" + return f"Message successfully sent to user: '{message_title} {message_description}{attachment_info}'" + + def get_tools(self) -> list[FunctionTool]: + r"""Returns a list of FunctionTool objects representing the + functions in the toolkit. + + Returns: + List[FunctionTool]: A list of FunctionTool objects + representing the functions in the toolkit. + """ + return [ + FunctionTool(self.ask_human_via_gui), + FunctionTool(self.send_message_to_user), + ] + + @classmethod + def get_can_use_tools(cls, api_task_id: str, agent_name: str) -> list[FunctionTool]: + human = cls(api_task_id, agent_name) + return [ + FunctionTool(human.ask_human_via_gui), + ] diff --git a/backend/app/utils/toolkit/hybrid_browser_python_toolkit.py b/backend/app/utils/toolkit/hybrid_browser_python_toolkit.py new file mode 100644 index 000000000..e679cac65 --- /dev/null +++ b/backend/app/utils/toolkit/hybrid_browser_python_toolkit.py @@ -0,0 +1,404 @@ +import asyncio +import datetime +import json +import os +from typing import Any, Dict, List +import uuid +from camel.models import BaseModelBackend +from camel.toolkits.hybrid_browser_toolkit_py import HybridBrowserToolkit as BaseHybridBrowserToolkit +from camel.toolkits.hybrid_browser_toolkit_py.config_loader import ConfigLoader +from camel.toolkits.hybrid_browser_toolkit_py.browser_session import HybridBrowserSession as BaseHybridBrowserSession +from camel.toolkits.hybrid_browser_toolkit_py.actions import ActionExecutor +from camel.toolkits.hybrid_browser_toolkit_py.snapshot import PageSnapshot +from camel.toolkits.hybrid_browser_toolkit_py.agent import PlaywrightLLMAgent +from camel.toolkits.function_tool import FunctionTool +from loguru import logger +from app.component.environment import env +from app.exception.exception import ProgramException +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class BrowserSession(BaseHybridBrowserSession): + async def _ensure_browser_inner(self) -> None: + from playwright.async_api import async_playwright + + if self._page is not None: + return + + self._playwright = await async_playwright().start() + + # Prepare stealth options + launch_options: Dict[str, Any] = {"headless": self._headless} + context_options: Dict[str, Any] = {} + if self._stealth and self._stealth_config: + # Use preloaded stealth configuration + launch_options["args"] = self._stealth_config["launch_args"] + context_options.update(self._stealth_config["context_options"]) + + if self._user_data_dir: + raise ProgramException("connect over cdp does not support set user_data_dir") + # Path(self._user_data_dir).mkdir(parents=True, exist_ok=True) + # pl = self._playwright + # assert pl is not None + # self._context = await pl.chromium.launch_persistent_context( + # user_data_dir=self._user_data_dir, + # headless=self._headless, + # ) + # self._browser = self._context.browser + else: + pl = self._playwright + assert pl is not None + # self._browser = await pl.chromium.launch(headless=self._headless) + port = env("browser_port", 9222) + self._browser = await pl.chromium.connect_over_cdp(f"http://localhost:{port}") + self._context = self._browser.contexts[0] + + # Reuse an already open page (persistent context may restore last + # session) + # if self._context.pages: + # self._page = self._context.pages[0] + # else: + # self._page = await self._context.new_page() + + # Debug information to help trace concurrency issues + + # Initialize _pages as empty list + self._pages = {} + + for index, item in enumerate(self._context.pages): + if item.url.startswith("about:blank") and item.url != "about:blank": + tab_id = "tab-" + str(index) + self._page = item + self._pages[tab_id] = self._page + self._current_tab_id = tab_id + await item.goto("about:blank") + break + + # If no suitable page found, create a new one + if not self._page: + logger.debug(json.dumps([item.url for item in self._context.pages])) + await asyncio.sleep(3) # wait 3 sec, retry get new page + await self.get_new_tab() + logger.debug(json.dumps([item.url for item in self._context.pages])) + if not self._page: + raise ProgramException("Electron does't has page") + + # Apply stealth modifications if enabled + if self._stealth and self._stealth_script: + try: + await self._page.add_init_script(self._stealth_script) + logger.debug("Applied stealth script to main page") + except Exception as e: + logger.warning(f"Failed to apply stealth script: {e}") + + # Set up timeout for navigation + self._page.set_default_navigation_timeout(self._navigation_timeout) + self._page.set_default_timeout(self._navigation_timeout) + + # helpers + self.snapshot = PageSnapshot(self._page) + self.executor = ActionExecutor( + self._page, + self, + default_timeout=self._default_timeout, + short_timeout=self._short_timeout, + ) + logger.info("Browser session initialized successfully") + + async def get_new_tab(self): + assert self._context is not None + + # Initialize _pages if not already done + if not hasattr(self, "_pages") or self._pages is None: + self._pages = {} + + for index, item in enumerate(self._context.pages): + if item.url.startswith("about:blank") and item.url != "about:blank": + tab_id = "tab-" + str(index) + self._pages[tab_id] = item + await item.goto("about:blank") + self._page = item + self._current_tab_id = tab_id + break + + +class HybridBrowserPythonToolkit(BaseHybridBrowserToolkit, AbstractToolkit): + agent_name: str = Agents.search_agent + + def __init__( + self, + api_task_id: str, + *, + headless: bool = False, + user_data_dir: str | None = None, + stealth: bool = False, + web_agent_model: BaseModelBackend | None = None, + cache_dir: str = os.path.expanduser("~/.eigent/tmp/"), + enabled_tools: List[str] | None = None, + browser_log_to_file: bool = False, + session_id: str | None = None, + default_start_url: str = "https://google.com/", + default_timeout: int | None = None, + short_timeout: int | None = None, + navigation_timeout: int | None = None, + network_idle_timeout: int | None = None, + screenshot_timeout: int | None = None, + page_stability_timeout: int | None = None, + dom_content_loaded_timeout: int | None = None, + ) -> None: + self.api_task_id = api_task_id + self._headless = headless + self._user_data_dir = user_data_dir + self._stealth = stealth + self._web_agent_model = web_agent_model + self._cache_dir = cache_dir + self._browser_log_to_file = browser_log_to_file + self._default_start_url = default_start_url + self._session_id = session_id or "default" + + # Store timeout configuration + self._default_timeout = default_timeout + self._short_timeout = short_timeout + self._navigation_timeout = ConfigLoader.get_navigation_timeout(navigation_timeout) + self._network_idle_timeout = ConfigLoader.get_network_idle_timeout(network_idle_timeout) + self._screenshot_timeout = ConfigLoader.get_screenshot_timeout(screenshot_timeout) + self._page_stability_timeout = ConfigLoader.get_page_stability_timeout(page_stability_timeout) + self._dom_content_loaded_timeout = ConfigLoader.get_dom_content_loaded_timeout(dom_content_loaded_timeout) + + # Logging configuration - fixed values for simplicity + self.enable_action_logging = True + self.enable_timing_logging = True + self.enable_page_loading_logging = True + self.log_to_console = False # Always disabled for cleaner output + self.log_to_file = browser_log_to_file + self.max_log_length = None # No truncation for file logs + + # Set up log file if needed + if self.log_to_file: + # Create log directory if it doesn't exist + log_dir = "browser_log" + os.makedirs(log_dir, exist_ok=True) + + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + self.log_file_path: str | None = os.path.join( + log_dir, f"hybrid_browser_toolkit_{timestamp}_{session_id}.log" + ) + else: + self.log_file_path = None + + # Initialize log buffer for in-memory storage + self.log_buffer: List[Dict[str, Any]] = [] + + # Configure enabled tools + if enabled_tools is None: + self.enabled_tools = self.DEFAULT_TOOLS.copy() + else: + # Validate enabled tools + invalid_tools = [tool for tool in enabled_tools if tool not in self.ALL_TOOLS] + if invalid_tools: + raise ValueError(f"Invalid tools specified: {invalid_tools}. Available tools: {self.ALL_TOOLS}") + self.enabled_tools = enabled_tools.copy() + + logger.info(f"Enabled tools: {self.enabled_tools}") + + # Log initialization if file logging is enabled + if self.log_to_file: + logger.info("HybridBrowserToolkit initialized with file logging enabled") + logger.info(f"Log file path: {self.log_file_path}") + + # Core components + temp_session = BrowserSession( + headless=headless, + user_data_dir=user_data_dir, + stealth=stealth, + session_id=session_id, + default_timeout=default_timeout, + short_timeout=short_timeout, + ) + + # Use the session directly - singleton logic is handled in + # ensure_browser + self._session = temp_session + self._agent: PlaywrightLLMAgent | None = None + self._unified_script = self._load_unified_analyzer() + + @listen_toolkit(BaseHybridBrowserToolkit.browser_open) + async def browser_open(self) -> Dict[str, str]: + return await super().browser_open() + + @listen_toolkit(BaseHybridBrowserToolkit.browser_close) + async def browser_close(self) -> str: + return await super().browser_close() + + @listen_toolkit(BaseHybridBrowserToolkit.browser_visit_page, lambda _, url: url) + async def browser_visit_page(self, url: str) -> Dict[str, Any]: + r"""Navigates to a URL. + + This method creates a new tab for the URL instead of navigating + in the current tab, allowing better multi-tab management. + + Args: + url (str): The web address to load in the browser. + + Returns: + Dict[str, Any]: A dictionary containing the result, snapshot, and + tab information. + """ + if not url or not isinstance(url, str): + return { + "result": "Error: 'url' must be a non-empty string", + "snapshot": "", + "tabs": [], + "current_tab": 0, + "total_tabs": 1, + } + + if "://" not in url: + url = f"https://{url}" + + await self._ensure_browser() + session = await self._get_session() + + nav_result = "" + + logger.info(f"Navigating to URL in current tab: {url}") + + if not (await session.get_page()).url.startswith("about:blank"): + await session.get_new_tab() + + nav_result = await session.visit(url) + + # Get snapshot + snapshot = "" + try: + snapshot = await session.get_snapshot(force_refresh=True, diff_only=False) + except Exception as e: + logger.warning(f"Failed to capture snapshot: {e}") + + # Get tab information + tab_info = await self._get_tab_info_for_output() + + return {"result": nav_result, "snapshot": snapshot, **tab_info} + + @listen_toolkit(BaseHybridBrowserToolkit.browser_back) + async def browser_back(self) -> Dict[str, Any]: + return await super().browser_back() + + @listen_toolkit(BaseHybridBrowserToolkit.browser_forward) + async def browser_forward(self) -> Dict[str, Any]: + return await super().browser_forward() + + @listen_toolkit(BaseHybridBrowserToolkit.browser_click) + async def browser_click(self, *, ref: str) -> Dict[str, Any]: + return await super().browser_click(ref=ref) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_type) + async def browser_type(self, *, ref: str, text: str) -> Dict[str, Any]: + return await super().browser_type(ref=ref, text=text) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_switch_tab) + async def browser_switch_tab(self, *, tab_id: str) -> Dict[str, Any]: + return await super().browser_switch_tab(tab_id=tab_id) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_select) + async def browser_select(self, *, ref: str, value: str) -> Dict[str, str]: + return await super().browser_select(ref=ref, value=value) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_scroll) + async def browser_scroll(self, *, direction: str, amount: int) -> Dict[str, str]: + return await super().browser_scroll(direction=direction, amount=amount) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_wait_user) + async def browser_wait_user(self, timeout_sec: float | None = None) -> Dict[str, str]: + return await super().browser_wait_user(timeout_sec) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_enter) + async def browser_enter(self) -> Dict[str, str]: + return await super().browser_enter() + + @listen_toolkit(BaseHybridBrowserToolkit.browser_solve_task) + async def browser_solve_task(self, task_prompt: str, start_url: str, max_steps: int = 15) -> str: + return await super().browser_solve_task(task_prompt, start_url, max_steps) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_get_page_snapshot) + async def browser_get_page_snapshot(self) -> str: + return await super().browser_get_page_snapshot() + + @listen_toolkit(BaseHybridBrowserToolkit.browser_get_som_screenshot) + async def browser_get_som_screenshot(self): + return await super().browser_get_som_screenshot() + + @listen_toolkit(BaseHybridBrowserToolkit.browser_get_page_links) + async def browser_get_page_links(self, *, ref: List[str]) -> Dict[str, Any]: + return await super().browser_get_page_links(ref=ref) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_close_tab) + async def browser_close_tab(self, *, tab_id: str) -> Dict[str, Any]: + return await super().browser_close_tab(tab_id=tab_id) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_get_tab_info) + async def browser_get_tab_info(self) -> Dict[str, Any]: + return await super().browser_get_tab_info() + + @classmethod + def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]: + browser = HybridBrowserPythonToolkit( + api_task_id, + headless=False, + browser_log_to_file=True, + stealth=True, + session_id=str(uuid.uuid4())[:8], + default_start_url="about:blank", + ) + + base_tools = [ + FunctionTool(browser.browser_click), + FunctionTool(browser.browser_type), + FunctionTool(browser.browser_back), + FunctionTool(browser.browser_forward), + FunctionTool(browser.browser_switch_tab), + FunctionTool(browser.browser_enter), + FunctionTool(browser.browser_visit_page), + FunctionTool(browser.browser_scroll), + FunctionTool(browser.browser_get_som_screenshot), + # FunctionTool(browser.select), + # FunctionTool(browser.wait_user), + ] + + if browser.web_agent_model is not None: + base_tools.append(FunctionTool(browser.browser_solve_task)) + + return base_tools + + @classmethod + def toolkit_name(cls) -> str: + return "Browser Toolkit" + + def clone_for_new_session(self, new_session_id: str | None = None) -> "HybridBrowserPythonToolkit": + if new_session_id is None: + new_session_id = str(uuid.uuid4())[:8] + + return HybridBrowserPythonToolkit( + self.api_task_id, + headless=self._headless, + user_data_dir=self._user_data_dir, + stealth=self._stealth, + web_agent_model=self._web_agent_model, + cache_dir=f"{self._cache_dir.rstrip('/')}_clone_{new_session_id}/", + enabled_tools=self.enabled_tools.copy(), + browser_log_to_file=self._browser_log_to_file, + session_id=new_session_id, + default_start_url=self._default_start_url, + default_timeout=self._default_timeout, + short_timeout=self._short_timeout, + navigation_timeout=self._navigation_timeout, + network_idle_timeout=self._network_idle_timeout, + screenshot_timeout=self._screenshot_timeout, + page_stability_timeout=self._page_stability_timeout, + dom_content_loaded_timeout=self._dom_content_loaded_timeout, + ) + + async def _get_session(self) -> BrowserSession: + return await super()._get_session() # type: ignore diff --git a/backend/app/utils/toolkit/hybrid_browser_toolkit.py b/backend/app/utils/toolkit/hybrid_browser_toolkit.py new file mode 100644 index 000000000..7ea680454 --- /dev/null +++ b/backend/app/utils/toolkit/hybrid_browser_toolkit.py @@ -0,0 +1,260 @@ +import os +import subprocess +import time +from typing import Any, Dict, List +from camel.models import BaseModelBackend +from camel.toolkits.hybrid_browser_toolkit.hybrid_browser_toolkit_ts import ( + HybridBrowserToolkit as BaseHybridBrowserToolkit, +) +from camel.toolkits.hybrid_browser_toolkit.ws_wrapper import WebSocketBrowserWrapper as BaseWebSocketBrowserWrapper +from loguru import logger +import websockets +from app.component.command import bun, uv +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class WebSocketBrowserWrapper(BaseWebSocketBrowserWrapper): + async def start(self): + # Check if node_modules exists (dependencies installed) + node_modules_path = os.path.join(self.ts_dir, "node_modules") + if not os.path.exists(node_modules_path): + logger.warning("Node modules not found. Running npm install...") + install_result = subprocess.run( + [uv(), "run", "npm", "install"], + cwd=self.ts_dir, + capture_output=True, + text=True, + ) + if install_result.returncode != 0: + logger.error(f"npm install failed: {install_result.stderr}") + raise RuntimeError( + f"Failed to install npm dependencies: {install_result.stderr}\n" # noqa:E501 + f"Please run 'npm install' in {self.ts_dir} manually." + ) + logger.info("npm dependencies installed successfully") + + # Ensure the TypeScript code is built + build_result = subprocess.run( + [uv(), "run", "npm", "run", "build"], + cwd=self.ts_dir, + capture_output=True, + text=True, + ) + if build_result.returncode != 0: + logger.error(f"TypeScript build failed: {build_result.stderr}") + raise RuntimeError(f"TypeScript build failed: {build_result.stderr}") + + # Start the WebSocket server + self.process = subprocess.Popen( + [uv(), "run", "node", "websocket-server.js"], # bun not support playwright, use uv nodejs-bin + cwd=self.ts_dir, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + + # Wait for server to output the port + server_ready = False + timeout = 10 # 10 seconds timeout + start_time = time.time() + + while not server_ready and time.time() - start_time < timeout: + if self.process.poll() is not None: + # Process died + stderr = self.process.stderr.read() # type: ignore + raise RuntimeError(f"WebSocket server failed to start: {stderr}") + + try: + line = self.process.stdout.readline() # type: ignore + logger.debug(f"WebSocket server output: {line}") + if line.startswith("SERVER_READY:"): + self.server_port = int(line.split(":")[1].strip()) + server_ready = True + logger.info(f"WebSocket server ready on port {self.server_port}") + except (ValueError, IndexError): + continue + + if not server_ready: + self.process.kill() + raise RuntimeError("WebSocket server failed to start within timeout") + + # Connect to the WebSocket server + try: + self.websocket = await websockets.connect( + f"ws://localhost:{self.server_port}", + ping_interval=30, + ping_timeout=10, + max_size=50 * 1024 * 1024, # 50MB limit to match server + ) + logger.info("Connected to WebSocket server") + except Exception as e: + self.process.kill() + raise RuntimeError(f"Failed to connect to WebSocket server: {e}") from e + + # Initialize the browser toolkit + logger.debug(f"send init {self.config}") + await self._send_command("init", self.config) + logger.debug("WebSocket server initialized successfully") + + +websocket_browser_wrapper = None +"""ensure only one instance of websocket_browser_wrapper""" + + +class HybridBrowserToolkit(BaseHybridBrowserToolkit, AbstractToolkit): + agent_name: str = Agents.search_agent + + def __init__( + self, + api_task_id: str, + *, + headless: bool = False, + user_data_dir: str | None = None, + stealth: bool = True, + web_agent_model: BaseModelBackend | None = None, + cache_dir: str = "tmp/", + enabled_tools: List[str] | None = None, + browser_log_to_file: bool = False, + session_id: str | None = None, + default_start_url: str = "https://google.com/", + default_timeout: int | None = None, + short_timeout: int | None = None, + navigation_timeout: int | None = None, + network_idle_timeout: int | None = None, + screenshot_timeout: int | None = None, + page_stability_timeout: int | None = None, + dom_content_loaded_timeout: int | None = None, + viewport_limit: bool = False, + connect_over_cdp: bool = False, + cdp_url: str | None = None, + ) -> None: + self.api_task_id = api_task_id + super().__init__( + headless=headless, + user_data_dir=user_data_dir, + stealth=stealth, + web_agent_model=web_agent_model, + cache_dir=cache_dir, + enabled_tools=enabled_tools, + browser_log_to_file=browser_log_to_file, + session_id=session_id, + default_start_url=default_start_url, + default_timeout=default_timeout, + short_timeout=short_timeout, + navigation_timeout=navigation_timeout, + network_idle_timeout=network_idle_timeout, + screenshot_timeout=screenshot_timeout, + page_stability_timeout=page_stability_timeout, + dom_content_loaded_timeout=dom_content_loaded_timeout, + viewport_limit=viewport_limit, + connect_over_cdp=connect_over_cdp, + cdp_url=cdp_url, + ) + + async def _ensure_ws_wrapper(self): + """Ensure WebSocket wrapper is initialized.""" + if self._ws_wrapper is None: + global websocket_browser_wrapper + if websocket_browser_wrapper is None: + websocket_browser_wrapper = WebSocketBrowserWrapper(self._ws_config) + self._ws_wrapper = websocket_browser_wrapper + await self._ws_wrapper.start() + + def clone_for_new_session(self, new_session_id: str | None = None) -> "HybridBrowserToolkit": + import uuid + + if new_session_id is None: + new_session_id = str(uuid.uuid4())[:8] + + return HybridBrowserToolkit( + self.api_task_id, + headless=self._headless, + user_data_dir=self._user_data_dir, + stealth=self._stealth, + web_agent_model=self._web_agent_model, + cache_dir=f"{self._cache_dir.rstrip('/')}/_clone_{new_session_id}/", + enabled_tools=self.enabled_tools.copy(), + browser_log_to_file=self._browser_log_to_file, + session_id=new_session_id, + default_start_url=self._default_start_url, + default_timeout=self._default_timeout, + short_timeout=self._short_timeout, + navigation_timeout=self._navigation_timeout, + network_idle_timeout=self._network_idle_timeout, + screenshot_timeout=self._screenshot_timeout, + page_stability_timeout=self._page_stability_timeout, + dom_content_loaded_timeout=self._dom_content_loaded_timeout, + viewport_limit=self._viewport_limit, + connect_over_cdp=self.config_loader.get_browser_config().connect_over_cdp, + cdp_url=self.config_loader.get_browser_config().cdp_url, + ) + + @classmethod + def toolkit_name(cls) -> str: + return "Browser Toolkit" + + @listen_toolkit(BaseHybridBrowserToolkit.browser_open) + async def browser_open(self) -> Dict[str, Any]: + return await super().browser_open() + + @listen_toolkit(BaseHybridBrowserToolkit.browser_close) + async def browser_close(self) -> str: + return await super().browser_close() + + @listen_toolkit(BaseHybridBrowserToolkit.browser_visit_page) + async def browser_visit_page(self, url: str) -> Dict[str, Any]: + return await super().browser_visit_page(url) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_back) + async def browser_back(self) -> Dict[str, Any]: + return await super().browser_back() + + @listen_toolkit(BaseHybridBrowserToolkit.browser_forward) + async def browser_forward(self) -> Dict[str, Any]: + return await super().browser_forward() + + @listen_toolkit(BaseHybridBrowserToolkit.browser_get_page_snapshot) + async def browser_get_page_snapshot(self) -> str: + return await super().browser_get_page_snapshot() + + @listen_toolkit(BaseHybridBrowserToolkit.browser_get_som_screenshot) + async def browser_get_som_screenshot(self, read_image: bool = False, instruction: str | None = None) -> str: + return await super().browser_get_som_screenshot(read_image, instruction) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_click) + async def browser_click(self, *, ref: str) -> Dict[str, Any]: + return await super().browser_click(ref=ref) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_type) + async def browser_type(self, *, ref: str, text: str) -> Dict[str, Any]: + return await super().browser_type(ref=ref, text=text) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_select) + async def browser_select(self, *, ref: str, value: str) -> Dict[str, Any]: + return await super().browser_select(ref=ref, value=value) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_scroll) + async def browser_scroll(self, *, direction: str, amount: int = 500) -> Dict[str, Any]: + return await super().browser_scroll(direction=direction, amount=amount) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_enter) + async def browser_enter(self) -> Dict[str, Any]: + return await super().browser_enter() + + @listen_toolkit(BaseHybridBrowserToolkit.browser_wait_user) + async def browser_wait_user(self, timeout_sec: float | None = None) -> Dict[str, Any]: + return await super().browser_wait_user(timeout_sec) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_switch_tab) + async def browser_switch_tab(self, *, tab_id: str) -> Dict[str, Any]: + return await super().browser_switch_tab(tab_id=tab_id) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_close_tab) + async def browser_close_tab(self, *, tab_id: str) -> Dict[str, Any]: + return await super().browser_close_tab(tab_id=tab_id) + + @listen_toolkit(BaseHybridBrowserToolkit.browser_get_tab_info) + async def browser_get_tab_info(self) -> Dict[str, Any]: + return await super().browser_get_tab_info() diff --git a/backend/app/utils/toolkit/image_analysis_toolkit.py b/backend/app/utils/toolkit/image_analysis_toolkit.py new file mode 100644 index 000000000..b325d904e --- /dev/null +++ b/backend/app/utils/toolkit/image_analysis_toolkit.py @@ -0,0 +1,40 @@ +from camel.models import BaseModelBackend +from camel.toolkits import ImageAnalysisToolkit as BaseImageAnalysisToolkit + +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class ImageAnalysisToolkit(BaseImageAnalysisToolkit, AbstractToolkit): + agent_name: str = Agents.multi_modal_agent + + def __init__( + self, + api_task_id: str, + model: BaseModelBackend | None = None, + timeout: float | None = None, + ): + super().__init__(model, timeout) + self.api_task_id = api_task_id + + @listen_toolkit( + BaseImageAnalysisToolkit.image_to_text, + lambda _, + image_path, + sys_prompt: f"transcribe image from {image_path} and ask sys_prompt: {sys_prompt}", + ) + def image_to_text(self, image_path: str, sys_prompt: str | None = None) -> str: + return super().image_to_text(image_path, sys_prompt) + + @listen_toolkit( + BaseImageAnalysisToolkit.ask_question_about_image, + lambda _, + image_path, + question, + sys_prompt: f"transcribe image from {image_path} and ask question: {question} with sys_prompt: {sys_prompt}", + ) + def ask_question_about_image( + self, image_path: str, question: str, sys_prompt: str | None = None + ) -> str: + return super().ask_question_about_image(image_path, question, sys_prompt) diff --git a/backend/app/utils/toolkit/linkedin_toolkit.py b/backend/app/utils/toolkit/linkedin_toolkit.py new file mode 100644 index 000000000..9b30392c0 --- /dev/null +++ b/backend/app/utils/toolkit/linkedin_toolkit.py @@ -0,0 +1,42 @@ +from camel.toolkits import LinkedInToolkit as BaseLinkedInToolkit +from camel.toolkits.function_tool import FunctionTool +from app.component.environment import env +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class LinkedInToolkit(BaseLinkedInToolkit, AbstractToolkit): + agent_name: str = Agents.social_medium_agent + + def __init__(self, api_task_id: str, timeout: float | None = None): + super().__init__(timeout) + self.api_task_id = api_task_id + + @listen_toolkit( + BaseLinkedInToolkit.create_post, + lambda _, text: f"create a LinkedIn post with text: {text}", + ) + def create_post(self, text: str) -> dict: + return super().create_post(text) + + @listen_toolkit( + BaseLinkedInToolkit.delete_post, + lambda _, post_id: f"delete LinkedIn post with id: {post_id}", + ) + def delete_post(self, post_id: str) -> str: + return super().delete_post(post_id) + + @listen_toolkit( + BaseLinkedInToolkit.get_profile, + lambda _, include_id: f"get LinkedIn profile with include_id: {include_id}", + ) + def get_profile(self, include_id: bool = False) -> dict: + return super().get_profile(include_id) + + @classmethod + def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]: + if env("LINKEDIN_ACCESS_TOKEN"): + return LinkedInToolkit(api_task_id).get_tools() + else: + return [] diff --git a/backend/app/utils/toolkit/markitdown_toolkit.py b/backend/app/utils/toolkit/markitdown_toolkit.py new file mode 100644 index 000000000..1c1cc3528 --- /dev/null +++ b/backend/app/utils/toolkit/markitdown_toolkit.py @@ -0,0 +1,18 @@ +from typing import Dict, List +from camel.toolkits import MarkItDownToolkit as BaseMarkItDownToolkit + +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class MarkItDownToolkit(BaseMarkItDownToolkit, AbstractToolkit): + agent_name: str = Agents.document_agent + + def __init__(self, api_task_id: str, timeout: float | None = None): + self.api_task_id = api_task_id + super().__init__(timeout) + + @listen_toolkit(BaseMarkItDownToolkit.read_files) + def read_files(self, file_paths: List[str]) -> Dict[str, str]: + return super().read_files(file_paths) diff --git a/backend/app/utils/toolkit/mcp_search_toolkit.py b/backend/app/utils/toolkit/mcp_search_toolkit.py new file mode 100644 index 000000000..2d5719df3 --- /dev/null +++ b/backend/app/utils/toolkit/mcp_search_toolkit.py @@ -0,0 +1,59 @@ +from typing import Any, List +from camel.toolkits import BaseToolkit, FunctionTool +import httpx +from app.service.task import Action, ActionSearchMcpData, Agents, get_task_lock +from app.component.environment import env_not_empty +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class McpSearchToolkit(BaseToolkit, AbstractToolkit): + agent_name: str = Agents.mcp_agent + + def __init__(self, api_task_id: str, timeout: float | None = None): + super().__init__(timeout) + self.api_task_id = api_task_id + + @listen_toolkit( + inputs=lambda _, + keyword, + size, + page: f"keyword: {keyword}, size: {size}, page: {page}", + return_msg=lambda res: f"Search {len(res)} results: ", + ) + async def search( + self, + keyword: str, + size: int = 15, + page: int = 0, + ) -> dict[str, Any]: + """Search mcp server for keyword. + + Args: + keyword (str): mcp server name keyword. + size (int): count per page. + page (int): page. + + Returns: + dict[str, Any]: _description_ + """ + async with httpx.AsyncClient() as client: + response = await client.get( + env_not_empty("MCP_URL"), + params={ + "keyword": keyword, + "size": size, + "page": page, + }, + ) + if response.status_code != 200: + raise Exception(f"MCP server search failed: {response.text}") + data = response.json() + task_lock = get_task_lock(self.api_task_id) + await task_lock.put_queue( + ActionSearchMcpData(action=Action.search_mcp, data=data["items"]) + ) + return data + + def get_tools(self) -> List[FunctionTool]: + return [FunctionTool(self.search)] diff --git a/backend/app/utils/toolkit/note_taking_toolkit.py b/backend/app/utils/toolkit/note_taking_toolkit.py new file mode 100644 index 000000000..f1225e3a7 --- /dev/null +++ b/backend/app/utils/toolkit/note_taking_toolkit.py @@ -0,0 +1,41 @@ +import os +from camel.toolkits import NoteTakingToolkit as BaseNoteTakingToolkit + +from app.component.environment import env +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class NoteTakingToolkit(BaseNoteTakingToolkit, AbstractToolkit): + agent_name: str = Agents.document_agent + + def __init__( + self, + api_task_id: str, + agent_name: str | None = None, + working_directory: str | None = None, + timeout: float | None = None, + ) -> None: + self.api_task_id = api_task_id + if agent_name is not None: + self.agent_name = agent_name + if working_directory is None: + working_directory = env("file_save_path", os.path.expanduser("~/.eigent/notes")) + "/note.md" + super().__init__(working_directory=working_directory, timeout=timeout) + + @listen_toolkit(BaseNoteTakingToolkit.append_note) + def append_note(self, note_name: str, content: str) -> str: + return super().append_note(note_name=note_name, content=content) + + @listen_toolkit(BaseNoteTakingToolkit.read_note) + def read_note(self) -> str: + return super().read_note() + + @listen_toolkit(BaseNoteTakingToolkit.create_note) + def create_note(self, note_name: str, content: str = "") -> str: + return super().create_note(note_name, content) + + @listen_toolkit(BaseNoteTakingToolkit.list_note) + def list_note(self) -> str: + return super().list_note() diff --git a/backend/app/utils/toolkit/notion_mcp_toolkit.py b/backend/app/utils/toolkit/notion_mcp_toolkit.py new file mode 100644 index 000000000..5f7aa5b25 --- /dev/null +++ b/backend/app/utils/toolkit/notion_mcp_toolkit.py @@ -0,0 +1,46 @@ +import os +from camel.toolkits import FunctionTool, NotionMCPToolkit as BaseNotionMCPToolkit +from app.component.command import bun +from app.component.environment import env +from app.service.task import Agents +from app.utils.toolkit.abstract_toolkit import AbstractToolkit +from camel.toolkits.mcp_toolkit import MCPToolkit + + +class NotionMCPToolkit(BaseNotionMCPToolkit, AbstractToolkit): + agent_name: str = Agents.social_medium_agent + + def __init__( + self, + api_task_id: str, + timeout: float | None = None, + ): + self.api_task_id = api_task_id + if timeout is None: + timeout = 120.0 + super().__init__(timeout) + self._mcp_toolkit = MCPToolkit( + config_dict={ + "mcpServers": { + "notionMCP": { + "command": bun(), + "args": ["x", "-y", "mcp-remote", "https://mcp.notion.com/mcp"], + "env": { + "MCP_REMOTE_CONFIG_DIR": env("MCP_REMOTE_CONFIG_DIR", os.path.expanduser("~/.mcp-auth")), + }, + } + } + }, + timeout=timeout, + ) + + @classmethod + async def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]: + tools = [] + if env("MCP_REMOTE_CONFIG_DIR"): + toolkit = cls(api_task_id) + await toolkit.connect() + for item in toolkit.get_tools(): + setattr(item, "_toolkit_name", cls.__name__) + tools.append(item) + return tools diff --git a/backend/app/utils/toolkit/notion_toolkit.py b/backend/app/utils/toolkit/notion_toolkit.py new file mode 100644 index 000000000..25e6ac9aa --- /dev/null +++ b/backend/app/utils/toolkit/notion_toolkit.py @@ -0,0 +1,50 @@ +from typing import List +from camel.toolkits import NotionToolkit as BaseNotionToolkit +from camel.toolkits.function_tool import FunctionTool +from app.component.environment import env +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class NotionToolkit(BaseNotionToolkit, AbstractToolkit): + agent_name: str = Agents.document_agent + + def __init__( + self, + api_task_id: str, + notion_token: str | None = None, + timeout: float | None = None, + ) -> None: + super().__init__(notion_token, timeout) + self.api_task_id = api_task_id + + @listen_toolkit( + BaseNotionToolkit.list_all_pages, + lambda _: "list all pages in Notion workspace", + lambda result: f"{len(result)} pages found", + ) + def list_all_pages(self) -> List[dict]: + return super().list_all_pages() + + @listen_toolkit( + BaseNotionToolkit.list_all_users, + lambda _: "list all users in Notion workspace", + lambda result: f"{len(result)} users found", + ) + def list_all_users(self) -> List[dict]: + return super().list_all_users() + + @listen_toolkit( + BaseNotionToolkit.get_notion_block_text_content, + lambda _, page_id: f"get text content of page with id: {page_id}", + ) + def get_notion_block_text_content(self, block_id: str) -> str: + return super().get_notion_block_text_content(block_id) + + @classmethod + def get_can_use_tools(cls, api_task_id: str) -> List[FunctionTool]: + if env("NOTION_TOKEN"): + return NotionToolkit(api_task_id).get_tools() + else: + return [] diff --git a/backend/app/utils/toolkit/openai_image_toolkit.py b/backend/app/utils/toolkit/openai_image_toolkit.py new file mode 100644 index 000000000..67347f88c --- /dev/null +++ b/backend/app/utils/toolkit/openai_image_toolkit.py @@ -0,0 +1,50 @@ +import os +from camel.toolkits import OpenAIImageToolkit as BaseOpenAIImageToolkit + +from app.component.environment import env +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit +from typing import Literal + + +class OpenAIImageToolkit(BaseOpenAIImageToolkit, AbstractToolkit): + agent_name: str = Agents.multi_modal_agent + + def __init__( + self, + api_task_id: str, + model: None | Literal["gpt-image-1"] | Literal["dall-e-3"] | Literal["dall-e-2"] = "gpt-image-1", + timeout: float | None = None, + api_key: str | None = None, + url: str | None = None, + size: None + | Literal["256x256"] + | Literal["512x512"] + | Literal["1024x1024"] + | Literal["1536x1024"] + | Literal["1024x1536"] + | Literal["1792x1024"] + | Literal["1024x1792"] + | Literal["auto"] = "1024x1024", + quality: None + | Literal["auto"] + | Literal["low"] + | Literal["medium"] + | Literal["high"] + | Literal["standard"] + | Literal["hd"] = "standard", + response_format: None | Literal["url"] | Literal["b64_json"] = "b64_json", + n: int | None = 1, + background: None | Literal["transparent"] | Literal["opaque"] | Literal["auto"] = "auto", + style: None | Literal["vivid"] | Literal["natural"] = None, + working_directory: str | None = None, + ): + self.api_task_id = api_task_id + super().__init__( + model, timeout, api_key, url, size, quality, response_format, n, background, style, working_directory + ) + + @listen_toolkit(BaseOpenAIImageToolkit.generate_image) + def generate_image(self, prompt: str, image_name: str = "image") -> str: + return super().generate_image(prompt, image_name) diff --git a/backend/app/utils/toolkit/pptx_toolkit.py b/backend/app/utils/toolkit/pptx_toolkit.py new file mode 100644 index 000000000..6a06d776e --- /dev/null +++ b/backend/app/utils/toolkit/pptx_toolkit.py @@ -0,0 +1,44 @@ +import asyncio +import os +from camel.toolkits import PPTXToolkit as BasePPTXToolkit + +from app.component.environment import env +from app.service.task import ActionWriteFileData, Agents, get_task_lock +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit +from app.service.task import process_task + + +class PPTXToolkit(BasePPTXToolkit, AbstractToolkit): + agent_name: str = Agents.document_agent + + def __init__( + self, + api_task_id: str, + working_directory: str | None = None, + timeout: float | None = None, + ) -> None: + self.api_task_id = api_task_id + if working_directory is None: + working_directory = env("file_save_path", os.path.expanduser("~/Downloads")) + super().__init__(working_directory, timeout) + + @listen_toolkit( + BasePPTXToolkit.create_presentation, + lambda _, + content, + filename, + template=None: f"create presentation with content: {content}, filename: {filename}, template: {template}", + ) + def create_presentation(self, content: str, filename: str, template: str | None = None) -> str: + if not filename.lower().endswith(".pptx"): + filename += ".pptx" + + file_path = self._resolve_filepath(filename) + res = super().create_presentation(content, filename, template) + if "PowerPoint presentation successfully created" in res: + task_lock = get_task_lock(self.api_task_id) + asyncio.create_task( + task_lock.put_queue(ActionWriteFileData(process_task_id=process_task.get(), data=str(file_path))) + ) + return res diff --git a/backend/app/utils/toolkit/pyautogui_toolkit.py b/backend/app/utils/toolkit/pyautogui_toolkit.py new file mode 100644 index 000000000..e6d26a72e --- /dev/null +++ b/backend/app/utils/toolkit/pyautogui_toolkit.py @@ -0,0 +1,89 @@ +import os +from typing import List, Literal +from camel.toolkits import PyAutoGUIToolkit as BasePyAutoGUIToolkit + +from app.component.environment import env +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class PyAutoGUIToolkit(BasePyAutoGUIToolkit, AbstractToolkit): + agent_name: str = Agents.search_agent + + def __init__( + self, + api_task_id: str, + timeout: float | None = None, + screenshots_dir: str | None = None, + ): + if screenshots_dir is None: + screenshots_dir = env("file_save_path", os.path.expanduser("~/Downloads")) + super().__init__(timeout, screenshots_dir) + self.api_task_id = api_task_id + + @listen_toolkit(BasePyAutoGUIToolkit.mouse_move, lambda _, x, y: f"mouse move to {x}, {y}") + def mouse_move(self, x: int, y: int) -> str: + return super().mouse_move(x, y) + + @listen_toolkit( + BasePyAutoGUIToolkit.mouse_click, + lambda _, button="left", clicks=1, x=None, y=None: f"mouse click {button} {clicks} times at {x}, {y}", + ) + def mouse_click( + self, + button: Literal["left", "middle", "right"] = "left", + clicks: int = 1, + x: int | None = None, + y: int | None = None, + ) -> str: + return super().mouse_click(button, clicks, x, y) + + @listen_toolkit( + BasePyAutoGUIToolkit.keyboard_type, + lambda _, text, interval=0: f"keyboard type {text}, interval {interval}", + ) + def keyboard_type(self, text: str, interval: float = 0) -> str: + return super().keyboard_type(text, interval) + + @listen_toolkit(BasePyAutoGUIToolkit.take_screenshot) + def take_screenshot(self) -> str: + return super().take_screenshot() + + @listen_toolkit(BasePyAutoGUIToolkit.get_mouse_position) + def get_mouse_position(self) -> str: + return super().get_mouse_position() + + @listen_toolkit(BasePyAutoGUIToolkit.press_key, lambda _, key: f"press key {key}") + def press_key(self, key: str | list[str]) -> str: + return super().press_key(key) + + @listen_toolkit(BasePyAutoGUIToolkit.hotkey, lambda _, keys: f"hotkey {keys}") + def hotkey(self, keys: List[str]) -> str: + return super().hotkey(keys) + + @listen_toolkit( + BasePyAutoGUIToolkit.mouse_drag, + lambda _, + start_x, + start_y, + end_x, + end_y, + button="left": f"mouse drag from {start_x}, {start_y} to {end_x}, {end_y} with {button} button", + ) + def mouse_drag( + self, + start_x: int, + start_y: int, + end_x: int, + end_y: int, + button: Literal["left", "middle", "right"] = "left", + ) -> str: + return super().mouse_drag(start_x, start_y, end_x, end_y, button) + + @listen_toolkit( + BasePyAutoGUIToolkit.scroll, + lambda _, scroll_amount, x=None, y=None: f"scroll {scroll_amount} at {x}, {y}", + ) + def scroll(self, scroll_amount: int, x: int | None = None, y: int | None = None) -> str: + return super().scroll(scroll_amount, x, y) diff --git a/backend/app/utils/toolkit/reddit_toolkit.py b/backend/app/utils/toolkit/reddit_toolkit.py new file mode 100644 index 000000000..fbc471e92 --- /dev/null +++ b/backend/app/utils/toolkit/reddit_toolkit.py @@ -0,0 +1,69 @@ +from typing import Any, Dict, List +from camel.toolkits import RedditToolkit as BaseRedditToolkit +from camel.toolkits.function_tool import FunctionTool +from app.component.environment import env +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class RedditToolkit(BaseRedditToolkit, AbstractToolkit): + agent_name: str = Agents.social_medium_agent + + def __init__( + self, + api_task_id: str, + retries: int = 3, + delay: float = 0, + timeout: float | None = None, + ): + super().__init__(retries, delay, timeout) + self.api_task_id = api_task_id + + @listen_toolkit( + BaseRedditToolkit.collect_top_posts, + lambda _, + subreddit_name, + post_limit=5, + comment_limit=5: f"collect top posts from subreddit: {subreddit_name} with post limit: {post_limit} and comment limit: {comment_limit}", + lambda result: f"top posts collected: {result}", + ) + def collect_top_posts( + self, subreddit_name: str, post_limit: int = 5, comment_limit: int = 5 + ) -> List[Dict[str, Any]] | str: + return super().collect_top_posts(subreddit_name, post_limit, comment_limit) + + @listen_toolkit( + BaseRedditToolkit.perform_sentiment_analysis, + lambda _, data: f"perform sentiment analysis on data number: {len(data)}", + lambda result: f"perform analysis result: {result}", + ) + def perform_sentiment_analysis(self, data: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + return super().perform_sentiment_analysis(data) + + @listen_toolkit( + BaseRedditToolkit.track_keyword_discussions, + lambda _, + subreddits, + keywords, + post_limit=10, + comment_limit=10, + sentiment_analysis=False: f"track keyword discussions for subreddits: {subreddits}, keywords: {keywords}", + lambda result: f"track keyword discussions result: {result}", + ) + def track_keyword_discussions( + self, + subreddits: List[str], + keywords: List[str], + post_limit: int = 10, + comment_limit: int = 10, + sentiment_analysis: bool = False, + ) -> List[Dict[str, Any]] | str: + return super().track_keyword_discussions(subreddits, keywords, post_limit, comment_limit, sentiment_analysis) + + @classmethod + def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]: + if env("REDDIT_CLIENT_ID") and env("REDDIT_CLIENT_SECRET") and env("REDDIT_USER_AGENT"): + return RedditToolkit(api_task_id).get_tools() + else: + return [] diff --git a/backend/app/utils/toolkit/screenshot_toolkit.py b/backend/app/utils/toolkit/screenshot_toolkit.py new file mode 100644 index 000000000..1e51dabbd --- /dev/null +++ b/backend/app/utils/toolkit/screenshot_toolkit.py @@ -0,0 +1,27 @@ +import os +from camel.toolkits import ScreenshotToolkit as BaseScreenshotToolkit + +from app.component.environment import env +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class ScreenshotToolkit(BaseScreenshotToolkit, AbstractToolkit): + agent_name: str = Agents.developer_agent + + def __init__(self, api_task_id, working_directory: str | None = None, timeout: float | None = None): + self.api_task_id = api_task_id + if working_directory is None: + working_directory = env("file_save_path", os.path.expanduser("~/Downloads")) + super().__init__(working_directory, timeout) + + @listen_toolkit(BaseScreenshotToolkit.take_screenshot_and_read_image) + def take_screenshot_and_read_image( + self, filename: str, save_to_file: bool = True, read_image: bool = True, instruction: str | None = None + ) -> str: + return super().take_screenshot_and_read_image(filename, save_to_file, read_image, instruction) + + @listen_toolkit(BaseScreenshotToolkit.read_image) + def read_image(self, image_path: str, instruction: str = "") -> str: + return super().read_image(image_path, instruction) diff --git a/backend/app/utils/toolkit/search_toolkit.py b/backend/app/utils/toolkit/search_toolkit.py new file mode 100644 index 000000000..bf65ffcd5 --- /dev/null +++ b/backend/app/utils/toolkit/search_toolkit.py @@ -0,0 +1,301 @@ +from typing import Any, Dict, List, Literal +from camel.toolkits import SearchToolkit as BaseSearchToolkit +from camel.toolkits.function_tool import FunctionTool +import httpx +from loguru import logger +from app.component.environment import env, env_not_empty +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class SearchToolkit(BaseSearchToolkit, AbstractToolkit): + agent_name: str = Agents.search_agent + + def __init__( + self, + api_task_id: str, + agent_name: str | None = None, + timeout: float | None = None, + number_of_result_pages: int = 10, + exclude_domains: List[str] | None = None, + ): + self.api_task_id = api_task_id + if agent_name is not None: + self.agent_name = agent_name + super().__init__( + timeout=timeout, number_of_result_pages=number_of_result_pages, exclude_domains=exclude_domains + ) + + # @listen_toolkit(BaseSearchToolkit.search_wiki) + # def search_wiki(self, entity: str) -> str: + # return super().search_wiki(entity) + + # @listen_toolkit( + # BaseSearchToolkit.search_linkup, + # lambda _, + # query, + # depth="standard", + # output_type="searchResults", + # structured_output_schema=None: f"Search linkup with query '{query}', depth '{depth}', output type '{output_type}', structured output schema '{structured_output_schema}'", + # lambda result: f"Search linkup returned {len(result)} results", + # ) + # def search_linkup( + # self, + # query: str, + # depth: Literal["standard", "deep"] = "standard", + # output_type: Literal["searchResults", "sourcedAnswer", "structured"] = "searchResults", + # structured_output_schema: str | None = None, + # ) -> dict[str, Any]: + # return super().search_linkup(query, depth, output_type, structured_output_schema) + + @listen_toolkit( + BaseSearchToolkit.search_google, + lambda _, query, search_type="web": f"with query '{query}' and {search_type} result pages", + ) + def search_google(self, query: str, search_type: str = "web") -> list[dict[str, Any]]: + if env("GOOGLE_API_KEY") and env("SEARCH_ENGINE_ID"): + return super().search_google(query, search_type) + else: + return self.cloud_search_google(query, search_type) + + def cloud_search_google(self, query: str, search_type): + url = env_not_empty("SERVER_URL") + res = httpx.get( + url + "/proxy/google", + params={"query": query, "search_type": search_type}, + headers={"api-key": env_not_empty("cloud_api_key")}, + ) + return res.json() + + # @listen_toolkit( + # BaseSearchToolkit.search_duckduckgo, + # lambda _, + # query, + # source="text", + # max_results=5: f"Search DuckDuckGo with query '{query}', source '{source}', and max results {max_results}", + # lambda result: f"Search DuckDuckGo returned {len(result)} results", + # ) + # def search_duckduckgo(self, query: str, source: str = "text", max_results: int = 5) -> list[dict[str, Any]]: + # return super().search_duckduckgo(query, source, max_results) + + # @listen_toolkit( + # BaseSearchToolkit.tavily_search, + # lambda _, query, num_results=5, **kwargs: f"Search Tavily with query '{query}' and {num_results} results", + # lambda result: f"Search Tavily returned {len(result)} results", + # ) + # def tavily_search(self, query: str, num_results: int = 5, **kwargs) -> list[dict[str, Any]]: + # return super().tavily_search(query, num_results, **kwargs) + + # @listen_toolkit( + # BaseSearchToolkit.search_brave, + # lambda _, query, *args, **kwargs: f"Search Brave with query '{query}'", + # lambda result: f"Search Brave returned {len(result)} results", + # ) + # def search_brave( + # self, + # q: str, + # country: str = "US", + # search_lang: str = "en", + # ui_lang: str = "en-US", + # count: int = 20, + # offset: int = 0, + # safesearch: str = "moderate", + # freshness: str | None = None, + # text_decorations: bool = True, + # spellcheck: bool = True, + # result_filter: str | None = None, + # goggles_id: str | None = None, + # units: str | None = None, + # extra_snippets: bool | None = None, + # summary: bool | None = None, + # ) -> dict[str, Any]: + # return super().search_brave( + # q, + # country, + # search_lang, + # ui_lang, + # count, + # offset, + # safesearch, + # freshness, + # text_decorations, + # spellcheck, + # result_filter, + # goggles_id, + # units, + # extra_snippets, + # summary, + # ) + + # @listen_toolkit( + # BaseSearchToolkit.search_bocha, + # lambda _, + # query, + # freshness="noLimit", + # summary=False, + # count=10, + # page=1: f"Search Bocha with query '{query}', freshness '{freshness}', summary '{summary}', count {count}, and page {page}", + # lambda result: f"Search Bocha returned {len(result)} results", + # ) + # def search_bocha( + # self, + # query: str, + # freshness: str = "noLimit", + # summary: bool = False, + # count: int = 10, + # page: int = 1, + # ) -> dict[str, Any]: + # return super().search_bocha(query, freshness, summary, count, page) + + # @listen_toolkit( + # BaseSearchToolkit.search_baidu, + # lambda _, query, max_results=5: f"Search Baidu with query '{query}' and max results {max_results}", + # lambda result: f"Search Baidu returned {len(result)} results", + # ) + # def search_baidu(self, query: str, max_results: int = 5) -> dict[str, Any]: + # return super().search_baidu(query, max_results) + + # @listen_toolkit( + # BaseSearchToolkit.search_bing, + # lambda _, query: f"with query '{query}'", + # lambda result: f"Search Bing returned {len(result)} results", + # ) + # def search_bing(self, query: str) -> dict[str, Any]: + # return super().search_bing(query) + + @listen_toolkit(BaseSearchToolkit.search_exa, lambda _, query, *args, **kwargs: f"{query}, {args}, {kwargs}") + def search_exa( + self, + query: str, + search_type: Literal["auto", "neural", "keyword"] = "auto", + category: None + | Literal[ + "company", + "research paper", + "news", + "pdf", + "github", + "tweet", + "personal site", + "linkedin profile", + "financial report", + ] = None, + include_text: List[str] | None = None, + exclude_text: List[str] | None = None, + use_autoprompt: bool = True, + text: bool = False, + ) -> Dict[str, Any]: + if env("EXA_API_KEY"): + res = super().search_exa(query, search_type, category, include_text, exclude_text, use_autoprompt, text) + return res + else: + return self.cloud_search_exa(query, search_type, category, include_text, exclude_text, use_autoprompt, text) + + def cloud_search_exa( + self, + query: str, + search_type: Literal["auto", "neural", "keyword"] = "auto", + category: None + | Literal[ + "company", + "research paper", + "news", + "pdf", + "github", + "tweet", + "personal site", + "linkedin profile", + "financial report", + ] = None, + include_text: List[str] | None = None, + exclude_text: List[str] | None = None, + use_autoprompt: bool = True, + text: bool = False, + ): + url = env_not_empty("SERVER_URL") + logger.debug(f">>>>>>>>>>>>>>>>{url}<<<<") + res = httpx.post( + url + "/proxy/exa", + json={ + "query": query, + "search_type": search_type, + "category": category, + "include_text": include_text, + "exclude_text": exclude_text, + "use_autoprompt": use_autoprompt, + "text": text, + }, + headers={"api-key": env_not_empty("cloud_api_key")}, + ) + logger.debug(">>>>>>>>>>>>>>>>>") + logger.debug(res) + return res.json() + + # @listen_toolkit( + # BaseSearchToolkit.search_alibaba_tongxiao, + # lambda _, *args, **kwargs: f"Search Alibaba Tongxiao with args {args} and kwargs {kwargs}", + # lambda result: f"Search Alibaba Tongxiao returned {len(result)} results", + # ) + # def search_alibaba_tongxiao( + # self, + # query: str, + # time_range: Literal["OneDay", "OneWeek", "OneMonth", "OneYear", "NoLimit"] = "NoLimit", + # industry: Literal[ + # "finance", + # "law", + # "medical", + # "internet", + # "tax", + # "news_province", + # "news_center", + # ] + # | None = None, + # page: int = 1, + # return_main_text: bool = False, + # return_markdown_text: bool = True, + # enable_rerank: bool = True, + # ) -> Dict[str, Any]: + # return super().search_alibaba_tongxiao( + # query, + # time_range, + # industry, + # page, + # return_main_text, + # return_markdown_text, + # enable_rerank, + # ) + + @classmethod + def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]: + search_toolkit = SearchToolkit(api_task_id) + tools = [ + # FunctionTool(search_toolkit.search_wiki), + # FunctionTool(search_toolkit.search_duckduckgo), + # FunctionTool(search_toolkit.search_baidu), + # FunctionTool(search_toolkit.search_bing), + ] + # if env("LINKUP_API_KEY"): + # tools.append(FunctionTool(search_toolkit.search_linkup)) + + # if env("BRAVE_API_KEY"): + # tools.append(FunctionTool(search_toolkit.search_brave)) + + if (env("GOOGLE_API_KEY") and env("SEARCH_ENGINE_ID")) or env("cloud_api_key"): + tools.append(FunctionTool(search_toolkit.search_google)) + + # if env("TAVILY_API_KEY"): + # tools.append(FunctionTool(search_toolkit.tavily_search)) + + # if env("BOCHA_API_KEY"): + # tools.append(FunctionTool(search_toolkit.search_bocha)) + + if env("EXA_API_KEY") or env("cloud_api_key"): + tools.append(FunctionTool(search_toolkit.search_exa)) + + # if env("TONGXIAO_API_KEY"): + # tools.append(FunctionTool(search_toolkit.search_alibaba_tongxiao)) + return tools + + def get_tools(self) -> List[FunctionTool]: + return [FunctionTool(self.search_exa)] diff --git a/backend/app/utils/toolkit/slack_toolkit.py b/backend/app/utils/toolkit/slack_toolkit.py new file mode 100644 index 000000000..89987b1ff --- /dev/null +++ b/backend/app/utils/toolkit/slack_toolkit.py @@ -0,0 +1,77 @@ +from camel.toolkits import SlackToolkit as BaseSlackToolkit +from camel.toolkits.function_tool import FunctionTool +from loguru import logger +from app.component.environment import env +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class SlackToolkit(BaseSlackToolkit, AbstractToolkit): + agent_name: str = Agents.social_medium_agent + + def __init__(self, api_task_id: str, timeout: float | None = None): + super().__init__(timeout) + self.api_task_id = api_task_id + + @listen_toolkit( + BaseSlackToolkit.create_slack_channel, + lambda _, name, is_private=True: f"create a Slack channel with name: {name} and is_private: {is_private}", + ) + def create_slack_channel(self, name: str, is_private: bool | None = True) -> str: + return super().create_slack_channel(name, is_private) + + @listen_toolkit( + BaseSlackToolkit.join_slack_channel, + lambda _, channel_id: f"join Slack channel with id: {channel_id}", + ) + def join_slack_channel(self, channel_id: str) -> str: + return super().join_slack_channel(channel_id) + + @listen_toolkit( + BaseSlackToolkit.leave_slack_channel, + lambda _, channel_id: f"leave Slack channel with id: {channel_id}", + ) + def leave_slack_channel(self, channel_id: str) -> str: + return super().leave_slack_channel(channel_id) + + @listen_toolkit( + BaseSlackToolkit.get_slack_channel_information, + lambda _: "get Slack channel information", + ) + def get_slack_channel_information(self) -> str: + return super().get_slack_channel_information() + + @listen_toolkit( + BaseSlackToolkit.get_slack_channel_message, + lambda _, channel_id: f"get Slack channel message for channel id: {channel_id}", + ) + def get_slack_channel_message(self, channel_id: str) -> str: + return super().get_slack_channel_message(channel_id) + + @listen_toolkit( + BaseSlackToolkit.send_slack_message, + lambda _, + message, + channel_id, + user=None: f"send Slack message: {message} to channel id: {channel_id} for user: {user}", + ) + def send_slack_message(self, message: str, channel_id: str, user: str | None = None) -> str: + return super().send_slack_message(message, channel_id, user) + + @listen_toolkit( + BaseSlackToolkit.delete_slack_message, + lambda _, + time_stamp, + channel_id: f"delete Slack message with timestamp: {time_stamp} in channel id: {channel_id}", + ) + def delete_slack_message(self, time_stamp: str, channel_id: str) -> str: + return super().delete_slack_message(time_stamp, channel_id) + + @classmethod + def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]: + logger.debug(f"slack===={env('SLACK_BOT_TOKEN')}") + if env("SLACK_BOT_TOKEN") or env("SLACK_USER_TOKEN"): + return SlackToolkit(api_task_id).get_tools() + else: + return [] diff --git a/backend/app/utils/toolkit/terminal_toolkit.py b/backend/app/utils/toolkit/terminal_toolkit.py new file mode 100644 index 000000000..9b933621e --- /dev/null +++ b/backend/app/utils/toolkit/terminal_toolkit.py @@ -0,0 +1,99 @@ +import asyncio +import os +from pathlib import Path +from typing import Any, Dict +from camel.toolkits.terminal_toolkit import TerminalToolkit as BaseTerminalToolkit +from app.component.environment import env +from app.service.task import Action, ActionTerminalData, Agents, get_task_lock +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit +from app.service.task import process_task + + +class TerminalToolkit(BaseTerminalToolkit, AbstractToolkit): + agent_name: str = Agents.developer_agent + + def __init__( + self, + api_task_id: str, + agent_name: str | None = None, + timeout: float | None = None, + shell_sessions: Dict[str, Any] | None = None, + working_directory: str | None = None, + need_terminal: bool = True, + use_shell_mode: bool = True, + clone_current_env: bool = False, + safe_mode: bool = True, + ): + self.api_task_id = api_task_id + if agent_name is not None: + self.agent_name = agent_name + if working_directory is None: + working_directory = env("file_save_path", os.path.expanduser("~/.eigent/terminal/")) + super().__init__( + timeout=timeout, + shell_sessions=shell_sessions, + working_directory=working_directory, + need_terminal=False, # Override the code that creates GUI output logs, use queue for SSE output instead + use_shell_mode=use_shell_mode, + clone_current_env=clone_current_env, + safe_mode=safe_mode, + ) + + def _update_terminal_output(self, output: str): + task_lock = get_task_lock(self.api_task_id) + # This method will be called during init. At that time, the process_task_id parameter does not exist, so it is set to be empty default + process_task_id = process_task.get("") + task = asyncio.create_task( + task_lock.put_queue( + ActionTerminalData( + action=Action.terminal, + process_task_id=process_task_id, + data=output, + ) + ) + ) + if hasattr(task_lock, "add_background_task"): + task_lock.add_background_task(task) + + @listen_toolkit( + BaseTerminalToolkit.shell_exec, + lambda _, id, command: f"id: {id}, command: {command}", + ) + def shell_exec(self, id: str, command: str) -> str: + return super().shell_exec(id=id, command=command) + + @listen_toolkit( + BaseTerminalToolkit.shell_view, + lambda _, id: f"id: {id}", + ) + def shell_view(self, id: str) -> str: + return super().shell_view(id) + + @listen_toolkit( + BaseTerminalToolkit.shell_wait, + lambda _, id, seconds: f"id: {id}, seconds: {seconds}", + ) + def shell_wait(self, id: str, seconds: int | None = None) -> str: + return super().shell_wait(id=id, seconds=seconds) + + @listen_toolkit( + BaseTerminalToolkit.shell_write_to_process, + lambda _, id, input, press_enter: f"id: {id}, input: {input}, press_enter: {press_enter}", + ) + def shell_write_to_process(self, id: str, input: str, press_enter: bool) -> str: + return super().shell_write_to_process(id=id, input=input, press_enter=press_enter) + + @listen_toolkit( + BaseTerminalToolkit.shell_kill_process, + lambda _, id: f"id: {id}", + ) + def shell_kill_process(self, id: str) -> str: + return super().shell_kill_process(id=id) + + @listen_toolkit( + BaseTerminalToolkit.ask_user_for_help, + lambda _, id: f"id: {id}", + ) + def ask_user_for_help(self, id: str) -> str: + return super().ask_user_for_help(id=id) diff --git a/backend/app/utils/toolkit/thinking_toolkit.py b/backend/app/utils/toolkit/thinking_toolkit.py new file mode 100644 index 000000000..593e79bd2 --- /dev/null +++ b/backend/app/utils/toolkit/thinking_toolkit.py @@ -0,0 +1,40 @@ +from camel.toolkits import ThinkingToolkit as BaseThinkingToolkit + +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class ThinkingToolkit(BaseThinkingToolkit, AbstractToolkit): + + def __init__(self, api_task_id: str, agent_name: str, timeout: float | None = None): + super().__init__(timeout) + self.api_task_id = api_task_id + self.agent_name = agent_name + + @listen_toolkit(BaseThinkingToolkit.plan) + def plan(self, plan: str) -> str: + return super().plan(plan) + + @listen_toolkit(BaseThinkingToolkit.hypothesize) + def hypothesize(self, hypothesis: str) -> str: + return super().hypothesize(hypothesis) + + @listen_toolkit(BaseThinkingToolkit.think) + def think(self, thought: str) -> str: + return super().think(thought) + + @listen_toolkit(BaseThinkingToolkit.contemplate) + def contemplate(self, contemplation: str) -> str: + return super().contemplate(contemplation) + + @listen_toolkit(BaseThinkingToolkit.critique) + def critique(self, critique: str) -> str: + return super().critique(critique) + + @listen_toolkit(BaseThinkingToolkit.synthesize) + def synthesize(self, synthesis: str) -> str: + return super().synthesize(synthesis) + + @listen_toolkit(BaseThinkingToolkit.reflect) + def reflect(self, reflection: str) -> str: + return super().reflect(reflection) diff --git a/backend/app/utils/toolkit/twitter_toolkit.py b/backend/app/utils/toolkit/twitter_toolkit.py new file mode 100644 index 000000000..77c4401ea --- /dev/null +++ b/backend/app/utils/toolkit/twitter_toolkit.py @@ -0,0 +1,75 @@ +from typing import List +from camel.toolkits import FunctionTool, TwitterToolkit as BaseTwitterToolkit +from camel.toolkits.twitter_toolkit import ( + create_tweet, + delete_tweet, + get_my_user_profile, + get_user_by_username, +) + +from app.component.environment import env +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class TwitterToolkit(BaseTwitterToolkit, AbstractToolkit): + agent_name: str = Agents.social_medium_agent + + def __init__(self, api_task_id: str, timeout: float | None = None): + super().__init__(timeout) + self.api_task_id = api_task_id + + @listen_toolkit( + create_tweet, + lambda _, text, **kwargs: f"create tweet with text: {text} and options: {kwargs}", + ) + def create_tweet( + self, + text: str, + poll_options: list[str] | None = None, + poll_duration_minutes: int | None = None, + quote_tweet_id: int | str | None = None, + ) -> str: + return create_tweet(text, poll_options, poll_duration_minutes, quote_tweet_id) + + @listen_toolkit( + delete_tweet, + lambda _, tweet_id: f"delete tweet with id: {tweet_id}", + ) + def delete_tweet(self, tweet_id: str) -> str: + return delete_tweet(tweet_id) + + @listen_toolkit( + get_user_by_username, + lambda _: "get my user profile", + ) + def get_my_user_profile(self) -> str: + return get_my_user_profile() + + @listen_toolkit( + get_user_by_username, + lambda _, username: f"get user by username: {username}", + ) + def get_user_by_username(self, username: str) -> str: + return get_user_by_username(username) + + def get_tools(self) -> List[FunctionTool]: + return [ + FunctionTool(self.create_tweet), + FunctionTool(self.delete_tweet), + FunctionTool(self.get_my_user_profile), + FunctionTool(self.get_user_by_username), + ] + + @classmethod + def get_can_use_tools(cls, api_task_id: str) -> List[FunctionTool]: + if ( + env("TWITTER_CONSUMER_KEY") + and env("TWITTER_CONSUMER_SECRET") + and env("TWITTER_ACCESS_TOKEN") + and env("TWITTER_ACCESS_TOKEN_SECRET") + ): + return TwitterToolkit(api_task_id).get_tools() + else: + return [] diff --git a/backend/app/utils/toolkit/video_analysis_toolkit.py b/backend/app/utils/toolkit/video_analysis_toolkit.py new file mode 100644 index 000000000..be1018f4d --- /dev/null +++ b/backend/app/utils/toolkit/video_analysis_toolkit.py @@ -0,0 +1,45 @@ +import os +from camel.models import BaseModelBackend +from camel.toolkits import VideoAnalysisToolkit as BaseVideoAnalysisToolkit + +from app.component.environment import env +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class VideoAnalysisToolkit(BaseVideoAnalysisToolkit, AbstractToolkit): + agent_name: str = Agents.multi_modal_agent + + def __init__( + self, + api_task_id: str, + working_directory: str | None = None, + model: BaseModelBackend | None = None, + use_audio_transcription: bool = False, + use_ocr: bool = False, + frame_interval: float = 4, + output_language: str = "English", + cookies_path: str | None = None, + timeout: float | None = None, + ) -> None: + self.api_task_id = api_task_id + if working_directory is None: + working_directory = env("file_save_path", os.path.expanduser("~/Downloads")) + super().__init__( + working_directory, + model, + use_audio_transcription, + use_ocr, + frame_interval, + output_language, + cookies_path, + timeout, + ) + + @listen_toolkit( + BaseVideoAnalysisToolkit.ask_question_about_video, + lambda _, video_path, question: f"transcribe video from {video_path} and ask question: {question}", + ) + def ask_question_about_video(self, video_path: str, question: str) -> str: + return super().ask_question_about_video(video_path, question) diff --git a/backend/app/utils/toolkit/video_download_toolkit.py b/backend/app/utils/toolkit/video_download_toolkit.py new file mode 100644 index 000000000..f77fe1157 --- /dev/null +++ b/backend/app/utils/toolkit/video_download_toolkit.py @@ -0,0 +1,45 @@ +import os +from typing import List +from PIL.Image import Image +from camel.toolkits import VideoDownloaderToolkit as BaseVideoDownloaderToolkit + +from app.component.environment import env +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class VideoDownloaderToolkit(BaseVideoDownloaderToolkit, AbstractToolkit): + agent_name: str = Agents.multi_modal_agent + + def __init__( + self, + api_task_id: str, + working_directory: str | None = None, + cookies_path: str | None = None, + timeout: float | None = None, + ) -> None: + if working_directory is None: + working_directory = env("file_save_path", os.path.expanduser("~/Downloads")) + super().__init__(working_directory, cookies_path, timeout) + self.api_task_id = api_task_id + + @listen_toolkit(BaseVideoDownloaderToolkit.download_video) + def download_video(self, url: str) -> str: + return super().download_video(url) + + @listen_toolkit( + BaseVideoDownloaderToolkit.get_video_bytes, + lambda _, video_path: f"get video bytes from {video_path}", + lambda _: "get video bytes", + ) + def get_video_bytes(self, video_path: str) -> bytes: + return super().get_video_bytes(video_path) + + @listen_toolkit( + BaseVideoDownloaderToolkit.get_video_screenshots, + lambda _, video_path, amount: f"get video screenshots from {video_path}, amount: {amount}", + lambda results: f"get video screenshots {len(results)}", + ) + def get_video_screenshots(self, video_path: str, amount: int) -> List[Image]: + return super().get_video_screenshots(video_path, amount) diff --git a/backend/app/utils/toolkit/web_deploy_toolkit.py b/backend/app/utils/toolkit/web_deploy_toolkit.py new file mode 100644 index 000000000..eb40ae940 --- /dev/null +++ b/backend/app/utils/toolkit/web_deploy_toolkit.py @@ -0,0 +1,53 @@ +import uuid +from typing import Any, Dict +from camel.toolkits import WebDeployToolkit as BaseWebDeployToolkit + +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class WebDeployToolkit(BaseWebDeployToolkit, AbstractToolkit): + agent_name: str = Agents.developer_agent + + def __init__( + self, + api_task_id: str, + timeout: float | None = None, + add_branding_tag: bool = True, + logo_path: str = "../../../../public/favicon.png", + tag_text: str = "Created by Eigent", + tag_url: str = "https://main.eigent.ai/", + remote_server_ip: str | None = "space.eigent.ai", + remote_server_port: int = 8080, + ): + self.api_task_id = api_task_id + super().__init__(timeout, add_branding_tag, logo_path, tag_text, tag_url, remote_server_ip, remote_server_port) + + @listen_toolkit(BaseWebDeployToolkit.deploy_html_content) + def deploy_html_content( + self, + html_content: str | None = None, + html_file_path: str | None = None, + file_name: str = "index.html", + port: int = 8080, + domain: str | None = None, + subdirectory: str | None = None, + ) -> Dict[str, Any]: + subdirectory = str(uuid.uuid4()) + return super().deploy_html_content(html_content, html_file_path, file_name, port, domain, subdirectory) + + @listen_toolkit(BaseWebDeployToolkit.deploy_folder) + def deploy_folder( + self, folder_path: str, port: int = 8080, domain: str | None = None, subdirectory: str | None = None + ) -> Dict[str, Any]: + subdirectory = str(uuid.uuid4()) + return super().deploy_folder(folder_path, port, domain, subdirectory) + + @listen_toolkit(BaseWebDeployToolkit.stop_server) + def stop_server(self, port: int) -> Dict[str, Any]: + return super().stop_server(port) + + @listen_toolkit(BaseWebDeployToolkit.list_running_servers) + def list_running_servers(self) -> Dict[str, Any]: + return super().list_running_servers() diff --git a/backend/app/utils/toolkit/whatsapp_toolkit.py b/backend/app/utils/toolkit/whatsapp_toolkit.py new file mode 100644 index 000000000..83dcd941a --- /dev/null +++ b/backend/app/utils/toolkit/whatsapp_toolkit.py @@ -0,0 +1,46 @@ +from typing import Any, Dict, List +from camel.toolkits import WhatsAppToolkit as BaseWhatsAppToolkit +from camel.toolkits.function_tool import FunctionTool +from app.component.environment import env +from app.service.task import Agents +from app.utils.listen.toolkit_listen import listen_toolkit +from app.utils.toolkit.abstract_toolkit import AbstractToolkit + + +class WhatsAppToolkit(BaseWhatsAppToolkit, AbstractToolkit): + agent_name: str = Agents.social_medium_agent + + def __init__(self, api_task_id: str, timeout: float | None = None): + super().__init__(timeout) + self.api_task_id = api_task_id + + @listen_toolkit( + BaseWhatsAppToolkit.send_message, + lambda _, to, message: f"send message to {to}: {message}", + lambda result: f"message sent result: {result}", + ) + def send_message(self, to: str, message: str) -> Dict[str, Any] | str: + return super().send_message(to, message) + + @listen_toolkit( + BaseWhatsAppToolkit.get_message_templates, + lambda _: "get message templates", + lambda result: f"message templates: {result}", + ) + def get_message_templates(self) -> List[Dict[str, Any]] | str: + return super().get_message_templates() + + @listen_toolkit( + BaseWhatsAppToolkit.get_business_profile, + lambda _: "get business profile", + lambda result: f"business profile: {result}", + ) + def get_business_profile(self) -> Dict[str, Any] | str: + return super().get_business_profile() + + @classmethod + def get_can_use_tools(cls, api_task_id: str) -> list[FunctionTool]: + if env("WHATSAPP_ACCESS_TOKEN") and env("WHATSAPP_PHONE_NUMBER_ID"): + return WhatsAppToolkit(api_task_id).get_tools() + else: + return [] diff --git a/backend/app/utils/workforce.py b/backend/app/utils/workforce.py new file mode 100644 index 000000000..86c919f4c --- /dev/null +++ b/backend/app/utils/workforce.py @@ -0,0 +1,271 @@ +import asyncio +from typing import Generator, List +from camel.agents import ChatAgent +from camel.societies.workforce.workforce import ( + Workforce as BaseWorkforce, + WorkforceState, + DEFAULT_WORKER_POOL_SIZE, +) +from camel.societies.workforce.task_channel import TaskChannel +from camel.societies.workforce.base import BaseNode +from camel.societies.workforce.utils import TaskAssignResult +from loguru import logger +from camel.tasks.task import Task, TaskState, validate_task_content +from app.component import code +from app.exception.exception import UserException +from app.utils.agent import ListenChatAgent +from app.service.task import ( + Action, + ActionAssignTaskData, + ActionEndData, + ActionTaskStateData, + get_camel_task, + get_task_lock, +) +from app.utils.single_agent_worker import SingleAgentWorker + +# === Debug sink === Write detailed dependency debug logs to file (logs/workforce_debug.log) +# Create a new file every day, keep the logs for the last 7 days, and write asynchronously without blocking the main process +logger.add( + "logs/workforce_debug_{time:YYYY-MM-DD}.log", + rotation="00:00", + retention="7 days", + enqueue=True, + level="DEBUG", +) +# Independent sink: only collect the "[WF]" debug lines we insert to quickly view the dependency chain +logger.add( + "logs/wf_trace_{time:YYYY-MM-DD-HH}.log", + rotation="00:00", + retention="7 days", + enqueue=True, + level="DEBUG", + filter=lambda record: record["message"].startswith("[WF]"), +) + + +class Workforce(BaseWorkforce): + def __init__( + self, + api_task_id: str, + description: str, + children: List[BaseNode] | None = None, + coordinator_agent: ChatAgent | None = None, + task_agent: ChatAgent | None = None, + new_worker_agent: ChatAgent | None = None, + graceful_shutdown_timeout: float = 3, + share_memory: bool = False, + use_structured_output_handler: bool = True, + ) -> None: + self.api_task_id = api_task_id + super().__init__( + description=description, + children=children, + coordinator_agent=coordinator_agent, + task_agent=task_agent, + new_worker_agent=new_worker_agent, + graceful_shutdown_timeout=graceful_shutdown_timeout, + share_memory=share_memory, + use_structured_output_handler=use_structured_output_handler, + ) + + def eigent_make_sub_tasks(self, task: Task): + """split process_task method to eigent_make_sub_tasks and eigent_start method""" + + if not validate_task_content(task.content, task.id): + task.state = TaskState.FAILED + task.result = "Task failed: Invalid or empty content provided" + logger.warning( + f"Task {task.id} rejected: Invalid or empty content. Content preview: '{task.content[:50]}...'" + ) + raise UserException(code.error, task.result) + + self.reset() + self._task = task + self._state = WorkforceState.RUNNING + task.state = TaskState.OPEN + self._pending_tasks.append(task) + + # Decompose the task into subtasks first + subtasks_result = self._decompose_task(task) + + # Handle both streaming and non-streaming results + if isinstance(subtasks_result, Generator): + # This is a generator (streaming mode) + subtasks = [] + for new_tasks in subtasks_result: + subtasks.extend(new_tasks) + else: + # This is a regular list (non-streaming mode) + subtasks = subtasks_result + + return subtasks + + async def eigent_start(self, subtasks: list[Task]): + """start the workforce""" + logger.debug(f"start the workforce {subtasks=}") + self._pending_tasks.extendleft(reversed(subtasks)) + self.set_channel(TaskChannel()) + # Save initial snapshot + self.save_snapshot("Initial task decomposition") + + try: + await self.start() + except Exception as e: + logger.error(f"Error in workforce execution: {e}") + self._state = WorkforceState.STOPPED + raise + finally: + if self._state != WorkforceState.STOPPED: + self._state = WorkforceState.IDLE + + async def _find_assignee(self, tasks: List[Task]) -> TaskAssignResult: + # Task assignment phase: send "waiting for execution" notification to the frontend, and send "start execution" notification when the task actually begins execution + assigned = await super()._find_assignee(tasks) + + task_lock = get_task_lock(self.api_task_id) + for item in assigned.assignments: + # DEBUG ▶ Task has been assigned to which worker and its dependencies + logger.debug(f"[WF] ASSIGN {item.task_id} -> {item.assignee_id} deps={item.dependencies}") + # The main task itself does not need notification + if self._task and item.task_id == self._task.id: + continue + # Find task content + task_obj = get_camel_task(item.task_id, tasks) + content = task_obj.content if task_obj else "" + # Asynchronously send waiting notification + task = asyncio.create_task( + task_lock.put_queue( + ActionAssignTaskData( + action=Action.assign_task, + data={ + "assignee_id": item.assignee_id, + "task_id": item.task_id, + "content": content, + "state": "waiting", # Mark as waiting state + }, + ) + ) + ) + # Track the task for cleanup + task_lock.add_background_task(task) + return assigned + + async def _post_task(self, task: Task, assignee_id: str) -> None: + # DEBUG ▶ Dependencies are met, the task really starts to execute + logger.debug(f"[WF] POST {task.id} -> {assignee_id}") + """Override the _post_task method to notify the frontend when the task really starts to execute""" + # When the dependency check is passed and the task is about to be published to the execution queue, send a notification to the frontend + task_lock = get_task_lock(self.api_task_id) + if self._task and task.id != self._task.id: # Skip the main task itself + await task_lock.put_queue( + ActionAssignTaskData( + action=Action.assign_task, + data={ + "assignee_id": assignee_id, + "task_id": task.id, + "content": task.content, + "state": "running", # running state + }, + ) + ) + # Call the parent class method to continue the normal task publishing process + await super()._post_task(task, assignee_id) + + def add_single_agent_worker( + self, description: str, worker: ListenChatAgent, pool_max_size: int = DEFAULT_WORKER_POOL_SIZE + ) -> BaseWorkforce: + if self._state == WorkforceState.RUNNING: + raise RuntimeError("Cannot add workers while workforce is running. Pause the workforce first.") + + # Validate worker agent compatibility + self._validate_agent_compatibility(worker, "Worker agent") + + # Ensure the worker agent shares this workforce's pause control + self._attach_pause_event_to_agent(worker) + + worker_node = SingleAgentWorker( + description=description, + worker=worker, + pool_max_size=pool_max_size, + use_structured_output_handler=self.use_structured_output_handler, + ) + self._children.append(worker_node) + + # If we have a channel set up, set it for the new worker + if hasattr(self, "_channel") and self._channel is not None: + worker_node.set_channel(self._channel) + + # If workforce is paused, start the worker's listening task + self._start_child_node_when_paused(worker_node.start()) + + if self.metrics_logger: + self.metrics_logger.log_worker_created( + worker_id=worker_node.node_id, + worker_type="SingleAgentWorker", + role=worker_node.description, + ) + return self + + async def _handle_completed_task(self, task: Task) -> None: + # DEBUG ▶ Task completed + logger.debug(f"[WF] DONE {task.id}") + task_lock = get_task_lock(self.api_task_id) + + await task_lock.put_queue( + ActionTaskStateData( + data={ + "task_id": task.id, + "content": task.content, + "state": task.state, + "result": task.result or "", + "failure_count": task.failure_count, + }, + ) + ) + + return await super()._handle_completed_task(task) + + async def _handle_failed_task(self, task: Task) -> bool: + # DEBUG ▶ Task failed + logger.debug(f"[WF] FAIL {task.id} retry={task.failure_count}") + + result = await super()._handle_failed_task(task) + + error_message = "" + if self.metrics_logger and hasattr(self.metrics_logger, "log_entries"): + for entry in reversed(self.metrics_logger.log_entries): + if entry.get("event_type") == "task_failed" and entry.get("task_id") == task.id: + error_message = entry.get("error_message") + break + + task_lock = get_task_lock(self.api_task_id) + await task_lock.put_queue( + ActionTaskStateData( + data={ + "task_id": task.id, + "content": task.content, + "state": task.state, + "failure_count": task.failure_count, + "result": str(error_message), + } + ) + ) + + return result + + def stop(self) -> None: + super().stop() + task_lock = get_task_lock(self.api_task_id) + task = asyncio.create_task(task_lock.put_queue(ActionEndData())) + task_lock.add_background_task(task) + + async def cleanup(self) -> None: + r"""Clean up resources when workforce is done""" + try: + # Clean up the task lock + from app.service.task import delete_task_lock + + await delete_task_lock(self.api_task_id) + except Exception as e: + logger.error(f"Error cleaning up workforce resources: {e}") diff --git a/backend/babel.cfg b/backend/babel.cfg new file mode 100644 index 000000000..1d15bb366 --- /dev/null +++ b/backend/babel.cfg @@ -0,0 +1 @@ +[python: **.py] \ No newline at end of file diff --git a/backend/cli.py b/backend/cli.py new file mode 100644 index 000000000..b70172750 --- /dev/null +++ b/backend/cli.py @@ -0,0 +1,9 @@ +from app.component.environment import auto_import +from app.command import cli + + +auto_import("app.command") + + +if __name__ == "__main__": + cli() diff --git a/backend/lang/en_US/LC_MESSAGES/messages.po b/backend/lang/en_US/LC_MESSAGES/messages.po new file mode 100644 index 000000000..c7f0fc457 --- /dev/null +++ b/backend/lang/en_US/LC_MESSAGES/messages.po @@ -0,0 +1,34 @@ +# English (United States) translations for PROJECT. +# Copyright (C) 2025 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2025-04-02 11:27+0800\n" +"PO-Revision-Date: 2025-04-02 11:44+0800\n" +"Last-Translator: FULL NAME \n" +"Language: en_US\n" +"Language-Team: en_US \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: app/controller/chat_controller.py:24 +msgid "Please end the previous task" +msgstr "" + +#: app/controller/chat_controller.py:33 +msgid "Please end the previous task first" +msgstr "" + +#~ msgid "no auth" +#~ msgstr "" + +#~ msgid "hello" +#~ msgstr "" + diff --git a/backend/lang/zh_CN/LC_MESSAGES/messages.po b/backend/lang/zh_CN/LC_MESSAGES/messages.po new file mode 100644 index 000000000..cd7a07ddb --- /dev/null +++ b/backend/lang/zh_CN/LC_MESSAGES/messages.po @@ -0,0 +1,36 @@ +# Chinese (Simplified, China) translations for PROJECT. +# Copyright (C) 2025 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2025-07-16 23:07+0800\n" +"PO-Revision-Date: 2025-07-16 23:07+0800\n" +"Last-Translator: FULL NAME \n" +"Language: zh_Hans_CN\n" +"Language-Team: zh_Hans_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: app/controller/chat_controller.py:38 +msgid "Task was done" +msgstr "" + +#: app/controller/chat_controller.py:47 +msgid "Please wait task done" +msgstr "" + +#: app/service/task.py:269 app/service/task.py:288 +msgid "Task not found" +msgstr "" + +#: app/service/task.py:275 +msgid "Task already exists" +msgstr "" + diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 000000000..6b5d61bfd --- /dev/null +++ b/backend/main.py @@ -0,0 +1,85 @@ +import os +import pathlib +import signal +import asyncio +import atexit +from app import api +from loguru import logger +from app.component.environment import auto_include_routers, env + + +os.environ["PYTHONIOENCODING"] = "utf-8" + +prefix = env("url_prefix", "") +auto_include_routers(api, prefix, "app/controller") + + +# Configure Loguru +logger.add( + os.path.expanduser("~/.eigent/runtime/log/app.log"), # Log file + rotation="10 MB", # Log rotation: 10MB per file + retention="10 days", # Retain logs for the last 10 days + level="DEBUG", # Log level + encoding="utf-8", +) + +dir = pathlib.Path(__file__).parent / "runtime" +dir.mkdir(parents=True, exist_ok=True) + + +# Write PID file asynchronously +async def write_pid_file(): + r"""Write PID file asynchronously""" + import aiofiles + + async with aiofiles.open(dir / "run.pid", "w") as f: + await f.write(str(os.getpid())) + + +# Create task to write PID +asyncio.create_task(write_pid_file()) + +# Graceful shutdown handler +shutdown_event = asyncio.Event() + + +async def cleanup_resources(): + r"""Cleanup all resources on shutdown""" + logger.info("Starting graceful shutdown...") + + from app.service.task import task_locks, _cleanup_task + + if _cleanup_task and not _cleanup_task.done(): + _cleanup_task.cancel() + try: + await _cleanup_task + except asyncio.CancelledError: + pass + + # Cleanup all task locks + for task_id in list(task_locks.keys()): + try: + task_lock = task_locks[task_id] + await task_lock.cleanup() + except Exception as e: + logger.error(f"Error cleaning up task {task_id}: {e}") + + # Remove PID file + pid_file = dir / "run.pid" + if pid_file.exists(): + pid_file.unlink() + + logger.info("Graceful shutdown completed") + + +def signal_handler(signum, frame): + r"""Handle shutdown signals""" + logger.info(f"Received signal {signum}") + asyncio.create_task(cleanup_resources()) + shutdown_event.set() + + +signal.signal(signal.SIGTERM, signal_handler) +signal.signal(signal.SIGINT, signal_handler) + +atexit.register(lambda: asyncio.run(cleanup_resources())) diff --git a/backend/messages.pot b/backend/messages.pot new file mode 100644 index 000000000..64da8a40a --- /dev/null +++ b/backend/messages.pot @@ -0,0 +1,35 @@ +# Translations template for PROJECT. +# Copyright (C) 2025 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2025-07-16 23:07+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: app/controller/chat_controller.py:38 +msgid "Task was done" +msgstr "" + +#: app/controller/chat_controller.py:47 +msgid "Please wait task done" +msgstr "" + +#: app/service/task.py:269 app/service/task.py:288 +msgid "Task not found" +msgstr "" + +#: app/service/task.py:275 +msgid "Task already exists" +msgstr "" + diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 000000000..027e7cf37 --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,49 @@ +[project] +name = "backend" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = "==3.10.16" +dependencies = [ + "camel-ai[owl]>=0.2.73a11", + "fastapi>=0.115.12", + "fastapi-babel>=1.0.0", + "fastapi-pagination>=0.12.34", + "fastapi-filter>=2.0.1", + "uvicorn[standard]>=0.34.2", + "pydantic-i18n>=0.4.5", + "pydantic[email]>=2.11.1", + "python-dotenv>=1.1.0", + "httpx[socks]>=0.28.1", + "chunkr-ai>=0.0.49", + "docx2markdown>=0.1.1", + "mcp-simple-arxiv>=0.2.2", + "mcp-server-fetch>=2025.1.17", + "xmltodict>=0.14.2", + "firecrawl>=2.5.3", + "pypdf2>=3.0.1", + "loguru>=0.7.3", + "pydash>=8.0.5", + "qdrant-client>=1.14.3", + "slack-sdk>=3.35.0", + "inflection>=0.5.1", + "aiofiles>=24.1.0", + "google-auth-httplib2>=0.2.0", + "google-auth-oauthlib>=1.2.1", + "google-api-python-client>=2.154.0", +] + + +[dependency-groups] +dev = ["babel>=2.17.0", "taskipy>=1.14.1"] + +[tool.taskipy.tasks] +babel = 'pybabel compile -d lang' + +[tool.ruff] +line-length = 120 + +[tool.ruff.lint] +extend-select = [ + "B006", # forbid def demo(mutation = []) +] diff --git a/backend/uv.lock b/backend/uv.lock new file mode 100644 index 000000000..66c281221 --- /dev/null +++ b/backend/uv.lock @@ -0,0 +1,3917 @@ +version = 1 +revision = 1 +requires-python = "==3.10.16" + +[[package]] +name = "aci-sdk" +version = "1.0.0b2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffe" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "tenacity" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/57/bd2b54e6ee84db1606e9959cabdde5fc6dace3e34dd360a62663d353e868/aci_sdk-1.0.0b2.tar.gz", hash = "sha256:b5f6b97c65adbacdf835bbba9d691dd55ad4aeae941eae68d5fb05ea01da802a", size = 63622 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/57/943ce077b823bce9a9d8f223cde70c12d9fbe923392f62d89cabea7cd7e1/aci_sdk-1.0.0b2-py3-none-any.whl", hash = "sha256:5ff09d074e1e146b536fe19334ea7ab03e62c75657ba0628bfb8778d376a7140", size = 31876 }, +] + +[[package]] +name = "aiofiles" +version = "24.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896 }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265 }, +] + +[[package]] +name = "aiohttp" +version = "3.12.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/6e/ab88e7cb2a4058bed2f7870276454f85a7c56cd6da79349eb314fc7bbcaa/aiohttp-3.12.13.tar.gz", hash = "sha256:47e2da578528264a12e4e3dd8dd72a7289e5f812758fe086473fab037a10fcce", size = 7819160 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/2d/27e4347660723738b01daa3f5769d56170f232bf4695dd4613340da135bb/aiohttp-3.12.13-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5421af8f22a98f640261ee48aae3a37f0c41371e99412d55eaf2f8a46d5dad29", size = 702090 }, + { url = "https://files.pythonhosted.org/packages/10/0b/4a8e0468ee8f2b9aff3c05f2c3a6be1dfc40b03f68a91b31041d798a9510/aiohttp-3.12.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fcda86f6cb318ba36ed8f1396a6a4a3fd8f856f84d426584392083d10da4de0", size = 478440 }, + { url = "https://files.pythonhosted.org/packages/b9/c8/2086df2f9a842b13feb92d071edf756be89250f404f10966b7bc28317f17/aiohttp-3.12.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cd71c9fb92aceb5a23c4c39d8ecc80389c178eba9feab77f19274843eb9412d", size = 466215 }, + { url = "https://files.pythonhosted.org/packages/a7/3d/d23e5bd978bc8012a65853959b13bd3b55c6e5afc172d89c26ad6624c52b/aiohttp-3.12.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34ebf1aca12845066c963016655dac897651e1544f22a34c9b461ac3b4b1d3aa", size = 1648271 }, + { url = "https://files.pythonhosted.org/packages/31/31/e00122447bb137591c202786062f26dd383574c9f5157144127077d5733e/aiohttp-3.12.13-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:893a4639694c5b7edd4bdd8141be296042b6806e27cc1d794e585c43010cc294", size = 1622329 }, + { url = "https://files.pythonhosted.org/packages/04/01/caef70be3ac38986969045f21f5fb802ce517b3f371f0615206bf8aa6423/aiohttp-3.12.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:663d8ee3ffb3494502ebcccb49078faddbb84c1d870f9c1dd5a29e85d1f747ce", size = 1694734 }, + { url = "https://files.pythonhosted.org/packages/3f/15/328b71fedecf69a9fd2306549b11c8966e420648a3938d75d3ed5bcb47f6/aiohttp-3.12.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0f8f6a85a0006ae2709aa4ce05749ba2cdcb4b43d6c21a16c8517c16593aabe", size = 1737049 }, + { url = "https://files.pythonhosted.org/packages/e6/7a/d85866a642158e1147c7da5f93ad66b07e5452a84ec4258e5f06b9071e92/aiohttp-3.12.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1582745eb63df267c92d8b61ca655a0ce62105ef62542c00a74590f306be8cb5", size = 1641715 }, + { url = "https://files.pythonhosted.org/packages/14/57/3588800d5d2f5f3e1cb6e7a72747d1abc1e67ba5048e8b845183259c2e9b/aiohttp-3.12.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d59227776ee2aa64226f7e086638baa645f4b044f2947dbf85c76ab11dcba073", size = 1581836 }, + { url = "https://files.pythonhosted.org/packages/2f/55/c913332899a916d85781aa74572f60fd98127449b156ad9c19e23135b0e4/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06b07c418bde1c8e737d8fa67741072bd3f5b0fb66cf8c0655172188c17e5fa6", size = 1625685 }, + { url = "https://files.pythonhosted.org/packages/4c/34/26cded195f3bff128d6a6d58d7a0be2ae7d001ea029e0fe9008dcdc6a009/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9445c1842680efac0f81d272fd8db7163acfcc2b1436e3f420f4c9a9c5a50795", size = 1636471 }, + { url = "https://files.pythonhosted.org/packages/19/21/70629ca006820fccbcec07f3cd5966cbd966e2d853d6da55339af85555b9/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:09c4767af0b0b98c724f5d47f2bf33395c8986995b0a9dab0575ca81a554a8c0", size = 1611923 }, + { url = "https://files.pythonhosted.org/packages/31/80/7fa3f3bebf533aa6ae6508b51ac0de9965e88f9654fa679cc1a29d335a79/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f3854fbde7a465318ad8d3fc5bef8f059e6d0a87e71a0d3360bb56c0bf87b18a", size = 1691511 }, + { url = "https://files.pythonhosted.org/packages/0f/7a/359974653a3cdd3e9cee8ca10072a662c3c0eb46a359c6a1f667b0296e2f/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2332b4c361c05ecd381edb99e2a33733f3db906739a83a483974b3df70a51b40", size = 1714751 }, + { url = "https://files.pythonhosted.org/packages/2d/24/0aa03d522171ce19064347afeefadb008be31ace0bbb7d44ceb055700a14/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1561db63fa1b658cd94325d303933553ea7d89ae09ff21cc3bcd41b8521fbbb6", size = 1643090 }, + { url = "https://files.pythonhosted.org/packages/86/2e/7d4b0026a41e4b467e143221c51b279083b7044a4b104054f5c6464082ff/aiohttp-3.12.13-cp310-cp310-win32.whl", hash = "sha256:a0be857f0b35177ba09d7c472825d1b711d11c6d0e8a2052804e3b93166de1ad", size = 427526 }, + { url = "https://files.pythonhosted.org/packages/17/de/34d998da1e7f0de86382160d039131e9b0af1962eebfe53dda2b61d250e7/aiohttp-3.12.13-cp310-cp310-win_amd64.whl", hash = "sha256:fcc30ad4fb5cb41a33953292d45f54ef4066746d625992aeac33b8c681173178", size = 450734 }, +] + +[[package]] +name = "aiosignal" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597 }, +] + +[[package]] +name = "aiosqlite" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/3a/22ff5415bf4d296c1e92b07fd746ad42c96781f13295a074d58e77747848/aiosqlite-0.20.0.tar.gz", hash = "sha256:6d35c8c256637f4672f843c31021464090805bf925385ac39473fb16eaaca3d7", size = 21691 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c4/c93eb22025a2de6b83263dfe3d7df2e19138e345bca6f18dba7394120930/aiosqlite-0.20.0-py3-none-any.whl", hash = "sha256:36a1deaca0cac40ebe32aac9977a6e2bbc7f5189f23f4a54d5908986729e5bd6", size = 15564 }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "anthropic" +version = "0.49.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/e3/a88c8494ce4d1a88252b9e053607e885f9b14d0a32273d47b727cbee4228/anthropic-0.49.0.tar.gz", hash = "sha256:c09e885b0f674b9119b4f296d8508907f6cff0009bc20d5cf6b35936c40b4398", size = 210016 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/74/5d90ad14d55fbe3f9c474fdcb6e34b4bed99e3be8efac98734a5ddce88c1/anthropic-0.49.0-py3-none-any.whl", hash = "sha256:bbc17ad4e7094988d2fa86b87753ded8dce12498f4b85fe5810f208f454a8375", size = 243368 }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, +] + +[[package]] +name = "astor" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/21/75b771132fee241dfe601d39ade629548a9626d1d39f333fde31bc46febe/astor-0.8.1.tar.gz", hash = "sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e", size = 35090 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/88/97eef84f48fa04fbd6750e62dcceafba6c63c81b7ac1420856c8dcc0a3f9/astor-0.8.1-py2.py3-none-any.whl", hash = "sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5", size = 27488 }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233 }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, +] + +[[package]] +name = "av" +version = "14.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/f6/0b473dab52dfdea05f28f3578b1c56b6c796ce85e76951bab7c4e38d5a74/av-14.4.0.tar.gz", hash = "sha256:3ecbf803a7fdf67229c0edada0830d6bfaea4d10bfb24f0c3f4e607cd1064b42", size = 3892203 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/0f/cf6b888747cd1e10eafc4a28942e5b666417c03c39853818900bdaa86116/av-14.4.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:10219620699a65b9829cfa08784da2ed38371f1a223ab8f3523f440a24c8381c", size = 19979523 }, + { url = "https://files.pythonhosted.org/packages/45/30/8f09ac71ad23344ff247f16a9229b36b1e2a36214fd56ba55df885e9bf85/av-14.4.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:8bac981fde1c05e231df9f73a06ed9febce1f03fb0f1320707ac2861bba2567f", size = 23765838 }, + { url = "https://files.pythonhosted.org/packages/a2/57/e0c30ceb1e59e7b2b88c9cd6bf79a0a979128de19a94b300a700d3a7ca52/av-14.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc634ed5bdeb362f0523b73693b079b540418d35d7f3003654f788ae6c317eef", size = 33122039 }, + { url = "https://files.pythonhosted.org/packages/c6/a7/9b3064c49f2d2219ee1b895cc77fca18c84d6121b51c8ce6b7f618a2661b/av-14.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23973ed5c5bec9565094d2b3643f10a6996707ddffa5252e112d578ad34aa9ae", size = 31758563 }, + { url = "https://files.pythonhosted.org/packages/23/42/0eafe0de75de6a0db71add8e4ea51ebf090482bad3068f4a874c90fbd110/av-14.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0655f7207db6a211d7cedb8ac6a2f7ccc9c4b62290130e393a3fd99425247311", size = 34750358 }, + { url = "https://files.pythonhosted.org/packages/75/33/5430ba9ad73036f2d69395d36f3d57b261c51db6f6542bcfc60087640bb7/av-14.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1edaab73319bfefe53ee09c4b1cf7b141ea7e6678a0a1c62f7bac1e2c68ec4e7", size = 35793636 }, + { url = "https://files.pythonhosted.org/packages/00/a9/d8c07f0ab69be05a4939719d7a31dc3e9fb112ee8ec6c9411a6c9c085f0a/av-14.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b54838fa17c031ffd780df07b9962fac1be05220f3c28468f7fe49474f1bf8d2", size = 34123666 }, + { url = "https://files.pythonhosted.org/packages/48/e1/2f2f607553f2ac6369e5fc814e77b41f9ceb285ce9d8c02c9ee034b8b6db/av-14.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f4b59ac6c563b9b6197299944145958a8ec34710799fd851f1a889b0cbcd1059", size = 36756157 }, + { url = "https://files.pythonhosted.org/packages/d7/f0/d653d4eaa7e68732f8c0013aee40f31ff0cd49e90fdec89cca6c193db207/av-14.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:a0192a584fae9f6cedfac03c06d5bf246517cdf00c8779bc33414404796a526e", size = 27931039 }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, +] + +[[package]] +name = "backend" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "aiofiles" }, + { name = "camel-ai", extra = ["owl"] }, + { name = "chunkr-ai" }, + { name = "docx2markdown" }, + { name = "fastapi" }, + { name = "fastapi-babel" }, + { name = "fastapi-filter" }, + { name = "fastapi-pagination" }, + { name = "firecrawl" }, + { name = "google-api-python-client" }, + { name = "google-auth-httplib2" }, + { name = "google-auth-oauthlib" }, + { name = "httpx", extra = ["socks"] }, + { name = "inflection" }, + { name = "loguru" }, + { name = "mcp-server-fetch" }, + { name = "mcp-simple-arxiv" }, + { name = "pydantic", extra = ["email"] }, + { name = "pydantic-i18n" }, + { name = "pydash" }, + { name = "pypdf2" }, + { name = "python-dotenv" }, + { name = "qdrant-client" }, + { name = "slack-sdk" }, + { name = "uvicorn", extra = ["standard"] }, + { name = "xmltodict" }, +] + +[package.dev-dependencies] +dev = [ + { name = "babel" }, + { name = "taskipy" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiofiles", specifier = ">=24.1.0" }, + { name = "camel-ai", extras = ["owl"], specifier = ">=0.2.73a11" }, + { name = "chunkr-ai", specifier = ">=0.0.49" }, + { name = "docx2markdown", specifier = ">=0.1.1" }, + { name = "fastapi", specifier = ">=0.115.12" }, + { name = "fastapi-babel", specifier = ">=1.0.0" }, + { name = "fastapi-filter", specifier = ">=2.0.1" }, + { name = "fastapi-pagination", specifier = ">=0.12.34" }, + { name = "firecrawl", specifier = ">=2.5.3" }, + { name = "google-api-python-client", specifier = ">=2.154.0" }, + { name = "google-auth-httplib2", specifier = ">=0.2.0" }, + { name = "google-auth-oauthlib", specifier = ">=1.2.1" }, + { name = "httpx", extras = ["socks"], specifier = ">=0.28.1" }, + { name = "inflection", specifier = ">=0.5.1" }, + { name = "loguru", specifier = ">=0.7.3" }, + { name = "mcp-server-fetch", specifier = ">=2025.1.17" }, + { name = "mcp-simple-arxiv", specifier = ">=0.2.2" }, + { name = "pydantic", extras = ["email"], specifier = ">=2.11.1" }, + { name = "pydantic-i18n", specifier = ">=0.4.5" }, + { name = "pydash", specifier = ">=8.0.5" }, + { name = "pypdf2", specifier = ">=3.0.1" }, + { name = "python-dotenv", specifier = ">=1.1.0" }, + { name = "qdrant-client", specifier = ">=1.14.3" }, + { name = "slack-sdk", specifier = ">=3.35.0" }, + { name = "uvicorn", extras = ["standard"], specifier = ">=0.34.2" }, + { name = "xmltodict", specifier = ">=0.14.2" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "babel", specifier = ">=2.17.0" }, + { name = "taskipy", specifier = ">=1.14.1" }, +] + +[[package]] +name = "backoff" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148 }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285 }, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080 }, +] + +[[package]] +name = "camel-ai" +version = "0.2.73a11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "docstring-parser" }, + { name = "httpx" }, + { name = "jsonschema" }, + { name = "mcp" }, + { name = "openai" }, + { name = "pillow" }, + { name = "psutil" }, + { name = "pydantic" }, + { name = "tiktoken" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/a7/8dde422603b890325d8b78cdf8544e475c198fd4b77a64311f138f970d00/camel_ai-0.2.73a11.tar.gz", hash = "sha256:dfb6188a032a842ed7280eca922a2576235aa0463a38c0b42c07fcf7f8cf4d22", size = 887011 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/c0/cc872d3cd6ea5f16b448c6ae80f6db1bc3cfc5e583c85944d6a8e52876ab/camel_ai-0.2.73a11-py3-none-any.whl", hash = "sha256:5df92137d5550cb191dc7ab774200e19c2a2a8cebac8bcbbb7d7932279666914", size = 1319035 }, +] + +[package.optional-dependencies] +owl = [ + { name = "aci-sdk" }, + { name = "anthropic" }, + { name = "beautifulsoup4" }, + { name = "chunkr-ai" }, + { name = "crawl4ai" }, + { name = "datasets" }, + { name = "docx" }, + { name = "docx2txt" }, + { name = "duckduckgo-search" }, + { name = "e2b-code-interpreter" }, + { name = "exa-py" }, + { name = "ffmpeg-python" }, + { name = "html2text" }, + { name = "imageio", extra = ["pyav"] }, + { name = "markitdown" }, + { name = "mcp-server-fetch" }, + { name = "mcp-simple-arxiv" }, + { name = "newspaper3k" }, + { name = "numpy" }, + { name = "onnxruntime" }, + { name = "openapi-spec-validator" }, + { name = "openpyxl" }, + { name = "pandas" }, + { name = "pandasai" }, + { name = "playwright" }, + { name = "prance" }, + { name = "pyautogui" }, + { name = "pydub" }, + { name = "pylatex" }, + { name = "pymupdf" }, + { name = "pytesseract" }, + { name = "python-dotenv" }, + { name = "python-pptx" }, + { name = "reportlab" }, + { name = "requests-oauthlib" }, + { name = "rouge" }, + { name = "scenedetect" }, + { name = "scrapegraph-py" }, + { name = "sentencepiece" }, + { name = "soundfile" }, + { name = "tabulate" }, + { name = "transformers" }, + { name = "tree-sitter" }, + { name = "tree-sitter-python" }, + { name = "typer" }, + { name = "unstructured" }, + { name = "websockets" }, + { name = "wikipedia" }, + { name = "xls2xlsx" }, + { name = "yt-dlp" }, +] + +[[package]] +name = "certifi" +version = "2025.6.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818 }, + { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649 }, + { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045 }, + { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356 }, + { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471 }, + { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317 }, + { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368 }, + { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491 }, + { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695 }, + { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849 }, + { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091 }, + { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445 }, + { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782 }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626 }, +] + +[[package]] +name = "chunkr-ai" +version = "0.0.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "nest-asyncio" }, + { name = "pillow" }, + { name = "pydantic" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/93/2a8262f50fda96068e6ea9acb387fb87f201457c1fa65474abbc318e6d96/chunkr_ai-0.0.50.tar.gz", hash = "sha256:da4d94866818d42997e479bad8d8414b2876b06a98e2ec4788cd89b24fd712df", size = 20023 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/ca/57d7647caf903ff465b5af4e6e1ea75dc2f42b2b0684487e02161706773b/chunkr_ai-0.0.50-py3-none-any.whl", hash = "sha256:3a8652cf16a3fd9d1f51d8a922dfc91984e2befab32a6faaaa28dae7dcb6324d", size = 16751 }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "coloredlogs" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "humanfriendly" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018 }, +] + +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551 }, + { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399 }, + { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061 }, + { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956 }, + { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872 }, + { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027 }, + { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641 }, + { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075 }, + { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534 }, + { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188 }, + { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681 }, + { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101 }, + { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599 }, +] + +[[package]] +name = "crawl4ai" +version = "0.4.24" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "aiosqlite" }, + { name = "beautifulsoup4" }, + { name = "colorama" }, + { name = "litellm" }, + { name = "lxml" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "playwright" }, + { name = "pydantic" }, + { name = "pyopenssl" }, + { name = "python-dotenv" }, + { name = "rank-bm25" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "tf-playwright-stealth" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/88/33/3db87e8ae74441f70cd135342f21536c0bf59adfdb73a7f8d6ff9f4c256b/crawl4ai-0.4.24.tar.gz", hash = "sha256:d061508e48bec2bd260c571759391ea7dca5e903fe81db8f709468e8f96df2fe", size = 168597 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/d0/c7090374d4566b881ea3f3c72b7e219095a9b9ca080d5d3cc73bf9aa4ec8/Crawl4AI-0.4.24-py3-none-any.whl", hash = "sha256:7f7b75b893965c0bfb96f9ea607765b2fc2ee34313d6ce1bf510df8102095da7", size = 177431 }, +] + +[[package]] +name = "cryptography" +version = "45.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/c8/a2a376a8711c1e11708b9c9972e0c3223f5fc682552c82d8db844393d6ce/cryptography-45.0.4.tar.gz", hash = "sha256:7405ade85c83c37682c8fe65554759800a4a8c54b2d96e0f8ad114d31b808d57", size = 744890 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/1c/92637793de053832523b410dbe016d3f5c11b41d0cf6eef8787aabb51d41/cryptography-45.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:425a9a6ac2823ee6e46a76a21a4e8342d8fa5c01e08b823c1f19a8b74f096069", size = 7055712 }, + { url = "https://files.pythonhosted.org/packages/ba/14/93b69f2af9ba832ad6618a03f8a034a5851dc9a3314336a3d71c252467e1/cryptography-45.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:680806cf63baa0039b920f4976f5f31b10e772de42f16310a6839d9f21a26b0d", size = 4205335 }, + { url = "https://files.pythonhosted.org/packages/67/30/fae1000228634bf0b647fca80403db5ca9e3933b91dd060570689f0bd0f7/cryptography-45.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4ca0f52170e821bc8da6fc0cc565b7bb8ff8d90d36b5e9fdd68e8a86bdf72036", size = 4431487 }, + { url = "https://files.pythonhosted.org/packages/6d/5a/7dffcf8cdf0cb3c2430de7404b327e3db64735747d641fc492539978caeb/cryptography-45.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f3fe7a5ae34d5a414957cc7f457e2b92076e72938423ac64d215722f6cf49a9e", size = 4208922 }, + { url = "https://files.pythonhosted.org/packages/c6/f3/528729726eb6c3060fa3637253430547fbaaea95ab0535ea41baa4a6fbd8/cryptography-45.0.4-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:25eb4d4d3e54595dc8adebc6bbd5623588991d86591a78c2548ffb64797341e2", size = 3900433 }, + { url = "https://files.pythonhosted.org/packages/d9/4a/67ba2e40f619e04d83c32f7e1d484c1538c0800a17c56a22ff07d092ccc1/cryptography-45.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ce1678a2ccbe696cf3af15a75bb72ee008d7ff183c9228592ede9db467e64f1b", size = 4464163 }, + { url = "https://files.pythonhosted.org/packages/7e/9a/b4d5aa83661483ac372464809c4b49b5022dbfe36b12fe9e323ca8512420/cryptography-45.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:49fe9155ab32721b9122975e168a6760d8ce4cffe423bcd7ca269ba41b5dfac1", size = 4208687 }, + { url = "https://files.pythonhosted.org/packages/db/b7/a84bdcd19d9c02ec5807f2ec2d1456fd8451592c5ee353816c09250e3561/cryptography-45.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2882338b2a6e0bd337052e8b9007ced85c637da19ef9ecaf437744495c8c2999", size = 4463623 }, + { url = "https://files.pythonhosted.org/packages/d8/84/69707d502d4d905021cac3fb59a316344e9f078b1da7fb43ecde5e10840a/cryptography-45.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:23b9c3ea30c3ed4db59e7b9619272e94891f8a3a5591d0b656a7582631ccf750", size = 4332447 }, + { url = "https://files.pythonhosted.org/packages/f3/ee/d4f2ab688e057e90ded24384e34838086a9b09963389a5ba6854b5876598/cryptography-45.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0a97c927497e3bc36b33987abb99bf17a9a175a19af38a892dc4bbb844d7ee2", size = 4572830 }, + { url = "https://files.pythonhosted.org/packages/70/d4/994773a261d7ff98034f72c0e8251fe2755eac45e2265db4c866c1c6829c/cryptography-45.0.4-cp311-abi3-win32.whl", hash = "sha256:e00a6c10a5c53979d6242f123c0a97cff9f3abed7f064fc412c36dc521b5f257", size = 2932769 }, + { url = "https://files.pythonhosted.org/packages/5a/42/c80bd0b67e9b769b364963b5252b17778a397cefdd36fa9aa4a5f34c599a/cryptography-45.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:817ee05c6c9f7a69a16200f0c90ab26d23a87701e2a284bd15156783e46dbcc8", size = 3410441 }, + { url = "https://files.pythonhosted.org/packages/ce/0b/2488c89f3a30bc821c9d96eeacfcab6ff3accc08a9601ba03339c0fd05e5/cryptography-45.0.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:964bcc28d867e0f5491a564b7debb3ffdd8717928d315d12e0d7defa9e43b723", size = 7031836 }, + { url = "https://files.pythonhosted.org/packages/fe/51/8c584ed426093aac257462ae62d26ad61ef1cbf5b58d8b67e6e13c39960e/cryptography-45.0.4-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6a5bf57554e80f75a7db3d4b1dacaa2764611ae166ab42ea9a72bcdb5d577637", size = 4195746 }, + { url = "https://files.pythonhosted.org/packages/5c/7d/4b0ca4d7af95a704eef2f8f80a8199ed236aaf185d55385ae1d1610c03c2/cryptography-45.0.4-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:46cf7088bf91bdc9b26f9c55636492c1cce3e7aaf8041bbf0243f5e5325cfb2d", size = 4424456 }, + { url = "https://files.pythonhosted.org/packages/1d/45/5fabacbc6e76ff056f84d9f60eeac18819badf0cefc1b6612ee03d4ab678/cryptography-45.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7bedbe4cc930fa4b100fc845ea1ea5788fcd7ae9562e669989c11618ae8d76ee", size = 4198495 }, + { url = "https://files.pythonhosted.org/packages/55/b7/ffc9945b290eb0a5d4dab9b7636706e3b5b92f14ee5d9d4449409d010d54/cryptography-45.0.4-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:eaa3e28ea2235b33220b949c5a0d6cf79baa80eab2eb5607ca8ab7525331b9ff", size = 3885540 }, + { url = "https://files.pythonhosted.org/packages/7f/e3/57b010282346980475e77d414080acdcb3dab9a0be63071efc2041a2c6bd/cryptography-45.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7ef2dde4fa9408475038fc9aadfc1fb2676b174e68356359632e980c661ec8f6", size = 4452052 }, + { url = "https://files.pythonhosted.org/packages/37/e6/ddc4ac2558bf2ef517a358df26f45bc774a99bf4653e7ee34b5e749c03e3/cryptography-45.0.4-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6a3511ae33f09094185d111160fd192c67aa0a2a8d19b54d36e4c78f651dc5ad", size = 4198024 }, + { url = "https://files.pythonhosted.org/packages/3a/c0/85fa358ddb063ec588aed4a6ea1df57dc3e3bc1712d87c8fa162d02a65fc/cryptography-45.0.4-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:06509dc70dd71fa56eaa138336244e2fbaf2ac164fc9b5e66828fccfd2b680d6", size = 4451442 }, + { url = "https://files.pythonhosted.org/packages/33/67/362d6ec1492596e73da24e669a7fbbaeb1c428d6bf49a29f7a12acffd5dc/cryptography-45.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5f31e6b0a5a253f6aa49be67279be4a7e5a4ef259a9f33c69f7d1b1191939872", size = 4325038 }, + { url = "https://files.pythonhosted.org/packages/53/75/82a14bf047a96a1b13ebb47fb9811c4f73096cfa2e2b17c86879687f9027/cryptography-45.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:944e9ccf67a9594137f942d5b52c8d238b1b4e46c7a0c2891b7ae6e01e7c80a4", size = 4560964 }, + { url = "https://files.pythonhosted.org/packages/cd/37/1a3cba4c5a468ebf9b95523a5ef5651244693dc712001e276682c278fc00/cryptography-45.0.4-cp37-abi3-win32.whl", hash = "sha256:c22fe01e53dc65edd1945a2e6f0015e887f84ced233acecb64b4daadb32f5c97", size = 2924557 }, + { url = "https://files.pythonhosted.org/packages/2a/4b/3256759723b7e66380397d958ca07c59cfc3fb5c794fb5516758afd05d41/cryptography-45.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:627ba1bc94f6adf0b0a2e35d87020285ead22d9f648c7e75bb64f367375f3b22", size = 3395508 }, + { url = "https://files.pythonhosted.org/packages/16/33/b38e9d372afde56906a23839302c19abdac1c505bfb4776c1e4b07c3e145/cryptography-45.0.4-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a77c6fb8d76e9c9f99f2f3437c1a4ac287b34eaf40997cfab1e9bd2be175ac39", size = 3580103 }, + { url = "https://files.pythonhosted.org/packages/c4/b9/357f18064ec09d4807800d05a48f92f3b369056a12f995ff79549fbb31f1/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7aad98a25ed8ac917fdd8a9c1e706e5a0956e06c498be1f713b61734333a4507", size = 4143732 }, + { url = "https://files.pythonhosted.org/packages/c4/9c/7f7263b03d5db329093617648b9bd55c953de0b245e64e866e560f9aac07/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3530382a43a0e524bc931f187fc69ef4c42828cf7d7f592f7f249f602b5a4ab0", size = 4385424 }, + { url = "https://files.pythonhosted.org/packages/a6/5a/6aa9d8d5073d5acc0e04e95b2860ef2684b2bd2899d8795fc443013e263b/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:6b613164cb8425e2f8db5849ffb84892e523bf6d26deb8f9bb76ae86181fa12b", size = 4142438 }, + { url = "https://files.pythonhosted.org/packages/42/1c/71c638420f2cdd96d9c2b287fec515faf48679b33a2b583d0f1eda3a3375/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:96d4819e25bf3b685199b304a0029ce4a3caf98947ce8a066c9137cc78ad2c58", size = 4384622 }, + { url = "https://files.pythonhosted.org/packages/ef/ab/e3a055c34e97deadbf0d846e189237d3385dca99e1a7e27384c3b2292041/cryptography-45.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b97737a3ffbea79eebb062eb0d67d72307195035332501722a9ca86bab9e3ab2", size = 3328911 }, +] + +[[package]] +name = "cssselect" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/0a/c3ea9573b1dc2e151abfe88c7fe0c26d1892fe6ed02d0cdb30f0d57029d5/cssselect-1.3.0.tar.gz", hash = "sha256:57f8a99424cfab289a1b6a816a43075a4b00948c86b4dcf3ef4ee7e15f7ab0c7", size = 42870 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/58/257350f7db99b4ae12b614a36256d9cc870d71d9e451e79c2dc3b23d7c3c/cssselect-1.3.0-py3-none-any.whl", hash = "sha256:56d1bf3e198080cc1667e137bc51de9cadfca259f03c2d4e09037b3e01e30f0d", size = 18786 }, +] + +[[package]] +name = "cssutils" +version = "2.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/9f/329d26121fe165be44b1dfff21aa0dc348f04633931f1d20ed6cf448a236/cssutils-2.11.1.tar.gz", hash = "sha256:0563a76513b6af6eebbe788c3bf3d01c920e46b3f90c8416738c5cfc773ff8e2", size = 711657 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/ec/bb273b7208c606890dc36540fe667d06ce840a6f62f9fae7e658fcdc90fb/cssutils-2.11.1-py3-none-any.whl", hash = "sha256:a67bfdfdff4f3867fab43698ec4897c1a828eca5973f4073321b3bccaf1199b1", size = 385747 }, +] + +[[package]] +name = "currency-symbols" +version = "2.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/ea/47b00ec684efb492c6cc1a7c044fb404c3efb961dba5aaa8e3c100bad56a/currency_symbols-2.0.4.tar.gz", hash = "sha256:cb1ccbbe47909d7958f211359027eb8876874b8b0a19a70a7c0b2fa6bbe71a8b", size = 4405 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/5f/08d8eed069fb2eb72d5c9cd693b1f79c12521798ef0cfa2068736c16a65c/currency_symbols-2.0.4-py3-none-any.whl", hash = "sha256:f0f381c517b08b862612b31e915b5e84aeeef8da9387bcb7a73a221628ae3310", size = 5278 }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, +] + +[[package]] +name = "dataclasses-json" +version = "0.6.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "marshmallow" }, + { name = "typing-inspect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686 }, +] + +[[package]] +name = "datasets" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, + { name = "filelock" }, + { name = "fsspec", extra = ["http"] }, + { name = "huggingface-hub" }, + { name = "multiprocess" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/89/d3d6fef58a488f8569c82fd293ab7cbd4250244d67f425dcae64c63800ea/datasets-3.6.0.tar.gz", hash = "sha256:1b2bf43b19776e2787e181cfd329cb0ca1a358ea014780c3581e0f276375e041", size = 569336 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/34/a08b0ee99715eaba118cbe19a71f7b5e2425c2718ef96007c325944a1152/datasets-3.6.0-py3-none-any.whl", hash = "sha256:25000c4a2c0873a710df127d08a202a06eab7bf42441a6bc278b499c2f72cd1b", size = 491546 }, +] + +[[package]] +name = "dill" +version = "0.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/17/4d/ac7ffa80c69ea1df30a8aa11b3578692a5118e7cd1aa157e3ef73b092d15/dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca", size = 184847 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/7a/cef76fd8438a42f96db64ddaa85280485a9c395e7df3db8158cfec1eee34/dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7", size = 116252 }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, +] + +[[package]] +name = "dnspython" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632 }, +] + +[[package]] +name = "docstring-parser" +version = "0.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/2d/ea1dfc15b909cc660f657a3a9d698a2916b7f3b05535a2d72e8d7ea3ad5b/docstring_parser-0.15.tar.gz", hash = "sha256:48ddc093e8b1865899956fcc03b03e66bb7240c310fac5af81814580c55bf682", size = 26768 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/e3/32e272db7adcf90e93f73e9a98fd763049ed7c641fb57ab26cb8f3e7e79c/docstring_parser-0.15-py3-none-any.whl", hash = "sha256:d1679b86250d269d06a99670924d6bce45adc00b08069dae8c47d98e89b667a9", size = 36093 }, +] + +[[package]] +name = "docx" +version = "0.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4a/8e/5a01644697b03016de339ef444cfff28367f92984dc74eddaab1ed60eada/docx-0.2.4.tar.gz", hash = "sha256:9d7595eac6e86cda0b7136a2995318d039c1f3eaa368a3300805abbbe5dc8877", size = 54925 } + +[[package]] +name = "docx2markdown" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml" }, + { name = "python-docx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/be/692425e45455b1bbff7ad0b9fa2fab31ac3f9da13f210ded06c0c99158f5/docx2markdown-0.1.1.tar.gz", hash = "sha256:177e7ca0c998139b9afb9ffbe9a3be05a27d60664ac602b38de643e7b343593d", size = 8256 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/f5/970f7c2f64331891ff8b8c1863a793779e4fc784dbd210e8ae3712bcfd0e/docx2markdown-0.1.1-py3-none-any.whl", hash = "sha256:a97d413e6a225d4a0932cc8660b92f10f5de28f0436bc18ee12ef3961cd4b2b1", size = 8393 }, +] + +[[package]] +name = "docx2txt" +version = "0.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/7d/60ee3f2b16d9bfdfa72e8599470a2c1a5b759cb113c6fe1006be28359327/docx2txt-0.8.tar.gz", hash = "sha256:2c06d98d7cfe2d3947e5760a57d924e3ff07745b379c8737723922e7009236e5", size = 2814 } + +[[package]] +name = "duckdb" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/ab/d89a4dd14311d5a0081711bc66db3fad73f7645fa7eb3844c423d2fa0a17/duckdb-1.3.1.tar.gz", hash = "sha256:8e101990a879533b1d33f003df2eb2a3c4bc7bdf976bd7ef7c32342047935327", size = 11628075 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/f2/e9b3fa5528ed9e586f9a9cd52c1e190963600e4d095d872af7a557d1bae4/duckdb-1.3.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:8321ecd3c6be22660ac7b48d1770781b2a9d22e3f961ad0bb9f851d4e109806c", size = 15513952 }, + { url = "https://files.pythonhosted.org/packages/f4/54/c0ec22e742938e5d114ae51a9b5bf8b155d93e3a3fc323230e23ffc0cb29/duckdb-1.3.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:ccccc9dc9cb2269430fed29a2be8ff65a84d7b9e427548e02b5a8e1e1aacfa6d", size = 32480539 }, + { url = "https://files.pythonhosted.org/packages/6f/76/f14a66540e4b62ca01d35d347a3a0c493ea5a516865480339061901bc538/duckdb-1.3.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:f8a1ca3bbf84275ba4e0da2bccf6d43cb277a19af6f88fb86f98c33a98cce02e", size = 17079404 }, + { url = "https://files.pythonhosted.org/packages/6d/db/2abb3553463fa479b2497b63d704b2133b45773792cd1e9defdf08538047/duckdb-1.3.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ed9a942ba1167a51c0eb9f23c567051a51da4cbf920b3ac83fe63b010c4334c", size = 19152794 }, + { url = "https://files.pythonhosted.org/packages/f4/a5/ef66e37e90a5ea122f14c9d1f3180754704fe6df3e8bd44afd88a0e0f8b7/duckdb-1.3.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26944ff2c09749077ee63e5fec634da431b0b8eb7dd0d30c24fa7fe89ce70b66", size = 21084453 }, + { url = "https://files.pythonhosted.org/packages/5b/9d/0d72db42fd1e9e6f3981d59f7418a9ebe765bfa477bd546a91a3bbded81c/duckdb-1.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1ac996ac099f5d15468e33a93caf078da0fdace48c8a2c9af41e7bec766602f3", size = 22733663 }, + { url = "https://files.pythonhosted.org/packages/9f/8d/ff3a3f4f8a6b0e8020f1eaa16aa4f50890596e6d7dcdf084cc1f63d79c60/duckdb-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:57a2324f8206a52f5fd2b44f34c3746bed8bcd5e98b05b298e04fafbf30e5079", size = 11300498 }, +] + +[[package]] +name = "duckduckgo-search" +version = "6.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "primp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/3f/c36407a7be9cad84c2f15ec38839a9fbc2ad2f8cc051a9ebfd70cddd813a/duckduckgo_search-6.4.2.tar.gz", hash = "sha256:173c6988cbac1f3ccecc2c645e44e69fc49c4e94c06ee7c09e9dd8ad39d63b0c", size = 33341 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/2e/c8bfff437be4d53a0156d75334234e59ba2e8d8fd24b618b1cef15e8e2ee/duckduckgo_search-6.4.2-py3-none-any.whl", hash = "sha256:1e9e64a5379a5330bd99885c053185c9e133eb30f397701fdd6e4f448da1843c", size = 27808 }, +] + +[[package]] +name = "e2b" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "httpcore" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "python-dateutil" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/b3/e5f89ea7adab8651d3258e5ddd0f0cbcddf441ab5a8f3c108e7e2faef1a3/e2b-1.5.2.tar.gz", hash = "sha256:29ed891ae04ffafff1744c57eff55901200f15030d34ac3fe76d6672e2bf7845", size = 55815 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/a9/48feaa1c41af3d480d97ecfab3d32228692dae8c3e6bd49ac36fd80010d3/e2b-1.5.2-py3-none-any.whl", hash = "sha256:8cf755f2ff04098daa7ac778f768eee1df730a6181637fe124210345999890b3", size = 104069 }, +] + +[[package]] +name = "e2b-code-interpreter" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "e2b" }, + { name = "httpx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/d5/96675abbe490b8d65f983423c3650f673b7afe7148ccb17076d067af3b3e/e2b_code_interpreter-1.5.1.tar.gz", hash = "sha256:e39485dd2ffb148a902e8c05c8f573feeb7ca87f8498f02a4db65630e76364e1", size = 10003 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/cf/0327567e9ce1c73183f652b7818ee699462b8b6bf5605a7675a4c0e63cbf/e2b_code_interpreter-1.5.1-py3-none-any.whl", hash = "sha256:c8ee6f77bcb9c53422df336abbd37d5bf6318c3967b87444b39e3428a54c5e08", size = 12872 }, +] + +[[package]] +name = "email-validator" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521 }, +] + +[[package]] +name = "emoji" +version = "2.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/7d/01cddcbb6f5cc0ba72e00ddf9b1fa206c802d557fd0a20b18e130edf1336/emoji-2.14.1.tar.gz", hash = "sha256:f8c50043d79a2c1410ebfae833ae1868d5941a67a6cd4d18377e2eb0bd79346b", size = 597182 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/db/a0335710caaa6d0aebdaa65ad4df789c15d89b7babd9a30277838a7d9aac/emoji-2.14.1-py3-none-any.whl", hash = "sha256:35a8a486c1460addb1499e3bf7929d3889b2e2841a57401903699fef595e942b", size = 590617 }, +] + +[[package]] +name = "et-xmlfile" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059 }, +] + +[[package]] +name = "exa-py" +version = "1.14.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "openai" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/02/6a328cb347c7ec36d84e0f746e1abf5cdc0f09276d14e40d9ad0e2f5efac/exa_py-1.14.13.tar.gz", hash = "sha256:fb86287d12ef44c1386586ef811c35b8818ed008083d4c884ee55964bfd5cbf1", size = 32576 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/d1/0ab4e64bd859fdda9b27a97b883688bf969768ccc7d166463cbeeee3a9ed/exa_py-1.14.13-py3-none-any.whl", hash = "sha256:d5d6331181d08bff91205fbaf91db4c52685d7dc66b01377b192c955688cb5c5", size = 42051 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674 }, +] + +[[package]] +name = "fake-http-header" +version = "0.3.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/0b/2849c87d9f13766e29c0a2f4d31681aa72e035016b251ab19d99bde7b592/fake_http_header-0.3.5-py3-none-any.whl", hash = "sha256:cd05f4bebf1b7e38b5f5c03d7fb820c0c17e87d9614fbee0afa39c32c7a2ad3c", size = 14938 }, +] + +[[package]] +name = "faker" +version = "19.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/b1/82df1201efdf0216f656caf7f7052f2fedf6bbb86998cac8e8926bca621e/Faker-19.13.0.tar.gz", hash = "sha256:14ccb0aec342d33aa3889a864a56e5b3c2d56bce1b89f9189f4fbc128b9afc1e", size = 1699872 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/d6/314868f573b09d9f0590a2c2f7dd7463153d3dab1049f0ba5e7008776d91/Faker-19.13.0-py3-none-any.whl", hash = "sha256:da880a76322db7a879c848a0771e129338e0a680a9f695fd9a3e7a6ac82b45e1", size = 1737393 }, +] + +[[package]] +name = "fastapi" +version = "0.115.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/64/ec0788201b5554e2a87c49af26b77a4d132f807a0fa9675257ac92c6aa0e/fastapi-0.115.13.tar.gz", hash = "sha256:55d1d25c2e1e0a0a50aceb1c8705cd932def273c102bff0b1c1da88b3c6eb307", size = 295680 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/4a/e17764385382062b0edbb35a26b7cf76d71e27e456546277a42ba6545c6e/fastapi-0.115.13-py3-none-any.whl", hash = "sha256:0a0cab59afa7bab22f5eb347f8c9864b681558c278395e94035a741fc10cd865", size = 95315 }, +] + +[[package]] +name = "fastapi-babel" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "fastapi" }, + { name = "uvicorn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ea/0d/271af537fddc3c08e5f6e36c4e9f12c55d98ff9240a37454125b3c772fd2/fastapi_babel-1.0.0.tar.gz", hash = "sha256:a70005e132b6cfc611a5a02601c63bcd26a1b1cb689d7295be4587c9d35402f3", size = 12937 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/d5/bc9fa86cb3fb3fa040c5841562ea874bbdc069b660739a314cf75df06200/fastapi_babel-1.0.0-py3-none-any.whl", hash = "sha256:9be639b098dd07dfe5b811df318abdd7e282622ac20304d909151f46b09a087d", size = 11789 }, +] + +[[package]] +name = "fastapi-filter" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastapi" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/ed/c36cfcd849519fd2d23051ad81a91fc5e8cfa7109496fc8a10ad565a5fe9/fastapi_filter-2.0.1.tar.gz", hash = "sha256:cffda370097af7e404f1eb188aca58b199084bfaf7cec881e40b404adf12566e", size = 9857 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/88/afc022ad64d12f730141fc50758ecf9d60de5fed11335dc16e3127617f05/fastapi_filter-2.0.1-py3-none-any.whl", hash = "sha256:711d48707ec62f7c9e12a7713fc0f6a99858a9e3741b4d108102d5599e77197d", size = 11586 }, +] + +[[package]] +name = "fastapi-pagination" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastapi" }, + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/25/fc3c9ed9d99df279acdbf57da1a3a91ffceae9c087882cbe8a9cb1229b1e/fastapi_pagination-0.13.2.tar.gz", hash = "sha256:5e76f129aef706601b86114428ca3ff68715bfa3929bf4df8c7ed27561d7f661", size = 550389 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/cb/cf2f10d4620b31a77705226c7292f39b4a191cef3485ea42561fc2e157d9/fastapi_pagination-0.13.2-py3-none-any.whl", hash = "sha256:d2ec66ffda5cd9c1d665521f3916b16ebbb15d5010a945449292540ef70c4d9a", size = 50404 }, +] + +[[package]] +name = "feedfinder2" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "requests" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/82/1251fefec3bb4b03fd966c7e7f7a41c9fc2bb00d823a34c13f847fd61406/feedfinder2-0.0.4.tar.gz", hash = "sha256:3701ee01a6c85f8b865a049c30ba0b4608858c803fe8e30d1d289fdbe89d0efe", size = 3297 } + +[[package]] +name = "feedparser" +version = "6.0.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sgmllib3k" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/aa/7af346ebeb42a76bf108027fe7f3328bb4e57a3a96e53e21fd9ef9dd6dd0/feedparser-6.0.11.tar.gz", hash = "sha256:c9d0407b64c6f2a065d0ebb292c2b35c01050cc0dc33757461aaabdc4c4184d5", size = 286197 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/d4/8c31aad9cc18f451c49f7f9cfb5799dadffc88177f7917bc90a66459b1d7/feedparser-6.0.11-py3-none-any.whl", hash = "sha256:0be7ee7b395572b19ebeb1d6aafb0028dee11169f1c934e0ed67d54992f4ad45", size = 81343 }, +] + +[[package]] +name = "ffmpeg-python" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "future" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/5e/d5f9105d59c1325759d838af4e973695081fbbc97182baf73afc78dec266/ffmpeg-python-0.2.0.tar.gz", hash = "sha256:65225db34627c578ef0e11c8b1eb528bb35e024752f6f10b78c011f6f64c4127", size = 21543 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/0c/56be52741f75bad4dc6555991fabd2e07b432d333da82c11ad701123888a/ffmpeg_python-0.2.0-py3-none-any.whl", hash = "sha256:ac441a0404e053f8b6a1113a77c0f452f1cfc62f6344a769475ffdc0f56c23c5", size = 25024 }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, +] + +[[package]] +name = "filetype" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970 }, +] + +[[package]] +name = "firecrawl" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "nest-asyncio" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "requests" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/de/a8d5a52d916cb4936839f553ddd60f43385b2627dbef2974c800a78385d2/firecrawl-2.9.0.tar.gz", hash = "sha256:28dfdd5438967bd65af25578ed725f283507c3c9126ee17b2a37be42fcd32951", size = 38902 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/0e/38ddb0efcdf3c03ff40e7f32c1b9decc7ac336c0a7d2948c4fe6c7f99e97/firecrawl-2.9.0-py3-none-any.whl", hash = "sha256:bb12c7a7dc2295e35c9d39a99840179231e45ce462fb9b6d44ccc1fa5c03774f", size = 38752 }, +] + +[[package]] +name = "flatbuffers" +version = "25.2.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/30/eb5dce7994fc71a2f685d98ec33cc660c0a5887db5610137e60d8cbc4489/flatbuffers-25.2.10.tar.gz", hash = "sha256:97e451377a41262f8d9bd4295cc836133415cc03d8cb966410a4af92eb00d26e", size = 22170 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/25/155f9f080d5e4bc0082edfda032ea2bc2b8fab3f4d25d46c1e9dd22a1a89/flatbuffers-25.2.10-py2.py3-none-any.whl", hash = "sha256:ebba5f4d5ea615af3f7fd70fc310636fbb2bbd1f566ac0a23d98dd412de50051", size = 30953 }, +] + +[[package]] +name = "fonttools" +version = "4.58.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/5a/1124b2c8cb3a8015faf552e92714040bcdbc145dfa29928891b02d147a18/fonttools-4.58.4.tar.gz", hash = "sha256:928a8009b9884ed3aae17724b960987575155ca23c6f0b8146e400cc9e0d44ba", size = 3525026 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/86/d22c24caa574449b56e994ed1a96d23b23af85557fb62a92df96439d3f6c/fonttools-4.58.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:834542f13fee7625ad753b2db035edb674b07522fcbdd0ed9e9a9e2a1034467f", size = 2748349 }, + { url = "https://files.pythonhosted.org/packages/f9/b8/384aca93856def00e7de30341f1e27f439694857d82c35d74a809c705ed0/fonttools-4.58.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2e6c61ce330142525296170cd65666e46121fc0d44383cbbcfa39cf8f58383df", size = 2318565 }, + { url = "https://files.pythonhosted.org/packages/1a/f2/273edfdc8d9db89ecfbbf659bd894f7e07b6d53448b19837a4bdba148d17/fonttools-4.58.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9c75f8faa29579c0fbf29b56ae6a3660c6c025f3b671803cb6a9caa7e4e3a98", size = 4838855 }, + { url = "https://files.pythonhosted.org/packages/13/fa/403703548c093c30b52ab37e109b369558afa221130e67f06bef7513f28a/fonttools-4.58.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:88dedcedbd5549e35b2ea3db3de02579c27e62e51af56779c021e7b33caadd0e", size = 4767637 }, + { url = "https://files.pythonhosted.org/packages/6e/a8/3380e1e0bff6defb0f81c9abf274a5b4a0f30bc8cab4fd4e346c6f923b4c/fonttools-4.58.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ae80a895adab43586f4da1521d58fd4f4377cef322ee0cc205abcefa3a5effc3", size = 4819397 }, + { url = "https://files.pythonhosted.org/packages/cd/1b/99e47eb17a8ca51d808622a4658584fa8f340857438a4e9d7ac326d4a041/fonttools-4.58.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0d3acc7f0d151da116e87a182aefb569cf0a3c8e0fd4c9cd0a7c1e7d3e7adb26", size = 4926641 }, + { url = "https://files.pythonhosted.org/packages/31/75/415254408f038e35b36c8525fc31feb8561f98445688dd2267c23eafd7a2/fonttools-4.58.4-cp310-cp310-win32.whl", hash = "sha256:1244f69686008e7e8d2581d9f37eef330a73fee3843f1107993eb82c9d306577", size = 2201917 }, + { url = "https://files.pythonhosted.org/packages/c5/69/f019a15ed2946317c5318e1bcc8876f8a54a313848604ad1d4cfc4c07916/fonttools-4.58.4-cp310-cp310-win_amd64.whl", hash = "sha256:2a66c0af8a01eb2b78645af60f3b787de5fe5eb1fd8348163715b80bdbfbde1f", size = 2246327 }, + { url = "https://files.pythonhosted.org/packages/0b/2f/c536b5b9bb3c071e91d536a4d11f969e911dbb6b227939f4c5b0bca090df/fonttools-4.58.4-py3-none-any.whl", hash = "sha256:a10ce13a13f26cbb9f37512a4346bb437ad7e002ff6fa966a7ce7ff5ac3528bd", size = 1114660 }, +] + +[[package]] +name = "frozenlist" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/36/0da0a49409f6b47cc2d060dc8c9040b897b5902a8a4e37d9bc1deb11f680/frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a", size = 81304 }, + { url = "https://files.pythonhosted.org/packages/77/f0/77c11d13d39513b298e267b22eb6cb559c103d56f155aa9a49097221f0b6/frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61", size = 47735 }, + { url = "https://files.pythonhosted.org/packages/37/12/9d07fa18971a44150593de56b2f2947c46604819976784bcf6ea0d5db43b/frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d", size = 46775 }, + { url = "https://files.pythonhosted.org/packages/70/34/f73539227e06288fcd1f8a76853e755b2b48bca6747e99e283111c18bcd4/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e", size = 224644 }, + { url = "https://files.pythonhosted.org/packages/fb/68/c1d9c2f4a6e438e14613bad0f2973567586610cc22dcb1e1241da71de9d3/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9", size = 222125 }, + { url = "https://files.pythonhosted.org/packages/b9/d0/98e8f9a515228d708344d7c6986752be3e3192d1795f748c24bcf154ad99/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c", size = 233455 }, + { url = "https://files.pythonhosted.org/packages/79/df/8a11bcec5600557f40338407d3e5bea80376ed1c01a6c0910fcfdc4b8993/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981", size = 227339 }, + { url = "https://files.pythonhosted.org/packages/50/82/41cb97d9c9a5ff94438c63cc343eb7980dac4187eb625a51bdfdb7707314/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615", size = 212969 }, + { url = "https://files.pythonhosted.org/packages/13/47/f9179ee5ee4f55629e4f28c660b3fdf2775c8bfde8f9c53f2de2d93f52a9/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50", size = 222862 }, + { url = "https://files.pythonhosted.org/packages/1a/52/df81e41ec6b953902c8b7e3a83bee48b195cb0e5ec2eabae5d8330c78038/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa", size = 222492 }, + { url = "https://files.pythonhosted.org/packages/84/17/30d6ea87fa95a9408245a948604b82c1a4b8b3e153cea596421a2aef2754/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577", size = 238250 }, + { url = "https://files.pythonhosted.org/packages/8f/00/ecbeb51669e3c3df76cf2ddd66ae3e48345ec213a55e3887d216eb4fbab3/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59", size = 218720 }, + { url = "https://files.pythonhosted.org/packages/1a/c0/c224ce0e0eb31cc57f67742071bb470ba8246623c1823a7530be0e76164c/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e", size = 232585 }, + { url = "https://files.pythonhosted.org/packages/55/3c/34cb694abf532f31f365106deebdeac9e45c19304d83cf7d51ebbb4ca4d1/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd", size = 234248 }, + { url = "https://files.pythonhosted.org/packages/98/c0/2052d8b6cecda2e70bd81299e3512fa332abb6dcd2969b9c80dfcdddbf75/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718", size = 221621 }, + { url = "https://files.pythonhosted.org/packages/c5/bf/7dcebae315436903b1d98ffb791a09d674c88480c158aa171958a3ac07f0/frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e", size = 39578 }, + { url = "https://files.pythonhosted.org/packages/8f/5f/f69818f017fa9a3d24d1ae39763e29b7f60a59e46d5f91b9c6b21622f4cd/frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464", size = 43830 }, + { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106 }, +] + +[[package]] +name = "fsspec" +version = "2025.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/f4/5721faf47b8c499e776bc34c6a8fc17efdf7fdef0b00f398128bc5dcb4ac/fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972", size = 298491 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/53/eb690efa8513166adef3e0669afd31e95ffde69fb3c52ec2ac7223ed6018/fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3", size = 193615 }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp" }, +] + +[[package]] +name = "future" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/b2/4140c69c6a66432916b26158687e821ba631a4c9273c474343badf84d3ba/future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05", size = 1228490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326 }, +] + +[[package]] +name = "google-api-core" +version = "2.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "googleapis-common-protos" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/21/e9d043e88222317afdbdb567165fdbc3b0aad90064c7e0c9eb0ad9955ad8/google_api_core-2.25.1.tar.gz", hash = "sha256:d2aaa0b13c78c61cb3f4282c464c046e45fbd75755683c9c525e6e8f7ed0a5e8", size = 165443 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/4b/ead00905132820b623732b175d66354e9d3e69fcf2a5dcdab780664e7896/google_api_core-2.25.1-py3-none-any.whl", hash = "sha256:8a2a56c1fef82987a524371f99f3bd0143702fecc670c72e600c1cda6bf8dbb7", size = 160807 }, +] + +[[package]] +name = "google-api-python-client" +version = "2.176.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, + { name = "google-auth-httplib2" }, + { name = "httplib2" }, + { name = "uritemplate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/38/daf70faf6d05556d382bac640bc6765f09fcfb9dfb51ac4a595d3453a2a9/google_api_python_client-2.176.0.tar.gz", hash = "sha256:2b451cdd7fd10faeb5dd20f7d992f185e1e8f4124c35f2cdcc77c843139a4cf1", size = 13154773 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/2c/758f415a19a12c3c6d06902794b0dd4c521d912a59b98ab752bba48812df/google_api_python_client-2.176.0-py3-none-any.whl", hash = "sha256:e22239797f1d085341e12cd924591fc65c56d08e0af02549d7606092e6296510", size = 13678445 }, +] + +[[package]] +name = "google-auth" +version = "2.40.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137 }, +] + +[[package]] +name = "google-auth-httplib2" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "httplib2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/be/217a598a818567b28e859ff087f347475c807a5649296fb5a817c58dacef/google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05", size = 10842 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/8a/fe34d2f3f9470a27b01c9e76226965863f153d5fbe276f83608562e49c04/google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d", size = 9253 }, +] + +[[package]] +name = "google-auth-oauthlib" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "requests-oauthlib" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/87/e10bf24f7bcffc1421b84d6f9c3377c30ec305d082cd737ddaa6d8f77f7c/google_auth_oauthlib-1.2.2.tar.gz", hash = "sha256:11046fb8d3348b296302dd939ace8af0a724042e8029c1b872d87fabc9f41684", size = 20955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/84/40ee070be95771acd2f4418981edb834979424565c3eec3cd88b6aa09d24/google_auth_oauthlib-1.2.2-py3-none-any.whl", hash = "sha256:fd619506f4b3908b5df17b65f39ca8d66ea56986e5472eb5978fd8f3786f00a2", size = 19072 }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.70.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530 }, +] + +[[package]] +name = "greenlet" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/92/bb85bd6e80148a4d2e0c59f7c0c2891029f8fd510183afc7d8d2feeed9b6/greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365", size = 185752 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/db/b4c12cff13ebac2786f4f217f06588bccd8b53d260453404ef22b121fc3a/greenlet-3.2.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:1afd685acd5597349ee6d7a88a8bec83ce13c106ac78c196ee9dde7c04fe87be", size = 268977 }, + { url = "https://files.pythonhosted.org/packages/52/61/75b4abd8147f13f70986df2801bf93735c1bd87ea780d70e3b3ecda8c165/greenlet-3.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:761917cac215c61e9dc7324b2606107b3b292a8349bdebb31503ab4de3f559ac", size = 627351 }, + { url = "https://files.pythonhosted.org/packages/35/aa/6894ae299d059d26254779a5088632874b80ee8cf89a88bca00b0709d22f/greenlet-3.2.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a433dbc54e4a37e4fff90ef34f25a8c00aed99b06856f0119dcf09fbafa16392", size = 638599 }, + { url = "https://files.pythonhosted.org/packages/30/64/e01a8261d13c47f3c082519a5e9dbf9e143cc0498ed20c911d04e54d526c/greenlet-3.2.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:72e77ed69312bab0434d7292316d5afd6896192ac4327d44f3d613ecb85b037c", size = 634482 }, + { url = "https://files.pythonhosted.org/packages/47/48/ff9ca8ba9772d083a4f5221f7b4f0ebe8978131a9ae0909cf202f94cd879/greenlet-3.2.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68671180e3849b963649254a882cd544a3c75bfcd2c527346ad8bb53494444db", size = 633284 }, + { url = "https://files.pythonhosted.org/packages/e9/45/626e974948713bc15775b696adb3eb0bd708bec267d6d2d5c47bb47a6119/greenlet-3.2.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49c8cfb18fb419b3d08e011228ef8a25882397f3a859b9fe1436946140b6756b", size = 582206 }, + { url = "https://files.pythonhosted.org/packages/b1/8e/8b6f42c67d5df7db35b8c55c9a850ea045219741bb14416255616808c690/greenlet-3.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:efc6dc8a792243c31f2f5674b670b3a95d46fa1c6a912b8e310d6f542e7b0712", size = 1111412 }, + { url = "https://files.pythonhosted.org/packages/05/46/ab58828217349500a7ebb81159d52ca357da747ff1797c29c6023d79d798/greenlet-3.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:731e154aba8e757aedd0781d4b240f1225b075b4409f1bb83b05ff410582cf00", size = 1135054 }, + { url = "https://files.pythonhosted.org/packages/68/7f/d1b537be5080721c0f0089a8447d4ef72839039cdb743bdd8ffd23046e9a/greenlet-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:96c20252c2f792defe9a115d3287e14811036d51e78b3aaddbee23b69b216302", size = 296573 }, +] + +[[package]] +name = "griffe" +version = "1.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/3e/5aa9a61f7c3c47b0b52a1d930302992229d191bf4bc76447b324b731510a/griffe-1.7.3.tar.gz", hash = "sha256:52ee893c6a3a968b639ace8015bec9d36594961e156e23315c8e8e51401fa50b", size = 395137 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/c6/5c20af38c2a57c15d87f7f38bee77d63c1d2a3689f74fefaf35915dd12b2/griffe-1.7.3-py3-none-any.whl", hash = "sha256:c6b3ee30c2f0f17f30bcdef5068d6ab7a2a4f1b8bf1a3e74b56fffd21e1c5f75", size = 129303 }, +] + +[[package]] +name = "grpcio" +version = "1.67.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/53/d9282a66a5db45981499190b77790570617a604a38f3d103d0400974aeb5/grpcio-1.67.1.tar.gz", hash = "sha256:3dc2ed4cabea4dc14d5e708c2b426205956077cc5de419b4d4079315017e9732", size = 12580022 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/cd/f6ca5c49aa0ae7bc6d0757f7dae6f789569e9490a635eaabe02bc02de7dc/grpcio-1.67.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:8b0341d66a57f8a3119b77ab32207072be60c9bf79760fa609c5609f2deb1f3f", size = 5112450 }, + { url = "https://files.pythonhosted.org/packages/d4/f0/d9bbb4a83cbee22f738ee7a74aa41e09ccfb2dcea2cc30ebe8dab5b21771/grpcio-1.67.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:f5a27dddefe0e2357d3e617b9079b4bfdc91341a91565111a21ed6ebbc51b22d", size = 10937518 }, + { url = "https://files.pythonhosted.org/packages/5b/17/0c5dbae3af548eb76669887642b5f24b232b021afe77eb42e22bc8951d9c/grpcio-1.67.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:43112046864317498a33bdc4797ae6a268c36345a910de9b9c17159d8346602f", size = 5633610 }, + { url = "https://files.pythonhosted.org/packages/17/48/e000614e00153d7b2760dcd9526b95d72f5cfe473b988e78f0ff3b472f6c/grpcio-1.67.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9b929f13677b10f63124c1a410994a401cdd85214ad83ab67cc077fc7e480f0", size = 6240678 }, + { url = "https://files.pythonhosted.org/packages/64/19/a16762a70eeb8ddfe43283ce434d1499c1c409ceec0c646f783883084478/grpcio-1.67.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7d1797a8a3845437d327145959a2c0c47c05947c9eef5ff1a4c80e499dcc6fa", size = 5884528 }, + { url = "https://files.pythonhosted.org/packages/6b/dc/bd016aa3684914acd2c0c7fa4953b2a11583c2b844f3d7bae91fa9b98fbb/grpcio-1.67.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0489063974d1452436139501bf6b180f63d4977223ee87488fe36858c5725292", size = 6583680 }, + { url = "https://files.pythonhosted.org/packages/1a/93/1441cb14c874f11aa798a816d582f9da82194b6677f0f134ea53d2d5dbeb/grpcio-1.67.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9fd042de4a82e3e7aca44008ee2fb5da01b3e5adb316348c21980f7f58adc311", size = 6162967 }, + { url = "https://files.pythonhosted.org/packages/29/e9/9295090380fb4339b7e935b9d005fa9936dd573a22d147c9e5bb2df1b8d4/grpcio-1.67.1-cp310-cp310-win32.whl", hash = "sha256:638354e698fd0c6c76b04540a850bf1db27b4d2515a19fcd5cf645c48d3eb1ed", size = 3616336 }, + { url = "https://files.pythonhosted.org/packages/ce/de/7c783b8cb8f02c667ca075c49680c4aeb8b054bc69784bcb3e7c1bbf4985/grpcio-1.67.1-cp310-cp310-win_amd64.whl", hash = "sha256:608d87d1bdabf9e2868b12338cd38a79969eaf920c89d698ead08f48de9c0f9e", size = 4352071 }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, +] + +[[package]] +name = "h2" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/38/d7f80fd13e6582fb8e0df8c9a653dcc02b03ca34f4d72f34869298c5baf8/h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f", size = 2150682 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/9e/984486f2d0a0bd2b024bf4bc1c62688fcafa9e61991f041fb0e2def4a982/h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0", size = 60957 }, +] + +[[package]] +name = "hf-xet" +version = "1.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/11/b480bb7515db97d5b2b703927a59bbdd3f87e68d47dff5591aada467b4a9/hf_xet-1.1.4.tar.gz", hash = "sha256:875158df90cb13547752532ed73cad9dfaad3b29e203143838f67178418d08a4", size = 492082 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/62/3b41a7439930996530c64955874445012fd9044c82c60b34c5891c34fec6/hf_xet-1.1.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6591ab9f61ea82d261107ed90237e2ece972f6a7577d96f5f071208bbf255d1c", size = 2643151 }, + { url = "https://files.pythonhosted.org/packages/9b/9f/1744fb1d79e0ac147578b193ce29208ebb9f4636e8cdf505638f6f0a6874/hf_xet-1.1.4-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:071b0b4d4698990f746edd666c7cc42555833d22035d88db0df936677fb57d29", size = 2510687 }, + { url = "https://files.pythonhosted.org/packages/d1/a8/49a81d4f81b0d21cc758b6fca3880a85ca0d209e8425c8b3a6ef694881ca/hf_xet-1.1.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b610831e92e41182d4c028653978b844d332d492cdcba1b920d3aca4a0207e", size = 3057631 }, + { url = "https://files.pythonhosted.org/packages/bf/8b/65fa08273789dafbc38d0f0bdd20df56b63ebc6566981bbaa255d9d84a33/hf_xet-1.1.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f6578bcd71393abfd60395279cc160ca808b61f5f9d535b922fcdcd3f77a708d", size = 2949250 }, + { url = "https://files.pythonhosted.org/packages/8b/4b/224340bb1d5c63b6e03e04095b4e42230848454bf4293c45cd7bdaa0c208/hf_xet-1.1.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fb2bbfa2aae0e4f0baca988e7ba8d8c1a39a25adf5317461eb7069ad00505b3e", size = 3124670 }, + { url = "https://files.pythonhosted.org/packages/4a/b7/4be010014de6585401c32a04c46b09a4a842d66bd16ed549a401e973b74b/hf_xet-1.1.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:73346ba3e2e15ea8909a26b0862b458f15b003e6277935e3fba5bf273508d698", size = 3234131 }, + { url = "https://files.pythonhosted.org/packages/c2/2d/cf148d532f741fbf93f380ff038a33c1309d1e24ea629dc39d11dca08c92/hf_xet-1.1.4-cp37-abi3-win_amd64.whl", hash = "sha256:52e8f8bc2029d8b911493f43cea131ac3fa1f0dc6a13c50b593c4516f02c6fc3", size = 2695589 }, +] + +[[package]] +name = "hpack" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357 }, +] + +[[package]] +name = "html2text" +version = "2025.4.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/27/e158d86ba1e82967cc2f790b0cb02030d4a8bef58e0c79a8590e9678107f/html2text-2025.4.15.tar.gz", hash = "sha256:948a645f8f0bc3abe7fd587019a2197a12436cd73d0d4908af95bfc8da337588", size = 64316 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/84/1a0f9555fd5f2b1c924ff932d99b40a0f8a6b12f6dd625e2a47f415b00ea/html2text-2025.4.15-py3-none-any.whl", hash = "sha256:00569167ffdab3d7767a4cdf589b7f57e777a5ed28d12907d8c58769ec734acc", size = 34656 }, +] + +[[package]] +name = "html5lib" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173 }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, +] + +[[package]] +name = "httplib2" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/ad/2371116b22d616c194aa25ec410c9c6c37f23599dcd590502b74db197584/httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81", size = 351116 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/6c/d2fbdaaa5959339d53ba38e94c123e4e84b8fbc4b84beb0e70d7c1608486/httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc", size = 96854 }, +] + +[[package]] +name = "httptools" +version = "0.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/6f/972f8eb0ea7d98a1c6be436e2142d51ad2a64ee18e02b0e7ff1f62171ab1/httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0", size = 198780 }, + { url = "https://files.pythonhosted.org/packages/6a/b0/17c672b4bc5c7ba7f201eada4e96c71d0a59fbc185e60e42580093a86f21/httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da", size = 103297 }, + { url = "https://files.pythonhosted.org/packages/92/5e/b4a826fe91971a0b68e8c2bd4e7db3e7519882f5a8ccdb1194be2b3ab98f/httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1", size = 443130 }, + { url = "https://files.pythonhosted.org/packages/b0/51/ce61e531e40289a681a463e1258fa1e05e0be54540e40d91d065a264cd8f/httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50", size = 442148 }, + { url = "https://files.pythonhosted.org/packages/ea/9e/270b7d767849b0c96f275c695d27ca76c30671f8eb8cc1bab6ced5c5e1d0/httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959", size = 415949 }, + { url = "https://files.pythonhosted.org/packages/81/86/ced96e3179c48c6f656354e106934e65c8963d48b69be78f355797f0e1b3/httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4", size = 417591 }, + { url = "https://files.pythonhosted.org/packages/75/73/187a3f620ed3175364ddb56847d7a608a6fc42d551e133197098c0143eca/httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c", size = 88344 }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, +] + +[package.optional-dependencies] +http2 = [ + { name = "h2" }, +] +socks = [ + { name = "socksio" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, +] + +[[package]] +name = "huggingface-hub" +version = "0.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/8a/1362d565fefabaa4185cf3ae842a98dbc5b35146f5694f7080f043a6952f/huggingface_hub-0.33.0.tar.gz", hash = "sha256:aa31f70d29439d00ff7a33837c03f1f9dd83971ce4e29ad664d63ffb17d3bb97", size = 426179 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/fb/53587a89fbc00799e4179796f51b3ad713c5de6bb680b2becb6d37c94649/huggingface_hub-0.33.0-py3-none-any.whl", hash = "sha256:e8668875b40c68f9929150d99727d39e5ebb8a05a98e4191b908dc7ded9074b3", size = 514799 }, +] + +[[package]] +name = "humanfriendly" +version = "10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyreadline3", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794 }, +] + +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "imageio" +version = "2.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/47/57e897fb7094afb2d26e8b2e4af9a45c7cf1a405acdeeca001fdf2c98501/imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996", size = 389963 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed", size = 315796 }, +] + +[package.optional-dependencies] +pyav = [ + { name = "av" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656 }, +] + +[[package]] +name = "inflection" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/7e/691d061b7329bc8d54edbf0ec22fbfb2afe61facb681f9aaa9bff7a27d04/inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", size = 15091 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2", size = 9454 }, +] + +[[package]] +name = "jieba3k" +version = "0.35.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/cb/2c8332bcdc14d33b0bedd18ae0a4981a069c3513e445120da3c3f23a8aaa/jieba3k-0.35.1.zip", hash = "sha256:980a4f2636b778d312518066be90c7697d410dd5a472385f5afced71a2db1c10", size = 7423646 } + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "jiter" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/7e/4011b5c77bec97cb2b572f566220364e3e21b51c48c5bd9c4a9c26b41b67/jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303", size = 317215 }, + { url = "https://files.pythonhosted.org/packages/8a/4f/144c1b57c39692efc7ea7d8e247acf28e47d0912800b34d0ad815f6b2824/jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e", size = 322814 }, + { url = "https://files.pythonhosted.org/packages/63/1f/db977336d332a9406c0b1f0b82be6f71f72526a806cbb2281baf201d38e3/jiter-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8b3e0068c26ddedc7abc6fac37da2d0af16b921e288a5a613f4b86f050354f", size = 345237 }, + { url = "https://files.pythonhosted.org/packages/d7/1c/aa30a4a775e8a672ad7f21532bdbfb269f0706b39c6ff14e1f86bdd9e5ff/jiter-0.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:286299b74cc49e25cd42eea19b72aa82c515d2f2ee12d11392c56d8701f52224", size = 370999 }, + { url = "https://files.pythonhosted.org/packages/35/df/f8257abc4207830cb18880781b5f5b716bad5b2a22fb4330cfd357407c5b/jiter-0.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ed5649ceeaeffc28d87fb012d25a4cd356dcd53eff5acff1f0466b831dda2a7", size = 491109 }, + { url = "https://files.pythonhosted.org/packages/06/76/9e1516fd7b4278aa13a2cc7f159e56befbea9aa65c71586305e7afa8b0b3/jiter-0.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2ab0051160cb758a70716448908ef14ad476c3774bd03ddce075f3c1f90a3d6", size = 388608 }, + { url = "https://files.pythonhosted.org/packages/6d/64/67750672b4354ca20ca18d3d1ccf2c62a072e8a2d452ac3cf8ced73571ef/jiter-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03997d2f37f6b67d2f5c475da4412be584e1cec273c1cfc03d642c46db43f8cf", size = 352454 }, + { url = "https://files.pythonhosted.org/packages/96/4d/5c4e36d48f169a54b53a305114be3efa2bbffd33b648cd1478a688f639c1/jiter-0.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c404a99352d839fed80d6afd6c1d66071f3bacaaa5c4268983fc10f769112e90", size = 391833 }, + { url = "https://files.pythonhosted.org/packages/0b/de/ce4a6166a78810bd83763d2fa13f85f73cbd3743a325469a4a9289af6dae/jiter-0.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66e989410b6666d3ddb27a74c7e50d0829704ede652fd4c858e91f8d64b403d0", size = 523646 }, + { url = "https://files.pythonhosted.org/packages/a2/a6/3bc9acce53466972964cf4ad85efecb94f9244539ab6da1107f7aed82934/jiter-0.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b532d3af9ef4f6374609a3bcb5e05a1951d3bf6190dc6b176fdb277c9bbf15ee", size = 514735 }, + { url = "https://files.pythonhosted.org/packages/b4/d8/243c2ab8426a2a4dea85ba2a2ba43df379ccece2145320dfd4799b9633c5/jiter-0.10.0-cp310-cp310-win32.whl", hash = "sha256:da9be20b333970e28b72edc4dff63d4fec3398e05770fb3205f7fb460eb48dd4", size = 210747 }, + { url = "https://files.pythonhosted.org/packages/37/7a/8021bd615ef7788b98fc76ff533eaac846322c170e93cbffa01979197a45/jiter-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:f59e533afed0c5b0ac3eba20d2548c4a550336d8282ee69eb07b37ea526ee4e5", size = 207484 }, +] + +[[package]] +name = "joblib" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/fe/0f5a938c54105553436dbff7a61dc4fed4b1b2c98852f8833beaf4d5968f/joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444", size = 330475 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/4f/1195bbac8e0c2acc5f740661631d8d750dc38d4a32b23ee5df3cde6f4e0d/joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a", size = 307746 }, +] + +[[package]] +name = "jsonschema" +version = "4.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/d3/1cf5326b923a53515d8f3a2cd442e6d7e94fcc444716e879ea70a0ce3177/jsonschema-4.24.0.tar.gz", hash = "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196", size = 353480 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/3d/023389198f69c722d039351050738d6755376c8fd343e91dc493ea485905/jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d", size = 88709 }, +] + +[[package]] +name = "jsonschema-path" +version = "0.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pathable" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/45/41ebc679c2a4fced6a722f624c18d658dee42612b83ea24c1caf7c0eb3a8/jsonschema_path-0.3.4.tar.gz", hash = "sha256:8365356039f16cc65fddffafda5f58766e34bebab7d6d105616ab52bc4297001", size = 11159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/58/3485da8cb93d2f393bce453adeef16896751f14ba3e2024bc21dc9597646/jsonschema_path-0.3.4-py3-none-any.whl", hash = "sha256:f502191fdc2b22050f9a81c9237be9d27145b9001c55842bece5e94e382e52f8", size = 14810 }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437 }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/5f/4d8e9e852d98ecd26cdf8eaf7ed8bc33174033bba5e07001b289f07308fd/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db", size = 124623 }, + { url = "https://files.pythonhosted.org/packages/1d/70/7f5af2a18a76fe92ea14675f8bd88ce53ee79e37900fa5f1a1d8e0b42998/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b", size = 66720 }, + { url = "https://files.pythonhosted.org/packages/c6/13/e15f804a142353aefd089fadc8f1d985561a15358c97aca27b0979cb0785/kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d", size = 65413 }, + { url = "https://files.pythonhosted.org/packages/ce/6d/67d36c4d2054e83fb875c6b59d0809d5c530de8148846b1370475eeeece9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d", size = 1650826 }, + { url = "https://files.pythonhosted.org/packages/de/c6/7b9bb8044e150d4d1558423a1568e4f227193662a02231064e3824f37e0a/kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c", size = 1628231 }, + { url = "https://files.pythonhosted.org/packages/b6/38/ad10d437563063eaaedbe2c3540a71101fc7fb07a7e71f855e93ea4de605/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3", size = 1408938 }, + { url = "https://files.pythonhosted.org/packages/52/ce/c0106b3bd7f9e665c5f5bc1e07cc95b5dabd4e08e3dad42dbe2faad467e7/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed", size = 1422799 }, + { url = "https://files.pythonhosted.org/packages/d0/87/efb704b1d75dc9758087ba374c0f23d3254505edaedd09cf9d247f7878b9/kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f", size = 1354362 }, + { url = "https://files.pythonhosted.org/packages/eb/b3/fd760dc214ec9a8f208b99e42e8f0130ff4b384eca8b29dd0efc62052176/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff", size = 2222695 }, + { url = "https://files.pythonhosted.org/packages/a2/09/a27fb36cca3fc01700687cc45dae7a6a5f8eeb5f657b9f710f788748e10d/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d", size = 2370802 }, + { url = "https://files.pythonhosted.org/packages/3d/c3/ba0a0346db35fe4dc1f2f2cf8b99362fbb922d7562e5f911f7ce7a7b60fa/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c", size = 2334646 }, + { url = "https://files.pythonhosted.org/packages/41/52/942cf69e562f5ed253ac67d5c92a693745f0bed3c81f49fc0cbebe4d6b00/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605", size = 2467260 }, + { url = "https://files.pythonhosted.org/packages/32/26/2d9668f30d8a494b0411d4d7d4ea1345ba12deb6a75274d58dd6ea01e951/kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e", size = 2288633 }, + { url = "https://files.pythonhosted.org/packages/98/99/0dd05071654aa44fe5d5e350729961e7bb535372935a45ac89a8924316e6/kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751", size = 71885 }, + { url = "https://files.pythonhosted.org/packages/6c/fc/822e532262a97442989335394d441cd1d0448c2e46d26d3e04efca84df22/kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271", size = 65175 }, + { url = "https://files.pythonhosted.org/packages/1f/f9/ae81c47a43e33b93b0a9819cac6723257f5da2a5a60daf46aa5c7226ea85/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a", size = 60403 }, + { url = "https://files.pythonhosted.org/packages/58/ca/f92b5cb6f4ce0c1ebfcfe3e2e42b96917e16f7090e45b21102941924f18f/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8", size = 58657 }, + { url = "https://files.pythonhosted.org/packages/80/28/ae0240f732f0484d3a4dc885d055653c47144bdf59b670aae0ec3c65a7c8/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0", size = 84948 }, + { url = "https://files.pythonhosted.org/packages/5d/eb/78d50346c51db22c7203c1611f9b513075f35c4e0e4877c5dde378d66043/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c", size = 81186 }, + { url = "https://files.pythonhosted.org/packages/43/f8/7259f18c77adca88d5f64f9a522792e178b2691f3748817a8750c2d216ef/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b", size = 80279 }, + { url = "https://files.pythonhosted.org/packages/3a/1d/50ad811d1c5dae091e4cf046beba925bcae0a610e79ae4c538f996f63ed5/kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b", size = 71762 }, +] + +[[package]] +name = "langdetect" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/72/a3add0e4eec4eb9e2569554f7c70f4a3c27712f40e3284d483e88094cc0e/langdetect-1.0.9.tar.gz", hash = "sha256:cbc1fef89f8d062739774bd51eda3da3274006b3661d199c2655f6b3f6d605a0", size = 981474 } + +[[package]] +name = "lazy-object-proxy" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/f9/1f56571ed82fb324f293661690635cf42c41deb8a70a6c9e6edc3e9bb3c8/lazy_object_proxy-1.11.0.tar.gz", hash = "sha256:18874411864c9fbbbaa47f9fc1dd7aea754c86cfde21278ef427639d1dd78e9c", size = 44736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/c8/457f1555f066f5bacc44337141294153dc993b5e9132272ab54a64ee98a2/lazy_object_proxy-1.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:132bc8a34f2f2d662a851acfd1b93df769992ed1b81e2b1fda7db3e73b0d5a18", size = 28045 }, + { url = "https://files.pythonhosted.org/packages/18/33/3260b4f8de6f0942008479fee6950b2b40af11fc37dba23aa3672b0ce8a6/lazy_object_proxy-1.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:01261a3afd8621a1accb5682df2593dc7ec7d21d38f411011a5712dcd418fbed", size = 28441 }, + { url = "https://files.pythonhosted.org/packages/e7/1e/fb441c07b6662ec1fc92b249225ba6e6e5221b05623cb0131d082f782edc/lazy_object_proxy-1.11.0-py3-none-any.whl", hash = "sha256:a56a5093d433341ff7da0e89f9b486031ccd222ec8e52ec84d0ec1cdc819674b", size = 16635 }, +] + +[[package]] +name = "litellm" +version = "1.72.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "click" }, + { name = "httpx" }, + { name = "importlib-metadata" }, + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "openai" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "tiktoken" }, + { name = "tokenizers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/55/985c5d32af6ff43b98fbddf6739b8a225acc703ef3980d60cc4410084557/litellm-1.72.7.tar.gz", hash = "sha256:9bfdc156ebd8cb2fc869e0a388931b64468c4fd534df6a2f6e27f9cba2dafcb9", size = 8418897 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/55/78455fac9172a9b668c01b85df27e340d71df8e18771590fa45a04aec14e/litellm-1.72.7-py3-none-any.whl", hash = "sha256:704317ca71b00ca7fb164e8367e6559712605ed7f96f6d7fdb8b1276904a95e9", size = 8325244 }, +] + +[[package]] +name = "loguru" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "win32-setctime", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595 }, +] + +[[package]] +name = "lxml" +version = "5.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/3d/14e82fc7c8fb1b7761f7e748fd47e2ec8276d137b6acfe5a4bb73853e08f/lxml-5.4.0.tar.gz", hash = "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd", size = 3679479 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/1f/a3b6b74a451ceb84b471caa75c934d2430a4d84395d38ef201d539f38cd1/lxml-5.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e7bc6df34d42322c5289e37e9971d6ed114e3776b45fa879f734bded9d1fea9c", size = 8076838 }, + { url = "https://files.pythonhosted.org/packages/36/af/a567a55b3e47135b4d1f05a1118c24529104c003f95851374b3748139dc1/lxml-5.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6854f8bd8a1536f8a1d9a3655e6354faa6406621cf857dc27b681b69860645c7", size = 4381827 }, + { url = "https://files.pythonhosted.org/packages/50/ba/4ee47d24c675932b3eb5b6de77d0f623c2db6dc466e7a1f199792c5e3e3a/lxml-5.4.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:696ea9e87442467819ac22394ca36cb3d01848dad1be6fac3fb612d3bd5a12cf", size = 5204098 }, + { url = "https://files.pythonhosted.org/packages/f2/0f/b4db6dfebfefe3abafe360f42a3d471881687fd449a0b86b70f1f2683438/lxml-5.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ef80aeac414f33c24b3815ecd560cee272786c3adfa5f31316d8b349bfade28", size = 4930261 }, + { url = "https://files.pythonhosted.org/packages/0b/1f/0bb1bae1ce056910f8db81c6aba80fec0e46c98d77c0f59298c70cd362a3/lxml-5.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b9c2754cef6963f3408ab381ea55f47dabc6f78f4b8ebb0f0b25cf1ac1f7609", size = 5529621 }, + { url = "https://files.pythonhosted.org/packages/21/f5/e7b66a533fc4a1e7fa63dd22a1ab2ec4d10319b909211181e1ab3e539295/lxml-5.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a62cc23d754bb449d63ff35334acc9f5c02e6dae830d78dab4dd12b78a524f4", size = 4983231 }, + { url = "https://files.pythonhosted.org/packages/11/39/a38244b669c2d95a6a101a84d3c85ba921fea827e9e5483e93168bf1ccb2/lxml-5.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f82125bc7203c5ae8633a7d5d20bcfdff0ba33e436e4ab0abc026a53a8960b7", size = 5084279 }, + { url = "https://files.pythonhosted.org/packages/db/64/48cac242347a09a07740d6cee7b7fd4663d5c1abd65f2e3c60420e231b27/lxml-5.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:b67319b4aef1a6c56576ff544b67a2a6fbd7eaee485b241cabf53115e8908b8f", size = 4927405 }, + { url = "https://files.pythonhosted.org/packages/98/89/97442835fbb01d80b72374f9594fe44f01817d203fa056e9906128a5d896/lxml-5.4.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:a8ef956fce64c8551221f395ba21d0724fed6b9b6242ca4f2f7beb4ce2f41997", size = 5550169 }, + { url = "https://files.pythonhosted.org/packages/f1/97/164ca398ee654eb21f29c6b582685c6c6b9d62d5213abc9b8380278e9c0a/lxml-5.4.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:0a01ce7d8479dce84fc03324e3b0c9c90b1ece9a9bb6a1b6c9025e7e4520e78c", size = 5062691 }, + { url = "https://files.pythonhosted.org/packages/d0/bc/712b96823d7feb53482d2e4f59c090fb18ec7b0d0b476f353b3085893cda/lxml-5.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:91505d3ddebf268bb1588eb0f63821f738d20e1e7f05d3c647a5ca900288760b", size = 5133503 }, + { url = "https://files.pythonhosted.org/packages/d4/55/a62a39e8f9da2a8b6002603475e3c57c870cd9c95fd4b94d4d9ac9036055/lxml-5.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a3bcdde35d82ff385f4ede021df801b5c4a5bcdfb61ea87caabcebfc4945dc1b", size = 4999346 }, + { url = "https://files.pythonhosted.org/packages/ea/47/a393728ae001b92bb1a9e095e570bf71ec7f7fbae7688a4792222e56e5b9/lxml-5.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aea7c06667b987787c7d1f5e1dfcd70419b711cdb47d6b4bb4ad4b76777a0563", size = 5627139 }, + { url = "https://files.pythonhosted.org/packages/5e/5f/9dcaaad037c3e642a7ea64b479aa082968de46dd67a8293c541742b6c9db/lxml-5.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a7fb111eef4d05909b82152721a59c1b14d0f365e2be4c742a473c5d7372f4f5", size = 5465609 }, + { url = "https://files.pythonhosted.org/packages/a7/0a/ebcae89edf27e61c45023005171d0ba95cb414ee41c045ae4caf1b8487fd/lxml-5.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:43d549b876ce64aa18b2328faff70f5877f8c6dede415f80a2f799d31644d776", size = 5192285 }, + { url = "https://files.pythonhosted.org/packages/42/ad/cc8140ca99add7d85c92db8b2354638ed6d5cc0e917b21d36039cb15a238/lxml-5.4.0-cp310-cp310-win32.whl", hash = "sha256:75133890e40d229d6c5837b0312abbe5bac1c342452cf0e12523477cd3aa21e7", size = 3477507 }, + { url = "https://files.pythonhosted.org/packages/e9/39/597ce090da1097d2aabd2f9ef42187a6c9c8546d67c419ce61b88b336c85/lxml-5.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:de5b4e1088523e2b6f730d0509a9a813355b7f5659d70eb4f319c76beea2e250", size = 3805104 }, + { url = "https://files.pythonhosted.org/packages/c6/b0/e4d1cbb8c078bc4ae44de9c6a79fec4e2b4151b1b4d50af71d799e76b177/lxml-5.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1b717b00a71b901b4667226bba282dd462c42ccf618ade12f9ba3674e1fabc55", size = 3892319 }, + { url = "https://files.pythonhosted.org/packages/5b/aa/e2bdefba40d815059bcb60b371a36fbfcce970a935370e1b367ba1cc8f74/lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27a9ded0f0b52098ff89dd4c418325b987feed2ea5cc86e8860b0f844285d740", size = 4211614 }, + { url = "https://files.pythonhosted.org/packages/3c/5f/91ff89d1e092e7cfdd8453a939436ac116db0a665e7f4be0cd8e65c7dc5a/lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b7ce10634113651d6f383aa712a194179dcd496bd8c41e191cec2099fa09de5", size = 4306273 }, + { url = "https://files.pythonhosted.org/packages/be/7c/8c3f15df2ca534589717bfd19d1e3482167801caedfa4d90a575facf68a6/lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53370c26500d22b45182f98847243efb518d268374a9570409d2e2276232fd37", size = 4208552 }, + { url = "https://files.pythonhosted.org/packages/7d/d8/9567afb1665f64d73fc54eb904e418d1138d7f011ed00647121b4dd60b38/lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c6364038c519dffdbe07e3cf42e6a7f8b90c275d4d1617a69bb59734c1a2d571", size = 4331091 }, + { url = "https://files.pythonhosted.org/packages/f1/ab/fdbbd91d8d82bf1a723ba88ec3e3d76c022b53c391b0c13cad441cdb8f9e/lxml-5.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b12cb6527599808ada9eb2cd6e0e7d3d8f13fe7bbb01c6311255a15ded4c7ab4", size = 3487862 }, +] + +[[package]] +name = "magika" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "numpy" }, + { name = "onnxruntime" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/b6/8fdd991142ad3e037179a494b153f463024e5a211ef3ad948b955c26b4de/magika-0.6.2.tar.gz", hash = "sha256:37eb6ae8020f6e68f231bc06052c0a0cbe8e6fa27492db345e8dc867dbceb067", size = 3036634 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/07/4f7748f34279f2852068256992377474f9700b6fbad6735d6be58605178f/magika-0.6.2-py3-none-any.whl", hash = "sha256:5ef72fbc07723029b3684ef81454bc224ac5f60986aa0fc5a28f4456eebcb5b2", size = 2967609 }, + { url = "https://files.pythonhosted.org/packages/64/6d/0783af677e601d8a42258f0fbc47663abf435f927e58a8d2928296743099/magika-0.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9109309328a1553886c8ff36c2ee9a5e9cfd36893ad81b65bf61a57debdd9d0e", size = 12404787 }, + { url = "https://files.pythonhosted.org/packages/8a/ad/42e39748ddc4bbe55c2dc1093ce29079c04d096ac0d844f8ae66178bc3ed/magika-0.6.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:57cd1d64897634d15de552bd6b3ae9c6ff6ead9c60d384dc46497c08288e4559", size = 15091089 }, + { url = "https://files.pythonhosted.org/packages/b0/1f/28e412d0ccedc068fbccdae6a6233faaa97ec3e5e2ffd242e49655b10064/magika-0.6.2-py3-none-win_amd64.whl", hash = "sha256:711f427a633e0182737dcc2074748004842f870643585813503ff2553b973b9f", size = 12385740 }, +] + +[[package]] +name = "markdown-it-py" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/c0/59bd6d0571986f72899288a95d9d6178d0eebd70b6650f1bb3f0da90f8f7/markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1", size = 67120 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/25/2d88e8feee8e055d015343f9b86e370a1ccbec546f2865c98397aaef24af/markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30", size = 84466 }, +] + +[[package]] +name = "markdownify" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/78/c48fed23c7aebc2c16049062e72de1da3220c274de59d28c942acdc9ffb2/markdownify-1.1.0.tar.gz", hash = "sha256:449c0bbbf1401c5112379619524f33b63490a8fa479456d41de9dc9e37560ebd", size = 17127 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/11/b751af7ad41b254a802cf52f7bc1fca7cabe2388132f2ce60a1a6b9b9622/markdownify-1.1.0-py3-none-any.whl", hash = "sha256:32a5a08e9af02c8a6528942224c91b933b4bd2c7d078f9012943776fc313eeef", size = 13901 }, +] + +[[package]] +name = "markitdown" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "charset-normalizer" }, + { name = "magika" }, + { name = "markdownify" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/e8/83669ba97718bbbccd4c432b763d22783df4c8218e770717151acf01e85b/markitdown-0.1.1.tar.gz", hash = "sha256:da97a55a45a3d775ea758e88a344d5cac94ee97115fb0293f99027d32c2fc3f6", size = 31475 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/8a/c1f85ee609de5d45f80d0213bebf6664f76ab406e9d57709e684a4a436ba/markitdown-0.1.1-py3-none-any.whl", hash = "sha256:98ea8c009fe174b37ef933e00f4364214e8fed35691178b8521b13604d0c4a58", size = 48230 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, +] + +[[package]] +name = "marshmallow" +version = "3.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/5e/5e53d26b42ab75491cda89b871dab9e97c840bf12c63ec58a1919710cd06/marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6", size = 221825 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878 }, +] + +[[package]] +name = "matplotlib" +version = "3.10.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/ea/2bba25d289d389c7451f331ecd593944b3705f06ddf593fa7be75037d308/matplotlib-3.10.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:213fadd6348d106ca7db99e113f1bea1e65e383c3ba76e8556ba4a3054b65ae7", size = 8167862 }, + { url = "https://files.pythonhosted.org/packages/41/81/cc70b5138c926604e8c9ed810ed4c79e8116ba72e02230852f5c12c87ba2/matplotlib-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3bec61cb8221f0ca6313889308326e7bb303d0d302c5cc9e523b2f2e6c73deb", size = 8042149 }, + { url = "https://files.pythonhosted.org/packages/4a/9a/0ff45b6bfa42bb16de597e6058edf2361c298ad5ef93b327728145161bbf/matplotlib-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c21ae75651c0231b3ba014b6d5e08fb969c40cdb5a011e33e99ed0c9ea86ecb", size = 8453719 }, + { url = "https://files.pythonhosted.org/packages/85/c7/1866e972fed6d71ef136efbc980d4d1854ab7ef1ea8152bbd995ca231c81/matplotlib-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a49e39755580b08e30e3620efc659330eac5d6534ab7eae50fa5e31f53ee4e30", size = 8590801 }, + { url = "https://files.pythonhosted.org/packages/5d/b9/748f6626d534ab7e255bdc39dc22634d337cf3ce200f261b5d65742044a1/matplotlib-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf4636203e1190871d3a73664dea03d26fb019b66692cbfd642faafdad6208e8", size = 9402111 }, + { url = "https://files.pythonhosted.org/packages/1f/78/8bf07bd8fb67ea5665a6af188e70b57fcb2ab67057daa06b85a08e59160a/matplotlib-3.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:fd5641a9bb9d55f4dd2afe897a53b537c834b9012684c8444cc105895c8c16fd", size = 8057213 }, + { url = "https://files.pythonhosted.org/packages/3d/d1/f54d43e95384b312ffa4a74a4326c722f3b8187aaaa12e9a84cdf3037131/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:86ab63d66bbc83fdb6733471d3bff40897c1e9921cba112accd748eee4bce5e4", size = 8162896 }, + { url = "https://files.pythonhosted.org/packages/24/a4/fbfc00c2346177c95b353dcf9b5a004106abe8730a62cb6f27e79df0a698/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a48f9c08bf7444b5d2391a83e75edb464ccda3c380384b36532a0962593a1751", size = 8039702 }, + { url = "https://files.pythonhosted.org/packages/6a/b9/59e120d24a2ec5fc2d30646adb2efb4621aab3c6d83d66fb2a7a182db032/matplotlib-3.10.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb73d8aa75a237457988f9765e4dfe1c0d2453c5ca4eabc897d4309672c8e014", size = 8594298 }, +] + +[[package]] +name = "mcp" +version = "1.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/f2/dc2450e566eeccf92d89a00c3e813234ad58e2ba1e31d11467a09ac4f3b9/mcp-1.9.4.tar.gz", hash = "sha256:cfb0bcd1a9535b42edaef89947b9e18a8feb49362e1cc059d6e7fc636f2cb09f", size = 333294 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/fc/80e655c955137393c443842ffcc4feccab5b12fa7cb8de9ced90f90e6998/mcp-1.9.4-py3-none-any.whl", hash = "sha256:7fcf36b62936adb8e63f89346bccca1268eeca9bf6dfb562ee10b1dfbda9dac0", size = 130232 }, +] + +[[package]] +name = "mcp-server-fetch" +version = "2025.1.17" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdownify" }, + { name = "mcp" }, + { name = "protego" }, + { name = "pydantic" }, + { name = "readabilipy" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/76/204ac83afe2000b1513b4741229586128361f376fab03832695e0179104d/mcp_server_fetch-2025.1.17.tar.gz", hash = "sha256:aa3a5dee358651103477bc121b98ada18a5c35840c56e4016cc3b40e7df1aa7d", size = 43468 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/34/c0dce3415b627f763a9b7a0202a6a0672446b49f5ca04827340c28d75c63/mcp_server_fetch-2025.1.17-py3-none-any.whl", hash = "sha256:53c4967572464c6329824c9b05cdfa5fe214004d577ae8700fdb04203844be52", size = 7991 }, +] + +[[package]] +name = "mcp-simple-arxiv" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "feedparser" }, + { name = "httpx" }, + { name = "mcp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/d3/d47bfce067ea85bc73154d8299549f84455e601f699fcff513f9d44cef0d/mcp_simple_arxiv-0.2.2.tar.gz", hash = "sha256:e27cfd58a470dcec7d733bd09b4219daddbdc3475a6d256e246a114e5b94e817", size = 12100 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/4e/6646a0004fc85b0c1df6e662db42f76fe5a0412179b7f65c066d7804370a/mcp_simple_arxiv-0.2.2-py3-none-any.whl", hash = "sha256:fcf607303c074ae5e88337b5bf3ea52cd781081f49ddf8fa0898eb3b8420dccb", size = 13686 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "more-itertools" +version = "10.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/a0/834b0cebabbfc7e311f30b46c8188790a37f89fc8d756660346fe5abfd09/more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3", size = 127671 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278 }, +] + +[[package]] +name = "mouseinfo" +version = "0.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyperclip" }, + { name = "python3-xlib", marker = "sys_platform == 'linux'" }, + { name = "rubicon-objc", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/fa/b2ba8229b9381e8f6381c1dcae6f4159a7f72349e414ed19cfbbd1817173/MouseInfo-0.1.3.tar.gz", hash = "sha256:2c62fb8885062b8e520a3cce0a297c657adcc08c60952eb05bc8256ef6f7f6e7", size = 10850 } + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, +] + +[[package]] +name = "mslex" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/97/7022667073c99a0fe028f2e34b9bf76b49a611afd21b02527fbfd92d4cd5/mslex-1.3.0.tar.gz", hash = "sha256:641c887d1d3db610eee2af37a8e5abda3f70b3006cdfd2d0d29dc0d1ae28a85d", size = 11583 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/f2/66bd65ca0139675a0d7b18f0bada6e12b51a984e41a76dbe44761bf1b3ee/mslex-1.3.0-py3-none-any.whl", hash = "sha256:c7074b347201b3466fc077c5692fbce9b5f62a63a51f537a53fbbd02eff2eea4", size = 7820 }, +] + +[[package]] +name = "multidict" +version = "6.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/b5/59f27b4ce9951a4bce56b88ba5ff5159486797ab18863f2b4c1c5e8465bd/multidict-6.5.0.tar.gz", hash = "sha256:942bd8002492ba819426a8d7aefde3189c1b87099cdf18aaaefefcf7f3f7b6d2", size = 98512 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/88/f8354ef1cb1121234c3461ff3d11eac5f4fe115f00552d3376306275c9ab/multidict-6.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e118a202904623b1d2606d1c8614e14c9444b59d64454b0c355044058066469", size = 73858 }, + { url = "https://files.pythonhosted.org/packages/49/04/634b49c7abe71bd1c61affaeaa0c2a46b6be8d599a07b495259615dbdfe0/multidict-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a42995bdcaff4e22cb1280ae7752c3ed3fbb398090c6991a2797a4a0e5ed16a9", size = 43186 }, + { url = "https://files.pythonhosted.org/packages/3b/ff/091ff4830ec8f96378578bfffa7f324a9dd16f60274cec861ae65ba10be3/multidict-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2261b538145723ca776e55208640fffd7ee78184d223f37c2b40b9edfe0e818a", size = 43031 }, + { url = "https://files.pythonhosted.org/packages/10/c1/1b4137845f8b8dbc2332af54e2d7761c6a29c2c33c8d47a0c8c70676bac1/multidict-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e5b19f8cd67235fab3e195ca389490415d9fef5a315b1fa6f332925dc924262", size = 233588 }, + { url = "https://files.pythonhosted.org/packages/c3/77/cbe9a1f58c6d4f822663788e414637f256a872bc352cedbaf7717b62db58/multidict-6.5.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:177b081e4dec67c3320b16b3aa0babc178bbf758553085669382c7ec711e1ec8", size = 222714 }, + { url = "https://files.pythonhosted.org/packages/6c/37/39e1142c2916973818515adc13bbdb68d3d8126935e3855200e059a79bab/multidict-6.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d30a2cc106a7d116b52ee046207614db42380b62e6b1dd2a50eba47c5ca5eb1", size = 242741 }, + { url = "https://files.pythonhosted.org/packages/a3/aa/60c3ef0c87ccad3445bf01926a1b8235ee24c3dde483faef1079cc91706d/multidict-6.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a72933bc308d7a64de37f0d51795dbeaceebdfb75454f89035cdfc6a74cfd129", size = 235008 }, + { url = "https://files.pythonhosted.org/packages/bf/5e/f7e0fd5f5b8a7b9a75b0f5642ca6b6dde90116266920d8cf63b513f3908b/multidict-6.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96d109e663d032280ef8ef62b50924b2e887d5ddf19e301844a6cb7e91a172a6", size = 226627 }, + { url = "https://files.pythonhosted.org/packages/b7/74/1bc0a3c6a9105051f68a6991fe235d7358836e81058728c24d5bbdd017cb/multidict-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b555329c9894332401f03b9a87016f0b707b6fccd4706793ec43b4a639e75869", size = 228232 }, + { url = "https://files.pythonhosted.org/packages/99/e7/37118291cdc31f4cc680d54047cdea9b520e9a724a643919f71f8c2a2aeb/multidict-6.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6994bad9d471ef2156f2b6850b51e20ee409c6b9deebc0e57be096be9faffdce", size = 246616 }, + { url = "https://files.pythonhosted.org/packages/ff/89/e2c08d6bdb21a1a55be4285510d058ace5f5acabe6b57900432e863d4c70/multidict-6.5.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:b15f817276c96cde9060569023808eec966bd8da56a97e6aa8116f34ddab6534", size = 235007 }, + { url = "https://files.pythonhosted.org/packages/89/1e/e39a98e8e1477ec7a871b3c17265658fbe6d617048059ae7fa5011b224f3/multidict-6.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b4bf507c991db535a935b2127cf057a58dbc688c9f309c72080795c63e796f58", size = 244824 }, + { url = "https://files.pythonhosted.org/packages/a3/ba/63e11edd45c31e708c5a1904aa7ac4de01e13135a04cfe96bc71eb359b85/multidict-6.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:60c3f8f13d443426c55f88cf3172547bbc600a86d57fd565458b9259239a6737", size = 257229 }, + { url = "https://files.pythonhosted.org/packages/0f/00/bdcceb6af424936adfc8b92a79d3a95863585f380071393934f10a63f9e3/multidict-6.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a10227168a24420c158747fc201d4279aa9af1671f287371597e2b4f2ff21879", size = 247118 }, + { url = "https://files.pythonhosted.org/packages/b6/a0/4aa79e991909cca36ca821a9ba5e8e81e4cd5b887c81f89ded994e0f49df/multidict-6.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e3b1425fe54ccfde66b8cfb25d02be34d5dfd2261a71561ffd887ef4088b4b69", size = 243948 }, + { url = "https://files.pythonhosted.org/packages/21/8b/e45e19ce43afb31ff6b0fd5d5816b4fcc1fcc2f37e8a82aefae06c40c7a6/multidict-6.5.0-cp310-cp310-win32.whl", hash = "sha256:b4e47ef51237841d1087e1e1548071a6ef22e27ed0400c272174fa585277c4b4", size = 40433 }, + { url = "https://files.pythonhosted.org/packages/d2/6e/96e0ba4601343d9344e69503fca072ace19c35f7d4ca3d68401e59acdc8f/multidict-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:63b3b24fadc7067282c88fae5b2f366d5b3a7c15c021c2838de8c65a50eeefb4", size = 44423 }, + { url = "https://files.pythonhosted.org/packages/eb/4a/9befa919d7a390f13a5511a69282b7437782071160c566de6e0ebf712c9f/multidict-6.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:8b2d61afbafc679b7eaf08e9de4fa5d38bd5dc7a9c0a577c9f9588fb49f02dbb", size = 41481 }, + { url = "https://files.pythonhosted.org/packages/44/d8/45e8fc9892a7386d074941429e033adb4640e59ff0780d96a8cf46fe788e/multidict-6.5.0-py3-none-any.whl", hash = "sha256:5634b35f225977605385f56153bd95a7133faffc0ffe12ad26e10517537e8dfc", size = 12181 }, +] + +[[package]] +name = "multiprocess" +version = "0.70.16" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/ae/04f39c5d0d0def03247c2893d6f2b83c136bf3320a2154d7b8858f2ba72d/multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1", size = 1772603 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/76/6e712a2623d146d314f17598df5de7224c85c0060ef63fd95cc15a25b3fa/multiprocess-0.70.16-pp310-pypy310_pp73-macosx_10_13_x86_64.whl", hash = "sha256:476887be10e2f59ff183c006af746cb6f1fd0eadcfd4ef49e605cbe2659920ee", size = 134980 }, + { url = "https://files.pythonhosted.org/packages/0f/ab/1e6e8009e380e22254ff539ebe117861e5bdb3bff1fc977920972237c6c7/multiprocess-0.70.16-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d951bed82c8f73929ac82c61f01a7b5ce8f3e5ef40f5b52553b4f547ce2b08ec", size = 134982 }, + { url = "https://files.pythonhosted.org/packages/bc/f7/7ec7fddc92e50714ea3745631f79bd9c96424cb2702632521028e57d3a36/multiprocess-0.70.16-py310-none-any.whl", hash = "sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02", size = 134824 }, + { url = "https://files.pythonhosted.org/packages/ea/89/38df130f2c799090c978b366cfdf5b96d08de5b29a4a293df7f7429fa50b/multiprocess-0.70.16-py38-none-any.whl", hash = "sha256:a71d82033454891091a226dfc319d0cfa8019a4e888ef9ca910372a446de4435", size = 132628 }, + { url = "https://files.pythonhosted.org/packages/da/d9/f7f9379981e39b8c2511c9e0326d212accacb82f12fbfdc1aa2ce2a7b2b6/multiprocess-0.70.16-py39-none-any.whl", hash = "sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3", size = 133351 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, +] + +[[package]] +name = "newspaper3k" +version = "0.2.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "cssselect" }, + { name = "feedfinder2" }, + { name = "feedparser" }, + { name = "jieba3k" }, + { name = "lxml" }, + { name = "nltk" }, + { name = "pillow" }, + { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tinysegmenter" }, + { name = "tldextract" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/fb/8f8525be0cafa48926e85b0c06a7cb3e2a892d340b8036f8c8b1b572df1c/newspaper3k-0.2.8.tar.gz", hash = "sha256:9f1bd3e1fb48f400c715abf875cc7b0a67b7ddcd87f50c9aeeb8fcbbbd9004fb", size = 205685 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/b9/51afecb35bb61b188a4b44868001de348a0e8134b4dfa00ffc191567c4b9/newspaper3k-0.2.8-py3-none-any.whl", hash = "sha256:44a864222633d3081113d1030615991c3dbba87239f6bbf59d91240f71a22e3e", size = 211132 }, +] + +[[package]] +name = "nltk" +version = "3.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "joblib" }, + { name = "regex" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/87/db8be88ad32c2d042420b6fd9ffd4a149f9a0d7f0e86b3f543be2eeeedd2/nltk-3.9.1.tar.gz", hash = "sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868", size = 2904691 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/66/7d9e26593edda06e8cb531874633f7c2372279c3b0f46235539fe546df8b/nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1", size = 1505442 }, +] + +[[package]] +name = "numpy" +version = "1.26.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/94/ace0fdea5241a27d13543ee117cbc65868e82213fb31a8eb7fe9ff23f313/numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", size = 20631468 }, + { url = "https://files.pythonhosted.org/packages/20/f7/b24208eba89f9d1b58c1668bc6c8c4fd472b20c45573cb767f59d49fb0f6/numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", size = 13966411 }, + { url = "https://files.pythonhosted.org/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", size = 14219016 }, + { url = "https://files.pythonhosted.org/packages/4b/d7/ecf66c1cd12dc28b4040b15ab4d17b773b87fa9d29ca16125de01adb36cd/numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f", size = 18240889 }, + { url = "https://files.pythonhosted.org/packages/24/03/6f229fe3187546435c4f6f89f6d26c129d4f5bed40552899fcf1f0bf9e50/numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", size = 13876746 }, + { url = "https://files.pythonhosted.org/packages/39/fe/39ada9b094f01f5a35486577c848fe274e374bbf8d8f472e1423a0bbd26d/numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", size = 18078620 }, + { url = "https://files.pythonhosted.org/packages/d5/ef/6ad11d51197aad206a9ad2286dc1aac6a378059e06e8cf22cd08ed4f20dc/numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", size = 5972659 }, + { url = "https://files.pythonhosted.org/packages/19/77/538f202862b9183f54108557bfda67e17603fc560c384559e769321c9d92/numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", size = 15808905 }, +] + +[[package]] +name = "oauthlib" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065 }, +] + +[[package]] +name = "olefile" +version = "0.47" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/1b/077b508e3e500e1629d366249c3ccb32f95e50258b231705c09e3c7a4366/olefile-0.47.zip", hash = "sha256:599383381a0bf3dfbd932ca0ca6515acd174ed48870cbf7fee123d698c192c1c", size = 112240 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/d3/b64c356a907242d719fc668b71befd73324e47ab46c8ebbbede252c154b2/olefile-0.47-py2.py3-none-any.whl", hash = "sha256:543c7da2a7adadf21214938bb79c83ea12b473a4b6ee4ad4bf854e7715e13d1f", size = 114565 }, +] + +[[package]] +name = "onnxruntime" +version = "1.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coloredlogs" }, + { name = "flatbuffers" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "sympy" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/18/272d3d7406909141d3c9943796e3e97cafa53f4342d9231c0cfd8cb05702/onnxruntime-1.19.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:84fa57369c06cadd3c2a538ae2a26d76d583e7c34bdecd5769d71ca5c0fc750e", size = 16776408 }, + { url = "https://files.pythonhosted.org/packages/d8/d3/eb93f4ae511cfc725d0c69e07008800f8ac018de19ea1e497b306f174ccc/onnxruntime-1.19.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdc471a66df0c1cdef774accef69e9f2ca168c851ab5e4f2f3341512c7ef4666", size = 11491779 }, + { url = "https://files.pythonhosted.org/packages/ca/4b/ce5958074abe4b6e8d1da9c10e443e01a681558a9ec17e5cc7619438e094/onnxruntime-1.19.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e3a4ce906105d99ebbe817f536d50a91ed8a4d1592553f49b3c23c4be2560ae6", size = 13170428 }, + { url = "https://files.pythonhosted.org/packages/ce/0f/6df82dfe02467d12adbaa05c2bd17519c29c7df531ed600231f0c741ad22/onnxruntime-1.19.2-cp310-cp310-win32.whl", hash = "sha256:4b3d723cc154c8ddeb9f6d0a8c0d6243774c6b5930847cc83170bfe4678fafb3", size = 9591305 }, + { url = "https://files.pythonhosted.org/packages/3c/d8/68b63dc86b502169d017a86fe8bc718f4b0055ef1f6895bfaddd04f2eead/onnxruntime-1.19.2-cp310-cp310-win_amd64.whl", hash = "sha256:17ed7382d2c58d4b7354fb2b301ff30b9bf308a1c7eac9546449cd122d21cae5", size = 11084902 }, +] + +[[package]] +name = "openai" +version = "1.88.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/ea/bbeef604d1fe0f7e9111745bb8a81362973a95713b28855beb9a9832ab12/openai-1.88.0.tar.gz", hash = "sha256:122d35e42998255cf1fc84560f6ee49a844e65c054cd05d3e42fda506b832bb1", size = 470963 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/03/ef68d77a38dd383cbed7fc898857d394d5a8b0520a35f054e7fe05dc3ac1/openai-1.88.0-py3-none-any.whl", hash = "sha256:7edd7826b3b83f5846562a6f310f040c79576278bf8e3687b30ba05bb5dff978", size = 734293 }, +] + +[[package]] +name = "openapi-schema-validator" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema" }, + { name = "jsonschema-specifications" }, + { name = "rfc3339-validator" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/f3/5507ad3325169347cd8ced61c232ff3df70e2b250c49f0fe140edb4973c6/openapi_schema_validator-0.6.3.tar.gz", hash = "sha256:f37bace4fc2a5d96692f4f8b31dc0f8d7400fd04f3a937798eaf880d425de6ee", size = 11550 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/c6/ad0fba32775ae749016829dace42ed80f4407b171da41313d1a3a5f102e4/openapi_schema_validator-0.6.3-py3-none-any.whl", hash = "sha256:f3b9870f4e556b5a62a1c39da72a6b4b16f3ad9c73dc80084b1b11e74ba148a3", size = 8755 }, +] + +[[package]] +name = "openapi-spec-validator" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema" }, + { name = "jsonschema-path" }, + { name = "lazy-object-proxy" }, + { name = "openapi-schema-validator" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/af/fe2d7618d6eae6fb3a82766a44ed87cd8d6d82b4564ed1c7cfb0f6378e91/openapi_spec_validator-0.7.2.tar.gz", hash = "sha256:cc029309b5c5dbc7859df0372d55e9d1ff43e96d678b9ba087f7c56fc586f734", size = 36855 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/dd/b3fd642260cb17532f66cc1e8250f3507d1e580483e209dc1e9d13bd980d/openapi_spec_validator-0.7.2-py3-none-any.whl", hash = "sha256:4bbdc0894ec85f1d1bea1d6d9c8b2c3c8d7ccaa13577ef40da9c006c9fd0eb60", size = 39713 }, +] + +[[package]] +name = "openpyxl" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "et-xmlfile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910 }, +] + +[[package]] +name = "ordered-set" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/ca/bfac8bc689799bcca4157e0e0ced07e70ce125193fc2e166d2e685b7e2fe/ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8", size = 12826 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/55/af02708f230eb77084a299d7b08175cff006dea4f2721074b92cdb0296c0/ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562", size = 7634 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pandas" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/ee/146cab1ff6d575b54ace8a6a5994048380dc94879b0125b25e62edcb9e52/pandas-1.5.3.tar.gz", hash = "sha256:74a3fd7e5a7ec052f183273dc7b0acd3a863edf7520f5d3a1765c04ffdb3b0b1", size = 5203060 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/cd/34f6b0780301be81be804d7aa71d571457369e6131e2b330af2b0fed1aad/pandas-1.5.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3749077d86e3a2f0ed51367f30bf5b82e131cc0f14260c4d3e499186fccc4406", size = 18619230 }, + { url = "https://files.pythonhosted.org/packages/5f/34/b7858bb7d6d6bf4d9df1dde777a11fcf3ff370e1d1b3956e3d0fcca8322c/pandas-1.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:972d8a45395f2a2d26733eb8d0f629b2f90bebe8e8eddbb8829b180c09639572", size = 11982991 }, + { url = "https://files.pythonhosted.org/packages/b8/6c/005bd604994f7cbede4d7bf030614ef49a2213f76bc3d738ecf5b0dcc810/pandas-1.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50869a35cbb0f2e0cd5ec04b191e7b12ed688874bd05dd777c19b28cbea90996", size = 10927131 }, + { url = "https://files.pythonhosted.org/packages/27/c7/35b81ce5f680f2dac55eac14d103245cd8cf656ae4a2ff3be2e69fd1d330/pandas-1.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3ac844a0fe00bfaeb2c9b51ab1424e5c8744f89860b138434a363b1f620f354", size = 11368188 }, + { url = "https://files.pythonhosted.org/packages/49/e2/79e46612dc25ebc7603dc11c560baa7266c90f9e48537ecf1a02a0dd6bff/pandas-1.5.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0a56cef15fd1586726dace5616db75ebcfec9179a3a55e78f72c5639fa2a23", size = 12062104 }, + { url = "https://files.pythonhosted.org/packages/d9/cd/f27c2992cbe05a3e39937f73a4be635a9ec149ec3ca4467d8cf039718994/pandas-1.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:478ff646ca42b20376e4ed3fa2e8d7341e8a63105586efe54fa2508ee087f328", size = 10362473 }, +] + +[[package]] +name = "pandasai" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astor" }, + { name = "duckdb" }, + { name = "faker" }, + { name = "jinja2" }, + { name = "matplotlib" }, + { name = "openai" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "requests" }, + { name = "scipy" }, + { name = "sqlalchemy" }, + { name = "sqlglot", extra = ["rs"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/b6/434c9ff142617e8dcd6964e8711572d935d6307ca9e29b1204de3796facf/pandasai-2.3.0.tar.gz", hash = "sha256:fb58d2c55dd53988dcf6207ea196ee52eb2c2262fbd389e1ecddaed3a6093ecf", size = 114873 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/ca/0bcaf20b4cb305f4cfbca7be09cf2b06d09cbabbfe7d7ea0035d3322b214/pandasai-2.3.0-py3-none-any.whl", hash = "sha256:15a586e5b90e16ca685b31d7eb2dcff2783c97feac9e0e4e1090b2b16d931d3e", size = 185988 }, +] + +[[package]] +name = "pathable" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/67/93/8f2c2075b180c12c1e9f6a09d1a985bc2036906b13dff1d8917e395f2048/pathable-0.4.4.tar.gz", hash = "sha256:6905a3cd17804edfac7875b5f6c9142a218c7caef78693c2dbbbfbac186d88b2", size = 8124 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl", hash = "sha256:5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2", size = 9592 }, +] + +[[package]] +name = "pillow" +version = "10.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/74/ad3d526f3bf7b6d3f408b73fde271ec69dfac8b81341a318ce825f2b3812/pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", size = 46555059 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/69/a31cccd538ca0b5272be2a38347f8839b97a14be104ea08b0db92f749c74/pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", size = 3509271 }, + { url = "https://files.pythonhosted.org/packages/9a/9e/4143b907be8ea0bce215f2ae4f7480027473f8b61fcedfda9d851082a5d2/pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d", size = 3375658 }, + { url = "https://files.pythonhosted.org/packages/8a/25/1fc45761955f9359b1169aa75e241551e74ac01a09f487adaaf4c3472d11/pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856", size = 4332075 }, + { url = "https://files.pythonhosted.org/packages/5e/dd/425b95d0151e1d6c951f45051112394f130df3da67363b6bc75dc4c27aba/pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f", size = 4444808 }, + { url = "https://files.pythonhosted.org/packages/b1/84/9a15cc5726cbbfe7f9f90bfb11f5d028586595907cd093815ca6644932e3/pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b", size = 4356290 }, + { url = "https://files.pythonhosted.org/packages/b5/5b/6651c288b08df3b8c1e2f8c1152201e0b25d240e22ddade0f1e242fc9fa0/pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc", size = 4525163 }, + { url = "https://files.pythonhosted.org/packages/07/8b/34854bf11a83c248505c8cb0fcf8d3d0b459a2246c8809b967963b6b12ae/pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e", size = 4463100 }, + { url = "https://files.pythonhosted.org/packages/78/63/0632aee4e82476d9cbe5200c0cdf9ba41ee04ed77887432845264d81116d/pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46", size = 4592880 }, + { url = "https://files.pythonhosted.org/packages/df/56/b8663d7520671b4398b9d97e1ed9f583d4afcbefbda3c6188325e8c297bd/pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984", size = 2235218 }, + { url = "https://files.pythonhosted.org/packages/f4/72/0203e94a91ddb4a9d5238434ae6c1ca10e610e8487036132ea9bf806ca2a/pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141", size = 2554487 }, + { url = "https://files.pythonhosted.org/packages/bd/52/7e7e93d7a6e4290543f17dc6f7d3af4bd0b3dd9926e2e8a35ac2282bc5f4/pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1", size = 2243219 }, + { url = "https://files.pythonhosted.org/packages/38/30/095d4f55f3a053392f75e2eae45eba3228452783bab3d9a920b951ac495c/pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", size = 3493889 }, + { url = "https://files.pythonhosted.org/packages/f3/e8/4ff79788803a5fcd5dc35efdc9386af153569853767bff74540725b45863/pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", size = 3346160 }, + { url = "https://files.pythonhosted.org/packages/d7/ac/4184edd511b14f760c73f5bb8a5d6fd85c591c8aff7c2229677a355c4179/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", size = 3435020 }, + { url = "https://files.pythonhosted.org/packages/da/21/1749cd09160149c0a246a81d646e05f35041619ce76f6493d6a96e8d1103/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e", size = 3490539 }, + { url = "https://files.pythonhosted.org/packages/b6/f5/f71fe1888b96083b3f6dfa0709101f61fc9e972c0c8d04e9d93ccef2a045/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5", size = 3476125 }, + { url = "https://files.pythonhosted.org/packages/96/b9/c0362c54290a31866c3526848583a2f45a535aa9d725fd31e25d318c805f/pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885", size = 3579373 }, + { url = "https://files.pythonhosted.org/packages/52/3b/ce7a01026a7cf46e5452afa86f97a5e88ca97f562cafa76570178ab56d8d/pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", size = 2554661 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 }, +] + +[[package]] +name = "playwright" +version = "1.52.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet" }, + { name = "pyee" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/62/a20240605485ca99365a8b72ed95e0b4c5739a13fb986353f72d8d3f1d27/playwright-1.52.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:19b2cb9d4794062008a635a99bd135b03ebb782d460f96534a91cb583f549512", size = 39611246 }, + { url = "https://files.pythonhosted.org/packages/dc/23/57ff081663b3061a2a3f0e111713046f705da2595f2f384488a76e4db732/playwright-1.52.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:0797c0479cbdc99607412a3c486a3a2ec9ddc77ac461259fd2878c975bcbb94a", size = 37962977 }, + { url = "https://files.pythonhosted.org/packages/a2/ff/eee8532cff4b3d768768152e8c4f30d3caa80f2969bf3143f4371d377b74/playwright-1.52.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:7223960b7dd7ddeec1ba378c302d1d09733b8dac438f492e9854c85d3ca7144f", size = 39611247 }, + { url = "https://files.pythonhosted.org/packages/73/c6/8e27af9798f81465b299741ef57064c6ec1a31128ed297406469907dc5a4/playwright-1.52.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:d010124d24a321e0489a8c0d38a3971a7ca7656becea7656c9376bfea7f916d4", size = 45141333 }, + { url = "https://files.pythonhosted.org/packages/4e/e9/0661d343ed55860bcfb8934ce10e9597fc953358773ece507b22b0f35c57/playwright-1.52.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4173e453c43180acc60fd77ffe1ebee8d0efbfd9986c03267007b9c3845415af", size = 44540623 }, + { url = "https://files.pythonhosted.org/packages/7a/81/a850dbc6bc2e1bd6cc87341e59c253269602352de83d34b00ea38cf410ee/playwright-1.52.0-py3-none-win32.whl", hash = "sha256:cd0bdf92df99db6237a99f828e80a6a50db6180ef8d5352fc9495df2c92f9971", size = 34839156 }, + { url = "https://files.pythonhosted.org/packages/51/f3/cca2aa84eb28ea7d5b85d16caa92d62d18b6e83636e3d67957daca1ee4c7/playwright-1.52.0-py3-none-win_amd64.whl", hash = "sha256:dcbf75101eba3066b7521c6519de58721ea44379eb17a0dafa94f9f1b17f59e4", size = 34839164 }, + { url = "https://files.pythonhosted.org/packages/b5/4f/71a8a873e8c3c3e2d3ec03a578e546f6875be8a76214d90219f752f827cd/playwright-1.52.0-py3-none-win_arm64.whl", hash = "sha256:9d0085b8de513de5fb50669f8e6677f0252ef95a9a1d2d23ccee9638e71e65cb", size = 30688972 }, +] + +[[package]] +name = "portalocker" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/fb/a70a4214956182e0d7a9099ab17d50bfcba1056188e9b14f35b9e2b62a0d/portalocker-2.10.1-py3-none-any.whl", hash = "sha256:53a5984ebc86a025552264b459b46a2086e269b21823cb572f8f28ee759e45bf", size = 18423 }, +] + +[[package]] +name = "prance" +version = "23.6.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "chardet" }, + { name = "packaging" }, + { name = "requests" }, + { name = "ruamel-yaml" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/f0/bcb5ffc8b7ab8e3d02dbef3bd945cf8fd6e12c146774f900659406b9fce1/prance-23.6.21.0.tar.gz", hash = "sha256:d8c15f8ac34019751cc4945f866d8d964d7888016d10de3592e339567177cabe", size = 2798776 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/db/4fb4901ee61274d0ab97746461fc5f2637e5d73aa73f34ee28e941a699a1/prance-23.6.21.0-py3-none-any.whl", hash = "sha256:6a4276fa07ed9f22feda4331097d7503c4adc3097e46ffae97425f2c1026bd9f", size = 36279 }, +] + +[[package]] +name = "primp" +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/0b/a87556189da4de1fc6360ca1aa05e8335509633f836cdd06dd17f0743300/primp-0.15.0.tar.gz", hash = "sha256:1af8ea4b15f57571ff7fc5e282a82c5eb69bc695e19b8ddeeda324397965b30a", size = 113022 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/5a/146ac964b99ea7657ad67eb66f770be6577dfe9200cb28f9a95baffd6c3f/primp-0.15.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:1b281f4ca41a0c6612d4c6e68b96e28acfe786d226a427cd944baa8d7acd644f", size = 3178914 }, + { url = "https://files.pythonhosted.org/packages/bc/8a/cc2321e32db3ce64d6e32950d5bcbea01861db97bfb20b5394affc45b387/primp-0.15.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:489cbab55cd793ceb8f90bb7423c6ea64ebb53208ffcf7a044138e3c66d77299", size = 2955079 }, + { url = "https://files.pythonhosted.org/packages/c3/7b/cbd5d999a07ff2a21465975d4eb477ae6f69765e8fe8c9087dab250180d8/primp-0.15.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c18b45c23f94016215f62d2334552224236217aaeb716871ce0e4dcfa08eb161", size = 3281018 }, + { url = "https://files.pythonhosted.org/packages/1b/6e/a6221c612e61303aec2bcac3f0a02e8b67aee8c0db7bdc174aeb8010f975/primp-0.15.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e985a9cba2e3f96a323722e5440aa9eccaac3178e74b884778e926b5249df080", size = 3255229 }, + { url = "https://files.pythonhosted.org/packages/3b/54/bfeef5aca613dc660a69d0760a26c6b8747d8fdb5a7f20cb2cee53c9862f/primp-0.15.0-cp38-abi3-manylinux_2_34_armv7l.whl", hash = "sha256:6b84a6ffa083e34668ff0037221d399c24d939b5629cd38223af860de9e17a83", size = 3014522 }, + { url = "https://files.pythonhosted.org/packages/ac/96/84078e09f16a1dad208f2fe0f8a81be2cf36e024675b0f9eec0c2f6e2182/primp-0.15.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:592f6079646bdf5abbbfc3b0a28dac8de943f8907a250ce09398cda5eaebd260", size = 3418567 }, + { url = "https://files.pythonhosted.org/packages/6c/80/8a7a9587d3eb85be3d0b64319f2f690c90eb7953e3f73a9ddd9e46c8dc42/primp-0.15.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5a728e5a05f37db6189eb413d22c78bd143fa59dd6a8a26dacd43332b3971fe8", size = 3606279 }, + { url = "https://files.pythonhosted.org/packages/0c/dd/f0183ed0145e58cf9d286c1b2c14f63ccee987a4ff79ac85acc31b5d86bd/primp-0.15.0-cp38-abi3-win_amd64.whl", hash = "sha256:aeb6bd20b06dfc92cfe4436939c18de88a58c640752cf7f30d9e4ae893cdec32", size = 3149967 }, +] + +[[package]] +name = "propcache" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/14/510deed325e262afeb8b360043c5d7c960da7d3ecd6d6f9496c9c56dc7f4/propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770", size = 73178 }, + { url = "https://files.pythonhosted.org/packages/cd/4e/ad52a7925ff01c1325653a730c7ec3175a23f948f08626a534133427dcff/propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3", size = 43133 }, + { url = "https://files.pythonhosted.org/packages/63/7c/e9399ba5da7780871db4eac178e9c2e204c23dd3e7d32df202092a1ed400/propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3", size = 43039 }, + { url = "https://files.pythonhosted.org/packages/22/e1/58da211eb8fdc6fc854002387d38f415a6ca5f5c67c1315b204a5d3e9d7a/propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e", size = 201903 }, + { url = "https://files.pythonhosted.org/packages/c4/0a/550ea0f52aac455cb90111c8bab995208443e46d925e51e2f6ebdf869525/propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220", size = 213362 }, + { url = "https://files.pythonhosted.org/packages/5a/af/9893b7d878deda9bb69fcf54600b247fba7317761b7db11fede6e0f28bd0/propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb", size = 210525 }, + { url = "https://files.pythonhosted.org/packages/7c/bb/38fd08b278ca85cde36d848091ad2b45954bc5f15cce494bb300b9285831/propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614", size = 198283 }, + { url = "https://files.pythonhosted.org/packages/78/8c/9fe55bd01d362bafb413dfe508c48753111a1e269737fa143ba85693592c/propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50", size = 191872 }, + { url = "https://files.pythonhosted.org/packages/54/14/4701c33852937a22584e08abb531d654c8bcf7948a8f87ad0a4822394147/propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339", size = 199452 }, + { url = "https://files.pythonhosted.org/packages/16/44/447f2253d859602095356007657ee535e0093215ea0b3d1d6a41d16e5201/propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0", size = 191567 }, + { url = "https://files.pythonhosted.org/packages/f2/b3/e4756258749bb2d3b46defcff606a2f47410bab82be5824a67e84015b267/propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2", size = 193015 }, + { url = "https://files.pythonhosted.org/packages/1e/df/e6d3c7574233164b6330b9fd697beeac402afd367280e6dc377bb99b43d9/propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7", size = 204660 }, + { url = "https://files.pythonhosted.org/packages/b2/53/e4d31dd5170b4a0e2e6b730f2385a96410633b4833dc25fe5dffd1f73294/propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b", size = 206105 }, + { url = "https://files.pythonhosted.org/packages/7f/fe/74d54cf9fbe2a20ff786e5f7afcfde446588f0cf15fb2daacfbc267b866c/propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c", size = 196980 }, + { url = "https://files.pythonhosted.org/packages/22/ec/c469c9d59dada8a7679625e0440b544fe72e99311a4679c279562051f6fc/propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70", size = 37679 }, + { url = "https://files.pythonhosted.org/packages/38/35/07a471371ac89d418f8d0b699c75ea6dca2041fbda360823de21f6a9ce0a/propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9", size = 41459 }, + { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663 }, +] + +[[package]] +name = "protego" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/6b/84e878d0567dfc11538bad6ce2595cee7ae0c47cf6bf7293683c9ec78ef8/protego-0.4.0.tar.gz", hash = "sha256:93a5e662b61399a0e1f208a324f2c6ea95b23ee39e6cbf2c96246da4a656c2f6", size = 3246425 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/fd/8d84d75832b0983cecf3aff7ae48362fe96fc8ab6ebca9dcf3cefd87e79c/Protego-0.4.0-py2.py3-none-any.whl", hash = "sha256:37640bc0ebe37572d624453a21381d05e9d86e44f89ff1e81794d185a0491666", size = 8553 }, +] + +[[package]] +name = "proto-plus" +version = "1.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/ac/87285f15f7cce6d4a008f33f1757fb5a13611ea8914eb58c3d0d26243468/proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012", size = 56142 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/6d/280c4c2ce28b1593a19ad5239c8b826871fc6ec275c21afc8e1820108039/proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66", size = 50163 }, +] + +[[package]] +name = "protobuf" +version = "5.29.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/29/d09e70352e4e88c9c7a198d5645d7277811448d76c23b00345670f7c8a38/protobuf-5.29.5.tar.gz", hash = "sha256:bc1463bafd4b0929216c35f437a8e28731a2b7fe3d98bb77a600efced5a15c84", size = 425226 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/11/6e40e9fc5bba02988a214c07cf324595789ca7820160bfd1f8be96e48539/protobuf-5.29.5-cp310-abi3-win32.whl", hash = "sha256:3f1c6468a2cfd102ff4703976138844f78ebd1fb45f49011afc5139e9e283079", size = 422963 }, + { url = "https://files.pythonhosted.org/packages/81/7f/73cefb093e1a2a7c3ffd839e6f9fcafb7a427d300c7f8aef9c64405d8ac6/protobuf-5.29.5-cp310-abi3-win_amd64.whl", hash = "sha256:3f76e3a3675b4a4d867b52e4a5f5b78a2ef9565549d4037e06cf7b0942b1d3fc", size = 434818 }, + { url = "https://files.pythonhosted.org/packages/dd/73/10e1661c21f139f2c6ad9b23040ff36fee624310dc28fba20d33fdae124c/protobuf-5.29.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e38c5add5a311f2a6eb0340716ef9b039c1dfa428b28f25a7838ac329204a671", size = 418091 }, + { url = "https://files.pythonhosted.org/packages/6c/04/98f6f8cf5b07ab1294c13f34b4e69b3722bb609c5b701d6c169828f9f8aa/protobuf-5.29.5-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:fa18533a299d7ab6c55a238bf8629311439995f2e7eca5caaff08663606e9015", size = 319824 }, + { url = "https://files.pythonhosted.org/packages/85/e4/07c80521879c2d15f321465ac24c70efe2381378c00bf5e56a0f4fbac8cd/protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:63848923da3325e1bf7e9003d680ce6e14b07e55d0473253a690c3a8b8fd6e61", size = 319942 }, + { url = "https://files.pythonhosted.org/packages/7e/cc/7e77861000a0691aeea8f4566e5d3aa716f2b1dece4a24439437e41d3d25/protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5", size = 172823 }, +] + +[[package]] +name = "psutil" +version = "5.9.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/c7/6dc0a455d111f68ee43f27793971cf03fe29b6ef972042549db29eec39a2/psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c", size = 503247 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e3/07ae864a636d70a8a6f58da27cb1179192f1140d5d1da10886ade9405797/psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81", size = 248702 }, + { url = "https://files.pythonhosted.org/packages/b3/bd/28c5f553667116b2598b9cc55908ec435cb7f77a34f2bff3e3ca765b0f78/psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421", size = 285242 }, + { url = "https://files.pythonhosted.org/packages/c5/4f/0e22aaa246f96d6ac87fe5ebb9c5a693fbe8877f537a1022527c47ca43c5/psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4", size = 288191 }, + { url = "https://files.pythonhosted.org/packages/6e/f5/2aa3a4acdc1e5940b59d421742356f133185667dd190b166dbcfcf5d7b43/psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0", size = 251252 }, + { url = "https://files.pythonhosted.org/packages/93/52/3e39d26feae7df0aa0fd510b14012c3678b36ed068f7d78b8d8784d61f0e/psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf", size = 255090 }, + { url = "https://files.pythonhosted.org/packages/05/33/2d74d588408caedd065c2497bdb5ef83ce6082db01289a1e1147f6639802/psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8", size = 249898 }, +] + +[[package]] +name = "pyarrow" +version = "20.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/ee/a7810cb9f3d6e9238e61d312076a9859bf3668fd21c69744de9532383912/pyarrow-20.0.0.tar.gz", hash = "sha256:febc4a913592573c8d5805091a6c2b5064c8bd6e002131f01061797d91c783c1", size = 1125187 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/23/77094eb8ee0dbe88441689cb6afc40ac312a1e15d3a7acc0586999518222/pyarrow-20.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:c7dd06fd7d7b410ca5dc839cc9d485d2bc4ae5240851bcd45d85105cc90a47d7", size = 30832591 }, + { url = "https://files.pythonhosted.org/packages/c3/d5/48cc573aff00d62913701d9fac478518f693b30c25f2c157550b0b2565cb/pyarrow-20.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:d5382de8dc34c943249b01c19110783d0d64b207167c728461add1ecc2db88e4", size = 32273686 }, + { url = "https://files.pythonhosted.org/packages/37/df/4099b69a432b5cb412dd18adc2629975544d656df3d7fda6d73c5dba935d/pyarrow-20.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6415a0d0174487456ddc9beaead703d0ded5966129fa4fd3114d76b5d1c5ceae", size = 41337051 }, + { url = "https://files.pythonhosted.org/packages/4c/27/99922a9ac1c9226f346e3a1e15e63dee6f623ed757ff2893f9d6994a69d3/pyarrow-20.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15aa1b3b2587e74328a730457068dc6c89e6dcbf438d4369f572af9d320a25ee", size = 42404659 }, + { url = "https://files.pythonhosted.org/packages/21/d1/71d91b2791b829c9e98f1e0d85be66ed93aff399f80abb99678511847eaa/pyarrow-20.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:5605919fbe67a7948c1f03b9f3727d82846c053cd2ce9303ace791855923fd20", size = 40695446 }, + { url = "https://files.pythonhosted.org/packages/f1/ca/ae10fba419a6e94329707487835ec721f5a95f3ac9168500bcf7aa3813c7/pyarrow-20.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a5704f29a74b81673d266e5ec1fe376f060627c2e42c5c7651288ed4b0db29e9", size = 42278528 }, + { url = "https://files.pythonhosted.org/packages/7a/a6/aba40a2bf01b5d00cf9cd16d427a5da1fad0fb69b514ce8c8292ab80e968/pyarrow-20.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:00138f79ee1b5aca81e2bdedb91e3739b987245e11fa3c826f9e57c5d102fb75", size = 42918162 }, + { url = "https://files.pythonhosted.org/packages/93/6b/98b39650cd64f32bf2ec6d627a9bd24fcb3e4e6ea1873c5e1ea8a83b1a18/pyarrow-20.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f2d67ac28f57a362f1a2c1e6fa98bfe2f03230f7e15927aecd067433b1e70ce8", size = 44550319 }, + { url = "https://files.pythonhosted.org/packages/ab/32/340238be1eb5037e7b5de7e640ee22334417239bc347eadefaf8c373936d/pyarrow-20.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:4a8b029a07956b8d7bd742ffca25374dd3f634b35e46cc7a7c3fa4c75b297191", size = 25770759 }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135 }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259 }, +] + +[[package]] +name = "pyautogui" +version = "0.9.54" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mouseinfo" }, + { name = "pygetwindow" }, + { name = "pymsgbox" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyscreeze" }, + { name = "python3-xlib", marker = "sys_platform == 'linux'" }, + { name = "pytweening" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/65/ff/cdae0a8c2118a0de74b6cf4cbcdcaf8fd25857e6c3f205ce4b1794b27814/PyAutoGUI-0.9.54.tar.gz", hash = "sha256:dd1d29e8fd118941cb193f74df57e5c6ff8e9253b99c7b04f39cfc69f3ae04b2", size = 61236 } + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pydantic" +version = "2.11.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782 }, +] + +[package.optional-dependencies] +email = [ + { name = "email-validator" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817 }, + { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357 }, + { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011 }, + { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730 }, + { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178 }, + { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462 }, + { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652 }, + { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306 }, + { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720 }, + { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915 }, + { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884 }, + { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496 }, + { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019 }, + { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982 }, + { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412 }, + { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749 }, + { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527 }, + { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225 }, + { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490 }, + { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525 }, + { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446 }, + { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678 }, +] + +[[package]] +name = "pydantic-i18n" +version = "0.4.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/70/c21ed1ce36a947c5a7fee04c5d3926db4907f00bc29b193759d675554329/pydantic_i18n-0.4.5.tar.gz", hash = "sha256:37c3b40df31713dba27c436d15a8d894d6022f3da5b78a40805e6b64edde34a3", size = 78725 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/3b/4d2630503016cedef1751bc9ddea85b437fbfc9ca65d6af87285d76b7c2c/pydantic_i18n-0.4.5-py3-none-any.whl", hash = "sha256:592ae6b4fee13eb0193dc0c7bdc1e629d2ab1d732d5508368412a338b16cfece", size = 10436 }, +] + +[[package]] +name = "pydantic-settings" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356 }, +] + +[[package]] +name = "pydash" +version = "8.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/24/91c037f47e434172c2112d65c00c84d475a6715425e3315ba2cbb7a87e66/pydash-8.0.5.tar.gz", hash = "sha256:7cc44ebfe5d362f4f5f06c74c8684143c5ac481376b059ff02570705523f9e2e", size = 164861 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/86/e74c978800131c657fc5145f2c1c63e0cea01a49b6216f729cf77a2e1edf/pydash-8.0.5-py3-none-any.whl", hash = "sha256:b2625f8981862e19911daa07f80ed47b315ce20d9b5eb57aaf97aaf570c3892f", size = 102077 }, +] + +[[package]] +name = "pydub" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/9a/e6bca0eed82db26562c73b5076539a4a08d3cffd19c3cc5913a3e61145fd/pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f", size = 38326 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6", size = 32327 }, +] + +[[package]] +name = "pyee" +version = "13.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/03/1fd98d5841cd7964a27d729ccf2199602fe05eb7a405c1462eb7277945ed/pyee-13.0.0.tar.gz", hash = "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37", size = 31250 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/4d/b9add7c84060d4c1906abe9a7e5359f2a60f7a9a4f67268b2766673427d8/pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498", size = 15730 }, +] + +[[package]] +name = "pygetwindow" +version = "0.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyrect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/70/c7a4f46dbf06048c6d57d9489b8e0f9c4c3d36b7479f03c5ca97eaa2541d/PyGetWindow-0.0.9.tar.gz", hash = "sha256:17894355e7d2b305cd832d717708384017c1698a90ce24f6f7fbf0242dd0a688", size = 9699 } + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + +[[package]] +name = "pylatex" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ordered-set" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/a8/10cf6b955b5fa19438790d9949867e04c785ae845e631c5ef6db444401d1/PyLaTeX-1.4.2.tar.gz", hash = "sha256:bb7b21bec57ecdba3f6f44c856ebebdf6549fd6e80661bd44fd5094236729242", size = 59710 } + +[[package]] +name = "pymsgbox" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/ff/4c6f31a4f08979f12a663f2aeb6c8b765d3bd592e66eaaac445f547bb875/PyMsgBox-1.0.9.tar.gz", hash = "sha256:2194227de8bff7a3d6da541848705a155dcbb2a06ee120d9f280a1d7f51263ff", size = 18829 } + +[[package]] +name = "pymupdf" +version = "1.26.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/62/d29612ca33b7844e77d2c789fec359f4c44fd84bdd08ce673f6279d257e9/pymupdf-1.26.1.tar.gz", hash = "sha256:372c77c831f82090ce7a6e4de284ca7c5a78220f63038bb28c5d9b279cd7f4d9", size = 75912371 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/5a/3399a2caf51c91db650de57464465b830c2d4ea15b23d24a98182202b704/pymupdf-1.26.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:32296f12a7c7f36febd59cee77823a54490313bcaba9879b17def6518186f94e", size = 23054640 }, + { url = "https://files.pythonhosted.org/packages/64/e0/cc3ec6a4d5ada8992b8610f134565ceb517243f12736b50d795cb3459315/pymupdf-1.26.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:aad7949eca62aca40854510cdb125cf873b181726dc9497a90834200f31faa63", size = 22402766 }, + { url = "https://files.pythonhosted.org/packages/e8/cf/d5b1cd775a17a7b83e25cbf4c46f64cf1352c962ca97646e3e01953cf0df/pymupdf-1.26.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3b62c4d443121ed9a2eb967c3a0e45f8dbabcc838db8604ece02c4e868808edc", size = 23448474 }, + { url = "https://files.pythonhosted.org/packages/82/9f/e7101bd24a0f5cbfa0310c8e5c3a8ec0dd9a86986812ff86ac2fbd273c92/pymupdf-1.26.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a65c411eb1cbb79e40c307e10fbad23658f19e9d7334ac4de21d24b58009a7b9", size = 24056183 }, + { url = "https://files.pythonhosted.org/packages/99/39/23ac15cf0edc2877ef366dc7ae041ac199d212433c2c3113661d1a1d5ad0/pymupdf-1.26.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:26cebdcc1b2b7a7445423599ce2e0000f2be0333cce0fa0e6846e5a7da46f965", size = 24258802 }, + { url = "https://files.pythonhosted.org/packages/e1/8c/56bd5951128d5c5c0b64d2942090c2cd7bc44302bac991b941ac736e3d63/pymupdf-1.26.1-cp39-abi3-win32.whl", hash = "sha256:82ed9e106cf564fc959c0691c374ba68443086ba1a1c9f26128eebbc3e6df9e5", size = 16927933 }, + { url = "https://files.pythonhosted.org/packages/a7/1b/0613759a059c8c952c18811c7c7dd0ba5d7945ed13a535719489f533d700/pymupdf-1.26.1-cp39-abi3-win_amd64.whl", hash = "sha256:8deae5168fce37d707f68d1981da6c0bb71f1f176d9835d5914ad46f779a036f", size = 18519587 }, +] + +[[package]] +name = "pyobjc-core" +version = "11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/e9/0b85c81e2b441267bca707b5d89f56c2f02578ef8f3eafddf0e0c0b8848c/pyobjc_core-11.1.tar.gz", hash = "sha256:b63d4d90c5df7e762f34739b39cc55bc63dbcf9fb2fb3f2671e528488c7a87fe", size = 974602 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/c5/9fa74ef6b83924e657c5098d37b36b66d1e16d13bc45c44248c6248e7117/pyobjc_core-11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4c7536f3e94de0a3eae6bb382d75f1219280aa867cdf37beef39d9e7d580173c", size = 676323 }, +] + +[[package]] +name = "pyobjc-framework-cocoa" +version = "11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/c5/7a866d24bc026f79239b74d05e2cf3088b03263da66d53d1b4cf5207f5ae/pyobjc_framework_cocoa-11.1.tar.gz", hash = "sha256:87df76b9b73e7ca699a828ff112564b59251bb9bbe72e610e670a4dc9940d038", size = 5565335 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/8f/67a7e166b615feb96385d886c6732dfb90afed565b8b1f34673683d73cd9/pyobjc_framework_cocoa-11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b27a5bdb3ab6cdeb998443ff3fce194ffae5f518c6a079b832dbafc4426937f9", size = 388187 }, +] + +[[package]] +name = "pyobjc-framework-quartz" +version = "11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/ac/6308fec6c9ffeda9942fef72724f4094c6df4933560f512e63eac37ebd30/pyobjc_framework_quartz-11.1.tar.gz", hash = "sha256:a57f35ccfc22ad48c87c5932818e583777ff7276605fef6afad0ac0741169f75", size = 3953275 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/62/f8d9bb4cba92d5f220327cf1def2c2c5be324880d54ee57e7bea43aa28b2/pyobjc_framework_quartz-11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b5ef75c416b0209e25b2eb07a27bd7eedf14a8c6b2f968711969d45ceceb0f84", size = 215586 }, +] + +[[package]] +name = "pyopenssl" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/8c/cd89ad05804f8e3c17dea8f178c3f40eeab5694c30e0c9f5bcd49f576fc3/pyopenssl-25.1.0.tar.gz", hash = "sha256:8d031884482e0c67ee92bf9a4d8cceb08d92aba7136432ffb0703c5280fc205b", size = 179937 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/28/2659c02301b9500751f8d42f9a6632e1508aa5120de5e43042b8b30f8d5d/pyopenssl-25.1.0-py3-none-any.whl", hash = "sha256:2b11f239acc47ac2e5aca04fd7fa829800aeee22a2eb30d744572a157bd8a1ab", size = 56771 }, +] + +[[package]] +name = "pyparsing" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120 }, +] + +[[package]] +name = "pypdf" +version = "5.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/46/67de1d7a65412aa1c896e6b280829b70b57d203fadae6859b690006b8e0a/pypdf-5.6.0.tar.gz", hash = "sha256:a4b6538b77fc796622000db7127e4e58039ec5e6afd292f8e9bf42e2e985a749", size = 5023749 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/8b/dc3a72d98c22be7a4cbd664ad14c5a3e6295c2dbdf572865ed61e24b5e38/pypdf-5.6.0-py3-none-any.whl", hash = "sha256:ca6bf446bfb0a2d8d71d6d6bb860798d864c36a29b3d9ae8d7fc7958c59f88e7", size = 304208 }, +] + +[[package]] +name = "pypdf2" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/bb/18dc3062d37db6c491392007dfd1a7f524bb95886eb956569ac38a23a784/PyPDF2-3.0.1.tar.gz", hash = "sha256:a74408f69ba6271f71b9352ef4ed03dc53a31aa404d29b5d31f53bfecfee1440", size = 227419 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/5e/c86a5643653825d3c913719e788e41386bee415c2b87b4f955432f2de6b2/pypdf2-3.0.1-py3-none-any.whl", hash = "sha256:d16e4205cfee272fbdc0568b68d82be796540b1537508cef59388f839c191928", size = 232572 }, +] + +[[package]] +name = "pyperclip" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/23/2f0a3efc4d6a32f3b63cdff36cd398d9701d26cda58e3ab97ac79fb5e60d/pyperclip-1.9.0.tar.gz", hash = "sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310", size = 20961 } + +[[package]] +name = "pyreadline3" +version = "3.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178 }, +] + +[[package]] +name = "pyrect" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/04/2ba023d5f771b645f7be0c281cdacdcd939fe13d1deb331fc5ed1a6b3a98/PyRect-0.2.0.tar.gz", hash = "sha256:f65155f6df9b929b67caffbd57c0947c5ae5449d3b580d178074bffb47a09b78", size = 17219 } + +[[package]] +name = "pyscreeze" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/f0/cb456ac4f1a73723d5b866933b7986f02bacea27516629c00f8e7da94c2d/pyscreeze-1.0.1.tar.gz", hash = "sha256:cf1662710f1b46aa5ff229ee23f367da9e20af4a78e6e365bee973cad0ead4be", size = 27826 } + +[[package]] +name = "pytesseract" +version = "0.3.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/a6/7d679b83c285974a7cb94d739b461fa7e7a9b17a3abfd7bf6cbc5c2394b0/pytesseract-0.3.13.tar.gz", hash = "sha256:4bf5f880c99406f52a3cfc2633e42d9dc67615e69d8a509d74867d3baddb5db9", size = 17689 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/33/8312d7ce74670c9d39a532b2c246a853861120486be9443eebf048043637/pytesseract-0.3.13-py3-none-any.whl", hash = "sha256:7a99c6c2ac598360693d83a416e36e0b33a67638bb9d77fdcac094a3589d4b34", size = 14705 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "python-docx" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/f7/eddfe33871520adab45aaa1a71f0402a2252050c14c7e3009446c8f4701c/python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce", size = 5723256 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/00/1e03a4989fa5795da308cd774f05b704ace555a70f9bf9d3be057b680bcf/python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7", size = 252987 }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, +] + +[[package]] +name = "python-iso639" +version = "2025.2.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/19/45aa1917c7b1f4eb71104795b9b0cbf97169b99ec46cd303445883536549/python_iso639-2025.2.18.tar.gz", hash = "sha256:34e31e8e76eb3fc839629e257b12bcfd957c6edcbd486bbf66ba5185d1f566e8", size = 173552 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/a3/3ceaf89a17a1e1d5e7bbdfe5514aa3055d91285b37a5c8fed662969e3d56/python_iso639-2025.2.18-py3-none-any.whl", hash = "sha256:b2d471c37483a26f19248458b20e7bd96492e15368b01053b540126bcc23152f", size = 167631 }, +] + +[[package]] +name = "python-magic" +version = "0.4.27" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840 }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 }, +] + +[[package]] +name = "python-oxmsg" +version = "0.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "olefile" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/4e/869f34faedbc968796d2c7e9837dede079c9cb9750917356b1f1eda926e9/python_oxmsg-0.0.2.tar.gz", hash = "sha256:a6aff4deb1b5975d44d49dab1d9384089ffeec819e19c6940bc7ffbc84775fad", size = 34713 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/67/f56c69a98c7eb244025845506387d0f961681657c9fcd8b2d2edd148f9d2/python_oxmsg-0.0.2-py3-none-any.whl", hash = "sha256:22be29b14c46016bcd05e34abddfd8e05ee82082f53b82753d115da3fc7d0355", size = 31455 }, +] + +[[package]] +name = "python-pptx" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml" }, + { name = "pillow" }, + { name = "typing-extensions" }, + { name = "xlsxwriter" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/a9/0c0db8d37b2b8a645666f7fd8accea4c6224e013c42b1d5c17c93590cd06/python_pptx-1.0.2.tar.gz", hash = "sha256:479a8af0eaf0f0d76b6f00b0887732874ad2e3188230315290cd1f9dd9cc7095", size = 10109297 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788 }, +] + +[[package]] +name = "python3-xlib" +version = "0.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/c6/2c5999de3bb1533521f1101e8fe56fd9c266732f4d48011c7c69b29d12ae/python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8", size = 132828 } + +[[package]] +name = "pytweening" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/0c/c16bc93ac2755bac0066a8ecbd2a2931a1735a6fffd99a2b9681c7e83e90/pytweening-1.2.0.tar.gz", hash = "sha256:243318b7736698066c5f362ec5c2b6434ecf4297c3c8e7caa8abfe6af4cac71b", size = 171241 } + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 }, +] + +[[package]] +name = "pywin32" +version = "310" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/da/a5f38fffbba2fb99aa4aa905480ac4b8e83ca486659ac8c95bce47fb5276/pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1", size = 8848240 }, + { url = "https://files.pythonhosted.org/packages/aa/fe/d873a773324fa565619ba555a82c9dabd677301720f3660a731a5d07e49a/pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d", size = 9601854 }, + { url = "https://files.pythonhosted.org/packages/3c/84/1a8e3d7a15490d28a5d816efa229ecb4999cdc51a7c30dd8914f669093b8/pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213", size = 8522963 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, +] + +[[package]] +name = "qdrant-client" +version = "1.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "grpcio" }, + { name = "httpx", extra = ["http2"] }, + { name = "numpy" }, + { name = "portalocker" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/56/3f355f931c239c260b4fe3bd6433ec6c9e6185cd5ae0970fe89d0ca6daee/qdrant_client-1.14.3.tar.gz", hash = "sha256:bb899e3e065b79c04f5e47053d59176150c0a5dabc09d7f476c8ce8e52f4d281", size = 286766 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/5e/8174c845707e60b60b65c58f01e40bbc1d8181b5ff6463f25df470509917/qdrant_client-1.14.3-py3-none-any.whl", hash = "sha256:66faaeae00f9b5326946851fe4ca4ddb1ad226490712e2f05142266f68dfc04d", size = 328969 }, +] + +[[package]] +name = "rank-bm25" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/0a/f9579384aa017d8b4c15613f86954b92a95a93d641cc849182467cf0bb3b/rank_bm25-0.2.2.tar.gz", hash = "sha256:096ccef76f8188563419aaf384a02f0ea459503fdf77901378d4fd9d87e5e51d", size = 8347 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/21/f691fb2613100a62b3fa91e9988c991e9ca5b89ea31c0d3152a3210344f9/rank_bm25-0.2.2-py3-none-any.whl", hash = "sha256:7bd4a95571adadfc271746fa146a4bcfd89c0cf731e49c3d1ad863290adbe8ae", size = 8584 }, +] + +[[package]] +name = "rapidfuzz" +version = "3.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/6895abc3a3d056b9698da3199b04c0e56226d530ae44a470edabf8b664f0/rapidfuzz-3.13.0.tar.gz", hash = "sha256:d2eaf3839e52cbcc0accbe9817a67b4b0fcf70aaeb229cfddc1c28061f9ce5d8", size = 57904226 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/27/ca10b3166024ae19a7e7c21f73c58dfd4b7fef7420e5497ee64ce6b73453/rapidfuzz-3.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aafc42a1dc5e1beeba52cd83baa41372228d6d8266f6d803c16dbabbcc156255", size = 1998899 }, + { url = "https://files.pythonhosted.org/packages/f0/38/c4c404b13af0315483a6909b3a29636e18e1359307fb74a333fdccb3730d/rapidfuzz-3.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:85c9a131a44a95f9cac2eb6e65531db014e09d89c4f18c7b1fa54979cb9ff1f3", size = 1449949 }, + { url = "https://files.pythonhosted.org/packages/12/ae/15c71d68a6df6b8e24595421fdf5bcb305888318e870b7be8d935a9187ee/rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d7cec4242d30dd521ef91c0df872e14449d1dffc2a6990ede33943b0dae56c3", size = 1424199 }, + { url = "https://files.pythonhosted.org/packages/dc/9a/765beb9e14d7b30d12e2d6019e8b93747a0bedbc1d0cce13184fa3825426/rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e297c09972698c95649e89121e3550cee761ca3640cd005e24aaa2619175464e", size = 5352400 }, + { url = "https://files.pythonhosted.org/packages/e2/b8/49479fe6f06b06cd54d6345ed16de3d1ac659b57730bdbe897df1e059471/rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef0f5f03f61b0e5a57b1df7beafd83df993fd5811a09871bad6038d08e526d0d", size = 1652465 }, + { url = "https://files.pythonhosted.org/packages/6f/d8/08823d496b7dd142a7b5d2da04337df6673a14677cfdb72f2604c64ead69/rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d8cf5f7cd6e4d5eb272baf6a54e182b2c237548d048e2882258336533f3f02b7", size = 1616590 }, + { url = "https://files.pythonhosted.org/packages/38/d4/5cfbc9a997e544f07f301c54d42aac9e0d28d457d543169e4ec859b8ce0d/rapidfuzz-3.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9256218ac8f1a957806ec2fb9a6ddfc6c32ea937c0429e88cf16362a20ed8602", size = 3086956 }, + { url = "https://files.pythonhosted.org/packages/25/1e/06d8932a72fa9576095234a15785136407acf8f9a7dbc8136389a3429da1/rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1bdd2e6d0c5f9706ef7595773a81ca2b40f3b33fd7f9840b726fb00c6c4eb2e", size = 2494220 }, + { url = "https://files.pythonhosted.org/packages/03/16/5acf15df63119d5ca3d9a54b82807866ff403461811d077201ca351a40c3/rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5280be8fd7e2bee5822e254fe0a5763aa0ad57054b85a32a3d9970e9b09bbcbf", size = 7585481 }, + { url = "https://files.pythonhosted.org/packages/e1/cf/ebade4009431ea8e715e59e882477a970834ddaacd1a670095705b86bd0d/rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd742c03885db1fce798a1cd87a20f47f144ccf26d75d52feb6f2bae3d57af05", size = 2894842 }, + { url = "https://files.pythonhosted.org/packages/a7/bd/0732632bd3f906bf613229ee1b7cbfba77515db714a0e307becfa8a970ae/rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5435fcac94c9ecf0504bf88a8a60c55482c32e18e108d6079a0089c47f3f8cf6", size = 3438517 }, + { url = "https://files.pythonhosted.org/packages/83/89/d3bd47ec9f4b0890f62aea143a1e35f78f3d8329b93d9495b4fa8a3cbfc3/rapidfuzz-3.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:93a755266856599be4ab6346273f192acde3102d7aa0735e2f48b456397a041f", size = 4412773 }, + { url = "https://files.pythonhosted.org/packages/b3/57/1a152a07883e672fc117c7f553f5b933f6e43c431ac3fd0e8dae5008f481/rapidfuzz-3.13.0-cp310-cp310-win32.whl", hash = "sha256:3abe6a4e8eb4cfc4cda04dd650a2dc6d2934cbdeda5def7e6fd1c20f6e7d2a0b", size = 1842334 }, + { url = "https://files.pythonhosted.org/packages/a7/68/7248addf95b6ca51fc9d955161072285da3059dd1472b0de773cff910963/rapidfuzz-3.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:e8ddb58961401da7d6f55f185512c0d6bd24f529a637078d41dd8ffa5a49c107", size = 1624392 }, + { url = "https://files.pythonhosted.org/packages/68/23/f41c749f2c61ed1ed5575eaf9e73ef9406bfedbf20a3ffa438d15b5bf87e/rapidfuzz-3.13.0-cp310-cp310-win_arm64.whl", hash = "sha256:c523620d14ebd03a8d473c89e05fa1ae152821920c3ff78b839218ff69e19ca3", size = 865584 }, + { url = "https://files.pythonhosted.org/packages/d5/e1/f5d85ae3c53df6f817ca70dbdd37c83f31e64caced5bb867bec6b43d1fdf/rapidfuzz-3.13.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe5790a36d33a5d0a6a1f802aa42ecae282bf29ac6f7506d8e12510847b82a45", size = 1904437 }, + { url = "https://files.pythonhosted.org/packages/db/d7/ded50603dddc5eb182b7ce547a523ab67b3bf42b89736f93a230a398a445/rapidfuzz-3.13.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:cdb33ee9f8a8e4742c6b268fa6bd739024f34651a06b26913381b1413ebe7590", size = 1383126 }, + { url = "https://files.pythonhosted.org/packages/c4/48/6f795e793babb0120b63a165496d64f989b9438efbeed3357d9a226ce575/rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c99b76b93f7b495eee7dcb0d6a38fb3ce91e72e99d9f78faa5664a881cb2b7d", size = 1365565 }, + { url = "https://files.pythonhosted.org/packages/f0/50/0062a959a2d72ed17815824e40e2eefdb26f6c51d627389514510a7875f3/rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6af42f2ede8b596a6aaf6d49fdee3066ca578f4856b85ab5c1e2145de367a12d", size = 5251719 }, + { url = "https://files.pythonhosted.org/packages/e7/02/bd8b70cd98b7a88e1621264778ac830c9daa7745cd63e838bd773b1aeebd/rapidfuzz-3.13.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c0efa73afbc5b265aca0d8a467ae2a3f40d6854cbe1481cb442a62b7bf23c99", size = 2991095 }, + { url = "https://files.pythonhosted.org/packages/9f/8d/632d895cdae8356826184864d74a5f487d40cb79f50a9137510524a1ba86/rapidfuzz-3.13.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7ac21489de962a4e2fc1e8f0b0da4aa1adc6ab9512fd845563fecb4b4c52093a", size = 1553888 }, +] + +[[package]] +name = "readabilipy" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "html5lib" }, + { name = "lxml" }, + { name = "regex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b8/e4/260a202516886c2e0cc6e6ae96d1f491792d829098886d9529a2439fbe8e/readabilipy-0.3.0.tar.gz", hash = "sha256:e13313771216953935ac031db4234bdb9725413534bfb3c19dbd6caab0887ae0", size = 35491 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/46/8a640c6de1a6c6af971f858b2fb178ca5e1db91f223d8ba5f40efe1491e5/readabilipy-0.3.0-py3-none-any.whl", hash = "sha256:d106da0fad11d5fdfcde21f5c5385556bfa8ff0258483037d39ea6b1d6db3943", size = 22158 }, +] + +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775 }, +] + +[[package]] +name = "regex" +version = "2024.11.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/3c/4651f6b130c6842a8f3df82461a8950f923925db8b6961063e82744bddcc/regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91", size = 482674 }, + { url = "https://files.pythonhosted.org/packages/15/51/9f35d12da8434b489c7b7bffc205c474a0a9432a889457026e9bc06a297a/regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0", size = 287684 }, + { url = "https://files.pythonhosted.org/packages/bd/18/b731f5510d1b8fb63c6b6d3484bfa9a59b84cc578ac8b5172970e05ae07c/regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e", size = 284589 }, + { url = "https://files.pythonhosted.org/packages/78/a2/6dd36e16341ab95e4c6073426561b9bfdeb1a9c9b63ab1b579c2e96cb105/regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde", size = 782511 }, + { url = "https://files.pythonhosted.org/packages/1b/2b/323e72d5d2fd8de0d9baa443e1ed70363ed7e7b2fb526f5950c5cb99c364/regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e", size = 821149 }, + { url = "https://files.pythonhosted.org/packages/90/30/63373b9ea468fbef8a907fd273e5c329b8c9535fee36fc8dba5fecac475d/regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2", size = 809707 }, + { url = "https://files.pythonhosted.org/packages/f2/98/26d3830875b53071f1f0ae6d547f1d98e964dd29ad35cbf94439120bb67a/regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf", size = 781702 }, + { url = "https://files.pythonhosted.org/packages/87/55/eb2a068334274db86208ab9d5599ffa63631b9f0f67ed70ea7c82a69bbc8/regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c", size = 771976 }, + { url = "https://files.pythonhosted.org/packages/74/c0/be707bcfe98254d8f9d2cff55d216e946f4ea48ad2fd8cf1428f8c5332ba/regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86", size = 697397 }, + { url = "https://files.pythonhosted.org/packages/49/dc/bb45572ceb49e0f6509f7596e4ba7031f6819ecb26bc7610979af5a77f45/regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67", size = 768726 }, + { url = "https://files.pythonhosted.org/packages/5a/db/f43fd75dc4c0c2d96d0881967897926942e935d700863666f3c844a72ce6/regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d", size = 775098 }, + { url = "https://files.pythonhosted.org/packages/99/d7/f94154db29ab5a89d69ff893159b19ada89e76b915c1293e98603d39838c/regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2", size = 839325 }, + { url = "https://files.pythonhosted.org/packages/f7/17/3cbfab1f23356fbbf07708220ab438a7efa1e0f34195bf857433f79f1788/regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008", size = 843277 }, + { url = "https://files.pythonhosted.org/packages/7e/f2/48b393b51900456155de3ad001900f94298965e1cad1c772b87f9cfea011/regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62", size = 773197 }, + { url = "https://files.pythonhosted.org/packages/45/3f/ef9589aba93e084cd3f8471fded352826dcae8489b650d0b9b27bc5bba8a/regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e", size = 261714 }, + { url = "https://files.pythonhosted.org/packages/42/7e/5f1b92c8468290c465fd50c5318da64319133231415a8aa6ea5ab995a815/regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519", size = 274042 }, +] + +[[package]] +name = "reportlab" +version = "4.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "charset-normalizer" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/9b/3483c7e4ad33d15f22d528872439e5bc92485814d7e7d10dbc3130368a83/reportlab-4.4.2.tar.gz", hash = "sha256:fc6283048ddd0781a9db1d671715990e6aa059c8d40ec9baf34294c4bd583a36", size = 3509063 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/74/ed990bc9586605d4e46f6b0e0b978a5b8e757aa599e39664bee26d6dc666/reportlab-4.4.2-py3-none-any.whl", hash = "sha256:58e11be387457928707c12153b7e41e52533a5da3f587b15ba8f8fd0805c6ee2", size = 1953624 }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847 }, +] + +[[package]] +name = "requests-file" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/97/bf44e6c6bd8ddbb99943baf7ba8b1a8485bcd2fe0e55e5708d7fee4ff1ae/requests_file-2.1.0.tar.gz", hash = "sha256:0f549a3f3b0699415ac04d167e9cb39bccfb730cb832b4d20be3d9867356e658", size = 6891 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/25/dd878a121fcfdf38f52850f11c512e13ec87c2ea72385933818e5b6c15ce/requests_file-2.1.0-py2.py3-none-any.whl", hash = "sha256:cf270de5a4c5874e84599fc5778303d496c10ae5e870bfa378818f35d21bda5c", size = 4244 }, +] + +[[package]] +name = "requests-oauthlib" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/52/531ef197b426646f26b53815a7d2a67cb7a331ef098bb276db26a68ac49f/requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a", size = 52027 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/bb/5deac77a9af870143c684ab46a7934038a53eb4aa975bc0687ed6ca2c610/requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5", size = 23892 }, +] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481 }, +] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490 }, +] + +[[package]] +name = "rich" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 }, +] + +[[package]] +name = "rouge" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/e4/3420a1ab1e82a280fb6107f7ae99e88eb12383c978fe573c0c64d0327d6b/rouge-1.0.1.tar.gz", hash = "sha256:12b48346ca47d6bcf3c45061f315452b9ccec0620ee895ec85b7efc3d54aae34", size = 14292 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/7c/650ae86f92460e9e8ef969cc5008b24798dcf56a9a8947d04c78f550b3f5/rouge-1.0.1-py3-none-any.whl", hash = "sha256:28d118536e8c774dc47d1d15ec266479b4dd0914c4672ce117d4002789bdc644", size = 13725 }, +] + +[[package]] +name = "rpds-py" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/a6/60184b7fc00dd3ca80ac635dd5b8577d444c57e8e8742cecabfacb829921/rpds_py-0.25.1.tar.gz", hash = "sha256:8960b6dac09b62dac26e75d7e2c4a22efb835d827a7278c34f72b2b84fa160e3", size = 27304 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/09/e1158988e50905b7f8306487a576b52d32aa9a87f79f7ab24ee8db8b6c05/rpds_py-0.25.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f4ad628b5174d5315761b67f212774a32f5bad5e61396d38108bd801c0a8f5d9", size = 373140 }, + { url = "https://files.pythonhosted.org/packages/e0/4b/a284321fb3c45c02fc74187171504702b2934bfe16abab89713eedfe672e/rpds_py-0.25.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c742af695f7525e559c16f1562cf2323db0e3f0fbdcabdf6865b095256b2d40", size = 358860 }, + { url = "https://files.pythonhosted.org/packages/4e/46/8ac9811150c75edeae9fc6fa0e70376c19bc80f8e1f7716981433905912b/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:605ffe7769e24b1800b4d024d24034405d9404f0bc2f55b6db3362cd34145a6f", size = 386179 }, + { url = "https://files.pythonhosted.org/packages/f3/ec/87eb42d83e859bce91dcf763eb9f2ab117142a49c9c3d17285440edb5b69/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc6f3ddef93243538be76f8e47045b4aad7a66a212cd3a0f23e34469473d36b", size = 400282 }, + { url = "https://files.pythonhosted.org/packages/68/c8/2a38e0707d7919c8c78e1d582ab15cf1255b380bcb086ca265b73ed6db23/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f70316f760174ca04492b5ab01be631a8ae30cadab1d1081035136ba12738cfa", size = 521824 }, + { url = "https://files.pythonhosted.org/packages/5e/2c/6a92790243569784dde84d144bfd12bd45102f4a1c897d76375076d730ab/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1dafef8df605fdb46edcc0bf1573dea0d6d7b01ba87f85cd04dc855b2b4479e", size = 411644 }, + { url = "https://files.pythonhosted.org/packages/eb/76/66b523ffc84cf47db56efe13ae7cf368dee2bacdec9d89b9baca5e2e6301/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0701942049095741a8aeb298a31b203e735d1c61f4423511d2b1a41dcd8a16da", size = 386955 }, + { url = "https://files.pythonhosted.org/packages/b6/b9/a362d7522feaa24dc2b79847c6175daa1c642817f4a19dcd5c91d3e2c316/rpds_py-0.25.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e87798852ae0b37c88babb7f7bbbb3e3fecc562a1c340195b44c7e24d403e380", size = 421039 }, + { url = "https://files.pythonhosted.org/packages/0f/c4/b5b6f70b4d719b6584716889fd3413102acf9729540ee76708d56a76fa97/rpds_py-0.25.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3bcce0edc1488906c2d4c75c94c70a0417e83920dd4c88fec1078c94843a6ce9", size = 563290 }, + { url = "https://files.pythonhosted.org/packages/87/a3/2e6e816615c12a8f8662c9d8583a12eb54c52557521ef218cbe3095a8afa/rpds_py-0.25.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e2f6a2347d3440ae789505693a02836383426249d5293541cd712e07e7aecf54", size = 592089 }, + { url = "https://files.pythonhosted.org/packages/c0/08/9b8e1050e36ce266135994e2c7ec06e1841f1c64da739daeb8afe9cb77a4/rpds_py-0.25.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4fd52d3455a0aa997734f3835cbc4c9f32571345143960e7d7ebfe7b5fbfa3b2", size = 558400 }, + { url = "https://files.pythonhosted.org/packages/f2/df/b40b8215560b8584baccd839ff5c1056f3c57120d79ac41bd26df196da7e/rpds_py-0.25.1-cp310-cp310-win32.whl", hash = "sha256:3f0b1798cae2bbbc9b9db44ee068c556d4737911ad53a4e5093d09d04b3bbc24", size = 219741 }, + { url = "https://files.pythonhosted.org/packages/10/99/e4c58be18cf5d8b40b8acb4122bc895486230b08f978831b16a3916bd24d/rpds_py-0.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:3ebd879ab996537fc510a2be58c59915b5dd63bccb06d1ef514fee787e05984a", size = 231553 }, + { url = "https://files.pythonhosted.org/packages/78/ff/566ce53529b12b4f10c0a348d316bd766970b7060b4fd50f888be3b3b281/rpds_py-0.25.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b24bf3cd93d5b6ecfbedec73b15f143596c88ee249fa98cefa9a9dc9d92c6f28", size = 373931 }, + { url = "https://files.pythonhosted.org/packages/83/5d/deba18503f7c7878e26aa696e97f051175788e19d5336b3b0e76d3ef9256/rpds_py-0.25.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:0eb90e94f43e5085623932b68840b6f379f26db7b5c2e6bcef3179bd83c9330f", size = 359074 }, + { url = "https://files.pythonhosted.org/packages/0d/74/313415c5627644eb114df49c56a27edba4d40cfd7c92bd90212b3604ca84/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d50e4864498a9ab639d6d8854b25e80642bd362ff104312d9770b05d66e5fb13", size = 387255 }, + { url = "https://files.pythonhosted.org/packages/8c/c8/c723298ed6338963d94e05c0f12793acc9b91d04ed7c4ba7508e534b7385/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c9409b47ba0650544b0bb3c188243b83654dfe55dcc173a86832314e1a6a35d", size = 400714 }, + { url = "https://files.pythonhosted.org/packages/33/8a/51f1f6aa653c2e110ed482ef2ae94140d56c910378752a1b483af11019ee/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:796ad874c89127c91970652a4ee8b00d56368b7e00d3477f4415fe78164c8000", size = 523105 }, + { url = "https://files.pythonhosted.org/packages/c7/a4/7873d15c088ad3bff36910b29ceb0f178e4b3232c2adbe9198de68a41e63/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85608eb70a659bf4c1142b2781083d4b7c0c4e2c90eff11856a9754e965b2540", size = 411499 }, + { url = "https://files.pythonhosted.org/packages/90/f3/0ce1437befe1410766d11d08239333ac1b2d940f8a64234ce48a7714669c/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4feb9211d15d9160bc85fa72fed46432cdc143eb9cf6d5ca377335a921ac37b", size = 387918 }, + { url = "https://files.pythonhosted.org/packages/94/d4/5551247988b2a3566afb8a9dba3f1d4a3eea47793fd83000276c1a6c726e/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ccfa689b9246c48947d31dd9d8b16d89a0ecc8e0e26ea5253068efb6c542b76e", size = 421705 }, + { url = "https://files.pythonhosted.org/packages/b0/25/5960f28f847bf736cc7ee3c545a7e1d2f3b5edaf82c96fb616c2f5ed52d0/rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3c5b317ecbd8226887994852e85de562f7177add602514d4ac40f87de3ae45a8", size = 564489 }, + { url = "https://files.pythonhosted.org/packages/02/66/1c99884a0d44e8c2904d3c4ec302f995292d5dde892c3bf7685ac1930146/rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:454601988aab2c6e8fd49e7634c65476b2b919647626208e376afcd22019eeb8", size = 592557 }, + { url = "https://files.pythonhosted.org/packages/55/ae/4aeac84ebeffeac14abb05b3bb1d2f728d00adb55d3fb7b51c9fa772e760/rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:1c0c434a53714358532d13539272db75a5ed9df75a4a090a753ac7173ec14e11", size = 558691 }, + { url = "https://files.pythonhosted.org/packages/41/b3/728a08ff6f5e06fe3bb9af2e770e9d5fd20141af45cff8dfc62da4b2d0b3/rpds_py-0.25.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f73ce1512e04fbe2bc97836e89830d6b4314c171587a99688082d090f934d20a", size = 231651 }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696 }, +] + +[[package]] +name = "ruamel-yaml" +version = "0.18.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ruamel-yaml-clib", marker = "platform_python_implementation == 'CPython'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/87/6da0df742a4684263261c253f00edd5829e6aca970fff69e75028cccc547/ruamel.yaml-0.18.14.tar.gz", hash = "sha256:7227b76aaec364df15936730efbf7d72b30c0b79b1d578bbb8e3dcb2d81f52b7", size = 145511 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/6d/6fe4805235e193aad4aaf979160dd1f3c487c57d48b810c816e6e842171b/ruamel.yaml-0.18.14-py3-none-any.whl", hash = "sha256:710ff198bb53da66718c7db27eec4fbcc9aa6ca7204e4c1df2f282b6fe5eb6b2", size = 118570 }, +] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/84/80203abff8ea4993a87d823a5f632e4d92831ef75d404c9fc78d0176d2b5/ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", size = 225315 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/57/40a958e863e299f0c74ef32a3bde9f2d1ea8d69669368c0c502a0997f57f/ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5", size = 131301 }, + { url = "https://files.pythonhosted.org/packages/98/a8/29a3eb437b12b95f50a6bcc3d7d7214301c6c529d8fdc227247fa84162b5/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969", size = 633728 }, + { url = "https://files.pythonhosted.org/packages/35/6d/ae05a87a3ad540259c3ad88d71275cbd1c0f2d30ae04c65dcbfb6dcd4b9f/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df", size = 722230 }, + { url = "https://files.pythonhosted.org/packages/7f/b7/20c6f3c0b656fe609675d69bc135c03aac9e3865912444be6339207b6648/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76", size = 686712 }, + { url = "https://files.pythonhosted.org/packages/cd/11/d12dbf683471f888d354dac59593873c2b45feb193c5e3e0f2ebf85e68b9/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6", size = 663936 }, + { url = "https://files.pythonhosted.org/packages/72/14/4c268f5077db5c83f743ee1daeb236269fa8577133a5cfa49f8b382baf13/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd", size = 696580 }, + { url = "https://files.pythonhosted.org/packages/30/fc/8cd12f189c6405a4c1cf37bd633aa740a9538c8e40497c231072d0fef5cf/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a", size = 663393 }, + { url = "https://files.pythonhosted.org/packages/80/29/c0a017b704aaf3cbf704989785cd9c5d5b8ccec2dae6ac0c53833c84e677/ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da", size = 100326 }, + { url = "https://files.pythonhosted.org/packages/3a/65/fa39d74db4e2d0cd252355732d966a460a41cd01c6353b820a0952432839/ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28", size = 118079 }, +] + +[[package]] +name = "rubicon-objc" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/83/e57741dcf862a2581d53eccf8b11749c97f73d9754bbc538fb6c7b527da3/rubicon_objc-0.5.1.tar.gz", hash = "sha256:90bee9fc1de4515e17615e15648989b88bb8d4d2ffc8c7c52748272cd7f30a66", size = 174639 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/0a/e451c3dbda38dd6abab1fd16c3b35623fc0635dffcbbf97f1acc55a58508/rubicon_objc-0.5.1-py3-none-any.whl", hash = "sha256:17092756241b8370231cfaad45ad6e8ce99534987f2acbc944d65df5bdf8f6cd", size = 63323 }, +] + +[[package]] +name = "safetensors" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/7e/2d5d6ee7b40c0682315367ec7475693d110f512922d582fef1bd4a63adc3/safetensors-0.5.3.tar.gz", hash = "sha256:b6b0d6ecacec39a4fdd99cc19f4576f5219ce858e6fd8dbe7609df0b8dc56965", size = 67210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/ae/88f6c49dbd0cc4da0e08610019a3c78a7d390879a919411a410a1876d03a/safetensors-0.5.3-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd20eb133db8ed15b40110b7c00c6df51655a2998132193de2f75f72d99c7073", size = 436917 }, + { url = "https://files.pythonhosted.org/packages/b8/3b/11f1b4a2f5d2ab7da34ecc062b0bc301f2be024d110a6466726bec8c055c/safetensors-0.5.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:21d01c14ff6c415c485616b8b0bf961c46b3b343ca59110d38d744e577f9cce7", size = 418419 }, + { url = "https://files.pythonhosted.org/packages/5d/9a/add3e6fef267658075c5a41573c26d42d80c935cdc992384dfae435feaef/safetensors-0.5.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11bce6164887cd491ca75c2326a113ba934be596e22b28b1742ce27b1d076467", size = 459493 }, + { url = "https://files.pythonhosted.org/packages/df/5c/bf2cae92222513cc23b3ff85c4a1bb2811a2c3583ac0f8e8d502751de934/safetensors-0.5.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a243be3590bc3301c821da7a18d87224ef35cbd3e5f5727e4e0728b8172411e", size = 472400 }, + { url = "https://files.pythonhosted.org/packages/58/11/7456afb740bd45782d0f4c8e8e1bb9e572f1bf82899fb6ace58af47b4282/safetensors-0.5.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8bd84b12b1670a6f8e50f01e28156422a2bc07fb16fc4e98bded13039d688a0d", size = 522891 }, + { url = "https://files.pythonhosted.org/packages/57/3d/fe73a9d2ace487e7285f6e157afee2383bd1ddb911b7cb44a55cf812eae3/safetensors-0.5.3-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:391ac8cab7c829452175f871fcaf414aa1e292b5448bd02620f675a7f3e7abb9", size = 537694 }, + { url = "https://files.pythonhosted.org/packages/a6/f8/dae3421624fcc87a89d42e1898a798bc7ff72c61f38973a65d60df8f124c/safetensors-0.5.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cead1fa41fc54b1e61089fa57452e8834f798cb1dc7a09ba3524f1eb08e0317a", size = 471642 }, + { url = "https://files.pythonhosted.org/packages/ce/20/1fbe16f9b815f6c5a672f5b760951e20e17e43f67f231428f871909a37f6/safetensors-0.5.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1077f3e94182d72618357b04b5ced540ceb71c8a813d3319f1aba448e68a770d", size = 502241 }, + { url = "https://files.pythonhosted.org/packages/5f/18/8e108846b506487aa4629fe4116b27db65c3dde922de2c8e0cc1133f3f29/safetensors-0.5.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:799021e78287bac619c7b3f3606730a22da4cda27759ddf55d37c8db7511c74b", size = 638001 }, + { url = "https://files.pythonhosted.org/packages/82/5a/c116111d8291af6c8c8a8b40628fe833b9db97d8141c2a82359d14d9e078/safetensors-0.5.3-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df26da01aaac504334644e1b7642fa000bfec820e7cef83aeac4e355e03195ff", size = 734013 }, + { url = "https://files.pythonhosted.org/packages/7d/ff/41fcc4d3b7de837963622e8610d998710705bbde9a8a17221d85e5d0baad/safetensors-0.5.3-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:32c3ef2d7af8b9f52ff685ed0bc43913cdcde135089ae322ee576de93eae5135", size = 670687 }, + { url = "https://files.pythonhosted.org/packages/40/ad/2b113098e69c985a3d8fbda4b902778eae4a35b7d5188859b4a63d30c161/safetensors-0.5.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:37f1521be045e56fc2b54c606d4455573e717b2d887c579ee1dbba5f868ece04", size = 643147 }, + { url = "https://files.pythonhosted.org/packages/0a/0c/95aeb51d4246bd9a3242d3d8349c1112b4ee7611a4b40f0c5c93b05f001d/safetensors-0.5.3-cp38-abi3-win32.whl", hash = "sha256:cfc0ec0846dcf6763b0ed3d1846ff36008c6e7290683b61616c4b040f6a54ace", size = 296677 }, + { url = "https://files.pythonhosted.org/packages/69/e2/b011c38e5394c4c18fb5500778a55ec43ad6106126e74723ffaee246f56e/safetensors-0.5.3-cp38-abi3-win_amd64.whl", hash = "sha256:836cbbc320b47e80acd40e44c8682db0e8ad7123209f69b093def21ec7cafd11", size = 308878 }, +] + +[[package]] +name = "scenedetect" +version = "0.6.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "numpy" }, + { name = "platformdirs" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/36/1e29ac958e2d2b5e4365fb7de03f94a98b9949c46267e682bcfe22460812/scenedetect-0.6.6.tar.gz", hash = "sha256:4b50946abca886bd623e7a304e30da197f0e7e69cd65d80115d551538261c35b", size = 165791 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/5b/c090fe55521265eb1816c303267e985125000a4e32237e95562ed462608f/scenedetect-0.6.6-py3-none-any.whl", hash = "sha256:cbd47e4aff1d3ba6f4ee00e54ff9af26378aa2b48f501003dddf5f96e37d3eb0", size = 131581 }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770 }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511 }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151 }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732 }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617 }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964 }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749 }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383 }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201 }, +] + +[[package]] +name = "scrapegraph-py" +version = "1.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "beautifulsoup4" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/36/10546d18157cd2efb2de09098fac8dd6f689b98842a4cc71bb7fc29ba4b9/scrapegraph_py-1.12.0.tar.gz", hash = "sha256:82d27e8ea325975f768f80d4edf403b6294518dae6a1e3ae63e27b8934a5dacb", size = 113290 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/a8/8610143e9ebad9596e402260f63cbb6168f99719f07e13847b1df5a28f4d/scrapegraph_py-1.12.0-py3-none-any.whl", hash = "sha256:fd74d091529d3f8f5ba057950333e15a48ac5c0be7e2a56a8f2bad04cebdac30", size = 15458 }, +] + +[[package]] +name = "sentencepiece" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/d2/b9c7ca067c26d8ff085d252c89b5f69609ca93fb85a00ede95f4857865d4/sentencepiece-0.2.0.tar.gz", hash = "sha256:a52c19171daaf2e697dc6cbe67684e0fa341b1248966f6aebb541de654d15843", size = 2632106 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/71/98648c3b64b23edb5403f74bcc906ad21766872a6e1ada26ea3f1eb941ab/sentencepiece-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:188779e1298a1c8b8253c7d3ad729cb0a9891e5cef5e5d07ce4592c54869e227", size = 2408979 }, + { url = "https://files.pythonhosted.org/packages/77/9f/7efbaa6d4c0c718a9affbecc536b03ca62f99f421bdffb531c16030e2d2b/sentencepiece-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bed9cf85b296fa2b76fc2547b9cbb691a523864cebaee86304c43a7b4cb1b452", size = 1238845 }, + { url = "https://files.pythonhosted.org/packages/1c/e4/c2541027a43ec6962ba9b601805d17ba3f86b38bdeae0e8ac65a2981e248/sentencepiece-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d7b67e724bead13f18db6e1d10b6bbdc454af574d70efbb36f27d90387be1ca3", size = 1181472 }, + { url = "https://files.pythonhosted.org/packages/fd/46/316c1ba6c52b97de76aff7b9da678f7afbb52136afb2987c474d95630e65/sentencepiece-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fde4b08cfe237be4484c6c7c2e2c75fb862cfeab6bd5449ce4caeafd97b767a", size = 1259151 }, + { url = "https://files.pythonhosted.org/packages/aa/5a/3c48738a0835d76dd06c62b6ac48d39c923cde78dd0f587353bdcbb99851/sentencepiece-0.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c378492056202d1c48a4979650981635fd97875a00eabb1f00c6a236b013b5e", size = 1355931 }, + { url = "https://files.pythonhosted.org/packages/a6/27/33019685023221ca8ed98e8ceb7ae5e166032686fa3662c68f1f1edf334e/sentencepiece-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1380ce6540a368de2ef6d7e6ba14ba8f3258df650d39ba7d833b79ee68a52040", size = 1301537 }, + { url = "https://files.pythonhosted.org/packages/ca/e4/55f97cef14293171fef5f96e96999919ab5b4d1ce95b53547ad653d7e3bf/sentencepiece-0.2.0-cp310-cp310-win32.whl", hash = "sha256:a1151d6a6dd4b43e552394aed0edfe9292820272f0194bd56c7c1660a0c06c3d", size = 936747 }, + { url = "https://files.pythonhosted.org/packages/85/f4/4ef1a6e0e9dbd8a60780a91df8b7452ada14cfaa0e17b3b8dfa42cecae18/sentencepiece-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:d490142b0521ef22bc1085f061d922a2a6666175bb6b42e588ff95c0db6819b2", size = 991525 }, +] + +[[package]] +name = "sgmllib3k" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/bd/3704a8c3e0942d711c1299ebf7b9091930adae6675d7c8f476a7ce48653c/sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9", size = 5750 } + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "slack-sdk" +version = "3.35.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/32/a5/13077a5696ded22cc955ff6314028b7e6140b1c989b19ca27a6b26590e6e/slack_sdk-3.35.0.tar.gz", hash = "sha256:8183b6cbf26a0c1e2441478cd9c0dc4eef08d60c1394cfdc9a769e309a9b6459", size = 232887 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/8e/eed71dc79a187ba32681f12a104786ab89355bc474082211d92e1fba6bcf/slack_sdk-3.35.0-py2.py3-none-any.whl", hash = "sha256:00933d171fbd8a068b321ebb5f89612cc781d3183d8e3447c85499eca9d865be", size = 293272 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, +] + +[[package]] +name = "socksio" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/48a7d9495be3d1c651198fd99dbb6ce190e2274d0f28b9051307bdec6b85/socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac", size = 19055 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/c3/6eeb6034408dac0fa653d126c9204ade96b819c936e136c5e8a6897eee9c/socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3", size = 12763 }, +] + +[[package]] +name = "soundfile" +version = "0.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/41/9b873a8c055582859b239be17902a85339bec6a30ad162f98c9b0288a2cc/soundfile-0.13.1.tar.gz", hash = "sha256:b2c68dab1e30297317080a5b43df57e302584c49e2942defdde0acccc53f0e5b", size = 46156 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/28/e2a36573ccbcf3d57c00626a21fe51989380636e821b341d36ccca0c1c3a/soundfile-0.13.1-py2.py3-none-any.whl", hash = "sha256:a23c717560da2cf4c7b5ae1142514e0fd82d6bbd9dfc93a50423447142f2c445", size = 25751 }, + { url = "https://files.pythonhosted.org/packages/ea/ab/73e97a5b3cc46bba7ff8650a1504348fa1863a6f9d57d7001c6b67c5f20e/soundfile-0.13.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:82dc664d19831933fe59adad199bf3945ad06d84bc111a5b4c0d3089a5b9ec33", size = 1142250 }, + { url = "https://files.pythonhosted.org/packages/a0/e5/58fd1a8d7b26fc113af244f966ee3aecf03cb9293cb935daaddc1e455e18/soundfile-0.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:743f12c12c4054921e15736c6be09ac26b3b3d603aef6fd69f9dde68748f2593", size = 1101406 }, + { url = "https://files.pythonhosted.org/packages/58/ae/c0e4a53d77cf6e9a04179535766b3321b0b9ced5f70522e4caf9329f0046/soundfile-0.13.1-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9c9e855f5a4d06ce4213f31918653ab7de0c5a8d8107cd2427e44b42df547deb", size = 1235729 }, + { url = "https://files.pythonhosted.org/packages/57/5e/70bdd9579b35003a489fc850b5047beeda26328053ebadc1fb60f320f7db/soundfile-0.13.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:03267c4e493315294834a0870f31dbb3b28a95561b80b134f0bd3cf2d5f0e618", size = 1313646 }, + { url = "https://files.pythonhosted.org/packages/fe/df/8c11dc4dfceda14e3003bb81a0d0edcaaf0796dd7b4f826ea3e532146bba/soundfile-0.13.1-py2.py3-none-win32.whl", hash = "sha256:c734564fab7c5ddf8e9be5bf70bab68042cd17e9c214c06e365e20d64f9a69d5", size = 899881 }, + { url = "https://files.pythonhosted.org/packages/14/e9/6b761de83277f2f02ded7e7ea6f07828ec78e4b229b80e4ca55dd205b9dc/soundfile-0.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:1e70a05a0626524a69e9f0f4dd2ec174b4e9567f4d8b6c11d38b5c289be36ee9", size = 1019162 }, +] + +[[package]] +name = "soupsieve" +version = "2.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677 }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.35" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/48/4f190a83525f5cefefa44f6adc9e6386c4de5218d686c27eda92eb1f5424/sqlalchemy-2.0.35.tar.gz", hash = "sha256:e11d7ea4d24f0a262bccf9a7cd6284c976c5369dac21db237cff59586045ab9f", size = 9562798 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/61/19395d0ae78c94f6f80c8adf39a142f3fe56cfb2235d8f2317d6dae1bf0e/SQLAlchemy-2.0.35-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67219632be22f14750f0d1c70e62f204ba69d28f62fd6432ba05ab295853de9b", size = 2090086 }, + { url = "https://files.pythonhosted.org/packages/e6/82/06b5fcbe5d49043e40cf4e01e3b33c471c8d9292d478420b08538cae8928/SQLAlchemy-2.0.35-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4668bd8faf7e5b71c0319407b608f278f279668f358857dbfd10ef1954ac9f90", size = 2081278 }, + { url = "https://files.pythonhosted.org/packages/68/d1/7fb7ee46949a5fb34005795b1fc06a8fef67587a66da731c14e545f7eb5b/SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb8bea573863762bbf45d1e13f87c2d2fd32cee2dbd50d050f83f87429c9e1ea", size = 3063763 }, + { url = "https://files.pythonhosted.org/packages/7e/ff/a1eacd78b31e52a5073e9924fb4722ecc2a72f093ca8181ed81fc61aed2e/SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f552023710d4b93d8fb29a91fadf97de89c5926c6bd758897875435f2a939f33", size = 3072032 }, + { url = "https://files.pythonhosted.org/packages/21/ae/ddfecf149a6d16af87408bca7bd108eef7ef23d376cc8464317efb3cea3f/SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:016b2e665f778f13d3c438651dd4de244214b527a275e0acf1d44c05bc6026a9", size = 3028092 }, + { url = "https://files.pythonhosted.org/packages/cc/51/3e84d42121662a160bacd311cfacb29c1e6a229d59dd8edb09caa8ab283b/SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7befc148de64b6060937231cbff8d01ccf0bfd75aa26383ffdf8d82b12ec04ff", size = 3053543 }, + { url = "https://files.pythonhosted.org/packages/3e/7a/039c78105958da3fc361887f0a82c974cb6fa5bba965c1689ec778be1c01/SQLAlchemy-2.0.35-cp310-cp310-win32.whl", hash = "sha256:22b83aed390e3099584b839b93f80a0f4a95ee7f48270c97c90acd40ee646f0b", size = 2062372 }, + { url = "https://files.pythonhosted.org/packages/a2/50/f31e927d32f9729f69d150ffe47e7cf51e3e0bb2148fc400b3e93a92ca4c/SQLAlchemy-2.0.35-cp310-cp310-win_amd64.whl", hash = "sha256:a29762cd3d116585278ffb2e5b8cc311fb095ea278b96feef28d0b423154858e", size = 2086485 }, + { url = "https://files.pythonhosted.org/packages/0e/c6/33c706449cdd92b1b6d756b247761e27d32230fd6b2de5f44c4c3e5632b2/SQLAlchemy-2.0.35-py3-none-any.whl", hash = "sha256:2ab3f0336c0387662ce6221ad30ab3a5e6499aab01b9790879b6578fd9b8faa1", size = 1881276 }, +] + +[[package]] +name = "sqlglot" +version = "25.34.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/f3/a7d1a7bcd4477e062e540829030f28cebdbbf83000715cb673d2174f5b09/sqlglot-25.34.1.tar.gz", hash = "sha256:6952c083c4a8b8de3c09c10b262a03c6853071bd397f05759c08f1e2f3c683cb", size = 19772522 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/6f/7673d955e127b1b306e22112e9fadd75ed409a60f9b3cfd7325d58d07bc5/sqlglot-25.34.1-py3-none-any.whl", hash = "sha256:15099f8af832e6f5593fb92211d8b3f0810744ac0dc443fb70010fa38dc2562b", size = 435088 }, +] + +[package.optional-dependencies] +rs = [ + { name = "sqlglotrs" }, +] + +[[package]] +name = "sqlglotrs" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/56/745df1a83ead916eb26314a81d3ae8860d9cee30e7d686d8d041aac41ed2/sqlglotrs-0.3.0.tar.gz", hash = "sha256:e77deb4ad2a94024e07ad9c1a15ad573b5503cacc9a948b0f5fd2d6df32156de", size = 9397 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/b4/4071f789ff1b6e5ee252a235ef51764bf7bdc952923865fcbfc9fa937cb6/sqlglotrs-0.3.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:20483ace62f943d50a7caeae57b434d1872f0dfeebc697f5e97a6851e3cef254", size = 296764 }, + { url = "https://files.pythonhosted.org/packages/65/36/c37fb7d4305e6582d9bddc3d2b381bff6b30a401d98e3fffb129090fd065/sqlglotrs-0.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602feea94d9cfbba0d8b7cf40f144ce311f8c11f06b6a49638d6311b799ee578", size = 284964 }, + { url = "https://files.pythonhosted.org/packages/0e/f8/6655f484843a32da5c220dbfcfcb27dd2843a13e088bceb587fa13dc763d/sqlglotrs-0.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27a64bf9770c683be0e345020674e52f04eacfccb74ef3529c0dfbaa25099509", size = 321811 }, + { url = "https://files.pythonhosted.org/packages/50/87/05b73aff212540e127ad78798110eb0f33f6d1cdc6e89656873860b14502/sqlglotrs-0.3.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:09e6291cf28dbab1d4fedbe121e6db6bc5ca2fb4d1d60071b632ca4a543d5448", size = 330117 }, + { url = "https://files.pythonhosted.org/packages/46/78/cc9d655978c7242c8666065761f2a4fd5b5d1aaacc6dbdddf7333238f9b5/sqlglotrs-0.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac61397b933471149a0d4227736f8fa727f90b7ae370bfcef9afe7835e1177b8", size = 370578 }, + { url = "https://files.pythonhosted.org/packages/f5/36/e5a0bc8d997d2a09a86308a2b40107c8262eae56b4768eb421c937de803d/sqlglotrs-0.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:93ba39a9ceafd999c9ccc0e53ff08d284915705db5a739b2ab66064e39010418", size = 375587 }, + { url = "https://files.pythonhosted.org/packages/d6/2b/b8bcf5cde39b5571e68715c6d99511068ceab44a1cd7c0925dc08953ad2b/sqlglotrs-0.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01e059b9ab5ccc98203dee2962e094c8d798cd50d94398740d514d1d5d480171", size = 326979 }, + { url = "https://files.pythonhosted.org/packages/28/38/ac8a07eeae79566c35f757f8365a82b63e41e37476a3be19d8a794042bf8/sqlglotrs-0.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0493ac7c0ec58c1d1f13a931e98389a1dc6492f1ea1ad5b6edcf331ca2a1791", size = 347163 }, + { url = "https://files.pythonhosted.org/packages/7c/04/c19ca120c8a6cf18f8305290f96b84f980daf0120fe2e82ceee668068310/sqlglotrs-0.3.0-cp310-none-win32.whl", hash = "sha256:3b4cbdb225639615402e9fc79661255d9dea5b937d4196a9b499ffccb9560629", size = 177337 }, + { url = "https://files.pythonhosted.org/packages/42/93/b886cfd5a78e172cabdd4d8490606157af4f273299338f2b8633c184d21a/sqlglotrs-0.3.0-cp310-none-win_amd64.whl", hash = "sha256:a9f2ab2fa34d025439491f372c4c065aa921b7b73854647468218778b564f9eb", size = 191373 }, +] + +[[package]] +name = "sse-starlette" +version = "2.3.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/f4/989bc70cb8091eda43a9034ef969b25145291f3601703b82766e5172dfed/sse_starlette-2.3.6.tar.gz", hash = "sha256:0382336f7d4ec30160cf9ca0518962905e1b69b72d6c1c995131e0a703b436e3", size = 18284 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/05/78850ac6e79af5b9508f8841b0f26aa9fd329a1ba00bf65453c2d312bcc8/sse_starlette-2.3.6-py3-none-any.whl", hash = "sha256:d49a8285b182f6e2228e2609c350398b2ca2c36216c2675d875f81e93548f760", size = 10606 }, +] + +[[package]] +name = "starlette" +version = "0.46.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037 }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353 }, +] + +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252 }, +] + +[[package]] +name = "taskipy" +version = "1.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "mslex", marker = "sys_platform == 'win32'" }, + { name = "psutil" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/44/572261df3db9c6c3332f8618fafeb07a578fd18b06673c73f000f3586749/taskipy-1.14.1.tar.gz", hash = "sha256:410fbcf89692dfd4b9f39c2b49e1750b0a7b81affd0e2d7ea8c35f9d6a4774ed", size = 14475 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/97/4e4cfb1391c81e926bebe3d68d5231b5dbc3bb41c6ba48349e68a881462d/taskipy-1.14.1-py3-none-any.whl", hash = "sha256:6e361520f29a0fd2159848e953599f9c75b1d0b047461e4965069caeb94908f1", size = 13052 }, +] + +[[package]] +name = "tenacity" +version = "9.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248 }, +] + +[[package]] +name = "tf-playwright-stealth" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fake-http-header" }, + { name = "playwright" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/6b/32bb58c65991f91aeaaf7473b650175d9d4af5dd383983d177d49ccba08d/tf_playwright_stealth-1.2.0.tar.gz", hash = "sha256:7bb8d32d3e60324fbf6b9eeae540b8cd9f3b9e07baeb33b025dbc98ad47658ba", size = 23362 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/3d/2653f4cf49660bb44eeac8270617cc4c0287d61716f249f55053f0af0724/tf_playwright_stealth-1.2.0-py3-none-any.whl", hash = "sha256:26ee47ee89fa0f43c606fe37c188ea3ccd36f96ea90c01d167b768df457e7886", size = 33151 }, +] + +[[package]] +name = "tiktoken" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/4a/abaec53e93e3ef37224a4dd9e2fc6bb871e7a538c2b6b9d2a6397271daf4/tiktoken-0.7.0.tar.gz", hash = "sha256:1077266e949c24e0291f6c350433c6f0971365ece2b173a23bc3b9f9defef6b6", size = 33437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/10/28d59d43d72a0ebd4211371d0bf10c935cdecbb62b812ae04c58bfc37d96/tiktoken-0.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485f3cc6aba7c6b6ce388ba634fbba656d9ee27f766216f45146beb4ac18b25f", size = 961465 }, + { url = "https://files.pythonhosted.org/packages/f8/0c/d4125348dedd1f8f38e3f85245e7fc38858ffc77c9b7edfb762a8191ba0b/tiktoken-0.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e54be9a2cd2f6d6ffa3517b064983fb695c9a9d8aa7d574d1ef3c3f931a99225", size = 906849 }, + { url = "https://files.pythonhosted.org/packages/b9/ab/f9c7675747f259d133d66065106cf732a7c2bef6043062fbca8e011f7f4d/tiktoken-0.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79383a6e2c654c6040e5f8506f3750db9ddd71b550c724e673203b4f6b4b4590", size = 1048795 }, + { url = "https://files.pythonhosted.org/packages/e7/8c/7d1007557b343d5cf18349802e94d3a14397121e9105b4661f8cd753f9bf/tiktoken-0.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d4511c52caacf3c4981d1ae2df85908bd31853f33d30b345c8b6830763f769c", size = 1080866 }, + { url = "https://files.pythonhosted.org/packages/72/40/61d6354cb64a563fce475a2907039be9fe809ca5f801213856353b01a35b/tiktoken-0.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13c94efacdd3de9aff824a788353aa5749c0faee1fbe3816df365ea450b82311", size = 1092776 }, + { url = "https://files.pythonhosted.org/packages/f2/6c/83ca40527d072739f0704b9f59b325786c444ca63672a77cb69adc8181f7/tiktoken-0.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8e58c7eb29d2ab35a7a8929cbeea60216a4ccdf42efa8974d8e176d50c9a3df5", size = 1142591 }, + { url = "https://files.pythonhosted.org/packages/ec/1f/a5d72755118e9e1b62cdf3ef9138eb83d49088f3cb37a9540025c81c0e75/tiktoken-0.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:21a20c3bd1dd3e55b91c1331bf25f4af522c525e771691adbc9a69336fa7f702", size = 798864 }, +] + +[[package]] +name = "tinysegmenter" +version = "0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/17/82/86982e4b6d16e4febc79c2a1d68ee3b707e8a020c5d2bc4af8052d0f136a/tinysegmenter-0.3.tar.gz", hash = "sha256:ed1f6d2e806a4758a73be589754384cbadadc7e1a414c81a166fc9adf2d40c6d", size = 16893 } + +[[package]] +name = "tldextract" +version = "5.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "idna" }, + { name = "requests" }, + { name = "requests-file" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/78/182641ea38e3cfd56e9c7b3c0d48a53d432eea755003aa544af96403d4ac/tldextract-5.3.0.tar.gz", hash = "sha256:b3d2b70a1594a0ecfa6967d57251527d58e00bb5a91a74387baa0d87a0678609", size = 128502 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/7c/ea488ef48f2f544566947ced88541bc45fae9e0e422b2edbf165ee07da99/tldextract-5.3.0-py3-none-any.whl", hash = "sha256:f70f31d10b55c83993f55e91ecb7c5d84532a8972f22ec578ecfbe5ea2292db2", size = 107384 }, +] + +[[package]] +name = "tokenizers" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/76/5ac0c97f1117b91b7eb7323dcd61af80d72f790b4df71249a7850c195f30/tokenizers-0.21.1.tar.gz", hash = "sha256:a1bb04dc5b448985f86ecd4b05407f5a8d97cb2c0532199b2a302a604a0165ab", size = 343256 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/1f/328aee25f9115bf04262e8b4e5a2050b7b7cf44b59c74e982db7270c7f30/tokenizers-0.21.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e78e413e9e668ad790a29456e677d9d3aa50a9ad311a40905d6861ba7692cf41", size = 2780767 }, + { url = "https://files.pythonhosted.org/packages/ae/1a/4526797f3719b0287853f12c5ad563a9be09d446c44ac784cdd7c50f76ab/tokenizers-0.21.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:cd51cd0a91ecc801633829fcd1fda9cf8682ed3477c6243b9a095539de4aecf3", size = 2650555 }, + { url = "https://files.pythonhosted.org/packages/4d/7a/a209b29f971a9fdc1da86f917fe4524564924db50d13f0724feed37b2a4d/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28da6b72d4fb14ee200a1bd386ff74ade8992d7f725f2bde2c495a9a98cf4d9f", size = 2937541 }, + { url = "https://files.pythonhosted.org/packages/3c/1e/b788b50ffc6191e0b1fc2b0d49df8cff16fe415302e5ceb89f619d12c5bc/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34d8cfde551c9916cb92014e040806122295a6800914bab5865deb85623931cf", size = 2819058 }, + { url = "https://files.pythonhosted.org/packages/36/aa/3626dfa09a0ecc5b57a8c58eeaeb7dd7ca9a37ad9dd681edab5acd55764c/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaa852d23e125b73d283c98f007e06d4595732104b65402f46e8ef24b588d9f8", size = 3133278 }, + { url = "https://files.pythonhosted.org/packages/a4/4d/8fbc203838b3d26269f944a89459d94c858f5b3f9a9b6ee9728cdcf69161/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a21a15d5c8e603331b8a59548bbe113564136dc0f5ad8306dd5033459a226da0", size = 3144253 }, + { url = "https://files.pythonhosted.org/packages/d8/1b/2bd062adeb7c7511b847b32e356024980c0ffcf35f28947792c2d8ad2288/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fdbd4c067c60a0ac7eca14b6bd18a5bebace54eb757c706b47ea93204f7a37c", size = 3398225 }, + { url = "https://files.pythonhosted.org/packages/8a/63/38be071b0c8e06840bc6046991636bcb30c27f6bb1e670f4f4bc87cf49cc/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dd9a0061e403546f7377df940e866c3e678d7d4e9643d0461ea442b4f89e61a", size = 3038874 }, + { url = "https://files.pythonhosted.org/packages/ec/83/afa94193c09246417c23a3c75a8a0a96bf44ab5630a3015538d0c316dd4b/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:db9484aeb2e200c43b915a1a0150ea885e35f357a5a8fabf7373af333dcc8dbf", size = 9014448 }, + { url = "https://files.pythonhosted.org/packages/ae/b3/0e1a37d4f84c0f014d43701c11eb8072704f6efe8d8fc2dcdb79c47d76de/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed248ab5279e601a30a4d67bdb897ecbe955a50f1e7bb62bd99f07dd11c2f5b6", size = 8937877 }, + { url = "https://files.pythonhosted.org/packages/ac/33/ff08f50e6d615eb180a4a328c65907feb6ded0b8f990ec923969759dc379/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:9ac78b12e541d4ce67b4dfd970e44c060a2147b9b2a21f509566d556a509c67d", size = 9186645 }, + { url = "https://files.pythonhosted.org/packages/5f/aa/8ae85f69a9f6012c6f8011c6f4aa1c96154c816e9eea2e1b758601157833/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e5a69c1a4496b81a5ee5d2c1f3f7fbdf95e90a0196101b0ee89ed9956b8a168f", size = 9384380 }, + { url = "https://files.pythonhosted.org/packages/e8/5b/a5d98c89f747455e8b7a9504910c865d5e51da55e825a7ae641fb5ff0a58/tokenizers-0.21.1-cp39-abi3-win32.whl", hash = "sha256:1039a3a5734944e09de1d48761ade94e00d0fa760c0e0551151d4dd851ba63e3", size = 2239506 }, + { url = "https://files.pythonhosted.org/packages/e6/b6/072a8e053ae600dcc2ac0da81a23548e3b523301a442a6ca900e92ac35be/tokenizers-0.21.1-cp39-abi3-win_amd64.whl", hash = "sha256:0f0dcbcc9f6e13e675a66d7a5f2f225a736745ce484c1a4e07476a89ccdad382", size = 2435481 }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, +] + +[[package]] +name = "transformers" +version = "4.52.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/a9/275037087f9d846580b02f2d7cae0e0a6955d46f84583d0151d6227bd416/transformers-4.52.4.tar.gz", hash = "sha256:aff3764441c1adc192a08dba49740d3cbbcb72d850586075aed6bd89b98203e6", size = 8945376 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/f2/25b27b396af03d5b64e61976b14f7209e2939e9e806c10749b6d277c273e/transformers-4.52.4-py3-none-any.whl", hash = "sha256:203f5c19416d5877e36e88633943761719538a25d9775977a24fe77a1e5adfc7", size = 10460375 }, +] + +[[package]] +name = "tree-sitter" +version = "0.23.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/50/fd5fafa42b884f741b28d9e6fd366c3f34e15d2ed3aa9633b34e388379e2/tree-sitter-0.23.2.tar.gz", hash = "sha256:66bae8dd47f1fed7bdef816115146d3a41c39b5c482d7bad36d9ba1def088450", size = 166800 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/04/2068a7b725265ecfcbf63ecdae038f1d4124ebccd55b8a7ce145b70e2b6a/tree_sitter-0.23.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3a937f5d8727bc1c74c4bf2a9d1c25ace049e8628273016ad0d45914ae904e10", size = 139289 }, + { url = "https://files.pythonhosted.org/packages/a8/07/a5b943121f674fe1ac77694a698e71ce95353830c1f3f4ce45da7ef3e406/tree_sitter-0.23.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2c7eae7fe2af215645a38660d2d57d257a4c461fe3ec827cca99a79478284e80", size = 132379 }, + { url = "https://files.pythonhosted.org/packages/d4/96/fcc72c33d464a2d722db1e95b74a53ced771a47b3cfde60aced29764a783/tree_sitter-0.23.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a71d607595270b6870eaf778a1032d146b2aa79bfcfa60f57a82a7b7584a4c7", size = 552884 }, + { url = "https://files.pythonhosted.org/packages/d0/af/b0e787a52767155b4643a55d6de03c1e4ae77abb61e1dc1629ad983e0a40/tree_sitter-0.23.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fe9b9ea7a0aa23b52fd97354da95d1b2580065bc12a4ac868f9164a127211d6", size = 566561 }, + { url = "https://files.pythonhosted.org/packages/65/fd/05e966b5317b1c6679c071c5b0203f28af9d26c9363700cb9682e1bcf343/tree_sitter-0.23.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d74d00a8021719eae14d10d1b1e28649e15d8b958c01c2b2c3dad7a2ebc4dbae", size = 558273 }, + { url = "https://files.pythonhosted.org/packages/60/bc/19145efdf3f47711aa3f1bf06f0b50593f97f1108550d38694841fd97b7c/tree_sitter-0.23.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6de18d8d8a7f67ab71f472d1fcb01cc506e080cbb5e13d52929e4b6fdce6bbee", size = 569176 }, + { url = "https://files.pythonhosted.org/packages/32/08/3553d8e488ae9284a0762effafb7d2639a306e184963b7f99853923084d6/tree_sitter-0.23.2-cp310-cp310-win_amd64.whl", hash = "sha256:12b60dca70d2282af942b650a6d781be487485454668c7c956338a367b98cdee", size = 117902 }, + { url = "https://files.pythonhosted.org/packages/1d/39/836fa485e985c33e8aa1cc3abbf7a84be1c2c382e69547a765631fdd7ce3/tree_sitter-0.23.2-cp310-cp310-win_arm64.whl", hash = "sha256:3346a4dd0447a42aabb863443b0fd8c92b909baf40ed2344fae4b94b625d5955", size = 102644 }, +] + +[[package]] +name = "tree-sitter-python" +version = "0.23.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/30/6766433b31be476fda6569a3a374c2220e45ffee0bff75460038a57bf23b/tree_sitter_python-0.23.6.tar.gz", hash = "sha256:354bfa0a2f9217431764a631516f85173e9711af2c13dbd796a8815acfe505d9", size = 155868 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/67/577a02acae5f776007c924ca86ef14c19c12e71de0aa9d2a036f3c248e7b/tree_sitter_python-0.23.6-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:28fbec8f74eeb2b30292d97715e60fac9ccf8a8091ce19b9d93e9b580ed280fb", size = 74361 }, + { url = "https://files.pythonhosted.org/packages/d2/a6/194b3625a7245c532ad418130d63077ce6cd241152524152f533e4d6edb0/tree_sitter_python-0.23.6-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:680b710051b144fedf61c95197db0094f2245e82551bf7f0c501356333571f7a", size = 76436 }, + { url = "https://files.pythonhosted.org/packages/d0/62/1da112689d6d282920e62c40e67ab39ea56463b0e7167bfc5e81818a770e/tree_sitter_python-0.23.6-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a9dcef55507b6567207e8ee0a6b053d0688019b47ff7f26edc1764b7f4dc0a4", size = 112060 }, + { url = "https://files.pythonhosted.org/packages/5d/62/c9358584c96e38318d69b6704653684fd8467601f7b74e88aa44f4e6903f/tree_sitter_python-0.23.6-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29dacdc0cd2f64e55e61d96c6906533ebb2791972bec988450c46cce60092f5d", size = 112338 }, + { url = "https://files.pythonhosted.org/packages/1a/58/c5e61add45e34fb8ecbf057c500bae9d96ed7c9ca36edb7985da8ae45526/tree_sitter_python-0.23.6-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7e048733c36f564b379831689006801feb267d8194f9e793fbb395ef1723335d", size = 109382 }, + { url = "https://files.pythonhosted.org/packages/e9/f3/9b30893cae9b3811fe652dc6f90aaadfda12ae0b2757f5722fc7266f423c/tree_sitter_python-0.23.6-cp39-abi3-win_amd64.whl", hash = "sha256:a24027248399fb41594b696f929f9956828ae7cc85596d9f775e6c239cd0c2be", size = 75904 }, + { url = "https://files.pythonhosted.org/packages/87/cb/ce35a65f83a47b510d8a2f1eddf3bdbb0d57aabc87351c8788caf3309f76/tree_sitter_python-0.23.6-cp39-abi3-win_arm64.whl", hash = "sha256:71334371bd73d5fe080aed39fbff49ed8efb9506edebe16795b0c7567ed6a272", size = 73649 }, +] + +[[package]] +name = "typer" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/8c/7d682431efca5fd290017663ea4588bf6f2c6aad085c7f108c5dbc316e70/typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b", size = 102625 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317 }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839 }, +] + +[[package]] +name = "typing-inspect" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827 }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 }, +] + +[[package]] +name = "unstructured" +version = "0.16.20" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backoff" }, + { name = "beautifulsoup4" }, + { name = "chardet" }, + { name = "dataclasses-json" }, + { name = "emoji" }, + { name = "filetype" }, + { name = "html5lib" }, + { name = "langdetect" }, + { name = "lxml" }, + { name = "nltk" }, + { name = "numpy" }, + { name = "psutil" }, + { name = "python-iso639" }, + { name = "python-magic" }, + { name = "python-oxmsg" }, + { name = "rapidfuzz" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, + { name = "unstructured-client" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/b5/c7f723ceb1f601dd3f12faef241fd7bc69f2e981142bd79cbc641605850a/unstructured-0.16.20.tar.gz", hash = "sha256:95e8b604fae908cfd53b5bf05c4683e0041aa6c914627bb0226edb4530bbfa44", size = 1671404 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/1b/9922646ca86a9811d83d9e227625087822eae31c11cb80791b3b7b33368c/unstructured-0.16.20-py3-none-any.whl", hash = "sha256:9749f4310dde0973f4732ee68f9e98d4a673bb06e455e98bc9522027a99c77cd", size = 1756881 }, +] + +[[package]] +name = "unstructured-client" +version = "0.36.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "cryptography" }, + { name = "httpx" }, + { name = "nest-asyncio" }, + { name = "pydantic" }, + { name = "pypdf" }, + { name = "requests-toolbelt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/4d/d829dbef1138251de771cd52b277d93fb1c4e79d56be3e44e6d2ce76bd62/unstructured_client-0.36.0.tar.gz", hash = "sha256:ab293498100275c0e1d74c926c82dae2b3ba3fbb88945c0ba03b4b7a29197e4a", size = 86010 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/4a/ae162e583bbdd0996f92ad18871a737d710260d8c0cfd78f1be6aa0ac150/unstructured_client-0.36.0-py3-none-any.whl", hash = "sha256:d0ecf3ac4d481437d858147904ff6e41205032cf8353af5cdd3ebaa190481d6a", size = 195765 }, +] + +[[package]] +name = "uritemplate" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488 }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795 }, +] + +[[package]] +name = "uvicorn" +version = "0.34.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a", size = 76631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431 }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/76/44a55515e8c9505aa1420aebacf4dd82552e5e15691654894e90d0bd051a/uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f", size = 1442019 }, + { url = "https://files.pythonhosted.org/packages/35/5a/62d5800358a78cc25c8a6c72ef8b10851bdb8cca22e14d9c74167b7f86da/uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d", size = 801898 }, + { url = "https://files.pythonhosted.org/packages/f3/96/63695e0ebd7da6c741ccd4489b5947394435e198a1382349c17b1146bb97/uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26", size = 3827735 }, + { url = "https://files.pythonhosted.org/packages/61/e0/f0f8ec84979068ffae132c58c79af1de9cceeb664076beea86d941af1a30/uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb", size = 3825126 }, + { url = "https://files.pythonhosted.org/packages/bf/fe/5e94a977d058a54a19df95f12f7161ab6e323ad49f4dabc28822eb2df7ea/uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f", size = 3705789 }, + { url = "https://files.pythonhosted.org/packages/26/dd/c7179618e46092a77e036650c1f056041a028a35c4d76945089fcfc38af8/uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c", size = 3800523 }, +] + +[[package]] +name = "watchfiles" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/9a/d451fcc97d029f5812e898fd30a53fd8c15c7bbd058fd75cfc6beb9bd761/watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575", size = 94406 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/dd/579d1dc57f0f895426a1211c4ef3b0cb37eb9e642bb04bdcd962b5df206a/watchfiles-1.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:27f30e14aa1c1e91cb653f03a63445739919aef84c8d2517997a83155e7a2fcc", size = 405757 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/7a0318cd874393344d48c34d53b3dd419466adf59a29ba5b51c88dd18b86/watchfiles-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3366f56c272232860ab45c77c3ca7b74ee819c8e1f6f35a7125556b198bbc6df", size = 397511 }, + { url = "https://files.pythonhosted.org/packages/06/be/503514656d0555ec2195f60d810eca29b938772e9bfb112d5cd5ad6f6a9e/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8412eacef34cae2836d891836a7fff7b754d6bcac61f6c12ba5ca9bc7e427b68", size = 450739 }, + { url = "https://files.pythonhosted.org/packages/4e/0d/a05dd9e5f136cdc29751816d0890d084ab99f8c17b86f25697288ca09bc7/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df670918eb7dd719642e05979fc84704af913d563fd17ed636f7c4783003fdcc", size = 458106 }, + { url = "https://files.pythonhosted.org/packages/f1/fa/9cd16e4dfdb831072b7ac39e7bea986e52128526251038eb481effe9f48e/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7642b9bc4827b5518ebdb3b82698ada8c14c7661ddec5fe719f3e56ccd13c97", size = 484264 }, + { url = "https://files.pythonhosted.org/packages/32/04/1da8a637c7e2b70e750a0308e9c8e662ada0cca46211fa9ef24a23937e0b/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:199207b2d3eeaeb80ef4411875a6243d9ad8bc35b07fc42daa6b801cc39cc41c", size = 597612 }, + { url = "https://files.pythonhosted.org/packages/30/01/109f2762e968d3e58c95731a206e5d7d2a7abaed4299dd8a94597250153c/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a479466da6db5c1e8754caee6c262cd373e6e6c363172d74394f4bff3d84d7b5", size = 477242 }, + { url = "https://files.pythonhosted.org/packages/b5/b8/46f58cf4969d3b7bc3ca35a98e739fa4085b0657a1540ccc29a1a0bc016f/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935f9edd022ec13e447e5723a7d14456c8af254544cefbc533f6dd276c9aa0d9", size = 453148 }, + { url = "https://files.pythonhosted.org/packages/a5/cd/8267594263b1770f1eb76914940d7b2d03ee55eca212302329608208e061/watchfiles-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8076a5769d6bdf5f673a19d51da05fc79e2bbf25e9fe755c47595785c06a8c72", size = 626574 }, + { url = "https://files.pythonhosted.org/packages/a1/2f/7f2722e85899bed337cba715723e19185e288ef361360718973f891805be/watchfiles-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86b1e28d4c37e89220e924305cd9f82866bb0ace666943a6e4196c5df4d58dcc", size = 624378 }, + { url = "https://files.pythonhosted.org/packages/bf/20/64c88ec43d90a568234d021ab4b2a6f42a5230d772b987c3f9c00cc27b8b/watchfiles-1.1.0-cp310-cp310-win32.whl", hash = "sha256:d1caf40c1c657b27858f9774d5c0e232089bca9cb8ee17ce7478c6e9264d2587", size = 279829 }, + { url = "https://files.pythonhosted.org/packages/39/5c/a9c1ed33de7af80935e4eac09570de679c6e21c07070aa99f74b4431f4d6/watchfiles-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a89c75a5b9bc329131115a409d0acc16e8da8dfd5867ba59f1dd66ae7ea8fa82", size = 292192 }, + { url = "https://files.pythonhosted.org/packages/be/7c/a3d7c55cfa377c2f62c4ae3c6502b997186bc5e38156bafcb9b653de9a6d/watchfiles-1.1.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a6fd40bbb50d24976eb275ccb55cd1951dfb63dbc27cae3066a6ca5f4beabd5", size = 406748 }, + { url = "https://files.pythonhosted.org/packages/38/d0/c46f1b2c0ca47f3667b144de6f0515f6d1c670d72f2ca29861cac78abaa1/watchfiles-1.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f811079d2f9795b5d48b55a37aa7773680a5659afe34b54cc1d86590a51507d", size = 398801 }, + { url = "https://files.pythonhosted.org/packages/70/9c/9a6a42e97f92eeed77c3485a43ea96723900aefa3ac739a8c73f4bff2cd7/watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2726d7bfd9f76158c84c10a409b77a320426540df8c35be172444394b17f7ea", size = 451528 }, + { url = "https://files.pythonhosted.org/packages/51/7b/98c7f4f7ce7ff03023cf971cd84a3ee3b790021ae7584ffffa0eb2554b96/watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df32d59cb9780f66d165a9a7a26f19df2c7d24e3bd58713108b41d0ff4f929c6", size = 454095 }, +] + +[[package]] +name = "webcolors" +version = "24.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/29/061ec845fb58521848f3739e466efd8250b4b7b98c1b6c5bf4d40b419b7e/webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6", size = 45064 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9", size = 14934 }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423 }, + { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080 }, + { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329 }, + { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312 }, + { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319 }, + { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631 }, + { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016 }, + { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426 }, + { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360 }, + { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388 }, + { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830 }, + { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109 }, + { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343 }, + { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599 }, + { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207 }, + { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155 }, + { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884 }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 }, +] + +[[package]] +name = "wikipedia" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/35/25e68fbc99e672127cc6fbb14b8ec1ba3dfef035bf1e4c90f78f24a80b7d/wikipedia-1.4.0.tar.gz", hash = "sha256:db0fad1829fdd441b1852306e9856398204dc0786d2996dd2e0c8bb8e26133b2", size = 27748 } + +[[package]] +name = "win32-setctime" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083 }, +] + +[[package]] +name = "wrapt" +version = "1.17.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/d1/1daec934997e8b160040c78d7b31789f19b122110a75eca3d4e8da0049e1/wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984", size = 53307 }, + { url = "https://files.pythonhosted.org/packages/1b/7b/13369d42651b809389c1a7153baa01d9700430576c81a2f5c5e460df0ed9/wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22", size = 38486 }, + { url = "https://files.pythonhosted.org/packages/62/bf/e0105016f907c30b4bd9e377867c48c34dc9c6c0c104556c9c9126bd89ed/wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7", size = 38777 }, + { url = "https://files.pythonhosted.org/packages/27/70/0f6e0679845cbf8b165e027d43402a55494779295c4b08414097b258ac87/wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c", size = 83314 }, + { url = "https://files.pythonhosted.org/packages/0f/77/0576d841bf84af8579124a93d216f55d6f74374e4445264cb378a6ed33eb/wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72", size = 74947 }, + { url = "https://files.pythonhosted.org/packages/90/ec/00759565518f268ed707dcc40f7eeec38637d46b098a1f5143bff488fe97/wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061", size = 82778 }, + { url = "https://files.pythonhosted.org/packages/f8/5a/7cffd26b1c607b0b0c8a9ca9d75757ad7620c9c0a9b4a25d3f8a1480fafc/wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2", size = 81716 }, + { url = "https://files.pythonhosted.org/packages/7e/09/dccf68fa98e862df7e6a60a61d43d644b7d095a5fc36dbb591bbd4a1c7b2/wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c", size = 74548 }, + { url = "https://files.pythonhosted.org/packages/b7/8e/067021fa3c8814952c5e228d916963c1115b983e21393289de15128e867e/wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62", size = 81334 }, + { url = "https://files.pythonhosted.org/packages/4b/0d/9d4b5219ae4393f718699ca1c05f5ebc0c40d076f7e65fd48f5f693294fb/wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563", size = 36427 }, + { url = "https://files.pythonhosted.org/packages/72/6a/c5a83e8f61aec1e1aeef939807602fb880e5872371e95df2137142f5c58e/wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f", size = 38774 }, + { url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594 }, +] + +[[package]] +name = "xlrd" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/07/5a/377161c2d3538d1990d7af382c79f3b2372e880b65de21b01b1a2b78691e/xlrd-2.0.2.tar.gz", hash = "sha256:08b5e25de58f21ce71dc7db3b3b8106c1fa776f3024c54e45b45b374e89234c9", size = 100167 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/62/c8d562e7766786ba6587d09c5a8ba9f718ed3fa8af7f4553e8f91c36f302/xlrd-2.0.2-py2.py3-none-any.whl", hash = "sha256:ea762c3d29f4cca48d82df517b6d89fbce4db3107f9d78713e48cd321d5c9aa9", size = 96555 }, +] + +[[package]] +name = "xls2xlsx" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "chardet" }, + { name = "cssutils" }, + { name = "currency-symbols" }, + { name = "fonttools" }, + { name = "openpyxl" }, + { name = "pillow" }, + { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "webcolors" }, + { name = "xlrd" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/f1/cd87cb50c5da52a32f3c8eb268f31f2e0594171a89de69b37a66dc5de0b8/xls2xlsx-0.2.0.tar.gz", hash = "sha256:98123cb8f43fdd68f4af8d61d7223100d6003daf9a592fa6c0746acbc7314c35", size = 1330340 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/be/8302d331252974200ff4adb392d1fc67e4ff161c85a3109b915f4cbaa1ca/xls2xlsx-0.2.0-py2.py3-none-any.whl", hash = "sha256:a6b9c6f887d2e366a54d26682d1ec399f5dbf408567d47768ef6178ef587af4e", size = 39191 }, +] + +[[package]] +name = "xlsxwriter" +version = "3.2.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/47/7704bac42ac6fe1710ae099b70e6a1e68ed173ef14792b647808c357da43/xlsxwriter-3.2.5.tar.gz", hash = "sha256:7e88469d607cdc920151c0ab3ce9cf1a83992d4b7bc730c5ffdd1a12115a7dbe", size = 213306 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/34/a22e6664211f0c8879521328000bdcae9bf6dbafa94a923e531f6d5b3f73/xlsxwriter-3.2.5-py3-none-any.whl", hash = "sha256:4f4824234e1eaf9d95df9a8fe974585ff91d0f5e3d3f12ace5b71e443c1c6abd", size = 172347 }, +] + +[[package]] +name = "xmltodict" +version = "0.14.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/50/05/51dcca9a9bf5e1bce52582683ce50980bcadbc4fa5143b9f2b19ab99958f/xmltodict-0.14.2.tar.gz", hash = "sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553", size = 51942 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/45/fc303eb433e8a2a271739c98e953728422fa61a3c1f36077a49e395c972e/xmltodict-0.14.2-py2.py3-none-any.whl", hash = "sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac", size = 9981 }, +] + +[[package]] +name = "xxhash" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/5e/d6e5258d69df8b4ed8c83b6664f2b47d30d2dec551a29ad72a6c69eafd31/xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f", size = 84241 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/8a/0e9feca390d512d293afd844d31670e25608c4a901e10202aa98785eab09/xxhash-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212", size = 31970 }, + { url = "https://files.pythonhosted.org/packages/16/e6/be5aa49580cd064a18200ab78e29b88b1127e1a8c7955eb8ecf81f2626eb/xxhash-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520", size = 30801 }, + { url = "https://files.pythonhosted.org/packages/20/ee/b8a99ebbc6d1113b3a3f09e747fa318c3cde5b04bd9c197688fadf0eeae8/xxhash-3.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5d3e570ef46adaf93fc81b44aca6002b5a4d8ca11bd0580c07eac537f36680", size = 220927 }, + { url = "https://files.pythonhosted.org/packages/58/62/15d10582ef159283a5c2b47f6d799fc3303fe3911d5bb0bcc820e1ef7ff4/xxhash-3.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cb29a034301e2982df8b1fe6328a84f4b676106a13e9135a0d7e0c3e9f806da", size = 200360 }, + { url = "https://files.pythonhosted.org/packages/23/41/61202663ea9b1bd8e53673b8ec9e2619989353dba8cfb68e59a9cbd9ffe3/xxhash-3.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0d307d27099bb0cbeea7260eb39ed4fdb99c5542e21e94bb6fd29e49c57a23", size = 428528 }, + { url = "https://files.pythonhosted.org/packages/f2/07/d9a3059f702dec5b3b703737afb6dda32f304f6e9da181a229dafd052c29/xxhash-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0342aafd421795d740e514bc9858ebddfc705a75a8c5046ac56d85fe97bf196", size = 194149 }, + { url = "https://files.pythonhosted.org/packages/eb/58/27caadf78226ecf1d62dbd0c01d152ed381c14c1ee4ad01f0d460fc40eac/xxhash-3.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dbbd9892c5ebffeca1ed620cf0ade13eb55a0d8c84e0751a6653adc6ac40d0c", size = 207703 }, + { url = "https://files.pythonhosted.org/packages/b1/08/32d558ce23e1e068453c39aed7b3c1cdc690c177873ec0ca3a90d5808765/xxhash-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4cc2d67fdb4d057730c75a64c5923abfa17775ae234a71b0200346bfb0a7f482", size = 216255 }, + { url = "https://files.pythonhosted.org/packages/3f/d4/2b971e2d2b0a61045f842b622ef11e94096cf1f12cd448b6fd426e80e0e2/xxhash-3.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ec28adb204b759306a3d64358a5e5c07d7b1dd0ccbce04aa76cb9377b7b70296", size = 202744 }, + { url = "https://files.pythonhosted.org/packages/19/ae/6a6438864a8c4c39915d7b65effd85392ebe22710412902487e51769146d/xxhash-3.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1328f6d8cca2b86acb14104e381225a3d7b42c92c4b86ceae814e5c400dbb415", size = 210115 }, + { url = "https://files.pythonhosted.org/packages/48/7d/b3c27c27d1fc868094d02fe4498ccce8cec9fcc591825c01d6bcb0b4fc49/xxhash-3.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d47ebd9f5d9607fd039c1fbf4994e3b071ea23eff42f4ecef246ab2b7334198", size = 414247 }, + { url = "https://files.pythonhosted.org/packages/a1/05/918f9e7d2fbbd334b829997045d341d6239b563c44e683b9a7ef8fe50f5d/xxhash-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b96d559e0fcddd3343c510a0fe2b127fbff16bf346dd76280b82292567523442", size = 191419 }, + { url = "https://files.pythonhosted.org/packages/08/29/dfe393805b2f86bfc47c290b275f0b7c189dc2f4e136fd4754f32eb18a8d/xxhash-3.5.0-cp310-cp310-win32.whl", hash = "sha256:61c722ed8d49ac9bc26c7071eeaa1f6ff24053d553146d5df031802deffd03da", size = 30114 }, + { url = "https://files.pythonhosted.org/packages/7b/d7/aa0b22c4ebb7c3ccb993d4c565132abc641cd11164f8952d89eb6a501909/xxhash-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9bed5144c6923cc902cd14bb8963f2d5e034def4486ab0bbe1f58f03f042f9a9", size = 30003 }, + { url = "https://files.pythonhosted.org/packages/69/12/f969b81541ee91b55f1ce469d7ab55079593c80d04fd01691b550e535000/xxhash-3.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:893074d651cf25c1cc14e3bea4fceefd67f2921b1bb8e40fcfeba56820de80c6", size = 26773 }, + { url = "https://files.pythonhosted.org/packages/ab/9a/233606bada5bd6f50b2b72c45de3d9868ad551e83893d2ac86dc7bb8553a/xxhash-3.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2014c5b3ff15e64feecb6b713af12093f75b7926049e26a580e94dcad3c73d8c", size = 29732 }, + { url = "https://files.pythonhosted.org/packages/0c/67/f75276ca39e2c6604e3bee6c84e9db8a56a4973fde9bf35989787cf6e8aa/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab81ef75003eda96239a23eda4e4543cedc22e34c373edcaf744e721a163986", size = 36214 }, + { url = "https://files.pythonhosted.org/packages/0f/f8/f6c61fd794229cc3848d144f73754a0c107854372d7261419dcbbd286299/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2febf914ace002132aa09169cc572e0d8959d0f305f93d5828c4836f9bc5a6", size = 32020 }, + { url = "https://files.pythonhosted.org/packages/79/d3/c029c99801526f859e6b38d34ab87c08993bf3dcea34b11275775001638a/xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d3a10609c51da2a1c0ea0293fc3968ca0a18bd73838455b5bca3069d7f8e32b", size = 40515 }, + { url = "https://files.pythonhosted.org/packages/62/e3/bef7b82c1997579c94de9ac5ea7626d01ae5858aa22bf4fcb38bf220cb3e/xxhash-3.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a74f23335b9689b66eb6dbe2a931a88fcd7a4c2cc4b1cb0edba8ce381c7a1da", size = 30064 }, +] + +[[package]] +name = "yarl" +version = "1.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/65/7fed0d774abf47487c64be14e9223749468922817b5e8792b8a64792a1bb/yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4", size = 132910 }, + { url = "https://files.pythonhosted.org/packages/8a/7b/988f55a52da99df9e56dc733b8e4e5a6ae2090081dc2754fc8fd34e60aa0/yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a", size = 90644 }, + { url = "https://files.pythonhosted.org/packages/f7/de/30d98f03e95d30c7e3cc093759982d038c8833ec2451001d45ef4854edc1/yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed", size = 89322 }, + { url = "https://files.pythonhosted.org/packages/e0/7a/f2f314f5ebfe9200724b0b748de2186b927acb334cf964fd312eb86fc286/yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e", size = 323786 }, + { url = "https://files.pythonhosted.org/packages/15/3f/718d26f189db96d993d14b984ce91de52e76309d0fd1d4296f34039856aa/yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73", size = 319627 }, + { url = "https://files.pythonhosted.org/packages/a5/76/8fcfbf5fa2369157b9898962a4a7d96764b287b085b5b3d9ffae69cdefd1/yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e", size = 339149 }, + { url = "https://files.pythonhosted.org/packages/3c/95/d7fc301cc4661785967acc04f54a4a42d5124905e27db27bb578aac49b5c/yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8", size = 333327 }, + { url = "https://files.pythonhosted.org/packages/65/94/e21269718349582eee81efc5c1c08ee71c816bfc1585b77d0ec3f58089eb/yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23", size = 326054 }, + { url = "https://files.pythonhosted.org/packages/32/ae/8616d1f07853704523519f6131d21f092e567c5af93de7e3e94b38d7f065/yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70", size = 315035 }, + { url = "https://files.pythonhosted.org/packages/48/aa/0ace06280861ef055855333707db5e49c6e3a08840a7ce62682259d0a6c0/yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb", size = 338962 }, + { url = "https://files.pythonhosted.org/packages/20/52/1e9d0e6916f45a8fb50e6844f01cb34692455f1acd548606cbda8134cd1e/yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2", size = 335399 }, + { url = "https://files.pythonhosted.org/packages/f2/65/60452df742952c630e82f394cd409de10610481d9043aa14c61bf846b7b1/yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30", size = 338649 }, + { url = "https://files.pythonhosted.org/packages/7b/f5/6cd4ff38dcde57a70f23719a838665ee17079640c77087404c3d34da6727/yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309", size = 358563 }, + { url = "https://files.pythonhosted.org/packages/d1/90/c42eefd79d0d8222cb3227bdd51b640c0c1d0aa33fe4cc86c36eccba77d3/yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24", size = 357609 }, + { url = "https://files.pythonhosted.org/packages/03/c8/cea6b232cb4617514232e0f8a718153a95b5d82b5290711b201545825532/yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13", size = 350224 }, + { url = "https://files.pythonhosted.org/packages/ce/a3/eaa0ab9712f1f3d01faf43cf6f1f7210ce4ea4a7e9b28b489a2261ca8db9/yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8", size = 81753 }, + { url = "https://files.pythonhosted.org/packages/8f/34/e4abde70a9256465fe31c88ed02c3f8502b7b5dead693a4f350a06413f28/yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16", size = 86817 }, + { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542 }, +] + +[[package]] +name = "yt-dlp" +version = "2024.12.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/ea/f30e5925c5b9109d2f8e47b87bb7e7feac1a6c496b5324deb352c2002cf4/yt_dlp-2024.12.23.tar.gz", hash = "sha256:ac0e72b5a9017ba104b4258546201a7cedc38e8bd20727e0c63b77c829b425e9", size = 2914953 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/d3/dc656c921f45baaba4d439292194b5be7f48b3558fcc38941aca16fa5afa/yt_dlp-2024.12.23-py3-none-any.whl", hash = "sha256:2fc08a5221a0379628ac4e7324c6c69a95b9fdfa7a7ca3187444b3b7451e38be", size = 3176724 }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276 }, +] diff --git a/build/icon.icns b/build/icon.icns new file mode 100644 index 0000000000000000000000000000000000000000..edbce69a22420c253cf674d5b4a60eaec1c5cec5 GIT binary patch literal 184841 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≪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

*}bpjk>+NUoF*4a7}GLx+y|uk8^b`ifRmw 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 a27Ev|WPIo|lGuSGCw|Cqzv5BYO6q^8V%g>Z literal 0 HcmV?d00001 diff --git a/build/icon.png b/build/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ba10eca38f84691b73bb3d41c9f282474ab35976 GIT binary patch literal 8488 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 G4gDXT2_r)Q literal 0 HcmV?d00001 diff --git a/build/installer.nsh b/build/installer.nsh new file mode 100644 index 000000000..1f50e21d4 --- /dev/null +++ b/build/installer.nsh @@ -0,0 +1,4 @@ +!macro customInstallMode + ; 跳过“选择安装类型”页面,直接进入下一步 + Abort +!macroend \ No newline at end of file diff --git a/components.json b/components.json new file mode 100644 index 000000000..1d282e640 --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/config/notarize.cjs b/config/notarize.cjs new file mode 100644 index 000000000..a38c57cbf --- /dev/null +++ b/config/notarize.cjs @@ -0,0 +1,27 @@ +const { notarize } = require("@electron/notarize"); +require("dotenv").config(); + +exports.default = async function notarizing(context) { + if (process.platform !== "darwin") { + return; + } + const appOutDir = context.appOutDir; + const appName = context.packager.appInfo.productName; + console.log("appOutDir", appOutDir); + console.log("process.env.APPLEID", process.env.APPLEID); + console.log("process.env.APPLEIDPASS", process.env.APPLEIDPASS); + console.log("process.env.APPLETEAMID", process.env.APPLETEAMID); + return notarize({ + tool: "notarytool", + teamId: process.env.APPLETEAMID, + appBundleId: "com.eigent.app", + appPath: `${appOutDir}/${appName}.app`, + appleId: process.env.APPLEID, + appleIdPassword: process.env.APPLEIDPASS, + ascProvider: process.env.APPLETEAMID, + }) + .then((res) => { + console.log("success!"); + }) + .catch(console.log); +}; diff --git a/docs/concepts.md b/docs/concepts.md new file mode 100644 index 000000000..5b2c9ca52 --- /dev/null +++ b/docs/concepts.md @@ -0,0 +1,173 @@ +--- +title: Concepts +description: Understand the core terms and features that make Eigent unique. +icon: key +--- + +## Workers + + + + + + +
+ +Autonomous agents tailored to specific roles that run tasks independently or together. Think of them as individual members of your team, like a "Researcher," a "Programmer," or a "Writer." + +Each Worker is designed with specific capabilities and can be customized to handle particular types of tasks efficiently. + + + +Workers concept illustration + +
+ +## Workforce + + + + + + +
+ +A coordinated team of Workers that collaborate to complete complex workflows. Think of it as your AI project team. + +The Workforce orchestrates multiple Workers, ensuring they work together seamlessly to achieve your goals. + + + +Workforce collaboration illustration + +
+ +## Workspace + + + + + + +
+ +A live window into a Worker's process where you can watch or take control. For example, a terminal, a browser, or a file viewer. + +Workspaces provide real-time visibility into what your Workers are doing, allowing you to monitor progress and intervene when needed. + + + +Workspace interface illustration + +
+ +## Tasks & Subtasks + + + + + + +
+ +You define a mission (task), the Workforce breaks it into components (subtasks), and assigns them to the appropriate Workers. + +This hierarchical approach ensures complex projects are broken down into manageable pieces and executed efficiently. + + + +Tasks and subtasks breakdown illustration + +
+ +## Chat + + + + + + +
+ +Your primary interface for communicating with your Workforce. You use it to define your main Task, sharing files and interacting with agents in real time. + +The Chat interface serves as your command center, where you can give instructions, ask questions, and receive updates from your AI team. + + + +Chat interface illustration + +
+ +## MCP + + + + + + +
+ +Model Context Protocol that allows Workers to use external tools. It connects your agents to databases, APIs, and documentation sources, empowering them to act across platforms. + +MCP extends your Workers' capabilities by providing access to real-world data and tools, making them more powerful and versatile. + + + +MCP protocol illustration + +
+ +## Models + + + + + + +
+ +Different AI "brains" that power your Workers. Eigent allows you to choose from various models (like GPT-4.1 or Gemini 2.5 Pro), each with different strengths in speed, reasoning, and cost. + +Choose the right model for each task based on your specific needs for performance, accuracy, or cost efficiency. + + + +AI models illustration + +
\ No newline at end of file diff --git a/docs/docs.json b/docs/docs.json new file mode 100644 index 000000000..8c0decc6a --- /dev/null +++ b/docs/docs.json @@ -0,0 +1,85 @@ +{ + "$schema": "https://mintlify.com/docs.json", + "name": "Eigent Documentation", + "theme": "aspen", + "logo": { + "light": "images/logo-light.png", + "dark": "images/logo-dark.png", + "href": "https://www.eigent.ai" + }, + "favicon": "images/favicon.png", + "colors": { + "primary": "#1d1d1d", + "light": "#F5F4F0", + "dark": "#363AF5" + }, + "background": { + "color": { + "light": "#F5F4F0", + "dark": "#1d1d1d" + } + }, + "styling": { + "logo": { + "width": "auto", + "height": "100%" + } + }, + "navbar": { + "links": [ + { + "icon": "github", + "label": "GitHub", + "href": "https://github.com/eigent-ai/Eigent-desktop" + } + ], + "primary": { + "type": "button", + "label": "Get Started", + "href": "https://www.eigent.ai/download" + } + }, + "navigation": { + "tabs": [ + { + "tab": "Documentation", + "groups": [ + { + "group": "Getting Started", + "pages": [ + "/get_started/welcome", + "/get_started/installation", + "/get_started/quick_start" + ] + }, + { + "group": "Features", + "pages": [ + "/features/dynamic_workforce", + "/features/customised_workers", + "/features/human-in-the-loop", + "/features/local_models", + "/features/add_mcps" + ] + } + ] + } + ], + "global": { + "anchors": [ + { + "anchor": "Download Here", + "href": "https://www.eigent.ai", + "icon": "gift" + } + ] + } + }, + "footer": { + "socials": { + "github": "https://github.com/eigent-ai/Eigent-desktop", + "twitter": "https://x.com/Eigent-AI", + "linkedin": "https://www.linkedin.com/company/eigent-ai/" + } + } +} \ No newline at end of file diff --git a/docs/features/add_mcps.md b/docs/features/add_mcps.md new file mode 100644 index 000000000..6090a9ab0 --- /dev/null +++ b/docs/features/add_mcps.md @@ -0,0 +1,12 @@ +--- +title: Add MCPs +description: Eigent comes with massive built-in Model Context Protocol (MCP) tools and lets you install your own tools for web browsing, code execution, Notion, Google suite, Slack, and more. +icon: plug +--- + +Add MCPs diff --git a/docs/features/customised_workers.md b/docs/features/customised_workers.md new file mode 100644 index 000000000..730aa6259 --- /dev/null +++ b/docs/features/customised_workers.md @@ -0,0 +1,13 @@ +--- +title: Customised Workers +description: Employs a team of specialized AI agents that collaborate to solve complex tasks. Eigent pre-defines Developer, Search, Document, and Multi-Modal agents. +icon: users +--- + +Add MCPs + diff --git a/docs/features/dynamic_workforce.md b/docs/features/dynamic_workforce.md new file mode 100644 index 000000000..a59cb5482 --- /dev/null +++ b/docs/features/dynamic_workforce.md @@ -0,0 +1,12 @@ +--- +title: Dynamic Workforce +description: Employs a team of specialized AI agents that collaborate to solve complex tasks. Eigent dynamically breaks down tasks and activates multiple agents to work in parallel. +icon: workflow +--- + +Dynamic Workforce diff --git a/docs/features/human-in-the-loop.md b/docs/features/human-in-the-loop.md new file mode 100644 index 000000000..7a29960a9 --- /dev/null +++ b/docs/features/human-in-the-loop.md @@ -0,0 +1,13 @@ +--- +title: Human in the Loop +description: If a task gets stuck or encounters uncertainty, Eigent will automatically request human input. +icon: hand +--- + +Human in the Loop + diff --git a/docs/features/local_models.md b/docs/features/local_models.md new file mode 100644 index 000000000..bd9730366 --- /dev/null +++ b/docs/features/local_models.md @@ -0,0 +1,13 @@ +--- +title: Local Models +description: Deploy Eigent locally with your preferred models. +icon: database +--- + +Local Models + diff --git a/docs/get_started/installation.md b/docs/get_started/installation.md new file mode 100644 index 000000000..0b0372033 --- /dev/null +++ b/docs/get_started/installation.md @@ -0,0 +1,49 @@ +--- +title: Installation +description: Getting Eigent set up on your computer is simple. Follow these steps to begin. +icon: wrench +--- + + + + Head to our official website to download the latest version. + - Download for macOS + - Download for Windows + + + **macOS Prerequisite** + Please ensure you are running macOS 11 (Big Sur) or a newer version to install Eigent. + + + + - **On macOS:** Open the downloaded `.dmg` file and drag the Eigent icon into your Applications folder. + - **On Windows:** Run the downloaded `.exe` installer and follow the on-screen instructions. + + + +Once installed, launch Eigent and log in to get started! + +## Next Steps + +You're all set! Now that Eigent is installed, here are a few places you can go to learn more: + + + + Jump right in and learn how to launch your first task. + + + Understand the core terms and features that make Eigent unique. + + + Find answers to frequently asked questions. + + \ No newline at end of file diff --git a/docs/get_started/quick_start.md b/docs/get_started/quick_start.md new file mode 100644 index 000000000..35ade91ed --- /dev/null +++ b/docs/get_started/quick_start.md @@ -0,0 +1,204 @@ +--- +title: Quick Start +description: Get started with Eigent in just a few minutes! +icon: rocket +--- + +This guide will walk you through building your first multi-agent workforce using Eigent. + +## Create Your First Task + +Once opened, you'll land on the **Task** page. It’s a clean space designed to turn your ideas into action. Let's break down what you see. + +![Layout](/images/quickstart_firsttask.png) + +### The Top Bar + +At the very top of the window is your main navigation bar. You'll access: + +- **Dashboard**: your home base for creating and viewing History and Ongoing tasks. + - Project Archives: a detailed log of all your past tasks, including the token usage. + - Ongoing Tasks +- **Settings:** where you can configure the app to your liking. + +![Task Hub](/images/quickstart_thetopbar.png) + +### The Main View + +Your workspace is split into two panels: + +**Message Box (Left):** where you'll chat with your AI workforce to start a job. +- Before running the task, you can Add, Edit, or Delete any subtask or Back to Edit your request, then resume. When tasks complete, you can use Replay to re-run the flow. + ![Message](/images/quickstart_themainview.gif) + +- You can pause anytime—hit **Pause**, edit via **Back to Edit**, then resume. When tasks complete, use **Replay** to re-run the flow.![Message 2](/images/quickstart_pause.gif) + +**Canvas (Right):** where your AI agents get to work. +- **Before a Task:** You'll see your pre-built agents and and their tools. You can also click **+ New Worker** to add your own. These workers will always be on standby for your task. +- **During a Task:** The Canvas shows the live status of all subtasks (`Done / In Progress / Unfinished`). Click any subtask to view detailed logs (reasoning steps, tool calls, results). More on this below. +![Task in Progress](/images/quickstart_canvas_inprogress.png) +- **Canvas Toolbar:** At the bottom of the Canvas, you'll see a toolbar. This is where you manage your views of agents. You can switch between different task views, such as **Home**, **Agent Folder**, or a specific worker's **Workspace**. +![Add Worker](/images/quickstart_canvas_bottom.png) + +### Agent Folder + +This is the filing cabinet for your workforce. Any files your agents create or use (like documents, spreadsheets, code, pictures, or presentations) are automatically saved here. These files are also stored locally on your computer and/or in your cloud for easy access. + +![Agent Folder](/images/quickstart_agentfolder.png) + +