@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.
- package/package.json +3 -3
- package/dist/app.js +0 -227
- package/dist/cli.bundle.js.map +0 -7
- package/dist/cli.js +0 -132
- package/dist/commands/add.js +0 -245
- package/dist/commands/agents-run.js +0 -71
- package/dist/commands/auth.js +0 -259
- package/dist/commands/branch.js +0 -1411
- package/dist/commands/claude-sync.js +0 -772
- package/dist/commands/comment.js +0 -568
- package/dist/commands/diff.js +0 -182
- package/dist/commands/index.js +0 -73
- package/dist/commands/init.js +0 -597
- package/dist/commands/ls.js +0 -135
- package/dist/commands/members.js +0 -687
- package/dist/commands/mv.js +0 -282
- package/dist/commands/recover.js +0 -207
- package/dist/commands/rm.js +0 -257
- package/dist/commands/spec.js +0 -386
- package/dist/commands/status.js +0 -296
- package/dist/commands/sync.js +0 -119
- package/dist/commands/trace.js +0 -1752
- package/dist/commands/workspace.js +0 -447
- package/dist/components/conflict-resolution-ui.js +0 -120
- package/dist/components/messages.js +0 -5
- package/dist/components/thread.js +0 -8
- package/dist/config/features.js +0 -83
- package/dist/containers/branches-container.js +0 -140
- package/dist/containers/directory-container.js +0 -92
- package/dist/containers/thread-container.js +0 -214
- package/dist/containers/threads-container.js +0 -27
- package/dist/containers/workspaces-container.js +0 -27
- package/dist/daemon/conflict-resolution.js +0 -172
- package/dist/daemon/content-hash.js +0 -31
- package/dist/daemon/file-sync.js +0 -985
- package/dist/daemon/index.js +0 -203
- package/dist/daemon/mime-types.js +0 -166
- package/dist/daemon/offline-queue.js +0 -211
- package/dist/daemon/path-utils.js +0 -64
- package/dist/daemon/share-policy.js +0 -83
- package/dist/daemon/wasm-errors.js +0 -189
- package/dist/daemon/worker.js +0 -557
- package/dist/daemon-worker.js +0 -258
- package/dist/errors/workspace-errors.js +0 -48
- package/dist/lib/auth-server.js +0 -216
- package/dist/lib/browser.js +0 -35
- package/dist/lib/diff.js +0 -284
- package/dist/lib/formatters.js +0 -204
- package/dist/lib/git.js +0 -137
- package/dist/lib/local-fs.js +0 -201
- package/dist/lib/prompts.js +0 -56
- package/dist/lib/storage.js +0 -213
- package/dist/lib/trace-formatters.js +0 -314
- package/dist/services/add-service.js +0 -554
- package/dist/services/add-validation.js +0 -124
- package/dist/services/automatic-file-tracker.js +0 -303
- package/dist/services/cli-orchestrator.js +0 -227
- package/dist/services/feature-flags.js +0 -187
- package/dist/services/file-import-service.js +0 -283
- package/dist/services/file-transformation-service.js +0 -218
- package/dist/services/logger.js +0 -44
- package/dist/services/mod-config.js +0 -67
- package/dist/services/modignore-service.js +0 -328
- package/dist/services/sync-daemon.js +0 -244
- package/dist/services/thread-notification-service.js +0 -50
- package/dist/services/thread-service.js +0 -147
- package/dist/stores/use-directory-store.js +0 -96
- package/dist/stores/use-threads-store.js +0 -46
- package/dist/stores/use-workspaces-store.js +0 -54
- package/dist/types/add-types.js +0 -99
- package/dist/types/config.js +0 -16
- package/dist/types/index.js +0 -2
- package/dist/types/workspace-connection.js +0 -53
- 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
|
-
*/
|