miladyai 2.0.0-alpha.27
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/dist/_virtual/_rolldown/runtime.js +7 -0
- package/dist/actions/emote.js +64 -0
- package/dist/actions/restart.js +81 -0
- package/dist/actions/send-message.js +152 -0
- package/dist/agent-admin-routes.js +82 -0
- package/dist/agent-lifecycle-routes.js +79 -0
- package/dist/agent-transfer-routes.js +102 -0
- package/dist/api/agent-admin-routes.js +82 -0
- package/dist/api/agent-lifecycle-routes.js +79 -0
- package/dist/api/agent-transfer-routes.js +102 -0
- package/dist/api/apps-hyperscape-routes.js +58 -0
- package/dist/api/apps-routes.js +114 -0
- package/dist/api/auth-routes.js +56 -0
- package/dist/api/autonomy-routes.js +44 -0
- package/dist/api/bug-report-routes.js +111 -0
- package/dist/api/character-routes.js +195 -0
- package/dist/api/cloud-routes.js +330 -0
- package/dist/api/cloud-status-routes.js +155 -0
- package/dist/api/compat-utils.js +111 -0
- package/dist/api/database.js +735 -0
- package/dist/api/diagnostics-routes.js +205 -0
- package/dist/api/drop-service.js +134 -0
- package/dist/api/early-logs.js +86 -0
- package/dist/api/http-helpers.js +131 -0
- package/dist/api/knowledge-routes.js +534 -0
- package/dist/api/memory-bounds.js +71 -0
- package/dist/api/models-routes.js +28 -0
- package/dist/api/og-tracker.js +36 -0
- package/dist/api/permissions-routes.js +109 -0
- package/dist/api/plugin-validation.js +198 -0
- package/dist/api/provider-switch-config.js +41 -0
- package/dist/api/registry-routes.js +86 -0
- package/dist/api/registry-service.js +164 -0
- package/dist/api/sandbox-routes.js +1112 -0
- package/dist/api/server.js +7949 -0
- package/dist/api/subscription-routes.js +172 -0
- package/dist/api/terminal-run-limits.js +24 -0
- package/dist/api/training-routes.js +158 -0
- package/dist/api/trajectory-routes.js +300 -0
- package/dist/api/trigger-routes.js +246 -0
- package/dist/api/twitter-verify.js +134 -0
- package/dist/api/tx-service.js +108 -0
- package/dist/api/wallet-routes.js +266 -0
- package/dist/api/wallet.js +568 -0
- package/dist/api/whatsapp-routes.js +182 -0
- package/dist/api/zip-utils.js +109 -0
- package/dist/apps-hyperscape-routes.js +58 -0
- package/dist/apps-routes.js +114 -0
- package/dist/ascii.js +20 -0
- package/dist/auth/anthropic.js +44 -0
- package/dist/auth/apply-stealth.js +41 -0
- package/dist/auth/claude-code-stealth.js +78 -0
- package/dist/auth/credentials.js +156 -0
- package/dist/auth/index.js +5 -0
- package/dist/auth/openai-codex.js +66 -0
- package/dist/auth/types.js +9 -0
- package/dist/auth-routes.js +56 -0
- package/dist/autonomy-routes.js +44 -0
- package/dist/bug-report-routes.js +111 -0
- package/dist/build-info.json +6 -0
- package/dist/character-routes.js +195 -0
- package/dist/cli/argv.js +63 -0
- package/dist/cli/banner.js +34 -0
- package/dist/cli/cli-name.js +21 -0
- package/dist/cli/cli-utils.js +16 -0
- package/dist/cli/git-commit.js +78 -0
- package/dist/cli/parse-duration.js +15 -0
- package/dist/cli/plugins-cli.js +590 -0
- package/dist/cli/profile-utils.js +9 -0
- package/dist/cli/profile.js +95 -0
- package/dist/cli/program/build-program.js +17 -0
- package/dist/cli/program/command-registry.js +23 -0
- package/dist/cli/program/help.js +47 -0
- package/dist/cli/program/preaction.js +33 -0
- package/dist/cli/program/register.config.js +106 -0
- package/dist/cli/program/register.configure.js +20 -0
- package/dist/cli/program/register.dashboard.js +124 -0
- package/dist/cli/program/register.models.js +23 -0
- package/dist/cli/program/register.setup.js +36 -0
- package/dist/cli/program/register.start.js +22 -0
- package/dist/cli/program/register.subclis.js +70 -0
- package/dist/cli/program/register.tui.js +163 -0
- package/dist/cli/program/register.update.js +154 -0
- package/dist/cli/program.js +3 -0
- package/dist/cli/run-main.js +37 -0
- package/dist/cli/version.js +7 -0
- package/dist/cloud/validate-url.js +93 -0
- package/dist/cloud-routes.js +330 -0
- package/dist/cloud-status-routes.js +155 -0
- package/dist/compat-utils.js +111 -0
- package/dist/config/config.js +69 -0
- package/dist/config/env-vars.js +19 -0
- package/dist/config/includes.js +121 -0
- package/dist/config/object-utils.js +7 -0
- package/dist/config/paths.js +38 -0
- package/dist/config/plugin-auto-enable.js +231 -0
- package/dist/config/schema.js +864 -0
- package/dist/config/telegram-custom-commands.js +76 -0
- package/dist/config/zod-schema.agent-runtime.js +519 -0
- package/dist/config/zod-schema.core.js +538 -0
- package/dist/config/zod-schema.hooks.js +103 -0
- package/dist/config/zod-schema.js +488 -0
- package/dist/config/zod-schema.providers-core.js +785 -0
- package/dist/config/zod-schema.session.js +73 -0
- package/dist/core-plugins.js +37 -0
- package/dist/custom-actions.js +250 -0
- package/dist/database.js +735 -0
- package/dist/diagnostics/integration-observability.js +57 -0
- package/dist/diagnostics-routes.js +205 -0
- package/dist/drop-service.js +134 -0
- package/dist/early-logs.js +24 -0
- package/dist/eliza.js +2061 -0
- package/dist/emotes/catalog.js +271 -0
- package/dist/entry.js +40 -0
- package/dist/hooks/discovery.js +167 -0
- package/dist/hooks/eligibility.js +64 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/loader.js +147 -0
- package/dist/hooks/registry.js +55 -0
- package/dist/http-helpers.js +131 -0
- package/dist/index.js +49 -0
- package/dist/knowledge-routes.js +534 -0
- package/dist/memory-bounds.js +71 -0
- package/dist/milady-plugin.js +90 -0
- package/dist/models-routes.js +28 -0
- package/dist/onboarding-names.js +78 -0
- package/dist/onboarding-presets.js +922 -0
- package/dist/package.json +1 -0
- package/dist/permissions-routes.js +109 -0
- package/dist/plugin-validation.js +107 -0
- package/dist/plugins/whatsapp/actions.js +91 -0
- package/dist/plugins/whatsapp/index.js +16 -0
- package/dist/plugins/whatsapp/service.js +270 -0
- package/dist/provider-switch-config.js +41 -0
- package/dist/providers/admin-trust.js +46 -0
- package/dist/providers/autonomous-state.js +101 -0
- package/dist/providers/session-bridge.js +86 -0
- package/dist/providers/session-utils.js +36 -0
- package/dist/providers/simple-mode.js +50 -0
- package/dist/providers/ui-catalog.js +15 -0
- package/dist/providers/workspace-provider.js +93 -0
- package/dist/providers/workspace.js +348 -0
- package/dist/registry-routes.js +86 -0
- package/dist/registry-service.js +164 -0
- package/dist/restart.js +40 -0
- package/dist/runtime/core-plugins.js +37 -0
- package/dist/runtime/custom-actions.js +250 -0
- package/dist/runtime/eliza.js +2061 -0
- package/dist/runtime/embedding-manager-support.js +185 -0
- package/dist/runtime/embedding-manager.js +193 -0
- package/dist/runtime/embedding-presets.js +54 -0
- package/dist/runtime/embedding-state.js +8 -0
- package/dist/runtime/milady-plugin.js +90 -0
- package/dist/runtime/onboarding-names.js +78 -0
- package/dist/runtime/restart.js +40 -0
- package/dist/runtime/version.js +7 -0
- package/dist/sandbox-routes.js +1112 -0
- package/dist/security/audit-log.js +149 -0
- package/dist/security/network-policy.js +70 -0
- package/dist/server.js +7949 -0
- package/dist/services/agent-export.js +559 -0
- package/dist/services/app-manager.js +389 -0
- package/dist/services/browser-capture.js +86 -0
- package/dist/services/fallback-training-service.js +128 -0
- package/dist/services/mcp-marketplace.js +134 -0
- package/dist/services/plugin-installer.js +396 -0
- package/dist/services/plugin-manager-types.js +15 -0
- package/dist/services/registry-client-app-meta.js +144 -0
- package/dist/services/registry-client-endpoints.js +166 -0
- package/dist/services/registry-client-local.js +271 -0
- package/dist/services/registry-client-network.js +93 -0
- package/dist/services/registry-client-queries.js +70 -0
- package/dist/services/registry-client.js +157 -0
- package/dist/services/sandbox-engine.js +511 -0
- package/dist/services/sandbox-manager.js +297 -0
- package/dist/services/self-updater.js +175 -0
- package/dist/services/skill-catalog-client.js +119 -0
- package/dist/services/skill-marketplace.js +521 -0
- package/dist/services/stream-manager.js +236 -0
- package/dist/services/update-checker.js +121 -0
- package/dist/services/update-notifier.js +29 -0
- package/dist/services/version-compat.js +78 -0
- package/dist/services/whatsapp-pairing.js +196 -0
- package/dist/shared/ui-catalog-prompt.js +728 -0
- package/dist/subscription-routes.js +172 -0
- package/dist/terminal/links.js +19 -0
- package/dist/terminal/palette.js +14 -0
- package/dist/terminal/theme.js +25 -0
- package/dist/terminal-run-limits.js +24 -0
- package/dist/training-routes.js +158 -0
- package/dist/trajectory-routes.js +300 -0
- package/dist/trigger-routes.js +246 -0
- package/dist/triggers/action.js +218 -0
- package/dist/triggers/runtime.js +281 -0
- package/dist/triggers/scheduling.js +295 -0
- package/dist/triggers/types.js +5 -0
- package/dist/tui/components/assistant-message.js +76 -0
- package/dist/tui/components/chat-editor.js +34 -0
- package/dist/tui/components/embeddings-overlay.js +46 -0
- package/dist/tui/components/footer.js +60 -0
- package/dist/tui/components/index.js +15 -0
- package/dist/tui/components/modal-frame.js +45 -0
- package/dist/tui/components/modal-style.js +15 -0
- package/dist/tui/components/model-selector.js +70 -0
- package/dist/tui/components/pinned-chat-layout.js +46 -0
- package/dist/tui/components/plugins-endpoints-tab.js +196 -0
- package/dist/tui/components/plugins-installed-tab-view.js +69 -0
- package/dist/tui/components/plugins-installed-tab.js +319 -0
- package/dist/tui/components/plugins-overlay-catalog.js +81 -0
- package/dist/tui/components/plugins-overlay-data-api.js +21 -0
- package/dist/tui/components/plugins-overlay-data-shared.js +20 -0
- package/dist/tui/components/plugins-overlay-data.js +323 -0
- package/dist/tui/components/plugins-overlay.js +117 -0
- package/dist/tui/components/plugins-store-tab.js +148 -0
- package/dist/tui/components/settings-overlay.js +61 -0
- package/dist/tui/components/status-bar.js +64 -0
- package/dist/tui/components/tool-execution.js +68 -0
- package/dist/tui/components/user-message.js +22 -0
- package/dist/tui/eliza-tui-bridge.js +606 -0
- package/dist/tui/index.js +370 -0
- package/dist/tui/modal-presets.js +33 -0
- package/dist/tui/model-spec.js +46 -0
- package/dist/tui/sse-parser.js +78 -0
- package/dist/tui/theme.js +110 -0
- package/dist/tui/titlebar-spinner.js +62 -0
- package/dist/tui/tui-app.js +311 -0
- package/dist/tui/ws-client.js +215 -0
- package/dist/twitter-verify.js +134 -0
- package/dist/tx-service.js +108 -0
- package/dist/utils/exec-safety.js +17 -0
- package/dist/utils/globals.js +20 -0
- package/dist/utils/milady-root.js +61 -0
- package/dist/utils/number-parsing.js +37 -0
- package/dist/version-resolver.js +37 -0
- package/dist/version.js +7 -0
- package/dist/wallet-routes.js +266 -0
- package/dist/wallet.js +568 -0
- package/dist/whatsapp-routes.js +182 -0
- package/dist/zip-utils.js +109 -0
- package/milady.mjs +14 -0
- package/package.json +111 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { tuiTheme } from "./theme.js";
|
|
2
|
+
import { ChatEditor } from "./components/chat-editor.js";
|
|
3
|
+
import { EmbeddingsOverlayComponent } from "./components/embeddings-overlay.js";
|
|
4
|
+
import { FooterComponent } from "./components/footer.js";
|
|
5
|
+
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
6
|
+
import { PinnedChatLayout } from "./components/pinned-chat-layout.js";
|
|
7
|
+
import { PluginsOverlayComponent } from "./components/plugins-overlay.js";
|
|
8
|
+
import { SettingsOverlayComponent } from "./components/settings-overlay.js";
|
|
9
|
+
import { StatusBar } from "./components/status-bar.js";
|
|
10
|
+
import "./components/index.js";
|
|
11
|
+
import { MODAL_PRESETS } from "./modal-presets.js";
|
|
12
|
+
import { TitlebarSpinner } from "./titlebar-spinner.js";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { getModels, getProviders } from "@mariozechner/pi-ai";
|
|
15
|
+
import { CombinedAutocompleteProvider, Container, ProcessTerminal, Spacer, TUI, Text } from "@mariozechner/pi-tui";
|
|
16
|
+
|
|
17
|
+
//#region src/tui/tui-app.ts
|
|
18
|
+
var MiladyTUI = class {
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.options = options;
|
|
21
|
+
this.terminal = new ProcessTerminal();
|
|
22
|
+
this.chatContainer = new Container();
|
|
23
|
+
this.ephemeralStatusContainer = new Container();
|
|
24
|
+
this.statusBar = new StatusBar();
|
|
25
|
+
this.footer = new FooterComponent();
|
|
26
|
+
this.modelOverlay = null;
|
|
27
|
+
this.settingsOverlay = null;
|
|
28
|
+
this.embeddingsOverlay = null;
|
|
29
|
+
this.pluginsOverlay = null;
|
|
30
|
+
this.toolOutputExpanded = false;
|
|
31
|
+
this.showThinking = process.env.MILADY_TUI_SHOW_THINKING === "1";
|
|
32
|
+
this.titlebarSpinner = new TitlebarSpinner({ setTitle: (title) => this.terminal.setTitle(title) });
|
|
33
|
+
}
|
|
34
|
+
setOnSubmit(handler) {
|
|
35
|
+
this.onSubmit = handler;
|
|
36
|
+
}
|
|
37
|
+
setOnCtrlC(handler) {
|
|
38
|
+
this.onCtrlC = handler;
|
|
39
|
+
}
|
|
40
|
+
setOnToggleToolExpand(handler) {
|
|
41
|
+
this.onToggleToolExpand = handler;
|
|
42
|
+
}
|
|
43
|
+
setOnToggleThinking(handler) {
|
|
44
|
+
this.onToggleThinking = handler;
|
|
45
|
+
}
|
|
46
|
+
getShowThinking() {
|
|
47
|
+
return this.showThinking;
|
|
48
|
+
}
|
|
49
|
+
setModelSelectorHandlers(handlers) {
|
|
50
|
+
this.modelSelectorHandlers = handlers;
|
|
51
|
+
}
|
|
52
|
+
setEmbeddingHandlers(handlers) {
|
|
53
|
+
this.embeddingHandlers = handlers;
|
|
54
|
+
}
|
|
55
|
+
getToolOutputExpanded() {
|
|
56
|
+
return this.toolOutputExpanded;
|
|
57
|
+
}
|
|
58
|
+
async start() {
|
|
59
|
+
this.ui = new TUI(this.terminal);
|
|
60
|
+
this.chatContainer = new Container();
|
|
61
|
+
this.ephemeralStatusContainer = new Container();
|
|
62
|
+
const agentName = this.options.runtime.character?.name ?? "milady";
|
|
63
|
+
this.statusBar.update({ agentName });
|
|
64
|
+
this.titlebarSpinner.setBaseTitle(this.getBaseTitle());
|
|
65
|
+
const logo = tuiTheme.bold(tuiTheme.accent("Milady")) + tuiTheme.dim(` — ${agentName}`);
|
|
66
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
67
|
+
this.chatContainer.addChild(new Text(logo, 1, 0));
|
|
68
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
69
|
+
this.editor = new ChatEditor(this.ui, tuiTheme.editor, { paddingX: 1 });
|
|
70
|
+
this.editor.onSubmit = (text) => {
|
|
71
|
+
const trimmed = text.trim();
|
|
72
|
+
if (!trimmed) return;
|
|
73
|
+
this.editor.addToHistory(trimmed);
|
|
74
|
+
this.editor.setText("");
|
|
75
|
+
this.onSubmit?.(trimmed);
|
|
76
|
+
};
|
|
77
|
+
this.editor.onCtrlC = () => {
|
|
78
|
+
this.onCtrlC?.();
|
|
79
|
+
};
|
|
80
|
+
this.editor.onCtrlE = () => {
|
|
81
|
+
this.toolOutputExpanded = !this.toolOutputExpanded;
|
|
82
|
+
this.onToggleToolExpand?.(this.toolOutputExpanded);
|
|
83
|
+
this.ui.requestRender();
|
|
84
|
+
};
|
|
85
|
+
this.editor.onCtrlP = () => {
|
|
86
|
+
this.showModelSelector();
|
|
87
|
+
};
|
|
88
|
+
this.editor.onCtrlG = () => {
|
|
89
|
+
this.showPlugins();
|
|
90
|
+
};
|
|
91
|
+
const getModelCompletions = (argumentPrefix) => {
|
|
92
|
+
const prefix = argumentPrefix.trim().toLowerCase();
|
|
93
|
+
const items = [];
|
|
94
|
+
for (const provider of getProviders()) for (const model of getModels(provider)) {
|
|
95
|
+
const spec = `${model.provider}/${model.id}`;
|
|
96
|
+
if (!prefix || spec.toLowerCase().startsWith(prefix)) items.push({
|
|
97
|
+
value: spec,
|
|
98
|
+
label: spec,
|
|
99
|
+
description: model.api
|
|
100
|
+
});
|
|
101
|
+
if (items.length >= 80) return items;
|
|
102
|
+
}
|
|
103
|
+
return items;
|
|
104
|
+
};
|
|
105
|
+
this.editor.setAutocompleteProvider(new CombinedAutocompleteProvider([
|
|
106
|
+
{
|
|
107
|
+
name: "model",
|
|
108
|
+
description: "Switch model (open selector or /model provider/id)",
|
|
109
|
+
getArgumentCompletions: (argumentPrefix) => getModelCompletions(argumentPrefix)
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "models",
|
|
113
|
+
description: "Alias for /model",
|
|
114
|
+
getArgumentCompletions: (argumentPrefix) => getModelCompletions(argumentPrefix)
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "embeddings",
|
|
118
|
+
description: "Open/switch embedding model (/embeddings [fallback|standard|performance])"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "clear",
|
|
122
|
+
description: "Clear chat"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "settings",
|
|
126
|
+
description: "Open settings panel"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "plugins",
|
|
130
|
+
description: "Open plugin manager"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "help",
|
|
134
|
+
description: "Show help"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: "exit",
|
|
138
|
+
description: "Quit"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "quit",
|
|
142
|
+
description: "Alias for /exit"
|
|
143
|
+
}
|
|
144
|
+
]));
|
|
145
|
+
this.ui.addChild(new PinnedChatLayout({
|
|
146
|
+
chat: this.chatContainer,
|
|
147
|
+
ephemeralStatus: this.ephemeralStatusContainer,
|
|
148
|
+
statusBar: this.statusBar,
|
|
149
|
+
editor: this.editor,
|
|
150
|
+
footer: this.footer,
|
|
151
|
+
getTerminalRows: () => this.terminal.rows,
|
|
152
|
+
spacerLines: 1
|
|
153
|
+
}));
|
|
154
|
+
this.ui.onDebug = () => {
|
|
155
|
+
const info = [
|
|
156
|
+
`chat children: ${this.chatContainer.children.length}`,
|
|
157
|
+
`overlays: model=${!!this.modelOverlay} embeddings=${!!this.embeddingsOverlay}`,
|
|
158
|
+
`tool expand: ${this.toolOutputExpanded}`,
|
|
159
|
+
`terminal: ${this.terminal.columns}×${this.terminal.rows}`
|
|
160
|
+
].join(" | ");
|
|
161
|
+
this.addToChatContainer(new Text(tuiTheme.dim(`[debug] ${info}`), 1, 0));
|
|
162
|
+
};
|
|
163
|
+
this.ui.setFocus(this.editor);
|
|
164
|
+
this.ui.start();
|
|
165
|
+
}
|
|
166
|
+
async stop() {
|
|
167
|
+
this.titlebarSpinner.dispose();
|
|
168
|
+
this.modelOverlay?.hide();
|
|
169
|
+
this.modelOverlay = null;
|
|
170
|
+
this.settingsOverlay?.hide();
|
|
171
|
+
this.settingsOverlay = null;
|
|
172
|
+
this.embeddingsOverlay?.hide();
|
|
173
|
+
this.embeddingsOverlay = null;
|
|
174
|
+
this.pluginsOverlay?.hide();
|
|
175
|
+
this.pluginsOverlay = null;
|
|
176
|
+
this.ui.stop();
|
|
177
|
+
}
|
|
178
|
+
addToChatContainer(component) {
|
|
179
|
+
this.chatContainer.addChild(component);
|
|
180
|
+
this.ui.requestRender();
|
|
181
|
+
}
|
|
182
|
+
setEphemeralStatus(component) {
|
|
183
|
+
this.ephemeralStatusContainer.clear();
|
|
184
|
+
this.ephemeralStatusContainer.addChild(component);
|
|
185
|
+
this.ui.requestRender();
|
|
186
|
+
}
|
|
187
|
+
clearEphemeralStatus() {
|
|
188
|
+
this.ephemeralStatusContainer.clear();
|
|
189
|
+
this.ui.requestRender();
|
|
190
|
+
}
|
|
191
|
+
requestRender() {
|
|
192
|
+
this.ui.requestRender();
|
|
193
|
+
}
|
|
194
|
+
getTUI() {
|
|
195
|
+
return this.ui;
|
|
196
|
+
}
|
|
197
|
+
getStatusBar() {
|
|
198
|
+
return this.statusBar;
|
|
199
|
+
}
|
|
200
|
+
openModelSelector() {
|
|
201
|
+
this.showModelSelector();
|
|
202
|
+
}
|
|
203
|
+
openSettings() {
|
|
204
|
+
this.showSettings();
|
|
205
|
+
}
|
|
206
|
+
openEmbeddings() {
|
|
207
|
+
this.showEmbeddings();
|
|
208
|
+
}
|
|
209
|
+
openPlugins() {
|
|
210
|
+
this.showPlugins();
|
|
211
|
+
}
|
|
212
|
+
setBusy(busy) {
|
|
213
|
+
if (busy) this.titlebarSpinner.start();
|
|
214
|
+
else this.titlebarSpinner.stop();
|
|
215
|
+
}
|
|
216
|
+
clearChat() {
|
|
217
|
+
this.chatContainer.clear();
|
|
218
|
+
this.ui.requestRender();
|
|
219
|
+
}
|
|
220
|
+
getBaseTitle() {
|
|
221
|
+
const cwd = path.basename(process.cwd());
|
|
222
|
+
return `${this.options.runtime.character?.name ?? "milady"} - ${cwd}`;
|
|
223
|
+
}
|
|
224
|
+
showSettings() {
|
|
225
|
+
if (this.settingsOverlay) return;
|
|
226
|
+
const settings = new SettingsOverlayComponent({
|
|
227
|
+
showThinking: this.showThinking,
|
|
228
|
+
toolExpand: this.toolOutputExpanded,
|
|
229
|
+
onToggleThinking: (enabled) => {
|
|
230
|
+
this.showThinking = enabled;
|
|
231
|
+
this.onToggleThinking?.(enabled);
|
|
232
|
+
},
|
|
233
|
+
onToggleToolExpand: (expanded) => {
|
|
234
|
+
this.toolOutputExpanded = expanded;
|
|
235
|
+
this.onToggleToolExpand?.(expanded);
|
|
236
|
+
},
|
|
237
|
+
onClose: () => {
|
|
238
|
+
this.settingsOverlay?.hide();
|
|
239
|
+
this.settingsOverlay = null;
|
|
240
|
+
this.ui.setFocus(this.editor);
|
|
241
|
+
this.ui.requestRender();
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
this.settingsOverlay = this.ui.showOverlay(settings, MODAL_PRESETS.compact);
|
|
245
|
+
this.ui.requestRender();
|
|
246
|
+
}
|
|
247
|
+
showEmbeddings() {
|
|
248
|
+
if (!this.embeddingHandlers) return;
|
|
249
|
+
if (this.embeddingsOverlay) return;
|
|
250
|
+
const embeddings = new EmbeddingsOverlayComponent({
|
|
251
|
+
options: this.embeddingHandlers.getOptions(),
|
|
252
|
+
onSelectTier: (tier) => {
|
|
253
|
+
this.embeddingsOverlay?.hide();
|
|
254
|
+
this.embeddingsOverlay = null;
|
|
255
|
+
this.ui.setFocus(this.editor);
|
|
256
|
+
this.ui.requestRender();
|
|
257
|
+
this.embeddingHandlers?.onSelectTier(tier);
|
|
258
|
+
},
|
|
259
|
+
onCancel: () => {
|
|
260
|
+
this.embeddingsOverlay?.hide();
|
|
261
|
+
this.embeddingsOverlay = null;
|
|
262
|
+
this.ui.setFocus(this.editor);
|
|
263
|
+
this.ui.requestRender();
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
this.embeddingsOverlay = this.ui.showOverlay(embeddings, MODAL_PRESETS.standard);
|
|
267
|
+
this.ui.requestRender();
|
|
268
|
+
}
|
|
269
|
+
showPlugins() {
|
|
270
|
+
if (this.pluginsOverlay) return;
|
|
271
|
+
const plugins = new PluginsOverlayComponent({
|
|
272
|
+
runtime: this.options.runtime,
|
|
273
|
+
apiBaseUrl: this.options.apiBaseUrl,
|
|
274
|
+
onClose: () => {
|
|
275
|
+
this.pluginsOverlay?.hide();
|
|
276
|
+
this.pluginsOverlay = null;
|
|
277
|
+
this.ui.setFocus(this.editor);
|
|
278
|
+
this.ui.requestRender();
|
|
279
|
+
},
|
|
280
|
+
requestRender: () => this.ui.requestRender()
|
|
281
|
+
});
|
|
282
|
+
this.pluginsOverlay = this.ui.showOverlay(plugins, MODAL_PRESETS.wide);
|
|
283
|
+
this.ui.requestRender();
|
|
284
|
+
}
|
|
285
|
+
showModelSelector() {
|
|
286
|
+
if (!this.modelSelectorHandlers) return;
|
|
287
|
+
if (this.modelOverlay) return;
|
|
288
|
+
const selector = new ModelSelectorComponent({
|
|
289
|
+
currentModel: this.modelSelectorHandlers.getCurrentModel(),
|
|
290
|
+
hasCredentials: this.modelSelectorHandlers.hasCredentials,
|
|
291
|
+
onSelect: (model) => {
|
|
292
|
+
this.modelSelectorHandlers?.onSelectModel(model);
|
|
293
|
+
this.modelOverlay?.hide();
|
|
294
|
+
this.modelOverlay = null;
|
|
295
|
+
this.ui.setFocus(this.editor);
|
|
296
|
+
this.ui.requestRender();
|
|
297
|
+
},
|
|
298
|
+
onCancel: () => {
|
|
299
|
+
this.modelOverlay?.hide();
|
|
300
|
+
this.modelOverlay = null;
|
|
301
|
+
this.ui.setFocus(this.editor);
|
|
302
|
+
this.ui.requestRender();
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
this.modelOverlay = this.ui.showOverlay(selector, MODAL_PRESETS.standard);
|
|
306
|
+
this.ui.requestRender();
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
//#endregion
|
|
311
|
+
export { MiladyTUI };
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { WebSocket } from "ws";
|
|
3
|
+
|
|
4
|
+
//#region src/tui/ws-client.ts
|
|
5
|
+
const WS_OPEN = 1;
|
|
6
|
+
const WS_CONNECTING = 0;
|
|
7
|
+
const WS_CLOSED = 3;
|
|
8
|
+
function defaultSocketFactory(url, options) {
|
|
9
|
+
return new WebSocket(url, { headers: options.headers });
|
|
10
|
+
}
|
|
11
|
+
function normalizeError(error) {
|
|
12
|
+
if (error instanceof Error) return error;
|
|
13
|
+
return new Error(String(error));
|
|
14
|
+
}
|
|
15
|
+
function decodeWsMessage(data) {
|
|
16
|
+
if (typeof data === "string") return data;
|
|
17
|
+
if (data instanceof ArrayBuffer) return Buffer.from(data).toString("utf8");
|
|
18
|
+
if (ArrayBuffer.isView(data)) return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8");
|
|
19
|
+
if (Array.isArray(data)) {
|
|
20
|
+
const buffers = data.map((part) => {
|
|
21
|
+
if (typeof part === "string") return Buffer.from(part, "utf8");
|
|
22
|
+
if (part instanceof ArrayBuffer) return Buffer.from(part);
|
|
23
|
+
if (ArrayBuffer.isView(part)) return Buffer.from(part.buffer, part.byteOffset, part.byteLength);
|
|
24
|
+
return Buffer.from(String(part), "utf8");
|
|
25
|
+
});
|
|
26
|
+
return Buffer.concat(buffers).toString("utf8");
|
|
27
|
+
}
|
|
28
|
+
return String(data);
|
|
29
|
+
}
|
|
30
|
+
var ApiModeWsClient = class {
|
|
31
|
+
constructor(options) {
|
|
32
|
+
this.options = options;
|
|
33
|
+
this.ws = null;
|
|
34
|
+
this.wsSendQueue = [];
|
|
35
|
+
this.reconnectTimer = null;
|
|
36
|
+
this.closed = false;
|
|
37
|
+
this.latestActiveConversationId = null;
|
|
38
|
+
this.lastSentActiveConversationId = null;
|
|
39
|
+
this.queueLimit = Math.max(1, options.queueLimit ?? 32);
|
|
40
|
+
this.backoffMs = options.reconnectInitialDelayMs ?? 500;
|
|
41
|
+
this.reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? 1e4;
|
|
42
|
+
this.wsUrl = this.buildWsUrl(options.apiBaseUrl);
|
|
43
|
+
this.socketFactory = options.socketFactory ?? defaultSocketFactory;
|
|
44
|
+
this.setTimeoutFn = options.setTimeoutFn ?? setTimeout;
|
|
45
|
+
this.clearTimeoutFn = options.clearTimeoutFn ?? clearTimeout;
|
|
46
|
+
}
|
|
47
|
+
connect() {
|
|
48
|
+
if (this.closed) return;
|
|
49
|
+
if (this.ws && (this.ws.readyState === WS_OPEN || this.ws.readyState === WS_CONNECTING)) return;
|
|
50
|
+
const token = this.getAuthToken();
|
|
51
|
+
const headers = {};
|
|
52
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
53
|
+
let socket;
|
|
54
|
+
try {
|
|
55
|
+
socket = this.socketFactory(this.wsUrl, { headers });
|
|
56
|
+
this.ws = socket;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
const rootCause = normalizeError(error);
|
|
59
|
+
if (token) this.handleError(/* @__PURE__ */ new Error(`Failed to connect websocket with Authorization header. When MILADY_API_TOKEN is set, TUI websocket auth requires header-capable websocket support. Root cause: ${rootCause.message}`));
|
|
60
|
+
else this.handleError(rootCause);
|
|
61
|
+
this.scheduleReconnect();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
socket.on("open", () => {
|
|
65
|
+
if (this.closed || this.ws !== socket) return;
|
|
66
|
+
this.backoffMs = this.options.reconnectInitialDelayMs ?? 500;
|
|
67
|
+
this.flushSendQueue();
|
|
68
|
+
this.syncActiveConversation();
|
|
69
|
+
});
|
|
70
|
+
socket.on("message", (data) => {
|
|
71
|
+
if (this.closed || this.ws !== socket) return;
|
|
72
|
+
this.handleIncomingMessage(data);
|
|
73
|
+
});
|
|
74
|
+
socket.on("close", () => {
|
|
75
|
+
if (this.ws !== socket) return;
|
|
76
|
+
this.ws = null;
|
|
77
|
+
this.lastSentActiveConversationId = null;
|
|
78
|
+
if (this.closed) return;
|
|
79
|
+
this.scheduleReconnect();
|
|
80
|
+
});
|
|
81
|
+
socket.on("error", (error) => {
|
|
82
|
+
if (this.closed || this.ws !== socket) return;
|
|
83
|
+
this.handleError(error);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
close() {
|
|
87
|
+
this.closed = true;
|
|
88
|
+
if (this.reconnectTimer) {
|
|
89
|
+
this.clearTimeoutFn(this.reconnectTimer);
|
|
90
|
+
this.reconnectTimer = null;
|
|
91
|
+
}
|
|
92
|
+
const socket = this.ws;
|
|
93
|
+
this.ws = null;
|
|
94
|
+
socket?.close();
|
|
95
|
+
}
|
|
96
|
+
sendMessage(data) {
|
|
97
|
+
const payload = JSON.stringify(data);
|
|
98
|
+
if (this.ws?.readyState === WS_OPEN) try {
|
|
99
|
+
this.ws.send(payload);
|
|
100
|
+
this.noteSentActiveConversation(data);
|
|
101
|
+
return;
|
|
102
|
+
} catch {}
|
|
103
|
+
this.queuePayload(payload, data);
|
|
104
|
+
if (!this.ws || this.ws.readyState === WS_CLOSED) this.connect();
|
|
105
|
+
}
|
|
106
|
+
setActiveConversationId(conversationId) {
|
|
107
|
+
const normalized = conversationId?.trim() || null;
|
|
108
|
+
this.latestActiveConversationId = normalized;
|
|
109
|
+
if (!normalized) {
|
|
110
|
+
this.wsSendQueue = this.wsSendQueue.filter((queued) => {
|
|
111
|
+
try {
|
|
112
|
+
return JSON.parse(queued).type !== "active-conversation";
|
|
113
|
+
} catch {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
this.lastSentActiveConversationId = null;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.sendMessage({
|
|
121
|
+
type: "active-conversation",
|
|
122
|
+
conversationId: normalized
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
buildWsUrl(apiBaseUrl) {
|
|
126
|
+
const base = new URL(apiBaseUrl);
|
|
127
|
+
const wsUrl = new URL(base.toString());
|
|
128
|
+
wsUrl.pathname = `${wsUrl.pathname.replace(/\/+$/, "")}/ws`.replace(/\/+/g, "/");
|
|
129
|
+
wsUrl.protocol = base.protocol === "https:" ? "wss:" : "ws:";
|
|
130
|
+
wsUrl.search = "";
|
|
131
|
+
wsUrl.hash = "";
|
|
132
|
+
return wsUrl.toString();
|
|
133
|
+
}
|
|
134
|
+
getAuthToken() {
|
|
135
|
+
if (this.options.getAuthToken) {
|
|
136
|
+
const explicit = this.options.getAuthToken();
|
|
137
|
+
if (typeof explicit !== "string") return null;
|
|
138
|
+
return explicit.trim() || null;
|
|
139
|
+
}
|
|
140
|
+
return process.env.MILADY_API_TOKEN?.trim() || null;
|
|
141
|
+
}
|
|
142
|
+
flushSendQueue() {
|
|
143
|
+
if (!this.ws || this.ws.readyState !== WS_OPEN || this.wsSendQueue.length < 1) return;
|
|
144
|
+
const pending = this.wsSendQueue;
|
|
145
|
+
this.wsSendQueue = [];
|
|
146
|
+
for (let i = 0; i < pending.length; i++) {
|
|
147
|
+
if (!this.ws || this.ws.readyState !== WS_OPEN) {
|
|
148
|
+
this.wsSendQueue = pending.slice(i).concat(this.wsSendQueue);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
const payload = pending[i];
|
|
152
|
+
try {
|
|
153
|
+
this.ws.send(payload);
|
|
154
|
+
this.noteSentActiveConversationFromPayload(payload);
|
|
155
|
+
} catch {
|
|
156
|
+
this.wsSendQueue = pending.slice(i).concat(this.wsSendQueue);
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
syncActiveConversation() {
|
|
162
|
+
if (!this.latestActiveConversationId) return;
|
|
163
|
+
if (this.lastSentActiveConversationId === this.latestActiveConversationId) return;
|
|
164
|
+
this.sendMessage({
|
|
165
|
+
type: "active-conversation",
|
|
166
|
+
conversationId: this.latestActiveConversationId
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
queuePayload(payload, data) {
|
|
170
|
+
if (data.type === "active-conversation") this.wsSendQueue = this.wsSendQueue.filter((queued) => {
|
|
171
|
+
try {
|
|
172
|
+
return JSON.parse(queued).type !== "active-conversation";
|
|
173
|
+
} catch {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
if (this.wsSendQueue.length >= this.queueLimit) this.wsSendQueue.shift();
|
|
178
|
+
this.wsSendQueue.push(payload);
|
|
179
|
+
}
|
|
180
|
+
scheduleReconnect() {
|
|
181
|
+
if (this.closed || this.reconnectTimer) return;
|
|
182
|
+
this.reconnectTimer = this.setTimeoutFn(() => {
|
|
183
|
+
this.reconnectTimer = null;
|
|
184
|
+
this.connect();
|
|
185
|
+
}, this.backoffMs);
|
|
186
|
+
this.backoffMs = Math.min(this.backoffMs * 1.5, this.reconnectMaxDelayMs);
|
|
187
|
+
}
|
|
188
|
+
handleIncomingMessage(rawData) {
|
|
189
|
+
const encoded = decodeWsMessage(rawData);
|
|
190
|
+
let parsed;
|
|
191
|
+
try {
|
|
192
|
+
parsed = JSON.parse(encoded);
|
|
193
|
+
} catch {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return;
|
|
197
|
+
this.options.onMessage(parsed);
|
|
198
|
+
}
|
|
199
|
+
handleError(error) {
|
|
200
|
+
this.options.onError?.(normalizeError(error));
|
|
201
|
+
}
|
|
202
|
+
noteSentActiveConversation(data) {
|
|
203
|
+
if (data.type !== "active-conversation") return;
|
|
204
|
+
this.lastSentActiveConversationId = (typeof data.conversationId === "string" ? data.conversationId.trim() : "") || null;
|
|
205
|
+
}
|
|
206
|
+
noteSentActiveConversationFromPayload(payload) {
|
|
207
|
+
try {
|
|
208
|
+
const parsed = JSON.parse(payload);
|
|
209
|
+
this.noteSentActiveConversation(parsed);
|
|
210
|
+
} catch {}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
//#endregion
|
|
215
|
+
export { ApiModeWsClient };
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { resolveStateDir } from "./config/paths.js";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { logger } from "@elizaos/core";
|
|
5
|
+
|
|
6
|
+
//#region src/api/twitter-verify.ts
|
|
7
|
+
/**
|
|
8
|
+
* Twitter/X verification for whitelist eligibility.
|
|
9
|
+
*
|
|
10
|
+
* Users post a verification message on X containing their agent name and
|
|
11
|
+
* wallet address. The app verifies the tweet exists using the FxTwitter API
|
|
12
|
+
* (free, no auth required). Verified addresses are stored locally and can
|
|
13
|
+
* be collected into a Merkle tree for on-chain whitelist proofs.
|
|
14
|
+
*/
|
|
15
|
+
const WHITELIST_FILE = "whitelist.json";
|
|
16
|
+
function generateVerificationMessage(agentName, walletAddress) {
|
|
17
|
+
return `Verifying my Milady agent "${agentName}" | ${`${walletAddress.slice(0, 6)}...${walletAddress.slice(-4)}`} #MiladyAgent`;
|
|
18
|
+
}
|
|
19
|
+
function parseTweetUrl(url) {
|
|
20
|
+
const match = url.match(/(?:twitter\.com|x\.com)\/(\w+)\/status\/(\d+)/);
|
|
21
|
+
if (!match) return null;
|
|
22
|
+
return {
|
|
23
|
+
screenName: match[1],
|
|
24
|
+
tweetId: match[2]
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async function verifyTweet(tweetUrl, walletAddress) {
|
|
28
|
+
const parsed = parseTweetUrl(tweetUrl);
|
|
29
|
+
if (!parsed) return {
|
|
30
|
+
verified: false,
|
|
31
|
+
error: "Invalid tweet URL. Use a twitter.com or x.com status URL.",
|
|
32
|
+
handle: null
|
|
33
|
+
};
|
|
34
|
+
const apiUrl = `https://api.fxtwitter.com/${parsed.screenName}/status/${parsed.tweetId}`;
|
|
35
|
+
let response;
|
|
36
|
+
try {
|
|
37
|
+
response = await fetch(apiUrl, {
|
|
38
|
+
headers: { "User-Agent": "MiladyVerifier/1.0" },
|
|
39
|
+
signal: AbortSignal.timeout(15e3)
|
|
40
|
+
});
|
|
41
|
+
} catch (err) {
|
|
42
|
+
logger.warn(`[twitter-verify] FxTwitter fetch failed: ${err}`);
|
|
43
|
+
return {
|
|
44
|
+
verified: false,
|
|
45
|
+
error: "Could not reach tweet verification service. Try again later.",
|
|
46
|
+
handle: null
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
if (response.status === 404) return {
|
|
51
|
+
verified: false,
|
|
52
|
+
error: "Tweet not found. Make sure the URL is correct and the tweet is public.",
|
|
53
|
+
handle: null
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
verified: false,
|
|
57
|
+
error: `Tweet fetch failed (HTTP ${response.status})`,
|
|
58
|
+
handle: null
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
let data;
|
|
62
|
+
try {
|
|
63
|
+
data = await response.json();
|
|
64
|
+
} catch {
|
|
65
|
+
return {
|
|
66
|
+
verified: false,
|
|
67
|
+
error: "Invalid response from verification service",
|
|
68
|
+
handle: null
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (!data.tweet?.text) return {
|
|
72
|
+
verified: false,
|
|
73
|
+
error: "Could not read tweet content",
|
|
74
|
+
handle: null
|
|
75
|
+
};
|
|
76
|
+
const tweetText = data.tweet.text;
|
|
77
|
+
const handle = data.tweet.author?.screen_name ?? parsed.screenName;
|
|
78
|
+
const shortAddr = `${walletAddress.slice(0, 6)}...${walletAddress.slice(-4)}`;
|
|
79
|
+
const hasAddress = tweetText.includes(shortAddr) || tweetText.toLowerCase().includes(walletAddress.toLowerCase().slice(0, 10));
|
|
80
|
+
const hasHashtag = tweetText.includes("#MiladyAgent");
|
|
81
|
+
if (!hasAddress) return {
|
|
82
|
+
verified: false,
|
|
83
|
+
error: "Tweet does not contain your wallet address. Make sure you copied the full verification message.",
|
|
84
|
+
handle
|
|
85
|
+
};
|
|
86
|
+
if (!hasHashtag) return {
|
|
87
|
+
verified: false,
|
|
88
|
+
error: "Tweet is missing #MiladyAgent hashtag.",
|
|
89
|
+
handle
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
verified: true,
|
|
93
|
+
error: null,
|
|
94
|
+
handle
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function whitelistPath() {
|
|
98
|
+
return path.join(resolveStateDir(), WHITELIST_FILE);
|
|
99
|
+
}
|
|
100
|
+
function loadWhitelist() {
|
|
101
|
+
const filePath = whitelistPath();
|
|
102
|
+
if (!fs.existsSync(filePath)) return { verified: {} };
|
|
103
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
104
|
+
return JSON.parse(raw);
|
|
105
|
+
}
|
|
106
|
+
function saveWhitelist(data) {
|
|
107
|
+
const filePath = whitelistPath();
|
|
108
|
+
const dir = path.dirname(filePath);
|
|
109
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, {
|
|
110
|
+
recursive: true,
|
|
111
|
+
mode: 448
|
|
112
|
+
});
|
|
113
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), {
|
|
114
|
+
encoding: "utf-8",
|
|
115
|
+
mode: 384
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function markAddressVerified(address, tweetUrl, handle) {
|
|
119
|
+
const wl = loadWhitelist();
|
|
120
|
+
wl.verified[address.toLowerCase()] = {
|
|
121
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
122
|
+
tweetUrl,
|
|
123
|
+
handle
|
|
124
|
+
};
|
|
125
|
+
saveWhitelist(wl);
|
|
126
|
+
logger.info(`[twitter-verify] Address ${address} verified via @${handle}`);
|
|
127
|
+
}
|
|
128
|
+
function isAddressWhitelisted(address) {
|
|
129
|
+
const wl = loadWhitelist();
|
|
130
|
+
return address.toLowerCase() in wl.verified;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
//#endregion
|
|
134
|
+
export { generateVerificationMessage, isAddressWhitelisted, markAddressVerified, verifyTweet };
|