@myrialabs/clopen 0.2.12 → 0.2.14
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/chat/stream-manager.ts +3 -0
- package/backend/engine/adapters/claude/stream.ts +2 -1
- package/backend/engine/types.ts +9 -0
- package/backend/mcp/config.ts +32 -6
- package/backend/snapshot/snapshot-service.ts +9 -7
- package/backend/terminal/stream-manager.ts +106 -155
- package/backend/ws/projects/crud.ts +3 -3
- package/backend/ws/snapshot/timeline.ts +6 -2
- package/backend/ws/terminal/persistence.ts +19 -33
- package/backend/ws/terminal/session.ts +37 -19
- package/bin/clopen.ts +376 -99
- package/bun.lock +6 -0
- package/frontend/components/chat/input/ChatInput.svelte +8 -0
- package/frontend/components/chat/input/components/LoadingIndicator.svelte +2 -2
- package/frontend/components/chat/input/composables/use-animations.svelte.ts +127 -111
- package/frontend/components/chat/input/composables/use-textarea-resize.svelte.ts +11 -1
- package/frontend/components/chat/widgets/FloatingTodoList.svelte +2 -2
- package/frontend/components/checkpoint/TimelineModal.svelte +3 -1
- package/frontend/components/common/overlay/Dialog.svelte +2 -2
- package/frontend/components/git/ChangesSection.svelte +104 -13
- package/frontend/components/preview/browser/BrowserPreview.svelte +7 -0
- package/frontend/components/preview/browser/components/Canvas.svelte +8 -0
- package/frontend/components/settings/engines/AIEnginesSettings.svelte +1 -1
- package/frontend/components/settings/general/AuthModeSettings.svelte +2 -2
- package/frontend/components/terminal/Terminal.svelte +5 -1
- package/frontend/components/tunnel/TunnelInactive.svelte +4 -4
- package/frontend/services/chat/chat.service.ts +52 -11
- package/frontend/services/terminal/project.service.ts +4 -60
- package/frontend/services/terminal/terminal.service.ts +18 -27
- package/frontend/stores/core/sessions.svelte.ts +6 -0
- package/frontend/stores/ui/settings-modal.svelte.ts +1 -1
- package/frontend/stores/ui/theme.svelte.ts +11 -11
- package/frontend/stores/ui/workspace.svelte.ts +1 -1
- package/index.html +2 -2
- package/package.json +4 -2
- package/shared/utils/anonymous-user.ts +4 -4
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// Info box visibility - load from localStorage
|
|
16
16
|
let showInfoBox = $state(
|
|
17
17
|
typeof window !== 'undefined'
|
|
18
|
-
? localStorage.getItem('tunnel-info-dismissed') !== 'true'
|
|
18
|
+
? localStorage.getItem('clopen-tunnel-info-dismissed') !== 'true'
|
|
19
19
|
: true
|
|
20
20
|
);
|
|
21
21
|
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
|
|
33
33
|
// Save "don't show again" preference
|
|
34
34
|
if (dontShowWarningAgain) {
|
|
35
|
-
localStorage.setItem('tunnel-warning-dismissed', 'true');
|
|
35
|
+
localStorage.setItem('clopen-tunnel-warning-dismissed', 'true');
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
// Close modal immediately
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
warningDismissed = false;
|
|
60
60
|
|
|
61
61
|
// Check if user has dismissed warning permanently
|
|
62
|
-
const securityWarningDismissed = localStorage.getItem('tunnel-warning-dismissed') === 'true';
|
|
62
|
+
const securityWarningDismissed = localStorage.getItem('clopen-tunnel-warning-dismissed') === 'true';
|
|
63
63
|
if (securityWarningDismissed) {
|
|
64
64
|
// Skip warning and start tunnel directly
|
|
65
65
|
handleStartTunnel();
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
|
|
71
71
|
function closeInfoBox() {
|
|
72
72
|
showInfoBox = false;
|
|
73
|
-
localStorage.setItem('tunnel-info-dismissed', 'true');
|
|
73
|
+
localStorage.setItem('clopen-tunnel-info-dismissed', 'true');
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
function dismissError() {
|
|
@@ -43,16 +43,14 @@ class ChatService {
|
|
|
43
43
|
|
|
44
44
|
static loadingTexts: string[] = [
|
|
45
45
|
'thinking', 'processing', 'analyzing', 'calculating', 'computing',
|
|
46
|
-
'strategizing', 'learningpatterns', '
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'bootingreasoning', 'activatingmodules', 'triggeringaction', 'deployinglogic',
|
|
55
|
-
'maintainingstate', 'clearingcache', 'updating', 'reflecting', 'syncinglogic',
|
|
46
|
+
'strategizing', 'learningpatterns', 'adaptingmodels', 'evaluatingoptions',
|
|
47
|
+
'executingplans', 'simulatingscenarios', 'predictingoutcomes', 'planningactions',
|
|
48
|
+
'processinginputs', 'optimizing', 'generatingresponses', 'refininglogic',
|
|
49
|
+
'validatingoutputs', 'modulatingresponse', 'updatingmemory', 'recognizingpatterns',
|
|
50
|
+
'switchingcontext', 'allocatingresources', 'prioritizingtasks',
|
|
51
|
+
'developingawareness', 'buildingstrategies', 'assessingscenarios',
|
|
52
|
+
'bootingreasoning', 'triggeringaction', 'deployinglogic', 'synthesizinginformation',
|
|
53
|
+
'maintainingstate', 'updating', 'reflecting', 'syncinglogic',
|
|
56
54
|
'connectingdots', 'compilingideas', 'brainstorming', 'schedulingtasks'
|
|
57
55
|
].map(text => text + '...');
|
|
58
56
|
|
|
@@ -61,7 +59,50 @@ class ChatService {
|
|
|
61
59
|
'Create a full-stack e-commerce platform with Next.js, Stripe, and PostgreSQL',
|
|
62
60
|
'Build a real-time chat application using Socket.io with room support and typing indicators',
|
|
63
61
|
'Create a SaaS dashboard with user management, billing, and analytics',
|
|
64
|
-
|
|
62
|
+
'Build a REST API with authentication, rate limiting, and Swagger documentation',
|
|
63
|
+
'Create a CLI tool in TypeScript that scaffolds new projects with custom templates',
|
|
64
|
+
// Debugging & fixing
|
|
65
|
+
'Debug a memory leak in a Node.js service causing it to crash every 24 hours',
|
|
66
|
+
'Fix race conditions in a concurrent queue processor causing duplicate jobs',
|
|
67
|
+
'Fix a CORS issue blocking requests between a frontend and backend on different origins',
|
|
68
|
+
'Fix broken JWT refresh logic that logs users out unexpectedly',
|
|
69
|
+
'Fix flaky tests that pass locally but fail randomly in CI',
|
|
70
|
+
// Code review & refactoring
|
|
71
|
+
'Refactor a 1000-line monolithic function into clean, testable modules',
|
|
72
|
+
'Convert a class-based React codebase to functional components with hooks',
|
|
73
|
+
'Migrate a JavaScript project to TypeScript with strict mode enabled',
|
|
74
|
+
'Refactor database queries to use an ORM with proper migrations',
|
|
75
|
+
'Clean up and standardize error handling across an entire Express application',
|
|
76
|
+
// Writing tests
|
|
77
|
+
'Write unit tests for a payment processing module with 100% coverage',
|
|
78
|
+
'Add end-to-end tests using Playwright for a multi-step checkout flow',
|
|
79
|
+
'Set up integration tests for a REST API using a real test database',
|
|
80
|
+
'Write property-based tests to find edge cases in a data validation library',
|
|
81
|
+
'Set up test coverage reporting and enforce a minimum threshold in CI',
|
|
82
|
+
// Performance & optimization
|
|
83
|
+
'Optimize a slow PostgreSQL query that runs 10 seconds on a 5M-row table',
|
|
84
|
+
'Implement Redis caching to reduce database load by 80%',
|
|
85
|
+
'Reduce bundle size of a React app from 4MB to under 500KB',
|
|
86
|
+
'Profile and optimize a Python data pipeline processing 1M records per hour',
|
|
87
|
+
'Add lazy loading and virtualization to a list rendering 10,000 items',
|
|
88
|
+
// Architecture & design
|
|
89
|
+
'Design a scalable event-driven architecture using Kafka for a high-traffic app',
|
|
90
|
+
'Plan a migration from a monolith to microservices without downtime',
|
|
91
|
+
'Design a multi-tenant SaaS architecture with data isolation per customer',
|
|
92
|
+
'Create an authentication system supporting SSO, OAuth, and MFA',
|
|
93
|
+
'Architect a real-time notification system using WebSockets and a message queue',
|
|
94
|
+
// DevOps & infrastructure
|
|
95
|
+
'Write a Dockerfile and docker-compose setup for a full-stack app with hot reload',
|
|
96
|
+
'Set up a GitHub Actions CI/CD pipeline with testing, linting, and auto-deploy',
|
|
97
|
+
'Configure Nginx as a reverse proxy with SSL termination and load balancing',
|
|
98
|
+
'Create Terraform scripts to provision a production-ready AWS infrastructure',
|
|
99
|
+
'Set up monitoring and alerting using Prometheus and Grafana',
|
|
100
|
+
// AI & data
|
|
101
|
+
'Build a RAG pipeline using LangChain, embeddings, and a vector database',
|
|
102
|
+
'Create a sentiment analysis API using a fine-tuned transformer model',
|
|
103
|
+
'Build a data scraper that extracts and structures product data at scale',
|
|
104
|
+
'Implement a recommendation engine using collaborative filtering',
|
|
105
|
+
'Create a real-time data dashboard ingesting from multiple streaming sources',
|
|
65
106
|
];
|
|
66
107
|
|
|
67
108
|
constructor() {
|
|
@@ -262,66 +262,10 @@ class TerminalProjectManager {
|
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
// First, restore base output from context (input/output sebelumnya)
|
|
270
|
-
if (context.sessionOutputs.has(sessionId)) {
|
|
271
|
-
const savedOutput = context.sessionOutputs.get(sessionId);
|
|
272
|
-
if (savedOutput) {
|
|
273
|
-
baseOutput = savedOutput.map(output => ({
|
|
274
|
-
content: output.content,
|
|
275
|
-
type: output.type as any,
|
|
276
|
-
timestamp: output.timestamp
|
|
277
|
-
}));
|
|
278
|
-
// Restored base output lines for session
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Second, get NEW output from server that was generated while we were away
|
|
283
|
-
// We need to track when we saved the output to know what's new
|
|
284
|
-
if (hasActiveStream && activeStreamInfo) {
|
|
285
|
-
try {
|
|
286
|
-
// Get the saved output count from context metadata
|
|
287
|
-
// This tells us how much output we had when we switched away
|
|
288
|
-
let savedOutputCount = 0;
|
|
289
|
-
const savedMetadata = context.sessionOutputs.get(`${sessionId}-metadata`);
|
|
290
|
-
if (savedMetadata && typeof savedMetadata === 'object' && 'outputCount' in savedMetadata) {
|
|
291
|
-
savedOutputCount = (savedMetadata as any).outputCount || 0;
|
|
292
|
-
} else {
|
|
293
|
-
// Fallback: count actual output lines in baseOutput
|
|
294
|
-
for (const line of baseOutput) {
|
|
295
|
-
if (line.type === 'output' || line.type === 'error') {
|
|
296
|
-
savedOutputCount++;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Get only NEW output from server (skip what we already have)
|
|
302
|
-
const data = await terminalService.getMissedOutput(
|
|
303
|
-
sessionId,
|
|
304
|
-
activeStreamInfo.streamId,
|
|
305
|
-
savedOutputCount
|
|
306
|
-
);
|
|
307
|
-
if (data.success && data.output && data.output.length > 0) {
|
|
308
|
-
// Convert server output to terminal lines
|
|
309
|
-
backgroundOutput = data.output.map((content: string) => ({
|
|
310
|
-
content: content,
|
|
311
|
-
type: 'output',
|
|
312
|
-
timestamp: new Date()
|
|
313
|
-
}));
|
|
314
|
-
debug.log('terminal', `Restored ${backgroundOutput.length} new output lines for session ${sessionId}`);
|
|
315
|
-
}
|
|
316
|
-
} catch (error) {
|
|
317
|
-
debug.error('terminal', 'Failed to fetch missed output:', error);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Combine base output with background output from server
|
|
322
|
-
// Base output contains previous input/output saved in context
|
|
323
|
-
// Background output contains new output from server (if any)
|
|
324
|
-
terminalSession.lines = [...baseOutput, ...backgroundOutput];
|
|
265
|
+
// Always start with empty lines.
|
|
266
|
+
// The create-session replay from headless xterm will provide the
|
|
267
|
+
// accurate current terminal state (respects clear, scrollback, etc.)
|
|
268
|
+
terminalSession.lines = [];
|
|
325
269
|
|
|
326
270
|
// Restore command history from context (persisted) rather than manager (temporary)
|
|
327
271
|
const savedCommandHistory = context.sessionCommandHistories.get(sessionId);
|
|
@@ -62,20 +62,6 @@ export class TerminalService {
|
|
|
62
62
|
// Create unique stream ID for this connection
|
|
63
63
|
const streamId = `${sessionId}-${Date.now()}`;
|
|
64
64
|
|
|
65
|
-
// Get current output count to mark where new output starts
|
|
66
|
-
let outputStartIndex = 0;
|
|
67
|
-
if (typeof window !== 'undefined') {
|
|
68
|
-
try {
|
|
69
|
-
const terminalStoreModule = await import('$frontend/stores/features/terminal.svelte');
|
|
70
|
-
const termSession = terminalStoreModule.terminalStore.getSession(sessionId);
|
|
71
|
-
if (termSession && termSession.lines) {
|
|
72
|
-
outputStartIndex = termSession.lines.length;
|
|
73
|
-
}
|
|
74
|
-
} catch {
|
|
75
|
-
// Ignore error, use default 0
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
65
|
// Setup WebSocket listeners for this session
|
|
80
66
|
const listeners: Array<() => void> = [];
|
|
81
67
|
|
|
@@ -173,8 +159,7 @@ export class TerminalService {
|
|
|
173
159
|
workingDirectory: session.workingDirectory,
|
|
174
160
|
projectPath,
|
|
175
161
|
cols: terminalSize?.cols || 80,
|
|
176
|
-
rows: terminalSize?.rows || 24
|
|
177
|
-
outputStartIndex
|
|
162
|
+
rows: terminalSize?.rows || 24
|
|
178
163
|
});
|
|
179
164
|
|
|
180
165
|
debug.log('terminal', `✅ Terminal session created:`, response);
|
|
@@ -230,6 +215,17 @@ export class TerminalService {
|
|
|
230
215
|
}
|
|
231
216
|
}
|
|
232
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Clear headless terminal on backend (sync with frontend clear)
|
|
220
|
+
*/
|
|
221
|
+
async clearHeadlessTerminal(sessionId: string): Promise<void> {
|
|
222
|
+
try {
|
|
223
|
+
await ws.http('terminal:clear', { sessionId });
|
|
224
|
+
} catch {
|
|
225
|
+
// Silently handle - non-critical
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
233
229
|
/**
|
|
234
230
|
* Resize terminal for a specific session
|
|
235
231
|
*/
|
|
@@ -301,39 +297,34 @@ export class TerminalService {
|
|
|
301
297
|
}
|
|
302
298
|
|
|
303
299
|
/**
|
|
304
|
-
* Get missed output for a session
|
|
300
|
+
* Get missed output for a session (serialized terminal state)
|
|
305
301
|
*/
|
|
306
302
|
async getMissedOutput(
|
|
307
303
|
sessionId: string,
|
|
308
|
-
streamId?: string
|
|
309
|
-
fromIndex: number = 0
|
|
304
|
+
streamId?: string
|
|
310
305
|
): Promise<{
|
|
311
306
|
success: boolean;
|
|
312
|
-
output: string
|
|
313
|
-
outputCount: number;
|
|
307
|
+
output: string;
|
|
314
308
|
status: string;
|
|
315
309
|
}> {
|
|
316
310
|
try {
|
|
317
|
-
const data = await ws.http('terminal:missed-output', { sessionId, streamId
|
|
311
|
+
const data = await ws.http('terminal:missed-output', { sessionId, streamId }, 5000);
|
|
318
312
|
if (data.sessionId === sessionId) {
|
|
319
313
|
return {
|
|
320
314
|
success: true,
|
|
321
315
|
output: data.output,
|
|
322
|
-
outputCount: data.outputCount,
|
|
323
316
|
status: data.status
|
|
324
317
|
};
|
|
325
318
|
}
|
|
326
319
|
return {
|
|
327
320
|
success: false,
|
|
328
|
-
output:
|
|
329
|
-
outputCount: 0,
|
|
321
|
+
output: '',
|
|
330
322
|
status: 'invalid_session'
|
|
331
323
|
};
|
|
332
324
|
} catch {
|
|
333
325
|
return {
|
|
334
326
|
success: false,
|
|
335
|
-
output:
|
|
336
|
-
outputCount: 0,
|
|
327
|
+
output: '',
|
|
337
328
|
status: 'timeout'
|
|
338
329
|
};
|
|
339
330
|
}
|
|
@@ -436,6 +436,12 @@ export async function initializeSessions() {
|
|
|
436
436
|
setupCollaborativeListeners();
|
|
437
437
|
setupEditModeListener();
|
|
438
438
|
|
|
439
|
+
// Skip loading if no project is active — both calls require WS project context
|
|
440
|
+
if (!projectState.currentProject) {
|
|
441
|
+
debug.log('session', 'No active project, skipping session load');
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
439
445
|
// Load sessions and restore edit mode in parallel
|
|
440
446
|
// Both only need WS project context (already set by initializeProjects)
|
|
441
447
|
await Promise.all([
|
|
@@ -3,7 +3,7 @@ import type { Theme } from '$shared/types/ui';
|
|
|
3
3
|
// Theme store using Svelte 5 runes
|
|
4
4
|
export const themeStore = $state({
|
|
5
5
|
current: {
|
|
6
|
-
name: '
|
|
6
|
+
name: 'clopen-modern',
|
|
7
7
|
primary: '#D97757',
|
|
8
8
|
secondary: '#4F46E5',
|
|
9
9
|
background: '#F9FAFB',
|
|
@@ -26,7 +26,7 @@ export function isDarkMode() {
|
|
|
26
26
|
// Theme presets
|
|
27
27
|
export const themes: Theme[] = [
|
|
28
28
|
{
|
|
29
|
-
name: '
|
|
29
|
+
name: 'clopen-modern',
|
|
30
30
|
primary: '#D97757',
|
|
31
31
|
secondary: '#4F46E5',
|
|
32
32
|
background: '#F9FAFB',
|
|
@@ -34,7 +34,7 @@ export const themes: Theme[] = [
|
|
|
34
34
|
mode: 'light'
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
|
-
name: '
|
|
37
|
+
name: 'clopen-dark',
|
|
38
38
|
primary: '#D97757',
|
|
39
39
|
secondary: '#4F46E5',
|
|
40
40
|
background: '#111827',
|
|
@@ -83,7 +83,7 @@ export function setTheme(theme: Theme) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// Save to localStorage
|
|
86
|
-
localStorage.setItem('
|
|
86
|
+
localStorage.setItem('clopen-theme', JSON.stringify(theme));
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
// Helper function to update theme color meta tag
|
|
@@ -105,12 +105,12 @@ export function toggleDarkMode() {
|
|
|
105
105
|
const newMode: 'light' | 'dark' = themeStore.isDark ? 'light' : 'dark';
|
|
106
106
|
|
|
107
107
|
// Use predefined themes for consistency
|
|
108
|
-
const newTheme = newMode === 'dark' ? themes[1] : themes[0]; //
|
|
108
|
+
const newTheme = newMode === 'dark' ? themes[1] : themes[0]; // clopen-dark or clopen-modern
|
|
109
109
|
|
|
110
110
|
setTheme(newTheme);
|
|
111
111
|
|
|
112
112
|
// Mark as manual theme choice
|
|
113
|
-
localStorage.setItem('
|
|
113
|
+
localStorage.setItem('clopen-theme-manual', 'true');
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
export function initializeTheme() {
|
|
@@ -121,8 +121,8 @@ export function initializeTheme() {
|
|
|
121
121
|
themeStore.isSystemDark = isSystemDark;
|
|
122
122
|
|
|
123
123
|
// Check for saved theme preference
|
|
124
|
-
const savedTheme = localStorage.getItem('
|
|
125
|
-
const isManualTheme = localStorage.getItem('
|
|
124
|
+
const savedTheme = localStorage.getItem('clopen-theme');
|
|
125
|
+
const isManualTheme = localStorage.getItem('clopen-theme-manual') === 'true';
|
|
126
126
|
|
|
127
127
|
let initialTheme: Theme;
|
|
128
128
|
|
|
@@ -160,7 +160,7 @@ export function initializeTheme() {
|
|
|
160
160
|
themeStore.isSystemDark = e.matches;
|
|
161
161
|
|
|
162
162
|
// Only follow system if no manual theme was set
|
|
163
|
-
if (!localStorage.getItem('
|
|
163
|
+
if (!localStorage.getItem('clopen-theme-manual')) {
|
|
164
164
|
const newTheme = e.matches ? themes[1] : themes[0];
|
|
165
165
|
setTheme(newTheme);
|
|
166
166
|
}
|
|
@@ -169,11 +169,11 @@ export function initializeTheme() {
|
|
|
169
169
|
|
|
170
170
|
export function setManualTheme(theme: Theme) {
|
|
171
171
|
setTheme(theme);
|
|
172
|
-
localStorage.setItem('
|
|
172
|
+
localStorage.setItem('clopen-theme-manual', 'true');
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
export function useSystemTheme() {
|
|
176
|
-
localStorage.removeItem('
|
|
176
|
+
localStorage.removeItem('clopen-theme-manual');
|
|
177
177
|
const defaultTheme = themeStore.isSystemDark ? themes[1] : themes[0];
|
|
178
178
|
setTheme(defaultTheme);
|
|
179
179
|
}
|
|
@@ -751,7 +751,7 @@ export function setActiveMobilePanel(panelId: PanelId): void {
|
|
|
751
751
|
// PERSISTENCE
|
|
752
752
|
// ============================================
|
|
753
753
|
|
|
754
|
-
const STORAGE_KEY = '
|
|
754
|
+
const STORAGE_KEY = 'clopen-workspace-layout';
|
|
755
755
|
|
|
756
756
|
export function saveWorkspaceState(): void {
|
|
757
757
|
try {
|
package/index.html
CHANGED
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
(function() {
|
|
21
21
|
// Check for saved theme preference
|
|
22
22
|
try {
|
|
23
|
-
const savedTheme = localStorage.getItem('
|
|
24
|
-
const isManualTheme = localStorage.getItem('
|
|
23
|
+
const savedTheme = localStorage.getItem('clopen-theme');
|
|
24
|
+
const isManualTheme = localStorage.getItem('clopen-theme-manual') === 'true';
|
|
25
25
|
|
|
26
26
|
let isDark = false;
|
|
27
27
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myrialabs/clopen",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.14",
|
|
4
4
|
"description": "All-in-one web workspace for Claude Code & OpenCode — chat, terminal, git, browser preview, checkpoints, and real-time collaboration",
|
|
5
5
|
"author": "Myria Labs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -77,17 +77,19 @@
|
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"@anthropic-ai/claude-agent-sdk": "0.2.63",
|
|
79
79
|
"@anthropic-ai/sdk": "0.78.0",
|
|
80
|
-
"@opencode-ai/sdk": "1.2.15",
|
|
81
80
|
"@elysiajs/cors": "^1.4.0",
|
|
82
81
|
"@iconify-json/lucide": "^1.2.57",
|
|
83
82
|
"@iconify-json/material-icon-theme": "^1.2.16",
|
|
84
83
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
85
84
|
"@monaco-editor/loader": "^1.5.0",
|
|
85
|
+
"@opencode-ai/sdk": "1.2.15",
|
|
86
86
|
"@xterm/addon-clipboard": "^0.2.0",
|
|
87
87
|
"@xterm/addon-fit": "^0.11.0",
|
|
88
88
|
"@xterm/addon-ligatures": "^0.10.0",
|
|
89
|
+
"@xterm/addon-serialize": "^0.14.0",
|
|
89
90
|
"@xterm/addon-unicode11": "^0.9.0",
|
|
90
91
|
"@xterm/addon-web-links": "^0.12.0",
|
|
92
|
+
"@xterm/headless": "^6.0.0",
|
|
91
93
|
"@xterm/xterm": "^6.0.0",
|
|
92
94
|
"bun-pty": "^0.4.2",
|
|
93
95
|
"cloudflared": "^0.7.1",
|
|
@@ -56,7 +56,7 @@ export async function getOrCreateAnonymousUser(): Promise<AnonymousUser | null>
|
|
|
56
56
|
return null;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
const stored = localStorage.getItem('
|
|
59
|
+
const stored = localStorage.getItem('clopen-anonymous-user');
|
|
60
60
|
|
|
61
61
|
if (stored) {
|
|
62
62
|
try {
|
|
@@ -76,7 +76,7 @@ export async function getOrCreateAnonymousUser(): Promise<AnonymousUser | null>
|
|
|
76
76
|
const newUser = await generateAnonymousUserFromServer();
|
|
77
77
|
|
|
78
78
|
if (newUser) {
|
|
79
|
-
localStorage.setItem('
|
|
79
|
+
localStorage.setItem('clopen-anonymous-user', JSON.stringify(newUser));
|
|
80
80
|
return newUser;
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -93,7 +93,7 @@ export function getCurrentAnonymousUser(): AnonymousUser | null {
|
|
|
93
93
|
return null;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
const stored = localStorage.getItem('
|
|
96
|
+
const stored = localStorage.getItem('clopen-anonymous-user');
|
|
97
97
|
|
|
98
98
|
if (stored) {
|
|
99
99
|
try {
|
|
@@ -146,7 +146,7 @@ export async function updateAnonymousUserName(newName: string): Promise<Anonymou
|
|
|
146
146
|
});
|
|
147
147
|
|
|
148
148
|
// Save to localStorage
|
|
149
|
-
localStorage.setItem('
|
|
149
|
+
localStorage.setItem('clopen-anonymous-user', JSON.stringify(response));
|
|
150
150
|
debug.log('user', '✅ Updated user name:', response.name);
|
|
151
151
|
return response;
|
|
152
152
|
} catch (error) {
|