@cliphijack/santaclaude 1.0.22 → 1.0.23

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 +46 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cliphijack/santaclaude",
3
- "version": "1.0.22",
3
+ "version": "1.0.23",
4
4
  "publishConfig": { "access": "public" },
5
5
  "description": "SantaClaude 커넥터 — 클라우드 예약을 내 로컬 Claude(tmux)에 발사",
6
6
  "bin": { "santaclaude": "./santaclaude.js" },
package/santaclaude.js CHANGED
@@ -16,6 +16,49 @@ const fs = require('fs');
16
16
  const os = require('os');
17
17
  const path = require('path');
18
18
 
19
+ const VER = (() => { try { return require('./package.json').version; } catch (e) { return '0'; } })();
20
+ const VIA_NPX = __dirname.includes('_npx') || __dirname.includes('npm-cache') || process.env.SC_AUTOUP === '1';
21
+
22
+ function verGt(a, b) { const pa = String(a).split('.').map(Number), pb = String(b).split('.').map(Number); for (let i = 0; i < 3; i++) { if ((pa[i] || 0) > (pb[i] || 0)) return true; if ((pa[i] || 0) < (pb[i] || 0)) return false; } return false; }
23
+ const _TGT = path.join(os.homedir(), '.santaclaude-target'); // 워커가 지정한 목표 버전
24
+ const _GOOD = path.join(os.homedir(), '.santaclaude-good'); // 60초+ 산 마지막 안정버전(롤백용)
25
+ const _rd = (f) => { try { return fs.readFileSync(f, 'utf8').trim(); } catch (e) { return ''; } };
26
+
27
+ // ── 🔄 자동 업데이트 슈퍼바이저 (안전가드 포함) ──
28
+ // npx로 띄운 경우만: 실제 워커를 자식(@목표버전)으로 돌린다. 워커가 새 버전 감지하면 75로 종료 → 여기서 재시작.
29
+ // 안전판: ①새 버전이 계속 빨리 죽으면(크래시루프) 안정버전으로 자동 롤백 ②5연속 실패면 자동재시작 중단.
30
+ if (VIA_NPX && process.env.SC_SUP !== '1' && !process.argv.slice(2).some((a) => a === 'status' || a === 'connect')) {
31
+ const { spawnSync } = require('child_process');
32
+ console.log('🛷 SantaClaude — 자동 업데이트 켜짐 (크래시 롤백·서버 킬스위치 안전가드 포함)');
33
+ let quickFails = 0;
34
+ while (true) {
35
+ let spec = _rd(_TGT) || 'latest';
36
+ if (quickFails >= 3) { const g = _rd(_GOOD); if (g) { spec = g; console.log(` 🛟 새 버전이 반복해서 죽음 → 안정버전 ${g} 으로 고정`); } }
37
+ const t0 = Date.now();
38
+ const r = spawnSync('npx', ['-y', '@cliphijack/santaclaude@' + spec], { stdio: 'inherit', env: { ...process.env, SC_SUP: '1' } });
39
+ const lived = Date.now() - t0;
40
+ if (r.signal === 'SIGINT' || r.signal === 'SIGTERM') process.exit(0); // 사람이 Ctrl+C
41
+ if (lived > 60000) quickFails = 0; else quickFails++; // 60초+ 살았으면 정상
42
+ if (r.status === 75) { console.log(' ⬆️ 업데이트 적용 — 재시작'); continue; }
43
+ if (quickFails >= 5) { console.log(' ⛔ 반복 실패 — 자동 재시작 중단(수동 확인 필요). 마지막 코드 ' + r.status); process.exit(r.status == null ? 1 : r.status); }
44
+ console.log(` ⚠️ 커넥터 종료(코드 ${r.status}) — 6초 후 재시작`); spawnSync('sleep', ['6']);
45
+ }
46
+ }
47
+ // 워커(SC_SUP=1): 서버 버전 게이트 확인 → 새 목표면 목표버전 기록하고 75로 종료(슈퍼바이저가 재시작)
48
+ function checkUpdate() {
49
+ fetch(DEFAULT_API + '/api/cli-version', { headers: { accept: 'application/json' } })
50
+ .then((r) => r.json()).then((j) => {
51
+ if (!j || j.paused) return; // 🔴 킬스위치: paused면 업데이트 중단
52
+ const target = String(j.target || '');
53
+ if (!target || target === VER) return; // 목표=현재면 그대로 (target≠VER이면 업/다운 둘다 = 롤백 가능)
54
+ if (process.env.SC_SUP === '1') {
55
+ console.log(` ⬆️ 새 버전 ${target} (현재 ${VER}) — 업데이트 재시작…`);
56
+ try { fs.writeFileSync(_TGT, target); } catch (e) {}
57
+ setTimeout(() => process.exit(75), 800);
58
+ } else console.log(` ⬆️ 새 버전 ${target} 나옴 (현재 ${VER}) — 재시작 권장: npx -y @cliphijack/santaclaude@latest`);
59
+ }).catch(() => {});
60
+ }
61
+
19
62
  const CONF = path.join(os.homedir(), '.santaclaude.json');
20
63
  const DEFAULT_API = 'https://santaclaude.app';
21
64
 
@@ -334,7 +377,9 @@ async function run(conf) {
334
377
 
335
378
  cleanOldImages();
336
379
  try { execFileSync('tmux', ['set-option', '-g', 'history-limit', '50000'], { stdio: 'ignore' }); } catch (e) {} // 새로 띄우는 루돌프 pane은 스크롤백 5만줄(기존 pane은 유지)
337
- console.log(`🛷 SantaClaude 커넥터 가동 pane=${pane} · ${every / 1000}s 폴링 · ${api}`);
380
+ setTimeout(() => { try { fs.writeFileSync(_GOOD, VER); } catch (e) {} }, 60000); // 60초+ 살면 버전을 안정버전으로 기록(롤백 기준)
381
+ setTimeout(checkUpdate, 90000); setInterval(checkUpdate, 3 * 3600 * 1000); // 자동 업데이트 체크: 시작 90초 후 + 3시간마다
382
+ console.log(`🛷 SantaClaude 커넥터 가동 v${VER} — pane=${pane} · ${every / 1000}s 폴링 · ${api}`);
338
383
  if (paneExists(pane)) {
339
384
  // 기존(고객) 세션엔 사람이 안 붙어있을 때만 리사이즈 — 직접 쓰고 있으면 화면 절대 안 건드림
340
385
  if (!clientsAttached(session)) { try { execFileSync('tmux', ['resize-window', '-t', session, '-x', '220', '-y', '50']); } catch (e) {} }