@cliphijack/santaclaude 0.9.3 → 0.9.5

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/santaclaude.js +27 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cliphijack/santaclaude",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "publishConfig": { "access": "public" },
5
5
  "description": "SantaClaude 커넥터 — 클라우드 예약을 내 로컬 Claude(tmux)에 발사",
6
6
  "bin": { "santaclaude": "./santaclaude.js" },
package/santaclaude.js CHANGED
@@ -114,6 +114,33 @@ async function run(conf) {
114
114
  // 루돌프 = 작업장(session) 안의 윈도우(탭). 별도 세션 아님.
115
115
  async function runControl(cmd) {
116
116
  try {
117
+ if (cmd.action === 'skill') {
118
+ // 마켓 스킬 이식 — 그 루돌프 프로젝트 폴더 .claude/skills/<name>/ 에 설치 + claude에 적용 주입
119
+ const w = (cmd.name && windowExists(session, cmd.name)) ? (session + ':' + cmd.name) : pane;
120
+ let r; try { r = await fetch(api + '/api/skill/get?token=' + encodeURIComponent(token) + '&id=' + encodeURIComponent(cmd.skillId || '')); } catch (e) { console.warn(' 스킬 받기 실패'); return; }
121
+ if (!r.ok) { console.warn(' 스킬 없음'); return; }
122
+ const sk = await r.json();
123
+ // 그 창의 작업 폴더 (없으면 홈)
124
+ let cwd = os.homedir();
125
+ try { const pc = execFileSync('tmux', ['display-message', '-t', w, '-p', '#{pane_current_path}'], { encoding: 'utf8' }).trim(); if (pc) cwd = pc; } catch (e) {}
126
+ // 폴더 네임스페이스 — santa-{퍼블리셔}-{스킬}: 충돌 방지 + 마켓 출처 식별. SKILL.md 파일명은 표준 고정
127
+ const pub = String(sk.publisher || 'me').replace(/[^a-zA-Z0-9-]/g, '').toLowerCase() || 'me';
128
+ const folder = ('santa-' + pub + '-' + sk.name).replace(/[^a-zA-Z0-9._-]/g, '-').slice(0, 80);
129
+ const sdir = path.join(cwd, '.claude', 'skills', folder);
130
+ let cnt = 0;
131
+ for (const f of (sk.files || [])) {
132
+ const safe = String(f.path || '').replace(/\\/g, '/').replace(/(^|\/)\.\.(\/|$)/g, '/').replace(/^\/+/, '');
133
+ if (!safe) continue;
134
+ const fp = path.join(sdir, safe);
135
+ if (!fp.startsWith(sdir)) continue; // 경로 탈출 방지
136
+ try { fs.mkdirSync(path.dirname(fp), { recursive: true }); fs.writeFileSync(fp, String(f.content || '')); cnt++; } catch (e) {}
137
+ }
138
+ console.log(` 🧩 스킬 "${sk.name}" ${cnt}개 파일 이식 → ${sdir}`);
139
+ const msg = (sk.prompt && sk.prompt.trim() ? sk.prompt.trim() + ' ' : '') + '(스킬 "' + sk.name + '"이 .claude/skills/' + folder + '/ 에 이식됨 — SKILL.md 읽고 적용)';
140
+ execFileSync('tmux', ['send-keys', '-t', w, '-l', msg]);
141
+ setTimeout(() => { try { execFileSync('tmux', ['send-keys', '-t', w, 'Enter']); } catch (e) {} }, 300);
142
+ return;
143
+ }
117
144
  if (cmd.action === 'image') {
118
145
  // 웹이 올린 이미지를 받아 로컬에 저장하고 claude에 경로 주입
119
146
  const dir = path.join(os.homedir(), '.santaclaude', 'img');