tina4-python 3.10.34__tar.gz → 3.10.38__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.
- {tina4_python-3.10.34 → tina4_python-3.10.38}/.gitignore +0 -3
- {tina4_python-3.10.34 → tina4_python-3.10.38}/PKG-INFO +1 -1
- {tina4_python-3.10.34 → tina4_python-3.10.38}/pyproject.toml +1 -1
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/__init__.py +1 -1
- tina4_python-3.10.38/tina4_python/ai/__init__.py +312 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/cli/__init__.py +9 -32
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/dev_admin/__init__.py +320 -6
- tina4_python-3.10.38/tina4_python/dev_admin/metrics.py +513 -0
- tina4_python-3.10.34/tina4_python/ai/__init__.py +0 -412
- {tina4_python-3.10.34 → tina4_python-3.10.38}/README.md +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/Testing.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/core/events.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/core/request.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/core/response.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/core/router.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/core/server.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/dev_reload.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.10.34 → tina4_python-3.10.38}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# Tina4 AI — Install AI coding assistant context files.
|
|
2
|
+
"""
|
|
3
|
+
Simple menu-driven installer for AI tool context files.
|
|
4
|
+
The user picks which tools they use, we install the appropriate files.
|
|
5
|
+
|
|
6
|
+
from tina4_python.ai import show_menu, install_selected
|
|
7
|
+
"""
|
|
8
|
+
import os
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Ordered list of supported AI tools
|
|
15
|
+
AI_TOOLS = [
|
|
16
|
+
{"name": "claude-code", "description": "Claude Code", "context_file": "CLAUDE.md", "config_dir": ".claude"},
|
|
17
|
+
{"name": "cursor", "description": "Cursor", "context_file": ".cursorules", "config_dir": ".cursor"},
|
|
18
|
+
{"name": "copilot", "description": "GitHub Copilot", "context_file": ".github/copilot-instructions.md", "config_dir": ".github"},
|
|
19
|
+
{"name": "windsurf", "description": "Windsurf", "context_file": ".windsurfrules", "config_dir": None},
|
|
20
|
+
{"name": "aider", "description": "Aider", "context_file": "CONVENTIONS.md", "config_dir": None},
|
|
21
|
+
{"name": "cline", "description": "Cline", "context_file": ".clinerules", "config_dir": None},
|
|
22
|
+
{"name": "codex", "description": "OpenAI Codex", "context_file": "AGENTS.md", "config_dir": None},
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def is_installed(root: str, tool: dict) -> bool:
|
|
27
|
+
"""Check if a tool's context file already exists."""
|
|
28
|
+
return (Path(root).resolve() / tool["context_file"]).exists()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def show_menu(root: str = ".") -> str:
|
|
32
|
+
"""Print the numbered menu and return user input."""
|
|
33
|
+
root = str(Path(root).resolve())
|
|
34
|
+
green = "\033[32m"
|
|
35
|
+
reset = "\033[0m"
|
|
36
|
+
|
|
37
|
+
print("\n Tina4 AI Context Installer\n")
|
|
38
|
+
for i, tool in enumerate(AI_TOOLS, 1):
|
|
39
|
+
installed = is_installed(root, tool)
|
|
40
|
+
marker = f" {green}[installed]{reset}" if installed else ""
|
|
41
|
+
print(f" {i}. {tool['description']:<20s} {tool['context_file']}{marker}")
|
|
42
|
+
|
|
43
|
+
# tina4-ai tools option
|
|
44
|
+
tina4_ai_installed = shutil.which("mdview") is not None
|
|
45
|
+
marker = f" {green}[installed]{reset}" if tina4_ai_installed else ""
|
|
46
|
+
print(f" 8. Install tina4-ai tools (requires Python){marker}")
|
|
47
|
+
print()
|
|
48
|
+
return input(" Select (comma-separated, or 'all'): ").strip()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def install_selected(root: str, selection: str) -> list[str]:
|
|
52
|
+
"""Install context files for the selected tools.
|
|
53
|
+
|
|
54
|
+
selection: comma-separated numbers like "1,2,3" or "all"
|
|
55
|
+
Returns list of created/updated file paths.
|
|
56
|
+
"""
|
|
57
|
+
root_path = Path(root).resolve()
|
|
58
|
+
created = []
|
|
59
|
+
|
|
60
|
+
if selection.lower() == "all":
|
|
61
|
+
indices = list(range(len(AI_TOOLS)))
|
|
62
|
+
install_tina4_ai = True
|
|
63
|
+
else:
|
|
64
|
+
parts = [s.strip() for s in selection.split(",") if s.strip()]
|
|
65
|
+
indices = []
|
|
66
|
+
install_tina4_ai = False
|
|
67
|
+
for p in parts:
|
|
68
|
+
try:
|
|
69
|
+
n = int(p)
|
|
70
|
+
if n == 8:
|
|
71
|
+
install_tina4_ai = True
|
|
72
|
+
elif 1 <= n <= len(AI_TOOLS):
|
|
73
|
+
indices.append(n - 1)
|
|
74
|
+
except ValueError:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
context = generate_context()
|
|
78
|
+
|
|
79
|
+
for idx in indices:
|
|
80
|
+
tool = AI_TOOLS[idx]
|
|
81
|
+
files = _install_for_tool(root_path, tool, context)
|
|
82
|
+
created.extend(files)
|
|
83
|
+
|
|
84
|
+
if install_tina4_ai:
|
|
85
|
+
_install_tina4_ai()
|
|
86
|
+
|
|
87
|
+
return created
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def install_all(root: str = ".") -> list[str]:
|
|
91
|
+
"""Install context for all AI tools (non-interactive)."""
|
|
92
|
+
return install_selected(root, "all")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _install_for_tool(root: Path, tool: dict, context: str) -> list[str]:
|
|
96
|
+
"""Install context file for a single tool."""
|
|
97
|
+
created = []
|
|
98
|
+
context_path = root / tool["context_file"]
|
|
99
|
+
|
|
100
|
+
# Create directories
|
|
101
|
+
if tool.get("config_dir"):
|
|
102
|
+
(root / tool["config_dir"]).mkdir(parents=True, exist_ok=True)
|
|
103
|
+
context_path.parent.mkdir(parents=True, exist_ok=True)
|
|
104
|
+
|
|
105
|
+
# Always overwrite — user chose to install
|
|
106
|
+
context_path.write_text(context, encoding="utf-8")
|
|
107
|
+
action = "Updated" if context_path.exists() else "Installed"
|
|
108
|
+
rel = str(context_path.relative_to(root))
|
|
109
|
+
created.append(rel)
|
|
110
|
+
print(f" \033[32m✓\033[0m {action} {rel}")
|
|
111
|
+
|
|
112
|
+
# Claude-specific extras
|
|
113
|
+
if tool["name"] == "claude-code":
|
|
114
|
+
skills = _install_claude_skills(root)
|
|
115
|
+
created.extend(skills)
|
|
116
|
+
|
|
117
|
+
return created
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _install_tina4_ai():
|
|
121
|
+
"""Install tina4-ai package (provides mdview for markdown viewing)."""
|
|
122
|
+
print(" Installing tina4-ai tools...")
|
|
123
|
+
for cmd in ["pip3", "pip"]:
|
|
124
|
+
if shutil.which(cmd):
|
|
125
|
+
try:
|
|
126
|
+
result = subprocess.run(
|
|
127
|
+
[cmd, "install", "--upgrade", "tina4-ai"],
|
|
128
|
+
capture_output=True, text=True, timeout=60,
|
|
129
|
+
)
|
|
130
|
+
if result.returncode == 0:
|
|
131
|
+
print(" \033[32m✓\033[0m Installed tina4-ai (mdview)")
|
|
132
|
+
return
|
|
133
|
+
else:
|
|
134
|
+
print(f" \033[33m!\033[0m {cmd} failed: {result.stderr.strip()[:100]}")
|
|
135
|
+
except (subprocess.TimeoutExpired, OSError):
|
|
136
|
+
continue
|
|
137
|
+
print(" \033[33m!\033[0m Python/pip not available — skip tina4-ai")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _install_claude_skills(root: Path) -> list[str]:
|
|
141
|
+
"""Copy Claude Code skill files from the framework's templates."""
|
|
142
|
+
created = []
|
|
143
|
+
commands_dir = root / ".claude" / "commands"
|
|
144
|
+
commands_dir.mkdir(parents=True, exist_ok=True)
|
|
145
|
+
|
|
146
|
+
pkg_dir = Path(__file__).parent.parent
|
|
147
|
+
source_dirs = [pkg_dir / "templates" / "ai" / "claude-commands"]
|
|
148
|
+
|
|
149
|
+
for source_dir in source_dirs:
|
|
150
|
+
if source_dir.is_dir():
|
|
151
|
+
for skill_file in source_dir.glob("*.md"):
|
|
152
|
+
target = commands_dir / skill_file.name
|
|
153
|
+
target.write_text(skill_file.read_text(encoding="utf-8"), encoding="utf-8")
|
|
154
|
+
rel = str(target.relative_to(root))
|
|
155
|
+
created.append(rel)
|
|
156
|
+
|
|
157
|
+
# Copy skill directories from framework .claude/skills/
|
|
158
|
+
framework_root = pkg_dir.parent
|
|
159
|
+
framework_skills_dir = framework_root / ".claude" / "skills"
|
|
160
|
+
if framework_skills_dir.is_dir():
|
|
161
|
+
target_skills_dir = root / ".claude" / "skills"
|
|
162
|
+
target_skills_dir.mkdir(parents=True, exist_ok=True)
|
|
163
|
+
for skill_dir in framework_skills_dir.iterdir():
|
|
164
|
+
if skill_dir.is_dir():
|
|
165
|
+
target_dir = target_skills_dir / skill_dir.name
|
|
166
|
+
if target_dir.exists():
|
|
167
|
+
shutil.rmtree(target_dir)
|
|
168
|
+
shutil.copytree(skill_dir, target_dir)
|
|
169
|
+
rel = str(target_dir.relative_to(root))
|
|
170
|
+
created.append(rel)
|
|
171
|
+
print(f" \033[32m✓\033[0m Updated {rel}")
|
|
172
|
+
|
|
173
|
+
return created
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def generate_context() -> str:
|
|
177
|
+
"""Generate the universal Tina4 context document for any AI assistant."""
|
|
178
|
+
return f"""# Tina4 Python — AI Context
|
|
179
|
+
|
|
180
|
+
This project uses **Tina4 Python**, a lightweight, batteries-included web framework
|
|
181
|
+
with zero third-party dependencies for core features.
|
|
182
|
+
|
|
183
|
+
**Documentation:** https://tina4.com
|
|
184
|
+
|
|
185
|
+
## Quick Start
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
tina4python init . # Scaffold project
|
|
189
|
+
tina4python serve # Start dev server on port 7145
|
|
190
|
+
tina4python migrate # Run database migrations
|
|
191
|
+
tina4python test # Run test suite
|
|
192
|
+
tina4python routes # List all registered routes
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Project Structure
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
src/routes/ — Route handlers (auto-discovered, one per resource)
|
|
199
|
+
src/orm/ — ORM models (one per file, filename = class name)
|
|
200
|
+
src/templates/ — Twig/Jinja2 templates (extends base.twig)
|
|
201
|
+
src/app/ — Shared helpers and service classes
|
|
202
|
+
src/scss/ — SCSS files (auto-compiled to public/css/)
|
|
203
|
+
src/public/ — Static assets served at /
|
|
204
|
+
src/locales/ — Translation JSON files
|
|
205
|
+
src/seeds/ — Database seeder scripts
|
|
206
|
+
migrations/ — SQL migration files (sequential numbered)
|
|
207
|
+
tests/ — pytest test files
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Built-in Features (No External Packages Needed)
|
|
211
|
+
|
|
212
|
+
| Feature | Module | Import |
|
|
213
|
+
|---------|--------|--------|
|
|
214
|
+
| Routing | router | `from tina4_python.core.router import get, post, put, delete` |
|
|
215
|
+
| ORM | orm | `from tina4_python.orm import ORM, IntegerField, StringField` |
|
|
216
|
+
| Database | database | `from tina4_python.database import Database` |
|
|
217
|
+
| Templates | template | `response.render("page.twig", data)` |
|
|
218
|
+
| JWT Auth | auth | `from tina4_python.auth import Auth, hash_password, check_password` |
|
|
219
|
+
| REST API Client | api | `from tina4_python.api import Api` |
|
|
220
|
+
| GraphQL | graphql | `from tina4_python.graphql import GraphQL, Schema` |
|
|
221
|
+
| WebSocket | websocket | `from tina4_python.websocket import WebSocketServer` |
|
|
222
|
+
| SOAP/WSDL | wsdl | `from tina4_python.wsdl import WSDL, wsdl_operation` |
|
|
223
|
+
| Email (SMTP+IMAP) | messenger | `from tina4_python.messenger import Messenger` |
|
|
224
|
+
| Background Queue | queue | `from tina4_python.queue import Queue` |
|
|
225
|
+
| SCSS Compilation | scss | Auto-compiled from src/scss/ |
|
|
226
|
+
| Migrations | migration | `tina4python migrate` CLI command |
|
|
227
|
+
| Seeder | seeder | `from tina4_python.seeder import FakeData, seed_table` |
|
|
228
|
+
| i18n | localization | `from tina4_python.localization import Localization` |
|
|
229
|
+
| Swagger/OpenAPI | swagger | Auto-generated at /swagger |
|
|
230
|
+
| Sessions | session | `request.session.get(key)` / `.set(key, value)` |
|
|
231
|
+
| Middleware | middleware | `@middleware(MyMiddleware)` decorator |
|
|
232
|
+
| HTML Builder | html_element | `from tina4_python.html_element import HTMLElement` |
|
|
233
|
+
| Form Tokens | template | `{{{{ form_token() }}}}` in Twig |
|
|
234
|
+
|
|
235
|
+
## Key Conventions
|
|
236
|
+
|
|
237
|
+
1. **Routes return `response()`** — always use `response(data)` not `response.json()`
|
|
238
|
+
2. **GET routes are public**, POST/PUT/PATCH/DELETE require auth by default
|
|
239
|
+
3. **Use `@noauth()`** to make write routes public, `@secured()` to protect GET routes
|
|
240
|
+
4. **Decorator order**: `@noauth/@secured` → `@description/@tags` → `@get/@post` (route decorator innermost)
|
|
241
|
+
5. **Every template extends `base.twig`** — no standalone HTML pages
|
|
242
|
+
6. **No inline styles** — use SCSS in `src/scss/` with CSS variables
|
|
243
|
+
7. **No hardcoded colors** — use `var(--primary)`, `var(--text)`, etc.
|
|
244
|
+
8. **All schema changes via migrations** — never create tables in route code
|
|
245
|
+
9. **Service pattern** — complex logic goes in `src/app/` service classes, routes stay thin
|
|
246
|
+
10. **Use built-in features** — never install packages for things Tina4 already provides
|
|
247
|
+
|
|
248
|
+
## AI Workflow — Available Skills
|
|
249
|
+
|
|
250
|
+
When using an AI coding assistant with Tina4, these skills are available:
|
|
251
|
+
|
|
252
|
+
| Skill | Description |
|
|
253
|
+
|-------|-------------|
|
|
254
|
+
| `/tina4-route` | Create a new route with proper decorators and auth |
|
|
255
|
+
| `/tina4-orm` | Create an ORM model with migration |
|
|
256
|
+
| `/tina4-crud` | Generate complete CRUD (migration, ORM, routes, template, tests) |
|
|
257
|
+
| `/tina4-auth` | Set up JWT authentication with login/register |
|
|
258
|
+
| `/tina4-api` | Create an external API integration |
|
|
259
|
+
| `/tina4-queue` | Set up background job processing |
|
|
260
|
+
| `/tina4-template` | Create a server-rendered template page |
|
|
261
|
+
| `/tina4-graphql` | Set up a GraphQL endpoint |
|
|
262
|
+
| `/tina4-websocket` | Set up WebSocket communication |
|
|
263
|
+
| `/tina4-wsdl` | Create a SOAP/WSDL service |
|
|
264
|
+
| `/tina4-messenger` | Set up email send/receive |
|
|
265
|
+
| `/tina4-test` | Write tests for a feature |
|
|
266
|
+
| `/tina4-migration` | Create a database migration |
|
|
267
|
+
| `/tina4-seed` | Generate fake data for development |
|
|
268
|
+
| `/tina4-i18n` | Set up internationalization |
|
|
269
|
+
| `/tina4-scss` | Set up SCSS stylesheets |
|
|
270
|
+
| `/tina4-frontend` | Set up a frontend framework |
|
|
271
|
+
|
|
272
|
+
## Common Patterns
|
|
273
|
+
|
|
274
|
+
### Route
|
|
275
|
+
```python
|
|
276
|
+
from tina4_python.core.router import get, post, noauth
|
|
277
|
+
from tina4_python.swagger import description, tags
|
|
278
|
+
|
|
279
|
+
@noauth()
|
|
280
|
+
@description("Create a widget")
|
|
281
|
+
@tags(["widgets"])
|
|
282
|
+
@post("/api/widgets")
|
|
283
|
+
async def create_widget(request, response):
|
|
284
|
+
data = request.body
|
|
285
|
+
return response({{"created": True}}, 201)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### ORM Model
|
|
289
|
+
```python
|
|
290
|
+
from tina4_python.orm import ORM, IntegerField, StringField
|
|
291
|
+
|
|
292
|
+
class Widget(ORM):
|
|
293
|
+
id = IntegerField(primary_key=True, auto_increment=True)
|
|
294
|
+
name = StringField()
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Template
|
|
298
|
+
```twig
|
|
299
|
+
{{% extends "base.twig" %}}
|
|
300
|
+
{{% block content %}}
|
|
301
|
+
<div class="container">
|
|
302
|
+
<h1>{{{{ title }}}}</h1>
|
|
303
|
+
{{% for item in items %}}
|
|
304
|
+
<p>{{{{ item.name }}}}</p>
|
|
305
|
+
{{% endfor %}}
|
|
306
|
+
</div>
|
|
307
|
+
{{% endblock %}}
|
|
308
|
+
```
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
__all__ = ["AI_TOOLS", "is_installed", "show_menu", "install_selected", "install_all", "generate_context"]
|
|
@@ -405,42 +405,19 @@ def _build(args):
|
|
|
405
405
|
|
|
406
406
|
|
|
407
407
|
def _ai(args):
|
|
408
|
-
"""
|
|
409
|
-
from tina4_python.ai import
|
|
408
|
+
"""Install AI coding assistant context files."""
|
|
409
|
+
from tina4_python.ai import show_menu, install_selected, install_all
|
|
410
410
|
|
|
411
411
|
root = "."
|
|
412
412
|
|
|
413
|
-
if "
|
|
414
|
-
#
|
|
415
|
-
|
|
416
|
-
if created:
|
|
417
|
-
print("Installed Tina4 context for all AI tools:")
|
|
418
|
-
for f in created:
|
|
419
|
-
print(f" + {f}")
|
|
420
|
-
else:
|
|
421
|
-
print("All AI context files already exist. Use --force to overwrite.")
|
|
422
|
-
elif "--status" in args or not args:
|
|
423
|
-
# Show detection status
|
|
424
|
-
print(status_report(root))
|
|
425
|
-
|
|
426
|
-
# Auto-install for detected tools
|
|
427
|
-
detected = [t for t in detect_ai(root) if t["installed"]]
|
|
428
|
-
if detected:
|
|
429
|
-
created = install_context(root)
|
|
430
|
-
if created:
|
|
431
|
-
print("Installed Tina4 context:")
|
|
432
|
-
for f in created:
|
|
433
|
-
print(f" + {f}")
|
|
434
|
-
else:
|
|
435
|
-
print("Context files already exist. Use --force to overwrite.")
|
|
413
|
+
if args and args[0].lower() == "all":
|
|
414
|
+
# Non-interactive: install everything
|
|
415
|
+
install_all(root)
|
|
436
416
|
else:
|
|
437
|
-
#
|
|
438
|
-
|
|
439
|
-
if
|
|
440
|
-
|
|
441
|
-
print(f" + {f}")
|
|
442
|
-
else:
|
|
443
|
-
print("Nothing to install.")
|
|
417
|
+
# Interactive: show menu, get selection
|
|
418
|
+
selection = show_menu(root)
|
|
419
|
+
if selection:
|
|
420
|
+
install_selected(root, selection)
|
|
444
421
|
|
|
445
422
|
|
|
446
423
|
def _generate(args):
|