@myrialabs/clopen 0.1.7 → 0.1.9
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/backend/lib/database/migrations/023_create_user_unread_sessions_table.ts +32 -0
- package/backend/lib/database/migrations/index.ts +7 -0
- package/backend/lib/database/queries/session-queries.ts +37 -0
- package/backend/lib/git/git-service.ts +1 -0
- package/backend/ws/sessions/crud.ts +34 -2
- package/backend/ws/user/crud.ts +8 -4
- package/bun.lock +34 -12
- package/frontend/lib/components/common/MonacoEditor.svelte +6 -6
- package/frontend/lib/components/common/xterm/XTerm.svelte +27 -108
- package/frontend/lib/components/common/xterm/terminal-config.ts +2 -2
- package/frontend/lib/components/common/xterm/types.ts +1 -0
- package/frontend/lib/components/common/xterm/xterm-service.ts +69 -20
- package/frontend/lib/components/files/FileTree.svelte +4 -6
- package/frontend/lib/components/files/FileViewer.svelte +45 -101
- package/frontend/lib/components/git/CommitForm.svelte +1 -1
- package/frontend/lib/components/git/GitLog.svelte +141 -101
- package/frontend/lib/components/preview/browser/components/Toolbar.svelte +81 -72
- package/frontend/lib/components/settings/SettingsModal.svelte +1 -8
- package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +3 -3
- package/frontend/lib/components/terminal/Terminal.svelte +1 -1
- package/frontend/lib/components/terminal/TerminalTabs.svelte +28 -26
- package/frontend/lib/components/workspace/PanelHeader.svelte +639 -623
- package/frontend/lib/components/workspace/WorkspaceLayout.svelte +3 -2
- package/frontend/lib/components/workspace/panels/GitPanel.svelte +34 -92
- package/frontend/lib/stores/core/app.svelte.ts +46 -0
- package/frontend/lib/stores/core/sessions.svelte.ts +24 -3
- package/frontend/lib/stores/ui/workspace.svelte.ts +14 -14
- package/package.json +8 -6
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DatabaseConnection } from '$shared/types/database/connection';
|
|
2
|
+
import { debug } from '$shared/utils/logger';
|
|
3
|
+
|
|
4
|
+
export const description = 'Create user_unread_sessions table for persisting per-user unread session state';
|
|
5
|
+
|
|
6
|
+
export const up = (db: DatabaseConnection): void => {
|
|
7
|
+
debug.log('migration', 'Creating user_unread_sessions table...');
|
|
8
|
+
|
|
9
|
+
db.exec(`
|
|
10
|
+
CREATE TABLE IF NOT EXISTS user_unread_sessions (
|
|
11
|
+
user_id TEXT NOT NULL,
|
|
12
|
+
session_id TEXT NOT NULL,
|
|
13
|
+
project_id TEXT NOT NULL,
|
|
14
|
+
marked_at TEXT NOT NULL,
|
|
15
|
+
PRIMARY KEY (user_id, session_id),
|
|
16
|
+
FOREIGN KEY (session_id) REFERENCES chat_sessions(id) ON DELETE CASCADE
|
|
17
|
+
)
|
|
18
|
+
`);
|
|
19
|
+
|
|
20
|
+
db.exec(`
|
|
21
|
+
CREATE INDEX IF NOT EXISTS idx_user_unread_sessions_user_project
|
|
22
|
+
ON user_unread_sessions(user_id, project_id)
|
|
23
|
+
`);
|
|
24
|
+
|
|
25
|
+
debug.log('migration', 'user_unread_sessions table created');
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const down = (db: DatabaseConnection): void => {
|
|
29
|
+
debug.log('migration', 'Dropping user_unread_sessions table...');
|
|
30
|
+
db.exec('DROP TABLE IF EXISTS user_unread_sessions');
|
|
31
|
+
debug.log('migration', 'user_unread_sessions table dropped');
|
|
32
|
+
};
|
|
@@ -21,6 +21,7 @@ import * as migration019 from './019_add_claude_account_to_sessions';
|
|
|
21
21
|
import * as migration020 from './020_add_snapshot_tree_hash';
|
|
22
22
|
import * as migration021 from './021_drop_prompt_templates_table';
|
|
23
23
|
import * as migration022 from './022_add_snapshot_changes_column';
|
|
24
|
+
import * as migration023 from './023_create_user_unread_sessions_table';
|
|
24
25
|
|
|
25
26
|
// Export all migrations in order
|
|
26
27
|
export const migrations = [
|
|
@@ -155,6 +156,12 @@ export const migrations = [
|
|
|
155
156
|
description: migration022.description,
|
|
156
157
|
up: migration022.up,
|
|
157
158
|
down: migration022.down
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: '023',
|
|
162
|
+
description: migration023.description,
|
|
163
|
+
up: migration023.up,
|
|
164
|
+
down: migration023.down
|
|
158
165
|
}
|
|
159
166
|
];
|
|
160
167
|
|
|
@@ -267,5 +267,42 @@ export const sessionQueries = {
|
|
|
267
267
|
SET head_message_id = ?
|
|
268
268
|
WHERE session_id = ? AND branch_name = ?
|
|
269
269
|
`).run(newHeadMessageId, sessionId, branchName);
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
// ==================== PER-USER UNREAD SESSION TRACKING ====================
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Mark a session as unread for a specific user
|
|
276
|
+
*/
|
|
277
|
+
markUnread(userId: string, sessionId: string, projectId: string): void {
|
|
278
|
+
const db = getDatabase();
|
|
279
|
+
const now = new Date().toISOString();
|
|
280
|
+
db.prepare(`
|
|
281
|
+
INSERT OR IGNORE INTO user_unread_sessions (user_id, session_id, project_id, marked_at)
|
|
282
|
+
VALUES (?, ?, ?, ?)
|
|
283
|
+
`).run(userId, sessionId, projectId, now);
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Mark a session as read for a specific user
|
|
288
|
+
*/
|
|
289
|
+
markRead(userId: string, sessionId: string): void {
|
|
290
|
+
const db = getDatabase();
|
|
291
|
+
db.prepare(`
|
|
292
|
+
DELETE FROM user_unread_sessions
|
|
293
|
+
WHERE user_id = ? AND session_id = ?
|
|
294
|
+
`).run(userId, sessionId);
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Get all unread session IDs for a user within a project
|
|
299
|
+
* Returns array of { sessionId, projectId }
|
|
300
|
+
*/
|
|
301
|
+
getUnreadSessions(userId: string, projectId: string): { session_id: string; project_id: string }[] {
|
|
302
|
+
const db = getDatabase();
|
|
303
|
+
return db.prepare(`
|
|
304
|
+
SELECT session_id, project_id FROM user_unread_sessions
|
|
305
|
+
WHERE user_id = ? AND project_id = ?
|
|
306
|
+
`).all(userId, projectId) as { session_id: string; project_id: string }[];
|
|
270
307
|
}
|
|
271
308
|
};
|
|
@@ -33,7 +33,11 @@ export const crudHandler = createRouter()
|
|
|
33
33
|
started_at: t.String(),
|
|
34
34
|
ended_at: t.Optional(t.String())
|
|
35
35
|
})),
|
|
36
|
-
currentSessionId: t.Optional(t.String())
|
|
36
|
+
currentSessionId: t.Optional(t.String()),
|
|
37
|
+
unreadSessionIds: t.Array(t.Object({
|
|
38
|
+
sessionId: t.String(),
|
|
39
|
+
projectId: t.String()
|
|
40
|
+
}))
|
|
37
41
|
})
|
|
38
42
|
}, async ({ conn }) => {
|
|
39
43
|
const projectId = ws.getProjectId(conn);
|
|
@@ -43,6 +47,10 @@ export const crudHandler = createRouter()
|
|
|
43
47
|
// Get the user's saved current session for this project
|
|
44
48
|
const currentSessionId = projectQueries.getCurrentSessionId(userId, projectId);
|
|
45
49
|
|
|
50
|
+
// Get unread sessions for this user/project
|
|
51
|
+
const unreadRows = sessionQueries.getUnreadSessions(userId, projectId);
|
|
52
|
+
debug.log('session', `[unread] sessions:list — user=${userId}, project=${projectId}, unreadCount=${unreadRows.length}`, unreadRows);
|
|
53
|
+
|
|
46
54
|
// Convert null to undefined for TypeScript optional fields
|
|
47
55
|
return {
|
|
48
56
|
sessions: sessions.map(session => ({
|
|
@@ -54,7 +62,8 @@ export const crudHandler = createRouter()
|
|
|
54
62
|
current_head_message_id: session.current_head_message_id ?? undefined,
|
|
55
63
|
ended_at: session.ended_at ?? undefined
|
|
56
64
|
})),
|
|
57
|
-
currentSessionId: currentSessionId ?? undefined
|
|
65
|
+
currentSessionId: currentSessionId ?? undefined,
|
|
66
|
+
unreadSessionIds: unreadRows.map(r => ({ sessionId: r.session_id, projectId: r.project_id }))
|
|
58
67
|
};
|
|
59
68
|
})
|
|
60
69
|
|
|
@@ -324,4 +333,27 @@ export const crudHandler = createRouter()
|
|
|
324
333
|
const userId = ws.getUserId(conn);
|
|
325
334
|
projectQueries.setCurrentSessionId(userId, projectId, data.sessionId);
|
|
326
335
|
debug.log('session', `User ${userId} set current session to ${data.sessionId} in project ${projectId}`);
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
// Mark a session as read for the current user
|
|
339
|
+
.on('sessions:mark-read', {
|
|
340
|
+
data: t.Object({
|
|
341
|
+
sessionId: t.String()
|
|
342
|
+
})
|
|
343
|
+
}, async ({ data, conn }) => {
|
|
344
|
+
const userId = ws.getUserId(conn);
|
|
345
|
+
sessionQueries.markRead(userId, data.sessionId);
|
|
346
|
+
debug.log('session', `[unread] Marked session ${data.sessionId} as READ for user ${userId}`);
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
// Mark a session as unread for the current user
|
|
350
|
+
.on('sessions:mark-unread', {
|
|
351
|
+
data: t.Object({
|
|
352
|
+
sessionId: t.String(),
|
|
353
|
+
projectId: t.String()
|
|
354
|
+
})
|
|
355
|
+
}, async ({ data, conn }) => {
|
|
356
|
+
const userId = ws.getUserId(conn);
|
|
357
|
+
sessionQueries.markUnread(userId, data.sessionId, data.projectId);
|
|
358
|
+
debug.log('session', `[unread] Marked session ${data.sessionId} as UNREAD for user ${userId} in project ${data.projectId}`);
|
|
327
359
|
});
|
package/backend/ws/user/crud.ts
CHANGED
|
@@ -108,7 +108,8 @@ export const crudHandler = createRouter()
|
|
|
108
108
|
response: t.Object({
|
|
109
109
|
currentProjectId: t.Union([t.String(), t.Null()]),
|
|
110
110
|
lastView: t.Union([t.String(), t.Null()]),
|
|
111
|
-
settings: t.Union([t.Any(), t.Null()])
|
|
111
|
+
settings: t.Union([t.Any(), t.Null()]),
|
|
112
|
+
unreadSessions: t.Union([t.Any(), t.Null()])
|
|
112
113
|
})
|
|
113
114
|
}, async ({ conn }) => {
|
|
114
115
|
const userId = ws.getUserId(conn);
|
|
@@ -116,17 +117,20 @@ export const crudHandler = createRouter()
|
|
|
116
117
|
const currentProjectId = getUserState(userId, 'currentProjectId') as string | null;
|
|
117
118
|
const lastView = getUserState(userId, 'lastView') as string | null;
|
|
118
119
|
const userSettings = getUserState(userId, 'settings');
|
|
120
|
+
const unreadSessions = getUserState(userId, 'unreadSessions');
|
|
119
121
|
|
|
120
122
|
debug.log('user', `Restored state for ${userId}:`, {
|
|
121
123
|
currentProjectId,
|
|
122
124
|
lastView,
|
|
123
|
-
hasSettings: !!userSettings
|
|
125
|
+
hasSettings: !!userSettings,
|
|
126
|
+
unreadSessionsCount: unreadSessions ? Object.keys(unreadSessions).length : 0
|
|
124
127
|
});
|
|
125
128
|
|
|
126
129
|
return {
|
|
127
130
|
currentProjectId: currentProjectId ?? null,
|
|
128
131
|
lastView: lastView ?? null,
|
|
129
|
-
settings: userSettings ?? null
|
|
132
|
+
settings: userSettings ?? null,
|
|
133
|
+
unreadSessions: unreadSessions ?? null
|
|
130
134
|
};
|
|
131
135
|
})
|
|
132
136
|
|
|
@@ -143,7 +147,7 @@ export const crudHandler = createRouter()
|
|
|
143
147
|
const userId = ws.getUserId(conn);
|
|
144
148
|
|
|
145
149
|
// Validate allowed keys to prevent arbitrary data storage
|
|
146
|
-
const allowedKeys = ['currentProjectId', 'lastView', 'settings'];
|
|
150
|
+
const allowedKeys = ['currentProjectId', 'lastView', 'settings', 'unreadSessions'];
|
|
147
151
|
if (!allowedKeys.includes(data.key)) {
|
|
148
152
|
throw new Error(`Invalid state key: ${data.key}. Allowed: ${allowedKeys.join(', ')}`);
|
|
149
153
|
}
|
package/bun.lock
CHANGED
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
14
14
|
"@monaco-editor/loader": "^1.5.0",
|
|
15
15
|
"@opencode-ai/sdk": "^1.2.15",
|
|
16
|
-
"@xterm/addon-
|
|
17
|
-
"@xterm/addon-
|
|
16
|
+
"@xterm/addon-clipboard": "^0.2.0",
|
|
17
|
+
"@xterm/addon-fit": "^0.11.0",
|
|
18
|
+
"@xterm/addon-ligatures": "^0.10.0",
|
|
19
|
+
"@xterm/addon-unicode11": "^0.9.0",
|
|
20
|
+
"@xterm/addon-web-links": "^0.12.0",
|
|
21
|
+
"@xterm/xterm": "^6.0.0",
|
|
18
22
|
"bun-pty": "^0.4.2",
|
|
19
23
|
"cloudflared": "^0.7.1",
|
|
20
24
|
"elysia": "^1.4.19",
|
|
@@ -26,7 +30,6 @@
|
|
|
26
30
|
"puppeteer": "^24.33.0",
|
|
27
31
|
"puppeteer-cluster": "^0.25.0",
|
|
28
32
|
"qrcode": "^1.5.4",
|
|
29
|
-
"xterm": "^5.3.0",
|
|
30
33
|
},
|
|
31
34
|
"devDependencies": {
|
|
32
35
|
"@eslint/js": "^9.31.0",
|
|
@@ -35,7 +38,6 @@
|
|
|
35
38
|
"@types/bun": "^1.2.18",
|
|
36
39
|
"@types/node": "^24.0.14",
|
|
37
40
|
"@types/qrcode": "^1.5.6",
|
|
38
|
-
"@types/xterm": "^3.0.0",
|
|
39
41
|
"concurrently": "^9.2.1",
|
|
40
42
|
"eslint": "^9.31.0",
|
|
41
43
|
"eslint-plugin-svelte": "^3.10.1",
|
|
@@ -310,8 +312,6 @@
|
|
|
310
312
|
|
|
311
313
|
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
|
|
312
314
|
|
|
313
|
-
"@types/xterm": ["@types/xterm@3.0.0", "", { "dependencies": { "xterm": "*" } }, "sha512-+VaAJQmE7E1d1ebkIh/Zdc2mbXBVwxZGGSgqwzDPpk/HKo0mNT+iX5ZrnswztHSV+CDV+bURl7Yg7PWF7IZfXQ=="],
|
|
314
|
-
|
|
315
315
|
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
|
|
316
316
|
|
|
317
317
|
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.56.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/type-utils": "8.56.1", "@typescript-eslint/utils": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.56.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A=="],
|
|
@@ -334,11 +334,17 @@
|
|
|
334
334
|
|
|
335
335
|
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw=="],
|
|
336
336
|
|
|
337
|
-
"@xterm/addon-
|
|
337
|
+
"@xterm/addon-clipboard": ["@xterm/addon-clipboard@0.2.0", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-Dl31BCtBhLaUEECUbEiVcCLvLBbaeGYdT7NofB8OJkGTD3MWgBsaLjXvfGAD4tQNHhm6mbKyYkR7XD8kiZsdNg=="],
|
|
338
|
+
|
|
339
|
+
"@xterm/addon-fit": ["@xterm/addon-fit@0.11.0", "", {}, "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g=="],
|
|
340
|
+
|
|
341
|
+
"@xterm/addon-ligatures": ["@xterm/addon-ligatures@0.10.0", "", { "dependencies": { "font-finder": "^1.1.0", "font-ligatures": "^1.4.1" } }, "sha512-/Few8ZSHMib7sGjRJoc5l7bCtEB9XJfkNofvPpOcWADxKaUl8og8P172j67OoACSNJAXqeCLIuvj8WFCBkcTxg=="],
|
|
342
|
+
|
|
343
|
+
"@xterm/addon-unicode11": ["@xterm/addon-unicode11@0.9.0", "", {}, "sha512-FxDnYcyuXhNl+XSqGZL/t0U9eiNb/q3EWT5rYkQT/zuig8Gz/VagnQANKHdDWFM2lTMk9ly0EFQxxxtZUoRetw=="],
|
|
338
344
|
|
|
339
|
-
"@xterm/addon-web-links": ["@xterm/addon-web-links@0.
|
|
345
|
+
"@xterm/addon-web-links": ["@xterm/addon-web-links@0.12.0", "", {}, "sha512-4Smom3RPyVp7ZMYOYDoC/9eGJJJqYhnPLGGqJ6wOBfB8VxPViJNSKdgRYb8NpaM6YSelEKbA2SStD7lGyqaobw=="],
|
|
340
346
|
|
|
341
|
-
"@xterm/xterm": ["@xterm/xterm@
|
|
347
|
+
"@xterm/xterm": ["@xterm/xterm@6.0.0", "", {}, "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg=="],
|
|
342
348
|
|
|
343
349
|
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
|
344
350
|
|
|
@@ -562,6 +568,10 @@
|
|
|
562
568
|
|
|
563
569
|
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
|
|
564
570
|
|
|
571
|
+
"font-finder": ["font-finder@1.1.0", "", { "dependencies": { "get-system-fonts": "^2.0.0", "promise-stream-reader": "^1.0.1" } }, "sha512-wpCL2uIbi6GurJbU7ZlQ3nGd61Ho+dSU6U83/xJT5UPFfN35EeCW/rOtS+5k+IuEZu2SYmHzDIPL9eA5tSYRAw=="],
|
|
572
|
+
|
|
573
|
+
"font-ligatures": ["font-ligatures@1.4.1", "", { "dependencies": { "font-finder": "^1.0.3", "lru-cache": "^6.0.0", "opentype.js": "^0.8.0" } }, "sha512-7W6zlfyhvCqShZ5ReUWqmSd9vBaUudW0Hxis+tqUjtHhsPU+L3Grf8mcZAtCiXHTzorhwdRTId2WeH/88gdFkw=="],
|
|
574
|
+
|
|
565
575
|
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
|
566
576
|
|
|
567
577
|
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
|
@@ -578,6 +588,8 @@
|
|
|
578
588
|
|
|
579
589
|
"get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
|
|
580
590
|
|
|
591
|
+
"get-system-fonts": ["get-system-fonts@2.0.2", "", {}, "sha512-zzlgaYnHMIEgHRrfC7x0Qp0Ylhw/sHpM6MHXeVBTYIsvGf5GpbnClB+Q6rAPdn+0gd2oZZIo6Tj3EaWrt4VhDQ=="],
|
|
592
|
+
|
|
581
593
|
"get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="],
|
|
582
594
|
|
|
583
595
|
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
|
@@ -638,6 +650,8 @@
|
|
|
638
650
|
|
|
639
651
|
"jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
|
|
640
652
|
|
|
653
|
+
"js-base64": ["js-base64@3.7.8", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="],
|
|
654
|
+
|
|
641
655
|
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
|
642
656
|
|
|
643
657
|
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
|
@@ -694,7 +708,7 @@
|
|
|
694
708
|
|
|
695
709
|
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
|
696
710
|
|
|
697
|
-
"lru-cache": ["lru-cache@
|
|
711
|
+
"lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
|
|
698
712
|
|
|
699
713
|
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
|
700
714
|
|
|
@@ -742,6 +756,8 @@
|
|
|
742
756
|
|
|
743
757
|
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
|
|
744
758
|
|
|
759
|
+
"opentype.js": ["opentype.js@0.8.0", "", { "dependencies": { "tiny-inflate": "^1.0.2" }, "bin": { "ot": "./bin/ot" } }, "sha512-FQHR4oGP+a0m/f6yHoRpBOIbn/5ZWxKd4D/djHVJu8+KpBTYrJda0b7mLcgDEMWXE9xBCJm+qb0yv6FcvPjukg=="],
|
|
760
|
+
|
|
745
761
|
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
|
746
762
|
|
|
747
763
|
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
|
@@ -790,6 +806,8 @@
|
|
|
790
806
|
|
|
791
807
|
"progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],
|
|
792
808
|
|
|
809
|
+
"promise-stream-reader": ["promise-stream-reader@1.0.1", "", {}, "sha512-Tnxit5trUjBAqqZCGWwjyxhmgMN4hGrtpW3Oc/tRI4bpm/O2+ej72BB08l6JBnGQgVDGCLvHFGjGgQS6vzhwXg=="],
|
|
810
|
+
|
|
793
811
|
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
|
|
794
812
|
|
|
795
813
|
"proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="],
|
|
@@ -904,6 +922,8 @@
|
|
|
904
922
|
|
|
905
923
|
"text-extensions": ["text-extensions@3.1.0", "", {}, "sha512-anOjtXr8OT5w4vc/2mP4AYTCE0GWc/21icGmaHtBHnI7pN7o01a/oqG9m06/rGzoAsDm/WNzggBpqptuCmRlZQ=="],
|
|
906
924
|
|
|
925
|
+
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
|
|
926
|
+
|
|
907
927
|
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
|
908
928
|
|
|
909
929
|
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
|
@@ -958,10 +978,10 @@
|
|
|
958
978
|
|
|
959
979
|
"ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
|
|
960
980
|
|
|
961
|
-
"xterm": ["xterm@5.3.0", "", {}, "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg=="],
|
|
962
|
-
|
|
963
981
|
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
|
964
982
|
|
|
983
|
+
"yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
|
|
984
|
+
|
|
965
985
|
"yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="],
|
|
966
986
|
|
|
967
987
|
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
|
@@ -1012,6 +1032,8 @@
|
|
|
1012
1032
|
|
|
1013
1033
|
"postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
|
1014
1034
|
|
|
1035
|
+
"proxy-agent/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
|
|
1036
|
+
|
|
1015
1037
|
"qrcode/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
|
|
1016
1038
|
|
|
1017
1039
|
"@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
indentGuide: '#21262d',
|
|
59
59
|
indentGuideActive: '#30363d',
|
|
60
60
|
ruler: '#21262d',
|
|
61
|
-
scrollbar: '#
|
|
62
|
-
scrollbarHover: '#
|
|
63
|
-
scrollbarActive: '#
|
|
61
|
+
scrollbar: '#6e768140',
|
|
62
|
+
scrollbarHover: '#6e768180',
|
|
63
|
+
scrollbarActive: '#8b949e'
|
|
64
64
|
},
|
|
65
65
|
tokens: {
|
|
66
66
|
comment: '6A9955',
|
|
@@ -87,9 +87,9 @@
|
|
|
87
87
|
indentGuide: '#e3e3e3',
|
|
88
88
|
indentGuideActive: '#d3d3d3',
|
|
89
89
|
ruler: '#e3e3e3',
|
|
90
|
-
scrollbar: '#
|
|
91
|
-
scrollbarHover: '#
|
|
92
|
-
scrollbarActive: '#
|
|
90
|
+
scrollbar: '#92929240',
|
|
91
|
+
scrollbarHover: '#92929280',
|
|
92
|
+
scrollbarActive: '#555555'
|
|
93
93
|
},
|
|
94
94
|
tokens: {
|
|
95
95
|
comment: '008000',
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { settings } from '$frontend/lib/stores/features/settings.svelte';
|
|
15
15
|
|
|
16
16
|
// Import CSS directly - Vite will handle it properly
|
|
17
|
-
import 'xterm/css/xterm.css';
|
|
17
|
+
import '@xterm/xterm/css/xterm.css';
|
|
18
18
|
|
|
19
19
|
// Props
|
|
20
20
|
const {
|
|
@@ -183,127 +183,39 @@
|
|
|
183
183
|
};
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
// Handle right-click copy/paste
|
|
187
|
-
function
|
|
186
|
+
// Handle right-click copy/paste via clipboard addon
|
|
187
|
+
function setupClipboardHandling() {
|
|
188
188
|
if (!terminalContainer || !xtermService.terminal) return;
|
|
189
189
|
|
|
190
|
-
const
|
|
191
|
-
event.preventDefault();
|
|
192
|
-
|
|
193
|
-
// Get selected text from xterm.js
|
|
190
|
+
const handleContextMenu = async (event: MouseEvent) => {
|
|
191
|
+
event.preventDefault();
|
|
192
|
+
|
|
194
193
|
const selectedText = xtermService.getSelectedText();
|
|
195
|
-
|
|
196
|
-
if (selectedText
|
|
194
|
+
|
|
195
|
+
if (selectedText?.trim()) {
|
|
197
196
|
// Copy selected text to clipboard
|
|
198
197
|
try {
|
|
199
198
|
await navigator.clipboard.writeText(selectedText);
|
|
200
|
-
|
|
201
|
-
// Clear selection after copy (like most terminals do)
|
|
202
199
|
xtermService.clearSelection();
|
|
203
|
-
|
|
204
|
-
// Show brief visual feedback
|
|
205
|
-
showCopyFeedback();
|
|
206
|
-
} catch (err) {
|
|
207
|
-
}
|
|
200
|
+
} catch { /* clipboard not available */ }
|
|
208
201
|
} else {
|
|
209
|
-
// No text selected - paste from clipboard
|
|
202
|
+
// No text selected - paste from clipboard
|
|
210
203
|
try {
|
|
211
|
-
const
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
// Use terminal's built-in paste functionality
|
|
215
|
-
// This simulates typing each character through the input handler
|
|
216
|
-
if ((xtermService as any).inputHandler) {
|
|
217
|
-
// Process each character through the input handler
|
|
218
|
-
for (const char of clipboardText) {
|
|
219
|
-
// Skip newlines - let user decide when to execute
|
|
220
|
-
if (char !== '\n' && char !== '\r') {
|
|
221
|
-
(xtermService as any).inputHandler(char);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Show brief visual feedback
|
|
227
|
-
showPasteFeedback();
|
|
204
|
+
const text = await navigator.clipboard.readText();
|
|
205
|
+
if (text?.trim()) {
|
|
206
|
+
xtermService.pasteText(text);
|
|
228
207
|
}
|
|
229
|
-
} catch {
|
|
230
|
-
// paste not supported
|
|
231
|
-
}
|
|
208
|
+
} catch { /* clipboard not available */ }
|
|
232
209
|
}
|
|
233
210
|
};
|
|
234
211
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
212
|
+
terminalContainer.addEventListener('contextmenu', handleContextMenu);
|
|
213
|
+
|
|
238
214
|
return () => {
|
|
239
|
-
terminalContainer?.removeEventListener('contextmenu',
|
|
215
|
+
terminalContainer?.removeEventListener('contextmenu', handleContextMenu);
|
|
240
216
|
};
|
|
241
217
|
}
|
|
242
218
|
|
|
243
|
-
// Show brief visual feedback for copy action
|
|
244
|
-
function showCopyFeedback() {
|
|
245
|
-
if (!terminalContainer) return;
|
|
246
|
-
|
|
247
|
-
// Create temporary feedback element
|
|
248
|
-
const feedback = document.createElement('div');
|
|
249
|
-
feedback.textContent = 'Copied!';
|
|
250
|
-
feedback.style.cssText = `
|
|
251
|
-
position: absolute;
|
|
252
|
-
top: 10px;
|
|
253
|
-
right: 10px;
|
|
254
|
-
background: rgb(34 197 94 / 0.9);
|
|
255
|
-
color: white;
|
|
256
|
-
padding: 4px 8px;
|
|
257
|
-
border-radius: 4px;
|
|
258
|
-
font-size: 12px;
|
|
259
|
-
font-family: system-ui, sans-serif;
|
|
260
|
-
z-index: 1000;
|
|
261
|
-
pointer-events: none;
|
|
262
|
-
`;
|
|
263
|
-
|
|
264
|
-
terminalContainer.style.position = 'relative';
|
|
265
|
-
terminalContainer.appendChild(feedback);
|
|
266
|
-
|
|
267
|
-
// Remove feedback after 1 second
|
|
268
|
-
setTimeout(() => {
|
|
269
|
-
if (feedback.parentNode) {
|
|
270
|
-
feedback.parentNode.removeChild(feedback);
|
|
271
|
-
}
|
|
272
|
-
}, 1000);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Show brief visual feedback for paste action
|
|
276
|
-
function showPasteFeedback() {
|
|
277
|
-
if (!terminalContainer) return;
|
|
278
|
-
|
|
279
|
-
// Create temporary feedback element
|
|
280
|
-
const feedback = document.createElement('div');
|
|
281
|
-
feedback.textContent = 'Pasted!';
|
|
282
|
-
feedback.style.cssText = `
|
|
283
|
-
position: absolute;
|
|
284
|
-
top: 10px;
|
|
285
|
-
right: 10px;
|
|
286
|
-
background: rgba(59, 130, 246, 0.9);
|
|
287
|
-
color: white;
|
|
288
|
-
padding: 4px 8px;
|
|
289
|
-
border-radius: 4px;
|
|
290
|
-
font-size: 12px;
|
|
291
|
-
font-family: system-ui, sans-serif;
|
|
292
|
-
z-index: 1000;
|
|
293
|
-
pointer-events: none;
|
|
294
|
-
`;
|
|
295
|
-
|
|
296
|
-
terminalContainer.style.position = 'relative';
|
|
297
|
-
terminalContainer.appendChild(feedback);
|
|
298
|
-
|
|
299
|
-
// Remove feedback after 1 second
|
|
300
|
-
setTimeout(() => {
|
|
301
|
-
if (feedback.parentNode) {
|
|
302
|
-
feedback.parentNode.removeChild(feedback);
|
|
303
|
-
}
|
|
304
|
-
}, 1000);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
219
|
// Handle theme changes
|
|
308
220
|
function setupThemeHandling() {
|
|
309
221
|
xtermService.updateTheme();
|
|
@@ -404,7 +316,9 @@
|
|
|
404
316
|
$effect(() => {
|
|
405
317
|
const size = settings.fontSize;
|
|
406
318
|
if (isInitialized) {
|
|
407
|
-
|
|
319
|
+
const fontSize = Math.round(size * 0.9);
|
|
320
|
+
const lineHeight = Math.round(size * 0.9);
|
|
321
|
+
xtermService.updateFontSize(fontSize, lineHeight, session?.id);
|
|
408
322
|
}
|
|
409
323
|
});
|
|
410
324
|
|
|
@@ -656,12 +570,12 @@
|
|
|
656
570
|
|
|
657
571
|
const cleanupResize = setupResizeHandling();
|
|
658
572
|
const cleanupTheme = setupThemeHandling();
|
|
659
|
-
const
|
|
573
|
+
const cleanupClipboard = setupClipboardHandling();
|
|
660
574
|
|
|
661
575
|
return () => {
|
|
662
576
|
cleanupResize();
|
|
663
577
|
cleanupTheme();
|
|
664
|
-
|
|
578
|
+
cleanupClipboard?.();
|
|
665
579
|
};
|
|
666
580
|
});
|
|
667
581
|
|
|
@@ -711,6 +625,11 @@
|
|
|
711
625
|
export function clearSelection() {
|
|
712
626
|
xtermService.clearSelection();
|
|
713
627
|
}
|
|
628
|
+
|
|
629
|
+
export function pasteText(text: string) {
|
|
630
|
+
xtermService.pasteText(text);
|
|
631
|
+
}
|
|
632
|
+
|
|
714
633
|
</script>
|
|
715
634
|
|
|
716
635
|
<!-- Pure xterm.js terminal container -->
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Centralized xterm.js configuration and utilities
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { ITerminalOptions } from 'xterm';
|
|
7
|
+
import type { ITerminalOptions } from '@xterm/xterm';
|
|
8
8
|
|
|
9
9
|
// Terminal theme configuration
|
|
10
10
|
export const terminalConfig: ITerminalOptions = {
|
|
@@ -40,7 +40,7 @@ export const terminalConfig: ITerminalOptions = {
|
|
|
40
40
|
convertEol: true,
|
|
41
41
|
scrollback: 1000,
|
|
42
42
|
tabStopWidth: 4,
|
|
43
|
-
allowProposedApi:
|
|
43
|
+
allowProposedApi: true,
|
|
44
44
|
altClickMovesCursor: true,
|
|
45
45
|
disableStdin: false, // ✅ ENABLED for interactive PTY mode - stdin forwards to backend
|
|
46
46
|
allowTransparency: false
|