SkillAgentSearch skills...

hatebucli

Fast, script-friendly CLI for Hatena Bookmark

Install / Use

claude mcp add yuiseki -- npx -y github:yuiseki/hatebucli

If the server publishes to npm under a different name, use that package instead β€” check the repo README.

About this skill
πŸ”Œ

MCP Server

Model Context Protocol server

Quality Score

78/100

Supported Platforms

Claude Code
Claude Desktop

Tags

Our assessment of hatebucli

hatebucli scores 78/100 on our quality scale, 3663rd of 4,656 Development & Engineering skills we index.

Its MCP Server is 16 KB long, well organised into 17 sections with 19 code examples: a thorough specification that gives an agent plenty to work with.

It has 10 GitHub stars, so there is little community track record yet; judge it on its content.

Substance
30/30
Structure
20/20
Description
8/15
Adoption
4/20
Freshness
15/15

Maintenance, license and trust

  • The repository was last updated 8 days ago, so hatebucli is actively maintained.
  • Our last check on 2026-09-29 found the source still online.
  • No license is declared. By default that means all rights are reserved: you can read it, but reusing or redistributing it is not clearly permitted. Ask the author before building on it commercially.
  • Its trust signals score 80/100, with 2 cautions from licensing, adoption, age or documentation. These come from repository metadata, not a code audit β€” read the skill file before letting an agent act on it.

Safety scan

No issues found

Our scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands. An AI review of the same text found nothing harmful.

AI review by kimi-k2.7-code on 2026-09-24. Automated pattern scan on 2026-09-24. It catches known dangerous patterns, not every risk β€” read a skill before letting an agent act on it.

hatebucli compared with similar skills

All 4 of these similar skills score higher than hatebucli; compare them before choosing.

SkillScoreStarsUpdatedFormat
hatebucli (this skill)by yuiseki78108d agoMCP Server
taste-skillby Leonxlnx100108.3k2d agoCLAUDE.md
Agent-Reachby Panniantong100107.8k1d agoCLAUDE.md
headroomby headroomlabs-ai10078.6ktodayCLAUDE.md
ai-job-searchby ai-job-search-dev10068.9ktodayCLAUDE.md

Frequently asked questions

How do I install hatebucli?
Run claude mcp add yuiseki -- npx -y github:yuiseki/hatebucli. The install tabs above show the steps for each supported agent.
Which AI agents does hatebucli work with?
It is written for Claude Code and Claude Desktop, as a MCP Server file. Other agents that read the same format can often use it too.
Is hatebucli safe to use?
Our scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands. An AI review of the same text found nothing harmful. It declares no license and scores 80/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
Is hatebucli still maintained?
The repository was last updated 8 days ago, so hatebucli is actively maintained.

@yuiseki/hatebucli

Your Hatena Bookmark history, on your machine and answerable.

Hatena Bookmark gives you a feed of what you bookmarked, one day at a time, and not much else. hatebu syncs those days into a local cache and then answers questions over the whole of it: what you were reading in 2019, which sites you keep coming back to, when a subject first showed up, whether you have already bookmarked the page in front of you. Everything except today is answered from the cache, so it works offline and it stays fast on an archive of hundreds of thousands of bookmarks.

It also runs as an MCP server, so an assistant can ask the same questions without you translating each one into flags.

Install

Not on npm yet. Install it from a checkout:

git clone https://github.com/yuiseki/hatebucli.git
cd hatebucli
npm install && npm run build
npm link            # puts `hatebu` on your PATH

Needs Node 22.12 or newer. That is where require() of an ES module landed, which some of the dependencies now need; Node 20 and earlier went end of life on 2026-04-30. Run it on an older one and it says so rather than failing from inside node_modules.

Getting started

hatebu config set username <your_hatena_id>
hatebu sync --days 30
hatebu words

sync is what fills the cache, one request per day, spaced out because the feed is somebody else's server. Nothing else here fetches anything, apart from today.

Commands

Every command that reports bookmarks or rankings takes -j, --json.

One day

hatebu list                      # today, fresh from the feed
hatebu list --date 2026-02-18    # an earlier day, from the cache

| Option | | | --- | --- | | -d, --date <yyyy-mm-dd> | the day. Defaults to today | | -j, --json | |

Search

hatebu search εœ°ε›³                          # every cached day, best match first
hatebu search zenn.dev --field url
hatebu search εœ°ε›³ --date 2026-02-18 -l 50

