@allternit/gizzi-code 1.0.0 → 1.0.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/bin/gizzi +4 -0
- package/bin/gizzi-ink +5 -0
- package/package.json +137 -31
- package/src/Tool.ts +26 -0
- package/src/bootstrap/state.ts +165 -322
- package/src/commands.ts +209 -0
- package/src/context/QueuedMessageContext.tsx +63 -0
- package/src/context/fpsMetrics.tsx +30 -0
- package/src/context/mailbox.tsx +38 -0
- package/src/context/modalContext.tsx +58 -0
- package/src/context/notifications.tsx +240 -0
- package/src/context/overlayContext.tsx +151 -0
- package/src/context/promptOverlayContext.tsx +125 -0
- package/src/context/stats.tsx +220 -0
- package/src/context/voice.tsx +88 -0
- package/src/hooks/useCanUseTool.ts +11 -0
- package/src/hooks/useExitOnCtrlCD.ts +40 -187
- package/src/hooks/useExitOnCtrlCDWithKeybindings.ts +68 -0
- package/src/hooks/useKeybinding.ts +68 -209
- package/src/hooks/useTerminalSize.ts +31 -99
- package/src/hooks.ts +37 -0
- package/src/ink.ts +204 -0
- package/src/main.ts +7 -0
- package/src/types/command.ts +216 -0
- package/src/types/connectorText.ts +39 -0
- package/src/types/fileSuggestion.ts +15 -0
- package/src/types/generated/events_mono/claude_code/v1/claude_code_internal_event.ts +865 -0
- package/src/types/generated/events_mono/common/v1/auth.ts +100 -0
- package/src/types/generated/events_mono/growthbook/v1/growthbook_experiment_event.ts +223 -0
- package/src/types/generated/google/protobuf/timestamp.ts +187 -0
- package/src/types/hooks.ts +319 -0
- package/src/types/ids.ts +44 -0
- package/src/types/logs.ts +331 -0
- package/src/types/merged/index.ts +5 -0
- package/src/types/merged/message.ts +552 -0
- package/src/types/message.ts +927 -186
- package/src/types/messageQueueTypes.ts +21 -0
- package/src/types/notebook.ts +72 -0
- package/src/types/permissions.ts +447 -0
- package/src/types/plugin.ts +363 -0
- package/src/types/statusLine.ts +15 -0
- package/src/types/textInputTypes.ts +387 -0
- package/src/types/tools.ts +149 -0
- package/src/types/utils.ts +20 -0
- package/src/utils/advisor.ts +12 -0
- package/src/utils/api.ts +29 -0
- package/src/utils/array.ts +7 -0
- package/src/utils/attachments.ts +60 -0
- package/src/utils/auth.ts +3 -0
- package/src/utils/claudeInChrome/chromeNativeHost.ts +15 -0
- package/src/utils/claudeInChrome/common.ts +12 -0
- package/src/utils/claudeInChrome/mcpServer.ts +15 -0
- package/src/utils/computerUse/common.ts +19 -0
- package/src/utils/computerUse/mcpServer.ts +15 -0
- package/src/utils/config.ts +35 -0
- package/src/utils/context.ts +19 -0
- package/src/utils/cwd.ts +3 -0
- package/src/utils/debug.ts +3 -0
- package/src/utils/earlyInput.ts +7 -0
- package/src/utils/effort.ts +13 -0
- package/src/utils/embeddedTools.ts +12 -0
- package/src/utils/envUtils.ts +3 -0
- package/src/utils/errors.ts +3 -0
- package/src/utils/execFileNoThrow.ts +24 -0
- package/src/utils/feature-flags.ts +112 -0
- package/src/utils/file.ts +3 -0
- package/src/utils/fileHistory.ts +17 -0
- package/src/utils/fingerprint.ts +7 -0
- package/src/utils/format.ts +75 -305
- package/src/utils/frontmatterParser.ts +33 -0
- package/src/utils/fsOperations.ts +31 -0
- package/src/utils/fullscreen.ts +40 -350
- package/src/utils/git.ts +15 -0
- package/src/utils/imageResizer.ts +30 -0
- package/src/utils/lazySchema.ts +9 -0
- package/src/utils/log.ts +3 -0
- package/src/utils/markdownConfigLoader.ts +12 -0
- package/src/utils/messages.ts +13 -0
- package/src/utils/migration.ts +137 -0
- package/src/utils/model/providers.ts +15 -0
- package/src/utils/path.ts +3 -0
- package/src/utils/permissions/PermissionResult.ts +14 -0
- package/src/utils/permissions/autoModeState.ts +12 -0
- package/src/utils/permissions/filesystem.ts +12 -0
- package/src/utils/plugins/loadPluginOutputStyles.ts +12 -0
- package/src/utils/plugins/schemas.ts +25 -0
- package/src/utils/process.ts +16 -0
- package/src/utils/sandbox/sandbox-adapter.ts +19 -0
- package/src/utils/sessionStorage.ts +238 -472
- package/src/utils/settings/constants.ts +10 -0
- package/src/utils/settings/settings.ts +19 -0
- package/src/utils/settings/types.ts +18 -0
- package/src/utils/shell/shellToolUtils.ts +9 -0
- package/src/utils/sinks.ts +11 -0
- package/src/utils/sleep.ts +7 -0
- package/src/utils/sliceAnsi.ts +7 -0
- package/src/utils/slowOperations.ts +3 -0
- package/src/utils/startupProfiler.ts +11 -0
- package/src/utils/stringUtils.ts +106 -0
- package/src/utils/systemPromptType.ts +9 -0
- package/src/utils/teleport/api.ts +15 -0
- package/src/utils/textHighlighting.ts +16 -0
- package/src/utils/theme.ts +17 -0
- package/src/utils/thinking.ts +58 -0
- package/src/utils/tokens.ts +11 -0
- package/src/utils/toolResultStorage.ts +17 -0
- package/src/utils/worktreeModeEnabled.ts +7 -0
- package/WORKSPACE_INTEGRATION.md +0 -290
- package/chat-sessions/sessions-index.json +0 -98
- package/package.json.original +0 -31
- package/package.json.publish +0 -47
- package/src/bootstrap/state.d.ts +0 -169
- package/src/bootstrap/state.js +0 -261
- package/src/context/pack-builder.d.ts +0 -24
- package/src/context/pack-builder.js +0 -281
- package/src/hooks/useExitOnCtrlCD.d.ts +0 -86
- package/src/hooks/useExitOnCtrlCD.js +0 -130
- package/src/hooks/useKeybinding.d.ts +0 -89
- package/src/hooks/useKeybinding.js +0 -163
- package/src/hooks/useTerminalSize.d.ts +0 -41
- package/src/hooks/useTerminalSize.js +0 -90
- package/src/index.d.ts +0 -23
- package/src/index.js +0 -24
- package/src/runtime/brand/brand.d.ts +0 -488
- package/src/runtime/brand/brand.js +0 -464
- package/src/runtime/brand/brand.ts +0 -667
- package/src/runtime/services/tokenEstimation.d.ts +0 -187
- package/src/runtime/services/tokenEstimation.js +0 -423
- package/src/runtime/services/tokenEstimation.ts +0 -547
- package/src/runtime/tools/Tool.d.ts +0 -239
- package/src/runtime/tools/Tool.js +0 -285
- package/src/runtime/tools/Tool.ts +0 -524
- package/src/scheduler/TaskScheduler.d.ts +0 -98
- package/src/scheduler/TaskScheduler.js +0 -201
- package/src/scheduler/TaskScheduler.ts +0 -259
- package/src/state/AppStateStore.d.ts +0 -347
- package/src/state/AppStateStore.js +0 -439
- package/src/state/AppStateStore.ts +0 -658
- package/src/state/WorkspaceStateStore.d.ts +0 -43
- package/src/state/WorkspaceStateStore.js +0 -224
- package/src/state/WorkspaceStateStore.ts +0 -297
- package/src/types/message.d.ts +0 -190
- package/src/types/message.js +0 -77
- package/src/utils/format.d.ts +0 -126
- package/src/utils/format.js +0 -230
- package/src/utils/fullscreen.d.ts +0 -161
- package/src/utils/fullscreen.js +0 -277
- package/src/utils/sessionStorage.d.ts +0 -210
- package/src/utils/sessionStorage.js +0 -435
- package/src/watcher/WorkspaceWatcher.d.ts +0 -70
- package/src/watcher/WorkspaceWatcher.js +0 -191
- package/src/watcher/WorkspaceWatcher.ts +0 -251
- package/src/workspace/loader.d.ts +0 -30
- package/src/workspace/loader.js +0 -117
- package/src/workspace/types.d.ts +0 -91
- package/src/workspace/types.js +0 -7
- package/threads/thread_1775361175022.json +0 -9
- package/tsconfig.json +0 -14
|
@@ -1,559 +1,325 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Provides session persistence, storage management, and user type detection
|
|
6
|
-
* with safe fallbacks for environments without storage access.
|
|
2
|
+
* Session Storage
|
|
3
|
+
* Production-quality session management with filesystem persistence
|
|
7
4
|
*/
|
|
8
5
|
|
|
9
|
-
import
|
|
10
|
-
import
|
|
6
|
+
import { writeFile, readFile, mkdir, access, constants } from 'fs/promises'
|
|
7
|
+
import { join } from 'path'
|
|
8
|
+
import { homedir } from 'os'
|
|
9
|
+
import { log } from '../runtime/util/log.js'
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
* Storage keys used throughout the application
|
|
14
|
-
* @constant {Record<string, string>}
|
|
15
|
-
*/
|
|
16
|
-
export const STORAGE_KEYS = {
|
|
17
|
-
SESSION: 'gizzi_session',
|
|
18
|
-
USER_TYPE: 'gizzi_user_type',
|
|
19
|
-
MESSAGES: 'gizzi_messages',
|
|
20
|
-
PREFERENCES: 'gizzi_preferences',
|
|
21
|
-
LAST_ACTIVITY: 'gizzi_last_activity',
|
|
22
|
-
SESSION_METADATA: 'gizzi_session_metadata',
|
|
23
|
-
} as const;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* User type categories
|
|
27
|
-
* @typedef {'anonymous' | 'free' | 'pro' | 'enterprise' | 'admin'} UserType
|
|
28
|
-
*/
|
|
29
|
-
export type UserType = 'anonymous' | 'free' | 'pro' | 'enterprise' | 'admin';
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Valid user types for runtime validation
|
|
33
|
-
* @constant {readonly UserType[]}
|
|
34
|
-
*/
|
|
35
|
-
export const VALID_USER_TYPES: readonly UserType[] = [
|
|
36
|
-
'anonymous',
|
|
37
|
-
'free',
|
|
38
|
-
'pro',
|
|
39
|
-
'enterprise',
|
|
40
|
-
'admin',
|
|
41
|
-
] as const;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Session metadata stored alongside the session
|
|
45
|
-
* @interface SessionMetadata
|
|
46
|
-
*/
|
|
47
|
-
export interface SessionMetadata {
|
|
48
|
-
/** When the session was created */
|
|
49
|
-
readonly createdAt: string;
|
|
50
|
-
|
|
51
|
-
/** Last activity timestamp (ISO string) */
|
|
52
|
-
readonly lastActivity: string;
|
|
53
|
-
|
|
54
|
-
/** Number of messages in this session */
|
|
55
|
-
readonly messageCount: number;
|
|
11
|
+
export type UserType = 'anonymous' | 'authenticated' | 'admin' | 'ant' | 'beta'
|
|
56
12
|
|
|
57
|
-
|
|
58
|
-
|
|
13
|
+
export interface SessionData {
|
|
14
|
+
sessionId: string
|
|
15
|
+
userId?: string
|
|
16
|
+
userType: UserType
|
|
17
|
+
email?: string
|
|
18
|
+
accessToken?: string
|
|
19
|
+
refreshToken?: string
|
|
20
|
+
createdAt: number
|
|
21
|
+
lastActivity: number
|
|
22
|
+
expiresAt?: number
|
|
23
|
+
metadata?: Record<string, unknown>
|
|
24
|
+
}
|
|
59
25
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
26
|
+
export interface UserPreferences {
|
|
27
|
+
theme?: 'dark' | 'light' | 'system'
|
|
28
|
+
fontSize?: number
|
|
29
|
+
autoSave?: boolean
|
|
30
|
+
notifications?: boolean
|
|
31
|
+
telemetry?: boolean
|
|
32
|
+
[key: string]: unknown
|
|
66
33
|
}
|
|
67
34
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
35
|
+
export interface Message {
|
|
36
|
+
id: string
|
|
37
|
+
role: 'user' | 'assistant' | 'system'
|
|
38
|
+
content: string
|
|
39
|
+
timestamp: number
|
|
40
|
+
attachments?: Attachment[]
|
|
41
|
+
}
|
|
75
42
|
|
|
76
|
-
|
|
77
|
-
|
|
43
|
+
export interface Attachment {
|
|
44
|
+
id: string
|
|
45
|
+
filename: string
|
|
46
|
+
contentType: string
|
|
47
|
+
size: number
|
|
48
|
+
path: string
|
|
49
|
+
}
|
|
78
50
|
|
|
79
|
-
|
|
80
|
-
|
|
51
|
+
// In-memory cache
|
|
52
|
+
const memoryStorage = new Map<string, unknown>()
|
|
53
|
+
let currentSession: SessionData | null = null
|
|
54
|
+
let preferencesCache: UserPreferences | null = null
|
|
81
55
|
|
|
82
|
-
|
|
83
|
-
|
|
56
|
+
// Config directory
|
|
57
|
+
function getConfigDir(): string {
|
|
58
|
+
return join(homedir(), '.config', 'gizzi')
|
|
84
59
|
}
|
|
85
60
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
*/
|
|
90
|
-
export interface StorageResult<T> {
|
|
91
|
-
/** Whether the operation succeeded */
|
|
92
|
-
readonly success: boolean;
|
|
93
|
-
|
|
94
|
-
/** The data if successful, null otherwise */
|
|
95
|
-
readonly data: T | null;
|
|
61
|
+
function getSessionFilePath(): string {
|
|
62
|
+
return join(getConfigDir(), 'session.json')
|
|
63
|
+
}
|
|
96
64
|
|
|
97
|
-
|
|
98
|
-
|
|
65
|
+
function getPreferencesFilePath(): string {
|
|
66
|
+
return join(getConfigDir(), 'preferences.json')
|
|
99
67
|
}
|
|
100
68
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
* @returns {boolean} True if storage APIs are available
|
|
104
|
-
*/
|
|
105
|
-
function isStorageAvailable(): boolean {
|
|
106
|
-
try {
|
|
107
|
-
if (typeof window === 'undefined') {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
const testKey = '__gizzi_storage_test__';
|
|
111
|
-
window.sessionStorage.setItem(testKey, 'test');
|
|
112
|
-
window.sessionStorage.removeItem(testKey);
|
|
113
|
-
return true;
|
|
114
|
-
} catch {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
69
|
+
function getMessagesFilePath(): string {
|
|
70
|
+
return join(getConfigDir(), 'messages.json')
|
|
117
71
|
}
|
|
118
72
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
*
|
|
122
|
-
* @template T
|
|
123
|
-
* @param {string} json - JSON string to parse
|
|
124
|
-
* @param {T} [defaultValue] - Default value if parsing fails
|
|
125
|
-
* @returns {T | null} Parsed value or default/null on error
|
|
126
|
-
*/
|
|
127
|
-
function safeJsonParse<T>(json: string, defaultValue?: T): T | null {
|
|
73
|
+
async function ensureConfigDir(): Promise<void> {
|
|
74
|
+
const configDir = getConfigDir()
|
|
128
75
|
try {
|
|
129
|
-
|
|
76
|
+
await access(configDir, constants.F_OK)
|
|
130
77
|
} catch {
|
|
131
|
-
|
|
78
|
+
await mkdir(configDir, { recursive: true })
|
|
132
79
|
}
|
|
133
80
|
}
|
|
134
81
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
82
|
+
// Session Management
|
|
83
|
+
export async function saveSession(data: SessionData): Promise<void> {
|
|
84
|
+
await ensureConfigDir()
|
|
85
|
+
|
|
86
|
+
const session: SessionData = {
|
|
87
|
+
...data,
|
|
88
|
+
lastActivity: Date.now(),
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
currentSession = session
|
|
92
|
+
memoryStorage.set('__session', session)
|
|
93
|
+
|
|
94
|
+
// Persist to disk
|
|
142
95
|
try {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
96
|
+
await writeFile(getSessionFilePath(), JSON.stringify(session, null, 2))
|
|
97
|
+
log('debug', 'Session persisted to disk', { sessionId: data.sessionId })
|
|
98
|
+
} catch (error) {
|
|
99
|
+
log('error', 'Failed to persist session', error)
|
|
100
|
+
throw new Error('Failed to save session')
|
|
146
101
|
}
|
|
147
102
|
}
|
|
148
103
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const memoryStorage: Map<string, string> = new Map();
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Gets an item from storage (sessionStorage or memory fallback)
|
|
157
|
-
*
|
|
158
|
-
* @param {string} key - Storage key
|
|
159
|
-
* @returns {string | null} The stored value or null
|
|
160
|
-
*/
|
|
161
|
-
function getStorageItem(key: string): string | null {
|
|
162
|
-
if (isStorageAvailable()) {
|
|
163
|
-
return window.sessionStorage.getItem(key);
|
|
104
|
+
export async function loadSession(): Promise<SessionData | null> {
|
|
105
|
+
// Check memory first
|
|
106
|
+
if (currentSession) {
|
|
107
|
+
return currentSession
|
|
164
108
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Sets an item in storage (sessionStorage or memory fallback)
|
|
170
|
-
*
|
|
171
|
-
* @param {string} key - Storage key
|
|
172
|
-
* @param {string} value - Value to store
|
|
173
|
-
* @returns {boolean} True if successful
|
|
174
|
-
*/
|
|
175
|
-
function setStorageItem(key: string, value: string): boolean {
|
|
109
|
+
|
|
110
|
+
// Try to load from disk
|
|
176
111
|
try {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
112
|
+
const data = await readFile(getSessionFilePath(), 'utf8')
|
|
113
|
+
const session: SessionData = JSON.parse(data)
|
|
114
|
+
|
|
115
|
+
// Check if session is expired
|
|
116
|
+
if (session.expiresAt && session.expiresAt < Date.now()) {
|
|
117
|
+
log('info', 'Session expired')
|
|
118
|
+
await clearSession()
|
|
119
|
+
return null
|
|
181
120
|
}
|
|
182
|
-
|
|
121
|
+
|
|
122
|
+
currentSession = session
|
|
123
|
+
memoryStorage.set('__session', session)
|
|
124
|
+
|
|
125
|
+
// Update last activity
|
|
126
|
+
session.lastActivity = Date.now()
|
|
127
|
+
await saveSession(session)
|
|
128
|
+
|
|
129
|
+
return session
|
|
183
130
|
} catch (error) {
|
|
184
|
-
|
|
185
|
-
return
|
|
131
|
+
// File doesn't exist or is corrupted
|
|
132
|
+
return null
|
|
186
133
|
}
|
|
187
134
|
}
|
|
188
135
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
* @returns {boolean} True if successful
|
|
194
|
-
*/
|
|
195
|
-
function removeStorageItem(key: string): boolean {
|
|
136
|
+
export async function clearSession(): Promise<void> {
|
|
137
|
+
currentSession = null
|
|
138
|
+
memoryStorage.delete('__session')
|
|
139
|
+
|
|
196
140
|
try {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
} else {
|
|
200
|
-
memoryStorage.delete(key);
|
|
201
|
-
}
|
|
202
|
-
return true;
|
|
141
|
+
await writeFile(getSessionFilePath(), '{}')
|
|
142
|
+
log('info', 'Session cleared')
|
|
203
143
|
} catch (error) {
|
|
204
|
-
|
|
205
|
-
return false;
|
|
144
|
+
log('error', 'Failed to clear session file', error)
|
|
206
145
|
}
|
|
207
146
|
}
|
|
208
147
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
* @param {UserSession} session - The session to save
|
|
213
|
-
* @param {Object} [options] - Save options
|
|
214
|
-
* @param {TypedMessage[]} [options.messages] - Messages to persist
|
|
215
|
-
* @param {Record<string, unknown>} [options.preferences] - Preferences to persist
|
|
216
|
-
* @returns {StorageResult<void>} Result of the save operation
|
|
217
|
-
* @example
|
|
218
|
-
* ```typescript
|
|
219
|
-
* const result = saveSession(session, { messages, preferences });
|
|
220
|
-
* if (!result.success) {
|
|
221
|
-
* console.error('Failed to save session:', result.error);
|
|
222
|
-
* }
|
|
223
|
-
* ```
|
|
224
|
-
*/
|
|
225
|
-
export function saveSession(
|
|
226
|
-
session: UserSession,
|
|
227
|
-
options: {
|
|
228
|
-
messages?: TypedMessage[];
|
|
229
|
-
preferences?: Record<string, unknown>;
|
|
230
|
-
} = {}
|
|
231
|
-
): StorageResult<void> {
|
|
232
|
-
if (!session) {
|
|
233
|
-
return { success: false, data: null, error: 'Session is required' };
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const existingMetadata = loadSessionMetadata();
|
|
237
|
-
const metadata: SessionMetadata = {
|
|
238
|
-
createdAt: existingMetadata?.createdAt ?? new Date().toISOString(),
|
|
239
|
-
lastActivity: new Date().toISOString(),
|
|
240
|
-
messageCount: options.messages?.length ?? 0,
|
|
241
|
-
version: 1,
|
|
242
|
-
deviceInfo: isStorageAvailable()
|
|
243
|
-
? {
|
|
244
|
-
userAgent: navigator.userAgent,
|
|
245
|
-
platform: navigator.platform,
|
|
246
|
-
language: navigator.language,
|
|
247
|
-
}
|
|
248
|
-
: undefined,
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const sessionData: SessionData = {
|
|
252
|
-
session,
|
|
253
|
-
metadata,
|
|
254
|
-
messages: options.messages ?? [],
|
|
255
|
-
preferences: options.preferences ?? {},
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
const serialized = safeJsonStringify(sessionData);
|
|
259
|
-
if (!serialized) {
|
|
260
|
-
return { success: false, data: null, error: 'Failed to serialize session data' };
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (!setStorageItem(STORAGE_KEYS.SESSION, serialized)) {
|
|
264
|
-
return { success: false, data: null, error: 'Failed to write to storage' };
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Also save user type separately for quick access
|
|
268
|
-
setStorageItem(STORAGE_KEYS.USER_TYPE, session.userType);
|
|
269
|
-
setStorageItem(STORAGE_KEYS.LAST_ACTIVITY, metadata.lastActivity);
|
|
270
|
-
|
|
271
|
-
return { success: true, data: undefined, error: null };
|
|
148
|
+
export function hasActiveSession(): boolean {
|
|
149
|
+
if (currentSession) return true
|
|
150
|
+
return memoryStorage.has('__session')
|
|
272
151
|
}
|
|
273
152
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
* @returns {StorageResult<SessionData>} The loaded session data or error
|
|
278
|
-
* @example
|
|
279
|
-
* ```typescript
|
|
280
|
-
* const result = loadSession();
|
|
281
|
-
* if (result.success && result.data) {
|
|
282
|
-
* const { session, messages } = result.data;
|
|
283
|
-
* // Restore session...
|
|
284
|
-
* }
|
|
285
|
-
* ```
|
|
286
|
-
*/
|
|
287
|
-
export function loadSession(): StorageResult<SessionData> {
|
|
288
|
-
const serialized = getStorageItem(STORAGE_KEYS.SESSION);
|
|
289
|
-
|
|
290
|
-
if (!serialized) {
|
|
291
|
-
return { success: false, data: null, error: 'No session found in storage' };
|
|
292
|
-
}
|
|
153
|
+
export function getSessionId(): string | undefined {
|
|
154
|
+
return currentSession?.sessionId
|
|
155
|
+
}
|
|
293
156
|
|
|
294
|
-
|
|
157
|
+
export function getUserType(): UserType {
|
|
158
|
+
return currentSession?.userType || 'anonymous'
|
|
159
|
+
}
|
|
295
160
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
161
|
+
export function isUserType(type: UserType): boolean {
|
|
162
|
+
return getUserType() === type
|
|
163
|
+
}
|
|
299
164
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return { success: false, data: null, error: 'Session has expired' };
|
|
306
|
-
}
|
|
307
|
-
}
|
|
165
|
+
export function isAuthenticated(): boolean {
|
|
166
|
+
return currentSession?.userType === 'authenticated' ||
|
|
167
|
+
currentSession?.userType === 'admin' ||
|
|
168
|
+
currentSession?.userType === 'ant'
|
|
169
|
+
}
|
|
308
170
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
// Silently update last activity in storage
|
|
316
|
-
const updatedData: SessionData = { ...data, metadata: updatedMetadata };
|
|
317
|
-
const serializedUpdated = safeJsonStringify(updatedData);
|
|
318
|
-
if (serializedUpdated) {
|
|
319
|
-
setStorageItem(STORAGE_KEYS.SESSION, serializedUpdated);
|
|
171
|
+
export async function updateLastActivity(): Promise<void> {
|
|
172
|
+
if (currentSession) {
|
|
173
|
+
currentSession.lastActivity = Date.now()
|
|
174
|
+
await saveSession(currentSession)
|
|
320
175
|
}
|
|
176
|
+
}
|
|
321
177
|
|
|
322
|
-
|
|
178
|
+
export function getLastActivity(): number | undefined {
|
|
179
|
+
return currentSession?.lastActivity
|
|
323
180
|
}
|
|
324
181
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
STORAGE_KEYS.SESSION,
|
|
337
|
-
STORAGE_KEYS.USER_TYPE,
|
|
338
|
-
STORAGE_KEYS.MESSAGES,
|
|
339
|
-
STORAGE_KEYS.PREFERENCES,
|
|
340
|
-
STORAGE_KEYS.LAST_ACTIVITY,
|
|
341
|
-
STORAGE_KEYS.SESSION_METADATA,
|
|
342
|
-
];
|
|
343
|
-
|
|
344
|
-
let allSuccess = true;
|
|
345
|
-
for (const key of keysToRemove) {
|
|
346
|
-
if (!removeStorageItem(key)) {
|
|
347
|
-
allSuccess = false;
|
|
348
|
-
}
|
|
182
|
+
// Preferences Management
|
|
183
|
+
export async function savePreferences(prefs: UserPreferences): Promise<void> {
|
|
184
|
+
await ensureConfigDir()
|
|
185
|
+
preferencesCache = prefs
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
await writeFile(getPreferencesFilePath(), JSON.stringify(prefs, null, 2))
|
|
189
|
+
log('debug', 'Preferences saved')
|
|
190
|
+
} catch (error) {
|
|
191
|
+
log('error', 'Failed to save preferences', error)
|
|
192
|
+
throw new Error('Failed to save preferences')
|
|
349
193
|
}
|
|
350
|
-
|
|
351
|
-
// Also clear memory storage
|
|
352
|
-
memoryStorage.clear();
|
|
353
|
-
|
|
354
|
-
return allSuccess;
|
|
355
194
|
}
|
|
356
195
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
*
|
|
361
|
-
* @returns {UserType} The stored user type or 'anonymous'
|
|
362
|
-
* @example
|
|
363
|
-
* ```typescript
|
|
364
|
-
* const userType = getUserType();
|
|
365
|
-
* if (userType === 'pro') {
|
|
366
|
-
* // Show pro features
|
|
367
|
-
* }
|
|
368
|
-
* ```
|
|
369
|
-
*/
|
|
370
|
-
export function getUserType(): UserType {
|
|
371
|
-
const stored = getStorageItem(STORAGE_KEYS.USER_TYPE);
|
|
372
|
-
|
|
373
|
-
if (stored && VALID_USER_TYPES.includes(stored as UserType)) {
|
|
374
|
-
return stored as UserType;
|
|
196
|
+
export async function loadPreferences(): Promise<UserPreferences> {
|
|
197
|
+
if (preferencesCache) {
|
|
198
|
+
return preferencesCache
|
|
375
199
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const data = await readFile(getPreferencesFilePath(), 'utf8')
|
|
203
|
+
const prefs: UserPreferences = JSON.parse(data)
|
|
204
|
+
preferencesCache = prefs
|
|
205
|
+
return prefs
|
|
206
|
+
} catch {
|
|
207
|
+
// Return defaults
|
|
208
|
+
return {
|
|
209
|
+
theme: 'dark',
|
|
210
|
+
fontSize: 14,
|
|
211
|
+
autoSave: true,
|
|
212
|
+
notifications: true,
|
|
213
|
+
telemetry: true,
|
|
383
214
|
}
|
|
384
215
|
}
|
|
385
|
-
|
|
386
|
-
return 'anonymous';
|
|
387
216
|
}
|
|
388
217
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
* @returns {SessionMetadata | null} The metadata or null if not found
|
|
393
|
-
*/
|
|
394
|
-
function loadSessionMetadata(): SessionMetadata | null {
|
|
395
|
-
const sessionResult = loadSession();
|
|
396
|
-
if (sessionResult.success && sessionResult.data) {
|
|
397
|
-
return sessionResult.data.metadata;
|
|
398
|
-
}
|
|
399
|
-
return null;
|
|
218
|
+
export async function getPreference<T>(key: string, defaultValue: T): Promise<T> {
|
|
219
|
+
const prefs = await loadPreferences()
|
|
220
|
+
return (prefs[key] as T) ?? defaultValue
|
|
400
221
|
}
|
|
401
222
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
* @returns {StorageResult<void>} Result of the operation
|
|
407
|
-
*/
|
|
408
|
-
export function saveMessages(messages: TypedMessage[]): StorageResult<void> {
|
|
409
|
-
const serialized = safeJsonStringify(messages);
|
|
410
|
-
|
|
411
|
-
if (!serialized) {
|
|
412
|
-
return { success: false, data: null, error: 'Failed to serialize messages' };
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
if (!setStorageItem(STORAGE_KEYS.MESSAGES, serialized)) {
|
|
416
|
-
return { success: false, data: null, error: 'Failed to write messages to storage' };
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Update message count in session metadata
|
|
420
|
-
const sessionResult = loadSession();
|
|
421
|
-
if (sessionResult.success && sessionResult.data) {
|
|
422
|
-
const { session, metadata, preferences } = sessionResult.data;
|
|
423
|
-
saveSession(session, {
|
|
424
|
-
messages,
|
|
425
|
-
preferences,
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return { success: true, data: undefined, error: null };
|
|
223
|
+
export async function setPreference<T>(key: string, value: T): Promise<void> {
|
|
224
|
+
const prefs = await loadPreferences()
|
|
225
|
+
prefs[key] = value
|
|
226
|
+
await savePreferences(prefs)
|
|
430
227
|
}
|
|
431
228
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
if (!serialized) {
|
|
441
|
-
return { success: false, data: null, error: 'No messages found in storage' };
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
const messages = safeJsonParse<TypedMessage[]>(serialized, []);
|
|
445
|
-
|
|
446
|
-
if (!messages) {
|
|
447
|
-
return { success: false, data: null, error: 'Failed to parse messages' };
|
|
229
|
+
// Message Persistence
|
|
230
|
+
export async function saveMessages(messages: Message[]): Promise<void> {
|
|
231
|
+
await ensureConfigDir()
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
await writeFile(getMessagesFilePath(), JSON.stringify(messages, null, 2))
|
|
235
|
+
} catch (error) {
|
|
236
|
+
log('error', 'Failed to save messages', error)
|
|
448
237
|
}
|
|
449
|
-
|
|
450
|
-
return { success: true, data: messages, error: null };
|
|
451
238
|
}
|
|
452
239
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
export function savePreferences(preferences: Record<string, unknown>): StorageResult<void> {
|
|
460
|
-
const serialized = safeJsonStringify(preferences);
|
|
461
|
-
|
|
462
|
-
if (!serialized) {
|
|
463
|
-
return { success: false, data: null, error: 'Failed to serialize preferences' };
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
if (!setStorageItem(STORAGE_KEYS.PREFERENCES, serialized)) {
|
|
467
|
-
return { success: false, data: null, error: 'Failed to write preferences to storage' };
|
|
240
|
+
export async function loadMessages(): Promise<Message[]> {
|
|
241
|
+
try {
|
|
242
|
+
const data = await readFile(getMessagesFilePath(), 'utf8')
|
|
243
|
+
return JSON.parse(data)
|
|
244
|
+
} catch {
|
|
245
|
+
return []
|
|
468
246
|
}
|
|
469
|
-
|
|
470
|
-
return { success: true, data: undefined, error: null };
|
|
471
247
|
}
|
|
472
248
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if (!serialized) {
|
|
482
|
-
return { success: false, data: null, error: 'No preferences found in storage' };
|
|
249
|
+
export async function addMessage(message: Message): Promise<void> {
|
|
250
|
+
const messages = await loadMessages()
|
|
251
|
+
messages.push(message)
|
|
252
|
+
|
|
253
|
+
// Keep only last 1000 messages
|
|
254
|
+
if (messages.length > 1000) {
|
|
255
|
+
messages.splice(0, messages.length - 1000)
|
|
483
256
|
}
|
|
257
|
+
|
|
258
|
+
await saveMessages(messages)
|
|
259
|
+
}
|
|
484
260
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
261
|
+
// Low-level storage (for other uses)
|
|
262
|
+
export async function getSessionItem<T>(key: string): Promise<T | undefined> {
|
|
263
|
+
// Check memory first
|
|
264
|
+
if (memoryStorage.has(key)) {
|
|
265
|
+
return memoryStorage.get(key) as T
|
|
489
266
|
}
|
|
490
|
-
|
|
491
|
-
|
|
267
|
+
|
|
268
|
+
// Could extend to read from separate files per key
|
|
269
|
+
return undefined
|
|
492
270
|
}
|
|
493
271
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
272
|
+
export async function setSessionItem<T>(key: string, value: T): Promise<void> {
|
|
273
|
+
memoryStorage.set(key, value)
|
|
274
|
+
|
|
275
|
+
// Persist to separate file
|
|
276
|
+
await ensureConfigDir()
|
|
277
|
+
try {
|
|
278
|
+
const filePath = join(getConfigDir(), `${key}.json`)
|
|
279
|
+
await writeFile(filePath, JSON.stringify(value, null, 2))
|
|
280
|
+
} catch (error) {
|
|
281
|
+
log('error', `Failed to persist ${key}`, error)
|
|
504
282
|
}
|
|
283
|
+
}
|
|
505
284
|
|
|
285
|
+
export async function removeSessionItem(key: string): Promise<void> {
|
|
286
|
+
memoryStorage.delete(key)
|
|
287
|
+
|
|
506
288
|
try {
|
|
507
|
-
const
|
|
508
|
-
|
|
289
|
+
const filePath = join(getConfigDir(), `${key}.json`)
|
|
290
|
+
await writeFile(filePath, '{}')
|
|
509
291
|
} catch {
|
|
510
|
-
|
|
292
|
+
// Ignore
|
|
511
293
|
}
|
|
512
294
|
}
|
|
513
295
|
|
|
514
|
-
/**
|
|
515
|
-
* Updates the last activity timestamp to now
|
|
516
|
-
*
|
|
517
|
-
* @returns {boolean} True if successful
|
|
518
|
-
*/
|
|
519
|
-
export function updateLastActivity(): boolean {
|
|
520
|
-
return setStorageItem(STORAGE_KEYS.LAST_ACTIVITY, new Date().toISOString());
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* Checks if there is an active session in storage
|
|
525
|
-
*
|
|
526
|
-
* @returns {boolean} True if a valid session exists
|
|
527
|
-
*/
|
|
528
|
-
export function hasActiveSession(): boolean {
|
|
529
|
-
const result = loadSession();
|
|
530
|
-
return result.success;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
/**
|
|
534
|
-
* Type guard to check if a value is a valid UserType
|
|
535
|
-
*
|
|
536
|
-
* @param {unknown} value - The value to check
|
|
537
|
-
* @returns {boolean} True if the value is a valid UserType
|
|
538
|
-
*/
|
|
539
|
-
export function isUserType(value: unknown): value is UserType {
|
|
540
|
-
return typeof value === 'string' && VALID_USER_TYPES.includes(value as UserType);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
296
|
// Default export
|
|
544
297
|
export default {
|
|
545
|
-
|
|
546
|
-
VALID_USER_TYPES,
|
|
298
|
+
// Session
|
|
547
299
|
saveSession,
|
|
548
300
|
loadSession,
|
|
549
301
|
clearSession,
|
|
302
|
+
hasActiveSession,
|
|
303
|
+
getSessionId,
|
|
550
304
|
getUserType,
|
|
551
|
-
|
|
552
|
-
|
|
305
|
+
isUserType,
|
|
306
|
+
isAuthenticated,
|
|
307
|
+
updateLastActivity,
|
|
308
|
+
getLastActivity,
|
|
309
|
+
|
|
310
|
+
// Preferences
|
|
553
311
|
savePreferences,
|
|
554
312
|
loadPreferences,
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
313
|
+
getPreference,
|
|
314
|
+
setPreference,
|
|
315
|
+
|
|
316
|
+
// Messages
|
|
317
|
+
saveMessages,
|
|
318
|
+
loadMessages,
|
|
319
|
+
addMessage,
|
|
320
|
+
|
|
321
|
+
// Low-level
|
|
322
|
+
getSessionItem,
|
|
323
|
+
setSessionItem,
|
|
324
|
+
removeSessionItem,
|
|
325
|
+
}
|