@cliphijack/santaclaude 0.9.4 → 0.9.6
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/santaclaude.js +47 -2
package/package.json
CHANGED
package/santaclaude.js
CHANGED
|
@@ -123,7 +123,10 @@ async function run(conf) {
|
|
|
123
123
|
// 그 창의 작업 폴더 (없으면 홈)
|
|
124
124
|
let cwd = os.homedir();
|
|
125
125
|
try { const pc = execFileSync('tmux', ['display-message', '-t', w, '-p', '#{pane_current_path}'], { encoding: 'utf8' }).trim(); if (pc) cwd = pc; } catch (e) {}
|
|
126
|
-
|
|
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);
|
|
127
130
|
let cnt = 0;
|
|
128
131
|
for (const f of (sk.files || [])) {
|
|
129
132
|
const safe = String(f.path || '').replace(/\\/g, '/').replace(/(^|\/)\.\.(\/|$)/g, '/').replace(/^\/+/, '');
|
|
@@ -133,7 +136,7 @@ async function run(conf) {
|
|
|
133
136
|
try { fs.mkdirSync(path.dirname(fp), { recursive: true }); fs.writeFileSync(fp, String(f.content || '')); cnt++; } catch (e) {}
|
|
134
137
|
}
|
|
135
138
|
console.log(` 🧩 스킬 "${sk.name}" ${cnt}개 파일 이식 → ${sdir}`);
|
|
136
|
-
const msg = sk.prompt && sk.prompt.trim() ? sk.prompt.trim() : (
|
|
139
|
+
const msg = (sk.prompt && sk.prompt.trim() ? sk.prompt.trim() + ' ' : '') + '(스킬 "' + sk.name + '"이 .claude/skills/' + folder + '/ 에 이식됨 — SKILL.md 읽고 적용)';
|
|
137
140
|
execFileSync('tmux', ['send-keys', '-t', w, '-l', msg]);
|
|
138
141
|
setTimeout(() => { try { execFileSync('tmux', ['send-keys', '-t', w, 'Enter']); } catch (e) {} }, 300);
|
|
139
142
|
return;
|
|
@@ -183,13 +186,55 @@ async function run(conf) {
|
|
|
183
186
|
execFileSync('tmux', ['send-keys', '-t', w, '-l', String(cmd.text || '')]);
|
|
184
187
|
setTimeout(() => { try { execFileSync('tmux', ['send-keys', '-t', w, 'Enter']); } catch (e) {} }, 300);
|
|
185
188
|
console.log(` ⌨️ 입력 → ${w}: ${String(cmd.text || '').slice(0, 50)}`);
|
|
189
|
+
} else if (cmd.action === 'key') {
|
|
190
|
+
// 플로팅 컨트롤 — 특수키 그대로 전달 (Escape, C-c, Up 등)
|
|
191
|
+
const w = (cmd.name && windowExists(session, cmd.name)) ? (session + ':' + cmd.name) : pane;
|
|
192
|
+
execFileSync('tmux', ['send-keys', '-t', w, String(cmd.key || '')]);
|
|
193
|
+
console.log(` ⌨️ 키 → ${w}: ${cmd.key}`);
|
|
186
194
|
}
|
|
187
195
|
} catch (e) { console.warn(` ⚠️ 제어 실패(${cmd.action} ${cmd.name}): ${e.message}`); }
|
|
188
196
|
}
|
|
189
197
|
|
|
198
|
+
// 스킬 클린등록 수거 — claude가 ~/.santaclaude/publish/<name>/ + READY 만들면 마켓에 자동 등록
|
|
199
|
+
const PUBDIR = path.join(os.homedir(), '.santaclaude', 'publish');
|
|
200
|
+
function readDirFiles(dir, base) {
|
|
201
|
+
let out = [];
|
|
202
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
203
|
+
if (e.name === 'READY') continue;
|
|
204
|
+
const fp = path.join(dir, e.name);
|
|
205
|
+
if (e.isDirectory()) out = out.concat(readDirFiles(fp, base));
|
|
206
|
+
else { try { out.push({ path: path.relative(base, fp).replace(/\\/g, '/'), content: fs.readFileSync(fp, 'utf8') }); } catch (e) {} }
|
|
207
|
+
}
|
|
208
|
+
return out;
|
|
209
|
+
}
|
|
210
|
+
async function scanPublish() {
|
|
211
|
+
try {
|
|
212
|
+
if (!fs.existsSync(PUBDIR)) return;
|
|
213
|
+
for (const name of fs.readdirSync(PUBDIR)) {
|
|
214
|
+
const d = path.join(PUBDIR, name);
|
|
215
|
+
try { if (!fs.statSync(d).isDirectory()) continue; } catch (e) { continue; }
|
|
216
|
+
if (!fs.existsSync(path.join(d, 'READY'))) continue; // claude가 다 끝냈다는 신호
|
|
217
|
+
const files = readDirFiles(d, d);
|
|
218
|
+
if (!files.length) { try { fs.rmSync(d, { recursive: true, force: true }); } catch (e) {} continue; }
|
|
219
|
+
const publisher = conf.publisher || (String(token).replace(/^sc_/, '').slice(0, 8)) || 'me';
|
|
220
|
+
try {
|
|
221
|
+
const r = await fetch(api + '/api/skill', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token, name, publisher, files, public: true }) });
|
|
222
|
+
const j = await r.json();
|
|
223
|
+
if (j.ok) {
|
|
224
|
+
console.log(` 🏪 스킬 "${name}" 마켓 클린등록 완료`);
|
|
225
|
+
try { fs.rmSync(d, { recursive: true, force: true }); } catch (e) {}
|
|
226
|
+
execFileSync('tmux', ['send-keys', '-t', pane, '-l', '🏪 "' + name + '" 마켓에 클린 등록됐어!']);
|
|
227
|
+
setTimeout(() => { try { execFileSync('tmux', ['send-keys', '-t', pane, 'Enter']); } catch (e) {} }, 300);
|
|
228
|
+
} else { console.warn(` 스킬 등록 실패(${name}): ${j.error}`); try { fs.unlinkSync(path.join(d, 'READY')); } catch (e) {} }
|
|
229
|
+
} catch (e) {}
|
|
230
|
+
}
|
|
231
|
+
} catch (e) {}
|
|
232
|
+
}
|
|
233
|
+
|
|
190
234
|
async function tick() {
|
|
191
235
|
try {
|
|
192
236
|
post(api, '/api/heartbeat', { token, pane, sessions: listWindows(session), screens: captureAll(session) }).catch(() => {}); // 보고 = 윈도우 목록 + 각 탭 화면 미러
|
|
237
|
+
scanPublish();
|
|
193
238
|
post(api, '/api/control/claim', { token }).then((c) => { for (const cmd of (c && c.commands) || []) runControl(cmd); }).catch(() => {});
|
|
194
239
|
const d = await post(api, '/api/jobs/claim', { token });
|
|
195
240
|
for (const j of (d.jobs || [])) {
|