A query is one or more terms separated by whitespace, and every term has to appear. A term of plain letters and digits has to appear as a whole word, so ai means AI and not mai, aichi or maplibre. Anything else is matched as written: εœ°ε›³ finds εœ°ε›³εΈ³ and η™½εœ°ε›³, and arxiv.org is a host. εœ°ε›³ AI is two terms and needs both. Results are ordered by score, then newest first, and the last line says how many matched when the limit cut them.

--match substring looks inside words, which is what finds LiteLLM when you search llm. See ADR 012 for the measurements behind the default.

To count what came from a site, use hatebu lookup instead: it matches the hostname, where a URL search matches the text of the URL anywhere in it.

| Option | | | --- | --- | | -f, --field <all\|title\|url> | where to match. Default all | | -m, --match <word\|substring> | how a plain term is matched. Default word | | -d, --date <yyyy-mm-dd> | one day only | | -l, --limit <number> | default 10 | | -j, --json | |

Rankings over a range

hatebu domains --date 2026       # the sites of a year
hatebu tags --date 2026-02       # the tags of a month
hatebu words                     # what last week was about
hatebu words --today
hatebu stats --days 30 --top 20  # a window as one summary

domains, tags and words take the same range options. With none of them the range is the week ending yesterday.

| Option | | | --- | --- | | --date <yyyy\|yyyy-mm\|yyyy-mm-dd> | a day, a month or a year | | --today | today only. Cannot be combined with --date | | -l, --limit <number> | rows. Default 10, capped at 10 for domains and tags, 30 for words | | --by <distinctive\|count> | words only. Default distinctive | | -j, --json | |

words runs the titles through a Japanese morphological analyser and ranks by how much more the range used each word than the archive usually does, so the answer is what the stretch was about rather than which words it contained. A word counts once per bookmark however often the title repeats it.

hatebu words --rebuild-background   # once, about a minute
hatebu words --date 2013

The background is one file of word counts over the whole archive, in total and by year, about 4MB. Building it tokenizes every title, which takes a minute, so it is a command you run rather than something that happens during sync; a day of new bookmarks moves a background of 374,000 by nothing. Without it, words falls back to raw counts and says so. --by count asks for those on purpose.

Ranking this way is what makes a year legible:

2013   js node grunt javascript fluentd css backbone sass angularjs chef
2019   nhk ニγƒ₯γƒΌγ‚Ή 朝ζ—₯ζ–°θž ラむブドをニγƒ₯γƒΌγ‚Ή 放火 yahoo pay 香港 ζ–‡ζ˜₯

Frequency alone answers ai github for and the https for every year of this archive. See ADR 010.

stats puts a window together: how much, at what hours, on what weekdays, and the sites and tags that led it. Markdown to read, --json to compute from; the two are cut the same way, so a bucket nobody bookmarked in is absent from both rather than reported as a zero.

| Option | | | --- | --- | | --date <yyyy\|yyyy-mm\|yyyy-mm-dd> | the day the window ends on. Defaults to yesterday | | --days <number> | window length. Default 7 | | --top <number> | rows per section. Default 10, capped at 20 | | -j, --json | |

Each of these reports the days of the range the cache does not hold, so a low count can be told apart from a gap in the sync.

The whole archive

hatebu lookup https://example.com/article   # have I bookmarked this page?
hatebu lookup example.com                   # what have I read from this site?
hatebu timeline --query maplibre --bars     # when was I reading about this?
hatebu timeline --by month --tag εœ°ε›³
hatebu tagged εœ°ε›³                           # what did I file under this tag?
hatebu random -n 5 --from 2010-01-01        # dig something out of the archive

lookup answers about one page when given a full URL, and about a site when given a bare hostname. A subdomain counts as part of the site, and the scheme and a trailing slash are ignored, because neither makes it a different page. A URL that was never bookmarked still reports how much came from the same site.

timeline counts per year or per month, oldest first, and takes --tag, --domain, --query, --from and --to in any combination. --bars draws each row. random takes the same filters.

These read every cached day rather than an index. Over an archive of a few hundred thousand bookmarks that is under a second.

Choosing a best of the day

hatebu pick                        # yesterday's bookmarks, numbered
hatebu pick 12                     # the entry page for number 12
hatebu pick --weekly               # the week's daily_best
hatebu pick --monthly --date 2026-09

pick writes nothing to Hatena. It lays out the candidates and gives you the entry page for the one you chose; you add the daily_best tag there, the way you would add any tag. The tag comes back into the cache on the next sync, which is what makes the weekly round able to read what the daily round chose:

| round | candidates | tag to add | | --- | --- | --- | | pick | the day's bookmarks | daily_best | | pick --weekly | daily_best of the last seven days | weekly_best | | pick --monthly | weekly_best of the month | monthly_best |

