shellbase 0.11.0 → 0.12.1
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/dist/agent.js +33 -2
- package/dist/browse.js +67 -4
- package/dist/config.js +3 -1
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -15,7 +15,7 @@ import { encryptFrame, decryptFrame, generateFrameKey } from './crypto.js';
|
|
|
15
15
|
import { registerDevice, heartbeatDevice, unregisterDevice, listOnlineNames, renameDevice, markSessionDone, keepDevice, } from './devices.js';
|
|
16
16
|
import { promptApproval } from './prompt.js';
|
|
17
17
|
import { touchRecentDir } from './recent-dirs.js';
|
|
18
|
-
import { listDirs } from './browse.js';
|
|
18
|
+
import { listDirs, deleteEntry, renameEntry } from './browse.js';
|
|
19
19
|
import { readTextFile, writeTextFile, chunkContent } from './files.js';
|
|
20
20
|
import { AUDIO_CHUNK_TTL_MS, MAX_AUDIO_BASE64, STT_SETUP_HINT, findSttServer, transcribe, } from './stt.js';
|
|
21
21
|
import { loadOpenSessions, saveOpenSessions } from './open-sessions.js';
|
|
@@ -784,7 +784,7 @@ export async function runAgent(options) {
|
|
|
784
784
|
const control = readControl(frame.data, session.frameKey);
|
|
785
785
|
if (!control)
|
|
786
786
|
return;
|
|
787
|
-
const listing = listDirs(typeof control.dir === 'string' && control.dir ? control.dir : liveCwd(session));
|
|
787
|
+
const listing = listDirs(typeof control.dir === 'string' && control.dir ? control.dir : liveCwd(session), { hidden: control.hidden === true });
|
|
788
788
|
void send({
|
|
789
789
|
kind: 'dirs',
|
|
790
790
|
to: session.deviceId,
|
|
@@ -792,6 +792,37 @@ export async function runAgent(options) {
|
|
|
792
792
|
});
|
|
793
793
|
return;
|
|
794
794
|
}
|
|
795
|
+
// 폴더 찾기에서 이름 바꾸기 / 지우기. 어느 쪽이든 끝나면 **새 목록**을 돌려줘서
|
|
796
|
+
// 폰 화면이 곧바로 최신 상태가 되게 한다 (실패 사유도 그 목록에 실어 보낸다).
|
|
797
|
+
if (frame.kind === 'path_action') {
|
|
798
|
+
const control = readControl(frame.data, session.frameKey);
|
|
799
|
+
if (!control)
|
|
800
|
+
return;
|
|
801
|
+
const dir = typeof control.dir === 'string' && control.dir ? control.dir : liveCwd(session);
|
|
802
|
+
const name = typeof control.name === 'string' ? control.name : '';
|
|
803
|
+
let failure;
|
|
804
|
+
if (control.op === 'rename') {
|
|
805
|
+
const to = typeof control.to === 'string' ? control.to : '';
|
|
806
|
+
failure = renameEntry(dir, name, to).error;
|
|
807
|
+
if (!failure)
|
|
808
|
+
console.log(`📱 폰에서 이름을 바꿨어요: ${name} → ${to} (${dir})`);
|
|
809
|
+
}
|
|
810
|
+
else if (control.op === 'delete') {
|
|
811
|
+
failure = deleteEntry(dir, name).error;
|
|
812
|
+
if (!failure)
|
|
813
|
+
console.log(`📱 폰에서 지웠어요: ${name} (${dir})`);
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
failure = '알 수 없는 요청이에요.';
|
|
817
|
+
}
|
|
818
|
+
const listing = listDirs(dir, { hidden: control.hidden === true });
|
|
819
|
+
void send({
|
|
820
|
+
kind: 'dirs',
|
|
821
|
+
to: session.deviceId,
|
|
822
|
+
data: encryptFrame(JSON.stringify(failure ? { ...listing, error: failure } : listing), session.frameKey),
|
|
823
|
+
});
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
795
826
|
if (frame.kind === 'new_session') {
|
|
796
827
|
const control = readControl(frame.data, session.frameKey);
|
|
797
828
|
if (!control || typeof control.dir !== 'string')
|
package/dist/browse.js
CHANGED
|
@@ -4,7 +4,69 @@ import path from 'node:path';
|
|
|
4
4
|
// 폰에서 폴더를 탭해서 파고들며 고를 수 있게, 요청받은 경로의 "하위 폴더 목록"만 돌려준다.
|
|
5
5
|
// 파일 이름은 보내지 않음 — 세션을 열 위치를 고르는 용도이고, 목록이 길어질 이유도 없음.
|
|
6
6
|
const MAX_ENTRIES = 300;
|
|
7
|
-
|
|
7
|
+
// 폴더 안의 항목 하나를 가리키는 안전한 경로를 만든다.
|
|
8
|
+
// 이름에 `/` 나 `..` 이 섞여 들어오면 폴더 밖을 건드릴 수 있으므로 여기서 전부 막는다
|
|
9
|
+
// (폰에서 오는 값이라 반드시 이 컴퓨터 쪽에서 검사해야 한다).
|
|
10
|
+
function childPath(dir, name) {
|
|
11
|
+
if (!name || name === '.' || name === '..')
|
|
12
|
+
return null;
|
|
13
|
+
if (name.includes('/') || name.includes('\\'))
|
|
14
|
+
return null;
|
|
15
|
+
const parent = path.resolve(dir);
|
|
16
|
+
const target = path.resolve(parent, name);
|
|
17
|
+
if (path.dirname(target) !== parent)
|
|
18
|
+
return null;
|
|
19
|
+
return target;
|
|
20
|
+
}
|
|
21
|
+
// 이름 바꾸기 — 같은 폴더 안에서만, 이미 있는 이름으로는 덮어쓰지 않는다
|
|
22
|
+
export function renameEntry(dir, name, to) {
|
|
23
|
+
const from = childPath(dir, name);
|
|
24
|
+
const dest = childPath(dir, to);
|
|
25
|
+
if (!from || !dest)
|
|
26
|
+
return { error: '이름에 쓸 수 없는 글자가 있어요.' };
|
|
27
|
+
if (from === dest)
|
|
28
|
+
return {};
|
|
29
|
+
if (fs.existsSync(dest))
|
|
30
|
+
return { error: `"${to}" 는 이미 있어요.` };
|
|
31
|
+
try {
|
|
32
|
+
fs.renameSync(from, dest);
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
return { error: `이름을 바꾸지 못했어요: ${err.message}` };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// 지우기 — 파일은 바로, 폴더는 **비어 있을 때만**.
|
|
40
|
+
// 폰에서 한 번 잘못 누르면 되돌릴 수 없으므로, 안에 든 것까지 통째로 지우는 일은 하지 않는다
|
|
41
|
+
// (정말 필요하면 터미널에서 직접 지우면 된다).
|
|
42
|
+
export function deleteEntry(dir, name) {
|
|
43
|
+
const target = childPath(dir, name);
|
|
44
|
+
if (!target)
|
|
45
|
+
return { error: '지울 수 없는 이름이에요.' };
|
|
46
|
+
let stat;
|
|
47
|
+
try {
|
|
48
|
+
stat = fs.lstatSync(target);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return { error: '이미 없어요.' };
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
if (stat.isDirectory()) {
|
|
55
|
+
const inside = fs.readdirSync(target);
|
|
56
|
+
if (inside.length > 0) {
|
|
57
|
+
return { error: `"${name}" 안에 ${inside.length}개가 들어 있어서 여기서는 못 지워요.` };
|
|
58
|
+
}
|
|
59
|
+
fs.rmdirSync(target);
|
|
60
|
+
return {};
|
|
61
|
+
}
|
|
62
|
+
fs.unlinkSync(target);
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
return { error: `지우지 못했어요: ${err.message}` };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export function listDirs(dir, opts = {}) {
|
|
8
70
|
const target = path.resolve(dir);
|
|
9
71
|
const up = path.dirname(target);
|
|
10
72
|
const base = {
|
|
@@ -23,8 +85,9 @@ export function listDirs(dir) {
|
|
|
23
85
|
}
|
|
24
86
|
const names = items
|
|
25
87
|
.filter((entry) => {
|
|
26
|
-
// 숨김 폴더(.git, .cache 등)는
|
|
27
|
-
|
|
88
|
+
// 숨김 폴더(.git, .cache 등)는 기본적으로 감춘다 — 목록이 길어지기 때문.
|
|
89
|
+
// 폰에서 「숨김 보기」를 켜면 그때만 함께 보여준다.
|
|
90
|
+
if (!opts.hidden && entry.name.startsWith('.'))
|
|
28
91
|
return false;
|
|
29
92
|
if (entry.isDirectory())
|
|
30
93
|
return true;
|
|
@@ -43,7 +106,7 @@ export function listDirs(dir) {
|
|
|
43
106
|
.sort((a, b) => a.localeCompare(b));
|
|
44
107
|
// 파일은 이름과 크기만 — 열어볼 수 있는지 폰에서 미리 판단할 수 있게 한다
|
|
45
108
|
const files = items
|
|
46
|
-
.filter((entry) => !entry.name.startsWith('.') && entry.isFile())
|
|
109
|
+
.filter((entry) => (opts.hidden || !entry.name.startsWith('.')) && entry.isFile())
|
|
47
110
|
.map((entry) => {
|
|
48
111
|
try {
|
|
49
112
|
return { name: entry.name, size: fs.statSync(path.join(target, entry.name)).size };
|
package/dist/config.js
CHANGED
|
@@ -11,7 +11,9 @@ export const CLI_AUTH_SESSIONS_TABLE_ID = '01a00aa0-802d-7730-88ba-b831b7405188'
|
|
|
11
11
|
export const WEB_APP_URL = 'https://shellbase.web.connectbase.world';
|
|
12
12
|
export const WEB_LOGIN_URL = `${WEB_APP_URL}/login`;
|
|
13
13
|
// 앱을 닫아둔 폰에 알림을 보내주는 서버 함수 (push.ts). 발송은 서버만 할 수 있어서 이걸 부른다.
|
|
14
|
-
|
|
14
|
+
// 이름이 아니라 **함수 id(UUID)** 로 불러야 한다 — 이름으로 부르면 `invalid function ID` 가 난다(실측).
|
|
15
|
+
// 이 앱의 모든 사용자가 같은 함수를 부르지만, 알림은 함수가 호출자 본인(ctx.memberId)에게만 보낸다.
|
|
16
|
+
export const PUSH_FUNCTION_ID = process.env.SHELLBASE_PUSH_FUNCTION ?? '01a01f8f-6ef8-7335-ba55-dc095587ba6d';
|
|
15
17
|
export const CONFIG_DIR = path.join(os.homedir(), '.shellbase');
|
|
16
18
|
export const CREDENTIALS_PATH = path.join(CONFIG_DIR, 'credentials.json');
|
|
17
19
|
// 폰이 보내는 제어 프레임(세션 끄기 / 새 세션 열기)에 함께 실어 보내는 확인 문자열.
|