入力欄の上に文脈の使用率・5時間/週間の利用枠とリセット時刻(JST)・Cloudクレジットの失効までを1行で出す

Obsidian の保管庫を「AI と共有する自分のナレッジレイク」にして、作業の多くを AI(Claude Code / Codex)に任せられるようにするための一式。 作者が自分の保管庫で実際に運用している仕組みから、個人の中身を抜いて汎用化したもの。
| フォルダ | 何か | 最初に読むもの |
|---|---|---|
knowledge-lake-kit/ | 保管庫の器。 フォルダ構成、AI への指示書(AGENTS.md)、各フォルダの README、テンプレート、運用の考え方 | START_HERE.md(人間)/ SETUP_FOR_AI.md(AI) |
agent-tooling/ | AI に持たせる道具。 スキル(/inbox・/prioritize など)、MCP(Gmail 横断・Google Tasks)、Claude Code の mod、フック、CLI、定期実行 | README.md(人間)/ FOR_AI.md(AI) |
git clone https://github.com/KaiMiyazawa/obsidian-ai-knowledge-lake.git
knowledge-lake-kit/START_HERE.md を上から順に進める。最後は AI がインタビューしながら保管庫を組み立てる。<クローンした場所>/agent-tooling/FOR_AI.md を読んで、この道具を私の保管庫に入れるのを手伝ってどちらも「AI が読んで、利用者に確認しながら進める」前提で書いてある。全部を一度に入れなくてよい。
you@gmail.com、<MAIN_LIST_ID> など)にしてある。MIT。ただし agent-tooling/outside-vault/gtasks-mcp/ は alvinjchoi/gtasks-mcp(MIT)を改造したもので、同フォルダの LICENSE に従う。
hooks/register.tsx 87 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register, SessionContextUsage, SessionRateLimit } from 'claude-code'
3
4const usage = atom({ plugin: 'status-band', key: 'usage' } as const, null)
5
6// AGENTS.md「Claude Code Cloud Session への委譲」。期限を過ぎたら表示しない(この定数ごと消してよい)。
7const CLOUD_CREDIT_EXPIRES = Date.parse('2026-11-05T16:59:00+09:00')
8
9const JST_MS = 9 * 3600 * 1000
10const LABELS: Record<string, string> = { five_hour: '5時間', seven_day: '週間' }
11
12const jst = (iso: string, withDate: boolean) => {
13 const t = new Date(Date.parse(iso) + JST_MS).toISOString()
14 const hm = t.slice(11, 16)
15 return withDate ? `${t.slice(5, 7)}/${t.slice(8, 10)} ${hm}` : hm
16}
17
18const levelColor = (p: number) => (p >= 90 ? 'red' : p >= 70 ? 'yellow' : 'green')
19
20const save = async ($: EngineInterface, context: SessionContextUsage, limits: readonly SessionRateLimit[]) => {
21 const model = await $.session.model()
22 await update($, usage, () => ({
23 model,
24 contextPercent: context.percent,
25 limits: limits.map(l => ({ kind: l.kind, percentUsed: l.percentUsed, resetsAt: l.resetsAt })),
26 }))
27}
28
29export const register: Register = on => {
30 on('session.start', async ($, e, next) => {
31 const ran = await next(e)
32 const now = await $.session.usage()
33 await save($, now.context, now.rateLimits)
34
35 return ran
36 })
37
38 on('session.measure', async ($, e, next) => {
39 await save($, e.context, e.rateLimits)
40
41 return next(e)
42 })
43
44 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
45 const u = await read($, usage)
46 if (e.props.hasSurvey || u === null) return next(e)
47
48 const { Box, Text } = $.ui.resolve(e)
49 const now = Date.now()
50 const ctx = u.contextPercent
51 const filled = ctx === undefined ? 0 : Math.round(ctx / 10)
52 const creditDays = Math.ceil((CLOUD_CREDIT_EXPIRES - now) / 86400000)
53
54 return (
55 <Box columnGap={1} flexWrap="wrap">
56 <Text dimColor>[{u.model}]</Text>
57 <Text>
58 文脈 {e.props.bodyColumns >= 65 && `[${'#'.repeat(filled)}${'-'.repeat(10 - filled)}] `}
59 </Text>
60 {ctx === undefined ? (
61 <Text dimColor>未取得</Text>
62 ) : (
63 <Text color={levelColor(ctx)}>{ctx}%</Text>
64 )}
65 {u.limits
66 .filter(l => LABELS[l.kind] !== undefined)
67 .map(l => {
68 const isPast = l.resetsAt !== undefined && Date.parse(l.resetsAt) <= now
69 const reset =
70 l.resetsAt === undefined ? '' : isPast ? '(更新待ち)' : `→${jst(l.resetsAt, l.kind === 'seven_day')}`
71 return (
72 <Text key={l.kind}>
73 | {LABELS[l.kind]} <Text color={levelColor(l.percentUsed)}>{Math.round(l.percentUsed)}%</Text>
74 {reset}
75 </Text>
76 )
77 })}
78 {creditDays > 0 && (
79 <Text color={creditDays <= 7 ? 'yellow' : undefined} dimColor={creditDays > 7}>
80 | Cloudクレジット失効まで{creditDays}日
81 </Text>
82 )}
83 </Box>
84 )
85 })
86}
87types/index.d.ts 14 lines1export type Usage = {
2 model: string
3 contextPercent?: number
4 limits: { kind: string; percentUsed: number; resetsAt?: string }[]
5}
6
7declare module 'claude-code' {
8 interface PluginState {
9 'status-band': {
10 usage: Usage | null
11 }
12 }
13}
14