tarsk 0.3.19 → 0.3.20

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 (67) hide show
  1. package/dist/index.js +24 -8
  2. package/dist/lib/conversation-content.d.ts +28 -0
  3. package/dist/lib/conversation-content.js +130 -0
  4. package/dist/lib/response-builder.d.ts +1 -1
  5. package/dist/managers/conversation-manager.d.ts +1 -4
  6. package/dist/managers/git-manager.d.ts +7 -1
  7. package/dist/managers/git-manager.js +8 -1
  8. package/dist/managers/metadata-manager.d.ts +14 -12
  9. package/dist/managers/metadata-manager.js +65 -6
  10. package/dist/managers/model-manager.d.ts +1 -1
  11. package/dist/managers/neovate-executor.d.ts +1 -1
  12. package/dist/managers/process-manager.d.ts +55 -0
  13. package/dist/managers/process-manager.js +221 -0
  14. package/dist/managers/project-manager.d.ts +110 -9
  15. package/dist/managers/project-manager.js +327 -82
  16. package/dist/managers/thread-manager.d.ts +1 -7
  17. package/dist/provider-data.d.ts +9 -9
  18. package/dist/provider-data.js +16 -5
  19. package/dist/public/assets/index-BLGMN24v.css +1 -0
  20. package/dist/public/assets/index-ClP26kjT.js +86 -0
  21. package/dist/public/index.html +2 -2
  22. package/dist/public/template-types/logo-angular.svg +33 -0
  23. package/dist/public/template-types/logo-astro.svg +11 -0
  24. package/dist/public/template-types/logo-capacitor.svg +8 -0
  25. package/dist/public/template-types/logo-hydrogen.svg +1 -0
  26. package/dist/public/template-types/logo-lit.svg +1 -0
  27. package/dist/public/template-types/logo-nextjs.svg +11 -0
  28. package/dist/public/template-types/logo-nuxt.svg +3 -0
  29. package/dist/public/template-types/logo-preact.svg +6 -0
  30. package/dist/public/template-types/logo-qwik.svg +5 -0
  31. package/dist/public/template-types/logo-react.svg +1 -0
  32. package/dist/public/template-types/logo-solid.svg +1 -0
  33. package/dist/public/template-types/logo-svelte.svg +15 -0
  34. package/dist/public/template-types/logo-vite.svg +15 -0
  35. package/dist/public/template-types/logo-vue.svg +1 -0
  36. package/dist/public/template-types/logo-waku.svg +19 -0
  37. package/dist/public/template-types/logo-web.svg +14 -0
  38. package/dist/routes/chat.js +21 -10
  39. package/dist/routes/git.js +173 -0
  40. package/dist/routes/onboarding.d.ts +17 -0
  41. package/dist/routes/onboarding.js +94 -0
  42. package/dist/routes/projects.js +158 -65
  43. package/dist/routes/run.d.ts +18 -0
  44. package/dist/routes/run.js +180 -0
  45. package/dist/routes/scaffold.d.ts +10 -0
  46. package/dist/routes/scaffold.js +48 -0
  47. package/dist/routes/threads.js +44 -45
  48. package/dist/scaffold/index.d.ts +7 -0
  49. package/dist/scaffold/index.js +5 -0
  50. package/dist/scaffold/runner.d.ts +46 -0
  51. package/dist/scaffold/runner.js +378 -0
  52. package/dist/scaffold/types.d.ts +24 -0
  53. package/dist/scaffold/types.js +5 -0
  54. package/dist/scaffold-templates.json +559 -0
  55. package/dist/utils/crypto.d.ts +29 -0
  56. package/dist/utils/crypto.js +122 -0
  57. package/dist/utils/open-router-models.d.ts +1 -1
  58. package/dist/utils/openai-models.d.ts +1 -1
  59. package/dist/utils/run-command-detector.d.ts +26 -0
  60. package/dist/utils/run-command-detector.js +98 -0
  61. package/package.json +7 -4
  62. package/dist/public/assets/index-DBN5QJqj.css +0 -1
  63. package/dist/public/assets/index-DsG11Rhy.js +0 -8499
  64. package/dist/public/placeholder-logo.svg +0 -1
  65. package/dist/public/placeholder.svg +0 -1
  66. package/dist/types/models.d.ts +0 -325
  67. package/dist/types/models.js +0 -11
