wikimem 0.8.0 → 0.8.1
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/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +97 -8
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/core/connectors.d.ts +1 -1
- package/dist/core/connectors.d.ts.map +1 -1
- package/dist/core/git.d.ts +1 -1
- package/dist/core/git.d.ts.map +1 -1
- package/dist/core/git.js.map +1 -1
- package/dist/core/ingest.d.ts.map +1 -1
- package/dist/core/ingest.js +74 -3
- package/dist/core/ingest.js.map +1 -1
- package/dist/core/lint.d.ts.map +1 -1
- package/dist/core/lint.js +23 -4
- package/dist/core/lint.js.map +1 -1
- package/dist/core/oauth-defaults.d.ts +31 -0
- package/dist/core/oauth-defaults.d.ts.map +1 -0
- package/dist/core/oauth-defaults.js +77 -0
- package/dist/core/oauth-defaults.js.map +1 -0
- package/dist/core/observer.d.ts +24 -1
- package/dist/core/observer.d.ts.map +1 -1
- package/dist/core/observer.js +146 -4
- package/dist/core/observer.js.map +1 -1
- package/dist/core/sync/gdrive.d.ts +14 -0
- package/dist/core/sync/gdrive.d.ts.map +1 -0
- package/dist/core/sync/gdrive.js +205 -0
- package/dist/core/sync/gdrive.js.map +1 -0
- package/dist/core/sync/github.d.ts +20 -0
- package/dist/core/sync/github.d.ts.map +1 -0
- package/dist/core/sync/github.js +206 -0
- package/dist/core/sync/github.js.map +1 -0
- package/dist/core/sync/gmail.d.ts +15 -0
- package/dist/core/sync/gmail.d.ts.map +1 -0
- package/dist/core/sync/gmail.js +159 -0
- package/dist/core/sync/gmail.js.map +1 -0
- package/dist/core/sync/index.d.ts +47 -0
- package/dist/core/sync/index.d.ts.map +1 -0
- package/dist/core/sync/index.js +100 -0
- package/dist/core/sync/index.js.map +1 -0
- package/dist/core/sync/jira.d.ts +15 -0
- package/dist/core/sync/jira.d.ts.map +1 -0
- package/dist/core/sync/jira.js +176 -0
- package/dist/core/sync/jira.js.map +1 -0
- package/dist/core/sync/linear.d.ts +15 -0
- package/dist/core/sync/linear.d.ts.map +1 -0
- package/dist/core/sync/linear.js +111 -0
- package/dist/core/sync/linear.js.map +1 -0
- package/dist/core/sync/notion.d.ts +14 -0
- package/dist/core/sync/notion.d.ts.map +1 -0
- package/dist/core/sync/notion.js +168 -0
- package/dist/core/sync/notion.js.map +1 -0
- package/dist/core/sync/rss.d.ts +20 -0
- package/dist/core/sync/rss.d.ts.map +1 -0
- package/dist/core/sync/rss.js +165 -0
- package/dist/core/sync/rss.js.map +1 -0
- package/dist/core/sync/scheduler.d.ts +31 -0
- package/dist/core/sync/scheduler.d.ts.map +1 -0
- package/dist/core/sync/scheduler.js +129 -0
- package/dist/core/sync/scheduler.js.map +1 -0
- package/dist/core/sync/slack.d.ts +16 -0
- package/dist/core/sync/slack.d.ts.map +1 -0
- package/dist/core/sync/slack.js +173 -0
- package/dist/core/sync/slack.js.map +1 -0
- package/dist/core/vault.d.ts +22 -0
- package/dist/core/vault.d.ts.map +1 -1
- package/dist/core/vault.js +65 -0
- package/dist/core/vault.js.map +1 -1
- package/dist/core/webhooks.d.ts +13 -0
- package/dist/core/webhooks.d.ts.map +1 -0
- package/dist/core/webhooks.js +206 -0
- package/dist/core/webhooks.js.map +1 -0
- package/dist/mcp-server.d.ts +11 -6
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +99 -6
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-tools-extended.d.ts +15 -0
- package/dist/mcp-tools-extended.d.ts.map +1 -0
- package/dist/mcp-tools-extended.js +277 -0
- package/dist/mcp-tools-extended.js.map +1 -0
- package/dist/processors/csv.d.ts +18 -0
- package/dist/processors/csv.d.ts.map +1 -0
- package/dist/processors/csv.js +230 -0
- package/dist/processors/csv.js.map +1 -0
- package/dist/processors/image.d.ts.map +1 -1
- package/dist/processors/image.js +55 -27
- package/dist/processors/image.js.map +1 -1
- package/dist/processors/pdf.d.ts.map +1 -1
- package/dist/processors/pdf.js +5 -1
- package/dist/processors/pdf.js.map +1 -1
- package/dist/processors/pptx.d.ts +3 -1
- package/dist/processors/pptx.d.ts.map +1 -1
- package/dist/processors/pptx.js +236 -95
- package/dist/processors/pptx.js.map +1 -1
- package/dist/processors/xlsx.d.ts +2 -0
- package/dist/processors/xlsx.d.ts.map +1 -1
- package/dist/processors/xlsx.js +182 -46
- package/dist/processors/xlsx.js.map +1 -1
- package/dist/templates/source-types.d.ts +33 -0
- package/dist/templates/source-types.d.ts.map +1 -0
- package/dist/templates/source-types.js +178 -0
- package/dist/templates/source-types.js.map +1 -0
- package/dist/web/public/index.html +1785 -103
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +746 -38
- package/dist/web/server.js.map +1 -1
- package/package.json +4 -1
- package/src/web/public/index.html +1785 -103
- package/templates/source-types/article.md +21 -0
- package/templates/source-types/book.md +21 -0
- package/templates/source-types/paper.md +23 -0
- package/templates/source-types/podcast.md +21 -0
- package/templates/source-types/raw-notes.md +17 -0
- package/templates/source-types/tweet-thread.md +19 -0
- package/templates/source-types/video.md +21 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Drive sync module — fetches recent files and exports Google Workspace
|
|
3
|
+
* documents as markdown wiki pages under wiki/sources/.
|
|
4
|
+
*/
|
|
5
|
+
import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
const API = 'https://www.googleapis.com/drive/v3';
|
|
8
|
+
/** Google Workspace MIME types we can export as text */
|
|
9
|
+
const EXPORT_MAP = {
|
|
10
|
+
'application/vnd.google-apps.document': { mimeType: 'text/plain', label: 'Google Doc' },
|
|
11
|
+
'application/vnd.google-apps.spreadsheet': { mimeType: 'text/csv', label: 'Google Sheet' },
|
|
12
|
+
'application/vnd.google-apps.presentation': { mimeType: 'text/plain', label: 'Google Slides' },
|
|
13
|
+
};
|
|
14
|
+
/** MIME types we can download directly as text */
|
|
15
|
+
const TEXT_MIME_TYPES = new Set([
|
|
16
|
+
'text/plain',
|
|
17
|
+
'text/markdown',
|
|
18
|
+
'text/csv',
|
|
19
|
+
'text/html',
|
|
20
|
+
'application/json',
|
|
21
|
+
'text/x-python',
|
|
22
|
+
'text/javascript',
|
|
23
|
+
'application/x-yaml',
|
|
24
|
+
]);
|
|
25
|
+
function sanitizeFilename(raw) {
|
|
26
|
+
return (raw
|
|
27
|
+
.toLowerCase()
|
|
28
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
29
|
+
.replace(/-+/g, '-')
|
|
30
|
+
.replace(/^-|-$/g, '')
|
|
31
|
+
.slice(0, 120) || 'untitled');
|
|
32
|
+
}
|
|
33
|
+
function errorHint(status, message) {
|
|
34
|
+
if (status === 401)
|
|
35
|
+
return 'Token expired — re-authenticate with Google OAuth';
|
|
36
|
+
if (status === 403)
|
|
37
|
+
return 'Insufficient permissions — ensure drive.readonly scope';
|
|
38
|
+
if (status === 429)
|
|
39
|
+
return 'Rate limited — try again later';
|
|
40
|
+
return `HTTP ${status}: ${message}`;
|
|
41
|
+
}
|
|
42
|
+
function recentTimestamp() {
|
|
43
|
+
const d = new Date();
|
|
44
|
+
d.setDate(d.getDate() - 30);
|
|
45
|
+
return d.toISOString();
|
|
46
|
+
}
|
|
47
|
+
async function driveFetch(url, token) {
|
|
48
|
+
const res = await fetch(url, {
|
|
49
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
50
|
+
});
|
|
51
|
+
if (!res.ok) {
|
|
52
|
+
const body = await res.text().catch(() => '');
|
|
53
|
+
return { ok: false, status: res.status, message: body };
|
|
54
|
+
}
|
|
55
|
+
return { ok: true, data: (await res.json()) };
|
|
56
|
+
}
|
|
57
|
+
async function driveFetchText(url, token) {
|
|
58
|
+
const res = await fetch(url, {
|
|
59
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
60
|
+
});
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
const body = await res.text().catch(() => '');
|
|
63
|
+
return { ok: false, status: res.status, message: body };
|
|
64
|
+
}
|
|
65
|
+
return { ok: true, data: await res.text() };
|
|
66
|
+
}
|
|
67
|
+
async function listFiles(token, maxFiles, folderId) {
|
|
68
|
+
const errors = [];
|
|
69
|
+
const allFiles = [];
|
|
70
|
+
let pageToken;
|
|
71
|
+
const since = recentTimestamp();
|
|
72
|
+
do {
|
|
73
|
+
const params = new URLSearchParams({
|
|
74
|
+
orderBy: 'modifiedTime desc',
|
|
75
|
+
pageSize: String(Math.min(50, maxFiles - allFiles.length)),
|
|
76
|
+
fields: 'nextPageToken,files(id,name,mimeType,modifiedTime,webViewLink)',
|
|
77
|
+
});
|
|
78
|
+
const queryParts = [`modifiedTime > '${since}'`, 'trashed = false'];
|
|
79
|
+
if (folderId) {
|
|
80
|
+
queryParts.push(`'${folderId}' in parents`);
|
|
81
|
+
}
|
|
82
|
+
params.set('q', queryParts.join(' and '));
|
|
83
|
+
if (pageToken) {
|
|
84
|
+
params.set('pageToken', pageToken);
|
|
85
|
+
}
|
|
86
|
+
const result = await driveFetch(`${API}/files?${params.toString()}`, token);
|
|
87
|
+
if (!result.ok) {
|
|
88
|
+
errors.push(`listFiles: ${errorHint(result.status, result.message)}`);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
allFiles.push(...result.data.files);
|
|
92
|
+
pageToken = result.data.nextPageToken;
|
|
93
|
+
} while (pageToken && allFiles.length < maxFiles);
|
|
94
|
+
return { files: allFiles.slice(0, maxFiles), errors };
|
|
95
|
+
}
|
|
96
|
+
async function exportFile(token, fileId, exportMimeType) {
|
|
97
|
+
const result = await driveFetchText(`${API}/files/${fileId}/export?mimeType=${encodeURIComponent(exportMimeType)}`, token);
|
|
98
|
+
if (!result.ok) {
|
|
99
|
+
return { content: null, error: `export(${fileId}): ${errorHint(result.status, result.message)}` };
|
|
100
|
+
}
|
|
101
|
+
return { content: result.data, error: null };
|
|
102
|
+
}
|
|
103
|
+
async function downloadFile(token, fileId) {
|
|
104
|
+
const result = await driveFetchText(`${API}/files/${fileId}?alt=media`, token);
|
|
105
|
+
if (!result.ok) {
|
|
106
|
+
return { content: null, error: `download(${fileId}): ${errorHint(result.status, result.message)}` };
|
|
107
|
+
}
|
|
108
|
+
return { content: result.data, error: null };
|
|
109
|
+
}
|
|
110
|
+
function fileToMarkdown(file, body, sourceLabel) {
|
|
111
|
+
const esc = (s) => s.replace(/'/g, "''");
|
|
112
|
+
const frontmatter = [
|
|
113
|
+
'---',
|
|
114
|
+
`type: source`,
|
|
115
|
+
`source-type: gdrive`,
|
|
116
|
+
`title: '${esc(file.name)}'`,
|
|
117
|
+
`gdrive-id: '${file.id}'`,
|
|
118
|
+
`mime-type: '${file.mimeType}'`,
|
|
119
|
+
`modified: '${file.modifiedTime}'`,
|
|
120
|
+
`created: '${new Date().toISOString()}'`,
|
|
121
|
+
file.webViewLink ? `link: '${file.webViewLink}'` : null,
|
|
122
|
+
`tags: [gdrive, ${sourceLabel.toLowerCase().replace(/\s+/g, '-')}]`,
|
|
123
|
+
'---',
|
|
124
|
+
]
|
|
125
|
+
.filter(Boolean)
|
|
126
|
+
.join('\n');
|
|
127
|
+
const links = file.webViewLink
|
|
128
|
+
? `\n## Links\n\n- [Open in Google Drive](${file.webViewLink})\n`
|
|
129
|
+
: '';
|
|
130
|
+
return {
|
|
131
|
+
filename: `gdrive-${sanitizeFilename(file.name)}.md`,
|
|
132
|
+
content: `${frontmatter}\n\n# ${file.name}\n\n> Source: ${sourceLabel} via Google Drive sync\n\n${body.trim()}\n${links}`,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
export async function syncGDrive(options) {
|
|
136
|
+
const start = Date.now();
|
|
137
|
+
const errors = [];
|
|
138
|
+
let filesWritten = 0;
|
|
139
|
+
const maxFiles = options.maxFiles ?? 50;
|
|
140
|
+
const outDir = join(options.vaultRoot, 'wiki', 'sources');
|
|
141
|
+
if (!existsSync(outDir))
|
|
142
|
+
mkdirSync(outDir, { recursive: true });
|
|
143
|
+
const { files, errors: listErrors } = await listFiles(options.token, maxFiles, options.folderId);
|
|
144
|
+
errors.push(...listErrors);
|
|
145
|
+
if (files.length === 0 && listErrors.length > 0) {
|
|
146
|
+
return { provider: 'gdrive', filesWritten: 0, errors, duration: Date.now() - start };
|
|
147
|
+
}
|
|
148
|
+
for (const file of files) {
|
|
149
|
+
const exportInfo = EXPORT_MAP[file.mimeType];
|
|
150
|
+
if (exportInfo) {
|
|
151
|
+
// Google Workspace file — export via /export endpoint
|
|
152
|
+
const { content, error } = await exportFile(options.token, file.id, exportInfo.mimeType);
|
|
153
|
+
if (error) {
|
|
154
|
+
errors.push(error);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (!content)
|
|
158
|
+
continue;
|
|
159
|
+
const { filename, content: md } = fileToMarkdown(file, content, exportInfo.label);
|
|
160
|
+
try {
|
|
161
|
+
writeFileSync(join(outDir, filename), md, 'utf-8');
|
|
162
|
+
filesWritten++;
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
166
|
+
errors.push(`Write failed (${filename}): ${msg}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else if (TEXT_MIME_TYPES.has(file.mimeType)) {
|
|
170
|
+
// Plain text file — download directly
|
|
171
|
+
const { content, error } = await downloadFile(options.token, file.id);
|
|
172
|
+
if (error) {
|
|
173
|
+
errors.push(error);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (!content)
|
|
177
|
+
continue;
|
|
178
|
+
const label = file.mimeType.split('/').pop() ?? 'text';
|
|
179
|
+
const { filename, content: md } = fileToMarkdown(file, content, label);
|
|
180
|
+
try {
|
|
181
|
+
writeFileSync(join(outDir, filename), md, 'utf-8');
|
|
182
|
+
filesWritten++;
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
186
|
+
errors.push(`Write failed (${filename}): ${msg}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// Binary file (PDF, image, etc.) — note existence but skip content
|
|
191
|
+
const body = `_Binary file (${file.mimeType}) — content not exported. Open in Google Drive to view._`;
|
|
192
|
+
const { filename, content: md } = fileToMarkdown(file, body, 'binary');
|
|
193
|
+
try {
|
|
194
|
+
writeFileSync(join(outDir, filename), md, 'utf-8');
|
|
195
|
+
filesWritten++;
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
199
|
+
errors.push(`Write failed (${filename}): ${msg}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return { provider: 'gdrive', filesWritten, errors, duration: Date.now() - start };
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=gdrive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gdrive.js","sourceRoot":"","sources":["../../../src/core/sync/gdrive.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Sync — fetches repos, issues, PRs, and READMEs into raw/ for wiki ingest.
|
|
3
|
+
* Uses GitHub REST API v3 with OAuth access_token from .wikimem/tokens.json.
|
|
4
|
+
*/
|
|
5
|
+
export interface GitHubSyncOptions {
|
|
6
|
+
token: string;
|
|
7
|
+
vaultRoot: string;
|
|
8
|
+
repos?: string[];
|
|
9
|
+
maxRepos?: number;
|
|
10
|
+
maxIssuesPerRepo?: number;
|
|
11
|
+
maxPRsPerRepo?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface PlatformSyncResult {
|
|
14
|
+
provider: string;
|
|
15
|
+
filesWritten: number;
|
|
16
|
+
errors: string[];
|
|
17
|
+
duration: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function syncGitHub(options: GitHubSyncOptions): Promise<PlatformSyncResult>;
|
|
20
|
+
//# sourceMappingURL=github.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../../src/core/sync/github.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AA6JD,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAqGxF"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Sync — fetches repos, issues, PRs, and READMEs into raw/ for wiki ingest.
|
|
3
|
+
* Uses GitHub REST API v3 with OAuth access_token from .wikimem/tokens.json.
|
|
4
|
+
*/
|
|
5
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
const API_BASE = 'https://api.github.com';
|
|
8
|
+
const MAX_TOTAL_CALLS = 200;
|
|
9
|
+
let callCount = 0;
|
|
10
|
+
async function ghFetch(path, token) {
|
|
11
|
+
if (callCount >= MAX_TOTAL_CALLS) {
|
|
12
|
+
return { data: null, error: `API call cap reached (${MAX_TOTAL_CALLS})` };
|
|
13
|
+
}
|
|
14
|
+
callCount++;
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch(`${API_BASE}${path}`, {
|
|
17
|
+
headers: {
|
|
18
|
+
Authorization: `Bearer ${token}`,
|
|
19
|
+
Accept: 'application/vnd.github.v3+json',
|
|
20
|
+
'User-Agent': 'wikimem-sync',
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
const remaining = res.headers.get('X-RateLimit-Remaining');
|
|
24
|
+
if (remaining !== null && parseInt(remaining, 10) < 10) {
|
|
25
|
+
const resetAt = res.headers.get('X-RateLimit-Reset');
|
|
26
|
+
const resetTime = resetAt ? new Date(parseInt(resetAt, 10) * 1000).toISOString() : 'unknown';
|
|
27
|
+
return { data: null, error: `Rate limit nearly exhausted (${remaining} left, resets ${resetTime})` };
|
|
28
|
+
}
|
|
29
|
+
if (!res.ok) {
|
|
30
|
+
return { data: null, error: `GitHub API ${res.status}: ${res.statusText} for ${path}` };
|
|
31
|
+
}
|
|
32
|
+
const data = (await res.json());
|
|
33
|
+
return { data, error: null };
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
37
|
+
return { data: null, error: `Fetch failed for ${path}: ${msg}` };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function todayDate() {
|
|
41
|
+
return new Date().toISOString().slice(0, 10);
|
|
42
|
+
}
|
|
43
|
+
function frontmatter(fields) {
|
|
44
|
+
const lines = ['---'];
|
|
45
|
+
for (const [key, val] of Object.entries(fields)) {
|
|
46
|
+
if (Array.isArray(val)) {
|
|
47
|
+
lines.push(`${key}: [${val.map((v) => `"${v}"`).join(', ')}]`);
|
|
48
|
+
}
|
|
49
|
+
else if (typeof val === 'string') {
|
|
50
|
+
lines.push(`${key}: "${val.replace(/"/g, '\\"')}"`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
lines.push(`${key}: ${String(val)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
lines.push('---');
|
|
57
|
+
return lines.join('\n');
|
|
58
|
+
}
|
|
59
|
+
function sanitizeFilename(name) {
|
|
60
|
+
return name.replace(/[^a-zA-Z0-9._-]/g, '-').slice(0, 100);
|
|
61
|
+
}
|
|
62
|
+
function writeMarkdown(dir, filename, content) {
|
|
63
|
+
mkdirSync(dir, { recursive: true });
|
|
64
|
+
writeFileSync(join(dir, filename), content, 'utf-8');
|
|
65
|
+
}
|
|
66
|
+
function buildRepoPage(repo) {
|
|
67
|
+
const fm = frontmatter({
|
|
68
|
+
title: repo.full_name, addedBy: 'connector', source: 'github', type: 'repository',
|
|
69
|
+
url: repo.html_url, language: repo.language ?? 'unknown',
|
|
70
|
+
stars: repo.stargazers_count, forks: repo.forks_count, syncedAt: new Date().toISOString(),
|
|
71
|
+
});
|
|
72
|
+
const topics = repo.topics.length > 0 ? `\n**Topics:** ${repo.topics.join(', ')}` : '';
|
|
73
|
+
return `${fm}\n\n# ${repo.full_name}\n\n${repo.description ?? '_No description._'}\n
|
|
74
|
+
| Stat | Value |
|
|
75
|
+
|------|-------|
|
|
76
|
+
| Language | ${repo.language ?? 'N/A'} |
|
|
77
|
+
| Stars | ${repo.stargazers_count} |
|
|
78
|
+
| Forks | ${repo.forks_count} |
|
|
79
|
+
| Open Issues | ${repo.open_issues_count} |
|
|
80
|
+
| Updated | ${repo.updated_at} |
|
|
81
|
+
| Private | ${repo.private ? 'Yes' : 'No'} |
|
|
82
|
+
${topics}\n\n[View on GitHub](${repo.html_url})\n`;
|
|
83
|
+
}
|
|
84
|
+
function buildIssuePage(repo, issue) {
|
|
85
|
+
const fm = frontmatter({
|
|
86
|
+
title: `#${issue.number} ${issue.title}`, addedBy: 'connector', source: 'github',
|
|
87
|
+
type: 'issue', repo, url: issue.html_url, state: issue.state,
|
|
88
|
+
author: issue.user?.login ?? 'unknown', labels: issue.labels.map((l) => l.name),
|
|
89
|
+
createdAt: issue.created_at, syncedAt: new Date().toISOString(),
|
|
90
|
+
});
|
|
91
|
+
const body = issue.body ? issue.body.slice(0, 5000) : '_No description._';
|
|
92
|
+
return `${fm}\n\n# ${issue.title}\n\n**Issue #${issue.number}** in \`${repo}\` | ${issue.state} | by ${issue.user?.login ?? 'unknown'}\n\n${body}\n`;
|
|
93
|
+
}
|
|
94
|
+
function buildPRPage(repo, pr) {
|
|
95
|
+
const fm = frontmatter({
|
|
96
|
+
title: `PR #${pr.number} ${pr.title}`, addedBy: 'connector', source: 'github',
|
|
97
|
+
type: 'pull-request', repo, url: pr.html_url, state: pr.state,
|
|
98
|
+
author: pr.user?.login ?? 'unknown', branch: `${pr.head.ref} -> ${pr.base.ref}`,
|
|
99
|
+
createdAt: pr.created_at, syncedAt: new Date().toISOString(),
|
|
100
|
+
});
|
|
101
|
+
const body = pr.body ? pr.body.slice(0, 5000) : '_No description._';
|
|
102
|
+
return `${fm}\n\n# ${pr.title}\n\n**PR #${pr.number}** in \`${repo}\` | ${pr.state} | \`${pr.head.ref}\` -> \`${pr.base.ref}\` | by ${pr.user?.login ?? 'unknown'}\n\n${body}\n`;
|
|
103
|
+
}
|
|
104
|
+
function buildReadmePage(repo, content, htmlUrl) {
|
|
105
|
+
const fm = frontmatter({
|
|
106
|
+
title: `${repo} README`, addedBy: 'connector', source: 'github',
|
|
107
|
+
type: 'readme', repo, url: htmlUrl, syncedAt: new Date().toISOString(),
|
|
108
|
+
});
|
|
109
|
+
return `${fm}\n\n${content.slice(0, 10000)}\n`;
|
|
110
|
+
}
|
|
111
|
+
export async function syncGitHub(options) {
|
|
112
|
+
const start = Date.now();
|
|
113
|
+
callCount = 0;
|
|
114
|
+
const errors = [];
|
|
115
|
+
let filesWritten = 0;
|
|
116
|
+
const date = todayDate();
|
|
117
|
+
const outDir = join(options.vaultRoot, 'raw', date);
|
|
118
|
+
mkdirSync(outDir, { recursive: true });
|
|
119
|
+
const maxRepos = options.maxRepos ?? 30;
|
|
120
|
+
const maxIssues = options.maxIssuesPerRepo ?? 20;
|
|
121
|
+
const maxPRs = options.maxPRsPerRepo ?? 10;
|
|
122
|
+
// Step 1: Get repos
|
|
123
|
+
let repos = [];
|
|
124
|
+
if (options.repos && options.repos.length > 0) {
|
|
125
|
+
for (const fullName of options.repos.slice(0, maxRepos)) {
|
|
126
|
+
const { data, error } = await ghFetch(`/repos/${fullName}`, options.token);
|
|
127
|
+
if (error) {
|
|
128
|
+
errors.push(error);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (data)
|
|
132
|
+
repos.push(data);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
const { data, error } = await ghFetch(`/user/repos?sort=updated&per_page=${maxRepos}&type=owner`, options.token);
|
|
137
|
+
if (error)
|
|
138
|
+
errors.push(error);
|
|
139
|
+
if (data)
|
|
140
|
+
repos = data;
|
|
141
|
+
}
|
|
142
|
+
// Step 2: Write repo summaries and fetch details
|
|
143
|
+
for (const repo of repos) {
|
|
144
|
+
const repoDir = join(outDir, 'github', sanitizeFilename(repo.full_name));
|
|
145
|
+
// Repo summary page
|
|
146
|
+
writeMarkdown(repoDir, 'repo.md', buildRepoPage(repo));
|
|
147
|
+
filesWritten++;
|
|
148
|
+
// README
|
|
149
|
+
const { data: readmeData, error: readmeErr } = await ghFetch(`/repos/${repo.full_name}/readme`, options.token);
|
|
150
|
+
if (readmeErr) {
|
|
151
|
+
if (!readmeErr.includes('404'))
|
|
152
|
+
errors.push(readmeErr);
|
|
153
|
+
}
|
|
154
|
+
else if (readmeData) {
|
|
155
|
+
try {
|
|
156
|
+
const decoded = Buffer.from(readmeData.content, 'base64').toString('utf-8');
|
|
157
|
+
writeMarkdown(repoDir, 'README.md', buildReadmePage(repo.full_name, decoded, readmeData.html_url));
|
|
158
|
+
filesWritten++;
|
|
159
|
+
}
|
|
160
|
+
catch (decodeErr) {
|
|
161
|
+
errors.push(`Failed to decode README for ${repo.full_name}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Issues
|
|
165
|
+
if (repo.open_issues_count > 0) {
|
|
166
|
+
const { data: issues, error: issueErr } = await ghFetch(`/repos/${repo.full_name}/issues?state=open&per_page=${maxIssues}&sort=updated`, options.token);
|
|
167
|
+
if (issueErr) {
|
|
168
|
+
errors.push(issueErr);
|
|
169
|
+
}
|
|
170
|
+
if (issues) {
|
|
171
|
+
const issueDir = join(repoDir, 'issues');
|
|
172
|
+
for (const issue of issues) {
|
|
173
|
+
// The issues endpoint includes PRs; skip them
|
|
174
|
+
if ('pull_request' in issue)
|
|
175
|
+
continue;
|
|
176
|
+
writeMarkdown(issueDir, `issue-${issue.number}.md`, buildIssuePage(repo.full_name, issue));
|
|
177
|
+
filesWritten++;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// PRs
|
|
182
|
+
const { data: prs, error: prErr } = await ghFetch(`/repos/${repo.full_name}/pulls?state=open&per_page=${maxPRs}&sort=updated`, options.token);
|
|
183
|
+
if (prErr) {
|
|
184
|
+
errors.push(prErr);
|
|
185
|
+
}
|
|
186
|
+
if (prs) {
|
|
187
|
+
const prDir = join(repoDir, 'prs');
|
|
188
|
+
for (const pr of prs) {
|
|
189
|
+
writeMarkdown(prDir, `pr-${pr.number}.md`, buildPRPage(repo.full_name, pr));
|
|
190
|
+
filesWritten++;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Bail early if hitting call cap
|
|
194
|
+
if (callCount >= MAX_TOTAL_CALLS) {
|
|
195
|
+
errors.push(`Stopped after ${repos.indexOf(repo) + 1} repos — API call cap reached`);
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
provider: 'github',
|
|
201
|
+
filesWritten,
|
|
202
|
+
errors,
|
|
203
|
+
duration: Date.now() - start,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../../src/core/sync/github.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface GmailSyncOptions {
|
|
2
|
+
token: string;
|
|
3
|
+
vaultRoot: string;
|
|
4
|
+
maxThreads?: number;
|
|
5
|
+
query?: string;
|
|
6
|
+
labelIds?: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface PlatformSyncResult {
|
|
9
|
+
provider: string;
|
|
10
|
+
filesWritten: number;
|
|
11
|
+
errors: string[];
|
|
12
|
+
duration: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function syncGmail(options: GmailSyncOptions): Promise<PlatformSyncResult>;
|
|
15
|
+
//# sourceMappingURL=gmail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gmail.d.ts","sourceRoot":"","sources":["../../../src/core/sync/gmail.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AA8KD,wBAAsB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAkCtF"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gmail sync module — fetches recent threads and writes them as markdown
|
|
3
|
+
* into the wiki vault's raw/{date}/ directory.
|
|
4
|
+
*/
|
|
5
|
+
import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
const API = 'https://gmail.googleapis.com/gmail/v1/users/me';
|
|
8
|
+
function getHeader(headers, name) {
|
|
9
|
+
return headers.find((h) => h.name.toLowerCase() === name.toLowerCase())?.value ?? '';
|
|
10
|
+
}
|
|
11
|
+
function decode64(data) {
|
|
12
|
+
return Buffer.from(data, 'base64url').toString('utf-8');
|
|
13
|
+
}
|
|
14
|
+
function extractPlainText(part) {
|
|
15
|
+
if (part.mimeType === 'text/plain' && part.body.data)
|
|
16
|
+
return decode64(part.body.data);
|
|
17
|
+
if (part.parts) {
|
|
18
|
+
for (const sub of part.parts) {
|
|
19
|
+
const text = extractPlainText(sub);
|
|
20
|
+
if (text)
|
|
21
|
+
return text;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
function extractBody(msg) {
|
|
27
|
+
const { payload } = msg;
|
|
28
|
+
if (payload.body.data)
|
|
29
|
+
return decode64(payload.body.data);
|
|
30
|
+
if (payload.parts) {
|
|
31
|
+
const plain = payload.parts.find((p) => p.mimeType === 'text/plain');
|
|
32
|
+
if (plain?.body.data)
|
|
33
|
+
return decode64(plain.body.data);
|
|
34
|
+
for (const part of payload.parts) {
|
|
35
|
+
const text = extractPlainText(part);
|
|
36
|
+
if (text)
|
|
37
|
+
return text;
|
|
38
|
+
}
|
|
39
|
+
const html = payload.parts.find((p) => p.mimeType === 'text/html');
|
|
40
|
+
if (html?.body.data)
|
|
41
|
+
return decode64(html.body.data);
|
|
42
|
+
}
|
|
43
|
+
return '(no body content)';
|
|
44
|
+
}
|
|
45
|
+
function sanitizeFilename(raw) {
|
|
46
|
+
return (raw.replace(/[/\\:*?"<>|]/g, '-').replace(/\s+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '').slice(0, 120) ||
|
|
47
|
+
'untitled');
|
|
48
|
+
}
|
|
49
|
+
function formatDate(internalDate) {
|
|
50
|
+
const ms = parseInt(internalDate, 10);
|
|
51
|
+
return Number.isNaN(ms) ? new Date().toISOString() : new Date(ms).toISOString();
|
|
52
|
+
}
|
|
53
|
+
function todayStr() {
|
|
54
|
+
const n = new Date();
|
|
55
|
+
return `${n.getFullYear()}-${String(n.getMonth() + 1).padStart(2, '0')}-${String(n.getDate()).padStart(2, '0')}`;
|
|
56
|
+
}
|
|
57
|
+
function errorHint(status, message) {
|
|
58
|
+
if (status === 401)
|
|
59
|
+
return 'Token expired — re-authenticate with Google OAuth';
|
|
60
|
+
if (status === 429)
|
|
61
|
+
return 'Rate limited — try again later';
|
|
62
|
+
return `HTTP ${status}: ${message}`;
|
|
63
|
+
}
|
|
64
|
+
async function gmailFetch(endpoint, token) {
|
|
65
|
+
const res = await fetch(`${API}${endpoint}`, {
|
|
66
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
67
|
+
});
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
const body = await res.text().catch(() => '');
|
|
70
|
+
return { ok: false, status: res.status, message: body };
|
|
71
|
+
}
|
|
72
|
+
return { ok: true, data: (await res.json()) };
|
|
73
|
+
}
|
|
74
|
+
async function listThreads(token, maxResults, query, labelIds) {
|
|
75
|
+
const params = new URLSearchParams({ maxResults: String(maxResults) });
|
|
76
|
+
if (query)
|
|
77
|
+
params.set('q', query);
|
|
78
|
+
if (labelIds?.length) {
|
|
79
|
+
for (const lid of labelIds)
|
|
80
|
+
params.append('labelIds', lid);
|
|
81
|
+
}
|
|
82
|
+
const result = await gmailFetch(`/threads?${params.toString()}`, token);
|
|
83
|
+
if (result.ok === false) {
|
|
84
|
+
return { threads: [], errors: [`listThreads: ${errorHint(result.status, result.message)}`] };
|
|
85
|
+
}
|
|
86
|
+
return { threads: result.data.threads ?? [], errors: [] };
|
|
87
|
+
}
|
|
88
|
+
async function getThread(token, threadId) {
|
|
89
|
+
const result = await gmailFetch(`/threads/${threadId}?format=full`, token);
|
|
90
|
+
if (result.ok === false) {
|
|
91
|
+
return { thread: null, error: `getThread(${threadId}): ${errorHint(result.status, result.message)}` };
|
|
92
|
+
}
|
|
93
|
+
return { thread: result.data, error: null };
|
|
94
|
+
}
|
|
95
|
+
function threadToMarkdown(thread) {
|
|
96
|
+
const messages = thread.messages ?? [];
|
|
97
|
+
const firstMsg = messages[0];
|
|
98
|
+
const headers = firstMsg?.payload.headers ?? [];
|
|
99
|
+
const subject = getHeader(headers, 'Subject') || '(no subject)';
|
|
100
|
+
const from = getHeader(headers, 'From');
|
|
101
|
+
const date = firstMsg ? formatDate(firstMsg.internalDate) : new Date().toISOString();
|
|
102
|
+
const esc = (s) => s.replace(/'/g, "''");
|
|
103
|
+
const frontmatter = [
|
|
104
|
+
'---',
|
|
105
|
+
`addedBy: 'connector'`,
|
|
106
|
+
`source: 'gmail'`,
|
|
107
|
+
`subject: '${esc(subject)}'`,
|
|
108
|
+
`from: '${esc(from)}'`,
|
|
109
|
+
`date: '${date}'`,
|
|
110
|
+
`threadId: '${thread.id}'`,
|
|
111
|
+
`messageCount: ${messages.length}`,
|
|
112
|
+
'---',
|
|
113
|
+
].join('\n');
|
|
114
|
+
const body = messages
|
|
115
|
+
.map((msg) => {
|
|
116
|
+
const mFrom = getHeader(msg.payload.headers, 'From');
|
|
117
|
+
const mDate = formatDate(msg.internalDate);
|
|
118
|
+
return `## From: ${mFrom}\n_${mDate}_\n\n${extractBody(msg).trim()}`;
|
|
119
|
+
})
|
|
120
|
+
.join('\n\n---\n\n');
|
|
121
|
+
return {
|
|
122
|
+
filename: `gmail-${sanitizeFilename(subject)}.md`,
|
|
123
|
+
content: `${frontmatter}\n\n# ${subject}\n\n${body}\n`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
export async function syncGmail(options) {
|
|
127
|
+
const start = Date.now();
|
|
128
|
+
const errors = [];
|
|
129
|
+
let filesWritten = 0;
|
|
130
|
+
const maxThreads = options.maxThreads ?? 50;
|
|
131
|
+
const outDir = join(options.vaultRoot, 'raw', todayStr());
|
|
132
|
+
if (!existsSync(outDir))
|
|
133
|
+
mkdirSync(outDir, { recursive: true });
|
|
134
|
+
const { threads, errors: listErrors } = await listThreads(options.token, maxThreads, options.query, options.labelIds);
|
|
135
|
+
errors.push(...listErrors);
|
|
136
|
+
if (threads.length === 0 && listErrors.length > 0) {
|
|
137
|
+
return { provider: 'gmail', filesWritten: 0, errors, duration: Date.now() - start };
|
|
138
|
+
}
|
|
139
|
+
for (const stub of threads) {
|
|
140
|
+
const { thread, error } = await getThread(options.token, stub.id);
|
|
141
|
+
if (error) {
|
|
142
|
+
errors.push(error);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (!thread)
|
|
146
|
+
continue;
|
|
147
|
+
const { filename, content } = threadToMarkdown(thread);
|
|
148
|
+
try {
|
|
149
|
+
writeFileSync(join(outDir, filename), content, 'utf-8');
|
|
150
|
+
filesWritten++;
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
154
|
+
errors.push(`Write failed (${filename}): ${msg}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { provider: 'gmail', filesWritten, errors, duration: Date.now() - start };
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=gmail.js.map
|