| name | pace-config |
| description | Generate a config.yaml for the pace personal dashboard from a user's natural-language description of interests. Maps topics to content adapters (RSS, Hacker News, Reddit, GitHub, arXiv, YouTube, Mastodon, etc.), composes transform pipelines, designs flexbox layouts, and optionally wires up LLM-powered summarization and ranking. Use when asked to configure, set up feeds for, or customize a pace dashboard.
|
Configure a pace dashboard
Generate a config.yaml for pace from a user's description of what they want to track.
Source of truth: The snippets in this skill are illustrative and may lag behind the
running version. Always confirm current shapes with:
pace adapters list
pace adapters explain <type>
pace transforms list
pace transforms explain <type>
pace presets list
Process
- Check whether a bundled preset already covers the user's request -- see "Start from a preset" below.
- Ask the user what topics, communities, or content types they care about.
- Map those interests to adapters (see reference below).
- Choose sensible params and transforms for each adapter.
- If multiple adapters feed the same topic, create a pipeline to merge and dedupe them.
- Design a layout that gives more space to primary interests.
- If the user wants summaries, filtering by relevance, or interest-based ranking, add LLM config.
- Write the final
config.yaml.
- Run
pace config check config.yaml to validate before handing off to the user.
Start from a preset
Before building from scratch, check whether a bundled preset already matches the user's theme:
pace presets list
To use a preset as the starting point:
pace --preset tech-news
docker run -d -p 7453:7453 -v pace-data:/app/data ghcr.io/av/pace:latest --preset tech-news
Guidance: prefer preset + targeted edits when the user's ask matches a theme. Only build
from scratch when no preset is close. When using a preset, copy it to config.yaml, make the
minimal changes the user requested, and validate with pace config check.
Modifying an existing config
When the user already has a config.yaml:
Example -- "drop YouTube, show only LLM news from HN":
adapters:
- type: hackernews
params:
feed: top
limit: 50
- type: youtube
params:
channels: [UCXuqSBlHAE6Xw-yeJA0Tunw]
limit: 15
adapters:
- type: hackernews
params:
feed: top
limit: 50
transforms:
- type: llm-filter
criteria: "relevant to LLM or AI research"
- Read the current file before making any edits.
- Make the minimal changes required -- remove/replace adapters the user no longer wants, add
new ones, adjust params and transforms.
- Validate the edited config:
pace config check config.yaml
This catches schema errors and unknown adapter types without starting the server -- fast
iteration loop.
- Restart required: pace reads
config.yaml once at startup; it does not watch the file
for changes. After writing the new config, the server must be restarted to pick it up:
docker compose restart
docker stop <container> && docker run ...
- Immediate panel refresh (after restart): trigger a panel to fetch content right away
without waiting for
refresh_interval:
curl -X POST http://localhost:7453/refresh/<panel-id>
Adapter reference
Every adapter has an optional refresh_interval (minutes, default 15, minimum 1) and an optional transforms list applied at ingest time. The name field disambiguates multiple instances of the same type.
hackernews
Hacker News stories.
- type: hackernews
params:
type: top
feed: top
stories: top
limit: 30
min_score: 0
rss
Any RSS or Atom feed.
- type: rss
params:
urls:
- https://example.com/feed.xml
limit: 50
reddit
Reddit subreddits.
Caveat: Reddit's public unauthenticated .json API often returns HTTP 403 upstream, so this adapter may not work without credentials. Bundled presets omit Reddit for this reason. For community discussions that work out of the box, prefer lemmy (used in tech-news and ml-ai presets).
- type: reddit
params:
subreddits: [programming, selfhosted]
sort: hot
limit: 25
min_score: 0
time: day
github
GitHub trending repos or releases from specific repos. Use mode to pick.
- type: github
params:
mode: trending
language: typescript
since: daily
limit: 15
- name: gh-releases
type: github
params:
mode: releases
repos: [facebook/react, oven-sh/bun]
limit: 10
token: ${GITHUB_TOKEN}
github-releases
Dedicated release tracker (alternative to github mode: releases).
- type: github-releases
params:
repos: [denoland/deno, oven-sh/bun]
limit: 5
token: ${GITHUB_TOKEN}
lobsters
Lobste.rs stories.
- type: lobsters
params:
feed: hottest
limit: 25
min_score: 0
tags: []
mastodon
Mastodon posts by hashtag or account.
- type: mastodon
params:
instance: fosstodon.org
hashtags: [rust, opensource]
accounts: []
limit: 20
min_favourites: 0
only_media: false
youtube
YouTube channel or playlist feeds.
- type: youtube
params:
channels:
- UCXuqSBlHAE6Xw-yeJA0Tunw
playlists: []
limit: 15
arxiv
Academic papers from arXiv.
- type: arxiv
params:
categories: [cs.AI, cs.LG]
query: ""
limit: 20
stackexchange
Questions from Stack Exchange sites.
- type: stackexchange
params:
site: stackoverflow
tags: [typescript, react]
sort: hot
limit: 20
min_score: 0
devto
DEV.to articles.
- type: devto
params:
tags: [typescript, webdev]
username: ""
limit: 15
per_page: 20
min_reactions: 0
top: 7
producthunt
Product Hunt launches.
- type: producthunt
params:
limit: 20
min_upvotes: 0
enrich: false
podcast
Podcast episodes from RSS feeds.
- type: podcast
params:
feeds:
- https://feeds.simplecast.com/54nAGcIl
limit: 10
twitter
Twitter/X lists and searches (requires authentication).
Caveat: Without params.bearer_token, this adapter always returns an empty list — no error, just no items. A Twitter API v2 bearer token is required for real usage. Run pace adapters explain twitter for the current param shape.
- type: twitter
params:
lists: []
searches: []
bearer_token: ${TWITTER_BEARER_TOKEN}
npm
npm registry package search.
- type: npm
params:
keywords: [typescript, cli]
scope: "my-org"
limit: 20
sort: optimal
lemmy
Lemmy federated community posts.
- type: lemmy
params:
instance: lemmy.ml
communities: [technology, linux]
sort: hot
limit: 25
min_score: 0
wikipedia
Wikipedia featured content.
- type: wikipedia
params:
modes:
- most_read
- on_this_day
language: en
limit: 20
bookmarks
Curated bookmark links defined directly in config (no network fetch).
- type: bookmarks
params:
items:
- title: "Linear"
url: "https://linear.app"
description: "Issue tracker"
tags: ["work", "daily"]
- title: "Figma"
url: "https://figma.com"
tags: ["design"]
counter
Fetches a JSON endpoint and extracts a numeric value for stat-card display. Use with display: counter on panels.
- type: counter
params:
url: https://api.github.com/repos/oven-sh/bun
json_path: stargazers_count
label: "Bun Stars"
Transform reference
Transforms run at ingest time on adapter or pipeline results. They apply sequentially in the order listed.
transforms:
- type: latest
count: 50
- type: filter
keywords: [ai, rust]
fields: [title, body]
- type: exclude
keywords: [sponsored, hiring]
fields: [title, body]
- type: sort
field: timestamp
direction: desc
- type: dedupe
strategy: url
threshold: 0.85
keep: highest-score
- type: time-decay
half_life: "12h"
engagement_weight: 0.7
recency_weight: 0.3
decay: exponential
min_score: 0.1
annotate: false
- type: keyword-score
keywords:
- term: "rust"
weight: 10
- term: "AI|machine learning"
weight: 15
regex: true
min_score: 0
annotate: false
- type: cluster
strategy: auto
min_cluster_size: 2
max_clusters: 10
similarity_threshold: 0.6
annotate: false
- type: llm-summarize
- type: llm-filter
criteria: "relevant to AI research"
- type: llm-rank
interests:
- open source software
- type: llm-merge
prompt: "Group items about the same topic"
Pipelines
Pipelines merge items from multiple adapters and apply cross-source transforms. Define them when the user wants a combined view.
pipelines:
- name: combined
sources: [hackernews, reddit]
refresh_interval: 15
transforms:
- type: dedupe
strategy: url
- type: latest
count: 40
A panel references a pipeline by using the pipeline name as its source.
Layout
Layout is a recursive flexbox tree. Each node is one of:
- Container (
direction + children): row or column flex wrapper.
- Panel (
panel + source): content feed panel. Optional display: counter renders stat cards instead of a content list.
- Image widget (
image): static image from a URL.
- Text widget (
text): static text block (plain, markdown, or html).
- Iframe widget (
iframe): embedded external page in a sandboxed iframe.
layout:
direction: row
gap: 16
children:
- panel: hackernews
source: hackernews
flex: 2
limit: 30
- direction: column
flex: 1
children:
- panel: reddit
source: reddit
- panel: releases
source: gh-releases
- panel: rss
flex: 1
source: rss
limit: 20
Widget nodes
Widgets are layout leaves that display static content (no adapter source needed).
- image: https://example.com/banner.png
alt: "Dashboard banner"
object_fit: cover
max_height: 200px
link: https://example.com
flex: 1
- text: "Welcome to the dashboard"
format: markdown
title: "Greeting"
flex: 1
- iframe: https://grafana.example.com/d/abc
title: "Grafana"
height: 400px
aspect_ratio: 16/9
sandbox: "allow-scripts allow-same-origin"
allow: "fullscreen"
flex: 1
Counter display mode
Panels using the counter adapter should set display: counter to render stat cards:
- panel: metrics
source: my-counter
display: counter
The special source all shows items from every adapter. If layout is omitted entirely, pace renders a single "all" panel as the default.
Below 768px the layout collapses to a single column automatically.
LLM config (optional)
LLM transforms (llm-summarize, llm-filter, llm-rank, llm-merge) pass items through unchanged when the llm block is absent or misconfigured -- they never error out.
llm:
provider: anthropic
model: claude-sonnet-4-6
api_key: ${LLM_API_KEY}
interests:
- artificial intelligence
- typescript
llm:
provider: openai
model: gpt-4o
api_key: ${LLM_API_KEY}
interests:
- artificial intelligence
- typescript
Wiring the API key:
LLM_API_KEY=sk-... bun run dev
docker run -d -p 7453:7453 \
-e LLM_API_KEY=sk-... \
-v ./config.yaml:/app/config.yaml:ro \
-v pace-data:/app/data \
ghcr.io/av/pace:latest
The provider value is passed to pi-ai; supported providers include anthropic, openai, google, groq, mistral, and others -- see pi-ai docs for the full list.
Optional: timeout_seconds caps each LLM completion (default 120). Raise it for slow local models (e.g. Ollama on modest hardware); a completion that exceeds it is abandoned and the items pass through unchanged.
llm:
provider: ollama
model: llama3.1
api_key: unused
base_url: http://localhost:11434/v1
timeout_seconds: 300
Server config (optional)
An optional top-level server block controls server behavior (unknown fields are a validation error):
server:
base_path: /pace
retention_days: 30
base_path (string): for deployments under a subpath like https://example.com/pace/. Normalized on load (leading / added, trailing / stripped). The server responds at both the prefix and the root, so it works whether or not the proxy strips the prefix.
retention_days (non-negative integer): items last fetched more than this many days ago are pruned at startup and every 24 hours. 0 keeps everything.
Port and config path are NOT config fields: use --port/$PORT and --config/--preset/$PACE_CONFIG.
Worked example
User says: "I want to follow AI/ML research, Rust programming news, and new open-source releases. I don't care about hiring posts."
Resulting config.yaml:
adapters:
- type: hackernews
params:
feed: top
limit: 50
refresh_interval: 10
transforms:
- type: filter
keywords: [ai, llm, rust, ml, neural, transformer, open source]
fields: [title]
- type: exclude
keywords: [hiring, "who is hiring"]
fields: [title]
- type: latest
count: 40
- type: arxiv
params:
categories: [cs.AI, cs.LG, cs.CL]
limit: 20
refresh_interval: 60
transforms:
- type: latest
count: 20
- type: rss
params:
urls:
- https://blog.rust-lang.org/feed.xml
- https://this-week-in-rust.org/atom.xml
refresh_interval: 30
- type: lobsters
params:
feed: hottest
limit: 25
tags: [rust]
refresh_interval: 15
- name: gh-releases
type: github
params:
mode: releases
repos: [rust-lang/rust, huggingface/transformers, pytorch/pytorch]
limit: 10
refresh_interval: 60
pipelines:
- name: ai-combined
sources: [hackernews, arxiv]
transforms:
- type: dedupe
strategy: title-similarity
threshold: 0.8
- type: latest
count: 40
- name: rust-combined
sources: [rss, lobsters]
transforms:
- type: dedupe
strategy: url
- type: latest
count: 25
layout:
direction: row
children:
- panel: ai
source: ai-combined
flex: 2
limit: 40
- direction: column
flex: 1
children:
- panel: rust
source: rust-combined
limit: 25
- panel: releases
source: gh-releases
limit: 10