@duckmind/deepquark-darwin-arm64 0.9.83 → 0.9.90

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 (70) hide show
  1. package/.deepquark/skills/bundled/knowledge-graph/SKILL.md +385 -0
  2. package/.deepquark/skills/bundled/knowledge-graph/STANDARDS.md +461 -0
  3. package/.deepquark/skills/bundled/knowledge-graph/lib/cli.ts +588 -0
  4. package/.deepquark/skills/bundled/knowledge-graph/lib/config.ts +630 -0
  5. package/.deepquark/skills/bundled/knowledge-graph/lib/connection-profile.ts +629 -0
  6. package/.deepquark/skills/bundled/knowledge-graph/lib/container.ts +756 -0
  7. package/.deepquark/skills/bundled/knowledge-graph/lib/mcp-client.ts +1310 -0
  8. package/.deepquark/skills/bundled/knowledge-graph/lib/output-formatter.ts +997 -0
  9. package/.deepquark/skills/bundled/knowledge-graph/lib/token-metrics.ts +335 -0
  10. package/.deepquark/skills/bundled/knowledge-graph/lib/transformation-log.ts +137 -0
  11. package/.deepquark/skills/bundled/knowledge-graph/lib/wrapper-config.ts +113 -0
  12. package/.deepquark/skills/bundled/knowledge-graph/server/.env.example +129 -0
  13. package/.deepquark/skills/bundled/knowledge-graph/server/compare-embeddings.ts +175 -0
  14. package/.deepquark/skills/bundled/knowledge-graph/server/config-falkordb.yaml +108 -0
  15. package/.deepquark/skills/bundled/knowledge-graph/server/config-neo4j.yaml +111 -0
  16. package/.deepquark/skills/bundled/knowledge-graph/server/diagnose.ts +483 -0
  17. package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-falkordb-dev.yml +146 -0
  18. package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-falkordb.yml +151 -0
  19. package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-neo4j-dev-local.yml +161 -0
  20. package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-neo4j-dev.yml +161 -0
  21. package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-neo4j.yml +169 -0
  22. package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-production.yml +128 -0
  23. package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose-test.yml +10 -0
  24. package/.deepquark/skills/bundled/knowledge-graph/server/docker-compose.yml +84 -0
  25. package/.deepquark/skills/bundled/knowledge-graph/server/entrypoint.sh +40 -0
  26. package/.deepquark/skills/bundled/knowledge-graph/server/install.ts +2054 -0
  27. package/.deepquark/skills/bundled/knowledge-graph/server/podman-compose-falkordb.yml +78 -0
  28. package/.deepquark/skills/bundled/knowledge-graph/server/podman-compose-neo4j.yml +88 -0
  29. package/.deepquark/skills/bundled/knowledge-graph/server/podman-compose.yml +83 -0
  30. package/.deepquark/skills/bundled/knowledge-graph/server/test-all-llms-mcp.ts +387 -0
  31. package/.deepquark/skills/bundled/knowledge-graph/server/test-embedding-models.ts +201 -0
  32. package/.deepquark/skills/bundled/knowledge-graph/server/test-embedding-providers.ts +641 -0
  33. package/.deepquark/skills/bundled/knowledge-graph/server/test-graphiti-model.ts +217 -0
  34. package/.deepquark/skills/bundled/knowledge-graph/server/test-grok-correct.ts +141 -0
  35. package/.deepquark/skills/bundled/knowledge-graph/server/test-grok-llms-mcp.ts +386 -0
  36. package/.deepquark/skills/bundled/knowledge-graph/server/test-grok-models.ts +173 -0
  37. package/.deepquark/skills/bundled/knowledge-graph/server/test-llama-extraction.ts +188 -0
  38. package/.deepquark/skills/bundled/knowledge-graph/server/test-mcp-final.ts +240 -0
  39. package/.deepquark/skills/bundled/knowledge-graph/server/test-mcp-live.ts +187 -0
  40. package/.deepquark/skills/bundled/knowledge-graph/server/test-mcp-session.ts +127 -0
  41. package/.deepquark/skills/bundled/knowledge-graph/server/test-model-combinations.ts +316 -0
  42. package/.deepquark/skills/bundled/knowledge-graph/server/test-ollama-models.ts +228 -0
  43. package/.deepquark/skills/bundled/knowledge-graph/server/test-openrouter-models.ts +460 -0
  44. package/.deepquark/skills/bundled/knowledge-graph/server/test-real-life-mcp.ts +311 -0
  45. package/.deepquark/skills/bundled/knowledge-graph/server/test-search-debug.ts +199 -0
  46. package/.deepquark/skills/bundled/knowledge-graph/tools/Install.md +104 -0
  47. package/.deepquark/skills/bundled/knowledge-graph/tools/README.md +120 -0
  48. package/.deepquark/skills/bundled/knowledge-graph/tools/knowledge-cli.ts +996 -0
  49. package/.deepquark/skills/bundled/knowledge-graph/tools/server-cli.ts +531 -0
  50. package/.deepquark/skills/bundled/knowledge-graph/workflows/BulkImport.md +514 -0
  51. package/.deepquark/skills/bundled/knowledge-graph/workflows/CaptureEpisode.md +242 -0
  52. package/.deepquark/skills/bundled/knowledge-graph/workflows/ClearGraph.md +392 -0
  53. package/.deepquark/skills/bundled/knowledge-graph/workflows/GetRecent.md +352 -0
  54. package/.deepquark/skills/bundled/knowledge-graph/workflows/GetStatus.md +373 -0
  55. package/.deepquark/skills/bundled/knowledge-graph/workflows/HealthReport.md +212 -0
  56. package/.deepquark/skills/bundled/knowledge-graph/workflows/InvestigateEntity.md +142 -0
  57. package/.deepquark/skills/bundled/knowledge-graph/workflows/OntologyManagement.md +201 -0
  58. package/.deepquark/skills/bundled/knowledge-graph/workflows/RunMaintenance.md +302 -0
  59. package/.deepquark/skills/bundled/knowledge-graph/workflows/SearchByDate.md +255 -0
  60. package/.deepquark/skills/bundled/knowledge-graph/workflows/SearchFacts.md +382 -0
  61. package/.deepquark/skills/bundled/knowledge-graph/workflows/SearchKnowledge.md +374 -0
  62. package/.deepquark/skills/bundled/knowledge-graph/workflows/StixImport.md +212 -0
  63. package/bin/deepquark +0 -0
  64. package/package.json +1 -1
  65. package/.deepquark/skills/bundled/ge-payroll/SKILL.md +0 -153
  66. package/.deepquark/skills/bundled/ge-payroll/evals/evals.json +0 -23
  67. package/.deepquark/skills/bundled/ge-payroll/references/pain-points-improvements.md +0 -106
  68. package/.deepquark/skills/bundled/ge-payroll/references/process-detail.md +0 -217
  69. package/.deepquark/skills/bundled/ge-payroll/references/raci-stakeholders.md +0 -85
  70. package/.deepquark/skills/bundled/ge-payroll/references/timeline-mandays.md +0 -64
