tarsk 0.2.5 → 0.3.0

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 (119) hide show
  1. package/README.md +1 -7
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.js +92 -32
  4. package/dist/lib/response-builder.d.ts +50 -0
  5. package/dist/lib/response-builder.js +56 -0
  6. package/dist/lib/stream-helper.d.ts +39 -0
  7. package/dist/lib/stream-helper.js +43 -0
  8. package/dist/managers/ConversationManager.d.ts +83 -0
  9. package/dist/managers/ConversationManager.js +129 -0
  10. package/dist/managers/GitManager.d.ts +133 -0
  11. package/dist/managers/GitManager.js +330 -0
  12. package/dist/managers/MetadataManager.d.ts +139 -0
  13. package/dist/managers/MetadataManager.js +309 -0
  14. package/dist/managers/ModelManager.d.ts +57 -0
  15. package/dist/managers/ModelManager.js +129 -0
  16. package/dist/managers/NeovateExecutor.d.ts +40 -0
  17. package/dist/managers/NeovateExecutor.js +138 -0
  18. package/dist/managers/ProjectManager.d.ts +162 -0
  19. package/dist/managers/ProjectManager.js +353 -0
  20. package/dist/managers/ThreadManager.d.ts +181 -0
  21. package/dist/managers/ThreadManager.js +325 -0
  22. package/dist/managers/conversation-manager.d.ts +83 -0
  23. package/dist/managers/conversation-manager.js +129 -0
  24. package/dist/managers/git-manager.d.ts +133 -0
  25. package/dist/managers/git-manager.js +330 -0
  26. package/dist/managers/metadata-manager.d.ts +139 -0
  27. package/dist/managers/metadata-manager.js +305 -0
  28. package/dist/managers/model-manager.d.ts +59 -0
  29. package/dist/managers/model-manager.js +144 -0
  30. package/dist/managers/neovate-executor.d.ts +43 -0
  31. package/dist/managers/neovate-executor.js +205 -0
  32. package/dist/managers/processing-state-manager.d.ts +40 -0
  33. package/dist/managers/processing-state-manager.js +27 -0
  34. package/dist/managers/project-manager.d.ts +199 -0
  35. package/dist/managers/project-manager.js +465 -0
  36. package/dist/managers/thread-manager.d.ts +193 -0
  37. package/dist/managers/thread-manager.js +368 -0
  38. package/dist/model-info-aihubmix.d.ts +25 -0
  39. package/dist/model-info-aihubmix.js +117 -0
  40. package/dist/model-info-openai.d.ts +17 -0
  41. package/dist/model-info-openai.js +59 -0
  42. package/dist/model-info-openrouter.d.ts +25 -0
  43. package/dist/model-info-openrouter.js +101 -0
  44. package/dist/model-info.d.ts +37 -0
  45. package/dist/model-info.js +39 -0
  46. package/dist/provider-data.d.ts +101 -0
  47. package/dist/provider-data.js +471 -0
  48. package/dist/provider.d.ts +10 -0
  49. package/dist/provider.js +192 -0
  50. package/dist/public/android-chrome-192x192.png +0 -0
  51. package/dist/public/android-chrome-512x512.png +0 -0
  52. package/dist/public/apple-touch-icon.png +0 -0
  53. package/dist/public/assets/index-B443aj9k.js +8506 -0
  54. package/dist/public/assets/index-CjXGVbI7.css +1 -0
  55. package/dist/public/assets/index-DJC-p914.js +8506 -0
  56. package/dist/public/favicon-16x16.png +0 -0
  57. package/dist/public/favicon-32x32.png +0 -0
  58. package/dist/public/favicon.ico +0 -0
  59. package/dist/public/index.html +28 -0
  60. package/dist/public/manifest.json +82 -0
  61. package/dist/public/placeholder-logo.svg +1 -0
  62. package/dist/public/placeholder.svg +1 -0
  63. package/dist/public/snpro.woff2 +0 -0
  64. package/dist/public/tarsk-color.svg +12 -0
  65. package/dist/public/tarsk.png +0 -0
  66. package/dist/public/tarsk.svg +12 -0
  67. package/dist/public/zalando-sans.woff2 +0 -0
  68. package/dist/routes/chat-old.d.ts +21 -0
  69. package/dist/routes/chat-old.js +251 -0
  70. package/dist/routes/chat.d.ts +21 -0
  71. package/dist/routes/chat.js +217 -0
  72. package/dist/routes/git.d.ts +4 -0
  73. package/dist/routes/git.js +668 -0
  74. package/dist/routes/models.d.ts +18 -0
  75. package/dist/routes/models.js +128 -0
  76. package/dist/routes/projects-old.d.ts +20 -0
  77. package/dist/routes/projects-old.js +297 -0
  78. package/dist/routes/projects.d.ts +20 -0
  79. package/dist/routes/projects.js +365 -0
  80. package/dist/routes/providers.d.ts +15 -0
  81. package/dist/routes/providers.js +130 -0
  82. package/dist/routes/threads-old.d.ts +14 -0
  83. package/dist/routes/threads-old.js +393 -0
  84. package/dist/routes/threads.d.ts +14 -0
  85. package/dist/routes/threads.js +352 -0
  86. package/dist/types/models.d.ts +315 -0
  87. package/dist/types/models.js +11 -0
  88. package/dist/utils/env-manager.d.ts +3 -0
  89. package/dist/utils/env-manager.js +60 -0
  90. package/dist/utils/open-router-models.d.ts +45 -0
  91. package/dist/utils/open-router-models.js +103 -0
  92. package/dist/utils/openai-models.d.ts +63 -0
  93. package/dist/utils/openai-models.js +152 -0
  94. package/dist/utils/openai-pricing-scraper.d.ts +17 -0
  95. package/dist/utils/openai-pricing-scraper.js +185 -0
  96. package/dist/utils/validation.d.ts +10 -0
  97. package/dist/utils/validation.js +20 -0
  98. package/dist/utils.d.ts +10 -0
  99. package/dist/utils.js +12 -0
  100. package/package.json +36 -22
  101. package/LICENSE.md +0 -7
  102. package/dist/agent/agent.js +0 -131
  103. package/dist/agent/interfaces.js +0 -1
  104. package/dist/api/encryption.js +0 -41
  105. package/dist/api/models.js +0 -169
  106. package/dist/api/prompt.js +0 -12
  107. package/dist/api/settings.js +0 -43
  108. package/dist/api/test.js +0 -29
  109. package/dist/api/tools.js +0 -287
  110. package/dist/api/utils.js +0 -18
  111. package/dist/interfaces/meta.js +0 -1
  112. package/dist/interfaces/model.js +0 -1
  113. package/dist/interfaces/settings.js +0 -1
  114. package/dist/log/log.js +0 -33
  115. package/dist/prompt.js +0 -49
  116. package/dist/tools.js +0 -84
  117. package/dist/utils/files.js +0 -14
  118. package/dist/utils/json-file.js +0 -28
  119. package/dist/utils/strip-markdown.js +0 -5
