nicheclock

Configuration

nicheclock reads one YAML file. init writes it, you edit it. Paths in it are resolved against the folder the file is in. Pick the file with --config (or -c), the NICHECLOCK_CONFIG environment variable, or ./nicheclock.yaml, in that order.

Secrets never go into the file. Fields that need one name an environment variable (token_env: TG_TOKEN) or a file (token_file: path) instead.

The whole file

identity:
  user_agent: "mysite-radar/1.0 (+mailto:you@example.com)"   # required before any fetch
  contact_email: you@example.com                              # required before any fetch
data_dir: ./data              # SQLite database and caches
out_dir: ./out                # reports: <out_dir>/<project>/{feed.json,feed.md,index.html,feed.xml}
default_lag_days: 3           # index lag used for publish_by until Search Console gives a measured one
projects:
  - name: example
    urls: [https://example.com]
    schedule: daily           # recorded for your scheduler; `run` does not read it (see Limitations)
    regions: [global]         # or country codes such as [US, GB]; see Regions
    niches:
      - name: birdwatching
        keywords: [birdwatching, bird watching]    # phrases that make an item relevant
        entities: [Snowy owl]                    # specific things, as Wikipedia titles where possible
        context: [Bird]                    # broad terms: count only next to a keyword or entity
        formats: [field guide, checklist]  # content formats, shown in the report, never matched as topics
        exclude: [tips, beginner]          # an item containing one of these is dropped
        sources: [google_trends, wikipedia, hn, github, gdelt]
        rss: []                            # feeds you supply (titles, links and dates only are kept)
        watch: []                          # feeds where any keyword hit is enough
        ics: []                            # calendars for the Upcoming table
        upcoming: true
        options: {geo: [US], hn_min_points: 50}
llm: null                     # your model, see Models; null means no model call, ever

Validate it any time with nicheclock doctor (add --offline to skip the network checks).

Fields

Field What it does
identity.user_agent, identity.contact_email Sent with every request. Required. There is no default.
data_dir, out_dir Where the database and the reports go.
default_lag_days Days between publishing and appearing in search, used for publish_by until Search Console measures yours.
projects[].urls The site or sites the project describes. Only your own site is crawled by init.
niches[].keywords Multi-word phrases beat single words. Generic phrases such as "small teams" are refused by the relevance floor.
niches[].entities Specific things. Wikipedia article titles work best because pageview signals are keyed by title.
niches[].context Broad terms (places, big companies, generic concepts). They count only beside a keyword or entity in the same item and never pass alone.
niches[].exclude Phrases that drop an item.
niches[].sources Which sources run for this niche. Remove a name to switch the source off (see Sources).
niches[].rss, ics, watch Your own feeds and calendars. watch marks feeds where any keyword hit is enough because you chose the feed.

nicheclock edit changes any of the list fields without opening an editor: --add-keyword, --remove-keyword, and the same for entity, format, exclude, source, rss, watch, ics. It also has --add-niche, --rename-niche, --remove-niche, --add-url, --remove-url, --add-notify and --preview. Run nicheclock edit --help for the full list.

Per-source options

Under niches[].options:

Option Default Source Meaning
geo [US] Google Trends Country codes of the trending lists to read.
wiki_lang en Wikipedia Language edition for pageviews.
wiki_max_entities 15 Wikipedia How many entities to look up per run.
wiki_min_ratio 1.5 Wikipedia A rising article must beat this multiple of its 14-day median views.
hn_days, hn_min_points, hn_max_queries 7, 10, 8 Hacker News Look-back window, minimum points, queries per run.
github_days, github_max_queries, github_token_env 30, 5, none GitHub Window, queries, and the name of the environment variable that holds your own token.
gdelt_max_queries, gdelt_min_ratio 2, 2.0 GDELT Queries per run and the news-volume ratio to report.
fr_days, fr_max_queries 7, 6 Federal Register Window and queries.
rss_days 7 RSS/Atom How old an item may be.
ics_horizon_days 365 ICS How far ahead events are read.
apple_countries, apple_charts [us], [top-free] Apple Storefronts and charts.

How a topic gets into the feed

Keywords and entities decide relevance. Fuzzy full-text searches (Hacker News, GitHub, GDELT, Federal Register) must contain a keyword or entity in the title. A topic then needs to clear a floor. Any one of these is enough:

Abstract-only Federal Register matches go to low_confidence in feed.json. Kept signals are stored in SQLite at <data_dir>/nicheclock.db.

GDELT is one global queue: ten seconds between requests, answers cached 24 hours. When it rate-limits you, the rest of the queue is saved to <data_dir>/cache/gdelt_queue.json and goes first in the next run.

What run writes

For each project, under <out_dir>/<project>/:

File Contents
feed.json Items (project, niche, kind, title, url, why, score, at; with a model also rule_score, llm_fit, llm_reason; topics also evidence, the signals behind them), the upcoming events, Search Console gaps, the niche definitions and, with a model, an llm usage summary. For scripts.
feed.md The same as Markdown, with an Upcoming table.
index.html The report page: niche tabs, a Trends table, an Upcoming table, Gaps, and the evidence and attribution behind each row when you click it. The data is embedded in the file, so it also opens from disk; the page needs JavaScript. The stylesheet, script and font are shared in <out_dir>/assets/.
feed.xml RSS for a feed reader.

<out_dir>/index.html links all projects. Exit status: 0 when everything worked, 1 when one or more sources failed (the files are still written), 2 for a configuration problem such as a missing identity.

Notifications

run sends each channel the top top_n (default 5) new items per niche, with the why and a link. Items already sent to a channel are skipped; the log lives at <data_dir>/<project>/notify_sent.json and entries expire after 90 days. A failing channel is reported and retried next run; it never blocks the others. --no-notify skips them for one run.

notify:
  - {type: telegram, token_env: TG_TOKEN, chat_id: "-100123", top_n: 5}
  - {type: discord, webhook_env: DISCORD_WEBHOOK, kinds: [topic, event]}
  - type: email
    host: smtp.example.com
    port: 587                    # 587 STARTTLS (default), 465 with security: ssl
    security: starttls           # starttls | ssl | none
    username: me@example.com
    password_env: SMTP_PASSWORD  # or password_file
    from: me@example.com
    to: [me@example.com]

Optional on every channel: top_n, kinds: [topic|event|gap], min_score. Telegram also takes api_base and message_thread_id; email takes ca_file for a private CA.

Project-level fields

regions and region_mode (per project, overridable per niche) choose which countries are read and whether each gets its own topics; see Regions.

schedule is stored for you and for your scheduler; nothing inside nicheclock reads it yet. gsc connects Search Console. profile (title, description) tells a model who the site is. llm configures your model.