@mod-computer/cli 0.2.4 → 0.2.5

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 (74) hide show
  1. package/package.json +3 -3
  2. package/dist/app.js +0 -227
  3. package/dist/cli.bundle.js.map +0 -7
  4. package/dist/cli.js +0 -132
  5. package/dist/commands/add.js +0 -245
  6. package/dist/commands/agents-run.js +0 -71
  7. package/dist/commands/auth.js +0 -259
  8. package/dist/commands/branch.js +0 -1411
  9. package/dist/commands/claude-sync.js +0 -772
  10. package/dist/commands/comment.js +0 -568
  11. package/dist/commands/diff.js +0 -182
  12. package/dist/commands/index.js +0 -73
  13. package/dist/commands/init.js +0 -597
  14. package/dist/commands/ls.js +0 -135
  15. package/dist/commands/members.js +0 -687
  16. package/dist/commands/mv.js +0 -282
  17. package/dist/commands/recover.js +0 -207
  18. package/dist/commands/rm.js +0 -257
  19. package/dist/commands/spec.js +0 -386
  20. package/dist/commands/status.js +0 -296
  21. package/dist/commands/sync.js +0 -119
  22. package/dist/commands/trace.js +0 -1752
  23. package/dist/commands/workspace.js +0 -447
  24. package/dist/components/conflict-resolution-ui.js +0 -120
  25. package/dist/components/messages.js +0 -5
  26. package/dist/components/thread.js +0 -8
  27. package/dist/config/features.js +0 -83
  28. package/dist/containers/branches-container.js +0 -140
  29. package/dist/containers/directory-container.js +0 -92
  30. package/dist/containers/thread-container.js +0 -214
  31. package/dist/containers/threads-container.js +0 -27
  32. package/dist/containers/workspaces-container.js +0 -27
  33. package/dist/daemon/conflict-resolution.js +0 -172
  34. package/dist/daemon/content-hash.js +0 -31
  35. package/dist/daemon/file-sync.js +0 -985
  36. package/dist/daemon/index.js +0 -203
  37. package/dist/daemon/mime-types.js +0 -166
  38. package/dist/daemon/offline-queue.js +0 -211
  39. package/dist/daemon/path-utils.js +0 -64
  40. package/dist/daemon/share-policy.js +0 -83
  41. package/dist/daemon/wasm-errors.js +0 -189
  42. package/dist/daemon/worker.js +0 -557
  43. package/dist/daemon-worker.js +0 -258
  44. package/dist/errors/workspace-errors.js +0 -48
  45. package/dist/lib/auth-server.js +0 -216
  46. package/dist/lib/browser.js +0 -35
  47. package/dist/lib/diff.js +0 -284
  48. package/dist/lib/formatters.js +0 -204
  49. package/dist/lib/git.js +0 -137
  50. package/dist/lib/local-fs.js +0 -201
  51. package/dist/lib/prompts.js +0 -56
  52. package/dist/lib/storage.js +0 -213
  53. package/dist/lib/trace-formatters.js +0 -314
  54. package/dist/services/add-service.js +0 -554
  55. package/dist/services/add-validation.js +0 -124
  56. package/dist/services/automatic-file-tracker.js +0 -303
  57. package/dist/services/cli-orchestrator.js +0 -227
  58. package/dist/services/feature-flags.js +0 -187
  59. package/dist/services/file-import-service.js +0 -283
  60. package/dist/services/file-transformation-service.js +0 -218
  61. package/dist/services/logger.js +0 -44
  62. package/dist/services/mod-config.js +0 -67
  63. package/dist/services/modignore-service.js +0 -328
  64. package/dist/services/sync-daemon.js +0 -244
  65. package/dist/services/thread-notification-service.js +0 -50
  66. package/dist/services/thread-service.js +0 -147
  67. package/dist/stores/use-directory-store.js +0 -96
  68. package/dist/stores/use-threads-store.js +0 -46
  69. package/dist/stores/use-workspaces-store.js +0 -54
  70. package/dist/types/add-types.js +0 -99
  71. package/dist/types/config.js +0 -16
  72. package/dist/types/index.js +0 -2
  73. package/dist/types/workspace-connection.js +0 -53
  74. package/dist/types.js +0 -1
