@cloudcli-ai/cloudcli 1.36.0 → 1.36.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.
Files changed (217) hide show
  1. package/README.de.md +17 -18
  2. package/README.ja.md +16 -17
  3. package/README.ko.md +17 -18
  4. package/README.md +19 -20
  5. package/README.ru.md +17 -18
  6. package/README.tr.md +17 -18
  7. package/README.zh-CN.md +17 -18
  8. package/README.zh-TW.md +17 -18
  9. package/dist/api-docs.html +2 -3
  10. package/dist/assets/{index-S6cIy7hj.js → index-Bz9vXV2m.js} +264 -259
  11. package/dist/assets/index-DLmhQ15L.css +32 -0
  12. package/dist/assets/{vendor-xterm-CJZjLICi.js → vendor-xterm-CS4rQ5Mr.js} +12 -12
  13. package/dist/index.html +3 -3
  14. package/dist-server/server/claude-sdk.js +37 -90
  15. package/dist-server/server/claude-sdk.js.map +1 -1
  16. package/dist-server/server/cli.js +1 -4
  17. package/dist-server/server/cli.js.map +1 -1
  18. package/dist-server/server/cursor-cli.js +12 -11
  19. package/dist-server/server/cursor-cli.js.map +1 -1
  20. package/dist-server/server/index.js +10 -141
  21. package/dist-server/server/index.js.map +1 -1
  22. package/dist-server/server/modules/assets/assets.routes.js +89 -0
  23. package/dist-server/server/modules/assets/assets.routes.js.map +1 -0
  24. package/dist-server/server/modules/assets/index.js +4 -0
  25. package/dist-server/server/modules/assets/index.js.map +1 -0
  26. package/dist-server/server/modules/assets/services/image-assets.service.js +57 -0
  27. package/dist-server/server/modules/assets/services/image-assets.service.js.map +1 -0
  28. package/dist-server/server/modules/assets/tests/image-assets.service.test.js +36 -0
  29. package/dist-server/server/modules/assets/tests/image-assets.service.test.js.map +1 -0
  30. package/dist-server/server/modules/browser-use/browser-use.service.js +6 -3
  31. package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
  32. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +2 -2
  33. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -1
  34. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +0 -1
  35. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
  36. package/dist-server/server/modules/projects/services/project-clone.service.js +2 -1
  37. package/dist-server/server/modules/projects/services/project-clone.service.js.map +1 -1
  38. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +28 -0
  39. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  40. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +54 -1
  41. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  42. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +36 -1
  43. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  44. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +3 -17
  45. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -1
  46. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js +7 -1
  47. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
  48. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +49 -13
  49. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
  50. package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js +0 -1
  51. package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js.map +1 -1
  52. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +8 -14
  53. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
  54. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +24 -5
  55. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
  56. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +10 -21
  57. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -1
  58. package/dist-server/server/modules/providers/provider.registry.js +0 -2
  59. package/dist-server/server/modules/providers/provider.registry.js.map +1 -1
  60. package/dist-server/server/modules/providers/provider.routes.js +0 -1
  61. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  62. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +7 -14
  63. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
  64. package/dist-server/server/modules/providers/services/provider-models.service.js +1 -1
  65. package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
  66. package/dist-server/server/modules/providers/services/session-conversations-search.service.js +3 -77
  67. package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
  68. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +0 -1
  69. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
  70. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +0 -13
  71. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  72. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +96 -0
  73. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -0
  74. package/dist-server/server/modules/providers/tests/mcp.test.js +3 -29
  75. package/dist-server/server/modules/providers/tests/mcp.test.js.map +1 -1
  76. package/dist-server/server/modules/providers/tests/opencode-models.test.js +7 -0
  77. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
  78. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +117 -0
  79. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
  80. package/dist-server/server/modules/providers/tests/provider-image-history.test.js +147 -0
  81. package/dist-server/server/modules/providers/tests/provider-image-history.test.js.map +1 -0
  82. package/dist-server/server/modules/providers/tests/provider-models.service.test.js +8 -8
  83. package/dist-server/server/modules/providers/tests/skills.test.js +3 -26
  84. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  85. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +14 -0
  86. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
  87. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +36 -2
  88. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  89. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +3 -12
  90. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  91. package/dist-server/server/modules/websocket/tests/chat-image-filter.test.js +33 -0
  92. package/dist-server/server/modules/websocket/tests/chat-image-filter.test.js.map +1 -0
  93. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +37 -4
  94. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
  95. package/dist-server/server/openai-codex.js +8 -2
  96. package/dist-server/server/openai-codex.js.map +1 -1
  97. package/dist-server/server/opencode-cli.js +43 -6
  98. package/dist-server/server/opencode-cli.js.map +1 -1
  99. package/dist-server/server/opencode-cli.test.js +103 -3
  100. package/dist-server/server/opencode-cli.test.js.map +1 -1
  101. package/dist-server/server/routes/agent.js +9 -19
  102. package/dist-server/server/routes/agent.js.map +1 -1
  103. package/dist-server/server/routes/commands.js +1 -2
  104. package/dist-server/server/routes/commands.js.map +1 -1
  105. package/dist-server/server/routes/git.js +179 -53
  106. package/dist-server/server/routes/git.js.map +1 -1
  107. package/dist-server/server/routes/git.test.js +80 -0
  108. package/dist-server/server/routes/git.test.js.map +1 -0
  109. package/dist-server/server/routes/taskmaster.js +3 -1
  110. package/dist-server/server/routes/taskmaster.js.map +1 -1
  111. package/dist-server/server/routes/user.js +2 -1
  112. package/dist-server/server/routes/user.js.map +1 -1
  113. package/dist-server/server/shared/image-attachments.js +270 -0
  114. package/dist-server/server/shared/image-attachments.js.map +1 -0
  115. package/dist-server/server/shared/tests/image-attachments.test.js +220 -0
  116. package/dist-server/server/shared/tests/image-attachments.test.js.map +1 -0
  117. package/dist-server/server/shared/utils.js +43 -0
  118. package/dist-server/server/shared/utils.js.map +1 -1
  119. package/dist-server/server/utils/gitConfig.js +2 -1
  120. package/dist-server/server/utils/gitConfig.js.map +1 -1
  121. package/dist-server/server/utils/plugin-process-manager.js +2 -1
  122. package/dist-server/server/utils/plugin-process-manager.js.map +1 -1
  123. package/electron/launcher/launcher.js +1 -1
  124. package/package.json +3 -3
  125. package/public/api-docs.html +2 -3
  126. package/server/claude-sdk.js +37 -105
  127. package/server/cli.js +1 -4
  128. package/server/cursor-cli.js +13 -12
  129. package/server/index.js +11 -159
  130. package/server/modules/assets/assets.routes.ts +103 -0
  131. package/server/modules/assets/index.ts +3 -0
  132. package/server/modules/assets/services/image-assets.service.ts +82 -0
  133. package/server/modules/assets/tests/image-assets.service.test.ts +46 -0
  134. package/server/modules/browser-use/browser-use.service.ts +7 -3
  135. package/server/modules/database/tests/sessions-provider-mapping.test.ts +2 -2
  136. package/server/modules/notifications/services/notification-orchestrator.service.js +0 -1
  137. package/server/modules/projects/services/project-clone.service.ts +3 -1
  138. package/server/modules/providers/README.md +1 -7
  139. package/server/modules/providers/list/claude/claude-sessions.provider.ts +30 -0
  140. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +60 -1
  141. package/server/modules/providers/list/codex/codex-sessions.provider.ts +41 -1
  142. package/server/modules/providers/list/cursor/cursor-models.provider.ts +3 -17
  143. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +7 -1
  144. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +56 -15
  145. package/server/modules/providers/list/opencode/opencode-auth.provider.ts +0 -1
  146. package/server/modules/providers/list/opencode/opencode-models.provider.ts +15 -19
  147. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +25 -4
  148. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +10 -21
  149. package/server/modules/providers/provider.registry.ts +0 -2
  150. package/server/modules/providers/provider.routes.ts +0 -1
  151. package/server/modules/providers/services/provider-capabilities.service.ts +7 -14
  152. package/server/modules/providers/services/provider-models.service.ts +1 -1
  153. package/server/modules/providers/services/session-conversations-search.service.ts +3 -93
  154. package/server/modules/providers/services/session-synchronizer.service.ts +0 -1
  155. package/server/modules/providers/services/sessions-watcher.service.ts +0 -14
  156. package/server/modules/providers/tests/codex-sessions.test.ts +111 -0
  157. package/server/modules/providers/tests/mcp.test.ts +3 -34
  158. package/server/modules/providers/tests/opencode-models.test.ts +7 -0
  159. package/server/modules/providers/tests/opencode-sessions.test.ts +139 -0
  160. package/server/modules/providers/tests/provider-image-history.test.ts +180 -0
  161. package/server/modules/providers/tests/provider-models.service.test.ts +8 -8
  162. package/server/modules/providers/tests/skills.test.ts +3 -42
  163. package/server/modules/websocket/services/chat-run-registry.service.ts +16 -0
  164. package/server/modules/websocket/services/chat-websocket.service.ts +41 -2
  165. package/server/modules/websocket/services/shell-websocket.service.ts +1 -11
  166. package/server/modules/websocket/tests/chat-image-filter.test.ts +44 -0
  167. package/server/modules/websocket/tests/chat-run-registry.test.ts +42 -4
  168. package/server/openai-codex.js +9 -1
  169. package/server/opencode-cli.js +44 -6
  170. package/server/opencode-cli.test.js +112 -3
  171. package/server/routes/agent.js +9 -19
  172. package/server/routes/commands.js +1 -2
  173. package/server/routes/git.js +201 -60
  174. package/server/routes/git.test.js +106 -0
  175. package/server/routes/taskmaster.js +3 -1
  176. package/server/routes/user.js +2 -1
  177. package/server/shared/image-attachments.ts +341 -0
  178. package/server/shared/tests/image-attachments.test.ts +298 -0
  179. package/server/shared/types.ts +1 -1
  180. package/server/shared/utils.ts +45 -0
  181. package/server/utils/gitConfig.js +2 -1
  182. package/server/utils/plugin-process-manager.js +3 -1
  183. package/dist/assets/index-uLUE6Qt1.css +0 -32
  184. package/dist/icons/gemini-ai-icon.svg +0 -1
  185. package/dist-server/server/gemini-cli.js +0 -551
  186. package/dist-server/server/gemini-cli.js.map +0 -1
  187. package/dist-server/server/gemini-response-handler.js +0 -106
  188. package/dist-server/server/gemini-response-handler.js.map +0 -1
  189. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js +0 -254
  190. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js.map +0 -1
  191. package/dist-server/server/modules/providers/list/gemini/gemini-mcp.provider.js +0 -82
  192. package/dist-server/server/modules/providers/list/gemini/gemini-mcp.provider.js.map +0 -1
  193. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +0 -24
  194. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +0 -1
  195. package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js +0 -312
  196. package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js.map +0 -1
  197. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +0 -474
  198. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +0 -1
  199. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +0 -40
  200. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +0 -1
  201. package/dist-server/server/modules/providers/list/gemini/gemini.provider.js +0 -19
  202. package/dist-server/server/modules/providers/list/gemini/gemini.provider.js.map +0 -1
  203. package/dist-server/server/routes/gemini.js +0 -21
  204. package/dist-server/server/routes/gemini.js.map +0 -1
  205. package/dist-server/server/sessionManager.js +0 -194
  206. package/dist-server/server/sessionManager.js.map +0 -1
  207. package/server/gemini-cli.js +0 -638
  208. package/server/gemini-response-handler.js +0 -117
  209. package/server/modules/providers/list/gemini/gemini-auth.provider.ts +0 -307
  210. package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +0 -110
  211. package/server/modules/providers/list/gemini/gemini-models.provider.ts +0 -39
  212. package/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.ts +0 -405
  213. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +0 -540
  214. package/server/modules/providers/list/gemini/gemini-skills.provider.ts +0 -44
  215. package/server/modules/providers/list/gemini/gemini.provider.ts +0 -27
  216. package/server/routes/gemini.js +0 -25
  217. package/server/sessionManager.js +0 -226