@@ -0,0 +1,325 @@
1
+ /**
2
+ * ThreadManager handles thread lifecycle and metadata management
3
+ *
4
+ * This manager is responsible for:
5
+ * - Creating new threads for existing projects
6
+ * - Managing thread metadata
7
+ * - Coordinating with GitManager for repository cloning
8
+ * - Providing CRUD operations for threads
9
+ * - Ensuring thread path uniqueness
10
+ */
11
+ import { randomUUID } from 'crypto';
12
+ import { join } from 'path';
13
+ /**
14
+ * ThreadManagerImpl provides the implementation for thread operations
15
+ */
16
+ export class ThreadManagerImpl {
17
+ metadataManager;
18
+ gitManager;
19
+ /**
20
+ * Create a new ThreadManager
21
+ * @param metadataManager - Manager for persisting metadata
22
+ * @param gitManager - Manager for git operations
23
+ *
24
+ * Requirements:
25
+ * - 2.1 - WHEN a user creates a new Thread for a Project, THE CLI SHALL create a new clone of the Project's git repository
26
+ * - 2.4 - THE System SHALL maintain a title for each Thread
27
+ */
28
+ constructor(metadataManager, gitManager) {
29
+ this.metadataManager = metadataManager;
30
+ this.gitManager = gitManager;
31
+ }
32
+ /**
33
+ * Creates a new thread for an existing project
34
+ *
35
+ * This method:
36
+ * 1. Validates that the project exists
37
+ * 2. Generates a unique thread ID
38
+ * 3. Gets the project's git URL
39
+ * 4. Generates a unique thread path
40
+ * 5. Delegates to GitManager for cloning
41
+ * 6. Saves thread metadata
42
+ * 7. Updates the project's thread list
43
+ * 8. Yields progress events during the operation
44
+ *
45
+ * @param projectId - The parent project ID
46
+ * @param title - Optional thread title (auto-generated if not provided)
47
+ * @yields ThreadEvent objects during the creation process
48
+ *
49
+ * Requirements:
50
+ * - 2.1 - WHEN a user creates a new Thread for a Project, THE CLI SHALL create a new clone of the Project's git repository
51
+ * - 2.2 - WHEN a Thread is created, THE App SHALL display it under its parent Project in the Side_Panel
52
+ * - 2.4 - THE System SHALL maintain a title for each Thread
53
+ * - 7.4 - THE CLI SHALL ensure each Thread clone is stored in a unique directory path
54
+ */
55
+ async *createThread(projectId, title) {
56
+ try {
57
+ // Load project to verify it exists and get git URL
58
+ const projects = await this.metadataManager.loadProjects();
59
+ const project = projects.find(p => p.id === projectId);
60
+ if (!project) {
61
+ yield {
62
+ type: 'error',
63
+ message: `Project not found: ${projectId}`
64
+ };
65
+ return;
66
+ }
67
+ // Generate unique thread ID
68
+ const threadId = randomUUID();
69
+ // Generate thread path: {projectPath}/{threadId}
70
+ const threadPath = this.generateThreadPath(project.path, threadId);
71
+ // Generate thread title if not provided
72
+ const existingThreads = await this.metadataManager.loadThreads();
73
+ const existingTitles = new Set(existingThreads
74
+ .filter(t => t.projectId === projectId)
75
+ .map(t => t.title));
76
+ const threadTitle = title || this.generateThreadTitle(existingTitles, threadId, project.name);
77
+ yield {
78
+ type: 'progress',
79
+ message: `Creating thread "${threadTitle}" for project "${project.name}"...`
80
+ };
81
+ yield {
82
+ type: 'progress',
83
+ message: `Cloning repository to ${threadPath}...`
84
+ };
85
+ // Stream git clone events
86
+ for await (const gitEvent of this.gitManager.cloneRepository(project.gitUrl, threadPath)) {
87
+ if (gitEvent.type === 'error') {
88
+ yield {
89
+ type: 'error',
90
+ message: `Git clone failed: ${gitEvent.message}`
91
+ };
92
+ return;
93
+ }
94
+ else if (gitEvent.type === 'stdout' || gitEvent.type === 'stderr') {
95
+ yield {
96
+ type: 'progress',
97
+ message: gitEvent.data
98
+ };
99
+ }
100
+ }
101
+ // Sanitize the thread title to create a valid branch name
102
+ let branchName = this.gitManager.sanitizeBranchName(threadTitle);
103
+ yield {
104
+ type: 'progress',
105
+ message: `Creating branch "${branchName}"...`
106
+ };
107
+ // Check if branch exists and find a unique name if needed
108
+ let branchExists = await this.gitManager.checkBranchExists(threadPath, branchName);
109
+ let counter = 2;
110
+ const baseBranchName = branchName;
111
+ while (branchExists) {
112
+ branchName = `${baseBranchName}-${counter}`;
113
+ branchExists = await this.gitManager.checkBranchExists(threadPath, branchName);
114
+ counter++;
115
+ }
116
+ if (branchName !== baseBranchName) {
117
+ yield {
118
+ type: 'progress',
119
+ message: `Branch "${baseBranchName}" exists, using "${branchName}" instead`
120
+ };
121
+ }
122
+ // Create and checkout the new branch
123
+ for await (const gitEvent of this.gitManager.createAndCheckoutBranch(threadPath, branchName)) {
124
+ if (gitEvent.type === 'error') {
125
+ yield {
126
+ type: 'error',
127
+ message: `Failed to create branch: ${gitEvent.message}`
128
+ };
129
+ return;
130
+ }
131
+ else if (gitEvent.type === 'stdout' || gitEvent.type === 'stderr') {
132
+ yield {
133
+ type: 'progress',
134
+ message: gitEvent.data
135
+ };
136
+ }
137
+ }
138
+ yield {
139
+ type: 'progress',
140
+ message: `Branch "${branchName}" created and checked out`
141
+ };
142
+ // Create thread metadata
143
+ const thread = {
144
+ id: threadId,
145
+ projectId,
146
+ title: threadTitle,
147
+ path: threadPath,
148
+ currentBranch: branchName,
149
+ createdAt: new Date()
150
+ };
151
+ // Save thread metadata
152
+ const threads = await this.metadataManager.loadThreads();
153
+ threads.push(thread);
154
+ await this.metadataManager.saveThreads(threads);
155
+ // Update project's thread list
156
+ project.threads.push(threadId);
157
+ const updatedProjects = projects.map(p => p.id === projectId ? project : p);
158
+ await this.metadataManager.saveProjects(updatedProjects);
159
+ yield {
160
+ type: 'progress',
161
+ message: `Thread "${threadTitle}" created successfully`
162
+ };
163
+ // Yield complete event with thread data
164
+ yield {
165
+ type: 'complete',
166
+ message: 'Thread creation complete',
167
+ thread
168
+ };
169
+ }
170
+ catch (error) {
171
+ yield {
172
+ type: 'error',
173
+ message: `Failed to create thread: ${error instanceof Error ? error.message : String(error)}`
174
+ };
175
+ }
176
+ }
177
+ /**
178
+ * Gets a thread by ID
179
+ * @param threadId - The thread ID
180
+ * @returns The thread or null if not found
181
+ */
182
+ async getThread(threadId) {
183
+ const threads = await this.metadataManager.loadThreads();
184
+ return threads.find(t => t.id === threadId) || null;
185
+ }
186
+ /**
187
+ * Lists all threads for a specific project
188
+ * @param projectId - The project ID to filter threads
189
+ * @returns Array of threads for the project
190
+ *
191
+ * Requirements:
192
+ * - 2.1 - WHEN a Thread is created, THE App SHALL display it under its parent Project in the Side_Panel
193
+ */
194
+ async listThreads(projectId) {
195
+ const threads = await this.metadataManager.loadThreads();
196
+ return threads.filter(t => t.projectId === projectId);
197
+ }
198
+ /**
199
+ * Deletes a thread and removes its directory
200
+ *
201
+ * This method:
202
+ * 1. Finds the thread by ID
203
+ * 2. Removes the thread directory from filesystem
204
+ * 3. Removes the thread from metadata
205
+ * 4. Updates the parent project's thread list
206
+ *
207
+ * @param threadId - The thread ID to delete
208
+ * @throws Error if thread not found
209
+ *
210
+ * Requirements:
211
+ * - 2.3 - WHEN a user deletes a Thread, THE System SHALL remove the Thread's folder and update the Side_Panel display
212
+ * - 7.5 - WHEN a Thread is deleted, THE CLI SHALL remove the associated git repository clone from the filesystem
213
+ */
214
+ async deleteThread(threadId) {
215
+ const threads = await this.metadataManager.loadThreads();
216
+ const threadIndex = threads.findIndex(t => t.id === threadId);
217
+ if (threadIndex === -1) {
218
+ throw new Error(`Thread not found: ${threadId}`);
219
+ }
220
+ const thread = threads[threadIndex];
221
+ // Remove thread directory from filesystem
222
+ try {
223
+ const { rm } = await import('fs/promises');
224
+ await rm(thread.path, { recursive: true, force: true });
225
+ }
226
+ catch (error) {
227
+ throw new Error(`Failed to remove thread directory: ${error instanceof Error ? error.message : String(error)}`);
228
+ }
229
+ // Remove thread from metadata
230
+ threads.splice(threadIndex, 1);
231
+ await this.metadataManager.saveThreads(threads);
232
+ // Update parent project's thread list
233
+ const projects = await this.metadataManager.loadProjects();
234
+ const project = projects.find(p => p.id === thread.projectId);
235
+ if (project) {
236
+ project.threads = project.threads.filter(id => id !== threadId);
237
+ await this.metadataManager.saveProjects(projects);
238
+ }
239
+ }
240
+ /**
241
+ * Updates a thread's metadata with partial data
242
+ *
243
+ * This method loads all threads, finds the target, merges the updates,
244
+ * and persists the result.
245
+ *
246
+ * @param threadId - The thread ID to update
247
+ * @param updates - Partial thread data to merge
248
+ * @throws Error if thread not found
249
+ */
250
+ async updateThread(threadId, updates) {
251
+ const threads = await this.metadataManager.loadThreads();
252
+ const threadIndex = threads.findIndex(t => t.id === threadId);
253
+ if (threadIndex === -1) {
254
+ throw new Error(`Thread not found: ${threadId}`);
255
+ }
256
+ threads[threadIndex] = { ...threads[threadIndex], ...updates };
257
+ await this.metadataManager.saveThreads(threads);
258
+ }
259
+ /**
260
+ * Selects a thread as the active thread
261
+ *
262
+ * @param threadId - The thread ID to select
263
+ */
264
+ async selectThread(threadId) {
265
+ await this.metadataManager.setSelectedThread(threadId);
266
+ }
267
+ /**
268
+ * Gets the currently selected thread ID
269
+ *
270
+ * @returns The selected thread ID or null
271
+ */
272
+ async getSelectedThreadId() {
273
+ return await this.metadataManager.getSelectedThread();
274
+ }
275
+ /**
276
+ * Generates a unique thread path
277
+ *
278
+ * Path format: {projectPath}/{threadId}
279
+ *
280
+ * This ensures thread paths are unique across all projects and threads.
281
+ *
282
+ * @param projectPath - The parent project path
283
+ * @param threadId - The thread ID
284
+ * @returns The absolute path to the thread folder
285
+ *
286
+ * Requirements:
287
+ * - 7.4 - THE CLI SHALL ensure each Thread clone is stored in a unique directory path
288
+ */
289
+ generateThreadPath(projectPath, threadId) {
290
+ return join(projectPath, threadId);
291
+ }
292
+ /**
293
+ * Generates a thread title if not provided
294
+ *
295
+ * Format: project-name Thread word1-word2
296
+ *
297
+ * @param existingTitles - Set of titles already in use
298
+ * @param threadId - Thread ID used as a fallback
299
+ * @param projectName - Optional project name to include in title
300
+ * @returns The generated thread title
301
+ */
302
+ generateThreadTitle(existingTitles, threadId, projectName) {
303
+ const wordsA = [
304
+ 'amber', 'brisk', 'calm', 'drift', 'ember', 'frost', 'glow', 'hollow',
305
+ 'ivory', 'jolly', 'keen', 'lucid', 'mellow', 'noble', 'opal', 'prism',
306
+ 'quiet', 'ripple', 'sunny', 'timber', 'ultra', 'vivid', 'wisp', 'zen'
307
+ ];
308
+ const wordsB = [
309
+ 'arch', 'bay', 'crest', 'dune', 'echo', 'fjord', 'glen', 'harbor',
310
+ 'isle', 'jewel', 'knoll', 'lagoon', 'mesa', 'nest', 'oasis', 'peak',
311
+ 'quarry', 'reef', 'strand', 'trail', 'vale', 'wharf', 'yard', 'zephyr'
312
+ ];
313
+ const prefix = projectName ? `${projectName} Thread ` : 'Thread ';
314
+ for (let attempt = 0; attempt < 12; attempt += 1) {
315
+ const word1 = wordsA[Math.floor(Math.random() * wordsA.length)];
316
+ const word2 = wordsB[Math.floor(Math.random() * wordsB.length)];
317
+ const candidate = `${prefix}${word1}-${word2}`;
318
+ if (!existingTitles.has(candidate)) {
319
+ return candidate;
320
+ }
321
+ }
322
+ return `${prefix}${threadId.slice(0, 8)}`;
323
+ }
324
+ }
325
+ //# sourceMappingURL=ThreadManager.js.map
@@ -0,0 +1,83 @@
1
+ /**
2
+ * ConversationManager handles conversation history storage and retrieval
3
+ *
4
+ * This manager is responsible for:
5
+ * - Storing request/response pairs in JSON format
6
+ * - Retrieving conversation history for a thread
7
+ * - Managing conversation persistence
8
+ */
9
+ import { ConversationHistory, ConversationMessage, NeovateEvent, FileData } from '../types/models.js';
10
+ /**
11
+ * ConversationManager interface defines the contract for conversation operations
12
+ */
13
+ export interface ConversationManager {
14
+ /**
15
+ * Starts a new conversation message (request captured)
16
+ * @param threadId - The thread ID
17
+ * @param threadPath - The absolute path to the thread directory
18
+ * @param message - The user's message
19
+ * @param model - The model being used
20
+ * @param attachments - Optional file attachments
21
+ * @param planMode - Optional plan mode flag
22
+ * @returns The message ID for this conversation
23
+ */
24
+ startMessage(threadId: string, threadPath: string, message: string, model: string, attachments?: FileData[], planMode?: boolean): Promise<string>;
25
+ /**
26
+ * Completes a conversation message (response captured)
27
+ * @param threadPath - The absolute path to the thread directory
28
+ * @param messageId - The message ID to complete
29
+ * @param content - The complete response content
30
+ * @param events - All events captured during execution
31
+ */
32
+ completeMessage(threadPath: string, messageId: string, content: string, events: NeovateEvent[]): Promise<void>;
33
+ /**
34
+ * Gets the conversation history for a thread
35
+ * @param threadPath - The absolute path to the thread directory
36
+ * @returns The conversation history or null if not found
37
+ */
38
+ getConversationHistory(threadPath: string): Promise<ConversationHistory | null>;
39
+ /**
40
+ * Gets the last N messages from conversation history
41
+ * @param threadPath - The absolute path to the thread directory
42
+ * @param count - Number of messages to retrieve (default: 1)
43
+ * @returns Array of conversation messages
44
+ */
45
+ getLastMessages(threadPath: string, count?: number): Promise<ConversationMessage[]>;
46
+ }
47
+ /**
48
+ * ConversationManagerImpl provides the implementation for conversation operations
49
+ */
50
+ export declare class ConversationManagerImpl implements ConversationManager {
51
+ private metadataDir;
52
+ /**
53
+ * Create a new ConversationManagerImpl
54
+ * @param rootFolder - Base directory where metadata will be stored
55
+ */
56
+ constructor(rootFolder?: string);
57
+ /**
58
+ * Starts a new conversation message
59
+ */
60
+ startMessage(threadId: string, threadPath: string, message: string, model: string, attachments?: FileData[], planMode?: boolean): Promise<string>;
61
+ /**
62
+ * Completes a conversation message
63
+ */
64
+ completeMessage(threadPath: string, messageId: string, content: string, events: NeovateEvent[]): Promise<void>;
65
+ /**
66
+ * Gets the conversation history for a thread
67
+ */
68
+ getConversationHistory(threadPath: string): Promise<ConversationHistory | null>;
69
+ /**
70
+ * Gets the last N messages from conversation history
71
+ */
72
+ getLastMessages(threadPath: string, count?: number): Promise<ConversationMessage[]>;
73
+ /**
74
+ * Saves conversation history to file
75
+ */
76
+ private saveConversationHistory;
77
+ /**
78
+ * Gets the file path for a thread's conversation history in the metadata folder
79
+ * Extracts threadId from threadPath and stores in .metadata/conversations/<threadId>/history.json
80
+ */
81
+ private getConversationFilePath;
82
+ }
83
+ //# sourceMappingURL=conversation-manager.d.ts.map
@@ -0,0 +1,129 @@
1
+ /**
2
+ * ConversationManager handles conversation history storage and retrieval
3
+ *
4
+ * This manager is responsible for:
5
+ * - Storing request/response pairs in JSON format
6
+ * - Retrieving conversation history for a thread
7
+ * - Managing conversation persistence
8
+ */
9
+ import { promises as fs } from 'fs';
10
+ import { join, dirname } from 'path';
11
+ import { randomUUID } from 'crypto';
12
+ const CONVERSATION_FILE = 'conversation-history.json';
13
+ const METADATA_DIR = '.metadata';
14
+ /**
15
+ * ConversationManagerImpl provides the implementation for conversation operations
16
+ */
17
+ export class ConversationManagerImpl {
18
+ metadataDir;
19
+ /**
20
+ * Create a new ConversationManagerImpl
21
+ * @param rootFolder - Base directory where metadata will be stored
22
+ */
23
+ constructor(rootFolder = './projects') {
24
+ this.metadataDir = join(rootFolder, METADATA_DIR);
25
+ }
26
+ /**
27
+ * Starts a new conversation message
28
+ */
29
+ async startMessage(threadId, threadPath, message, model, attachments, planMode) {
30
+ const messageId = randomUUID();
31
+ const timestamp = new Date().toISOString();
32
+ // Load existing history or create new
33
+ let history = await this.getConversationHistory(threadPath);
34
+ if (!history) {
35
+ history = {
36
+ threadId,
37
+ messages: [],
38
+ lastUpdated: timestamp,
39
+ };
40
+ }
41
+ // Create incomplete message (will be completed later)
42
+ const incompleteMessage = {
43
+ id: messageId,
44
+ timestamp,
45
+ request: {
46
+ message,
47
+ model,
48
+ ...(attachments && { attachments }),
49
+ ...(planMode && { planMode }),
50
+ },
51
+ response: null,
52
+ };
53
+ history.messages.push(incompleteMessage);
54
+ history.lastUpdated = timestamp;
55
+ // Save to file
56
+ await this.saveConversationHistory(threadPath, history);
57
+ return messageId;
58
+ }
59
+ /**
60
+ * Completes a conversation message
61
+ */
62
+ async completeMessage(threadPath, messageId, content, events) {
63
+ const history = await this.getConversationHistory(threadPath);
64
+ if (!history) {
65
+ throw new Error('Conversation history not found');
66
+ }
67
+ // Find the message to complete
68
+ const message = history.messages.find((m) => m.id === messageId);
69
+ if (!message) {
70
+ throw new Error(`Message ${messageId} not found in conversation history`);
71
+ }
72
+ // Complete the message with response data
73
+ message.response = {
74
+ content,
75
+ events,
76
+ completedAt: new Date().toISOString(),
77
+ };
78
+ history.lastUpdated = new Date().toISOString();
79
+ // Save to file
80
+ await this.saveConversationHistory(threadPath, history);
81
+ }
82
+ /**
83
+ * Gets the conversation history for a thread
84
+ */
85
+ async getConversationHistory(threadPath) {
86
+ try {
87
+ const filePath = this.getConversationFilePath(threadPath);
88
+ const content = await fs.readFile(filePath, 'utf-8');
89
+ return JSON.parse(content);
90
+ }
91
+ catch (error) {
92
+ if (error instanceof Error && error.code === 'ENOENT') {
93
+ return null;
94
+ }
95
+ throw error;
96
+ }
97
+ }
98
+ /**
99
+ * Gets the last N messages from conversation history
100
+ */
101
+ async getLastMessages(threadPath, count = 1) {
102
+ const history = await this.getConversationHistory(threadPath);
103
+ if (!history || history.messages.length === 0) {
104
+ return [];
105
+ }
106
+ // Return last N messages
107
+ return history.messages.slice(-count);
108
+ }
109
+ /**
110
+ * Saves conversation history to file
111
+ */
112
+ async saveConversationHistory(threadPath, history) {
113
+ const filePath = this.getConversationFilePath(threadPath);
114
+ // Ensure all parent directories exist
115
+ await fs.mkdir(dirname(filePath), { recursive: true });
116
+ await fs.writeFile(filePath, JSON.stringify(history, null, 2), 'utf-8');
117
+ }
118
+ /**
119
+ * Gets the file path for a thread's conversation history in the metadata folder
120
+ * Extracts threadId from threadPath and stores in .metadata/conversations/<threadId>/history.json
121
+ */
122
+ getConversationFilePath(threadPath) {
123
+ // Extract thread ID from path (last part of the path)
124
+ const threadId = threadPath.split(/[\\/]/).pop() || 'unknown';
125
+ const conversationsDir = join(this.metadataDir, 'conversations');
126
+ return join(conversationsDir, threadId, CONVERSATION_FILE);
127
+ }
128
+ }
129
+ //# sourceMappingURL=conversation-manager.js.map
@@ -0,0 +1,133 @@
1
+ /**
2
+ * GitManager handles git operations including URL validation and repository cloning
3
+ *
4
+ * This manager is responsible for:
5
+ * - Validating git URLs in various formats
6
+ * - Cloning repositories with streaming output
7
+ * - Handling git operation errors
8
+ */
9
+ import { GitEvent } from '../types/models.js';
10
+ /**
11
+ * GitManager interface defines the contract for git operations
12
+ */
13
+ export interface GitManager {
14
+ /**
15
+ * Validates a git URL format
16
+ * @param url - The git URL to validate
17
+ * @returns true if the URL is a valid git URL, false otherwise
18
+ */
19
+ validateGitUrl(url: string): boolean;
20
+ /**
21
+ * Clones a git repository to the specified target path
22
+ * @param gitUrl - The git repository URL to clone
23
+ * @param targetPath - The local path where the repository should be cloned
24
+ * @yields GitEvent objects for stdout, stderr, complete, and error events
25
+ */
26
+ cloneRepository(gitUrl: string, targetPath: string): AsyncGenerator<GitEvent>;
27
+ /**
28
+ * Sanitizes a branch name to ensure it only contains valid git branch characters
29
+ * Replaces spaces with dashes and removes invalid characters
30
+ * @param name - The branch name to sanitize
31
+ * @returns The sanitized branch name
32
+ */
33
+ sanitizeBranchName(name: string): string;
34
+ /**
35
+ * Checks if a branch exists in a git repository
36
+ * @param repoPath - The path to the git repository
37
+ * @param branchName - The branch name to check
38
+ * @returns true if the branch exists, false otherwise
39
+ */
40
+ checkBranchExists(repoPath: string, branchName: string): Promise<boolean>;
41
+ /**
42
+ * Creates and checks out a new branch in a git repository
43
+ * @param repoPath - The path to the git repository
44
+ * @param branchName - The branch name to create
45
+ * @yields GitEvent objects for stdout, stderr, complete, and error events
46
+ */
47
+ createAndCheckoutBranch(repoPath: string, branchName: string): AsyncGenerator<GitEvent>;
48
+ }
49
+ /**
50
+ * GitManagerImpl provides the implementation for git operations
51
+ */
52
+ export declare class GitManagerImpl implements GitManager {
53
+ /**
54
+ * Regular expressions for validating different git URL formats
55
+ */
56
+ private readonly gitUrlPatterns;
57
+ /**
58
+ * Validates a git URL against common git URL formats
59
+ *
60
+ * Supports the following formats:
61
+ * - HTTPS: https://github.com/user/repo.git
62
+ * - Git protocol: git://github.com/user/repo.git
63
+ * - SSH (git@): git@github.com:user/repo.git
64
+ * - SSH (ssh://): ssh://git@github.com/user/repo.git
65
+ *
66
+ * @param url - The URL to validate
67
+ * @returns true if the URL matches a valid git URL format, false otherwise
68
+ *
69
+ * Requirements: 7.1 - WHEN cloning a git repository, THE CLI SHALL validate the git URL format
70
+ */
71
+ validateGitUrl(url: string): boolean;
72
+ /**
73
+ * Clones a git repository to the specified target path with streaming output
74
+ *
75
+ * This method:
76
+ * 1. Validates the git URL
77
+ * 2. Creates the target directory if needed
78
+ * 3. Spawns a git clone process
79
+ * 4. Streams stdout and stderr output
80
+ * 5. Handles completion and errors
81
+ *
82
+ * @param gitUrl - The git repository URL to clone
83
+ * @param targetPath - The local path where the repository should be cloned
84
+ * @yields GitEvent objects during the clone operation
85
+ *
86
+ * Requirements:
87
+ * - 1.1 - WHEN a user provides a git URL to create a new Project, THE CLI SHALL clone the repository
88
+ * - 1.3 - WHEN the clone operation executes, THE CLI SHALL stream the git clone output back to the App
89
+ * - 7.1 - WHEN cloning a git repository, THE CLI SHALL validate the git URL format
90
+ * - 7.2 - IF a git clone operation fails, THEN THE CLI SHALL return a descriptive error message
91
+ * - 7.3 - WHEN a clone operation is in progress, THE CLI SHALL stream git output to provide progress feedback
92
+ */
93
+ cloneRepository(gitUrl: string, targetPath: string): AsyncGenerator<GitEvent>;
94
+ /**
95
+ * Sanitizes a branch name to ensure it only contains valid git branch characters
96
+ *
97
+ * This method:
98
+ * 1. Trims whitespace
99
+ * 2. Converts to lowercase
100
+ * 3. Replaces spaces with dashes
101
+ * 4. Removes invalid characters (only allows alphanumeric, dash, underscore, slash, dot)
102
+ * 5. Removes leading/trailing slashes and dots
103
+ * 6. Collapses multiple consecutive dashes into one
104
+ *
105
+ * @param name - The branch name to sanitize
106
+ * @returns The sanitized branch name (all lowercase)
107
+ */
108
+ sanitizeBranchName(name: string): string;
109
+ /**
110
+ * Checks if a branch exists in a git repository
111
+ *
112
+ * Uses `git show-ref` to check for the branch without checking out
113
+ *
114
+ * @param repoPath - The path to the git repository
115
+ * @param branchName - The branch name to check
116
+ * @returns true if the branch exists, false otherwise
117
+ */
118
+ checkBranchExists(repoPath: string, branchName: string): Promise<boolean>;
119
+ /**
120
+ * Creates and checks out a new branch in a git repository with streaming output
121
+ *
122
+ * This method:
123
+ * 1. Creates a new branch from the current HEAD
124
+ * 2. Checks out the new branch
125
+ * 3. Streams output during the operation
126
+ *
127
+ * @param repoPath - The path to the git repository
128
+ * @param branchName - The branch name to create
129
+ * @yields GitEvent objects during the branch creation
130
+ */
131
+ createAndCheckoutBranch(repoPath: string, branchName: string): AsyncGenerator<GitEvent>;
132
+ }
133
+ //# sourceMappingURL=git-manager.d.ts.map