stkit 17.2.1__tar.gz → 17.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {stkit-17.2.1 → stkit-17.2.2}/CHANGELOG.md +9 -0
- {stkit-17.2.1 → stkit-17.2.2}/PKG-INFO +2 -2
- {stkit-17.2.1 → stkit-17.2.2}/pyproject.toml +2 -2
- {stkit-17.2.1 → stkit-17.2.2}/stk/__init__.py +1 -1
- {stkit-17.2.1 → stkit-17.2.2}/stk/cli/base.py +3 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/cli/develop.py +2 -5
- {stkit-17.2.1 → stkit-17.2.2}/stk/tasks/__init__.py +33 -7
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/index.html +1 -1
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_cli.py +17 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_jobs.py +17 -1
- {stkit-17.2.1 → stkit-17.2.2}/.gitignore +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/LICENSE +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/README.md +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/SECURITY.md +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/examples/minimal/.env-sample +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/examples/minimal/.gitignore +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/examples/minimal/README.md +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/examples/minimal/minimal/__init__.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/examples/minimal/minimal/app.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/examples/minimal/minimal/migrations/364232ae77d5_add_note.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/examples/minimal/minimal/note/__init__.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/examples/minimal/minimal/note/models.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/examples/minimal/minimal/note/templates/note/index.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/examples/minimal/minimal/note/views.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/examples/minimal/minimal/settings.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/examples/minimal/minimal/templates/layout.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/examples/minimal/pyproject.toml +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/examples/minimal/tests/__init__.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/examples/minimal/tests/test_app.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/examples/minimal/uv.lock +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/__main__.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/agent_login.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/app.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/checks.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/cli/__init__.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/cli/agent.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/cli/copy_rules.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/cli/database.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/cli/doctor.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/cli/main.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/cli/reports.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/cli/smoke.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/cli/users.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/cli/watch.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/extensions.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/files/__init__.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/files/models.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/files/store.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/files/views.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/mcp/__init__.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/mcp/models.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/mcp/server.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/mcp/views.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/migrations/__init__.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/migrations/env.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/migrations/script.py.mako +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/migrations/versions/stk_0001_initial_schema.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/migrations/versions/stk_0002_jobs.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/migrations/versions/stk_0003_files.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/migrations/versions/stk_0004_notifications.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/migrations/versions/stk_0005_mcp_tokens.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/notification/__init__.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/notification/models.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/notification/views.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/portal/views.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/public/__init__.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/public/models.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/public/views.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/__init__.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/CLAUDE.md +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/Dockerfile +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/README.md +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/__app__/__init__.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/__app__/app.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/__app__/settings.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/claude/skills/stk-api/SKILL.md +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/claude/skills/stk-blueprint/SKILL.md +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/claude/skills/stk-deploy/SKILL.md +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/claude/skills/stk-lessons/SKILL.md +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/claude/skills/stk-migrate/SKILL.md +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/docker-compose.yml +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/dockerignore +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/env-sample +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/github/workflows/checks.yml +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/gitignore +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/nginx/nginx.conf +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/nginx/stk.conf +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/postgres/init-db.sh +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/pyproject.toml +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/tests/__init__.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/app/tests/test_app.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/generator.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/scaffold/templates.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/security.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/settings.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/shell.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/VERSIONS.txt +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/css/app.css +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/css/layout.css +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/css/vuetify.min.css +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.animals.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.arrows-1.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.arrows-2.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.badges.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.brand-1.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.brand-2.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.buildings.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.charts.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.communication.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.computers.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.core.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.currencies.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.database.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.design-1.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.design-2.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.development.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.devices-1.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.devices-2.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.document.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.e-commerce.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.electrical.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.extensions.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.food.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.games.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.gender.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.gestures.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.health.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.laundry.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.letters.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.logic.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.map.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.math.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.media.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.mood.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.nature.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.numbers.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.photography.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.shapes.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.sport.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.symbols.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.system-1.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.system-2.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.system-3.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.text.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.vehicles.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.version-control.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.weather.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/fonts/tabler-icons.zodiac.woff2 +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/icons/tabler-icons.min.css +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/img/auth-bg.webp +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/img/favicon.ico +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/img/stk.svg +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/js/axios.min.js +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/js/components/NotificationDropdown.js +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/js/components/ThemeSwitcher.js +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/js/components/TransitionExpand.js +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/js/components/VerticalNavGroup.js +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/js/components/VerticalNavLink.js +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/js/components/VerticalNavSectionTitle.js +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/js/components/index.js +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/js/config.js +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/js/vue.min.js +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/js/vuetify.min.js +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/static/robots.txt +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/tasks/models.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/401.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/404.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/500.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/_brand.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/auth_layout.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/cms/activities.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/cms/jobs.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/cms/roles.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/cms/users.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/dashboard.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/layout.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/login-layout.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/mcp/tokens.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/_macros.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/_messages.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/change_password.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/confirm_email.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/email/change_notice.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/email/change_notice.txt +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/email/confirmation_instructions.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/email/confirmation_instructions.txt +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/email/login_instructions.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/email/login_instructions.txt +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/email/reset_instructions.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/email/reset_instructions.txt +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/email/reset_notice.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/email/reset_notice.txt +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/email/welcome.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/email/welcome.txt +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/forgot_password.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/login_user.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/mf_recovery.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/mf_recovery_codes.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/register_user.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/reset_password.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/send_confirmation.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/send_login.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/two_factor_select.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/two_factor_setup.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/two_factor_verify_code.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/wan_register.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/wan_signin.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/templates/security/wan_verify.html +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/user/__init__.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/user/forms.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/user/models.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/user/views.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/utils/email.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/utils/netguard.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/utils/ratelimit.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/stk/websocket.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_agent_operability.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_app_index.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_branding.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_builder_ux.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_composition.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_copy_rules.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_csrf_exempt.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_error_handler.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_files.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_mcp.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_migration_drift.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_netguard.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_notifications.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_production_security.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_revision_imports.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_scaffolder.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_security_compatibility.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_shell.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_smoke_pages.py +0 -0
- {stkit-17.2.1 → stkit-17.2.2}/tests/test_static_versioning.py +0 -0
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v17.2.2 (2026-10-31)
|
|
4
|
+
|
|
5
|
+
No migration required.
|
|
6
|
+
|
|
7
|
+
- `stk add-role` and `stk cleanup-sessions` crashed with `TypeError: 'NoneType' object is not callable` because they used the database before the app was built. Every command that runs async work now builds the app first. Run `cleanup-sessions` from cron again if you had stopped it.
|
|
8
|
+
- `stk worker` stops on SIGTERM or Ctrl-C at a safe point: it cancels only the running job, hands it back, and exits without a traceback.
|
|
9
|
+
- The home page describes stk as running on Quart, not Flask.
|
|
10
|
+
- Requires quart-security 2.0.5: login takes the same time whether or not an email has an account, and passkey registration refuses a credential ID another user already holds (Authentication).
|
|
11
|
+
|
|
3
12
|
## v17.2.1 (2026-10-31)
|
|
4
13
|
|
|
5
14
|
No migration required.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: stkit
|
|
3
|
-
Version: 17.2.
|
|
3
|
+
Version: 17.2.2
|
|
4
4
|
Summary: An async Quart framework optimized for AI-assisted development workflows
|
|
5
5
|
Project-URL: Documentation, https://docs.stkit.dev
|
|
6
6
|
Project-URL: Source, https://github.com/level09/stk
|
|
@@ -28,7 +28,7 @@ Requires-Dist: pillow>=13.0.0
|
|
|
28
28
|
Requires-Dist: psycopg2-binary>=2.9.10
|
|
29
29
|
Requires-Dist: python-dotenv>=1.2.2
|
|
30
30
|
Requires-Dist: qrcode>=8.2
|
|
31
|
-
Requires-Dist: quart-security<3,>=2.0.
|
|
31
|
+
Requires-Dist: quart-security<3,>=2.0.5
|
|
32
32
|
Requires-Dist: quart-session>=3.0.0
|
|
33
33
|
Requires-Dist: quart>=0.20.0
|
|
34
34
|
Requires-Dist: rich>=14.0.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "stkit"
|
|
3
|
-
version = "17.2.
|
|
3
|
+
version = "17.2.2"
|
|
4
4
|
description = "An async Quart framework optimized for AI-assisted development workflows"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Level09", email = "hello@level09.com"}
|
|
@@ -30,7 +30,7 @@ dependencies = [
|
|
|
30
30
|
"python-dotenv>=1.2.2",
|
|
31
31
|
"qrcode>=8.2",
|
|
32
32
|
"quart>=0.20.0",
|
|
33
|
-
"quart-security>=2.0.
|
|
33
|
+
"quart-security>=2.0.5,<3",
|
|
34
34
|
"quart-session>=3.0.0",
|
|
35
35
|
"rich>=14.0.0",
|
|
36
36
|
"sqlalchemy[asyncio]>=2.0.41",
|
|
@@ -68,6 +68,9 @@ def cli_app():
|
|
|
68
68
|
|
|
69
69
|
def run_async(coro):
|
|
70
70
|
"""Run an async function in sync context (for CLI commands)."""
|
|
71
|
+
# The engine and session factory exist only once the app is built; loading it
|
|
72
|
+
# here means no command can reach the database without it.
|
|
73
|
+
cli_app()
|
|
71
74
|
|
|
72
75
|
async def _wrapper():
|
|
73
76
|
try:
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""Starting an app, serving it locally, and running its durable jobs."""
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
|
|
6
5
|
import click
|
|
@@ -82,7 +81,5 @@ def worker(timeout):
|
|
|
82
81
|
await work(timeout)
|
|
83
82
|
|
|
84
83
|
console.print("[green]Worker started[/]")
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
except (KeyboardInterrupt, asyncio.CancelledError):
|
|
88
|
-
console.print("Worker stopped")
|
|
84
|
+
run_async(run())
|
|
85
|
+
console.print("Worker stopped")
|
|
@@ -25,6 +25,7 @@ from stk.tasks.models import Job
|
|
|
25
25
|
logger = logging.getLogger(__name__)
|
|
26
26
|
|
|
27
27
|
JOBS = {}
|
|
28
|
+
_running = None # the job task `stk worker` is awaiting, for a clean shutdown
|
|
28
29
|
TIMEOUT = 3600
|
|
29
30
|
POLL_SECONDS = 2
|
|
30
31
|
RECOVER_SECONDS = 60
|
|
@@ -164,8 +165,13 @@ async def run_next(timeout=TIMEOUT):
|
|
|
164
165
|
job_id, name, payload, attempts, max_attempts = claimed
|
|
165
166
|
async with ext.async_session_factory() as session:
|
|
166
167
|
try:
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
global _running
|
|
169
|
+
_running = asyncio.create_task(JOBS[name](session, **payload))
|
|
170
|
+
try:
|
|
171
|
+
# wait_for cancels the job itself on timeout or when this is cancelled.
|
|
172
|
+
await asyncio.wait_for(_running, timeout)
|
|
173
|
+
finally:
|
|
174
|
+
_running = None
|
|
169
175
|
done = await session.execute(
|
|
170
176
|
_owned(job_id, lease).values(
|
|
171
177
|
status="done", locked_until=None, last_error=None
|
|
@@ -216,11 +222,28 @@ async def recover():
|
|
|
216
222
|
|
|
217
223
|
async def work(timeout=TIMEOUT):
|
|
218
224
|
"""Run due jobs one at a time until SIGTERM or Ctrl-C."""
|
|
219
|
-
asyncio.
|
|
220
|
-
|
|
221
|
-
)
|
|
225
|
+
stop = asyncio.Event()
|
|
226
|
+
|
|
227
|
+
def shutdown():
|
|
228
|
+
# Cancel only the job, never the worker mid-query: it hands the job back
|
|
229
|
+
# and the loop ends at its next check.
|
|
230
|
+
stop.set()
|
|
231
|
+
if _running is not None:
|
|
232
|
+
_running.cancel()
|
|
233
|
+
|
|
234
|
+
loop = asyncio.get_running_loop()
|
|
235
|
+
for sig in (signal.SIGTERM, signal.SIGINT):
|
|
236
|
+
loop.add_signal_handler(sig, shutdown)
|
|
237
|
+
try:
|
|
238
|
+
await _drain(timeout, stop)
|
|
239
|
+
except asyncio.CancelledError:
|
|
240
|
+
if not stop.is_set():
|
|
241
|
+
raise
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
async def _drain(timeout, stop):
|
|
222
245
|
recovered = None
|
|
223
|
-
while
|
|
246
|
+
while not stop.is_set():
|
|
224
247
|
# Also between jobs, so a queue that never empties still recovers.
|
|
225
248
|
if recovered is None or datetime.now() - recovered >= timedelta(
|
|
226
249
|
seconds=RECOVER_SECONDS
|
|
@@ -228,4 +251,7 @@ async def work(timeout=TIMEOUT):
|
|
|
228
251
|
await recover()
|
|
229
252
|
recovered = datetime.now()
|
|
230
253
|
if not await run_next(timeout):
|
|
231
|
-
|
|
254
|
+
try:
|
|
255
|
+
await asyncio.wait_for(stop.wait(), POLL_SECONDS)
|
|
256
|
+
except TimeoutError:
|
|
257
|
+
pass
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
<h1 class="text-headline-large font-weight-bold mb-3">Build faster with stk</h1>
|
|
10
10
|
<p class="text-body-large text-medium-emphasis mb-8">
|
|
11
|
-
|
|
11
|
+
An async Python framework for building web applications, on Quart.
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
14
|
{% if current_user.is_authenticated %}
|
|
@@ -61,3 +61,20 @@ class DowngradeArgumentTest(unittest.TestCase):
|
|
|
61
61
|
result = CliRunner().invoke(main, ["db", "downgrade", "-1"])
|
|
62
62
|
self.assertEqual(result.exit_code, 0, result.output)
|
|
63
63
|
self.assertEqual(run.call_args.args[-1], "-1")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class DatabaseCommandTest(unittest.TestCase):
|
|
67
|
+
def test_run_async_builds_the_app_before_touching_the_database(self):
|
|
68
|
+
"""`add-role` and `cleanup-sessions` crashed: nothing had built the engine."""
|
|
69
|
+
with (
|
|
70
|
+
patch("stk.cli.base.cli_app") as load,
|
|
71
|
+
patch("stk.tasks.cleanup_expired_sessions") as cleanup,
|
|
72
|
+
):
|
|
73
|
+
cleanup.side_effect = lambda: _done()
|
|
74
|
+
result = CliRunner().invoke(main, ["cleanup-sessions"])
|
|
75
|
+
self.assertEqual(result.exit_code, 0, result.output)
|
|
76
|
+
load.assert_called()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def _done():
|
|
80
|
+
return None
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""Durable jobs: enqueue, claim, retry, failure, and recovery of a dead worker's job."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
import signal
|
|
4
6
|
import unittest
|
|
5
7
|
from datetime import datetime, timedelta
|
|
6
8
|
|
|
@@ -11,7 +13,7 @@ from sqlalchemy import select, update
|
|
|
11
13
|
import stk.extensions as ext
|
|
12
14
|
from stk.app import Stk
|
|
13
15
|
from stk.settings import TestingConfig
|
|
14
|
-
from stk.tasks import enqueue, job, recover, run_next
|
|
16
|
+
from stk.tasks import enqueue, job, recover, run_next, work
|
|
15
17
|
from stk.tasks.models import Job
|
|
16
18
|
from stk.user.models import Activity, Base, User
|
|
17
19
|
from stk.user.views import api_jobs
|
|
@@ -163,6 +165,20 @@ class JobTests(unittest.IsolatedAsyncioTestCase):
|
|
|
163
165
|
self.assertEqual(row.status, "running")
|
|
164
166
|
self.assertNotIn("lost race", await self.activities())
|
|
165
167
|
|
|
168
|
+
async def test_sigterm_hands_back_the_running_job_and_stops_cleanly(self):
|
|
169
|
+
job_id = await enqueue("jobs_test_slow")
|
|
170
|
+
worker = asyncio.create_task(work())
|
|
171
|
+
for _ in range(50):
|
|
172
|
+
await asyncio.sleep(0.05)
|
|
173
|
+
if (await self.get(job_id)).status == "running":
|
|
174
|
+
break
|
|
175
|
+
os.kill(os.getpid(), signal.SIGTERM)
|
|
176
|
+
await asyncio.wait_for(worker, 5) # returns, not CancelledError
|
|
177
|
+
row = await self.get(job_id)
|
|
178
|
+
self.assertEqual(
|
|
179
|
+
(row.status, row.attempts, row.locked_until), ("queued", 0, None)
|
|
180
|
+
)
|
|
181
|
+
|
|
166
182
|
async def test_recovers_job_left_running_by_a_dead_worker(self):
|
|
167
183
|
lost = await enqueue("jobs_test_record", user_id=self.user_id, action="lost")
|
|
168
184
|
spent = await enqueue("jobs_test_record", user_id=self.user_id, action="spent")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|