skills-viewer 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/build/server/index.js +4 -2
- package/build/server/lint.js +57 -0
- package/build/server/scan.js +17 -2
- package/dist/assets/{index-D2xAPQHX.css → index-CIxN1tbB.css} +1 -1
- package/dist/assets/index-DoP2Wrhx.js +67 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/dist/assets/index-BF9RnD9N.js +0 -67
package/README.md
CHANGED
|
@@ -17,7 +17,9 @@ npx skills-viewer
|
|
|
17
17
|
## Features
|
|
18
18
|
|
|
19
19
|
- **All scopes in one view** — user (`~/.claude/skills`), every project's `.claude/skills` / `.claude/commands`, installed plugins, and Claude Code built-ins, grouped by source
|
|
20
|
-
- **Search / sort** — incremental search over name + description + usage; sort by name, usage count, last used, or
|
|
20
|
+
- **Search / sort** — incremental search over name + description + usage; sort by name, usage count, last used, updated date, or token cost
|
|
21
|
+
- **Diagnostics** — an _unused_ badge (no recorded use within the transcript retention window) with an unused-only filter, plus static description lint: missing / too-short / too-long descriptions, missing trigger conditions ("use when …") that make auto-invocation unlikely, and name-echo descriptions
|
|
22
|
+
- **Token cost** — since every name + description is injected into each session, the estimated token overhead is shown per item, per scope, and as a per-session total for the current project
|
|
21
23
|
- **Usage stats** — invocation counts and last-used dates aggregated from Claude Code session transcripts (`~/.claude/projects/`), covering both user-typed `/skill` calls and model-invoked Skill tool calls
|
|
22
24
|
- **AI summaries** — one-click summarization of each SKILL.md via `claude -p --model haiku`, cached by content hash in `~/.cache/skills-viewer/` so unchanged skills are never re-summarized
|
|
23
25
|
- **Detail pane** — master/detail layout with an Overview tab (description, usage, bundled files) and a rendered SKILL.md tab
|
|
@@ -68,7 +70,8 @@ dist/ # prebuilt UI shipped in the npm package (generated by prepack
|
|
|
68
70
|
|
|
69
71
|
## Notes
|
|
70
72
|
|
|
71
|
-
- Usage stats only cover the transcript retention window of Claude Code (`cleanupPeriodDays`, default 30 days)
|
|
73
|
+
- Usage stats only cover the transcript retention window of Claude Code (`cleanupPeriodDays`, default 30 days) — the _unused_ badge has the same limitation
|
|
74
|
+
- Token costs are heuristic estimates (≈4 chars/token for ASCII, ≈1.5 for CJK), not exact tokenizer counts
|
|
72
75
|
- Usage is attributed per calling project (worktrees roll up to their parent project by encoded-path prefix). When scopes share a name, the resolution order project > user > plugin > built-in is assumed
|
|
73
76
|
- The built-in skill list is hardcoded in `server/scan.js` (they live inside the Claude Code binary); check `/skills` inside Claude Code for the authoritative list
|
|
74
77
|
- AI summaries require a logged-in `claude` CLI
|
package/build/server/index.js
CHANGED
|
@@ -67,6 +67,7 @@ const MIME = {
|
|
|
67
67
|
* 呼び出し元プロジェクト(トランスクリプトのディレクトリ)を特定し、そのプロジェクトの
|
|
68
68
|
* skill → user → plugin → built-in の解決優先順で1つの定義にだけ加算する。
|
|
69
69
|
* 呼び出し元が未知(worktree・削除済みプロジェクト等)の場合は user 以降にフォールバック。
|
|
70
|
+
* 戻り値はトランスクリプトが1件でも存在したか(false なら「未使用」判定は無意味)。
|
|
70
71
|
*/
|
|
71
72
|
function attributeUsage(sections) {
|
|
72
73
|
const byDir = (0, usage_1.scanUsageByDir)();
|
|
@@ -121,10 +122,11 @@ function attributeUsage(sections) {
|
|
|
121
122
|
target.lastUsed = Math.max(target.lastUsed || 0, u.last);
|
|
122
123
|
}
|
|
123
124
|
}
|
|
125
|
+
return Object.keys(byDir).length > 0;
|
|
124
126
|
}
|
|
125
127
|
function collect(cwd, lang) {
|
|
126
128
|
const sections = (0, scan_1.scanSections)(cwd, lang);
|
|
127
|
-
attributeUsage(sections);
|
|
129
|
+
const usageAvailable = attributeUsage(sections);
|
|
128
130
|
const summaries = (0, summary_1.loadSummaries)();
|
|
129
131
|
for (const sec of sections) {
|
|
130
132
|
for (const it of sec.items) {
|
|
@@ -153,7 +155,7 @@ function collect(cwd, lang) {
|
|
|
153
155
|
.sort((a, b) => path.basename(a).localeCompare(path.basename(b)))
|
|
154
156
|
.map((p) => ({ label: path.basename(p), sub: p, path: p })),
|
|
155
157
|
];
|
|
156
|
-
return { generatedAt: new Date().toISOString(), cwd, sections, targets, aiStale };
|
|
158
|
+
return { generatedAt: new Date().toISOString(), cwd, sections, targets, aiStale, usageAvailable };
|
|
157
159
|
}
|
|
158
160
|
/* DNS rebinding 対策: same-origin GET には Origin が付かないため Host 側も検証する */
|
|
159
161
|
function hostOk(req) {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* description の静的リントと、セッション注入トークン量の概算。
|
|
4
|
+
* Claude Code はスキル選択のために frontmatter の name + description を毎セッション
|
|
5
|
+
* コンテキストへ注入する。lint は「モデルが発動判断に使える description か」の観点で、
|
|
6
|
+
* frontmatter の meta(本文フォールバック前)に対して行う。
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.estimateTokens = estimateTokens;
|
|
10
|
+
exports.lintItem = lintItem;
|
|
11
|
+
/*
|
|
12
|
+
* トークン数の概算(厳密なトークナイザは依存が重いので入れない)。
|
|
13
|
+
* 目安: ASCII ≈ 4 文字/トークン、非 ASCII(日本語等)≈ 1.5 文字/トークン。
|
|
14
|
+
*/
|
|
15
|
+
function estimateTokens(text) {
|
|
16
|
+
let ascii = 0;
|
|
17
|
+
let other = 0;
|
|
18
|
+
for (const ch of text) {
|
|
19
|
+
if (ch.charCodeAt(0) < 128)
|
|
20
|
+
ascii++;
|
|
21
|
+
else
|
|
22
|
+
other++;
|
|
23
|
+
}
|
|
24
|
+
return Math.ceil(ascii / 4 + other / 1.5);
|
|
25
|
+
}
|
|
26
|
+
/*
|
|
27
|
+
* 発動条件らしい語句。en は "use when/for …"・条件節、ja は「〜のときに使用」系。
|
|
28
|
+
* command は人間が明示的に打つ起点なので発動条件は要求しない(呼び出し側で除外)。
|
|
29
|
+
*/
|
|
30
|
+
const TRIGGER_RE = /\b(use|when|whenever|if|for|before|after|trigger|invoke)\b|使用|使う|使って|用い|とき|時に|場合|際|なら|向け/i;
|
|
31
|
+
const SHORT_LIMIT = 30;
|
|
32
|
+
const LONG_LIMIT = 1024;
|
|
33
|
+
/*
|
|
34
|
+
* meta: frontmatter のパース結果。name はフォールバック解決後の表示名。
|
|
35
|
+
* 戻り値は言語非依存のコード(表示ラベルは web の i18n で解決)。
|
|
36
|
+
*/
|
|
37
|
+
function lintItem(meta, name, kind) {
|
|
38
|
+
if (kind === 'hook')
|
|
39
|
+
return [];
|
|
40
|
+
const warnings = [];
|
|
41
|
+
const desc = (meta.description || '').trim();
|
|
42
|
+
if (!desc)
|
|
43
|
+
return ['no-description'];
|
|
44
|
+
if (desc.length < SHORT_LIMIT)
|
|
45
|
+
warnings.push('short-description');
|
|
46
|
+
if (desc.length > LONG_LIMIT)
|
|
47
|
+
warnings.push('long-description');
|
|
48
|
+
// 記号を除いて比較し、name をほぼ繰り返しただけの description を検出
|
|
49
|
+
const norm = (s) => s.toLowerCase().replace(/[^a-z0-9ぁ-んァ-ン一-龠]/g, '');
|
|
50
|
+
if (norm(desc) === norm(name))
|
|
51
|
+
warnings.push('name-echo');
|
|
52
|
+
// skill / agent はモデルが description を見て自動発動するため、発動条件の記述を求める
|
|
53
|
+
if ((kind === 'skill' || kind === 'agent') && !TRIGGER_RE.test(desc)) {
|
|
54
|
+
warnings.push('no-trigger');
|
|
55
|
+
}
|
|
56
|
+
return warnings;
|
|
57
|
+
}
|
package/build/server/scan.js
CHANGED
|
@@ -41,6 +41,7 @@ exports.scanSections = scanSections;
|
|
|
41
41
|
const fs = __importStar(require("node:fs"));
|
|
42
42
|
const os = __importStar(require("node:os"));
|
|
43
43
|
const path = __importStar(require("node:path"));
|
|
44
|
+
const lint_1 = require("./lint");
|
|
44
45
|
exports.HOME = os.homedir();
|
|
45
46
|
/* ---------- frontmatter parsing (minimal YAML: scalars + block scalars) ---------- */
|
|
46
47
|
function parseFrontmatter(raw) {
|
|
@@ -129,8 +130,10 @@ function readSkillDir(dir, nameHint) {
|
|
|
129
130
|
return null; // 権限エラー等で読めない skill はスキップ(一覧全体を落とさない)
|
|
130
131
|
}
|
|
131
132
|
const { meta, body } = parseFrontmatter(raw);
|
|
133
|
+
const name = meta.name || nameHint;
|
|
134
|
+
const lint = (0, lint_1.lintItem)(meta, name, 'skill');
|
|
132
135
|
return {
|
|
133
|
-
name
|
|
136
|
+
name,
|
|
134
137
|
description: meta.description || firstBodyLine(body),
|
|
135
138
|
argumentHint: meta['argument-hint'] || '',
|
|
136
139
|
version: meta.version || '',
|
|
@@ -138,6 +141,7 @@ function readSkillDir(dir, nameHint) {
|
|
|
138
141
|
path: skillMd,
|
|
139
142
|
updatedAt: fileMtime(skillMd),
|
|
140
143
|
files: listFiles(dir).sort(),
|
|
144
|
+
...(lint.length ? { lint } : {}),
|
|
141
145
|
_body: body, // 参照抽出用(scanSections で refs 化して破棄)
|
|
142
146
|
};
|
|
143
147
|
}
|
|
@@ -171,8 +175,10 @@ function scanMdRoot(root, kind) {
|
|
|
171
175
|
continue; // 読めないファイルはスキップ(一覧全体を落とさない)
|
|
172
176
|
}
|
|
173
177
|
const { meta, body } = parseFrontmatter(raw);
|
|
178
|
+
const name = meta.name || entry.name.replace(/\.md$/, '');
|
|
179
|
+
const lint = (0, lint_1.lintItem)(meta, name, kind);
|
|
174
180
|
items.push({
|
|
175
|
-
name
|
|
181
|
+
name,
|
|
176
182
|
description: meta.description || firstBodyLine(body),
|
|
177
183
|
argumentHint: kind === 'command' ? meta['argument-hint'] || '' : '',
|
|
178
184
|
version: meta.version || '',
|
|
@@ -180,6 +186,7 @@ function scanMdRoot(root, kind) {
|
|
|
180
186
|
path: fp,
|
|
181
187
|
updatedAt: fileMtime(fp),
|
|
182
188
|
files: [entry.name],
|
|
189
|
+
...(lint.length ? { lint } : {}),
|
|
183
190
|
_body: body,
|
|
184
191
|
});
|
|
185
192
|
}
|
|
@@ -440,5 +447,13 @@ function scanSections(cwd, lang = 'en') {
|
|
|
440
447
|
},
|
|
441
448
|
];
|
|
442
449
|
attachRefs(sections);
|
|
450
|
+
// name + description は毎セッション注入されるため、その分のトークンを概算しておく。
|
|
451
|
+
// hook は設定エントリ(description 注入なし)なので対象外
|
|
452
|
+
for (const s of sections) {
|
|
453
|
+
for (const it of s.items) {
|
|
454
|
+
if (it.kind !== 'hook')
|
|
455
|
+
it.tokens = (0, lint_1.estimateTokens)(it.name + ': ' + it.description);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
443
458
|
return sections;
|
|
444
459
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
*{box-sizing:border-box}body{margin:0;background:#faf9f7;color:#1b1a17;font-family:Helvetica Neue,system-ui,-apple-system,Hiragino Sans,sans-serif}button{font-family:inherit}.mono{font-family:ui-monospace,SF Mono,Menlo,monospace}.wrap{margin:0 auto;min-height:100vh}.wrap.fixed{max-width:1200px}.wrap.fixed .grid{grid-template-columns:repeat(3,minmax(0,1fr))}@media(max-width:900px){.wrap.fixed .grid{grid-template-columns:repeat(2,minmax(0,1fr))}}.hd{padding:28px 32px 0}.hd .t-row{display:flex;align-items:baseline;gap:12px}.hd h1{font-size:22px;font-weight:700;letter-spacing:-.01em;margin:0}.hd h1 a{color:#1b1a17;text-decoration:none}.hd h1 a:hover{color:#57534a}.hd .sub{font-size:12px;color:#8a8579}.hd .count{margin-left:auto;font-size:12px;color:#8a8579}.hd input.q{margin-top:16px;width:100%;padding:11px 14px;border:1px solid #ddd8ce;border-radius:10px;font-size:13.5px;background:#fff;color:#1b1a17;outline:none;font-family:inherit}.controls{display:flex;gap:8px;margin-top:12px;flex-wrap:wrap;align-items:center}.chip{padding:6px 14px;border-radius:999px;font-size:12px;cursor:pointer;background:#fff;border:1px solid #ddd8ce;color:#6b6558}.chip.on{background:#1b1a17;border-color:#1b1a17;color:#fff}.grp-label{display:flex;align-items:center;gap:7px;font-size:12px;color:#57534a;cursor:pointer;margin-left:auto}.grp-label input{accent-color:#1b1a17;width:15px;height:15px;cursor:pointer}.ctrl-sep{width:1px;height:18px;background:#ddd8ce;margin:0 4px;align-self:center}.sel{-moz-appearance:none;appearance:none;-webkit-appearance:none;font-family:inherit;font-size:12px;color:#6b6558;background:#fff;border:1px solid #ddd8ce;border-radius:999px;padding:6px 30px 6px 14px;cursor:pointer;outline:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5'%3E%3Cpath d='M0 0l4 5 4-5z' fill='%238a8579'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 12px center}.sel:focus{border-color:#1b1a17}.seg{display:inline-flex;border:1px solid #ddd8ce;border-radius:999px;overflow:hidden;background:#fff}.seg button{border:0;background:transparent;font-family:inherit;font-size:12px;color:#6b6558;padding:6px 13px;cursor:pointer}.seg button+button{border-left:1px solid #efede8}.seg button.on{background:#1b1a17;color:#fff}.set-label{font-size:12px;font-weight:600;color:#57534a;margin-bottom:10px}.set-options{display:flex;flex-direction:column;gap:8px;margin-bottom:20px}.set-option{display:flex;align-items:center;gap:9px;font-size:13px;cursor:pointer}.set-option input{accent-color:#1b1a17}.set-scheme{font-family:ui-monospace,Menlo,monospace;font-size:10.5px;color:#a39d90;margin-left:auto}.set-input{font-family:ui-monospace,Menlo,monospace;font-size:12px;padding:8px 10px;border:1px solid #ddd8ce;border-radius:8px;outline:none;margin-left:24px}.set-input:focus{border-color:#1b1a17}.pbtn.primary{background:#1b1a17;border-color:#1b1a17;color:#fff}.pbtn.primary:hover{background:#3a372f;border-color:#3a372f}.sec-h{display:flex;align-items:center;gap:8px;margin:14px 0 6px;position:sticky;top:0;z-index:5;background:#faf9f7;padding:10px 0 8px}.sec-h .sq{width:8px;height:8px;border-radius:2px;flex:none}.sec-h .lbl{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#57534a}.sec-h .n{font-size:11px;color:#a39d90}.sec-h .ln{flex:1;height:1px;background:#e8e4dc}.sec-h.sm{margin:8px 0 4px;padding:8px 0 6px}.sec-h.sm .lbl{font-size:10.5px}.grid-pad{padding:6px 32px 32px}.grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}.card{background:#fff;border:1px solid #e8e4dc;border-radius:10px;padding:18px 20px;display:flex;flex-direction:column;gap:10px;cursor:pointer;text-align:left;min-width:0}.card:hover{border-color:#b9b2a3;box-shadow:0 2px 8px #1b1a170f}.card .top{display:flex;align-items:baseline;gap:8px}.card .nm{font-size:15px;font-weight:600;color:#1b1a17;word-break:break-all}.card .ver{margin-left:auto;font-size:10.5px;color:#a39d90;font-family:ui-monospace,Menlo,monospace;white-space:nowrap}.card .desc{font-size:13px;line-height:1.65;color:#57534a;margin:0;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.card .usage{font-family:ui-monospace,Menlo,monospace;font-size:11.5px;background:#f4f1ea;border-radius:6px;padding:8px 10px;color:#6b6558;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card .meta{display:flex;gap:8px;font-size:10.5px;color:#8a8579}.card .meta span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.card .meta .meta-r{margin-left:auto;flex:none;display:inline-flex;gap:8px;align-items:center}.scope-mini{display:inline-flex;align-items:center;gap:5px;font-size:10px;font-weight:600;letter-spacing:.04em;color:#8a8579;margin-bottom:-4px}.dot5{width:6px;height:6px;border-radius:2px;flex:none}.empty{text-align:center;padding:44px;font-size:13px;color:#8a8579}.ai-mark{color:#b0836a;font-size:11px}.kbadge{font-size:9.5px;font-weight:700;padding:2px 8px;border-radius:999px;background:#f1efe9;color:#8a8579;white-space:nowrap;flex:none;vertical-align:3px;margin-left:8px}.same-actions{margin-left:auto;display:inline-flex;gap:6px}.pbtn.sm{font-size:11px;padding:3px 10px}.pbtn.sm.on{background:#1b1a17;border-color:#1b1a17;color:#fff}.diff-wrap{margin-top:10px}.diff-legend{display:flex;gap:14px;align-items:center;font-size:11px;margin-bottom:6px}.d-del-mark{color:#b0392e}.d-add-mark{color:#1f8a5b}.d-count{color:#8a8579;margin-left:auto}.diff{border:1px solid #e8e4dc;border-radius:8px;overflow-x:auto;background:#fff;font-family:ui-monospace,Menlo,monospace;font-size:11px;line-height:1.7;max-height:480px;overflow-y:auto}.d-line{padding:0 12px;white-space:pre}.d-add{background:#1f8a5b14;color:#16603f}.d-del{background:#b0392e12;color:#8c2d24}.d-skip{padding:2px 12px;color:#a39d90;background:#faf9f7;text-align:center;border-top:1px solid #efede8;border-bottom:1px solid #efede8}.inv-badge{font-size:9.5px;font-weight:700;padding:2.5px 8px;border-radius:999px;white-space:nowrap;flex:none}.inv-human{color:#1f8a5b;background:#1f8a5b1a}.inv-agent{color:#7b5bd6;background:#7b5bd61a}.inv-both{color:#b8862f;background:#c07a1f21}.inv-ai{opacity:.85}.rel-chips{display:flex;flex-wrap:wrap;gap:8px}.rel-chip{display:inline-flex;align-items:center;gap:7px;background:#fff;border:1px solid #ddd8ce;border-radius:999px;padding:7px 12px;cursor:pointer}.rel-chip:hover{background:#f4f1ea}.rel-chip .rt{font-size:9.5px;font-weight:700;text-transform:uppercase;color:#8a8579}.rel-chip .rn{font-family:ui-monospace,Menlo,monospace;font-size:12px;color:#1b1a17}.rel-chip.missing{border-style:dashed;cursor:default}.rel-chip.missing .rn{color:#a39d90}.rel-chip.missing .rm{font-size:9.5px;color:#b0836a}.md-wrap{display:flex;align-items:stretch}.left-col{width:340px;flex:none;border-right:1px solid #eeeae2;padding:8px 20px 28px 32px;position:sticky;top:0;max-height:100vh;overflow-y:auto;overscroll-behavior:contain}.ccard{display:flex;flex-direction:column;gap:4px;width:100%;background:#fff;border:1px solid #e8e4dc;border-radius:10px;padding:12px 14px;cursor:pointer;text-align:left;margin-bottom:8px}.ccard:hover{border-color:#b9b2a3}.ccard.sel{border-color:#1b1a17;box-shadow:0 1px 5px #1b1a171a}.ccard .r1{display:flex;align-items:center;gap:7px}.ccard .dot7{width:7px;height:7px;border-radius:50%;flex:none}.ccard .nm{font-size:13.5px;font-weight:600;color:#1b1a17;word-break:break-all}.ccard .ver{margin-left:auto;font-size:10px;color:#a39d90;font-family:ui-monospace,Menlo,monospace}.ccard .d1{font-size:11.5px;color:#8a8579;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pane{flex:1;min-width:0;max-width:980px;padding:20px 36px 36px 32px}.back{background:#fff;border:1px solid #ddd8ce;border-radius:999px;font-size:12px;color:#6b6558;padding:7px 14px;cursor:pointer}.back:hover{background:#f4f1ea}.meta-row{display:flex;align-items:center;gap:10px;margin-top:20px;position:relative;flex-wrap:wrap}.badge{font-size:10.5px;font-weight:700;padding:4px 11px;border-radius:999px}.m-ver{font-size:11px;color:#8a8579;font-family:ui-monospace,Menlo,monospace}.m-upd{margin-left:auto;font-size:11px;color:#8a8579}.pbtn{background:#fff;border:1px solid #ddd8ce;border-radius:999px;font-size:12px;color:#6b6558;padding:6px 13px;cursor:pointer}.pbtn:hover{background:#f4f1ea}.pbtn.danger{color:#b0392e;border-color:#e4ccc7}.pbtn.danger:hover{background:#fbf1ef}.d-name{font-size:26px;font-weight:700;color:#1b1a17;margin:14px 0 0;word-break:break-all}.tabs{display:flex;gap:20px;margin-top:18px;border-bottom:1px solid #eeeae2}.tab{background:none;border:0;font-size:13px;color:#8a8579;padding:8px 2px 10px;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px}.tab.on{color:#1b1a17;font-weight:600;border-bottom-color:#1b1a17}.sec-t{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.07em;color:#8a8579;margin:26px 0 10px}.full-desc{font-size:13.5px;line-height:1.8;color:#57534a;margin:0;white-space:pre-wrap}.ex-block{font-family:ui-monospace,Menlo,monospace;font-size:12px;background:#f4f1ea;border-radius:8px;padding:10px 13px;color:#57534a;margin-bottom:8px;overflow-x:auto;white-space:pre}.f-row{display:flex;align-items:center;gap:9px;border-bottom:1px solid #eeeae2;padding:8px 4px}.f-row .sq6{width:6px;height:6px;border-radius:2px;background:#c5bfb2;flex:none}.f-row .p{font-family:ui-monospace,Menlo,monospace;font-size:12px;color:#57534a;word-break:break-all}.fm-box{font-family:ui-monospace,Menlo,monospace;font-size:10.5px;color:#8a8579;background:#faf9f7;border:1px solid #e8e4dc;border-radius:8px;padding:12px 14px;white-space:pre-wrap;word-break:break-word;margin:18px 0}.md-body{font-size:12.5px;line-height:1.9;color:#3a372f}.md-body h1,.md-body h2{border-bottom:1px solid #eeeae2;padding-bottom:6px}.md-body h1{font-size:19px;margin:26px 0 10px;color:#1b1a17}.md-body h2{font-size:16px;margin:24px 0 10px;color:#1b1a17}.md-body h3{font-size:14px;margin:20px 0 8px;color:#1b1a17}.md-body h4{font-size:13px;margin:16px 0 6px;color:#1b1a17}.md-body p{margin:8px 0}.md-body ul,.md-body ol{margin:8px 0;padding-left:22px}.md-body li{margin:3px 0}.md-body pre{background:#f4f1ea;border-radius:8px;padding:12px 14px;overflow-x:auto;font-family:ui-monospace,Menlo,monospace;font-size:11.5px;line-height:1.6}.md-body code{font-family:ui-monospace,Menlo,monospace;font-size:.9em;background:#f4f1ea;color:#8a5a2b;border-radius:4px;padding:1px 5px}.md-body pre code{background:none;color:inherit;padding:0}.md-body blockquote{border-left:3px solid #ddd8ce;margin:8px 0;padding:2px 0 2px 14px;color:#6b6558}.md-body hr{border:0;border-top:1px solid #eeeae2;margin:18px 0}.md-body a{color:#2a6fdb}.drop{position:absolute;top:calc(100% + 6px);right:0;width:260px;background:#fff;border:1px solid #ddd8ce;border-radius:10px;box-shadow:0 10px 28px #1b1a1724;z-index:30;padding:8px 0;max-height:320px;overflow-y:auto}.drop .dh{font-size:10px;text-transform:uppercase;letter-spacing:.07em;color:#8a8579;padding:6px 14px 4px}.drop .di{display:block;width:100%;text-align:left;background:none;border:0;padding:8px 14px;cursor:pointer}.drop .di:hover{background:#f4f1ea}.drop .di .l1{font-size:12.5px;font-weight:600;color:#1b1a17}.drop .di .l2{font-size:10.5px;color:#a39d90;font-family:ui-monospace,Menlo,monospace;word-break:break-all}.overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#1b1a1759;z-index:40;display:flex;align-items:center;justify-content:center}.modal{width:390px;max-width:90vw;background:#fff;border-radius:14px;padding:26px 28px}.modal h3{font-size:15px;font-weight:700;margin:0 0 10px;color:#1b1a17}.modal p{font-size:12.5px;line-height:1.7;color:#57534a;margin:0 0 20px}.modal .btns{display:flex;justify-content:flex-end;gap:8px}.modal .del{background:#b0392e;border:1px solid #b0392e;color:#fff;border-radius:999px;font-size:12px;padding:7px 16px;cursor:pointer}.modal .del:hover{background:#962f27}@media(max-width:900px){.grid{grid-template-columns:repeat(2,minmax(0,1fr))}.left-col{display:none}}@media(min-width:1480px){.grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(min-width:1960px){.grid{grid-template-columns:repeat(5,minmax(0,1fr))}}
|
|
1
|
+
*{box-sizing:border-box}body{margin:0;background:#faf9f7;color:#1b1a17;font-family:Helvetica Neue,system-ui,-apple-system,Hiragino Sans,sans-serif}button{font-family:inherit}.mono{font-family:ui-monospace,SF Mono,Menlo,monospace}.wrap{margin:0 auto;min-height:100vh}.wrap.fixed{max-width:1200px}.wrap.fixed .grid{grid-template-columns:repeat(3,minmax(0,1fr))}@media(max-width:900px){.wrap.fixed .grid{grid-template-columns:repeat(2,minmax(0,1fr))}}.hd{padding:28px 32px 0}.hd .t-row{display:flex;align-items:baseline;gap:12px}.hd h1{font-size:22px;font-weight:700;letter-spacing:-.01em;margin:0}.hd h1 a{color:#1b1a17;text-decoration:none}.hd h1 a:hover{color:#57534a}.hd .sub{font-size:12px;color:#8a8579}.hd .count{margin-left:auto;font-size:12px;color:#8a8579}.hd input.q{margin-top:16px;width:100%;padding:11px 14px;border:1px solid #ddd8ce;border-radius:10px;font-size:13.5px;background:#fff;color:#1b1a17;outline:none;font-family:inherit}.controls{display:flex;gap:8px;margin-top:12px;flex-wrap:wrap;align-items:center}.chip{padding:6px 14px;border-radius:999px;font-size:12px;cursor:pointer;background:#fff;border:1px solid #ddd8ce;color:#6b6558}.chip.on{background:#1b1a17;border-color:#1b1a17;color:#fff}.grp-label{display:flex;align-items:center;gap:7px;font-size:12px;color:#57534a;cursor:pointer;margin-left:auto}.grp-label input{accent-color:#1b1a17;width:15px;height:15px;cursor:pointer}.ctrl-sep{width:1px;height:18px;background:#ddd8ce;margin:0 4px;align-self:center}.sel{-moz-appearance:none;appearance:none;-webkit-appearance:none;font-family:inherit;font-size:12px;color:#6b6558;background:#fff;border:1px solid #ddd8ce;border-radius:999px;padding:6px 30px 6px 14px;cursor:pointer;outline:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5'%3E%3Cpath d='M0 0l4 5 4-5z' fill='%238a8579'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 12px center}.sel:focus{border-color:#1b1a17}.seg{display:inline-flex;border:1px solid #ddd8ce;border-radius:999px;overflow:hidden;background:#fff}.seg button{border:0;background:transparent;font-family:inherit;font-size:12px;color:#6b6558;padding:6px 13px;cursor:pointer}.seg button+button{border-left:1px solid #efede8}.seg button.on{background:#1b1a17;color:#fff}.set-label{font-size:12px;font-weight:600;color:#57534a;margin-bottom:10px}.set-options{display:flex;flex-direction:column;gap:8px;margin-bottom:20px}.set-option{display:flex;align-items:center;gap:9px;font-size:13px;cursor:pointer}.set-option input{accent-color:#1b1a17}.set-scheme{font-family:ui-monospace,Menlo,monospace;font-size:10.5px;color:#a39d90;margin-left:auto}.set-input{font-family:ui-monospace,Menlo,monospace;font-size:12px;padding:8px 10px;border:1px solid #ddd8ce;border-radius:8px;outline:none;margin-left:24px}.set-input:focus{border-color:#1b1a17}.pbtn.primary{background:#1b1a17;border-color:#1b1a17;color:#fff}.pbtn.primary:hover{background:#3a372f;border-color:#3a372f}.sec-h{display:flex;align-items:center;gap:8px;margin:14px 0 6px;position:sticky;top:0;z-index:5;background:#faf9f7;padding:10px 0 8px}.sec-h .sq{width:8px;height:8px;border-radius:2px;flex:none}.sec-h .lbl{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#57534a}.sec-h .n{font-size:11px;color:#a39d90}.sec-h .ln{flex:1;height:1px;background:#e8e4dc}.sec-h.sm{margin:8px 0 4px;padding:8px 0 6px}.sec-h.sm .lbl{font-size:10.5px}.grid-pad{padding:6px 32px 32px}.grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}.card{background:#fff;border:1px solid #e8e4dc;border-radius:10px;padding:18px 20px;display:flex;flex-direction:column;gap:10px;cursor:pointer;text-align:left;min-width:0}.card:hover{border-color:#b9b2a3;box-shadow:0 2px 8px #1b1a170f}.card .top{display:flex;align-items:baseline;gap:8px}.card .nm{font-size:15px;font-weight:600;color:#1b1a17;word-break:break-all}.card .ver{margin-left:auto;font-size:10.5px;color:#a39d90;font-family:ui-monospace,Menlo,monospace;white-space:nowrap}.card .desc{font-size:13px;line-height:1.65;color:#57534a;margin:0;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.card .usage{font-family:ui-monospace,Menlo,monospace;font-size:11.5px;background:#f4f1ea;border-radius:6px;padding:8px 10px;color:#6b6558;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card .meta{display:flex;gap:8px;font-size:10.5px;color:#8a8579}.card .meta span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.card .meta .meta-r{margin-left:auto;flex:none;display:inline-flex;gap:8px;align-items:center}.scope-mini{display:inline-flex;align-items:center;gap:5px;font-size:10px;font-weight:600;letter-spacing:.04em;color:#8a8579;margin-bottom:-4px}.dot5{width:6px;height:6px;border-radius:2px;flex:none}.empty{text-align:center;padding:44px;font-size:13px;color:#8a8579}.ai-mark{color:#b0836a;font-size:11px}.kbadge{font-size:9.5px;font-weight:700;padding:2px 8px;border-radius:999px;background:#f1efe9;color:#8a8579;white-space:nowrap;flex:none;vertical-align:3px;margin-left:8px}.same-actions{margin-left:auto;display:inline-flex;gap:6px}.pbtn.sm{font-size:11px;padding:3px 10px}.pbtn.sm.on{background:#1b1a17;border-color:#1b1a17;color:#fff}.diff-wrap{margin-top:10px}.diff-legend{display:flex;gap:14px;align-items:center;font-size:11px;margin-bottom:6px}.d-del-mark{color:#b0392e}.d-add-mark{color:#1f8a5b}.d-count{color:#8a8579;margin-left:auto}.diff{border:1px solid #e8e4dc;border-radius:8px;overflow-x:auto;background:#fff;font-family:ui-monospace,Menlo,monospace;font-size:11px;line-height:1.7;max-height:480px;overflow-y:auto}.d-line{padding:0 12px;white-space:pre}.d-add{background:#1f8a5b14;color:#16603f}.d-del{background:#b0392e12;color:#8c2d24}.d-skip{padding:2px 12px;color:#a39d90;background:#faf9f7;text-align:center;border-top:1px solid #efede8;border-bottom:1px solid #efede8}.inv-badge{font-size:9.5px;font-weight:700;padding:2.5px 8px;border-radius:999px;white-space:nowrap;flex:none}.inv-human{color:#1f8a5b;background:#1f8a5b1a}.inv-agent{color:#7b5bd6;background:#7b5bd61a}.inv-both{color:#b8862f;background:#c07a1f21}.inv-ai{opacity:.85}.rel-chips{display:flex;flex-wrap:wrap;gap:8px}.rel-chip{display:inline-flex;align-items:center;gap:7px;background:#fff;border:1px solid #ddd8ce;border-radius:999px;padding:7px 12px;cursor:pointer}.rel-chip:hover{background:#f4f1ea}.rel-chip .rt{font-size:9.5px;font-weight:700;text-transform:uppercase;color:#8a8579}.rel-chip .rn{font-family:ui-monospace,Menlo,monospace;font-size:12px;color:#1b1a17}.rel-chip.missing{border-style:dashed;cursor:default}.rel-chip.missing .rn{color:#a39d90}.rel-chip.missing .rm{font-size:9.5px;color:#b0836a}.md-wrap{display:flex;align-items:stretch}.left-col{width:340px;flex:none;border-right:1px solid #eeeae2;padding:8px 20px 28px 32px;position:sticky;top:0;max-height:100vh;overflow-y:auto;overscroll-behavior:contain}.ccard{display:flex;flex-direction:column;gap:4px;width:100%;background:#fff;border:1px solid #e8e4dc;border-radius:10px;padding:12px 14px;cursor:pointer;text-align:left;margin-bottom:8px}.ccard:hover{border-color:#b9b2a3}.ccard.sel{border-color:#1b1a17;box-shadow:0 1px 5px #1b1a171a}.ccard .r1{display:flex;align-items:center;gap:7px}.ccard .dot7{width:7px;height:7px;border-radius:50%;flex:none}.ccard .nm{font-size:13.5px;font-weight:600;color:#1b1a17;word-break:break-all}.ccard .ver{margin-left:auto;font-size:10px;color:#a39d90;font-family:ui-monospace,Menlo,monospace}.ccard .d1{font-size:11.5px;color:#8a8579;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pane{flex:1;min-width:0;max-width:980px;padding:20px 36px 36px 32px}.back{background:#fff;border:1px solid #ddd8ce;border-radius:999px;font-size:12px;color:#6b6558;padding:7px 14px;cursor:pointer}.back:hover{background:#f4f1ea}.meta-row{display:flex;align-items:center;gap:10px;margin-top:20px;position:relative;flex-wrap:wrap}.badge{font-size:10.5px;font-weight:700;padding:4px 11px;border-radius:999px}.m-ver{font-size:11px;color:#8a8579;font-family:ui-monospace,Menlo,monospace}.m-upd{margin-left:auto;font-size:11px;color:#8a8579}.pbtn{background:#fff;border:1px solid #ddd8ce;border-radius:999px;font-size:12px;color:#6b6558;padding:6px 13px;cursor:pointer}.pbtn:hover{background:#f4f1ea}.pbtn.danger{color:#b0392e;border-color:#e4ccc7}.pbtn.danger:hover{background:#fbf1ef}.d-name{font-size:26px;font-weight:700;color:#1b1a17;margin:14px 0 0;word-break:break-all}.tabs{display:flex;gap:20px;margin-top:18px;border-bottom:1px solid #eeeae2}.tab{background:none;border:0;font-size:13px;color:#8a8579;padding:8px 2px 10px;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px}.tab.on{color:#1b1a17;font-weight:600;border-bottom-color:#1b1a17}.sec-t{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.07em;color:#8a8579;margin:26px 0 10px}.full-desc{font-size:13.5px;line-height:1.8;color:#57534a;margin:0;white-space:pre-wrap}.ex-block{font-family:ui-monospace,Menlo,monospace;font-size:12px;background:#f4f1ea;border-radius:8px;padding:10px 13px;color:#57534a;margin-bottom:8px;overflow-x:auto;white-space:pre}.f-row{display:flex;align-items:center;gap:9px;border-bottom:1px solid #eeeae2;padding:8px 4px}.f-row .sq6{width:6px;height:6px;border-radius:2px;background:#c5bfb2;flex:none}.f-row .p{font-family:ui-monospace,Menlo,monospace;font-size:12px;color:#57534a;word-break:break-all}.fm-box{font-family:ui-monospace,Menlo,monospace;font-size:10.5px;color:#8a8579;background:#faf9f7;border:1px solid #e8e4dc;border-radius:8px;padding:12px 14px;white-space:pre-wrap;word-break:break-word;margin:18px 0}.md-body{font-size:12.5px;line-height:1.9;color:#3a372f}.md-body h1,.md-body h2{border-bottom:1px solid #eeeae2;padding-bottom:6px}.md-body h1{font-size:19px;margin:26px 0 10px;color:#1b1a17}.md-body h2{font-size:16px;margin:24px 0 10px;color:#1b1a17}.md-body h3{font-size:14px;margin:20px 0 8px;color:#1b1a17}.md-body h4{font-size:13px;margin:16px 0 6px;color:#1b1a17}.md-body p{margin:8px 0}.md-body ul,.md-body ol{margin:8px 0;padding-left:22px}.md-body li{margin:3px 0}.md-body pre{background:#f4f1ea;border-radius:8px;padding:12px 14px;overflow-x:auto;font-family:ui-monospace,Menlo,monospace;font-size:11.5px;line-height:1.6}.md-body code{font-family:ui-monospace,Menlo,monospace;font-size:.9em;background:#f4f1ea;color:#8a5a2b;border-radius:4px;padding:1px 5px}.md-body pre code{background:none;color:inherit;padding:0}.md-body blockquote{border-left:3px solid #ddd8ce;margin:8px 0;padding:2px 0 2px 14px;color:#6b6558}.md-body hr{border:0;border-top:1px solid #eeeae2;margin:18px 0}.md-body a{color:#2a6fdb}.drop{position:absolute;top:calc(100% + 6px);right:0;width:260px;background:#fff;border:1px solid #ddd8ce;border-radius:10px;box-shadow:0 10px 28px #1b1a1724;z-index:30;padding:8px 0;max-height:320px;overflow-y:auto}.drop .dh{font-size:10px;text-transform:uppercase;letter-spacing:.07em;color:#8a8579;padding:6px 14px 4px}.drop .di{display:block;width:100%;text-align:left;background:none;border:0;padding:8px 14px;cursor:pointer}.drop .di:hover{background:#f4f1ea}.drop .di .l1{font-size:12.5px;font-weight:600;color:#1b1a17}.drop .di .l2{font-size:10.5px;color:#a39d90;font-family:ui-monospace,Menlo,monospace;word-break:break-all}.overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#1b1a1759;z-index:40;display:flex;align-items:center;justify-content:center}.modal{width:390px;max-width:90vw;background:#fff;border-radius:14px;padding:26px 28px}.modal h3{font-size:15px;font-weight:700;margin:0 0 10px;color:#1b1a17}.modal p{font-size:12.5px;line-height:1.7;color:#57534a;margin:0 0 20px}.modal .btns{display:flex;justify-content:flex-end;gap:8px}.modal .del{background:#b0392e;border:1px solid #b0392e;color:#fff;border-radius:999px;font-size:12px;padding:7px 16px;cursor:pointer}.modal .del:hover{background:#962f27}@media(max-width:900px){.grid{grid-template-columns:repeat(2,minmax(0,1fr))}.left-col{display:none}}@media(min-width:1480px){.grid{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(min-width:1960px){.grid{grid-template-columns:repeat(5,minmax(0,1fr))}}.unused-badge{font-size:9.5px;font-weight:700;padding:2.5px 8px;border-radius:999px;white-space:nowrap;flex:none;color:#5b7285;background:#5b72851f}.warn-badge{font-size:9.5px;font-weight:700;padding:2.5px 8px;border-radius:999px;white-space:nowrap;flex:none;color:#b0432f;background:#c848301a;position:relative;cursor:help}.card .top .top-r{margin-left:auto;display:inline-flex;align-items:center;gap:8px;flex:none}.card .top .top-r .ver{margin-left:0}.card .chips{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:-4px}.card .chips .kbadge{margin-left:0;vertical-align:0}.warn-badge .tip{display:none;position:absolute;top:calc(100% + 6px);right:-6px;z-index:40;width:250px;padding:10px 12px;border-radius:8px;background:#1b1a17;color:#f4f1ea;font-size:11.5px;font-weight:400;line-height:1.6;white-space:normal;text-align:left;box-shadow:0 4px 14px #1b1a1740}.warn-badge:hover .tip{display:block}.warn-badge .tip .tip-t{display:block;font-weight:700;margin-bottom:4px}.warn-badge .tip .tip-line{display:block;margin-top:3px;padding-left:12px;text-indent:-12px}.warn-badge .tip .tip-line:before{content:"· "}.card .meta .tok,.tok-total{font-family:ui-monospace,Menlo,monospace;white-space:nowrap}.hd .tok-total{cursor:help}.sec-h .sec-tok{font-size:11px;color:#a39d90;font-family:ui-monospace,Menlo,monospace;white-space:nowrap;cursor:help}.lint-row{display:flex;gap:8px;align-items:baseline;font-size:13px;line-height:1.65;color:#57534a;padding:2px 0}.lint-row .lint-mark{color:#b0432f;flex:none}
|