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 +3 -2
- package/dist/agent.js +32 -10
- package/dist/devices.js +7 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,8 +121,9 @@ shellbase start --no-restore # 복구하지 않고 이 폴더 세션 하나
|
|
|
121
121
|
`~/.shellbase/images/`** 에 파일로 저장되고 그 **파일 경로가 터미널에 자동으로 입력**됩니다.
|
|
122
122
|
이어서 "이 화면 좀 봐줘" 처럼 쓰고 엔터를 누르면 Claude Code 가 그 사진을 읽어요.
|
|
123
123
|
|
|
124
|
-
- 폰에서는 「사진 찍기 / 사진 보관함」이 함께 떠요 — 방금 찍은 **스크린샷은 보관함**에 있습니다
|
|
125
|
-
-
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
607
|
-
if (saved.length === 0)
|
|
612
|
+
if (savedRecords.length === 0)
|
|
608
613
|
return;
|
|
609
614
|
// 같은 컴퓨터에서 `shellbase start` 를 또 실행한 경우, 이미 살아있는 세션을 중복으로 띄우지 않는다
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
-
//
|
|
30
|
+
// 복구할 때 "이미 다른 프로세스가 띄워둔 세션"을 또 띄우지 않도록, 지금 살아있는 세션들의 이름을 모은다.
|
|
31
|
+
// 예전엔 작업 폴더로 판단했는데, 같은 폴더에 세션을 여러 개 열어둔 경우(홈에서 용도별로 2~3개)
|
|
32
|
+
// 하나만 복구되고 나머지가 조용히 사라지는 문제가 있었다 — 세션을 구분하는 건 이름이므로 이름으로 본다.
|
|
32
33
|
const ONLINE_WINDOW_MS = 30_000;
|
|
33
|
-
export async function
|
|
34
|
-
const
|
|
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
|
-
|
|
46
|
+
names.add(row.data.device_name);
|
|
46
47
|
}
|
|
47
|
-
return
|
|
48
|
+
return names;
|
|
48
49
|
}
|
|
49
50
|
// 폰에서 세션 이름을 바꿀 때 — 목록 row 의 표시 이름만 갱신한다 (세션 자체는 그대로 유지)
|
|
50
51
|
export async function renameDevice(cb, rowId, deviceName) {
|