brains-mcp 1.61.2
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 +95 -0
- package/dist/access/projectShares.d.ts +48 -0
- package/dist/access/projectShares.d.ts.map +1 -0
- package/dist/access/projectShares.js +203 -0
- package/dist/access/projectShares.js.map +1 -0
- package/dist/access/sharedInstructionsNote.d.ts +37 -0
- package/dist/access/sharedInstructionsNote.d.ts.map +1 -0
- package/dist/access/sharedInstructionsNote.js +76 -0
- package/dist/access/sharedInstructionsNote.js.map +1 -0
- package/dist/admin/seedUsers.d.ts +23 -0
- package/dist/admin/seedUsers.d.ts.map +1 -0
- package/dist/admin/seedUsers.js +168 -0
- package/dist/admin/seedUsers.js.map +1 -0
- package/dist/api/openapi.d.ts +2 -0
- package/dist/api/openapi.d.ts.map +1 -0
- package/dist/api/openapi.js +942 -0
- package/dist/api/openapi.js.map +1 -0
- package/dist/auth/clientStore.d.ts +43 -0
- package/dist/auth/clientStore.d.ts.map +1 -0
- package/dist/auth/clientStore.js +168 -0
- package/dist/auth/clientStore.js.map +1 -0
- package/dist/auth/deleteAccount.d.ts +34 -0
- package/dist/auth/deleteAccount.d.ts.map +1 -0
- package/dist/auth/deleteAccount.js +203 -0
- package/dist/auth/deleteAccount.js.map +1 -0
- package/dist/auth/oauth.d.ts +118 -0
- package/dist/auth/oauth.d.ts.map +1 -0
- package/dist/auth/oauth.js +491 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/auth/patStore.d.ts +87 -0
- package/dist/auth/patStore.d.ts.map +1 -0
- package/dist/auth/patStore.js +308 -0
- package/dist/auth/patStore.js.map +1 -0
- package/dist/auth/supabase.d.ts +15 -0
- package/dist/auth/supabase.d.ts.map +1 -0
- package/dist/auth/supabase.js +66 -0
- package/dist/auth/supabase.js.map +1 -0
- package/dist/auth/tokenStore.d.ts +73 -0
- package/dist/auth/tokenStore.d.ts.map +1 -0
- package/dist/auth/tokenStore.js +261 -0
- package/dist/auth/tokenStore.js.map +1 -0
- package/dist/backup/drive.d.ts +24 -0
- package/dist/backup/drive.d.ts.map +1 -0
- package/dist/backup/drive.js +70 -0
- package/dist/backup/drive.js.map +1 -0
- package/dist/billing/tierStore.d.ts +59 -0
- package/dist/billing/tierStore.d.ts.map +1 -0
- package/dist/billing/tierStore.js +152 -0
- package/dist/billing/tierStore.js.map +1 -0
- package/dist/digest/activityDigest.d.ts +32 -0
- package/dist/digest/activityDigest.d.ts.map +1 -0
- package/dist/digest/activityDigest.js +299 -0
- package/dist/digest/activityDigest.js.map +1 -0
- package/dist/digest/digestCron.d.ts +40 -0
- package/dist/digest/digestCron.d.ts.map +1 -0
- package/dist/digest/digestCron.js +146 -0
- package/dist/digest/digestCron.js.map +1 -0
- package/dist/digest/digestPreferences.d.ts +25 -0
- package/dist/digest/digestPreferences.d.ts.map +1 -0
- package/dist/digest/digestPreferences.js +79 -0
- package/dist/digest/digestPreferences.js.map +1 -0
- package/dist/digest/emailSender.d.ts +20 -0
- package/dist/digest/emailSender.d.ts.map +1 -0
- package/dist/digest/emailSender.js +35 -0
- package/dist/digest/emailSender.js.map +1 -0
- package/dist/drive/client.d.ts +43 -0
- package/dist/drive/client.d.ts.map +1 -0
- package/dist/drive/client.js +282 -0
- package/dist/drive/client.js.map +1 -0
- package/dist/drive/errors.d.ts +27 -0
- package/dist/drive/errors.d.ts.map +1 -0
- package/dist/drive/errors.js +51 -0
- package/dist/drive/errors.js.map +1 -0
- package/dist/drive/fileCache.d.ts +22 -0
- package/dist/drive/fileCache.d.ts.map +1 -0
- package/dist/drive/fileCache.js +57 -0
- package/dist/drive/fileCache.js.map +1 -0
- package/dist/governance/accessControl.d.ts +87 -0
- package/dist/governance/accessControl.d.ts.map +1 -0
- package/dist/governance/accessControl.js +179 -0
- package/dist/governance/accessControl.js.map +1 -0
- package/dist/html/components.d.ts +8 -0
- package/dist/html/components.d.ts.map +1 -0
- package/dist/html/components.js +19 -0
- package/dist/html/components.js.map +1 -0
- package/dist/html/layout.d.ts +7 -0
- package/dist/html/layout.d.ts.map +1 -0
- package/dist/html/layout.js +24 -0
- package/dist/html/layout.js.map +1 -0
- package/dist/html/metricsUi.d.ts +8 -0
- package/dist/html/metricsUi.d.ts.map +1 -0
- package/dist/html/metricsUi.js +271 -0
- package/dist/html/metricsUi.js.map +1 -0
- package/dist/httpServer.d.ts +19 -0
- package/dist/httpServer.d.ts.map +1 -0
- package/dist/httpServer.js +3913 -0
- package/dist/httpServer.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs/asyncJobRegistry.d.ts +30 -0
- package/dist/jobs/asyncJobRegistry.d.ts.map +1 -0
- package/dist/jobs/asyncJobRegistry.js +70 -0
- package/dist/jobs/asyncJobRegistry.js.map +1 -0
- package/dist/observability/metricsQuery.d.ts +41 -0
- package/dist/observability/metricsQuery.d.ts.map +1 -0
- package/dist/observability/metricsQuery.js +106 -0
- package/dist/observability/metricsQuery.js.map +1 -0
- package/dist/observability/relevanceSignal.d.ts +26 -0
- package/dist/observability/relevanceSignal.d.ts.map +1 -0
- package/dist/observability/relevanceSignal.js +95 -0
- package/dist/observability/relevanceSignal.js.map +1 -0
- package/dist/observability/requestEvents.d.ts +31 -0
- package/dist/observability/requestEvents.d.ts.map +1 -0
- package/dist/observability/requestEvents.js +54 -0
- package/dist/observability/requestEvents.js.map +1 -0
- package/dist/observability/slowQueryAlert.d.ts +25 -0
- package/dist/observability/slowQueryAlert.d.ts.map +1 -0
- package/dist/observability/slowQueryAlert.js +67 -0
- package/dist/observability/slowQueryAlert.js.map +1 -0
- package/dist/observability/zeroResultTriage.d.ts +22 -0
- package/dist/observability/zeroResultTriage.d.ts.map +1 -0
- package/dist/observability/zeroResultTriage.js +116 -0
- package/dist/observability/zeroResultTriage.js.map +1 -0
- package/dist/search/embeddings.d.ts +31 -0
- package/dist/search/embeddings.d.ts.map +1 -0
- package/dist/search/embeddings.js +91 -0
- package/dist/search/embeddings.js.map +1 -0
- package/dist/search/fileWatcher.d.ts +28 -0
- package/dist/search/fileWatcher.d.ts.map +1 -0
- package/dist/search/fileWatcher.js +103 -0
- package/dist/search/fileWatcher.js.map +1 -0
- package/dist/search/hotQueryCache.d.ts +31 -0
- package/dist/search/hotQueryCache.d.ts.map +1 -0
- package/dist/search/hotQueryCache.js +103 -0
- package/dist/search/hotQueryCache.js.map +1 -0
- package/dist/search/postgresIndex.d.ts +439 -0
- package/dist/search/postgresIndex.d.ts.map +1 -0
- package/dist/search/postgresIndex.js +1610 -0
- package/dist/search/postgresIndex.js.map +1 -0
- package/dist/search/sqliteIndex.d.ts +66 -0
- package/dist/search/sqliteIndex.d.ts.map +1 -0
- package/dist/search/sqliteIndex.js +342 -0
- package/dist/search/sqliteIndex.js.map +1 -0
- package/dist/server.d.ts +43 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +532 -0
- package/dist/server.js.map +1 -0
- package/dist/session/bundle.d.ts +53 -0
- package/dist/session/bundle.d.ts.map +1 -0
- package/dist/session/bundle.js +97 -0
- package/dist/session/bundle.js.map +1 -0
- package/dist/storage/client.d.ts +157 -0
- package/dist/storage/client.d.ts.map +1 -0
- package/dist/storage/client.js +825 -0
- package/dist/storage/client.js.map +1 -0
- package/dist/tools/addWebhookPattern.d.ts +15 -0
- package/dist/tools/addWebhookPattern.d.ts.map +1 -0
- package/dist/tools/addWebhookPattern.js +15 -0
- package/dist/tools/addWebhookPattern.js.map +1 -0
- package/dist/tools/appendLog.d.ts +18 -0
- package/dist/tools/appendLog.d.ts.map +1 -0
- package/dist/tools/appendLog.js +18 -0
- package/dist/tools/appendLog.js.map +1 -0
- package/dist/tools/auditIndex.d.ts +15 -0
- package/dist/tools/auditIndex.d.ts.map +1 -0
- package/dist/tools/auditIndex.js +165 -0
- package/dist/tools/auditIndex.js.map +1 -0
- package/dist/tools/batchDeletePages.d.ts +18 -0
- package/dist/tools/batchDeletePages.d.ts.map +1 -0
- package/dist/tools/batchDeletePages.js +143 -0
- package/dist/tools/batchDeletePages.js.map +1 -0
- package/dist/tools/coerce.d.ts +3 -0
- package/dist/tools/coerce.d.ts.map +1 -0
- package/dist/tools/coerce.js +12 -0
- package/dist/tools/coerce.js.map +1 -0
- package/dist/tools/createPage.d.ts +24 -0
- package/dist/tools/createPage.d.ts.map +1 -0
- package/dist/tools/createPage.js +99 -0
- package/dist/tools/createPage.js.map +1 -0
- package/dist/tools/createPages.d.ts +51 -0
- package/dist/tools/createPages.d.ts.map +1 -0
- package/dist/tools/createPages.js +91 -0
- package/dist/tools/createPages.js.map +1 -0
- package/dist/tools/deletePage.d.ts +19 -0
- package/dist/tools/deletePage.d.ts.map +1 -0
- package/dist/tools/deletePage.js +127 -0
- package/dist/tools/deletePage.js.map +1 -0
- package/dist/tools/exportWiki.d.ts +48 -0
- package/dist/tools/exportWiki.d.ts.map +1 -0
- package/dist/tools/exportWiki.js +233 -0
- package/dist/tools/exportWiki.js.map +1 -0
- package/dist/tools/extractLinks.d.ts +14 -0
- package/dist/tools/extractLinks.d.ts.map +1 -0
- package/dist/tools/extractLinks.js +25 -0
- package/dist/tools/extractLinks.js.map +1 -0
- package/dist/tools/generateClaudeInstructions.d.ts +27 -0
- package/dist/tools/generateClaudeInstructions.d.ts.map +1 -0
- package/dist/tools/generateClaudeInstructions.js +263 -0
- package/dist/tools/generateClaudeInstructions.js.map +1 -0
- package/dist/tools/generateGptInstructions.d.ts +27 -0
- package/dist/tools/generateGptInstructions.d.ts.map +1 -0
- package/dist/tools/generateGptInstructions.js +208 -0
- package/dist/tools/generateGptInstructions.js.map +1 -0
- package/dist/tools/getBacklinks.d.ts +12 -0
- package/dist/tools/getBacklinks.d.ts.map +1 -0
- package/dist/tools/getBacklinks.js +13 -0
- package/dist/tools/getBacklinks.js.map +1 -0
- package/dist/tools/getRelated.d.ts +12 -0
- package/dist/tools/getRelated.d.ts.map +1 -0
- package/dist/tools/getRelated.js +13 -0
- package/dist/tools/getRelated.js.map +1 -0
- package/dist/tools/healthCheck.d.ts +5 -0
- package/dist/tools/healthCheck.d.ts.map +1 -0
- package/dist/tools/healthCheck.js +167 -0
- package/dist/tools/healthCheck.js.map +1 -0
- package/dist/tools/importBrainSource.d.ts +36 -0
- package/dist/tools/importBrainSource.d.ts.map +1 -0
- package/dist/tools/importBrainSource.js +314 -0
- package/dist/tools/importBrainSource.js.map +1 -0
- package/dist/tools/importWikiZip.d.ts +60 -0
- package/dist/tools/importWikiZip.d.ts.map +1 -0
- package/dist/tools/importWikiZip.js +379 -0
- package/dist/tools/importWikiZip.js.map +1 -0
- package/dist/tools/initProject.d.ts +34 -0
- package/dist/tools/initProject.d.ts.map +1 -0
- package/dist/tools/initProject.js +443 -0
- package/dist/tools/initProject.js.map +1 -0
- package/dist/tools/instructionContext.d.ts +60 -0
- package/dist/tools/instructionContext.d.ts.map +1 -0
- package/dist/tools/instructionContext.js +210 -0
- package/dist/tools/instructionContext.js.map +1 -0
- package/dist/tools/listArchivedPages.d.ts +17 -0
- package/dist/tools/listArchivedPages.d.ts.map +1 -0
- package/dist/tools/listArchivedPages.js +51 -0
- package/dist/tools/listArchivedPages.js.map +1 -0
- package/dist/tools/listPages.d.ts +28 -0
- package/dist/tools/listPages.d.ts.map +1 -0
- package/dist/tools/listPages.js +166 -0
- package/dist/tools/listPages.js.map +1 -0
- package/dist/tools/listWebhooks.d.ts +7 -0
- package/dist/tools/listWebhooks.d.ts.map +1 -0
- package/dist/tools/listWebhooks.js +10 -0
- package/dist/tools/listWebhooks.js.map +1 -0
- package/dist/tools/movePage.d.ts +18 -0
- package/dist/tools/movePage.d.ts.map +1 -0
- package/dist/tools/movePage.js +182 -0
- package/dist/tools/movePage.js.map +1 -0
- package/dist/tools/pageHistory.d.ts +36 -0
- package/dist/tools/pageHistory.d.ts.map +1 -0
- package/dist/tools/pageHistory.js +60 -0
- package/dist/tools/pageHistory.js.map +1 -0
- package/dist/tools/patchPage.d.ts +141 -0
- package/dist/tools/patchPage.d.ts.map +1 -0
- package/dist/tools/patchPage.js +287 -0
- package/dist/tools/patchPage.js.map +1 -0
- package/dist/tools/pullSessionBundle.d.ts +34 -0
- package/dist/tools/pullSessionBundle.d.ts.map +1 -0
- package/dist/tools/pullSessionBundle.js +432 -0
- package/dist/tools/pullSessionBundle.js.map +1 -0
- package/dist/tools/purgeWiki.d.ts +23 -0
- package/dist/tools/purgeWiki.d.ts.map +1 -0
- package/dist/tools/purgeWiki.js +113 -0
- package/dist/tools/purgeWiki.js.map +1 -0
- package/dist/tools/pushSessionBundle.d.ts +139 -0
- package/dist/tools/pushSessionBundle.d.ts.map +1 -0
- package/dist/tools/pushSessionBundle.js +282 -0
- package/dist/tools/pushSessionBundle.js.map +1 -0
- package/dist/tools/readGovernanceAudit.d.ts +17 -0
- package/dist/tools/readGovernanceAudit.d.ts.map +1 -0
- package/dist/tools/readGovernanceAudit.js +21 -0
- package/dist/tools/readGovernanceAudit.js.map +1 -0
- package/dist/tools/readPage.d.ts +13 -0
- package/dist/tools/readPage.d.ts.map +1 -0
- package/dist/tools/readPage.js +46 -0
- package/dist/tools/readPage.js.map +1 -0
- package/dist/tools/removeWebhook.d.ts +14 -0
- package/dist/tools/removeWebhook.d.ts.map +1 -0
- package/dist/tools/removeWebhook.js +12 -0
- package/dist/tools/removeWebhook.js.map +1 -0
- package/dist/tools/resolveProject.d.ts +58 -0
- package/dist/tools/resolveProject.d.ts.map +1 -0
- package/dist/tools/resolveProject.js +175 -0
- package/dist/tools/resolveProject.js.map +1 -0
- package/dist/tools/restorePage.d.ts +47 -0
- package/dist/tools/restorePage.d.ts.map +1 -0
- package/dist/tools/restorePage.js +111 -0
- package/dist/tools/restorePage.js.map +1 -0
- package/dist/tools/searchPages.d.ts +28 -0
- package/dist/tools/searchPages.d.ts.map +1 -0
- package/dist/tools/searchPages.js +372 -0
- package/dist/tools/searchPages.js.map +1 -0
- package/dist/tools/setWebhook.d.ts +30 -0
- package/dist/tools/setWebhook.d.ts.map +1 -0
- package/dist/tools/setWebhook.js +30 -0
- package/dist/tools/setWebhook.js.map +1 -0
- package/dist/tools/shareProject.d.ts +25 -0
- package/dist/tools/shareProject.d.ts.map +1 -0
- package/dist/tools/shareProject.js +56 -0
- package/dist/tools/shareProject.js.map +1 -0
- package/dist/tools/suggestLinks.d.ts +29 -0
- package/dist/tools/suggestLinks.d.ts.map +1 -0
- package/dist/tools/suggestLinks.js +214 -0
- package/dist/tools/suggestLinks.js.map +1 -0
- package/dist/tools/updatePage.d.ts +27 -0
- package/dist/tools/updatePage.d.ts.map +1 -0
- package/dist/tools/updatePage.js +118 -0
- package/dist/tools/updatePage.js.map +1 -0
- package/dist/webhooks/dispatch.d.ts +49 -0
- package/dist/webhooks/dispatch.d.ts.map +1 -0
- package/dist/webhooks/dispatch.js +159 -0
- package/dist/webhooks/dispatch.js.map +1 -0
- package/dist/webhooks/webhookRules.d.ts +34 -0
- package/dist/webhooks/webhookRules.d.ts.map +1 -0
- package/dist/webhooks/webhookRules.js +129 -0
- package/dist/webhooks/webhookRules.js.map +1 -0
- package/dist/wiki/frontmatter.d.ts +45 -0
- package/dist/wiki/frontmatter.d.ts.map +1 -0
- package/dist/wiki/frontmatter.js +68 -0
- package/dist/wiki/frontmatter.js.map +1 -0
- package/dist/wiki/index.d.ts +22 -0
- package/dist/wiki/index.d.ts.map +1 -0
- package/dist/wiki/index.js +203 -0
- package/dist/wiki/index.js.map +1 -0
- package/dist/wiki/ingestPolicy.d.ts +18 -0
- package/dist/wiki/ingestPolicy.d.ts.map +1 -0
- package/dist/wiki/ingestPolicy.js +64 -0
- package/dist/wiki/ingestPolicy.js.map +1 -0
- package/dist/wiki/layoutLint.d.ts +51 -0
- package/dist/wiki/layoutLint.d.ts.map +1 -0
- package/dist/wiki/layoutLint.js +146 -0
- package/dist/wiki/layoutLint.js.map +1 -0
- package/dist/wiki/links.d.ts +6 -0
- package/dist/wiki/links.d.ts.map +1 -0
- package/dist/wiki/links.js +45 -0
- package/dist/wiki/links.js.map +1 -0
- package/dist/wiki/log.d.ts +58 -0
- package/dist/wiki/log.d.ts.map +1 -0
- package/dist/wiki/log.js +126 -0
- package/dist/wiki/log.js.map +1 -0
- package/dist/wiki/path.d.ts +8 -0
- package/dist/wiki/path.d.ts.map +1 -0
- package/dist/wiki/path.js +25 -0
- package/dist/wiki/path.js.map +1 -0
- package/dist/wiki/storageLimits.d.ts +163 -0
- package/dist/wiki/storageLimits.d.ts.map +1 -0
- package/dist/wiki/storageLimits.js +281 -0
- package/dist/wiki/storageLimits.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth token persistence layer — local session sync.
|
|
3
|
+
*
|
|
4
|
+
* Provides a `TokenStore` interface with two implementations:
|
|
5
|
+
*
|
|
6
|
+
* - `LocalSQLiteTokenStore` — SQLite-backed, stored next to the search
|
|
7
|
+
* index under `<storageLocation>/.cache/oauth-tokens.db`. Zero extra
|
|
8
|
+
* dependencies (uses the already-bundled `better-sqlite3`). Suitable
|
|
9
|
+
* for single-instance local and Railway deployments.
|
|
10
|
+
*
|
|
11
|
+
* - `PostgresTokenStore` — Supabase PostgREST-backed, stored in an
|
|
12
|
+
* `oauth_tokens` table. Enables multi-instance / stateless deployments
|
|
13
|
+
* where the process may restart without losing active sessions. See
|
|
14
|
+
* `scripts/oauth-tokens.sql` for the required migration.
|
|
15
|
+
*
|
|
16
|
+
* `createTokenStore()` auto-selects the backend based on env vars:
|
|
17
|
+
* - `SUPABASE_URL` + `SUPABASE_SERVICE_ROLE_KEY` → PostgresTokenStore
|
|
18
|
+
* - otherwise → LocalSQLiteTokenStore
|
|
19
|
+
*/
|
|
20
|
+
import fs from "fs";
|
|
21
|
+
import path from "path";
|
|
22
|
+
import Database from "better-sqlite3";
|
|
23
|
+
// ─── LocalSQLiteTokenStore ────────────────────────────────────────────────────
|
|
24
|
+
export class LocalSQLiteTokenStore {
|
|
25
|
+
db;
|
|
26
|
+
constructor(storageLocation) {
|
|
27
|
+
const cacheDir = path.join(storageLocation, ".cache");
|
|
28
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
29
|
+
const dbPath = path.join(cacheDir, "oauth-tokens.db");
|
|
30
|
+
this.db = new Database(dbPath);
|
|
31
|
+
this.db.pragma("journal_mode = WAL");
|
|
32
|
+
this.db.pragma("synchronous = NORMAL");
|
|
33
|
+
this.initSchema();
|
|
34
|
+
}
|
|
35
|
+
initSchema() {
|
|
36
|
+
this.db.exec(`
|
|
37
|
+
CREATE TABLE IF NOT EXISTS oauth_tokens (
|
|
38
|
+
type TEXT NOT NULL,
|
|
39
|
+
token TEXT NOT NULL,
|
|
40
|
+
client_id TEXT NOT NULL,
|
|
41
|
+
subject TEXT NOT NULL,
|
|
42
|
+
scope TEXT NOT NULL,
|
|
43
|
+
expires_at INTEGER NOT NULL,
|
|
44
|
+
redirect_uri TEXT,
|
|
45
|
+
code_challenge TEXT,
|
|
46
|
+
code_challenge_method TEXT,
|
|
47
|
+
PRIMARY KEY (type, token)
|
|
48
|
+
);
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_oauth_tokens_expires_at
|
|
50
|
+
ON oauth_tokens (expires_at);
|
|
51
|
+
`);
|
|
52
|
+
}
|
|
53
|
+
async save(record) {
|
|
54
|
+
this.db
|
|
55
|
+
.prepare(`INSERT INTO oauth_tokens
|
|
56
|
+
(type, token, client_id, subject, scope, expires_at,
|
|
57
|
+
redirect_uri, code_challenge, code_challenge_method)
|
|
58
|
+
VALUES
|
|
59
|
+
(@type, @token, @client_id, @subject, @scope, @expires_at,
|
|
60
|
+
@redirect_uri, @code_challenge, @code_challenge_method)
|
|
61
|
+
ON CONFLICT(type, token) DO UPDATE SET
|
|
62
|
+
client_id = excluded.client_id,
|
|
63
|
+
subject = excluded.subject,
|
|
64
|
+
scope = excluded.scope,
|
|
65
|
+
expires_at = excluded.expires_at,
|
|
66
|
+
redirect_uri = excluded.redirect_uri,
|
|
67
|
+
code_challenge = excluded.code_challenge,
|
|
68
|
+
code_challenge_method = excluded.code_challenge_method`)
|
|
69
|
+
.run({
|
|
70
|
+
type: record.type,
|
|
71
|
+
token: record.token,
|
|
72
|
+
client_id: record.clientId,
|
|
73
|
+
subject: record.subject,
|
|
74
|
+
scope: JSON.stringify(record.scope),
|
|
75
|
+
expires_at: record.expiresAt,
|
|
76
|
+
redirect_uri: record.redirectUri ?? null,
|
|
77
|
+
code_challenge: record.codeChallenge ?? null,
|
|
78
|
+
code_challenge_method: record.codeChallengeMethod ?? null,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async find(type, token) {
|
|
82
|
+
const row = this.db
|
|
83
|
+
.prepare("SELECT * FROM oauth_tokens WHERE type = ? AND token = ? AND expires_at > ?")
|
|
84
|
+
.get(type, token, Date.now());
|
|
85
|
+
if (!row)
|
|
86
|
+
return null;
|
|
87
|
+
return rowToStoredToken(row);
|
|
88
|
+
}
|
|
89
|
+
async delete(type, token) {
|
|
90
|
+
this.db
|
|
91
|
+
.prepare("DELETE FROM oauth_tokens WHERE type = ? AND token = ?")
|
|
92
|
+
.run(type, token);
|
|
93
|
+
}
|
|
94
|
+
async deleteAllForSubject(subject, clientId, type) {
|
|
95
|
+
this.db
|
|
96
|
+
.prepare("DELETE FROM oauth_tokens WHERE subject = ? AND client_id = ? AND type = ?")
|
|
97
|
+
.run(subject, clientId, type);
|
|
98
|
+
}
|
|
99
|
+
async deleteAllTokensForSubject(subject) {
|
|
100
|
+
this.db
|
|
101
|
+
.prepare("DELETE FROM oauth_tokens WHERE subject = ?")
|
|
102
|
+
.run(subject);
|
|
103
|
+
}
|
|
104
|
+
async pruneExpired() {
|
|
105
|
+
this.db
|
|
106
|
+
.prepare("DELETE FROM oauth_tokens WHERE expires_at <= ?")
|
|
107
|
+
.run(Date.now());
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export class PostgresTokenStore {
|
|
111
|
+
supabaseUrl;
|
|
112
|
+
serviceRoleKey;
|
|
113
|
+
constructor(supabaseUrl, serviceRoleKey) {
|
|
114
|
+
this.supabaseUrl = supabaseUrl.trim().replace(/\/$/, "");
|
|
115
|
+
this.serviceRoleKey = serviceRoleKey.trim();
|
|
116
|
+
}
|
|
117
|
+
authHeaders(extra) {
|
|
118
|
+
return {
|
|
119
|
+
apikey: this.serviceRoleKey,
|
|
120
|
+
authorization: `Bearer ${this.serviceRoleKey}`,
|
|
121
|
+
"content-type": "application/json",
|
|
122
|
+
prefer: "return=minimal",
|
|
123
|
+
...extra,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
get baseUrl() {
|
|
127
|
+
return `${this.supabaseUrl}/rest/v1/oauth_tokens`;
|
|
128
|
+
}
|
|
129
|
+
async save(record) {
|
|
130
|
+
const body = JSON.stringify({
|
|
131
|
+
type: record.type,
|
|
132
|
+
token: record.token,
|
|
133
|
+
client_id: record.clientId,
|
|
134
|
+
subject: record.subject,
|
|
135
|
+
scope: JSON.stringify(record.scope),
|
|
136
|
+
expires_at: record.expiresAt,
|
|
137
|
+
redirect_uri: record.redirectUri ?? null,
|
|
138
|
+
code_challenge: record.codeChallenge ?? null,
|
|
139
|
+
code_challenge_method: record.codeChallengeMethod ?? null,
|
|
140
|
+
});
|
|
141
|
+
const response = await fetch(this.baseUrl, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: this.authHeaders({ prefer: "resolution=merge-duplicates,return=minimal" }),
|
|
144
|
+
body,
|
|
145
|
+
});
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
const text = await response.text().catch(() => "");
|
|
148
|
+
throw new Error(`PostgresTokenStore.save failed (${response.status}): ${text}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async find(type, token) {
|
|
152
|
+
const url = `${this.baseUrl}?type=eq.${encodeURIComponent(type)}&token=eq.${encodeURIComponent(token)}&expires_at=gt.${Date.now()}&limit=1`;
|
|
153
|
+
const response = await fetch(url, {
|
|
154
|
+
method: "GET",
|
|
155
|
+
headers: this.authHeaders({ prefer: "return=representation" }),
|
|
156
|
+
}).catch(() => null);
|
|
157
|
+
if (!response || !response.ok)
|
|
158
|
+
return null;
|
|
159
|
+
const rows = (await response.json().catch(() => []));
|
|
160
|
+
const row = rows[0];
|
|
161
|
+
if (!row)
|
|
162
|
+
return null;
|
|
163
|
+
return postgresRowToStoredToken(row);
|
|
164
|
+
}
|
|
165
|
+
async delete(type, token) {
|
|
166
|
+
const url = `${this.baseUrl}?type=eq.${encodeURIComponent(type)}&token=eq.${encodeURIComponent(token)}`;
|
|
167
|
+
const response = await fetch(url, {
|
|
168
|
+
method: "DELETE",
|
|
169
|
+
headers: this.authHeaders(),
|
|
170
|
+
});
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
const text = await response.text().catch(() => "");
|
|
173
|
+
throw new Error(`PostgresTokenStore.delete failed (${response.status}): ${text}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async deleteAllForSubject(subject, clientId, type) {
|
|
177
|
+
const url = `${this.baseUrl}?subject=eq.${encodeURIComponent(subject)}&client_id=eq.${encodeURIComponent(clientId)}&type=eq.${encodeURIComponent(type)}`;
|
|
178
|
+
const response = await fetch(url, {
|
|
179
|
+
method: "DELETE",
|
|
180
|
+
headers: this.authHeaders(),
|
|
181
|
+
});
|
|
182
|
+
if (!response.ok) {
|
|
183
|
+
const text = await response.text().catch(() => "");
|
|
184
|
+
throw new Error(`PostgresTokenStore.deleteAllForSubject failed (${response.status}): ${text}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async deleteAllTokensForSubject(subject) {
|
|
188
|
+
const url = `${this.baseUrl}?subject=eq.${encodeURIComponent(subject)}`;
|
|
189
|
+
const response = await fetch(url, {
|
|
190
|
+
method: "DELETE",
|
|
191
|
+
headers: this.authHeaders(),
|
|
192
|
+
});
|
|
193
|
+
if (!response.ok) {
|
|
194
|
+
const text = await response.text().catch(() => "");
|
|
195
|
+
throw new Error(`PostgresTokenStore.deleteAllTokensForSubject failed (${response.status}): ${text}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async pruneExpired() {
|
|
199
|
+
const url = `${this.baseUrl}?expires_at=lte.${Date.now()}`;
|
|
200
|
+
await fetch(url, {
|
|
201
|
+
method: "DELETE",
|
|
202
|
+
headers: this.authHeaders(),
|
|
203
|
+
}).catch(() => null);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// ─── Factory ──────────────────────────────────────────────────────────────────
|
|
207
|
+
/**
|
|
208
|
+
* Select and instantiate a token store based on the environment:
|
|
209
|
+
* - SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY present → PostgresTokenStore
|
|
210
|
+
* - otherwise → LocalSQLiteTokenStore (stored under `storageLocation/.cache/`)
|
|
211
|
+
*/
|
|
212
|
+
export function createTokenStore(storageLocation) {
|
|
213
|
+
const supabaseUrl = (process.env.SUPABASE_URL ?? "").trim();
|
|
214
|
+
const serviceRoleKey = (process.env.SUPABASE_SERVICE_ROLE_KEY ?? "").trim();
|
|
215
|
+
if (supabaseUrl && serviceRoleKey) {
|
|
216
|
+
return new PostgresTokenStore(supabaseUrl, serviceRoleKey);
|
|
217
|
+
}
|
|
218
|
+
return new LocalSQLiteTokenStore(storageLocation);
|
|
219
|
+
}
|
|
220
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
221
|
+
function rowToStoredToken(row) {
|
|
222
|
+
let scope = [];
|
|
223
|
+
try {
|
|
224
|
+
scope = JSON.parse(row.scope);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
scope = [];
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
type: row.type,
|
|
231
|
+
token: row.token,
|
|
232
|
+
clientId: row.client_id,
|
|
233
|
+
subject: row.subject,
|
|
234
|
+
scope,
|
|
235
|
+
expiresAt: row.expires_at,
|
|
236
|
+
redirectUri: row.redirect_uri ?? undefined,
|
|
237
|
+
codeChallenge: row.code_challenge ?? undefined,
|
|
238
|
+
codeChallengeMethod: row.code_challenge_method ?? undefined,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function postgresRowToStoredToken(row) {
|
|
242
|
+
let scope = [];
|
|
243
|
+
try {
|
|
244
|
+
scope = JSON.parse(row.scope);
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
scope = [];
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
type: row.type,
|
|
251
|
+
token: row.token,
|
|
252
|
+
clientId: row.client_id,
|
|
253
|
+
subject: row.subject,
|
|
254
|
+
scope,
|
|
255
|
+
expiresAt: Number(row.expires_at),
|
|
256
|
+
redirectUri: row.redirect_uri ?? undefined,
|
|
257
|
+
codeChallenge: row.code_challenge ?? undefined,
|
|
258
|
+
codeChallengeMethod: row.code_challenge_method ?? undefined,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=tokenStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenStore.js","sourceRoot":"","sources":["../../src/auth/tokenStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional Google Drive backup for the brains wiki.
|
|
3
|
+
*
|
|
4
|
+
* This module is only activated when DRIVE_BACKUP_ENABLED=true and all
|
|
5
|
+
* required Google OAuth2 credentials are set. When active, every write to
|
|
6
|
+
* local storage is also mirrored to a single Drive folder asynchronously.
|
|
7
|
+
* Failures are logged but never propagated — Drive is backup, not primary.
|
|
8
|
+
*/
|
|
9
|
+
export declare class DriveBackup {
|
|
10
|
+
private readonly drive;
|
|
11
|
+
private readonly folderId;
|
|
12
|
+
private constructor();
|
|
13
|
+
/**
|
|
14
|
+
* Return a DriveBackup instance if all required env vars are present,
|
|
15
|
+
* or null if backup is disabled or misconfigured.
|
|
16
|
+
*/
|
|
17
|
+
static tryCreate(): DriveBackup | null;
|
|
18
|
+
/**
|
|
19
|
+
* Upsert a file to The Drive backup folder.
|
|
20
|
+
* Called fire-and-forget after every local write — errors are swallowed by the caller.
|
|
21
|
+
*/
|
|
22
|
+
backup(name: string, content: string): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=drive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drive.d.ts","sourceRoot":"","sources":["../../src/backup/drive.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,OAAO;IAYP;;;OAGG;IACH,MAAM,CAAC,SAAS,IAAI,WAAW,GAAG,IAAI;IAwBtC;;;OAGG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA8B3D"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional Google Drive backup for the brains wiki.
|
|
3
|
+
*
|
|
4
|
+
* This module is only activated when DRIVE_BACKUP_ENABLED=true and all
|
|
5
|
+
* required Google OAuth2 credentials are set. When active, every write to
|
|
6
|
+
* local storage is also mirrored to a single Drive folder asynchronously.
|
|
7
|
+
* Failures are logged but never propagated — Drive is backup, not primary.
|
|
8
|
+
*/
|
|
9
|
+
import { google } from "googleapis";
|
|
10
|
+
export class DriveBackup {
|
|
11
|
+
drive;
|
|
12
|
+
folderId;
|
|
13
|
+
constructor(clientId, clientSecret, refreshToken, folderId) {
|
|
14
|
+
const auth = new google.auth.OAuth2(clientId, clientSecret);
|
|
15
|
+
auth.setCredentials({ refresh_token: refreshToken });
|
|
16
|
+
this.drive = google.drive({ version: "v3", auth: auth });
|
|
17
|
+
this.folderId = folderId;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Return a DriveBackup instance if all required env vars are present,
|
|
21
|
+
* or null if backup is disabled or misconfigured.
|
|
22
|
+
*/
|
|
23
|
+
static tryCreate() {
|
|
24
|
+
if (process.env.DRIVE_BACKUP_ENABLED !== "true")
|
|
25
|
+
return null;
|
|
26
|
+
const { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN, DRIVE_FOLDER_ID } = process.env;
|
|
27
|
+
if (!GOOGLE_CLIENT_ID || !GOOGLE_CLIENT_SECRET || !GOOGLE_REFRESH_TOKEN || !DRIVE_FOLDER_ID) {
|
|
28
|
+
console.warn("[backup] DRIVE_BACKUP_ENABLED=true but one or more required env vars are missing " +
|
|
29
|
+
"(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN, DRIVE_FOLDER_ID). " +
|
|
30
|
+
"Drive backup is disabled.");
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
console.info("[backup] Drive backup enabled. Folder:", DRIVE_FOLDER_ID);
|
|
34
|
+
return new DriveBackup(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN, DRIVE_FOLDER_ID);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Upsert a file to The Drive backup folder.
|
|
38
|
+
* Called fire-and-forget after every local write — errors are swallowed by the caller.
|
|
39
|
+
*/
|
|
40
|
+
async backup(name, content) {
|
|
41
|
+
// Check if file already exists in Drive
|
|
42
|
+
const listRes = await this.drive.files.list({
|
|
43
|
+
q: `'${this.folderId}' in parents and name='${name}' and trashed=false`,
|
|
44
|
+
fields: "files(id)",
|
|
45
|
+
pageSize: 1,
|
|
46
|
+
});
|
|
47
|
+
const existing = listRes.data.files?.[0];
|
|
48
|
+
if (existing?.id) {
|
|
49
|
+
// Update existing file
|
|
50
|
+
await this.drive.files.update({
|
|
51
|
+
fileId: existing.id,
|
|
52
|
+
media: { mimeType: "text/plain", body: content },
|
|
53
|
+
fields: "id",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Create new file
|
|
58
|
+
await this.drive.files.create({
|
|
59
|
+
requestBody: {
|
|
60
|
+
name,
|
|
61
|
+
parents: [this.folderId],
|
|
62
|
+
mimeType: "text/plain",
|
|
63
|
+
},
|
|
64
|
+
media: { mimeType: "text/plain", body: content },
|
|
65
|
+
fields: "id",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=drive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drive.js","sourceRoot":"","sources":["../../src/backup/drive.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAY,MAAM,YAAY,CAAC;AAG9C,MAAM,OAAO,WAAW;IACL,KAAK,CAAiB;IACtB,QAAQ,CAAS;IAElC,YACE,QAAgB,EAChB,YAAoB,EACpB,YAAoB,EACpB,QAAgB;QAEhB,MAAM,IAAI,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAC5D,IAAI,CAAC,cAAc,CAAC,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC,CAAC;QACrD,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAA+B,EAAE,CAAC,CAAC;QACpF,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,SAAS;QACd,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC;QAE7D,MAAM,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,eAAe,EAAE,GACrF,OAAO,CAAC,GAAG,CAAC;QAEd,IAAI,CAAC,gBAAgB,IAAI,CAAC,oBAAoB,IAAI,CAAC,oBAAoB,IAAI,CAAC,eAAe,EAAE,CAAC;YAC5F,OAAO,CAAC,IAAI,CACV,mFAAmF;gBACjF,mFAAmF;gBACnF,2BAA2B,CAC9B,CAAC;YACF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,wCAAwC,EAAE,eAAe,CAAC,CAAC;QACxE,OAAO,IAAI,WAAW,CACpB,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,CAChB,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM,CAAC,IAAY,EAAE,OAAe;QACxC,wCAAwC;QACxC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC;YAC1C,CAAC,EAAE,IAAI,IAAI,CAAC,QAAQ,0BAA0B,IAAI,qBAAqB;YACvE,MAAM,EAAE,WAAW;YACnB,QAAQ,EAAE,CAAC;SACZ,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QAEzC,IAAI,QAAQ,EAAE,EAAE,EAAE,CAAC;YACjB,uBAAuB;YACvB,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;gBAC5B,MAAM,EAAE,QAAQ,CAAC,EAAE;gBACnB,KAAK,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE;gBAChD,MAAM,EAAE,IAAI;aACb,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,kBAAkB;YAClB,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;gBAC5B,WAAW,EAAE;oBACX,IAAI;oBACJ,OAAO,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC;oBACxB,QAAQ,EAAE,YAAY;iBACvB;gBACD,KAAK,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE;gBAChD,MAAM,EAAE,IAAI;aACb,CAAC,CAAC;QACL,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Billing tier lookup (GH #473).
|
|
3
|
+
*
|
|
4
|
+
* The page limit used to be decided by two env vars and god-mode membership, so
|
|
5
|
+
* the "Upgrade to Solo for 2,000 pages" message pointed at a tier that raised
|
|
6
|
+
* nothing. This resolves a user's tier so `getPageLimit` can honour it.
|
|
7
|
+
*
|
|
8
|
+
* PostgREST-backed, mirroring PostgresPATStore. There is no SQLite backend on
|
|
9
|
+
* purpose: `assertPageLimit` returns early unless the Postgres index is
|
|
10
|
+
* configured, so a local run never reaches a tier lookup.
|
|
11
|
+
*
|
|
12
|
+
* Failure is non-fatal by design. A missing table, an unreachable database or a
|
|
13
|
+
* malformed row all resolve to `null` — "no tier assigned" — and the caller
|
|
14
|
+
* falls back to the env-var defaults.
|
|
15
|
+
*
|
|
16
|
+
* That matters because on production, code and schema move on different
|
|
17
|
+
* triggers. Railway redeploys production code when `main` moves, but
|
|
18
|
+
* `apply-migrations.mjs` runs there only inside `deploy-production.yml`, which
|
|
19
|
+
* is `workflow_dispatch` and fires only when a human asks. So production can be
|
|
20
|
+
* running code that reads this table days before anyone applies it. (Staging
|
|
21
|
+
* does not have the gap — `deploy-staging.yml` runs migrations on every push to
|
|
22
|
+
* `main`.) Refusing writes in that window would take production down for
|
|
23
|
+
* everyone in exchange for a limit that is merely stale.
|
|
24
|
+
*/
|
|
25
|
+
/** Tiers the pricing page sells, plus the two legacy names seedUsers already writes. */
|
|
26
|
+
export type BillingTier = "free" | "solo" | "pro" | "unlimited" | "free_beta" | "launch_user";
|
|
27
|
+
export declare const BILLING_TIERS: readonly BillingTier[];
|
|
28
|
+
export declare function isBillingTier(value: string): value is BillingTier;
|
|
29
|
+
export interface TierStore {
|
|
30
|
+
/** Resolve a subject's tier, or null when none is assigned or lookup fails. */
|
|
31
|
+
get(subject: string): Promise<BillingTier | null>;
|
|
32
|
+
/** Assign a tier. Returns false when the write fails. */
|
|
33
|
+
set(subject: string, tier: BillingTier, updatedBy: string): Promise<boolean>;
|
|
34
|
+
/** Drop cached lookups. */
|
|
35
|
+
invalidate(subject?: string): void;
|
|
36
|
+
}
|
|
37
|
+
export declare class PostgresTierStore implements TierStore {
|
|
38
|
+
private readonly supabaseUrl;
|
|
39
|
+
private readonly serviceRoleKey;
|
|
40
|
+
private readonly cache;
|
|
41
|
+
constructor(supabaseUrl: string, serviceRoleKey: string);
|
|
42
|
+
private get baseUrl();
|
|
43
|
+
private headers;
|
|
44
|
+
private cacheTtlMs;
|
|
45
|
+
private cacheSet;
|
|
46
|
+
invalidate(subject?: string): void;
|
|
47
|
+
get(subject: string): Promise<BillingTier | null>;
|
|
48
|
+
set(subject: string, tier: BillingTier, updatedBy: string): Promise<boolean>;
|
|
49
|
+
}
|
|
50
|
+
/** No-op store for deployments without Postgres — every lookup is "no tier". */
|
|
51
|
+
export declare class NullTierStore implements TierStore {
|
|
52
|
+
get(): Promise<BillingTier | null>;
|
|
53
|
+
set(): Promise<boolean>;
|
|
54
|
+
invalidate(): void;
|
|
55
|
+
}
|
|
56
|
+
export declare function getTierStore(): TierStore;
|
|
57
|
+
/** Test seam. */
|
|
58
|
+
export declare function setTierStore(store: TierStore | null): void;
|
|
59
|
+
//# sourceMappingURL=tierStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tierStore.d.ts","sourceRoot":"","sources":["../../src/billing/tierStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAKH,wFAAwF;AACxF,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,WAAW,GAAG,WAAW,GAAG,aAAa,CAAC;AAE9F,eAAO,MAAM,aAAa,EAAE,SAAS,WAAW,EAO/C,CAAC;AAEF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,WAAW,CAEjE;AAOD,MAAM,WAAW,SAAS;IACxB,+EAA+E;IAC/E,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAClD,yDAAyD;IACzD,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7E,2BAA2B;IAC3B,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,qBAAa,iBAAkB,YAAW,SAAS;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;gBAE3C,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM;IAKvD,OAAO,KAAK,OAAO,GAElB;IAED,OAAO,CAAC,OAAO;IASf,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,QAAQ;IAQhB,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAK5B,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IA4BjD,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAoBnF;AAED,gFAAgF;AAChF,qBAAa,aAAc,YAAW,SAAS;IACvC,GAAG,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAGlC,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;IAG7B,UAAU,IAAI,IAAI;CAGnB;AAID,wBAAgB,YAAY,IAAI,SAAS,CAMxC;AAED,iBAAiB;AACjB,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,CAE1D"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Billing tier lookup (GH #473).
|
|
3
|
+
*
|
|
4
|
+
* The page limit used to be decided by two env vars and god-mode membership, so
|
|
5
|
+
* the "Upgrade to Solo for 2,000 pages" message pointed at a tier that raised
|
|
6
|
+
* nothing. This resolves a user's tier so `getPageLimit` can honour it.
|
|
7
|
+
*
|
|
8
|
+
* PostgREST-backed, mirroring PostgresPATStore. There is no SQLite backend on
|
|
9
|
+
* purpose: `assertPageLimit` returns early unless the Postgres index is
|
|
10
|
+
* configured, so a local run never reaches a tier lookup.
|
|
11
|
+
*
|
|
12
|
+
* Failure is non-fatal by design. A missing table, an unreachable database or a
|
|
13
|
+
* malformed row all resolve to `null` — "no tier assigned" — and the caller
|
|
14
|
+
* falls back to the env-var defaults.
|
|
15
|
+
*
|
|
16
|
+
* That matters because on production, code and schema move on different
|
|
17
|
+
* triggers. Railway redeploys production code when `main` moves, but
|
|
18
|
+
* `apply-migrations.mjs` runs there only inside `deploy-production.yml`, which
|
|
19
|
+
* is `workflow_dispatch` and fires only when a human asks. So production can be
|
|
20
|
+
* running code that reads this table days before anyone applies it. (Staging
|
|
21
|
+
* does not have the gap — `deploy-staging.yml` runs migrations on every push to
|
|
22
|
+
* `main`.) Refusing writes in that window would take production down for
|
|
23
|
+
* everyone in exchange for a limit that is merely stale.
|
|
24
|
+
*/
|
|
25
|
+
const TIER_CACHE_TTL_MS = 60_000;
|
|
26
|
+
const TIER_CACHE_MAX_ENTRIES = 5_000;
|
|
27
|
+
export const BILLING_TIERS = [
|
|
28
|
+
"free",
|
|
29
|
+
"solo",
|
|
30
|
+
"pro",
|
|
31
|
+
"unlimited",
|
|
32
|
+
"free_beta",
|
|
33
|
+
"launch_user",
|
|
34
|
+
];
|
|
35
|
+
export function isBillingTier(value) {
|
|
36
|
+
return BILLING_TIERS.includes(value);
|
|
37
|
+
}
|
|
38
|
+
export class PostgresTierStore {
|
|
39
|
+
supabaseUrl;
|
|
40
|
+
serviceRoleKey;
|
|
41
|
+
cache = new Map();
|
|
42
|
+
constructor(supabaseUrl, serviceRoleKey) {
|
|
43
|
+
this.supabaseUrl = supabaseUrl.trim().replace(/\/$/, "");
|
|
44
|
+
this.serviceRoleKey = serviceRoleKey.trim();
|
|
45
|
+
}
|
|
46
|
+
get baseUrl() {
|
|
47
|
+
return `${this.supabaseUrl}/rest/v1/user_tiers`;
|
|
48
|
+
}
|
|
49
|
+
headers(extra) {
|
|
50
|
+
return {
|
|
51
|
+
apikey: this.serviceRoleKey,
|
|
52
|
+
authorization: `Bearer ${this.serviceRoleKey}`,
|
|
53
|
+
"content-type": "application/json",
|
|
54
|
+
...extra,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
cacheTtlMs() {
|
|
58
|
+
const raw = parseInt(process.env.BRAINS_TIER_CACHE_TTL_MS ?? "", 10);
|
|
59
|
+
return Number.isFinite(raw) && raw >= 0 ? raw : TIER_CACHE_TTL_MS;
|
|
60
|
+
}
|
|
61
|
+
cacheSet(subject, tier) {
|
|
62
|
+
if (this.cache.size >= TIER_CACHE_MAX_ENTRIES) {
|
|
63
|
+
const oldest = this.cache.keys().next().value;
|
|
64
|
+
if (oldest !== undefined)
|
|
65
|
+
this.cache.delete(oldest);
|
|
66
|
+
}
|
|
67
|
+
this.cache.set(subject, { tier, at: Date.now() });
|
|
68
|
+
}
|
|
69
|
+
invalidate(subject) {
|
|
70
|
+
if (subject === undefined)
|
|
71
|
+
this.cache.clear();
|
|
72
|
+
else
|
|
73
|
+
this.cache.delete(subject);
|
|
74
|
+
}
|
|
75
|
+
async get(subject) {
|
|
76
|
+
if (!subject)
|
|
77
|
+
return null;
|
|
78
|
+
const hit = this.cache.get(subject);
|
|
79
|
+
if (hit && Date.now() - hit.at < this.cacheTtlMs())
|
|
80
|
+
return hit.tier;
|
|
81
|
+
try {
|
|
82
|
+
const url = `${this.baseUrl}?user_id=eq.${encodeURIComponent(subject)}&select=tier&limit=1`;
|
|
83
|
+
const res = await fetch(url, { headers: this.headers() });
|
|
84
|
+
if (!res.ok) {
|
|
85
|
+
// 404 here means the migration has not been applied to this database.
|
|
86
|
+
// Cache the negative so a missing table does not add a round trip to
|
|
87
|
+
// every write until someone deploys it.
|
|
88
|
+
this.cacheSet(subject, null);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const rows = (await res.json());
|
|
92
|
+
const raw = Array.isArray(rows) && rows.length > 0 ? rows[0]?.tier : undefined;
|
|
93
|
+
const tier = typeof raw === "string" && isBillingTier(raw) ? raw : null;
|
|
94
|
+
this.cacheSet(subject, tier);
|
|
95
|
+
return tier;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Network/DNS failure. Do not cache — the next write should retry rather
|
|
99
|
+
// than run on a stale negative for the full TTL.
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async set(subject, tier, updatedBy) {
|
|
104
|
+
if (!subject)
|
|
105
|
+
return false;
|
|
106
|
+
try {
|
|
107
|
+
const res = await fetch(this.baseUrl, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: this.headers({ prefer: "resolution=merge-duplicates,return=minimal" }),
|
|
110
|
+
body: JSON.stringify({
|
|
111
|
+
user_id: subject,
|
|
112
|
+
tier,
|
|
113
|
+
updated_by: updatedBy,
|
|
114
|
+
updated_at: new Date().toISOString(),
|
|
115
|
+
}),
|
|
116
|
+
});
|
|
117
|
+
if (!res.ok)
|
|
118
|
+
return false;
|
|
119
|
+
this.cacheSet(subject, tier);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** No-op store for deployments without Postgres — every lookup is "no tier". */
|
|
128
|
+
export class NullTierStore {
|
|
129
|
+
async get() {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
async set() {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
invalidate() {
|
|
136
|
+
/* nothing cached */
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
let cached = null;
|
|
140
|
+
export function getTierStore() {
|
|
141
|
+
if (cached)
|
|
142
|
+
return cached;
|
|
143
|
+
const url = (process.env.SUPABASE_URL ?? "").trim();
|
|
144
|
+
const key = (process.env.SUPABASE_SERVICE_ROLE_KEY ?? "").trim();
|
|
145
|
+
cached = url && key ? new PostgresTierStore(url, key) : new NullTierStore();
|
|
146
|
+
return cached;
|
|
147
|
+
}
|
|
148
|
+
/** Test seam. */
|
|
149
|
+
export function setTierStore(store) {
|
|
150
|
+
cached = store;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=tierStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tierStore.js","sourceRoot":"","sources":["../../src/billing/tierStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* activityDigest.ts
|
|
3
|
+
*
|
|
4
|
+
* Gathers weekly activity signals from a user's wiki storage and generates
|
|
5
|
+
* the digest markdown content and email body.
|
|
6
|
+
*
|
|
7
|
+
* Signals collected:
|
|
8
|
+
* - Pages created / modified (from log.md events + file listing)
|
|
9
|
+
* - Decisions made (pages tagged "decision" modified this week)
|
|
10
|
+
* - Open loops closed (pages tagged "open-loop" with status resolved)
|
|
11
|
+
* - Active tags (tags appearing in recently modified pages)
|
|
12
|
+
*/
|
|
13
|
+
import type { StorageClient } from "../storage/client.js";
|
|
14
|
+
export interface DigestSignals {
|
|
15
|
+
weekLabel: string;
|
|
16
|
+
weekRange: string;
|
|
17
|
+
pagesCreated: string[];
|
|
18
|
+
pagesModified: string[];
|
|
19
|
+
decisionsMade: string[];
|
|
20
|
+
loopsClosed: string[];
|
|
21
|
+
activeTags: Array<{
|
|
22
|
+
tag: string;
|
|
23
|
+
count: number;
|
|
24
|
+
}>;
|
|
25
|
+
totalEvents: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function isoWeekLabel(date: Date): string;
|
|
28
|
+
export declare function gatherDigestSignals(drive: StorageClient, now?: Date, userId?: string): Promise<DigestSignals>;
|
|
29
|
+
export declare function buildDigestMarkdown(signals: DigestSignals, dateStr: string): string;
|
|
30
|
+
export declare function buildDigestHtml(signals: DigestSignals): string;
|
|
31
|
+
export declare function buildDigestText(signals: DigestSignals): string;
|
|
32
|
+
//# sourceMappingURL=activityDigest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activityDigest.d.ts","sourceRoot":"","sources":["../../src/digest/activityDigest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAK1D,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,UAAU,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,WAAW,EAAE,MAAM,CAAC;CACrB;AAID,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAW/C;AAyFD,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,aAAa,EACpB,GAAG,GAAE,IAAiB,EACtB,MAAM,SAAK,GACV,OAAO,CAAC,aAAa,CAAC,CAyGxB;AAID,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CA6CnF;AAID,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAyD9D;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CA0B9D"}
|