Kindleでハイライトした短いセンテンスをClaude Codeのspinner verbsに設定する。 作業中のスピナーに、自分の読書体験からの一節がランダムに流れるようになる。
以下のパスにこのSKILL.mdを配置する。
/.claude/skills/kindle-spinner/SKILL.md
もしくは、このプロンプトをコピペでClaude Codeに教えても実行できるはず。
/kindle-spinner # デフォルト30冊
/kindle-spinner 300 # 300冊から収集
引数で巡回する冊数を指定できる。省略時は30冊。
スキル起動時の引数を冊数として使う(省略時は30)。以下のコード内の TARGET_COUNT を実際の数値に置き換えてからユーザーに提示し、ブラウザで実行してもらう。
ユーザーへの案内:
- https://read.amazon.co.jp/notebook をブラウザで開く
- 開発者コンソール(F12 → Console)を開く
- 以下のコードを貼り付けて実行する
- ランダムな冊数(スキル起動時の引数、デフォルト30)から1冊1ハイライトずつ集め、自動で
kindle_spinner_verbs.jsonがダウンロードされる
// Kindle → Claude Code Spinner Verbs
// https://read.amazon.co.jp/notebook の開発者コンソール(F12)で実行
(async () => {
const bookEls = document.querySelectorAll('#kp-notebook-library .kp-notebook-library-each-book');
const indices = [...Array(bookEls.length).keys()];
for (let i = indices.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[indices[i], indices[j]] = [indices[j], indices[i]];
}
const target = Math.min(TARGET_COUNT, bookEls.length);
const books = []; // { title, author, highlights[] } を本ごとに収集
let visited = 0;
console.log(`📚 ${bookEls.length}冊からランダムに${target}冊を巡回します(指定: ${TARGET_COUNT}冊)...\n`);
for (const idx of indices) {
if (visited >= target) break;
const book = bookEls[idx];
const title = book.querySelector('h2')?.textContent?.trim() || '';
const author = book.querySelector('p')?.textContent?.trim()?.replace(/^著者:\s*/, '') || '';
console.log(`📖「${title}」を巡回中… (${visited + 1}/${target})`);
book.querySelector('a')?.click();
await new Promise(r => setTimeout(r, 2000));
const highlights = [];
document.querySelectorAll('#highlight').forEach(h => {
const text = h.textContent?.trim();
if (text && text.length >= 10 && text.length <= 80 && !text.includes('\n')) {
highlights.push(text);
}
});
if (highlights.length > 0) {
books.push({ title, author, highlights });
console.log(` ✨ ${highlights.length}件の候補をストック`);
} else {
console.log(` (短いハイライトなし、スキップ)`);
}
visited++;
}
// 全冊の候補が揃ってから、各本からランダムに1件ずつ選ぶ
const verbs = books.map(b => {
const pick = b.highlights[Math.floor(Math.random() * b.highlights.length)];
return `「${pick}」 - ${b.title} ${b.author}`;
});
console.log(`\n🎉 ${target}冊を巡回し、${books.length}冊から${verbs.length}件のハイライトを選出!`);
const blob = new Blob([JSON.stringify(verbs, null, 2)], { type: 'application/json' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'kindle_spinner_verbs.json';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
console.log('📥 kindle_spinner_verbs.json をダウンロードしました');
})();ユーザーからダウンロード完了の報告を受けたら、以下を実行する。
cp ~/.claude/settings.json ~/.claude/settings.json.bak.$(date +%Y%m%d%H%M%S)ruby -rjson -e '
verbs = JSON.parse(File.read(File.expand_path("~/Downloads/kindle_spinner_verbs.json")))
original_count = verbs.length
# KindleのUI都合で文の切れ目がずれるのをトリム
verbs = verbs.map { |v|
next nil unless v =~ /\A「(.+?)」( - .+)\z/m
text, meta = $1, $2
text = text.sub(/\A[ぁ-んァ-ヶa-zA-Z]{0,5}[。!?」)\)]\s*/, "")
text = text.sub(/\A[、。]\s*/, "")
text = text.sub(/[、,(\(「]\s*\z/, "")
text = text.sub(/[\[[][A-Za-z]*\s*\z/, "")
text = text.rstrip
next nil if text.length < 10 || text.length > 80
"「#{text}」#{meta}"
}.compact
settings = JSON.parse(File.read(File.expand_path("~/.claude/settings.json")))
settings["spinnerVerbs"] = { "mode" => "replace", "verbs" => verbs }
File.write(File.expand_path("~/.claude/settings.json"), JSON.pretty_generate(settings) + "\n")
puts "#{original_count} -> #{verbs.length} verbs written (#{original_count - verbs.length} trimmed out)"
'再起動不要で即座に反映される。
- Kindle Notebookの本切り替えは
book.querySelector('a').click()で行う(Amazon の a-declarative フレームワークのためdispatchEventでは動かない) - 長いハイライトはターミナルで複数行になり表示が崩れるため、80文字以内に限定する