morpheus-cli 0.6.6 → 0.6.7
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.
|
@@ -7,8 +7,58 @@ import { homedir } from "os";
|
|
|
7
7
|
import Database from "better-sqlite3";
|
|
8
8
|
import { TaskRepository } from "../tasks/repository.js";
|
|
9
9
|
import { TaskRequestContext } from "../tasks/context.js";
|
|
10
|
+
import { isEnvVarSet } from "../../config/precedence.js";
|
|
10
11
|
// ─── Shared ───────────────────────────────────────────────────────────────────
|
|
11
12
|
const shortMemoryDbPath = path.join(homedir(), ".morpheus", "memory", "short-memory.db");
|
|
13
|
+
/**
|
|
14
|
+
* Map of config paths to their corresponding environment variable names.
|
|
15
|
+
* Used to check if a config field is being overridden by an env var.
|
|
16
|
+
*/
|
|
17
|
+
const CONFIG_TO_ENV_MAP = {
|
|
18
|
+
'llm.provider': ['MORPHEUS_LLM_PROVIDER'],
|
|
19
|
+
'llm.model': ['MORPHEUS_LLM_MODEL'],
|
|
20
|
+
'llm.temperature': ['MORPHEUS_LLM_TEMPERATURE'],
|
|
21
|
+
'llm.max_tokens': ['MORPHEUS_LLM_MAX_TOKENS'],
|
|
22
|
+
'llm.api_key': ['MORPHEUS_LLM_API_KEY', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'GOOGLE_API_KEY', 'OPENROUTER_API_KEY'],
|
|
23
|
+
'llm.context_window': ['MORPHEUS_LLM_CONTEXT_WINDOW'],
|
|
24
|
+
'sati.provider': ['MORPHEUS_SATI_PROVIDER'],
|
|
25
|
+
'sati.model': ['MORPHEUS_SATI_MODEL'],
|
|
26
|
+
'sati.temperature': ['MORPHEUS_SATI_TEMPERATURE'],
|
|
27
|
+
'sati.api_key': ['MORPHEUS_SATI_API_KEY'],
|
|
28
|
+
'neo.provider': ['MORPHEUS_NEO_PROVIDER'],
|
|
29
|
+
'neo.model': ['MORPHEUS_NEO_MODEL'],
|
|
30
|
+
'neo.temperature': ['MORPHEUS_NEO_TEMPERATURE'],
|
|
31
|
+
'neo.api_key': ['MORPHEUS_NEO_API_KEY'],
|
|
32
|
+
'apoc.provider': ['MORPHEUS_APOC_PROVIDER'],
|
|
33
|
+
'apoc.model': ['MORPHEUS_APOC_MODEL'],
|
|
34
|
+
'apoc.temperature': ['MORPHEUS_APOC_TEMPERATURE'],
|
|
35
|
+
'apoc.api_key': ['MORPHEUS_APOC_API_KEY'],
|
|
36
|
+
'apoc.working_dir': ['MORPHEUS_APOC_WORKING_DIR'],
|
|
37
|
+
'apoc.timeout_ms': ['MORPHEUS_APOC_TIMEOUT_MS'],
|
|
38
|
+
'trinity.provider': ['MORPHEUS_TRINITY_PROVIDER'],
|
|
39
|
+
'trinity.model': ['MORPHEUS_TRINITY_MODEL'],
|
|
40
|
+
'trinity.temperature': ['MORPHEUS_TRINITY_TEMPERATURE'],
|
|
41
|
+
'trinity.api_key': ['MORPHEUS_TRINITY_API_KEY'],
|
|
42
|
+
'audio.provider': ['MORPHEUS_AUDIO_PROVIDER'],
|
|
43
|
+
'audio.model': ['MORPHEUS_AUDIO_MODEL'],
|
|
44
|
+
'audio.apiKey': ['MORPHEUS_AUDIO_API_KEY'],
|
|
45
|
+
'audio.maxDurationSeconds': ['MORPHEUS_AUDIO_MAX_DURATION'],
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Checks if a config field is overridden by an environment variable.
|
|
49
|
+
*/
|
|
50
|
+
function isFieldOverriddenByEnv(fieldPath) {
|
|
51
|
+
const envVars = CONFIG_TO_ENV_MAP[fieldPath];
|
|
52
|
+
if (!envVars)
|
|
53
|
+
return false;
|
|
54
|
+
return envVars.some(envVar => isEnvVarSet(envVar));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Gets all fields that are overridden by environment variables.
|
|
58
|
+
*/
|
|
59
|
+
function getOverriddenFields() {
|
|
60
|
+
return Object.keys(CONFIG_TO_ENV_MAP).filter(isFieldOverriddenByEnv);
|
|
61
|
+
}
|
|
12
62
|
// ─── Config ───────────────────────────────────────────────────────────────────
|
|
13
63
|
function setNestedValue(obj, dotPath, value) {
|
|
14
64
|
const keys = dotPath.split(".");
|
|
@@ -43,23 +93,37 @@ export const ConfigQueryTool = tool(async ({ key }) => {
|
|
|
43
93
|
}),
|
|
44
94
|
});
|
|
45
95
|
export const ConfigUpdateTool = tool(async ({ updates }) => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
96
|
+
const configManager = ConfigManager.getInstance();
|
|
97
|
+
await configManager.load();
|
|
98
|
+
// Check if any fields are overridden by environment variables
|
|
99
|
+
const overriddenFields = [];
|
|
100
|
+
for (const key in updates) {
|
|
101
|
+
if (isFieldOverriddenByEnv(key)) {
|
|
102
|
+
overriddenFields.push(key);
|
|
53
103
|
}
|
|
54
|
-
await configManager.save(newConfig);
|
|
55
|
-
return JSON.stringify({ success: true, message: "Configuration updated successfully" });
|
|
56
104
|
}
|
|
57
|
-
|
|
58
|
-
|
|
105
|
+
if (overriddenFields.length > 0) {
|
|
106
|
+
const envVarNames = overriddenFields
|
|
107
|
+
.flatMap(field => CONFIG_TO_ENV_MAP[field] || [])
|
|
108
|
+
.filter((v, i, arr) => arr.indexOf(v) === i) // Remove duplicates
|
|
109
|
+
.join(', ');
|
|
110
|
+
const errorMsg = `BLOCKED_BY_ENV: Cannot update ${overriddenFields.join(', ')} because these fields are controlled by environment variables (${envVarNames}). To change them, edit your .env file and restart Morpheus.`;
|
|
111
|
+
throw new Error(errorMsg);
|
|
112
|
+
}
|
|
113
|
+
const currentConfig = configManager.get();
|
|
114
|
+
const newConfig = { ...currentConfig };
|
|
115
|
+
for (const key in updates) {
|
|
116
|
+
setNestedValue(newConfig, key, updates[key]);
|
|
59
117
|
}
|
|
118
|
+
await configManager.save(newConfig);
|
|
119
|
+
return JSON.stringify({
|
|
120
|
+
success: true,
|
|
121
|
+
message: "Configuration updated successfully",
|
|
122
|
+
updatedFields: Object.keys(updates),
|
|
123
|
+
});
|
|
60
124
|
}, {
|
|
61
125
|
name: "morpheus_config_update",
|
|
62
|
-
description: "Updates configuration values with validation. Accepts an 'updates' object containing key-value pairs to update. Supports dot notation for nested fields (e.g. 'llm.model').",
|
|
126
|
+
description: "Updates configuration values with validation. Accepts an 'updates' object containing key-value pairs to update. Supports dot notation for nested fields (e.g. 'llm.model'). Note: Fields overridden by environment variables cannot be updated.",
|
|
63
127
|
schema: z.object({
|
|
64
128
|
updates: z.object({}).passthrough(),
|
|
65
129
|
}),
|
|
@@ -77,30 +141,39 @@ export const DiagnosticTool = tool(async () => {
|
|
|
77
141
|
const config = configManager.get();
|
|
78
142
|
const requiredFields = ["llm", "logging", "ui"];
|
|
79
143
|
const missingFields = requiredFields.filter((field) => !(field in config));
|
|
144
|
+
// Check MORPHEUS_SECRET
|
|
145
|
+
const hasSecret = !!process.env.MORPHEUS_SECRET;
|
|
80
146
|
if (missingFields.length === 0) {
|
|
81
147
|
const sati = config.sati;
|
|
82
148
|
const apoc = config.apoc;
|
|
149
|
+
const details = {
|
|
150
|
+
oracleProvider: config.llm?.provider,
|
|
151
|
+
oracleModel: config.llm?.model,
|
|
152
|
+
satiProvider: sati?.provider ?? `${config.llm?.provider} (inherited)`,
|
|
153
|
+
satiModel: sati?.model ?? `${config.llm?.model} (inherited)`,
|
|
154
|
+
apocProvider: apoc?.provider ?? `${config.llm?.provider} (inherited)`,
|
|
155
|
+
apocModel: apoc?.model ?? `${config.llm?.model} (inherited)`,
|
|
156
|
+
apocWorkingDir: apoc?.working_dir ?? "not set",
|
|
157
|
+
uiEnabled: config.ui?.enabled,
|
|
158
|
+
uiPort: config.ui?.port,
|
|
159
|
+
morpheusSecret: hasSecret ? "configured ✓" : "NOT SET ⚠️",
|
|
160
|
+
};
|
|
83
161
|
components.config = {
|
|
84
|
-
status: "healthy",
|
|
85
|
-
message:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
satiProvider: sati?.provider ?? `${config.llm?.provider} (inherited)`,
|
|
90
|
-
satiModel: sati?.model ?? `${config.llm?.model} (inherited)`,
|
|
91
|
-
apocProvider: apoc?.provider ?? `${config.llm?.provider} (inherited)`,
|
|
92
|
-
apocModel: apoc?.model ?? `${config.llm?.model} (inherited)`,
|
|
93
|
-
apocWorkingDir: apoc?.working_dir ?? "not set",
|
|
94
|
-
uiEnabled: config.ui?.enabled,
|
|
95
|
-
uiPort: config.ui?.port,
|
|
96
|
-
},
|
|
162
|
+
status: hasSecret ? "healthy" : "warning",
|
|
163
|
+
message: hasSecret
|
|
164
|
+
? "Configuration is valid and complete"
|
|
165
|
+
: "Configuration is valid but MORPHEUS_SECRET is not set. API keys and database passwords will be stored in plaintext.",
|
|
166
|
+
details,
|
|
97
167
|
};
|
|
98
168
|
}
|
|
99
169
|
else {
|
|
100
170
|
components.config = {
|
|
101
171
|
status: "warning",
|
|
102
172
|
message: `Missing required configuration fields: ${missingFields.join(", ")}`,
|
|
103
|
-
details: {
|
|
173
|
+
details: {
|
|
174
|
+
missingFields,
|
|
175
|
+
morpheusSecret: hasSecret ? "configured ✓" : "NOT SET ⚠️",
|
|
176
|
+
},
|
|
104
177
|
};
|
|
105
178
|
}
|
|
106
179
|
}
|