Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 11 additions & 24 deletions .claude/skills/aeon/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,47 +50,34 @@ Anything it prints is on disk but unconfigured. **Orientation — what's install

Goal: one real notification in their phone, fast. Do not configure a schedule first.

1. **Get a repo. Ask public or private before you run anything** — it changes the command, and switching later means moving the repo.

**Public** (recommend this): Actions minutes are free, and upstream skill updates arrive with one command.

```bash
gh repo fork aeonfun/aeon --clone && cd aeon
gh repo set-default <owner>/aeon # REQUIRED — see below
```

**Private**: a fork of a public repo is always public, so a private instance is a mirror, not a fork.
1. **Run `./aeon init`.** Ask public or private first (public: Actions minutes are free; private: `--private`, minutes bill against the account quota, 2,000/mo on Free). Then, from a clone of the template:

```bash
gh repo create <name> --private
git clone --bare https://github.com/aeonfun/aeon.git
git -C aeon.git push --mirror https://github.com/<owner>/<name>.git
rm -rf aeon.git && git clone https://github.com/<owner>/<name>.git && cd <name>
git remote add upstream https://github.com/aeonfun/aeon.git
gh repo set-default <owner>/<name> # REQUIRED — see below
git clone https://github.com/aeonfun/aeon && cd aeon
./aeon init # add --private, --name <repo>, --harness <h> as needed
```

Say both costs out loud before they pick private: Actions minutes bill against the account quota (2,000/mo on Free — scheduled skills burn it), and updates come from `git fetch upstream && git merge upstream/main` instead of `gh repo sync`.
It is idempotent and prints a check or a fix per step: signs in to GitHub with the `workflow` scope, creates `<owner>/<name>` from the **template** (not a fork: forks start with Actions disabled), points this folder at it (`aeonfun/aeon` stays as the `upstream` remote), runs `gh repo set-default`, enables Actions and lets them open PRs (the default token permission is left as is), offers to store the gh token as `GH_GLOBAL` (only if it has `repo` + `workflow`), connects a model from the credential manifest, and links Telegram with a `/start` deep link. Re-run it any time; `bin/onboard` is the read-only check. `--dry-run` shows every step without changing anything.

**Pin the default repo before any other command — both paths.** Both end up with an `upstream` remote (`gh repo fork --clone` adds one for you), and with no default pinned **`gh` prefers `upstream` over `origin`**. Everything in Aeon routes through `gh -R $(gh repo view …)`, so an unpinned checkout silently writes secrets to and dispatches runs against `aeonfun/aeon` instead of their instance — with no error, because the commands genuinely succeed on the wrong repo. Verify:
**If they set things up by hand, pin the default repo before any other command.** With an `upstream` remote and no default pinned, **`gh` prefers `upstream` over `origin`**, so secrets and runs silently land on `aeonfun/aeon`. Fix and verify:

```bash
gh repo set-default <owner>/<repo>
gh repo view --json nameWithOwner -q .nameWithOwner # must print THEIR repo
```

Everything after this step is identical either way.
2. **Auth a model.** At least one is required. Fastest is `./aeon auth --harness claude-code` (Claude Pro/Max, opens a browser), or `./aeon auth --key <key>`, which detects the provider **from the key prefix** — `sk-ant-oat` (OAuth), `sk-or-` (OpenRouter), `bk_` (Bankr), `inf_` (Surplus), `xai-` (Grok); anything else lands in `ANTHROPIC_API_KEY`.
2. **Auth a model** (if `init` skipped it). At least one is required. The choices per harness, in the order the workflow uses them, are in `harness-adapter/harnesses.json` (`credentials`) and the table in `docs/harnesses.md`. Fastest is `./aeon auth --harness claude-code` (Claude Pro/Max, opens a browser), or `./aeon auth --key <key>`, which detects the provider **from the key prefix**: `sk-ant-oat` (OAuth), `sk-or-` (OpenRouter), `bk_` (Bankr), `inf_` (Surplus), `xai-` (Grok); anything else lands in `ANTHROPIC_API_KEY`.

