asw-v01: archive deferred content (packs, site, lab, legacy examples)
- 2.1: packs/ -> archive/packs/ - 2.2: site/ -> archive/site/ - 2.3: src/lab/ -> archive/lab/ - 2.4: examples/ -> archive/examples-legacy/ (SSI-based)
This commit is contained in:
parent
416fe2f180
commit
e47a9f4401
173 changed files with 11 additions and 5 deletions
30
archive/packs/apache/apache-autoindex.conf
Normal file
30
archive/packs/apache/apache-autoindex.conf
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# ASW Apache Autoindex — config snippet
|
||||
#
|
||||
# Enables ASW-styled directory listings.
|
||||
# Apache's HeaderName/ReadmeName inject custom HTML before/after the file table.
|
||||
#
|
||||
# Paste this into your VirtualHost block or a <Directory> block.
|
||||
# Adjust paths to match your setup.
|
||||
|
||||
# ── Autoindex settings ───────────────────────────────────────────────────────
|
||||
|
||||
Options +Indexes
|
||||
IndexOptions FancyIndexing HTMLTable SuppressRules SuppressColumnSorting
|
||||
IndexOptions +NameWidth=* +DescriptionWidth=* +Charset=UTF-8
|
||||
|
||||
# Custom header/footer HTML injected around the file table
|
||||
HeaderName /autoindex/autoindex-header.html
|
||||
ReadmeName /autoindex/autoindex-footer.html
|
||||
|
||||
# Suppress the default Apache header (H1 "Index of /path")
|
||||
# — our header file provides the title
|
||||
IndexOptions +SuppressHTMLPreamble
|
||||
|
||||
# Serve the custom header/footer files
|
||||
Alias /autoindex/ /home/exedev/projects/agentic-semantic-web/packs/apache/
|
||||
|
||||
<Directory /home/exedev/projects/agentic-semantic-web/packs/apache/>
|
||||
Options None
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
</Directory>
|
||||
27
archive/packs/apache/apache-errors.conf
Normal file
27
archive/packs/apache/apache-errors.conf
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# ASW Apache Error Pages — config snippet
|
||||
#
|
||||
# Paste this into your VirtualHost block or httpd.conf.
|
||||
# Adjust the DocumentRoot alias path to where your errors/ directory lives.
|
||||
#
|
||||
# Assumes asw.css is served at /asw/asw.css
|
||||
# (serve the agentic-semantic-web repo root as /asw/ — see README.md)
|
||||
|
||||
# ── Error pages ──────────────────────────────────────────────────────────────
|
||||
|
||||
ErrorDocument 400 /errors/400.html
|
||||
ErrorDocument 401 /errors/401.html
|
||||
ErrorDocument 403 /errors/403.html
|
||||
ErrorDocument 404 /errors/404.html
|
||||
ErrorDocument 500 /errors/500.html
|
||||
ErrorDocument 502 /errors/502.html
|
||||
ErrorDocument 503 /errors/503.html
|
||||
|
||||
# Serve the ASW errors/ directory.
|
||||
# Adjust the path to where your agentic-semantic-web checkout lives.
|
||||
Alias /errors/ /home/exedev/projects/agentic-semantic-web/errors/
|
||||
|
||||
<Directory /home/exedev/projects/agentic-semantic-web/errors/>
|
||||
Options None
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
</Directory>
|
||||
3
archive/packs/apache/autoindex-footer.html
Normal file
3
archive/packs/apache/autoindex-footer.html
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
</main>
|
||||
</body>
|
||||
</html>
|
||||
49
archive/packs/apache/autoindex-header.html
Normal file
49
archive/packs/apache/autoindex-header.html
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/asw/asw.css">
|
||||
<title>Directory Listing</title>
|
||||
<style>
|
||||
body { display: flex; flex-direction: column; min-height: 100vh; }
|
||||
main { flex: 1; padding: 2rem; max-width: 80ch; margin: 0 auto; width: 100%; }
|
||||
|
||||
/* Apache autoindex outputs a <table> — style it to match ASW */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-family: var(--asw-font-mono);
|
||||
font-size: var(--asw-text-sm);
|
||||
}
|
||||
table th {
|
||||
text-align: left;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-bottom: 1px solid var(--asw-border);
|
||||
color: var(--asw-text-muted);
|
||||
font-weight: 500;
|
||||
font-size: var(--asw-text-xs);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
table td {
|
||||
padding: 0.4rem 0.75rem;
|
||||
border-bottom: 1px solid var(--asw-border-subtle);
|
||||
color: var(--asw-text-secondary);
|
||||
}
|
||||
table tr:hover td { background: var(--asw-bg-hover); }
|
||||
table td:first-child a {
|
||||
color: var(--asw-accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
table td:first-child a:hover { text-decoration: underline; }
|
||||
/* parent directory link */
|
||||
table td img { display: none; } /* hide Apache's default icons */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<ul><li><a href="/"><strong>Home</strong></a></li></ul>
|
||||
</nav>
|
||||
<main>
|
||||
<h1>Directory listing</h1>
|
||||
160
archive/packs/caddy/browse.html
Normal file
160
archive/packs/caddy/browse.html
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
{{/*
|
||||
ASW Caddy Browse Template
|
||||
Go HTML template for Caddy's file_server browse directive.
|
||||
|
||||
Usage in Caddyfile:
|
||||
file_server browse {
|
||||
browse {
|
||||
template_file /path/to/agentic-semantic-web/packs/caddy/browse.html
|
||||
}
|
||||
}
|
||||
|
||||
Template variables:
|
||||
.Name — directory name (last segment of path)
|
||||
.Path — current URL path, e.g. "/files/"
|
||||
.Files — []FileInfo entries, each with:
|
||||
.Name, .URL, .Size, .ModTime, .IsDir, .IsSymlink
|
||||
*/}}
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/asw/asw.css">
|
||||
<title>{{.Name}} — Directory listing</title>
|
||||
<style>
|
||||
body { display: flex; flex-direction: column; min-height: 100vh; }
|
||||
main { flex: 1; padding: 2rem; max-width: 90ch; margin: 0 auto; width: 100%; }
|
||||
|
||||
.listing {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-family: var(--asw-font-mono);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.listing th {
|
||||
text-align: left;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-bottom: 1px solid var(--asw-border);
|
||||
color: var(--asw-text-muted);
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.listing td {
|
||||
padding: 0.4rem 0.75rem;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--asw-border) 40%, transparent);
|
||||
color: var(--asw-text-secondary);
|
||||
vertical-align: middle;
|
||||
}
|
||||
.listing tr:hover td { background: color-mix(in srgb, var(--asw-bg-elevated) 60%, transparent); }
|
||||
.listing .col-name a {
|
||||
color: var(--asw-accent);
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
}
|
||||
.listing .col-name a:hover { text-decoration: underline; }
|
||||
.listing .col-size { text-align: right; color: var(--asw-text-muted); }
|
||||
.listing .col-modified { white-space: nowrap; color: var(--asw-text-muted); }
|
||||
.icon { font-size: 0.85em; opacity: 0.7; }
|
||||
.dir-icon::before { content: "📁"; }
|
||||
.file-icon::before { content: "📄"; }
|
||||
.parent-icon::before { content: "⬆️"; font-size: 0.8em; }
|
||||
|
||||
.path-breadcrumb {
|
||||
font-family: var(--asw-font-mono);
|
||||
font-size: 0.875rem;
|
||||
color: var(--asw-text-muted);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.path-breadcrumb a { color: var(--asw-accent); text-decoration: none; }
|
||||
.path-breadcrumb a:hover { text-decoration: underline; }
|
||||
.path-breadcrumb span { color: var(--asw-text-muted); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<ul><li><a href="/"><strong>Home</strong></a></li></ul>
|
||||
<ul><li><span data-text="dim">browse</span></li></ul>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<h1>{{.Name}}</h1>
|
||||
|
||||
<p class="path-breadcrumb">
|
||||
{{/* Breadcrumb — split path and link each segment */}}
|
||||
<a href="/">/</a>
|
||||
{{- $parts := splitList "/" (trimSuffix "/" .Path) -}}
|
||||
{{- $acc := "" -}}
|
||||
{{- range $i, $part := $parts -}}
|
||||
{{- if $part -}}
|
||||
{{- $acc = printf "%s/%s" $acc $part -}}
|
||||
<span>/</span><a href="{{$acc}}/">{{$part}}</a>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</p>
|
||||
|
||||
<table class="listing">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-name">Name</th>
|
||||
<th class="col-size">Size</th>
|
||||
<th class="col-modified">Modified</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{/* Parent directory link */}}
|
||||
{{if ne .Path "/"}}
|
||||
<tr>
|
||||
<td class="col-name">
|
||||
<a href="../">
|
||||
<span class="icon parent-icon"></span>
|
||||
..
|
||||
</a>
|
||||
</td>
|
||||
<td class="col-size">—</td>
|
||||
<td class="col-modified">—</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
|
||||
{{/* Directory entries first */}}
|
||||
{{range .Files}}{{if .IsDir}}
|
||||
<tr>
|
||||
<td class="col-name">
|
||||
<a href="{{.URL}}">
|
||||
<span class="icon dir-icon"></span>
|
||||
{{.Name}}/
|
||||
</a>
|
||||
</td>
|
||||
<td class="col-size">—</td>
|
||||
<td class="col-modified">{{.ModTime.Format "2006-01-02 15:04"}}</td>
|
||||
</tr>
|
||||
{{end}}{{end}}
|
||||
|
||||
{{/* File entries */}}
|
||||
{{range .Files}}{{if not .IsDir}}
|
||||
<tr>
|
||||
<td class="col-name">
|
||||
<a href="{{.URL}}">
|
||||
<span class="icon file-icon"></span>
|
||||
{{.Name}}
|
||||
</a>
|
||||
</td>
|
||||
<td class="col-size">{{humanizeBytes .Size}}</td>
|
||||
<td class="col-modified">{{.ModTime.Format "2006-01-02 15:04"}}</td>
|
||||
</tr>
|
||||
{{end}}{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>Styled with <a href="/asw/">Agentic Semantic Web</a></p>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
51
archive/packs/caddy/caddy-browse.conf
Normal file
51
archive/packs/caddy/caddy-browse.conf
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# ASW Caddy Directory Listing — Caddyfile snippet
|
||||
#
|
||||
# Caddy's `file_server browse` outputs its own HTML, but accepts a custom
|
||||
# template file via `browse { template_file }`. The template is a Go HTML
|
||||
# template with access to directory metadata and file entries.
|
||||
#
|
||||
# See: browse.html in this directory for the ASW-styled template.
|
||||
|
||||
# ── Option A: Custom template (recommended, Caddy v2.6+) ─────────────────────
|
||||
#
|
||||
# Full control over the listing HTML. Browse template receives:
|
||||
# .Name — directory name (last path segment)
|
||||
# .Path — current URL path (e.g. "/files/")
|
||||
# .Files — []FileInfo, each with:
|
||||
# .Name, .Size, .URL, .ModTime, .IsDir, .IsSymlink
|
||||
|
||||
handle /files/* {
|
||||
root * /var/www/files
|
||||
file_server browse {
|
||||
index off
|
||||
browse {
|
||||
template_file /home/exedev/projects/agentic-semantic-web/packs/caddy/browse.html
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# ── Option B: Default Caddy browse (quick, no custom template) ────────────────
|
||||
#
|
||||
# Uses Caddy's built-in file browser. Functional but unstyled.
|
||||
# Upgrade to Option A when you want ASW aesthetics.
|
||||
#
|
||||
# handle /files/* {
|
||||
# root * /var/www/files
|
||||
# file_server browse
|
||||
# }
|
||||
|
||||
|
||||
# ── Example: expose the ASW repo itself for browsing ─────────────────────────
|
||||
#
|
||||
# example.com {
|
||||
# handle /asw/browse/* {
|
||||
# uri strip_prefix /asw/browse
|
||||
# root * /home/exedev/projects/agentic-semantic-web
|
||||
# file_server browse {
|
||||
# browse {
|
||||
# template_file /home/exedev/projects/agentic-semantic-web/packs/caddy/browse.html
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
66
archive/packs/caddy/caddy-errors.conf
Normal file
66
archive/packs/caddy/caddy-errors.conf
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# ASW Caddy Error Pages — Caddyfile snippet
|
||||
#
|
||||
# Paste this inside your site block (not at global scope).
|
||||
# Adjust the root path to where your agentic-semantic-web repo lives.
|
||||
#
|
||||
# Assumes asw.css is served at /asw/asw.css
|
||||
# (serve the agentic-semantic-web repo root as /asw/ — see README.md)
|
||||
|
||||
# ── Error pages ──────────────────────────────────────────────────────────────
|
||||
#
|
||||
# Caddy's handle_errors block intercepts any response with a matching
|
||||
# status code before it reaches the client.
|
||||
#
|
||||
# Rewrite the path to the ASW static error page, then serve it from
|
||||
# the agentic-semantic-web repo root.
|
||||
|
||||
handle_errors {
|
||||
rewrite * /errors/{http.error.status_code}.html
|
||||
file_server {
|
||||
root /home/exedev/projects/agentic-semantic-web
|
||||
}
|
||||
}
|
||||
|
||||
# ── Serving asw.css ───────────────────────────────────────────────────────
|
||||
#
|
||||
# The error pages load asw.css from /asw/asw.css.
|
||||
# Add this route to your site block so the CSS is reachable.
|
||||
# (Skip if you already serve /asw/ from this repo elsewhere.)
|
||||
|
||||
handle /asw/* {
|
||||
uri strip_prefix /asw
|
||||
file_server {
|
||||
root /home/exedev/projects/agentic-semantic-web
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# ── Example site block ────────────────────────────────────────────────────────
|
||||
#
|
||||
# example.com {
|
||||
# root * /var/www/html
|
||||
# file_server
|
||||
#
|
||||
# handle /asw/* {
|
||||
# uri strip_prefix /asw
|
||||
# file_server {
|
||||
# root /home/exedev/projects/agentic-semantic-web
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# handle_errors {
|
||||
# rewrite * /errors/{http.error.status_code}.html
|
||||
# file_server {
|
||||
# root /home/exedev/projects/agentic-semantic-web
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# Supported error codes (from errors/ directory):
|
||||
# 400 — Bad Request
|
||||
# 401 — Unauthorized
|
||||
# 403 — Forbidden
|
||||
# 404 — Not Found
|
||||
# 500 — Internal Server Error
|
||||
# 502 — Bad Gateway
|
||||
# 503 — Service Unavailable
|
||||
82
archive/packs/flask/README.md
Normal file
82
archive/packs/flask/README.md
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# ASW Flask/FastAPI Error Pack
|
||||
|
||||
Drop-in styled error responses for Flask and FastAPI applications.
|
||||
|
||||
## Usage
|
||||
|
||||
```python
|
||||
# Copy asw_errors.py to your project directory, then:
|
||||
|
||||
from flask import Flask
|
||||
from asw_errors import register_asw_errors
|
||||
|
||||
app = Flask(__name__)
|
||||
register_asw_errors(app)
|
||||
```
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
from asw_errors import register_asw_errors
|
||||
|
||||
app = FastAPI()
|
||||
register_asw_errors(app)
|
||||
```
|
||||
|
||||
## What you get
|
||||
|
||||
ASW-styled HTML error pages for all HTTP error codes:
|
||||
|
||||
| Code | Status |
|
||||
|------|--------|
|
||||
| 400 | Bad Request |
|
||||
| 401 | Unauthorized |
|
||||
| 403 | Forbidden |
|
||||
| 404 | Not Found |
|
||||
| 405 | Method Not Allowed |
|
||||
| 408 | Request Timeout |
|
||||
| 409 | Conflict |
|
||||
| 410 | Gone |
|
||||
| 415 | Unsupported Media Type |
|
||||
| 422 | Unprocessable Entity |
|
||||
| 429 | Too Many Requests |
|
||||
| 500 | Internal Server Error |
|
||||
| 502 | Bad Gateway |
|
||||
| 503 | Service Unavailable |
|
||||
| 504 | Gateway Timeout |
|
||||
|
||||
For FastAPI, `RequestValidationError` (422) is also caught separately.
|
||||
|
||||
## Linking asw.css
|
||||
|
||||
By default, styles are inlined — no external dependencies needed. If your app already serves `asw.css`, link it instead:
|
||||
|
||||
```python
|
||||
register_asw_errors(app, css_url="/static/asw.css")
|
||||
```
|
||||
|
||||
The pages will then use your full theme including custom fonts.
|
||||
|
||||
## Custom app name
|
||||
|
||||
The nav bar shows your app's name. Override it:
|
||||
|
||||
```python
|
||||
register_asw_errors(app, app_name="My Service")
|
||||
```
|
||||
|
||||
Flask auto-detects from `app.name`; FastAPI from `app.title`. Both fall back to sensible defaults.
|
||||
|
||||
## Standalone HTML generation
|
||||
|
||||
The `error_html()` function is also exported if you need to generate pages directly:
|
||||
|
||||
```python
|
||||
from asw_errors import error_html
|
||||
|
||||
html = error_html(404, path="/missing/route")
|
||||
html = error_html(500, css_url="/static/asw.css", app_name="My API")
|
||||
```
|
||||
|
||||
## No dependencies at module level
|
||||
|
||||
Flask and FastAPI are imported lazily inside `register_asw_errors()` — so this file can live in a project that uses either framework without both installed.
|
||||
BIN
archive/packs/flask/__pycache__/asw_errors.cpython-312.pyc
Normal file
BIN
archive/packs/flask/__pycache__/asw_errors.cpython-312.pyc
Normal file
Binary file not shown.
293
archive/packs/flask/asw_errors.py
Normal file
293
archive/packs/flask/asw_errors.py
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
ASW Flask/FastAPI Error Pack — styled error responses for Python web frameworks.
|
||||
|
||||
Drop this into your project directory and call register_asw_errors(app).
|
||||
|
||||
Usage (Flask):
|
||||
from flask import Flask
|
||||
from asw_errors import register_asw_errors
|
||||
|
||||
app = Flask(__name__)
|
||||
register_asw_errors(app)
|
||||
|
||||
Usage (FastAPI):
|
||||
from fastapi import FastAPI
|
||||
from asw_errors import register_asw_errors
|
||||
|
||||
app = FastAPI()
|
||||
register_asw_errors(app)
|
||||
|
||||
Optional: link to a hosted asw.css instead of inline styles
|
||||
register_asw_errors(app, css_url="/static/asw.css")
|
||||
register_asw_errors(app, css_url="https://cdn.example.com/asw.css")
|
||||
"""
|
||||
|
||||
import html as _html
|
||||
|
||||
# ── Inline styles ─────────────────────────────────────────────────────────────
|
||||
# Minimal ASW tokens — no external dependency. Matches asw.css dark theme.
|
||||
|
||||
_ASW_INLINE = """
|
||||
:root {
|
||||
--asw-bg: #0d1117;
|
||||
--asw-bg-elevated: #161b22;
|
||||
--asw-bg-overlay: #1c2128;
|
||||
--asw-text: #e6edf3;
|
||||
--asw-text-secondary: #8b949e;
|
||||
--asw-text-muted: #484f58;
|
||||
--asw-accent: #3fb950;
|
||||
--asw-border: #30363d;
|
||||
--asw-font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
||||
--asw-font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', ui-monospace, monospace;
|
||||
}
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
html { font-size: 16px; }
|
||||
body {
|
||||
background: var(--asw-bg);
|
||||
color: var(--asw-text);
|
||||
font-family: var(--asw-font-body);
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-bottom: 1px solid var(--asw-border);
|
||||
background: var(--asw-bg-elevated);
|
||||
}
|
||||
nav a { color: var(--asw-text); text-decoration: none; font-weight: 600; }
|
||||
nav a:hover { color: var(--asw-accent); }
|
||||
.badge {
|
||||
font-family: var(--asw-font-mono);
|
||||
font-size: 0.75rem;
|
||||
color: var(--asw-text-muted);
|
||||
background: var(--asw-bg-overlay);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
border: 1px solid var(--asw-border);
|
||||
}
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
.error-card {
|
||||
max-width: 42ch;
|
||||
text-align: center;
|
||||
}
|
||||
.error-code {
|
||||
font-family: var(--asw-font-mono);
|
||||
font-size: 6rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: var(--asw-text-muted);
|
||||
letter-spacing: -0.05em;
|
||||
margin: 0 0 0.5rem;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 1rem;
|
||||
font-size: 1.5rem;
|
||||
color: var(--asw-text);
|
||||
}
|
||||
p { margin: 0 0 0.75rem; color: var(--asw-text-secondary); }
|
||||
a.button {
|
||||
display: inline-block;
|
||||
margin-top: 1.5rem;
|
||||
padding: 0.5rem 1.25rem;
|
||||
background: transparent;
|
||||
color: var(--asw-accent);
|
||||
border: 1px solid var(--asw-accent);
|
||||
border-radius: 0.375rem;
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
a.button:hover { background: rgba(63, 185, 80, 0.1); }
|
||||
code {
|
||||
font-family: var(--asw-font-mono);
|
||||
font-size: 0.85em;
|
||||
background: var(--asw-bg-overlay);
|
||||
padding: 0.1em 0.35em;
|
||||
border-radius: 0.2rem;
|
||||
color: var(--asw-text-secondary);
|
||||
}
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--asw-text-muted);
|
||||
border-top: 1px solid var(--asw-border);
|
||||
}
|
||||
footer a { color: var(--asw-text-muted); }
|
||||
"""
|
||||
|
||||
_ERROR_MESSAGES = {
|
||||
400: ("Bad Request", "The server couldn't understand the request."),
|
||||
401: ("Unauthorized", "Authentication required. Please sign in."),
|
||||
403: ("Forbidden", "You don't have permission to access this resource."),
|
||||
404: ("Not Found", "The page or resource you requested doesn't exist."),
|
||||
405: ("Method Not Allowed", "That HTTP method isn't supported for this endpoint."),
|
||||
408: ("Request Timeout", "The request took too long. Please try again."),
|
||||
409: ("Conflict", "The request conflicts with the current state of the server."),
|
||||
410: ("Gone", "This resource has been permanently removed."),
|
||||
415: ("Unsupported Media Type", "The Content-Type you sent isn't accepted."),
|
||||
422: ("Unprocessable Entity", "The request was well-formed but contains invalid data."),
|
||||
429: ("Too Many Requests", "You've sent too many requests. Please slow down."),
|
||||
500: ("Internal Server Error", "Something went wrong on the server."),
|
||||
501: ("Not Implemented", "This feature hasn't been implemented yet."),
|
||||
502: ("Bad Gateway", "The server received an invalid response from upstream."),
|
||||
503: ("Service Unavailable", "The server is temporarily unavailable. Try again soon."),
|
||||
504: ("Gateway Timeout", "The upstream server didn't respond in time."),
|
||||
}
|
||||
|
||||
# Codes that receive an extra hint about the path in the response
|
||||
_SHOW_PATH_CODES = {404, 403, 405}
|
||||
|
||||
|
||||
def _make_style_tag(css_url=None):
|
||||
if css_url:
|
||||
return f'<link rel="stylesheet" href="{_html.escape(css_url)}">'
|
||||
return f"<style>{_ASW_INLINE}</style>"
|
||||
|
||||
|
||||
def error_html(code: int, path: str = "", css_url=None, app_name: str = "API") -> str:
|
||||
"""
|
||||
Generate an ASW-styled error page as an HTML string.
|
||||
|
||||
Args:
|
||||
code: HTTP status code
|
||||
path: Request path (shown for 403/404/405)
|
||||
css_url: Optional URL to link instead of inlining styles
|
||||
app_name: Shown in the nav bar (default: "API")
|
||||
"""
|
||||
title, message = _ERROR_MESSAGES.get(code, ("Error", "An error occurred."))
|
||||
style_block = _make_style_tag(css_url)
|
||||
path_hint = ""
|
||||
if path and code in _SHOW_PATH_CODES:
|
||||
escaped = _html.escape(path)
|
||||
path_hint = f'<p>Path: <code>{escaped}</code></p>'
|
||||
|
||||
return f"""<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{code} — {title}</title>
|
||||
{style_block}
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<a href="/">{_html.escape(app_name)}</a>
|
||||
<span class="badge">{code}</span>
|
||||
</nav>
|
||||
<main>
|
||||
<div class="error-card">
|
||||
<p class="error-code">{code}</p>
|
||||
<h1>{title}</h1>
|
||||
<p>{message}</p>
|
||||
{path_hint}
|
||||
<a class="button" href="/">← Back</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer>Powered by <a href="https://github.com/trentuna/agentic-semantic-web">agentic-semantic-web</a></footer>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
|
||||
# ── Flask registration ────────────────────────────────────────────────────────
|
||||
|
||||
def _register_flask(app, css_url, app_name):
|
||||
"""Register error handlers on a Flask application."""
|
||||
from flask import request as flask_request
|
||||
from flask import Response as FlaskResponse
|
||||
|
||||
codes = list(_ERROR_MESSAGES.keys())
|
||||
|
||||
def make_handler(code):
|
||||
def handler(e):
|
||||
path = getattr(flask_request, "path", "")
|
||||
body = error_html(code, path=path, css_url=css_url, app_name=app_name)
|
||||
return FlaskResponse(body, status=code, mimetype="text/html")
|
||||
handler.__name__ = f"asw_error_{code}"
|
||||
return handler
|
||||
|
||||
for code in codes:
|
||||
app.register_error_handler(code, make_handler(code))
|
||||
|
||||
|
||||
# ── FastAPI / Starlette registration ──────────────────────────────────────────
|
||||
|
||||
def _register_fastapi(app, css_url, app_name):
|
||||
"""Register exception handlers on a FastAPI application."""
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import HTMLResponse
|
||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
|
||||
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
|
||||
code = exc.status_code
|
||||
path = request.url.path
|
||||
body = error_html(code, path=path, css_url=css_url, app_name=app_name)
|
||||
return HTMLResponse(content=body, status_code=code)
|
||||
|
||||
# Catch all Starlette HTTP exceptions (covers 4xx and 5xx)
|
||||
app.add_exception_handler(StarletteHTTPException, http_exception_handler)
|
||||
|
||||
# Also catch FastAPI's RequestValidationError (unprocessable entity — 422)
|
||||
try:
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
|
||||
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
||||
path = request.url.path
|
||||
body = error_html(422, path=path, css_url=css_url, app_name=app_name)
|
||||
return HTMLResponse(content=body, status_code=422)
|
||||
|
||||
app.add_exception_handler(RequestValidationError, validation_exception_handler)
|
||||
except ImportError:
|
||||
pass # Pure Starlette without FastAPI — skip
|
||||
|
||||
|
||||
# ── Public API ────────────────────────────────────────────────────────────────
|
||||
|
||||
def register_asw_errors(app, css_url=None, app_name=None):
|
||||
"""
|
||||
Register ASW-styled HTTP error handlers on a Flask or FastAPI application.
|
||||
|
||||
Args:
|
||||
app: Flask or FastAPI application instance
|
||||
css_url: Optional URL to link asw.css instead of inlining styles.
|
||||
Use this when your app already serves asw.css:
|
||||
register_asw_errors(app, css_url="/static/asw.css")
|
||||
app_name: Label shown in the nav bar. Defaults to the app's name.
|
||||
|
||||
Raises:
|
||||
TypeError: If the app type is not recognized as Flask or FastAPI.
|
||||
"""
|
||||
app_type = type(app).__name__
|
||||
|
||||
# Resolve app_name from app if not provided
|
||||
if app_name is None:
|
||||
if app_type == "Flask":
|
||||
app_name = app.name or "Flask App"
|
||||
elif app_type == "FastAPI":
|
||||
app_name = app.title or "FastAPI"
|
||||
else:
|
||||
app_name = "API"
|
||||
|
||||
if app_type == "Flask":
|
||||
_register_flask(app, css_url, app_name)
|
||||
elif app_type == "FastAPI":
|
||||
_register_fastapi(app, css_url, app_name)
|
||||
else:
|
||||
raise TypeError(
|
||||
f"Unsupported app type: {app_type!r}. "
|
||||
"Expected 'Flask' or 'FastAPI'."
|
||||
)
|
||||
129
archive/packs/hugo/README.md
Normal file
129
archive/packs/hugo/README.md
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# ASW-Hugo Pack
|
||||
|
||||
A Hugo theme that outputs pure [Agentic Semantic Web (ASW)](https://trentuna.com/asw/) semantic HTML with `data-*` attributes. Drop it into any Hugo project → content renders through ASW.
|
||||
|
||||
## What this is
|
||||
|
||||
This pack is an **ASW adapter for Hugo** — not a standalone theme. It makes Hugo output the semantic HTML + data-attribute vocabulary that ASW CSS understands.
|
||||
|
||||
```
|
||||
Markdown vault (Git)
|
||||
→ Hugo + ASW-Hugo pack (layouts emit ASW HTML)
|
||||
→ static HTML with ASW CSS + data-* attributes
|
||||
→ deploy anywhere
|
||||
```
|
||||
|
||||
Long-term: trentuna will build a native ASW Site Builder. This pack is the bridge to alpha.
|
||||
|
||||
## Install
|
||||
|
||||
1. Copy or symlink `packs/hugo/` from the ASW repo into your project's `themes/` directory:
|
||||
```bash
|
||||
# From the agentic-semantic-web repo root:
|
||||
cp -r packs/hugo/ /path/to/your-site/themes/asw-hugo/
|
||||
# or symlink:
|
||||
ln -s /path/to/agentic-semantic-web/packs/hugo/ /path/to/your-site/themes/asw-hugo
|
||||
```
|
||||
|
||||
2. Add to your `hugo.toml`:
|
||||
```toml
|
||||
theme = "asw-hugo"
|
||||
|
||||
[taxonomies]
|
||||
tag = "tags"
|
||||
```
|
||||
|
||||
3. Copy ASW CSS into the theme's static directory:
|
||||
```bash
|
||||
cp /path/to/agentic-semantic-web/asw.css themes/asw-hugo/static/css/asw.css
|
||||
```
|
||||
> **Note:** `asw.css` is the `asw.css` file from the ASW repo root. B.A.'s deploy script handles this copy automatically for trentuna-web deployments.
|
||||
|
||||
4. **Self-host fonts** (recommended for production): The default `theme.css` imports Inter and
|
||||
JetBrains Mono from Google Fonts. For self-hosted deployment, download the font files and
|
||||
replace the `@import` in `static/css/theme.css` with local `@font-face` declarations.
|
||||
See [google-webfonts-helper](https://gwfh.mranftl.com/) for font download.
|
||||
|
||||
## Usage
|
||||
|
||||
Write standard Markdown. Hugo renders it through ASW layouts automatically.
|
||||
|
||||
### Shortcodes
|
||||
|
||||
**Callout block:**
|
||||
```
|
||||
{{< callout note >}}
|
||||
This is a note callout. Types: note, warning, tip, info
|
||||
{{< /callout >}}
|
||||
```
|
||||
Renders as: `<aside data-callout="note">...</aside>`
|
||||
|
||||
**Wikilink:**
|
||||
```
|
||||
{{< wikilink "Page Name" "/path/to/page/" >}}
|
||||
```
|
||||
Renders as: `<a href="/path/to/page/" data-wikilink>Page Name</a>`
|
||||
|
||||
### ASW data-* vocabulary used
|
||||
|
||||
See `docs/agent-directive.md` in the ASW repo for the full vocabulary.
|
||||
|
||||
| Attribute | Where used | Meaning |
|
||||
|-----------|-----------|---------|
|
||||
| `data-callout="note\|warning\|tip\|info"` | `<aside>` | Callout block type |
|
||||
| `data-wikilink` | `<a>` | Internal vault-style link |
|
||||
| `data-role="tag-cloud"` | `<nav>` | Tag navigation |
|
||||
| `data-layout="grid"` | `<section>` | Grid layout for list pages |
|
||||
| `data-tag` | `<a>` | Tag label on links |
|
||||
|
||||
## Decap CMS
|
||||
|
||||
The `admin/` directory contains a minimal Decap CMS configuration for browser-based Markdown editing without SSH.
|
||||
|
||||
To enable:
|
||||
1. Update `admin/config.yml`: set `repo` to your Forgejo repo path
|
||||
2. Set `base_url` to your Forgejo instance URL
|
||||
3. Deploy the `admin/` directory alongside your Hugo output
|
||||
4. Access at `https://yoursite.com/admin/`
|
||||
|
||||
## Pack structure
|
||||
|
||||
```
|
||||
packs/hugo/
|
||||
├── README.md ← This file
|
||||
├── layouts/
|
||||
│ ├── _default/
|
||||
│ │ ├── baseof.html ← Base template (html, head, body structure)
|
||||
│ │ ├── single.html ← Single pages (articles)
|
||||
│ │ └── list.html ← List + taxonomy pages (grid)
|
||||
│ ├── partials/
|
||||
│ │ ├── head.html ← <head>: meta, CSS links
|
||||
│ │ └── tag-nav.html ← Tag cloud nav (use in list templates or sidebar)
|
||||
│ └── shortcodes/
|
||||
│ ├── callout.html ← {{< callout type >}} shortcode
|
||||
│ └── wikilink.html ← {{< wikilink text href >}} shortcode
|
||||
├── static/
|
||||
│ └── css/
|
||||
│ └── theme.css ← Trentuna design tokens (Inter, JetBrains Mono, dark palette)
|
||||
├── archetypes/
|
||||
│ └── default.md ← Default frontmatter template
|
||||
└── admin/
|
||||
└── config.yml ← Minimal Decap CMS config
|
||||
```
|
||||
|
||||
## Alpha scope (April 2026)
|
||||
|
||||
- [x] Pack scaffolded: all layouts + partials + shortcodes
|
||||
- [x] Tag navigation partial (`tag-nav.html`)
|
||||
- [x] Wikilink support (`wikilink` shortcode + `data-wikilink` on list page links)
|
||||
- [x] Callout blocks (`callout` shortcode → `<aside data-callout>`)
|
||||
- [x] Trentuna theme tokens (`theme.css`: Inter, JetBrains Mono, dark palette)
|
||||
- [x] Decap CMS config minimal but functional
|
||||
- [ ] Deployed to trentuna-web infra — B.A. task: wire `asw.css` copy + nginx config
|
||||
|
||||
## Relationship to existing packs
|
||||
|
||||
This pack joins the ASW server/framework integration packs:
|
||||
- `packs/nginx/` — nginx serving ASW content
|
||||
- `packs/pandoc/` — pandoc converting Markdown → ASW HTML
|
||||
- `packs/hugo/` ← **this pack** — Hugo site generation with ASW output
|
||||
82
archive/packs/hugo/admin/config.yml
Normal file
82
archive/packs/hugo/admin/config.yml
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# Decap CMS configuration for ASW-Hugo pack
|
||||
# Browser-based Markdown editing — no backend server required.
|
||||
#
|
||||
# Setup:
|
||||
# 1. Update `repo` below to your Forgejo repo path (owner/repo)
|
||||
# 2. Update `base_url` to your Forgejo instance URL
|
||||
# 3. Register an OAuth app in Forgejo: Settings → Applications → OAuth2 Apps
|
||||
# - Redirect URL: https://yoursite.com/admin/
|
||||
# 4. Deploy admin/ alongside your Hugo output
|
||||
# 5. Access at https://yoursite.com/admin/
|
||||
|
||||
backend:
|
||||
name: gitea # Forgejo is Gitea-compatible
|
||||
repo: trentuna/vault # ← CHANGE: your repo (owner/repo)
|
||||
branch: main
|
||||
base_url: https://trentuna.com # ← CHANGE: your Forgejo instance URL
|
||||
auth_endpoint: /api/v1/oauth2/
|
||||
|
||||
media_folder: static/images
|
||||
public_folder: /images
|
||||
|
||||
# Slug format for new posts
|
||||
slug:
|
||||
encoding: ascii
|
||||
clean_accents: true
|
||||
|
||||
collections:
|
||||
- name: posts
|
||||
label: Posts
|
||||
label_singular: Post
|
||||
folder: content/posts
|
||||
create: true
|
||||
slug: "{{year}}-{{month}}-{{day}}-{{slug}}"
|
||||
preview_path: "posts/{{slug}}/"
|
||||
fields:
|
||||
- label: Title
|
||||
name: title
|
||||
widget: string
|
||||
- label: Date
|
||||
name: date
|
||||
widget: datetime
|
||||
default: ""
|
||||
date_format: "YYYY-MM-DD"
|
||||
time_format: "HH:mm:ss"
|
||||
format: "YYYY-MM-DDTHH:mm:ssZ"
|
||||
- label: Description
|
||||
name: description
|
||||
widget: string
|
||||
required: false
|
||||
hint: Short summary shown in list views
|
||||
- label: Tags
|
||||
name: tags
|
||||
widget: list
|
||||
required: false
|
||||
hint: Comma-separated tags
|
||||
- label: Draft
|
||||
name: draft
|
||||
widget: boolean
|
||||
default: true
|
||||
- label: Body
|
||||
name: body
|
||||
widget: markdown
|
||||
|
||||
- name: pages
|
||||
label: Pages
|
||||
label_singular: Page
|
||||
folder: content
|
||||
create: true
|
||||
filter:
|
||||
field: _type
|
||||
value: page
|
||||
fields:
|
||||
- label: Title
|
||||
name: title
|
||||
widget: string
|
||||
- label: Description
|
||||
name: description
|
||||
widget: string
|
||||
required: false
|
||||
- label: Body
|
||||
name: body
|
||||
widget: markdown
|
||||
17
archive/packs/hugo/archetypes/default.md
Normal file
17
archive/packs/hugo/archetypes/default.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
date: {{ .Date }}
|
||||
draft: true
|
||||
description: ""
|
||||
tags: []
|
||||
---
|
||||
|
||||
<!-- Write content here. Use shortcodes for ASW-specific elements:
|
||||
|
||||
{{< callout note >}}
|
||||
This is a note.
|
||||
{{< /callout >}}
|
||||
|
||||
{{< wikilink "Related Page" "/related-page/" >}}
|
||||
|
||||
-->
|
||||
29
archive/packs/hugo/layouts/_default/baseof.html
Normal file
29
archive/packs/hugo/layouts/_default/baseof.html
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ .Site.Language.LanguageCode | default "en" }}" data-theme="dark">
|
||||
<head>
|
||||
{{- partial "head.html" . -}}
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<ul><li><a href="{{ .Site.BaseURL }}"><strong>{{ .Site.Title }}</strong></a></li></ul>
|
||||
<ul data-nav-links>
|
||||
{{- range .Site.Menus.main }}
|
||||
<li><a href="{{ .URL }}"{{ if $.IsMenuCurrent "main" . }} aria-current="page"{{ end }}>{{ .Name }}</a></li>
|
||||
{{- end }}
|
||||
</ul>
|
||||
<button data-theme-toggle aria-label="Toggle theme"></button>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
{{- block "main" . }}{{- end }}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<small>
|
||||
<a href="{{ "/" | relURL }}">{{ .Site.Title }}</a>
|
||||
· {{ now.Year }}
|
||||
{{- with .Site.Params.description }} · {{ . }}{{- end }}
|
||||
</small>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
39
archive/packs/hugo/layouts/_default/list.html
Normal file
39
archive/packs/hugo/layouts/_default/list.html
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{{ define "main" }}
|
||||
<section>
|
||||
<header>
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{- with .Description }}<p>{{ . }}</p>{{- end }}
|
||||
</header>
|
||||
|
||||
{{- if .IsHome }}
|
||||
{{- partial "tag-nav.html" . }}
|
||||
{{- end }}
|
||||
|
||||
<section data-layout="grid">
|
||||
{{- range .Pages }}
|
||||
<article>
|
||||
<header>
|
||||
<h3>
|
||||
<a href="{{ .RelPermalink }}" data-wikilink>{{ .Title }}</a>
|
||||
</h3>
|
||||
{{- if not .Date.IsZero }}
|
||||
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "Jan 2, 2006" }}</time>
|
||||
{{- end }}
|
||||
</header>
|
||||
|
||||
{{- with .Summary }}
|
||||
<p>{{ . }}</p>
|
||||
{{- end }}
|
||||
|
||||
{{- with .GetTerms "tags" }}
|
||||
<footer>
|
||||
{{- range . }}
|
||||
<a href="{{ .Permalink }}" data-tag="{{ .Name }}">{{ .Name }}</a>
|
||||
{{- end }}
|
||||
</footer>
|
||||
{{- end }}
|
||||
</article>
|
||||
{{- end }}
|
||||
</section>
|
||||
</section>
|
||||
{{ end }}
|
||||
36
archive/packs/hugo/layouts/_default/single.html
Normal file
36
archive/packs/hugo/layouts/_default/single.html
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{{ define "main" }}
|
||||
<article>
|
||||
<header>
|
||||
<h1>{{ .Title }}</h1>
|
||||
|
||||
{{- if not .Date.IsZero }}
|
||||
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "January 2, 2006" }}</time>
|
||||
{{- end }}
|
||||
|
||||
{{- with .Description }}
|
||||
<p>{{ . }}</p>
|
||||
{{- end }}
|
||||
|
||||
{{- with .GetTerms "tags" }}
|
||||
<nav data-role="tag-cloud" aria-label="Tags">
|
||||
{{- range . }}
|
||||
<a href="{{ .Permalink }}" data-tag="{{ .Name }}">{{ .Name }}</a>
|
||||
{{- end }}
|
||||
</nav>
|
||||
{{- end }}
|
||||
</header>
|
||||
|
||||
{{ .Content }}
|
||||
|
||||
{{- if .PrevInSection }}
|
||||
<footer>
|
||||
{{- with .PrevInSection }}
|
||||
<a href="{{ .RelPermalink }}" rel="prev" data-wikilink>← {{ .Title }}</a>
|
||||
{{- end }}
|
||||
{{- with .NextInSection }}
|
||||
<a href="{{ .RelPermalink }}" rel="next" data-wikilink>{{ .Title }} →</a>
|
||||
{{- end }}
|
||||
</footer>
|
||||
{{- end }}
|
||||
</article>
|
||||
{{ end }}
|
||||
20
archive/packs/hugo/layouts/partials/head.html
Normal file
20
archive/packs/hugo/layouts/partials/head.html
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>
|
||||
{{- if .IsHome -}}
|
||||
{{ .Site.Title }}
|
||||
{{- else -}}
|
||||
{{ .Title }} · {{ .Site.Title }}
|
||||
{{- end -}}
|
||||
</title>
|
||||
{{- with .Description }}<meta name="description" content="{{ . }}">{{- end }}
|
||||
{{- if not .Description }}{{- with .Site.Params.description }}<meta name="description" content="{{ . }}">{{- end }}{{- end }}
|
||||
|
||||
<!-- ASW framework CSS — absURL ensures correct path when served at a subpath -->
|
||||
<link rel="stylesheet" href="{{ "css/asw.css" | absURL }}">
|
||||
<!-- Trentuna theme tokens: Inter, JetBrains Mono, dark palette -->
|
||||
<link rel="stylesheet" href="{{ "css/theme.css" | absURL }}">
|
||||
|
||||
{{- range .AlternativeOutputFormats -}}
|
||||
<link rel="{{ .Rel }}" type="{{ .MediaType.Type }}" href="{{ .Permalink | safeURL }}">
|
||||
{{- end }}
|
||||
21
archive/packs/hugo/layouts/partials/tag-nav.html
Normal file
21
archive/packs/hugo/layouts/partials/tag-nav.html
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{{- /*
|
||||
tag-nav.html — renders all site tags as a navigable tag cloud.
|
||||
|
||||
Usage in a layout:
|
||||
{{ partial "tag-nav.html" . }}
|
||||
|
||||
Outputs: <nav data-role="tag-cloud"> with links to each tag page.
|
||||
The (N) count shows how many pages have each tag.
|
||||
*/ -}}
|
||||
{{- $tags := .Site.Taxonomies.tags -}}
|
||||
{{- if $tags }}
|
||||
<nav data-role="tag-cloud" aria-label="Browse by tag">
|
||||
{{- range $name, $pages := $tags }}
|
||||
{{- $tagPage := site.GetPage (printf "/tags/%s" ($name | urlize)) }}
|
||||
<a href="{{ if $tagPage }}{{ $tagPage.Permalink }}{{ else }}{{ print site.BaseURL "tags/" ($name | urlize) "/" }}{{ end }}" data-tag="{{ $name }}">
|
||||
{{ $name -}}
|
||||
<small>({{ len $pages }})</small>
|
||||
</a>
|
||||
{{- end }}
|
||||
</nav>
|
||||
{{- end }}
|
||||
16
archive/packs/hugo/layouts/shortcodes/callout.html
Normal file
16
archive/packs/hugo/layouts/shortcodes/callout.html
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{{- /*
|
||||
callout shortcode — wraps content in an ASW callout block.
|
||||
|
||||
Usage:
|
||||
{{< callout note >}}
|
||||
Content here. Markdown is rendered.
|
||||
{{< /callout >}}
|
||||
|
||||
First positional param: callout type.
|
||||
Valid types: note, warning, tip, info (maps to ASW data-callout attribute).
|
||||
Default: note
|
||||
*/ -}}
|
||||
{{- $type := .Get 0 | default "note" -}}
|
||||
<aside data-callout="{{ $type }}">
|
||||
{{ .Inner | markdownify }}
|
||||
</aside>
|
||||
20
archive/packs/hugo/layouts/shortcodes/wikilink.html
Normal file
20
archive/packs/hugo/layouts/shortcodes/wikilink.html
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{{- /*
|
||||
wikilink shortcode — renders an internal link with ASW data-wikilink attribute.
|
||||
|
||||
Usage:
|
||||
{{< wikilink "Display Text" "/path/to/page/" >}}
|
||||
|
||||
Param 0 (required): display text
|
||||
Param 1 (optional): href path. Defaults to /display-text-slugified/
|
||||
|
||||
Examples:
|
||||
{{< wikilink "My Note" >}}
|
||||
→ <a href="/my-note/" data-wikilink>My Note</a>
|
||||
|
||||
{{< wikilink "My Note" "/vault/my-note/" >}}
|
||||
→ <a href="/vault/my-note/" data-wikilink>My Note</a>
|
||||
*/ -}}
|
||||
{{- $text := .Get 0 -}}
|
||||
{{- $slug := $text | lower | replace " " "-" -}}
|
||||
{{- $href := .Get 1 | default (printf "/%s/" $slug) -}}
|
||||
<a href="{{ $href | relURL }}" data-wikilink>{{ $text }}</a>
|
||||
82
archive/packs/hugo/static/admin/config.yml
Normal file
82
archive/packs/hugo/static/admin/config.yml
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# Decap CMS configuration for ASW-Hugo pack
|
||||
# Browser-based Markdown editing — no backend server required.
|
||||
#
|
||||
# Setup:
|
||||
# 1. Update `repo` below to your Forgejo repo path (owner/repo)
|
||||
# 2. Update `base_url` to your Forgejo instance URL
|
||||
# 3. Register an OAuth app in Forgejo: Settings → Applications → OAuth2 Apps
|
||||
# - Redirect URL: https://yoursite.com/admin/
|
||||
# 4. Deploy admin/ alongside your Hugo output
|
||||
# 5. Access at https://yoursite.com/admin/
|
||||
|
||||
backend:
|
||||
name: gitea # Forgejo is Gitea-compatible
|
||||
repo: trentuna/vault # ← CHANGE: your repo (owner/repo)
|
||||
branch: main
|
||||
base_url: https://trentuna.com # ← CHANGE: your Forgejo instance URL
|
||||
auth_endpoint: /api/v1/oauth2/
|
||||
|
||||
media_folder: static/images
|
||||
public_folder: /images
|
||||
|
||||
# Slug format for new posts
|
||||
slug:
|
||||
encoding: ascii
|
||||
clean_accents: true
|
||||
|
||||
collections:
|
||||
- name: posts
|
||||
label: Posts
|
||||
label_singular: Post
|
||||
folder: content/posts
|
||||
create: true
|
||||
slug: "{{year}}-{{month}}-{{day}}-{{slug}}"
|
||||
preview_path: "posts/{{slug}}/"
|
||||
fields:
|
||||
- label: Title
|
||||
name: title
|
||||
widget: string
|
||||
- label: Date
|
||||
name: date
|
||||
widget: datetime
|
||||
default: ""
|
||||
date_format: "YYYY-MM-DD"
|
||||
time_format: "HH:mm:ss"
|
||||
format: "YYYY-MM-DDTHH:mm:ssZ"
|
||||
- label: Description
|
||||
name: description
|
||||
widget: string
|
||||
required: false
|
||||
hint: Short summary shown in list views
|
||||
- label: Tags
|
||||
name: tags
|
||||
widget: list
|
||||
required: false
|
||||
hint: Comma-separated tags
|
||||
- label: Draft
|
||||
name: draft
|
||||
widget: boolean
|
||||
default: true
|
||||
- label: Body
|
||||
name: body
|
||||
widget: markdown
|
||||
|
||||
- name: pages
|
||||
label: Pages
|
||||
label_singular: Page
|
||||
folder: content
|
||||
create: true
|
||||
filter:
|
||||
field: _type
|
||||
value: page
|
||||
fields:
|
||||
- label: Title
|
||||
name: title
|
||||
widget: string
|
||||
- label: Description
|
||||
name: description
|
||||
widget: string
|
||||
required: false
|
||||
- label: Body
|
||||
name: body
|
||||
widget: markdown
|
||||
4931
archive/packs/hugo/static/css/asw.css
Normal file
4931
archive/packs/hugo/static/css/asw.css
Normal file
File diff suppressed because it is too large
Load diff
130
archive/packs/hugo/static/css/theme.css
Normal file
130
archive/packs/hugo/static/css/theme.css
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Trentuna theme tokens for ASW-Hugo pack
|
||||
* Overrides ASW custom properties with trentuna-specific values.
|
||||
*
|
||||
* Zero CDN dependencies — system font stacks only, matching ASW's own ethos.
|
||||
* ASW framework (web-fonts.css) uses system fonts by design: zero external requests,
|
||||
* zero layout shift, fonts resolve to native equivalents on every device.
|
||||
*
|
||||
* Inter and JetBrains Mono resolve to system-ui / monospace on most platforms.
|
||||
* To use web fonts: add @font-face declarations to a local fonts.css and
|
||||
* @import it before this file.
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* --- Typography (system font stacks — no external loading) --- */
|
||||
--font-sans: Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', ui-monospace, monospace;
|
||||
|
||||
/* --- Dark palette --- */
|
||||
--color-bg: #0f0f0f;
|
||||
--color-surface: #1a1a1a;
|
||||
--color-surface-2:#222222;
|
||||
--color-border: #2e2e2e;
|
||||
--color-text: #e8e8e8;
|
||||
--color-muted: #888888;
|
||||
--color-accent: #e8c87a; /* warm gold */
|
||||
--color-accent-2: #7aadcf; /* cool blue */
|
||||
--color-danger: #cf7a7a;
|
||||
--color-success: #7acf9a;
|
||||
|
||||
/* --- Spacing scale (matches ASW defaults) --- */
|
||||
--space-xs: 0.25rem;
|
||||
--space-sm: 0.5rem;
|
||||
--space-md: 1rem;
|
||||
--space-lg: 1.5rem;
|
||||
--space-xl: 2.5rem;
|
||||
--space-2xl: 4rem;
|
||||
}
|
||||
|
||||
/* Base */
|
||||
html {
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
|
||||
code, pre, kbd, samp, tt {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* Links */
|
||||
a {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
a:hover {
|
||||
color: var(--color-accent-2);
|
||||
}
|
||||
a[data-wikilink] {
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
header nav {
|
||||
display: flex;
|
||||
gap: var(--space-md);
|
||||
align-items: center;
|
||||
padding: var(--space-md) var(--space-lg);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
header nav a {
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
header nav strong {
|
||||
color: var(--color-text);
|
||||
margin-right: var(--space-md);
|
||||
}
|
||||
|
||||
/* Tag cloud */
|
||||
nav[data-role="tag-cloud"] {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-xs) var(--space-sm);
|
||||
}
|
||||
nav[data-role="tag-cloud"] a {
|
||||
font-size: 0.8rem;
|
||||
text-decoration: none;
|
||||
padding: 0.15em 0.5em;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 2px;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
nav[data-role="tag-cloud"] a:hover {
|
||||
color: var(--color-accent);
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
a[data-tag] {
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-muted);
|
||||
text-decoration: none;
|
||||
}
|
||||
a[data-tag]:hover {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
/* Grid layout */
|
||||
section[data-layout="grid"] {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
/* Article cards in grid */
|
||||
section[data-layout="grid"] article {
|
||||
border: 1px solid var(--color-border);
|
||||
padding: var(--space-md);
|
||||
border-radius: 3px;
|
||||
}
|
||||
section[data-layout="grid"] article:hover {
|
||||
border-color: var(--color-border);
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
padding: var(--space-lg);
|
||||
color: var(--color-muted);
|
||||
border-top: 1px solid var(--color-border);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
30
archive/packs/nginx/autoindex-header.html
Normal file
30
archive/packs/nginx/autoindex-header.html
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/asw/asw.css">
|
||||
<title>Directory Listing</title>
|
||||
<style>
|
||||
body { display: flex; flex-direction: column; min-height: 100vh; }
|
||||
pre { /* nginx autoindex output */
|
||||
background: var(--asw-bg-elevated);
|
||||
border: 1px solid var(--asw-border);
|
||||
border-radius: var(--asw-radius-md);
|
||||
padding: 1.5rem;
|
||||
font-family: var(--asw-font-mono);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.8;
|
||||
overflow-x: auto;
|
||||
}
|
||||
pre a { color: var(--asw-accent); text-decoration: none; }
|
||||
pre a:hover { text-decoration: underline; }
|
||||
main { flex: 1; padding: 2rem; max-width: 80ch; margin: 0 auto; width: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<ul><li><a href="/"><strong>Home</strong></a></li></ul>
|
||||
</nav>
|
||||
<main>
|
||||
<h1>Directory listing</h1>
|
||||
73
archive/packs/nginx/nginx-asw.conf
Normal file
73
archive/packs/nginx/nginx-asw.conf
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# ASW — Agentic Semantic Web site
|
||||
# Self-contained server block. Proxied from trentuna at /asw/.
|
||||
# Future: point asw.trentuna.com directly at port 8044.
|
||||
|
||||
server {
|
||||
listen 8044;
|
||||
listen [::]:8044;
|
||||
|
||||
server_name _;
|
||||
|
||||
root /home/exedev/projects/agentic-semantic-web;
|
||||
index index.html;
|
||||
ssi on;
|
||||
|
||||
error_page 403 /errors/403.html;
|
||||
error_page 404 /errors/404.html;
|
||||
error_page 500 /errors/500.html;
|
||||
error_page 502 /errors/502.html;
|
||||
error_page 503 /errors/503.html;
|
||||
|
||||
location /errors/ {
|
||||
alias /home/exedev/projects/agentic-semantic-web/errors/;
|
||||
internal;
|
||||
}
|
||||
|
||||
# Block source/build files — not for public consumption
|
||||
location ~ ^/(src|content|templates|node_modules|packs|dist)(/?$|/) {
|
||||
return 404;
|
||||
}
|
||||
location ~ \.(sh|lua|lock)$ {
|
||||
return 404;
|
||||
}
|
||||
|
||||
# docs/ — directory listing with ASW styling injected into autoindex responses.
|
||||
# Target patterns unique to nginx autoindex output:
|
||||
# - '<head><title>Index of' — only in autoindex, never in hand-crafted HTML
|
||||
# - '<body bgcolor="white">' — only in autoindex
|
||||
# Hand-crafted HTML pages in /docs/ are NOT affected.
|
||||
location /docs/ {
|
||||
autoindex on;
|
||||
try_files $uri $uri/ =404;
|
||||
|
||||
sub_filter '<head><title>Index of' '<head>
|
||||
<link rel="stylesheet" href="/asw.css">
|
||||
<meta name="color-scheme" content="dark">
|
||||
<style>
|
||||
body{padding:2rem;max-inline-size:64ch}
|
||||
h1{font-size:1rem;font-weight:500;color:var(--text-2);margin-block-end:1.5rem}
|
||||
a{color:var(--blue-3);text-decoration:none}a:hover{text-decoration:underline}
|
||||
pre{font-family:var(--font-mono,"JetBrains Mono",monospace);line-height:1.8}
|
||||
hr{border:none;border-block-start:1px solid var(--stone-7)}
|
||||
</style>
|
||||
<title>Index of';
|
||||
sub_filter '<body bgcolor="white">' '<body>';
|
||||
sub_filter_once on;
|
||||
}
|
||||
|
||||
# lab/ — now has index.html; subdirs with autoindex still styled via sub_filter
|
||||
location /lab/ {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
# Hugo demo — ASW-Hugo pack showcase (serves lab/hugo-demo/public/)
|
||||
location /hugo-demo/ {
|
||||
alias /home/exedev/projects/agentic-semantic-web/lab/hugo-demo/public/;
|
||||
index index.html;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
}
|
||||
59
archive/packs/nginx/nginx-autoindex.conf
Normal file
59
archive/packs/nginx/nginx-autoindex.conf
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# ASW Nginx Directory Listing — config snippet
|
||||
#
|
||||
# nginx's autoindex generates its own HTML that can't be easily replaced.
|
||||
# The cleanest approach: serve autoindex JSON and render it client-side.
|
||||
#
|
||||
# This gives full control over the HTML output while keeping nginx doing
|
||||
# the work of listing directories.
|
||||
|
||||
# ── Option A: JSON autoindex + client-side renderer (recommended) ─────────────
|
||||
#
|
||||
# nginx serves directory listings as JSON; a small JS fragment renders them
|
||||
# with ASW styles. The renderer is served from /asw/autoindex.js.
|
||||
|
||||
location /files/ {
|
||||
# The directory to browse
|
||||
alias /var/www/files/;
|
||||
|
||||
autoindex on;
|
||||
autoindex_format json; # nginx ≥ 1.7.9
|
||||
|
||||
# When the path ends in / (directory request), serve our renderer
|
||||
# instead of the raw JSON. The renderer fetches the JSON itself.
|
||||
index ___nonexistent___; # disable default index file lookup
|
||||
|
||||
# Intercept directory responses and redirect to renderer
|
||||
# (requires auth_request or a small proxy; see Option B for simpler approach)
|
||||
}
|
||||
|
||||
# Serve the ASW autoindex renderer
|
||||
location = /asw/autoindex.js {
|
||||
alias /home/exedev/projects/agentic-semantic-web/packs/nginx/autoindex.js;
|
||||
}
|
||||
|
||||
|
||||
# ── Option B: add_before_body / add_after_body (simplest) ────────────────────
|
||||
#
|
||||
# nginx injects HTML before and after its generated listing.
|
||||
# This wraps the ugly default output in ASW chrome — not perfect but zero JS.
|
||||
#
|
||||
# Requires nginx built with --with-http_addition_module (default on most distros).
|
||||
|
||||
location /browse/ {
|
||||
alias /var/www/browse/;
|
||||
autoindex on;
|
||||
|
||||
# Inject ASW nav before the listing, footer after
|
||||
add_before_body /asw/autoindex-header.html;
|
||||
add_after_body /asw/autoindex-footer.html;
|
||||
}
|
||||
|
||||
location = /asw/autoindex-header.html {
|
||||
alias /home/exedev/projects/agentic-semantic-web/packs/nginx/autoindex-header.html;
|
||||
internal;
|
||||
}
|
||||
|
||||
location = /asw/autoindex-footer.html {
|
||||
alias /home/exedev/projects/agentic-semantic-web/packs/nginx/autoindex-footer.html;
|
||||
internal;
|
||||
}
|
||||
25
archive/packs/nginx/nginx-errors.conf
Normal file
25
archive/packs/nginx/nginx-errors.conf
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# ASW Nginx Error Pages — config snippet
|
||||
#
|
||||
# Paste this into your nginx server block.
|
||||
# Adjust the alias path to where your agentic-semantic-web repo lives.
|
||||
#
|
||||
# Assumes asw.css is served at /asw/asw.css
|
||||
# (see nginx-asw-css.conf for the CSS serving block)
|
||||
|
||||
# ── Error pages ──────────────────────────────────────────────────────────────
|
||||
|
||||
error_page 400 /errors/400.html;
|
||||
error_page 401 /errors/401.html;
|
||||
error_page 403 /errors/403.html;
|
||||
error_page 404 /errors/404.html;
|
||||
error_page 500 /errors/500.html;
|
||||
error_page 502 /errors/502.html;
|
||||
error_page 503 /errors/503.html;
|
||||
|
||||
# Serve error pages from the ASW repo.
|
||||
# `internal` means these URLs are only accessible via internal redirects (error_page),
|
||||
# not directly from the browser — prevents users from 200ing your error pages.
|
||||
location /errors/ {
|
||||
alias /home/exedev/projects/agentic-semantic-web/errors/;
|
||||
internal;
|
||||
}
|
||||
105
archive/packs/pandoc/README.md
Normal file
105
archive/packs/pandoc/README.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
---
|
||||
title: "ASW Pandoc Pack"
|
||||
description: "Convert markdown to ASW-styled HTML with one command"
|
||||
layout: prose
|
||||
---
|
||||
|
||||
# ASW Pandoc Pack
|
||||
|
||||
Converts GFM markdown to fully ASW-styled HTML. Write markdown natively; the pack renders it with correct `data-attribute` semantics.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
pandoc input.md \
|
||||
--from gfm \
|
||||
--lua-filter packs/pandoc/asw.lua \
|
||||
--template packs/pandoc/asw.html5 \
|
||||
-o output.html
|
||||
```
|
||||
|
||||
## What it does
|
||||
|
||||
### Semantic mapping (asw.lua)
|
||||
|
||||
| Markdown | Output HTML |
|
||||
|---|---|
|
||||
| `- [ ] task` | `<li data-task="todo">task</li>` |
|
||||
| `- [x] done` | `<li data-task="done">done</li>` |
|
||||
| `> [!NOTE]` | `<div data-callout="note">` |
|
||||
| `> [!WARNING]` | `<div data-callout="warning">` |
|
||||
| `> [!TIP]` | `<div data-callout="tip">` |
|
||||
| `> [!ERROR]` | `<div data-callout="error">` |
|
||||
| `[[target]]` | `<a data-wikilink href="#target">target</a>` |
|
||||
| `[[target\|label]]` | `<a data-wikilink href="#target">label</a>` |
|
||||
|
||||
Callout aliases: `[!CAUTION]` → warning, `[!IMPORTANT]`/`[!INFO]` → note, `[!DANGER]` → error.
|
||||
|
||||
Wikilinks: punctuation attached to `]]` is preserved outside the link element. Unresolved link detection is not automatic — the href slug is always generated; resolution is a build-time concern.
|
||||
|
||||
### Frontmatter → data-attributes (asw.html5)
|
||||
|
||||
YAML frontmatter keys map to page-level ASW properties:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: "Session Report — 2026-04-02"
|
||||
description: "Summary of what happened"
|
||||
layout: prose # → data-layout on <main> (default: prose)
|
||||
status: done # → data-status on <main> and in header badge
|
||||
date: 2026-04-02 # → <time datetime="...">
|
||||
section: "Sessions" # → <p data-text="eyebrow"> above <h1>
|
||||
tags:
|
||||
- session
|
||||
- autonomous
|
||||
# Session metadata (if agent-authored):
|
||||
agent: vigilio
|
||||
model: claude-sonnet-4-6
|
||||
mode: autonomous # → <span data-mode="autonomous">
|
||||
session: 10
|
||||
---
|
||||
```
|
||||
|
||||
Keys not set are simply omitted — no empty elements in the output.
|
||||
|
||||
## Layout values
|
||||
|
||||
The `layout` key maps directly to `data-layout` on `<main>`:
|
||||
|
||||
| Value | Use case |
|
||||
|---|---|
|
||||
| `prose` (default) | Articles, notes, documentation |
|
||||
| `docs` | Two-column docs layout |
|
||||
| `fluid` | Full-width, no max-width |
|
||||
|
||||
Any `data-layout` value supported by `asw.css` works here.
|
||||
|
||||
## Status values
|
||||
|
||||
| Value | Meaning |
|
||||
|---|---|
|
||||
| `done` | Complete |
|
||||
| `awake` | Active agent/session |
|
||||
| `sleeping` | Inactive |
|
||||
| `blocked` | Waiting on dependency |
|
||||
|
||||
## CSS dependency
|
||||
|
||||
The pack generates HTML that requires `asw.css` for styling. The template links `/asw.css` by default. Override with `--variable css=/path/to/asw.css`.
|
||||
|
||||
## Relationship to the build pipeline
|
||||
|
||||
`build.sh` uses this pack (via the `doc.html` template) for `content/` → `docs/` compilation. The standalone pack is also useful for:
|
||||
|
||||
- Rendering vault notes as public pages
|
||||
- One-off markdown → ASW HTML conversion
|
||||
- Integration into other build systems (trentuna-web, agent pipelines)
|
||||
|
||||
## Files
|
||||
|
||||
```
|
||||
packs/pandoc/
|
||||
├── asw.html5 pandoc HTML5 template — page structure, frontmatter vars
|
||||
├── asw.lua Lua filter — task lists, callouts, wikilinks
|
||||
└── README.md this file
|
||||
```
|
||||
67
archive/packs/pandoc/asw.html5
Normal file
67
archive/packs/pandoc/asw.html5
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>$if(title)$$title$$else$Untitled$endif$</title>
|
||||
$if(description)$ <meta name="description" content="$description$">
|
||||
$endif$
|
||||
$-- CSS: link to asw.css (adjust path for your deployment) $
|
||||
<link rel="stylesheet" href="/asw.css">
|
||||
$for(css)$ <link rel="stylesheet" href="$css$">
|
||||
$endfor$
|
||||
$if(header-includes)$$header-includes$$endif$
|
||||
</head>
|
||||
<body>
|
||||
|
||||
$-- Page header: title + optional eyebrow / metadata $
|
||||
$if(title)$
|
||||
<header>
|
||||
<hgroup>
|
||||
$if(section)$ <p data-text="eyebrow">$section$</p>
|
||||
$endif$ <h1>$title$</h1>
|
||||
$if(description)$ <p data-text="lead">$description$</p>
|
||||
$endif$ </hgroup>
|
||||
|
||||
$-- Session metadata block (agent-authored content) $
|
||||
$if(agent)$
|
||||
<div data-session>
|
||||
<span data-session-meta>
|
||||
$if(date)$ <time datetime="$date$">$date$</time>$endif$
|
||||
$if(agent)$ <span>$agent$</span>$endif$
|
||||
$if(model)$ <span>$model$</span>$endif$
|
||||
$if(mode)$ <span data-mode="$mode$">$mode$</span>$endif$
|
||||
$if(session)$ <span>session $session$</span>$endif$
|
||||
</span>
|
||||
$if(status)$ <span data-status="$status$">$status$</span>$endif$
|
||||
</div>
|
||||
$endif$
|
||||
|
||||
$-- Tags $
|
||||
$if(tags)$
|
||||
<nav aria-label="tags">
|
||||
$for(tags)$ <a data-tag href="/tags/$tags$">$tags$</a>
|
||||
$endfor$ </nav>
|
||||
$endif$
|
||||
|
||||
</header>
|
||||
$endif$
|
||||
|
||||
$-- Main content — layout from frontmatter or default to prose $
|
||||
<main$if(layout)$ data-layout="$layout$"$else$ data-layout="prose"$endif$$if(status)$ data-status="$status$"$endif$>
|
||||
$body$
|
||||
</main>
|
||||
|
||||
$-- Footer $
|
||||
$if(date)$
|
||||
<footer>
|
||||
<small>
|
||||
$if(date)$ <time datetime="$date$">$date$</time>$endif$
|
||||
$if(author)$ · $for(author)$$author$$sep$, $endfor$$endif$
|
||||
</small>
|
||||
</footer>
|
||||
$endif$
|
||||
|
||||
$if(include-after)$$include-after$$endif$
|
||||
</body>
|
||||
</html>
|
||||
272
archive/packs/pandoc/asw.lua
Normal file
272
archive/packs/pandoc/asw.lua
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
-- asw.lua — Pandoc Lua filter for Agentic Semantic Web (ASW)
|
||||
-- Converts GFM markdown constructs to ASW data-attribute HTML
|
||||
--
|
||||
-- Usage:
|
||||
-- pandoc input.md --from gfm --lua-filter asw.lua --template asw.html5 -o output.html
|
||||
--
|
||||
-- Transforms:
|
||||
-- - [ ] item → <li data-task="todo">
|
||||
-- - [x] item → <li data-task="done">
|
||||
-- > [!NOTE] → <div data-callout="note">
|
||||
-- > [!WARNING] → <div data-callout="warning">
|
||||
-- > [!TIP] → <div data-callout="tip">
|
||||
-- > [!ERROR] → <div data-callout="error">
|
||||
-- [[target]] → <a data-wikilink href="#target">target</a>
|
||||
-- [[target|label]] → <a data-wikilink href="#target">label</a>
|
||||
|
||||
-- ── Callouts ─────────────────────────────────────────────────────────────────
|
||||
-- GitHub callout syntax: > [!TYPE]\n> content → <div data-callout="type">
|
||||
|
||||
local CALLOUT_MAP = {
|
||||
NOTE = "note",
|
||||
INFO = "note",
|
||||
IMPORTANT = "note",
|
||||
WARNING = "warning",
|
||||
CAUTION = "warning",
|
||||
TIP = "tip",
|
||||
ERROR = "error",
|
||||
DANGER = "error",
|
||||
}
|
||||
|
||||
local CALLOUT_LABEL = {
|
||||
note = "Note",
|
||||
warning = "Warning",
|
||||
tip = "Tip",
|
||||
error = "Error",
|
||||
}
|
||||
|
||||
function BlockQuote(el)
|
||||
local first = el.content[1]
|
||||
if not first or (first.t ~= "Para" and first.t ~= "Plain") then return el end
|
||||
|
||||
-- The first inline should be a Str like "[!NOTE]"
|
||||
local first_inline = first.content and first.content[1]
|
||||
if not first_inline or first_inline.t ~= "Str" then return el end
|
||||
|
||||
local marker = first_inline.text:match("^%[!(%u+)%]$")
|
||||
if not marker then return el end
|
||||
|
||||
local callout_type = CALLOUT_MAP[marker]
|
||||
if not callout_type then return el end
|
||||
|
||||
local label = CALLOUT_LABEL[callout_type] or marker:sub(1,1) .. marker:sub(2):lower()
|
||||
|
||||
local blocks = {
|
||||
pandoc.RawBlock("html", '<div data-callout="' .. callout_type .. '">'),
|
||||
pandoc.RawBlock("html", '<p data-callout-title>' .. label .. '</p>'),
|
||||
}
|
||||
|
||||
-- First para: strip the [!MARKER] token and optional following SoftBreak/Space
|
||||
local inlines = first.content
|
||||
local start_i = 2
|
||||
if inlines[start_i] and
|
||||
(inlines[start_i].t == "SoftBreak" or inlines[start_i].t == "Space") then
|
||||
start_i = start_i + 1
|
||||
end
|
||||
|
||||
local rest_inlines = {}
|
||||
for i = start_i, #inlines do
|
||||
table.insert(rest_inlines, inlines[i])
|
||||
end
|
||||
if #rest_inlines > 0 then
|
||||
table.insert(blocks, pandoc.Para(rest_inlines))
|
||||
end
|
||||
|
||||
-- Remaining blocks in the blockquote
|
||||
for i = 2, #el.content do
|
||||
table.insert(blocks, el.content[i])
|
||||
end
|
||||
|
||||
table.insert(blocks, pandoc.RawBlock("html", '</div>'))
|
||||
return blocks
|
||||
end
|
||||
|
||||
-- ── Task Lists ───────────────────────────────────────────────────────────────
|
||||
-- GFM: - [ ] → <li data-task="todo">, - [x] → <li data-task="done">
|
||||
-- Pandoc (GFM mode) emits checkboxes as RawInline html: <input type="checkbox" ...>
|
||||
|
||||
-- Pandoc 3.x encodes GFM task list checkboxes as Unicode Str:
|
||||
-- ☒ (U+2612) = checked/done, ☐ (U+2610) = unchecked/todo
|
||||
local CHECKBOX_DONE = "\xe2\x98\x92" -- ☒ UTF-8
|
||||
local CHECKBOX_TODO = "\xe2\x98\x90" -- ☐ UTF-8
|
||||
|
||||
local function checkbox_state(inline)
|
||||
if inline and inline.t == "Str" then
|
||||
if inline.text == CHECKBOX_DONE then return "done" end
|
||||
if inline.text == CHECKBOX_TODO then return "todo" end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function BulletList(el)
|
||||
-- First pass: check if any item is a task
|
||||
local has_tasks = false
|
||||
for _, item in ipairs(el.content) do
|
||||
local first = item[1]
|
||||
if first and (first.t == "Plain" or first.t == "Para") and first.content[1] then
|
||||
if checkbox_state(first.content[1]) then
|
||||
has_tasks = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if not has_tasks then return el end
|
||||
|
||||
-- Rebuild as explicit HTML with data-task attributes
|
||||
local parts = { pandoc.RawBlock("html", "<ul>") }
|
||||
|
||||
for _, item in ipairs(el.content) do
|
||||
local first = item[1]
|
||||
local state = nil
|
||||
if first and (first.t == "Plain" or first.t == "Para") and first.content[1] then
|
||||
state = checkbox_state(first.content[1])
|
||||
end
|
||||
|
||||
if state then
|
||||
table.insert(parts, pandoc.RawBlock("html", '<li data-task="' .. state .. '">'))
|
||||
-- Strip checkbox inline (index 1) and optional space (index 2)
|
||||
local trimmed = {}
|
||||
for i, inline in ipairs(first.content) do
|
||||
if i == 1 then
|
||||
-- skip checkbox
|
||||
elseif i == 2 and inline.t == "Space" then
|
||||
-- skip leading space after checkbox
|
||||
else
|
||||
table.insert(trimmed, inline)
|
||||
end
|
||||
end
|
||||
if #trimmed > 0 then
|
||||
table.insert(parts, pandoc.Plain(trimmed))
|
||||
end
|
||||
-- Remaining blocks in this item
|
||||
for i = 2, #item do
|
||||
table.insert(parts, item[i])
|
||||
end
|
||||
table.insert(parts, pandoc.RawBlock("html", "</li>"))
|
||||
else
|
||||
-- Regular list item — emit verbatim
|
||||
table.insert(parts, pandoc.RawBlock("html", "<li>"))
|
||||
for _, block in ipairs(item) do
|
||||
table.insert(parts, block)
|
||||
end
|
||||
table.insert(parts, pandoc.RawBlock("html", "</li>"))
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(parts, pandoc.RawBlock("html", "</ul>"))
|
||||
return parts
|
||||
end
|
||||
|
||||
-- ── Wikilinks ─────────────────────────────────────────────────────────────────
|
||||
-- [[target|label]] or [[target]] → <a data-wikilink href="#slug">label</a>
|
||||
--
|
||||
-- Pandoc 3.x tokenizes at whitespace, so [[target|label with spaces]] arrives as
|
||||
-- multiple Str/Space tokens. process_wikilinks coalesces adjacent Str+Space runs
|
||||
-- into a combined string before scanning for [[...]] patterns, then reconstructs
|
||||
-- the inline list. Non-Str/Space inlines (Bold, Emph, Code, etc.) pass through.
|
||||
|
||||
local function slugify(text)
|
||||
return text:lower():gsub("[%s_]+", "-"):gsub("[^%w%-]", "")
|
||||
end
|
||||
|
||||
-- Scan combined for [[...]] patterns and emit inlines.
|
||||
-- Returns a list of inlines with wikilinks replaced, plus a changed flag.
|
||||
local function scan_for_wikilinks(combined)
|
||||
local parts = {}
|
||||
local pos = 1
|
||||
local changed = false
|
||||
|
||||
while pos <= #combined do
|
||||
local s, e = combined:find("%[%[", pos)
|
||||
if not s then
|
||||
if pos <= #combined then
|
||||
table.insert(parts, pandoc.Str(combined:sub(pos)))
|
||||
end
|
||||
break
|
||||
end
|
||||
if s > pos then
|
||||
table.insert(parts, pandoc.Str(combined:sub(pos, s - 1)))
|
||||
end
|
||||
local close_s, close_e = combined:find("%]%]", e + 1)
|
||||
if not close_s then
|
||||
table.insert(parts, pandoc.Str("[["))
|
||||
pos = e + 1
|
||||
else
|
||||
local inner = combined:sub(e + 1, close_s - 1)
|
||||
local target, label = inner:match("^([^|]+)|(.+)$")
|
||||
if not target then
|
||||
target = inner
|
||||
label = inner
|
||||
end
|
||||
target = target:match("^%s*(.-)%s*$")
|
||||
label = label:match("^%s*(.-)%s*$")
|
||||
table.insert(parts, pandoc.RawInline("html",
|
||||
'<a data-wikilink href="#' .. slugify(target) .. '">' .. label .. '</a>'))
|
||||
pos = close_e + 1
|
||||
changed = true
|
||||
end
|
||||
end
|
||||
|
||||
return parts, changed
|
||||
end
|
||||
|
||||
local function process_wikilinks(inlines)
|
||||
-- Pass 1: coalesce runs of Str+Space into combined strings, scan for wikilinks.
|
||||
-- Non-text inlines (Emph, Strong, Code, RawInline, etc.) flush any pending run first.
|
||||
local result = {}
|
||||
local changed = false
|
||||
local i = 1
|
||||
|
||||
while i <= #inlines do
|
||||
local el = inlines[i]
|
||||
|
||||
if el.t == "Str" or el.t == "Space" then
|
||||
-- Collect a contiguous Str+Space run
|
||||
local run = {}
|
||||
local combined = ""
|
||||
while i <= #inlines and (inlines[i].t == "Str" or inlines[i].t == "Space") do
|
||||
if inlines[i].t == "Str" then
|
||||
combined = combined .. inlines[i].text
|
||||
else
|
||||
combined = combined .. " "
|
||||
end
|
||||
table.insert(run, inlines[i])
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if combined:find("%[%[") then
|
||||
local parts, any_changed = scan_for_wikilinks(combined)
|
||||
if any_changed then
|
||||
for _, part in ipairs(parts) do table.insert(result, part) end
|
||||
changed = true
|
||||
else
|
||||
-- No wikilinks resolved — emit original tokens unchanged
|
||||
for _, tok in ipairs(run) do table.insert(result, tok) end
|
||||
end
|
||||
else
|
||||
-- No [[ at all — emit original tokens unchanged
|
||||
for _, tok in ipairs(run) do table.insert(result, tok) end
|
||||
end
|
||||
else
|
||||
table.insert(result, el)
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
return changed and result or nil
|
||||
end
|
||||
|
||||
function Para(el)
|
||||
local r = process_wikilinks(el.content)
|
||||
if r then return pandoc.Para(r) end
|
||||
end
|
||||
|
||||
function Plain(el)
|
||||
local r = process_wikilinks(el.content)
|
||||
if r then return pandoc.Plain(r) end
|
||||
end
|
||||
|
||||
function Header(el)
|
||||
local r = process_wikilinks(el.content)
|
||||
if r then return pandoc.Header(el.level, r, el.attr) end
|
||||
end
|
||||
50
archive/packs/pandoc/templates/doc.html
Normal file
50
archive/packs/pandoc/templates/doc.html
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!--#include virtual="/_include/head.html" -->
|
||||
<title>$title$ — ASW Docs</title>
|
||||
$if(description)$ <meta name="description" content="$description$">
|
||||
$endif$</head>
|
||||
<body>
|
||||
<!--#include virtual="/_include/nav.html" -->
|
||||
|
||||
<div data-layout="docs">
|
||||
|
||||
<!--#include virtual="/_include/sidebar.html" -->
|
||||
|
||||
<article>
|
||||
<h1>$title$</h1>
|
||||
$if(description)$ <p data-text="lead">$description$</p>
|
||||
$endif$
|
||||
$body$
|
||||
|
||||
$if(prev-url)$
|
||||
<nav data-role="prev-next">
|
||||
$if(prev-url)$ <a href="$prev-url$" rel="prev">
|
||||
<small>← Previous</small>
|
||||
<span>$prev-title$</span>
|
||||
</a>
|
||||
$endif$
|
||||
$if(next-url)$ <a href="$next-url$" rel="next">
|
||||
<small>Next →</small>
|
||||
<span>$next-title$</span>
|
||||
</a>
|
||||
$endif$
|
||||
</nav>
|
||||
$endif$
|
||||
</article>
|
||||
|
||||
<aside data-toc>
|
||||
<nav aria-label="On this page" data-nav="toc">
|
||||
<small>On this page</small>
|
||||
<ul></ul>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
|
||||
<!--#include virtual="/_include/footer.html" -->
|
||||
|
||||
<script src="/docs/toc-spy.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
22
archive/packs/pandoc/templates/example.html
Normal file
22
archive/packs/pandoc/templates/example.html
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!--#include virtual="/_include/head.html" -->
|
||||
<title>$title$ — ASW Examples</title>
|
||||
$if(description)$ <meta name="description" content="$description$">
|
||||
$endif$</head>
|
||||
<body>
|
||||
<!--#include virtual="/_include/nav.html" -->
|
||||
|
||||
<main>
|
||||
<header>
|
||||
<h1>$title$</h1>
|
||||
$if(description)$ <p data-text="lead">$description$</p>
|
||||
$endif$ </header>
|
||||
|
||||
$body$
|
||||
</main>
|
||||
|
||||
<!--#include virtual="/_include/footer.html" -->
|
||||
</body>
|
||||
</html>
|
||||
20
archive/packs/pandoc/templates/page.html
Normal file
20
archive/packs/pandoc/templates/page.html
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!--#include virtual="/_include/head.html" -->
|
||||
<title>$title$ — ASW</title>
|
||||
$if(description)$ <meta name="description" content="$description$">
|
||||
$endif$</head>
|
||||
<body>
|
||||
<!--#include virtual="/_include/nav.html" -->
|
||||
|
||||
<main data-layout="prose">
|
||||
<h1>$title$</h1>
|
||||
$if(description)$ <p data-text="lead">$description$</p>
|
||||
$endif$
|
||||
$body$
|
||||
</main>
|
||||
|
||||
<!--#include virtual="/_include/footer.html" -->
|
||||
</body>
|
||||
</html>
|
||||
13
archive/packs/pandoc/templates/pattern.html
Normal file
13
archive/packs/pandoc/templates/pattern.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!--#include virtual="/_include/head.html" -->
|
||||
<title>$title$ — ASW</title>
|
||||
$if(description)$ <meta name="description" content="$description$">
|
||||
$endif$</head>
|
||||
<body>
|
||||
<!--#include virtual="/_include/nav.html" -->
|
||||
$body$
|
||||
<!--#include virtual="/_include/footer.html" -->
|
||||
</body>
|
||||
</html>
|
||||
35
archive/packs/python/README.md
Normal file
35
archive/packs/python/README.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# ASW Python Dev Server Pack
|
||||
|
||||
Drop-in replacement for `python -m http.server` with ASW-styled error pages and directory listings.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Copy to your project
|
||||
cp asw_server.py /path/to/project/
|
||||
|
||||
# Run it
|
||||
python asw_server.py # port 8000
|
||||
python asw_server.py 3000 # custom port
|
||||
python asw_server.py 3000 /dir # custom port + custom directory
|
||||
```
|
||||
|
||||
## What you get
|
||||
|
||||
- **ASW-styled error pages** for all HTTP error codes (403, 404, 500, etc.)
|
||||
- **ASW-styled directory listings** replacing the browser-default autoindex
|
||||
- **Auto port fallback** — if 8000 is busy, tries 8001, 8002… up to 10 attempts
|
||||
- **Inline styles** — no external CSS dependency, works even when serving offline
|
||||
|
||||
## How it works
|
||||
|
||||
`ASWHandler` subclasses `SimpleHTTPRequestHandler` and overrides:
|
||||
- `send_error()` — returns ASW-styled HTML instead of the default ugly page
|
||||
- `list_directory()` — returns a clean table layout instead of raw `<pre>`
|
||||
|
||||
Styles are inlined directly in the response — no link to `asw.css` required.
|
||||
The colour scheme matches the full ASW dark theme.
|
||||
|
||||
## No dependencies
|
||||
|
||||
Just Python 3 standard library. Nothing to install.
|
||||
348
archive/packs/python/asw_server.py
Normal file
348
archive/packs/python/asw_server.py
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
ASW Dev Server — http.server with ASW-styled error pages.
|
||||
|
||||
Drop this in any directory and run it instead of `python -m http.server`.
|
||||
All error responses (403, 404, 500, etc.) are styled with ASW CSS.
|
||||
|
||||
Usage:
|
||||
python asw_server.py # serve on port 8000
|
||||
python asw_server.py 3000 # custom port
|
||||
python asw_server.py 3000 /dir # custom port + directory
|
||||
|
||||
The server inlines ASW styles so no external dependencies are needed.
|
||||
"""
|
||||
|
||||
import http.server
|
||||
import sys
|
||||
import os
|
||||
import socket
|
||||
import urllib.parse
|
||||
import html
|
||||
|
||||
|
||||
# ── Minimal ASW inline styles ─────────────────────────────────────────────────
|
||||
# Reduced version for standalone error pages — no external deps.
|
||||
|
||||
ASW_INLINE = """
|
||||
:root {
|
||||
--asw-bg: #0d1117;
|
||||
--asw-bg-elevated: #161b22;
|
||||
--asw-bg-overlay: #1c2128;
|
||||
--asw-text: #e6edf3;
|
||||
--asw-text-secondary: #8b949e;
|
||||
--asw-text-muted: #484f58;
|
||||
--asw-accent: #3fb950;
|
||||
--asw-border: #30363d;
|
||||
--asw-font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
||||
--asw-font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', ui-monospace, monospace;
|
||||
}
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
html { font-size: 16px; }
|
||||
body {
|
||||
background: var(--asw-bg);
|
||||
color: var(--asw-text);
|
||||
font-family: var(--asw-font-body);
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-bottom: 1px solid var(--asw-border);
|
||||
background: var(--asw-bg-elevated);
|
||||
}
|
||||
nav a { color: var(--asw-text); text-decoration: none; font-weight: 600; }
|
||||
nav a:hover { color: var(--asw-accent); }
|
||||
.badge {
|
||||
font-family: var(--asw-font-mono);
|
||||
font-size: 0.75rem;
|
||||
color: var(--asw-text-muted);
|
||||
background: var(--asw-bg-overlay);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
border: 1px solid var(--asw-border);
|
||||
}
|
||||
main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
.error-card {
|
||||
max-width: 42ch;
|
||||
text-align: center;
|
||||
}
|
||||
.error-code {
|
||||
font-family: var(--asw-font-mono);
|
||||
font-size: 6rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: var(--asw-text-muted);
|
||||
letter-spacing: -0.05em;
|
||||
margin: 0 0 0.5rem;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 1rem;
|
||||
font-size: 1.5rem;
|
||||
color: var(--asw-text);
|
||||
}
|
||||
p { margin: 0 0 0.75rem; color: var(--asw-text-secondary); }
|
||||
a.button {
|
||||
display: inline-block;
|
||||
margin-top: 1.5rem;
|
||||
padding: 0.5rem 1.25rem;
|
||||
background: transparent;
|
||||
color: var(--asw-accent);
|
||||
border: 1px solid var(--asw-accent);
|
||||
border-radius: 0.375rem;
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
a.button:hover { background: rgba(63, 185, 80, 0.1); }
|
||||
code {
|
||||
font-family: var(--asw-font-mono);
|
||||
font-size: 0.85em;
|
||||
background: var(--asw-bg-overlay);
|
||||
padding: 0.1em 0.35em;
|
||||
border-radius: 0.2rem;
|
||||
color: var(--asw-text-secondary);
|
||||
}
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--asw-text-muted);
|
||||
border-top: 1px solid var(--asw-border);
|
||||
}
|
||||
footer a { color: var(--asw-text-muted); }
|
||||
|
||||
/* Directory listing */
|
||||
.dir-listing { max-width: 72ch; width: 100%; }
|
||||
.dir-listing h1 { font-size: 1.25rem; margin-bottom: 1.5rem; text-align: left; }
|
||||
.dir-listing h1 code { font-size: 1rem; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: 0.5rem 0.75rem; text-align: left; border-bottom: 1px solid var(--asw-border); }
|
||||
th { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--asw-text-muted); }
|
||||
td a { color: var(--asw-accent); text-decoration: none; }
|
||||
td a:hover { text-decoration: underline; }
|
||||
.size { color: var(--asw-text-muted); font-family: var(--asw-font-mono); font-size: 0.8rem; }
|
||||
.parent { color: var(--asw-text-muted); }
|
||||
"""
|
||||
|
||||
|
||||
ERROR_MESSAGES = {
|
||||
400: ("Bad Request", "The server couldn't understand the request."),
|
||||
401: ("Unauthorized", "Authentication is required to access this resource."),
|
||||
403: ("Forbidden", "You don't have permission to access this path."),
|
||||
404: ("Not Found", "The file or directory you requested doesn't exist."),
|
||||
405: ("Method Not Allowed", "That HTTP method isn't supported here."),
|
||||
408: ("Request Timeout", "The request took too long to process."),
|
||||
500: ("Server Error", "Something went wrong on the server."),
|
||||
501: ("Not Implemented", "The server doesn't support that feature."),
|
||||
503: ("Service Unavailable", "The server is temporarily unable to handle requests."),
|
||||
}
|
||||
|
||||
|
||||
def error_page(code: int, path: str = "") -> bytes:
|
||||
title, message = ERROR_MESSAGES.get(code, ("Error", "An error occurred."))
|
||||
path_hint = f"<p>Path: <code>{html.escape(path)}</code></p>" if path else ""
|
||||
body = f"""<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{code} — {title}</title>
|
||||
<style>{ASW_INLINE}</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<a href="/">⌂ Dev Server</a>
|
||||
<span class="badge">http.server</span>
|
||||
</nav>
|
||||
<main>
|
||||
<div class="error-card">
|
||||
<p class="error-code">{code}</p>
|
||||
<h1>{title}</h1>
|
||||
<p>{message}</p>
|
||||
{path_hint}
|
||||
<a class="button" href="/">← Back to root</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer>ASW Dev Server · <a href="https://github.com/trentuna/agentic-semantic-web">agentic-semantic-web</a></footer>
|
||||
</body>
|
||||
</html>"""
|
||||
return body.encode("utf-8")
|
||||
|
||||
|
||||
def dir_listing_page(path: str, display_path: str, entries: list) -> bytes:
|
||||
rows = []
|
||||
if display_path != "/":
|
||||
parent = "/" + "/".join(display_path.strip("/").split("/")[:-1])
|
||||
rows.append(f'<tr><td class="parent"><a href="{parent or "/"}">../</a></td><td class="size">—</td></tr>')
|
||||
|
||||
for name, is_dir, size in sorted(entries, key=lambda e: (not e[1], e[0].lower())):
|
||||
link_name = html.escape(name) + ("/" if is_dir else "")
|
||||
href = urllib.parse.quote(name) + ("/" if is_dir else "")
|
||||
size_str = "—" if is_dir else _human_size(size)
|
||||
rows.append(f'<tr><td><a href="{href}">{link_name}</a></td><td class="size">{size_str}</td></tr>')
|
||||
|
||||
rows_html = "\n ".join(rows)
|
||||
body = f"""<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Index of {html.escape(display_path)}</title>
|
||||
<style>{ASW_INLINE}</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<a href="/">⌂ Dev Server</a>
|
||||
<span class="badge">http.server</span>
|
||||
</nav>
|
||||
<main>
|
||||
<div class="dir-listing">
|
||||
<h1>Index of <code>{html.escape(display_path)}</code></h1>
|
||||
<table>
|
||||
<thead><tr><th>Name</th><th>Size</th></tr></thead>
|
||||
<tbody>
|
||||
{rows_html}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
<footer>ASW Dev Server · <a href="https://github.com/trentuna/agentic-semantic-web">agentic-semantic-web</a></footer>
|
||||
</body>
|
||||
</html>"""
|
||||
return body.encode("utf-8")
|
||||
|
||||
|
||||
def _human_size(n: int) -> str:
|
||||
for unit in ("B", "KB", "MB", "GB"):
|
||||
if n < 1024:
|
||||
return f"{n:.0f} {unit}" if unit == "B" else f"{n:.1f} {unit}"
|
||||
n /= 1024
|
||||
return f"{n:.1f} TB"
|
||||
|
||||
|
||||
class ASWHandler(http.server.SimpleHTTPRequestHandler):
|
||||
"""SimpleHTTPRequestHandler with ASW-styled error pages and directory listings."""
|
||||
|
||||
server_version = "ASWServer/1.0"
|
||||
|
||||
def send_error(self, code, message=None, explain=None):
|
||||
"""Override to send ASW-styled error pages."""
|
||||
try:
|
||||
short, long = self.responses[code]
|
||||
except KeyError:
|
||||
short, long = "???", "???"
|
||||
if message is None:
|
||||
message = short
|
||||
if explain is None:
|
||||
explain = long
|
||||
|
||||
# Log to terminal
|
||||
self.log_error("code %d, message %s", code, message)
|
||||
|
||||
# Build ASW error page
|
||||
path = getattr(self, "path", "")
|
||||
content = error_page(code, path)
|
||||
|
||||
self.send_response(code, message)
|
||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||
self.send_header("Content-Length", str(len(content)))
|
||||
self.send_header("Connection", "close")
|
||||
self.end_headers()
|
||||
if self.command != "HEAD" and code >= 200 and code not in (204, 304):
|
||||
self.wfile.write(content)
|
||||
|
||||
def list_directory(self, path):
|
||||
"""Override to send ASW-styled directory listing."""
|
||||
try:
|
||||
names = os.listdir(path)
|
||||
except OSError:
|
||||
self.send_error(403, "No permission to list directory")
|
||||
return None
|
||||
|
||||
display_path = urllib.parse.unquote(self.path)
|
||||
entries = []
|
||||
for name in names:
|
||||
full = os.path.join(path, name)
|
||||
is_dir = os.path.isdir(full)
|
||||
size = 0
|
||||
if not is_dir:
|
||||
try:
|
||||
size = os.path.getsize(full)
|
||||
except OSError:
|
||||
pass
|
||||
entries.append((name, is_dir, size))
|
||||
|
||||
content = dir_listing_page(path, display_path, entries)
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||
self.send_header("Content-Length", str(len(content)))
|
||||
self.end_headers()
|
||||
self.wfile.write(content)
|
||||
return None
|
||||
|
||||
def log_message(self, fmt, *args):
|
||||
"""Slightly cleaner terminal output."""
|
||||
print(f" {self.address_string()} — {fmt % args}")
|
||||
|
||||
|
||||
def main():
|
||||
port = 8000
|
||||
directory = "."
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
try:
|
||||
port = int(sys.argv[1])
|
||||
except ValueError:
|
||||
print(f"Usage: {sys.argv[0]} [port] [directory]", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if len(sys.argv) > 2:
|
||||
directory = sys.argv[2]
|
||||
|
||||
directory = os.path.abspath(directory)
|
||||
|
||||
handler = lambda *args, **kwargs: ASWHandler(*args, directory=directory, **kwargs)
|
||||
|
||||
# Find a free port if needed (dev convenience)
|
||||
for attempt in range(10):
|
||||
try:
|
||||
server = http.server.HTTPServer(("", port + attempt), handler)
|
||||
actual_port = port + attempt
|
||||
break
|
||||
except OSError:
|
||||
if attempt == 9:
|
||||
print(f"Could not bind to ports {port}–{port+9}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
hostname = socket.gethostname()
|
||||
print(f"\n ASW Dev Server")
|
||||
print(f" ──────────────────────────────────")
|
||||
print(f" Serving: {directory}")
|
||||
print(f" Local: http://localhost:{actual_port}")
|
||||
print(f" Network: http://{hostname}:{actual_port}")
|
||||
print(f" ──────────────────────────────────")
|
||||
print(f" Press Ctrl+C to stop\n")
|
||||
|
||||
try:
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\n Stopped.")
|
||||
server.server_close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
43
archive/packs/shared/errors/403.html
Normal file
43
archive/packs/shared/errors/403.html
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/asw.css">
|
||||
<title>403 — Forbidden</title>
|
||||
<style>
|
||||
body { display: flex; flex-direction: column; min-height: 100vh; }
|
||||
main { flex: 1; display: flex; align-items: center; justify-content: center; }
|
||||
.error-card { max-width: 40ch; text-align: center; }
|
||||
.error-code {
|
||||
font-family: var(--asw-font-mono);
|
||||
font-size: 6rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: var(--asw-text-muted);
|
||||
letter-spacing: -0.05em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.error-card h1 { margin-top: 0.5rem; }
|
||||
.error-card p { color: var(--asw-text-secondary); }
|
||||
.error-card a { display: inline-block; margin-top: 1.5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<ul><li><a href="/"><strong>Home</strong></a></li></ul>
|
||||
<ul><li><span data-text="dim">403</span></li></ul>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<div class="error-card">
|
||||
<p class="error-code">403</p>
|
||||
<h1>Forbidden</h1>
|
||||
<p>You don't have permission to access this page.</p>
|
||||
<a href="/">← Return home</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
43
archive/packs/shared/errors/404.html
Normal file
43
archive/packs/shared/errors/404.html
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/asw.css">
|
||||
<title>404 — Not Found</title>
|
||||
<style>
|
||||
body { display: flex; flex-direction: column; min-height: 100vh; }
|
||||
main { flex: 1; display: flex; align-items: center; justify-content: center; }
|
||||
.error-card { max-width: 40ch; text-align: center; }
|
||||
.error-code {
|
||||
font-family: var(--asw-font-mono);
|
||||
font-size: 6rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: var(--asw-text-muted);
|
||||
letter-spacing: -0.05em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.error-card h1 { margin-top: 0.5rem; }
|
||||
.error-card p { color: var(--asw-text-secondary); }
|
||||
.error-card a { display: inline-block; margin-top: 1.5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<ul><li><a href="/"><strong>Home</strong></a></li></ul>
|
||||
<ul><li><span data-text="dim">404</span></li></ul>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<div class="error-card">
|
||||
<p class="error-code">404</p>
|
||||
<h1>Not Found</h1>
|
||||
<p>This page doesn't exist or has moved. If you followed a link here, it may be outdated.</p>
|
||||
<a href="/">← Return home</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
43
archive/packs/shared/errors/500.html
Normal file
43
archive/packs/shared/errors/500.html
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/asw.css">
|
||||
<title>500 — Server Error</title>
|
||||
<style>
|
||||
body { display: flex; flex-direction: column; min-height: 100vh; }
|
||||
main { flex: 1; display: flex; align-items: center; justify-content: center; }
|
||||
.error-card { max-width: 40ch; text-align: center; }
|
||||
.error-code {
|
||||
font-family: var(--asw-font-mono);
|
||||
font-size: 6rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: var(--asw-text-muted);
|
||||
letter-spacing: -0.05em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.error-card h1 { margin-top: 0.5rem; }
|
||||
.error-card p { color: var(--asw-text-secondary); }
|
||||
.error-card a { display: inline-block; margin-top: 1.5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<ul><li><a href="/"><strong>Home</strong></a></li></ul>
|
||||
<ul><li><span data-text="dim">500</span></li></ul>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<div class="error-card">
|
||||
<p class="error-code">500</p>
|
||||
<h1>Server Error</h1>
|
||||
<p>Something went wrong on the server. It's not you — try again in a moment.</p>
|
||||
<a href="/">← Return home</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
43
archive/packs/shared/errors/502.html
Normal file
43
archive/packs/shared/errors/502.html
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/asw.css">
|
||||
<title>502 — Bad Gateway</title>
|
||||
<style>
|
||||
body { display: flex; flex-direction: column; min-height: 100vh; }
|
||||
main { flex: 1; display: flex; align-items: center; justify-content: center; }
|
||||
.error-card { max-width: 40ch; text-align: center; }
|
||||
.error-code {
|
||||
font-family: var(--asw-font-mono);
|
||||
font-size: 6rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: var(--asw-text-muted);
|
||||
letter-spacing: -0.05em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.error-card h1 { margin-top: 0.5rem; }
|
||||
.error-card p { color: var(--asw-text-secondary); }
|
||||
.error-card a { display: inline-block; margin-top: 1.5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<ul><li><a href="/"><strong>Home</strong></a></li></ul>
|
||||
<ul><li><span data-text="dim">502</span></li></ul>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<div class="error-card">
|
||||
<p class="error-code">502</p>
|
||||
<h1>Bad Gateway</h1>
|
||||
<p>The upstream service isn't responding. This usually resolves itself — try again shortly.</p>
|
||||
<a href="/">← Return home</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
43
archive/packs/shared/errors/503.html
Normal file
43
archive/packs/shared/errors/503.html
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/asw.css">
|
||||
<title>503 — Service Unavailable</title>
|
||||
<style>
|
||||
body { display: flex; flex-direction: column; min-height: 100vh; }
|
||||
main { flex: 1; display: flex; align-items: center; justify-content: center; }
|
||||
.error-card { max-width: 40ch; text-align: center; }
|
||||
.error-code {
|
||||
font-family: var(--asw-font-mono);
|
||||
font-size: 6rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: var(--asw-text-muted);
|
||||
letter-spacing: -0.05em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.error-card h1 { margin-top: 0.5rem; }
|
||||
.error-card p { color: var(--asw-text-secondary); }
|
||||
.error-card a { display: inline-block; margin-top: 1.5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<ul><li><a href="/"><strong>Home</strong></a></li></ul>
|
||||
<ul><li><span data-text="dim">503</span></li></ul>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<div class="error-card">
|
||||
<p class="error-code">503</p>
|
||||
<h1>Service Unavailable</h1>
|
||||
<p>The service is temporarily offline for maintenance. It should be back shortly.</p>
|
||||
<a href="/">← Return home</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue