morpheus-cli 0.7.0 → 0.7.2
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/README.md
CHANGED
|
@@ -312,6 +312,7 @@ neo:
|
|
|
312
312
|
model: gpt-4o-mini
|
|
313
313
|
temperature: 0.2
|
|
314
314
|
context_window: 100
|
|
315
|
+
personality: analytical_engineer
|
|
315
316
|
|
|
316
317
|
apoc:
|
|
317
318
|
provider: openai
|
|
@@ -319,11 +320,13 @@ apoc:
|
|
|
319
320
|
temperature: 0.2
|
|
320
321
|
working_dir: /home/user/projects
|
|
321
322
|
timeout_ms: 30000
|
|
323
|
+
personality: pragmatic_dev
|
|
322
324
|
|
|
323
325
|
trinity:
|
|
324
326
|
provider: openai
|
|
325
327
|
model: gpt-4o-mini
|
|
326
328
|
temperature: 0.2
|
|
329
|
+
personality: data_specialist
|
|
327
330
|
|
|
328
331
|
chronos:
|
|
329
332
|
check_interval_ms: 60000 # polling interval in ms (minimum 60000)
|
|
@@ -400,6 +403,7 @@ Generic Morpheus overrides (selected):
|
|
|
400
403
|
| `MORPHEUS_NEO_CONTEXT_WINDOW` | `neo.context_window` |
|
|
401
404
|
| `MORPHEUS_NEO_API_KEY` | `neo.api_key` |
|
|
402
405
|
| `MORPHEUS_NEO_BASE_URL` | `neo.base_url` |
|
|
406
|
+
| `MORPHEUS_NEO_PERSONALITY` | `neo.personality` |
|
|
403
407
|
| `MORPHEUS_APOC_PROVIDER` | `apoc.provider` |
|
|
404
408
|
| `MORPHEUS_APOC_MODEL` | `apoc.model` |
|
|
405
409
|
| `MORPHEUS_APOC_TEMPERATURE` | `apoc.temperature` |
|
|
@@ -408,10 +412,12 @@ Generic Morpheus overrides (selected):
|
|
|
408
412
|
| `MORPHEUS_APOC_API_KEY` | `apoc.api_key` |
|
|
409
413
|
| `MORPHEUS_APOC_WORKING_DIR` | `apoc.working_dir` |
|
|
410
414
|
| `MORPHEUS_APOC_TIMEOUT_MS` | `apoc.timeout_ms` |
|
|
415
|
+
| `MORPHEUS_APOC_PERSONALITY` | `apoc.personality` |
|
|
411
416
|
| `MORPHEUS_TRINITY_PROVIDER` | `trinity.provider` |
|
|
412
417
|
| `MORPHEUS_TRINITY_MODEL` | `trinity.model` |
|
|
413
418
|
| `MORPHEUS_TRINITY_TEMPERATURE` | `trinity.temperature` |
|
|
414
419
|
| `MORPHEUS_TRINITY_API_KEY` | `trinity.api_key` |
|
|
420
|
+
| `MORPHEUS_TRINITY_PERSONALITY` | `trinity.personality` |
|
|
415
421
|
| `MORPHEUS_AUDIO_PROVIDER` | `audio.provider` |
|
|
416
422
|
| `MORPHEUS_AUDIO_MODEL` | `audio.model` |
|
|
417
423
|
| `MORPHEUS_AUDIO_ENABLED` | `audio.enabled` |
|
|
@@ -77,50 +77,68 @@ function intervalToCron(expression) {
|
|
|
77
77
|
`Supported formats: "every N minutes/hours/days/weeks", "every minute/hour/day/week", ` +
|
|
78
78
|
`"every monday [at 9am]", "every monday and friday at 18:30", "every weekday", "every weekend", "daily", "weekly".`);
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Creates a Date in UTC from a local time in a specific timezone.
|
|
82
|
+
* E.g., createDateInTimezone(2026, 2, 26, 23, 0, 'America/Sao_Paulo') returns
|
|
83
|
+
* a Date representing 23:00 BRT = 02:00 UTC (next day).
|
|
84
|
+
*/
|
|
85
|
+
function createDateInTimezone(year, month, day, hour, minute, timezone) {
|
|
86
|
+
// Create a candidate UTC date
|
|
87
|
+
const candidateUtc = Date.UTC(year, month - 1, day, hour, minute, 0, 0);
|
|
88
|
+
// Get the offset at that moment for the timezone
|
|
89
|
+
const offsetMs = ianaToOffsetMinutes(timezone, new Date(candidateUtc)) * 60_000;
|
|
90
|
+
// Subtract offset: if BRT is -180 min (-3h), local 23:00 = UTC 23:00 - (-3h) = UTC 02:00
|
|
91
|
+
return new Date(candidateUtc - offsetMs);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Gets the current date components (year, month, day) in a specific timezone.
|
|
95
|
+
*/
|
|
96
|
+
function getDatePartsInTimezone(date, timezone) {
|
|
97
|
+
const formatter = new Intl.DateTimeFormat('en-CA', { timeZone: timezone, year: 'numeric', month: '2-digit', day: '2-digit' });
|
|
98
|
+
const parts = formatter.formatToParts(date);
|
|
99
|
+
return {
|
|
100
|
+
year: parseInt(parts.find(p => p.type === 'year').value, 10),
|
|
101
|
+
month: parseInt(parts.find(p => p.type === 'month').value, 10),
|
|
102
|
+
day: parseInt(parts.find(p => p.type === 'day').value, 10),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
80
105
|
/**
|
|
81
106
|
* Parses Portuguese time expressions and converts to ISO 8601 format.
|
|
82
107
|
* Handles patterns like "às 15h", "hoje às 15:30", "amanhã às 9h".
|
|
83
108
|
*/
|
|
84
109
|
function parsePortugueseTimeExpression(expression, refDate, timezone) {
|
|
85
110
|
const lower = expression.toLowerCase().trim();
|
|
111
|
+
const { year, month, day } = getDatePartsInTimezone(refDate, timezone);
|
|
86
112
|
// Pattern: "às 15h", "as 15h", "às 15:30", "as 15:30"
|
|
87
113
|
const timeOnlyMatch = lower.match(/^(?:à|a)s?\s+(\d{1,2})(?::(\d{2}))?h?$/);
|
|
88
114
|
if (timeOnlyMatch) {
|
|
89
|
-
|
|
115
|
+
const hour = parseInt(timeOnlyMatch[1], 10);
|
|
90
116
|
const minute = timeOnlyMatch[2] ? parseInt(timeOnlyMatch[2], 10) : 0;
|
|
91
|
-
|
|
92
|
-
// We use Intl.DateTimeFormat to properly handle timezone
|
|
93
|
-
const targetDate = new Date();
|
|
94
|
-
const tzDate = new Date(targetDate.toLocaleString('en-US', { timeZone: timezone }));
|
|
95
|
-
tzDate.setHours(hour, minute, 0, 0);
|
|
117
|
+
let result = createDateInTimezone(year, month, day, hour, minute, timezone);
|
|
96
118
|
// If time is in the past today, schedule for tomorrow
|
|
97
|
-
if (
|
|
98
|
-
|
|
119
|
+
if (result.getTime() <= refDate.getTime()) {
|
|
120
|
+
result = createDateInTimezone(year, month, day + 1, hour, minute, timezone);
|
|
99
121
|
}
|
|
100
|
-
return
|
|
122
|
+
return result;
|
|
101
123
|
}
|
|
102
124
|
// Pattern: "hoje às 15h", "hoje as 15:30"
|
|
103
125
|
const todayMatch = lower.match(/^hoje\s+(?:à|a)s?\s+(\d{1,2})(?::(\d{2}))?h?$/);
|
|
104
126
|
if (todayMatch) {
|
|
105
|
-
|
|
127
|
+
const hour = parseInt(todayMatch[1], 10);
|
|
106
128
|
const minute = todayMatch[2] ? parseInt(todayMatch[2], 10) : 0;
|
|
107
|
-
const
|
|
108
|
-
tzDate.setHours(hour, minute, 0, 0);
|
|
129
|
+
const result = createDateInTimezone(year, month, day, hour, minute, timezone);
|
|
109
130
|
// If already passed, return null (can't schedule in the past)
|
|
110
|
-
if (
|
|
131
|
+
if (result.getTime() <= refDate.getTime()) {
|
|
111
132
|
return null;
|
|
112
133
|
}
|
|
113
|
-
return
|
|
134
|
+
return result;
|
|
114
135
|
}
|
|
115
136
|
// Pattern: "amanhã às 15h", "amanha as 15:30", "amanhã às 15h da tarde"
|
|
116
|
-
const tomorrowMatch = lower.match(/^
|
|
137
|
+
const tomorrowMatch = lower.match(/^amanh[aã]\s+(?:à|a)s?\s+(\d{1,2})(?::(\d{2}))?h?(?:\s+(?:da|do)\s+(?:manh[aã]|tarde|noite))?$/);
|
|
117
138
|
if (tomorrowMatch) {
|
|
118
|
-
|
|
139
|
+
const hour = parseInt(tomorrowMatch[1], 10);
|
|
119
140
|
const minute = tomorrowMatch[2] ? parseInt(tomorrowMatch[2], 10) : 0;
|
|
120
|
-
|
|
121
|
-
tzDate.setDate(tzDate.getDate() + 1);
|
|
122
|
-
tzDate.setHours(hour, minute, 0, 0);
|
|
123
|
-
return tzDate;
|
|
141
|
+
return createDateInTimezone(year, month, day + 1, hour, minute, timezone);
|
|
124
142
|
}
|
|
125
143
|
// Pattern: "daqui a X minutos/horas/dias"
|
|
126
144
|
const relativeMatch = lower.match(/^daqui\s+a\s+(\d+)\s+(minutos?|horas?|dias?|semanas?)$/);
|
|
@@ -135,6 +153,22 @@ function parsePortugueseTimeExpression(expression, refDate, timezone) {
|
|
|
135
153
|
}
|
|
136
154
|
return null;
|
|
137
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Converts an IANA timezone name (e.g. "America/Sao_Paulo") to a UTC offset in minutes.
|
|
158
|
+
* chrono-node only understands abbreviations (EST, BRT) and numeric offsets,
|
|
159
|
+
* NOT IANA names — passing an unrecognised string makes it silently fall back
|
|
160
|
+
* to the system timezone, which breaks on servers running in UTC.
|
|
161
|
+
*/
|
|
162
|
+
function ianaToOffsetMinutes(timezone, refDate) {
|
|
163
|
+
try {
|
|
164
|
+
const utcStr = refDate.toLocaleString('en-US', { timeZone: 'UTC' });
|
|
165
|
+
const tzStr = refDate.toLocaleString('en-US', { timeZone: timezone });
|
|
166
|
+
return Math.round((new Date(tzStr).getTime() - new Date(utcStr).getTime()) / 60_000);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return 0; // fall back to UTC on invalid timezone
|
|
170
|
+
}
|
|
171
|
+
}
|
|
138
172
|
function formatDatetime(date, timezone) {
|
|
139
173
|
try {
|
|
140
174
|
return date.toLocaleString('en-US', {
|
|
@@ -180,7 +214,9 @@ export function parseScheduleExpression(expression, type, opts = {}) {
|
|
|
180
214
|
}
|
|
181
215
|
// 4. chrono-node NLP fallback ("tomorrow at 9am", "next friday", etc.)
|
|
182
216
|
if (!parsed) {
|
|
183
|
-
|
|
217
|
+
// chrono-node does NOT support IANA timezone names — convert to numeric offset
|
|
218
|
+
const tzOffset = ianaToOffsetMinutes(timezone, refDate);
|
|
219
|
+
const results = chrono.parse(expression, { instant: refDate, timezone: tzOffset });
|
|
184
220
|
if (results.length > 0 && results[0].date()) {
|
|
185
221
|
parsed = results[0].date();
|
|
186
222
|
}
|