@twelvehart/supermemory-runtime 1.0.0-next.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.
Files changed (156) hide show
  1. package/.env.example +57 -0
  2. package/README.md +374 -0
  3. package/dist/index.js +189 -0
  4. package/dist/mcp/index.js +1132 -0
  5. package/docker-compose.prod.yml +91 -0
  6. package/docker-compose.yml +358 -0
  7. package/drizzle/0000_dapper_the_professor.sql +159 -0
  8. package/drizzle/0001_api_keys.sql +51 -0
  9. package/drizzle/meta/0000_snapshot.json +1532 -0
  10. package/drizzle/meta/_journal.json +13 -0
  11. package/drizzle.config.ts +20 -0
  12. package/package.json +114 -0
  13. package/scripts/add-extraction-job.ts +122 -0
  14. package/scripts/benchmark-pgvector.ts +122 -0
  15. package/scripts/bootstrap.sh +209 -0
  16. package/scripts/check-runtime-pack.ts +111 -0
  17. package/scripts/claude-mcp-config.ts +336 -0
  18. package/scripts/docker-entrypoint.sh +183 -0
  19. package/scripts/doctor.ts +377 -0
  20. package/scripts/init-db.sql +33 -0
  21. package/scripts/install.sh +1110 -0
  22. package/scripts/mcp-setup.ts +271 -0
  23. package/scripts/migrations/001_create_pgvector_extension.sql +31 -0
  24. package/scripts/migrations/002_create_memory_embeddings_table.sql +75 -0
  25. package/scripts/migrations/003_create_hnsw_index.sql +94 -0
  26. package/scripts/migrations/004_create_memory_embeddings_standalone.sql +70 -0
  27. package/scripts/migrations/005_create_chunks_table.sql +95 -0
  28. package/scripts/migrations/006_create_processing_queue.sql +45 -0
  29. package/scripts/migrations/generate_test_data.sql +42 -0
  30. package/scripts/migrations/phase1_comprehensive_test.sql +204 -0
  31. package/scripts/migrations/run_migrations.sh +286 -0
  32. package/scripts/migrations/test_hnsw_index.sql +255 -0
  33. package/scripts/pre-commit-secrets +282 -0
  34. package/scripts/run-extraction-worker.ts +46 -0
  35. package/scripts/run-phase1-tests.sh +291 -0
  36. package/scripts/setup.ts +222 -0
  37. package/scripts/smoke-install.sh +12 -0
  38. package/scripts/test-health-endpoint.sh +328 -0
  39. package/src/api/index.ts +2 -0
  40. package/src/api/middleware/auth.ts +80 -0
  41. package/src/api/middleware/csrf.ts +308 -0
  42. package/src/api/middleware/errorHandler.ts +166 -0
  43. package/src/api/middleware/rateLimit.ts +360 -0
  44. package/src/api/middleware/validation.ts +514 -0
  45. package/src/api/routes/documents.ts +286 -0
  46. package/src/api/routes/profiles.ts +237 -0
  47. package/src/api/routes/search.ts +71 -0
  48. package/src/api/stores/index.ts +58 -0
  49. package/src/config/bootstrap-env.ts +3 -0
  50. package/src/config/env.ts +71 -0
  51. package/src/config/feature-flags.ts +25 -0
  52. package/src/config/index.ts +140 -0
  53. package/src/config/secrets.config.ts +291 -0
  54. package/src/db/client.ts +92 -0
  55. package/src/db/index.ts +73 -0
  56. package/src/db/postgres.ts +72 -0
  57. package/src/db/schema/chunks.schema.ts +31 -0
  58. package/src/db/schema/containers.schema.ts +46 -0
  59. package/src/db/schema/documents.schema.ts +49 -0
  60. package/src/db/schema/embeddings.schema.ts +32 -0
  61. package/src/db/schema/index.ts +11 -0
  62. package/src/db/schema/memories.schema.ts +72 -0
  63. package/src/db/schema/profiles.schema.ts +34 -0
  64. package/src/db/schema/queue.schema.ts +59 -0
  65. package/src/db/schema/relationships.schema.ts +42 -0
  66. package/src/db/schema.ts +223 -0
  67. package/src/db/worker-connection.ts +47 -0
  68. package/src/index.ts +235 -0
  69. package/src/mcp/CLAUDE.md +1 -0
  70. package/src/mcp/index.ts +1380 -0
  71. package/src/mcp/legacyState.ts +22 -0
  72. package/src/mcp/rateLimit.ts +358 -0
  73. package/src/mcp/resources.ts +309 -0
  74. package/src/mcp/results.ts +104 -0
  75. package/src/mcp/tools.ts +401 -0
  76. package/src/queues/config.ts +119 -0
  77. package/src/queues/index.ts +289 -0
  78. package/src/sdk/client.ts +225 -0
  79. package/src/sdk/errors.ts +266 -0
  80. package/src/sdk/http.ts +560 -0
  81. package/src/sdk/index.ts +244 -0
  82. package/src/sdk/resources/base.ts +65 -0
  83. package/src/sdk/resources/connections.ts +204 -0
  84. package/src/sdk/resources/documents.ts +163 -0
  85. package/src/sdk/resources/index.ts +10 -0
  86. package/src/sdk/resources/memories.ts +150 -0
  87. package/src/sdk/resources/search.ts +60 -0
  88. package/src/sdk/resources/settings.ts +36 -0
  89. package/src/sdk/types.ts +674 -0
  90. package/src/services/chunking/index.ts +451 -0
  91. package/src/services/chunking.service.ts +650 -0
  92. package/src/services/csrf.service.ts +252 -0
  93. package/src/services/documents.repository.ts +219 -0
  94. package/src/services/documents.service.ts +191 -0
  95. package/src/services/embedding.service.ts +404 -0
  96. package/src/services/extraction.service.ts +300 -0
  97. package/src/services/extractors/code.extractor.ts +451 -0
  98. package/src/services/extractors/index.ts +9 -0
  99. package/src/services/extractors/markdown.extractor.ts +461 -0
  100. package/src/services/extractors/pdf.extractor.ts +315 -0
  101. package/src/services/extractors/text.extractor.ts +118 -0
  102. package/src/services/extractors/url.extractor.ts +243 -0
  103. package/src/services/index.ts +235 -0
  104. package/src/services/ingestion.service.ts +177 -0
  105. package/src/services/llm/anthropic.ts +400 -0
  106. package/src/services/llm/base.ts +460 -0
  107. package/src/services/llm/contradiction-detector.service.ts +526 -0
  108. package/src/services/llm/heuristics.ts +148 -0
  109. package/src/services/llm/index.ts +309 -0
  110. package/src/services/llm/memory-classifier.service.ts +383 -0
  111. package/src/services/llm/memory-extension-detector.service.ts +523 -0
  112. package/src/services/llm/mock.ts +470 -0
  113. package/src/services/llm/openai.ts +398 -0
  114. package/src/services/llm/prompts.ts +438 -0
  115. package/src/services/llm/types.ts +373 -0
  116. package/src/services/memory.repository.ts +1769 -0
  117. package/src/services/memory.service.ts +1338 -0
  118. package/src/services/memory.types.ts +234 -0
  119. package/src/services/persistence/index.ts +295 -0
  120. package/src/services/pipeline.service.ts +509 -0
  121. package/src/services/profile.repository.ts +436 -0
  122. package/src/services/profile.service.ts +560 -0
  123. package/src/services/profile.types.ts +270 -0
  124. package/src/services/relationships/detector.ts +1128 -0
  125. package/src/services/relationships/index.ts +268 -0
  126. package/src/services/relationships/memory-integration.ts +459 -0
  127. package/src/services/relationships/strategies.ts +132 -0
  128. package/src/services/relationships/types.ts +370 -0
  129. package/src/services/search.service.ts +761 -0
  130. package/src/services/search.types.ts +220 -0
  131. package/src/services/secrets.service.ts +384 -0
  132. package/src/services/vectorstore/base.ts +327 -0
  133. package/src/services/vectorstore/index.ts +444 -0
  134. package/src/services/vectorstore/memory.ts +286 -0
  135. package/src/services/vectorstore/migration.ts +295 -0
  136. package/src/services/vectorstore/mock.ts +403 -0
  137. package/src/services/vectorstore/pgvector.ts +695 -0
  138. package/src/services/vectorstore/types.ts +247 -0
  139. package/src/startup.ts +389 -0
  140. package/src/types/api.types.ts +193 -0
  141. package/src/types/document.types.ts +103 -0
  142. package/src/types/index.ts +241 -0
  143. package/src/types/profile.base.ts +133 -0
  144. package/src/utils/errors.ts +447 -0
  145. package/src/utils/id.ts +15 -0
  146. package/src/utils/index.ts +101 -0
  147. package/src/utils/logger.ts +313 -0
  148. package/src/utils/sanitization.ts +501 -0
  149. package/src/utils/secret-validation.ts +273 -0
  150. package/src/utils/synonyms.ts +188 -0
  151. package/src/utils/validation.ts +581 -0
  152. package/src/workers/chunking.worker.ts +242 -0
  153. package/src/workers/embedding.worker.ts +358 -0
  154. package/src/workers/extraction.worker.ts +346 -0
  155. package/src/workers/indexing.worker.ts +505 -0
  156. package/tsconfig.json +38 -0
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env tsx
2
+ import { execFileSync } from 'node:child_process'
3
+ import { existsSync, rmSync } from 'node:fs'
4
+ import { tmpdir } from 'node:os'
5
+ import { join, resolve } from 'node:path'
6
+
7
+ interface PackFileEntry {
8
+ path?: string
9
+ }
10
+
11
+ interface PackResult {
12
+ files?: PackFileEntry[]
13
+ }
14
+
15
+ const REQUIRED_PATHS = [
16
+ '.env.example',
17
+ 'docker-compose.prod.yml',
18
+ 'docker-compose.yml',
19
+ 'package.json',
20
+ 'scripts/doctor.ts',
21
+ 'scripts/install.sh',
22
+ 'scripts/mcp-setup.ts',
23
+ 'drizzle/0000_dapper_the_professor.sql',
24
+ 'src/index.ts',
25
+ 'src/db/schema.ts',
26
+ 'src/mcp/index.ts',
27
+ 'tsconfig.json',
28
+ ] as const
29
+
30
+ const FORBIDDEN_PATTERNS = [
31
+ /^coverage\//,
32
+ /^data\//,
33
+ /^dist\//,
34
+ /^packages\//,
35
+ /^tests\//,
36
+ /^2026-03-05-npx-first-installer-/,
37
+ ] as const
38
+
39
+ function parsePackResult(rawOutput: string): PackResult {
40
+ const parsed = JSON.parse(rawOutput) as unknown
41
+ if (!Array.isArray(parsed) || parsed.length === 0) {
42
+ throw new Error('npm pack --json did not return a pack result array')
43
+ }
44
+
45
+ const [firstResult] = parsed
46
+ if (!firstResult || typeof firstResult !== 'object') {
47
+ throw new Error('npm pack --json returned an invalid pack result')
48
+ }
49
+
50
+ return firstResult as PackResult
51
+ }
52
+
53
+ function collectPackPaths(result: PackResult): string[] {
54
+ const packPaths = result.files
55
+ ?.map((entry) => entry.path)
56
+ .filter((path): path is string => typeof path === 'string')
57
+ .sort()
58
+
59
+ if (!packPaths || packPaths.length === 0) {
60
+ throw new Error('npm pack --json did not report packaged file paths')
61
+ }
62
+
63
+ return packPaths
64
+ }
65
+
66
+ function assertRequiredPaths(packPaths: string[]): void {
67
+ const missingPaths = REQUIRED_PATHS.filter((requiredPath) => !packPaths.includes(requiredPath))
68
+ if (missingPaths.length > 0) {
69
+ throw new Error(`Runtime package is missing required files: ${missingPaths.join(', ')}`)
70
+ }
71
+ }
72
+
73
+ function assertForbiddenPaths(packPaths: string[]): void {
74
+ const forbiddenPaths = packPaths.filter((packPath) =>
75
+ FORBIDDEN_PATTERNS.some((pattern) => pattern.test(packPath))
76
+ )
77
+
78
+ if (forbiddenPaths.length > 0) {
79
+ throw new Error(`Runtime package includes forbidden files: ${forbiddenPaths.join(', ')}`)
80
+ }
81
+ }
82
+
83
+ export function validateRuntimePack(repoRoot = resolve(import.meta.dirname, '..')): string[] {
84
+ const packDestination = join(tmpdir(), `supermemory-runtime-pack-${process.pid}-${Date.now()}`)
85
+
86
+ try {
87
+ const rawOutput = execFileSync(
88
+ 'npm',
89
+ ['pack', '--json', '--dry-run', '--pack-destination', packDestination],
90
+ {
91
+ cwd: repoRoot,
92
+ encoding: 'utf8',
93
+ }
94
+ )
95
+
96
+ const packPaths = collectPackPaths(parsePackResult(rawOutput))
97
+ assertRequiredPaths(packPaths)
98
+ assertForbiddenPaths(packPaths)
99
+
100
+ return packPaths
101
+ } finally {
102
+ if (existsSync(packDestination)) {
103
+ rmSync(packDestination, { recursive: true, force: true })
104
+ }
105
+ }
106
+ }
107
+
108
+ if (import.meta.url === new URL(process.argv[1], 'file://').href) {
109
+ const packPaths = validateRuntimePack()
110
+ console.log(`Runtime package validated (${packPaths.length} files)`)
111
+ }
@@ -0,0 +1,336 @@
1
+ #!/usr/bin/env tsx
2
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join, resolve } from 'node:path';
5
+
6
+ export type ClaudeMcpScope = 'user' | 'project' | 'local';
7
+
8
+ type JsonObject = Record<string, unknown>;
9
+
10
+ export interface ClaudeMcpRegistrationEntry {
11
+ scope: ClaudeMcpScope;
12
+ sourcePath: string;
13
+ command: string;
14
+ args: string[];
15
+ type: string;
16
+ }
17
+
18
+ export interface ClaudeMcpRegistrationCheck {
19
+ status: 'missing' | 'match' | 'mismatch';
20
+ scope: ClaudeMcpScope;
21
+ projectKey: string;
22
+ expectedCommand: string;
23
+ expectedArgs: string[];
24
+ entries: ClaudeMcpRegistrationEntry[];
25
+ }
26
+
27
+ interface CheckOptions {
28
+ expectedArgs: string[];
29
+ expectedCommand: string;
30
+ homeDir?: string;
31
+ name: string;
32
+ projectDir?: string;
33
+ scope: ClaudeMcpScope;
34
+ }
35
+
36
+ const HOME_CONFIG_CANDIDATES = ['.claude/settings.json', '.claude.json'] as const;
37
+
38
+ function isObject(value: unknown): value is JsonObject {
39
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
40
+ }
41
+
42
+ function readJsonObject(path: string): JsonObject | null {
43
+ if (!existsSync(path)) {
44
+ return null;
45
+ }
46
+
47
+ try {
48
+ const parsed = JSON.parse(readFileSync(path, 'utf-8')) as unknown;
49
+ return isObject(parsed) ? parsed : null;
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ function resolveRealPath(path: string): string {
56
+ try {
57
+ return realpathSync.native(path);
58
+ } catch {
59
+ return resolve(path);
60
+ }
61
+ }
62
+
63
+ function normalizeComparableValue(value: string): string {
64
+ const trimmed = value.trim();
65
+ if (!trimmed) {
66
+ return trimmed;
67
+ }
68
+
69
+ if (trimmed.startsWith('-')) {
70
+ return trimmed;
71
+ }
72
+
73
+ const absoluteCandidate = trimmed.startsWith('/') ? trimmed : resolve(trimmed);
74
+ if (existsSync(absoluteCandidate)) {
75
+ return resolveRealPath(absoluteCandidate);
76
+ }
77
+
78
+ return trimmed;
79
+ }
80
+
81
+ function getHomeConfigPaths(homeDir = homedir()): string[] {
82
+ const resolvedHome = resolve(homeDir);
83
+ return HOME_CONFIG_CANDIDATES.map((relativePath) => join(resolvedHome, relativePath));
84
+ }
85
+
86
+ export function getClaudeProjectKey(projectDir = process.cwd()): string {
87
+ return resolveRealPath(projectDir);
88
+ }
89
+
90
+ function getEntryFromHomeConfig(
91
+ configPath: string,
92
+ scope: ClaudeMcpScope,
93
+ name: string,
94
+ projectKey: string
95
+ ): ClaudeMcpRegistrationEntry | null {
96
+ const config = readJsonObject(configPath);
97
+ if (!config) {
98
+ return null;
99
+ }
100
+
101
+ let serverConfig: JsonObject | null = null;
102
+ if (scope === 'user') {
103
+ const mcpServers = isObject(config.mcpServers) ? config.mcpServers : null;
104
+ serverConfig = mcpServers && isObject(mcpServers[name]) ? (mcpServers[name] as JsonObject) : null;
105
+ } else {
106
+ const projects = isObject(config.projects) ? config.projects : null;
107
+ const projectConfig = projects && isObject(projects[projectKey]) ? (projects[projectKey] as JsonObject) : null;
108
+ const mcpServers = projectConfig && isObject(projectConfig.mcpServers) ? projectConfig.mcpServers : null;
109
+ serverConfig = mcpServers && isObject(mcpServers[name]) ? (mcpServers[name] as JsonObject) : null;
110
+ }
111
+
112
+ if (!serverConfig) {
113
+ return null;
114
+ }
115
+
116
+ return {
117
+ scope,
118
+ sourcePath: configPath,
119
+ command: typeof serverConfig.command === 'string' ? serverConfig.command : '',
120
+ args: Array.isArray(serverConfig.args)
121
+ ? serverConfig.args.filter((arg): arg is string => typeof arg === 'string')
122
+ : [],
123
+ type: typeof serverConfig.type === 'string' ? serverConfig.type : '',
124
+ };
125
+ }
126
+
127
+ function getEntryFromProjectConfig(
128
+ projectDir: string,
129
+ name: string
130
+ ): ClaudeMcpRegistrationEntry | null {
131
+ const configPath = join(resolve(projectDir), '.mcp.json');
132
+ const config = readJsonObject(configPath);
133
+ if (!config) {
134
+ return null;
135
+ }
136
+
137
+ const mcpServers = isObject(config.mcpServers) ? config.mcpServers : null;
138
+ const serverConfig = mcpServers && isObject(mcpServers[name]) ? (mcpServers[name] as JsonObject) : null;
139
+ if (!serverConfig) {
140
+ return null;
141
+ }
142
+
143
+ return {
144
+ scope: 'project',
145
+ sourcePath: configPath,
146
+ command: typeof serverConfig.command === 'string' ? serverConfig.command : '',
147
+ args: Array.isArray(serverConfig.args)
148
+ ? serverConfig.args.filter((arg): arg is string => typeof arg === 'string')
149
+ : [],
150
+ type: typeof serverConfig.type === 'string' ? serverConfig.type : '',
151
+ };
152
+ }
153
+
154
+ function getRegistrationEntries(
155
+ scope: ClaudeMcpScope,
156
+ name: string,
157
+ projectDir: string,
158
+ homeDir?: string
159
+ ): ClaudeMcpRegistrationEntry[] {
160
+ if (scope === 'project') {
161
+ const projectEntry = getEntryFromProjectConfig(projectDir, name);
162
+ return projectEntry ? [projectEntry] : [];
163
+ }
164
+
165
+ const projectKey = getClaudeProjectKey(projectDir);
166
+ return getHomeConfigPaths(homeDir)
167
+ .map((configPath) => getEntryFromHomeConfig(configPath, scope, name, projectKey))
168
+ .filter((entry): entry is ClaudeMcpRegistrationEntry => entry !== null);
169
+ }
170
+
171
+ export function findClaudeMcpRegistrations(
172
+ name: string,
173
+ projectDir = process.cwd(),
174
+ homeDir?: string
175
+ ): ClaudeMcpRegistrationEntry[] {
176
+ const scopes: ClaudeMcpScope[] = ['project', 'local', 'user'];
177
+ return scopes.flatMap((scope) => getRegistrationEntries(scope, name, projectDir, homeDir));
178
+ }
179
+
180
+ function isExpectedRegistration(
181
+ entry: ClaudeMcpRegistrationEntry,
182
+ expectedCommand: string,
183
+ expectedArgs: string[]
184
+ ): boolean {
185
+ if (entry.type && entry.type !== 'stdio') {
186
+ return false;
187
+ }
188
+
189
+ if (entry.command !== expectedCommand) {
190
+ return false;
191
+ }
192
+
193
+ const normalizedExpectedArgs = expectedArgs.map(normalizeComparableValue);
194
+ const normalizedActualArgs = entry.args.map(normalizeComparableValue);
195
+
196
+ if (normalizedExpectedArgs.length !== normalizedActualArgs.length) {
197
+ return false;
198
+ }
199
+
200
+ return normalizedExpectedArgs.every((arg, index) => arg === normalizedActualArgs[index]);
201
+ }
202
+
203
+ export function checkClaudeMcpRegistration(options: CheckOptions): ClaudeMcpRegistrationCheck {
204
+ const projectDir = resolve(options.projectDir ?? process.cwd());
205
+ const projectKey = getClaudeProjectKey(projectDir);
206
+ const entries = getRegistrationEntries(options.scope, options.name, projectDir, options.homeDir);
207
+ const expectedArgs = options.expectedArgs.map(normalizeComparableValue);
208
+
209
+ if (entries.length === 0) {
210
+ return {
211
+ status: 'missing',
212
+ scope: options.scope,
213
+ projectKey,
214
+ expectedCommand: options.expectedCommand,
215
+ expectedArgs,
216
+ entries: [],
217
+ };
218
+ }
219
+
220
+ if (entries.some((entry) => isExpectedRegistration(entry, options.expectedCommand, expectedArgs))) {
221
+ return {
222
+ status: 'match',
223
+ scope: options.scope,
224
+ projectKey,
225
+ expectedCommand: options.expectedCommand,
226
+ expectedArgs,
227
+ entries,
228
+ };
229
+ }
230
+
231
+ return {
232
+ status: 'mismatch',
233
+ scope: options.scope,
234
+ projectKey,
235
+ expectedCommand: options.expectedCommand,
236
+ expectedArgs,
237
+ entries,
238
+ };
239
+ }
240
+
241
+ function parseScope(value: string): ClaudeMcpScope {
242
+ if (value === 'user' || value === 'project' || value === 'local') {
243
+ return value;
244
+ }
245
+ throw new Error(`Invalid scope: ${value}`);
246
+ }
247
+
248
+ function parseCliArgs(): CheckOptions {
249
+ const args = process.argv.slice(2);
250
+ if (args[0] !== 'check') {
251
+ throw new Error('Usage: claude-mcp-config.ts check --scope <scope> --name <name> --command <command> [--arg <value>]');
252
+ }
253
+
254
+ let scope: ClaudeMcpScope | undefined;
255
+ let name = '';
256
+ let expectedCommand = '';
257
+ const expectedArgs: string[] = [];
258
+ let projectDir: string | undefined;
259
+ let homeDir: string | undefined;
260
+
261
+ for (let index = 1; index < args.length; index += 1) {
262
+ const arg = args[index];
263
+
264
+ if (arg === '--scope') {
265
+ scope = parseScope(args[index + 1] ?? '');
266
+ index += 1;
267
+ continue;
268
+ }
269
+
270
+ if (arg === '--name') {
271
+ name = args[index + 1] ?? '';
272
+ index += 1;
273
+ continue;
274
+ }
275
+
276
+ if (arg === '--command') {
277
+ expectedCommand = args[index + 1] ?? '';
278
+ index += 1;
279
+ continue;
280
+ }
281
+
282
+ if (arg === '--arg') {
283
+ expectedArgs.push(args[index + 1] ?? '');
284
+ index += 1;
285
+ continue;
286
+ }
287
+
288
+ if (arg === '--project-dir') {
289
+ projectDir = args[index + 1];
290
+ index += 1;
291
+ continue;
292
+ }
293
+
294
+ if (arg === '--home-dir') {
295
+ homeDir = args[index + 1];
296
+ index += 1;
297
+ continue;
298
+ }
299
+
300
+ throw new Error(`Unknown argument: ${arg}`);
301
+ }
302
+
303
+ if (!scope || !name || !expectedCommand) {
304
+ throw new Error('Missing required arguments for check');
305
+ }
306
+
307
+ return { scope, name, expectedCommand, expectedArgs, projectDir, homeDir };
308
+ }
309
+
310
+ function runCli(): void {
311
+ const result = checkClaudeMcpRegistration(parseCliArgs());
312
+ console.log(JSON.stringify(result));
313
+
314
+ switch (result.status) {
315
+ case 'match':
316
+ process.exit(0);
317
+ case 'missing':
318
+ process.exit(10);
319
+ case 'mismatch':
320
+ process.exit(11);
321
+ }
322
+ }
323
+
324
+ const executedPath = process.argv[1];
325
+ if (executedPath) {
326
+ const currentPath = resolveRealPath(executedPath);
327
+ const modulePath = resolveRealPath(new URL(import.meta.url).pathname);
328
+ if (currentPath === modulePath) {
329
+ try {
330
+ runCli();
331
+ } catch (error) {
332
+ console.error(error instanceof Error ? error.message : String(error));
333
+ process.exit(2);
334
+ }
335
+ }
336
+ }
@@ -0,0 +1,183 @@
1
+ #!/bin/sh
2
+ # =============================================================================
3
+ # Docker Entrypoint Script for SuperMemory Clone API
4
+ # =============================================================================
5
+ # This script runs before the main application starts:
6
+ # 1. Waits for required services (database, redis) to be ready
7
+ # 2. Runs database migrations
8
+ # 3. Starts the application
9
+ #
10
+ # Environment variables:
11
+ # DATABASE_URL - Database connection string
12
+ # REDIS_URL - Redis connection string (optional)
13
+ # SKIP_MIGRATIONS - Set to "true" to skip migrations
14
+ # MAX_RETRIES - Max connection attempts (default: 30)
15
+ # RETRY_INTERVAL - Seconds between retries (default: 2)
16
+ # =============================================================================
17
+
18
+ set -e
19
+
20
+ # -----------------------------------------------------------------------------
21
+ # Configuration
22
+ # -----------------------------------------------------------------------------
23
+ MAX_RETRIES="${MAX_RETRIES:-30}"
24
+ RETRY_INTERVAL="${RETRY_INTERVAL:-2}"
25
+
26
+ # Colors for output (if terminal supports it)
27
+ RED='\033[0;31m'
28
+ GREEN='\033[0;32m'
29
+ YELLOW='\033[1;33m'
30
+ NC='\033[0m' # No Color
31
+
32
+ # -----------------------------------------------------------------------------
33
+ # Logging functions
34
+ # -----------------------------------------------------------------------------
35
+ log_info() {
36
+ echo "${GREEN}[INFO]${NC} $1"
37
+ }
38
+
39
+ log_warn() {
40
+ echo "${YELLOW}[WARN]${NC} $1"
41
+ }
42
+
43
+ log_error() {
44
+ echo "${RED}[ERROR]${NC} $1"
45
+ }
46
+
47
+ # -----------------------------------------------------------------------------
48
+ # Wait for PostgreSQL to be ready
49
+ # -----------------------------------------------------------------------------
50
+ wait_for_postgres() {
51
+ if [ -z "$DATABASE_URL" ]; then
52
+ log_warn "DATABASE_URL not set, skipping PostgreSQL check"
53
+ return 0
54
+ fi
55
+
56
+ # Check if this is a PostgreSQL connection string
57
+ if echo "$DATABASE_URL" | grep -q "postgresql://\|postgres://"; then
58
+ log_info "Waiting for PostgreSQL to be ready..."
59
+
60
+ # Extract host and port from DATABASE_URL
61
+ # Format: postgresql://user:pass@host:port/dbname
62
+ DB_HOST=$(echo "$DATABASE_URL" | sed -n 's/.*@\([^:]*\):.*/\1/p')
63
+ DB_PORT=$(echo "$DATABASE_URL" | sed -n 's/.*:\([0-9]*\)\/.*/\1/p')
64
+
65
+ DB_HOST="${DB_HOST:-localhost}"
66
+ DB_PORT="${DB_PORT:-5432}"
67
+
68
+ RETRIES=0
69
+ until nc -z "$DB_HOST" "$DB_PORT" 2>/dev/null; do
70
+ RETRIES=$((RETRIES + 1))
71
+ if [ $RETRIES -ge $MAX_RETRIES ]; then
72
+ log_error "PostgreSQL is not available after $MAX_RETRIES attempts"
73
+ exit 1
74
+ fi
75
+ log_info "PostgreSQL is unavailable - attempt $RETRIES/$MAX_RETRIES - sleeping ${RETRY_INTERVAL}s"
76
+ sleep $RETRY_INTERVAL
77
+ done
78
+
79
+ log_info "PostgreSQL is ready!"
80
+ else
81
+ log_info "Using SQLite database, no connection wait needed"
82
+ fi
83
+ }
84
+
85
+ # -----------------------------------------------------------------------------
86
+ # Wait for Redis to be ready (optional)
87
+ # -----------------------------------------------------------------------------
88
+ wait_for_redis() {
89
+ if [ -z "$REDIS_URL" ]; then
90
+ log_warn "REDIS_URL not set, skipping Redis check"
91
+ return 0
92
+ fi
93
+
94
+ log_info "Waiting for Redis to be ready..."
95
+
96
+ # Extract host and port from REDIS_URL
97
+ # Format: redis://host:port
98
+ REDIS_HOST=$(echo "$REDIS_URL" | sed -n 's/redis:\/\/\([^:]*\):.*/\1/p')
99
+ REDIS_PORT=$(echo "$REDIS_URL" | sed -n 's/redis:\/\/[^:]*:\([0-9]*\).*/\1/p')
100
+
101
+ REDIS_HOST="${REDIS_HOST:-localhost}"
102
+ REDIS_PORT="${REDIS_PORT:-6379}"
103
+
104
+ RETRIES=0
105
+ until nc -z "$REDIS_HOST" "$REDIS_PORT" 2>/dev/null; do
106
+ RETRIES=$((RETRIES + 1))
107
+ if [ $RETRIES -ge $MAX_RETRIES ]; then
108
+ log_error "Redis is not available after $MAX_RETRIES attempts"
109
+ exit 1
110
+ fi
111
+ log_info "Redis is unavailable - attempt $RETRIES/$MAX_RETRIES - sleeping ${RETRY_INTERVAL}s"
112
+ sleep $RETRY_INTERVAL
113
+ done
114
+
115
+ log_info "Redis is ready!"
116
+ }
117
+
118
+ # -----------------------------------------------------------------------------
119
+ # Run database migrations
120
+ # -----------------------------------------------------------------------------
121
+ run_migrations() {
122
+ if [ "$SKIP_MIGRATIONS" = "true" ]; then
123
+ log_warn "SKIP_MIGRATIONS is set, skipping database migrations"
124
+ return 0
125
+ fi
126
+
127
+ log_info "Running database migrations..."
128
+
129
+ # Check if drizzle directory exists with migrations
130
+ if [ -d "/app/drizzle" ] && [ "$(ls -A /app/drizzle 2>/dev/null)" ]; then
131
+ # For production, use drizzle-kit migrate
132
+ # This requires drizzle-kit to be installed
133
+ if command -v npx >/dev/null 2>&1; then
134
+ npx drizzle-kit migrate || {
135
+ log_warn "Migration failed, continuing anyway (migrations may already be applied)"
136
+ }
137
+ else
138
+ log_warn "npx not available, skipping drizzle migrations"
139
+ fi
140
+ else
141
+ log_info "No migrations found in /app/drizzle, skipping"
142
+ fi
143
+
144
+ log_info "Database migrations complete!"
145
+ }
146
+
147
+ # -----------------------------------------------------------------------------
148
+ # Create data directory if needed
149
+ # -----------------------------------------------------------------------------
150
+ setup_directories() {
151
+ log_info "Setting up directories..."
152
+
153
+ # Ensure data directory exists and is writable
154
+ mkdir -p /app/data
155
+
156
+ log_info "Directories ready!"
157
+ }
158
+
159
+ # -----------------------------------------------------------------------------
160
+ # Main entrypoint
161
+ # -----------------------------------------------------------------------------
162
+ main() {
163
+ log_info "Starting SuperMemory Clone API..."
164
+ log_info "Environment: ${NODE_ENV:-development}"
165
+
166
+ # Setup directories
167
+ setup_directories
168
+
169
+ # Wait for services
170
+ wait_for_postgres
171
+ wait_for_redis
172
+
173
+ # Run migrations
174
+ run_migrations
175
+
176
+ log_info "Starting application..."
177
+
178
+ # Execute the main command (node dist/index.js)
179
+ exec node dist/index.js
180
+ }
181
+
182
+ # Run main function
183
+ main "$@"