@iwo-szapar/data-mcp 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server.js +2 -2
- package/dist/tools/memory/link-create.js +2 -2
- package/dist/tools/memory/link-delete.js +1 -1
- package/dist/tools/memory/link-related.js +1 -1
- package/dist/tools/memory/link-suggest.js +1 -1
- package/dist/tools/register.js +3 -1
- package/dist/tools/setup/setup-bootstrap.js +71 -0
- package/dist/tools/setup/setup-migrate.js +20 -16
- package/dist/tools/setup/setup-status.js +11 -6
- package/migrations/pocketbase/001_core_schema.js +24 -28
- package/migrations/pocketbase/002_goals_tasks.js +15 -18
- package/migrations/pocketbase/003_contacts.js +9 -12
- package/migrations/pocketbase/004_entity_aliases.js +9 -12
- package/migrations/pocketbase/005_prospects.js +11 -14
- package/migrations/pocketbase/006_business.js +29 -33
- package/migrations/pocketbase/007_newsletter_affiliates.js +16 -19
- package/migrations/pocketbase/008_knowledge_links.js +34 -37
- package/migrations/supabase/009_align_to_production.sql +14 -11
- package/package.json +1 -1
- package/README.md +0 -286
package/dist/server.js
CHANGED
|
@@ -29,8 +29,8 @@ const SERVER_INSTRUCTIONS = `You are the user's AI Second Brain data layer. This
|
|
|
29
29
|
- Use setup_status to check database readiness`;
|
|
30
30
|
export function createServer(adapter) {
|
|
31
31
|
const server = new McpServer({
|
|
32
|
-
name: '@
|
|
33
|
-
version: '0.
|
|
32
|
+
name: '@second-brain/data-mcp',
|
|
33
|
+
version: '0.1.0',
|
|
34
34
|
}, {
|
|
35
35
|
instructions: SERVER_INSTRUCTIONS,
|
|
36
36
|
});
|
|
@@ -12,9 +12,9 @@ export function registerLinkCreate(server, adapter) {
|
|
|
12
12
|
'Links express how knowledge items relate: supports, contradicts, derived_from, etc. ' +
|
|
13
13
|
'Deduplicates by (source, target, relation_type).', {
|
|
14
14
|
source_type: z.enum(ENTITY_TYPES).describe('Type of the source entity'),
|
|
15
|
-
source_id: z.string().
|
|
15
|
+
source_id: z.string().uuid().describe('UUID of the source entity'),
|
|
16
16
|
target_type: z.enum(ENTITY_TYPES).describe('Type of the target entity'),
|
|
17
|
-
target_id: z.string().
|
|
17
|
+
target_id: z.string().uuid().describe('UUID of the target entity'),
|
|
18
18
|
relation_type: z.enum(RELATION_TYPES).describe('Type of relationship'),
|
|
19
19
|
confidence: z.number().min(0).max(1).optional().default(0.8).describe('Confidence in this relationship (0-1)'),
|
|
20
20
|
notes: z.string().max(500).optional().describe('Optional context for why this link exists'),
|
|
@@ -7,7 +7,7 @@ import { z } from 'zod';
|
|
|
7
7
|
import { makeToolResponse, handleAdapterError, withGracefulDegradation } from '../shared.js';
|
|
8
8
|
export function registerLinkDelete(server, adapter) {
|
|
9
9
|
server.tool('link_delete', 'Delete a knowledge link by its ID.', {
|
|
10
|
-
link_id: z.string().
|
|
10
|
+
link_id: z.string().uuid().describe('UUID of the link to delete'),
|
|
11
11
|
}, withGracefulDegradation('knowledge_links', adapter, async (params) => {
|
|
12
12
|
try {
|
|
13
13
|
try {
|
|
@@ -9,7 +9,7 @@ const ENTITY_TYPES = ['knowledge', 'decision', 'session', 'blog_post', 'prospect
|
|
|
9
9
|
export function registerLinkRelated(server, adapter) {
|
|
10
10
|
server.tool('link_related', 'Get all links for an entity. Shows outgoing and incoming relationships with resolved titles.', {
|
|
11
11
|
entity_type: z.enum(ENTITY_TYPES).describe('Type of the entity'),
|
|
12
|
-
entity_id: z.string().
|
|
12
|
+
entity_id: z.string().uuid().describe('UUID of the entity'),
|
|
13
13
|
direction: z.enum(['both', 'outgoing', 'incoming']).optional().default('both').describe('Filter direction'),
|
|
14
14
|
relation_type: z.enum(['supports', 'contradicts', 'derived_from', 'example_of', 'supersedes', 'part_of', 'prerequisite'])
|
|
15
15
|
.optional().describe('Filter by relation type'),
|
|
@@ -8,7 +8,7 @@ import { makeToolResponse, handleAdapterError, withGracefulDegradation } from '.
|
|
|
8
8
|
export function registerLinkSuggest(server, adapter) {
|
|
9
9
|
server.tool('link_suggest', 'Find knowledge items similar to a given item and suggest links. ' +
|
|
10
10
|
'Uses text search to find related items. Returns matches with suggested relation types.', {
|
|
11
|
-
item_id: z.string().
|
|
11
|
+
item_id: z.string().uuid().describe('UUID of the knowledge item to find suggestions for'),
|
|
12
12
|
limit: z.number().min(1).max(20).optional().default(5).describe('Max suggestions'),
|
|
13
13
|
}, withGracefulDegradation('knowledge', adapter, async (params) => {
|
|
14
14
|
try {
|
package/dist/tools/register.js
CHANGED
|
@@ -28,9 +28,10 @@ import { registerLinkCreate } from './memory/link-create.js';
|
|
|
28
28
|
import { registerLinkDelete } from './memory/link-delete.js';
|
|
29
29
|
import { registerLinkRelated } from './memory/link-related.js';
|
|
30
30
|
import { registerLinkSuggest } from './memory/link-suggest.js';
|
|
31
|
-
// Setup tools (
|
|
31
|
+
// Setup tools (4)
|
|
32
32
|
import { registerSetupStatus } from './setup/setup-status.js';
|
|
33
33
|
import { registerSetupMigrate } from './setup/setup-migrate.js';
|
|
34
|
+
import { registerSetupBootstrap } from './setup/setup-bootstrap.js';
|
|
34
35
|
import { registerSetupSeed } from './setup/setup-seed.js';
|
|
35
36
|
// Business tools (11)
|
|
36
37
|
import { registerProspectCreate } from './business/prospect-create.js';
|
|
@@ -75,6 +76,7 @@ export function registerAllTools(server, adapter) {
|
|
|
75
76
|
// Setup tools
|
|
76
77
|
registerSetupStatus(server, adapter);
|
|
77
78
|
registerSetupMigrate(server, adapter);
|
|
79
|
+
registerSetupBootstrap(server, adapter);
|
|
78
80
|
registerSetupSeed(server, adapter);
|
|
79
81
|
// Business tools
|
|
80
82
|
registerProspectCreate(server, adapter);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool: setup_bootstrap
|
|
3
|
+
*
|
|
4
|
+
* RC-2 fix (incident 2026-05-11): Generates a single SQL block containing
|
|
5
|
+
* every bundled Supabase migration concatenated in numeric order, followed
|
|
6
|
+
* by NOTIFY pgrst, 'reload schema'. Customer pastes the block into the
|
|
7
|
+
* Supabase SQL Editor and runs it once. Stops short of running the SQL
|
|
8
|
+
* itself because Supabase service-role over PostgREST does not expose
|
|
9
|
+
* arbitrary SQL execution — that needs either a custom exec_sql function
|
|
10
|
+
* (Approach B, opt-in by customer) or the Management API with a personal
|
|
11
|
+
* access token (Approach C, adds auth surface). This is Approach A: lowest
|
|
12
|
+
* friction, no new auth, works on Cloud + self-hosted.
|
|
13
|
+
*
|
|
14
|
+
* After the customer runs the block, setup_migrate will report all
|
|
15
|
+
* collections present.
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
18
|
+
import { dirname, join } from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import { makeToolResponse, makeErrorResponse } from '../shared.js';
|
|
21
|
+
const MIGRATIONS_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'migrations', 'supabase');
|
|
22
|
+
export function registerSetupBootstrap(server, adapter) {
|
|
23
|
+
server.tool('setup_bootstrap', 'Generate a paste-ready SQL block (all bundled Supabase migrations + PostgREST reload). Open the returned sql_editor_url, paste sql, run. Idempotent. Supabase backend only — PocketBase customers use setup_migrate.', {}, async () => {
|
|
24
|
+
if (adapter.backend !== 'supabase') {
|
|
25
|
+
return makeErrorResponse('setup_bootstrap is Supabase-only. PocketBase customers use setup_migrate.');
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const files = readdirSync(MIGRATIONS_DIR).filter((f) => f.endsWith('.sql')).sort();
|
|
29
|
+
if (files.length === 0) {
|
|
30
|
+
return makeErrorResponse(`No bundled migration files found at ${MIGRATIONS_DIR}. Package install may be corrupted.`);
|
|
31
|
+
}
|
|
32
|
+
const blocks = files.map((f) => `-- ===== ${f} =====\n${readFileSync(join(MIGRATIONS_DIR, f), 'utf-8').trimEnd()}\n`);
|
|
33
|
+
const sql = [
|
|
34
|
+
...blocks,
|
|
35
|
+
'-- ===== Reload PostgREST schema cache =====',
|
|
36
|
+
"-- Without this, PostgREST keeps serving stale cache and knowledge_* calls fail.",
|
|
37
|
+
"NOTIFY pgrst, 'reload schema';",
|
|
38
|
+
'',
|
|
39
|
+
].join('\n');
|
|
40
|
+
// Supabase URL format: https://<ref>.supabase.co. The SQL Editor URL is
|
|
41
|
+
// https://supabase.com/dashboard/project/<ref>/sql/new. We try to extract
|
|
42
|
+
// the ref; if the URL doesn't match the expected format (self-hosted,
|
|
43
|
+
// etc.), we fall back to the generic dashboard.
|
|
44
|
+
const supabaseUrl = adapter.supabaseUrl || '';
|
|
45
|
+
const refMatch = supabaseUrl.match(/https?:\/\/([a-z0-9]+)\.supabase\.co/);
|
|
46
|
+
const sqlEditorUrl = refMatch
|
|
47
|
+
? `https://supabase.com/dashboard/project/${refMatch[1]}/sql/new`
|
|
48
|
+
: 'https://supabase.com/dashboard/project/_/sql/new';
|
|
49
|
+
return makeToolResponse({
|
|
50
|
+
backend: 'supabase',
|
|
51
|
+
migrations_path: MIGRATIONS_DIR,
|
|
52
|
+
files_count: files.length,
|
|
53
|
+
files,
|
|
54
|
+
sql,
|
|
55
|
+
sql_editor_url: sqlEditorUrl,
|
|
56
|
+
instructions: [
|
|
57
|
+
`1. Open ${sqlEditorUrl}`,
|
|
58
|
+
'2. Paste the contents of the `sql` field above into the editor.',
|
|
59
|
+
'3. Run.',
|
|
60
|
+
'4. Verify with setup_migrate (it should report 0 missing collections).',
|
|
61
|
+
],
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
const msg = err instanceof Error ? err.message : 'unknown';
|
|
66
|
+
console.error('[setup_bootstrap] Error:', err);
|
|
67
|
+
return makeErrorResponse(`Failed to assemble bootstrap SQL from ${MIGRATIONS_DIR}: ${msg}`);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=setup-bootstrap.js.map
|
|
@@ -3,8 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Apply migrations — additive only, skips existing collections.
|
|
5
5
|
* Creates all expected collections in the database.
|
|
6
|
+
*
|
|
7
|
+
* RC-3 fix (incident 2026-05-11): Replaced hardcoded project-relative
|
|
8
|
+
* "migrations/supabase/" string with runtime-resolved absolute path to
|
|
9
|
+
* the bundled SQL files inside this package.
|
|
6
10
|
*/
|
|
11
|
+
import { dirname, join } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
7
13
|
import { makeToolResponse, makeErrorResponse } from '../shared.js';
|
|
14
|
+
const PACKAGE_MIGRATIONS_SUPABASE = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'migrations', 'supabase');
|
|
15
|
+
const PACKAGE_MIGRATIONS_POCKETBASE = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'migrations', 'pocketbase');
|
|
8
16
|
/** Collection schemas for PocketBase creation */
|
|
9
17
|
const COLLECTION_SCHEMAS = [
|
|
10
18
|
{ name: 'knowledge', description: 'Knowledge items (facts, patterns, insights, lessons, references)' },
|
|
@@ -23,10 +31,11 @@ const COLLECTION_SCHEMAS = [
|
|
|
23
31
|
{ name: 'affiliates', description: 'Affiliate partners and commissions' },
|
|
24
32
|
];
|
|
25
33
|
export function registerSetupMigrate(server, adapter) {
|
|
26
|
-
server.tool('setup_migrate', 'Create missing database collections/tables. Additive only — existing collections are skipped. For PocketBase, creates collections via API. For Supabase, reports SQL migrations
|
|
34
|
+
server.tool('setup_migrate', 'Create missing database collections/tables. Additive only — existing collections are skipped. For PocketBase, creates collections via API. For Supabase, reports SQL migrations and recommends running setup_bootstrap for paste-ready SQL.', {}, async () => {
|
|
27
35
|
try {
|
|
28
36
|
const skipped = [];
|
|
29
37
|
const needsMigration = [];
|
|
38
|
+
const migrationsPath = adapter.backend === 'supabase' ? PACKAGE_MIGRATIONS_SUPABASE : PACKAGE_MIGRATIONS_POCKETBASE;
|
|
30
39
|
for (const schema of COLLECTION_SCHEMAS) {
|
|
31
40
|
try {
|
|
32
41
|
const exists = await adapter.collectionExists(schema.name);
|
|
@@ -34,18 +43,12 @@ export function registerSetupMigrate(server, adapter) {
|
|
|
34
43
|
skipped.push(schema.name);
|
|
35
44
|
continue;
|
|
36
45
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
needsMigration.push({
|
|
45
|
-
name: schema.name,
|
|
46
|
-
instruction: 'Run PocketBase migrations from migrations/pocketbase/ directory.',
|
|
47
|
-
});
|
|
48
|
-
}
|
|
46
|
+
needsMigration.push({
|
|
47
|
+
name: schema.name,
|
|
48
|
+
instruction: adapter.backend === 'supabase'
|
|
49
|
+
? `Apply SQL from ${migrationsPath}/. Easiest path: call setup_bootstrap for a paste-ready SQL block.`
|
|
50
|
+
: `Apply migration from ${migrationsPath}/.`,
|
|
51
|
+
});
|
|
49
52
|
}
|
|
50
53
|
catch (err) {
|
|
51
54
|
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
@@ -55,6 +58,7 @@ export function registerSetupMigrate(server, adapter) {
|
|
|
55
58
|
}
|
|
56
59
|
return makeToolResponse({
|
|
57
60
|
backend: adapter.backend,
|
|
61
|
+
migrations_path: migrationsPath,
|
|
58
62
|
existing: skipped.length,
|
|
59
63
|
needs_migration: needsMigration.length,
|
|
60
64
|
details: {
|
|
@@ -62,8 +66,8 @@ export function registerSetupMigrate(server, adapter) {
|
|
|
62
66
|
needs_migration: needsMigration.length > 0 ? needsMigration : undefined,
|
|
63
67
|
},
|
|
64
68
|
message: adapter.backend === 'pocketbase'
|
|
65
|
-
? `Schema check complete. ${skipped.length} existing, ${needsMigration.length} need migration files.
|
|
66
|
-
: `Schema check complete. ${skipped.length} existing, ${needsMigration.length} need SQL
|
|
69
|
+
? `Schema check complete. ${skipped.length} existing, ${needsMigration.length} need migration files. Apply from ${migrationsPath}/.`
|
|
70
|
+
: `Schema check complete. ${skipped.length} existing, ${needsMigration.length} need SQL. Run setup_bootstrap for a paste-ready block, or apply from ${migrationsPath}/ manually.`,
|
|
67
71
|
});
|
|
68
72
|
}
|
|
69
73
|
catch (error) {
|
|
@@ -72,4 +76,4 @@ export function registerSetupMigrate(server, adapter) {
|
|
|
72
76
|
}
|
|
73
77
|
});
|
|
74
78
|
}
|
|
75
|
-
//# sourceMappingURL=setup-migrate.js.map
|
|
79
|
+
//# sourceMappingURL=setup-migrate.js.map
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
* Tool: setup_status
|
|
3
3
|
*
|
|
4
4
|
* Check backend connection, list existing/missing collections, report status.
|
|
5
|
+
*
|
|
6
|
+
* RC-1 fix (incident 2026-05-11): Previously called adapter.listCollections()
|
|
7
|
+
* which uses rpc('get_public_tables') — a custom Postgres function absent from
|
|
8
|
+
* fresh Supabase projects. Fallback to information_schema also failed because
|
|
9
|
+
* PostgREST only exposes the public schema. Now uses the same code path as
|
|
10
|
+
* setup_migrate (adapter.collectionExists per expected collection) so the
|
|
11
|
+
* diagnostic agrees with the data tools.
|
|
5
12
|
*/
|
|
6
13
|
import { makeToolResponse, makeErrorResponse } from '../shared.js';
|
|
7
14
|
const EXPECTED_COLLECTIONS = [
|
|
@@ -23,20 +30,18 @@ const EXPECTED_COLLECTIONS = [
|
|
|
23
30
|
export function registerSetupStatus(server, adapter) {
|
|
24
31
|
server.tool('setup_status', 'Check database connection status, list existing and missing collections, and report schema readiness.', {}, { readOnlyHint: true }, async () => {
|
|
25
32
|
try {
|
|
26
|
-
// Test connection by listing collections
|
|
27
|
-
const existing = await adapter.listCollections();
|
|
28
|
-
const existingSet = new Set(existing);
|
|
29
33
|
const present = [];
|
|
30
34
|
const missing = [];
|
|
31
35
|
for (const collection of EXPECTED_COLLECTIONS) {
|
|
32
|
-
|
|
36
|
+
const exists = await adapter.collectionExists(collection);
|
|
37
|
+
if (exists) {
|
|
33
38
|
present.push(collection);
|
|
34
39
|
}
|
|
35
40
|
else {
|
|
36
41
|
missing.push(collection);
|
|
37
42
|
}
|
|
38
43
|
}
|
|
39
|
-
|
|
44
|
+
const existingSet = new Set(present);
|
|
40
45
|
let schemaVersion = null;
|
|
41
46
|
if (existingSet.has('settings')) {
|
|
42
47
|
try {
|
|
@@ -75,4 +80,4 @@ export function registerSetupStatus(server, adapter) {
|
|
|
75
80
|
}
|
|
76
81
|
});
|
|
77
82
|
}
|
|
78
|
-
//# sourceMappingURL=setup-status.js.map
|
|
83
|
+
//# sourceMappingURL=setup-status.js.map
|
|
@@ -2,26 +2,24 @@
|
|
|
2
2
|
* PocketBase Migration 001: Core schema
|
|
3
3
|
*
|
|
4
4
|
* Creates knowledge, decisions, sessions collections.
|
|
5
|
-
* PocketBase v0.23+ field format.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
/// <reference path="../pb_data/types.d.ts" />
|
|
9
8
|
|
|
10
9
|
migrate((app) => {
|
|
10
|
+
// knowledge collection
|
|
11
11
|
const knowledge = new Collection({
|
|
12
12
|
name: 'knowledge',
|
|
13
13
|
type: 'base',
|
|
14
|
-
|
|
15
|
-
{ name: 'type', type: 'select', required: true,
|
|
16
|
-
{ name: 'title', type: 'text', required: true,
|
|
17
|
-
{ name: 'content', type: 'editor', required: true, maxSize: 50000 },
|
|
18
|
-
{ name: 'summary', type: 'text',
|
|
14
|
+
schema: [
|
|
15
|
+
{ name: 'type', type: 'select', required: true, options: { values: ['fact', 'knowledge', 'pattern', 'insight', 'lesson', 'reference'] } },
|
|
16
|
+
{ name: 'title', type: 'text', required: true, options: { maxSize: 500 } },
|
|
17
|
+
{ name: 'content', type: 'editor', required: true, options: { maxSize: 50000 } },
|
|
18
|
+
{ name: 'summary', type: 'text', options: { maxSize: 2000 } },
|
|
19
19
|
{ name: 'tags', type: 'json' },
|
|
20
|
-
{ name: 'source', type: 'text',
|
|
21
|
-
{ name: 'confidence', type: 'number', min: 0, max: 1 },
|
|
20
|
+
{ name: 'source', type: 'text', options: { maxSize: 500 } },
|
|
21
|
+
{ name: 'confidence', type: 'number', options: { min: 0, max: 1 } },
|
|
22
22
|
{ name: 'last_validated_at', type: 'date' },
|
|
23
|
-
{ name: 'created', type: 'autodate', onCreate: true },
|
|
24
|
-
{ name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
|
|
25
23
|
],
|
|
26
24
|
indexes: [
|
|
27
25
|
'CREATE INDEX idx_knowledge_type ON knowledge (type)',
|
|
@@ -30,47 +28,45 @@ migrate((app) => {
|
|
|
30
28
|
});
|
|
31
29
|
app.save(knowledge);
|
|
32
30
|
|
|
31
|
+
// decisions collection
|
|
33
32
|
const decisions = new Collection({
|
|
34
33
|
name: 'decisions',
|
|
35
34
|
type: 'base',
|
|
36
|
-
|
|
37
|
-
{ name: 'title', type: 'text', required: true,
|
|
38
|
-
{ name: 'context', type: 'editor', maxSize: 5000 },
|
|
35
|
+
schema: [
|
|
36
|
+
{ name: 'title', type: 'text', required: true, options: { maxSize: 500 } },
|
|
37
|
+
{ name: 'context', type: 'editor', options: { maxSize: 5000 } },
|
|
39
38
|
{ name: 'options_considered', type: 'json', required: true },
|
|
40
|
-
{ name: 'chosen_option', type: 'text', required: true,
|
|
41
|
-
{ name: 'rationale', type: 'editor', maxSize: 5000 },
|
|
42
|
-
{ name: 'outcome', type: 'editor', maxSize: 5000 },
|
|
39
|
+
{ name: 'chosen_option', type: 'text', required: true, options: { maxSize: 500 } },
|
|
40
|
+
{ name: 'rationale', type: 'editor', options: { maxSize: 5000 } },
|
|
41
|
+
{ name: 'outcome', type: 'editor', options: { maxSize: 5000 } },
|
|
43
42
|
{ name: 'tags', type: 'json' },
|
|
44
|
-
{ name: 'created', type: 'autodate', onCreate: true },
|
|
45
|
-
{ name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
|
|
46
43
|
],
|
|
47
44
|
});
|
|
48
45
|
app.save(decisions);
|
|
49
46
|
|
|
47
|
+
// sessions collection
|
|
50
48
|
const sessions = new Collection({
|
|
51
49
|
name: 'sessions',
|
|
52
50
|
type: 'base',
|
|
53
|
-
|
|
54
|
-
{ name: 'title', type: 'text', required: true,
|
|
55
|
-
{ name: 'summary', type: 'editor', required: true, maxSize: 10000 },
|
|
51
|
+
schema: [
|
|
52
|
+
{ name: 'title', type: 'text', required: true, options: { maxSize: 500 } },
|
|
53
|
+
{ name: 'summary', type: 'editor', required: true, options: { maxSize: 10000 } },
|
|
56
54
|
{ name: 'session_date', type: 'date' },
|
|
57
55
|
{ name: 'skills_used', type: 'json' },
|
|
58
56
|
{ name: 'files_changed', type: 'json' },
|
|
59
57
|
{ name: 'decisions_made', type: 'json' },
|
|
60
58
|
{ name: 'duration_minutes', type: 'number' },
|
|
61
|
-
{ name: 'task_id', type: 'text',
|
|
62
|
-
{ name: 'branch', type: 'text',
|
|
59
|
+
{ name: 'task_id', type: 'text', options: { maxSize: 100 } },
|
|
60
|
+
{ name: 'branch', type: 'text', options: { maxSize: 200 } },
|
|
63
61
|
{ name: 'patterns_learned', type: 'json' },
|
|
64
62
|
{ name: 'knowledge_created', type: 'number' },
|
|
65
63
|
{ name: 'knowledge_updated', type: 'number' },
|
|
66
64
|
{ name: 'metadata', type: 'json' },
|
|
67
|
-
{ name: 'created', type: 'autodate', onCreate: true },
|
|
68
|
-
{ name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
|
|
69
65
|
],
|
|
70
66
|
});
|
|
71
67
|
app.save(sessions);
|
|
72
68
|
}, (app) => {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
69
|
+
app.delete(app.findCollectionByNameOrId('sessions'));
|
|
70
|
+
app.delete(app.findCollectionByNameOrId('decisions'));
|
|
71
|
+
app.delete(app.findCollectionByNameOrId('knowledge'));
|
|
76
72
|
});
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PocketBase Migration 002: Goals and tasks
|
|
3
|
-
* PocketBase v0.23+ field format.
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
/// <reference path="../pb_data/types.d.ts" />
|
|
7
6
|
|
|
8
7
|
migrate((app) => {
|
|
8
|
+
// goals collection
|
|
9
9
|
const goals = new Collection({
|
|
10
10
|
name: 'goals',
|
|
11
11
|
type: 'base',
|
|
12
|
-
|
|
13
|
-
{ name: 'title', type: 'text', required: true,
|
|
14
|
-
{ name: 'description', type: 'editor', maxSize: 5000 },
|
|
15
|
-
{ name: 'timeframe', type: 'select', required: true,
|
|
16
|
-
{ name: 'status', type: 'select', required: true,
|
|
12
|
+
schema: [
|
|
13
|
+
{ name: 'title', type: 'text', required: true, options: { maxSize: 500 } },
|
|
14
|
+
{ name: 'description', type: 'editor', options: { maxSize: 5000 } },
|
|
15
|
+
{ name: 'timeframe', type: 'select', required: true, options: { values: ['daily', 'weekly', 'monthly', 'quarterly', 'yearly'] } },
|
|
16
|
+
{ name: 'status', type: 'select', required: true, options: { values: ['active', 'completed', 'paused', 'abandoned'] } },
|
|
17
17
|
{ name: 'key_results', type: 'json' },
|
|
18
18
|
{ name: 'tags', type: 'json' },
|
|
19
|
-
{ name: 'created', type: 'autodate', onCreate: true },
|
|
20
|
-
{ name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
|
|
21
19
|
],
|
|
22
20
|
indexes: [
|
|
23
21
|
'CREATE INDEX idx_goals_status ON goals (status)',
|
|
@@ -26,19 +24,18 @@ migrate((app) => {
|
|
|
26
24
|
});
|
|
27
25
|
app.save(goals);
|
|
28
26
|
|
|
27
|
+
// tasks collection
|
|
29
28
|
const tasks = new Collection({
|
|
30
29
|
name: 'tasks',
|
|
31
30
|
type: 'base',
|
|
32
|
-
|
|
33
|
-
{ name: 'title', type: 'text', required: true,
|
|
34
|
-
{ name: 'description', type: 'editor', maxSize: 5000 },
|
|
35
|
-
{ name: 'status', type: 'select', required: true,
|
|
36
|
-
{ name: 'priority', type: 'select', required: true,
|
|
31
|
+
schema: [
|
|
32
|
+
{ name: 'title', type: 'text', required: true, options: { maxSize: 500 } },
|
|
33
|
+
{ name: 'description', type: 'editor', options: { maxSize: 5000 } },
|
|
34
|
+
{ name: 'status', type: 'select', required: true, options: { values: ['todo', 'in_progress', 'done', 'cancelled'] } },
|
|
35
|
+
{ name: 'priority', type: 'select', required: true, options: { values: ['low', 'medium', 'high', 'urgent'] } },
|
|
37
36
|
{ name: 'due_date', type: 'date' },
|
|
38
37
|
{ name: 'tags', type: 'json' },
|
|
39
|
-
{ name: 'goal_id', type: 'text',
|
|
40
|
-
{ name: 'created', type: 'autodate', onCreate: true },
|
|
41
|
-
{ name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
|
|
38
|
+
{ name: 'goal_id', type: 'text', options: { maxSize: 100 } },
|
|
42
39
|
],
|
|
43
40
|
indexes: [
|
|
44
41
|
'CREATE INDEX idx_tasks_status ON tasks (status)',
|
|
@@ -47,6 +44,6 @@ migrate((app) => {
|
|
|
47
44
|
});
|
|
48
45
|
app.save(tasks);
|
|
49
46
|
}, (app) => {
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
app.delete(app.findCollectionByNameOrId('tasks'));
|
|
48
|
+
app.delete(app.findCollectionByNameOrId('goals'));
|
|
52
49
|
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PocketBase Migration 003: Contacts
|
|
3
|
-
* PocketBase v0.23+ field format.
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
/// <reference path="../pb_data/types.d.ts" />
|
|
@@ -9,18 +8,16 @@ migrate((app) => {
|
|
|
9
8
|
const contacts = new Collection({
|
|
10
9
|
name: 'contacts',
|
|
11
10
|
type: 'base',
|
|
12
|
-
|
|
13
|
-
{ name: 'name', type: 'text', required: true,
|
|
14
|
-
{ name: 'company', type: 'text',
|
|
15
|
-
{ name: 'role', type: 'text',
|
|
16
|
-
{ name: 'email', type: 'email' },
|
|
17
|
-
{ name: 'phone', type: 'text',
|
|
18
|
-
{ name: 'relationship', type: 'select',
|
|
19
|
-
{ name: 'notes', type: 'editor', maxSize: 5000 },
|
|
11
|
+
schema: [
|
|
12
|
+
{ name: 'name', type: 'text', required: true, options: { maxSize: 200 } },
|
|
13
|
+
{ name: 'company', type: 'text', options: { maxSize: 200 } },
|
|
14
|
+
{ name: 'role', type: 'text', options: { maxSize: 200 } },
|
|
15
|
+
{ name: 'email', type: 'email', options: { maxSize: 200 } },
|
|
16
|
+
{ name: 'phone', type: 'text', options: { maxSize: 50 } },
|
|
17
|
+
{ name: 'relationship', type: 'select', options: { values: ['colleague', 'client', 'prospect', 'partner', 'other'] } },
|
|
18
|
+
{ name: 'notes', type: 'editor', options: { maxSize: 5000 } },
|
|
20
19
|
{ name: 'tags', type: 'json' },
|
|
21
20
|
{ name: 'last_contact_date', type: 'date' },
|
|
22
|
-
{ name: 'created', type: 'autodate', onCreate: true },
|
|
23
|
-
{ name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
|
|
24
21
|
],
|
|
25
22
|
indexes: [
|
|
26
23
|
'CREATE INDEX idx_contacts_name ON contacts (name)',
|
|
@@ -28,5 +25,5 @@ migrate((app) => {
|
|
|
28
25
|
});
|
|
29
26
|
app.save(contacts);
|
|
30
27
|
}, (app) => {
|
|
31
|
-
|
|
28
|
+
app.delete(app.findCollectionByNameOrId('contacts'));
|
|
32
29
|
});
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PocketBase Migration 004: Entity aliases + settings
|
|
3
|
-
* PocketBase v0.23+ field format.
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
/// <reference path="../pb_data/types.d.ts" />
|
|
7
6
|
|
|
8
7
|
migrate((app) => {
|
|
8
|
+
// entity_aliases collection
|
|
9
9
|
const aliases = new Collection({
|
|
10
10
|
name: 'entity_aliases',
|
|
11
11
|
type: 'base',
|
|
12
|
-
|
|
13
|
-
{ name: 'canonical', type: 'text', required: true,
|
|
14
|
-
{ name: 'alias', type: 'text', required: true,
|
|
15
|
-
{ name: 'created', type: 'autodate', onCreate: true },
|
|
16
|
-
{ name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
|
|
12
|
+
schema: [
|
|
13
|
+
{ name: 'canonical', type: 'text', required: true, options: { maxSize: 100 } },
|
|
14
|
+
{ name: 'alias', type: 'text', required: true, options: { maxSize: 200 } },
|
|
17
15
|
],
|
|
18
16
|
indexes: [
|
|
19
17
|
'CREATE UNIQUE INDEX idx_entity_aliases_unique ON entity_aliases (canonical, alias)',
|
|
@@ -23,14 +21,13 @@ migrate((app) => {
|
|
|
23
21
|
});
|
|
24
22
|
app.save(aliases);
|
|
25
23
|
|
|
24
|
+
// settings collection
|
|
26
25
|
const settings = new Collection({
|
|
27
26
|
name: 'settings',
|
|
28
27
|
type: 'base',
|
|
29
|
-
|
|
30
|
-
{ name: 'key', type: 'text', required: true,
|
|
28
|
+
schema: [
|
|
29
|
+
{ name: 'key', type: 'text', required: true, options: { maxSize: 100 } },
|
|
31
30
|
{ name: 'value', type: 'editor' },
|
|
32
|
-
{ name: 'created', type: 'autodate', onCreate: true },
|
|
33
|
-
{ name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
|
|
34
31
|
],
|
|
35
32
|
indexes: [
|
|
36
33
|
'CREATE UNIQUE INDEX idx_settings_key ON settings (key)',
|
|
@@ -38,6 +35,6 @@ migrate((app) => {
|
|
|
38
35
|
});
|
|
39
36
|
app.save(settings);
|
|
40
37
|
}, (app) => {
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
app.delete(app.findCollectionByNameOrId('settings'));
|
|
39
|
+
app.delete(app.findCollectionByNameOrId('entity_aliases'));
|
|
43
40
|
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PocketBase Migration 005: Prospects (CRM)
|
|
3
|
-
* PocketBase v0.23+ field format.
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
/// <reference path="../pb_data/types.d.ts" />
|
|
@@ -9,22 +8,20 @@ migrate((app) => {
|
|
|
9
8
|
const prospects = new Collection({
|
|
10
9
|
name: 'prospects',
|
|
11
10
|
type: 'base',
|
|
12
|
-
|
|
13
|
-
{ name: 'name', type: 'text', required: true,
|
|
14
|
-
{ name: 'email', type: 'email' },
|
|
15
|
-
{ name: 'company', type: 'text',
|
|
16
|
-
{ name: 'role', type: 'text',
|
|
17
|
-
{ name: 'stage', type: 'select', required: true,
|
|
18
|
-
{ name: 'source', type: 'text',
|
|
11
|
+
schema: [
|
|
12
|
+
{ name: 'name', type: 'text', required: true, options: { maxSize: 200 } },
|
|
13
|
+
{ name: 'email', type: 'email', options: { maxSize: 200 } },
|
|
14
|
+
{ name: 'company', type: 'text', options: { maxSize: 200 } },
|
|
15
|
+
{ name: 'role', type: 'text', options: { maxSize: 200 } },
|
|
16
|
+
{ name: 'stage', type: 'select', required: true, options: { values: ['new', 'contacted', 'responded', 'interested', 'ready_to_buy', 'proposal_sent', 'negotiating', 'closed_won', 'closed_lost', 'nurturing'] } },
|
|
17
|
+
{ name: 'source', type: 'text', options: { maxSize: 200 } },
|
|
19
18
|
{ name: 'estimated_value', type: 'number' },
|
|
20
|
-
{ name: 'next_action_type', type: 'text',
|
|
19
|
+
{ name: 'next_action_type', type: 'text', options: { maxSize: 100 } },
|
|
21
20
|
{ name: 'next_followup_date', type: 'date' },
|
|
22
21
|
{ name: 'last_contact_date', type: 'date' },
|
|
23
|
-
{ name: 'notes', type: 'editor', maxSize: 10000 },
|
|
22
|
+
{ name: 'notes', type: 'editor', options: { maxSize: 10000 } },
|
|
24
23
|
{ name: 'tags', type: 'json' },
|
|
25
|
-
{ name: 'linkedin_url', type: 'url' },
|
|
26
|
-
{ name: 'created', type: 'autodate', onCreate: true },
|
|
27
|
-
{ name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
|
|
24
|
+
{ name: 'linkedin_url', type: 'url', options: { maxSize: 500 } },
|
|
28
25
|
],
|
|
29
26
|
indexes: [
|
|
30
27
|
'CREATE INDEX idx_prospects_stage ON prospects (stage)',
|
|
@@ -32,5 +29,5 @@ migrate((app) => {
|
|
|
32
29
|
});
|
|
33
30
|
app.save(prospects);
|
|
34
31
|
}, (app) => {
|
|
35
|
-
|
|
32
|
+
app.delete(app.findCollectionByNameOrId('prospects'));
|
|
36
33
|
});
|
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PocketBase Migration 006: Business collections (blog, email, content)
|
|
3
|
-
* PocketBase v0.23+ field format.
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
/// <reference path="../pb_data/types.d.ts" />
|
|
7
6
|
|
|
8
7
|
migrate((app) => {
|
|
8
|
+
// blog_posts collection
|
|
9
9
|
const blogPosts = new Collection({
|
|
10
10
|
name: 'blog_posts',
|
|
11
11
|
type: 'base',
|
|
12
|
-
|
|
13
|
-
{ name: 'title', type: 'text', required: true,
|
|
14
|
-
{ name: 'slug', type: 'text', required: true,
|
|
12
|
+
schema: [
|
|
13
|
+
{ name: 'title', type: 'text', required: true, options: { maxSize: 500 } },
|
|
14
|
+
{ name: 'slug', type: 'text', required: true, options: { maxSize: 200 } },
|
|
15
15
|
{ name: 'content', type: 'editor', required: true },
|
|
16
|
-
{ name: 'excerpt', type: 'text',
|
|
17
|
-
{ name: 'status', type: 'select', required: true,
|
|
16
|
+
{ name: 'excerpt', type: 'text', options: { maxSize: 500 } },
|
|
17
|
+
{ name: 'status', type: 'select', required: true, options: { values: ['draft', 'published', 'archived'] } },
|
|
18
18
|
{ name: 'published_at', type: 'date' },
|
|
19
19
|
{ name: 'tags', type: 'json' },
|
|
20
|
-
{ name: 'seo_title', type: 'text',
|
|
21
|
-
{ name: 'seo_description', type: 'text',
|
|
22
|
-
{ name: 'og_image_url', type: 'url' },
|
|
23
|
-
{ name: 'created', type: 'autodate', onCreate: true },
|
|
24
|
-
{ name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
|
|
20
|
+
{ name: 'seo_title', type: 'text', options: { maxSize: 200 } },
|
|
21
|
+
{ name: 'seo_description', type: 'text', options: { maxSize: 300 } },
|
|
22
|
+
{ name: 'og_image_url', type: 'url', options: { maxSize: 500 } },
|
|
25
23
|
],
|
|
26
24
|
indexes: [
|
|
27
25
|
'CREATE UNIQUE INDEX idx_blog_posts_slug ON blog_posts (slug)',
|
|
@@ -30,25 +28,24 @@ migrate((app) => {
|
|
|
30
28
|
});
|
|
31
29
|
app.save(blogPosts);
|
|
32
30
|
|
|
31
|
+
// email_queue collection
|
|
33
32
|
const emailQueue = new Collection({
|
|
34
33
|
name: 'email_queue',
|
|
35
34
|
type: 'base',
|
|
36
|
-
|
|
37
|
-
{ name: 'to_email', type: 'email', required: true },
|
|
38
|
-
{ name: 'to_name', type: 'text',
|
|
39
|
-
{ name: 'subject', type: 'text', required: true,
|
|
35
|
+
schema: [
|
|
36
|
+
{ name: 'to_email', type: 'email', required: true, options: { maxSize: 200 } },
|
|
37
|
+
{ name: 'to_name', type: 'text', options: { maxSize: 200 } },
|
|
38
|
+
{ name: 'subject', type: 'text', required: true, options: { maxSize: 500 } },
|
|
40
39
|
{ name: 'body_html', type: 'editor', required: true },
|
|
41
40
|
{ name: 'body_text', type: 'editor' },
|
|
42
|
-
{ name: 'status', type: 'select', required: true,
|
|
43
|
-
{ name: 'sequence_id', type: 'text',
|
|
41
|
+
{ name: 'status', type: 'select', required: true, options: { values: ['queued', 'sent', 'failed', 'bounced'] } },
|
|
42
|
+
{ name: 'sequence_id', type: 'text', options: { maxSize: 100 } },
|
|
44
43
|
{ name: 'sequence_step', type: 'number' },
|
|
45
|
-
{ name: 'prospect_id', type: 'text',
|
|
44
|
+
{ name: 'prospect_id', type: 'text', options: { maxSize: 100 } },
|
|
46
45
|
{ name: 'scheduled_at', type: 'date' },
|
|
47
46
|
{ name: 'sent_at', type: 'date' },
|
|
48
47
|
{ name: 'error', type: 'editor' },
|
|
49
|
-
{ name: 'resend_id', type: 'text',
|
|
50
|
-
{ name: 'created', type: 'autodate', onCreate: true },
|
|
51
|
-
{ name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
|
|
48
|
+
{ name: 'resend_id', type: 'text', options: { maxSize: 200 } },
|
|
52
49
|
],
|
|
53
50
|
indexes: [
|
|
54
51
|
'CREATE INDEX idx_email_queue_status ON email_queue (status)',
|
|
@@ -56,20 +53,19 @@ migrate((app) => {
|
|
|
56
53
|
});
|
|
57
54
|
app.save(emailQueue);
|
|
58
55
|
|
|
56
|
+
// content_calendar collection
|
|
59
57
|
const contentCalendar = new Collection({
|
|
60
58
|
name: 'content_calendar',
|
|
61
59
|
type: 'base',
|
|
62
|
-
|
|
63
|
-
{ name: 'title', type: 'text', required: true,
|
|
60
|
+
schema: [
|
|
61
|
+
{ name: 'title', type: 'text', required: true, options: { maxSize: 500 } },
|
|
64
62
|
{ name: 'content', type: 'editor' },
|
|
65
|
-
{ name: 'platform', type: 'select', required: true,
|
|
66
|
-
{ name: 'pillar', type: 'text',
|
|
67
|
-
{ name: 'status', type: 'select', required: true,
|
|
63
|
+
{ name: 'platform', type: 'select', required: true, options: { values: ['linkedin', 'newsletter', 'blog', 'twitter', 'other'] } },
|
|
64
|
+
{ name: 'pillar', type: 'text', options: { maxSize: 100 } },
|
|
65
|
+
{ name: 'status', type: 'select', required: true, options: { values: ['idea', 'drafting', 'ready', 'published'] } },
|
|
68
66
|
{ name: 'scheduled_date', type: 'date' },
|
|
69
|
-
{ name: 'published_url', type: 'url' },
|
|
70
|
-
{ name: 'persona', type: 'text',
|
|
71
|
-
{ name: 'created', type: 'autodate', onCreate: true },
|
|
72
|
-
{ name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
|
|
67
|
+
{ name: 'published_url', type: 'url', options: { maxSize: 500 } },
|
|
68
|
+
{ name: 'persona', type: 'text', options: { maxSize: 100 } },
|
|
73
69
|
],
|
|
74
70
|
indexes: [
|
|
75
71
|
'CREATE INDEX idx_content_calendar_platform ON content_calendar (platform)',
|
|
@@ -78,7 +74,7 @@ migrate((app) => {
|
|
|
78
74
|
});
|
|
79
75
|
app.save(contentCalendar);
|
|
80
76
|
}, (app) => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
app.delete(app.findCollectionByNameOrId('content_calendar'));
|
|
78
|
+
app.delete(app.findCollectionByNameOrId('email_queue'));
|
|
79
|
+
app.delete(app.findCollectionByNameOrId('blog_posts'));
|
|
84
80
|
});
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PocketBase Migration 007: Newsletter subscribers and affiliates
|
|
3
|
-
* PocketBase v0.23+ field format.
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
/// <reference path="../pb_data/types.d.ts" />
|
|
7
6
|
|
|
8
7
|
migrate((app) => {
|
|
8
|
+
// newsletter_subscribers collection
|
|
9
9
|
const subscribers = new Collection({
|
|
10
10
|
name: 'newsletter_subscribers',
|
|
11
11
|
type: 'base',
|
|
12
|
-
|
|
13
|
-
{ name: 'email', type: 'email', required: true },
|
|
14
|
-
{ name: 'name', type: 'text',
|
|
15
|
-
{ name: 'status', type: 'select', required: true,
|
|
16
|
-
{ name: 'source', type: 'text',
|
|
12
|
+
schema: [
|
|
13
|
+
{ name: 'email', type: 'email', required: true, options: { maxSize: 200 } },
|
|
14
|
+
{ name: 'name', type: 'text', options: { maxSize: 200 } },
|
|
15
|
+
{ name: 'status', type: 'select', required: true, options: { values: ['active', 'unsubscribed', 'bounced'] } },
|
|
16
|
+
{ name: 'source', type: 'text', options: { maxSize: 200 } },
|
|
17
17
|
{ name: 'tags', type: 'json' },
|
|
18
18
|
{ name: 'subscribed_at', type: 'date' },
|
|
19
19
|
{ name: 'unsubscribed_at', type: 'date' },
|
|
20
|
-
{ name: 'created', type: 'autodate', onCreate: true },
|
|
21
|
-
{ name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
|
|
22
20
|
],
|
|
23
21
|
indexes: [
|
|
24
22
|
'CREATE UNIQUE INDEX idx_newsletter_subscribers_email ON newsletter_subscribers (email)',
|
|
@@ -26,20 +24,19 @@ migrate((app) => {
|
|
|
26
24
|
});
|
|
27
25
|
app.save(subscribers);
|
|
28
26
|
|
|
27
|
+
// affiliates collection
|
|
29
28
|
const affiliates = new Collection({
|
|
30
29
|
name: 'affiliates',
|
|
31
30
|
type: 'base',
|
|
32
|
-
|
|
33
|
-
{ name: 'name', type: 'text', required: true,
|
|
34
|
-
{ name: 'email', type: 'email', required: true },
|
|
35
|
-
{ name: 'code', type: 'text', required: true,
|
|
36
|
-
{ name: 'commission_rate', type: 'number', min: 0, max: 1 },
|
|
37
|
-
{ name: 'status', type: 'select', required: true,
|
|
31
|
+
schema: [
|
|
32
|
+
{ name: 'name', type: 'text', required: true, options: { maxSize: 200 } },
|
|
33
|
+
{ name: 'email', type: 'email', required: true, options: { maxSize: 200 } },
|
|
34
|
+
{ name: 'code', type: 'text', required: true, options: { maxSize: 100 } },
|
|
35
|
+
{ name: 'commission_rate', type: 'number', options: { min: 0, max: 1 } },
|
|
36
|
+
{ name: 'status', type: 'select', required: true, options: { values: ['pending', 'active', 'paused', 'terminated'] } },
|
|
38
37
|
{ name: 'total_earned_cents', type: 'number' },
|
|
39
38
|
{ name: 'total_paid_cents', type: 'number' },
|
|
40
|
-
{ name: 'stripe_account_id', type: 'text',
|
|
41
|
-
{ name: 'created', type: 'autodate', onCreate: true },
|
|
42
|
-
{ name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
|
|
39
|
+
{ name: 'stripe_account_id', type: 'text', options: { maxSize: 200 } },
|
|
43
40
|
],
|
|
44
41
|
indexes: [
|
|
45
42
|
'CREATE UNIQUE INDEX idx_affiliates_email ON affiliates (email)',
|
|
@@ -48,6 +45,6 @@ migrate((app) => {
|
|
|
48
45
|
});
|
|
49
46
|
app.save(affiliates);
|
|
50
47
|
}, (app) => {
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
app.delete(app.findCollectionByNameOrId('affiliates'));
|
|
49
|
+
app.delete(app.findCollectionByNameOrId('newsletter_subscribers'));
|
|
53
50
|
});
|
|
@@ -1,41 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PocketBase
|
|
2
|
+
* PocketBase migration: knowledge_links collection
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* text search fallback.
|
|
4
|
+
* Creates the knowledge_links collection for typed relationships
|
|
5
|
+
* between MemoryOS entities.
|
|
7
6
|
*
|
|
8
|
-
* PocketBase
|
|
9
|
-
*
|
|
7
|
+
* Note: PocketBase does not support pgvector.
|
|
8
|
+
* Link suggestions use keyword-based text search fallback.
|
|
10
9
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
if (links) app.delete(links);
|
|
41
|
-
});
|
|
10
|
+
module.exports = {
|
|
11
|
+
async up(db) {
|
|
12
|
+
const collection = new Collection({
|
|
13
|
+
name: 'knowledge_links',
|
|
14
|
+
type: 'base',
|
|
15
|
+
schema: [
|
|
16
|
+
{ name: 'owner_id', type: 'text', required: true, options: { maxSize: 100 } },
|
|
17
|
+
{ name: 'source_type', type: 'text', required: true, options: { maxSize: 50 } },
|
|
18
|
+
{ name: 'source_id', type: 'text', required: true, options: { maxSize: 36 } },
|
|
19
|
+
{ name: 'target_type', type: 'text', required: true, options: { maxSize: 50 } },
|
|
20
|
+
{ name: 'target_id', type: 'text', required: true, options: { maxSize: 36 } },
|
|
21
|
+
{ name: 'relation_type', type: 'text', required: true, options: { maxSize: 50 } },
|
|
22
|
+
{ name: 'confidence', type: 'number', options: { min: 0, max: 1 } },
|
|
23
|
+
{ name: 'notes', type: 'text', options: { maxSize: 500 } },
|
|
24
|
+
{ name: 'auto_suggested', type: 'bool' },
|
|
25
|
+
],
|
|
26
|
+
indexes: [
|
|
27
|
+
'CREATE INDEX idx_kl_source ON knowledge_links (owner_id, source_type, source_id)',
|
|
28
|
+
'CREATE INDEX idx_kl_target ON knowledge_links (owner_id, target_type, target_id)',
|
|
29
|
+
'CREATE UNIQUE INDEX idx_kl_unique ON knowledge_links (owner_id, source_type, source_id, target_type, target_id, relation_type)',
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
return db.save(collection);
|
|
33
|
+
},
|
|
34
|
+
async down(db) {
|
|
35
|
+
const collection = await db.findCollectionByNameOrId('knowledge_links');
|
|
36
|
+
return db.delete(collection);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -8,14 +8,12 @@
|
|
|
8
8
|
-- 5. Add decisions-specific columns: rationale, outcome_rating, session_id
|
|
9
9
|
-- 6. Make decisions.options_considered nullable (production data shows this)
|
|
10
10
|
-- 7. Make goals.timeframe nullable (not always known at creation time)
|
|
11
|
-
--
|
|
12
|
-
-- Each `text[] -> jsonb` conversion must DROP DEFAULT first, change TYPE, then
|
|
13
|
-
-- SET a jsonb DEFAULT. Postgres cannot auto-cast the text[] default '{}' to jsonb.
|
|
14
11
|
|
|
15
12
|
-- === KNOWLEDGE ===
|
|
16
|
-
|
|
13
|
+
-- Convert tags from text[] to jsonb
|
|
17
14
|
ALTER TABLE knowledge ALTER COLUMN tags TYPE jsonb USING to_jsonb(tags);
|
|
18
15
|
ALTER TABLE knowledge ALTER COLUMN tags SET DEFAULT '[]'::jsonb;
|
|
16
|
+
-- Add missing columns
|
|
19
17
|
ALTER TABLE knowledge ADD COLUMN IF NOT EXISTS owner_id text NOT NULL DEFAULT 'default';
|
|
20
18
|
ALTER TABLE knowledge ADD COLUMN IF NOT EXISTS metadata jsonb DEFAULT '{}'::jsonb;
|
|
21
19
|
ALTER TABLE knowledge ADD COLUMN IF NOT EXISTS source_file text;
|
|
@@ -23,13 +21,14 @@ ALTER TABLE knowledge ADD COLUMN IF NOT EXISTS decay_score numeric;
|
|
|
23
21
|
ALTER TABLE knowledge ADD COLUMN IF NOT EXISTS triggers jsonb;
|
|
24
22
|
|
|
25
23
|
-- === DECISIONS ===
|
|
26
|
-
|
|
24
|
+
-- Convert options_considered from text[] to jsonb
|
|
27
25
|
ALTER TABLE decisions ALTER COLUMN options_considered TYPE jsonb USING to_jsonb(options_considered);
|
|
28
26
|
ALTER TABLE decisions ALTER COLUMN options_considered DROP NOT NULL;
|
|
29
27
|
ALTER TABLE decisions ALTER COLUMN options_considered SET DEFAULT '[]'::jsonb;
|
|
30
|
-
|
|
28
|
+
-- Convert tags from text[] to jsonb
|
|
31
29
|
ALTER TABLE decisions ALTER COLUMN tags TYPE jsonb USING to_jsonb(tags);
|
|
32
30
|
ALTER TABLE decisions ALTER COLUMN tags SET DEFAULT '[]'::jsonb;
|
|
31
|
+
-- Add missing columns
|
|
33
32
|
ALTER TABLE decisions ADD COLUMN IF NOT EXISTS owner_id text NOT NULL DEFAULT 'default';
|
|
34
33
|
ALTER TABLE decisions ADD COLUMN IF NOT EXISTS metadata jsonb DEFAULT '{}'::jsonb;
|
|
35
34
|
ALTER TABLE decisions ADD COLUMN IF NOT EXISTS rationale text;
|
|
@@ -37,33 +36,37 @@ ALTER TABLE decisions ADD COLUMN IF NOT EXISTS outcome_rating text;
|
|
|
37
36
|
ALTER TABLE decisions ADD COLUMN IF NOT EXISTS session_id uuid;
|
|
38
37
|
|
|
39
38
|
-- === SESSIONS ===
|
|
40
|
-
|
|
39
|
+
-- Convert skills_used, files_changed from text[] to jsonb
|
|
41
40
|
ALTER TABLE sessions ALTER COLUMN skills_used TYPE jsonb USING to_jsonb(skills_used);
|
|
42
41
|
ALTER TABLE sessions ALTER COLUMN skills_used SET DEFAULT '[]'::jsonb;
|
|
43
|
-
ALTER TABLE sessions ALTER COLUMN files_changed DROP DEFAULT;
|
|
44
42
|
ALTER TABLE sessions ALTER COLUMN files_changed TYPE jsonb USING to_jsonb(files_changed);
|
|
45
43
|
ALTER TABLE sessions ALTER COLUMN files_changed SET DEFAULT '[]'::jsonb;
|
|
44
|
+
-- Add missing columns
|
|
46
45
|
ALTER TABLE sessions ADD COLUMN IF NOT EXISTS owner_id text NOT NULL DEFAULT 'default';
|
|
47
46
|
|
|
48
47
|
-- === GOALS ===
|
|
49
|
-
|
|
48
|
+
-- Convert tags from text[] to jsonb
|
|
50
49
|
ALTER TABLE goals ALTER COLUMN tags TYPE jsonb USING to_jsonb(tags);
|
|
51
50
|
ALTER TABLE goals ALTER COLUMN tags SET DEFAULT '[]'::jsonb;
|
|
51
|
+
-- Make timeframe nullable (not always known)
|
|
52
52
|
ALTER TABLE goals ALTER COLUMN timeframe DROP NOT NULL;
|
|
53
|
+
-- Add missing columns
|
|
53
54
|
ALTER TABLE goals ADD COLUMN IF NOT EXISTS owner_id text NOT NULL DEFAULT 'default';
|
|
54
55
|
ALTER TABLE goals ADD COLUMN IF NOT EXISTS metadata jsonb DEFAULT '{}'::jsonb;
|
|
55
56
|
|
|
56
57
|
-- === TASKS ===
|
|
57
|
-
|
|
58
|
+
-- Convert tags from text[] to jsonb
|
|
58
59
|
ALTER TABLE tasks ALTER COLUMN tags TYPE jsonb USING to_jsonb(tags);
|
|
59
60
|
ALTER TABLE tasks ALTER COLUMN tags SET DEFAULT '[]'::jsonb;
|
|
61
|
+
-- Add missing columns
|
|
60
62
|
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS owner_id text NOT NULL DEFAULT 'default';
|
|
61
63
|
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS metadata jsonb DEFAULT '{}'::jsonb;
|
|
62
64
|
|
|
63
65
|
-- === CONTACTS ===
|
|
64
|
-
|
|
66
|
+
-- Convert tags from text[] to jsonb
|
|
65
67
|
ALTER TABLE contacts ALTER COLUMN tags TYPE jsonb USING to_jsonb(tags);
|
|
66
68
|
ALTER TABLE contacts ALTER COLUMN tags SET DEFAULT '[]'::jsonb;
|
|
69
|
+
-- Add missing columns
|
|
67
70
|
ALTER TABLE contacts ADD COLUMN IF NOT EXISTS owner_id text NOT NULL DEFAULT 'default';
|
|
68
71
|
ALTER TABLE contacts ADD COLUMN IF NOT EXISTS metadata jsonb DEFAULT '{}'::jsonb;
|
|
69
72
|
ALTER TABLE contacts ADD COLUMN IF NOT EXISTS last_interaction_at timestamptz;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iwo-szapar/data-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Unified data MCP server for Second Brain — PocketBase and Supabase adapters. 40 tools: knowledge, sessions, goals, tasks, contacts, CRM, blog, email, content calendar.",
|
|
5
5
|
"author": "Iwo Szapar <iwo.szapar@gmail.com> (https://iwoszapar.com)",
|
|
6
6
|
"homepage": "https://iwoszapar.com/second-brain-ai",
|
package/README.md
DELETED
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
# @iwo-szapar/data-mcp
|
|
2
|
-
|
|
3
|
-
Unified data MCP server for [Second Brain](https://iwoszapar.com/second-brain-ai). One MCP, two backends: PocketBase (local, free) or Supabase (cloud, multi-device).
|
|
4
|
-
|
|
5
|
-
40 tools across knowledge, sessions, goals, tasks, contacts, CRM prospects, blog, email queue, and content calendar. Used in production by Second Brain v2 customers.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Install
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm install -g @iwo-szapar/data-mcp
|
|
13
|
-
# or run on demand
|
|
14
|
-
npx @iwo-szapar/data-mcp
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Requires Node.js `>=20`.
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Quick start — PocketBase (local)
|
|
22
|
-
|
|
23
|
-
PocketBase runs on your laptop. Good for single-device workflows. Stops when you close the terminal.
|
|
24
|
-
|
|
25
|
-
1. **Install PocketBase** ([pocketbase.io](https://pocketbase.io)) and start it:
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
./pocketbase serve
|
|
29
|
-
# Admin UI: http://127.0.0.1:8090/_/
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
2. **Create an admin account** via the Admin UI on first run.
|
|
33
|
-
|
|
34
|
-
3. **Apply the schema migrations** (required — the MCP does NOT apply them automatically):
|
|
35
|
-
|
|
36
|
-
Copy the files in `migrations/pocketbase/` (shipped with this package) into your PocketBase instance's `pb_migrations/` directory, then run:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
./pocketbase migrate up
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
This creates all 14 collections (`knowledge`, `decisions`, `sessions`, `goals`, `tasks`, `contacts`, `entity_aliases`, `settings`, `prospects`, `blog_posts`, `email_queue`, `content_calendar`, `newsletter_subscribers`, `affiliates`).
|
|
43
|
-
|
|
44
|
-
4. **Configure your MCP client** (Claude Code, Claude Desktop, Cursor, etc.):
|
|
45
|
-
|
|
46
|
-
```json
|
|
47
|
-
{
|
|
48
|
-
"mcpServers": {
|
|
49
|
-
"data-mcp": {
|
|
50
|
-
"command": "npx",
|
|
51
|
-
"args": ["-y", "@iwo-szapar/data-mcp"],
|
|
52
|
-
"env": {
|
|
53
|
-
"SB_BACKEND": "pocketbase",
|
|
54
|
-
"SB_POCKETBASE_URL": "http://127.0.0.1:8090",
|
|
55
|
-
"SB_POCKETBASE_ADMIN_EMAIL": "you@example.com",
|
|
56
|
-
"SB_POCKETBASE_ADMIN_PASSWORD": "your-admin-password"
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
5. **Verify**: in your MCP client, call the `setup_status` tool. It reports which collections exist and flags any missing ones.
|
|
64
|
-
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
## Quick start — Supabase (cloud, multi-device)
|
|
68
|
-
|
|
69
|
-
Supabase is a hosted Postgres. Runs 24/7, reachable from any device. Good for multi-device setups and phone-friendly workflows.
|
|
70
|
-
|
|
71
|
-
1. **Create a Supabase project** at [supabase.com](https://supabase.com). Note the Project URL and `service_role` key (Settings → API).
|
|
72
|
-
|
|
73
|
-
2. **Apply the SQL migrations** via the SQL editor or the Supabase CLI:
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
# Using the Supabase CLI
|
|
77
|
-
for f in migrations/supabase/*.sql; do
|
|
78
|
-
psql "$SUPABASE_DB_URL" -f "$f"
|
|
79
|
-
done
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Apply them in order `001` through `010`. The MCP does NOT apply them automatically.
|
|
83
|
-
|
|
84
|
-
3. **Configure your MCP client**:
|
|
85
|
-
|
|
86
|
-
```json
|
|
87
|
-
{
|
|
88
|
-
"mcpServers": {
|
|
89
|
-
"data-mcp": {
|
|
90
|
-
"command": "npx",
|
|
91
|
-
"args": ["-y", "@iwo-szapar/data-mcp"],
|
|
92
|
-
"env": {
|
|
93
|
-
"SB_BACKEND": "supabase",
|
|
94
|
-
"SB_SUPABASE_URL": "https://YOUR_PROJECT.supabase.co",
|
|
95
|
-
"SB_SUPABASE_KEY": "your-service-role-key"
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Use the `service_role` key, not `anon`. The MCP needs full access.
|
|
103
|
-
|
|
104
|
-
4. **Verify** with `setup_status`.
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## Environment variables
|
|
109
|
-
|
|
110
|
-
| Variable | Required | Applies to | Description |
|
|
111
|
-
|---|---|---|---|
|
|
112
|
-
| `SB_BACKEND` | yes | both | `pocketbase` or `supabase` |
|
|
113
|
-
| `SB_POCKETBASE_URL` | yes (PB) | pocketbase | e.g. `http://127.0.0.1:8090` |
|
|
114
|
-
| `SB_POCKETBASE_ADMIN_EMAIL` | yes (PB) | pocketbase | PocketBase admin email |
|
|
115
|
-
| `SB_POCKETBASE_ADMIN_PASSWORD` | yes (PB) | pocketbase | PocketBase admin password |
|
|
116
|
-
| `SB_SUPABASE_URL` | yes (SB) | supabase | Project URL |
|
|
117
|
-
| `SB_SUPABASE_KEY` | yes (SB) | supabase | `service_role` key |
|
|
118
|
-
| `SB_SCHEMA_MAP` | no | both | JSON object mapping logical names to real table names (e.g. `{"prospects":"my_leads"}`) |
|
|
119
|
-
| `SB_RESEND_API_KEY` | no | both | Resend key for email tooling (optional) |
|
|
120
|
-
|
|
121
|
-
Missing any required var on startup → the server exits with `Missing required environment variable: SB_XXX`.
|
|
122
|
-
|
|
123
|
-
---
|
|
124
|
-
|
|
125
|
-
## Tool reference (40 tools)
|
|
126
|
-
|
|
127
|
-
All tools return JSON. Every tool uses *graceful degradation*: if the required table doesn't exist, the tool returns a clear error asking you to apply migrations instead of crashing.
|
|
128
|
-
|
|
129
|
-
### Knowledge (8)
|
|
130
|
-
|
|
131
|
-
| Tool | Purpose |
|
|
132
|
-
|---|---|
|
|
133
|
-
| `knowledge_store` | Store a fact / pattern / insight / lesson / reference. Dedup by `(type, title)`. |
|
|
134
|
-
| `knowledge_recall` | Search knowledge by query, tags, or type. |
|
|
135
|
-
| `knowledge_learn` | Shortcut for storing a `lesson`. |
|
|
136
|
-
| `knowledge_decide` | Record a decision with context, options, chosen option, and rationale (writes to `decisions`). |
|
|
137
|
-
| `knowledge_validate` | Mark an item as freshly validated (resets `last_validated_at`). |
|
|
138
|
-
| `knowledge_update` | Update title / content / tags on an existing item. |
|
|
139
|
-
| `knowledge_delete` | Delete a knowledge item by ID. |
|
|
140
|
-
| `knowledge_list` | List or filter knowledge items. |
|
|
141
|
-
|
|
142
|
-
### Sessions (2)
|
|
143
|
-
|
|
144
|
-
| Tool | Purpose |
|
|
145
|
-
|---|---|
|
|
146
|
-
| `session_log` | Log a completed work session with skills used, files changed, decisions made. |
|
|
147
|
-
| `session_list` | List recent sessions. |
|
|
148
|
-
|
|
149
|
-
### Goals (3)
|
|
150
|
-
|
|
151
|
-
| Tool | Purpose |
|
|
152
|
-
|---|---|
|
|
153
|
-
| `goal_create` / `goal_update` / `goal_list` | Track goals with key results. |
|
|
154
|
-
|
|
155
|
-
### Tasks (3)
|
|
156
|
-
|
|
157
|
-
| Tool | Purpose |
|
|
158
|
-
|---|---|
|
|
159
|
-
| `task_create` / `task_update` / `task_list` | Task management with status and priority. |
|
|
160
|
-
|
|
161
|
-
### Contacts (4)
|
|
162
|
-
|
|
163
|
-
| Tool | Purpose |
|
|
164
|
-
|---|---|
|
|
165
|
-
| `contact_create` / `contact_update` / `contact_list` / `contact_search` | Contact records with relationship type and tags. |
|
|
166
|
-
|
|
167
|
-
### Brain health (2)
|
|
168
|
-
|
|
169
|
-
| Tool | Purpose |
|
|
170
|
-
|---|---|
|
|
171
|
-
| `brain_stats` | Aggregate counts across collections and knowledge-type distribution. |
|
|
172
|
-
| `brain_decay` | Find stale knowledge items (not validated recently). |
|
|
173
|
-
|
|
174
|
-
### Knowledge links (4)
|
|
175
|
-
|
|
176
|
-
| Tool | Purpose |
|
|
177
|
-
|---|---|
|
|
178
|
-
| `link_create` / `link_delete` / `link_related` / `link_suggest` | Graph-lite relationships between knowledge items. |
|
|
179
|
-
|
|
180
|
-
### Setup (3)
|
|
181
|
-
|
|
182
|
-
| Tool | Purpose |
|
|
183
|
-
|---|---|
|
|
184
|
-
| `setup_status` | Report which collections exist. **Run this first** after installation. |
|
|
185
|
-
| `setup_migrate` | **Reports** missing collections and points to the migration files. Does **not** apply migrations automatically — you must run them via PocketBase CLI or `psql`. |
|
|
186
|
-
| `setup_seed` | Load seed data (e.g. `entity_aliases` for search). |
|
|
187
|
-
|
|
188
|
-
### CRM prospects (4)
|
|
189
|
-
|
|
190
|
-
| Tool | Purpose |
|
|
191
|
-
|---|---|
|
|
192
|
-
| `prospect_create` / `prospect_update` / `prospect_list` / `prospect_search` | Sales pipeline. Stages: `new → contacted → responded → interested → ready_to_buy → proposal_sent → negotiating → closed_won / closed_lost / nurturing`. |
|
|
193
|
-
|
|
194
|
-
### Blog (4)
|
|
195
|
-
|
|
196
|
-
| Tool | Purpose |
|
|
197
|
-
|---|---|
|
|
198
|
-
| `blog_create` / `blog_update` / `blog_list` / `blog_delete` | Blog post content management. |
|
|
199
|
-
|
|
200
|
-
### Email + content queues (3)
|
|
201
|
-
|
|
202
|
-
| Tool | Purpose |
|
|
203
|
-
|---|---|
|
|
204
|
-
| `email_queue_add` | Queue an email (does NOT send — sending is done out-of-band). |
|
|
205
|
-
| `content_queue_add` / `content_queue_list` | Content calendar for scheduled posts. |
|
|
206
|
-
|
|
207
|
-
---
|
|
208
|
-
|
|
209
|
-
## Common failures (and how to recover)
|
|
210
|
-
|
|
211
|
-
### "The 'X' table does not exist yet. Run setup_migrate to create the database schema."
|
|
212
|
-
|
|
213
|
-
**What it means:** The collection backing this tool hasn't been created.
|
|
214
|
-
|
|
215
|
-
**Fix:** `setup_migrate` only *reports* missing tables — it does not apply them. You need to run the actual migrations:
|
|
216
|
-
|
|
217
|
-
- **PocketBase:** `./pocketbase migrate up` (after copying the files in `migrations/pocketbase/` into your `pb_migrations/` directory).
|
|
218
|
-
- **Supabase:** run each file in `migrations/supabase/` in order via the SQL editor or `psql`.
|
|
219
|
-
|
|
220
|
-
Then call `setup_status` to confirm.
|
|
221
|
-
|
|
222
|
-
### "Only knowledge tools work, everything else fails"
|
|
223
|
-
|
|
224
|
-
**Symptom:** `knowledge_store` and `knowledge_recall` succeed but `goal_create`, `task_create`, `contact_create` all return the "table does not exist" error.
|
|
225
|
-
|
|
226
|
-
**Cause:** You applied only the first migration (`001_core_schema`) which creates `knowledge`, `decisions`, and `sessions`. The rest of the collections come from migrations `002` through `010` (Supabase) or `002` through `008` (PocketBase).
|
|
227
|
-
|
|
228
|
-
**Fix:** Apply all migrations in order.
|
|
229
|
-
|
|
230
|
-
### PocketBase disconnects between terminal sessions
|
|
231
|
-
|
|
232
|
-
**Cause:** `pocketbase serve` runs in the foreground. When you close the terminal, the server stops.
|
|
233
|
-
|
|
234
|
-
**Fix options:**
|
|
235
|
-
- Run PocketBase under a process manager (pm2, forever) or a launchd plist on macOS.
|
|
236
|
-
- Switch to the Supabase backend — it runs 24/7 in the cloud.
|
|
237
|
-
|
|
238
|
-
### MCP server disconnected after Claude Code restart
|
|
239
|
-
|
|
240
|
-
**Cause:** Your MCP client is not reading the server config on startup, or the `npx -y` download got interrupted.
|
|
241
|
-
|
|
242
|
-
**Fix:** Install globally once (`npm install -g @iwo-szapar/data-mcp`) and point `command` at `data-mcp` instead of `npx`. Restart your MCP client.
|
|
243
|
-
|
|
244
|
-
### "Database authentication failed"
|
|
245
|
-
|
|
246
|
-
**PocketBase:** check `SB_POCKETBASE_ADMIN_EMAIL` / `PASSWORD` match an admin account in the Admin UI.
|
|
247
|
-
|
|
248
|
-
**Supabase:** confirm you are using the `service_role` key, not `anon`. The anon key does not have write access to these tables.
|
|
249
|
-
|
|
250
|
-
---
|
|
251
|
-
|
|
252
|
-
## Schema mapping (optional)
|
|
253
|
-
|
|
254
|
-
If your real tables have different names, set `SB_SCHEMA_MAP` to a JSON object:
|
|
255
|
-
|
|
256
|
-
```bash
|
|
257
|
-
SB_SCHEMA_MAP='{"prospects":"sales_leads","contacts":"people"}'
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
Logical names used by the tools (`prospects`, `contacts`, etc.) are transparently rewritten to your real table names. Empty keys or missing keys pass through unchanged.
|
|
261
|
-
|
|
262
|
-
---
|
|
263
|
-
|
|
264
|
-
## File layout
|
|
265
|
-
|
|
266
|
-
```
|
|
267
|
-
dist/ compiled JS (entry: dist/index.js)
|
|
268
|
-
migrations/
|
|
269
|
-
pocketbase/ *.js migration files (PocketBase migrate format)
|
|
270
|
-
supabase/ *.sql migration files (run in order)
|
|
271
|
-
seed/ seed data (entity_aliases.json, etc.)
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
The published package ships `dist/`, `migrations/`, `seed/`.
|
|
275
|
-
|
|
276
|
-
---
|
|
277
|
-
|
|
278
|
-
## License
|
|
279
|
-
|
|
280
|
-
MIT
|
|
281
|
-
|
|
282
|
-
---
|
|
283
|
-
|
|
284
|
-
## Support
|
|
285
|
-
|
|
286
|
-
This package is maintained by [Iwo Szapar](https://iwoszapar.com) as part of the Second Brain ecosystem. For issues specific to Second Brain v2 customers, reply to your purchase confirmation email. For general bugs, open an issue against the package on npm.
|