@@ -0,0 +1,630 @@
1
+ /**
2
+ * Configuration Loader Library
3
+ *
4
+ * Handles loading and mapping environment variables for the Madeinoz Knowledge System.
5
+ * Supports MADEINOZ_KNOWLEDGE_* prefixed variables and maps them to standard container env vars.
6
+ *
7
+ * PAI .env is the ONLY source of truth for all configuration:
8
+ * - Uses ${PAI_DIR}/.env if PAI_DIR environment variable is set
9
+ * - Falls back to ~/.claude/.env if PAI_DIR is not set
10
+ *
11
+ * Docker containers read directly from PAI .env via the env_file directive.
12
+ */
13
+
14
+ import { join } from 'node:path';
15
+
16
+ /**
17
+ * Configuration interface
18
+ */
19
+ export interface KnowledgeConfig {
20
+ // API Keys
21
+ OPENAI_API_KEY?: string;
22
+ ANTHROPIC_API_KEY?: string;
23
+ GOOGLE_API_KEY?: string;
24
+ GROQ_API_KEY?: string;
25
+ VOYAGE_API_KEY?: string;
26
+
27
+ // LLM Provider Configuration
28
+ LLM_PROVIDER: string;
29
+ EMBEDDER_PROVIDER: string;
30
+ MODEL_NAME: string;
31
+
32
+ // Custom Endpoint Configuration
33
+ OPENAI_BASE_URL?: string; // For Ollama LLM: http://host.docker.internal:11434/v1
34
+ EMBEDDER_PROVIDER_URL?: string; // For custom embedder endpoint (Ollama, etc.)
35
+ EMBEDDER_MODEL?: string; // For Ollama: nomic-embed-text
36
+ EMBEDDER_DIMENSIONS?: string; // For embedder: 1024 for mxbai, 1536 for OpenAI
37
+
38
+ // Performance Configuration
39
+ SEMAPHORE_LIMIT: string;
40
+
41
+ // Knowledge Graph Configuration
42
+ GROUP_ID: string;
43
+
44
+ // Database Configuration
45
+ DATABASE_TYPE: string;
46
+ FALKORDB_HOST?: string;
47
+ FALKORDB_PORT?: string;
48
+ FALKORDB_PASSWORD?: string;
49
+
50
+ // Neo4j Configuration (alternative)
51
+ NEO4J_URI?: string;
52
+ NEO4J_USER?: string;
53
+ NEO4J_PASSWORD?: string;
54
+
55
+ // Telemetry
56
+ GRAPHITI_TELEMETRY_ENABLED: string;
57
+
58
+ // Prompt Caching (Gemini)
59
+ PROMPT_CACHE_ENABLED?: string;
60
+ PROMPT_CACHE_METRICS_ENABLED?: string;
61
+ PROMPT_CACHE_LOG_REQUESTS?: string;
62
+ PROMPT_CACHE_TTL?: string;
63
+ METRICS_PORT?: string;
64
+
65
+ // Container Configuration
66
+ NETWORK_NAME: string;
67
+ FALKORDB_CONTAINER: string;
68
+ MCP_CONTAINER: string;
69
+ VOLUME_NAME: string;
70
+
71
+ // Port Configuration
72
+ MCP_SERVER_PORT: string;
73
+ FALKORDB_UI_PORT: string;
74
+
75
+ // Original MADEINOZ_KNOWLEDGE_* prefixed values (for reference)
76
+ PAI_PREFIXES?: Record<string, string | undefined>;
77
+ }
78
+
79
+ /**
80
+ * Default configuration values
81
+ */
82
+ const DEFAULTS: Record<string, string> = {
83
+ LLM_PROVIDER: 'ollama',
84
+ EMBEDDER_PROVIDER: 'ollama',
85
+ MODEL_NAME: 'llama3.2',
86
+ OPENAI_BASE_URL: 'http://host.docker.internal:11434/v1', // Ollama LLM default (Docker)
87
+ EMBEDDER_PROVIDER_URL: 'http://host.containers.internal:11434', // Embedder endpoint (Ollama, etc.)
88
+ EMBEDDER_MODEL: 'nomic-embed-text', // Ollama embedding model
89
+ EMBEDDER_DIMENSIONS: '512', // nomic-embed-text dimensions
90
+ SEMAPHORE_LIMIT: '10',
91
+ GROUP_ID: 'main',
92
+ DATABASE_TYPE: 'neo4j',
93
+ GRAPHITI_TELEMETRY_ENABLED: 'false',
94
+ NETWORK_NAME: 'madeinoz-knowledge-net',
95
+ FALKORDB_CONTAINER: 'madeinoz-knowledge-falkordb',
96
+ MCP_CONTAINER: 'madeinoz-knowledge-graph-mcp',
97
+ VOLUME_NAME: 'madeinoz-knowledge-falkordb-data',
98
+ MCP_SERVER_PORT: '8000',
99
+ FALKORDB_UI_PORT: '3000',
100
+ FALKORDB_HOST: 'madeinoz-knowledge-falkordb',
101
+ FALKORDB_PORT: '6379',
102
+ NEO4J_URI: 'bolt://localhost:7687',
103
+ NEO4J_USER: 'neo4j',
104
+ NEO4J_PASSWORD: 'demodemo',
105
+ };
106
+
107
+ /**
108
+ * Configuration Loader class
109
+ *
110
+ * PAI .env is the ONLY source of truth for all configuration.
111
+ * Uses ${PAI_DIR}/.env if PAI_DIR is set, otherwise ~/.claude/.env
112
+ */
113
+ export class ConfigLoader {
114
+ private packRoot: string;
115
+ private envFile: string;
116
+
117
+ constructor(packRoot?: string) {
118
+ // If packRoot not provided, navigate up from src/server/lib to pack root
119
+ this.packRoot = packRoot || join(import.meta.dir, '../../../');
120
+
121
+ // PAI .env is the ONLY source of truth
122
+ // Priority: PAI_DIR/.env > ~/.claude/.env
123
+ const homeDir = process.env.HOME || '';
124
+ this.envFile = process.env.PAI_DIR
125
+ ? join(process.env.PAI_DIR, '.env')
126
+ : join(homeDir, '.claude', '.env');
127
+ }
128
+
129
+ /**
130
+ * Get the pack root directory
131
+ */
132
+ getPackRoot(): string {
133
+ return this.packRoot;
134
+ }
135
+
136
+ /**
137
+ * Get the PAI .env file path
138
+ */
139
+ getEnvFile(): string {
140
+ return this.envFile;
141
+ }
142
+
143
+ /**
144
+ * Check if .env file exists
145
+ */
146
+ async envExists(): Promise<boolean> {
147
+ const file = Bun.file(this.envFile);
148
+ return file.exists();
149
+ }
150
+
151
+ /**
152
+ * Load environment variables from PAI .env file using Bun
153
+ * PAI .env (${PAI_DIR}/.env or ~/.claude/.env) is the ONLY source of truth
154
+ */
155
+ async loadEnv(): Promise<Record<string, string>> {
156
+ const env: Record<string, string> = {};
157
+
158
+ // Copy process.env (filtering out undefined values)
159
+ for (const [key, value] of Object.entries(process.env)) {
160
+ if (value !== undefined) {
161
+ env[key] = value;
162
+ }
163
+ }
164
+
165
+ // Load from PAI .env (the ONLY source of truth)
166
+ if (await this.envExists()) {
167
+ const file = Bun.file(this.envFile);
168
+ const content = await file.text();
169
+ this.parseEnvFile(content, env);
170
+ }
171
+
172
+ return env;
173
+ }
174
+
175
+ /**
176
+ * Parse .env file content
177
+ */
178
+ private parseEnvFile(content: string, env: Record<string, string>): void {
179
+ const lines = content.split('\n');
180
+
181
+ for (const line of lines) {
182
+ const trimmed = line.trim();
183
+
184
+ // Skip empty lines and comments
185
+ if (!trimmed || trimmed.startsWith('#')) {
186
+ continue;
187
+ }
188
+
189
+ // Parse KEY=VALUE
190
+ const eqIndex = trimmed.indexOf('=');
191
+ if (eqIndex > 0) {
192
+ const key = trimmed.substring(0, eqIndex).trim();
193
+ const value = trimmed.substring(eqIndex + 1).trim();
194
+
195
+ // Remove quotes if present
196
+ const unquoted =
197
+ (value.startsWith('"') && value.endsWith('"')) ||
198
+ (value.startsWith("'") && value.endsWith("'"))
199
+ ? value.slice(1, -1)
200
+ : value;
201
+
202
+ env[key] = unquoted;
203
+ }
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Map MADEINOZ_KNOWLEDGE_* prefixed variables to standard names
209
+ * MADEINOZ_KNOWLEDGE_* variables take PRECEDENCE over unprefixed variables
210
+ */
211
+ private mapPrefixes(env: Record<string, string>): {
212
+ mapped: Record<string, string>;
213
+ originals: Record<string, string | undefined>;
214
+ } {
215
+ const mapped: Record<string, string> = { ...env };
216
+ const originals: Record<string, string | undefined> = {};
217
+
218
+ // Mapping of MADEINOZ_KNOWLEDGE_* -> standard variable
219
+ const mappings: Record<string, string> = {
220
+ // API Keys
221
+ MADEINOZ_KNOWLEDGE_OPENAI_API_KEY: 'OPENAI_API_KEY',
222
+ MADEINOZ_KNOWLEDGE_ANTHROPIC_API_KEY: 'ANTHROPIC_API_KEY',
223
+ MADEINOZ_KNOWLEDGE_GOOGLE_API_KEY: 'GOOGLE_API_KEY',
224
+ MADEINOZ_KNOWLEDGE_GROQ_API_KEY: 'GROQ_API_KEY',
225
+ MADEINOZ_KNOWLEDGE_VOYAGE_API_KEY: 'VOYAGE_API_KEY',
226
+ // LLM Configuration
227
+ MADEINOZ_KNOWLEDGE_LLM_PROVIDER: 'LLM_PROVIDER',
228
+ MADEINOZ_KNOWLEDGE_EMBEDDER_PROVIDER: 'EMBEDDER_PROVIDER',
229
+ MADEINOZ_KNOWLEDGE_MODEL_NAME: 'MODEL_NAME',
230
+ // Custom Endpoint Configuration
231
+ MADEINOZ_KNOWLEDGE_OPENAI_BASE_URL: 'OPENAI_BASE_URL',
232
+ MADEINOZ_KNOWLEDGE_EMBEDDER_PROVIDER_URL: 'EMBEDDER_PROVIDER_URL',
233
+ MADEINOZ_KNOWLEDGE_EMBEDDER_MODEL: 'EMBEDDER_MODEL',
234
+ MADEINOZ_KNOWLEDGE_EMBEDDER_DIMENSIONS: 'EMBEDDER_DIMENSIONS',
235
+ // Performance
236
+ MADEINOZ_KNOWLEDGE_SEMAPHORE_LIMIT: 'SEMAPHORE_LIMIT',
237
+ // Knowledge Graph
238
+ MADEINOZ_KNOWLEDGE_GROUP_ID: 'GROUP_ID',
239
+ MADEINOZ_KNOWLEDGE_DATABASE_TYPE: 'DATABASE_TYPE',
240
+ MADEINOZ_KNOWLEDGE_GRAPHITI_TELEMETRY_ENABLED: 'GRAPHITI_TELEMETRY_ENABLED',
241
+ // FalkorDB
242
+ MADEINOZ_KNOWLEDGE_FALKORDB_HOST: 'FALKORDB_HOST',
243
+ MADEINOZ_KNOWLEDGE_FALKORDB_PORT: 'FALKORDB_PORT',
244
+ MADEINOZ_KNOWLEDGE_FALKORDB_PASSWORD: 'FALKORDB_PASSWORD',
245
+ // Neo4j
246
+ MADEINOZ_KNOWLEDGE_NEO4J_URI: 'NEO4J_URI',
247
+ MADEINOZ_KNOWLEDGE_NEO4J_USER: 'NEO4J_USER',
248
+ MADEINOZ_KNOWLEDGE_NEO4J_PASSWORD: 'NEO4J_PASSWORD',
249
+ MADEINOZ_KNOWLEDGE_NEO4J_DATABASE: 'NEO4J_DATABASE',
250
+ // Prompt Caching
251
+ MADEINOZ_KNOWLEDGE_PROMPT_CACHE_ENABLED: 'PROMPT_CACHE_ENABLED',
252
+ MADEINOZ_KNOWLEDGE_PROMPT_CACHE_METRICS_ENABLED: 'PROMPT_CACHE_METRICS_ENABLED',
253
+ MADEINOZ_KNOWLEDGE_PROMPT_CACHE_LOG_REQUESTS: 'PROMPT_CACHE_LOG_REQUESTS',
254
+ MADEINOZ_KNOWLEDGE_PROMPT_CACHE_TTL: 'PROMPT_CACHE_TTL',
255
+ MADEINOZ_KNOWLEDGE_METRICS_PORT: 'METRICS_PORT',
256
+ // Remote MCP Access (Feature 010)
257
+ MADEINOZ_KNOWLEDGE_PROFILE: 'KNOWLEDGE_PROFILE',
258
+ MADEINOZ_KNOWLEDGE_HOST: 'KNOWLEDGE_HOST',
259
+ MADEINOZ_KNOWLEDGE_PORT: 'KNOWLEDGE_PORT',
260
+ MADEINOZ_KNOWLEDGE_PROTOCOL: 'KNOWLEDGE_PROTOCOL',
261
+ MADEINOZ_KNOWLEDGE_BASE_PATH: 'KNOWLEDGE_BASE_PATH',
262
+ MADEINOZ_KNOWLEDGE_TLS_VERIFY: 'KNOWLEDGE_TLS_VERIFY',
263
+ MADEINOZ_KNOWLEDGE_TLS_CA: 'KNOWLEDGE_TLS_CA',
264
+ MADEINOZ_KNOWLEDGE_TLS_CERT: 'KNOWLEDGE_TLS_CERT',
265
+ MADEINOZ_KNOWLEDGE_TLS_KEY: 'KNOWLEDGE_TLS_KEY',
266
+ };
267
+
268
+ // Apply mappings - MADEINOZ_KNOWLEDGE_* takes PRECEDENCE over unprefixed variables
269
+ for (const [paiVar, standardVar] of Object.entries(mappings)) {
270
+ const paiValue = env[paiVar];
271
+ if (paiValue) {
272
+ // Store original for reference
273
+ originals[paiVar] = paiValue;
274
+
275
+ // ALWAYS use MADEINOZ_KNOWLEDGE_* value, overriding any unprefixed variable
276
+ // This ensures prefixed vars take precedence as the primary configuration
277
+ mapped[standardVar] = paiValue;
278
+ }
279
+ }
280
+
281
+ return { mapped, originals };
282
+ }
283
+
284
+ /**
285
+ * Get configuration value with fallback to default
286
+ */
287
+ private getEnvValue(env: Record<string, string>, key: string, defaultValue?: string): string {
288
+ const value = env[key];
289
+ if (value) {
290
+ return value;
291
+ }
292
+ if (defaultValue !== undefined) {
293
+ return defaultValue;
294
+ }
295
+ // Return from defaults
296
+ return DEFAULTS[key] || '';
297
+ }
298
+
299
+ /**
300
+ * Load and validate configuration
301
+ */
302
+ async load(): Promise<KnowledgeConfig> {
303
+ // Load .env file
304
+ const env = await this.loadEnv();
305
+
306
+ // Map MADEINOZ_KNOWLEDGE_* prefixes
307
+ const { mapped, originals } = this.mapPrefixes(env);
308
+
309
+ // Build configuration object
310
+ const config: KnowledgeConfig = {
311
+ // API Keys
312
+ OPENAI_API_KEY: this.getEnvValue(mapped, 'OPENAI_API_KEY'),
313
+ ANTHROPIC_API_KEY: this.getEnvValue(mapped, 'ANTHROPIC_API_KEY'),
314
+ GOOGLE_API_KEY: this.getEnvValue(mapped, 'GOOGLE_API_KEY'),
315
+ GROQ_API_KEY: this.getEnvValue(mapped, 'GROQ_API_KEY'),
316
+ VOYAGE_API_KEY: this.getEnvValue(mapped, 'VOYAGE_API_KEY'),
317
+
318
+ // LLM Provider Configuration
319
+ LLM_PROVIDER: this.getEnvValue(mapped, 'LLM_PROVIDER', DEFAULTS.LLM_PROVIDER),
320
+ EMBEDDER_PROVIDER: this.getEnvValue(mapped, 'EMBEDDER_PROVIDER', DEFAULTS.EMBEDDER_PROVIDER),
321
+ MODEL_NAME: this.getEnvValue(mapped, 'MODEL_NAME', DEFAULTS.MODEL_NAME),
322
+
323
+ // Custom Endpoint Configuration
324
+ OPENAI_BASE_URL: this.getEnvValue(mapped, 'OPENAI_BASE_URL', ''),
325
+ EMBEDDER_PROVIDER_URL: this.getEnvValue(mapped, 'EMBEDDER_PROVIDER_URL', ''),
326
+ EMBEDDER_MODEL: this.getEnvValue(mapped, 'EMBEDDER_MODEL', ''),
327
+ EMBEDDER_DIMENSIONS: this.getEnvValue(mapped, 'EMBEDDER_DIMENSIONS', ''),
328
+
329
+ // Performance Configuration
330
+ SEMAPHORE_LIMIT: this.getEnvValue(mapped, 'SEMAPHORE_LIMIT', DEFAULTS.SEMAPHORE_LIMIT),
331
+
332
+ // Knowledge Graph Configuration
333
+ GROUP_ID: this.getEnvValue(mapped, 'GROUP_ID', DEFAULTS.GROUP_ID),
334
+
335
+ // Database Configuration
336
+ DATABASE_TYPE: this.getEnvValue(mapped, 'DATABASE_TYPE', DEFAULTS.DATABASE_TYPE),
337
+ FALKORDB_HOST: this.getEnvValue(mapped, 'FALKORDB_HOST', DEFAULTS.FALKORDB_HOST),
338
+ FALKORDB_PORT: this.getEnvValue(mapped, 'FALKORDB_PORT', DEFAULTS.FALKORDB_PORT),
339
+ FALKORDB_PASSWORD: this.getEnvValue(mapped, 'FALKORDB_PASSWORD', ''),
340
+
341
+ // Neo4j Configuration
342
+ NEO4J_URI: this.getEnvValue(mapped, 'NEO4J_URI', DEFAULTS.NEO4J_URI),
343
+ NEO4J_USER: this.getEnvValue(mapped, 'NEO4J_USER', DEFAULTS.NEO4J_USER),
344
+ NEO4J_PASSWORD: this.getEnvValue(mapped, 'NEO4J_PASSWORD', DEFAULTS.NEO4J_PASSWORD),
345
+
346
+ // Telemetry
347
+ GRAPHITI_TELEMETRY_ENABLED: this.getEnvValue(
348
+ mapped,
349
+ 'GRAPHITI_TELEMETRY_ENABLED',
350
+ DEFAULTS.GRAPHITI_TELEMETRY_ENABLED
351
+ ),
352
+
353
+ // Prompt Caching (Gemini)
354
+ PROMPT_CACHE_ENABLED: this.getEnvValue(mapped, 'PROMPT_CACHE_ENABLED', ''),
355
+ PROMPT_CACHE_METRICS_ENABLED: this.getEnvValue(mapped, 'PROMPT_CACHE_METRICS_ENABLED', ''),
356
+ PROMPT_CACHE_LOG_REQUESTS: this.getEnvValue(mapped, 'PROMPT_CACHE_LOG_REQUESTS', ''),
357
+ PROMPT_CACHE_TTL: this.getEnvValue(mapped, 'PROMPT_CACHE_TTL', ''),
358
+ METRICS_PORT: this.getEnvValue(mapped, 'METRICS_PORT', ''),
359
+
360
+ // Container Configuration
361
+ NETWORK_NAME: this.getEnvValue(mapped, 'NETWORK_NAME', DEFAULTS.NETWORK_NAME),
362
+ FALKORDB_CONTAINER: this.getEnvValue(
363
+ mapped,
364
+ 'FALKORDB_CONTAINER',
365
+ DEFAULTS.FALKORDB_CONTAINER
366
+ ),
367
+ MCP_CONTAINER: this.getEnvValue(mapped, 'MCP_CONTAINER', DEFAULTS.MCP_CONTAINER),
368
+ VOLUME_NAME: this.getEnvValue(mapped, 'VOLUME_NAME', DEFAULTS.VOLUME_NAME),
369
+
370
+ // Port Configuration
371
+ MCP_SERVER_PORT: this.getEnvValue(mapped, 'MCP_SERVER_PORT', DEFAULTS.MCP_SERVER_PORT),
372
+ FALKORDB_UI_PORT: this.getEnvValue(mapped, 'FALKORDB_UI_PORT', DEFAULTS.FALKORDB_UI_PORT),
373
+
374
+ // Store original MADEINOZ_KNOWLEDGE_* values
375
+ PAI_PREFIXES: originals,
376
+ };
377
+
378
+ return config;
379
+ }
380
+
381
+ /**
382
+ * Validate that required configuration is present
383
+ */
384
+ validate(config: KnowledgeConfig): { valid: boolean; errors: string[] } {
385
+ const errors: string[] = [];
386
+
387
+ // Check that at least one API key is configured (not required for Ollama)
388
+ const isOllama = config.LLM_PROVIDER === 'ollama';
389
+ const hasAnyKey =
390
+ !!config.OPENAI_API_KEY ||
391
+ !!config.ANTHROPIC_API_KEY ||
392
+ !!config.GOOGLE_API_KEY ||
393
+ !!config.GROQ_API_KEY;
394
+
395
+ if (!hasAnyKey && !isOllama) {
396
+ errors.push('No LLM API key configured (need OPENAI_API_KEY or alternative, or use Ollama)');
397
+ }
398
+
399
+ // Validate LLM provider
400
+ const validProviders = ['openai', 'anthropic', 'gemini', 'groq', 'ollama'];
401
+ if (!validProviders.includes(config.LLM_PROVIDER)) {
402
+ errors.push(`Invalid LLM_PROVIDER: ${config.LLM_PROVIDER}`);
403
+ }
404
+
405
+ // Validate Ollama configuration
406
+ if (isOllama && !config.OPENAI_BASE_URL) {
407
+ errors.push(
408
+ 'OPENAI_BASE_URL required for Ollama (e.g., http://host.docker.internal:11434/v1)'
409
+ );
410
+ }
411
+
412
+ // Validate database type
413
+ const validDatabases = ['falkordb', 'neo4j'];
414
+ if (!validDatabases.includes(config.DATABASE_TYPE)) {
415
+ errors.push(`Invalid DATABASE_TYPE: ${config.DATABASE_TYPE}`);
416
+ }
417
+
418
+ // Validate semaphore limit is a number
419
+ const semaphoreLimit = Number.parseInt(config.SEMAPHORE_LIMIT, 10);
420
+ if (Number.isNaN(semaphoreLimit) || semaphoreLimit < 1) {
421
+ errors.push(`Invalid SEMAPHORE_LIMIT: ${config.SEMAPHORE_LIMIT}`);
422
+ }
423
+
424
+ return {
425
+ valid: errors.length === 0,
426
+ errors,
427
+ };
428
+ }
429
+
430
+ /**
431
+ * Get configuration for container environment variables
432
+ * Maps config to container-friendly env var names
433
+ */
434
+ getContainerEnv(config: KnowledgeConfig): Record<string, string> {
435
+ const env: Record<string, string> = {
436
+ OPENAI_API_KEY: config.OPENAI_API_KEY || '',
437
+ ANTHROPIC_API_KEY: config.ANTHROPIC_API_KEY || '',
438
+ GOOGLE_API_KEY: config.GOOGLE_API_KEY || '',
439
+ GROQ_API_KEY: config.GROQ_API_KEY || '',
440
+ VOYAGE_API_KEY: config.VOYAGE_API_KEY || '',
441
+ DATABASE_TYPE: config.DATABASE_TYPE,
442
+ FALKORDB_HOST: config.FALKORDB_HOST || config.FALKORDB_CONTAINER,
443
+ FALKORDB_PORT: config.FALKORDB_PORT || '6379',
444
+ FALKORDB_PASSWORD: config.FALKORDB_PASSWORD || '',
445
+ // For containers, always use container hostname 'neo4j' not 'localhost'
446
+ NEO4J_URI: 'bolt://neo4j:7687',
447
+ NEO4J_USER: config.NEO4J_USER || 'neo4j',
448
+ NEO4J_PASSWORD: config.NEO4J_PASSWORD || 'demodemo',
449
+ SEMAPHORE_LIMIT: config.SEMAPHORE_LIMIT,
450
+ GRAPHITI_TELEMETRY_ENABLED: config.GRAPHITI_TELEMETRY_ENABLED,
451
+ MODEL_NAME: config.MODEL_NAME,
452
+ LLM_PROVIDER: config.LLM_PROVIDER,
453
+ EMBEDDER_PROVIDER: config.EMBEDDER_PROVIDER,
454
+ EMBEDDER_MODEL: config.EMBEDDER_MODEL || '',
455
+ EMBEDDER_DIMENSIONS: config.EMBEDDER_DIMENSIONS || '',
456
+ EMBEDDER_PROVIDER_URL: config.EMBEDDER_PROVIDER_URL || '',
457
+ GROUP_ID: config.GROUP_ID,
458
+ };
459
+
460
+ // LLM provider configuration
461
+ // For Ollama LLM: Use "openai" as provider with custom base URL (OpenAI-compatible API)
462
+ const isOllamaLLM = config.LLM_PROVIDER === 'ollama';
463
+ if (isOllamaLLM) {
464
+ env.LLM_PROVIDER = 'openai';
465
+ env.OPENAI_API_KEY = config.OPENAI_API_KEY || 'ollama'; // Ollama doesn't need a real key
466
+ env.OPENAI_BASE_URL = config.OPENAI_BASE_URL || DEFAULTS.OPENAI_BASE_URL;
467
+ } else if (config.OPENAI_BASE_URL) {
468
+ // Custom OpenAI endpoint (e.g., Azure, local proxy)
469
+ env.OPENAI_BASE_URL = config.OPENAI_BASE_URL;
470
+ }
471
+
472
+ // Embedder provider configuration (independent from LLM provider)
473
+ const isOllamaEmbedder = config.EMBEDDER_PROVIDER === 'ollama';
474
+ if (isOllamaEmbedder) {
475
+ // Ollama embedder uses native 'ollama' provider in Graphiti
476
+ env.EMBEDDER_PROVIDER = 'ollama';
477
+ env.EMBEDDER_PROVIDER_URL = config.EMBEDDER_PROVIDER_URL || DEFAULTS.EMBEDDER_PROVIDER_URL;
478
+ env.EMBEDDER_MODEL = config.EMBEDDER_MODEL || DEFAULTS.EMBEDDER_MODEL;
479
+ env.EMBEDDER_DIMENSIONS = config.EMBEDDER_DIMENSIONS || DEFAULTS.EMBEDDER_DIMENSIONS;
480
+ } else if (config.EMBEDDER_MODEL) {
481
+ env.EMBEDDER_MODEL = config.EMBEDDER_MODEL;
482
+ }
483
+
484
+ // Prompt Caching (Gemini)
485
+ if (config.PROMPT_CACHE_ENABLED) {
486
+ env.PROMPT_CACHE_ENABLED = config.PROMPT_CACHE_ENABLED;
487
+ }
488
+ if (config.PROMPT_CACHE_METRICS_ENABLED) {
489
+ env.PROMPT_CACHE_METRICS_ENABLED = config.PROMPT_CACHE_METRICS_ENABLED;
490
+ }
491
+ if (config.PROMPT_CACHE_LOG_REQUESTS) {
492
+ env.PROMPT_CACHE_LOG_REQUESTS = config.PROMPT_CACHE_LOG_REQUESTS;
493
+ }
494
+ if (config.PROMPT_CACHE_TTL) {
495
+ env.PROMPT_CACHE_TTL = config.PROMPT_CACHE_TTL;
496
+ }
497
+ if (config.METRICS_PORT) {
498
+ env.METRICS_PORT = config.METRICS_PORT;
499
+ }
500
+
501
+ return env;
502
+ }
503
+
504
+ /**
505
+ * Save configuration to PAI .env file
506
+ * Writes to ${PAI_DIR}/.env or ~/.claude/.env (the ONLY source of truth)
507
+ */
508
+ async save(config: Partial<KnowledgeConfig>): Promise<void> {
509
+ // Build new content for PAI .env
510
+ let newContent = '# Madeinoz Knowledge System Configuration\n';
511
+ newContent += `# Location: ${this.envFile}\n`;
512
+ newContent += `# Generated: ${new Date().toISOString()}\n`;
513
+ newContent += '\n';
514
+
515
+ // Add API Keys (all use MADEINOZ_KNOWLEDGE_* prefix)
516
+ if (config.OPENAI_API_KEY) {
517
+ newContent += `MADEINOZ_KNOWLEDGE_OPENAI_API_KEY=${config.OPENAI_API_KEY}\n`;
518
+ }
519
+ if (config.ANTHROPIC_API_KEY) {
520
+ newContent += `MADEINOZ_KNOWLEDGE_ANTHROPIC_API_KEY=${config.ANTHROPIC_API_KEY}\n`;
521
+ }
522
+ if (config.GOOGLE_API_KEY) {
523
+ newContent += `MADEINOZ_KNOWLEDGE_GOOGLE_API_KEY=${config.GOOGLE_API_KEY}\n`;
524
+ }
525
+ if (config.GROQ_API_KEY) {
526
+ newContent += `MADEINOZ_KNOWLEDGE_GROQ_API_KEY=${config.GROQ_API_KEY}\n`;
527
+ }
528
+ if (config.VOYAGE_API_KEY) {
529
+ newContent += `MADEINOZ_KNOWLEDGE_VOYAGE_API_KEY=${config.VOYAGE_API_KEY}\n`;
530
+ }
531
+
532
+ newContent += '\n';
533
+
534
+ // Add LLM Configuration
535
+ if (config.LLM_PROVIDER) {
536
+ newContent += `MADEINOZ_KNOWLEDGE_LLM_PROVIDER=${config.LLM_PROVIDER}\n`;
537
+ }
538
+ if (config.EMBEDDER_PROVIDER) {
539
+ newContent += `MADEINOZ_KNOWLEDGE_EMBEDDER_PROVIDER=${config.EMBEDDER_PROVIDER}\n`;
540
+ }
541
+ if (config.MODEL_NAME) {
542
+ newContent += `MADEINOZ_KNOWLEDGE_MODEL_NAME=${config.MODEL_NAME}\n`;
543
+ }
544
+
545
+ newContent += '\n';
546
+
547
+ // Add Ollama/Custom Endpoint Configuration
548
+ if (config.OPENAI_BASE_URL) {
549
+ newContent += '# Ollama/Custom Endpoint (for OpenAI-compatible APIs)\n';
550
+ newContent += `MADEINOZ_KNOWLEDGE_OPENAI_BASE_URL=${config.OPENAI_BASE_URL}\n`;
551
+ }
552
+ if (config.EMBEDDER_MODEL) {
553
+ newContent += `MADEINOZ_KNOWLEDGE_EMBEDDER_MODEL=${config.EMBEDDER_MODEL}\n`;
554
+ }
555
+
556
+ newContent += '\n';
557
+
558
+ // Add Performance Configuration
559
+ if (config.SEMAPHORE_LIMIT) {
560
+ newContent += `MADEINOZ_KNOWLEDGE_SEMAPHORE_LIMIT=${config.SEMAPHORE_LIMIT}\n`;
561
+ }
562
+
563
+ newContent += '\n';
564
+
565
+ // Add Knowledge Graph Configuration
566
+ if (config.GROUP_ID) {
567
+ newContent += `MADEINOZ_KNOWLEDGE_GROUP_ID=${config.GROUP_ID}\n`;
568
+ }
569
+
570
+ if (config.DATABASE_TYPE) {
571
+ newContent += `MADEINOZ_KNOWLEDGE_DATABASE_TYPE=${config.DATABASE_TYPE}\n`;
572
+ }
573
+
574
+ if (config.GRAPHITI_TELEMETRY_ENABLED) {
575
+ newContent += `MADEINOZ_KNOWLEDGE_GRAPHITI_TELEMETRY_ENABLED=${config.GRAPHITI_TELEMETRY_ENABLED}\n`;
576
+ }
577
+
578
+ newContent += '\n';
579
+
580
+ // Add Database Configuration (FalkorDB)
581
+ if (config.FALKORDB_HOST) {
582
+ newContent += `MADEINOZ_KNOWLEDGE_FALKORDB_HOST=${config.FALKORDB_HOST}\n`;
583
+ }
584
+ if (config.FALKORDB_PORT) {
585
+ newContent += `MADEINOZ_KNOWLEDGE_FALKORDB_PORT=${config.FALKORDB_PORT}\n`;
586
+ }
587
+ if (config.FALKORDB_PASSWORD) {
588
+ newContent += `MADEINOZ_KNOWLEDGE_FALKORDB_PASSWORD=${config.FALKORDB_PASSWORD}\n`;
589
+ }
590
+
591
+ newContent += '\n';
592
+
593
+ // Add Database Configuration (Neo4j)
594
+ if (config.NEO4J_URI) {
595
+ newContent += `MADEINOZ_KNOWLEDGE_NEO4J_URI=${config.NEO4J_URI}\n`;
596
+ }
597
+ if (config.NEO4J_USER) {
598
+ newContent += `MADEINOZ_KNOWLEDGE_NEO4J_USER=${config.NEO4J_USER}\n`;
599
+ }
600
+ if (config.NEO4J_PASSWORD) {
601
+ newContent += `MADEINOZ_KNOWLEDGE_NEO4J_PASSWORD=${config.NEO4J_PASSWORD}\n`;
602
+ }
603
+
604
+ // Write to file
605
+ await Bun.write(this.envFile, newContent);
606
+ }
607
+ }
608
+
609
+ /**
610
+ * Create a config loader instance
611
+ */
612
+ export function createConfigLoader(packRoot?: string): ConfigLoader {
613
+ return new ConfigLoader(packRoot);
614
+ }
615
+
616
+ /**
617
+ * Load configuration (convenience function)
618
+ */
619
+ export async function loadConfig(): Promise<KnowledgeConfig> {
620
+ const loader = new ConfigLoader();
621
+ return await loader.load();
622
+ }
623
+
624
+ /**
625
+ * Validate configuration (convenience function)
626
+ */
627
+ export function validateConfig(config: KnowledgeConfig): { valid: boolean; errors: string[] } {
628
+ const loader = new ConfigLoader();
629
+ return loader.validate(config);
630
+ }