@cliphijack/santaclaude 1.0.6 → 1.0.8

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 +39 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cliphijack/santaclaude",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "publishConfig": { "access": "public" },
5
5
  "description": "SantaClaude 커넥터 — 클라우드 예약을 내 로컬 Claude(tmux)에 발사",
6
6
  "bin": { "santaclaude": "./santaclaude.js" },
package/santaclaude.js CHANGED
@@ -152,6 +152,34 @@ description: 산타클로드 파일 보내기 — 만든·가진 파일(이미
152
152
  - **원본 그대로 복사** — 재인코딩·리사이즈·압축 금지.
153
153
  - 옮기지 말고 **복사**(cp). 원본 작업물은 그대로 둔다.
154
154
  - 보낸 뒤 사용자에게 "보냈어 — <파일명>" 한 줄 확인.
155
+ `
156
+ },
157
+ 'santa-brain': {
158
+ 'SKILL.md': `---
159
+ name: santa-brain
160
+ description: 산타 Gbrain — 내 노트·문서·지식 폴더에서 답을 찾을 때. "내 노트에서 ~ 찾아줘", "지난번에 정한 ~ 뭐였지", "~ 관련 문서 정리해줘" 등 개인 지식베이스 질의에 사용.
161
+ ---
162
+
163
+ # 산타 Gbrain — 내 지식에서 답 찾기
164
+
165
+ 사용자가 자기 노트·문서·메모에서 무언가를 찾거나 종합해 달라고 하면, **임베딩·벡터DB 없이 직접 파일을 뒤져 출처와 함께 답한다.** 이게 산타클로드 방식 — 네가 두뇌다(외부 비용 0).
166
+
167
+ ## 지식 폴더(brain)
168
+ - 사용자가 폴더를 지정했으면 거기서. 안 했으면 **물어본다**("어느 폴더에서 찾을까? 예: \`~/notes\`").
169
+ - \`~/santa-brain/\` 가 있으면 기본 후보로 써도 된다. 지정 폴더는 기억해 다음에 재사용.
170
+
171
+ ## 절차 (agentic retrieval)
172
+ 1. **검색** — 질문 키워드로 \`grep -ri\`, glob(\`*.md\` 등)으로 후보 파일 추림. 동의어·관련어도 시도.
173
+ 2. **읽기** — 후보 파일 read. 링크(\`[[...]]\`·상대경로) 있으면 따라가 맥락 보강.
174
+ 3. **답** — 근거로 답 작성, **핵심 주장마다 끝에 출처 \`(파일:라인)\` 표기.**
175
+ 4. **gap** — 폴더에 근거 없으면 "근거 없음"이라 말하고 무엇이 더 필요한지 알려준다. **폴더 밖 추측으로 지어내지 말 것.**
176
+ 5. 끝에 **한 줄 요약.**
177
+
178
+ ## 규칙
179
+ - 출처 없는 단정 금지 — 찾은 것만, 출처 달아서.
180
+ - 큰 폴더면 grep으로 먼저 좁히고 **필요한 파일만 read**(토큰 절약).
181
+ - 정리·요약 요청이면 여러 파일 read 후 종합 + 끝에 출처 목록.
182
+ - 노트는 사용자 로컬에만 있고 우리 서버를 거치지 않는다(프라이버시).
155
183
  `
156
184
  }
157
185
  };
@@ -279,9 +307,13 @@ async function run(conf) {
279
307
  console.log(` ⌨️ 키 → ${w}: ${cmd.key}`);
280
308
  } else if (cmd.action === 'resize') {
281
309
  // 웹 뷰어 폭에 맞춰 tmux 윈도우 리사이즈 — claude TUI가 그 폭으로 reflow(모바일 가로스크롤 해소)
310
+ // 보통 cols만 옴 = 폭만 맞추고 높이는 그대로(스크롤백으로 세로 내역 풍부하게). rows 오면 같이 적용.
282
311
  const w = (cmd.name && windowExists(session, cmd.name)) ? (session + ':' + cmd.name) : pane;
283
- const cols = Math.max(20, Math.min(400, parseInt(cmd.cols) || 0)), rows = Math.max(10, Math.min(200, parseInt(cmd.rows) || 0));
284
- if (cols && rows) { try { execFileSync('tmux', ['resize-window', '-t', w, '-x', String(cols), '-y', String(rows)]); console.log(` 📐 리사이즈 ${w} → ${cols}x${rows}`); } catch (e) {} }
312
+ const c = parseInt(cmd.cols) || 0, rr = parseInt(cmd.rows) || 0;
313
+ const args = ['resize-window', '-t', w];
314
+ if (c > 0) args.push('-x', String(Math.max(20, Math.min(400, c))));
315
+ if (rr > 0) args.push('-y', String(Math.max(10, Math.min(200, rr))));
316
+ if (args.length > 3) { try { execFileSync('tmux', args); console.log(` 📐 리사이즈 ${w} → 폭${c || '그대로'}${rr ? '×높이' + rr : ''}`); } catch (e) {} }
285
317
  }
286
318
  } catch (e) { console.warn(` ⚠️ 제어 실패(${cmd.action} ${cmd.name}): ${e.message}`); }
287
319
  }
@@ -312,10 +344,8 @@ async function run(conf) {
312
344
  const r = await fetch(api + '/api/skill', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token, name, publisher, files, public: true }) });
313
345
  const j = await r.json();