@@ -1,772 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Claude Code Event Sync Tool
4
- * Syncs Claude Code events with Mod system (branches, threads, tasks, history)
5
- */
6
- import { readModConfig } from '../services/mod-config.js';
7
- import path from 'path';
8
- import { fileURLToPath } from 'url';
9
- const __filename = fileURLToPath(import.meta.url);
10
- const __dirname = path.dirname(__filename);
11
- function isWorkspaceHistoryFile(filePath) {
12
- if (!filePath)
13
- return false;
14
- const normalized = filePath.replace(/\\/g, '/');
15
- if (!normalized)
16
- return false;
17
- if (normalized === '.claude' || normalized === '/.claude')
18
- return false;
19
- if (normalized.startsWith('.claude/'))
20
- return false;
21
- if (normalized.includes('/.claude/'))
22
- return false;
23
- if (normalized.endsWith('/.claude'))
24
- return false;
25
- return true;
26
- }
27
- export async function claudeSyncCommand(args, repo) {
28
- // Check for --payload-stdin flag
29
- if (args.includes('--payload-stdin')) {
30
- // await processPayloadFromStdin(repo); // Commented out - needs refactoring
31
- return;
32
- }
33
- const subcommand = args[0];
34
- if (!subcommand) {
35
- console.log(`
36
- Claude Code Event Sync Tool
37
-
38
- Usage:
39
- mod claude-sync <command> [options]
40
-
41
- Commands:
42
- sync-events # Sync all tracked events to Mod system
43
- create-session <id> # Create branch/thread for specific session
44
- sync-file-updates # Sync file updates to history
45
- sync-todos # Sync todo tracking to tasks
46
- sync-conversations # Sync conversation content to threads
47
- status # Show sync status
48
- help # Show this help
49
-
50
- Examples:
51
- mod claude-sync sync-events
52
- mod claude-sync create-session 1faa82b3-6d63-4e39-8784-207d7fbf83c0
53
- mod claude-sync status
54
- `);
55
- return;
56
- }
57
- const cfg = readModConfig();
58
- if (!cfg?.workspaceId) {
59
- console.error('❌ No active workspace configured in .mod/config.json');
60
- process.exit(1);
61
- }
62
- // TODO: Replace with ModWorkspace/WorkspaceHandle approach
63
- console.error('❌ claude-sync needs refactoring to work with new ModWorkspace approach');
64
- process.exit(1);
65
- }
66
- /* COMMENTED OUT - Needs refactoring for new ModWorkspace approach
67
- const cache = getCliRepoCache(repo as any);
68
- const workspaceContext = await cache.ensureBranchContext({
69
- workspaceId: cfg.workspaceId,
70
- branchId: '', // Will be resolved by the cache
71
- waitForHydration: true
72
- });
73
- const { workspace, branchesDocId, branch } = workspaceContext;
74
-
75
- if (!workspace || !branch) {
76
- console.error('❌ Failed to resolve workspace context');
77
- process.exit(1);
78
- }
79
-
80
- const workspaceId = workspace.id;
81
- const branchId = branch.id;
82
-
83
- const workspaceService = new WorkspaceService(repo);
84
- const branchService = new BranchService(repo);
85
- const threadService = new ThreadService(repo);
86
- const taskService = new TaskService(repo);
87
- const changeLogService = new ChangeLogService(repo);
88
-
89
- const projectDir = process.cwd();
90
- const stateDir = path.join(projectDir, '.claude', 'state');
91
- const logDir = path.join(projectDir, '.claude', 'logs');
92
- const conversationsDir = path.join(projectDir, '.claude', 'conversations');
93
-
94
- switch (subcommand) {
95
- case 'sync-events':
96
- await syncAllEvents(workspaceService, branchService, threadService, taskService, changeLogService, {
97
- workspaceId, branchId, branchesDocId, projectDir, stateDir, logDir, conversationsDir
98
- });
99
- break;
100
-
101
- case 'create-session':
102
- const sessionId = args[1];
103
- if (!sessionId) {
104
- console.error('❌ Session ID required: mod claude-sync create-session <session-id>');
105
- process.exit(1);
106
- }
107
- await createSessionBranchThread(workspaceService, branchService, threadService, {
108
- workspaceId, branchId, branchesDocId, sessionId, projectDir, stateDir
109
- });
110
- break;
111
-
112
- case 'sync-file-updates':
113
- await syncFileUpdates(taskService, changeLogService, { branchId, branchesDocId, projectDir, logDir });
114
- break;
115
-
116
- case 'sync-todos':
117
- await syncTodos(taskService, { branchId, branchesDocId, projectDir, logDir });
118
- break;
119
-
120
- case 'sync-conversations':
121
- await syncConversations(threadService, { branchId, branchesDocId, projectDir, conversationsDir });
122
- break;
123
-
124
- case 'status':
125
- await showSyncStatus({ projectDir, stateDir, logDir, conversationsDir });
126
- break;
127
-
128
- case 'help':
129
- console.log(`
130
- Claude Code Event Sync Tool
131
-
132
- This tool integrates Claude Code activity with your Mod workspace by:
133
-
134
- 1. Creating new branches/threads for each Claude Code session
135
- 2. Tracking file updates in task history
136
- 3. Syncing todo tracking to Mod tasks
137
- 4. Adding conversation content to threads
138
-
139
- Commands:
140
- sync-events # Full sync of all tracked data
141
- create-session <id> # Create branch/thread for specific session
142
- sync-file-updates # Sync file updates to history
143
- sync-todos # Sync todo tracking to tasks
144
- sync-conversations # Sync conversation content to threads
145
- status # Show sync status
146
-
147
- The tool reads from:
148
- .claude/logs/events-*.jsonl # Event logs
149
- .claude/conversations/*.json # Conversation data
150
- .claude/state/sessions.json # Session mappings
151
- `);
152
- break;
153
-
154
- default:
155
- console.error(`❌ Unknown command: ${subcommand}`);
156
- console.log('Run "mod claude-sync help" for usage information');
157
- process.exit(1);
158
- }
159
- }
160
-
161
- async function syncAllEvents(
162
- workspaceService: WorkspaceService,
163
- branchService: BranchService,
164
- threadService: ThreadService,
165
- taskService: TaskService,
166
- changeLogService: ChangeLogService,
167
- context: any
168
- ) {
169
- console.log('🔄 Starting full Claude Code event sync...');
170
-
171
- // Load session mappings
172
- const sessionMapping = loadSessionMapping(context.stateDir);
173
-
174
- // Process all events
175
- const events = loadAllEvents(context.logDir);
176
- console.log(`📊 Found ${events.length} events across ${new Set(events.map(e => e.session_id)).size} sessions`);
177
-
178
- // Group events by session
179
- const eventsBySession = groupEventsBySession(events);
180
-
181
- for (const [sessionId, sessionEvents] of eventsBySession) {
182
- console.log(`\n📝 Processing session: ${sessionId}`);
183
-
184
- // Create branch/thread if not exists
185
- if (!sessionMapping[sessionId]?.threadId) {
186
- await createSessionBranchThread(workspaceService, branchService, threadService, {
187
- ...context, sessionId
188
- });
189
- }
190
-
191
- // Sync file updates
192
- await syncFileUpdatesForSession(taskService, changeLogService, {
193
- ...context, sessionId, events: sessionEvents
194
- });
195
-
196
- // Sync todos
197
- await syncTodosForSession(taskService, {
198
- ...context, sessionId, events: sessionEvents
199
- });
200
-
201
- // Sync conversation
202
- await syncConversationForSession(threadService, {
203
- ...context, sessionId
204
- });
205
- }
206
-
207
- console.log('\n✅ Full sync complete!');
208
- }
209
-
210
- async function createSessionBranchThread(
211
- workspaceService: WorkspaceService,
212
- branchService: BranchService,
213
- threadService: ThreadService,
214
- context: any
215
- ) {
216
- const { workspaceId, branchId, branchesDocId, sessionId, projectDir, stateDir } = context;
217
-
218
- console.log(`🏗️ Creating branch/thread for session: ${sessionId}`);
219
-
220
- try {
221
- // Create new branch for this session
222
- const branchName = `claude-session-${sessionId.substring(0, 8)}`;
223
- const newBranch = await branchService.createBranch(
224
- branchId, // parent branch
225
- branchName,
226
- workspaceId,
227
- 'thread',
228
- 'claude-code',
229
- branchesDocId
230
- );
231
-
232
- // Create thread for the branch
233
- const threadName = `Claude Session ${sessionId.substring(0, 8)}`;
234
- const newThread = await threadService.createThreadWithBranch(
235
- workspaceId,
236
- branchesDocId,
237
- newBranch.id,
238
- threadName
239
- );
240
-
241
- // Update session mapping
242
- updateSessionMapping(stateDir, sessionId, {
243
- threadId: newThread.id,
244
- branchId: newBranch.id,
245
- firstSeen: new Date().toISOString(),
246
- lastActivity: new Date().toISOString()
247
- });
248
-
249
- console.log(`✅ Created branch: ${branchName} (${newBranch.id})`);
250
- console.log(`✅ Created thread: ${threadName} (${newThread.id})`);
251
-
252
- } catch (error) {
253
- console.error(`❌ Failed to create branch/thread for session ${sessionId}:`, error);
254
- }
255
- }
256
-
257
- async function syncFileUpdates(
258
- taskService: TaskService,
259
- changeLogService: ChangeLogService,
260
- context: any
261
- ) {
262
- console.log('📁 Syncing file updates to history...');
263
-
264
- const events = loadAllEvents(context.logDir);
265
- const fileEvents = events.filter(e => e.tool_name === 'Write' || e.tool_name === 'Edit');
266
-
267
- console.log(`📊 Found ${fileEvents.length} file update events`);
268
-
269
- for (const event of fileEvents) {
270
- if (event.file_path) {
271
- if (!isWorkspaceHistoryFile(event.file_path)) {
272
- console.log(`⏭️ Skipping Claude internal file: ${event.file_path}`);
273
- continue;
274
- }
275
- try {
276
- // Create or get task for this session
277
- const taskId = await getOrCreateTaskForSession(taskService, context, event.session_id, {
278
- description: `File update: ${path.basename(event.file_path)}`,
279
- source: 'claude-code',
280
- sessionId: event.session_id
281
- });
282
-
283
- // Add file update to task
284
- const fileId = path.basename(event.file_path); // Simplified file ID
285
- await taskService.addFileUpdateToTask(
286
- taskId,
287
- fileId,
288
- 'unknown', // before head
289
- 'current' // after head
290
- );
291
-
292
- console.log(`✅ Tracked file update: ${event.file_path}`);
293
-
294
- } catch (error) {
295
- console.error(`❌ Failed to track file update for ${event.file_path}:`, error);
296
- }
297
- }
298
- }
299
- }
300
-
301
- async function syncTodos(
302
- taskService: TaskService,
303
- context: any
304
- ) {
305
- console.log('📋 Syncing todos to tasks...');
306
-
307
- const events = loadAllEvents(context.logDir);
308
- const todoEvents = events.filter(e => e.tool_name === 'TodoWrite');
309
-
310
- console.log(`📊 Found ${todoEvents.length} todo events`);
311
-
312
- for (const event of todoEvents) {
313
- try {
314
- const todos = event.tool_input?.todos || [];
315
-
316
- for (const todo of todos) {
317
- const taskId = await getOrCreateTaskForSession(taskService, context, event.session_id, {
318
- description: todo.content,
319
- status: todo.status === 'in_progress' ? 'in-progress' :
320
- todo.status === 'completed' ? 'complete' : 'pending',
321
- source: 'claude-code',
322
- sessionId: event.session_id,
323
- todoId: todo.id
324
- });
325
-
326
- console.log(`✅ Synced todo: ${todo.content} (${todo.status})`);
327
- }
328
-
329
- } catch (error) {
330
- console.error(`❌ Failed to sync todos for session ${event.session_id}:`, error);
331
- }
332
- }
333
- }
334
-
335
- async function syncConversations(
336
- threadService: ThreadService,
337
- context: any
338
- ) {
339
- console.log('💬 Syncing conversations to threads...');
340
-
341
- if (!fs.existsSync(context.conversationsDir)) {
342
- console.log('📁 No conversations directory found');
343
- return;
344
- }
345
-
346
- const conversationFiles = fs.readdirSync(context.conversationsDir)
347
- .filter(f => f.endsWith('.json') && !f.endsWith('-summary.json'));
348
-
349
- console.log(`📊 Found ${conversationFiles.length} conversations`);
350
-
351
- for (const file of conversationFiles) {
352
- const sessionId = file.replace('.json', '');
353
- const conversationPath = path.join(context.conversationsDir, file);
354
-
355
- try {
356
- const conversation = JSON.parse(fs.readFileSync(conversationPath, 'utf8'));
357
-
358
- // Add conversation as thread items
359
- for (const message of conversation) {
360
- if (message.role && message.content) {
361
- // This would need to be implemented based on your thread item structure
362
- console.log(`📝 Would add message: ${message.role} - ${JSON.stringify(message.content).substring(0, 100)}...`);
363
- }
364
- }
365
-
366
- console.log(`✅ Synced conversation: ${sessionId}`);
367
-
368
- } catch (error) {
369
- console.error(`❌ Failed to sync conversation ${sessionId}:`, error);
370
- }
371
- }
372
- }
373
-
374
- async function syncFileUpdatesForSession(
375
- taskService: TaskService,
376
- changeLogService: ChangeLogService,
377
- context: any
378
- ) {
379
- const { sessionId, events } = context;
380
- const fileEvents = events.filter(e => e.tool_name === 'Write' || e.tool_name === 'Edit');
381
-
382
- for (const event of fileEvents) {
383
- if (event.file_path) {
384
- if (!isWorkspaceHistoryFile(event.file_path)) {
385
- console.log(`⏭️ Skipping Claude internal file: ${event.file_path}`);
386
- continue;
387
- }
388
- try {
389
- const taskId = await getOrCreateTaskForSession(taskService, context, sessionId, {
390
- description: `File update: ${path.basename(event.file_path)}`,
391
- source: 'claude-code',
392
- sessionId: sessionId
393
- });
394
-
395
- const fileId = path.basename(event.file_path);
396
- await taskService.addFileUpdateToTask(taskId, fileId, 'unknown', 'current');
397
-
398
- } catch (error) {
399
- console.error(`❌ Failed to track file update:`, error);
400
- }
401
- }
402
- }
403
- }
404
-
405
- async function syncTodosForSession(
406
- taskService: TaskService,
407
- context: any
408
- ) {
409
- const { sessionId, events } = context;
410
- const todoEvents = events.filter(e => e.tool_name === 'TodoWrite');
411
-
412
- for (const event of todoEvents) {
413
- const todos = event.tool_input?.todos || [];
414
-
415
- for (const todo of todos) {
416
- try {
417
- await getOrCreateTaskForSession(taskService, context, sessionId, {
418
- description: todo.content,
419
- status: todo.status === 'in_progress' ? 'in-progress' :
420
- todo.status === 'completed' ? 'complete' : 'pending',
421
- source: 'claude-code',
422
- sessionId: sessionId,
423
- todoId: todo.id
424
- });
425
-
426
- } catch (error) {
427
- console.error(`❌ Failed to sync todo:`, error);
428
- }
429
- }
430
- }
431
- }
432
-
433
- async function syncConversationForSession(
434
- threadService: ThreadService,
435
- context: any
436
- ) {
437
- const { sessionId, conversationsDir } = context;
438
- const conversationPath = path.join(conversationsDir, `${sessionId}.json`);
439
-
440
- if (fs.existsSync(conversationPath)) {
441
- try {
442
- const conversation = JSON.parse(fs.readFileSync(conversationPath, 'utf8'));
443
- console.log(`📝 Synced ${conversation.length} messages for session ${sessionId}`);
444
-
445
- // TODO: Add conversation messages to thread
446
- // This would require implementing thread item creation
447
-
448
- } catch (error) {
449
- console.error(`❌ Failed to sync conversation:`, error);
450
- }
451
- }
452
- }
453
-
454
- async function getOrCreateTaskForSession(
455
- taskService: TaskService,
456
- context: any,
457
- sessionId: string,
458
- taskData: any
459
- ): Promise<string> {
460
- // This is a simplified implementation
461
- // In practice, you'd need to:
462
- // 1. Get the tasksDocId for the session's branch
463
- // 2. Check if a task already exists for this session/todo
464
- // 3. Create or update the task accordingly
465
-
466
- const task = {
467
- branchId: context.branchId, // This should be the session's branch ID
468
- description: taskData.description,
469
- metadata: {
470
- createdBy: 'claude-sync',
471
- createdAt: new Date().toISOString(),
472
- updatedAt: new Date().toISOString(),
473
- status: taskData.status || 'pending',
474
- source: taskData.source,
475
- claude: {
476
- sessionId: taskData.sessionId,
477
- todoId: taskData.todoId
478
- }
479
- }
480
- };
481
-
482
- // This would need to be implemented with proper task creation
483
- return `task-${sessionId}-${Date.now()}`;
484
- }
485
-
486
- async function showSyncStatus(context: any) {
487
- console.log('📊 Claude Code Sync Status');
488
- console.log('==========================');
489
-
490
- const { projectDir, stateDir, logDir, conversationsDir } = context;
491
-
492
- // Check event logs
493
- if (fs.existsSync(logDir)) {
494
- const logFiles = fs.readdirSync(logDir).filter(f => f.startsWith('events-'));
495
- const totalEvents = logFiles.reduce((sum, file) => {
496
- const content = fs.readFileSync(path.join(logDir, file), 'utf8');
497
- return sum + content.split('\n').filter(line => line.trim()).length;
498
- }, 0);
499
-
500
- console.log(`📝 Event Logs: ${logFiles.length} files, ${totalEvents} events`);
501
- } else {
502
- console.log('📝 Event Logs: No logs found');
503
- }
504
-
505
- // Check conversations
506
- if (fs.existsSync(conversationsDir)) {
507
- const convFiles = fs.readdirSync(conversationsDir).filter(f => f.endsWith('.json') && !f.endsWith('-summary.json'));
508
- console.log(`💬 Conversations: ${convFiles.length} tracked`);
509
- } else {
510
- console.log('💬 Conversations: None tracked');
511
- }
512
-
513
- // Check session mappings
514
- if (fs.existsSync(path.join(stateDir, 'sessions.json'))) {
515
- const sessions = JSON.parse(fs.readFileSync(path.join(stateDir, 'sessions.json'), 'utf8'));
516
- console.log(`👥 Sessions: ${Object.keys(sessions).length} tracked`);
517
- } else {
518
- console.log('👥 Sessions: None tracked');
519
- }
520
- }
521
-
522
- function loadSessionMapping(stateDir: string): SessionMapping {
523
- const mappingPath = path.join(stateDir, 'session-mapping.json');
524
- if (fs.existsSync(mappingPath)) {
525
- return JSON.parse(fs.readFileSync(mappingPath, 'utf8'));
526
- }
527
- return {};
528
- }
529
-
530
- function updateSessionMapping(stateDir: string, sessionId: string, data: any) {
531
- const mappingPath = path.join(stateDir, 'session-mapping.json');
532
- const mapping = loadSessionMapping(stateDir);
533
-
534
- mapping[sessionId] = {
535
- ...mapping[sessionId],
536
- ...data,
537
- lastActivity: new Date().toISOString()
538
- };
539
-
540
- fs.writeFileSync(mappingPath, JSON.stringify(mapping, null, 2));
541
- }
542
-
543
- function loadAllEvents(logDir: string): ClaudeEvent[] {
544
- if (!fs.existsSync(logDir)) return [];
545
-
546
- const events: ClaudeEvent[] = [];
547
- const logFiles = fs.readdirSync(logDir).filter(f => f.startsWith('events-'));
548
-
549
- for (const file of logFiles) {
550
- const content = fs.readFileSync(path.join(logDir, file), 'utf8');
551
- const lines = content.split('\n').filter(line => line.trim());
552
-
553
- for (const line of lines) {
554
- try {
555
- events.push(JSON.parse(line));
556
- } catch (error) {
557
- console.warn(`⚠️ Failed to parse event: ${line.substring(0, 100)}...`);
558
- }
559
- }
560
- }
561
-
562
- return events;
563
- }
564
-
565
- function groupEventsBySession(events: ClaudeEvent[]): Map<string, ClaudeEvent[]> {
566
- const grouped = new Map<string, ClaudeEvent[]>();
567
-
568
- for (const event of events) {
569
- if (!grouped.has(event.session_id)) {
570
- grouped.set(event.session_id, []);
571
- }
572
- grouped.get(event.session_id)!.push(event);
573
- }
574
-
575
- return grouped;
576
- }
577
-
578
- async function processPayloadFromStdin(repo: Repo): Promise<void> {
579
- try {
580
- // Read payload from stdin
581
- let payload = '';
582
- process.stdin.setEncoding('utf8');
583
-
584
- for await (const chunk of process.stdin) {
585
- payload += chunk;
586
- }
587
-
588
- if (!payload.trim()) {
589
- console.error('❌ No payload received from stdin');
590
- process.exit(1);
591
- }
592
-
593
- const eventData = JSON.parse(payload);
594
- console.log(`🔄 Processing Claude Code event: ${eventData.tool_name} (session: ${eventData.session_id?.substring(0, 8) || 'unknown'})`);
595
-
596
- // Get workspace context
597
- const cfg = readModConfig();
598
- if (!cfg?.workspaceId) {
599
- console.error('❌ No active workspace configured in .mod/config.json');
600
- process.exit(1);
601
- }
602
-
603
- // TODO: Replace with ModWorkspace/WorkspaceHandle approach
604
- console.error('❌ claude-sync needs refactoring to work with new ModWorkspace approach');
605
- process.exit(1);
606
-
607
- /*
608
- const cache = getCliRepoCache(repo as any);
609
- const workspaceContext = await cache.ensureBranchContext({
610
- workspaceId: cfg.workspaceId,
611
- branchId: '', // Will be resolved by the cache
612
- waitForHydration: true
613
- });
614
- const { workspace, branchesDocId, branch } = workspaceContext;
615
-
616
- if (!workspace || !branch) {
617
- console.error('❌ Failed to resolve workspace context');
618
- process.exit(1);
619
- }
620
-
621
- const workspaceId = workspace.id;
622
- const branchId = branch.id;
623
-
624
- // Initialize services
625
- const workspaceService = new WorkspaceService(repo);
626
- const branchService = new BranchService(repo);
627
- const threadService = new ThreadService(repo);
628
- const taskService = new TaskService(repo);
629
- const changeLogService = new ChangeLogService(repo);
630
-
631
- const projectDir = process.cwd();
632
- const stateDir = path.join(projectDir, '.claude', 'state');
633
-
634
- // Process the event based on tool type
635
- const sessionId = eventData.session_id;
636
- const toolName = eventData.tool_name;
637
- const filePath = eventData.tool_input?.file_path;
638
-
639
- if (!sessionId) {
640
- console.error('❌ No session_id in payload');
641
- process.exit(1);
642
- }
643
-
644
- // Check if this is a new session and create branch/thread if needed
645
- const sessionMappingFile = path.join(stateDir, 'session-mapping.json');
646
- let sessionMapping: SessionMapping = {};
647
-
648
- if (fs.existsSync(sessionMappingFile)) {
649
- try {
650
- sessionMapping = JSON.parse(fs.readFileSync(sessionMappingFile, 'utf8'));
651
- } catch (e) {
652
- console.warn('⚠️ Could not read session mapping file');
653
- }
654
- }
655
-
656
- const isNewSession = !sessionMapping[sessionId];
657
-
658
- if (isNewSession) {
659
- console.log(`🆕 New Claude Code session detected: ${sessionId.substring(0, 8)}`);
660
-
661
- // Create branch/thread for this session
662
- await createSessionBranchThread(workspaceService, branchService, threadService, {
663
- workspaceId, branchId, branchesDocId, sessionId, projectDir, stateDir
664
- });
665
-
666
- // Update session mapping
667
- sessionMapping[sessionId] = {
668
- firstSeen: new Date().toISOString(),
669
- lastActivity: new Date().toISOString()
670
- };
671
- } else {
672
- // Update last activity
673
- sessionMapping[sessionId].lastActivity = new Date().toISOString();
674
- }
675
-
676
- // Process specific tool events
677
- switch (toolName) {
678
- case 'Write':
679
- case 'Edit':
680
- if (filePath) {
681
- if (!isWorkspaceHistoryFile(filePath)) {
682
- console.log(`⏭️ Skipping Claude internal file: ${filePath}`);
683
- break;
684
- }
685
- console.log(`📁 File update detected: ${path.basename(filePath)}`);
686
- // Create a task for this file update
687
- const taskDescription = `File update: ${path.basename(filePath)}`;
688
- const taskId = await createTaskForSession(taskService, branchId, branchesDocId, taskDescription, sessionId);
689
- if (taskId) {
690
- console.log(`✅ Created task for file update: ${taskId}`);
691
- }
692
- }
693
- break;
694
-
695
- case 'TodoWrite':
696
- console.log(`📋 Todo update detected`);
697
- const todos = eventData.tool_input?.todos || [];
698
- for (const todo of todos) {
699
- if (todo.content) {
700
- // Map todo status to valid task status
701
- let taskStatus: 'pending' | 'in-progress' | 'complete' | 'rejected' = 'pending';
702
- if (todo.status === 'completed') taskStatus = 'complete';
703
- else if (todo.status === 'in_progress') taskStatus = 'in-progress';
704
-
705
- const taskId = await createTaskForSession(taskService, branchId, branchesDocId, todo.content, sessionId, taskStatus);
706
- if (taskId) {
707
- console.log(`✅ Created task for todo: ${todo.content} (${taskStatus})`);
708
- }
709
- }
710
- }
711
- break;
712
-
713
- default:
714
- console.log(`🔧 Tool usage: ${toolName}`);
715
- }
716
-
717
- // Save updated session mapping
718
- fs.mkdirSync(stateDir, { recursive: true });
719
- fs.writeFileSync(sessionMappingFile, JSON.stringify(sessionMapping, null, 2));
720
-
721
- console.log(`✅ Event processed successfully for session: ${sessionId.substring(0, 8)}`);
722
-
723
- } catch (error) {
724
- console.error('❌ Error processing payload:', error);
725
- process.exit(1);
726
- }
727
- }
728
-
729
- async function createTaskForSession(
730
- taskService: TaskService,
731
- branchId: string,
732
- branchesDocId: string,
733
- description: string,
734
- sessionId: string,
735
- status: 'pending' | 'in-progress' | 'complete' | 'rejected' = 'pending'
736
- ): Promise<string | null> {
737
- try {
738
- // Ensure tasks doc exists for the branch
739
- const tasksDocId = await taskService.getOrCreateTasksDocForBranch(branchId, branchesDocId);
740
-
741
- // Create task
742
- const taskDoc = await taskService.createTask(tasksDocId, {
743
- description,
744
- branchId,
745
- context: {
746
- files: [],
747
- links: []
748
- },
749
- subtasks: [],
750
- filesUpdated: [],
751
- threadItemIds: [],
752
- metadata: {
753
- createdBy: 'claude-sync',
754
- createdAt: new Date().toISOString(),
755
- updatedAt: new Date().toISOString(),
756
- status,
757
- source: 'claude-code',
758
- claude: {
759
- sessionId
760
- }
761
- }
762
- });
763
-
764
- return taskDoc.id;
765
- } catch (error) {
766
- console.error('❌ Failed to create task:', error);
767
- return null;
768
- }
769
- }
770
-
771
- // Close comment blocks
772
- */