tokenbill-mcp 1.1.7 → 1.1.9
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/package.json +1 -1
- package/uploader/toolbox.js +17 -9
- package/uploader/viewer.js +26 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenbill-mcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.9",
|
|
4
4
|
"description": "내 AI 작업실 — AI 쓰는 만큼 쌓이는 기록으로 tokenbill.my 리더보드, 로컬 대화 뷰어, 작업 보드·스프린트를. Your AI workshop: a leaderboard, a local transcript viewer and a task board built from your AI work logs.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"tokenbill-mcp": "uploader/index.js"
|
package/uploader/toolbox.js
CHANGED
|
@@ -81,17 +81,19 @@ function claudeProjects() {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
// ── 스킬 모으기 ─────────────────────────────────────────────
|
|
84
|
+
// Claude Code가 공식적으로 스킬을 읽는 자리만 본다.
|
|
85
|
+
// 개인: ~/.claude/skills/<이름>/SKILL.md — 어느 폴더에서 열든 읽힘
|
|
86
|
+
// 프로젝트: <저장소>/.claude/skills/<이름>/SKILL.md — 그 폴더에서 열 때만 읽힘
|
|
87
|
+
// 플러그인: ~/.claude/plugins/…/skills/<이름>/SKILL.md
|
|
88
|
+
// 다른 도구의 스킬 폴더나 내려받은 폴더는 Claude Code가 읽지 않으므로 목록에 넣지 않는다.
|
|
84
89
|
function scanSkills() {
|
|
85
90
|
const roots = [
|
|
86
|
-
{ root: SKILLS_HOME, depth: 2, source: "
|
|
87
|
-
{ root: path.join(HOME, ".claude", "plugins"), depth:
|
|
88
|
-
{ root: path.join(HOME, ".bxca", "assistant-skills"), depth: 3, source: "bxca", label: "회사 어시스턴트" },
|
|
89
|
-
{ root: path.join(HOME, ".bxcabackup", "assistant-skills"), depth: 3, source: "bxca", label: "회사 어시스턴트 (백업)" },
|
|
90
|
-
{ root: path.join(HOME, "Downloads"), depth: 6, source: "folder", label: "내려받은 폴더" },
|
|
91
|
-
{ root: path.join(HOME, "Documents"), depth: 5, source: "folder", label: "문서 폴더" },
|
|
91
|
+
{ root: SKILLS_HOME, depth: 2, source: "personal", label: "개인 — 어디서 열든" },
|
|
92
|
+
{ root: path.join(HOME, ".claude", "plugins"), depth: 6, source: "plugin", label: "플러그인" },
|
|
92
93
|
];
|
|
93
94
|
for (const p of claudeProjects()) {
|
|
94
|
-
roots.push({ root: path.join(p, ".claude", "skills"), depth: 2, source: "project",
|
|
95
|
+
roots.push({ root: path.join(p, ".claude", "skills"), depth: 2, source: "project",
|
|
96
|
+
label: `프로젝트 — ${path.basename(p)}에서 열 때만`, project: short(p) });
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
const found = [], seenDir = new Set();
|
|
@@ -102,8 +104,10 @@ function scanSkills() {
|
|
|
102
104
|
seenDir.add(dir);
|
|
103
105
|
const meta = readSkillMeta(file);
|
|
104
106
|
if (!meta) continue;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
// 플러그인 폴더에서는 skills/ 아래 것만 스킬이다 (다른 SKILL.md 사본 제외)
|
|
108
|
+
if (r.source === "plugin" && path.basename(path.dirname(dir)) !== "skills") continue;
|
|
109
|
+
found.push({ ...meta, source: r.source, origin: r.label, dir_short: short(dir), project: r.project || "",
|
|
110
|
+
installed: r.source === "personal" });
|
|
107
111
|
}
|
|
108
112
|
}
|
|
109
113
|
// 같은 스킬이 여러 자리에 있으면 하나로 묶는다 (이름 기준, 사본 목록을 함께)
|
|
@@ -218,6 +222,10 @@ function copyDir(from, to) {
|
|
|
218
222
|
function installSkill(dir, { overwrite = false } = {}) {
|
|
219
223
|
const src = path.resolve(String(dir || ""));
|
|
220
224
|
if (!fs.existsSync(path.join(src, "SKILL.md"))) throw new Error("SKILL.md가 없는 폴더입니다");
|
|
225
|
+
// Claude Code 공식 자리(프로젝트의 .claude/skills)에 있는 것만 개인 폴더로 옮긴다
|
|
226
|
+
const official = claudeProjects().some((p) =>
|
|
227
|
+
src.toLowerCase().startsWith(path.resolve(p, ".claude", "skills").toLowerCase() + path.sep));
|
|
228
|
+
if (!official) throw new Error("Claude Code 스킬 폴더(.claude/skills)에 있는 스킬만 옮길 수 있습니다");
|
|
221
229
|
const meta = readSkillMeta(path.join(src, "SKILL.md"));
|
|
222
230
|
const dest = destFor(meta.name);
|
|
223
231
|
const exists = fs.existsSync(dest);
|
package/uploader/viewer.js
CHANGED
|
@@ -537,20 +537,33 @@ function renderSkills(){
|
|
|
537
537
|
box.textContent = "";
|
|
538
538
|
var head = el("div","tb-head");
|
|
539
539
|
head.appendChild(el("h2","","스킬 " + tb.skills.length + "개"));
|
|
540
|
-
head.appendChild(el("span","note2","
|
|
540
|
+
head.appendChild(el("span","note2","Claude Code가 공식적으로 읽는 자리만 보여줍니다 — 개인(" + tb.installed_dir + ") · 프로젝트(.claude/skills) · 플러그인"));
|
|
541
541
|
box.appendChild(head);
|
|
542
|
+
if (!tb.skills.length) {
|
|
543
|
+
box.appendChild(el("p","empty","아직 Claude Code 스킬이 없어요. " + tb.installed_dir + "/<이름>/SKILL.md 로 두면 어느 폴더에서든 읽힙니다."));
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
// 개인 → 프로젝트 → 플러그인 순서
|
|
547
|
+
var ORDER = { personal: 0, project: 1, plugin: 2 };
|
|
542
548
|
var groups = {};
|
|
543
|
-
tb.skills.forEach(function(s){ (groups[s.
|
|
544
|
-
Object.keys(groups).sort(function(a,b){
|
|
549
|
+
tb.skills.forEach(function(s){ (groups[s.origin] = groups[s.origin] || []).push(s); });
|
|
550
|
+
Object.keys(groups).sort(function(a,b){
|
|
551
|
+
return (ORDER[groups[a][0].source] - ORDER[groups[b][0].source]) || a.localeCompare(b,"ko");
|
|
552
|
+
})
|
|
545
553
|
.forEach(function(g){
|
|
546
554
|
box.appendChild(el("div","tb-group", g + " · " + groups[g].length));
|
|
547
555
|
groups[g].forEach(function(s){
|
|
548
556
|
var nm = el("div","nm");
|
|
549
557
|
nm.appendChild(document.createTextNode(s.name));
|
|
550
|
-
if (s.
|
|
558
|
+
if (s.source === "personal") nm.appendChild(badge("어디서나 사용","ok"));
|
|
559
|
+
if (s.source === "project") nm.appendChild(badge("이 폴더에서만"));
|
|
560
|
+
if (s.source === "plugin") nm.appendChild(badge("플러그인"));
|
|
551
561
|
if (s.copies.length) nm.appendChild(badge("사본 " + s.copies.length));
|
|
552
562
|
if (s.files.length) nm.appendChild(badge(s.files.join(", ").slice(0,40)));
|
|
553
|
-
|
|
563
|
+
// 플러그인 스킬은 플러그인이 관리한다 — 여기서 건드리지 않는다
|
|
564
|
+
if (s.source === "plugin") { box.appendChild(card(nm, s.description, s.dir_short, [])); return; }
|
|
565
|
+
var btn = el("button","btn2" + (s.installed ? "" : " primary"), s.installed ? "제거" : "어디서나 쓰기");
|
|
566
|
+
btn.title = s.installed ? "개인 스킬 폴더에서 지웁니다" : "개인 스킬 폴더(" + tb.installed_dir + ")로 복사해 어느 폴더에서든 쓰게 합니다";
|
|
554
567
|
btn.addEventListener("click", function(){
|
|
555
568
|
if (s.installed) {
|
|
556
569
|
if (!confirm(s.name + " 스킬을 " + tb.installed_dir + " 에서 지울까요?")) return;
|
|
@@ -628,7 +641,14 @@ document.querySelectorAll(".vtabs button").forEach(function(b){
|
|
|
628
641
|
b.addEventListener("click", function(){ setTab(b.dataset.v); });
|
|
629
642
|
});
|
|
630
643
|
loadList();
|
|
631
|
-
|
|
644
|
+
// 주소의 #skill·#mcp로 바로 들어올 수 있다 (포탈 도구 메뉴가 이 주소를 쓴다)
|
|
645
|
+
var fromHash = (location.hash || "").replace("#", "");
|
|
646
|
+
if (fromHash === "skill" || fromHash === "mcp" || fromHash === "chat") setTab(fromHash);
|
|
647
|
+
else { try { var saved = localStorage.getItem("tb-viewer-tab"); if (saved && saved !== "chat") setTab(saved); } catch (e) {} }
|
|
648
|
+
window.addEventListener("hashchange", function () {
|
|
649
|
+
var v = (location.hash || "").replace("#", "");
|
|
650
|
+
if (v === "skill" || v === "mcp" || v === "chat") setTab(v);
|
|
651
|
+
});
|
|
632
652
|
})();
|
|
633
653
|
</script></body></html>`;
|
|
634
654
|
|