pick fetches the days of its window first, unless the cache already shows the round as decided, because a tag added on the entry page is invisible until that day is fetched again. A decided round costs no requests; an undecided week costs its seven days. --no-sync reads the cache as it stands.

A round that is already decided shows what was chosen and stops, rather than asking you to read the day again. A day may have more than one best; they are all shown, and all of them go forward as candidates for the weekly round. --all lists the candidates anyway, with the chosen one marked, and a number still resolves against that list if you change your mind. See ADR 011 for why this does not go through the write API.

Reading the archive against itself

hatebu compare 2023 2026            # what rose, and what fell away
hatebu compare 2025 2026 --by domains
hatebu context https://example.com/article   # what it was read alongside
hatebu timeline --query maplibre --bars      # count and share, per period
hatebu bursts --query maplibre               # when it flared, and how far above usual
hatebu resurface --peaked-after 2015         # what you cared about and stopped mentioning

compare scores each period against the other, so a word common to both says nothing and a word rare in both can be the whole of the difference. Every row carries both counts and both rates per thousand, because two periods are rarely the same size.

More in 2026
  claude: 18 β†’ 260  (0.2 β†’ 3.8 per 1000)
  γ‚¨γƒΌγ‚Έγ‚§γƒ³γƒˆ: 15 β†’ 155  (0.2 β†’ 2.3 per 1000)
More in 2023
  chatgpt: 510 β†’ 62  (5.9 β†’ 0.9 per 1000)
  langchain: 109 β†’ 5  (1.3 β†’ 0.1 per 1000)

bursts names the episodes a timeline only draws. A month counts when it runs at three times the subject's own rate over the whole archive and holds at least three matches, so a subject is bursting because it is unusual for itself, not because the month was busy:

maplibre: 123 bookmarks, usually 0.3 per 1000
  2022-11..2023-01  15 bookmarks, peak 2023-01 at 5.6 per 1000 (17x)
  2024-10..2024-12  17 bookmarks, peak 2024-11 at 7.4 per 1000 (22.5x)
  2026-05..2026-09  28 bookmarks, peak 2026-08 at 12.2 per 1000 (37.1x)

resurface takes no subject at all: it reads the vocabulary year by year for the shape of something that burned and went out, scoring how loud it got, how gone it is, how long it has been dormant and whether there was ever enough of it. It needs the background model that words uses, rebuilt with the version that carries a breakdown by year. See ADR 013.

word        peak year   then     now   dormant
ニγƒ₯γƒΌγ‚Ή        2020   125.22   30.78   6y   (17259 bookmarks)
android      2015    56.42    4.92   11y   (4411 bookmarks)
γ‚³γƒ­γƒŠ         2020    77.82    0.94   6y   (1687 bookmarks)
vr           2018    42.69    0.39   8y   (1090 bookmarks)

context answers why a page was bookmarked, by showing what was bookmarked around it, grouped into sittings by the quiet between them. Give it a URL or a time as 2026-04-10T21:15.

Filling the cache

hatebu sync                       # yesterday
hatebu sync --days 30             # yesterday back thirty days
hatebu sync --date 2019-07-04     # one day
hatebu import ./old-bookmarks     # a legacy <dir>/YYYY/MM/*.json tree

sync never fetches today by default: today is still changing, and a cached copy of a half-finished day would be wrong from the moment it was written. --days and --date cannot be combined.

Configuration

hatebu config set username <your_hatena_id>
hatebu config get username

Where things live

| | | | --- | --- | | Bookmarks | ${XDG_CACHE_HOME:-~/.cache}/hatebucli/YYYY/MM/DD.json | | Username | ~/.config/hatebu/credentials.json | | HATENA_USER | the user ID, and it wins over the stored one | | HATENA_BOOKMARK_RSS_URL | override the feed URL. %s is the user ID |

The cache is plain JSON per day and safe to copy, diff or back up. Earlier versions also kept a search index under hatebucli/index; nothing writes there any more and it can be deleted (see ADR 005).

What the archive can and cannot tell you

  • A day that was never synced is not a day with nothing in it, and the commands say which is which rather than reporting a zero.
  • Tags come from the feed as it stood on the day. A stretch from before you started tagging has no tags, which is a gap in the dat

Truncated for display β€” read the full file on GitHub.

Related Skills

View on GitHub
GitHub Stars10
CategoryDevelopment
Updated8d ago
Forks0

Languages

TypeScript

Trust signals

80/100

From repository metadata: license, adoption, age and documentation. Not a code audit β€” see the Safety scan above for what the skill file itself contains.

1 medium1 low1 info
hatebucli β€” MCP Server: Install & Safety Check | SkillAgent