claude-remote 0.5.2 → 0.6.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.
@@ -0,0 +1,81 @@
1
+ // ============================================================
2
+ // Debug Logging
3
+ // ============================================================
4
+ import { CLIENT_INSTANCE_KEY } from './constants.js';
5
+ import { S } from './state.js';
6
+
7
+ function getClientInstanceId() {
8
+ const fallback = `client_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
9
+ try {
10
+ let id = sessionStorage.getItem(CLIENT_INSTANCE_KEY);
11
+ if (!id) {
12
+ id = fallback;
13
+ sessionStorage.setItem(CLIENT_INSTANCE_KEY, id);
14
+ }
15
+ return id;
16
+ } catch {
17
+ return fallback;
18
+ }
19
+ }
20
+
21
+ export const CLIENT_INSTANCE_ID = getClientInstanceId();
22
+ let debugLogSeq = 0;
23
+ const MAX_PENDING_DEBUG_LOGS = 120;
24
+ export const pendingDebugLogs = [];
25
+
26
+ export function wsReadyStateName(ws) {
27
+ if (!ws) return 'null';
28
+ switch (ws.readyState) {
29
+ case WebSocket.CONNECTING: return 'CONNECTING';
30
+ case WebSocket.OPEN: return 'OPEN';
31
+ case WebSocket.CLOSING: return 'CLOSING';
32
+ case WebSocket.CLOSED: return 'CLOSED';
33
+ default: return String(ws.readyState);
34
+ }
35
+ }
36
+
37
+ export function queueDebugPayload(payload) {
38
+ pendingDebugLogs.push(payload);
39
+ if (pendingDebugLogs.length > MAX_PENDING_DEBUG_LOGS) pendingDebugLogs.shift();
40
+ }
41
+
42
+ export function restorePendingDebugLogs(payloads) {
43
+ if (!payloads || !payloads.length) return;
44
+ pendingDebugLogs.unshift(...payloads);
45
+ while (pendingDebugLogs.length > MAX_PENDING_DEBUG_LOGS) pendingDebugLogs.shift();
46
+ }
47
+
48
+ export function sendDebugPayload(payload) {
49
+ try {
50
+ if (S.ws && S.ws.readyState === WebSocket.OPEN) {
51
+ S.ws.send(JSON.stringify({ type: 'debug_log', ...payload }));
52
+ return true;
53
+ }
54
+ } catch {}
55
+ return false;
56
+ }
57
+
58
+ export function flushPendingDebugLogs() {
59
+ if (!pendingDebugLogs.length) return;
60
+ if (!S.ws || S.ws.readyState !== WebSocket.OPEN) return;
61
+ const backlog = pendingDebugLogs.splice(0, pendingDebugLogs.length);
62
+ for (let i = 0; i < backlog.length; i += 1) {
63
+ const payload = backlog[i];
64
+ if (!sendDebugPayload(payload)) {
65
+ restorePendingDebugLogs(backlog.slice(i));
66
+ break;
67
+ }
68
+ }
69
+ }
70
+
71
+ export function debugLog(event, detail = {}) {
72
+ const payload = {
73
+ clientInstanceId: CLIENT_INSTANCE_ID,
74
+ event,
75
+ detail,
76
+ seq: ++debugLogSeq,
77
+ ts: new Date().toISOString(),
78
+ };
79
+ console.log('[bridge-debug]', payload);
80
+ if (!sendDebugPayload(payload)) queueDebugPayload(payload);
81
+ }
@@ -0,0 +1,128 @@
1
+ // ============================================================
2
+ // Directory Picker
3
+ // ============================================================
4
+ import { $, esc, shortenPath, normalizePathForCompare } from './utils.js';
5
+ import { S, dirBrowserState } from './state.js';
6
+ import { showToast } from './toast.js';
7
+ import { showConfirm } from './confirm.js';
8
+ import { sessionListCache, getSessionCwd } from './sessions.js';
9
+ import { closeSettings } from './settings.js';
10
+
11
+ function openDirPicker(startCwd = '') {
12
+ $('dir-overlay').classList.add('visible');
13
+ requestDirList(startCwd || dirBrowserState.cwd || S.cwd || '');
14
+ }
15
+
16
+ function closeDirPicker() {
17
+ $('dir-overlay').classList.remove('visible');
18
+ }
19
+
20
+ function requestDirList(cwd) {
21
+ if (!S.ws || S.ws.readyState !== WebSocket.OPEN) {
22
+ showToast('Connection unavailable');
23
+ return;
24
+ }
25
+ $('dir-list').innerHTML = '<div class="drawer-loading">Loading...</div>';
26
+ S.ws.send(JSON.stringify({ type: 'list_dirs', cwd }));
27
+ }
28
+
29
+ export function renderDirBrowser(payload) {
30
+ dirBrowserState.cwd = payload.cwd || '';
31
+ dirBrowserState.parent = payload.parent || null;
32
+ dirBrowserState.roots = Array.isArray(payload.roots) ? payload.roots : [];
33
+ dirBrowserState.entries = Array.isArray(payload.entries) ? payload.entries : [];
34
+
35
+ $('dir-current-path').textContent = dirBrowserState.cwd || 'Unknown';
36
+
37
+ const $roots = $('dir-roots');
38
+ $roots.innerHTML = '';
39
+ for (const root of dirBrowserState.roots) {
40
+ const chip = document.createElement('button');
41
+ chip.className = 'dir-root-chip' + (normalizePathForCompare(root) === normalizePathForCompare(dirBrowserState.cwd) ? ' active' : '');
42
+ chip.textContent = root;
43
+ chip.addEventListener('click', () => requestDirList(root));
44
+ $roots.appendChild(chip);
45
+ }
46
+
47
+ const $list = $('dir-list');
48
+ $list.innerHTML = '';
49
+ if (payload.error) {
50
+ showToast(payload.error);
51
+ }
52
+
53
+ if (dirBrowserState.parent) {
54
+ $list.appendChild(buildDirItem('..', dirBrowserState.parent, true));
55
+ }
56
+
57
+ if (!dirBrowserState.entries.length) {
58
+ const empty = document.createElement('div');
59
+ empty.className = 'dir-empty';
60
+ empty.textContent = payload.error ? 'Folder unavailable' : 'No subfolders';
61
+ $list.appendChild(empty);
62
+ return;
63
+ }
64
+
65
+ for (const entry of dirBrowserState.entries) {
66
+ $list.appendChild(buildDirItem(entry.name, entry.path, false));
67
+ }
68
+ }
69
+
70
+ function buildDirItem(name, fullPath, isParent) {
71
+ const el = document.createElement('div');
72
+ el.className = 'dir-item';
73
+ el.innerHTML =
74
+ `<div class="dir-item-main">` +
75
+ `<div class="dir-item-icon">${isParent ? '&#8593;' : '&#128193;'}</div>` +
76
+ `<div>` +
77
+ `<div class="dir-item-name">${esc(name)}</div>` +
78
+ `<div class="dir-item-path">${esc(shortenPath(fullPath))}</div>` +
79
+ `</div>` +
80
+ `</div>` +
81
+ `<div class="dir-item-arrow">&#8250;</div>`;
82
+ el.addEventListener('click', () => requestDirList(fullPath));
83
+ return el;
84
+ }
85
+
86
+ export function updateSettingsCwd() {
87
+ $('settings-cwd-display').textContent = S.cwd || 'Unknown';
88
+ $('settings-cwd-input').value = S.cwd || '';
89
+ const cwdSet = new Set();
90
+ for (const s of sessionListCache) {
91
+ const cwd = getSessionCwd(s);
92
+ if (cwd && cwd !== S.cwd) cwdSet.add(cwd);
93
+ }
94
+ const $list = $('settings-cwd-list');
95
+ $list.innerHTML = '';
96
+ for (const cwd of cwdSet) {
97
+ const chip = document.createElement('button');
98
+ chip.className = 'cwd-chip';
99
+ chip.textContent = shortenPath(cwd);
100
+ chip.title = cwd;
101
+ chip.addEventListener('click', () => confirmChangeCwd(cwd));
102
+ $list.appendChild(chip);
103
+ }
104
+ }
105
+
106
+ async function confirmChangeCwd(newCwd) {
107
+ if (!newCwd || normalizePathForCompare(newCwd) === normalizePathForCompare(S.cwd)) return;
108
+ const ok = await showConfirm(`切换工作目录到 "${shortenPath(newCwd)}"?\n将重启 Claude 进程。`);
109
+ if (!ok) return;
110
+ closeSettings();
111
+ if (S.ws && S.ws.readyState === WebSocket.OPEN) {
112
+ S.ws.send(JSON.stringify({ type: 'change_cwd', cwd: newCwd }));
113
+ }
114
+ }
115
+
116
+ export function initDirPicker() {
117
+ $('dir-drawer-close').addEventListener('click', closeDirPicker);
118
+ $('dir-overlay').addEventListener('click', e => {
119
+ if (e.target === $('dir-overlay')) closeDirPicker();
120
+ });
121
+ $('btn-select-cwd').addEventListener('click', async () => {
122
+ if (!dirBrowserState.cwd) return;
123
+ closeDirPicker();
124
+ await confirmChangeCwd(dirBrowserState.cwd);
125
+ });
126
+ $('btn-change-cwd').addEventListener('click', () => openDirPicker(S.cwd));
127
+ $('settings-cwd-input').addEventListener('click', () => openDirPicker(S.cwd));
128
+ }