Not in the top 1,000 by GitHub stars

Hyaxia/blogwatcher

stay on top of your favorite blogs

History

Review weekly changes to SKILL.md before updating. Dates mark the weeks in which we observed each version; checks apply to the recorded commits.

View weekly changes

SKILL.md

Week of Oct 26, 2026 · Line counts unavailable

Before Not checked yet → After Network or broad shell

First observation → 58acfeb16258

No earlier version was observed for comparison.

Compare on GitHub

skills/SKILL.md

Week of Oct 26, 2026 · Line counts unavailable

Before Not checked yet → After Instructions only

First observation → 58acfeb16258

No earlier version was observed for comparison.

Compare on GitHub

View on GitHubAdd to my skills

Verified commit 58acfeb16258bd0e64d72f8f977ae8143cae5fa3

git clone --filter=blob:none --no-checkout https://github.com/Hyaxia/blogwatcher.git .hotskills/hyaxia-blogwatcher && git -C .hotskills/hyaxia-blogwatcher checkout --detach 58acfeb16258bd0e64d72f8f977ae8143cae5fa3 # Copy each selected skill directory from .hotskills/hyaxia-blogwatcher into .claude/skills/<skill-name>/
git clone --filter=blob:none --no-checkout https://github.com/Hyaxia/blogwatcher.git .hotskills/hyaxia-blogwatcher && git -C .hotskills/hyaxia-blogwatcher checkout --detach 58acfeb16258bd0e64d72f8f977ae8143cae5fa3 # Copy each selected skill directory from .hotskills/hyaxia-blogwatcher into .agents/skills/<skill-name>/
149GitHub stars
0stars in 7 days
2skills in this repository
0commits in 30 days

Heat

0.4
  • Star growth, last 7 daysNot available
  • Star growth, last 24 hoursNot available
  • Rank movement, last 7 daysNot available
  • Commits, last 30 daysNot available
  • Accounts asking about it through MCP, last 7 daysNot available
  • Hot votes this weekNot available

Heat never changes the order by stars, and sponsorship never touches it. How heat works

Stars over time

Oct 25, 2026Oct 25, 2026149

30-day change not yet available: not enough history.

Individual skills in this repository

How it works

blogwatcher-cli

When your agent uses it

Use when managing or interacting with favorite blogs via the BlogWatcher CLI—adding/removing blogs, scanning for new posts, listing articles, marking read/unread, or modifying related CLI behavior, scanning, storage, and tests.

What it loads into context

Frontmatter

54 tokens

Always available to the agent

SKILL.md

517 tokens

When the skill is activated

Reference documents

2,445 tokens

Loaded on request

Token counts are estimates. Scripts run separately; they are not counted as reference context.

The playbook

  1. Quick Orientation
  2. Run Commands
  3. Change Workflow
  4. Test Guidance
  5. Output Conventions

Scripts executed separately

  • cmd/blogwatcher/main.go
  • internal/cli/commands.go
  • internal/cli/errors.go
  • internal/cli/metadata.go
  • internal/cli/metadata_test.go
  • internal/cli/root.go
  • internal/controller/controller.go
  • internal/controller/controller_test.go
  • internal/model/model.go
  • internal/rss/rss.go
  • internal/rss/rss_test.go
  • internal/scanner/scanner.go
  • internal/scanner/scanner_test.go
  • internal/scraper/scraper.go
  • internal/scraper/scraper_test.go
  • internal/storage/database.go
  • internal/storage/database_test.go
  • internal/version/version.go

