tina4-python 3.11.22__tar.gz → 3.11.24__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.11.22 → tina4_python-3.11.24}/PKG-INFO +1 -1
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/__init__.py +1 -1
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/dev_admin/__init__.py +231 -1
- tina4_python-3.11.24/tina4_python/docs.py +821 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/mcp/tools.py +31 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/js/tina4-dev-admin.js +260 -111
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/js/tina4-dev-admin.min.js +260 -111
- {tina4_python-3.11.22 → tina4_python-3.11.24}/.gitignore +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/README.md +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/pyproject.toml +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/Testing.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/events.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/request.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/response.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/router.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/core/server.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.11.22 → tina4_python-3.11.24}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -288,6 +288,94 @@ def register():
|
|
|
288
288
|
Router.get(path, handler)
|
|
289
289
|
else:
|
|
290
290
|
Router.post(path, handler)
|
|
291
|
+
# Auto-discovery: drop `.tina4/mcp.json` so MCP-aware AI tools
|
|
292
|
+
# (Claude Code, Cursor, etc.) discover the local Live Docs +
|
|
293
|
+
# MCP server without the user authoring config. Idempotent.
|
|
294
|
+
write_mcp_discovery_file()
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def write_mcp_discovery_file() -> None:
|
|
298
|
+
"""Drop `.tina4/mcp.json` and append `.tina4/` to `.gitignore`.
|
|
299
|
+
|
|
300
|
+
Both are idempotent — running twice is a no-op when the state is
|
|
301
|
+
already correct. Skipped silently outside debug mode and on
|
|
302
|
+
filesystem errors (read-only project dir, etc.) — discovery is
|
|
303
|
+
a convenience, not a requirement.
|
|
304
|
+
|
|
305
|
+
See plan/v3/22-LIVE-API-RAG.md §"Auto-discovery file" for the
|
|
306
|
+
JSON shape.
|
|
307
|
+
"""
|
|
308
|
+
import json
|
|
309
|
+
import os
|
|
310
|
+
|
|
311
|
+
is_dev = os.environ.get("TINA4_DEBUG", "false").lower() in ("1", "true", "yes")
|
|
312
|
+
if not is_dev:
|
|
313
|
+
return
|
|
314
|
+
root = os.getcwd()
|
|
315
|
+
tina4_dir = os.path.join(root, ".tina4")
|
|
316
|
+
mcp_file = os.path.join(tina4_dir, "mcp.json")
|
|
317
|
+
port = (os.environ.get("TINA4_PORT")
|
|
318
|
+
or os.environ.get("PORT")
|
|
319
|
+
or "7146")
|
|
320
|
+
expected = {
|
|
321
|
+
"mcpServers": {
|
|
322
|
+
"tina4-live-docs": {
|
|
323
|
+
"url": f"http://localhost:{port}/__dev/api/mcp",
|
|
324
|
+
"description": "Live API docs for this Tina4 project (framework + user code)",
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
expected_json = json.dumps(expected, indent=2) + "\n"
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
if os.path.isfile(mcp_file):
|
|
332
|
+
with open(mcp_file, "r", encoding="utf-8") as f:
|
|
333
|
+
existing = f.read()
|
|
334
|
+
if existing.strip() == expected_json.strip():
|
|
335
|
+
_ensure_gitignore(root)
|
|
336
|
+
return
|
|
337
|
+
os.makedirs(tina4_dir, exist_ok=True)
|
|
338
|
+
with open(mcp_file, "w", encoding="utf-8") as f:
|
|
339
|
+
f.write(expected_json)
|
|
340
|
+
_ensure_gitignore(root)
|
|
341
|
+
except OSError:
|
|
342
|
+
# Read-only fs, permission denied, etc. Silently skip —
|
|
343
|
+
# discovery is convenience.
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _ensure_gitignore(root: str) -> None:
|
|
348
|
+
"""Append `.tina4/` to `.gitignore` if not already excluded.
|
|
349
|
+
|
|
350
|
+
Tolerates leading slashes, trailing slashes, and existing comment
|
|
351
|
+
lines so we never duplicate. Only touches `.gitignore` if `.git/`
|
|
352
|
+
exists (don't pollute non-git projects).
|
|
353
|
+
"""
|
|
354
|
+
import os
|
|
355
|
+
|
|
356
|
+
if not os.path.isdir(os.path.join(root, ".git")):
|
|
357
|
+
return
|
|
358
|
+
gi_path = os.path.join(root, ".gitignore")
|
|
359
|
+
existing = ""
|
|
360
|
+
if os.path.isfile(gi_path):
|
|
361
|
+
try:
|
|
362
|
+
with open(gi_path, "r", encoding="utf-8") as f:
|
|
363
|
+
existing = f.read()
|
|
364
|
+
except OSError:
|
|
365
|
+
return
|
|
366
|
+
for raw in existing.splitlines():
|
|
367
|
+
line = raw.strip()
|
|
368
|
+
if not line or line.startswith("#"):
|
|
369
|
+
continue
|
|
370
|
+
normal = line.strip("/").strip()
|
|
371
|
+
if normal == ".tina4":
|
|
372
|
+
return # already excluded
|
|
373
|
+
suffix = "" if existing.endswith("\n") or existing == "" else "\n"
|
|
374
|
+
try:
|
|
375
|
+
with open(gi_path, "a", encoding="utf-8") as f:
|
|
376
|
+
f.write(suffix + ".tina4/\n")
|
|
377
|
+
except OSError:
|
|
378
|
+
pass
|
|
291
379
|
|
|
292
380
|
|
|
293
381
|
def get_api_handlers() -> dict:
|
|
@@ -388,6 +476,17 @@ def get_api_handlers() -> dict:
|
|
|
388
476
|
# without shelling out from the browser.
|
|
389
477
|
"/__dev/api/scaffold": ("GET", _api_scaffold_list),
|
|
390
478
|
"/__dev/api/scaffold/run": ("POST", _api_scaffold_run),
|
|
479
|
+
# ── Live Docs (per plan/v3/22-LIVE-API-RAG.md) ──
|
|
480
|
+
# Thin HTTP wrappers around tina4_python.docs.Docs. Both
|
|
481
|
+
# framework public API and the user's src/ surface are
|
|
482
|
+
# returned, tagged with `source = framework | user`. AI tools
|
|
483
|
+
# (Claude Code, Cursor, dev-admin chat) hit these for ground-
|
|
484
|
+
# truth introspection instead of guessing from training data.
|
|
485
|
+
"/__dev/api/docs/search": ("GET", _api_docs_search),
|
|
486
|
+
"/__dev/api/docs/class": ("GET", _api_docs_class),
|
|
487
|
+
"/__dev/api/docs/method": ("GET", _api_docs_method),
|
|
488
|
+
"/__dev/api/docs/index": ("GET", _api_docs_index),
|
|
489
|
+
"/__dev/api/docs/.well-known.json": ("GET", _api_docs_well_known),
|
|
391
490
|
}
|
|
392
491
|
|
|
393
492
|
|
|
@@ -1766,10 +1865,56 @@ def render_dev_toolbar(method: str, path: str, matched_pattern: str,
|
|
|
1766
1865
|
<span style="color:#ffeb3b;">req:{request_id}</span>
|
|
1767
1866
|
<span style="color:#90caf9;">{route_count} routes</span>
|
|
1768
1867
|
<span style="color:#888;">Python {python_version}</span>
|
|
1769
|
-
<a href="#" onclick="
|
|
1868
|
+
<a href="#" onclick="window.__tina4ToggleOverlay(event)" style="color:#ef9a9a;margin-left:auto;text-decoration:none;cursor:pointer;">Dashboard ↗</a>
|
|
1770
1869
|
<span onclick="this.parentElement.style.display='none'" style="cursor:pointer;color:#888;margin-left:8px;">✕</span>
|
|
1771
1870
|
</div>
|
|
1772
1871
|
<script>
|
|
1872
|
+
// Overlay open/toggle helper + auto-restore. Persist the dev-admin
|
|
1873
|
+
// iframe's open/closed state across parent reloads so saving a file
|
|
1874
|
+
// (which kicks the watcher → location.reload) doesn't lose the
|
|
1875
|
+
// user's dev-admin chat / plan / file tree. Cross-framework parity
|
|
1876
|
+
// with PHP / Ruby / Node — same localStorage key, same shape.
|
|
1877
|
+
(function(){{
|
|
1878
|
+
var STATE_KEY = 'tina4_dev_overlay_open';
|
|
1879
|
+
function buildOverlay() {{
|
|
1880
|
+
var c = document.createElement('div');
|
|
1881
|
+
c.id = 'tina4-dev-panel';
|
|
1882
|
+
c.style.cssText = 'position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all 0.2s';
|
|
1883
|
+
var f = document.createElement('iframe');
|
|
1884
|
+
f.src = '/__dev';
|
|
1885
|
+
f.style.cssText = 'width:100%;height:100%;border:1px solid #3572A5;border-radius:0.5rem;box-shadow:0 8px 32px rgba(0,0,0,0.5);background:#0f172a';
|
|
1886
|
+
c.appendChild(f);
|
|
1887
|
+
document.body.appendChild(c);
|
|
1888
|
+
return c;
|
|
1889
|
+
}}
|
|
1890
|
+
window.__tina4ToggleOverlay = function(e) {{
|
|
1891
|
+
if (e) e.preventDefault();
|
|
1892
|
+
var p = document.getElementById('tina4-dev-panel');
|
|
1893
|
+
if (p) {{
|
|
1894
|
+
var hide = p.style.display !== 'none';
|
|
1895
|
+
p.style.display = hide ? 'none' : 'block';
|
|
1896
|
+
try {{ localStorage.setItem(STATE_KEY, hide ? '0' : '1'); }} catch (_) {{}}
|
|
1897
|
+
return;
|
|
1898
|
+
}}
|
|
1899
|
+
buildOverlay();
|
|
1900
|
+
try {{ localStorage.setItem(STATE_KEY, '1'); }} catch (_) {{}}
|
|
1901
|
+
}};
|
|
1902
|
+
function restoreIfOpen() {{
|
|
1903
|
+
try {{
|
|
1904
|
+
if (location.pathname.indexOf('/__dev') === 0) return;
|
|
1905
|
+
if (localStorage.getItem(STATE_KEY) === '1' && !document.getElementById('tina4-dev-panel')) {{
|
|
1906
|
+
buildOverlay();
|
|
1907
|
+
}}
|
|
1908
|
+
}} catch (_) {{}}
|
|
1909
|
+
}}
|
|
1910
|
+
if (document.readyState === 'loading') {{
|
|
1911
|
+
document.addEventListener('DOMContentLoaded', restoreIfOpen);
|
|
1912
|
+
}} else {{
|
|
1913
|
+
restoreIfOpen();
|
|
1914
|
+
}}
|
|
1915
|
+
}})();
|
|
1916
|
+
</script>
|
|
1917
|
+
<script>
|
|
1773
1918
|
{'(function(){})();' if no_reload else f"""(function(){{
|
|
1774
1919
|
var _t4_mtime=0,_t4_css_exts=['.css','.scss'],_t4_debounce=null;
|
|
1775
1920
|
var _t4_interval=parseInt('{poll_interval_ms}')||3000;
|
|
@@ -2582,5 +2727,90 @@ async def _api_scaffold_run(request, response):
|
|
|
2582
2727
|
return response({"ok": False, "error": str(exc)}, 500)
|
|
2583
2728
|
|
|
2584
2729
|
|
|
2730
|
+
_DOCS_SINGLETON = None # cached per-process so the framework index
|
|
2731
|
+
# builds once. User portion still mtime-refreshes
|
|
2732
|
+
# inside Docs.
|
|
2733
|
+
|
|
2734
|
+
def _docs_instance():
|
|
2735
|
+
"""Lazy singleton for the Live Docs module — bound to the project
|
|
2736
|
+
cwd at first call. Subsequent calls reuse the same Docs instance,
|
|
2737
|
+
which keeps the framework index hot across requests while still
|
|
2738
|
+
refreshing the user portion when src/ files change."""
|
|
2739
|
+
global _DOCS_SINGLETON
|
|
2740
|
+
if _DOCS_SINGLETON is None:
|
|
2741
|
+
import os
|
|
2742
|
+
from tina4_python.docs import Docs
|
|
2743
|
+
_DOCS_SINGLETON = Docs(project_root=os.getcwd())
|
|
2744
|
+
return _DOCS_SINGLETON
|
|
2745
|
+
|
|
2746
|
+
|
|
2747
|
+
async def _api_docs_search(request, response):
|
|
2748
|
+
"""GET /__dev/api/docs/search?q=...&k=...&source=...&include_private=..."""
|
|
2749
|
+
q = (request.query.get("q") or "").strip() if hasattr(request, "query") else ""
|
|
2750
|
+
if not q:
|
|
2751
|
+
return response({"ok": False, "error": "missing required 'q' param"}, 400)
|
|
2752
|
+
try:
|
|
2753
|
+
k = int(request.query.get("k", 5))
|
|
2754
|
+
except (TypeError, ValueError):
|
|
2755
|
+
k = 5
|
|
2756
|
+
source = request.query.get("source", "all")
|
|
2757
|
+
include_private = (request.query.get("include_private", "")
|
|
2758
|
+
or "").lower() in ("1", "true", "yes")
|
|
2759
|
+
import time
|
|
2760
|
+
t0 = time.perf_counter()
|
|
2761
|
+
hits = _docs_instance().search(q, k=k, source=source, include_private=include_private)
|
|
2762
|
+
took_ms = int((time.perf_counter() - t0) * 1000)
|
|
2763
|
+
return response({"ok": True, "query": q, "results": hits, "took_ms": took_ms})
|
|
2764
|
+
|
|
2765
|
+
|
|
2766
|
+
async def _api_docs_class(request, response):
|
|
2767
|
+
"""GET /__dev/api/docs/class?name=<fqn>"""
|
|
2768
|
+
name = (request.query.get("name") or "").strip()
|
|
2769
|
+
if not name:
|
|
2770
|
+
return response({"ok": False, "error": "missing required 'name' param"}, 400)
|
|
2771
|
+
spec = _docs_instance().class_spec(name)
|
|
2772
|
+
if spec is None:
|
|
2773
|
+
return response({"ok": False, "error": f"class not found: {name}"}, 404)
|
|
2774
|
+
return response({"ok": True, "class": spec})
|
|
2775
|
+
|
|
2776
|
+
|
|
2777
|
+
async def _api_docs_method(request, response):
|
|
2778
|
+
"""GET /__dev/api/docs/method?class=<fqn>&name=<method>"""
|
|
2779
|
+
cls = (request.query.get("class") or "").strip()
|
|
2780
|
+
name = (request.query.get("name") or "").strip()
|
|
2781
|
+
if not cls or not name:
|
|
2782
|
+
return response({"ok": False, "error": "both 'class' and 'name' params are required"}, 400)
|
|
2783
|
+
spec = _docs_instance().method_spec(cls, name)
|
|
2784
|
+
if spec is None:
|
|
2785
|
+
return response({"ok": False, "error": f"method not found: {cls}.{name}"}, 404)
|
|
2786
|
+
return response({"ok": True, "method": spec})
|
|
2787
|
+
|
|
2788
|
+
|
|
2789
|
+
async def _api_docs_index(request, response):
|
|
2790
|
+
"""GET /__dev/api/docs/index?source=<framework|user|all>"""
|
|
2791
|
+
source = request.query.get("source", "all")
|
|
2792
|
+
entities = _docs_instance().index()
|
|
2793
|
+
if source != "all":
|
|
2794
|
+
entities = [e for e in entities if e.get("source") == source]
|
|
2795
|
+
return response({"ok": True, "count": len(entities), "entities": entities})
|
|
2796
|
+
|
|
2797
|
+
|
|
2798
|
+
async def _api_docs_well_known(request, response):
|
|
2799
|
+
"""Public well-known doc — describes what the docs surface offers
|
|
2800
|
+
so non-MCP AI tools know what endpoints to call."""
|
|
2801
|
+
return response({
|
|
2802
|
+
"ok": True,
|
|
2803
|
+
"service": "tina4-live-docs",
|
|
2804
|
+
"version": "1",
|
|
2805
|
+
"endpoints": {
|
|
2806
|
+
"search": "/__dev/api/docs/search?q={query}&k={int}&source={framework|user|all}",
|
|
2807
|
+
"class": "/__dev/api/docs/class?name={fqn}",
|
|
2808
|
+
"method": "/__dev/api/docs/method?class={fqn}&name={method}",
|
|
2809
|
+
"index": "/__dev/api/docs/index?source={framework|user|all}",
|
|
2810
|
+
},
|
|
2811
|
+
"description": "Live API reflection for this Tina4 project — framework + user code combined.",
|
|
2812
|
+
})
|
|
2813
|
+
|
|
2814
|
+
|
|
2585
2815
|
__all__ = ["MessageLog", "RequestInspector", "BrokenTracker",
|
|
2586
2816
|
"get_api_handlers", "render_dev_toolbar"]
|