@kirosnn/mosaic 0.74.0 → 0.75.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.
package/src/index.tsx CHANGED
@@ -208,14 +208,27 @@ if (parsed.directory) {
208
208
  }
209
209
 
210
210
  import { addRecentProject } from './utils/config';
211
+ import { appendFileSync } from 'fs';
212
+ import { join } from 'path';
213
+ import { homedir } from 'os';
214
+
215
+ const DEBUG_LOG = join(homedir(), '.mosaic', 'debug.log');
216
+ const debugLog = (msg: string) => {
217
+ try { appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] ${msg}\n`); } catch {}
218
+ };
219
+
220
+ debugLog('--- Mosaic starting ---');
211
221
  addRecentProject(process.cwd());
212
222
 
223
+ debugLog('MCP init...');
213
224
  const { initializeMcp } = await import('./mcp/index');
214
- await initializeMcp().catch(() => { });
225
+ await initializeMcp().catch((e) => { debugLog(`MCP init error: ${e}`); });
226
+ debugLog('MCP init done');
215
227
 
216
228
  process.title = '⁘ Mosaic';
217
229
 
218
230
  const cleanup = async (code = 0) => {
231
+ debugLog(`cleanup called with code=${code}`);
219
232
  try {
220
233
  const { shutdownMcp } = await import('./mcp/index');
221
234
  await shutdownMcp();
@@ -224,17 +237,31 @@ const cleanup = async (code = 0) => {
224
237
  process.exit(code);
225
238
  };
226
239
 
227
- process.on('SIGINT', () => cleanup(0));
228
- process.on('SIGTERM', () => cleanup(0));
229
- process.on('uncaughtException', () => cleanup(1));
230
- process.on('unhandledRejection', () => cleanup(1));
240
+ process.on('SIGINT', () => { debugLog('SIGINT received'); cleanup(0); });
241
+ process.on('SIGTERM', () => { debugLog('SIGTERM received'); cleanup(0); });
242
+ process.on('uncaughtException', (err) => {
243
+ const msg = `Uncaught exception: ${err?.stack ?? err}`;
244
+ debugLog(msg);
245
+ originalStderrWrite(msg + '\n');
246
+ cleanup(1);
247
+ });
248
+ process.on('unhandledRejection', (reason) => {
249
+ const msg = `Unhandled rejection: ${reason instanceof Error ? reason.stack : reason}`;
250
+ debugLog(msg);
251
+ originalStderrWrite(msg + '\n');
252
+ cleanup(1);
253
+ });
231
254
 
232
255
  await new Promise(resolve => setTimeout(resolve, 100));
233
256
 
257
+ debugLog('Creating renderer...');
234
258
  try {
235
259
  const renderer = await createCliRenderer();
260
+ debugLog('Renderer created, mounting React...');
236
261
  createRoot(renderer).render(<App initialMessage={parsed.initialMessage} />);
262
+ debugLog('React mounted');
237
263
  } catch (error) {
264
+ debugLog(`Renderer/React error: ${error}`);
238
265
  console.error(error);
239
266
  cleanup(1);
240
267
  }
@@ -20,4 +20,4 @@ async function main(): Promise<void> {
20
20
  main().catch(error => {
21
21
  console.error(error);
22
22
  process.exit(1);
23
- });
23
+ });
@@ -29,7 +29,7 @@ async function resolveRedirects(page: Page, results: SearchResult[]): Promise<Se
29
29
  if (decoded.startsWith('http')) return { ...r, href: decoded };
30
30
  }
31
31
  }
32
- } catch {}
32
+ } catch { }
33
33
  return r;
34
34
  });
35
35
  }, results);
@@ -160,7 +160,7 @@ async function dismissConsentDialogs(page: Page): Promise<void> {
160
160
  ).first();
161
161
  if (await consentButton.isVisible({ timeout: 1500 })) {
162
162
  await consentButton.click();
163
- await page.waitForLoadState('domcontentloaded', { timeout: 3000 }).catch(() => {});
163
+ await page.waitForLoadState('domcontentloaded', { timeout: 3000 }).catch(() => { });
164
164
  }
165
165
  } catch {
166
166
  // No consent dialog
@@ -176,7 +176,7 @@ function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise
176
176
 
177
177
  export function registerTools(server: McpServer) {
178
178
  server.registerTool('navigation_search', {
179
- description: 'Search the web and return top results (titles, links, snippets). Defaults to Bing. Supports bing, duckduckgo, and google engines.',
179
+ description: 'Search the web and return top results (titles, links, snippets). Defaults to Google. Supports bing, duckduckgo, and google engines.',
180
180
  inputSchema: {
181
181
  query: z.string(),
182
182
  limit: z.number().optional(),
@@ -184,9 +184,9 @@ export function registerTools(server: McpServer) {
184
184
  newTab: z.boolean().optional(),
185
185
  },
186
186
  }, async (args) => {
187
- const engineName: SearchEngine = args.engine ?? 'bing';
187
+ const engineName: SearchEngine = args.engine ?? 'google';
188
188
  const engine = engines[engineName];
189
- const limit = args.limit ?? 8;
189
+ const limit = args.limit ?? 10;
190
190
 
191
191
  const doSearch = async () => {
192
192
  const target = args.newTab ? await ensureContext().then(async ctx => {
@@ -14,4 +14,4 @@ export type ContextConfig = {
14
14
  extraHTTPHeaders?: Record<string, string>;
15
15
  locale?: string;
16
16
  timezoneId?: string;
17
- };
17
+ };
@@ -14,7 +14,7 @@ export async function waitForStability(page: Page, timeout = 10000): Promise<voi
14
14
  try {
15
15
  await page.waitForLoadState('networkidle', { timeout });
16
16
  } catch {
17
- await page.waitForLoadState('domcontentloaded', { timeout: 5000 }).catch(() => {});
17
+ await page.waitForLoadState('domcontentloaded', { timeout: 5000 }).catch(() => { });
18
18
  await randomDelay(300, 600);
19
19
  }
20
- }
20
+ }
@@ -1,31 +1,31 @@
1
- import { existsSync, mkdirSync, writeFileSync, readdirSync, readFileSync, unlinkSync } from 'fs';
1
+ import { existsSync, mkdirSync, writeFileSync, readdirSync, readFileSync, unlinkSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { homedir } from 'os';
4
4
 
5
- export interface ConversationStep {
6
- type: 'user' | 'assistant' | 'tool';
7
- content: string;
8
- images?: import("./images").ImageAttachment[];
9
- toolName?: string;
10
- toolArgs?: Record<string, unknown>;
11
- toolResult?: unknown;
12
- timestamp: number;
13
- responseDuration?: number;
14
- blendWord?: string;
15
- }
16
-
17
- export interface ConversationHistory {
18
- id: string;
19
- timestamp: number;
20
- steps: ConversationStep[];
21
- totalSteps: number;
22
- title?: string | null;
23
- workspace?: string | null;
24
- totalTokens?: {
25
- prompt: number;
26
- completion: number;
27
- total: number;
28
- };
5
+ export interface ConversationStep {
6
+ type: 'user' | 'assistant' | 'tool';
7
+ content: string;
8
+ images?: import("./images").ImageAttachment[];
9
+ toolName?: string;
10
+ toolArgs?: Record<string, unknown>;
11
+ toolResult?: unknown;
12
+ timestamp: number;
13
+ responseDuration?: number;
14
+ blendWord?: string;
15
+ }
16
+
17
+ export interface ConversationHistory {
18
+ id: string;
19
+ timestamp: number;
20
+ steps: ConversationStep[];
21
+ totalSteps: number;
22
+ title?: string | null;
23
+ workspace?: string | null;
24
+ totalTokens?: {
25
+ prompt: number;
26
+ completion: number;
27
+ total: number;
28
+ };
29
29
  model?: string;
30
30
  provider?: string;
31
31
  }
@@ -41,49 +41,49 @@ export function getHistoryDir(): string {
41
41
  return historyDir;
42
42
  }
43
43
 
44
- export function saveConversation(conversation: ConversationHistory): void {
45
- const historyDir = getHistoryDir();
46
- const filename = `${conversation.id}.json`;
47
- const filepath = join(historyDir, filename);
48
-
49
- writeFileSync(filepath, JSON.stringify(conversation, null, 2), 'utf-8');
50
- }
51
-
52
- export function updateConversationTitle(id: string, title: string | null): boolean {
53
- const historyDir = getHistoryDir();
54
- const filepath = join(historyDir, `${id}.json`);
55
-
56
- if (!existsSync(filepath)) {
57
- return false;
58
- }
59
-
60
- try {
61
- const content = readFileSync(filepath, 'utf-8');
62
- const data = JSON.parse(content) as ConversationHistory;
63
- data.title = title;
64
- writeFileSync(filepath, JSON.stringify(data, null, 2), 'utf-8');
65
- return true;
66
- } catch (error) {
67
- return false;
68
- }
69
- }
70
-
71
- export function deleteConversation(id: string): boolean {
72
- const historyDir = getHistoryDir();
73
- const filepath = join(historyDir, `${id}.json`);
74
-
75
- if (!existsSync(filepath)) {
76
- return false;
77
- }
78
-
79
- try {
80
- unlinkSync(filepath);
81
- return true;
82
- } catch (error) {
83
- return false;
84
- }
85
- }
86
-
44
+ export function saveConversation(conversation: ConversationHistory): void {
45
+ const historyDir = getHistoryDir();
46
+ const filename = `${conversation.id}.json`;
47
+ const filepath = join(historyDir, filename);
48
+
49
+ writeFileSync(filepath, JSON.stringify(conversation, null, 2), 'utf-8');
50
+ }
51
+
52
+ export function updateConversationTitle(id: string, title: string | null): boolean {
53
+ const historyDir = getHistoryDir();
54
+ const filepath = join(historyDir, `${id}.json`);
55
+
56
+ if (!existsSync(filepath)) {
57
+ return false;
58
+ }
59
+
60
+ try {
61
+ const content = readFileSync(filepath, 'utf-8');
62
+ const data = JSON.parse(content) as ConversationHistory;
63
+ data.title = title;
64
+ writeFileSync(filepath, JSON.stringify(data, null, 2), 'utf-8');
65
+ return true;
66
+ } catch (error) {
67
+ return false;
68
+ }
69
+ }
70
+
71
+ export function deleteConversation(id: string): boolean {
72
+ const historyDir = getHistoryDir();
73
+ const filepath = join(historyDir, `${id}.json`);
74
+
75
+ if (!existsSync(filepath)) {
76
+ return false;
77
+ }
78
+
79
+ try {
80
+ unlinkSync(filepath);
81
+ return true;
82
+ } catch (error) {
83
+ return false;
84
+ }
85
+ }
86
+
87
87
  export function loadConversations(): ConversationHistory[] {
88
88
  const historyDir = getHistoryDir();
89
89
 
@@ -91,19 +91,19 @@ export function loadConversations(): ConversationHistory[] {
91
91
  return [];
92
92
  }
93
93
 
94
- const files = readdirSync(historyDir).filter(f => f.endsWith('.json') && f !== 'inputs.json');
95
- const conversations: ConversationHistory[] = [];
96
-
97
- for (const file of files) {
98
- try {
99
- const content = readFileSync(join(historyDir, file), 'utf-8');
100
- const parsed = JSON.parse(content) as ConversationHistory;
101
- if (!parsed || !Array.isArray(parsed.steps)) continue;
102
- conversations.push(parsed);
103
- } catch (error) {
104
- console.error(`Failed to load ${file}:`, error);
105
- }
106
- }
94
+ const files = readdirSync(historyDir).filter(f => f.endsWith('.json') && f !== 'inputs.json');
95
+ const conversations: ConversationHistory[] = [];
96
+
97
+ for (const file of files) {
98
+ try {
99
+ const content = readFileSync(join(historyDir, file), 'utf-8');
100
+ const parsed = JSON.parse(content) as ConversationHistory;
101
+ if (!parsed || !Array.isArray(parsed.steps)) continue;
102
+ conversations.push(parsed);
103
+ } catch (error) {
104
+ console.error(`Failed to load ${file}:`, error);
105
+ }
106
+ }
107
107
 
108
108
  return conversations.sort((a, b) => b.timestamp - a.timestamp);
109
109
  }
@@ -145,4 +145,4 @@ export function addInputToHistory(input: string): void {
145
145
 
146
146
  saveInputHistory(history);
147
147
  }
148
- }
148
+ }