@cliphijack/santaclaude 0.9.2 → 0.9.3

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 +35 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cliphijack/santaclaude",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
4
4
  "publishConfig": { "access": "public" },
5
5
  "description": "SantaClaude 커넥터 — 클라우드 예약을 내 로컬 Claude(tmux)에 발사",
6
6
  "bin": { "santaclaude": "./santaclaude.js" },
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,33 @@ 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 === 'image') {
118
+ // 웹이 올린 이미지를 받아 로컬에 저장하고 claude에 경로 주입
119
+ const dir = path.join(os.homedir(), '.santaclaude', 'img');
120
+ try { fs.mkdirSync(dir, { recursive: true }); } catch (e) {}
121
+ const paths = [];
122
+ for (const im of (cmd.imgs || [])) {
123
+ try {
124
+ const r = await fetch(api + '/api/img?token=' + encodeURIComponent(token) + '&id=' + encodeURIComponent(im.id));
125
+ if (!r.ok) continue;
126
+ const j = await r.json();
127
+ const ext = String(j.mime || '').includes('png') ? 'png' : String(j.mime || '').includes('webp') ? 'webp' : 'jpg';
128
+ const fp = path.join(dir, Date.now() + '_' + paths.length + '.' + ext);
129
+ fs.writeFileSync(fp, Buffer.from(j.data, 'base64'));
130
+ paths.push(fp);
131
+ } catch (e) {}
132
+ }
133
+ if (paths.length) {
134
+ const w = (cmd.name && windowExists(session, cmd.name)) ? (session + ':' + cmd.name) : pane;
135
+ const msg = (cmd.note ? cmd.note + ' ' : '') + '첨부 이미지 ' + paths.length + '장 봐줘: ' + paths.join(' ');
136
+ execFileSync('tmux', ['send-keys', '-t', w, '-l', msg]);
137
+ setTimeout(() => { try { execFileSync('tmux', ['send-keys', '-t', w, 'Enter']); } catch (e) {} }, 300);
138
+ console.log(` 🖼️ 이미지 ${paths.length}장 → ${w}`);
139
+ }
140
+ return;
141
+ }
108
142
  if (cmd.action === 'create') {
109
143
  if (!paneExists(session)) spawnClaude(session, claudeCmd); // 작업장 세션 없으면 먼저 띄움
110
144
  if (windowExists(session, cmd.name)) { console.log(` 🦌 "${cmd.name}" 탭 이미 있어 — 안 만듦`); return; }