Ctrl K
ComunidadGuías…

config.sample.toml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
[bot]
name = "cartero"
interval = "10m"
run_once = false

[storage]
# Postgres with the pgvector extension is required by the existing migrations.
type = "postgres"
dsn  = "postgres://cartero:cartero@localhost:5432/cartero?sslmode=disable"

# Small embedding model used only for semantic dedup (embed_text + embed_dedupe).
# type = "ollama" uses OLLAMA_HOST; type = "openai" calls any OpenAI-compatible
# /embeddings endpoint at base_url.
[platforms.embedder]
type = "ollama"
enabled = true
[platforms.embedder.settings]
embedding_model = "qwen3-embedding:0.6b"

# Jev evaluates every article against [interests]; required when interests are set.
[platforms.jev]
type = "jev"
enabled = true
[platforms.jev.settings]
api_key = "${TYPESAFE_API_KEY}"
# Pin a version so thresholds keep meaning the same thing; jev-latest can move.
model = "jev-1.13.0"

[platforms.discord]
type = "discord"
sleep = "1s"
[platforms.discord.settings]
bot_token = "YOUR_BOT_TOKEN_HERE"
timeout = "60s"

[platforms.bluesky]
type = "bluesky"
enabled = false
[platforms.bluesky.settings]
identifier = "your-handle.bsky.social"
password = "your-app-password"

[platforms.telegram]
type = "telegram"
enabled = false
[platforms.telegram.settings]
tg_bot_token = "YOUR_TELEGRAM_BOT_TOKEN_HERE"

[sources.hackernews]
type = "hackernews"
enabled = true
targets = ["discord_hn"]
[sources.hackernews.settings]
story_type = "topstories"
max_items = 30

[sources.lobsters]
type = "lobsters"
enabled = false
targets = ["discord_lobsters"]
[sources.lobsters.settings]
max_items = 20
sort_by = "hot"
include_categories = ["rust", "go", "programming"]
exclude_categories = ["rant", "culture"]

[sources.rss_example]
type = "rss"
enabled = false
targets = ["discord_rss"]
[sources.rss_example.settings]
feed_url = "https://example.com/feed.xml"
max_items = 20

[sources.lesswrong]
type = "lesswrong"
enabled = false
targets = ["discord_lw"]
[sources.lesswrong.settings]
max_items = 20

[sources.scraper_internal]
type = "scraper"
enabled = false
targets = ["feed_target"]
[sources.scraper_internal.settings]
scraper_type = "internal"
scraper_name = "techcrunch"
max_items = 20
[sources.scraper_internal.settings.config]

[sources.scraper_external]
type = "scraper"
enabled = false
targets = ["feed_target"]
[sources.scraper_external.settings]
scraper_type = "external"
script_path = "./scripts/custom_scraper.lua"
max_items = 20
[sources.scraper_external.settings.config]
custom_param = "value"

[processors.dedupe]
type = "dedupe"
enabled = true
[processors.dedupe.settings]
ttl = "24h"

# Semantic dedup: embeds titles and drops articles whose title is at least
# embed_threshold similar to one published within embed_window, before Jev.
[processors.embed_text]
type = "embed_text"
enabled = true

[processors.embed_dedupe]
type = "embed_dedupe"
enabled = true
[processors.embed_dedupe.settings]
embed_threshold = 0.8
embed_window = "168h"

[processors.score_filter]
type = "filter_score"
enabled = true
order = 2
[processors.score_filter.settings]
min_score = 100

[processors.extract_text]
type = "extract_text"
enabled = true
[processors.extract_text.settings]
min_content_length = 100
concurrency = 12
timeout_seconds = 8
flaresolverr_url = "http://flaresolverr:8191"
flaresolverr_timeout_ms = 60000

[interests]
keywords_file = "https://gist.githubusercontent.com/you/id/raw/keywords.json"
# Jev assigns each article a probability of matching none of the interests.
# Articles at or above this are rejected and never re-evaluated. The article is
# tagged with its most likely interest.
off_topic_threshold = 0.4
# Jev rates each article's substance from 0 (worthless) to 4 (exceptional) using
# the article text. Articles below this score are rejected as low_quality.
min_quality = 2.0
# Lower bar for articles whose text could not be fetched, since Jev then only
# sees the title and summary.
min_quality_incomplete = 1.4

