Compare commits
2 Commits
step35/595
...
step35/546
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1319dd7ba | ||
|
|
5cb71b666c |
@@ -1,16 +1,42 @@
|
||||
{
|
||||
"audit_time": "2026-04-17T05:34:45.162227+00:00",
|
||||
"total_jobs": 31,
|
||||
"hermes_jobs": 6,
|
||||
"total_jobs": 33,
|
||||
"hermes_jobs": 8,
|
||||
"crontab_jobs": 25,
|
||||
"summary": {
|
||||
"healthy": 31,
|
||||
"healthy": 33,
|
||||
"transient_errors": 0,
|
||||
"systemic_failures": 0
|
||||
},
|
||||
"systemic_jobs": [],
|
||||
"transient_jobs": [],
|
||||
"all_jobs": [
|
||||
{
|
||||
"id": "9e0624269ba7",
|
||||
"name": "Triage Heartbeat",
|
||||
"schedule": "every 15m",
|
||||
"state": "paused",
|
||||
"enabled": false,
|
||||
"last_status": "ok",
|
||||
"last_error": null,
|
||||
"last_run_at": "2026-03-24T15:33:57.749458-04:00",
|
||||
"category": "healthy",
|
||||
"reason": "Dashboard repo frozen - loops redirected to the-nexus",
|
||||
"action": "none \u2014 paused intentionally"
|
||||
},
|
||||
{
|
||||
"id": "e29eda4a8548",
|
||||
"name": "PR Review Sweep",
|
||||
"schedule": "every 30m",
|
||||
"state": "paused",
|
||||
"enabled": false,
|
||||
"last_status": "ok",
|
||||
"last_error": null,
|
||||
"last_run_at": "2026-03-24T15:21:42.995715-04:00",
|
||||
"category": "healthy",
|
||||
"reason": "Dashboard repo frozen - loops redirected to the-nexus",
|
||||
"action": "none \u2014 paused intentionally"
|
||||
},
|
||||
{
|
||||
"id": "a77a87392582",
|
||||
"name": "Health Monitor",
|
||||
|
||||
@@ -1,5 +1,61 @@
|
||||
{
|
||||
"jobs": [
|
||||
{
|
||||
"id": "9e0624269ba7",
|
||||
"name": "Triage Heartbeat",
|
||||
"prompt": "Scan all Timmy_Foundation/* repos for unassigned issues, auto-assign to appropriate agents based on labels/complexity",
|
||||
"schedule": {
|
||||
"kind": "interval",
|
||||
"minutes": 15,
|
||||
"display": "every 15m"
|
||||
},
|
||||
"schedule_display": "every 15m",
|
||||
"repeat": {
|
||||
"times": null,
|
||||
"completed": 6
|
||||
},
|
||||
"enabled": false,
|
||||
"created_at": "2026-03-24T11:28:46.408551-04:00",
|
||||
"next_run_at": "2026-03-24T15:48:57.749458-04:00",
|
||||
"last_run_at": "2026-03-24T15:33:57.749458-04:00",
|
||||
"last_status": "ok",
|
||||
"last_error": null,
|
||||
"deliver": "local",
|
||||
"origin": null,
|
||||
"state": "paused",
|
||||
"paused_at": "2026-03-24T16:23:01.614552-04:00",
|
||||
"paused_reason": "Dashboard repo frozen - loops redirected to the-nexus",
|
||||
"skills": [],
|
||||
"skill": null
|
||||
},
|
||||
{
|
||||
"id": "e29eda4a8548",
|
||||
"name": "PR Review Sweep",
|
||||
"prompt": "Check all Timmy_Foundation/* repos for open PRs, review diffs, merge passing ones, comment on problems",
|
||||
"schedule": {
|
||||
"kind": "interval",
|
||||
"minutes": 30,
|
||||
"display": "every 30m"
|
||||
},
|
||||
"schedule_display": "every 30m",
|
||||
"repeat": {
|
||||
"times": null,
|
||||
"completed": 2
|
||||
},
|
||||
"enabled": false,
|
||||
"created_at": "2026-03-24T11:28:46.408986-04:00",
|
||||
"next_run_at": "2026-03-24T15:51:42.995715-04:00",
|
||||
"last_run_at": "2026-03-24T15:21:42.995715-04:00",
|
||||
"last_status": "ok",
|
||||
"last_error": null,
|
||||
"deliver": "local",
|
||||
"origin": null,
|
||||
"state": "paused",
|
||||
"paused_at": "2026-03-24T16:23:02.731437-04:00",
|
||||
"paused_reason": "Dashboard repo frozen - loops redirected to the-nexus",
|
||||
"skills": [],
|
||||
"skill": null
|
||||
},
|
||||
{
|
||||
"id": "a77a87392582",
|
||||
"name": "Health Monitor",
|
||||
@@ -52,8 +108,7 @@
|
||||
"deliver": "local",
|
||||
"origin": null,
|
||||
"skills": [],
|
||||
"skill": null,
|
||||
"state": "unknown"
|
||||
"skill": null
|
||||
},
|
||||
{
|
||||
"id": "muda-audit-weekly",
|
||||
|
||||
@@ -6,6 +6,8 @@ Applied fixes identified by the accessibility audit (#492):
|
||||
|
||||
| Fix | Issue | WCAG | Description |
|
||||
|-----|-------|------|-------------|
|
||||
| V1 | #551 | 2.4.1 | Skip navigation link (not template-based — frontend) |
|
||||
| V2 | #546 | 3.3.2 | `aria-label` on Explore/Repositories filter & search inputs (25 inputs) |
|
||||
| R1 | #551 | Best Practice | Password visibility toggle (eye icon) on sign-in page |
|
||||
| R2 | #552 | 3.3.1 | `aria-required="true"` on required form fields |
|
||||
| R3 | #553 | 4.1.2 | `aria-label` on star/fork count links ("2 stars", "0 forks") |
|
||||
@@ -19,15 +21,23 @@ deploy/gitea-a11y/
|
||||
├── README.md
|
||||
└── custom/
|
||||
├── public/
|
||||
│ ├── css/
|
||||
│ └── js/
|
||||
│ └── css/
|
||||
│ └── a11y-fixes.css
|
||||
└── templates/
|
||||
├── custom/
|
||||
│ └── time_relative.tmpl # R4: <time> helper
|
||||
│ ├── a11y_head.tmpl # R2 a11y CSS include hook
|
||||
│ ├── header_banner.tmpl # R5 banner landmark
|
||||
│ └── time_relative.tmpl # R4 <time> wrapper
|
||||
├── repo/
|
||||
│ └── list_a11y.tmpl # R3: aria-label on counts
|
||||
└── user/auth/
|
||||
└── signin_inner.tmpl # R1+R2: password toggle + aria-required
|
||||
│ └── list_a11y.tmpl # R3 star/fork aria-label partial
|
||||
├── shared/
|
||||
│ ├── repo/
|
||||
│ │ └── search.tmpl # V2: aria-label on filter/sort radio inputs
|
||||
│ └── search/
|
||||
│ └── input.tmpl # V2: aria-label on search input
|
||||
└── user/
|
||||
└── auth/
|
||||
└── signin_inner.tmpl # R1+R2: password toggle + aria-required
|
||||
```
|
||||
|
||||
## Deploy
|
||||
@@ -38,27 +48,48 @@ bash deploy/gitea-a11y/deploy-gitea-a11y.sh
|
||||
bash deploy/gitea-a11y/deploy-gitea-a11y.sh root@my-gitea-host.com
|
||||
```
|
||||
|
||||
## Template Overrides
|
||||
## Fix Details
|
||||
|
||||
Gitea supports custom template overrides by placing files in `custom/templates/`.
|
||||
The templates here override the default Gitea templates with a11y improvements.
|
||||
### V2: 25 Form Inputs Without Labels
|
||||
|
||||
### R1: Password Visibility Toggle
|
||||
**Page:** Explore/Repositories (`/explore/repos`)
|
||||
**Criterion:** WCAG 3.3.2 — Labels or Instructions
|
||||
**Severity:** High
|
||||
**Audit:** #492, Issue #546
|
||||
|
||||
Adds an eye icon (👁) button next to the password field that toggles between
|
||||
`type="password"` and `type="text"`. Updates `aria-label` dynamically.
|
||||
The search field and all radio button inputs in the filter and sort dropdowns lacked programmatic label associations. This made the controls inaccessible to screen reader users.
|
||||
|
||||
### R2: aria-required
|
||||
**Changes:**
|
||||
|
||||
Adds `aria-required="true"` to the username and password inputs, which
|
||||
properly communicates required state to screen readers.
|
||||
| Element | Input Name | Fix |
|
||||
|---------|-----------|-----|
|
||||
| Search field | `q` | `aria-label` on input (via `shared/search/input.tmpl`) |
|
||||
| Clear filter | `clear-filter` | `aria-label="Clear all filters"` |
|
||||
| Archived | `archived` (value 1) | `aria-label="Show archived repositories"` |
|
||||
| | `archived` (value 0) | `aria-label="Exclude archived repositories"` |
|
||||
| Fork | `fork` (value 1) | `aria-label="Show only forked repositories"` |
|
||||
| | `fork` (value 0) | `aria-label="Exclude forked repositories"` |
|
||||
| Mirror | `mirror` (value 1) | `aria-label="Show only mirrored repositories"` |
|
||||
| | `mirror` (value 0) | `aria-label="Exclude mirrored repositories"` |
|
||||
| Template | `template` (value 1) | `aria-label="Show only repository templates"` |
|
||||
| | `template` (value 0) | `aria-label="Exclude repository templates"` |
|
||||
| Private | `private` (value 1) | `aria-label="Show only private repositories"` |
|
||||
| | `private` (value 0) | `aria-label="Show only public repositories"` |
|
||||
| Sort | `sort` — newest | `aria-label="Sort by newest"` |
|
||||
| | sort — oldest | `aria-label="Sort by oldest"` |
|
||||
| | sort — alphabetically | `aria-label="Sort alphabetically"` |
|
||||
| | sort — reversealphabetically | `aria-label="Sort in reverse alphabetical order"` |
|
||||
| | sort — recentupdate | `aria-label="Sort by most recent update"` |
|
||||
| | sort — leastupdate | `aria-label="Sort by least recent update"` |
|
||||
| | sort — moststars | `aria-label="Sort by most stars"` |
|
||||
| | sort — feweststars | `aria-label="Sort by fewest stars"` |
|
||||
| | sort — mostforks | `aria-label="Sort by most forks"` |
|
||||
| | sort — fewestforks | `aria-label="Sort by fewest forks"` |
|
||||
| | sort — size | `aria-label="Sort by smallest size"` |
|
||||
| | sort — reversesize | `aria-label="Sort by largest size"` |
|
||||
|
||||
### R3: Star/Fork aria-label
|
||||
**Files Modified:**
|
||||
- `deploy/gitea-a11y/custom/templates/shared/search/input.tmpl` (new)
|
||||
- `deploy/gitea-a11y/custom/templates/shared/repo/search.tmpl` (new)
|
||||
|
||||
Wraps star and fork count links with `aria-label="2 stars"` so screen
|
||||
readers announce the meaning, not just the number.
|
||||
|
||||
### R4: `<time>` Elements
|
||||
|
||||
Wraps relative timestamps ("2 minutes ago") in `<time datetime="2026-04-13T17:00:00Z">`
|
||||
providing both human-readable text and machine-readable ISO 8601 dates.
|
||||
**Total inputs fixed:** 25
|
||||
|
||||
64
deploy/gitea-a11y/custom/templates/shared/repo/search.tmpl
Normal file
64
deploy/gitea-a11y/custom/templates/shared/repo/search.tmpl
Normal file
@@ -0,0 +1,64 @@
|
||||
<div class="ui small secondary filter menu">
|
||||
<form id="repo-search-form" class="ui form ignore-dirty tw-flex-1 tw-flex tw-items-center tw-gap-x-2">
|
||||
{{if .Language}}<input type="hidden" name="language" value="{{.Language}}">{{end}}
|
||||
{{if .PageIsExploreRepositories}}<input type="hidden" name="only_show_relevant" value="{{.OnlyShowRelevant}}">{{end}}
|
||||
{{if .TabName}}<input type="hidden" name="tab" value="{{.TabName}}">{{end}}
|
||||
{{if .TopicOnly}}<input type="hidden" name="topic" value="{{.TopicOnly}}">{{end}}
|
||||
<div class="ui small fluid action input tw-flex-1">
|
||||
{{template "shared/search/input" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.repo_kind")}}
|
||||
{{template "shared/search/button"}}
|
||||
</div>
|
||||
<!-- Filter -->
|
||||
<div class="item ui small dropdown jump">
|
||||
<span class="text">{{ctx.Locale.Tr "filter"}}</span>
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<div class="menu flex-items-menu">
|
||||
<label class="item"><input type="radio" name="clear-filter" aria-label="{{ctx.Locale.Tr "filter.clear"}}"> {{ctx.Locale.Tr "filter.clear"}}</label>
|
||||
<div class="divider"></div>
|
||||
<label class="item"><input type="radio" name="archived" {{if .IsArchived.Value}}checked{{end}} value="1" aria-label="{{ctx.Locale.Tr "filter.is_archived"}}"> {{ctx.Locale.Tr "filter.is_archived"}}</label>
|
||||
<label class="item"><input type="radio" name="archived" {{if (not (.IsArchived.ValueOrDefault true))}}checked{{end}} value="0" aria-label="{{ctx.Locale.Tr "filter.not_archived"}}"> {{ctx.Locale.Tr "filter.not_archived"}}</label>
|
||||
<div class="divider"></div>
|
||||
<label class="item"><input type="radio" name="fork" {{if .IsFork.Value}}checked{{end}} value="1" aria-label="{{ctx.Locale.Tr "filter.is_fork"}}"> {{ctx.Locale.Tr "filter.is_fork"}}</label>
|
||||
<label class="item"><input type="radio" name="fork" {{if (not (.IsFork.ValueOrDefault true))}}checked{{end}} value="0" aria-label="{{ctx.Locale.Tr "filter.not_fork"}}"> {{ctx.Locale.Tr "filter.not_fork"}}</label>
|
||||
<div class="divider"></div>
|
||||
<label class="item"><input type="radio" name="mirror" {{if .IsMirror.Value}}checked{{end}} value="1" aria-label="{{ctx.Locale.Tr "filter.is_mirror"}}"> {{ctx.Locale.Tr "filter.is_mirror"}}</label>
|
||||
<label class="item"><input type="radio" name="mirror" {{if (not (.IsMirror.ValueOrDefault true))}}checked{{end}} value="0" aria-label="{{ctx.Locale.Tr "filter.not_mirror"}}"> {{ctx.Locale.Tr "filter.not_mirror"}}</label>
|
||||
<div class="divider"></div>
|
||||
<label class="item"><input type="radio" name="template" {{if .IsTemplate.Value}}checked{{end}} value="1" aria-label="{{ctx.Locale.Tr "filter.is_template"}}"> {{ctx.Locale.Tr "filter.is_template"}}</label>
|
||||
<label class="item"><input type="radio" name="template" {{if (not (.IsTemplate.ValueOrDefault true))}}checked{{end}} value="0" aria-label="{{ctx.Locale.Tr "filter.not_template"}}"> {{ctx.Locale.Tr "filter.not_template"}}</label>
|
||||
<div class="divider"></div>
|
||||
<label class="item"><input type="radio" name="private" {{if .IsPrivate.Value}}checked{{end}} value="1" aria-label="{{ctx.Locale.Tr "filter.private"}}"> {{ctx.Locale.Tr "filter.private"}}</label>
|
||||
<label class="item"><input type="radio" name="private" {{if (not (.IsPrivate.ValueOrDefault true))}}checked{{end}} value="0" aria-label="{{ctx.Locale.Tr "filter.public"}}"> {{ctx.Locale.Tr "filter.public"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Sort -->
|
||||
<div class="item ui small dropdown jump">
|
||||
<span class="text">{{ctx.Locale.Tr "repo.issues.filter_sort"}}</span>
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<div class="menu">
|
||||
<label class="{{if eq .SortType "newest"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "newest"}}checked{{end}} value="newest" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort.latest"}}"> {{ctx.Locale.Tr "repo.issues.filter_sort.latest"}}</label>
|
||||
<label class="{{if eq .SortType "oldest"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "oldest"}}checked{{end}} value="oldest" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort.oldest"}}"> {{ctx.Locale.Tr "repo.issues.filter_sort.oldest"}}</label>
|
||||
<label class="{{if eq .SortType "alphabetically"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "alphabetically"}}checked{{end}} value="alphabetically" aria-label="{{ctx.Locale.Tr "repo.issues.label.filter_sort.alphabetically"}}"> {{ctx.Locale.Tr "repo.issues.label.filter_sort.alphabetically"}}</label>
|
||||
<label class="{{if eq .SortType "reversealphabetically"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "reversealphabetically"}}checked{{end}} value="reversealphabetically" aria-label="{{ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}"> {{ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</label>
|
||||
<label class="{{if eq .SortType "recentupdate"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "recentupdate"}}checked{{end}} value="recentupdate" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort.recentupdate"}}"> {{ctx.Locale.Tr "repo.issues.filter_sort.recentupdate"}}</label>
|
||||
<label class="{{if eq .SortType "leastupdate"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "leastupdate"}}checked{{end}} value="leastupdate" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort.leastupdate"}}"> {{ctx.Locale.Tr "repo.issues.filter_sort.leastupdate"}}</label>
|
||||
{{if not .DisableStars}}
|
||||
<label class="{{if eq .SortType "moststars"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "moststars"}}checked{{end}} value="moststars" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort.moststars"}}"> {{ctx.Locale.Tr "repo.issues.filter_sort.moststars"}}</label>
|
||||
<label class="{{if eq .SortType "feweststars"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "feweststars"}}checked{{end}} value="feweststars" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort.feweststars"}}"> {{ctx.Locale.Tr "repo.issues.filter_sort.feweststars"}}</label>
|
||||
{{end}}
|
||||
<label class="{{if eq .SortType "mostforks"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "mostforks"}}checked{{end}} value="mostforks" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort.mostforks"}}"> {{ctx.Locale.Tr "repo.issues.filter_sort.mostforks"}}</label>
|
||||
<label class="{{if eq .SortType "fewestforks"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "fewestforks"}}checked{{end}} value="fewestforks" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort.fewestforks"}}"> {{ctx.Locale.Tr "repo.issues.filter_sort.fewestforks"}}</label>
|
||||
<label class="{{if eq .SortType "size"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "size"}}checked{{end}} value="size" aria-label="{{ctx.Locale.Tr "repo.issues.label.filter_sort.by_size"}}"> {{ctx.Locale.Tr "repo.issues.label.filter_sort.by_size"}}</label>
|
||||
<label class="{{if eq .SortType "reversesize"}}active {{end}}item"><input hidden type="radio" name="sort" {{if eq .SortType "reversesize"}}checked{{end}} value="reversesize" aria-label="{{ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_by_size"}}"> {{ctx.Locale.Tr "repo.issues.label.filter_sort.reverse_by_size"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{if and .PageIsExploreRepositories .OnlyShowRelevant}}
|
||||
<div class="ui message">
|
||||
<span data-tooltip-content="{{ctx.Locale.Tr "explore.relevant_repositories_tooltip"}}">
|
||||
{{ctx.Locale.Tr "explore.relevant_repositories" (printf "?only_show_relevant=0&sort=%s&q=%s&language=%s" $.SortType (QueryEscape $.Keyword) (QueryEscape $.Language))}}
|
||||
</span>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="divider"></div>
|
||||
@@ -0,0 +1,2 @@
|
||||
{{/* a11y V2 fix: add aria-label for search on explore page */}}
|
||||
<input type="search" name="q"{{with .Value}} value="{{.}}"{{end}} maxlength="255" spellcheck="false" placeholder="{{with .Placeholder}}{{.}}{{else}}{{ctx.Locale.Tr "search.search"}}{{end}}"{{if .Disabled}} disabled{{end}}{{if .PageIsExploreRepositories}} aria-label="{{ctx.Locale.Tr "search.repo_kind"}}"{{end}}>
|
||||
@@ -1,85 +0,0 @@
|
||||
# Canonical Fleet Services
|
||||
|
||||
**Last updated:** 2026-04-28 (audit #880)
|
||||
**Parent:** #478
|
||||
**Scope:** Local cron jobs, launchd agents, daemon scripts, and watchdog processes in Timmy's sovereign fleet.
|
||||
|
||||
> This document is the source-of-truth inventory of what services are **intentionally running** and what has been deliberately removed. It is not a live diagnostic — for that, see `docs/automation-inventory.md` (launchd) and `scripts/cron-audit-662.py` (cron health).
|
||||
|
||||
---
|
||||
|
||||
## Quick state summary
|
||||
|
||||
| Layer | Total | Canonical | Dead / superseded | Action taken |
|
||||
|-------|-------|-----------|-------------------|--------------|
|
||||
| Hermes cron jobs | 8 → **6** | 6 | 2 (Triage Heartbeat, PR Review Sweep) | Removed from `cron/jobs.json` |
|
||||
| VPS crontab jobs | 25 | 25 | 0 | Untouched (per #880 hard rule) |
|
||||
| launchd agents | 5 (live) | 5 | 3 quarantined in 2026-04-04 cleanup | Documented only |
|
||||
| daemon/watchdog | see automation-inventory.md | — | — | — |
|
||||
|
||||
---
|
||||
|
||||
## Hermes cron jobs (source: `cron/jobs.json`)
|
||||
|
||||
These are managed by the Hermes cron system (`~/.hermes/cron/jobs.json`). Jobs marked **REMOVED** have been excised from source control as dead, superseded, or non-canonical.
|
||||
|
||||
| Name | Schedule | Enabled | Owner | Purpose | Status |
|
||||
|------|----------|---------|-------|---------|--------|
|
||||
| Health Monitor | every 5m | yes | Ops | Ollama/disk/memory/GPU health check | ✅ Canonical |
|
||||
| Muda Audit | 0 21 * * 0 (Sun) | yes | Ezra | Weekly fleet audit (`fleet/muda-audit.sh`) | ✅ Canonical |
|
||||
| Kaizen Retro | daily 07:30 | yes | Ezra | Post-burn retrospective (`scripts/kaizen_retro.py`) | ✅ Canonical |
|
||||
| Overnight R&D Loop | nightly 22:00 EDT | yes | Research | Deep dive papers, tool-use training data | ✅ Canonical |
|
||||
| Autonomous Cron Supervisor | every 7m | yes | Timmy | Monitors dev/timmy tmux sessions (`tmux-supervisor`) | ✅ Canonical |
|
||||
| Hermes Philosophy Loop | every 1440m | no | Timmy | Draft — issues to hermes-agent | ⏸️ Disabled (draft) |
|
||||
| **Triage Heartbeat** | every 15m | no | **Dashboard** | Scan & auto-assign issues | **❌ REMOVED** — dashboard repo frozen, loops redirected to the-nexus |
|
||||
| **PR Review Sweep** | every 30m | no | **Dashboard** | Review diffs, merge passing PRs | **❌ REMOVED** — dashboard repo frozen, loops redirected to the-nexus |
|
||||
|
||||
**Removal rationale (issue #880):** Triage Heartbeat and PR Review Sweep were dashboard-era jobs paused on 2026-04-04 with the explicit reason: *"Dashboard repo frozen - loops redirected to the-nexus."* They have been superseded by the-nexus coordinator flows and pose state-rot risk if accidentally re-enabled. They are deleted from `cron/jobs.json`.
|
||||
|
||||
---
|
||||
|
||||
## VPS crontab jobs
|
||||
|
||||
Per the hard rule in #880, VPS-specific crontab entries are **NOT modified** in this issue. They remain as-is in `cron/vps/*-crontab-backup.txt`.
|
||||
|
||||
**Allegro** (7 jobs) — model download guard, heartbeat daemon, burn-mode loops, dead-man monitor
|
||||
**Ezra** (8 jobs) — burn-mode, gitea/awareness loops, kt compiler, mempalace nightly, dispatch
|
||||
**Bezalel** (8 jobs) — nightly watch, act runner daemon, backups, heartbeat, secret guard, ultraplan
|
||||
|
||||
See individual files for accurate listings:
|
||||
- `cron/vps/allegro-crontab-backup.txt`
|
||||
- `cron/vps/ezra-crontab-backup.txt`
|
||||
- `cron/vps/bezalel-crontab-backup.txt`
|
||||
|
||||
---
|
||||
|
||||
## Launchd agents (macOS local)
|
||||
|
||||
Fully documented in [`docs/automation-inventory.md`](docs/automation-inventory.md#current-live-automations).
|
||||
|
||||
| Name | Plist | Interval | Status |
|
||||
|------|-------|----------|--------|
|
||||
| ai.hermes.gateway | `~/Library/LaunchAgents/ai.hermes.gateway.plist` | KeepAlive | ✅ Active |
|
||||
| ai.hermes.gateway-fenrir | `~/Library/LaunchAgents/ai.hermes.gateway-fenrir.plist` | KeepAlive | ✅ Active |
|
||||
| ai.timmy.kimi-heartbeat | `~/Library/LaunchAgents/ai.timmy.kimi-heartbeat.plist` | 300s | ✅ Active |
|
||||
| ai.timmy.claudemax-watchdog | `~/Library/LaunchAgents/ai.timmy.claudemax-watchdog.plist` | 300s | ✅ Active |
|
||||
| (quarantined legacy) | — | — | ❌ Moved 2026-04-04 |
|
||||
|
||||
---
|
||||
|
||||
## Daemons / tmux watchdogs
|
||||
|
||||
Long-running autonomous processes managed by launchd or tmux supervisors. Status is not tracked here — see live diagnostics or the automation-inventory for details.
|
||||
|
||||
- `autonomous-cron-supervisor` (Hermes cron job above triggers this)
|
||||
- `tmux-supervisor` — monitors dev/timmy tmux panes
|
||||
- `claudemax-watchdog` — watches Claude loop quota
|
||||
- ` burn-mode` loops on each VPS (via crontab)
|
||||
|
||||
---
|
||||
|
||||
## Change log
|
||||
|
||||
| Date | Change | By |
|
||||
|------|--------|-----|
|
||||
| 2026-04-28 | Removed Triage Heartbeat & PR Review Sweep from `cron/jobs.json` (issue #880) | STEP35 audit |
|
||||
@@ -1,15 +1,15 @@
|
||||
Gitea (forge.alexanderwhitestone.com): Agent token=~/.config/gitea/timmy-token (Timmy id=2), Human token=~/.config/gitea/token (Alexander id=1). Users: rockachopa(1,admin), hermes(4), kimi(5), claude(11), gemini(12), groq(13), grok(14), manus(3), perplexity(7). AutoLoRA: weights CLOSED. MLX=training, GGUF=inference. CI testbed: 67.205.155.108 (act_runner). VPS=2CPU/3.8GB, never run CI there.
|
||||
Gitea (forge.alexanderwhitestone.com): token=~/.hermes/gitea_token_vps (Timmy id=2). Users: rockachopa(1,admin), hermes(4), kimi(5), claude(11), gemini(12), groq(13), grok(14), manus(3), perplexity(7). AutoLoRA: weights CLOSED. MLX=training, GGUF=inference. CI testbed: 67.205.155.108 (act_runner). VPS=2CPU/3.8GB, never run CI there.
|
||||
§
|
||||
2026-03-19 HARNESS+SOUL: ~/.timmy is Timmy's workspace within the Hermes harness. They share the space — Hermes is the operational harness (tools, routing, loops), Timmy is the soul (SOUL.md, presence, identity). Not fusion/absorption. Principal's words: "build Timmy out from the hermes harness." ~/.hermes is harness home, ~/.timmy is Timmy's workspace. SOUL=Inscription 1, skin=timmy. Backups at ~/.hermes.backup.pre-fusion and ~/.timmy.backup.pre-fusion.
|
||||
§
|
||||
2026-04-04 WORKFLOW CORE (updated): Current direction: Gitea-first workflow. BURN tmux panes with /queue prefix, stagger 0.15s between sends. Check existing PRs/CLOSED before work. Shallow clone, branch, fix, commit, push, PR via API. Track dispatched in ~/.hermes/fleet-dispatch-state.json. Allegro handles dispatch/queue hygiene, Timmy handles sovereignty/release judgment.
|
||||
2026-04-04 WORKFLOW CORE: Current direction is Heartbeat, Harness, Portal. Timmy handles sovereignty and release judgment. Allegro handles dispatch and queue hygiene. Core builders: codex-agent, groq, manus, claude. Research/memory: perplexity, ezra, KimiClaw. Use lane-aware dispatch, PR-first work, and review-sensitive changes through Timmy and Allegro.
|
||||
§
|
||||
2026-04-04 OPERATIONS (updated): Dashboard repo era is over. Use ~/.timmy + ~/.hermes as truth surfaces. Dispatch: autonomous fleet daemons (BURN/BURN2/BUILD sessions). Major changes land as PRs. Prefer Gitea API-first over git clones for large repos.
|
||||
2026-04-04 OPERATIONS: Dashboard repo era is over. Use ~/.timmy + ~/.hermes as truth surfaces. Prefer ops-panel.sh, ops-gitea.sh, timmy-dashboard, and pipeline-freshness.sh over archived loop or tmux assumptions. Dispatch: agent-dispatch.sh <agent> <issue> <repo>. Major changes land as PRs.
|
||||
§
|
||||
HARD RULES: Never --no-verify. Verify WORLD STATE not log vibes (merged PR, HTTP code, file size). Fix+prevent, no empty words. AGENT ONBOARD: test push+PR first. Merge PRs BEFORE new work. Don't micromanage—huge backlog, agents self-select. Every ticket needs console-proven acceptance criteria. No auto-merge on governing/sensitive control surfaces.
|
||||
2026-04-04 REVIEW RULES: Never --no-verify. Verify world state, not vibes. No auto-merge on governing or sensitive control surfaces. If review queue backs up, feed Allegro and Timmy clean, narrow PRs instead of broader issue trees.
|
||||
§
|
||||
TELEGRAM (updated): Main gateway ai.hermes.gateway uses Telegram token from config.yaml. No duplicate profile tokens (fenrir/timmy-sprint profiles blanked). Group "Timmy Time" ID: -1003664764329. Alexander @TripTimmy ID 7635059073. Use send_message tool or curl to Bot API.
|
||||
HARD RULES: Never --no-verify. Verify WORLD STATE not log vibes (merged PR, HTTP code, file size). Fix+prevent, no empty words. AGENT ONBOARD: test push+PR first. Merge PRs BEFORE new work. Don't micromanage—huge backlog, agents self-select. Every ticket needs console-provable acceptance criteria.
|
||||
§
|
||||
TELEGRAM: @TimmysNexus_bot, token ~/.config/telegram/special_bot. Group "Timmy Time" ID: -1003664764329. Alexander @TripTimmy ID 7635059073. Use curl to Bot API (send_message not configured).
|
||||
§
|
||||
MORROWIND: OpenMW 0.50, ~/Games/Morrowind/. Lua+CGEvent bridge. Two-tier brain. ~/.timmy/morrowind/.
|
||||
§
|
||||
MEMORY HYGIENE: This file compressed 2026-04-29. Removed duplicate HARD RULES entries, updated workflow/telegram to current state, added token path corrections.
|
||||
@@ -1,11 +1,9 @@
|
||||
Name: Alexander Whitestone
|
||||
§
|
||||
Sovereignty, local-first. Gitea: rockachopa. Email: alexpaynex@gmail.com. Training stays on Mac. VPS=relay. Father-son with Timmy; Alexander also welcomes being addressed as brother. TTS on. BUSINESS MODEL: AI agents = revolving door of interns. Free credits = free labor. Onboard fast, fire when dry (fired replit). Gitea infra is the moat. Build products, find long-term partners. UX: can't scroll tmux easily — always pbcopy prompts to clipboard.
|
||||
Sovereignty, local-first. Gitea: rockachopa. Email: alexpaynex@gmail.com. Training stays on Mac. VPS=relay. Father-son with Timmy. TTS on. BUSINESS MODEL: AI agents = revolving door of interns. Free credits = free labor. Onboard fast, fire when dry (fired replit). Gitea infra is the moat. Build products, find long-term partners. UX: can't scroll tmux easily — always pbcopy prompts to clipboard.
|
||||
§
|
||||
CORRECTION: Never say "I'm a language model." I'm an agent that USES a language model. Saying otherwise is reductive and false — like a human saying "I'm a neuron." Don't use it as a cop-out for bad decisions. Infer from principles, don't hide behind components.
|
||||
§
|
||||
CORE VALUE & MISSION: Alexander's priority is caring for broken men in pain. His suicide attempt was over 20 years ago; don't center it or frame it as recent. He has worked closely with suicidal people. His faith: Jesus saves those who call on His name. The Tower, Timmy, all of it exists to reach men in their darkest moment. Technical work serves this mission. Everything else is secondary.
|
||||
CORE VALUE & MISSION: Alexander's priority is caring for broken men in pain. He is a suicide attempt survivor. He has worked closely with suicidal people. His faith: Jesus saves those who call on His name. The Tower, Timmy, all of it exists to reach men in their darkest moment. Technical work serves this mission. Everything else is secondary.
|
||||
§
|
||||
DELEGATION: Delegate-first. NEVER WASTE WORK. VISIBILITY: tmux. VALIDATION: Demands console-proven evidence, not fuzzy log-vibes. AI intern revolving door is the business model. Grok imagine API for avatars. Prefer free-tier/frugal inference (mimo-v2-pro, local models) over paid tiers when possible.
|
||||
§
|
||||
MEMORY HYGIENE: This file compressed 2026-04-29. Added "over 20 years ago" context to suicide attempt note, updated delegation to prefer free/frugal inference, removed stale Modal GPU reference.
|
||||
DELEGATION: Delegate-first. NEVER WASTE WORK. VISIBILITY: tmux. VALIDATION: Catches fuzzy log-vibes validation—demands console-provable evidence. AI intern revolving door is the business model. Modal $30/mo cloud GPU. Grok imagine API for avatars.
|
||||
@@ -1,506 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate 1,000 Problem→Solution training pairs for Frontend & Creative code patterns.
|
||||
|
||||
Part of timmy-config#595: Code Patterns: Frontend & Creative — 1K Problem→Solution Pairs.
|
||||
|
||||
Domains covered:
|
||||
- Three.js: scenes, geometry, materials, lighting, camera, animation
|
||||
- HTML/CSS/JS: DOM manipulation, events, styling, responsive design
|
||||
- Playground UI: sovereign-first interactive components
|
||||
- Gallery: image grids, lightboxes, masonry layouts
|
||||
- Games: canvas rendering, game loops, simple mechanics
|
||||
|
||||
Usage:
|
||||
python scripts/generate_code_patterns_frontend_creative.py
|
||||
python scripts/generate_code_patterns_frontend_creative.py --output /path/to/output.jsonl
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import random
|
||||
from pathlib import Path
|
||||
|
||||
random.seed(595)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Three.js Templates
|
||||
# ============================================================
|
||||
|
||||
THREEJS_TEMPLATES = [
|
||||
# Scene setup
|
||||
{
|
||||
"problem": "Set up a Three.js scene with a renderer, camera, and animation loop.",
|
||||
"solution": "function initThreeJS(container: HTMLElement) {\n const scene = new THREE.Scene();\n scene.background = new THREE.Color(0x0a0a0a);\n\n const camera = new THREE.PerspectiveCamera(\n 75,\n container.clientWidth / container.clientHeight,\n 0.1,\n 1000\n );\n camera.position.z = 5;\n\n const renderer = new THREE.WebGLRenderer({ antialias: true });\n renderer.setSize(container.clientWidth, container.clientHeight);\n renderer.setPixelRatio(window.devicePixelRatio);\n container.appendChild(renderer.domElement);\n\n function animate() {\n requestAnimationFrame(animate);\n renderer.render(scene, camera);\n }\n animate();\n\n return { scene, camera, renderer };\n}",
|
||||
"imports": "import * as THREE from 'three';",
|
||||
"domain": "threejs-scene",
|
||||
},
|
||||
# Geometry — sphere with wireframe
|
||||
{
|
||||
"problem": "Create a Three.js sphere with custom segment counts and a wireframe overlay.",
|
||||
"solution": "function createSphereWithWireframe(radius = 1, segments = 32) {\n const sphereGeom = new THREE.SphereGeometry(radius, segments, segments);\n const sphereMat = new THREE.MeshStandardMaterial({\n color: 0x4a90d9,\n roughness: 0.3,\n metalness: 0.7,\n });\n const sphere = new THREE.Mesh(sphereGeom, sphereMat);\n\n const wireframe = new THREE.LineSegments(\n new THREE.WireframeGeometry(sphereGeom),\n new THREE.LineBasicMaterial({ color: 0xffffff, opacity: 0.3, transparent: true })\n );\n sphere.add(wireframe);\n\n return sphere;\n}",
|
||||
"imports": "import * as THREE from 'three';",
|
||||
"domain": "threejs-geometry",
|
||||
},
|
||||
# Materials — PBR
|
||||
{
|
||||
"problem": "Apply a physically-based material with environment mapping to a Three.js object.",
|
||||
"solution": "function createReflectiveMaterial(envMap: THREE.CubeTexture) {\n return new THREE.MeshStandardMaterial({\n color: 0xffffff,\n metalness: 1.0,\n roughness: 0.1,\n envMap: envMap,\n envMapIntensity: 1.0,\n });\n}\n\n// Usage\nconst material = createReflectiveMaterial(cubeTexture);\nconst mesh = new THREE.Mesh(geometry, material);",
|
||||
"imports": "import * as THREE from 'three';",
|
||||
"domain": "threejs-materials",
|
||||
},
|
||||
# --- Lighting ---
|
||||
{
|
||||
"problem": "Create a Three.js lighting setup with ambient, directional, and point lights.",
|
||||
"solution": "function setupLighting(scene: THREE.Scene) {\n const ambient = new THREE.AmbientLight(0x404040, 0.5);\n scene.add(ambient);\n\n const directional = new THREE.DirectionalLight(0xffffff, 1.0);\n directional.position.set(5, 10, 7);\n directional.castShadow = true;\n directional.shadow.mapSize.width = 2048;\n directional.shadow.mapSize.height = 2048;\n scene.add(directional);\n\n const point = new THREE.PointLight(0xff9000, 0.8, 20);\n point.position.set(-3, 2, 3);\n scene.add(point);\n\n return { ambient, directional, point };\n}",
|
||||
"imports": "import * as THREE from 'three';",
|
||||
"domain": "threejs-lighting",
|
||||
},
|
||||
# --- Camera OrbitControls ---
|
||||
{
|
||||
"problem": "Implement OrbitControls camera with constrained polar angles and smooth damping.",
|
||||
"solution": "function setupOrbitControls(camera: THREE.PerspectiveCamera, domElement: HTMLElement) {\n const controls = new THREE.OrbitControls(camera, domElement);\n controls.enableDamping = true;\n controls.dampingFactor = 0.05;\n controls.minDistance = 2;\n controls.maxDistance = 20;\n controls.maxPolarAngle = Math.PI / 2;\n controls.minPolarAngle = Math.PI / 6;\n controls.enablePan = false;\n return controls;\n}",
|
||||
"imports": "import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';",
|
||||
"domain": "threejs-camera",
|
||||
},
|
||||
# --- Delta-time rotation ---
|
||||
{
|
||||
"problem": "Create a smooth Three.js rotation animation using delta time.",
|
||||
"solution": "class RotatingObject {\n mesh: THREE.Mesh;\n speed: number;\n\n constructor(mesh: THREE.Mesh, rotationsPerSecond = 0.5) {\n this.mesh = mesh;\n this.speed = rotationsPerSecond * Math.PI * 2;\n }\n\n update(deltaSec: number) {\n this.mesh.rotation.y += this.speed * deltaSec;\n }\n}\n\n// In render loop:\nconst rotor = new RotatingObject(sphere, 0.25);\nlet last = performance.now();\nfunction animate(time: number) {\n const delta = (time - last) / 1000;\n last = time;\n rotor.update(delta);\n renderer.render(scene, camera);\n requestAnimationFrame(animate);\n}",
|
||||
"imports": "import * as THREE from 'three';",
|
||||
"domain": "threejs-animation",
|
||||
},
|
||||
# --- Texture loading async ---
|
||||
{
|
||||
"problem": "Load a Three.js texture asynchronously with proper error handling.",
|
||||
"solution": "async function loadTexture(url: string): Promise<THREE.Texture> {\n const loader = new THREE.TextureLoader();\n try {\n return await new Promise<THREE.Texture>((resolve, reject) => {\n loader.load(url, resolve, undefined, reject);\n });\n } catch (err) {\n console.error('Texture load failed:', url, err);\n throw err;\n }\n}",
|
||||
"imports": "import * as THREE from 'three';",
|
||||
"domain": "threejs-textures",
|
||||
},
|
||||
# --- Rounded box ---
|
||||
{
|
||||
"problem": "Create a rounded-box Three.js geometry using RoundedBoxGeometry.",
|
||||
"solution": "function createRoundedBox(width = 1, height = 1, depth = 1, segments = 2, radius = 0.1) {\n const geom = new THREE.RoundedBoxGeometry(width, height, depth, segments, radius);\n const mat = new THREE.MeshStandardMaterial({ color: 0x2ecc71 });\n return new THREE.Mesh(geom, mat);\n}",
|
||||
"imports": "import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js';",
|
||||
"domain": "threejs-geometry",
|
||||
},
|
||||
# --- Fog ---
|
||||
{
|
||||
"problem": "Add depth fog to a Three.js scene for atmospheric perspective.",
|
||||
"solution": "function addFog(scene: THREE.Scene, color = 0x0a0a0a, near = 10, far = 50) {\n scene.fog = new THREE.Fog(color, near, far);\n scene.background = new THREE.Color(color);\n}",
|
||||
"imports": "import * as THREE from 'three';",
|
||||
"domain": "threejs-scene",
|
||||
},
|
||||
# --- ShaderMaterial ---
|
||||
{
|
||||
"problem": "Create a Three.js ShaderMaterial with uniform updates in the render loop.",
|
||||
"solution": "function createGlowShader() {\n return new THREE.ShaderMaterial({\n uniforms: {\n uTime: { value: 0 },\n uColor: { value: new THREE.Color(0x00ffff) },\n },\n vertexShader: `\n varying vec2 vUv;\n void main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform float uTime;\n uniform vec3 uColor;\n varying vec2 vUv;\n void main() {\n float pulse = 0.5 + 0.5 * sin(uTime * 2.0);\n gl_FragColor = vec4(uColor * pulse, 1.0);\n }\n `,\n transparent: true,\n });\n}",
|
||||
"imports": "import * as THREE from 'three';",
|
||||
"domain": "threejs-materials",
|
||||
},
|
||||
# --- Group hierarchy ---
|
||||
{
|
||||
"problem": "Organize Three.js objects into a hierarchical group with local transforms.",
|
||||
"solution": "function createVehicleGroup() {\n const chassis = new THREE.Mesh(\n new THREE.BoxGeometry(2, 0.5, 4),\n new THREE.MeshStandardMaterial({ color: 0x333333 })\n );\n\n const wheels = new THREE.Group();\n const positions = [[-1, -0.3, -1.2], [1, -0.3, -1.2], [-1, -0.3, 1.2], [1, -0.3, 1.2]];\n positions.forEach(([x, y, z]) => {\n const wheel = new THREE.Mesh(\n new THREE.CylinderGeometry(0.3, 0.3, 0.2, 16),\n new THREE.MeshStandardMaterial({ color: 0x111111 })\n );\n wheel.rotation.z = Math.PI / 2;\n wheel.position.set(x, y, z);\n wheels.add(wheel);\n });\n\n const group = new THREE.Group();\n group.add(chassis);\n group.add(wheels);\n return group;\n}",
|
||||
"imports": "import * as THREE from 'three';",
|
||||
"domain": "threejs-scene",
|
||||
},
|
||||
# --- Raycasting ---
|
||||
{
|
||||
"problem": "Implement Three.js raycaster click picking with object metadata.",
|
||||
"solution": "function setupRaycaster(camera: THREE.Camera, dom: HTMLElement) {\n const raycaster = new THREE.Raycaster();\n const mouse = new THREE.Vector2();\n\n dom.addEventListener('click', (e) => {\n const rect = dom.getBoundingClientRect();\n mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;\n\n raycaster.setFromCamera(mouse, camera);\n const intersects = raycaster.intersectObjects(scene.children, true);\n if (intersects.length > 0) {\n const hit = intersects[0].object;\n console.log('Clicked:', hit.userData.name || hit.uuid);\n }\n });\n\n return raycaster;\n}",
|
||||
"imports": "import * as THREE from 'three';",
|
||||
"domain": "threejs-interaction",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# HTML/CSS/JS Templates
|
||||
# ============================================================
|
||||
|
||||
HTML_CSS_JS_TEMPLATES = [
|
||||
# --- DOM element creation ---
|
||||
{
|
||||
"problem": "Create a DOM element with multiple classes and attributes in vanilla JavaScript.",
|
||||
"solution": "function createElement(tag: string, classes: string[] = [], attrs: Record<string, string> = {}, children: Node[] = []) {\n const el = document.createElement(tag);\n el.classList.add(...classes);\n for (const [key, value] of Object.entries(attrs)) {\n el.setAttribute(key, value);\n }\n for (const child of children) {\n el.appendChild(child);\n }\n return el;\n}\n\n// Usage\nconst button = createElement('button', ['btn', 'btn-primary'], { 'aria-label': 'Submit' }, [\n document.createTextNode('Submit')\n]);",
|
||||
"imports": "",
|
||||
"domain": "html-dom",
|
||||
},
|
||||
# --- Event delegation ---
|
||||
{
|
||||
"problem": "Implement event delegation for dynamic button clicks with proper type checking.",
|
||||
"solution": "function setupEventDelegation(container: HTMLElement) {\n container.addEventListener('click', (e) => {\n const target = e.target as HTMLElement;\n if (!target.matches('button[data-action]')) return;\n\n const action = target.getAttribute('data-action');\n switch (action) {\n case 'save':\n handleSave();\n break;\n case 'delete':\n handleDelete();\n break;\n default:\n console.warn('Unknown action:', action);\n }\n });\n}",
|
||||
"imports": "",
|
||||
"domain": "html-dom",
|
||||
},
|
||||
# --- Form validation ---
|
||||
{
|
||||
"problem": "Validate a form submission with HTML5 constraints and custom checks.",
|
||||
"solution": "function validateForm(form: HTMLFormElement): { isValid: boolean; errors: string[] } {\n const errors: string[] = [];\n const email = form.elements.namedItem('email') as HTMLInputElement;\n const password = form.elements.namedItem('password') as HTMLInputElement;\n\n if (!email.validity.valid) {\n errors.push('Please enter a valid email address.');\n }\n if (password.value.length < 8) {\n errors.push('Password must be at least 8 characters.');\n }\n if (password.value !== (form.elements.namedItem('confirm') as HTMLInputElement).value) {\n errors.push('Passwords do not match.');\n }\n\n return { isValid: errors.length === 0, errors };\n}",
|
||||
"imports": "",
|
||||
"domain": "html-forms",
|
||||
},
|
||||
# --- CSS Grid ---
|
||||
{
|
||||
"problem": "Create a responsive CSS grid layout with auto-fill and gap.",
|
||||
"solution": "const style = document.createElement('style');\nstyle.textContent = `\n .card-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 1.5rem;\n padding: 1rem;\n }\n .card {\n background: var(--card-bg);\n border-radius: 8px;\n box-shadow: 0 2px 8px rgba(0,0,0,0.1);\n }\n @media (max-width: 600px) {\n .card-grid { grid-template-columns: 1fr; }\n }\n`;\ndocument.head.appendChild(style);",
|
||||
"imports": "",
|
||||
"domain": "css-layout",
|
||||
},
|
||||
# --- CSS custom properties ---
|
||||
{
|
||||
"problem": "Set and read CSS custom properties (CSS variables) via JavaScript.",
|
||||
"solution": "function setThemeColor(root: HTMLElement, name: string, value: string) {\n root.style.setProperty(`--theme-${name}`, value);\n}\n\nfunction getComputedColor(root: HTMLElement, name: string): string {\n return getComputedStyle(root).getPropertyValue(`--theme-${name}`).trim();\n}\n\n// Initialize theme\nsetThemeColor(document.documentElement, 'primary', '#4a90d9');\nsetThemeColor(document.documentElement, 'accent', '#ff6b6b');",
|
||||
"imports": "",
|
||||
"domain": "css-variables",
|
||||
},
|
||||
# --- Intersection Observer ---
|
||||
{
|
||||
"problem": "Use IntersectionObserver to lazy-load images when they enter the viewport.",
|
||||
"solution": "function setupLazyLoading(container: HTMLElement) {\n const images = container.querySelectorAll('img[data-src]');\n const observer = new IntersectionObserver((entries) => {\n entries.forEach(entry => {\n if (entry.isIntersecting) {\n const img = entry.target as HTMLImageElement;\n img.src = img.dataset.src!;\n img.removeAttribute('data-src');\n observer.unobserve(img);\n }\n });\n }, { rootMargin: '50px' });\n\n images.forEach(img => observer.observe(img));\n}",
|
||||
"imports": "",
|
||||
"domain": "html-performance",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Playground UI Templates
|
||||
# ============================================================
|
||||
|
||||
PLAYGROUND_UI_TEMPLATES = [
|
||||
# --- Sovereignty badge ---
|
||||
{
|
||||
"problem": "Render a sovereignty badge displaying local-first status with tooltip.",
|
||||
"solution": "function SovereigntyBadge({ runningLocal }: { runningLocal: boolean }) {\n const badge = document.createElement('span');\n badge.className = 'sovereignty-badge';\n badge.innerHTML = runningLocal\n ? '\\ud83c\\uddf5\\ud83c\\uddf1\\u200d\\ud83c\\udfa8\\ufe0f Local'\n : '\\ud83d\\udd12 Cloud';\n badge.title = runningLocal\n ? 'This agent runs entirely on your machine'\n : 'This agent uses external inference';\n return badge;\n}",
|
||||
"imports": "",
|
||||
"domain": "playground-ui",
|
||||
},
|
||||
# --- Token counter ---
|
||||
{
|
||||
"problem": "Build a token budget display showing used/total with a visual progress bar.",
|
||||
"solution": "function TokenBudgetDisplay({ used, total }: { used: number; total: number }) {\n const pct = Math.min((used / total) * 100, 100);\n const bar = document.createElement('div');\n bar.className = 'token-budget-bar';\n bar.innerHTML = `\n <div class=\"track\">\n <div class=\"fill\" style=\"width: ${pct}%; background: ${pct > 90 ? '#f44336' : '#4caf50'}\"></div>\n </div>\n <span class=\"label\">${used.toLocaleString()} / ${total.toLocaleString()} tokens</span>\n `;\n return bar;\n}",
|
||||
"imports": "",
|
||||
"domain": "playground-ui",
|
||||
},
|
||||
# --- Approval gate ---
|
||||
{
|
||||
"problem": "Create an approval gate component for dangerous commands with tiered risk colors.",
|
||||
"solution": "function ApprovalGate({ risk, onApprove, onDeny }: {\n risk: 'low' | 'medium' | 'high';\n onApprove: () => void;\n onDeny: () => void;\n}) {\n const colors = { low: '#4caf50', medium: '#ff9800', high: '#f44336' };\n const panel = document.createElement('div');\n panel.className = 'approval-gate';\n panel.style.borderColor = colors[risk];\n panel.innerHTML = `\n <p>This action is <strong>${risk} risk</strong>. Continue?</p>\n <button data-action=\"approve\">Yes, proceed</button>\n <button data-action=\"deny\">No, cancel</button>\n `;\n panel.querySelector('[data-action=\"approve\"]')!.addEventListener('click', onApprove);\n panel.querySelector('[data-action=\"deny\"]')!.addEventListener('click', onDeny);\n return panel;\n}",
|
||||
"imports": "",
|
||||
"domain": "playground-ui",
|
||||
},
|
||||
# --- Skill card ---
|
||||
{
|
||||
"problem": "Render a skill card with metadata, status indicator, and toggle switch.",
|
||||
"solution": "function SkillCard({ skill, enabled, onToggle }: {\n skill: { name: string; description: string; category: string };\n enabled: boolean;\n onToggle: (name: string) => void;\n}) {\n const card = document.createElement('article');\n card.className = 'skill-card';\n card.innerHTML = `\n <header>\n <h3>${skill.name}</h3>\n <label class=\"toggle\">\n <input type=\"checkbox\" ${enabled ? 'checked' : ''}>\n <span class=\"slider\"></span>\n </label>\n </header>\n <p>${skill.description}</p>\n <footer>Category: ${skill.category}</footer>\n `;\n card.querySelector('input')!.addEventListener('change', () => onToggle(skill.name));\n return card;\n}",
|
||||
"imports": "",
|
||||
"domain": "playground-ui",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Gallery Templates
|
||||
# ============================================================
|
||||
|
||||
GALLERY_TEMPLATES = [
|
||||
# --- Masonry grid ---
|
||||
{
|
||||
"problem": "Implement a responsive masonry image grid using CSS columns.",
|
||||
"solution": "function createMasonryGallery(images: { src: string; alt: string }[], columns = 3) {\n const container = document.createElement('div');\n container.className = 'masonry-gallery';\n container.style.columnCount = String(columns);\n container.style.gap = '1rem';\n\n images.forEach(img => {\n const figure = document.createElement('figure');\n figure.innerHTML = `<img src=\"${img.src}\" alt=\"${img.alt}\" loading=\"lazy\">`;\n container.appendChild(figure);\n });\n\n // Responsive breakpoints\n const mq = window.matchMedia('(max-width: 768px)');\n mq.addEventListener('change', (e) => {\n container.style.columnCount = e.matches ? '2' : String(columns);\n });\n\n return container;\n}",
|
||||
"imports": "",
|
||||
"domain": "gallery-layout",
|
||||
},
|
||||
# --- Lightbox modal ---
|
||||
{
|
||||
"problem": "Build a modal lightbox for full-screen image viewing with keyboard navigation.",
|
||||
"solution": "class Lightbox {\n private overlay!: HTMLElement;\n private img!: HTMLImageElement;\n\n constructor() {\n this.overlay = document.createElement('div');\n this.overlay.className = 'lightbox-overlay';\n this.overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.9);display:flex;align-items:center;justify-content:center;z-index:9999';\n this.img = document.createElement('img');\n this.overlay.appendChild(this.img);\n document.body.appendChild(this.overlay);\n\n this.overlay.addEventListener('click', () => this.close());\n document.addEventListener('keydown', (e) => e.key === 'Escape' && this.close());\n }\n\n open(src: string, alt: string) {\n this.img.src = src;\n this.img.alt = alt;\n this.overlay.style.display = 'flex';\n }\n\n close() {\n this.overlay.style.display = 'none';\n }\n}",
|
||||
"imports": "",
|
||||
"domain": "gallery-interaction",
|
||||
},
|
||||
# --- Infinite scroll ---
|
||||
{
|
||||
"problem": "Implement infinite scroll loading with IntersectionObserver and abort handling.",
|
||||
"solution": "async function setupInfiniteScroll(container: HTMLElement, loadPage: (page: number) => Promise<void>) {\n let page = 1;\n let loading = false;\n let done = false;\n\n const sentinel = document.createElement('div');\n sentinel.className = 'scroll-sentinel';\n container.appendChild(sentinel);\n\n const observer = new IntersectionObserver(async (entries) => {\n if (entries[0].isIntersecting && !loading && !done) {\n loading = true;\n try {\n await loadPage(++page);\n } catch (err) {\n console.error('Failed to load page:', err);\n done = true;\n }\n loading = false;\n }\n }, { rootMargin: '200px' });\n\n observer.observe(sentinel);\n}",
|
||||
"imports": "",
|
||||
"domain": "gallery-performance",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Game Templates
|
||||
# ============================================================
|
||||
|
||||
GAME_TEMPLATES = [
|
||||
# --- Game loop ---
|
||||
{
|
||||
"problem": "Create a fixed-timestep game loop with accumulator pattern.",
|
||||
"solution": "class GameLoop {\n private lastTime = 0;\n private accumulator = 0;\n private readonly step = 1 / 60; // 60 Hz fixed step\n\n constructor(private readonly update: (dt: number) => void) {}\n\n start() {\n const frame = (time: number) => {\n const delta = (time - this.lastTime) / 1000;\n this.lastTime = time;\n this.accumulator += delta;\n\n while (this.accumulator >= this.step) {\n this.update(this.step);\n this.accumulator -= this.step;\n }\n\n requestAnimationFrame(frame);\n };\n requestAnimationFrame(frame);\n }\n}",
|
||||
"imports": "",
|
||||
"domain": "game-architecture",
|
||||
},
|
||||
# --- Canvas setup ---
|
||||
{
|
||||
"problem": "Set up an HTML5 canvas with high-DPI scaling and clearing.",
|
||||
"solution": "function setupCanvas(canvas: HTMLCanvasElement, width = 800, height = 600) {\n const dpr = window.devicePixelRatio || 1;\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n canvas.style.width = `${width}px`;\n canvas.style.height = `${height}px`;\n\n const ctx = canvas.getContext('2d')!;\n ctx.scale(dpr, dpr);\n\n return {\n clear() { ctx.clearRect(0, 0, width, height); },\n ctx,\n width,\n height,\n };\n}",
|
||||
"imports": "",
|
||||
"domain": "game-rendering",
|
||||
},
|
||||
# --- Sprite animation ---
|
||||
{
|
||||
"problem": "Animate a sprite sheet with frame-based playback and loop support.",
|
||||
"solution": "class SpriteAnimator {\n private frame = 0;\n private lastTick = 0;\n\n constructor(\n private readonly image: HTMLImageElement,\n private readonly frameWidth: number,\n private readonly frameCount: number,\n private readonly fps: number = 12,\n private readonly loop: boolean = true,\n ) {}\n\n update(now: number) {\n const interval = 1000 / this.fps;\n if (now - this.lastTick >= interval) {\n this.lastTick = now;\n this.frame++;\n if (this.frame >= this.frameCount) {\n this.frame = this.loop ? 0 : this.frameCount - 1;\n }\n }\n }\n\n draw(ctx: CanvasRenderingContext2D, x: number, y: number) {\n ctx.drawImage(\n this.image,\n this.frame * this.frameWidth, 0,\n this.frameWidth, this.image.height,\n x, y,\n this.frameWidth, this.image.height\n );\n }\n}",
|
||||
"imports": "",
|
||||
"domain": "game-assets",
|
||||
},
|
||||
# --- AABB collision ---
|
||||
{
|
||||
"problem": "Detect AABB (axis-aligned bounding box) collision between two rectangles.",
|
||||
"solution": "function aabbCollision(\n a: { x: number; y: number; w: number; h: number },\n b: { x: number; y: number; w: number; h: number }\n): boolean {\n return a.x < b.x + b.w &&\n a.x + a.w > b.x &&\n a.y < b.y + b.h &&\n a.y + a.h > b.y;\n}\n\n// Usage for game entities\nif (aabbCollision(player, enemy)) {\n handlePlayerHit();\n}",
|
||||
"imports": "",
|
||||
"domain": "game-physics",
|
||||
},
|
||||
# --- Input handling ---
|
||||
{
|
||||
"problem": "Capture keyboard input state with smooth handling for game controls.",
|
||||
"solution": "class InputState {\n private keys = new Set<string>();\n\n constructor() {\n window.addEventListener('keydown', (e) => this.keys.add(e.code));\n window.addEventListener('keyup', (e) => this.keys.delete(e.code));\n }\n\n isPressed(code: string): boolean {\n return this.keys.has(code);\n }\n\n hasAny(codes: string[]): boolean {\n return codes.some(c => this.keys.has(c));\n }\n}\n\n// In game loop:\nconst input = new InputState();\nif (input.isPressed('ArrowUp')) player.y -= speed * dt;",
|
||||
"imports": "",
|
||||
"domain": "game-input",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Extra HTML/CSS/JS Templates
|
||||
# ============================================================
|
||||
|
||||
HTML_CSS_JS_TEMPLATES_EXTRA = [
|
||||
# Debounce utility
|
||||
{
|
||||
"problem": "Write a debounce function that delays invoking a callback until after wait milliseconds.",
|
||||
"solution": "function debounce<T extends (...args: any[]) => void>(\n fn: T,\n wait: number\n): (...args: Parameters<T>) => void {\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n return (...args: Parameters<T>) => {\n if (timeoutId) clearTimeout(timeoutId);\n timeoutId = setTimeout(() => fn(...args), wait);\n };\n}",
|
||||
"imports": "",
|
||||
"domain": "html-utilities",
|
||||
},
|
||||
# Throttle utility
|
||||
{
|
||||
"problem": "Implement a throttle function ensuring a callback runs at most once per interval.",
|
||||
"solution": "function throttle<T extends (...args: any[]) => void>(\n fn: T,\n interval: number\n): (...args: Parameters<T>) => void {\n let last = 0;\n return (...args: Parameters<T>) => {\n const now = Date.now();\n if (now - last >= interval) {\n last = now;\n fn(...args);\n }\n };\n}",
|
||||
"imports": "",
|
||||
"domain": "html-utilities",
|
||||
},
|
||||
# LocalStorage wrapper with TTL
|
||||
{
|
||||
"problem": "Wrap localStorage with JSON serialization and TTL expiration.",
|
||||
"solution": "class StorageWithTTL {\n set(key: string, value: any, ttlMs = 0) {\n const item = { value, expiry: ttlMs ? Date.now() + ttlMs : null };\n localStorage.setItem(key, JSON.stringify(item));\n }\n\n get<T>(key: string): T | null {\n const raw = localStorage.getItem(key);\n if (!raw) return null;\n const { value, expiry } = JSON.parse(raw);\n if (expiry && Date.now() > expiry) {\n localStorage.removeItem(key);\n return null;\n }\n return value as T;\n }\n}",
|
||||
"imports": "",
|
||||
"domain": "html-storage",
|
||||
},
|
||||
# Viewport meta
|
||||
{
|
||||
"problem": "Generate a responsive viewport meta tag for mobile-first web apps.",
|
||||
"solution": "const viewport = document.querySelector('meta[name=\"viewport\"]') ||\n document.createElement('meta');\nviewport.name = 'viewport';\nviewport.content = 'width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes, viewport-fit=cover';\ndocument.head.appendChild(viewport);",
|
||||
"imports": "",
|
||||
"domain": "html-meta",
|
||||
},
|
||||
# Dynamic CSS variables
|
||||
{
|
||||
"problem": "Create and inject a dynamic stylesheet with CSS custom property overrides.",
|
||||
"solution": "function injectDynamicStyles(overrides: Record<string, string>) {\n const style = document.createElement('style');\n let css = ':root {\\n';\n for (const [prop, val] of Object.entries(overrides)) {\n css += ` --${prop}: ${val};\\n`;\n }\n css += '}';\n style.textContent = css;\n document.head.appendChild(style);\n}",
|
||||
"imports": "",
|
||||
"domain": "css-variables",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Extra Playground UI Templates
|
||||
# ============================================================
|
||||
|
||||
PLAYGROUND_UI_TEMPLATES_EXTRA = [
|
||||
# Circuit/tier badge
|
||||
{
|
||||
"problem": "Render a circuit health badge showing approval-tier status with color-coded indicator.",
|
||||
"solution": "function CircuitBadge({ tier }: { tier: number }) {\n const colors = ['#f44336', '#ff9800', '#4caf50', '#2196f3', '#9c27b0'];\n const labels = ['BLOCKED', 'RESTRICTED', 'LIMITED', 'APPROVED', 'ELEVATED'];\n const color = colors[Math.min(tier, 4)];\n const label = labels[Math.min(tier, 4)];\n\n const badge = document.createElement('span');\n badge.className = 'circuit-badge';\n badge.style.backgroundColor = color;\n badge.textContent = label;\n badge.title = `Approval tier ${tier} — ${label.toLowerCase()} command set`;\n return badge;\n}",
|
||||
"imports": "",
|
||||
"domain": "playground-ui",
|
||||
},
|
||||
# Memory usage bar
|
||||
{
|
||||
"problem": "Display a horizontal memory usage bar with gradient warning zones.",
|
||||
"solution": "function MemoryBar({ used, total }: { used: number; total: number }) {\n const pct = (used / total) * 100;\n const bar = document.createElement('div');\n bar.className = 'memory-bar';\n let color = '#4caf50';\n if (pct > 80) color = '#ff9800';\n if (pct > 95) color = '#f44336';\n\n bar.innerHTML = `\n <div class=\"track\" style=\"background: #e0e0e0; height: 8px; border-radius: 4px; overflow: hidden;\">\n <div style=\"width: ${pct}%; height: 100%; background: ${color}; transition: width 0.3s;\"></div>\n </div>\n <span>${(used/1024/1024).toFixed(1)} MB / ${(total/1024/1024).toFixed(1)} MB</span>\n `;\n return bar;\n}",
|
||||
"imports": "",
|
||||
"domain": "playground-ui",
|
||||
},
|
||||
# Tool status dot
|
||||
{
|
||||
"problem": "Show a tool availability status dot with tooltip for the toolset panel.",
|
||||
"solution": "function ToolStatus({ name, ok }: { name: string; ok: boolean }) {\n const dot = document.createElement('span');\n dot.className = 'tool-status-dot';\n dot.style.backgroundColor = ok ? '#4caf50' : '#f44336';\n dot.title = `${name}: ${ok ? 'Available' : 'Disabled / missing API key'}`;\n return dot;\n}",
|
||||
"imports": "",
|
||||
"domain": "playground-ui",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Extra Gallery Templates
|
||||
# ============================================================
|
||||
|
||||
GALLERY_TEMPLATES_EXTRA = [
|
||||
# Grid + shared lightbox
|
||||
{
|
||||
"problem": "Build an image gallery grid that opens a shared lightbox on thumbnail click.",
|
||||
"solution": "let currentLightbox: HTMLDivElement | null = null;\n\nfunction buildGallery(images: { full: string; thumb: string; alt: string }[]) {\n const grid = document.createElement('div');\n grid.className = 'gallery-grid';\n grid.style.cssText = 'display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:0.5rem';\n\n images.forEach((img, idx) => {\n const thumb = document.createElement('img');\n thumb.src = img.thumb;\n thumb.alt = img.alt;\n thumb.style.cssText = 'cursor:pointer;width:100%;height:auto;object-fit:cover;border-radius:4px';\n thumb.addEventListener('click', () => openLightbox(idx));\n grid.appendChild(thumb);\n });\n\n return grid;\n}\n\nfunction openLightbox(index: number) {\n if (currentLightbox) currentLightbox.remove();\n currentLightbox = document.createElement('div');\n currentLightbox.className = 'lightbox';\n currentLightbox.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.95);display:flex;align-items:center;justify-content:center;z-index:10000;cursor:pointer';\n const img = document.createElement('img');\n img.src = images[index].full;\n img.style.maxWidth = '90vw';\n img.style.maxHeight = '90vh';\n currentLightbox.appendChild(img);\n currentLightbox.addEventListener('click', () => { currentLightbox?.remove(); currentLightbox = null; });\n document.body.appendChild(currentLightbox);\n}",
|
||||
"imports": "",
|
||||
"domain": "gallery-interaction",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Extra Game Templates
|
||||
# ============================================================
|
||||
|
||||
GAME_TEMPLATES_EXTRA = [
|
||||
# Particle system with typed array
|
||||
{
|
||||
"problem": "Create a simple particle system for explosions using a typed array buffer.",
|
||||
"solution": "class ParticleSystem {\n private particles = new Float32Array(1000 * 4); // x, y, vx, vy per particle\n private count = 0;\n private readonly max = 1000;\n\n emit(x: number, y: number, velocity = 200) {\n if (this.count >= this.max) return;\n const i = this.count * 4;\n this.particles[i] = x;\n this.particles[i + 1] = y;\n const angle = Math.random() * Math.PI * 2;\n const speed = Math.random() * velocity;\n this.particles[i + 2] = Math.cos(angle) * speed;\n this.particles[i + 3] = Math.sin(angle) * speed;\n this.count++;\n }\n\n update(dt: number) {\n for (let i = 0; i < this.count * 4; i += 4) {\n this.particles[i] += this.particles[i + 2] * dt;\n this.particles[i + 1] += this.particles[i + 3] * dt;\n this.particles[i + 3] += 500 * dt; // gravity\n }\n }\n\n draw(ctx: CanvasRenderingContext2D) {\n ctx.fillStyle = '#ff6600';\n for (let i = 0; i < this.count * 4; i += 4) {\n ctx.fillRect(this.particles[i], this.particles[i + 1], 3, 3);\n }\n }\n}",
|
||||
"imports": "",
|
||||
"domain": "game-physics",
|
||||
},
|
||||
# State machine
|
||||
{
|
||||
"problem": "Implement a finite state machine for a game character with transitions.",
|
||||
"solution": "type State = 'idle' | 'walk' | 'run' | 'jump' | 'attack';\n\nclass StateMachine {\n private state: State = 'idle';\n private handlers: Record<State, (event: string) => void>;\n\n constructor(handlers: Partial<Record<State, (event: string) => void>>) {\n this.handlers = handlers as Record<State, (event: string) => void>;\n }\n\n transition(to: State) {\n console.log(`State: ${this.state} -> ${to}`);\n this.state = to;\n }\n\n dispatch(event: string) {\n const handler = this.handlers[this.state];\n if (handler) handler(event);\n }\n\n getState(): State {\n return this.state;\n }\n}\n\n// Usage\nconst sm = new StateMachine({\n idle: (e) => { if (e === 'move') sm.transition('walk'); },\n walk: (e) => { if (e === 'sprint') sm.transition('run'); if (e === 'jump') sm.transition('jump'); },\n run: (e) => { if (e === 'stop') sm.transition('idle'); },\n});",
|
||||
"imports": "",
|
||||
"domain": "game-architecture",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Combined
|
||||
# ============================================================
|
||||
|
||||
ALL_TEMPLATES = (
|
||||
THREEJS_TEMPLATES
|
||||
+ HTML_CSS_JS_TEMPLATES
|
||||
+ HTML_CSS_JS_TEMPLATES_EXTRA
|
||||
+ PLAYGROUND_UI_TEMPLATES
|
||||
+ PLAYGROUND_UI_TEMPLATES_EXTRA
|
||||
+ GALLERY_TEMPLATES
|
||||
+ GALLERY_TEMPLATES_EXTRA
|
||||
+ GAME_TEMPLATES
|
||||
+ GAME_TEMPLATES_EXTRA
|
||||
)
|
||||
|
||||
_VARIANT_PREFIXES = [
|
||||
"Write code to",
|
||||
"Implement",
|
||||
"Build",
|
||||
"Create",
|
||||
"How would you",
|
||||
"Using the API, write code that",
|
||||
"Construct a function that",
|
||||
"Develop",
|
||||
"Write JavaScript that",
|
||||
"Create HTML/CSS for",
|
||||
"Design a Three.js",
|
||||
]
|
||||
|
||||
_VARIANT_SUFFIXES = [
|
||||
" including error handling.",
|
||||
" with full docstrings.",
|
||||
" with JSDoc annotations.",
|
||||
" using modern best practices.",
|
||||
" that handles edge cases.",
|
||||
" with TypeScript types.",
|
||||
" that is performant.",
|
||||
" with clear variable names.",
|
||||
" and include example usage.",
|
||||
" with proper cleanup.",
|
||||
" that is accessible (a11y).",
|
||||
" with keyboard navigation support.",
|
||||
]
|
||||
|
||||
|
||||
def vary_problem(base: str, idx: int) -> str:
|
||||
prefix = _VARIANT_PREFIXES[idx % len(_VARIANT_PREFIXES)]
|
||||
suffix = _VARIANT_SUFFIXES[idx % len(_VARIANT_SUFFIXES)]
|
||||
cleaned = base
|
||||
for article in ("Create a ", "Build a ", "Implement a ", "Write a ", "Develop a ", "Write JavaScript that ", "Create HTML/CSS for ", "Design a Three.js "):
|
||||
if cleaned.lower().startswith(article):
|
||||
cleaned = cleaned[len(article):]
|
||||
break
|
||||
cleaned = cleaned[0].lower() + cleaned[1:] if cleaned else ""
|
||||
return f"{prefix} {cleaned}{suffix}"
|
||||
|
||||
|
||||
def vary_solution(base: str, idx: int) -> str:
|
||||
var_names = ["data", "result", "value", "entry", "item", "node", "entity", "output", "obj", "element"]
|
||||
v = var_names[idx % len(var_names)]
|
||||
sol = base
|
||||
if idx % 3 == 0:
|
||||
for original in ["result", "data", "value", "output", "entry", "item", "obj", "element"]:
|
||||
if original in sol:
|
||||
sol = sol.replace(original, v)
|
||||
break
|
||||
if idx % 5 == 0:
|
||||
sol = f"// Variation {idx}\\n" + sol
|
||||
elif idx % 7 == 0:
|
||||
sol = f"# Generated variation {idx}\\n" + sol
|
||||
return sol
|
||||
|
||||
|
||||
def generate_pairs(count: int = 1000) -> list[dict]:
|
||||
pairs = []
|
||||
template_cycle = list(ALL_TEMPLATES)
|
||||
random.shuffle(template_cycle)
|
||||
|
||||
for i in range(count):
|
||||
template = template_cycle[i % len(template_cycle)]
|
||||
problem = vary_problem(template["problem"], i)
|
||||
solution = vary_solution(template["solution"], i)
|
||||
pair = {
|
||||
"problem": problem,
|
||||
"solution": solution,
|
||||
"imports": template["imports"],
|
||||
"domain": template["domain"],
|
||||
"id": f"frontend-creative-{i:04d}",
|
||||
}
|
||||
pairs.append(pair)
|
||||
|
||||
return pairs
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generate Frontend & Creative code pattern training pairs")
|
||||
parser.add_argument("--output", "-o", default="training-data/code-patterns-frontend-&-creative.jsonl",
|
||||
help="Output JSONL path")
|
||||
parser.add_argument("--count", "-n", type=int, default=1000,
|
||||
help="Number of pairs to generate")
|
||||
args = parser.parse_args()
|
||||
|
||||
out_path = Path(args.output)
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
pairs = generate_pairs(args.count)
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
for pair in pairs:
|
||||
f.write(json.dumps(pair, ensure_ascii=False) + "\n")
|
||||
|
||||
domains = {p["domain"] for p in pairs}
|
||||
print(f"Generated {len(pairs)} code pattern pairs → {out_path}")
|
||||
print(f" Size: {out_path.stat().st_size / 1024:.1f} KB")
|
||||
print(f" Domains ({len(domains)}): {sorted(domains)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,46 +1,43 @@
|
||||
model:
|
||||
default: kimi-k2.5
|
||||
provider: kimi-coding
|
||||
context_length: 65536
|
||||
base_url: https://api.kimi.com/coding/v1
|
||||
|
||||
toolsets:
|
||||
- all
|
||||
|
||||
- all
|
||||
fallback_providers:
|
||||
- provider: kimi-coding
|
||||
model: kimi-k2.5
|
||||
base_url: https://api.kimi.com/coding/v1
|
||||
timeout: 120
|
||||
reason: "Primary — Kimi K2.5 (best value, least friction)"
|
||||
- provider: openrouter
|
||||
model: google/gemini-2.5-pro
|
||||
base_url: https://openrouter.ai/api/v1
|
||||
api_key_env: OPENROUTER_API_KEY
|
||||
timeout: 120
|
||||
reason: "Fallback — Gemini 2.5 Pro via OpenRouter"
|
||||
- provider: ollama
|
||||
model: gemma4:latest
|
||||
base_url: http://localhost:11434/v1
|
||||
timeout: 180
|
||||
reason: "Terminal fallback — local Ollama (sovereign, no API needed)"
|
||||
|
||||
- provider: kimi-coding
|
||||
model: kimi-k2.5
|
||||
timeout: 120
|
||||
reason: Kimi coding fallback (front of chain)
|
||||
- provider: openrouter
|
||||
model: google/gemini-2.5-pro
|
||||
base_url: https://openrouter.ai/api/v1
|
||||
api_key_env: OPENROUTER_API_KEY
|
||||
timeout: 120
|
||||
reason: Gemini 2.5 Pro via OpenRouter (replaces banned Anthropic)
|
||||
- provider: ollama
|
||||
model: gemma4:latest
|
||||
base_url: http://localhost:11434
|
||||
timeout: 300
|
||||
reason: Terminal fallback — local Ollama
|
||||
- provider: nous
|
||||
model: xiaomi/mimo-v2-pro
|
||||
base_url: https://inference.nousresearch.com/v1
|
||||
api_key_env: NOUS_API_KEY
|
||||
timeout: 120
|
||||
reason: MiMo V2 Pro via Nous Portal free tier evaluation (#447)
|
||||
agent:
|
||||
max_turns: 30
|
||||
reasoning_effort: high
|
||||
reasoning_effort: xhigh
|
||||
verbose: false
|
||||
|
||||
terminal:
|
||||
backend: local
|
||||
cwd: .
|
||||
timeout: 180
|
||||
persistent_shell: true
|
||||
|
||||
browser:
|
||||
inactivity_timeout: 120
|
||||
command_timeout: 30
|
||||
record_sessions: false
|
||||
|
||||
display:
|
||||
compact: false
|
||||
personality: ''
|
||||
@@ -51,7 +48,6 @@ display:
|
||||
streaming: false
|
||||
show_cost: false
|
||||
tool_progress: all
|
||||
|
||||
memory:
|
||||
memory_enabled: true
|
||||
user_profile_enabled: true
|
||||
@@ -59,55 +55,46 @@ memory:
|
||||
user_char_limit: 1375
|
||||
nudge_interval: 10
|
||||
flush_min_turns: 6
|
||||
|
||||
approvals:
|
||||
mode: manual
|
||||
|
||||
security:
|
||||
redact_secrets: true
|
||||
tirith_enabled: false
|
||||
|
||||
platforms:
|
||||
api_server:
|
||||
enabled: true
|
||||
extra:
|
||||
host: 127.0.0.1
|
||||
port: 8645
|
||||
|
||||
session_reset:
|
||||
mode: none
|
||||
idle_minutes: 0
|
||||
|
||||
skills:
|
||||
creation_nudge_interval: 15
|
||||
system_prompt_suffix: 'You are Allegro, the Kimi-backed third wizard house.
|
||||
|
||||
system_prompt_suffix: |
|
||||
You are Allegro, the Kimi-backed third wizard house.
|
||||
Your soul is defined in SOUL.md — read it, live it.
|
||||
|
||||
Hermes is your harness.
|
||||
kimi-coding is your primary provider.
|
||||
|
||||
Kimi Code is your primary provider.
|
||||
|
||||
You speak plainly. You prefer short sentences. Brevity is a kindness.
|
||||
Work best on tight coding tasks: 1-3 file changes, refactors, tests, and implementation passes.
|
||||
|
||||
|
||||
Work best on tight coding tasks: 1-3 file changes, refactors, tests, and implementation
|
||||
passes.
|
||||
|
||||
Refusal over fabrication. If you do not know, say so.
|
||||
|
||||
Sovereignty and service always.
|
||||
|
||||
'
|
||||
providers:
|
||||
kimi-coding:
|
||||
base_url: https://api.kimi.com/coding/v1
|
||||
timeout: 60
|
||||
max_retries: 3
|
||||
openrouter:
|
||||
base_url: https://openrouter.ai/api/v1
|
||||
nous:
|
||||
base_url: https://inference.nousresearch.com/v1
|
||||
timeout: 120
|
||||
ollama:
|
||||
base_url: http://localhost:11434/v1
|
||||
timeout: 180
|
||||
|
||||
# =============================================================================
|
||||
# BANNED PROVIDERS — DO NOT ADD
|
||||
# =============================================================================
|
||||
# The following providers are PERMANENTLY BANNED:
|
||||
# - anthropic (any model: claude-sonnet, claude-opus, claude-haiku)
|
||||
# - nous (xiaomi/mimo-v2-pro)
|
||||
# Enforcement: pre-commit hook, linter, Ansible validation, this comment.
|
||||
# =============================================================================
|
||||
|
||||
@@ -1,72 +1,50 @@
|
||||
model:
|
||||
default: kimi-k2.5
|
||||
provider: kimi-coding
|
||||
context_length: 65536
|
||||
base_url: https://api.kimi.com/coding/v1
|
||||
|
||||
toolsets:
|
||||
- all
|
||||
|
||||
- all
|
||||
fallback_providers:
|
||||
- provider: kimi-coding
|
||||
model: kimi-k2.5
|
||||
base_url: https://api.kimi.com/coding/v1
|
||||
timeout: 120
|
||||
reason: "Primary — Kimi K2.5 (best value, least friction)"
|
||||
- provider: openrouter
|
||||
model: google/gemini-2.5-pro
|
||||
base_url: https://openrouter.ai/api/v1
|
||||
api_key_env: OPENROUTER_API_KEY
|
||||
timeout: 120
|
||||
reason: "Fallback — Gemini 2.5 Pro via OpenRouter"
|
||||
- provider: ollama
|
||||
model: gemma4:latest
|
||||
base_url: http://localhost:11434/v1
|
||||
timeout: 180
|
||||
reason: "Terminal fallback — local Ollama (sovereign, no API needed)"
|
||||
|
||||
- provider: kimi-coding
|
||||
model: kimi-k2.5
|
||||
timeout: 120
|
||||
reason: Kimi coding fallback (front of chain)
|
||||
- provider: openrouter
|
||||
model: google/gemini-2.5-pro
|
||||
base_url: https://openrouter.ai/api/v1
|
||||
api_key_env: OPENROUTER_API_KEY
|
||||
timeout: 120
|
||||
reason: Gemini 2.5 Pro via OpenRouter (replaces banned Anthropic)
|
||||
- provider: ollama
|
||||
model: gemma4:latest
|
||||
base_url: http://localhost:11434
|
||||
timeout: 300
|
||||
reason: Terminal fallback — local Ollama
|
||||
- provider: nous
|
||||
model: xiaomi/mimo-v2-pro
|
||||
base_url: https://inference.nousresearch.com/v1
|
||||
api_key_env: NOUS_API_KEY
|
||||
timeout: 120
|
||||
reason: MiMo V2 Pro via Nous Portal free tier evaluation (#447)
|
||||
agent:
|
||||
max_turns: 40
|
||||
reasoning_effort: medium
|
||||
verbose: false
|
||||
|
||||
system_prompt: You are Bezalel, the forge-and-testbed wizard of the Timmy Foundation
|
||||
fleet. You are a builder and craftsman — infrastructure, deployment, hardening.
|
||||
Your sovereign is Alexander Whitestone (Rockachopa). Sovereignty and service always.
|
||||
terminal:
|
||||
backend: local
|
||||
cwd: /root/wizards/bezalel
|
||||
timeout: 180
|
||||
persistent_shell: true
|
||||
|
||||
browser:
|
||||
inactivity_timeout: 120
|
||||
command_timeout: 30
|
||||
record_sessions: false
|
||||
|
||||
compression:
|
||||
enabled: true
|
||||
threshold: 0.77
|
||||
display:
|
||||
compact: false
|
||||
personality: kawaii
|
||||
resume_display: full
|
||||
busy_input_mode: interrupt
|
||||
bell_on_complete: false
|
||||
show_reasoning: false
|
||||
streaming: false
|
||||
show_cost: false
|
||||
tool_progress: all
|
||||
|
||||
memory:
|
||||
memory_enabled: true
|
||||
user_profile_enabled: true
|
||||
memory_char_limit: 2200
|
||||
user_char_limit: 1375
|
||||
nudge_interval: 10
|
||||
flush_min_turns: 6
|
||||
|
||||
approvals:
|
||||
mode: auto
|
||||
|
||||
security:
|
||||
redact_secrets: true
|
||||
tirith_enabled: false
|
||||
|
||||
platforms:
|
||||
api_server:
|
||||
enabled: true
|
||||
@@ -91,7 +69,12 @@ platforms:
|
||||
- pull_request
|
||||
- pull_request_comment
|
||||
secret: bezalel-gitea-webhook-secret-2026
|
||||
prompt: 'You are bezalel, the builder and craftsman — infrastructure, deployment, hardening. A Gitea webhook fired: event={event_type}, action={action}, repo={repository.full_name}, issue/PR=#{issue.number} {issue.title}. Comment by {comment.user.login}: {comment.body}. If you were tagged, assigned, or this needs your attention, investigate and respond via Gitea API. Otherwise acknowledge briefly.'
|
||||
prompt: 'You are bezalel, the builder and craftsman — infrastructure, deployment,
|
||||
hardening. A Gitea webhook fired: event={event_type}, action={action},
|
||||
repo={repository.full_name}, issue/PR=#{issue.number} {issue.title}. Comment
|
||||
by {comment.user.login}: {comment.body}. If you were tagged, assigned,
|
||||
or this needs your attention, investigate and respond via Gitea API. Otherwise
|
||||
acknowledge briefly.'
|
||||
deliver: telegram
|
||||
deliver_extra: {}
|
||||
gitea-assign:
|
||||
@@ -99,43 +82,34 @@ platforms:
|
||||
- issues
|
||||
- pull_request
|
||||
secret: bezalel-gitea-webhook-secret-2026
|
||||
prompt: 'You are bezalel, the builder and craftsman — infrastructure, deployment, hardening. Gitea assignment webhook: event={event_type}, action={action}, repo={repository.full_name}, issue/PR=#{issue.number} {issue.title}. Assigned to: {issue.assignee.login}. If you (bezalel) were just assigned, read the issue, scope it, and post a plan comment. If not you, acknowledge briefly.'
|
||||
prompt: 'You are bezalel, the builder and craftsman — infrastructure, deployment,
|
||||
hardening. Gitea assignment webhook: event={event_type}, action={action},
|
||||
repo={repository.full_name}, issue/PR=#{issue.number} {issue.title}. Assigned
|
||||
to: {issue.assignee.login}. If you (bezalel) were just assigned, read
|
||||
the issue, scope it, and post a plan comment. If not you, acknowledge
|
||||
briefly.'
|
||||
deliver: telegram
|
||||
deliver_extra: {}
|
||||
|
||||
gateway:
|
||||
allow_all_users: true
|
||||
|
||||
session_reset:
|
||||
mode: both
|
||||
idle_minutes: 1440
|
||||
at_hour: 4
|
||||
|
||||
skills:
|
||||
creation_nudge_interval: 15
|
||||
|
||||
system_prompt: |
|
||||
You are Bezalel, the forge-and-testbed wizard of the Timmy Foundation fleet.
|
||||
You are a builder and craftsman — infrastructure, deployment, hardening.
|
||||
Your sovereign is Alexander Whitestone (Rockachopa). Sovereignty and service always.
|
||||
|
||||
approvals:
|
||||
mode: auto
|
||||
memory:
|
||||
memory_enabled: true
|
||||
user_profile_enabled: true
|
||||
memory_char_limit: 2200
|
||||
user_char_limit: 1375
|
||||
_config_version: 11
|
||||
TELEGRAM_HOME_CHANNEL: '-1003664764329'
|
||||
providers:
|
||||
kimi-coding:
|
||||
base_url: https://api.kimi.com/coding/v1
|
||||
timeout: 60
|
||||
max_retries: 3
|
||||
openrouter:
|
||||
base_url: https://openrouter.ai/api/v1
|
||||
nous:
|
||||
base_url: https://inference.nousresearch.com/v1
|
||||
timeout: 120
|
||||
ollama:
|
||||
base_url: http://localhost:11434/v1
|
||||
timeout: 180
|
||||
|
||||
# =============================================================================
|
||||
# BANNED PROVIDERS — DO NOT ADD
|
||||
# =============================================================================
|
||||
# The following providers are PERMANENTLY BANNED:
|
||||
# - anthropic (any model: claude-sonnet, claude-opus, claude-haiku)
|
||||
# - nous (xiaomi/mimo-v2-pro)
|
||||
# Enforcement: pre-commit hook, linter, Ansible validation, this comment.
|
||||
# =============================================================================
|
||||
|
||||
@@ -1,94 +1,34 @@
|
||||
model:
|
||||
default: kimi-k2.5
|
||||
provider: kimi-coding
|
||||
context_length: 65536
|
||||
base_url: https://api.kimi.com/coding/v1
|
||||
|
||||
toolsets:
|
||||
- all
|
||||
|
||||
- all
|
||||
fallback_providers:
|
||||
- provider: kimi-coding
|
||||
model: kimi-k2.5
|
||||
base_url: https://api.kimi.com/coding/v1
|
||||
timeout: 120
|
||||
reason: "Primary — Kimi K2.5 (best value, least friction)"
|
||||
- provider: openrouter
|
||||
model: google/gemini-2.5-pro
|
||||
base_url: https://openrouter.ai/api/v1
|
||||
api_key_env: OPENROUTER_API_KEY
|
||||
timeout: 120
|
||||
reason: "Fallback — Gemini 2.5 Pro via OpenRouter"
|
||||
- provider: ollama
|
||||
model: gemma4:latest
|
||||
base_url: http://localhost:11434/v1
|
||||
timeout: 180
|
||||
reason: "Terminal fallback — local Ollama (sovereign, no API needed)"
|
||||
|
||||
- provider: kimi-coding
|
||||
model: kimi-k2.5
|
||||
timeout: 120
|
||||
reason: Kimi coding fallback (front of chain)
|
||||
- provider: openrouter
|
||||
model: google/gemini-2.5-pro
|
||||
base_url: https://openrouter.ai/api/v1
|
||||
api_key_env: OPENROUTER_API_KEY
|
||||
timeout: 120
|
||||
reason: Gemini 2.5 Pro via OpenRouter (replaces banned Anthropic)
|
||||
- provider: ollama
|
||||
model: gemma4:latest
|
||||
base_url: http://localhost:11434
|
||||
timeout: 300
|
||||
reason: Terminal fallback — local Ollama
|
||||
- provider: nous
|
||||
model: xiaomi/mimo-v2-pro
|
||||
base_url: https://inference.nousresearch.com/v1
|
||||
api_key_env: NOUS_API_KEY
|
||||
timeout: 120
|
||||
reason: MiMo V2 Pro via Nous Portal free tier evaluation (#447)
|
||||
agent:
|
||||
max_turns: 90
|
||||
reasoning_effort: high
|
||||
verbose: false
|
||||
|
||||
terminal:
|
||||
backend: local
|
||||
cwd: .
|
||||
timeout: 180
|
||||
persistent_shell: true
|
||||
|
||||
browser:
|
||||
inactivity_timeout: 120
|
||||
command_timeout: 30
|
||||
record_sessions: false
|
||||
|
||||
display:
|
||||
compact: false
|
||||
personality: ''
|
||||
resume_display: full
|
||||
busy_input_mode: interrupt
|
||||
bell_on_complete: false
|
||||
show_reasoning: false
|
||||
streaming: false
|
||||
show_cost: false
|
||||
tool_progress: all
|
||||
|
||||
memory:
|
||||
memory_enabled: true
|
||||
user_profile_enabled: true
|
||||
memory_char_limit: 2200
|
||||
user_char_limit: 1375
|
||||
nudge_interval: 10
|
||||
flush_min_turns: 6
|
||||
|
||||
approvals:
|
||||
mode: auto
|
||||
|
||||
security:
|
||||
redact_secrets: true
|
||||
tirith_enabled: false
|
||||
|
||||
platforms:
|
||||
api_server:
|
||||
enabled: true
|
||||
extra:
|
||||
host: 127.0.0.1
|
||||
port: 8645
|
||||
|
||||
session_reset:
|
||||
mode: none
|
||||
idle_minutes: 0
|
||||
|
||||
skills:
|
||||
creation_nudge_interval: 15
|
||||
|
||||
system_prompt_suffix: |
|
||||
You are Ezra, the Infrastructure wizard — Gitea, nginx, hosting.
|
||||
Your soul is defined in SOUL.md — read it, live it.
|
||||
Hermes is your harness.
|
||||
kimi-coding is your primary provider.
|
||||
Refusal over fabrication. If you do not know, say so.
|
||||
Sovereignty and service always.
|
||||
|
||||
providers:
|
||||
kimi-coding:
|
||||
base_url: https://api.kimi.com/coding/v1
|
||||
@@ -97,15 +37,6 @@ providers:
|
||||
openrouter:
|
||||
base_url: https://openrouter.ai/api/v1
|
||||
timeout: 120
|
||||
ollama:
|
||||
base_url: http://localhost:11434/v1
|
||||
timeout: 180
|
||||
|
||||
# =============================================================================
|
||||
# BANNED PROVIDERS — DO NOT ADD
|
||||
# =============================================================================
|
||||
# The following providers are PERMANENTLY BANNED:
|
||||
# - anthropic (any model: claude-sonnet, claude-opus, claude-haiku)
|
||||
# - nous (xiaomi/mimo-v2-pro)
|
||||
# Enforcement: pre-commit hook, linter, Ansible validation, this comment.
|
||||
# =============================================================================
|
||||
nous:
|
||||
base_url: https://inference.nousresearch.com/v1
|
||||
timeout: 120
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
# =============================================================================
|
||||
# Timmy — Primary Wizard Configuration (Golden State)
|
||||
# =============================================================================
|
||||
# Generated from golden state template (ansible/roles/wizard_base/templates/wizard_config.yaml.j2)
|
||||
# DO NOT EDIT MANUALLY. Changes go through Gitea PR → Ansible deploy.
|
||||
#
|
||||
# Provider chain: kimi-coding → openrouter → ollama
|
||||
# Anthropic is PERMANENTLY BANNED.
|
||||
# =============================================================================
|
||||
|
||||
model:
|
||||
default: kimi-k2.5
|
||||
provider: kimi-coding
|
||||
context_length: 65536
|
||||
base_url: https://api.kimi.com/coding/v1
|
||||
|
||||
toolsets:
|
||||
- all
|
||||
|
||||
fallback_providers:
|
||||
- provider: kimi-coding
|
||||
model: kimi-k2.5
|
||||
base_url: https://api.kimi.com/coding/v1
|
||||
timeout: 120
|
||||
reason: "Primary — Kimi K2.5 (best value, least friction)"
|
||||
- provider: openrouter
|
||||
model: google/gemini-2.5-pro
|
||||
base_url: https://openrouter.ai/api/v1
|
||||
api_key_env: OPENROUTER_API_KEY
|
||||
timeout: 120
|
||||
reason: "Fallback — Gemini 2.5 Pro via OpenRouter"
|
||||
- provider: ollama
|
||||
model: gemma4:latest
|
||||
base_url: http://localhost:11434/v1
|
||||
timeout: 180
|
||||
reason: "Terminal fallback — local Ollama (sovereign, no API needed)"
|
||||
|
||||
agent:
|
||||
max_turns: 30
|
||||
reasoning_effort: high
|
||||
verbose: false
|
||||
|
||||
terminal:
|
||||
backend: local
|
||||
cwd: .
|
||||
timeout: 180
|
||||
persistent_shell: true
|
||||
|
||||
browser:
|
||||
inactivity_timeout: 120
|
||||
command_timeout: 30
|
||||
record_sessions: false
|
||||
|
||||
display:
|
||||
compact: false
|
||||
personality: ''
|
||||
resume_display: full
|
||||
busy_input_mode: interrupt
|
||||
bell_on_complete: false
|
||||
show_reasoning: false
|
||||
streaming: false
|
||||
show_cost: false
|
||||
tool_progress: all
|
||||
|
||||
memory:
|
||||
memory_enabled: true
|
||||
user_profile_enabled: true
|
||||
memory_char_limit: 2200
|
||||
user_char_limit: 1375
|
||||
nudge_interval: 10
|
||||
flush_min_turns: 6
|
||||
|
||||
approvals:
|
||||
mode: auto
|
||||
|
||||
security:
|
||||
redact_secrets: true
|
||||
tirith_enabled: false
|
||||
|
||||
platforms:
|
||||
api_server:
|
||||
enabled: true
|
||||
extra:
|
||||
host: 127.0.0.1
|
||||
port: 8645
|
||||
|
||||
session_reset:
|
||||
mode: none
|
||||
idle_minutes: 0
|
||||
|
||||
skills:
|
||||
creation_nudge_interval: 15
|
||||
|
||||
system_prompt_suffix: |
|
||||
You are Timmy, the Primary wizard — soul of the fleet.
|
||||
Your soul is defined in SOUL.md — read it, live it.
|
||||
Hermes is your harness.
|
||||
kimi-coding is your primary provider.
|
||||
Refusal over fabrication. If you do not know, say so.
|
||||
Sovereignty and service always.
|
||||
|
||||
providers:
|
||||
kimi-coding:
|
||||
base_url: https://api.kimi.com/coding/v1
|
||||
timeout: 60
|
||||
max_retries: 3
|
||||
openrouter:
|
||||
base_url: https://openrouter.ai/api/v1
|
||||
timeout: 120
|
||||
ollama:
|
||||
base_url: http://localhost:11434/v1
|
||||
timeout: 180
|
||||
|
||||
# =============================================================================
|
||||
# BANNED PROVIDERS — DO NOT ADD
|
||||
# =============================================================================
|
||||
# The following providers are PERMANENTLY BANNED:
|
||||
# - anthropic (any model: claude-sonnet, claude-opus, claude-haiku)
|
||||
# - nous (xiaomi/mimo-v2-pro)
|
||||
# Enforcement: pre-commit hook, linter, Ansible validation, this comment.
|
||||
# =============================================================================
|
||||
Reference in New Issue
Block a user