claudit 0.1.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/README.md +139 -0
- package/bin/claudit-mcp.js +3 -0
- package/bin/claudit.js +11 -0
- package/client/dist/assets/index-DhjH_2Wd.css +32 -0
- package/client/dist/assets/index-Dwom-XdC.js +98 -0
- package/client/dist/index.html +13 -0
- package/package.json +40 -0
- package/server/dist/server/src/index.js +170 -0
- package/server/dist/server/src/mcp-server.js +144 -0
- package/server/dist/server/src/routes/cron.js +101 -0
- package/server/dist/server/src/routes/filesystem.js +71 -0
- package/server/dist/server/src/routes/groups.js +60 -0
- package/server/dist/server/src/routes/sessions.js +206 -0
- package/server/dist/server/src/routes/todo.js +93 -0
- package/server/dist/server/src/routes/todoProviders.js +179 -0
- package/server/dist/server/src/services/claudeProcess.js +220 -0
- package/server/dist/server/src/services/cronScheduler.js +163 -0
- package/server/dist/server/src/services/cronStorage.js +154 -0
- package/server/dist/server/src/services/database.js +103 -0
- package/server/dist/server/src/services/eventBus.js +11 -0
- package/server/dist/server/src/services/groupStorage.js +52 -0
- package/server/dist/server/src/services/historyIndex.js +100 -0
- package/server/dist/server/src/services/jsonStore.js +41 -0
- package/server/dist/server/src/services/managedSessions.js +96 -0
- package/server/dist/server/src/services/providerConfigStorage.js +80 -0
- package/server/dist/server/src/services/providers/TodoProvider.js +1 -0
- package/server/dist/server/src/services/providers/larkDocsProvider.js +75 -0
- package/server/dist/server/src/services/providers/mcpClient.js +151 -0
- package/server/dist/server/src/services/providers/meegoProvider.js +99 -0
- package/server/dist/server/src/services/providers/registry.js +17 -0
- package/server/dist/server/src/services/providers/supabaseProvider.js +172 -0
- package/server/dist/server/src/services/ptyManager.js +263 -0
- package/server/dist/server/src/services/sessionIndexCache.js +24 -0
- package/server/dist/server/src/services/sessionParser.js +98 -0
- package/server/dist/server/src/services/sessionScanner.js +244 -0
- package/server/dist/server/src/services/todoStorage.js +112 -0
- package/server/dist/server/src/services/todoSyncEngine.js +170 -0
- package/server/dist/server/src/types.js +1 -0
- package/server/dist/shared/src/index.js +1 -0
- package/server/dist/shared/src/types.js +2 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import { db } from './database.js';
|
|
3
|
+
// --- Prepared statements ---
|
|
4
|
+
const stmtAll = db.prepare('SELECT * FROM todos ORDER BY position ASC, createdAt ASC');
|
|
5
|
+
const stmtById = db.prepare('SELECT * FROM todos WHERE id = ?');
|
|
6
|
+
const stmtInsert = db.prepare(`
|
|
7
|
+
INSERT INTO todos (id, title, description, completed, priority, sessionId, sessionLabel, groupId, position, createdAt, completedAt,
|
|
8
|
+
providerId, configId, externalId, externalUrl, lastSyncedAt, syncStatus, syncError)
|
|
9
|
+
VALUES (@id, @title, @description, @completed, @priority, @sessionId, @sessionLabel, @groupId, @position, @createdAt, @completedAt,
|
|
10
|
+
@providerId, @configId, @externalId, @externalUrl, @lastSyncedAt, @syncStatus, @syncError)
|
|
11
|
+
`);
|
|
12
|
+
const stmtDelete = db.prepare('DELETE FROM todos WHERE id = ?');
|
|
13
|
+
const stmtMaxPosition = db.prepare('SELECT COALESCE(MAX(position), 0) as maxPos FROM todos WHERE groupId IS ?');
|
|
14
|
+
const stmtUpdatePositionGroup = db.prepare('UPDATE todos SET position = ?, groupId = ? WHERE id = ?');
|
|
15
|
+
function rowToTodo(row) {
|
|
16
|
+
const todo = {
|
|
17
|
+
id: row.id,
|
|
18
|
+
title: row.title,
|
|
19
|
+
completed: row.completed === 1,
|
|
20
|
+
priority: row.priority,
|
|
21
|
+
position: row.position ?? 0,
|
|
22
|
+
createdAt: row.createdAt,
|
|
23
|
+
};
|
|
24
|
+
if (row.description != null)
|
|
25
|
+
todo.description = row.description;
|
|
26
|
+
if (row.sessionId != null)
|
|
27
|
+
todo.sessionId = row.sessionId;
|
|
28
|
+
if (row.sessionLabel != null)
|
|
29
|
+
todo.sessionLabel = row.sessionLabel;
|
|
30
|
+
if (row.groupId != null)
|
|
31
|
+
todo.groupId = row.groupId;
|
|
32
|
+
if (row.completedAt != null)
|
|
33
|
+
todo.completedAt = row.completedAt;
|
|
34
|
+
if (row.providerId != null) {
|
|
35
|
+
todo.provider = {
|
|
36
|
+
providerId: row.providerId,
|
|
37
|
+
configId: row.configId,
|
|
38
|
+
externalId: row.externalId,
|
|
39
|
+
externalUrl: row.externalUrl,
|
|
40
|
+
lastSyncedAt: row.lastSyncedAt,
|
|
41
|
+
syncStatus: row.syncStatus,
|
|
42
|
+
syncError: row.syncError,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return todo;
|
|
46
|
+
}
|
|
47
|
+
function todoToParams(todo) {
|
|
48
|
+
const p = todo.provider;
|
|
49
|
+
return {
|
|
50
|
+
id: todo.id,
|
|
51
|
+
title: todo.title,
|
|
52
|
+
description: todo.description ?? null,
|
|
53
|
+
completed: todo.completed ? 1 : 0,
|
|
54
|
+
priority: todo.priority,
|
|
55
|
+
sessionId: todo.sessionId ?? null,
|
|
56
|
+
sessionLabel: todo.sessionLabel ?? null,
|
|
57
|
+
groupId: todo.groupId ?? null,
|
|
58
|
+
position: todo.position ?? 0,
|
|
59
|
+
createdAt: todo.createdAt,
|
|
60
|
+
completedAt: todo.completedAt ?? null,
|
|
61
|
+
providerId: p?.providerId ?? null,
|
|
62
|
+
configId: p?.configId ?? null,
|
|
63
|
+
externalId: p?.externalId ?? null,
|
|
64
|
+
externalUrl: p?.externalUrl ?? null,
|
|
65
|
+
lastSyncedAt: p?.lastSyncedAt ?? null,
|
|
66
|
+
syncStatus: p?.syncStatus ?? null,
|
|
67
|
+
syncError: p?.syncError ?? null,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export function getNextPosition(groupId) {
|
|
71
|
+
const row = stmtMaxPosition.get(groupId ?? null);
|
|
72
|
+
return (row?.maxPos ?? 0) + 1000;
|
|
73
|
+
}
|
|
74
|
+
export function getAllTodos() {
|
|
75
|
+
return stmtAll.all().map(rowToTodo);
|
|
76
|
+
}
|
|
77
|
+
export function getTodo(id) {
|
|
78
|
+
const row = stmtById.get(id);
|
|
79
|
+
return row ? rowToTodo(row) : undefined;
|
|
80
|
+
}
|
|
81
|
+
export function createTodo(data) {
|
|
82
|
+
const todo = {
|
|
83
|
+
...data,
|
|
84
|
+
id: crypto.randomUUID(),
|
|
85
|
+
createdAt: new Date().toISOString(),
|
|
86
|
+
position: data.position ?? getNextPosition(data.groupId),
|
|
87
|
+
};
|
|
88
|
+
stmtInsert.run(todoToParams(todo));
|
|
89
|
+
return todo;
|
|
90
|
+
}
|
|
91
|
+
export function updateTodo(id, updates) {
|
|
92
|
+
const existing = getTodo(id);
|
|
93
|
+
if (!existing)
|
|
94
|
+
return null;
|
|
95
|
+
const merged = { ...existing, ...updates, id };
|
|
96
|
+
// Delete and re-insert (simple, keeps same prepared stmt)
|
|
97
|
+
stmtDelete.run(id);
|
|
98
|
+
stmtInsert.run(todoToParams(merged));
|
|
99
|
+
return merged;
|
|
100
|
+
}
|
|
101
|
+
export function deleteTodo(id) {
|
|
102
|
+
const result = stmtDelete.run(id);
|
|
103
|
+
return result.changes > 0;
|
|
104
|
+
}
|
|
105
|
+
export function reorderTodos(items) {
|
|
106
|
+
const txn = db.transaction(() => {
|
|
107
|
+
for (const item of items) {
|
|
108
|
+
stmtUpdatePositionGroup.run(item.position, item.groupId ?? null, item.id);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
txn();
|
|
112
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { getProvider } from './providers/registry.js';
|
|
2
|
+
import { getAllConfigs, getConfig, updateConfig } from './providerConfigStorage.js';
|
|
3
|
+
import { getAllTodos, createTodo, updateTodo } from './todoStorage.js';
|
|
4
|
+
/**
|
|
5
|
+
* Sync a single provider config: fetch external items and upsert into local todos.
|
|
6
|
+
*/
|
|
7
|
+
export async function syncProvider(config) {
|
|
8
|
+
const result = {
|
|
9
|
+
configId: config.id,
|
|
10
|
+
imported: 0,
|
|
11
|
+
updated: 0,
|
|
12
|
+
errors: [],
|
|
13
|
+
};
|
|
14
|
+
const provider = getProvider(config.providerId);
|
|
15
|
+
if (!provider) {
|
|
16
|
+
result.errors.push(`Provider "${config.providerId}" not found`);
|
|
17
|
+
updateConfig(config.id, { lastSyncError: result.errors[0] });
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const externalItems = await provider.fetchItems(config.config);
|
|
22
|
+
const localTodos = getAllTodos();
|
|
23
|
+
// Index existing provider-linked todos by externalId for this config
|
|
24
|
+
const localByExternalId = new Map();
|
|
25
|
+
for (const todo of localTodos) {
|
|
26
|
+
if (todo.provider?.configId === config.id && todo.provider.externalId) {
|
|
27
|
+
localByExternalId.set(todo.provider.externalId, todo);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const ext of externalItems) {
|
|
31
|
+
try {
|
|
32
|
+
const existing = localByExternalId.get(ext.externalId);
|
|
33
|
+
if (existing) {
|
|
34
|
+
// Update if changed
|
|
35
|
+
const needsUpdate = existing.title !== ext.title ||
|
|
36
|
+
existing.description !== ext.description ||
|
|
37
|
+
existing.completed !== ext.completed ||
|
|
38
|
+
existing.priority !== ext.priority;
|
|
39
|
+
if (needsUpdate && existing.provider?.syncStatus !== 'local_modified') {
|
|
40
|
+
updateTodo(existing.id, {
|
|
41
|
+
title: ext.title,
|
|
42
|
+
description: ext.description,
|
|
43
|
+
completed: ext.completed,
|
|
44
|
+
completedAt: ext.completed && !existing.completedAt
|
|
45
|
+
? new Date().toISOString()
|
|
46
|
+
: existing.completedAt,
|
|
47
|
+
priority: ext.priority,
|
|
48
|
+
provider: {
|
|
49
|
+
...existing.provider,
|
|
50
|
+
lastSyncedAt: new Date().toISOString(),
|
|
51
|
+
syncStatus: 'synced',
|
|
52
|
+
syncError: undefined,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
result.updated++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// Create new
|
|
60
|
+
createTodo({
|
|
61
|
+
title: ext.title,
|
|
62
|
+
description: ext.description,
|
|
63
|
+
completed: ext.completed,
|
|
64
|
+
completedAt: ext.completed ? new Date().toISOString() : undefined,
|
|
65
|
+
priority: ext.priority,
|
|
66
|
+
position: 0,
|
|
67
|
+
provider: {
|
|
68
|
+
providerId: config.providerId,
|
|
69
|
+
configId: config.id,
|
|
70
|
+
externalId: ext.externalId,
|
|
71
|
+
externalUrl: ext.externalUrl,
|
|
72
|
+
lastSyncedAt: new Date().toISOString(),
|
|
73
|
+
syncStatus: 'synced',
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
result.imported++;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
result.errors.push(`Item ${ext.externalId}: ${err.message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
updateConfig(config.id, {
|
|
84
|
+
lastSyncAt: new Date().toISOString(),
|
|
85
|
+
lastSyncError: result.errors.length > 0 ? result.errors.join('; ') : undefined,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
const errMsg = `Sync failed: ${err.message}`;
|
|
90
|
+
result.errors.push(errMsg);
|
|
91
|
+
updateConfig(config.id, { lastSyncError: errMsg });
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* When a provider-linked todo is completed locally, push completion to external system.
|
|
97
|
+
*/
|
|
98
|
+
export async function pushCompletion(todo) {
|
|
99
|
+
if (!todo.provider)
|
|
100
|
+
return;
|
|
101
|
+
const config = getConfig(todo.provider.configId);
|
|
102
|
+
if (!config)
|
|
103
|
+
return;
|
|
104
|
+
const provider = getProvider(todo.provider.providerId);
|
|
105
|
+
if (!provider?.completeItem)
|
|
106
|
+
return;
|
|
107
|
+
try {
|
|
108
|
+
await provider.completeItem(config.config, todo.provider.externalId);
|
|
109
|
+
updateTodo(todo.id, {
|
|
110
|
+
provider: {
|
|
111
|
+
...todo.provider,
|
|
112
|
+
lastSyncedAt: new Date().toISOString(),
|
|
113
|
+
syncStatus: 'synced',
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
updateTodo(todo.id, {
|
|
119
|
+
provider: {
|
|
120
|
+
...todo.provider,
|
|
121
|
+
syncStatus: 'sync_error',
|
|
122
|
+
syncError: err.message,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Sync all enabled provider configs.
|
|
129
|
+
*/
|
|
130
|
+
export async function syncAllProviders() {
|
|
131
|
+
const configs = getAllConfigs().filter(c => c.enabled);
|
|
132
|
+
const results = [];
|
|
133
|
+
for (const config of configs) {
|
|
134
|
+
const result = await syncProvider(config);
|
|
135
|
+
results.push(result);
|
|
136
|
+
}
|
|
137
|
+
return results;
|
|
138
|
+
}
|
|
139
|
+
let syncInterval = null;
|
|
140
|
+
/**
|
|
141
|
+
* Initialize periodic sync. Checks every 60s if any config needs syncing.
|
|
142
|
+
*/
|
|
143
|
+
export function initProviderSync() {
|
|
144
|
+
if (syncInterval)
|
|
145
|
+
return;
|
|
146
|
+
syncInterval = setInterval(async () => {
|
|
147
|
+
const configs = getAllConfigs().filter(c => c.enabled && c.syncIntervalMinutes && c.syncIntervalMinutes > 0);
|
|
148
|
+
for (const config of configs) {
|
|
149
|
+
const intervalMs = config.syncIntervalMinutes * 60 * 1000;
|
|
150
|
+
const lastSync = config.lastSyncAt ? new Date(config.lastSyncAt).getTime() : 0;
|
|
151
|
+
if (Date.now() - lastSync >= intervalMs) {
|
|
152
|
+
try {
|
|
153
|
+
await syncProvider(config);
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
console.error(`Auto-sync failed for config "${config.name}":`, err);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}, 60_000);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Stop periodic sync.
|
|
164
|
+
*/
|
|
165
|
+
export function stopProviderSync() {
|
|
166
|
+
if (syncInterval) {
|
|
167
|
+
clearInterval(syncInterval);
|
|
168
|
+
syncInterval = null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|