mercury-agent 0.4.7 → 0.4.8
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/docs/auth/dashboard.md +28 -28
- package/examples/extensions/voice-synth/index.ts +94 -94
- package/package.json +1 -1
- package/src/adapters/whatsapp.ts +635 -632
- package/src/agent/model-capabilities.ts +231 -231
- package/src/bridges/discord.ts +178 -178
- package/src/bridges/slack.ts +179 -179
- package/src/bridges/teams.ts +162 -162
- package/src/bridges/telegram.ts +579 -579
- package/src/cli/mercury.ts +2536 -2536
- package/src/cli/whatsapp-auth.ts +263 -260
- package/src/config.ts +316 -316
- package/src/core/permissions.ts +196 -196
- package/src/core/router.ts +191 -191
- package/src/core/routes/chat.ts +175 -175
- package/src/core/routes/dashboard.ts +2491 -2491
- package/src/core/routes/messages.ts +37 -37
- package/src/core/routes/mutes.ts +95 -95
- package/src/core/routes/roles.ts +135 -135
- package/src/core/runtime.ts +1140 -1140
- package/src/core/task-scheduler.ts +139 -139
- package/src/extensions/catalog.ts +117 -117
- package/src/extensions/hooks.ts +161 -161
- package/src/extensions/installer.ts +306 -306
- package/src/extensions/loader.ts +271 -271
- package/src/extensions/permission-guard.ts +52 -52
- package/src/server.ts +391 -391
- package/src/storage/db.ts +1625 -1625
- package/src/storage/pi-auth.ts +95 -95
- package/src/tts/azure.ts +52 -52
- package/src/tts/synthesize.ts +133 -133
|
@@ -1,139 +1,139 @@
|
|
|
1
|
-
import { CronExpressionParser } from "cron-parser";
|
|
2
|
-
import { logger } from "../logger.js";
|
|
3
|
-
import type { Db } from "../storage/db.js";
|
|
4
|
-
|
|
5
|
-
type TaskHandler = (task: {
|
|
6
|
-
id: number;
|
|
7
|
-
spaceId: string;
|
|
8
|
-
prompt: string;
|
|
9
|
-
createdBy: string;
|
|
10
|
-
silent: boolean;
|
|
11
|
-
}) => Promise<void>;
|
|
12
|
-
|
|
13
|
-
export class TaskScheduler {
|
|
14
|
-
private timer: NodeJS.Timeout | null = null;
|
|
15
|
-
private handler: TaskHandler | null = null;
|
|
16
|
-
|
|
17
|
-
constructor(
|
|
18
|
-
private readonly db: Db,
|
|
19
|
-
private readonly pollIntervalMs = 5_000,
|
|
20
|
-
) {}
|
|
21
|
-
|
|
22
|
-
start(handler: TaskHandler) {
|
|
23
|
-
if (this.timer) return;
|
|
24
|
-
this.handler = handler;
|
|
25
|
-
this.recalculateNextRuns();
|
|
26
|
-
|
|
27
|
-
const tick = async () => {
|
|
28
|
-
try {
|
|
29
|
-
const due = this.db.getDueTasks(Date.now());
|
|
30
|
-
for (const task of due) {
|
|
31
|
-
// For cron tasks, update next run before execution
|
|
32
|
-
// For at-tasks, we'll delete after execution
|
|
33
|
-
if (task.cron) {
|
|
34
|
-
const next = this.computeNextRun(
|
|
35
|
-
task.cron,
|
|
36
|
-
task.timezone ?? undefined,
|
|
37
|
-
);
|
|
38
|
-
this.db.updateTaskNextRun(task.id, next);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
await handler({
|
|
43
|
-
id: task.id,
|
|
44
|
-
spaceId: task.spaceId,
|
|
45
|
-
prompt: task.prompt,
|
|
46
|
-
createdBy: task.createdBy,
|
|
47
|
-
silent: task.silent === 1,
|
|
48
|
-
});
|
|
49
|
-
} catch (error) {
|
|
50
|
-
logger.error("Scheduler task handler failed", {
|
|
51
|
-
taskId: task.id,
|
|
52
|
-
spaceId: task.spaceId,
|
|
53
|
-
error: error instanceof Error ? error.message : String(error),
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// For at-tasks, delete after execution (regardless of success/failure)
|
|
58
|
-
if (task.at) {
|
|
59
|
-
this.db.deleteTaskById(task.id);
|
|
60
|
-
logger.info("One-shot task completed and deleted", {
|
|
61
|
-
taskId: task.id,
|
|
62
|
-
spaceId: task.spaceId,
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
} catch (error) {
|
|
67
|
-
logger.error(
|
|
68
|
-
"Scheduler error",
|
|
69
|
-
error instanceof Error ? error : undefined,
|
|
70
|
-
);
|
|
71
|
-
} finally {
|
|
72
|
-
this.timer = setTimeout(tick, this.pollIntervalMs);
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
this.timer = setTimeout(tick, this.pollIntervalMs);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
stop() {
|
|
80
|
-
if (!this.timer) return;
|
|
81
|
-
clearTimeout(this.timer);
|
|
82
|
-
this.timer = null;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
computeNextRun(cron: string, timezone?: string, from = new Date()): number {
|
|
86
|
-
try {
|
|
87
|
-
const interval = CronExpressionParser.parse(cron, {
|
|
88
|
-
currentDate: from,
|
|
89
|
-
tz: timezone ?? "UTC",
|
|
90
|
-
});
|
|
91
|
-
return interval.next().getTime();
|
|
92
|
-
} catch {
|
|
93
|
-
// Timezone rejected by cron-parser — fall back to UTC
|
|
94
|
-
const interval = CronExpressionParser.parse(cron, { currentDate: from });
|
|
95
|
-
return interval.next().getTime();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
private recalculateNextRuns(): void {
|
|
100
|
-
const tasks = this.db.listTasks();
|
|
101
|
-
let updated = 0;
|
|
102
|
-
for (const task of tasks) {
|
|
103
|
-
if (!task.active || !task.cron) continue;
|
|
104
|
-
let correct: number;
|
|
105
|
-
try {
|
|
106
|
-
correct = this.computeNextRun(task.cron, task.timezone ?? undefined);
|
|
107
|
-
} catch (err) {
|
|
108
|
-
logger.warn("Skipping task with invalid cron expression", {
|
|
109
|
-
taskId: task.id,
|
|
110
|
-
cron: task.cron,
|
|
111
|
-
error: err instanceof Error ? err.message : String(err),
|
|
112
|
-
});
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
if (correct !== task.nextRunAt) {
|
|
116
|
-
this.db.updateTaskNextRun(task.id, correct);
|
|
117
|
-
updated++;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
if (updated > 0) {
|
|
121
|
-
logger.info(`Recalculated next_run_at for ${updated} cron task(s)`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async triggerTask(taskId: number): Promise<boolean> {
|
|
126
|
-
if (!this.handler) return false;
|
|
127
|
-
const task = this.db.getTask(taskId);
|
|
128
|
-
if (!task?.active) return false;
|
|
129
|
-
|
|
130
|
-
await this.handler({
|
|
131
|
-
id: task.id,
|
|
132
|
-
spaceId: task.spaceId,
|
|
133
|
-
prompt: task.prompt,
|
|
134
|
-
createdBy: task.createdBy,
|
|
135
|
-
silent: task.silent === 1,
|
|
136
|
-
});
|
|
137
|
-
return true;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
1
|
+
import { CronExpressionParser } from "cron-parser";
|
|
2
|
+
import { logger } from "../logger.js";
|
|
3
|
+
import type { Db } from "../storage/db.js";
|
|
4
|
+
|
|
5
|
+
type TaskHandler = (task: {
|
|
6
|
+
id: number;
|
|
7
|
+
spaceId: string;
|
|
8
|
+
prompt: string;
|
|
9
|
+
createdBy: string;
|
|
10
|
+
silent: boolean;
|
|
11
|
+
}) => Promise<void>;
|
|
12
|
+
|
|
13
|
+
export class TaskScheduler {
|
|
14
|
+
private timer: NodeJS.Timeout | null = null;
|
|
15
|
+
private handler: TaskHandler | null = null;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
private readonly db: Db,
|
|
19
|
+
private readonly pollIntervalMs = 5_000,
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
start(handler: TaskHandler) {
|
|
23
|
+
if (this.timer) return;
|
|
24
|
+
this.handler = handler;
|
|
25
|
+
this.recalculateNextRuns();
|
|
26
|
+
|
|
27
|
+
const tick = async () => {
|
|
28
|
+
try {
|
|
29
|
+
const due = this.db.getDueTasks(Date.now());
|
|
30
|
+
for (const task of due) {
|
|
31
|
+
// For cron tasks, update next run before execution
|
|
32
|
+
// For at-tasks, we'll delete after execution
|
|
33
|
+
if (task.cron) {
|
|
34
|
+
const next = this.computeNextRun(
|
|
35
|
+
task.cron,
|
|
36
|
+
task.timezone ?? undefined,
|
|
37
|
+
);
|
|
38
|
+
this.db.updateTaskNextRun(task.id, next);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
await handler({
|
|
43
|
+
id: task.id,
|
|
44
|
+
spaceId: task.spaceId,
|
|
45
|
+
prompt: task.prompt,
|
|
46
|
+
createdBy: task.createdBy,
|
|
47
|
+
silent: task.silent === 1,
|
|
48
|
+
});
|
|
49
|
+
} catch (error) {
|
|
50
|
+
logger.error("Scheduler task handler failed", {
|
|
51
|
+
taskId: task.id,
|
|
52
|
+
spaceId: task.spaceId,
|
|
53
|
+
error: error instanceof Error ? error.message : String(error),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// For at-tasks, delete after execution (regardless of success/failure)
|
|
58
|
+
if (task.at) {
|
|
59
|
+
this.db.deleteTaskById(task.id);
|
|
60
|
+
logger.info("One-shot task completed and deleted", {
|
|
61
|
+
taskId: task.id,
|
|
62
|
+
spaceId: task.spaceId,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
logger.error(
|
|
68
|
+
"Scheduler error",
|
|
69
|
+
error instanceof Error ? error : undefined,
|
|
70
|
+
);
|
|
71
|
+
} finally {
|
|
72
|
+
this.timer = setTimeout(tick, this.pollIntervalMs);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
this.timer = setTimeout(tick, this.pollIntervalMs);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
stop() {
|
|
80
|
+
if (!this.timer) return;
|
|
81
|
+
clearTimeout(this.timer);
|
|
82
|
+
this.timer = null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
computeNextRun(cron: string, timezone?: string, from = new Date()): number {
|
|
86
|
+
try {
|
|
87
|
+
const interval = CronExpressionParser.parse(cron, {
|
|
88
|
+
currentDate: from,
|
|
89
|
+
tz: timezone ?? "UTC",
|
|
90
|
+
});
|
|
91
|
+
return interval.next().getTime();
|
|
92
|
+
} catch {
|
|
93
|
+
// Timezone rejected by cron-parser — fall back to UTC
|
|
94
|
+
const interval = CronExpressionParser.parse(cron, { currentDate: from });
|
|
95
|
+
return interval.next().getTime();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private recalculateNextRuns(): void {
|
|
100
|
+
const tasks = this.db.listTasks();
|
|
101
|
+
let updated = 0;
|
|
102
|
+
for (const task of tasks) {
|
|
103
|
+
if (!task.active || !task.cron) continue;
|
|
104
|
+
let correct: number;
|
|
105
|
+
try {
|
|
106
|
+
correct = this.computeNextRun(task.cron, task.timezone ?? undefined);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
logger.warn("Skipping task with invalid cron expression", {
|
|
109
|
+
taskId: task.id,
|
|
110
|
+
cron: task.cron,
|
|
111
|
+
error: err instanceof Error ? err.message : String(err),
|
|
112
|
+
});
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (correct !== task.nextRunAt) {
|
|
116
|
+
this.db.updateTaskNextRun(task.id, correct);
|
|
117
|
+
updated++;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (updated > 0) {
|
|
121
|
+
logger.info(`Recalculated next_run_at for ${updated} cron task(s)`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async triggerTask(taskId: number): Promise<boolean> {
|
|
126
|
+
if (!this.handler) return false;
|
|
127
|
+
const task = this.db.getTask(taskId);
|
|
128
|
+
if (!task?.active) return false;
|
|
129
|
+
|
|
130
|
+
await this.handler({
|
|
131
|
+
id: task.id,
|
|
132
|
+
spaceId: task.spaceId,
|
|
133
|
+
prompt: task.prompt,
|
|
134
|
+
createdBy: task.createdBy,
|
|
135
|
+
silent: task.silent === 1,
|
|
136
|
+
});
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -1,117 +1,117 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bundled extension catalog for dashboard install. Sources live under
|
|
3
|
-
* `examples/extensions/<sourceDir>/` in the mercury-agent package (or repo).
|
|
4
|
-
*/
|
|
5
|
-
export type ExtensionCatalogCategory =
|
|
6
|
-
| "browsing"
|
|
7
|
-
| "automation"
|
|
8
|
-
| "knowledge"
|
|
9
|
-
| "voice"
|
|
10
|
-
| "code"
|
|
11
|
-
| "other";
|
|
12
|
-
|
|
13
|
-
export interface ExtensionCatalogEntry {
|
|
14
|
-
/** Installed directory name under `.mercury/extensions/` */
|
|
15
|
-
name: string;
|
|
16
|
-
label: string;
|
|
17
|
-
description: string;
|
|
18
|
-
category: ExtensionCatalogCategory;
|
|
19
|
-
/** Subdirectory of `examples/extensions/` to copy from */
|
|
20
|
-
sourceDir: string;
|
|
21
|
-
requiredEnvVars?: string[];
|
|
22
|
-
requiresRestart: boolean;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const EXTENSION_CATALOG: ExtensionCatalogEntry[] = [
|
|
26
|
-
{
|
|
27
|
-
name: "web-browser",
|
|
28
|
-
label: "Web browsing & automation",
|
|
29
|
-
description:
|
|
30
|
-
"Web search (Brave) and interactive browser (pinchtab) for sites like Gmail, banks, and forms.",
|
|
31
|
-
category: "automation",
|
|
32
|
-
sourceDir: "pinchtab",
|
|
33
|
-
requiredEnvVars: ["MERCURY_BRAVE_API_KEY"],
|
|
34
|
-
requiresRestart: true,
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
name: "napkin",
|
|
38
|
-
label: "Knowledge vault",
|
|
39
|
-
description:
|
|
40
|
-
"Obsidian-style vault, napkin CLI, and optional KB distillation job.",
|
|
41
|
-
category: "knowledge",
|
|
42
|
-
sourceDir: "napkin",
|
|
43
|
-
requiresRestart: true,
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
name: "charts",
|
|
47
|
-
label: "Charts",
|
|
48
|
-
description: "Minimal charts CLI extension example.",
|
|
49
|
-
category: "other",
|
|
50
|
-
sourceDir: "charts",
|
|
51
|
-
requiresRestart: true,
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
name: "pdf",
|
|
55
|
-
label: "PDF tools",
|
|
56
|
-
description: "PDF form filling and scripts (see extension skill).",
|
|
57
|
-
category: "other",
|
|
58
|
-
sourceDir: "pdf",
|
|
59
|
-
requiresRestart: true,
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
name: "gws",
|
|
63
|
-
label: "Google Workspace",
|
|
64
|
-
description: "Google Workspace integration (see extension skill).",
|
|
65
|
-
category: "other",
|
|
66
|
-
sourceDir: "gws",
|
|
67
|
-
requiresRestart: true,
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
name: "voice-transcribe",
|
|
71
|
-
label: "Voice transcription",
|
|
72
|
-
description:
|
|
73
|
-
"Transcribe voice with local Python (Transformers or Faster-Whisper) or Hugging Face Inference API.",
|
|
74
|
-
category: "voice",
|
|
75
|
-
sourceDir: "voice-transcribe",
|
|
76
|
-
requiresRestart: true,
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
name: "voice-synth",
|
|
80
|
-
label: "Voice synthesis (TTS)",
|
|
81
|
-
description:
|
|
82
|
-
"Google or Azure cloud TTS for English/Hebrew; mrctl tts synthesize and optional auto voice per space. Set Azure key+region and/or Google credentials path on the host.",
|
|
83
|
-
category: "voice",
|
|
84
|
-
sourceDir: "voice-synth",
|
|
85
|
-
requiresRestart: true,
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
name: "tradestation",
|
|
89
|
-
label: "TradeStation",
|
|
90
|
-
description:
|
|
91
|
-
"TradeStation API v3 (accounts, balances, positions, bars, host-gated orders via mrctl) with OAuth refresh; admin-only by default.",
|
|
92
|
-
category: "other",
|
|
93
|
-
sourceDir: "tradestation",
|
|
94
|
-
requiredEnvVars: [
|
|
95
|
-
"MERCURY_TS_CLIENT_ID",
|
|
96
|
-
"MERCURY_TS_CLIENT_SECRET",
|
|
97
|
-
"MERCURY_TRADESTATION_REFRESH_TOKEN",
|
|
98
|
-
],
|
|
99
|
-
requiresRestart: true,
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
name: "yahoo-mail",
|
|
103
|
-
label: "Yahoo Mail",
|
|
104
|
-
description:
|
|
105
|
-
"Read, search, and send Yahoo Mail via IMAP/SMTP with an app-specific password.",
|
|
106
|
-
category: "other",
|
|
107
|
-
sourceDir: "yahoo-mail",
|
|
108
|
-
requiredEnvVars: ["MERCURY_YAHOO_EMAIL", "MERCURY_YAHOO_APP_PASSWORD"],
|
|
109
|
-
requiresRestart: true,
|
|
110
|
-
},
|
|
111
|
-
];
|
|
112
|
-
|
|
113
|
-
export function getCatalogEntryByName(
|
|
114
|
-
name: string,
|
|
115
|
-
): ExtensionCatalogEntry | undefined {
|
|
116
|
-
return EXTENSION_CATALOG.find((e) => e.name === name);
|
|
117
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Bundled extension catalog for dashboard install. Sources live under
|
|
3
|
+
* `examples/extensions/<sourceDir>/` in the mercury-agent package (or repo).
|
|
4
|
+
*/
|
|
5
|
+
export type ExtensionCatalogCategory =
|
|
6
|
+
| "browsing"
|
|
7
|
+
| "automation"
|
|
8
|
+
| "knowledge"
|
|
9
|
+
| "voice"
|
|
10
|
+
| "code"
|
|
11
|
+
| "other";
|
|
12
|
+
|
|
13
|
+
export interface ExtensionCatalogEntry {
|
|
14
|
+
/** Installed directory name under `.mercury/extensions/` */
|
|
15
|
+
name: string;
|
|
16
|
+
label: string;
|
|
17
|
+
description: string;
|
|
18
|
+
category: ExtensionCatalogCategory;
|
|
19
|
+
/** Subdirectory of `examples/extensions/` to copy from */
|
|
20
|
+
sourceDir: string;
|
|
21
|
+
requiredEnvVars?: string[];
|
|
22
|
+
requiresRestart: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const EXTENSION_CATALOG: ExtensionCatalogEntry[] = [
|
|
26
|
+
{
|
|
27
|
+
name: "web-browser",
|
|
28
|
+
label: "Web browsing & automation",
|
|
29
|
+
description:
|
|
30
|
+
"Web search (Brave) and interactive browser (pinchtab) for sites like Gmail, banks, and forms.",
|
|
31
|
+
category: "automation",
|
|
32
|
+
sourceDir: "pinchtab",
|
|
33
|
+
requiredEnvVars: ["MERCURY_BRAVE_API_KEY"],
|
|
34
|
+
requiresRestart: true,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "napkin",
|
|
38
|
+
label: "Knowledge vault",
|
|
39
|
+
description:
|
|
40
|
+
"Obsidian-style vault, napkin CLI, and optional KB distillation job.",
|
|
41
|
+
category: "knowledge",
|
|
42
|
+
sourceDir: "napkin",
|
|
43
|
+
requiresRestart: true,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: "charts",
|
|
47
|
+
label: "Charts",
|
|
48
|
+
description: "Minimal charts CLI extension example.",
|
|
49
|
+
category: "other",
|
|
50
|
+
sourceDir: "charts",
|
|
51
|
+
requiresRestart: true,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "pdf",
|
|
55
|
+
label: "PDF tools",
|
|
56
|
+
description: "PDF form filling and scripts (see extension skill).",
|
|
57
|
+
category: "other",
|
|
58
|
+
sourceDir: "pdf",
|
|
59
|
+
requiresRestart: true,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "gws",
|
|
63
|
+
label: "Google Workspace",
|
|
64
|
+
description: "Google Workspace integration (see extension skill).",
|
|
65
|
+
category: "other",
|
|
66
|
+
sourceDir: "gws",
|
|
67
|
+
requiresRestart: true,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "voice-transcribe",
|
|
71
|
+
label: "Voice transcription",
|
|
72
|
+
description:
|
|
73
|
+
"Transcribe voice with local Python (Transformers or Faster-Whisper) or Hugging Face Inference API.",
|
|
74
|
+
category: "voice",
|
|
75
|
+
sourceDir: "voice-transcribe",
|
|
76
|
+
requiresRestart: true,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "voice-synth",
|
|
80
|
+
label: "Voice synthesis (TTS)",
|
|
81
|
+
description:
|
|
82
|
+
"Google or Azure cloud TTS for English/Hebrew; mrctl tts synthesize and optional auto voice per space. Set Azure key+region and/or Google credentials path on the host.",
|
|
83
|
+
category: "voice",
|
|
84
|
+
sourceDir: "voice-synth",
|
|
85
|
+
requiresRestart: true,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "tradestation",
|
|
89
|
+
label: "TradeStation",
|
|
90
|
+
description:
|
|
91
|
+
"TradeStation API v3 (accounts, balances, positions, bars, host-gated orders via mrctl) with OAuth refresh; admin-only by default.",
|
|
92
|
+
category: "other",
|
|
93
|
+
sourceDir: "tradestation",
|
|
94
|
+
requiredEnvVars: [
|
|
95
|
+
"MERCURY_TS_CLIENT_ID",
|
|
96
|
+
"MERCURY_TS_CLIENT_SECRET",
|
|
97
|
+
"MERCURY_TRADESTATION_REFRESH_TOKEN",
|
|
98
|
+
],
|
|
99
|
+
requiresRestart: true,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "yahoo-mail",
|
|
103
|
+
label: "Yahoo Mail",
|
|
104
|
+
description:
|
|
105
|
+
"Read, search, and send Yahoo Mail via IMAP/SMTP with an app-specific password.",
|
|
106
|
+
category: "other",
|
|
107
|
+
sourceDir: "yahoo-mail",
|
|
108
|
+
requiredEnvVars: ["MERCURY_YAHOO_EMAIL", "MERCURY_YAHOO_APP_PASSWORD"],
|
|
109
|
+
requiresRestart: true,
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
export function getCatalogEntryByName(
|
|
114
|
+
name: string,
|
|
115
|
+
): ExtensionCatalogEntry | undefined {
|
|
116
|
+
return EXTENSION_CATALOG.find((e) => e.name === name);
|
|
117
|
+
}
|