Status-line countdown to prompt-cache expiry; auto-compacts when 5 minutes remain.

Portable skills, slash commands, and agent definitions for AI coding assistants. Everything is plain Markdown with YAML frontmatter — no tool lock-in. The repo is the source of truth; config dirs on each machine just link to it.
| Directory | Contents | Claude Code target |
|---|---|---|
skills/ | One directory per skill (SKILL.md + optional refs) | ~/.claude/skills |
commands/ | One Markdown file per slash command (subdirs = namespaces, e.g. git/commit.md → /git:commit) | ~/.claude/commands |
agents/ | One Markdown file per subagent definition | ~/.claude/agents |
The same files work with any tool that understands Markdown + frontmatter — point sync.sh --target at a different config dir to adapt.
git clone git@github.com:siinghd/skills.git
cd skills
./sync.sh install # symlinks into ~/.claude (edits stay in the repo)
# or: ./sync.sh install --copy # plain copies instead of symlinks
git pull # get latest (symlinked installs pick it up instantly)
./sync.sh status # see what's linked / copied / missing
./sync.sh capture # pull back edits made directly in ~/.claude (copy mode)
git add -A && git commit -m "update skills" && git push
Drop the file in the matching directory, commit, push, and run ./sync.sh install on machines using copy mode (symlink mode needs a rerun only when a new top-level entry appears).
skills/openrouter + agents/openrouter-worker.md (driven by /orchestrate ... use <model>) add a cheap delegation tier: workers on any OpenRouter-hosted model (DeepSeek, Qwen, GLM, Kimi, …) execute mechanical packets at ~2% of premium-model rates, dispatched either as one-shot "oracles", fully agentic headless Claude Code workers, native chaperone agents, or MCP tools in Claude Desktop.
One-command setup on a new machine (after ./sync.sh install):
~/.claude/skills/openrouter/setup.sh sk-or-v1-<your-openrouter-key>
That checks dependencies, stores the key via the platform's best backend (macOS Keychain, Linux Secret Service if available, else a 0600 key file), starts the local LiteLLM gateway, and registers the Claude Desktop MCP server with the per-OS config path. Re-run any time — it's idempotent. Details in skills/openrouter/SKILL.md. Works on macOS, Linux (including WSL), and Windows under Git Bash/MSYS2 — these are bash scripts, so native cmd.exe/PowerShell is not supported. On Windows the key file is not encrypted at rest; WSL is the recommended route for full parity.
mods/ holds Claude Code plugins (mods) that hook the engine itself.
mods/cache-clock — status-line countdown to prompt-cache expiry; when 5 minutes remain and the session is idle (context ≥ 30% full), it compacts so the post-expiry rebuild is the small summary. Assumes the 1 h cache TTL; edit the constants at the top of hooks/register.tsx otherwise.Load one for a single session, or for every session via your shell profile:
claude --plugin-dir /path/to/skills/mods/cache-clock
export CLAUDE_CODE_PLUGIN_DIRS="/path/to/skills/mods/cache-clock"
.claude-plugin/types/ is generated by the engine on load and is not tracked.
hooks/register.tsx 80 lines1import type { Register } from 'claude-code'
2
3// The cache TTL is 5 min by default, 1 h on the extended tier. Set it to match
4// your session; the compact trigger only makes sense when TTL > COMPACT_AT.
5const TTL_MS = 60 * 60 * 1000
6const COMPACT_AT_MS = 5 * 60 * 1000
7// Compacting spends tokens (reads the history, writes a summary), so it only
8// pays off when rebuilding the cache after expiry would cost more. Below this
9// context fill, let the cache expire and skip the compact.
10const MIN_CONTEXT_PERCENT = 30
11
12const fmt = (ms: number) => {
13 const s = Math.max(0, Math.ceil(ms / 1000))
14 return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`
15}
16
17export const register: Register = on => {
18 // The API times the TTL from the START of a request that writes or reads the
19 // cache. We touch at prompt/tool-call time, which is at or before that start,
20 // so the countdown errs short (compacts early), never late.
21 let lastTouch: number | null = null
22 let isCompacting = false
23 let hasCompactedThisWindow = false
24
25 on('session.start', async ($, e, next) => {
26 lastTouch = await $.clock.now()
27 hasCompactedThisWindow = false
28
29 $.clock.every(1000, async () => {
30 if (lastTouch === null) {
31 $.ui.status('cache: cold')
32 return
33 }
34 const left = TTL_MS - ((await $.clock.now()) - lastTouch)
35 if (left <= 0) {
36 $.ui.status('cache: expired')
37 return
38 }
39 $.ui.status(`cache ${fmt(left)}`)
40
41 // Compact while idle so the rebuild after expiry is the small summary,
42 // not the full conversation. compact() rejects mid-turn; retry next tick.
43 if (left <= COMPACT_AT_MS && !hasCompactedThisWindow && !isCompacting) {
44 isCompacting = true
45 try {
46 const { context } = await $.session.usage()
47 if ((context.percent ?? 0) < MIN_CONTEXT_PERCENT) {
48 hasCompactedThisWindow = true // small context: not worth the tokens
49 return
50 }
51 await $.session.compact()
52 hasCompactedThisWindow = true
53 lastTouch = null // new prefix is not cached until the next request
54 $.ui.toast('cache-clock: auto-compacted before cache expiry')
55 } catch {
56 // a turn is running; try again on the next tick
57 } finally {
58 isCompacting = false
59 }
60 }
61 })
62
63 return next(e)
64 })
65
66 // On failure the hook counts as absent, so the countdown never blocks a
67 // prompt or tool call.
68 on('prompt.submit', async ($, e, next) => {
69 lastTouch = await $.clock.now()
70 hasCompactedThisWindow = false
71 return next(e)
72 }).catch(() => undefined)
73
74 on('tool.call', async ($, e, next) => {
75 lastTouch = await $.clock.now()
76 hasCompactedThisWindow = false
77 return next(e)
78 }).catch(() => undefined)
79}
80