@cliphijack/santaclaude 0.9.5 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/santaclaude.js +42 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cliphijack/santaclaude",
3
- "version": "0.9.5",
3
+ "version": "0.9.6",
4
4
  "publishConfig": { "access": "public" },
5
5
  "description": "SantaClaude 커넥터 — 클라우드 예약을 내 로컬 Claude(tmux)에 발사",
6
6
  "bin": { "santaclaude": "./santaclaude.js" },
package/santaclaude.js CHANGED
@@ -186,13 +186,55 @@ async function run(conf) {
186
186
  execFileSync('tmux', ['send-keys', '-t', w, '-l', String(cmd.text || '')]);
187
187
  setTimeout(() => { try { execFileSync('tmux', ['send-keys', '-t', w, 'Enter']); } catch (e) {} }, 300);
188
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}`);
189
194
  }
190
195
  } catch (e) { console.warn(` ⚠️ 제어 실패(${cmd.action} ${cmd.name}): ${e.message}`); }
191
196
  }
192
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
+
193
234
  async function tick() {
194
235
  try {
195
236
  post(api, '/api/heartbeat', { token, pane, sessions: listWindows(session), screens: captureAll(session) }).catch(() => {}); // 보고 = 윈도우 목록 + 각 탭 화면 미러
237
+ scanPublish();
196
238
  post(api, '/api/control/claim', { token }).then((c) => { for (const cmd of (c && c.commands) || []) runControl(cmd); }).catch(() => {});
197
239
  const d = await post(api, '/api/jobs/claim', { token });
198
240
  for (const j of (d.jobs || [])) {