ahok-skill 1.3.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/.prettierrc +8 -0
- package/Dockerfile +59 -0
- package/RAW_SKILL.md +219 -0
- package/README.md +277 -0
- package/SKILL.md +58 -0
- package/bin/opm.js +268 -0
- package/data/openmemory.sqlite +0 -0
- package/data/openmemory.sqlite-shm +0 -0
- package/data/openmemory.sqlite-wal +0 -0
- package/dist/ai/graph.js +293 -0
- package/dist/ai/mcp.js +397 -0
- package/dist/cli.js +78 -0
- package/dist/core/cfg.js +87 -0
- package/dist/core/db.js +636 -0
- package/dist/core/memory.js +116 -0
- package/dist/core/migrate.js +227 -0
- package/dist/core/models.js +105 -0
- package/dist/core/telemetry.js +57 -0
- package/dist/core/types.js +2 -0
- package/dist/core/vector/postgres.js +52 -0
- package/dist/core/vector/valkey.js +246 -0
- package/dist/core/vector_store.js +2 -0
- package/dist/index.js +44 -0
- package/dist/memory/decay.js +301 -0
- package/dist/memory/embed.js +675 -0
- package/dist/memory/hsg.js +959 -0
- package/dist/memory/reflect.js +131 -0
- package/dist/memory/user_summary.js +99 -0
- package/dist/migrate.js +9 -0
- package/dist/ops/compress.js +255 -0
- package/dist/ops/dynamics.js +189 -0
- package/dist/ops/extract.js +333 -0
- package/dist/ops/ingest.js +214 -0
- package/dist/server/index.js +109 -0
- package/dist/server/middleware/auth.js +137 -0
- package/dist/server/routes/auth.js +186 -0
- package/dist/server/routes/compression.js +108 -0
- package/dist/server/routes/dashboard.js +399 -0
- package/dist/server/routes/docs.js +241 -0
- package/dist/server/routes/dynamics.js +312 -0
- package/dist/server/routes/ide.js +280 -0
- package/dist/server/routes/index.js +33 -0
- package/dist/server/routes/keys.js +132 -0
- package/dist/server/routes/langgraph.js +61 -0
- package/dist/server/routes/memory.js +213 -0
- package/dist/server/routes/sources.js +140 -0
- package/dist/server/routes/system.js +63 -0
- package/dist/server/routes/temporal.js +293 -0
- package/dist/server/routes/users.js +101 -0
- package/dist/server/routes/vercel.js +57 -0
- package/dist/server/server.js +211 -0
- package/dist/server.js +3 -0
- package/dist/sources/base.js +223 -0
- package/dist/sources/github.js +171 -0
- package/dist/sources/google_drive.js +166 -0
- package/dist/sources/google_sheets.js +112 -0
- package/dist/sources/google_slides.js +139 -0
- package/dist/sources/index.js +34 -0
- package/dist/sources/notion.js +165 -0
- package/dist/sources/onedrive.js +143 -0
- package/dist/sources/web_crawler.js +166 -0
- package/dist/temporal_graph/index.js +20 -0
- package/dist/temporal_graph/query.js +240 -0
- package/dist/temporal_graph/store.js +116 -0
- package/dist/temporal_graph/timeline.js +241 -0
- package/dist/temporal_graph/types.js +2 -0
- package/dist/utils/chunking.js +60 -0
- package/dist/utils/index.js +31 -0
- package/dist/utils/keyword.js +94 -0
- package/dist/utils/text.js +120 -0
- package/nodemon.json +7 -0
- package/package.json +50 -0
- package/references/api_reference.md +66 -0
- package/references/examples.md +45 -0
- package/src/ai/graph.ts +363 -0
- package/src/ai/mcp.ts +494 -0
- package/src/cli.ts +94 -0
- package/src/core/cfg.ts +110 -0
- package/src/core/db.ts +1052 -0
- package/src/core/memory.ts +99 -0
- package/src/core/migrate.ts +302 -0
- package/src/core/models.ts +107 -0
- package/src/core/telemetry.ts +47 -0
- package/src/core/types.ts +130 -0
- package/src/core/vector/postgres.ts +61 -0
- package/src/core/vector/valkey.ts +261 -0
- package/src/core/vector_store.ts +9 -0
- package/src/index.ts +5 -0
- package/src/memory/decay.ts +427 -0
- package/src/memory/embed.ts +707 -0
- package/src/memory/hsg.ts +1245 -0
- package/src/memory/reflect.ts +158 -0
- package/src/memory/user_summary.ts +110 -0
- package/src/migrate.ts +8 -0
- package/src/ops/compress.ts +296 -0
- package/src/ops/dynamics.ts +272 -0
- package/src/ops/extract.ts +360 -0
- package/src/ops/ingest.ts +286 -0
- package/src/server/index.ts +159 -0
- package/src/server/middleware/auth.ts +156 -0
- package/src/server/routes/auth.ts +223 -0
- package/src/server/routes/compression.ts +106 -0
- package/src/server/routes/dashboard.ts +420 -0
- package/src/server/routes/docs.ts +380 -0
- package/src/server/routes/dynamics.ts +516 -0
- package/src/server/routes/ide.ts +283 -0
- package/src/server/routes/index.ts +32 -0
- package/src/server/routes/keys.ts +131 -0
- package/src/server/routes/langgraph.ts +71 -0
- package/src/server/routes/memory.ts +440 -0
- package/src/server/routes/sources.ts +111 -0
- package/src/server/routes/system.ts +68 -0
- package/src/server/routes/temporal.ts +335 -0
- package/src/server/routes/users.ts +111 -0
- package/src/server/routes/vercel.ts +55 -0
- package/src/server/server.js +215 -0
- package/src/server.ts +1 -0
- package/src/sources/base.ts +257 -0
- package/src/sources/github.ts +156 -0
- package/src/sources/google_drive.ts +144 -0
- package/src/sources/google_sheets.ts +85 -0
- package/src/sources/google_slides.ts +115 -0
- package/src/sources/index.ts +19 -0
- package/src/sources/notion.ts +148 -0
- package/src/sources/onedrive.ts +131 -0
- package/src/sources/web_crawler.ts +161 -0
- package/src/temporal_graph/index.ts +4 -0
- package/src/temporal_graph/query.ts +299 -0
- package/src/temporal_graph/store.ts +156 -0
- package/src/temporal_graph/timeline.ts +319 -0
- package/src/temporal_graph/types.ts +41 -0
- package/src/utils/chunking.ts +66 -0
- package/src/utils/index.ts +25 -0
- package/src/utils/keyword.ts +137 -0
- package/src/utils/text.ts +115 -0
- package/tests/test_api_workspace_management.ts +413 -0
- package/tests/test_bulk_delete.ts +267 -0
- package/tests/test_omnibus.ts +166 -0
- package/tests/test_workspace_management.ts +278 -0
- package/tests/verify.ts +104 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* google drive source for openmemory - production grade
|
|
3
|
+
* requires: googleapis
|
|
4
|
+
* env vars: GOOGLE_SERVICE_ACCOUNT_FILE or GOOGLE_CREDENTIALS_JSON
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { base_source, source_config_error, source_item, source_content } from './base';
|
|
8
|
+
|
|
9
|
+
export class google_drive_source extends base_source {
|
|
10
|
+
name = 'google_drive';
|
|
11
|
+
private service: any = null;
|
|
12
|
+
private auth: any = null;
|
|
13
|
+
|
|
14
|
+
async _connect(creds: Record<string, any>): Promise<boolean> {
|
|
15
|
+
let google: any;
|
|
16
|
+
try {
|
|
17
|
+
google = await import('googleapis').then(m => m.google);
|
|
18
|
+
} catch {
|
|
19
|
+
throw new source_config_error('missing deps: npm install googleapis', this.name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const scopes = ['https://www.googleapis.com/auth/drive.readonly'];
|
|
23
|
+
|
|
24
|
+
if (creds.credentials_json) {
|
|
25
|
+
this.auth = new google.auth.GoogleAuth({
|
|
26
|
+
credentials: creds.credentials_json,
|
|
27
|
+
scopes
|
|
28
|
+
});
|
|
29
|
+
} else if (creds.service_account_file) {
|
|
30
|
+
this.auth = new google.auth.GoogleAuth({
|
|
31
|
+
keyFile: creds.service_account_file,
|
|
32
|
+
scopes
|
|
33
|
+
});
|
|
34
|
+
} else if (process.env.GOOGLE_CREDENTIALS_JSON) {
|
|
35
|
+
this.auth = new google.auth.GoogleAuth({
|
|
36
|
+
credentials: JSON.parse(process.env.GOOGLE_CREDENTIALS_JSON),
|
|
37
|
+
scopes
|
|
38
|
+
});
|
|
39
|
+
} else if (process.env.GOOGLE_SERVICE_ACCOUNT_FILE) {
|
|
40
|
+
this.auth = new google.auth.GoogleAuth({
|
|
41
|
+
keyFile: process.env.GOOGLE_SERVICE_ACCOUNT_FILE,
|
|
42
|
+
scopes
|
|
43
|
+
});
|
|
44
|
+
} else {
|
|
45
|
+
throw new source_config_error(
|
|
46
|
+
'no credentials: set GOOGLE_SERVICE_ACCOUNT_FILE or GOOGLE_CREDENTIALS_JSON',
|
|
47
|
+
this.name
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.service = google.drive({ version: 'v3', auth: this.auth });
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async _list_items(filters: Record<string, any>): Promise<source_item[]> {
|
|
56
|
+
const q_parts = ['trashed=false'];
|
|
57
|
+
|
|
58
|
+
if (filters.folder_id) {
|
|
59
|
+
q_parts.push(`'${filters.folder_id}' in parents`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (filters.mime_types?.length) {
|
|
63
|
+
const mime_q = filters.mime_types.map((m: string) => `mimeType='${m}'`).join(' or ');
|
|
64
|
+
q_parts.push(`(${mime_q})`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const query = q_parts.join(' and ');
|
|
68
|
+
const results: source_item[] = [];
|
|
69
|
+
let page_token: string | undefined;
|
|
70
|
+
|
|
71
|
+
do {
|
|
72
|
+
const resp = await this.service.files.list({
|
|
73
|
+
q: query,
|
|
74
|
+
spaces: 'drive',
|
|
75
|
+
fields: 'nextPageToken, files(id, name, mimeType, modifiedTime, size)',
|
|
76
|
+
pageToken: page_token,
|
|
77
|
+
pageSize: 100
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
for (const f of resp.data.files || []) {
|
|
81
|
+
results.push({
|
|
82
|
+
id: f.id!,
|
|
83
|
+
name: f.name!,
|
|
84
|
+
type: f.mimeType!,
|
|
85
|
+
modified: f.modifiedTime,
|
|
86
|
+
size: f.size
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
page_token = resp.data.nextPageToken;
|
|
91
|
+
} while (page_token);
|
|
92
|
+
|
|
93
|
+
return results;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async _fetch_item(item_id: string): Promise<source_content> {
|
|
97
|
+
const meta = await this.service.files.get({
|
|
98
|
+
fileId: item_id,
|
|
99
|
+
fields: 'id,name,mimeType'
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const mime = meta.data.mimeType;
|
|
103
|
+
let text = '';
|
|
104
|
+
let data: string | Buffer = '';
|
|
105
|
+
|
|
106
|
+
// google docs -> export as text
|
|
107
|
+
if (mime === 'application/vnd.google-apps.document') {
|
|
108
|
+
const resp = await this.service.files.export({ fileId: item_id, mimeType: 'text/plain' });
|
|
109
|
+
text = resp.data;
|
|
110
|
+
data = text;
|
|
111
|
+
}
|
|
112
|
+
// google sheets -> export as csv
|
|
113
|
+
else if (mime === 'application/vnd.google-apps.spreadsheet') {
|
|
114
|
+
const resp = await this.service.files.export({ fileId: item_id, mimeType: 'text/csv' });
|
|
115
|
+
text = resp.data;
|
|
116
|
+
data = text;
|
|
117
|
+
}
|
|
118
|
+
// google slides -> export as plain text
|
|
119
|
+
else if (mime === 'application/vnd.google-apps.presentation') {
|
|
120
|
+
const resp = await this.service.files.export({ fileId: item_id, mimeType: 'text/plain' });
|
|
121
|
+
text = resp.data;
|
|
122
|
+
data = text;
|
|
123
|
+
}
|
|
124
|
+
// other files -> download raw
|
|
125
|
+
else {
|
|
126
|
+
const resp = await this.service.files.get({ fileId: item_id, alt: 'media' }, { responseType: 'arraybuffer' });
|
|
127
|
+
data = Buffer.from(resp.data);
|
|
128
|
+
try {
|
|
129
|
+
text = data.toString('utf-8');
|
|
130
|
+
} catch {
|
|
131
|
+
text = '';
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
id: item_id,
|
|
137
|
+
name: meta.data.name!,
|
|
138
|
+
type: mime!,
|
|
139
|
+
text,
|
|
140
|
+
data,
|
|
141
|
+
meta: { source: 'google_drive', file_id: item_id, mime_type: mime }
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* google sheets source for openmemory - production grade
|
|
3
|
+
* requires: googleapis
|
|
4
|
+
* env vars: GOOGLE_SERVICE_ACCOUNT_FILE or GOOGLE_CREDENTIALS_JSON
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { base_source, source_config_error, source_item, source_content } from './base';
|
|
8
|
+
|
|
9
|
+
export class google_sheets_source extends base_source {
|
|
10
|
+
name = 'google_sheets';
|
|
11
|
+
private service: any = null;
|
|
12
|
+
private auth: any = null;
|
|
13
|
+
|
|
14
|
+
async _connect(creds: Record<string, any>): Promise<boolean> {
|
|
15
|
+
let google: any;
|
|
16
|
+
try {
|
|
17
|
+
google = await import('googleapis').then(m => m.google);
|
|
18
|
+
} catch {
|
|
19
|
+
throw new source_config_error('missing deps: npm install googleapis', this.name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const scopes = ['https://www.googleapis.com/auth/spreadsheets.readonly'];
|
|
23
|
+
|
|
24
|
+
if (creds.credentials_json) {
|
|
25
|
+
this.auth = new google.auth.GoogleAuth({ credentials: creds.credentials_json, scopes });
|
|
26
|
+
} else if (creds.service_account_file) {
|
|
27
|
+
this.auth = new google.auth.GoogleAuth({ keyFile: creds.service_account_file, scopes });
|
|
28
|
+
} else if (process.env.GOOGLE_CREDENTIALS_JSON) {
|
|
29
|
+
this.auth = new google.auth.GoogleAuth({ credentials: JSON.parse(process.env.GOOGLE_CREDENTIALS_JSON), scopes });
|
|
30
|
+
} else if (process.env.GOOGLE_SERVICE_ACCOUNT_FILE) {
|
|
31
|
+
this.auth = new google.auth.GoogleAuth({ keyFile: process.env.GOOGLE_SERVICE_ACCOUNT_FILE, scopes });
|
|
32
|
+
} else {
|
|
33
|
+
throw new source_config_error('no credentials: set GOOGLE_SERVICE_ACCOUNT_FILE or GOOGLE_CREDENTIALS_JSON', this.name);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.service = google.sheets({ version: 'v4', auth: this.auth });
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async _list_items(filters: Record<string, any>): Promise<source_item[]> {
|
|
41
|
+
if (!filters.spreadsheet_id) {
|
|
42
|
+
throw new source_config_error('spreadsheet_id is required', this.name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const meta = await this.service.spreadsheets.get({ spreadsheetId: filters.spreadsheet_id });
|
|
46
|
+
|
|
47
|
+
return (meta.data.sheets || []).map((sheet: any, i: number) => ({
|
|
48
|
+
id: `${filters.spreadsheet_id}!${sheet.properties?.title || 'Sheet1'}`,
|
|
49
|
+
name: sheet.properties?.title || 'Sheet1',
|
|
50
|
+
type: 'sheet',
|
|
51
|
+
index: i,
|
|
52
|
+
spreadsheet_id: filters.spreadsheet_id
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async _fetch_item(item_id: string): Promise<source_content> {
|
|
57
|
+
const [spreadsheet_id, sheet_range] = item_id.includes('!')
|
|
58
|
+
? item_id.split('!', 2)
|
|
59
|
+
: [item_id, 'A:ZZ'];
|
|
60
|
+
|
|
61
|
+
const result = await this.service.spreadsheets.values.get({
|
|
62
|
+
spreadsheetId: spreadsheet_id,
|
|
63
|
+
range: sheet_range
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const values = result.data.values || [];
|
|
67
|
+
|
|
68
|
+
// convert to markdown table
|
|
69
|
+
const lines = values.map((row: any[], i: number) => {
|
|
70
|
+
const line = row.map(String).join(' | ');
|
|
71
|
+
return i === 0 ? `${line}\n${row.map(() => '---').join(' | ')}` : line;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const text = lines.join('\n');
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
id: item_id,
|
|
78
|
+
name: sheet_range,
|
|
79
|
+
type: 'spreadsheet',
|
|
80
|
+
text,
|
|
81
|
+
data: text,
|
|
82
|
+
meta: { source: 'google_sheets', spreadsheet_id, range: sheet_range, row_count: values.length }
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* google slides source for openmemory - production grade
|
|
3
|
+
* requires: googleapis
|
|
4
|
+
* env vars: GOOGLE_SERVICE_ACCOUNT_FILE or GOOGLE_CREDENTIALS_JSON
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { base_source, source_config_error, source_item, source_content } from './base';
|
|
8
|
+
|
|
9
|
+
export class google_slides_source extends base_source {
|
|
10
|
+
name = 'google_slides';
|
|
11
|
+
private service: any = null;
|
|
12
|
+
private auth: any = null;
|
|
13
|
+
|
|
14
|
+
async _connect(creds: Record<string, any>): Promise<boolean> {
|
|
15
|
+
let google: any;
|
|
16
|
+
try {
|
|
17
|
+
google = await import('googleapis').then(m => m.google);
|
|
18
|
+
} catch {
|
|
19
|
+
throw new source_config_error('missing deps: npm install googleapis', this.name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const scopes = ['https://www.googleapis.com/auth/presentations.readonly'];
|
|
23
|
+
|
|
24
|
+
if (creds.credentials_json) {
|
|
25
|
+
this.auth = new google.auth.GoogleAuth({ credentials: creds.credentials_json, scopes });
|
|
26
|
+
} else if (creds.service_account_file) {
|
|
27
|
+
this.auth = new google.auth.GoogleAuth({ keyFile: creds.service_account_file, scopes });
|
|
28
|
+
} else if (process.env.GOOGLE_CREDENTIALS_JSON) {
|
|
29
|
+
this.auth = new google.auth.GoogleAuth({ credentials: JSON.parse(process.env.GOOGLE_CREDENTIALS_JSON), scopes });
|
|
30
|
+
} else if (process.env.GOOGLE_SERVICE_ACCOUNT_FILE) {
|
|
31
|
+
this.auth = new google.auth.GoogleAuth({ keyFile: process.env.GOOGLE_SERVICE_ACCOUNT_FILE, scopes });
|
|
32
|
+
} else {
|
|
33
|
+
throw new source_config_error('no credentials: set GOOGLE_SERVICE_ACCOUNT_FILE or GOOGLE_CREDENTIALS_JSON', this.name);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.service = google.slides({ version: 'v1', auth: this.auth });
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async _list_items(filters: Record<string, any>): Promise<source_item[]> {
|
|
41
|
+
if (!filters.presentation_id) {
|
|
42
|
+
throw new source_config_error('presentation_id is required', this.name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const pres = await this.service.presentations.get({ presentationId: filters.presentation_id });
|
|
46
|
+
|
|
47
|
+
return (pres.data.slides || []).map((slide: any, i: number) => ({
|
|
48
|
+
id: `${filters.presentation_id}#${slide.objectId}`,
|
|
49
|
+
name: `Slide ${i + 1}`,
|
|
50
|
+
type: 'slide',
|
|
51
|
+
index: i,
|
|
52
|
+
presentation_id: filters.presentation_id,
|
|
53
|
+
object_id: slide.objectId
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async _fetch_item(item_id: string): Promise<source_content> {
|
|
58
|
+
const [presentation_id, slide_id] = item_id.includes('#')
|
|
59
|
+
? item_id.split('#', 2)
|
|
60
|
+
: [item_id, null];
|
|
61
|
+
|
|
62
|
+
const pres = await this.service.presentations.get({ presentationId: presentation_id });
|
|
63
|
+
|
|
64
|
+
const extract_text = (element: any): string => {
|
|
65
|
+
const texts: string[] = [];
|
|
66
|
+
|
|
67
|
+
if (element.shape?.text) {
|
|
68
|
+
for (const te of element.shape.text.textElements || []) {
|
|
69
|
+
if (te.textRun) texts.push(te.textRun.content || '');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (element.table) {
|
|
74
|
+
for (const row of element.table.tableRows || []) {
|
|
75
|
+
for (const cell of row.tableCells || []) {
|
|
76
|
+
if (cell.text) {
|
|
77
|
+
for (const te of cell.text.textElements || []) {
|
|
78
|
+
if (te.textRun) texts.push(te.textRun.content || '');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return texts.join('');
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const all_text: string[] = [];
|
|
89
|
+
|
|
90
|
+
for (let i = 0; i < (pres.data.slides || []).length; i++) {
|
|
91
|
+
const slide = pres.data.slides![i];
|
|
92
|
+
if (slide_id && slide.objectId !== slide_id) continue;
|
|
93
|
+
|
|
94
|
+
const slide_texts = [`## Slide ${i + 1}`];
|
|
95
|
+
|
|
96
|
+
for (const element of slide.pageElements || []) {
|
|
97
|
+
const txt = extract_text(element);
|
|
98
|
+
if (txt.trim()) slide_texts.push(txt.trim());
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
all_text.push(...slide_texts);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const text = all_text.join('\n\n');
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
id: item_id,
|
|
108
|
+
name: pres.data.title || 'Untitled Presentation',
|
|
109
|
+
type: 'presentation',
|
|
110
|
+
text,
|
|
111
|
+
data: text,
|
|
112
|
+
meta: { source: 'google_slides', presentation_id, slide_count: pres.data.slides?.length || 0 }
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* openmemory sources - production-grade data source integrations
|
|
3
|
+
*
|
|
4
|
+
* provides connectors for external data sources:
|
|
5
|
+
* - google drive, sheets, slides
|
|
6
|
+
* - notion
|
|
7
|
+
* - onedrive
|
|
8
|
+
* - github
|
|
9
|
+
* - web crawler
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export * from './base';
|
|
13
|
+
export * from './google_drive';
|
|
14
|
+
export * from './google_sheets';
|
|
15
|
+
export * from './google_slides';
|
|
16
|
+
export * from './notion';
|
|
17
|
+
export * from './onedrive';
|
|
18
|
+
export * from './github';
|
|
19
|
+
export * from './web_crawler';
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* notion source for openmemory - production grade
|
|
3
|
+
* requires: @notionhq/client
|
|
4
|
+
* env vars: NOTION_API_KEY
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { base_source, source_config_error, source_item, source_content } from './base';
|
|
8
|
+
|
|
9
|
+
export class notion_source extends base_source {
|
|
10
|
+
name = 'notion';
|
|
11
|
+
private client: any = null;
|
|
12
|
+
|
|
13
|
+
async _connect(creds: Record<string, any>): Promise<boolean> {
|
|
14
|
+
let Client: any;
|
|
15
|
+
try {
|
|
16
|
+
Client = await import('@notionhq/client').then(m => m.Client);
|
|
17
|
+
} catch {
|
|
18
|
+
throw new source_config_error('missing deps: npm install @notionhq/client', this.name);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const api_key = creds.api_key || process.env.NOTION_API_KEY;
|
|
22
|
+
|
|
23
|
+
if (!api_key) {
|
|
24
|
+
throw new source_config_error('no credentials: set NOTION_API_KEY', this.name);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.client = new Client({ auth: api_key });
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private extract_title(page: any): string {
|
|
32
|
+
const props = page.properties || {};
|
|
33
|
+
for (const prop of Object.values(props) as any[]) {
|
|
34
|
+
if (prop.type === 'title' && prop.title?.[0]) {
|
|
35
|
+
return prop.title[0].plain_text || '';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async _list_items(filters: Record<string, any>): Promise<source_item[]> {
|
|
42
|
+
const results: source_item[] = [];
|
|
43
|
+
|
|
44
|
+
if (filters.database_id) {
|
|
45
|
+
let has_more = true;
|
|
46
|
+
let start_cursor: string | undefined;
|
|
47
|
+
|
|
48
|
+
while (has_more) {
|
|
49
|
+
const resp = await this.client.databases.query({
|
|
50
|
+
database_id: filters.database_id,
|
|
51
|
+
start_cursor
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
for (const page of resp.results) {
|
|
55
|
+
results.push({
|
|
56
|
+
id: page.id,
|
|
57
|
+
name: this.extract_title(page) || 'Untitled',
|
|
58
|
+
type: 'page',
|
|
59
|
+
url: page.url || '',
|
|
60
|
+
last_edited: page.last_edited_time
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
has_more = resp.has_more;
|
|
65
|
+
start_cursor = resp.next_cursor;
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
const resp = await this.client.search({ filter: { property: 'object', value: 'page' } });
|
|
69
|
+
|
|
70
|
+
for (const page of resp.results) {
|
|
71
|
+
results.push({
|
|
72
|
+
id: page.id,
|
|
73
|
+
name: this.extract_title(page) || 'Untitled',
|
|
74
|
+
type: 'page',
|
|
75
|
+
url: page.url || '',
|
|
76
|
+
last_edited: page.last_edited_time
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return results;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private block_to_text(block: any): string {
|
|
85
|
+
const texts: string[] = [];
|
|
86
|
+
const type = block.type;
|
|
87
|
+
|
|
88
|
+
const text_blocks = ['paragraph', 'heading_1', 'heading_2', 'heading_3',
|
|
89
|
+
'bulleted_list_item', 'numbered_list_item', 'quote', 'callout'];
|
|
90
|
+
|
|
91
|
+
if (text_blocks.includes(type)) {
|
|
92
|
+
const rich_text = block[type]?.rich_text || [];
|
|
93
|
+
for (const rt of rich_text) {
|
|
94
|
+
texts.push(rt.plain_text || '');
|
|
95
|
+
}
|
|
96
|
+
} else if (type === 'code') {
|
|
97
|
+
const rich_text = block.code?.rich_text || [];
|
|
98
|
+
const lang = block.code?.language || '';
|
|
99
|
+
const code = rich_text.map((rt: any) => rt.plain_text || '').join('');
|
|
100
|
+
texts.push(`\`\`\`${lang}\n${code}\n\`\`\``);
|
|
101
|
+
} else if (type === 'to_do') {
|
|
102
|
+
const checked = block.to_do?.checked || false;
|
|
103
|
+
const rich_text = block.to_do?.rich_text || [];
|
|
104
|
+
const prefix = checked ? '[x] ' : '[ ] ';
|
|
105
|
+
texts.push(prefix + rich_text.map((rt: any) => rt.plain_text || '').join(''));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return texts.join('');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async _fetch_item(item_id: string): Promise<source_content> {
|
|
112
|
+
const page = await this.client.pages.retrieve({ page_id: item_id });
|
|
113
|
+
const title = this.extract_title(page);
|
|
114
|
+
|
|
115
|
+
// get all blocks
|
|
116
|
+
const blocks: any[] = [];
|
|
117
|
+
let has_more = true;
|
|
118
|
+
let start_cursor: string | undefined;
|
|
119
|
+
|
|
120
|
+
while (has_more) {
|
|
121
|
+
const resp = await this.client.blocks.children.list({
|
|
122
|
+
block_id: item_id,
|
|
123
|
+
start_cursor
|
|
124
|
+
});
|
|
125
|
+
blocks.push(...resp.results);
|
|
126
|
+
has_more = resp.has_more;
|
|
127
|
+
start_cursor = resp.next_cursor;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const text_parts = title ? [`# ${title}`] : [];
|
|
131
|
+
|
|
132
|
+
for (const block of blocks) {
|
|
133
|
+
const txt = this.block_to_text(block);
|
|
134
|
+
if (txt.trim()) text_parts.push(txt);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const text = text_parts.join('\n\n');
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
id: item_id,
|
|
141
|
+
name: title || 'Untitled',
|
|
142
|
+
type: 'notion_page',
|
|
143
|
+
text,
|
|
144
|
+
data: text,
|
|
145
|
+
meta: { source: 'notion', page_id: item_id, url: page.url || '', block_count: blocks.length }
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* onedrive source for openmemory - production grade
|
|
3
|
+
* requires: @azure/msal-node
|
|
4
|
+
* env vars: AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { base_source, source_config_error, source_auth_error, source_item, source_content } from './base';
|
|
8
|
+
|
|
9
|
+
export class onedrive_source extends base_source {
|
|
10
|
+
name = 'onedrive';
|
|
11
|
+
private access_token: string | null = null;
|
|
12
|
+
private graph_url = 'https://graph.microsoft.com/v1.0';
|
|
13
|
+
|
|
14
|
+
async _connect(creds: Record<string, any>): Promise<boolean> {
|
|
15
|
+
if (creds.access_token) {
|
|
16
|
+
this.access_token = creds.access_token;
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let msal: any;
|
|
21
|
+
try {
|
|
22
|
+
msal = await import('@azure/msal-node');
|
|
23
|
+
} catch {
|
|
24
|
+
throw new source_config_error('missing deps: npm install @azure/msal-node', this.name);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const client_id = creds.client_id || process.env.AZURE_CLIENT_ID;
|
|
28
|
+
const client_secret = creds.client_secret || process.env.AZURE_CLIENT_SECRET;
|
|
29
|
+
const tenant_id = creds.tenant_id || process.env.AZURE_TENANT_ID;
|
|
30
|
+
|
|
31
|
+
if (!client_id || !client_secret || !tenant_id) {
|
|
32
|
+
throw new source_config_error(
|
|
33
|
+
'no credentials: set AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID',
|
|
34
|
+
this.name
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const app = new msal.ConfidentialClientApplication({
|
|
39
|
+
auth: {
|
|
40
|
+
clientId: client_id,
|
|
41
|
+
clientSecret: client_secret,
|
|
42
|
+
authority: `https://login.microsoftonline.com/${tenant_id}`
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const result = await app.acquireTokenByClientCredential({
|
|
47
|
+
scopes: ['https://graph.microsoft.com/.default']
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (result?.accessToken) {
|
|
51
|
+
this.access_token = result.accessToken;
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
throw new source_auth_error('auth failed: no access token returned', this.name);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async _list_items(filters: Record<string, any>): Promise<source_item[]> {
|
|
59
|
+
const folder_path = filters.folder_path || '/';
|
|
60
|
+
const user_principal = filters.user_principal;
|
|
61
|
+
|
|
62
|
+
const base = user_principal
|
|
63
|
+
? `${this.graph_url}/users/${user_principal}/drive`
|
|
64
|
+
: `${this.graph_url}/me/drive`;
|
|
65
|
+
|
|
66
|
+
const url = folder_path === '/'
|
|
67
|
+
? `${base}/root/children`
|
|
68
|
+
: `${base}/root:/${folder_path.replace(/^\/|\/$/g, '')}:/children`;
|
|
69
|
+
|
|
70
|
+
const results: source_item[] = [];
|
|
71
|
+
let next_url: string | null = url;
|
|
72
|
+
|
|
73
|
+
while (next_url) {
|
|
74
|
+
const resp: Response = await fetch(next_url, {
|
|
75
|
+
headers: { Authorization: `Bearer ${this.access_token}` }
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (!resp.ok) throw new Error(`http ${resp.status}: ${resp.statusText}`);
|
|
79
|
+
|
|
80
|
+
const data: any = await resp.json();
|
|
81
|
+
|
|
82
|
+
for (const item of data.value || []) {
|
|
83
|
+
results.push({
|
|
84
|
+
id: item.id,
|
|
85
|
+
name: item.name,
|
|
86
|
+
type: 'folder' in item ? 'folder' : item.file?.mimeType || 'file',
|
|
87
|
+
size: item.size || 0,
|
|
88
|
+
modified: item.lastModifiedDateTime,
|
|
89
|
+
path: item.parentReference?.path || ''
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
next_url = data['@odata.nextLink'] || null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return results;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async _fetch_item(item_id: string): Promise<source_content> {
|
|
100
|
+
const base = `${this.graph_url}/me/drive`;
|
|
101
|
+
|
|
102
|
+
const meta_resp = await fetch(`${base}/items/${item_id}`, {
|
|
103
|
+
headers: { Authorization: `Bearer ${this.access_token}` }
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (!meta_resp.ok) throw new Error(`http ${meta_resp.status}`);
|
|
107
|
+
const meta = await meta_resp.json();
|
|
108
|
+
|
|
109
|
+
const content_resp = await fetch(`${base}/items/${item_id}/content`, {
|
|
110
|
+
headers: { Authorization: `Bearer ${this.access_token}` },
|
|
111
|
+
redirect: 'follow'
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (!content_resp.ok) throw new Error(`http ${content_resp.status}`);
|
|
115
|
+
const data = Buffer.from(await content_resp.arrayBuffer());
|
|
116
|
+
|
|
117
|
+
let text = '';
|
|
118
|
+
try {
|
|
119
|
+
text = data.toString('utf-8');
|
|
120
|
+
} catch { }
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
id: item_id,
|
|
124
|
+
name: meta.name || 'unknown',
|
|
125
|
+
type: meta.file?.mimeType || 'unknown',
|
|
126
|
+
text,
|
|
127
|
+
data,
|
|
128
|
+
meta: { source: 'onedrive', item_id, size: meta.size || 0, mime_type: meta.file?.mimeType || '' }
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|