package/dist/index.js CHANGED
@@ -19,7 +19,10 @@ import { createChatRoutes } from './routes/chat.js';
19
19
  import { createProviderRoutes } from './routes/providers.js';
20
20
  import { createModelRoutes } from './routes/models.js';
21
21
  import { createGitRoutes } from './routes/git.js';
22
- import { AVAILABLE_PROGRAMS } from './types/models.js';
22
+ import { createRunRoutes } from './routes/run.js';
23
+ import { createOnboardingRoutes } from './routes/onboarding.js';
24
+ import { createScaffoldRoutes } from './routes/scaffold.js';
25
+ import { AVAILABLE_PROGRAMS } from '@tarsk/shared';
23
26
  import { getDataDir } from './paths.js';
24
27
  const __filename = fileURLToPath(import.meta.url);
25
28
  const __dirname = path.dirname(__filename);
@@ -67,21 +70,34 @@ app.get('/api/threads/processing', (c) => {
67
70
  });
68
71
  // Register API routes
69
72
  app.route('/api/projects', createProjectRoutes(projectManager, threadManager));
73
+ app.route('/api/projects', createRunRoutes(projectManager));
70
74
  app.route('/api/threads', createThreadRoutes(threadManager, gitManager, conversationManager));
71
75
  app.route('/api/chat', createChatRoutes(threadManager, neovateExecutor, conversationManager, processingStateManager));
72
76
  app.route('/api/providers', createProviderRoutes(metadataManager));
73
77
  app.route('/api/models', createModelRoutes(metadataManager));
74
78
  app.route('/api/git', createGitRoutes(metadataManager));
79
+ app.route('/api/onboarding', createOnboardingRoutes(metadataManager));
80
+ app.route('/api/scaffold', createScaffoldRoutes(projectManager));
75
81
  // Serve static files from the 'public' directory
76
82
  // In production, this will be the built frontend app
77
83
  const publicDir = path.join(__dirname, 'public');
78
- app.use('/*', serveStatic({
79
- root: path.relative(process.cwd(), publicDir)
80
- }));
81
- // Fallback to index.html for SPA routing
82
- app.get('*', serveStatic({
83
- path: path.relative(process.cwd(), path.join(publicDir, 'index.html'))
84
- }));
84
+ const staticRoot = path.relative(process.cwd(), publicDir);
85
+ app.use('/*', async (c, next) => {
86
+ // Never serve static files or SPA fallback for API routes
87
+ if (c.req.path.startsWith('/api/')) {
88
+ return next();
89
+ }
90
+ return serveStatic({ root: staticRoot })(c, next);
91
+ });
92
+ // Fallback to index.html for SPA routing (only for non-API paths)
93
+ app.get('*', async (c, next) => {
94
+ if (c.req.path.startsWith('/api/')) {
95
+ return next();
96
+ }
97
+ return serveStatic({
98
+ path: path.relative(process.cwd(), path.join(publicDir, 'index.html'))
99
+ })(c, next);
100
+ });
85
101
  // Error handling middleware for unmatched routes
