mercury-agent 0.4.5
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/LICENSE +22 -0
- package/README.md +438 -0
- package/container/Dockerfile +127 -0
- package/container/Dockerfile.base +109 -0
- package/container/Dockerfile.power +17 -0
- package/container/agent-package.json +8 -0
- package/container/build.sh +54 -0
- package/docs/TODOS.md +147 -0
- package/docs/auth/dashboard.md +28 -0
- package/docs/auth/overview.md +109 -0
- package/docs/auth/whatsapp.md +173 -0
- package/docs/configuration.md +54 -0
- package/docs/container-lifecycle.md +349 -0
- package/docs/context-architecture.md +87 -0
- package/docs/deployment.md +199 -0
- package/docs/extensions.md +375 -0
- package/docs/graceful-shutdown.md +62 -0
- package/docs/kb-distillation.md +77 -0
- package/docs/media/overview.md +140 -0
- package/docs/media/whatsapp.md +171 -0
- package/docs/memory.md +137 -0
- package/docs/permissions.md +217 -0
- package/docs/pipeline.md +228 -0
- package/docs/prd-chat-memory.md +76 -0
- package/docs/prd-config-load.md +82 -0
- package/docs/rate-limiting.md +166 -0
- package/docs/scheduler.md +288 -0
- package/docs/setup-discord.md +100 -0
- package/docs/setup-slack.md +119 -0
- package/docs/setup-whatsapp.md +94 -0
- package/docs/subagents.md +166 -0
- package/docs/web-search.md +62 -0
- package/examples/extensions/README.md +12 -0
- package/examples/extensions/charts/index.ts +13 -0
- package/examples/extensions/charts/skill/SKILL.md +98 -0
- package/examples/extensions/gws/README.md +52 -0
- package/examples/extensions/gws/index.ts +106 -0
- package/examples/extensions/gws/skill/SKILL.md +57 -0
- package/examples/extensions/gws/skill/references/calendar.md +101 -0
- package/examples/extensions/gws/skill/references/docs.md +65 -0
- package/examples/extensions/gws/skill/references/drive.md +79 -0
- package/examples/extensions/gws/skill/references/gmail.md +85 -0
- package/examples/extensions/gws/skill/references/sheets.md +60 -0
- package/examples/extensions/napkin/index.ts +821 -0
- package/examples/extensions/napkin/prompts/consolidation-monthly.md +73 -0
- package/examples/extensions/napkin/prompts/consolidation-weekly.md +67 -0
- package/examples/extensions/napkin/prompts/kb-distillation.md +176 -0
- package/examples/extensions/napkin/skill/SKILL.md +728 -0
- package/examples/extensions/pdf/index.ts +23 -0
- package/examples/extensions/pdf/skill/LICENSE.txt +30 -0
- package/examples/extensions/pdf/skill/SKILL.md +314 -0
- package/examples/extensions/pdf/skill/forms.md +294 -0
- package/examples/extensions/pdf/skill/reference.md +612 -0
- package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +65 -0
- package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +11 -0
- package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +33 -0
- package/examples/extensions/pdf/skill/scripts/create_validation_image.py +37 -0
- package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +122 -0
- package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +115 -0
- package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +98 -0
- package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +107 -0
- package/examples/extensions/permission-guard/index.ts +65 -0
- package/examples/extensions/pinchtab/index.ts +199 -0
- package/examples/extensions/pinchtab/lib/session-injector.ts +144 -0
- package/examples/extensions/pinchtab/skill/SKILL.md +224 -0
- package/examples/extensions/pinchtab/skill/TRUST.md +69 -0
- package/examples/extensions/pinchtab/skill/references/api.md +297 -0
- package/examples/extensions/pinchtab/skill/references/env.md +45 -0
- package/examples/extensions/pinchtab/skill/references/profiles.md +107 -0
- package/examples/extensions/tradestation/host/refresh.ts +102 -0
- package/examples/extensions/tradestation/index.ts +153 -0
- package/examples/extensions/tradestation/skill/SKILL.md +67 -0
- package/examples/extensions/tradestation/skill/scripts/ts-cli.ts +111 -0
- package/examples/extensions/voice-synth/index.ts +94 -0
- package/examples/extensions/voice-synth/skill/SKILL.md +38 -0
- package/examples/extensions/voice-transcribe/index.ts +381 -0
- package/examples/extensions/voice-transcribe/requirements.txt +8 -0
- package/examples/extensions/voice-transcribe/scripts/transcribe.py +179 -0
- package/examples/extensions/voice-transcribe/skill/SKILL.md +53 -0
- package/examples/extensions/web-search/index.ts +22 -0
- package/examples/extensions/web-search/skill/SKILL.md +114 -0
- package/examples/extensions/web-search/skill/references/apartments.md +178 -0
- package/examples/extensions/web-search/skill/references/car-purchase.md +132 -0
- package/examples/extensions/web-search/skill/references/car-rental.md +113 -0
- package/examples/extensions/web-search/skill/references/flights.md +133 -0
- package/examples/extensions/web-search/skill/references/hotels.md +148 -0
- package/examples/extensions/yahoo-mail/cli/bun.lock +66 -0
- package/examples/extensions/yahoo-mail/cli/package.json +13 -0
- package/examples/extensions/yahoo-mail/cli/ymail.mjs +353 -0
- package/examples/extensions/yahoo-mail/index.ts +57 -0
- package/examples/extensions/yahoo-mail/skill/SKILL.md +78 -0
- package/package.json +106 -0
- package/resources/agents/explore.md +50 -0
- package/resources/agents/worker.md +24 -0
- package/resources/builtin-extensions.txt +3 -0
- package/resources/connection-env-vars.json +25 -0
- package/resources/extensions/.gitkeep +0 -0
- package/resources/pi-extensions/subagent/agents.ts +126 -0
- package/resources/pi-extensions/subagent/index.ts +964 -0
- package/resources/profiles/coding/AGENTS.md +43 -0
- package/resources/profiles/coding/mercury-profile.yaml +15 -0
- package/resources/profiles/general/AGENTS.md +31 -0
- package/resources/profiles/general/mercury-profile.yaml +15 -0
- package/resources/profiles/research/AGENTS.md +40 -0
- package/resources/profiles/research/mercury-profile.yaml +15 -0
- package/resources/skills/config/SKILL.md +25 -0
- package/resources/skills/context/SKILL.md +33 -0
- package/resources/skills/conversation-recap/SKILL.md +19 -0
- package/resources/skills/media/SKILL.md +27 -0
- package/resources/skills/mutes/SKILL.md +31 -0
- package/resources/skills/permissions/SKILL.md +19 -0
- package/resources/skills/preferences/SKILL.md +31 -0
- package/resources/skills/recall/SKILL.md +24 -0
- package/resources/skills/roles/SKILL.md +18 -0
- package/resources/skills/spaces/SKILL.md +18 -0
- package/resources/skills/tasks/SKILL.md +45 -0
- package/resources/templates/AGENTS.md +157 -0
- package/resources/templates/env.template +34 -0
- package/resources/templates/mercury.example.yaml +75 -0
- package/src/adapters/discord-native.ts +534 -0
- package/src/adapters/discord.ts +38 -0
- package/src/adapters/setup.ts +89 -0
- package/src/adapters/slack.ts +9 -0
- package/src/adapters/whatsapp-media.ts +337 -0
- package/src/adapters/whatsapp.ts +629 -0
- package/src/agent/api-socket.ts +127 -0
- package/src/agent/container-entry.ts +967 -0
- package/src/agent/container-error.ts +49 -0
- package/src/agent/container-runner.ts +1272 -0
- package/src/agent/model-capabilities-core.ts +23 -0
- package/src/agent/model-capabilities.ts +231 -0
- package/src/agent/pi-failure-class.ts +83 -0
- package/src/agent/pi-jsonl-parser.ts +306 -0
- package/src/agent/preferences-prompt.ts +20 -0
- package/src/agent/user-error-messages.ts +78 -0
- package/src/bridges/discord.ts +171 -0
- package/src/bridges/slack.ts +177 -0
- package/src/bridges/teams.ts +160 -0
- package/src/bridges/telegram.ts +571 -0
- package/src/bridges/whatsapp.ts +290 -0
- package/src/chat-shim.ts +259 -0
- package/src/cli/mercury.ts +2508 -0
- package/src/cli/mrctl-http.ts +27 -0
- package/src/cli/mrctl.ts +611 -0
- package/src/cli/whatsapp-auth.ts +260 -0
- package/src/config-file.ts +397 -0
- package/src/config-model-chain.ts +30 -0
- package/src/config.ts +316 -0
- package/src/core/api-types.ts +58 -0
- package/src/core/api.ts +105 -0
- package/src/core/commands.ts +76 -0
- package/src/core/conversation.ts +47 -0
- package/src/core/handler.ts +206 -0
- package/src/core/media.ts +200 -0
- package/src/core/mute-duration.ts +22 -0
- package/src/core/outbox.ts +76 -0
- package/src/core/permissions.ts +192 -0
- package/src/core/profiles.ts +245 -0
- package/src/core/rate-limiter.ts +127 -0
- package/src/core/router.ts +191 -0
- package/src/core/routes/chat.ts +172 -0
- package/src/core/routes/config-builtin.ts +107 -0
- package/src/core/routes/config.ts +81 -0
- package/src/core/routes/connections.ts +190 -0
- package/src/core/routes/console.ts +668 -0
- package/src/core/routes/control.ts +46 -0
- package/src/core/routes/conversations.ts +66 -0
- package/src/core/routes/dashboard.ts +2491 -0
- package/src/core/routes/extensions.ts +37 -0
- package/src/core/routes/index.ts +14 -0
- package/src/core/routes/media.ts +72 -0
- package/src/core/routes/messages.ts +37 -0
- package/src/core/routes/mutes.ts +89 -0
- package/src/core/routes/prefs.ts +95 -0
- package/src/core/routes/roles.ts +125 -0
- package/src/core/routes/spaces.ts +60 -0
- package/src/core/routes/storage.ts +126 -0
- package/src/core/routes/tasks.ts +189 -0
- package/src/core/routes/tradestation.ts +268 -0
- package/src/core/routes/tts.ts +51 -0
- package/src/core/runtime.ts +1140 -0
- package/src/core/space-queue.ts +103 -0
- package/src/core/storage-cleanup.ts +140 -0
- package/src/core/storage-guard.ts +24 -0
- package/src/core/task-scheduler.ts +132 -0
- package/src/core/telegram-format.ts +178 -0
- package/src/core/trigger.ts +142 -0
- package/src/dashboard/index.html +729 -0
- package/src/dashboard/tokens.css +53 -0
- package/src/extensions/api.ts +252 -0
- package/src/extensions/catalog.ts +117 -0
- package/src/extensions/config-registry.ts +83 -0
- package/src/extensions/context.ts +36 -0
- package/src/extensions/hooks.ts +156 -0
- package/src/extensions/image-builder.ts +617 -0
- package/src/extensions/installer.ts +306 -0
- package/src/extensions/jobs.ts +122 -0
- package/src/extensions/loader.ts +271 -0
- package/src/extensions/permission-guard.ts +52 -0
- package/src/extensions/reserved.ts +28 -0
- package/src/extensions/skills.ts +123 -0
- package/src/extensions/types.ts +462 -0
- package/src/logger.ts +174 -0
- package/src/main.ts +586 -0
- package/src/server.ts +391 -0
- package/src/storage/db.ts +1624 -0
- package/src/storage/memory.ts +45 -0
- package/src/storage/pi-auth.ts +95 -0
- package/src/text/markdown.ts +117 -0
- package/src/text/rtl.ts +38 -0
- package/src/tradestation/host-api.ts +77 -0
- package/src/tradestation/pending-orders.ts +69 -0
- package/src/tts/azure.ts +52 -0
- package/src/tts/google.ts +128 -0
- package/src/tts/index.ts +8 -0
- package/src/tts/language.ts +20 -0
- package/src/tts/synthesize.ts +133 -0
- package/src/types.ts +295 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure HTTP-transport helpers for mrctl. Kept side-effect free (no env reads, no
|
|
3
|
+
* process.exit) so it is unit-testable without triggering mrctl's CLI bootstrap.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Build the fetch init for an inner→outer API call.
|
|
8
|
+
*
|
|
9
|
+
* - gVisor mode: `apiSocket` is set, so the request is routed over the per-agent
|
|
10
|
+
* unix socket (Bun ignores the host/port in the URL when `unix` is present).
|
|
11
|
+
* - runc / local: `apiSocket` is undefined, so the TCP `API_URL` is used as-is —
|
|
12
|
+
* byte-for-byte the pre-socket behaviour.
|
|
13
|
+
*/
|
|
14
|
+
export function buildRequestInit(
|
|
15
|
+
method: string,
|
|
16
|
+
headers: Record<string, string>,
|
|
17
|
+
body: unknown,
|
|
18
|
+
apiSocket: string | undefined,
|
|
19
|
+
): RequestInit & { unix?: string } {
|
|
20
|
+
const init: RequestInit & { unix?: string } = {
|
|
21
|
+
method,
|
|
22
|
+
headers,
|
|
23
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
24
|
+
};
|
|
25
|
+
if (apiSocket) init.unix = apiSocket;
|
|
26
|
+
return init;
|
|
27
|
+
}
|
package/src/cli/mrctl.ts
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import type { StorageResponse } from "../core/routes/storage.js";
|
|
6
|
+
import { buildRequestInit } from "./mrctl-http.js";
|
|
7
|
+
|
|
8
|
+
const API_URL = process.env.API_URL;
|
|
9
|
+
const CALLER_ID = process.env.CALLER_ID;
|
|
10
|
+
const SPACE_ID = process.env.SPACE_ID;
|
|
11
|
+
const API_SECRET = process.env.API_SECRET;
|
|
12
|
+
// gVisor mode: the outer container is off docker0, so reach the API over a
|
|
13
|
+
// per-agent unix socket instead of TCP. When set, host/port in API_URL are
|
|
14
|
+
// ignored (Bun routes the request through the socket). Unset for runc/local.
|
|
15
|
+
const API_SOCKET = process.env.API_SOCKET;
|
|
16
|
+
|
|
17
|
+
function fmtBytes(bytes: number): string {
|
|
18
|
+
if (bytes >= 1_073_741_824) return `${(bytes / 1_073_741_824).toFixed(1)} GB`;
|
|
19
|
+
if (bytes >= 1_048_576) return `${(bytes / 1_048_576).toFixed(1)} MB`;
|
|
20
|
+
if (bytes >= 1_024) return `${(bytes / 1_024).toFixed(1)} KB`;
|
|
21
|
+
return `${bytes} B`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function fatal(msg: string): never {
|
|
25
|
+
process.stderr.write(`error: ${msg}\n`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!API_URL) fatal("API_URL not set");
|
|
30
|
+
if (!CALLER_ID) fatal("CALLER_ID not set");
|
|
31
|
+
if (!SPACE_ID) fatal("SPACE_ID not set");
|
|
32
|
+
|
|
33
|
+
const headers: Record<string, string> = {
|
|
34
|
+
"x-mercury-caller": CALLER_ID,
|
|
35
|
+
"x-mercury-space": SPACE_ID,
|
|
36
|
+
"content-type": "application/json",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
if (API_SECRET) {
|
|
40
|
+
headers.authorization = `Bearer ${API_SECRET}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function api(
|
|
44
|
+
method: string,
|
|
45
|
+
path: string,
|
|
46
|
+
body?: unknown,
|
|
47
|
+
): Promise<unknown> {
|
|
48
|
+
const res = await fetch(
|
|
49
|
+
`${API_URL}${path}`,
|
|
50
|
+
buildRequestInit(method, headers, body, API_SOCKET),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const data = (await res.json()) as Record<string, unknown>;
|
|
54
|
+
|
|
55
|
+
if (!res.ok) {
|
|
56
|
+
const msg =
|
|
57
|
+
typeof data.error === "string" ? data.error : JSON.stringify(data);
|
|
58
|
+
fatal(`${res.status} — ${msg}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return data;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function print(data: unknown): void {
|
|
65
|
+
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function usage(): never {
|
|
69
|
+
process.stderr.write(`mrctl — manage mercury from inside the agent container
|
|
70
|
+
|
|
71
|
+
Built-in commands:
|
|
72
|
+
mrctl whoami
|
|
73
|
+
mrctl tasks list|create|pause|resume|run|delete
|
|
74
|
+
mrctl config get|set
|
|
75
|
+
mrctl prefs list|get|set|delete
|
|
76
|
+
mrctl roles list|grant|revoke
|
|
77
|
+
mrctl permissions show|set
|
|
78
|
+
mrctl spaces list|name|delete
|
|
79
|
+
mrctl conversations list
|
|
80
|
+
mrctl mute <platform-user-id> <duration> [--reason <reason>]
|
|
81
|
+
mrctl unmute <platform-user-id>
|
|
82
|
+
mrctl mutes
|
|
83
|
+
mrctl tradestation order --account <id> --symbol <sym> --quantity <n> \\
|
|
84
|
+
--action BUY|SELL|... [--type Market] [--duration DAY] [--route Intelligent] \\
|
|
85
|
+
[--limit-price p] [--stop-price p] [--expiration-date d] [--confirm] [--pending-id uuid]
|
|
86
|
+
mrctl media clear [--inbox] [--outbox]
|
|
87
|
+
mrctl disk [--json]
|
|
88
|
+
mrctl stop
|
|
89
|
+
mrctl compact
|
|
90
|
+
mrctl clear
|
|
91
|
+
mrctl recall <search text> [--limit N]
|
|
92
|
+
mrctl tts synthesize --text "Hello" --out outbox/reply.mp3 \\
|
|
93
|
+
[--language auto|he-IL|en-US] [--provider google|azure|auto]
|
|
94
|
+
Environment:
|
|
95
|
+
API_URL Host API base URL
|
|
96
|
+
API_SOCKET Unix socket to the host API (gVisor mode; overrides API_URL transport)
|
|
97
|
+
CALLER_ID Platform user ID of the caller
|
|
98
|
+
SPACE_ID Current space ID
|
|
99
|
+
`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function requireArg(args: string[], index: number, name: string): string {
|
|
104
|
+
const val = args[index];
|
|
105
|
+
if (!val) fatal(`Missing required argument: ${name}`);
|
|
106
|
+
return val;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function parseFlag(args: string[], flag: string): string | undefined {
|
|
110
|
+
const idx = args.indexOf(flag);
|
|
111
|
+
if (idx === -1 || idx + 1 >= args.length) return undefined;
|
|
112
|
+
return args[idx + 1];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function main() {
|
|
116
|
+
const args = process.argv.slice(2);
|
|
117
|
+
if (args.length === 0) usage();
|
|
118
|
+
|
|
119
|
+
const cmd = args[0];
|
|
120
|
+
const sub = args[1];
|
|
121
|
+
|
|
122
|
+
switch (cmd) {
|
|
123
|
+
case "whoami": {
|
|
124
|
+
print(await api("GET", "/api/whoami"));
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
case "tasks": {
|
|
129
|
+
if (!sub) usage();
|
|
130
|
+
switch (sub) {
|
|
131
|
+
case "list":
|
|
132
|
+
print(await api("GET", "/api/tasks"));
|
|
133
|
+
break;
|
|
134
|
+
case "create": {
|
|
135
|
+
const cron = parseFlag(args, "--cron");
|
|
136
|
+
const at = parseFlag(args, "--at");
|
|
137
|
+
const prompt = parseFlag(args, "--prompt");
|
|
138
|
+
const silent = args.includes("--silent");
|
|
139
|
+
if (!prompt) fatal("Missing --prompt");
|
|
140
|
+
if (!cron && !at) fatal("Must specify --cron or --at");
|
|
141
|
+
if (cron && at) fatal("Cannot specify both --cron and --at");
|
|
142
|
+
print(await api("POST", "/api/tasks", { cron, at, prompt, silent }));
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case "pause": {
|
|
146
|
+
const id = requireArg(args, 2, "task id");
|
|
147
|
+
print(await api("POST", `/api/tasks/${id}/pause`));
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case "resume": {
|
|
151
|
+
const id = requireArg(args, 2, "task id");
|
|
152
|
+
print(await api("POST", `/api/tasks/${id}/resume`));
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case "run": {
|
|
156
|
+
const id = requireArg(args, 2, "task id");
|
|
157
|
+
print(await api("POST", `/api/tasks/${id}/run`));
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case "delete": {
|
|
161
|
+
const id = requireArg(args, 2, "task id");
|
|
162
|
+
print(await api("DELETE", `/api/tasks/${id}`));
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
default:
|
|
166
|
+
fatal(`Unknown tasks subcommand: ${sub}`);
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
case "config": {
|
|
172
|
+
if (!sub) usage();
|
|
173
|
+
switch (sub) {
|
|
174
|
+
case "get": {
|
|
175
|
+
const data = (await api("GET", "/api/config")) as {
|
|
176
|
+
config: Record<string, string>;
|
|
177
|
+
};
|
|
178
|
+
const key = args[2];
|
|
179
|
+
if (key) {
|
|
180
|
+
const value = data.config[key];
|
|
181
|
+
if (value === undefined) fatal(`Config key not set: ${key}`);
|
|
182
|
+
process.stdout.write(`${value}\n`);
|
|
183
|
+
} else {
|
|
184
|
+
print(data);
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
case "set": {
|
|
189
|
+
const key = requireArg(args, 2, "key");
|
|
190
|
+
const value = requireArg(args, 3, "value");
|
|
191
|
+
print(await api("PUT", "/api/config", { key, value }));
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
default:
|
|
195
|
+
fatal(`Unknown config subcommand: ${sub}`);
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
case "prefs": {
|
|
201
|
+
if (!sub) usage();
|
|
202
|
+
switch (sub) {
|
|
203
|
+
case "list": {
|
|
204
|
+
print(await api("GET", "/api/prefs"));
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
case "get": {
|
|
208
|
+
const key = requireArg(args, 2, "key");
|
|
209
|
+
const data = (await api(
|
|
210
|
+
"GET",
|
|
211
|
+
`/api/prefs/${encodeURIComponent(key)}`,
|
|
212
|
+
)) as { key: string; value: string };
|
|
213
|
+
process.stdout.write(`${data.value}\n`);
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
case "set": {
|
|
217
|
+
const key = requireArg(args, 2, "key");
|
|
218
|
+
const rest = args.slice(3);
|
|
219
|
+
if (rest.length === 0) fatal("Missing value");
|
|
220
|
+
const value = rest.join(" ");
|
|
221
|
+
print(await api("PUT", "/api/prefs", { key, value }));
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
case "delete": {
|
|
225
|
+
const key = requireArg(args, 2, "key");
|
|
226
|
+
print(await api("DELETE", `/api/prefs/${encodeURIComponent(key)}`));
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
default:
|
|
230
|
+
fatal(`Unknown prefs subcommand: ${sub}`);
|
|
231
|
+
}
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
case "roles": {
|
|
236
|
+
if (!sub) usage();
|
|
237
|
+
switch (sub) {
|
|
238
|
+
case "list":
|
|
239
|
+
print(await api("GET", "/api/roles"));
|
|
240
|
+
break;
|
|
241
|
+
case "grant": {
|
|
242
|
+
const userId = requireArg(args, 2, "platform-user-id");
|
|
243
|
+
const role = parseFlag(args, "--role") ?? "admin";
|
|
244
|
+
print(
|
|
245
|
+
await api("POST", "/api/roles", { platformUserId: userId, role }),
|
|
246
|
+
);
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
case "revoke": {
|
|
250
|
+
const userId = requireArg(args, 2, "platform-user-id");
|
|
251
|
+
print(
|
|
252
|
+
await api("DELETE", `/api/roles/${encodeURIComponent(userId)}`),
|
|
253
|
+
);
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
default:
|
|
257
|
+
fatal(`Unknown roles subcommand: ${sub}`);
|
|
258
|
+
}
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
case "permissions": {
|
|
263
|
+
if (!sub) usage();
|
|
264
|
+
switch (sub) {
|
|
265
|
+
case "show": {
|
|
266
|
+
const role = parseFlag(args, "--role");
|
|
267
|
+
const query = role ? `?role=${encodeURIComponent(role)}` : "";
|
|
268
|
+
print(await api("GET", `/api/permissions${query}`));
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
case "set": {
|
|
272
|
+
const targetRole = requireArg(args, 2, "role");
|
|
273
|
+
const permsStr = requireArg(args, 3, "permissions (comma-separated)");
|
|
274
|
+
const permissions = permsStr
|
|
275
|
+
.split(",")
|
|
276
|
+
.map((s) => s.trim())
|
|
277
|
+
.filter(Boolean);
|
|
278
|
+
print(
|
|
279
|
+
await api("PUT", "/api/permissions", {
|
|
280
|
+
role: targetRole,
|
|
281
|
+
permissions,
|
|
282
|
+
}),
|
|
283
|
+
);
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
default:
|
|
287
|
+
fatal(`Unknown permissions subcommand: ${sub}`);
|
|
288
|
+
}
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
case "spaces": {
|
|
293
|
+
if (!sub) usage();
|
|
294
|
+
switch (sub) {
|
|
295
|
+
case "list": {
|
|
296
|
+
const data = (await api("GET", "/api/spaces")) as {
|
|
297
|
+
spaces: Array<{ id: string; name: string; tags: string | null }>;
|
|
298
|
+
};
|
|
299
|
+
for (const s of data.spaces) {
|
|
300
|
+
const tags = s.tags ? ` [${s.tags}]` : "";
|
|
301
|
+
process.stdout.write(`${s.id}\t${s.name}${tags}\n`);
|
|
302
|
+
}
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
case "name": {
|
|
306
|
+
const name = args[2];
|
|
307
|
+
if (name) {
|
|
308
|
+
print(await api("PUT", "/api/spaces/current/name", { name }));
|
|
309
|
+
} else {
|
|
310
|
+
const data = (await api("GET", "/api/spaces/current")) as {
|
|
311
|
+
space: { id: string; name: string };
|
|
312
|
+
};
|
|
313
|
+
process.stdout.write(`${data.space.name}\n`);
|
|
314
|
+
}
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
case "delete":
|
|
318
|
+
print(await api("DELETE", "/api/spaces/current"));
|
|
319
|
+
break;
|
|
320
|
+
default:
|
|
321
|
+
fatal(`Unknown spaces subcommand: ${sub}`);
|
|
322
|
+
}
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
case "conversations": {
|
|
327
|
+
const action = sub ?? "list";
|
|
328
|
+
switch (action) {
|
|
329
|
+
case "list": {
|
|
330
|
+
const data = (await api("GET", "/api/conversations")) as {
|
|
331
|
+
conversations: Array<{
|
|
332
|
+
id: number;
|
|
333
|
+
platform: string;
|
|
334
|
+
externalId: string;
|
|
335
|
+
kind: string;
|
|
336
|
+
observedTitle: string | null;
|
|
337
|
+
spaceId: string | null;
|
|
338
|
+
}>;
|
|
339
|
+
};
|
|
340
|
+
for (const convo of data.conversations) {
|
|
341
|
+
const title = convo.observedTitle || convo.externalId;
|
|
342
|
+
const status = convo.spaceId ? `→ ${convo.spaceId}` : "(unlinked)";
|
|
343
|
+
process.stdout.write(
|
|
344
|
+
`${convo.id}\t${convo.platform}\t${title}\t${status}\n`,
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
default:
|
|
350
|
+
fatal(`Unknown conversations subcommand: ${action}`);
|
|
351
|
+
}
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
case "tradestation": {
|
|
356
|
+
if (sub !== "order") {
|
|
357
|
+
fatal("Expected: mrctl tradestation order --account ...");
|
|
358
|
+
}
|
|
359
|
+
const accountKey = parseFlag(args, "--account");
|
|
360
|
+
const symbol = parseFlag(args, "--symbol");
|
|
361
|
+
const quantity = parseFlag(args, "--quantity");
|
|
362
|
+
const action = parseFlag(args, "--action");
|
|
363
|
+
const orderType = parseFlag(args, "--type") ?? "Market";
|
|
364
|
+
const duration = parseFlag(args, "--duration") ?? "DAY";
|
|
365
|
+
const route = parseFlag(args, "--route") ?? "Intelligent";
|
|
366
|
+
const limitPrice = parseFlag(args, "--limit-price");
|
|
367
|
+
const stopPrice = parseFlag(args, "--stop-price");
|
|
368
|
+
const expirationDate = parseFlag(args, "--expiration-date");
|
|
369
|
+
const pendingId = parseFlag(args, "--pending-id");
|
|
370
|
+
const confirm = args.includes("--confirm");
|
|
371
|
+
|
|
372
|
+
if (!accountKey || !symbol || !quantity || !action) {
|
|
373
|
+
fatal(
|
|
374
|
+
"tradestation order requires --account, --symbol, --quantity, --action (e.g. SELL, BUY)",
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const body: Record<string, unknown> = {
|
|
379
|
+
accountKey,
|
|
380
|
+
symbol,
|
|
381
|
+
quantity,
|
|
382
|
+
tradeAction: action,
|
|
383
|
+
orderType,
|
|
384
|
+
timeInForceDuration: duration,
|
|
385
|
+
route,
|
|
386
|
+
};
|
|
387
|
+
if (limitPrice !== undefined) body.limitPrice = limitPrice;
|
|
388
|
+
if (stopPrice !== undefined) body.stopPrice = stopPrice;
|
|
389
|
+
if (expirationDate !== undefined) {
|
|
390
|
+
body.timeInForceExpirationDate = expirationDate;
|
|
391
|
+
}
|
|
392
|
+
if (confirm) body.confirm = true;
|
|
393
|
+
if (pendingId !== undefined) body.pendingId = pendingId;
|
|
394
|
+
|
|
395
|
+
const result = (await api("POST", "/api/tradestation/orders", body)) as {
|
|
396
|
+
warning?: boolean;
|
|
397
|
+
message?: string;
|
|
398
|
+
pendingId?: string;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
if (result.warning) {
|
|
402
|
+
process.stdout.write(`${result.message}\n`);
|
|
403
|
+
if (result.pendingId) {
|
|
404
|
+
process.stdout.write(
|
|
405
|
+
`\nAfter the user confirms, run the same mrctl command with --confirm --pending-id ${result.pendingId}\n`,
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
} else {
|
|
409
|
+
print(result);
|
|
410
|
+
}
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
case "mute": {
|
|
415
|
+
const userId = requireArg(args, 1, "platform-user-id");
|
|
416
|
+
const duration = requireArg(args, 2, "duration (e.g. 10m, 1h, 24h)");
|
|
417
|
+
const reason = parseFlag(args, "--reason");
|
|
418
|
+
const confirm = args.includes("--confirm");
|
|
419
|
+
|
|
420
|
+
const result = (await api("POST", "/api/mutes", {
|
|
421
|
+
platformUserId: userId,
|
|
422
|
+
duration,
|
|
423
|
+
reason,
|
|
424
|
+
confirm,
|
|
425
|
+
})) as { warning?: boolean; message?: string };
|
|
426
|
+
|
|
427
|
+
if (result.warning) {
|
|
428
|
+
process.stdout.write(`${result.message}\n`);
|
|
429
|
+
process.stdout.write(
|
|
430
|
+
`\nTo confirm, run: mrctl mute ${userId} ${duration}${reason ? ` --reason "${reason}"` : ""} --confirm\n`,
|
|
431
|
+
);
|
|
432
|
+
} else {
|
|
433
|
+
print(result);
|
|
434
|
+
}
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
case "unmute": {
|
|
439
|
+
const userId = requireArg(args, 1, "platform-user-id");
|
|
440
|
+
print(await api("DELETE", `/api/mutes/${encodeURIComponent(userId)}`));
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
case "mutes": {
|
|
445
|
+
print(await api("GET", "/api/mutes"));
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
case "stop": {
|
|
450
|
+
print(await api("POST", "/api/stop"));
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
case "compact": {
|
|
455
|
+
print(await api("POST", "/api/compact"));
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
case "clear": {
|
|
460
|
+
print(await api("POST", "/api/clear"));
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
case "recall": {
|
|
465
|
+
let end = args.length;
|
|
466
|
+
const li = args.indexOf("--limit");
|
|
467
|
+
let limit = "20";
|
|
468
|
+
if (li !== -1) {
|
|
469
|
+
limit = requireArg(args, li + 1, "limit value after --limit");
|
|
470
|
+
end = li;
|
|
471
|
+
}
|
|
472
|
+
const query = args.slice(1, end).join(" ").trim();
|
|
473
|
+
if (!query) fatal("Usage: mrctl recall <search text> [--limit N]");
|
|
474
|
+
|
|
475
|
+
const qs = new URLSearchParams({ q: query, limit });
|
|
476
|
+
const data = (await api(
|
|
477
|
+
"GET",
|
|
478
|
+
`/api/messages/search?${qs.toString()}`,
|
|
479
|
+
)) as {
|
|
480
|
+
messages?: Array<{
|
|
481
|
+
role: string;
|
|
482
|
+
content: string;
|
|
483
|
+
createdAt: number;
|
|
484
|
+
}>;
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
const list = data.messages ?? [];
|
|
488
|
+
if (list.length === 0) {
|
|
489
|
+
process.stdout.write("(no matches)\n");
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
for (const m of list) {
|
|
493
|
+
const ts = new Date(m.createdAt).toISOString();
|
|
494
|
+
process.stdout.write(`[${ts}] ${m.role}: ${m.content}\n`);
|
|
495
|
+
}
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
case "tts": {
|
|
500
|
+
if (sub !== "synthesize") usage();
|
|
501
|
+
const text = parseFlag(args, "--text");
|
|
502
|
+
const out = parseFlag(args, "--out");
|
|
503
|
+
const language = parseFlag(args, "--language") as
|
|
504
|
+
| "auto"
|
|
505
|
+
| "he-IL"
|
|
506
|
+
| "en-US"
|
|
507
|
+
| undefined;
|
|
508
|
+
const provider = parseFlag(args, "--provider") as
|
|
509
|
+
| "google"
|
|
510
|
+
| "azure"
|
|
511
|
+
| "auto"
|
|
512
|
+
| undefined;
|
|
513
|
+
if (!text) fatal("Missing --text");
|
|
514
|
+
if (!out) fatal("Missing --out");
|
|
515
|
+
|
|
516
|
+
const spaceId = SPACE_ID as string;
|
|
517
|
+
const spacePrefix = path.join("/spaces", spaceId);
|
|
518
|
+
const resolvedOut = path.isAbsolute(out)
|
|
519
|
+
? (() => {
|
|
520
|
+
const norm = path.normalize(out);
|
|
521
|
+
const prefixWithSep = `${spacePrefix}${path.sep}`;
|
|
522
|
+
if (norm !== spacePrefix && !norm.startsWith(prefixWithSep)) {
|
|
523
|
+
fatal(`--out must be under ${spacePrefix}`);
|
|
524
|
+
}
|
|
525
|
+
return norm;
|
|
526
|
+
})()
|
|
527
|
+
: path.join(spacePrefix, out);
|
|
528
|
+
|
|
529
|
+
mkdirSync(path.dirname(resolvedOut), { recursive: true });
|
|
530
|
+
|
|
531
|
+
const payload: Record<string, unknown> = { text };
|
|
532
|
+
if (language) payload.language = language;
|
|
533
|
+
if (provider) payload.provider = provider;
|
|
534
|
+
|
|
535
|
+
const data = (await api("POST", "/api/tts/synthesize", payload)) as {
|
|
536
|
+
dataBase64?: string;
|
|
537
|
+
filename?: string;
|
|
538
|
+
mimeType?: string;
|
|
539
|
+
sizeBytes?: number;
|
|
540
|
+
error?: string;
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
if (!data.dataBase64) {
|
|
544
|
+
fatal(data.error ?? "TTS response missing audio");
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
writeFileSync(resolvedOut, Buffer.from(data.dataBase64, "base64"));
|
|
548
|
+
process.stderr.write(
|
|
549
|
+
`Wrote ${String(data.sizeBytes ?? "?")} bytes → ${resolvedOut} (${data.mimeType ?? "audio"})\n`,
|
|
550
|
+
);
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
case "media": {
|
|
555
|
+
if (sub !== "clear") {
|
|
556
|
+
fatal("Expected: mrctl media clear [--inbox] [--outbox]");
|
|
557
|
+
}
|
|
558
|
+
const inboxFlag = args.includes("--inbox");
|
|
559
|
+
const outboxFlag = args.includes("--outbox");
|
|
560
|
+
const body: Record<string, boolean> = {};
|
|
561
|
+
if (inboxFlag || outboxFlag) {
|
|
562
|
+
body.inbox = inboxFlag;
|
|
563
|
+
body.outbox = outboxFlag;
|
|
564
|
+
}
|
|
565
|
+
print(await api("POST", "/api/media/purge", body));
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
case "disk": {
|
|
570
|
+
const jsonFlag = args.includes("--json");
|
|
571
|
+
const data = (await api(
|
|
572
|
+
"GET",
|
|
573
|
+
"/api/console/storage",
|
|
574
|
+
)) as StorageResponse;
|
|
575
|
+
|
|
576
|
+
if (jsonFlag) {
|
|
577
|
+
print(data);
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const { disk, spaces, databaseBytes } = data;
|
|
582
|
+
process.stdout.write(
|
|
583
|
+
`Disk: ${fmtBytes(disk.totalBytes)} total · ${fmtBytes(disk.usedBytes)} used (${disk.usedPercent.toFixed(1)}%) · ${fmtBytes(disk.freeBytes)} free\n`,
|
|
584
|
+
);
|
|
585
|
+
if (spaces.length > 0) {
|
|
586
|
+
process.stdout.write("\nSpaces:\n");
|
|
587
|
+
for (const s of spaces) {
|
|
588
|
+
process.stdout.write(
|
|
589
|
+
` ${s.spaceId.padEnd(14)} inbox: ${fmtBytes(s.inboxBytes).padStart(8)} outbox: ${fmtBytes(s.outboxBytes).padStart(8)} total: ${fmtBytes(s.totalBytes)}\n`,
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
process.stdout.write(`\nDatabase: ${fmtBytes(databaseBytes)}\n`);
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
case "help":
|
|
598
|
+
case "--help":
|
|
599
|
+
case "-h":
|
|
600
|
+
usage();
|
|
601
|
+
break;
|
|
602
|
+
|
|
603
|
+
default:
|
|
604
|
+
// Should not reach here since non-builtins are handled above
|
|
605
|
+
fatal(`Unknown command: ${cmd}`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
main().catch((err) => {
|
|
610
|
+
fatal(String(err));
|
|
611
|
+
});
|