astris-python 0.1.0__tar.gz → 0.1.1__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.
- {astris_python-0.1.0 → astris_python-0.1.1}/PKG-INFO +12 -5
- {astris_python-0.1.0 → astris_python-0.1.1}/README.md +4 -1
- {astris_python-0.1.0 → astris_python-0.1.1}/pyproject.toml +10 -4
- {astris_python-0.1.0 → astris_python-0.1.1}/pyproject.toml.orig +10 -4
- astris_python-0.1.1/src/astris/.agents/skills/astris/SKILL.md +342 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/__init__.py +3 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/cli.py +77 -20
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/config.py +13 -1
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/installer.py +93 -8
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/kernel.py +1 -1
- {astris_python-0.1.0 → astris_python-0.1.1}/LICENSE +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/assets/favicon.ico +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/auth/__init__.py +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/auth/installer.py +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/auth/session.py +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/database/__init__.py +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/database/migrations.py +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/database/session.py +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/http/__init__.py +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/http/static.py +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/inertia/__init__.py +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/inertia/exceptions.py +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/inertia/response.py +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/inertia/shared.py +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/inertia/vite.py +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/py.typed +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/routing/__init__.py +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/routing/router.py +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/security/__init__.py +0 -0
- {astris_python-0.1.0 → astris_python-0.1.1}/src/astris/security/csrf.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astris-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: The modern full-stack web framework for Python. Everything you need to go from idea to orbit; for developers who want to build and ship at escape velocity.
|
|
5
5
|
Author: Felix Gomez
|
|
6
6
|
Author-email: Felix Gomez <felix@felixgomez.com>
|
|
@@ -36,16 +36,20 @@ Requires-Dist: alembic>=1.19.1
|
|
|
36
36
|
Requires-Dist: fastapi>=0.141.1
|
|
37
37
|
Requires-Dist: itsdangerous>=2.2.0
|
|
38
38
|
Requires-Dist: pwdlib[argon2]>=0.3.1
|
|
39
|
-
Requires-Dist: pydantic>=2.13.
|
|
39
|
+
Requires-Dist: pydantic>=2.13.5
|
|
40
40
|
Requires-Dist: pydantic-settings>=2.15.0
|
|
41
|
-
Requires-Dist: sqlmodel>=0.0.
|
|
42
|
-
Requires-Dist: typer>=0.27.
|
|
41
|
+
Requires-Dist: sqlmodel>=0.0.42
|
|
42
|
+
Requires-Dist: typer>=0.27.2
|
|
43
43
|
Requires-Dist: uvicorn[standard]>=0.52.4
|
|
44
44
|
Requires-Python: >=3.11
|
|
45
|
+
Project-URL: Homepage, https://astris.dev
|
|
46
|
+
Project-URL: Documentation, https://astris.dev
|
|
47
|
+
Project-URL: Repository, https://github.com/TheFelixGomez/astris
|
|
48
|
+
Project-URL: Issues, https://github.com/TheFelixGomez/astris/issues
|
|
45
49
|
Description-Content-Type: text/markdown
|
|
46
50
|
|
|
47
51
|
<p align="center" style="padding: 20px 0 10px 0;">
|
|
48
|
-
<a href="https://
|
|
52
|
+
<a href="https://astris.dev">
|
|
49
53
|
<img src="https://raw.githubusercontent.com/TheFelixGomez/astris/main/.github/assets/astris-logo-name.png" alt="Astris" width="380">
|
|
50
54
|
</a>
|
|
51
55
|
</p>
|
|
@@ -56,6 +60,7 @@ Description-Content-Type: text/markdown
|
|
|
56
60
|
</p>
|
|
57
61
|
|
|
58
62
|
<p align="center">
|
|
63
|
+
<a href="https://astris.dev"><img src="https://img.shields.io/badge/docs-astris.dev-0ea5e9.svg" alt="Documentation"></a>
|
|
59
64
|
<a href="https://pypi.org/project/astris-python/"><img src="https://img.shields.io/pypi/v/astris-python.svg" alt="PyPI version"></a>
|
|
60
65
|
<a href="https://pypi.org/project/astris-python/"><img src="https://img.shields.io/pypi/pyversions/astris-python.svg" alt="Python Versions"></a>
|
|
61
66
|
<a href="https://github.com/TheFelixGomez/astris/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
|
|
@@ -71,6 +76,8 @@ Description-Content-Type: text/markdown
|
|
|
71
76
|
|
|
72
77
|
By combining **FastAPI**, **Inertia.js**, **Vue 3**, **Tailwind CSS v4**, and **SQLModel**, Astris lets you build rich, dynamic single-page applications without the overhead of maintaining separate API layers or complex state synchronization.
|
|
73
78
|
|
|
79
|
+
📖 **Explore the full documentation, guides, and tutorials at [astris.dev](https://astris.dev).**
|
|
80
|
+
|
|
74
81
|
---
|
|
75
82
|
|
|
76
83
|
## 🌟 Key Features
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center" style="padding: 20px 0 10px 0;">
|
|
2
|
-
<a href="https://
|
|
2
|
+
<a href="https://astris.dev">
|
|
3
3
|
<img src="https://raw.githubusercontent.com/TheFelixGomez/astris/main/.github/assets/astris-logo-name.png" alt="Astris" width="380">
|
|
4
4
|
</a>
|
|
5
5
|
</p>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
|
+
<a href="https://astris.dev"><img src="https://img.shields.io/badge/docs-astris.dev-0ea5e9.svg" alt="Documentation"></a>
|
|
13
14
|
<a href="https://pypi.org/project/astris-python/"><img src="https://img.shields.io/pypi/v/astris-python.svg" alt="PyPI version"></a>
|
|
14
15
|
<a href="https://pypi.org/project/astris-python/"><img src="https://img.shields.io/pypi/pyversions/astris-python.svg" alt="Python Versions"></a>
|
|
15
16
|
<a href="https://github.com/TheFelixGomez/astris/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
|
|
@@ -25,6 +26,8 @@
|
|
|
25
26
|
|
|
26
27
|
By combining **FastAPI**, **Inertia.js**, **Vue 3**, **Tailwind CSS v4**, and **SQLModel**, Astris lets you build rich, dynamic single-page applications without the overhead of maintaining separate API layers or complex state synchronization.
|
|
27
28
|
|
|
29
|
+
📖 **Explore the full documentation, guides, and tutorials at [astris.dev](https://astris.dev).**
|
|
30
|
+
|
|
28
31
|
---
|
|
29
32
|
|
|
30
33
|
## 🌟 Key Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "astris-python"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.1"
|
|
4
4
|
description = "The modern full-stack web framework for Python. Everything you need to go from idea to orbit; for developers who want to build and ship at escape velocity."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -39,10 +39,10 @@ dependencies = [
|
|
|
39
39
|
"fastapi>=0.141.1",
|
|
40
40
|
"itsdangerous>=2.2.0",
|
|
41
41
|
"pwdlib[argon2]>=0.3.1",
|
|
42
|
-
"pydantic>=2.13.
|
|
42
|
+
"pydantic>=2.13.5",
|
|
43
43
|
"pydantic-settings>=2.15.0",
|
|
44
|
-
"sqlmodel>=0.0.
|
|
45
|
-
"typer>=0.27.
|
|
44
|
+
"sqlmodel>=0.0.42",
|
|
45
|
+
"typer>=0.27.2",
|
|
46
46
|
"uvicorn[standard]>=0.52.4",
|
|
47
47
|
]
|
|
48
48
|
|
|
@@ -50,6 +50,12 @@ dependencies = [
|
|
|
50
50
|
name = "Felix Gomez"
|
|
51
51
|
email = "felix@felixgomez.com"
|
|
52
52
|
|
|
53
|
+
[project.urls]
|
|
54
|
+
Homepage = "https://astris.dev"
|
|
55
|
+
Documentation = "https://astris.dev"
|
|
56
|
+
Repository = "https://github.com/TheFelixGomez/astris"
|
|
57
|
+
Issues = "https://github.com/TheFelixGomez/astris/issues"
|
|
58
|
+
|
|
53
59
|
[project.scripts]
|
|
54
60
|
astris = "astris.installer:installer_cli"
|
|
55
61
|
astris-python = "astris.installer:installer_cli"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "astris-python"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.1"
|
|
4
4
|
description = "The modern full-stack web framework for Python. Everything you need to go from idea to orbit; for developers who want to build and ship at escape velocity."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -42,13 +42,19 @@ dependencies = [
|
|
|
42
42
|
"fastapi>=0.141.1",
|
|
43
43
|
"itsdangerous>=2.2.0",
|
|
44
44
|
"pwdlib[argon2]>=0.3.1",
|
|
45
|
-
"pydantic>=2.13.
|
|
45
|
+
"pydantic>=2.13.5",
|
|
46
46
|
"pydantic-settings>=2.15.0",
|
|
47
|
-
"sqlmodel>=0.0.
|
|
48
|
-
"typer>=0.27.
|
|
47
|
+
"sqlmodel>=0.0.42",
|
|
48
|
+
"typer>=0.27.2",
|
|
49
49
|
"uvicorn[standard]>=0.52.4",
|
|
50
50
|
]
|
|
51
51
|
|
|
52
|
+
[project.urls]
|
|
53
|
+
Homepage = "https://astris.dev"
|
|
54
|
+
Documentation = "https://astris.dev"
|
|
55
|
+
Repository = "https://github.com/TheFelixGomez/astris"
|
|
56
|
+
Issues = "https://github.com/TheFelixGomez/astris/issues"
|
|
57
|
+
|
|
52
58
|
[project.scripts]
|
|
53
59
|
astris = "astris.installer:installer_cli"
|
|
54
60
|
astris-python = "astris.installer:installer_cli"
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: astris
|
|
3
|
+
description: Astris best practices and conventions. Use when working with Astris full-stack Python applications, controllers, Inertia.js Vue 3 views, SQLModel databases, Orbit CLI, and security. Keeps Astris code clean and up to date with the latest features and patterns.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Astris
|
|
7
|
+
|
|
8
|
+
Official Astris skill to write code with best practices, keeping up to date with new versions and features.
|
|
9
|
+
|
|
10
|
+
## Quick Reference
|
|
11
|
+
|
|
12
|
+
* Application entrypoint: `app = Astris()` in `main.py`; see [Application Kernel](#application-kernel).
|
|
13
|
+
* Configuration: manage via `.env` and `app/core/config.py`; see [Configuration](#configuration-appcoreconfigpy).
|
|
14
|
+
* Controllers: define in `app/modules/<name>/<name>_controller.py` with `Controller`; see [Controllers & Routing](#controllers--routing).
|
|
15
|
+
* Views: return `InertiaResponse(request, "Component/Path", props={...})`; see [Inertia.js Monolith](#inertiajs-monolith-vue-3--tailwind-css-v4).
|
|
16
|
+
* Mutations: return `RedirectResponse(url="/...", status_code=status.HTTP_303_SEE_OTHER)` for POST/PUT/PATCH/DELETE; see [Form Handling & Mutations](#form-handling--mutations).
|
|
17
|
+
* Database sessions: inject `DatabaseSession` into services via `Depends()`; see [Database & Services](#database--services).
|
|
18
|
+
* Models: define in `app/modules/<name>/<name>_model.py` with `SQLModel`; see [SQLModel Models](#sqlmodel-models).
|
|
19
|
+
* Flash messages: use `flash(request, "type", "message")`; see [Flash Messages & Shared Data](#flash-messages--shared-data).
|
|
20
|
+
* Authentication: use `auth_required` / `guest_required` route dependencies and `AuthUser`; see [Authentication & Security](#authentication--security).
|
|
21
|
+
* Orbit CLI: use `uv run orbit <command>`; see [Orbit CLI](#orbit-cli).
|
|
22
|
+
|
|
23
|
+
## Application Kernel
|
|
24
|
+
|
|
25
|
+
Instantiate `Astris()` in `main.py`:
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from astris import Astris
|
|
29
|
+
|
|
30
|
+
app = Astris()
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The kernel automatically:
|
|
34
|
+
1. Loads settings from `app/core/config.py` and `.env`.
|
|
35
|
+
2. Registers security middlewares (CORS, PublicStatic, Session, Flash, CSRF).
|
|
36
|
+
3. Auto-discovers and attaches all controllers in `app/modules/`.
|
|
37
|
+
4. Binds the Inertia.js view engine to `resources/views/root.html`.
|
|
38
|
+
|
|
39
|
+
## Configuration (`app/core/config.py`)
|
|
40
|
+
|
|
41
|
+
Astris generates `app/core/config.py` extending the base `Settings` class with automatic `.env` type-casting:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from astris.config import Settings as BaseAppSettings
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Settings(BaseAppSettings):
|
|
48
|
+
"""Extend application settings with custom environment variables."""
|
|
49
|
+
|
|
50
|
+
stripe_api_key: str | None = None
|
|
51
|
+
redis_url: str = "redis://localhost:6379"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
settings = Settings()
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Access strongly typed settings anywhere in your app:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from app.core.config import settings
|
|
61
|
+
|
|
62
|
+
api_key = settings.stripe_api_key
|
|
63
|
+
db_url = settings.database_url
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Controllers & Routing
|
|
67
|
+
|
|
68
|
+
Place controllers inside `app/modules/<module>/<module>_controller.py`. Astris automatically discovers any `Controller` in files matching `*_controller.py`. `Controller` is a direct subclass of FastAPI's `APIRouter`.
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from astris.routing import Controller, Depends, Path, Query
|
|
72
|
+
from astris.http import Request
|
|
73
|
+
from astris.inertia import InertiaResponse
|
|
74
|
+
from app.modules.billing.billing_service import BillingService
|
|
75
|
+
|
|
76
|
+
controller = Controller(prefix="/billing", tags=["Billing"])
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@controller.get("/")
|
|
80
|
+
async def index(
|
|
81
|
+
request: Request,
|
|
82
|
+
billing: BillingService = Depends(),
|
|
83
|
+
) -> InertiaResponse:
|
|
84
|
+
invoices = billing.get_recent_invoices()
|
|
85
|
+
return InertiaResponse(
|
|
86
|
+
request,
|
|
87
|
+
"Billing/Index",
|
|
88
|
+
props={"invoices": [inv.model_dump() for inv in invoices]},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@controller.get("/{invoice_id}")
|
|
93
|
+
async def show(
|
|
94
|
+
request: Request,
|
|
95
|
+
invoice_id: int,
|
|
96
|
+
billing: BillingService = Depends(),
|
|
97
|
+
) -> InertiaResponse:
|
|
98
|
+
invoice = billing.get_invoice(invoice_id)
|
|
99
|
+
return InertiaResponse(
|
|
100
|
+
request,
|
|
101
|
+
"Billing/Show",
|
|
102
|
+
props={"invoice": invoice.model_dump()},
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Form Handling & Mutations
|
|
107
|
+
|
|
108
|
+
Inertia requires that mutating requests (`POST`, `PUT`, `PATCH`, `DELETE`) redirect with a `303 See Other` status code.
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from astris.routing import Controller, Depends
|
|
112
|
+
from astris.http import Request, RedirectResponse, status
|
|
113
|
+
from astris.inertia import flash
|
|
114
|
+
from app.modules.billing.billing_service import BillingService
|
|
115
|
+
from app.modules.billing.billing_model import InvoiceCreate
|
|
116
|
+
|
|
117
|
+
controller = Controller(prefix="/billing", tags=["Billing"])
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@controller.post("/")
|
|
121
|
+
async def store(
|
|
122
|
+
request: Request,
|
|
123
|
+
dto: InvoiceCreate,
|
|
124
|
+
billing: BillingService = Depends(),
|
|
125
|
+
) -> RedirectResponse:
|
|
126
|
+
invoice = billing.create_invoice(dto)
|
|
127
|
+
flash(request, "success", f"Invoice #{invoice.id} created successfully!")
|
|
128
|
+
return RedirectResponse(url="/billing", status_code=status.HTTP_303_SEE_OTHER)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@controller.delete("/{invoice_id}")
|
|
132
|
+
async def destroy(
|
|
133
|
+
request: Request,
|
|
134
|
+
invoice_id: int,
|
|
135
|
+
billing: BillingService = Depends(),
|
|
136
|
+
) -> RedirectResponse:
|
|
137
|
+
billing.delete_invoice(invoice_id)
|
|
138
|
+
flash(request, "success", "Invoice deleted successfully.")
|
|
139
|
+
return RedirectResponse(url="/billing", status_code=status.HTTP_303_SEE_OTHER)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Flash Messages & Shared Data
|
|
143
|
+
|
|
144
|
+
* Flash messages set with `flash(request, "success", "...")` are automatically shared with the frontend in `$page.props.flash`.
|
|
145
|
+
* Global props can be shared per-request using `share(request, "key", value)`.
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from astris.inertia import flash, share
|
|
149
|
+
|
|
150
|
+
flash(request, "error", "Invalid payment credentials.")
|
|
151
|
+
share(request, "team_name", "Acme Corp")
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Database & Services
|
|
155
|
+
|
|
156
|
+
### 1. Service Layer with Injected Session
|
|
157
|
+
Accept `session: DatabaseSession` in the constructor:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from astris.database import DatabaseSession, select
|
|
161
|
+
from app.modules.billing.billing_model import Invoice, InvoiceCreate
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class BillingService:
|
|
165
|
+
def __init__(self, session: DatabaseSession):
|
|
166
|
+
self.session = session
|
|
167
|
+
|
|
168
|
+
def get_recent_invoices(self) -> list[Invoice]:
|
|
169
|
+
return self.session.exec(select(Invoice).order_by(Invoice.id.desc())).all()
|
|
170
|
+
|
|
171
|
+
def get_invoice(self, invoice_id: int) -> Invoice:
|
|
172
|
+
return self.session.get(Invoice, invoice_id)
|
|
173
|
+
|
|
174
|
+
def create_invoice(self, dto: InvoiceCreate) -> Invoice:
|
|
175
|
+
invoice = Invoice.model_validate(dto)
|
|
176
|
+
self.session.add(invoice)
|
|
177
|
+
self.session.commit()
|
|
178
|
+
self.session.refresh(invoice)
|
|
179
|
+
return invoice
|
|
180
|
+
|
|
181
|
+
def delete_invoice(self, invoice_id: int) -> None:
|
|
182
|
+
invoice = self.get_invoice(invoice_id)
|
|
183
|
+
if invoice:
|
|
184
|
+
self.session.delete(invoice)
|
|
185
|
+
self.session.commit()
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### 2. Standalone Scripts & Background Tasks
|
|
189
|
+
Use `with db.session() as session:` only outside HTTP requests (in seeders, CLI scripts, or background workers):
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from astris.database import db, select
|
|
193
|
+
from app.modules.billing.billing_model import Invoice
|
|
194
|
+
|
|
195
|
+
with db.session() as session:
|
|
196
|
+
invoices = session.exec(select(Invoice)).all()
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## SQLModel Models
|
|
200
|
+
|
|
201
|
+
Define models in `app/modules/<module>/<module>_model.py`. Astris auto-imports models to register table metadata with Alembic:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
from astris.database import Field, SQLModel
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class InvoiceBase(SQLModel):
|
|
208
|
+
customer_name: str = Field(index=True)
|
|
209
|
+
amount: float
|
|
210
|
+
status: str = Field(default="pending")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class Invoice(InvoiceBase, table=True):
|
|
214
|
+
id: int | None = Field(default=None, primary_key=True)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class InvoiceCreate(InvoiceBase):
|
|
218
|
+
pass
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class InvoicePublic(InvoiceBase):
|
|
222
|
+
id: int
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Inertia.js Monolith (Vue 3 + Tailwind CSS v4)
|
|
226
|
+
|
|
227
|
+
Place page components in `resources/js/Pages/<Module>/<Page>.vue`. Always use `<script setup lang="ts">`:
|
|
228
|
+
|
|
229
|
+
```vue
|
|
230
|
+
<script setup lang="ts">
|
|
231
|
+
import { useForm, Head, Link, usePage } from '@inertiajs/vue3'
|
|
232
|
+
|
|
233
|
+
interface Invoice {
|
|
234
|
+
id: number
|
|
235
|
+
customer_name: string
|
|
236
|
+
amount: number
|
|
237
|
+
status: string
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
defineProps<{
|
|
241
|
+
invoices: Invoice[]
|
|
242
|
+
}>()
|
|
243
|
+
|
|
244
|
+
const page = usePage()
|
|
245
|
+
|
|
246
|
+
const form = useForm({
|
|
247
|
+
customer_name: '',
|
|
248
|
+
amount: 0,
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
const submit = () => {
|
|
252
|
+
form.post('/billing', {
|
|
253
|
+
onSuccess: () => form.reset(),
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
</script>
|
|
257
|
+
|
|
258
|
+
<template>
|
|
259
|
+
<Head title="Billing" />
|
|
260
|
+
<div class="max-w-4xl mx-auto p-6 space-y-6">
|
|
261
|
+
<!-- Flash Banner -->
|
|
262
|
+
<div
|
|
263
|
+
v-if="page.props.flash?.success"
|
|
264
|
+
class="p-4 rounded-xl bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-sm"
|
|
265
|
+
>
|
|
266
|
+
{{ page.props.flash.success }}
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
<h1 class="text-2xl font-bold text-slate-900 dark:text-white">Invoices</h1>
|
|
270
|
+
|
|
271
|
+
<!-- Form with Automatic Server Validation Error Bindings -->
|
|
272
|
+
<form @submit.prevent="submit" class="space-y-4">
|
|
273
|
+
<div>
|
|
274
|
+
<label class="block text-sm font-medium">Customer Name</label>
|
|
275
|
+
<input
|
|
276
|
+
v-model="form.customer_name"
|
|
277
|
+
type="text"
|
|
278
|
+
class="w-full px-3 py-2 border rounded-lg"
|
|
279
|
+
:class="{ 'border-rose-500': form.errors.customer_name }"
|
|
280
|
+
/>
|
|
281
|
+
<p v-if="form.errors.customer_name" class="text-sm text-rose-500 mt-1">
|
|
282
|
+
{{ form.errors.customer_name }}
|
|
283
|
+
</p>
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
<button
|
|
287
|
+
type="submit"
|
|
288
|
+
:disabled="form.processing"
|
|
289
|
+
class="px-4 py-2 bg-sky-500 text-white rounded-lg disabled:opacity-50"
|
|
290
|
+
>
|
|
291
|
+
Create Invoice
|
|
292
|
+
</button>
|
|
293
|
+
</form>
|
|
294
|
+
</div>
|
|
295
|
+
</template>
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Authentication & Security
|
|
299
|
+
|
|
300
|
+
1. **Guards on Controllers**:
|
|
301
|
+
```python
|
|
302
|
+
from astris.routing import Controller
|
|
303
|
+
from astris.auth import auth_required, guest_required, AuthUser
|
|
304
|
+
from astris.http import Request
|
|
305
|
+
from astris.inertia import InertiaResponse
|
|
306
|
+
|
|
307
|
+
controller = Controller(prefix="/dashboard", dependencies=[auth_required])
|
|
308
|
+
|
|
309
|
+
@controller.get("/")
|
|
310
|
+
async def dashboard(request: Request, user: AuthUser) -> InertiaResponse:
|
|
311
|
+
return InertiaResponse(request, "Dashboard", props={"user": user})
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
2. **Session Authentication Helpers**:
|
|
315
|
+
* `login_user(request, user_id)`: Log a user in by storing signed session cookie.
|
|
316
|
+
* `logout_user(request)`: Terminate current session.
|
|
317
|
+
* `hash_password(password)` / `verify_password(plain, hashed)`: Secure Argon2id password management.
|
|
318
|
+
|
|
319
|
+
3. **CSRF Protection**:
|
|
320
|
+
Astris sets the `XSRF-TOKEN` cookie automatically. Inertia and Axios send the `X-XSRF-TOKEN` header on all mutating requests with zero frontend configuration.
|
|
321
|
+
|
|
322
|
+
## Orbit CLI
|
|
323
|
+
|
|
324
|
+
Always run Orbit commands via `uv run orbit`:
|
|
325
|
+
|
|
326
|
+
* `uv run orbit serve`: Start local development server (Uvicorn backend + Vite frontend HMR concurrently).
|
|
327
|
+
* `uv run orbit serve --prod`: Start production server on `0.0.0.0` with multi-worker processes.
|
|
328
|
+
* `uv run orbit make:module <name>`: Scaffold domain module (`controller`, `service`, `model`).
|
|
329
|
+
* `uv run orbit make:controller <name> [--module <mod>]`: Generate a controller.
|
|
330
|
+
* `uv run orbit make:model <name> [--module <mod>]`: Generate a SQLModel database table.
|
|
331
|
+
* `uv run orbit make:migration "<message>"`: Autogenerate Alembic schema migration from models.
|
|
332
|
+
* `uv run orbit migrate`: Execute pending migrations.
|
|
333
|
+
* `uv run orbit migrate:rollback`: Roll back database migrations.
|
|
334
|
+
* `uv run orbit migrate:status`: Check current migration revisions.
|
|
335
|
+
* `uv run orbit key:generate`: Generate a 32-byte secret key and update `APP_KEY` in `.env`.
|
|
336
|
+
|
|
337
|
+
## Key Rules
|
|
338
|
+
|
|
339
|
+
* Always configure via `.env` and `app/core/config.py`.
|
|
340
|
+
* Never hardcode secrets in code.
|
|
341
|
+
* Always use `DatabaseSession` with `Depends()` in route handlers and services.
|
|
342
|
+
* Always redirect with `status.HTTP_303_SEE_OTHER` after POST/PUT/PATCH/DELETE mutations.
|
|
@@ -31,20 +31,36 @@ def list_commands(ctx: typer.Context):
|
|
|
31
31
|
|
|
32
32
|
@orbit_cli.command()
|
|
33
33
|
def serve(
|
|
34
|
-
host: str = typer.Option(
|
|
35
|
-
|
|
34
|
+
host: str | None = typer.Option(
|
|
35
|
+
None, "--host", "-h", help="Bind socket to this host (defaults to 127.0.0.1 in dev, 0.0.0.0 in prod)"
|
|
36
36
|
),
|
|
37
37
|
port: int = typer.Option(8000, "--port", "-p", help="Bind socket to this port"),
|
|
38
|
-
reload: bool = typer.Option(
|
|
39
|
-
|
|
38
|
+
reload: bool | None = typer.Option(
|
|
39
|
+
None, "--reload/--no-reload", help="Enable/disable auto-reload"
|
|
40
40
|
),
|
|
41
|
-
vite: bool = typer.Option(
|
|
42
|
-
|
|
41
|
+
vite: bool | None = typer.Option(
|
|
42
|
+
None,
|
|
43
43
|
"--vite/--no-vite",
|
|
44
44
|
help="Start concurrent Vite dev server if package.json exists",
|
|
45
45
|
),
|
|
46
|
+
prod: bool = typer.Option(
|
|
47
|
+
False,
|
|
48
|
+
"--prod",
|
|
49
|
+
help="Run in production mode (disables reload and Vite, sets host to 0.0.0.0, enables multi-worker)",
|
|
50
|
+
),
|
|
51
|
+
workers: int | None = typer.Option(
|
|
52
|
+
None,
|
|
53
|
+
"--workers",
|
|
54
|
+
"-w",
|
|
55
|
+
help="Number of worker processes for production (defaults to 4 in prod)",
|
|
56
|
+
),
|
|
46
57
|
):
|
|
47
|
-
"""Start the
|
|
58
|
+
"""Start the application server (development with Vite HMR or production with --prod)."""
|
|
59
|
+
resolved_host = host or ("0.0.0.0" if prod else "127.0.0.1")
|
|
60
|
+
resolved_reload = False if prod else (True if reload is None else reload)
|
|
61
|
+
resolved_vite = False if prod else (True if vite is None else vite)
|
|
62
|
+
resolved_workers = workers if workers is not None else (4 if prod else 1)
|
|
63
|
+
|
|
48
64
|
cwd = Path.cwd()
|
|
49
65
|
cwd_str = str(cwd)
|
|
50
66
|
if cwd_str not in sys.path:
|
|
@@ -54,7 +70,7 @@ def serve(
|
|
|
54
70
|
package_json = cwd / "package.json"
|
|
55
71
|
vite_proc = None
|
|
56
72
|
|
|
57
|
-
if package_json.exists() and
|
|
73
|
+
if package_json.exists() and resolved_vite:
|
|
58
74
|
typer.secho(
|
|
59
75
|
"⚡ Full-stack project detected. Starting Vite dev server...",
|
|
60
76
|
fg=typer.colors.MAGENTA,
|
|
@@ -73,26 +89,30 @@ def serve(
|
|
|
73
89
|
fg=typer.colors.YELLOW,
|
|
74
90
|
)
|
|
75
91
|
|
|
92
|
+
mode_label = "in production mode" if prod else ""
|
|
76
93
|
typer.secho(
|
|
77
|
-
f"🚀 Astris entering orbit on http://{
|
|
94
|
+
f"🚀 Astris entering orbit {mode_label} on http://{resolved_host}:{port}",
|
|
95
|
+
fg=typer.colors.CYAN,
|
|
78
96
|
)
|
|
79
97
|
|
|
80
98
|
try:
|
|
81
99
|
reload_dirs = []
|
|
82
|
-
if
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
100
|
+
if resolved_reload:
|
|
101
|
+
if (cwd / "app").exists():
|
|
102
|
+
reload_dirs.append(str(cwd / "app"))
|
|
103
|
+
if (cwd / "database").exists():
|
|
104
|
+
reload_dirs.append(str(cwd / "database"))
|
|
105
|
+
if not reload_dirs:
|
|
106
|
+
reload_dirs = [cwd_str]
|
|
88
107
|
|
|
89
108
|
uvicorn.run(
|
|
90
109
|
"main:app",
|
|
91
|
-
host=
|
|
110
|
+
host=resolved_host,
|
|
92
111
|
port=port,
|
|
93
|
-
reload=
|
|
94
|
-
|
|
95
|
-
|
|
112
|
+
reload=resolved_reload,
|
|
113
|
+
workers=resolved_workers if not resolved_reload else None,
|
|
114
|
+
reload_dirs=reload_dirs if resolved_reload else None,
|
|
115
|
+
reload_includes=["*.py", ".env*"] if resolved_reload else None,
|
|
96
116
|
reload_excludes=[
|
|
97
117
|
"node_modules",
|
|
98
118
|
"resources",
|
|
@@ -102,7 +122,7 @@ def serve(
|
|
|
102
122
|
"dist",
|
|
103
123
|
"build",
|
|
104
124
|
]
|
|
105
|
-
if
|
|
125
|
+
if resolved_reload
|
|
106
126
|
else None,
|
|
107
127
|
app_dir=cwd_str,
|
|
108
128
|
)
|
|
@@ -419,3 +439,40 @@ def make_auth():
|
|
|
419
439
|
except Exception as e:
|
|
420
440
|
typer.secho(f"Error generating auth scaffolding: {e}", fg=typer.colors.RED)
|
|
421
441
|
raise typer.Exit(1) from e
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
@orbit_cli.command("skills:install")
|
|
445
|
+
def skills_install(
|
|
446
|
+
claude: bool = typer.Option(
|
|
447
|
+
False, "--claude", help="Install skills for Claude Code in .claude/skills as well"
|
|
448
|
+
),
|
|
449
|
+
):
|
|
450
|
+
"""Install or regenerate AI agent skills for the project."""
|
|
451
|
+
astris_skill = Path(__file__).parent / ".agents" / "skills" / "astris" / "SKILL.md"
|
|
452
|
+
if not astris_skill.exists():
|
|
453
|
+
typer.secho("Error: Astris skill file not found in package.", fg=typer.colors.RED)
|
|
454
|
+
raise typer.Exit(1)
|
|
455
|
+
|
|
456
|
+
skill_text = astris_skill.read_text(encoding="utf-8")
|
|
457
|
+
targets = [Path.cwd() / ".agents" / "skills" / "astris"]
|
|
458
|
+
if claude:
|
|
459
|
+
targets.append(Path.cwd() / ".claude" / "skills" / "astris")
|
|
460
|
+
|
|
461
|
+
for target_dir in targets:
|
|
462
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
463
|
+
(target_dir / "SKILL.md").write_text(skill_text, encoding="utf-8")
|
|
464
|
+
|
|
465
|
+
typer.secho("✓ Astris AI skill installed successfully!", fg=typer.colors.GREEN)
|
|
466
|
+
typer.echo(" - Universal: .agents/skills/astris/SKILL.md")
|
|
467
|
+
if claude:
|
|
468
|
+
typer.echo(" - Claude: .claude/skills/astris/SKILL.md")
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
@orbit_cli.command("skills:update")
|
|
472
|
+
def skills_update(
|
|
473
|
+
claude: bool = typer.Option(
|
|
474
|
+
False, "--claude", help="Update skills for Claude Code in .claude/skills as well"
|
|
475
|
+
),
|
|
476
|
+
):
|
|
477
|
+
"""Update and re-sync all AI agent skills to match installed package versions."""
|
|
478
|
+
skills_install(claude=claude)
|
|
@@ -28,9 +28,21 @@ class Settings(BaseSettings):
|
|
|
28
28
|
# Sessions & Security
|
|
29
29
|
session_cookie_name: str = "astris_session"
|
|
30
30
|
session_max_age: int | None = 14 * 24 * 60 * 60 # 14 days
|
|
31
|
-
session_https_only: bool =
|
|
31
|
+
session_https_only: bool | None = None
|
|
32
32
|
session_same_site: Literal["lax", "strict", "none"] = "lax"
|
|
33
33
|
|
|
34
|
+
@property
|
|
35
|
+
def is_production(self) -> bool:
|
|
36
|
+
"""Return True if running in production mode."""
|
|
37
|
+
return self.app_env.lower() in ("production", "prod")
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def resolved_session_https_only(self) -> bool:
|
|
41
|
+
"""Auto-enable HTTPS-only cookies in production unless explicitly overridden."""
|
|
42
|
+
if self.session_https_only is not None:
|
|
43
|
+
return self.session_https_only
|
|
44
|
+
return self.is_production
|
|
45
|
+
|
|
34
46
|
# CORS & CSRF
|
|
35
47
|
cors_origins: list[str] = [
|
|
36
48
|
"http://localhost:5173",
|
|
@@ -36,6 +36,11 @@ def new(
|
|
|
36
36
|
"--auth/--no-auth",
|
|
37
37
|
help="Scaffold full-stack authentication starter kit (default: enabled)",
|
|
38
38
|
),
|
|
39
|
+
claude: bool = typer.Option(
|
|
40
|
+
False,
|
|
41
|
+
"--claude",
|
|
42
|
+
help="Scaffold Claude Code agent skills in .claude/skills as well",
|
|
43
|
+
),
|
|
39
44
|
):
|
|
40
45
|
"""Scaffold a brand-new Astris full-stack project."""
|
|
41
46
|
project_dir = Path.cwd() / name
|
|
@@ -99,13 +104,31 @@ def new(
|
|
|
99
104
|
ensure_migration_setup(project_dir)
|
|
100
105
|
|
|
101
106
|
# 1b. Centralized settings: app/core/config.py
|
|
102
|
-
config_stub = """
|
|
107
|
+
config_stub = """\"\"\"
|
|
108
|
+
|--------------------------------------------------------------------------
|
|
109
|
+
| Application Configuration
|
|
110
|
+
|--------------------------------------------------------------------------
|
|
111
|
+
|
|
|
112
|
+
| Centralized, type-safe application settings powered by Pydantic Settings.
|
|
113
|
+
| Environment variables defined in your .env file are automatically
|
|
114
|
+
| validated and type-cast when the application boots.
|
|
115
|
+
|
|
|
116
|
+
| Access settings anywhere in your app:
|
|
117
|
+
| from app.core.config import settings
|
|
118
|
+
| api_key = settings.stripe_api_key
|
|
119
|
+
|
|
|
120
|
+
\"\"\"
|
|
121
|
+
|
|
122
|
+
from astris.config import Settings as BaseAppSettings
|
|
103
123
|
|
|
104
124
|
|
|
105
125
|
class Settings(BaseAppSettings):
|
|
106
126
|
\"\"\"Extend application settings with custom environment variables.\"\"\"
|
|
107
127
|
|
|
108
|
-
#
|
|
128
|
+
# Define custom environment variables below:
|
|
129
|
+
# stripe_api_key: str | None = None
|
|
130
|
+
# redis_url: str = "redis://localhost:6379"
|
|
131
|
+
# max_upload_size_mb: int = 25
|
|
109
132
|
pass
|
|
110
133
|
|
|
111
134
|
|
|
@@ -145,7 +168,7 @@ dependencies = []
|
|
|
145
168
|
'''
|
|
146
169
|
(project_dir / "pyproject.toml").write_text(pyproject_content, encoding="utf-8")
|
|
147
170
|
project_readme = f"""<p align="center" style="padding: 20px 0 10px 0;">
|
|
148
|
-
<a href="https://
|
|
171
|
+
<a href="https://astris.dev">
|
|
149
172
|
<img src="https://raw.githubusercontent.com/TheFelixGomez/astris/main/.github/assets/astris-logo-name.png" alt="Astris" width="380">
|
|
150
173
|
</a>
|
|
151
174
|
</p>
|
|
@@ -184,12 +207,26 @@ Open **`http://localhost:8000`** in your browser.
|
|
|
184
207
|
| Command | Description |
|
|
185
208
|
| :--- | :--- |
|
|
186
209
|
| `uv run orbit serve` | Start full-stack development server with hot-reloading |
|
|
187
|
-
| `uv run orbit make:module <name>` | Scaffold a complete domain module (Controller,
|
|
210
|
+
| `uv run orbit make:module <name>` | Scaffold a complete domain module (Controller, Service, Model) |
|
|
188
211
|
| `uv run orbit make:controller <name>` | Generate an Astris controller |
|
|
189
212
|
| `uv run orbit make:model <name>` | Generate a database model |
|
|
190
213
|
| `uv run orbit migrate` | Run all pending database migrations |
|
|
191
214
|
| `uv run orbit make:migration "<message>"` | Auto-generate a new database schema migration |
|
|
192
215
|
| `uv run orbit key:generate` | Generate a new 32-byte secret `APP_KEY` in `.env` |
|
|
216
|
+
| `uv run orbit skills:install` | Install or regenerate AI agent skills for the project |
|
|
217
|
+
| `uv run orbit skills:update` | Re-sync AI agent skills to match installed package versions |
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## 🤖 Building with AI
|
|
222
|
+
|
|
223
|
+
This project comes pre-configured with official Astris skills in `.agents/skills/astris/` (Universal assistants) and `.claude/skills/astris/` (Claude Code).
|
|
224
|
+
To refresh or reinstall skills after updating dependencies:
|
|
225
|
+
```bash
|
|
226
|
+
uv run orbit skills:install
|
|
227
|
+
# Or for Claude Code:
|
|
228
|
+
uv run orbit skills:install --claude
|
|
229
|
+
```
|
|
193
230
|
|
|
194
231
|
---
|
|
195
232
|
|
|
@@ -200,6 +237,7 @@ Open **`http://localhost:8000`** in your browser.
|
|
|
200
237
|
* **`resources/js/Pages/`**: Frontend single-page application views.
|
|
201
238
|
* **`database/migrations/`**: Database schema migrations managed by Orbit.
|
|
202
239
|
* **`public/`**: Web root directory for static assets (favicon, images, robots.txt).
|
|
240
|
+
* **`.agents/skills/` & `.claude/skills/`**: Official AI agent skills for Antigravity, Claude Code, Codex, Cursor, and universal assistants.
|
|
203
241
|
|
|
204
242
|
---
|
|
205
243
|
|
|
@@ -207,6 +245,7 @@ Open **`http://localhost:8000`** in your browser.
|
|
|
207
245
|
|
|
208
246
|
**Astris** is a modern full-stack web framework for Python with expressive, type-safe elegance. Designed to help developers build and ship modern web applications with speed and simplicity.
|
|
209
247
|
|
|
248
|
+
* **Documentation**: [https://astris.dev](https://astris.dev)
|
|
210
249
|
* **Repository**: [https://github.com/TheFelixGomez/astris](https://github.com/TheFelixGomez/astris)
|
|
211
250
|
* **Author**: Felix Gomez ([@TheFelixGomez](https://github.com/TheFelixGomez))
|
|
212
251
|
"""
|
|
@@ -217,14 +256,48 @@ Open **`http://localhost:8000`** in your browser.
|
|
|
217
256
|
)
|
|
218
257
|
|
|
219
258
|
# 3. main.py entry point
|
|
220
|
-
main_content = """
|
|
259
|
+
main_content = """\"\"\"
|
|
260
|
+
|--------------------------------------------------------------------------
|
|
261
|
+
| Application Entrypoint & Kernel
|
|
262
|
+
|--------------------------------------------------------------------------
|
|
263
|
+
|
|
|
264
|
+
| This file instantiates the Astris application instance.
|
|
265
|
+
| The Astris() kernel automatically:
|
|
266
|
+
| 1. Loads configuration from app/core/config.py and .env
|
|
267
|
+
| 2. Configures the security and session middleware pipeline
|
|
268
|
+
| 3. Discovers domain controllers in app/modules/
|
|
269
|
+
| 4. Connects the Inertia.js Vue 3 frontend view engine
|
|
270
|
+
|
|
|
271
|
+
| Run development server:
|
|
272
|
+
| uv run orbit serve
|
|
273
|
+
|
|
|
274
|
+
| Run in production:
|
|
275
|
+
| uv run orbit serve --prod
|
|
276
|
+
|
|
|
277
|
+
\"\"\"
|
|
278
|
+
|
|
279
|
+
from astris import Astris
|
|
221
280
|
|
|
222
281
|
app = Astris()
|
|
223
282
|
"""
|
|
224
283
|
(project_dir / "main.py").write_text(main_content, encoding="utf-8")
|
|
225
284
|
|
|
226
285
|
# 4. Default Welcome Controller using InertiaResponse
|
|
227
|
-
welcome_controller = """
|
|
286
|
+
welcome_controller = """\"\"\"
|
|
287
|
+
|--------------------------------------------------------------------------
|
|
288
|
+
| Welcome Controller
|
|
289
|
+
|--------------------------------------------------------------------------
|
|
290
|
+
|
|
|
291
|
+
| This controller handles the root URL ('/') of your Astris application.
|
|
292
|
+
| It returns an InertiaResponse which renders the Vue 3 component located
|
|
293
|
+
| at resources/js/Pages/Welcome.vue with typed server props.
|
|
294
|
+
|
|
|
295
|
+
| Feel free to modify, replace, or remove this module and controller
|
|
296
|
+
| to suit your application's requirements.
|
|
297
|
+
|
|
|
298
|
+
\"\"\"
|
|
299
|
+
|
|
300
|
+
from astris.http import Request
|
|
228
301
|
from astris.inertia import InertiaResponse
|
|
229
302
|
from astris.routing import Controller
|
|
230
303
|
|
|
@@ -239,10 +312,10 @@ async def index(request: Request) -> InertiaResponse:
|
|
|
239
312
|
props={
|
|
240
313
|
"status": "online",
|
|
241
314
|
"message": "Welcome to your Astris application! 🚀",
|
|
242
|
-
"version": "0.1.
|
|
315
|
+
"version": "0.1.1",
|
|
243
316
|
"api_docs_url": "/docs",
|
|
244
317
|
"redoc_url": "/redoc",
|
|
245
|
-
"docs_url": "https://astris.dev
|
|
318
|
+
"docs_url": "https://astris.dev",
|
|
246
319
|
},
|
|
247
320
|
)
|
|
248
321
|
"""
|
|
@@ -586,6 +659,18 @@ const page = usePage();
|
|
|
586
659
|
else:
|
|
587
660
|
subprocess.run(["uv", "add", "astris-python"], cwd=project_dir, check=True)
|
|
588
661
|
|
|
662
|
+
# 12. Scaffold bundled Astris agent skills
|
|
663
|
+
astris_skill = Path(__file__).parent / ".agents" / "skills" / "astris" / "SKILL.md"
|
|
664
|
+
if astris_skill.exists():
|
|
665
|
+
skill_text = astris_skill.read_text(encoding="utf-8")
|
|
666
|
+
target_dirs = [project_dir / ".agents" / "skills" / "astris"]
|
|
667
|
+
if claude:
|
|
668
|
+
target_dirs.append(project_dir / ".claude" / "skills" / "astris")
|
|
669
|
+
|
|
670
|
+
for skill_dir_path in target_dirs:
|
|
671
|
+
skill_dir_path.mkdir(parents=True, exist_ok=True)
|
|
672
|
+
(skill_dir_path / "SKILL.md").write_text(skill_text, encoding="utf-8")
|
|
673
|
+
|
|
589
674
|
typer.secho(
|
|
590
675
|
f"\n✓ Project {name} created successfully!", fg=typer.colors.GREEN, bold=True
|
|
591
676
|
)
|
|
@@ -79,7 +79,7 @@ class Astris:
|
|
|
79
79
|
self.session_https_only = (
|
|
80
80
|
session_https_only
|
|
81
81
|
if session_https_only is not None
|
|
82
|
-
else self.config.
|
|
82
|
+
else self.config.resolved_session_https_only
|
|
83
83
|
)
|
|
84
84
|
self.session_same_site = (
|
|
85
85
|
session_same_site or self.config.session_same_site # type: ignore[assignment]
|
|
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
|