last30days-cn
A Claude Code skill that researches what Chinese internet users said recently across Weibo, Xiaohongshu, Bilibili, Zhihu, Douyin, WeChat, Baidu and Toutiao. It ranks posts by engagement and recency and writes a sourced research report.
What nutmeg runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Claude Code
> /plugin marketplace add withqwerty/nutmeg> /plugin install nutmeg@nutmeg
Cursor
Cursor also runs Claude Code hooks from .claude/settings.json, so copy these hooks there to use them in Cursor.
What fires, and when
SessionStart
Fires once when a session begins, and again after a context compaction. It is where a plugin sets up its environment, or restores state the compaction dropped.
sh "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh"PreToolUse
Bashsh "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh" gate_hook.pyStop
sh "${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.sh" stop_check.pyIn the plugin's words
How nutmeg describes its own hook set.
Adds nutmeg's core football-data and research-project rules to each session. Inside an active research project, shows a gate card before runs and checks outputs against the claim ledger before a step ends.
Where it lives
"""PreToolUse hook: show a gate card before runs inside a research project.
Called through run-hook.sh, which has already found an active project. Prints
a permission decision as JSON, or nothing to leave the command to Claude
Code's normal permission flow.
"""
import json
import os
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "core"))
from nutmeg_core import gate, project # noqa: E402
def main():
try:
data = json.load(sys.stdin)
except (json.JSONDecodeError, ValueError):
return 0
if data.get("tool_name") != "Bash":
return 0
command = (data.get("tool_input") or {}).get("command") or ""
cwd = data.get("cwd") or os.getcwd()
repo = Path(os.environ.get("NUTMEG_REPO_ROOT") or cwd)
active = project.active_project(repo)
try:
decision = gate.decide(command, repo, active, cwd)
except Exception as exc: # a broken gate must not wave a run through
if active is None or gate.parse(command).kind == "other":
return 0
decision = gate.Decision("ask", f"nutmeg research gate failed ({exc}); read the command yourself before approving.")
if decision is not None:
print(json.dumps(decision.hook_output()))
return 0
if __name__ == "__main__":
sys.exit(main())
#!/bin/sh
# Run a nutmeg research hook only when a research project is active.
#
# sh run-hook.sh <hook script in this folder> (hook JSON on stdin)
#
# With no active project this exits at once, before Python starts, so
# ordinary nutmeg use and Claude Code's normal permission flow are unchanged.
# Without python3 >= 3.10 it warns once per repository and exits 0.
# It uses only shell built-ins until it knows a project is active.
script="$1"
here="${0%/*}"
# Find research/.active from the project folder up to the repository root.
# Stop at the filesystem root, a drive root (C:) or after 64 steps.
dir="${CLAUDE_PROJECT_DIR:-$PWD}"
steps=0
while :; do
if [ -f "$dir/research/.active" ]; then
break
fi
if [ -e "$dir/.git" ]; then
exit 0
fi
parent="${dir%/*}"
if [ "$parent" = "$dir" ]; then
parent="${dir%\\*}"
fi
steps=$((steps + 1))
if [ -z "$parent" ] || [ "$parent" = "$dir" ] || [ "$steps" -gt 64 ]; then
exit 0
fi
dir="$parent"
done
if ! command -v python3 >/dev/null 2>&1 || ! python3 -c 'import sys; sys.exit(sys.version_info < (3, 10))' 2>/dev/null; then
marker="$dir/research/.python-warned"
if [ ! -f "$marker" ]; then
: > "$marker" 2>/dev/null
printf '%s\n' '{"systemMessage": "nutmeg: a research project is active, but python3 3.10 or newer was not found, so the run gate and checks are off. Install Python 3.10+ to turn them on."}'
fi
exit 0
fi
NUTMEG_REPO_ROOT="$dir" exec python3 "$here/$script"
#!/bin/sh
# Print the SessionStart context with the plugin path filled in.
#
# The research-project rules (session-start-research.txt, stored JSON-escaped) are added only when the project has a
# research/ folder, so sessions without research projects do not pay for them.
#
# Pure shell, no sed: the path is JSON-escaped (backslashes and quotes) and
# inserted literally, so characters such as & or | in it cannot corrupt the JSON.
root="${CLAUDE_PLUGIN_ROOT:-${0%/hooks/*}}"
file="$root/hooks/session-start.json"
fragment="$root/hooks/session-start-research.txt"
project="${CLAUDE_PROJECT_DIR:-$PWD}"
# JSON-escape the path, one character at a time.
newline='
'
tab=$(printf '\t')
cr=$(printf '\r')
escaped=""
rest="$root"
while [ -n "$rest" ]; do
char="${rest%"${rest#?}"}"
rest="${rest#?}"
case "$char" in
\\) escaped="$escaped\\\\" ;;
\") escaped="$escaped\\\"" ;;
"$newline") escaped="$escaped\\n" ;;
"$tab") escaped="$escaped\\t" ;;
"$cr") escaped="$escaped\\r" ;;
*) escaped="$escaped$char" ;;
esac
done
# Any other control character: leave the placeholder for a variable name rather than emit invalid JSON.
case "$escaped" in
*[[:cntrl:]]*) escaped='$CLAUDE_PLUGIN_ROOT' ;;
esac
# replace MARKER VALUE: every MARKER in $text becomes VALUE (no pattern characters are interpreted).
replace() {
out=""
while :; do
case "$text" in
*"$1"*)
out="$out${text%%"$1"*}$2"
text="${text#*"$1"}"
;;
*)
break
;;
esac
done
text="$out$text"
}
research=""
if [ -d "$project/research" ] && [ -f "$fragment" ]; then
research=$(cat "$fragment") || research=""
fi
text=$(cat "$file") || exit 0
replace "@RESEARCH@" "$research"
replace "@PLUGIN_ROOT@" "$escaped"
printf '%s\n' "$text"
"""Stop hook: check the active research project's outputs before a step ends.
Called through run-hook.sh, which has already found an active project. Blocks
the stop once for each new open problem, with the list; a second stop
(stop_hook_active) is always allowed. Open problems stay in checks.json, and
publishing refuses until each is fixed or accepted with a reason.
"""
import json
import os
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "core"))
from nutmeg_core import check, project # noqa: E402
def main():
try:
data = json.load(sys.stdin)
except (json.JSONDecodeError, ValueError):
data = {}
if data.get("stop_hook_active"):
return 0
repo = Path(os.environ.get("NUTMEG_REPO_ROOT") or data.get("cwd") or os.getcwd())
active = project.active_project(repo)
if active is None or not check.output_files(active):
return 0
try:
state = check.check(active)
except Exception as exc: # never trap the user in a loop because the check broke
print(json.dumps({"systemMessage": f"nutmeg check could not run: {exc}"}))
return 0
new = [f for f in state["open"] if f["id"] not in state["last_blocked"]]
if not new:
return 0
state["last_blocked"] = sorted(set(state["last_blocked"]) | {f["id"] for f in state["open"]})
check.save_state(active, state)
name = active.relative_to(repo) if active.is_relative_to(repo) else active
lines = [f"nutmeg check: {len(state['open'])} open problem(s) in the outputs of {name}:"]
lines += [f"- {check.describe(f)}" for f in state["open"][:20]]
if len(state["open"]) > 20:
lines.append(f"- … {len(state['open']) - 20} more (run `nutmeg check`)")
lines.append(
"Fix each one: add the missing claim with its evidence, or correct the output, then run `nutmeg check`. "
"If the user says a problem is not one, record their reason with "
"`nutmeg check --accept <id> --reason \"...\"`. Publishing refuses while problems are open."
)
print(json.dumps({"decision": "block", "reason": "\n".join(lines)}))
return 0
if __name__ == "__main__":
sys.exit(main())
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
A Claude Code plugin that makes Claude an expert at football data analytics. Who it's for: Anyone who works with football data — analysts, developers, journalists, researchers, hobbyists.
A Claude Code skill that researches what Chinese internet users said recently across Weibo, Xiaohongshu, Bilibili, Zhihu, Douyin, WeChat, Baidu and Toutiao. It ranks posts by engagement and recency and writes a sourced research report.
Semantic search over past Claude Code conversations, so a session can recall earlier discussions, decisions, and patterns. It adds a remembering-conversations skill that checks prior sessions before the assistant says it doesn't know.
Gives Claude Code a long-term memory layer with semantic search, recall injection, and graph entities, letting agents recall, save, inspect, and audit memories through bundled MCP tools.
Skills and agent plugins that let AI coding agents such as Claude Code work with Databricks, covering AI Functions, Agent Bricks, AI/BI dashboards, GPU training workloads and Databricks Apps design.
A lightweight plugin that integrates Pinecone vector database capabilities directly into Claude Code, enabling semantic search, index management, and RAG (Retrieval Augmented Generation) workflows.
A research workflow plugin for Claude Code that turns folders of papers, notes and drafts into a linked, Obsidian-compatible knowledge base. It adds skills to adopt existing material, compile sources into concept cards, answer questions with citations, and write up unattended runs.
Archives every Claude Code session locally in a searchable SQLite store and saves durable facts into persistent memory. Adds skills to flush and review memory at session start or before clearing a conversation.
Adds graph-based memory to Claude Code that records past interactions and learns recurring patterns across sessions. Runs as hooks and an MCP server.