@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.
- package/package.json +1 -1
- package/santaclaude.js +39 -12
package/package.json
CHANGED
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
|
|
284
|
-
|
|
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
|
-
|
|
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 — 못
|
|
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
|
}
|