**UsePod and Venice keys have no prefix** and are undetectable, so a bare `--key` files them as a plain Anthropic key and the run fails later with a confusing auth error. They must be named:
**UsePod, Venice, GLM and HivemindOS keys have no prefix** and are undetectable, so a bare `--key` files them as a plain Anthropic key and the run fails later with a confusing auth error. They must be named:

```bash
./aeon auth --key <token> --provider usepod # same for venice
./aeon auth --key <token> --provider usepod # same for venice, glm, hivemindos
```

`--dry-run` prints the resolved `method=… → secret …` without calling `gh` or `claude` — worth running whenever the provider is in doubt.
`--dry-run` prints the resolved `method=... -> secret ...` without calling `gh` or `claude`; run it whenever the provider is in doubt.

**Don't assume they have a Claude subscription:** ten providers work, including OpenRouter, Grok, GLM, and crypto-settled gateways. See "Providers and harnesses".
3. **Wire one channel.** Telegram is the fastest: create a bot with @BotFather, then `./aeon secrets set TELEGRAM_BOT_TOKEN --stdin` and `TELEGRAM_CHAT_ID`. Skip Discord/Slack/email for now — one channel is enough to prove it works.
3. **Wire one channel** (if `init` skipped it). Telegram is the fastest: `./aeon init` asks for the @BotFather token and links the chat for them; by hand it is `./aeon secrets set TELEGRAM_BOT_TOKEN --stdin` and `TELEGRAM_CHAT_ID`. Skip Discord/Slack/email for now; one channel is enough to prove it works.
4. **Run one skill now.** Pick it with Mode 6 — ask what they want handled, propose one — then `./aeon skills run <name>`. Wait for it, then `./aeon runs logs <id>`. They should get a Telegram message.
5. **Only then, schedule it.** `./aeon skills enable <name>` and set a time (see Mode 2).

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/aeon/references/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Thirteen `ci-*.yml` workflows. Twelve are **path-filtered** and fire on `pull_re
| `ci-readme-catalog` | `.github/README.md`, `catalog/*.json`, `docs/skill-packs.md`, `docs/aeon-setup.md`, `docs/examples/README.md`, the hero SVG | README skill tables and skill counts match the catalog | `node scripts/validate-readme-catalog.mjs` |
| `ci-tests` | `scripts/**`, `bin/**`, `aeon.yml`, `.github/workflows/aeon.yml`, `harness-adapter/adapters/**` | the `scripts/tests/` suites + config validation | see below |
| `ci-shellcheck` | `aeon`, `scripts/**`, `bin/**`, `harness-adapter/**`, `skills/**/*.sh` | shellcheck on the tracked shell surface | `bash scripts/lint-shell.sh` |
| `ci-harnesses-json` | `harness-adapter/adapters/**`, `harness-adapter/bin/generate-harnesses-json`, `harness-adapter/harnesses.json` | committed harness manifest == fresh regen | `harness-adapter/bin/generate-harnesses-json` |
| `ci-harnesses-json` | `harness-adapter/adapters/**`, `harness-adapter/bin/generate-harnesses-json`, `harness-adapter/harnesses.json`, `harness-adapter/gateways.json` | committed harness + gateway manifests == fresh regen | `harness-adapter/bin/generate-harnesses-json` |
| `ci-capabilities-parity` | `bin/install-skill-pack`, `docs/CAPABILITIES.md` | capabilities taxonomy in sync across both | `bash scripts/check-capabilities-parity.sh` |
| `ci-skill-packs` | `catalog/skill-packs.json`, `docs/community-skill-packs.md`, `bin/install-skill-pack`, `skills/security/trusted-sources.txt` | community registry well-formed + matches the Listed packs table in `docs/community-skill-packs.md`; no unbacked `trust_level: trusted` | `node scripts/validate-skill-packs.mjs` |
| `ci-agents-md` | `CLAUDE.md`, `STRATEGY.md`, `AGENTS.md`, `scripts/gen-agents-md.js` | `AGENTS.md` regenerated from `CLAUDE.md` (with `STRATEGY.md` inlined) | `node scripts/gen-agents-md.js --check` |
Expand Down
2 changes: 2 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ sidecar, like Venice/Surplus).
4. **`scripts/llm-gateway.sh`** — add an `aeon_present()` case, add the slug to the auto-resolver's default `GATEWAY_ORDER`, and add a `case` branch (a **native** provider exports `ANTHROPIC_BASE_URL` + the auth token; a **sidecar** provider calls `start_ccr_sidecar <slug> <openai-url> <key> <model>`).
5. **`.github/workflows/aeon.yml`** — pass the new secret (and any `*_MODEL` override **variables**) into the run's `env:` (also `messages.yml`), so the resolver can see it.

6. **`harness-adapter/adapters/claude.sh`** - add an entry to the `gw-meta` block in the same position as in `GATEWAY_ORDER` (label, secrets, prefixes, transport, base URL, where to get a key), then run `harness-adapter/bin/generate-harnesses-json` and commit the regenerated `harness-adapter/gateways.json`. `aeon init` and `bin/onboard` read it, and `scripts/tests/test_credential_manifest.sh` (ci-tests) fails if it disagrees with steps 1, 4 or 5.

Then add a row to the gateway table in [`docs/CONFIGURATION.md`](../docs/CONFIGURATION.md#llm-gateways). To
verify the full loop: paste a key in the dashboard (prefix should auto-detect, or
pick it from the dropdown) and run any skill — the workflow log prints
Expand Down
29 changes: 23 additions & 6 deletions .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,33 @@
<img src="../docs/assets/quickstart-aeon.jpg" alt="Quick start in four steps: 1 Fork - Use this template to get your own repo copy. 2 Connect - add a Telegram, Discord, Slack, or email channel. 3 Pick skills - toggle skills on and set their cron schedule. 4 Runs itself - Aeon runs unattended on GitHub Actions." width="100%" />
</p>

You need **Node.js 20+**, the **[GitHub CLI](https://cli.github.com/) (`gh`)** authenticated (`gh auth login`), and **your own copy** - click **Use this template** on [the repo page](https://github.com/aeonfun/aeon) (keep it public; Actions minutes are free), or `gh repo fork aeonfun/aeon --clone`.
You need **Node.js 20+** and the **[GitHub CLI](https://cli.github.com/) (`gh`)**. Then:

```bash
git clone https://github.com/<you>/aeon # skip if you used `gh repo fork --clone`
cd aeon
gh repo set-default <you>/aeon # a fork defaults to upstream aeonfun/aeon otherwise
./aeon
git clone https://github.com/aeonfun/aeon && cd aeon
./aeon init
```

Open [localhost:5555](http://localhost:5555) and follow the dashboard: **Authenticate** (any of nine [harnesses](../docs/harnesses.md)) → **add a channel** → **pick skills** → **Run**. That's it - Aeon runs unattended. Everything is also an `./aeon` command ([CLI](../apps/cli/README.md)) or a `/aeon` chat command ([setup skill](../docs/aeon-setup.md), installable as a [Claude Code or Codex plugin](../docs/aeon-setup.md#install)).
`./aeon init` does the rest and tells you what it did at each step: signs you in to GitHub, creates **your own repo** from the template (public by default - Actions minutes are free; `--private` if you prefer), points this folder and `gh` at it, turns on GitHub Actions, stores your GitHub token for runs, connects a model (any of nine [harnesses](../docs/harnesses.md): a Claude subscription, an API key, or one OpenRouter key) and, if you want, links Telegram. It is safe to re-run: every step checks first and skips what is already done. Then `./aeon` opens the dashboard at [localhost:5555](http://localhost:5555) to **pick skills** and **Run**. `bin/onboard` re-checks the whole setup any time (read-only).

<details>
<summary><strong>Prefer to do it by hand?</strong></summary>

1. Click **Use this template** on [the repo page](https://github.com/aeonfun/aeon) (keep it public; Actions minutes are free). Use the template rather than **Fork**: a fork starts with **GitHub Actions disabled** and its schedules never fire until you enable workflows in its Actions tab.
2. Clone it and point `gh` at it - without this, secrets you set land on `aeonfun/aeon` instead of your repo:

```bash
gh auth login --web -s workflow
git clone https://github.com/<you>/aeon && cd aeon
gh repo set-default <you>/aeon
./aeon
```

3. In the dashboard: **Authenticate** -> **add a channel** -> **pick skills** -> **Run**.

Everything is also an `./aeon` command ([CLI](../apps/cli/README.md)) or a `/aeon` chat command ([setup skill](../docs/aeon-setup.md), installable as a [Claude Code or Codex plugin](../docs/aeon-setup.md#install)). For skills that reach other repos, add a classic PAT with `repo` + `workflow` as `GH_GLOBAL` ([details](../docs/CONFIGURATION.md#cross-repo-access)).

</details>

<details>
<summary><strong>No admin rights / can't install <code>gh</code>?</strong></summary>
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/ci-apps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ jobs:
- name: Lint (eslint, borrows the dashboard install)
run: npm run lint
working-directory: apps/cli
# `aeon init` end to end against a fake gh (a directory of bare repos):
# template switch-over, re-run, resume, --dir, refusals. No network.
- name: aeon init sandbox tests
run: bash apps/cli/test/init-sandbox.sh

mcp-server:
name: mcp-server — build
Expand Down
21 changes: 15 additions & 6 deletions .github/workflows/ci-harnesses-json.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
name: ci-harnesses-json

# harness-adapter/harnesses.json is the capability manifest for the seven adapters -
# harness-adapter/harnesses.json is the capability manifest for the nine adapters -
# the local analog of a UHP GET /v1/harnesses discovery response. It is generated
# from each adapters/<h>.sh rh-meta block by
# harness-adapter/bin/generate-harnesses-json. This workflow fails any PR that
# edits a generator input without committing the regenerated manifest, so the
# manifest cannot drift from the adapters it describes.
# manifest cannot drift from the adapters it describes. The same generator writes
# harness-adapter/gateways.json (the claude gateway cascade, from the gw-meta
# block in adapters/claude.sh), so it is diffed the same way.
#
# Same shape as ci-skills-json.yml. There are no git-derived fields here, so only
# the `generated` timestamp is normalized out of the diff.
Expand All @@ -16,13 +18,15 @@ on:
- 'harness-adapter/adapters/**'
- 'harness-adapter/bin/generate-harnesses-json'
- 'harness-adapter/harnesses.json'
- 'harness-adapter/gateways.json'
- '.github/workflows/ci-harnesses-json.yml'
push:
branches: [main]
paths:
- 'harness-adapter/adapters/**'
- 'harness-adapter/bin/generate-harnesses-json'
- 'harness-adapter/harnesses.json'
- 'harness-adapter/gateways.json'
workflow_dispatch:

permissions:
Expand All @@ -39,15 +43,20 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Regenerate harnesses.json and diff (ignoring generated timestamp)
- name: Regenerate harnesses.json + gateways.json and diff (ignoring generated timestamp)
run: |
set -euo pipefail
norm() { sed -E 's/"generated": *"[^"]*"/"generated":""/' "$1"; }
norm harness-adapter/harnesses.json > /tmp/hn.committed.json
norm harness-adapter/gateways.json > /tmp/gw.committed.json
harness-adapter/bin/generate-harnesses-json
norm harness-adapter/harnesses.json > /tmp/hn.regenerated.json
if ! diff -u /tmp/hn.committed.json /tmp/hn.regenerated.json; then
echo "::error::harnesses.json is stale - run harness-adapter/bin/generate-harnesses-json and commit the result (the 'generated' timestamp is ignored)."
norm harness-adapter/gateways.json > /tmp/gw.regenerated.json
stale=0
diff -u /tmp/hn.committed.json /tmp/hn.regenerated.json || stale=1
diff -u /tmp/gw.committed.json /tmp/gw.regenerated.json || stale=1
if [ "$stale" = 1 ]; then
echo "::error::harnesses.json or gateways.json is stale - run harness-adapter/bin/generate-harnesses-json and commit the result (the 'generated' timestamp is ignored)."
exit 1
fi
echo "harnesses-json: OK - committed manifest matches a fresh regen (generated timestamp aside)."
echo "harnesses-json: OK - committed manifests match a fresh regen (generated timestamp aside)."
9 changes: 9 additions & 0 deletions .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ on:
- '.github/workflows/chain-runner.yml'
- 'skills/**'
- 'apps/dashboard/lib/constants.ts'
- 'apps/dashboard/lib/harness-auth.ts'
- 'apps/dashboard/lib/gateway-registry.ts'
- '.github/workflows/ci-tests.yml'
push:
branches: [main]
Expand All @@ -35,6 +37,8 @@ on:
- '.github/workflows/chain-runner.yml'
- 'skills/**'
- 'apps/dashboard/lib/constants.ts'
- 'apps/dashboard/lib/harness-auth.ts'
- 'apps/dashboard/lib/gateway-registry.ts'
- '.github/workflows/ci-tests.yml'
workflow_dispatch:

Expand Down Expand Up @@ -121,6 +125,11 @@ jobs:
run: bash scripts/tests/test_generate_harnesses_json.sh
- name: harness resolution tests
run: bash scripts/tests/test_resolve_harness.sh
# harnesses.json credentials/default_model + gateways.json mirror code in
# resolve-harness.sh, llm-gateway.sh, install-harness.sh, aeon.yml and the
# dashboard registries; this fails the moment any of them drifts.
- name: credential manifest consistency tests
run: bash scripts/tests/test_credential_manifest.sh
- name: workflow harness choice tests
run: bash scripts/tests/test_workflow_harness_choices.sh
- name: dashboard model lists vs workflow model choices + harness defaults
Expand Down
40 changes: 31 additions & 9 deletions aeon
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,22 @@ fi

cd "$DASHBOARD"

# Install deps if needed
# Install deps on first run. Quietly: the raw npm output is pages of peer
# warnings (eslint plugin ranges) and an audit of dev-only lint deps, which
# reads like an error to a first-time user. It goes to a log that is shown
# only when the install fails. `npm ci` when the lockfile exists, so the
# install is exactly what CI tested.
if [ ! -d node_modules ]; then
echo "Installing dependencies..."
npm install
echo "Installing dashboard dependencies (first run only)..."
NPM_LOG="$(mktemp "${TMPDIR:-/tmp}/aeon-npm-install.XXXXXX")"
NPM_CMD=install
[ -f package-lock.json ] && NPM_CMD=ci
if ! npm "$NPM_CMD" --no-audit --no-fund --loglevel=error >"$NPM_LOG" 2>&1; then
echo "npm $NPM_CMD failed. Last lines (full log: $NPM_LOG):" >&2
tail -n 30 "$NPM_LOG" >&2
exit 1
fi
rm -f "$NPM_LOG"
fi

PORT="${PORT:-5555}"
Expand All @@ -73,13 +85,23 @@ while lsof -iTCP:"$PORT" -sTCP:LISTEN -t >/dev/null 2>&1; do
PORT=$((PORT + 1))
done

# Banner. Plain one-column characters only inside the box (an emoji is two
# columns wide in most terminals and breaks the right edge), and every row is
# padded to the same inner width, whatever the port length.
URL="http://127.0.0.1:$PORT"
INNER=38
[ $((${#URL} + 6)) -gt "$INNER" ] && INNER=$((${#URL} + 6))
bar="$(printf '%*s' "$INNER" '' | sed 's/ /═/g')"
row() { printf ' ║%-*s║\n' "$INNER" "$1"; }
TITLE="Aeon Dashboard"
pad=$(((INNER - ${#TITLE}) / 2))
echo ""
echo " ╔══════════════════════════════════════╗"
echo " ║ ⚡ Aeon Dashboard ⚡ ║"
echo " ║ ║"
printf " ║ ➜ http://127.0.0.1:%-15s ║\n" "$PORT"
echo " ║ ║"
echo " ╚══════════════════════════════════════╝"
echo " ╔${bar}╗"
row "$(printf '%*s%s' "$pad" '' "$TITLE")"
row ""
row " -> $URL"
row ""
echo " ╚${bar}╝"
echo ""

# Bind loopback only. The /api/* routes can set/delete repo secrets and
Expand Down
Loading
Loading