Files

  • .github/workflows/release.ymlasset736 bytes
  • .gitignoreasset86 bytes
  • .goreleaser.yamlasset1,049 bytes
  • AGENTS.mddoc2,245 bytes
  • CLAUDE.mddoc863 bytes
  • LICENSEasset1,056 bytes
  • README.mddoc7,677 bytes
  • SKILL.mdcore2,215 bytes
  • cmd/blogwatcher/main.goruns98 bytes
  • go.modasset1,269 bytes
  • go.sumasset14,057 bytes
  • internal/cli/commands.goruns12,165 bytes
  • internal/cli/errors.goruns330 bytes
  • internal/cli/metadata.goruns2,459 bytes
  • internal/cli/metadata_test.goruns7,239 bytes
  • internal/cli/root.goruns900 bytes
  • internal/controller/controller.goruns3,584 bytes
  • internal/controller/controller_test.goruns2,378 bytes
  • internal/model/model.goruns451 bytes
  • internal/rss/rss.goruns10,047 bytes
  • internal/rss/rss_test.goruns11,161 bytes
  • internal/scanner/scanner.goruns5,756 bytes
  • internal/scanner/scanner_test.goruns7,765 bytes
  • internal/scraper/scraper.goruns2,901 bytes
  • internal/scraper/scraper_test.goruns882 bytes
  • internal/storage/database.goruns13,141 bytes
  • internal/storage/database_test.goruns15,990 bytes
  • internal/version/version.goruns37 bytes
Read SKILL.md

# BlogWatcher CLI

## Quick Orientation
- Use the Cobra entry point in `cmd/blogwatcher/main.go` and `internal/cli/`.
- Route business logic through `internal/controller` and persistence through `internal/storage`.
- Use scanning pipeline packages in `internal/scanner`, `internal/rss`, and `internal/scraper`.
- Remember the default SQLite path is `~/.blogwatcher/blogwatcher.db` (override with `BLOGWATCHER_DB`) and is created on demand.

## Run Commands
- Run locally with `go run ./cmd/blogwatcher ...`.
- Build with `go build ./cmd/blogwatcher`.

## Change Workflow
1. Add or adjust CLI commands in `internal/cli/commands.go` (Cobra options, arguments, output formatting).
View the complete source on GitHub

Skills included

  • blogwatcher-cli
  • blogwatcher

Repository facts

License
MIT
Created
Dec 23, 2025
Last pushed
Aug 31, 2026
Forks
25
Open issues
0
Project activity
Slowing

Before you install

Network or broad shell

Automated signals, not a guarantee

Checked Nov 4, 2026

Show all 181 signals by file

.github/workflows/release.yml

  • Dangerous shell operationline 25
    run: sudo apt-get update && sudo apt-get install -y mingw-w64

.goreleaser.yaml

  • Network accessline 39
    - name: blogwatcher     description: "Track blog articles and detect new posts."     homepage: "https://github.com/Hyaxia/blogwatcher"
  • Network accessline 40
    description: "Track blog articles and detect new posts."     homepage: "https://github.com/Hyaxia/blogwatcher"     license: "MIT"
  • Network accessline 41
    homepage: "https://github.com/Hyaxia/blogwatcher"
  • Access to secretsline 46
    [Credential-related source omitted]

README.md

  • Network accessline 35
    # Add a blog (auto-discovers RSS feed) blogwatcher add "My Favorite Blog" https://example.com/blog
  • Network accessline 36
    blogwatcher add "My Favorite Blog" https://example.com/blog
  • Network accessline 38
    # Add with explicit feed URL blogwatcher add "Tech Blog" https://techblog.com --feed-url https://techblog.com/rss.xml
  • Network accessline 39
    blogwatcher add "Tech Blog" https://techblog.com --feed-url https://techblog.com/rss.xml
  • Network accessline 41
    # Add with HTML scraping selector (for blogs without feeds) blogwatcher add "No-RSS Blog" https://norss.com --scrape-selector "article h2 a"
  • Network accessline 42
    blogwatcher add "No-RSS Blog" https://norss.com --scrape-selector "article h2 a"
  • Network accessline 44
    # Add with per-blog User-Agent override blogwatcher add "Blocked Blog" https://blocked.example --feed-url https://blocked.example/feed --user-agent "Mozilla/5.0 ..."
  • Network accessline 45
    blogwatcher add "Blocked Blog" https://blocked.example --feed-url https://blocked.example/feed --user-agent "Mozilla/5.0 ..."