@@ -1,405 +0,0 @@
1
- import crypto from 'node:crypto';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import { readFile } from 'node:fs/promises';
5
-
6
- import { projectsDb, sessionsDb } from '@/modules/database/index.js';
7
- import {
8
- findFilesRecursivelyCreatedAfter,
9
- normalizeProjectPath,
10
- normalizeSessionName,
11
- readFileTimestamps,
12
- } from '@/shared/utils.js';
13
- import type { IProviderSessionSynchronizer } from '@/shared/interfaces.js';
14
- import type { AnyRecord } from '@/shared/types.js';
15
-
16
- type ParsedSession = {
17
- sessionId: string;
18
- projectPath: string;
19
- sessionName?: string;
20
- };
21
-
22
- type GeminiJsonlMetadata = {
23
- sessionId: string;
24
- projectPath?: string;
25
- projectHash?: string;
26
- firstUserMessage?: string;
27
- };
28
-
29
- /**
30
- * Session indexer for Gemini transcript artifacts.
31
- */
32
- export class GeminiSessionSynchronizer implements IProviderSessionSynchronizer {
33
- private readonly provider = 'gemini' as const;
34
- private readonly geminiHome = path.join(os.homedir(), '.gemini');
35
-
36
- /**
37
- * Scans Gemini legacy JSON and new JSONL artifacts and upserts sessions into DB.
38
- */
39
- async synchronize(since?: Date): Promise<number> {
40
- const projectHashLookup = this.buildProjectHashLookup();
41
-
42
- // const legacySessionFiles = await findFilesRecursivelyCreatedAfter(
43
- // path.join(this.geminiHome, 'sessions'),
44
- // '.json',
45
- // since ?? null
46
- // );
47
- // Gemini creates overlapping artifacts across `sessions/` and `tmp/`.
48
- // We currently index only `tmp/*/chats/*.jsonl` because those files are the
49
- // live transcript source and avoid duplicate session rows from mirrored files.
50
- // const legacyTempFiles = await findFilesRecursivelyCreatedAfter(
51
- // path.join(this.geminiHome, 'tmp'),
52
- // '.json',
53
- // since ?? null
54
- // );
55
- // const jsonlSessionFiles = await findFilesRecursivelyCreatedAfter(
56
- // path.join(this.geminiHome, 'sessions'),
57
- // '.jsonl',
58
- // since ?? null
59
- // );
60
- const jsonlTempFiles = await findFilesRecursivelyCreatedAfter(
61
- path.join(this.geminiHome, 'tmp'),
62
- '.jsonl',
63
- since ?? null
64
- );
65
-
66
- // Current strategy: index only temp chat JSONL artifacts.
67
- const files = [
68
- // ...legacySessionFiles,
69
- // Intentionally disabled to avoid duplicate indexing from mirrored
70
- // `sessions/*.json` and `sessions/*.jsonl` artifacts.
71
- // ...legacyTempFiles,
72
- // ...jsonlSessionFiles,
73
- ...jsonlTempFiles,
74
- ];
75
-
76
- let processed = 0;
77
- for (const filePath of files) {
78
- if (this.shouldSkipTempArtifact(filePath)) {
79
- continue;
80
- }
81
-
82
- const parsed = filePath.endsWith('.jsonl')
83
- ? await this.processJsonlSessionFile(filePath, projectHashLookup)
84
- : await this.processLegacySessionFile(filePath);
85
- if (!parsed) {
86
- continue;
87
- }
88
-
89
- const timestamps = await readFileTimestamps(filePath);
90
- sessionsDb.createSession(
91
- parsed.sessionId,
92
- this.provider,
93
- parsed.projectPath,
94
- parsed.sessionName,
95
- timestamps.createdAt,
96
- timestamps.updatedAt,
97
- filePath
98
- );
99
- processed += 1;
100
- }
101
-
102
- return processed;
103
- }
104
-
105
- /**
106
- * Parses and upserts one Gemini legacy JSON or JSONL artifact.
107
- */
108
- async synchronizeFile(filePath: string): Promise<string | null> {
109
- if (!filePath.endsWith('.json') && !filePath.endsWith('.jsonl')) {
110
- return null;
111
- }
112
-
113
- if (this.shouldSkipTempArtifact(filePath)) {
114
- return null;
115
- }
116
-
117
- const parsed = filePath.endsWith('.jsonl')
118
- ? await this.processJsonlSessionFile(filePath, this.buildProjectHashLookup())
119
- : await this.processLegacySessionFile(filePath);
120
- if (!parsed) {
121
- return null;
122
- }
123
-
124
- const timestamps = await readFileTimestamps(filePath);
125
- return sessionsDb.createSession(
126
- parsed.sessionId,
127
- this.provider,
128
- parsed.projectPath,
129
- parsed.sessionName,
130
- timestamps.createdAt,
131
- timestamps.updatedAt,
132
- filePath
133
- );
134
- }
135
-
136
- /**
137
- * Extracts session metadata from one Gemini legacy JSON artifact.
138
- */
139
- private async processLegacySessionFile(filePath: string): Promise<ParsedSession | null> {
140
- try {
141
- const content = await readFile(filePath, 'utf8');
142
- const data = JSON.parse(content) as AnyRecord;
143
-
144
- const sessionId =
145
- typeof data.sessionId === 'string'
146
- ? data.sessionId
147
- : typeof data.id === 'string'
148
- ? data.id
149
- : undefined;
150
- if (!sessionId) {
151
- return null;
152
- }
153
-
154
- const workspaceProjectPath = await this.resolveProjectPathFromChatWorkspace(filePath);
155
- const projectPath = typeof data.projectPath === 'string' && data.projectPath.trim().length > 0
156
- ? data.projectPath
157
- : workspaceProjectPath;
158
- if (!projectPath) {
159
- return null;
160
- }
161
-
162
- const messages = Array.isArray(data.messages) ? data.messages : [];
163
- const firstMessage = messages[0] as AnyRecord | undefined;
164
- let rawName: string | undefined;
165
-
166
- if (Array.isArray(firstMessage?.content) && typeof firstMessage.content[0]?.text === 'string') {
167
- rawName = firstMessage.content[0].text;
168
- } else if (typeof firstMessage?.content === 'string') {
169
- rawName = firstMessage.content;
170
- }
171
-
172
- return {
173
- sessionId,
174
- projectPath,
175
- sessionName: normalizeSessionName(rawName, 'New Gemini Chat'),
176
- };
177
- } catch {
178
- return null;
179
- }
180
- }
181
-
182
- /**
183
- * Extracts session metadata from one Gemini JSONL artifact.
184
- */
185
- private async processJsonlSessionFile(
186
- filePath: string,
187
- projectHashLookup: Map<string, string>
188
- ): Promise<ParsedSession | null> {
189
- const metadata = await this.extractJsonlMetadata(filePath);
190
- if (!metadata) {
191
- return null;
192
- }
193
-
194
- let projectPath = typeof metadata.projectPath === 'string' ? metadata.projectPath.trim() : '';
195
- if (!projectPath) {
196
- const workspaceProjectPath = await this.resolveProjectPathFromChatWorkspace(filePath);
197
- if (workspaceProjectPath) {
198
- projectPath = workspaceProjectPath;
199
- }
200
- }
201
- if (!projectPath && typeof metadata.projectHash === 'string') {
202
- projectPath = projectHashLookup.get(metadata.projectHash.trim().toLowerCase()) ?? '';
203
- }
204
- if (!projectPath) {
205
- return null;
206
- }
207
-
208
- // Once we resolve a project hash/path pair, keep it in-memory for this sync run.
209
- if (typeof metadata.projectHash === 'string' && metadata.projectHash.trim()) {
210
- projectHashLookup.set(metadata.projectHash.trim().toLowerCase(), projectPath);
211
- }
212
-
213
- return {
214
- sessionId: metadata.sessionId,
215
- projectPath,
216
- sessionName: normalizeSessionName(metadata.firstUserMessage, 'New Gemini Chat'),
217
- };
218
- }
219
-
220
- /**
221
- * Reads first useful metadata from Gemini JSONL files.
222
- */
223
- private async extractJsonlMetadata(filePath: string): Promise<GeminiJsonlMetadata | null> {
224
- try {
225
- const content = await readFile(filePath, 'utf8');
226
- const lines = content.split('\n');
227
-
228
- let sessionId: string | undefined;
229
- let projectPath: string | undefined;
230
- let projectHash: string | undefined;
231
- let firstUserMessage: string | undefined;
232
-
233
- for (const line of lines) {
234
- const trimmed = line.trim();
235
- if (!trimmed) {
236
- continue;
237
- }
238
-
239
- let parsed: AnyRecord;
240
- try {
241
- parsed = JSON.parse(trimmed) as AnyRecord;
242
- } catch {
243
- continue;
244
- }
245
-
246
- if (!sessionId && typeof parsed.sessionId === 'string') {
247
- sessionId = parsed.sessionId;
248
- }
249
- if (!projectPath && typeof parsed.projectPath === 'string') {
250
- projectPath = parsed.projectPath;
251
- }
252
- if (!projectHash && typeof parsed.projectHash === 'string') {
253
- projectHash = parsed.projectHash;
254
- }
255
-
256
- if (!firstUserMessage && parsed.type === 'user') {
257
- firstUserMessage = this.extractGeminiTextContent(parsed.content);
258
- }
259
-
260
- if (sessionId && (projectPath || projectHash) && firstUserMessage) {
261
- break;
262
- }
263
- }
264
-
265
- if (!sessionId) {
266
- return null;
267
- }
268
-
269
- return {
270
- sessionId,
271
- projectPath,
272
- projectHash,
273
- firstUserMessage,
274
- };
275
- } catch {
276
- return null;
277
- }
278
- }
279
-
280
- /**
281
- * Tries to resolve project root from Gemini tmp chat workspaces.
282
- */
283
- private async resolveProjectPathFromChatWorkspace(filePath: string): Promise<string> {
284
- if (!filePath.includes(`${path.sep}chats${path.sep}`)) {
285
- return '';
286
- }
287
-
288
- const chatsDir = path.dirname(filePath);
289
- const workspaceDir = path.dirname(chatsDir);
290
- const projectRootPath = path.join(workspaceDir, '.project_root');
291
-
292
- try {
293
- const rootContent = await readFile(projectRootPath, 'utf8');
294
- return rootContent.trim();
295
- } catch {
296
- return '';
297
- }
298
- }
299
-
300
- /**
301
- * Builds a hash->path lookup for Gemini JSONL metadata that stores projectHash.
302
- */
303
- private buildProjectHashLookup(): Map<string, string> {
304
- const lookup = new Map<string, string>();
305
- const knownPaths = new Set<string>();
306
-
307
- for (const project of projectsDb.getProjectPaths()) {
308
- if (typeof project.project_path === 'string' && project.project_path.trim()) {
309
- knownPaths.add(project.project_path.trim());
310
- }
311
- }
312
-
313
- for (const session of sessionsDb.getAllSessions()) {
314
- if (session.provider === this.provider && typeof session.project_path === 'string' && session.project_path.trim()) {
315
- knownPaths.add(session.project_path.trim());
316
- }
317
- }
318
-
319
- for (const knownPath of knownPaths) {
320
- this.addProjectHashCandidates(lookup, knownPath);
321
- }
322
-
323
- return lookup;
324
- }
325
-
326
- /**
327
- * Adds likely Gemini hash variants for one project path.
328
- */
329
- private addProjectHashCandidates(lookup: Map<string, string>, projectPath: string): void {
330
- const trimmed = projectPath.trim();
331
- if (!trimmed) {
332
- return;
333
- }
334
-
335
- const normalized = normalizeProjectPath(trimmed);
336
- const resolved = path.resolve(trimmed);
337
- const resolvedNormalized = normalizeProjectPath(resolved);
338
-
339
- const candidates = new Set<string>([
340
- trimmed,
341
- normalized,
342
- resolved,
343
- resolvedNormalized,
344
- ]);
345
-
346
- if (process.platform === 'win32') {
347
- for (const candidate of [...candidates]) {
348
- candidates.add(candidate.toLowerCase());
349
- }
350
- }
351
-
352
- for (const candidate of candidates) {
353
- if (!candidate) {
354
- continue;
355
- }
356
-
357
- const hash = this.sha256(candidate);
358
- if (!lookup.has(hash)) {
359
- lookup.set(hash, trimmed);
360
- }
361
- }
362
- }
363
-
364
- /**
365
- * Returns first user text from Gemini content payload shapes.
366
- */
367
- private extractGeminiTextContent(content: unknown): string | undefined {
368
- if (typeof content === 'string' && content.trim().length > 0) {
369
- return content;
370
- }
371
-
372
- if (!Array.isArray(content)) {
373
- return undefined;
374
- }
375
-
376
- for (const part of content) {
377
- if (typeof part === 'string' && part.trim().length > 0) {
378
- return part;
379
- }
380
-
381
- if (part && typeof part === 'object' && typeof (part as AnyRecord).text === 'string') {
382
- const text = (part as AnyRecord).text;
383
- if (text.trim().length > 0) {
384
- return text;
385
- }
386
- }
387
- }
388
-
389
- return undefined;
390
- }
391
-
392
- /**
393
- * Keeps tmp scanning scoped to chat artifacts only.
394
- */
395
- private shouldSkipTempArtifact(filePath: string): boolean {
396
- return (
397
- filePath.startsWith(path.join(this.geminiHome, 'tmp'))
398
- && !filePath.includes(`${path.sep}chats${path.sep}`)
399
- );
400
- }
401
-
402
- private sha256(value: string): string {
403
- return crypto.createHash('sha256').update(value).digest('hex');
404
- }
405
- }