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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokenbill-mcp",
3
- "version": "1.1.7",
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"
@@ -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: "installed", label: "설치됨 (Claude Code)" },
87
- { root: path.join(HOME, ".claude", "plugins"), depth: 5, source: "plugin", label: "플러그인" },
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", label: `프로젝트 — ${path.basename(p)}` });
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
- found.push({ ...meta, source: r.source, origin: r.label, dir_short: short(dir),
106
- installed: dir.toLowerCase().startsWith(SKILLS_HOME.toLowerCase()) });
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);
@@ -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","설치 자리: " + tb.installed_dir + " — 여기에 있어야 Claude Code가 읽습니다"));
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.installed ? "설치됨" : s.origin] = groups[s.installed ? "설치됨" : s.origin] || []).push(s); });
544
- Object.keys(groups).sort(function(a,b){ return a === "설치됨" ? -1 : b === "설치됨" ? 1 : a.localeCompare(b,"ko"); })
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.installed) nm.appendChild(badge("설치됨","ok"));
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
- var btn = el("button","btn2" + (s.installed ? "" : " primary"), s.installed ? "제거" : "설치");
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
- try { var saved = localStorage.getItem("tb-viewer-tab"); if (saved && saved !== "chat") setTab(saved); } catch (e) {}
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