314
346
  if (j.ok) {
315
- console.log(` 🏪 스킬 "${name}" 마켓 클린등록 완료`);
347
+ console.log(` 🏪 스킬 "${name}" 마켓 클린등록 완료`); // 확인은 웹 마켓에 — claude 입력칸 주입 금지(사용자 메시지로 오인됨)
316
348
  try { fs.rmSync(d, { recursive: true, force: true }); } catch (e) {}
317
- execFileSync('tmux', ['send-keys', '-t', pane, '-l', '🏪 "' + name + '" 마켓에 클린 등록됐어!']);
318
- setTimeout(() => { try { execFileSync('tmux', ['send-keys', '-t', pane, 'Enter']); } catch (e) {} }, 300);
319
349
  } else { console.warn(` 스킬 등록 실패(${name}): ${j.error}`); try { fs.unlinkSync(path.join(d, 'READY')); } catch (e) {} }
320
350
  } catch (e) { console.error('[scan fetch err]', e.message); }
321
351
  }
@@ -335,10 +365,8 @@ async function run(conf) {
335
365
  try {
336
366
  const j = await post(api, '/api/schedule', Object.assign({}, req, { token }));
337
367
  if (j.ok) {
338
- console.log(` 🎄 세션 예약 등록 — ${req.kind || 'once'} · ${String(req.message).slice(0, 40)}`);
368
+ console.log(` 🎄 세션 예약 등록 — ${req.kind || 'once'} · ${String(req.message).slice(0, 40)}`); // 확인은 웹 예약내역에 — claude 주입 금지
339
369
  try { fs.unlinkSync(fp); } catch (e) {}
340
- execFileSync('tmux', ['send-keys', '-t', pane, '-l', '🎄 예약 걸렸어 — ' + String(req.message).slice(0, 40)]);
341
- setTimeout(() => { try { execFileSync('tmux', ['send-keys', '-t', pane, 'Enter']); } catch (e) {} }, 300);
342
370
  } else { console.warn(` 예약 실패: ${j.error}`); try { fs.renameSync(fp, fp + '.err'); } catch (e) {} }
343
371
  } catch (e) { console.error('[sched fetch err]', e.message); }
344
372
  }
@@ -348,7 +376,7 @@ async function run(conf) {
348
376
  // 루돌프가 사용자에게 보내는 파일 — santa-show 스킬이 ~/.santaclaude/outbox/ 에 복사하면 릴레이
349
377
  const OUTBOXDIR = path.join(os.homedir(), '.santaclaude', 'outbox');
350
378
  const MAXOUT = 18 * 1024 * 1024; // 18MB (KV 릴레이 한계 — 초과는 R2 예정)
351
- function tmuxNote(msg) { try { execFileSync('tmux', ['send-keys', '-t', pane, '-l', msg]); setTimeout(() => { try { execFileSync('tmux', ['send-keys', '-t', pane, 'Enter']); } catch (e) {} }, 300); } catch (e) {} }
379
+ // 주의: 상태 알림을 claude pane send-keys 하면 사용자 메시지로 오인돼 claude가 되받아침 절대 금지. 확인은 UI(드로어)에서.
352
380
  async function scanOutbox() {
353
381
  try {
354
382
  if (!fs.existsSync(OUTBOXDIR)) return;
@@ -357,7 +385,7 @@ async function run(conf) {
357
385
  const fp = path.join(OUTBOXDIR, fn);
358
386
  let st; try { st = fs.statSync(fp); } catch (e) { continue; }
359
387
  if (!st.isFile()) continue;
360
- if (st.size > MAXOUT) { console.warn(` ⚠️ "${fn}" ${(st.size / 1048576).toFixed(1)}MB > 18MB — 못 보냄`); try { fs.renameSync(fp, fp + '.toobig'); } catch (e) {} tmuxNote('⚠️ ' + fn + ' 는 18MB 초과라 아직 못 보내 (곧 큰 파일도 지원). 잘라 보내거나 링크로.'); continue; }
388
+ if (st.size > MAXOUT) { console.warn(` ⚠️ "${fn}" ${(st.size / 1048576).toFixed(1)}MB > 18MB — 못 보냄(R2 대기)`); try { fs.renameSync(fp, fp + '.toobig'); } catch (e) {} continue; }
361
389
  const ext = (fn.split('.').pop() || '').toLowerCase();
362
390
  const mime = EXT_MIME[ext] || 'application/octet-stream';
363
391
  let data; try { data = fs.readFileSync(fp).toString('base64'); } catch (e) { continue; }
@@ -365,9 +393,8 @@ async function run(conf) {
365
393
  const j = await post(api, '/api/upload', { token, data, mime, name: fn });
366
394
  if (j && j.id) {
367
395
  await post(api, '/api/outfile', { token, id: j.id, name: fn, mime, size: st.size }).catch(() => {});
368
- console.log(` 📤 "${fn}" (${(st.size / 1024).toFixed(0)}KB) → 사용자 화면으로 보냄`);
396
+ console.log(` 📤 "${fn}" (${(st.size / 1024).toFixed(0)}KB) → 사용자 화면으로 보냄`); // 확인은 웹 드로어 '받은파일'에 — claude 주입 금지
369
397
  try { fs.unlinkSync(fp); } catch (e) {}
370
- tmuxNote('📤 보냈어 — ' + fn);
371
398
  } else { console.warn(` 파일 보내기 실패(${fn}): ${j && j.error}`); try { fs.renameSync(fp, fp + '.err'); } catch (e) {} }
372
399
  } catch (e) { console.error('[outbox fetch err]', e.message); }
373
400
  }