mojulo 0.0.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/README.md +54 -4
  2. package/lib/audit-logger-new.js +11 -0
  3. package/lib/auth/gate.js +25 -0
  4. package/lib/auth/service.js +17 -0
  5. package/lib/auth/session.js +63 -0
  6. package/lib/builder/chat-processor.js +607 -0
  7. package/lib/builder/composer-bridge.js +82 -0
  8. package/lib/builder/evaluator.js +159 -0
  9. package/lib/builder/executor.js +252 -0
  10. package/lib/builder/index.js +48 -0
  11. package/lib/builder/session.js +248 -0
  12. package/lib/builder/system-prompt.js +422 -0
  13. package/lib/builder/tone-presets.js +75 -0
  14. package/lib/builder/tool-executors.js +1527 -0
  15. package/lib/builder/tools.js +338 -0
  16. package/lib/builder/validators.js +239 -0
  17. package/lib/composer/composer.js +225 -0
  18. package/lib/composer/index.js +40 -0
  19. package/lib/composer/protocols/00_base.txt +19 -0
  20. package/lib/composer/protocols/01_knowledge.txt +9 -0
  21. package/lib/composer/protocols/02_form-gathering.txt +32 -0
  22. package/lib/composer/protocols/03_appointments.txt +16 -0
  23. package/lib/composer/protocols/04_triage.txt +15 -0
  24. package/lib/composer/protocols/05_optical-read.txt +22 -0
  25. package/lib/composer/response-builder.js +98 -0
  26. package/lib/config-builder.js +650 -0
  27. package/lib/db/ids.js +10 -0
  28. package/lib/db/index.js +179 -0
  29. package/lib/db/repositories/apiKeys.js +72 -0
  30. package/lib/db/repositories/auditLogs.js +12 -0
  31. package/lib/db/repositories/botSpaces.js +12 -0
  32. package/lib/db/repositories/builderSessions.js +312 -0
  33. package/lib/db/repositories/deploymentEvents.js +12 -0
  34. package/lib/db/repositories/deployments.js +385 -0
  35. package/lib/db/repositories/documents.js +68 -0
  36. package/lib/db/repositories/mcpJobs.js +84 -0
  37. package/lib/deployers/bot-fleet.js +110 -0
  38. package/lib/deployers/bot-proxy.js +72 -0
  39. package/lib/deployers/build.js +89 -0
  40. package/lib/deployers/cloud-deploy.js +310 -0
  41. package/lib/deployers/docker.js +439 -0
  42. package/lib/deployers/fly.js +432 -0
  43. package/lib/deployers/index.js +38 -0
  44. package/lib/deployment-auth.js +36 -0
  45. package/lib/document-parser.js +171 -0
  46. package/lib/embedder/chunker.js +93 -0
  47. package/lib/embedder/local.js +101 -0
  48. package/lib/embedder/preview-rag.js +93 -0
  49. package/lib/envelope-schema.js +54 -0
  50. package/lib/fleet/scoped-sql.js +342 -0
  51. package/lib/form-schema-config/base.js +135 -0
  52. package/lib/form-schema-config/index.js +286 -0
  53. package/lib/form-schema-config/locales/af-ZA.js +153 -0
  54. package/lib/form-schema-config/locales/ar-AE.js +142 -0
  55. package/lib/form-schema-config/locales/ar-SA.js +164 -0
  56. package/lib/form-schema-config/locales/de-DE.js +152 -0
  57. package/lib/form-schema-config/locales/en-AU.js +161 -0
  58. package/lib/form-schema-config/locales/en-CA.js +115 -0
  59. package/lib/form-schema-config/locales/en-GB.js +132 -0
  60. package/lib/form-schema-config/locales/en-IN.js +219 -0
  61. package/lib/form-schema-config/locales/en-MY.js +171 -0
  62. package/lib/form-schema-config/locales/en-NG.js +198 -0
  63. package/lib/form-schema-config/locales/en-PH.js +186 -0
  64. package/lib/form-schema-config/locales/en-SG.js +153 -0
  65. package/lib/form-schema-config/locales/en-US.js +138 -0
  66. package/lib/form-schema-config/locales/es-ES.js +171 -0
  67. package/lib/form-schema-config/locales/es-MX.js +193 -0
  68. package/lib/form-schema-config/locales/fr-CA.js +138 -0
  69. package/lib/form-schema-config/locales/fr-FR.js +155 -0
  70. package/lib/form-schema-config/locales/hi-IN.js +219 -0
  71. package/lib/form-schema-config/locales/it-IT.js +157 -0
  72. package/lib/form-schema-config/locales/ja-JP.js +169 -0
  73. package/lib/form-schema-config/locales/ko-KR.js +140 -0
  74. package/lib/form-schema-config/locales/nl-NL.js +149 -0
  75. package/lib/form-schema-config/locales/pt-BR.js +168 -0
  76. package/lib/form-schema-config/locales/zh-CN.js +172 -0
  77. package/lib/form-schema-config/locales/zh-HK.js +142 -0
  78. package/lib/form-structure-schema.js +191 -0
  79. package/lib/llm-providers.js +828 -0
  80. package/lib/markdown.js +197 -0
  81. package/lib/mcp/catalysts/appointment-to-calendar.md +84 -0
  82. package/lib/mcp/catalysts/conversations-to-channel-digest.md +104 -0
  83. package/lib/mcp/catalysts/document-extract-to-store.md +92 -0
  84. package/lib/mcp/catalysts/knowledge-gap-miner.md +96 -0
  85. package/lib/mcp/catalysts/loader.js +144 -0
  86. package/lib/mcp/catalysts/qualify-lead-to-crm.md +83 -0
  87. package/lib/mcp/catalysts/scan-conversations-for-signal.md +92 -0
  88. package/lib/mcp/catalysts/submission-to-ticket.md +83 -0
  89. package/lib/mcp/catalysts/submissions-to-warehouse.md +103 -0
  90. package/lib/mcp/catalysts/weekly-submissions-digest.md +82 -0
  91. package/lib/mcp/jobs.js +64 -0
  92. package/lib/mcp/server.js +184 -0
  93. package/lib/mcp/session-binding.js +130 -0
  94. package/lib/mcp/tools/build.js +123 -0
  95. package/lib/mcp/tools/catalysts.js +477 -0
  96. package/lib/mcp/tools/context.js +325 -0
  97. package/lib/mcp/tools/fleet.js +391 -0
  98. package/lib/mcp/tools/jobs-tools.js +240 -0
  99. package/lib/mcp/tools/operate.js +314 -0
  100. package/lib/preview/build-preview-config.js +136 -0
  101. package/lib/rate-limiter.js +11 -0
  102. package/lib/resolve-api-key.js +142 -0
  103. package/lib/storage/index.js +40 -0
  104. package/messages/de.json +2136 -0
  105. package/messages/en.json +2136 -0
  106. package/messages/es.json +2136 -0
  107. package/messages/fr.json +2136 -0
  108. package/messages/it.json +2136 -0
  109. package/messages/ja.json +2136 -0
  110. package/messages/ko.json +2136 -0
  111. package/messages/nl.json +2136 -0
  112. package/messages/pl.json +2136 -0
  113. package/messages/pt.json +2136 -0
  114. package/messages/ru.json +2136 -0
  115. package/messages/uk.json +2136 -0
  116. package/messages/zh.json +2136 -0
  117. package/package.json +68 -5
  118. package/scripts/mcp-config.mjs +162 -0
  119. package/scripts/mcp-stdio-loader.mjs +42 -0
  120. package/scripts/mcp-stdio.mjs +108 -0
  121. package/scripts/mojulo-paths.mjs +48 -0