SKILL.md

  • Network accessline 39
    [12] [new] Understanding Click Contexts        Blog: Real Python        URL: https://realpython.com/click-context/
  • Network accessline 40
    Blog: Real Python        URL: https://realpython.com/click-context/        Published: 2025-11-02
  • Network accessline 41
    URL: https://realpython.com/click-context/
  • Network accessline 44
    [13] [new] Async IO in Practice        Blog: Test & Code        URL: https://testandcode.com/async-io-in-practice/
  • Network accessline 46
    URL: https://testandcode.com/async-io-in-practice/

cmd/blogwatcher/main.go

  • Executable scriptline 1
    Executable file

internal/cli/commands.go

  • Executable scriptline 1
    Executable file
  • Instruction override signalline 80
    }, 	} 	cmd.Flags().BoolVarP(&yes, "yes", "y", false, "Skip confirmation prompt")
  • Instruction override signalline 81
    } 	cmd.Flags().BoolVarP(&yes, "yes", "y", false, "Skip confirmation prompt") 	return cmd
  • Instruction override signalline 82
    cmd.Flags().BoolVarP(&yes, "yes", "y", false, "Skip confirmation prompt")
  • Instruction override signalline 339
    cmd.Flags().StringVarP(&blogName, "blog", "b", "", "Only mark articles from this blog") 	cmd.Flags().BoolVarP(&yes, "yes", "y", false, "Skip confirmation prompt")
  • Instruction override signalline 340
    cmd.Flags().BoolVarP(&yes, "yes", "y", false, "Skip confirmation prompt")

internal/cli/errors.go

  • Executable scriptline 1
    Executable file

internal/cli/metadata.go

  • Executable scriptline 1
    Executable file

