shellbase 0.1.9 → 0.1.10

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
@@ -53,7 +53,7 @@ shellbase start --dir ~/projects/my-app --name "내 노트북"
53
53
 
54
54
  | 하고 싶은 것 | 폰에서 하는 법 |
55
55
  |---|---|
56
- | 세션 하나 더 열기 | 목록 화면의 **`+ 새 세션`** 폴더 고르고 열기 (켜져 있는 세션이 컴퓨터에서 하나 띄워줘요) |
56
+ | 세션 하나 더 열기 | 목록 화면의 **`+ 새 세션`**, 또는 터미널 화면의 기기 이름(▾) **`+ 컴퓨터에서 세션 열기`** (열리면 그 세션으로 바로 넘어가요) |
57
57
  | 세션 갈아타기 | 터미널 화면 위쪽의 **기기 이름(▾)** 탭 → 원하는 세션 선택 |
58
58
  | 세션 끄기 | 목록의 **전원 아이콘**, 또는 터미널 화면의 기기 이름(▾) → **이 세션 끄기** |
59
59
  | 화면만 닫기 | `← 목록` 으로 나가기 — 컴퓨터의 세션은 계속 살아있어요 |
package/dist/browse.js ADDED
@@ -0,0 +1,48 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ // 폰에서 폴더를 탭해서 파고들며 고를 수 있게, 요청받은 경로의 "하위 폴더 목록"만 돌려준다.
5
+ // 파일 이름은 보내지 않음 — 세션을 열 위치를 고르는 용도이고, 목록이 길어질 이유도 없음.
6
+ const MAX_ENTRIES = 300;
7
+ export function listDirs(dir) {
8
+ const target = path.resolve(dir);
9
+ const up = path.dirname(target);
10
+ const base = {
11
+ dir: target,
12
+ parent: up === target ? null : up,
13
+ home: os.homedir(),
14
+ entries: [],
15
+ };
16
+ let items;
17
+ try {
18
+ items = fs.readdirSync(target, { withFileTypes: true });
19
+ }
20
+ catch (err) {
21
+ return { ...base, error: `폴더를 열 수 없어요: ${err.message}` };
22
+ }
23
+ const names = items
24
+ .filter((entry) => {
25
+ // 숨김 폴더(.git, .cache 등)는 제외 — 목록이 길어지고 세션을 열 곳도 아님
26
+ if (entry.name.startsWith('.'))
27
+ return false;
28
+ if (entry.isDirectory())
29
+ return true;
30
+ // 심볼릭 링크는 가리키는 대상이 폴더일 때만 포함
31
+ if (entry.isSymbolicLink()) {
32
+ try {
33
+ return fs.statSync(path.join(target, entry.name)).isDirectory();
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
39
+ return false;
40
+ })
41
+ .map((entry) => entry.name)
42
+ .sort((a, b) => a.localeCompare(b));
43
+ return {
44
+ ...base,
45
+ entries: names.slice(0, MAX_ENTRIES),
46
+ truncated: names.length > MAX_ENTRIES,
47
+ };
48
+ }
package/dist/session.js CHANGED
@@ -10,6 +10,7 @@ import { registerDevice, heartbeatDevice, unregisterDevice } from './devices.js'
10
10
  import { promptApproval } from './prompt.js';
11
11
  import { touchRecentDir } from './recent-dirs.js';
12
12
  import { spawnSession } from './spawner.js';
13
+ import { listDirs } from './browse.js';
13
14
  const HEARTBEAT_MS = 20_000;
14
15
  const APPROVAL_TIMEOUT_MS = 30_000;
15
16
  const SCROLLBACK_MAX = 16_000;
@@ -150,6 +151,21 @@ export async function startSession(options) {
150
151
  void shutdown();
151
152
  return;
152
153
  }
154
+ // 폰의 폴더 탐색기 — 요청받은 경로의 하위 폴더 목록을 암호화해서 돌려준다 (세션 열 곳 고르기용)
155
+ if (frame.kind === 'list_dirs') {
156
+ const control = readControl(frame.data, device.frameKey);
157
+ if (!control)
158
+ return;
159
+ const listing = listDirs(typeof control.dir === 'string' && control.dir ? control.dir : cwd);
160
+ channel
161
+ .send({
162
+ kind: 'dirs',
163
+ to: device.deviceId,
164
+ data: encryptFrame(JSON.stringify(listing), device.frameKey),
165
+ }, { includeSelf: false })
166
+ .catch((err) => console.error('폴더 목록 전송 실패:', err.message));
167
+ return;
168
+ }
153
169
  // 폰에서 "새 세션 열기" — 이 세션이 CLI 를 한 번 더 띄워주고, 새 세션은 폰 목록에 따로 나타난다.
154
170
  if (frame.kind === 'new_session') {
155
171
  const control = readControl(frame.data, device.frameKey);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shellbase",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "내 컴퓨터 터미널(특히 Claude Code 세션)을 폰 브라우저로 실시간 접속하게 해주는 데스크톱 에이전트",
5
5
  "type": "module",
6
6
  "bin": {