@cliphijack/santaclaude 0.9.2 → 0.9.4
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 +59 -1
package/package.json
CHANGED
package/santaclaude.js
CHANGED
|
@@ -74,6 +74,14 @@ function captureWindow(session, name) {
|
|
|
74
74
|
function captureAll(session) {
|
|
75
75
|
const o = {}; for (const w of listWindows(session)) o[w] = captureWindow(session, w); return o;
|
|
76
76
|
}
|
|
77
|
+
// 받은 이미지 24시간 지난 건 로컬에서 청소
|
|
78
|
+
function cleanOldImages() {
|
|
79
|
+
try {
|
|
80
|
+
const dir = path.join(os.homedir(), '.santaclaude', 'img'); if (!fs.existsSync(dir)) return;
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
for (const f of fs.readdirSync(dir)) { const fp = path.join(dir, f); try { if (now - fs.statSync(fp).mtimeMs > 86400000) fs.unlinkSync(fp); } catch (e) {} }
|
|
83
|
+
} catch (e) {}
|
|
84
|
+
}
|
|
77
85
|
|
|
78
86
|
async function post(api, p, body) {
|
|
79
87
|
const r = await fetch(api + p, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
|
@@ -88,6 +96,7 @@ async function run(conf) {
|
|
|
88
96
|
const claudeCmd = conf.claudeCmd || 'claude --dangerously-skip-permissions';
|
|
89
97
|
let warned = false; // 중복방지는 서버가 책임 (claim 시 next_fire 원자적 전진) — 클라 영구셋은 반복예약을 영구차단하므로 안 둠
|
|
90
98
|
|
|
99
|
+
cleanOldImages();
|
|
91
100
|
console.log(`🛷 SantaClaude 커넥터 가동 — pane=${pane} · ${every / 1000}s 폴링 · ${api}`);
|
|
92
101
|
if (paneExists(pane)) {
|
|
93
102
|
try { execFileSync('tmux', ['resize-window', '-t', session, '-x', '220', '-y', '50']); } catch (e) {} // 기존 세션도 크게(더 많은 줄)
|
|
@@ -103,8 +112,57 @@ async function run(conf) {
|
|
|
103
112
|
}
|
|
104
113
|
|
|
105
114
|
// 루돌프 = 작업장(session) 안의 윈도우(탭). 별도 세션 아님.
|
|
106
|
-
function runControl(cmd) {
|
|
115
|
+
async function runControl(cmd) {
|
|
107
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
|
+
const sdir = path.join(cwd, '.claude', 'skills', sk.name);
|
|
127
|
+
let cnt = 0;
|
|
128
|
+
for (const f of (sk.files || [])) {
|
|
129
|
+
const safe = String(f.path || '').replace(/\\/g, '/').replace(/(^|\/)\.\.(\/|$)/g, '/').replace(/^\/+/, '');
|
|
130
|
+
if (!safe) continue;
|
|
131
|
+
const fp = path.join(sdir, safe);
|
|
132
|
+
if (!fp.startsWith(sdir)) continue; // 경로 탈출 방지
|
|
133
|
+
try { fs.mkdirSync(path.dirname(fp), { recursive: true }); fs.writeFileSync(fp, String(f.content || '')); cnt++; } catch (e) {}
|
|
134
|
+
}
|
|
135
|
+
console.log(` 🧩 스킬 "${sk.name}" ${cnt}개 파일 이식 → ${sdir}`);
|
|
136
|
+
const msg = sk.prompt && sk.prompt.trim() ? sk.prompt.trim() : ('스킬 "' + sk.name + '"이 .claude/skills/' + sk.name + '/ 에 이식됐어. SKILL.md 읽고 적용해줘.');
|
|
137
|
+
execFileSync('tmux', ['send-keys', '-t', w, '-l', msg]);
|
|
138
|
+
setTimeout(() => { try { execFileSync('tmux', ['send-keys', '-t', w, 'Enter']); } catch (e) {} }, 300);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (cmd.action === 'image') {
|
|
142
|
+
// 웹이 올린 이미지를 받아 로컬에 저장하고 claude에 경로 주입
|
|
143
|
+
const dir = path.join(os.homedir(), '.santaclaude', 'img');
|
|
144
|
+
try { fs.mkdirSync(dir, { recursive: true }); } catch (e) {}
|
|
145
|
+
const paths = [];
|
|
146
|
+
for (const im of (cmd.imgs || [])) {
|
|
147
|
+
try {
|
|
148
|
+
const r = await fetch(api + '/api/img?token=' + encodeURIComponent(token) + '&id=' + encodeURIComponent(im.id));
|
|
149
|
+
if (!r.ok) continue;
|
|
150
|
+
const j = await r.json();
|
|
151
|
+
const ext = String(j.mime || '').includes('png') ? 'png' : String(j.mime || '').includes('webp') ? 'webp' : 'jpg';
|
|
152
|
+
const fp = path.join(dir, Date.now() + '_' + paths.length + '.' + ext);
|
|
153
|
+
fs.writeFileSync(fp, Buffer.from(j.data, 'base64'));
|
|
154
|
+
paths.push(fp);
|
|
155
|
+
} catch (e) {}
|
|
156
|
+
}
|
|
157
|
+
if (paths.length) {
|
|
158
|
+
const w = (cmd.name && windowExists(session, cmd.name)) ? (session + ':' + cmd.name) : pane;
|
|
159
|
+
const msg = (cmd.note ? cmd.note + ' ' : '') + '첨부 이미지 ' + paths.length + '장 봐줘: ' + paths.join(' ');
|
|
160
|
+
execFileSync('tmux', ['send-keys', '-t', w, '-l', msg]);
|
|
161
|
+
setTimeout(() => { try { execFileSync('tmux', ['send-keys', '-t', w, 'Enter']); } catch (e) {} }, 300);
|
|
162
|
+
console.log(` 🖼️ 이미지 ${paths.length}장 → ${w}`);
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
108
166
|
if (cmd.action === 'create') {
|
|
109
167
|
if (!paneExists(session)) spawnClaude(session, claudeCmd); // 작업장 세션 없으면 먼저 띄움
|
|
110
168
|
if (windowExists(session, cmd.name)) { console.log(` 🦌 "${cmd.name}" 탭 이미 있어 — 안 만듦`); return; }
|