shellbase 0.7.0 → 0.7.2

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/README.md CHANGED
@@ -121,8 +121,9 @@ shellbase start --no-restore # 복구하지 않고 이 폴더 세션 하나
121
121
  `~/.shellbase/images/`** 에 파일로 저장되고 그 **파일 경로가 터미널에 자동으로 입력**됩니다.
122
122
  이어서 "이 화면 좀 봐줘" 처럼 쓰고 엔터를 누르면 Claude Code 가 그 사진을 읽어요.
123
123
 
124
- - 폰에서는 「사진 찍기 / 사진 보관함」이 함께 떠요 — 방금 찍은 **스크린샷은 보관함**에 있습니다
125
- - 스크린샷을 **복사해서 붙여넣기**(Ctrl+V) 해도 첨부돼요
124
+ - 폰에서는 📎 버튼을 누르면 「사진 찍기 / 사진 보관함」이 함께 떠요 — 방금 찍은 **스크린샷은 보관함**에 있습니다
125
+ - **PC 스크린샷 파일을 터미널 위로 끌어다 놓으면(드래그&드롭)** 바로 첨부돼요 (📎 버튼은 폰 전용)
126
+ - 스크린샷을 **복사해서 붙여넣기**(Ctrl+V) 해도 첨부돼요 (PC·폰 공통)
126
127
  - 사진은 보내기 전에 폰에서 **긴 변 1568px 로 줄여서** 올라가요(전송이 빠르고 인식 정확도는 그대로)
127
128
  - 저장된 사진은 **7일이 지나면 자동으로 지워져요**, 파일 권한은 본인만 읽기(600)
128
129
  - 사진도 터미널 화면과 똑같이 **세션 키로 암호화**해서 전송돼요
package/dist/agent.js CHANGED
@@ -7,7 +7,7 @@ import { requireClient } from './auth.js';
7
7
  import { TERMINAL_CATEGORY, CONTROL_TOKEN } from './config.js';
8
8
  import { startSleepGuard, stopSleepGuard } from './sleep-guard.js';
9
9
  import { encryptFrame, decryptFrame, generateFrameKey } from './crypto.js';
10
- import { registerDevice, heartbeatDevice, unregisterDevice, listOnlineDirs, renameDevice, } from './devices.js';
10
+ import { registerDevice, heartbeatDevice, unregisterDevice, listOnlineNames, renameDevice, } from './devices.js';
11
11
  import { promptApproval } from './prompt.js';
12
12
  import { touchRecentDir } from './recent-dirs.js';
13
13
  import { listDirs } from './browse.js';