86
102
  app.all('*', (c) => {
87
103
  const errorResponse = {
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Helpers for extracting and normalizing conversation content from Neovate events.
3
+ * Used when saving completed messages (store only final answer) and when
4
+ * mapping stream events to thinking vs content for the UI.
5
+ */
6
+ import type { NeovateEvent, ConversationMessage } from '@tarsk/shared';
7
+ /**
8
+ * Formats completed conversation messages into a single context string to prepend
9
+ * to the next user prompt so the model retains conversation history.
10
+ */
11
+ export declare function formatConversationContext(messages: ConversationMessage[], maxMessages?: number): string;
12
+ /**
13
+ * Parses assistant message content into content blocks when it is a JSON array
14
+ * (e.g. [{ type: 'reasoning', text: '...' }, { type: 'text', text: '...' }, { type: 'tool_use', ... }]).
15
+ * Returns null if content is not a structured array.
16
+ */
17
+ export declare function parseAssistantContentBlocks(content: string): Array<{
18
+ type: string;
19
+ [key: string]: unknown;
20
+ }> | null;
21
+ /** Returns true if content looks like tool result/use JSON */
22
+ export declare function isToolLikeContent(content: string): boolean;
23
+ /**
24
+ * Returns only the final assistant answer text from events (no user message, no tool JSON).
25
+ * Use this when persisting response content so the stored file stays clean.
26
+ */
27
+ export declare function extractAssistantContent(events: NeovateEvent[] | undefined, fallback: string): string;
28
+ //# sourceMappingURL=conversation-content.d.ts.map
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Helpers for extracting and normalizing conversation content from Neovate events.
3
+ * Used when saving completed messages (store only final answer) and when
4
+ * mapping stream events to thinking vs content for the UI.
5
+ */
6
+ /** Max number of prior exchanges to include in context (user + assistant pairs) */
7
+ const DEFAULT_MAX_CONTEXT_MESSAGES = 10;
8
+ /**
9
+ * Formats completed conversation messages into a single context string to prepend
10
+ * to the next user prompt so the model retains conversation history.
11
+ */
12
+ export function formatConversationContext(messages, maxMessages = DEFAULT_MAX_CONTEXT_MESSAGES) {
13
+ const completed = messages.filter((m) => m.response != null);
14
+ const recent = completed.slice(-maxMessages);
15
+ if (recent.length === 0)
16
+ return '';
17
+ const lines = ['Previous conversation:'];
18
+ for (const m of recent) {
19
+ lines.push(`User: ${m.request.message}`);
20
+ lines.push(`Assistant: ${m.response.content}`);
21
+ }
22
+ return lines.join('\n');
23
+ }
24
+ const extractTextBlocksFromContent = (content) => {
25
+ const trimmed = content.trim();
26
+ if (!trimmed.startsWith('[') && !trimmed.startsWith('{')) {
27
+ return [];
28
+ }
29
+ try {
30
+ const parsed = JSON.parse(trimmed);
31
+ if (Array.isArray(parsed)) {
32
+ return parsed
33
+ .filter((block) => block && typeof block === 'object' && block.type === 'text')
34
+ .map((block) => (typeof block.text === 'string' ? block.text : ''))
35
+ .filter((text) => text.length > 0);
36
+ }
37
+ if (parsed && typeof parsed === 'object' && 'text' in parsed && typeof parsed.text === 'string') {
38
+ return [parsed.text];
39
+ }
40
+ }
41
+ catch {
42
+ // ignore
43
+ }
44
+ return [];
45
+ };
46
+ /**
47
+ * Parses assistant message content into content blocks when it is a JSON array
48
+ * (e.g. [{ type: 'reasoning', text: '...' }, { type: 'text', text: '...' }, { type: 'tool_use', ... }]).
49
+ * Returns null if content is not a structured array.
50
+ */
51
+ export function parseAssistantContentBlocks(content) {
52
+ const trimmed = content.trim();
53
+ if (!trimmed.startsWith('['))
54
+ return null;
55
+ try {
56
+ const parsed = JSON.parse(trimmed);
57
+ if (!Array.isArray(parsed) || parsed.length === 0)
58
+ return null;
59
+ const blocks = [];
60
+ for (const item of parsed) {
61
+ if (item && typeof item === 'object' && 'type' in item) {
62
+ blocks.push(item);
63
+ }
64
+ }
65
+ return blocks.length > 0 ? blocks : null;
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ }
71
+ /** Returns true if content looks like tool result/use JSON */
72
+ export function isToolLikeContent(content) {
73
+ const trimmed = content.trim();
74
+ if (!trimmed.startsWith('[') && !trimmed.startsWith('{'))
75
+ return false;
76
+ try {
77
+ const parsed = JSON.parse(trimmed);
78
+ if (Array.isArray(parsed)) {
79
+ return parsed.some((item) => item &&
80
+ typeof item === 'object' &&
81
+ 'type' in item &&
82
+ (item.type === 'tool-result' || item.type === 'tool_use'));
83
+ }
84
+ return (typeof parsed === 'object' &&
85
+ parsed !== null &&
86
+ 'type' in parsed &&
87
+ (parsed.type === 'tool-result' || parsed.type === 'tool_use'));
88
+ }
89
+ catch {
90
+ return false;
91
+ }
92
+ }
93
+ /**
94
+ * Returns only the final assistant answer text from events (no user message, no tool JSON).
95
+ * Use this when persisting response content so the stored file stays clean.
96
+ */
97
+ export function extractAssistantContent(events, fallback) {
98
+ if (!events || events.length === 0) {
99
+ return fallback;
100
+ }
101
+ const resultEvent = [...events].reverse().find((event) => event.type === 'result');
102
+ if (resultEvent && typeof resultEvent.content === 'string' && resultEvent.content.trim().length > 0) {
103
+ return resultEvent.content;
104
+ }
105
+ const assistantMessages = events.filter((event) => event.type === 'message' && event.role === 'assistant');
106
+ let lastTextBlock = null;
107
+ const assistantChunks = [];
108
+ for (const event of assistantMessages) {
109
+ if (typeof event.content !== 'string') {
110
+ continue;
111
+ }
112
+ if (isToolLikeContent(event.content)) {
113
+ continue;
114
+ }
115
+ assistantChunks.push(event.content);
116
+ const textBlocks = extractTextBlocksFromContent(event.content);
117
+ if (textBlocks.length > 0) {
118
+ lastTextBlock = textBlocks[textBlocks.length - 1];
119
+ }
120
+ }
121
+ if (lastTextBlock) {
122
+ return lastTextBlock;
123
+ }
124
+ if (assistantChunks.length === 0) {
125
+ return fallback;
126
+ }
127
+ const combined = assistantChunks.join('');
128
+ return combined.trim().length > 0 ? combined : fallback;
129
+ }
130
+ //# sourceMappingURL=conversation-content.js.map
@@ -5,7 +5,7 @@
5
5
  * across all route handlers. This eliminates duplication of response formatting
6
6
  * logic and ensures consistent error handling throughout the API.
7
7
  */
8
- import type { ErrorResponse } from '../types/models.js';
8
+ import type { ErrorResponse } from '@tarsk/shared';
9
9
  /**
10
10
  * Factory for building consistent API responses
11
11
  */
@@ -6,7 +6,7 @@
6
6
  * - Retrieving conversation history for a thread
7
7
  * - Managing conversation persistence
8
8
  */
9
- import { ConversationHistory, ConversationMessage, NeovateEvent, FileData } from '../types/models.js';
9
+ import { ConversationHistory, ConversationMessage, NeovateEvent, FileData } from '@tarsk/shared';
10
10
  /**
11
11
  * ConversationManager interface defines the contract for conversation operations
12
12
  */
@@ -71,9 +71,6 @@ export declare class ConversationManagerImpl implements ConversationManager {
71
71
  * Gets the last N messages from conversation history
72
72
  */
73
73
  getLastMessages(threadPath: string, count?: number): Promise<ConversationMessage[]>;
74
- /**
75
- * Saves conversation history to file
76
- */
77
74
  private saveConversationHistory;
78
75
  /**
79
76
  * Gets the file path for a thread's conversation history in the metadata folder
@@ -6,7 +6,7 @@
6
6
  * - Cloning repositories with streaming output
7
7
  * - Handling git operation errors
8
8
  */
9
- import { GitEvent } from '../types/models.js';
9
+ import { GitEvent } from '@tarsk/shared';
10
10
  /**
11
11
  * GitManager interface defines the contract for git operations
12
12
  */
@@ -45,6 +45,11 @@ export interface GitManager {
45
45
  * @yields GitEvent objects for stdout, stderr, complete, and error events
46
46
  */
47
47
  createAndCheckoutBranch(repoPath: string, branchName: string): AsyncGenerator<GitEvent>;
48
+ /**
49
+ * Initializes a new git repository at the given path
50
+ * @param repoPath - The path where to run git init
51
+ */
52
+ initRepository(repoPath: string): Promise<void>;
48
53
  }
49
54
  /**
50
55
  * GitManagerImpl provides the implementation for git operations
@@ -129,5 +134,6 @@ export declare class GitManagerImpl implements GitManager {
129
134
  * @yields GitEvent objects during the branch creation
130
135
  */
131
136
  createAndCheckoutBranch(repoPath: string, branchName: string): AsyncGenerator<GitEvent>;
137
+ initRepository(repoPath: string): Promise<void>;
132
138
  }
133
139
  //# sourceMappingURL=git-manager.d.ts.map
@@ -6,7 +6,7 @@
6
6
  * - Cloning repositories with streaming output
7
7
  * - Handling git operation errors
8
8
  */
9
- import { spawn } from 'child_process';
9
+ import { spawn, spawnSync } from 'child_process';
10
10
  import { mkdir } from 'fs/promises';
11
11
  import { dirname } from 'path';
12
12
  /**
@@ -326,5 +326,12 @@ export class GitManagerImpl {
326
326
  };
327
327
  }
328
328
  }
329
+ async initRepository(repoPath) {
330
+ const result = spawnSync('git', ['init'], { cwd: repoPath, encoding: 'utf-8' });
331
+ if (result.status !== 0) {
332
+ const stderr = result.stderr?.trim() || result.error?.message || 'Unknown error';
333
+ throw new Error(`git init failed: ${stderr}`);
334
+ }
335
+ }
329
336
  }
330
337
  //# sourceMappingURL=git-manager.js.map
@@ -4,7 +4,7 @@
4
4
  * This manager provides JSON file-based storage with atomic writes
5
5
  * to ensure data consistency even if the process crashes during write operations.
6
6
  */
7
- import { Project, Thread } from "../types/models.js";
7
+ import { Project, Thread } from "@tarsk/shared";
8
8
  /**
9
9
  * MetadataManager manages persistence of projects and threads
10
10
  */
@@ -12,6 +12,7 @@ interface AppState {
12
12
  selectedThreadId: string | null;
13
13
  providerKeys: Record<string, string>;
14
14
  enabledModels: Record<string, string[]>;
15
+ onboardingCompleted: boolean;
15
16
  }
16
17
  export declare class MetadataManager {
17
18
  private projectsFile;
@@ -48,15 +49,6 @@ export declare class MetadataManager {
48
49
  * @returns Array of threads
49
50
  */
50
51
  loadThreads(): Promise<Thread[]>;
51
- /**
52
- * Perform atomic write using temp file
53
- *
54
- * This ensures that if the process crashes during write,
55
- * we don't end up with corrupted or partial data.
56
- *
57
- * @param filePath - Target file path
58
- * @param data - Data to write
59
- */
60
52
  private atomicWrite;
61
53
  /**
62
54
  * Get the metadata directory path
@@ -88,12 +80,12 @@ export declare class MetadataManager {
88
80
  /**
89
81
  * Update provider keys
90
82
  * @param providerName - Name of the provider
91
- * @param apiKey - API key to save
83
+ * @param apiKey - API key to save (will be encrypted)
92
84
  */
93
85
  saveProviderKey(providerName: string, apiKey: string): Promise<void>;
94
86
  /**
95
87
  * Get all provider keys
96
- * @returns Record of provider names to API keys
88
+ * @returns Record of provider names to decrypted API keys
97
89
  */
98
90
  getProviderKeys(): Promise<Record<string, string>>;
99
91
  /**
@@ -135,6 +127,16 @@ export declare class MetadataManager {
135
127
  * @param modelIds - Array of model IDs to enable
136
128
  */
137
129
  setEnabledModels(provider: string, modelIds: string[]): Promise<void>;
130
+ /**
131
+ * Set onboarding completed flag
132
+ * @param completed - Whether onboarding is completed
133
+ */
134
+ setOnboardingCompleted(completed: boolean): Promise<void>;
135
+ /**
136
+ * Get onboarding completed flag
137
+ * @returns Whether onboarding has been completed
138
+ */
139
+ getOnboardingCompleted(): Promise<boolean>;
138
140
  }
139
141
  export {};
140
142
  //# sourceMappingURL=metadata-manager.d.ts.map
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { promises as fs } from "fs";
8
8
  import { join, dirname } from "path";
9
+ import { encrypt, decrypt, isEncrypted } from "../utils/crypto.js";
9
10
  export class MetadataManager {
10
11
  projectsFile;
11
12
  threadsFile;
@@ -49,6 +50,7 @@ export class MetadataManager {
49
50
  selectedThreadId: null,
50
51
  providerKeys: {},
51
52
  enabledModels: {},
53
+ onboardingCompleted: false,
52
54
  });
53
55
  }
54
56
  }
@@ -187,11 +189,15 @@ export class MetadataManager {
187
189
  if (!state.enabledModels) {
188
190
  state.enabledModels = {};
189
191
  }
192
+ // Ensure onboardingCompleted exists
193
+ if (state.onboardingCompleted === undefined) {
194
+ state.onboardingCompleted = false;
195
+ }
190
196
  return state;
191
197
  }
192
198
  catch (error) {
193
199
  if (error.code === "ENOENT") {
194
- return { selectedThreadId: null, providerKeys: {}, enabledModels: {} };
200
+ return { selectedThreadId: null, providerKeys: {}, enabledModels: {}, onboardingCompleted: false };
195
201
  }
196
202
  throw new Error(`Failed to load state: ${error}`);
197
203
  }
@@ -202,26 +208,62 @@ export class MetadataManager {
202
208
  */
203
209
  async saveAllProviderKeys(keys) {
204
210
  const state = await this.loadState();
205
- state.providerKeys = { ...state.providerKeys, ...keys };
211
+ // Encrypt all keys before saving
212
+ const encryptedKeys = {};
213
+ for (const [provider, key] of Object.entries(keys)) {
214
+ encryptedKeys[provider] = key ? encrypt(key) : '';
215
+ }
216
+ state.providerKeys = { ...state.providerKeys, ...encryptedKeys };
206
217
  await this.saveState(state);
207
218
  }
208
219
  /**
209
220
  * Update provider keys
210
221
  * @param providerName - Name of the provider
211
- * @param apiKey - API key to save
222
+ * @param apiKey - API key to save (will be encrypted)
212
223
  */
213
224
  async saveProviderKey(providerName, apiKey) {
214
225
  const state = await this.loadState();
215
- state.providerKeys[providerName] = apiKey;
226
+ // Encrypt the API key before saving
227
+ state.providerKeys[providerName] = apiKey ? encrypt(apiKey) : '';
216
228
  await this.saveState(state);
217
229
  }
218
230
  /**
219
231
  * Get all provider keys
220
- * @returns Record of provider names to API keys
232
+ * @returns Record of provider names to decrypted API keys
221
233
  */
222
234
  async getProviderKeys() {
223
235
  const state = await this.loadState();
224
- return state.providerKeys;
236
+ // Decrypt all keys and migrate any plain-text keys
237
+ const decryptedKeys = {};
238
+ let needsMigration = false;
239
+ for (const [provider, encryptedKey] of Object.entries(state.providerKeys)) {
240
+ if (!encryptedKey) {
241
+ decryptedKeys[provider] = '';
242
+ continue;
243
+ }
244
+ // Check if key is already encrypted
245
+ if (isEncrypted(encryptedKey)) {
246
+ try {
247
+ decryptedKeys[provider] = decrypt(encryptedKey);
248
+ }
249
+ catch (error) {
250
+ console.error(`Failed to decrypt key for ${provider}:`, error);
251
+ // Keep the encrypted value if decryption fails
252
+ decryptedKeys[provider] = '';
253
+ }
254
+ }
255
+ else {
256
+ // Plain-text key found - needs migration
257
+ decryptedKeys[provider] = encryptedKey;
258
+ needsMigration = true;
259
+ }
260
+ }
261
+ // If we found plain-text keys, encrypt them
262
+ if (needsMigration) {
263
+ console.log('Migrating plain-text API keys to encrypted format...');
264
+ await this.saveAllProviderKeys(decryptedKeys);
265
+ }
266
+ return decryptedKeys;
225
267
  }
226
268
  /**
227
269
  * Set the selected thread ID
@@ -302,5 +344,22 @@ export class MetadataManager {
302
344
  }
303
345
  await this.saveState(state);
304
346
  }
347
+ /**
348
+ * Set onboarding completed flag
349
+ * @param completed - Whether onboarding is completed
350
+ */
351
+ async setOnboardingCompleted(completed) {
352
+ const state = await this.loadState();
353
+ state.onboardingCompleted = completed;
354
+ await this.saveState(state);
355
+ }
356
+ /**
357
+ * Get onboarding completed flag
358
+ * @returns Whether onboarding has been completed
359
+ */
360
+ async getOnboardingCompleted() {
361
+ const state = await this.loadState();
362
+ return state.onboardingCompleted;
363
+ }
305
364
  }
306
365
  //# sourceMappingURL=metadata-manager.js.map
@@ -4,7 +4,7 @@
4
4
  * Manages available and enabled models across different providers.
5
5
  * Enriches models with pricing (and name/description) from model-info when available (e.g. OpenRouter).
6
6
  */
7
- import { Model } from '../types/models.js';
7
+ import { Model } from '@tarsk/shared';
8
8
  import { MetadataManager } from './metadata-manager.js';
9
9
  /**
10
10
  * ModelManager handles model availability and selection
@@ -6,7 +6,7 @@
6
6
  * - Streaming result messages back to the caller
7
7
  * - Handling execution errors
8
8
  */
9
- import { NeovateEvent, ExecutionContext } from "../types/models.js";
9
+ import { NeovateEvent, ExecutionContext } from "@tarsk/shared";
10
10
  import { MetadataManager } from "./metadata-manager.js";
11
11
  /**
12
12
  * NeovateExecutor interface defines the contract for neovate command execution
@@ -0,0 +1,55 @@
1
+ /**
2
+ * ProcessManager handles running and stopping dev server processes
3
+ *
4
+ * This manager is responsible for:
5
+ * - Starting dev server processes with output capture
6
+ * - Detecting URLs from process output
7
+ * - Stopping running processes
8
+ * - Streaming output back to the frontend
9
+ */
10
+ import { EventEmitter } from 'events';
11
+ export interface ProcessOutput {
12
+ type: 'stdout' | 'stderr' | 'url' | 'error' | 'complete';
13
+ content?: string;
14
+ url?: string;
15
+ error?: unknown;
16
+ }
17
+ export declare class ProcessManager extends EventEmitter {
18
+ private processes;
19
+ private detectedUrls;
20
+ private queues;
21
+ private resolvers;
22
+ /**
23
+ * Run a command and stream its output
24
+ * @param projectId - The project ID (used as a key)
25
+ * @param command - The command to run (e.g., "npm run dev")
26
+ * @param cwd - Working directory to run the command in
27
+ * @returns AsyncGenerator that yields ProcessOutput events
28
+ */
29
+ runProcess(projectId: string, command: string, cwd: string): AsyncGenerator<ProcessOutput>;
30
+ /**
31
+ * Stop a running process
32
+ * @param projectId - The project ID
33
+ * @returns true if process was stopped, false if no process was running
34
+ */
35
+ stopProcess(projectId: string): boolean;
36
+ /**
37
+ * Check if a process is running for a project
38
+ * @param projectId - The project ID
39
+ * @returns true if a process is running
40
+ */
41
+ isRunning(projectId: string): boolean;
42
+ /**
43
+ * Get the detected URL for a running process
44
+ * @param projectId - The project ID
45
+ * @returns The URL if detected, undefined otherwise
46
+ */
47
+ getDetectedUrl(projectId: string): string | undefined;
48
+ /**
49
+ * Detect URL from text output
50
+ * @param text - The text to search for URLs
51
+ * @returns The first detected URL or undefined
52
+ */
53
+ private detectUrl;
54
+ }
55
+ //# sourceMappingURL=process-manager.d.ts.map