internal/cli/metadata_test.go

  • Executable scriptline 1
    Executable file
  • Network accessline 122
    published := time.Date(2026, 8, 25, 0, 0, 0, 0, time.UTC) 	article := model.Article{ 		ID: 7, Title: "Title", URL: "https://example.com/article", PublishedDate: &published,
  • Network accessline 123
    article := model.Article{ 		ID: 7, Title: "Title", URL: "https://example.com/article", PublishedDate: &published, 		Description: "Stored metadata must stay hidden.", Keywords: "one,two",
  • Network accessline 124
    ID: 7, Title: "Title", URL: "https://example.com/article", PublishedDate: &published,
  • Network accessline 128
    want := "  [7] [new] Title\n" + 		"       Blog: Example\n" + 		"       URL: https://example.com/article\n" +
  • Network accessline 129
    "       Blog: Example\n" + 		"       URL: https://example.com/article\n" + 		"       Published: 2026-08-25\n\n"
  • Network accessline 130
    "       URL: https://example.com/article\n" +
  • Network accessline 137
    func TestPrintArticleMetadataFlagsAreIndependent(t *testing.T) { 	description := strings.Repeat("complete description ", 8) 	article := model.Article{ID: 1, Title: "Title", URL: "https://example.com/1", Description: description, Keywords: "
  • Network accessline 139
    article := model.Article{ID: 1, Title: "Title", URL: "https://example.com/1", Description: description, Keywords: "one,two"}

internal/cli/root.go

  • Executable scriptline 1
    Executable file

internal/controller/controller.go

  • Executable scriptline 1
    Executable file

internal/controller/controller_test.go

  • Executable scriptline 1
    Executable file
  • Network accessline 15
    blog, err := AddBlog(db, "Test", "https://example.com", "", "", "")
  • Network accessline 20
    if _, err := AddBlog(db, "Test", "https://other.com", "", "", ""); err == nil {
  • Network accessline 24
    if _, err := AddBlog(db, "Other", "https://example.com", "", "", ""); err == nil {
  • Network accessline 37
    blog, err := AddBlog(db, "Test", "https://example.com", "", "", "")
  • Network accessline 39
    t.Fatalf("add blog: %v", err) 	} 	article, err := db.AddArticle(model.Article{BlogID: blog.ID, Title: "Title", URL: "https://example.com/1"})
  • Network accessline 40
    } 	article, err := db.AddArticle(model.Article{BlogID: blog.ID, Title: "Title", URL: "https://example.com/1"}) 	if err != nil {
  • Network accessline 41
    article, err := db.AddArticle(model.Article{BlogID: blog.ID, Title: "Title", URL: "https://example.com/1"})
  • Network accessline 67
    blog, err := AddBlog(db, "Test", "https://example.com", "", "", "")
  • Network accessline 69
    t.Fatalf("add blog: %v", err) 	} 	_, err = db.AddArticle(model.Article{BlogID: blog.ID, Title: "Title", URL: "https://example.com/1"})
  • Network accessline 70
    } 	_, err = db.AddArticle(model.Article{BlogID: blog.ID, Title: "Title", URL: "https://example.com/1"}) 	if err != nil {
  • Network accessline 71
    _, err = db.AddArticle(model.Article{BlogID: blog.ID, Title: "Title", URL: "https://example.com/1"})

internal/model/model.go

  • Executable scriptline 1
    Executable file

internal/rss/rss.go

  • Executable scriptline 1
    Executable file
  • Network accessline 62
    client := &http.Client{Timeout: timeout}
  • Network accessline 275
    func DiscoverFeedURL(blogURL string, timeout time.Duration, userAgent string) (string, error) { 	client := &http.Client{Timeout: timeout}
  • Network accessline 276
    client := &http.Client{Timeout: timeout}
  • Network accessline 355
    func isValidFeed(feedURL string, timeout time.Duration, userAgent string) (bool, error) { 	client := &http.Client{Timeout: timeout}
  • Network accessline 356
    client := &http.Client{Timeout: timeout}
  • Network accessline 375
    func getWithOptionalUserAgent(client *http.Client, targetURL string, userAgent string) (*http.Response, error) {

internal/rss/rss_test.go

  • Executable scriptline 1
    Executable file
  • Network accessline 15
    <item> <title>First</title> <link>https://example.com/1</link>
  • Network accessline 16
    <title>First</title> <link>https://example.com/1</link> <pubDate>Mon, 02 Jan 2006 15:04:05 GMT</pubDate>
  • Network accessline 17
    <link>https://example.com/1</link>
  • Network accessline 22
    <item> <title>Second</title> <link>https://example.com/2</link>
  • Network accessline 23
    <title>Second</title> <link>https://example.com/2</link> </item>
  • Network accessline 24
    <link>https://example.com/2</link>
  • Network accessline 51
    func TestParseFeedMetadataOptionsAreIndependent(t *testing.T) { 	feed := `<?xml version="1.0"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel>
  • Network accessline 52
    feed := `<?xml version="1.0"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel>
  • Network accessline 53
    <title>Metadata</title><item><title>First</title><link>https://example.com/1</link>
  • Network accessline 91
    feed := `<?xml version="1.0"?><rss version="2.0"><channel>` + 		`<title>Large feed</title><item><title>Kept</title>` + 		`<link>https://example.com/kept</link><keyword>omitted</keyword><description>` +
  • Network accessline 92
    `<title>Large feed</title><item><title>Kept</title>` + 		`<link>https://example.com/kept</link><keyword>omitted</keyword><description>` + 		largeDescription + `</description></item></channel></rss>`
  • Network accessline 93
    `<link>https://example.com/kept</link><keyword>omitted</keyword><description>` +
  • Network accessline 188
    func TestParseFeedKeywordAlignment(t *testing.T) { 	feed := `<?xml version="1.0"?><feed xmlns="http://www.w3.org/2005/Atom">
  • Network accessline 189
    feed := `<?xml version="1.0"?><feed xmlns="http://www.w3.org/2005/Atom">
  • Network accessline 190
    <title>Alignment</title> <entry><title>Skipped</title><keyword>first</keyword></entry> <entry><title>Kept</title><link href="https://example.com/kept"/><keyword>second</keyword></entry>
  • Network accessline 191
    <entry><title>Skipped</title><keyword>first</keyword></entry> <entry><title>Kept</title><link href="https://example.com/kept"/><keyword>second</keyword></entry> </feed>`
  • Network accessline 192
    <entry><title>Kept</title><link href="https://example.com/kept"/><keyword>second</keyword></entry>
  • Network accessline 209
    server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 		w.Header().Set("Content-Type", "application/feed+json") 		_, _ = w.Write([]byte(`{"version":"https://jsonfeed.org/version/1.1","title":"JSON","it
  • Network accessline 210
    w.Header().Set("Content-Type", "application/feed+json") 		_, _ = w.Write([]byte(`{"version":"https://jsonfeed.org/version/1.1","title":"JSON","items":[{"id":"1","url":"https://example.com/1","title":"One"}]}`)) 	}))
  • Network accessline 211
    _, _ = w.Write([]byte(`{"version":"https://jsonfeed.org/version/1.1","title":"JSON","items":[{"id":"1","url":"https://example.com/1","title":"One"}]}`))

internal/scanner/scanner.go

  • Executable scriptline 1
    Executable file

internal/scanner/scanner_test.go

  • Executable scriptline 1
    Executable file
  • Network accessline 18
    <item> <title>First</title> <link>https://example.com/1</link>
  • Network accessline 19
    <title>First</title> <link>https://example.com/1</link> </item>
  • Network accessline 20
    <link>https://example.com/1</link>
  • Network accessline 22
    <item> <title>Second</title> <link>https://example.com/2</link>
  • Network accessline 23
    <title>Second</title> <link>https://example.com/2</link> </item>
  • Network accessline 24
    <link>https://example.com/2</link>
  • Network accessline 29
    const metadataFeed = `<?xml version="1.0" encoding="UTF-8" ?> <rss version="2.0"><channel><title>Metadata Feed</title><item> <title>Metadata article</title><link>https://example.com/metadata</link>
  • Network accessline 30
    <rss version="2.0"><channel><title>Metadata Feed</title><item> <title>Metadata article</title><link>https://example.com/metadata</link> <description><![CDATA[<p>Stored description.</p>]]></description>
  • Network accessline 31
    <title>Metadata article</title><link>https://example.com/metadata</link>
  • Network accessline 46
    blog, err := db.AddBlog(model.Blog{Name: "Test", URL: "https://example.com", FeedURL: server.URL})
  • Network accessline 74
    db := openTestDB(t) 	defer db.Close() 	blog, err := db.AddBlog(model.Blog{Name: "Test", URL: "https://example.com", FeedURL: server.URL})
  • Network accessline 75
    defer db.Close() 	blog, err := db.AddBlog(model.Blog{Name: "Test", URL: "https://example.com", FeedURL: server.URL}) 	if err != nil {
  • Network accessline 76
    blog, err := db.AddBlog(model.Blog{Name: "Test", URL: "https://example.com", FeedURL: server.URL})
  • Network accessline 113
    db := openTestDB(t) 			defer db.Close() 			blog, err := db.AddBlog(model.Blog{Name: test.name, URL: "https://example.com/" + test.name, FeedURL: server.URL})
  • Network accessline 114
    defer db.Close() 			blog, err := db.AddBlog(model.Blog{Name: test.name, URL: "https://example.com/" + test.name, FeedURL: server.URL}) 			if err != nil {
  • Network accessline 115
    blog, err := db.AddBlog(model.Blog{Name: test.name, URL: "https://example.com/" + test.name, FeedURL: server.URL})
  • Network accessline 141
    db := openTestDB(t) 	defer db.Close() 	blog, err := db.AddBlog(model.Blog{Name: "Test", URL: "https://example.com", FeedURL: server.URL})
  • Network accessline 142
    defer db.Close() 	blog, err := db.AddBlog(model.Blog{Name: "Test", URL: "https://example.com", FeedURL: server.URL}) 	if err != nil {
  • Network accessline 143
    blog, err := db.AddBlog(model.Blog{Name: "Test", URL: "https://example.com", FeedURL: server.URL})
  • Network accessline 145
    t.Fatalf("add blog: %v", err) 	} 	article, err := db.AddArticle(model.Article{BlogID: blog.ID, Title: "Metadata article", URL: "https://example.com/metadata"})
  • Network accessline 146
    } 	article, err := db.AddArticle(model.Article{BlogID: blog.ID, Title: "Metadata article", URL: "https://example.com/metadata"}) 	if err != nil {
  • Network accessline 147
    article, err := db.AddArticle(model.Article{BlogID: blog.ID, Title: "Metadata article", URL: "https://example.com/metadata"})
  • Network accessline 214
    for i, name := range []string{"TestA", "TestB"} { 		_, err := db.AddBlog(model.Blog{Name: name, URL: "https://example.com/" + name, FeedURL: server.URL})
  • Network accessline 215
    _, err := db.AddBlog(model.Blog{Name: name, URL: "https://example.com/" + name, FeedURL: server.URL})
  • Network accessline 250
    blog, err := db.AddBlog(model.Blog{Name: "Test", URL: "https://example.com", FeedURL: server.URL})
  • Network accessline 255
    _, err = db.AddArticle(model.Article{BlogID: blog.ID, Title: "First", URL: "https://example.com/1", DiscoveredDate: ptrTime(time.Now())})

internal/scraper/scraper.go

  • Executable scriptline 1
    Executable file
  • Network accessline 28
    func ScrapeBlog(blogURL string, selector string, timeout time.Duration, userAgent string) ([]ScrapedArticle, error) { 	client := &http.Client{Timeout: timeout}
  • Network accessline 29
    client := &http.Client{Timeout: timeout}
  • Network accessline 106
    func getWithOptionalUserAgent(client *http.Client, targetURL string, userAgent string) (*http.Response, error) {

internal/scraper/scraper_test.go

  • Executable scriptline 1
    Executable file

internal/storage/database.go

  • Executable scriptline 1
    Executable file

internal/storage/database_test.go

  • Executable scriptline 1
    Executable file
  • Network accessline 28
    blog, err := db.AddBlog(model.Blog{Name: "Test", URL: "https://example.com"})
  • Network accessline 44
    articles := []model.Article{ 		{BlogID: blog.ID, Title: "One", URL: "https://example.com/1"},
  • Network accessline 45
    {BlogID: blog.ID, Title: "One", URL: "https://example.com/1"},
  • Network accessline 46
    {BlogID: blog.ID, Title: "Two", URL: "https://example.com/2"},
  • Network accessline 140
    blog, err := db.AddBlog(model.Blog{Name: "Test", URL: "https://example.com"})
  • Network accessline 145
    _, err = db.AddArticle(model.Article{BlogID: blog.ID, Title: "One", URL: "https://example.com/1"})
  • Network accessline 150
    existing, err := db.GetExistingArticleURLs([]string{"https://example.com/1", "https://example.com/2"})
  • Network accessline 152
    t.Fatalf("get existing: %v", err) 	} 	if _, ok := existing["https://example.com/1"]; !ok {
  • Network accessline 153
    } 	if _, ok := existing["https://example.com/1"]; !ok { 		t.Fatalf("expected existing url")
  • Network accessline 154
    if _, ok := existing["https://example.com/1"]; !ok {
  • Network accessline 155
    t.Fatalf("expected existing url") 	} 	if _, ok := existing["https://example.com/2"]; ok {
  • Network accessline 156
    } 	if _, ok := existing["https://example.com/2"]; ok { 		t.Fatalf("did not expect url")
  • Network accessline 157
    if _, ok := existing["https://example.com/2"]; ok {
  • Network accessline 171
    if _, err := db.AddArticle(model.Article{BlogID: 9999, Title: "Orphan", URL: "https://example.com/orphan"}); err == nil {
  • Network accessline 185
    blog, err := db.AddBlog(model.Blog{Name: "Test", URL: "https://example.com"})
  • Network accessline 212
    blog, err := db.AddBlog(model.Blog{ 		Name:        "Test", 		URL:         "https://example.com",
  • Network accessline 213
    Name:        "Test", 		URL:         "https://example.com", 		LastScanned: &now,
  • Network accessline 214
    URL:         "https://example.com",
  • Network accessline 242
    blog, err := db.AddBlog(model.Blog{Name: "Test", URL: "https://example.com"})
  • Network accessline 249
    BlogID:        blog.ID, 		Title:         "Title", 		URL:           "https://example.com/1",
  • Network accessline 250
    Title:         "Title", 		URL:           "https://example.com/1", 		PublishedDate: &published,
  • Network accessline 251
    URL:           "https://example.com/1",
  • Network accessline 280
    blog, err := db.AddBlog(model.Blog{Name: "Test", URL: "https://example.com"})
  • Network accessline 282
    t.Fatalf("add blog: %v", err) 	} 	plain, err := db.AddArticle(model.Article{BlogID: blog.ID, Title: "Plain", URL: "https://example.com/plain"})
  • Network accessline 283
    } 	plain, err := db.AddArticle(model.Article{BlogID: blog.ID, Title: "Plain", URL: "https://example.com/plain"}) 	if err != nil {
  • Network accessline 284
    plain, err := db.AddArticle(model.Article{BlogID: blog.ID, Title: "Plain", URL: "https://example.com/plain"})
  • Network accessline 296
    metadata, err := db.AddArticle(model.Article{ 		BlogID: blog.ID, Title: "Metadata", URL: "https://example.com/metadata",
  • Network accessline 297
    BlogID: blog.ID, Title: "Metadata", URL: "https://example.com/metadata",
  • Network accessline 311
    _, err = db.AddArticlesBulk([]model.Article{{ 		BlogID: blog.ID, Title: "Bulk", URL: "https://example.com/bulk",
  • Network accessline 312
    BlogID: blog.ID, Title: "Bulk", URL: "https://example.com/bulk",
  • Network accessline 322
    foundBulk := false 	for _, article := range listed { 		if article.URL == "https://example.com/bulk" {
  • Network accessline 323
    for _, article := range listed { 		if article.URL == "https://example.com/bulk" { 			foundBulk = article.Description == "Bulk description." && article.Keywords == "bulk,test"
  • Network accessline 324
    if article.URL == "https://example.com/bulk" {
  • Network accessline 342
    blogA, err := db.AddBlog(model.Blog{Name: "A", URL: "https://a.example.com"})
  • Network accessline 344
    t.Fatalf("add blog: %v", err) 	} 	blogB, err := db.AddBlog(model.Blog{Name: "B", URL: "https://b.example.com"})
  • Network accessline 345
    } 	blogB, err := db.AddBlog(model.Blog{Name: "B", URL: "https://b.example.com"}) 	if err != nil {
  • Network accessline 346
    blogB, err := db.AddBlog(model.Blog{Name: "B", URL: "https://b.example.com"})
  • Network accessline 354
    first, err := db.AddArticle(model.Article{BlogID: blogA.ID, Title: "Old", URL: "https://a.example.com/old", DiscoveredDate: &t1})
  • Network accessline 356
    t.Fatalf("add article: %v", err) 	} 	second, err := db.AddArticle(model.Article{BlogID: blogA.ID, Title: "New", URL: "https://a.example.com/new", DiscoveredDate: &t2})
  • Network accessline 357
    } 	second, err := db.AddArticle(model.Article{BlogID: blogA.ID, Title: "New", URL: "https://a.example.com/new", DiscoveredDate: &t2}) 	if err != nil {
  • Network accessline 358
    second, err := db.AddArticle(model.Article{BlogID: blogA.ID, Title: "New", URL: "https://a.example.com/new", DiscoveredDate: &t2})
  • Network accessline 360
    t.Fatalf("add article: %v", err) 	} 	_, err = db.AddArticle(model.Article{BlogID: blogB.ID, Title: "Other", URL: "https://b.example.com/1", DiscoveredDate: &t2})
  • Network accessline 361
    } 	_, err = db.AddArticle(model.Article{BlogID: blogB.ID, Title: "Other", URL: "https://b.example.com/1", DiscoveredDate: &t2}) 	if err != nil {
  • Network accessline 362
    _, err = db.AddArticle(model.Article{BlogID: blogB.ID, Title: "Other", URL: "https://b.example.com/1", DiscoveredDate: &t2})
  • Network accessline 409
    blog, err := db.AddBlog(model.Blog{Name: "Test", URL: "https://example.com"})
  • Network accessline 418
    _, err = db.AddArticle(model.Article{BlogID: blog.ID, Title: "Existing", URL: "https://example.com/existing"})
  • Network accessline 423
    dupArticles := []model.Article{ 		{BlogID: blog.ID, Title: "Dup", URL: "https://example.com/dup"},
  • Network accessline 424
    {BlogID: blog.ID, Title: "Dup", URL: "https://example.com/dup"},
  • Network accessline 425
    {BlogID: blog.ID, Title: "Dup2", URL: "https://example.com/dup"},
  • Network accessline 450
    t.Fatalf("expected missing blog by name") 	} 	if blog, err := db.GetBlogByURL("https://missing.example.com"); err != nil || blog != nil {
  • Network accessline 451
    } 	if blog, err := db.GetBlogByURL("https://missing.example.com"); err != nil || blog != nil { 		t.Fatalf("expected missing blog by url")
  • Network accessline 452
    if blog, err := db.GetBlogByURL("https://missing.example.com"); err != nil || blog != nil {
  • Network accessline 456
    blog, err := db.AddBlog(model.Blog{Name: "Test", URL: "https://example.com"})
  • Network accessline 458
    t.Fatalf("add blog: %v", err) 	} 	article, err := db.AddArticle(model.Article{BlogID: blog.ID, Title: "Title", URL: "https://example.com/1"})
  • Network accessline 459
    } 	article, err := db.AddArticle(model.Article{BlogID: blog.ID, Title: "Title", URL: "https://example.com/1"}) 	if err != nil {
  • Network accessline 460
    article, err := db.AddArticle(model.Article{BlogID: blog.ID, Title: "Title", URL: "https://example.com/1"})
  • Network accessline 469
    t.Fatalf("expected article to exist") 	} 	if exists, err := db.ArticleExists("https://example.com/missing"); err != nil || exists {
  • Network accessline 470
    } 	if exists, err := db.ArticleExists("https://example.com/missing"); err != nil || exists { 		t.Fatalf("expected missing article to not exist")
  • Network accessline 471
    if exists, err := db.ArticleExists("https://example.com/missing"); err != nil || exists {
  • Network accessline 543
    blog, err := db.AddBlog(model.Blog{Name: "Test", URL: "https://example.com", UserAgent: "MyBot/1.0"})
  • Network accessline 553
    } 	article, err := db.AddArticle(model.Article{ 		BlogID: blog.ID, Title: "Migrated", URL: "https://example.com/migrated",
  • Network accessline 554
    article, err := db.AddArticle(model.Article{ 		BlogID: blog.ID, Title: "Migrated", URL: "https://example.com/migrated", 		Description: "Migrated description.", Keywords: "migration,test",
  • Network accessline 555
    BlogID: blog.ID, Title: "Migrated", URL: "https://example.com/migrated",

internal/version/version.go

  • Executable scriptline 1
    Executable file

skills/SKILL.md

  • Network accessline 20
    - Add a blog: `blogwatcher add "My Blog" https://example.com`
  • Network accessline 34
    xkcd     URL: https://xkcd.com
  • Network accessline 35
    URL: https://xkcd.com

How the signals fit together

Facts from a static read of the files at the pinned commit. They describe what the code does, not why.

Some files could not be read; the facts below still hold.

Signal confidence: 176 facts

and 126 more

Works with

Agent compatibility, from the files in this repository
AgentStatusEvidenceInstall to
Claude CodeNativePath CLAUDE.md.claude/skills/<skill-name>/
CodexNativeManifest AGENTS.md.agents/skills/<skill-name>/
CursorCompatibleStandard SKILL.md SKILL.md.cursor/skills/<skill-name>/
Gemini CLICompatibleStandard SKILL.md SKILL.md.gemini/skills/<skill-name>/
GitHub CopilotCompatibleStandard SKILL.md SKILL.md.github/skills/<skill-name>/
OpenCodeCompatibleStandard SKILL.md SKILL.md.opencode/skills/<skill-name>/
  • Native The repository ships a manifest, folder or frontmatter field for this agent.
  • Compatible A standard SKILL.md that this agent can read.
  • Unknown No evidence either way.

Derived automatically from paths, manifests and frontmatter. Not tested on each agent.