@@ -139,9 +139,7 @@ export async function runAgent(options) {
139
139
  .then(async () => {
140
140
  if (session.closing)
141
141
  return;
142
- const approved = !requireApproval
143
- ? true
144
- : await promptApproval(`\n📱 "${session.name}" 세션에 모바일 접속 요청이 왔어요. 입력을 허용할까요? (y/N, 30초 내 미응답 시 자동 거부): `, APPROVAL_TIMEOUT_MS);
142
+ const approved = await promptApproval(`\n📱 "${session.name}" 세션에 모바일 접속 요청이 왔어요. 입력을 허용할까요? (y/N, 30초 내 미응답 시 자동 거부): `, APPROVAL_TIMEOUT_MS);
145
143
  session.inputApproved = approved;
146
144
  session.approvalInFlight = false;
147
145
  await send({
@@ -313,6 +311,14 @@ export async function runAgent(options) {
313
311
  data: encryptFrame(session.scrollback, session.frameKey),
314
312
  });
315
313
  }
314
+ // 승인을 묻지 않는 기본 설정에서는 요청이 올 때마다 곧바로 답한다.
315
+ // (예전엔 "승인 진행 중"이면 건너뛰었는데, 그 사이 온 요청은 영영 답을 못 받아서
316
+ // 폰이 "승인 대기 중" 화면에 갇히는 일이 생겼음 — 자동 승인은 기다릴 게 없으므로 항상 응답)
317
+ if (!requireApproval) {
318
+ session.inputApproved = true;
319
+ void send({ kind: 'connect_approved', to: session.deviceId });
320
+ return;
321
+ }
316
322
  if (session.approvalInFlight)
317
323
  return;
318
324
  askApproval(session);
@@ -603,14 +609,30 @@ export async function runAgent(options) {
603
609
  console.log('이전 세션 복구는 건너뛰어요 (--no-restore)');
604
610
  return;
605
611
  }
606
- const saved = savedRecords.filter((entry) => entry.dir !== primary.cwd);
607
- if (saved.length === 0)
612
+ if (savedRecords.length === 0)
608
613
  return;
609
614
  // 같은 컴퓨터에서 `shellbase start` 를 또 실행한 경우, 이미 살아있는 세션을 중복으로 띄우지 않는다
610
- const liveDirs = await listOnlineDirs(cb).catch(() => new Set());
611
- for (const entry of saved) {
612
- if (liveDirs.has(entry.dir)) {
613
- console.log(` 건너뜀 (이미 켜져 있어요): ${entry.dir}`);
615
+ const liveNames = await listOnlineNames(cb).catch(() => new Set());
616
+ // 세션이 기본 이름 그대로인데 저장 목록에 같은 폴더의 세션이 있으면, 그 이름을 물려받는다.
617
+ // (도커/서비스로 뜨는 첫 세션이 사실은 예전에 이름을 바꿔둔 그 세션인 경우 — 이렇게 해야
618
+ // 재시작 후에도 "원래 있던 그대로"가 되고, 같은 세션이 이름만 다른 채 하나 더 생기지 않는다)
619
+ const remaining = [...savedRecords];
620
+ const adoptIndex = remaining.findIndex((entry) => entry.dir === primary.cwd && entry.name !== primary.name && !liveNames.has(entry.name));
621
+ if (adoptIndex >= 0) {
622
+ const adopted = remaining.splice(adoptIndex, 1)[0];
623
+ try {
624
+ await renameDevice(cb, primary.rowId, adopted.name);
625
+ console.log(` 이름 복구: "${primary.name}" → "${adopted.name}"`);
626
+ primary.name = adopted.name;
627
+ persistOpenSessions();
628
+ }
629
+ catch (err) {
630
+ console.error('이름 복구 실패:', err.message);
631
+ }
632
+ }
633
+ for (const entry of remaining) {
634
+ if (entry.name === primary.name || liveNames.has(entry.name)) {
635
+ console.log(` 건너뜀 (이미 켜져 있어요): ${entry.name}`);
614
636
  continue;
615
637
  }
616
638
  try {
package/dist/devices.js CHANGED
@@ -27,11 +27,12 @@ export async function heartbeatDevice(cb, rowId) {
27
27
  export async function unregisterDevice(cb, rowId) {
28
28
  await cb.database.deleteData(DEVICES_TABLE_ID, rowId);
29
29
  }
30
- // 복구할 때 "이미 다른 프로세스가 띄워둔 세션"을 또 띄우지 않도록, 지금 살아있는 세션들의 작업 폴더를 모은다.
31
- // (같은 컴퓨터에서 `shellbase start` 실행하는 경우까지 안전하게 처리하기 위함)
30
+ // 복구할 때 "이미 다른 프로세스가 띄워둔 세션"을 또 띄우지 않도록, 지금 살아있는 세션들의 이름을 모은다.
31
+ // 예전엔 작업 폴더로 판단했는데, 같은 폴더에 세션을 여러 열어둔 경우(홈에서 용도별로 2~3개)
32
+ // 하나만 복구되고 나머지가 조용히 사라지는 문제가 있었다 — 세션을 구분하는 건 이름이므로 이름으로 본다.
32
33
  const ONLINE_WINDOW_MS = 30_000;
33
- export async function listOnlineDirs(cb) {
34
- const dirs = new Set();
34
+ export async function listOnlineNames(cb) {
35
+ const names = new Set();
35
36
  const { data } = await cb.database.getData(DEVICES_TABLE_ID, {
36
37
  orderBy: 'last_seen_at',
37
38
  orderDirection: 'desc',
@@ -42,9 +43,9 @@ export async function listOnlineDirs(cb) {
42
43
  continue;
43
44
  if (Date.now() - new Date(row.data.last_seen_at).getTime() > ONLINE_WINDOW_MS)
44
45
  continue;
45
- dirs.add(row.data.cwd);
46
+ names.add(row.data.device_name);
46
47
  }
47
- return dirs;
48
+ return names;
48
49
  }
49
50
  // 폰에서 세션 이름을 바꿀 때 — 목록 row 의 표시 이름만 갱신한다 (세션 자체는 그대로 유지)
50
51
  export async function renameDevice(cb, rowId, deviceName) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shellbase",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "내 컴퓨터 터미널(특히 Claude Code 세션)을 폰 브라우저로 실시간 접속하게 해주는 데스크톱 에이전트",
5
5
  "type": "module",
6
6
  "bin": {