# Optional higher quality floors for specific sources. The key is the pipeline
# source name. Each feed loaded from an OPML file is named "<source>_<feed>",
# so an arXiv feed under tech_blogs is tech_blogs_arxiv. The floor replaces
# min_quality for that source, including when the article text is incomplete.
[interests.source_min_quality]
# tech_blogs_arxiv = 3.0

# Questions Jev answers for every article: the topic, a 0-4 quality score from
# the levels below (lowest first), and yes/no flags that reject at threshold.
[prompts.topic]
instructions = "Which topic is this article about? Judge by its main subject, not passing mentions."
none = "The article is unrelated to all of the other topics, such as general news, politics, lifestyle or culture."

[prompts.quality]
instructions = """
How valuable is this to an experienced software engineer who reads for depth rather than news? \
Judge the substance, not how popular its topic is. \
For a software project or tool, judge how useful and technically interesting the project is, not how long its description is. \
If content_status is incomplete, rate what the title and summary suggest instead of treating the article as empty.\
"""
incomplete = "incomplete: the article text could not be fully retrieved"
levels = [
  "Worthless: spam, marketing or a sales pitch, SEO filler, AI-generated fluff, or no real content.",
  "Thin: a news blurb, rebrand or product announcement, link roundup, listicle, or opinion with little evidence or technical detail.",
  "Decent: a clear article with some useful information or perspective, or a working open-source project or tool with a clear description of what it does.",
  "Substantive: original insight, real technical depth, data, or hands-on experience an experienced engineer would learn from, or a notable project with real technical substance.",
  "Exceptional: an in-depth investigation, definitive deep-dive, novel result, or standout project worth saving and sharing.",
]

[[prompts.flags]]
name = "clickbait"
question = "Is the title clickbait: does it tease, exaggerate or withhold what the article is about to bait clicks (e.g. 'You won't believe', 'This one trick', vague teasers) instead of plainly stating the subject?"
threshold = 0.7

[[prompts.flags]]
name = "advertisement"
question = "Is this a sales pitch: content whose main purpose is getting readers to buy, subscribe to or sign up for a commercial product or service (landing page, sponsored post, webinar or event promo, customer case study)? Announcements of new AI models, software or library releases, and open-source projects are news, not sales pitches."
threshold = 0.7

[[prompts.flags]]
name = "research_slop"
question = "Is this an unrefereed preprint (for example on arXiv) whose main contribution is a benchmark, evaluation, survey or a minor variation of existing work, rather than a new result or method worth reading?"
threshold = 0.6

[blocklist]
domains_file = "https://gist.githubusercontent.com/you/id/raw/blocklist.txt"

[processors.rate_limiter]
type = "rate_limit"
enabled = false
order = 4
[processors.rate_limiter.settings]
limit = 10
window = "1m"

[targets.discord_hn]
type = "discord"
enabled = true
platform = "discord"
[targets.discord_hn.settings]
channel_id = "YOUR_CHANNEL_ID_HERE"
channel_type = "forum"

[targets.discord_lobsters]
type = "discord"
enabled = true
platform = "discord"
[targets.discord_lobsters.settings]
channel_id = "YOUR_CHANNEL_ID_HERE"
channel_type = "forum"

[targets.discord_rss]
type = "discord"
enabled = false
platform = "discord"
[targets.discord_rss.settings]
channel_id = "YOUR_CHANNEL_ID_HERE"
channel_type = "text"

[targets.bluesky_example]
type = "bluesky"
enabled = false
platform = "bluesky"
[targets.bluesky_example.settings]
languages = ["en"]

[targets.telegram_hn]
type = "telegram"
enabled = false
platform = "telegram"
[targets.telegram_hn.settings]
chat_id = -1001234567890

[targets.feed_target]
type = "feed"
enabled = true
[targets.feed_target.settings]
port = "8080"
feed_size = 100
max_items = 50