jowork 0.2.5 → 0.3.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/dist/{chunk-ROIINI33.js → chunk-4PIT2GZ4.js} +13 -1
- package/dist/{chunk-XLYRHKG6.js → chunk-54SD5GBF.js} +1 -1
- package/dist/chunk-63AMINQC.js +156 -0
- package/dist/{chunk-XAEGXSEO.js → chunk-74AHY7X6.js} +4 -0
- package/dist/{chunk-7U3SXINY.js → chunk-ATAUWJYD.js} +320 -50
- package/dist/chunk-DQW74UCN.js +671 -0
- package/dist/chunk-EYP6WMFF.js +153 -0
- package/dist/{chunk-JSTXMDXI.js → chunk-FCFZCZHR.js} +1 -1
- package/dist/chunk-FX6Z3QHV.js +34 -0
- package/dist/chunk-HENAABEL.js +419 -0
- package/dist/chunk-OXWWOKC7.js +201 -0
- package/dist/{chunk-HUHDL7WV.js → chunk-QGHJ45PL.js} +276 -199
- package/dist/chunk-RO3KK5RC.js +132 -0
- package/dist/{chunk-JE6TOU7W.js → chunk-TFMF3EXE.js} +2 -7
- package/dist/{chunk-TN327MDF.js → chunk-VX662YLA.js} +3 -3
- package/dist/cli.js +308 -135
- package/dist/{config-AI6UIJJN.js → config-FH2XLN7A.js} +2 -2
- package/dist/content-reader-VPGTR2SF.js +10 -0
- package/dist/context-ZNI3WOB7.js +10 -0
- package/dist/{credential-store-ZRZCSRPC.js → credential-store-OS5ZY4OW.js} +2 -2
- package/dist/{feishu-A6YVFKEN.js → feishu-XW5T6ER2.js} +8 -3
- package/dist/{git-manager-N35XSG4Y.js → git-manager-RVWV2GSV.js} +2 -1
- package/dist/github-PQKAYTLO.js +11 -0
- package/dist/{paths-JXOMBYIT.js → paths-FFRET6F7.js} +7 -3
- package/dist/{server-5GVWN2NB.js → server-WEADPUST.js} +59 -66
- package/dist/{setup-SYBQIL2O.js → setup-S2S2CHB2.js} +76 -30
- package/dist/sync-SRLFR5NA.js +21 -0
- package/dist/transport.js +6 -4
- package/package.json +1 -1
- package/src/dashboard/public/app.js +34 -8
- package/src/dashboard/public/style.css +14 -0
- package/dist/chunk-L5ZR7TSK.js +0 -82
- package/dist/chunk-LS2AJM5A.js +0 -163
- package/dist/chunk-QMOFQX7X.js +0 -612
- package/dist/chunk-YJWTKFWX.js +0 -451
- package/dist/github-SHWUFNYB.js +0 -10
- package/dist/sync-KDSPGY4A.js +0 -18
|
@@ -2,40 +2,42 @@ import {
|
|
|
2
2
|
linkAllUnprocessed,
|
|
3
3
|
syncGitLab,
|
|
4
4
|
syncLinear
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-HENAABEL.js";
|
|
6
|
+
import {
|
|
7
|
+
syncGitHub
|
|
8
|
+
} from "./chunk-63AMINQC.js";
|
|
9
|
+
import {
|
|
10
|
+
GitManager
|
|
11
|
+
} from "./chunk-EYP6WMFF.js";
|
|
6
12
|
import {
|
|
7
13
|
DbManager
|
|
8
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-74AHY7X6.js";
|
|
9
15
|
import {
|
|
10
16
|
listCredentials,
|
|
11
17
|
loadCredential
|
|
12
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-54SD5GBF.js";
|
|
13
19
|
import {
|
|
14
20
|
dbPath,
|
|
15
21
|
fileRepoDir
|
|
16
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-4PIT2GZ4.js";
|
|
17
23
|
import {
|
|
18
|
-
syncGitHub
|
|
19
|
-
} from "./chunk-LS2AJM5A.js";
|
|
20
|
-
import {
|
|
21
|
-
contentHash,
|
|
22
|
-
formatAnalytics,
|
|
23
|
-
formatMessages,
|
|
24
24
|
syncFeishu,
|
|
25
25
|
syncFeishuApprovals,
|
|
26
26
|
syncFeishuDocs,
|
|
27
|
+
syncFeishuLinks,
|
|
27
28
|
syncFeishuMeetings
|
|
28
|
-
} from "./chunk-
|
|
29
|
-
import {
|
|
30
|
-
createId
|
|
31
|
-
} from "./chunk-JE6TOU7W.js";
|
|
29
|
+
} from "./chunk-DQW74UCN.js";
|
|
32
30
|
import {
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
formatAnalytics,
|
|
32
|
+
formatMessages
|
|
33
|
+
} from "./chunk-RO3KK5RC.js";
|
|
35
34
|
import {
|
|
36
35
|
logError,
|
|
37
36
|
logInfo
|
|
38
37
|
} from "./chunk-MYDK7MWB.js";
|
|
38
|
+
import {
|
|
39
|
+
SyncContext
|
|
40
|
+
} from "./chunk-OXWWOKC7.js";
|
|
39
41
|
|
|
40
42
|
// src/commands/sync.ts
|
|
41
43
|
import { existsSync as existsSync2 } from "fs";
|
|
@@ -46,7 +48,7 @@ var defaultLogger = {
|
|
|
46
48
|
warn: (msg, ctx) => logError("sync", msg, ctx),
|
|
47
49
|
error: (msg, ctx) => logError("sync", msg, ctx)
|
|
48
50
|
};
|
|
49
|
-
async function syncPostHog(
|
|
51
|
+
async function syncPostHog(ctx, data, logger = defaultLogger) {
|
|
50
52
|
const { apiKey, host, projectId: rawProjectId } = data;
|
|
51
53
|
if (!apiKey) throw new Error("Missing PostHog API key");
|
|
52
54
|
const baseUrl = host || "https://app.posthog.com";
|
|
@@ -55,123 +57,139 @@ async function syncPostHog(sqlite, data, logger = defaultLogger, fileWriter) {
|
|
|
55
57
|
Authorization: `Bearer ${apiKey}`,
|
|
56
58
|
"Content-Type": "application/json"
|
|
57
59
|
};
|
|
58
|
-
let events = 0, insights = 0, newObjects = 0;
|
|
59
|
-
const checkExists = sqlite.prepare("SELECT id FROM objects WHERE uri = ?");
|
|
60
|
-
const insertObj = sqlite.prepare(`
|
|
61
|
-
INSERT INTO objects (id, source, source_type, uri, title, summary, tags, content_hash, last_synced_at, created_at)
|
|
62
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
63
|
-
`);
|
|
64
|
-
const insertBody = sqlite.prepare(`
|
|
65
|
-
INSERT OR REPLACE INTO object_bodies (object_id, content, content_type, fetched_at)
|
|
66
|
-
VALUES (?, ?, ?, ?)
|
|
67
|
-
`);
|
|
68
|
-
const insertFts = sqlite.prepare(`
|
|
69
|
-
INSERT INTO objects_fts(rowid, title, summary, tags, source, source_type, body_excerpt)
|
|
70
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
71
|
-
`);
|
|
72
|
-
const getRowid = sqlite.prepare("SELECT rowid FROM objects WHERE id = ?");
|
|
60
|
+
let events = 0, insights = 0, newObjects = 0, updatedObjects = 0;
|
|
73
61
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
batch(insightsData.results ?? []);
|
|
62
|
+
let insightsUrl = `${baseUrl}/api/projects/${projectId}/insights/?limit=100`;
|
|
63
|
+
const allInsights = [];
|
|
64
|
+
while (insightsUrl) {
|
|
65
|
+
const insightsRes = await fetch(insightsUrl, { headers });
|
|
66
|
+
if (!insightsRes.ok) {
|
|
67
|
+
logger.warn(`Failed to fetch insights: ${insightsRes.status}`);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
const page = await insightsRes.json();
|
|
71
|
+
allInsights.push(...page.results ?? []);
|
|
72
|
+
insightsUrl = page.next;
|
|
73
|
+
if (insightsUrl) await new Promise((r) => setTimeout(r, 200));
|
|
74
|
+
}
|
|
75
|
+
if (allInsights.length > 0) {
|
|
76
|
+
const items = [];
|
|
77
|
+
for (const insight of allInsights) {
|
|
78
|
+
const uri = `posthog://insight/${insight.id}`;
|
|
79
|
+
const summary = insight.description || `Insight: ${insight.name}`;
|
|
80
|
+
const body = JSON.stringify({
|
|
81
|
+
name: insight.name,
|
|
82
|
+
description: insight.description,
|
|
83
|
+
filters: insight.filters,
|
|
84
|
+
lastRefresh: insight.last_refresh
|
|
85
|
+
}, null, 2);
|
|
86
|
+
const fileContent = formatAnalytics({
|
|
87
|
+
name: insight.name,
|
|
88
|
+
description: insight.description,
|
|
89
|
+
filters: insight.filters,
|
|
90
|
+
lastRefresh: insight.last_refresh
|
|
91
|
+
});
|
|
92
|
+
items.push({
|
|
93
|
+
source: "posthog",
|
|
94
|
+
sourceType: "insight",
|
|
95
|
+
uri,
|
|
96
|
+
title: insight.name,
|
|
97
|
+
summary,
|
|
98
|
+
tags: ["posthog", "insight", ...Object.keys(insight.filters).slice(0, 3)],
|
|
99
|
+
content: body,
|
|
100
|
+
contentType: "application/json",
|
|
101
|
+
fileContent,
|
|
102
|
+
fileMeta: { title: insight.name }
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
const result = ctx.batchUpsert(items);
|
|
106
|
+
newObjects += result.inserted;
|
|
107
|
+
updatedObjects += result.updated;
|
|
108
|
+
insights += items.length;
|
|
122
109
|
logger.info(`Synced ${insights} insights`);
|
|
123
|
-
} else {
|
|
124
|
-
logger.warn(`Failed to fetch insights: ${insightsRes.status}`);
|
|
125
110
|
}
|
|
126
111
|
} catch (err) {
|
|
127
112
|
logger.error(`Insights sync error: ${err}`);
|
|
128
113
|
}
|
|
129
114
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
newObjects++;
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
batch(eventsData.results ?? []);
|
|
115
|
+
let eventsUrl = `${baseUrl}/api/projects/${projectId}/event_definitions/?limit=100`;
|
|
116
|
+
const allEvents = [];
|
|
117
|
+
while (eventsUrl) {
|
|
118
|
+
const eventsRes = await fetch(eventsUrl, { headers });
|
|
119
|
+
if (!eventsRes.ok) break;
|
|
120
|
+
const page = await eventsRes.json();
|
|
121
|
+
allEvents.push(...page.results ?? []);
|
|
122
|
+
eventsUrl = page.next;
|
|
123
|
+
if (eventsUrl) await new Promise((r) => setTimeout(r, 200));
|
|
124
|
+
}
|
|
125
|
+
if (allEvents.length > 0) {
|
|
126
|
+
const items = [];
|
|
127
|
+
for (const event of allEvents) {
|
|
128
|
+
const uri = `posthog://event/${event.name}`;
|
|
129
|
+
const summary = `${event.name}: ${event.description ?? "no description"} (30d volume: ${event.volume_30_day ?? "N/A"})`;
|
|
130
|
+
const body = JSON.stringify(event, null, 2);
|
|
131
|
+
const fileContent = formatAnalytics(event);
|
|
132
|
+
items.push({
|
|
133
|
+
source: "posthog",
|
|
134
|
+
sourceType: "event_definition",
|
|
135
|
+
uri,
|
|
136
|
+
title: event.name,
|
|
137
|
+
summary,
|
|
138
|
+
tags: ["posthog", "event_definition"],
|
|
139
|
+
content: body,
|
|
140
|
+
contentType: "application/json",
|
|
141
|
+
fileContent,
|
|
142
|
+
fileMeta: { title: event.name }
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const result = ctx.batchUpsert(items);
|
|
146
|
+
newObjects += result.inserted;
|
|
147
|
+
updatedObjects += result.updated;
|
|
148
|
+
events += items.length;
|
|
168
149
|
logger.info(`Synced ${events} event definitions`);
|
|
169
150
|
}
|
|
170
151
|
} catch (err) {
|
|
171
152
|
logger.error(`Events sync error: ${err}`);
|
|
172
153
|
}
|
|
173
|
-
|
|
174
|
-
|
|
154
|
+
try {
|
|
155
|
+
let actionsUrl = `${baseUrl}/api/projects/${projectId}/actions/?limit=100`;
|
|
156
|
+
while (actionsUrl) {
|
|
157
|
+
const actionsRes = await fetch(actionsUrl, { headers });
|
|
158
|
+
if (!actionsRes.ok) break;
|
|
159
|
+
const page = await actionsRes.json();
|
|
160
|
+
const items = [];
|
|
161
|
+
for (const action of page.results ?? []) {
|
|
162
|
+
const uri = `posthog://action/${action.id}`;
|
|
163
|
+
const summary = action.description || `Action: ${action.name}`;
|
|
164
|
+
const body = JSON.stringify(action, null, 2);
|
|
165
|
+
const fileContent = formatAnalytics(action);
|
|
166
|
+
items.push({
|
|
167
|
+
source: "posthog",
|
|
168
|
+
sourceType: "action",
|
|
169
|
+
uri,
|
|
170
|
+
title: action.name,
|
|
171
|
+
summary,
|
|
172
|
+
tags: ["posthog", "action"],
|
|
173
|
+
content: body,
|
|
174
|
+
contentType: "application/json",
|
|
175
|
+
createdAt: new Date(action.created_at).getTime(),
|
|
176
|
+
fileContent,
|
|
177
|
+
fileMeta: { title: action.name }
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
const result = ctx.batchUpsert(items);
|
|
181
|
+
newObjects += result.inserted;
|
|
182
|
+
updatedObjects += result.updated;
|
|
183
|
+
actionsUrl = page.next;
|
|
184
|
+
if (actionsUrl) await new Promise((r) => setTimeout(r, 200));
|
|
185
|
+
}
|
|
186
|
+
} catch (err) {
|
|
187
|
+
logger.warn(`Actions sync error (non-fatal): ${err}`);
|
|
188
|
+
}
|
|
189
|
+
ctx.saveTimestampCursor("posthog:insights");
|
|
190
|
+
ctx.saveTimestampCursor("posthog:events");
|
|
191
|
+
logger.info("PostHog sync complete", { events, insights, newObjects, updatedObjects });
|
|
192
|
+
return { events, insights, newObjects, updatedObjects };
|
|
175
193
|
}
|
|
176
194
|
|
|
177
195
|
// src/sync/firebase.ts
|
|
@@ -180,24 +198,10 @@ var defaultLogger2 = {
|
|
|
180
198
|
warn: (msg, ctx) => logError("sync", msg, ctx),
|
|
181
199
|
error: (msg, ctx) => logError("sync", msg, ctx)
|
|
182
200
|
};
|
|
183
|
-
async function syncFirebase(
|
|
201
|
+
async function syncFirebase(ctx, data, logger = defaultLogger2) {
|
|
184
202
|
const { projectId, apiKey } = data;
|
|
185
203
|
if (!projectId) throw new Error("Missing Firebase project ID");
|
|
186
|
-
let events = 0, newObjects = 0;
|
|
187
|
-
const checkExists = sqlite.prepare("SELECT id FROM objects WHERE uri = ?");
|
|
188
|
-
const insertObj = sqlite.prepare(`
|
|
189
|
-
INSERT INTO objects (id, source, source_type, uri, title, summary, tags, content_hash, last_synced_at, created_at)
|
|
190
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
191
|
-
`);
|
|
192
|
-
const insertBody = sqlite.prepare(`
|
|
193
|
-
INSERT OR REPLACE INTO object_bodies (object_id, content, content_type, fetched_at)
|
|
194
|
-
VALUES (?, ?, ?, ?)
|
|
195
|
-
`);
|
|
196
|
-
const insertFts = sqlite.prepare(`
|
|
197
|
-
INSERT INTO objects_fts(rowid, title, summary, tags, source, source_type, body_excerpt)
|
|
198
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
199
|
-
`);
|
|
200
|
-
const getRowid = sqlite.prepare("SELECT rowid FROM objects WHERE id = ?");
|
|
204
|
+
let events = 0, newObjects = 0, updatedObjects = 0;
|
|
201
205
|
if (apiKey) {
|
|
202
206
|
try {
|
|
203
207
|
const propertyId = data.propertyId ?? projectId;
|
|
@@ -207,51 +211,41 @@ async function syncFirebase(sqlite, data, logger = defaultLogger2, fileWriter) {
|
|
|
207
211
|
method: "POST",
|
|
208
212
|
headers: { "Content-Type": "application/json" },
|
|
209
213
|
body: JSON.stringify({
|
|
210
|
-
dateRanges: [{ startDate: "
|
|
214
|
+
dateRanges: [{ startDate: "30daysAgo", endDate: "today" }],
|
|
211
215
|
dimensions: [{ name: "eventName" }],
|
|
212
|
-
metrics: [{ name: "eventCount" }],
|
|
213
|
-
limit:
|
|
216
|
+
metrics: [{ name: "eventCount" }, { name: "totalUsers" }],
|
|
217
|
+
limit: 500
|
|
214
218
|
})
|
|
215
219
|
}
|
|
216
220
|
);
|
|
217
221
|
if (res.ok) {
|
|
218
222
|
const report = await res.json();
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}, fileContent);
|
|
246
|
-
sqlite.prepare("UPDATE objects SET file_path = ? WHERE id = ?").run(filePath, id);
|
|
247
|
-
} catch {
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
events++;
|
|
251
|
-
newObjects++;
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
batch(report.rows ?? []);
|
|
223
|
+
const items = [];
|
|
224
|
+
for (const row of report.rows ?? []) {
|
|
225
|
+
const eventName = row.dimensionValues[0]?.value ?? "unknown";
|
|
226
|
+
const eventCount = parseInt(row.metricValues[0]?.value ?? "0");
|
|
227
|
+
const totalUsers = parseInt(row.metricValues[1]?.value ?? "0");
|
|
228
|
+
const uri = `firebase://${projectId}/event/${eventName}`;
|
|
229
|
+
const summary = `${eventName}: ${eventCount} events, ${totalUsers} users (last 30 days)`;
|
|
230
|
+
const body = JSON.stringify({ eventName, eventCount, totalUsers, period: "30daysAgo..today" }, null, 2);
|
|
231
|
+
const fileContent = formatAnalytics({ eventName, eventCount, totalUsers, period: "30daysAgo..today" });
|
|
232
|
+
items.push({
|
|
233
|
+
source: "firebase",
|
|
234
|
+
sourceType: "analytics_event",
|
|
235
|
+
uri,
|
|
236
|
+
title: eventName,
|
|
237
|
+
summary,
|
|
238
|
+
tags: ["firebase", "analytics", "event"],
|
|
239
|
+
content: body,
|
|
240
|
+
contentType: "application/json",
|
|
241
|
+
fileContent,
|
|
242
|
+
fileMeta: { title: eventName }
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
const result = ctx.batchUpsert(items);
|
|
246
|
+
newObjects += result.inserted;
|
|
247
|
+
updatedObjects += result.updated;
|
|
248
|
+
events += items.length;
|
|
255
249
|
} else {
|
|
256
250
|
logger.warn(`Firebase Analytics API: ${res.status}`);
|
|
257
251
|
}
|
|
@@ -261,8 +255,9 @@ async function syncFirebase(sqlite, data, logger = defaultLogger2, fileWriter) {
|
|
|
261
255
|
} else {
|
|
262
256
|
logger.warn("Firebase sync requires apiKey. Provide via jowork connect firebase --api-key <key>");
|
|
263
257
|
}
|
|
264
|
-
|
|
265
|
-
|
|
258
|
+
ctx.saveTimestampCursor("firebase:analytics");
|
|
259
|
+
logger.info("Firebase sync complete", { events, newObjects, updatedObjects });
|
|
260
|
+
return { events, newObjects, updatedObjects };
|
|
266
261
|
}
|
|
267
262
|
|
|
268
263
|
// src/sync/file-writer.ts
|
|
@@ -354,6 +349,8 @@ ${sanitizeContent(m.content)}`).join("\n");
|
|
|
354
349
|
return join("feishu", "approvals", `${slugify(meta.title ?? "approval")}-${meta.id}.md`);
|
|
355
350
|
if (sourceType === "document")
|
|
356
351
|
return join("feishu", "docs", `${slugify(meta.title ?? "doc")}.md`);
|
|
352
|
+
if (sourceType === "link")
|
|
353
|
+
return join("feishu", "links", `${slugify(meta.title ?? meta.id)}.md`);
|
|
357
354
|
return join("feishu", "other", `${meta.id}.md`);
|
|
358
355
|
}
|
|
359
356
|
case "posthog":
|
|
@@ -438,6 +435,30 @@ function elapsed(start) {
|
|
|
438
435
|
const ms = Date.now() - start;
|
|
439
436
|
return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(1)}s`;
|
|
440
437
|
}
|
|
438
|
+
function getSyncErrorHint(source, error) {
|
|
439
|
+
const is401 = /401|unauthorized|auth.*fail/i.test(error);
|
|
440
|
+
const is403 = /403|forbidden/i.test(error);
|
|
441
|
+
if (is401 || is403) {
|
|
442
|
+
switch (source) {
|
|
443
|
+
case "github":
|
|
444
|
+
return "Token invalid or expired. Create a new one at https://github.com/settings/tokens";
|
|
445
|
+
case "gitlab":
|
|
446
|
+
return "Token invalid. Check at https://gitlab.com/-/profile/personal_access_tokens";
|
|
447
|
+
case "feishu":
|
|
448
|
+
return "App ID/Secret invalid. Check at https://open.feishu.cn/app";
|
|
449
|
+
case "linear":
|
|
450
|
+
return "API key invalid. Get one at https://linear.app/settings/api";
|
|
451
|
+
case "posthog":
|
|
452
|
+
return "API key invalid. Get one at https://app.posthog.com/project/settings";
|
|
453
|
+
case "firebase":
|
|
454
|
+
return "API key invalid. Check at https://console.cloud.google.com \u2192 APIs & Services \u2192 Credentials";
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (/ENOTFOUND|ECONNREFUSED|network/i.test(error)) {
|
|
458
|
+
return "Network error. Check your internet connection and try again.";
|
|
459
|
+
}
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
441
462
|
async function runSync(sources) {
|
|
442
463
|
const db = new DbManager(dbPath());
|
|
443
464
|
db.ensureTables();
|
|
@@ -470,63 +491,82 @@ async function runSync(sources) {
|
|
|
470
491
|
try {
|
|
471
492
|
switch (source) {
|
|
472
493
|
case "feishu": {
|
|
473
|
-
const
|
|
494
|
+
const feishuCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
|
|
495
|
+
const result = await syncFeishu(feishuCtx, cred.data, logger);
|
|
474
496
|
resultLine(true, `${result.newMessages} new messages from ${result.chats} chats`);
|
|
475
497
|
totalNew += result.newMessages;
|
|
476
498
|
syncResults.push({ source: "feishu", newObjects: result.newMessages, label: "messages" });
|
|
477
499
|
try {
|
|
478
|
-
const mr = await syncFeishuMeetings(
|
|
500
|
+
const mr = await syncFeishuMeetings(feishuCtx, cred.data, logger);
|
|
479
501
|
if (mr.newObjects > 0) resultLine(true, `${mr.newObjects} calendar events`);
|
|
480
502
|
syncResults.push({ source: "feishu/meetings", newObjects: mr.newObjects, label: "events" });
|
|
481
503
|
} catch {
|
|
482
504
|
}
|
|
483
505
|
try {
|
|
484
|
-
const dr = await syncFeishuDocs(
|
|
506
|
+
const dr = await syncFeishuDocs(feishuCtx, cred.data, logger);
|
|
485
507
|
if (dr.newObjects > 0) resultLine(true, `${dr.newObjects} documents`);
|
|
486
508
|
syncResults.push({ source: "feishu/docs", newObjects: dr.newObjects, label: "docs" });
|
|
487
509
|
} catch {
|
|
488
510
|
}
|
|
489
511
|
try {
|
|
490
|
-
const ar = await syncFeishuApprovals(
|
|
512
|
+
const ar = await syncFeishuApprovals(feishuCtx, cred.data, logger);
|
|
491
513
|
if (ar.newObjects > 0) resultLine(true, `${ar.newObjects} approvals`);
|
|
492
514
|
syncResults.push({ source: "feishu/approvals", newObjects: ar.newObjects, label: "approvals" });
|
|
493
515
|
} catch {
|
|
494
516
|
}
|
|
517
|
+
try {
|
|
518
|
+
const lr = await syncFeishuLinks(feishuCtx, cred.data, logger);
|
|
519
|
+
if (lr.fetched > 0) resultLine(true, `${lr.fetched} link contents fetched (${lr.extracted} URLs found)`);
|
|
520
|
+
else if (lr.extracted > 0) resultLine(false, `${lr.extracted} URLs found, ${lr.failed} failed to fetch`);
|
|
521
|
+
syncResults.push({ source: "feishu/links", newObjects: lr.fetched, label: "links" });
|
|
522
|
+
} catch {
|
|
523
|
+
}
|
|
495
524
|
break;
|
|
496
525
|
}
|
|
497
526
|
case "github": {
|
|
498
|
-
const
|
|
499
|
-
|
|
527
|
+
const ghCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
|
|
528
|
+
const r = await syncGitHub(ghCtx, cred.data, logger);
|
|
529
|
+
const changeInfo = r.updatedObjects > 0 ? `, ${r.updatedObjects} updated` : "";
|
|
530
|
+
const cloneInfo = r.clonedRepos > 0 ? ` | ${r.clonedRepos} repos cloned` : "";
|
|
531
|
+
resultLine(true, `${r.repos} repos, ${r.prs} PRs, ${r.issues} issues ${c.dim}(${r.newObjects} new${changeInfo})${cloneInfo}${c.reset}`);
|
|
500
532
|
totalNew += r.newObjects;
|
|
501
|
-
syncResults.push({ source: "github", newObjects: r.newObjects });
|
|
533
|
+
syncResults.push({ source: "github", newObjects: r.newObjects + r.updatedObjects });
|
|
502
534
|
break;
|
|
503
535
|
}
|
|
504
536
|
case "gitlab": {
|
|
505
|
-
const
|
|
506
|
-
|
|
537
|
+
const glCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
|
|
538
|
+
const r = await syncGitLab(glCtx, cred.data, logger);
|
|
539
|
+
const glChangeInfo = r.updatedObjects > 0 ? `, ${r.updatedObjects} updated` : "";
|
|
540
|
+
resultLine(true, `${r.projects} projects, ${r.mrs} MRs, ${r.issues} issues ${c.dim}(${r.newObjects} new${glChangeInfo})${c.reset}`);
|
|
507
541
|
totalNew += r.newObjects;
|
|
508
|
-
syncResults.push({ source: "gitlab", newObjects: r.newObjects });
|
|
542
|
+
syncResults.push({ source: "gitlab", newObjects: r.newObjects + r.updatedObjects });
|
|
509
543
|
break;
|
|
510
544
|
}
|
|
511
545
|
case "linear": {
|
|
512
|
-
const
|
|
513
|
-
|
|
546
|
+
const lnCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
|
|
547
|
+
const r = await syncLinear(lnCtx, cred.data, logger);
|
|
548
|
+
const lnChangeInfo = r.updatedObjects > 0 ? `, ${r.updatedObjects} updated` : "";
|
|
549
|
+
resultLine(true, `${r.issues} issues ${c.dim}(${r.newObjects} new${lnChangeInfo})${c.reset}`);
|
|
514
550
|
totalNew += r.newObjects;
|
|
515
|
-
syncResults.push({ source: "linear", newObjects: r.newObjects, label: "issues" });
|
|
551
|
+
syncResults.push({ source: "linear", newObjects: r.newObjects + r.updatedObjects, label: "issues" });
|
|
516
552
|
break;
|
|
517
553
|
}
|
|
518
554
|
case "posthog": {
|
|
519
|
-
const
|
|
520
|
-
|
|
555
|
+
const phCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
|
|
556
|
+
const r = await syncPostHog(phCtx, cred.data, logger);
|
|
557
|
+
const phChangeInfo = r.updatedObjects > 0 ? `, ${r.updatedObjects} updated` : "";
|
|
558
|
+
resultLine(true, `${r.insights} insights, ${r.events} events ${c.dim}(${r.newObjects} new${phChangeInfo})${c.reset}`);
|
|
521
559
|
totalNew += r.newObjects;
|
|
522
|
-
syncResults.push({ source: "posthog", newObjects: r.newObjects });
|
|
560
|
+
syncResults.push({ source: "posthog", newObjects: r.newObjects + r.updatedObjects });
|
|
523
561
|
break;
|
|
524
562
|
}
|
|
525
563
|
case "firebase": {
|
|
526
|
-
const
|
|
527
|
-
|
|
564
|
+
const fbCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
|
|
565
|
+
const r = await syncFirebase(fbCtx, cred.data, logger);
|
|
566
|
+
const fbChangeInfo = r.updatedObjects > 0 ? `, ${r.updatedObjects} updated` : "";
|
|
567
|
+
resultLine(true, `${r.events} events ${c.dim}(${r.newObjects} new${fbChangeInfo})${c.reset}`);
|
|
528
568
|
totalNew += r.newObjects;
|
|
529
|
-
syncResults.push({ source: "firebase", newObjects: r.newObjects, label: "events" });
|
|
569
|
+
syncResults.push({ source: "firebase", newObjects: r.newObjects + r.updatedObjects, label: "events" });
|
|
530
570
|
break;
|
|
531
571
|
}
|
|
532
572
|
default:
|
|
@@ -535,7 +575,12 @@ async function runSync(sources) {
|
|
|
535
575
|
console.log(` ${c.dim}${elapsed(sourceStart)}${c.reset}`);
|
|
536
576
|
} catch (err) {
|
|
537
577
|
logError("sync", `Failed to sync ${source}`, { error: String(err) });
|
|
538
|
-
|
|
578
|
+
const errStr = String(err);
|
|
579
|
+
console.log(` ${icon.fail} ${c.red}sync failed${c.reset} ${c.dim}${errStr.slice(0, 60)}${c.reset}`);
|
|
580
|
+
const hint = getSyncErrorHint(source, errStr);
|
|
581
|
+
if (hint) {
|
|
582
|
+
console.log(` ${c.yellow}Hint: ${hint}${c.reset}`);
|
|
583
|
+
}
|
|
539
584
|
}
|
|
540
585
|
if (sources.length > 1) {
|
|
541
586
|
console.log(` ${progressBar(i + 1, sources.length)}`);
|
|
@@ -547,6 +592,38 @@ async function runSync(sources) {
|
|
|
547
592
|
if (processed > 0) {
|
|
548
593
|
resultLine(true, `${linksCreated} links from ${processed} objects`);
|
|
549
594
|
}
|
|
595
|
+
try {
|
|
596
|
+
const sqlite = db.getSqlite();
|
|
597
|
+
const { readObjectContent } = await import("./content-reader-VPGTR2SF.js");
|
|
598
|
+
const noFilePath = sqlite.prepare(`
|
|
599
|
+
SELECT o.id, o.source, o.source_type, o.uri, o.title
|
|
600
|
+
FROM objects o
|
|
601
|
+
WHERE o.file_path IS NULL
|
|
602
|
+
LIMIT 500
|
|
603
|
+
`).all();
|
|
604
|
+
if (noFilePath.length > 0) {
|
|
605
|
+
console.log(` ${icon.sync} ${c.dim}backfilling ${noFilePath.length} objects without files...${c.reset}`);
|
|
606
|
+
let backfilled = 0;
|
|
607
|
+
for (const obj of noFilePath) {
|
|
608
|
+
try {
|
|
609
|
+
const content = readObjectContent(sqlite, obj.id, null);
|
|
610
|
+
if (!content) continue;
|
|
611
|
+
const filePath = fileWriter.writeObject(obj.source, obj.source_type, {
|
|
612
|
+
id: obj.id,
|
|
613
|
+
title: obj.title,
|
|
614
|
+
uri: obj.uri
|
|
615
|
+
}, content);
|
|
616
|
+
sqlite.prepare("UPDATE objects SET file_path = ? WHERE id = ?").run(filePath, obj.id);
|
|
617
|
+
backfilled++;
|
|
618
|
+
} catch {
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (backfilled > 0) {
|
|
622
|
+
resultLine(true, `${backfilled} files backfilled`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
} catch {
|
|
626
|
+
}
|
|
550
627
|
db.close();
|
|
551
628
|
if (gitManager) {
|
|
552
629
|
try {
|