package/package.json CHANGED
@@ -1,9 +1,72 @@
1
1
  {
2
2
  "name": "mojulo",
3
- "version": "0.0.0",
4
- "description": "Placeholder. Real release will expose a stdio MCP server for building self-hosted bots from inside Claude.",
3
+ "version": "0.1.1",
4
+ "license": "Apache-2.0",
5
+ "description": "Mojulo — MCP server for building self-hosted chatbots from inside Claude.",
6
+ "author": "Franz Ombico",
5
7
  "homepage": "https://github.com/zombico/mojulo",
6
- "repository": { "type": "git", "url": "https://github.com/zombico/mojulo.git" },
7
- "author": "Franz Ombico <franzombico@gmail.com>",
8
- "license": "UNLICENSED"
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/zombico/mojulo.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/zombico/mojulo/issues"
14
+ },
15
+ "keywords": [
16
+ "mcp",
17
+ "claude",
18
+ "anthropic",
19
+ "claude-code",
20
+ "chatbot",
21
+ "bot-builder",
22
+ "rag",
23
+ "self-hosted",
24
+ "mojulo"
25
+ ],
26
+ "engines": {
27
+ "node": ">=20"
28
+ },
29
+ "bin": {
30
+ "mojulo": "./scripts/mcp-stdio.mjs",
31
+ "mojulo-config": "./scripts/mcp-config.mjs"
32
+ },
33
+ "files": [
34
+ "lib/**",
35
+ "!lib/embedder/models/**",
36
+ "!lib/**/*.test.js",
37
+ "scripts/mcp-stdio.mjs",
38
+ "scripts/mcp-stdio-loader.mjs",
39
+ "scripts/mcp-config.mjs",
40
+ "scripts/mojulo-paths.mjs",
41
+ "messages/**"
42
+ ],
43
+ "scripts": {
44
+ "dev": "next dev -p 3001",
45
+ "build": "next build",
46
+ "start": "next start -p 3001",
47
+ "build:bot": "docker build -t mojulo/bot:latest ../lite-template",
48
+ "mcp-stdio": "node scripts/mcp-stdio.mjs",
49
+ "mcp-config": "node scripts/mcp-config.mjs",
50
+ "stage-lite-template": "node scripts/stage-lite-template.mjs",
51
+ "test": "vitest run"
52
+ },
53
+ "dependencies": {
54
+ "@aws-sdk/client-bedrock-runtime": "^3.1011.0",
55
+ "@huggingface/transformers": "^4.2.0",
56
+ "archiver": "^7.0.1",
57
+ "better-sqlite3": "^12.4.1",
58
+ "dotenv": "^17.2.3",
59
+ "next": "^16.0.7",
60
+ "next-intl": "^4.8.3",
61
+ "officeparser": "^5.2.2",
62
+ "pdf2json": "^4.0.0",
63
+ "react": "19.2.0",
64
+ "react-dom": "19.2.0",
65
+ "swr": "^2.4.1"
66
+ },
67
+ "devDependencies": {
68
+ "@tailwindcss/postcss": "^4",
69
+ "tailwindcss": "^4",
70
+ "vitest": "^4.1.6"
71
+ }
9
72
  }
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Config CLI for the mojulo control plane.
4
+ *
5
+ * Writes / reads provider keys in the existing encrypted api_keys table —
6
+ * same source the Settings UI populates, same AES-256-GCM encryption
7
+ * ([@/lib/deployment-auth](../lib/deployment-auth.js)). This deviates from
8
+ * §9 of the release plan (which sketched a plaintext ~/.mojulo/config.json):
9
+ * single source of truth, reuses encryption, no downstream resolver shim.
10
+ *
11
+ * Usage:
12
+ * mojulo-mcp-config set anthropic sk-ant-...
13
+ * mojulo-mcp-config set openai sk-...
14
+ * mojulo-mcp-config set ollama http://localhost:11434
15
+ * mojulo-mcp-config set fly fo1_...
16
+ * mojulo-mcp-config list
17
+ * mojulo-mcp-config unset openai
18
+ *
19
+ * `set` replaces any existing key(s) for that provider with a single fresh
20
+ * row, and marks it default if no default exists.
21
+ */
22
+
23
+ import { register } from 'node:module';
24
+ import { fileURLToPath } from 'node:url';
25
+ import path from 'node:path';
26
+ import { resolveMojuloPaths } from './mojulo-paths.mjs';
27
+
28
+ register('./mcp-stdio-loader.mjs', import.meta.url);
29
+
30
+ resolveMojuloPaths();
31
+
32
+ const CONTROL_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
33
+ process.chdir(CONTROL_DIR);
34
+
35
+ const ALLOWED_PROVIDERS = new Set(['anthropic', 'openai', 'ollama', 'fly']);
36
+
37
+ function usage(exitCode = 0) {
38
+ const lines = [
39
+ 'Usage:',
40
+ ' mojulo-mcp-config set <provider> <value>',
41
+ ' mojulo-mcp-config list',
42
+ ' mojulo-mcp-config unset <provider>',
43
+ '',
44
+ `Providers: ${[...ALLOWED_PROVIDERS].join(', ')}`,
45
+ ];
46
+ process.stdout.write(lines.join('\n') + '\n');
47
+ process.exit(exitCode);
48
+ }
49
+
50
+ function maskValue(plaintext) {
51
+ if (!plaintext) return '(empty)';
52
+ if (plaintext.length <= 8) return '*'.repeat(plaintext.length);
53
+ return `${plaintext.slice(0, 4)}…${plaintext.slice(-4)}`;
54
+ }
55
+
56
+ async function setKey(provider, value) {
57
+ const { ApiKeyRepository } = await import('@/lib/db/repositories/apiKeys');
58
+ const { encryptApiKey } = await import('@/lib/deployment-auth');
59
+
60
+ const existing = await ApiKeyRepository.findByUserId('local');
61
+ const sameProvider = existing.filter((k) => k.provider === provider);
62
+ for (const row of sameProvider) {
63
+ await ApiKeyRepository.delete(row.id);
64
+ }
65
+
66
+ const remaining = existing.filter((k) => k.provider !== provider);
67
+ const isDefault = !remaining.some((k) => k.isDefault);
68
+
69
+ await ApiKeyRepository.create({
70
+ name: `${provider}-cli`,
71
+ provider,
72
+ encryptedKey: encryptApiKey(value),
73
+ isDefault,
74
+ });
75
+
76
+ process.stdout.write(
77
+ `Set ${provider} → ${maskValue(value)}${isDefault ? ' (default)' : ''}\n`
78
+ );
79
+ }
80
+
81
+ async function listKeys() {
82
+ const { ApiKeyRepository } = await import('@/lib/db/repositories/apiKeys');
83
+ const keys = await ApiKeyRepository.findByUserId('local');
84
+ if (keys.length === 0) {
85
+ process.stdout.write('No provider keys configured.\n');
86
+ process.stdout.write('Set one with: mojulo-mcp-config set anthropic sk-ant-...\n');
87
+ return;
88
+ }
89
+ const rows = keys.map((k) => ({
90
+ provider: k.provider,
91
+ name: k.name,
92
+ default: k.isDefault ? '*' : '',
93
+ }));
94
+ const widths = {
95
+ provider: Math.max(8, ...rows.map((r) => r.provider.length)),
96
+ name: Math.max(4, ...rows.map((r) => r.name.length)),
97
+ default: 7,
98
+ };
99
+ const fmt = (r) =>
100
+ `${r.provider.padEnd(widths.provider)} ${r.name.padEnd(widths.name)} ${r.default}`;
101
+ process.stdout.write(fmt({ provider: 'provider', name: 'name', default: 'default' }) + '\n');
102
+ process.stdout.write(
103
+ `${'-'.repeat(widths.provider)} ${'-'.repeat(widths.name)} ${'-'.repeat(widths.default)}\n`
104
+ );
105
+ for (const r of rows) process.stdout.write(fmt(r) + '\n');
106
+ }
107
+
108
+ async function unsetKey(provider) {
109
+ const { ApiKeyRepository } = await import('@/lib/db/repositories/apiKeys');
110
+ const existing = await ApiKeyRepository.findByUserId('local');
111
+ const sameProvider = existing.filter((k) => k.provider === provider);
112
+ if (sameProvider.length === 0) {
113
+ process.stdout.write(`No keys for ${provider}.\n`);
114
+ return;
115
+ }
116
+ for (const row of sameProvider) {
117
+ await ApiKeyRepository.delete(row.id);
118
+ }
119
+ process.stdout.write(`Removed ${sameProvider.length} key(s) for ${provider}.\n`);
120
+ }
121
+
122
+ const [, , subcommand, ...rest] = process.argv;
123
+
124
+ if (!subcommand || subcommand === '-h' || subcommand === '--help') {
125
+ usage(0);
126
+ }
127
+
128
+ try {
129
+ if (subcommand === 'set') {
130
+ const [provider, value] = rest;
131
+ if (!provider || !value) {
132
+ process.stderr.write('set requires <provider> <value>\n');
133
+ usage(2);
134
+ }
135
+ if (!ALLOWED_PROVIDERS.has(provider)) {
136
+ process.stderr.write(`Unknown provider: ${provider}\n`);
137
+ usage(2);
138
+ }
139
+ await setKey(provider, value);
140
+ } else if (subcommand === 'list') {
141
+ await listKeys();
142
+ } else if (subcommand === 'unset') {
143
+ const [provider] = rest;
144
+ if (!provider) {
145
+ process.stderr.write('unset requires <provider>\n');
146
+ usage(2);
147
+ }
148
+ if (!ALLOWED_PROVIDERS.has(provider)) {
149
+ process.stderr.write(`Unknown provider: ${provider}\n`);
150
+ usage(2);
151
+ }
152
+ await unsetKey(provider);
153
+ } else {
154
+ process.stderr.write(`Unknown subcommand: ${subcommand}\n`);
155
+ usage(2);
156
+ }
157
+ } catch (err) {
158
+ process.stderr.write(`mojulo-mcp-config: ${err.message || err}\n`);
159
+ process.exit(1);
160
+ }
161
+
162
+ process.exit(0);
@@ -0,0 +1,42 @@
1
+ /**
2
+ * ESM resolver hook that maps `@/...` imports to the control directory so the
3
+ * stdio MCP entry can run under plain Node (Next.js handles `@/` natively via
4
+ * jsconfig.json, but Node does not).
5
+ *
6
+ * Registered by [mcp-stdio.mjs](./mcp-stdio.mjs) via `module.register`. Hooks
7
+ * run in a worker thread and fire for every subsequent dynamic import — the
8
+ * stdio entry's `await import('@/lib/mcp/server')` is resolved here.
9
+ */
10
+
11
+ import path from 'node:path';
12
+ import { fileURLToPath, pathToFileURL } from 'node:url';
13
+ import { existsSync, statSync } from 'node:fs';
14
+
15
+ const CONTROL_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
16
+
17
+ const EXTS = ['', '.js', '.mjs', '.cjs', '.json'];
18
+
19
+ function resolveAlias(specifier) {
20
+ const base = path.join(CONTROL_DIR, specifier.slice(2));
21
+ for (const ext of EXTS) {
22
+ const candidate = base + ext;
23
+ if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
24
+ }
25
+ if (existsSync(base) && statSync(base).isDirectory()) {
26
+ for (const ext of ['.js', '.mjs']) {
27
+ const indexFile = path.join(base, `index${ext}`);
28
+ if (existsSync(indexFile)) return indexFile;
29
+ }
30
+ }
31
+ return null;
32
+ }
33
+
34
+ export function resolve(specifier, context, nextResolve) {
35
+ if (specifier.startsWith('@/')) {
36
+ const resolved = resolveAlias(specifier);
37
+ if (resolved) {
38
+ return nextResolve(pathToFileURL(resolved).href, context);
39
+ }
40
+ }
41
+ return nextResolve(specifier, context);
42
+ }
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Stdio MCP transport for the control plane.
4
+ *
5
+ * One process per MCP connection (the client spawns this and communicates over
6
+ * stdin/stdout). Newline-delimited JSON-RPC 2.0 frames; logs go to stderr only
7
+ * because stdout is the protocol channel.
8
+ *
9
+ * Usage:
10
+ * claude mcp add mojulo --command "node /abs/path/to/control/scripts/mcp-stdio.mjs"
11
+ *
12
+ * This is the working-tree entry referenced in §12 Milestone 1 of
13
+ * [lite-template/integration/npx_package_release_plan.md]. The npm package
14
+ * (§12 Milestone 3) will ship a bundled equivalent as `bin/mojulo-mcp`.
15
+ */
16
+
17
+ import { register } from 'node:module';
18
+ import { fileURLToPath } from 'node:url';
19
+ import path from 'node:path';
20
+ import readline from 'node:readline';
21
+ import { resolveMojuloPaths } from './mojulo-paths.mjs';
22
+
23
+ // Resolve `@/...` like Next.js does, so the stdio entry can reuse the same
24
+ // server.js + tool modules the Next.js route uses.
25
+ register('./mcp-stdio-loader.mjs', import.meta.url);
26
+
27
+ // User data lives under MOJULO_HOME (default ~/.mojulo). This populates
28
+ // SQLITE_PATH / ARTIFACTS_DIR / STORAGE_ROOT / MOJULO_MODELS_DIR so the lib
29
+ // code lands user state there instead of a cwd-relative ./data/.
30
+ resolveMojuloPaths();
31
+
32
+ // chdir to control/ for packaged-asset paths the lib still reads from cwd
33
+ // (lib/composer/composer.js PROTOCOLS_DIR default). M3 v0.2.0 will swap
34
+ // these for __dirname-relative resolution when the UI bin lands and we
35
+ // re-ship lite-template/ for the wizard preview path.
36
+ const CONTROL_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
37
+ process.chdir(CONTROL_DIR);
38
+
39
+ // Stdout is the MCP protocol channel — any stray log corrupts the frame
40
+ // stream. Several tool executors and the composer emit progress via
41
+ // console.log; pin those to stderr for the stdio process only. The Next.js
42
+ // route is unaffected.
43
+ console.log = console.error;
44
+ console.info = console.error;
45
+
46
+ const { dispatchMcpRequest, ensureToolsRegistered } = await import('@/lib/mcp/server');
47
+ await ensureToolsRegistered();
48
+
49
+ // Kick off the embedder model fetch in background. The first RAG bot build
50
+ // is the iconic genesis flow, and the model load (~113MB on a cold cache)
51
+ // is the longest single step. Starting it now lets the download/load overlap
52
+ // with Claude's initial exchanges instead of blocking process_documents at
53
+ // T6. The lazy path in lib/embedder/local.js shares promise state, so a
54
+ // tool call arriving mid-load simply awaits whatever's left. Failures here
55
+ // surface at first use — don't crash the MCP server.
56
+ import('@/lib/embedder/local').then(({ preloadModel }) =>
57
+ preloadModel().catch((err) =>
58
+ console.error('[mcp-stdio] embedder preload failed:', err.message)
59
+ )
60
+ );
61
+
62
+ const CONTEXT = { mcpSessionId: 'stdio', userId: 'local' };
63
+
64
+ function writeFrame(payload) {
65
+ process.stdout.write(JSON.stringify(payload) + '\n');
66
+ }
67
+
68
+ function parseError() {
69
+ return { jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } };
70
+ }
71
+
72
+ async function handleLine(line) {
73
+ let body;
74
+ try {
75
+ body = JSON.parse(line);
76
+ } catch {
77
+ writeFrame(parseError());
78
+ return;
79
+ }
80
+
81
+ if (Array.isArray(body)) {
82
+ const responses = [];
83
+ for (const msg of body) {
84
+ const resp = await dispatchMcpRequest(msg, CONTEXT);
85
+ if (resp !== null) responses.push(resp);
86
+ }
87
+ if (responses.length > 0) writeFrame(responses);
88
+ return;
89
+ }
90
+
91
+ const resp = await dispatchMcpRequest(body, CONTEXT);
92
+ if (resp !== null) writeFrame(resp);
93
+ }
94
+
95
+ const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
96
+
97
+ rl.on('line', (line) => {
98
+ if (!line.trim()) return;
99
+ handleLine(line).catch((err) => {
100
+ console.error('[mcp-stdio] dispatch error:', err);
101
+ });
102
+ });
103
+
104
+ rl.on('close', () => process.exit(0));
105
+
106
+ for (const signal of ['SIGINT', 'SIGTERM']) {
107
+ process.on(signal, () => process.exit(0));
108
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Resolve the mojulo home / data / models directories and seed the env vars
3
+ * the rest of the control plane reads.
4
+ *
5
+ * Layered defaults:
6
+ * MOJULO_HOME (default: ~/.mojulo)
7
+ * MOJULO_DATA_DIR (default: $MOJULO_HOME/data)
8
+ * MOJULO_MODELS_DIR (default: $MOJULO_HOME/models)
9
+ *
10
+ * From those, sets — without overriding — the lower-level env vars the lib
11
+ * code already honors:
12
+ * SQLITE_PATH → $MOJULO_DATA_DIR/mojulo-lite.db
13
+ * ARTIFACTS_DIR → $MOJULO_DATA_DIR/artifacts
14
+ * STORAGE_ROOT → $MOJULO_DATA_DIR/storage
15
+ *
16
+ * Shared by [mcp-stdio.mjs](./mcp-stdio.mjs) and [mcp-config.mjs](./mcp-config.mjs)
17
+ * so a fresh `~/.mojulo/` works for both stdio and the config CLI.
18
+ */
19
+
20
+ import os from 'node:os';
21
+ import path from 'node:path';
22
+ import fs from 'node:fs';
23
+
24
+ export function resolveMojuloPaths() {
25
+ const home = process.env.MOJULO_HOME || path.join(os.homedir(), '.mojulo');
26
+ const dataDir = process.env.MOJULO_DATA_DIR || path.join(home, 'data');
27
+ const modelsDir = process.env.MOJULO_MODELS_DIR || path.join(home, 'models');
28
+
29
+ process.env.MOJULO_HOME ??= home;
30
+ process.env.MOJULO_DATA_DIR ??= dataDir;
31
+ process.env.MOJULO_MODELS_DIR ??= modelsDir;
32
+ process.env.SQLITE_PATH ??= path.join(dataDir, 'mojulo-lite.db');
33
+ process.env.ARTIFACTS_DIR ??= path.join(dataDir, 'artifacts');
34
+ process.env.STORAGE_ROOT ??= path.join(dataDir, 'storage');
35
+
36
+ for (const dir of [home, dataDir, modelsDir, process.env.ARTIFACTS_DIR, process.env.STORAGE_ROOT]) {
37
+ fs.mkdirSync(dir, { recursive: true });
38
+ }
39
+
40
+ return {
41
+ home,
42
+ dataDir,
43
+ modelsDir,
44
+ dbPath: process.env.SQLITE_PATH,
45
+ artifactsDir: process.env.ARTIFACTS_DIR,
46
+ storageRoot: process.env.STORAGE_ROOT,
47
+ };
48
+ }