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.
Files changed (241) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +7 -0
  2. package/dist/actions/emote.js +64 -0
  3. package/dist/actions/restart.js +81 -0
  4. package/dist/actions/send-message.js +152 -0
  5. package/dist/agent-admin-routes.js +82 -0
  6. package/dist/agent-lifecycle-routes.js +79 -0
  7. package/dist/agent-transfer-routes.js +102 -0
  8. package/dist/api/agent-admin-routes.js +82 -0
  9. package/dist/api/agent-lifecycle-routes.js +79 -0
  10. package/dist/api/agent-transfer-routes.js +102 -0
  11. package/dist/api/apps-hyperscape-routes.js +58 -0
  12. package/dist/api/apps-routes.js +114 -0
  13. package/dist/api/auth-routes.js +56 -0
  14. package/dist/api/autonomy-routes.js +44 -0
  15. package/dist/api/bug-report-routes.js +111 -0
  16. package/dist/api/character-routes.js +195 -0
  17. package/dist/api/cloud-routes.js +330 -0
  18. package/dist/api/cloud-status-routes.js +155 -0
  19. package/dist/api/compat-utils.js +111 -0
  20. package/dist/api/database.js +735 -0
  21. package/dist/api/diagnostics-routes.js +205 -0
  22. package/dist/api/drop-service.js +134 -0
  23. package/dist/api/early-logs.js +86 -0
  24. package/dist/api/http-helpers.js +131 -0
  25. package/dist/api/knowledge-routes.js +534 -0
  26. package/dist/api/memory-bounds.js +71 -0
  27. package/dist/api/models-routes.js +28 -0
  28. package/dist/api/og-tracker.js +36 -0
  29. package/dist/api/permissions-routes.js +109 -0
  30. package/dist/api/plugin-validation.js +198 -0
  31. package/dist/api/provider-switch-config.js +41 -0
  32. package/dist/api/registry-routes.js +86 -0
  33. package/dist/api/registry-service.js +164 -0
  34. package/dist/api/sandbox-routes.js +1112 -0
  35. package/dist/api/server.js +7949 -0
  36. package/dist/api/subscription-routes.js +172 -0
  37. package/dist/api/terminal-run-limits.js +24 -0
  38. package/dist/api/training-routes.js +158 -0
  39. package/dist/api/trajectory-routes.js +300 -0
  40. package/dist/api/trigger-routes.js +246 -0
  41. package/dist/api/twitter-verify.js +134 -0
  42. package/dist/api/tx-service.js +108 -0
  43. package/dist/api/wallet-routes.js +266 -0
  44. package/dist/api/wallet.js +568 -0
  45. package/dist/api/whatsapp-routes.js +182 -0
  46. package/dist/api/zip-utils.js +109 -0
  47. package/dist/apps-hyperscape-routes.js +58 -0
  48. package/dist/apps-routes.js +114 -0
  49. package/dist/ascii.js +20 -0
  50. package/dist/auth/anthropic.js +44 -0
  51. package/dist/auth/apply-stealth.js +41 -0
  52. package/dist/auth/claude-code-stealth.js +78 -0
  53. package/dist/auth/credentials.js +156 -0
  54. package/dist/auth/index.js +5 -0
  55. package/dist/auth/openai-codex.js +66 -0
  56. package/dist/auth/types.js +9 -0
  57. package/dist/auth-routes.js +56 -0
  58. package/dist/autonomy-routes.js +44 -0
  59. package/dist/bug-report-routes.js +111 -0
  60. package/dist/build-info.json +6 -0
  61. package/dist/character-routes.js +195 -0
  62. package/dist/cli/argv.js +63 -0
  63. package/dist/cli/banner.js +34 -0
  64. package/dist/cli/cli-name.js +21 -0
  65. package/dist/cli/cli-utils.js +16 -0
  66. package/dist/cli/git-commit.js +78 -0
  67. package/dist/cli/parse-duration.js +15 -0
  68. package/dist/cli/plugins-cli.js +590 -0
  69. package/dist/cli/profile-utils.js +9 -0
  70. package/dist/cli/profile.js +95 -0
  71. package/dist/cli/program/build-program.js +17 -0
  72. package/dist/cli/program/command-registry.js +23 -0
  73. package/dist/cli/program/help.js +47 -0
  74. package/dist/cli/program/preaction.js +33 -0
  75. package/dist/cli/program/register.config.js +106 -0
  76. package/dist/cli/program/register.configure.js +20 -0
  77. package/dist/cli/program/register.dashboard.js +124 -0
  78. package/dist/cli/program/register.models.js +23 -0
  79. package/dist/cli/program/register.setup.js +36 -0
  80. package/dist/cli/program/register.start.js +22 -0
  81. package/dist/cli/program/register.subclis.js +70 -0
  82. package/dist/cli/program/register.tui.js +163 -0
  83. package/dist/cli/program/register.update.js +154 -0
  84. package/dist/cli/program.js +3 -0
  85. package/dist/cli/run-main.js +37 -0
  86. package/dist/cli/version.js +7 -0
  87. package/dist/cloud/validate-url.js +93 -0
  88. package/dist/cloud-routes.js +330 -0
  89. package/dist/cloud-status-routes.js +155 -0
  90. package/dist/compat-utils.js +111 -0
  91. package/dist/config/config.js +69 -0
  92. package/dist/config/env-vars.js +19 -0
  93. package/dist/config/includes.js +121 -0
  94. package/dist/config/object-utils.js +7 -0
  95. package/dist/config/paths.js +38 -0
  96. package/dist/config/plugin-auto-enable.js +231 -0
  97. package/dist/config/schema.js +864 -0
  98. package/dist/config/telegram-custom-commands.js +76 -0
  99. package/dist/config/zod-schema.agent-runtime.js +519 -0
  100. package/dist/config/zod-schema.core.js +538 -0
  101. package/dist/config/zod-schema.hooks.js +103 -0
  102. package/dist/config/zod-schema.js +488 -0
  103. package/dist/config/zod-schema.providers-core.js +785 -0
  104. package/dist/config/zod-schema.session.js +73 -0
  105. package/dist/core-plugins.js +37 -0
  106. package/dist/custom-actions.js +250 -0
  107. package/dist/database.js +735 -0
  108. package/dist/diagnostics/integration-observability.js +57 -0
  109. package/dist/diagnostics-routes.js +205 -0
  110. package/dist/drop-service.js +134 -0
  111. package/dist/early-logs.js +24 -0
  112. package/dist/eliza.js +2061 -0
  113. package/dist/emotes/catalog.js +271 -0
  114. package/dist/entry.js +40 -0
  115. package/dist/hooks/discovery.js +167 -0
  116. package/dist/hooks/eligibility.js +64 -0
  117. package/dist/hooks/index.js +4 -0
  118. package/dist/hooks/loader.js +147 -0
  119. package/dist/hooks/registry.js +55 -0
  120. package/dist/http-helpers.js +131 -0
  121. package/dist/index.js +49 -0
  122. package/dist/knowledge-routes.js +534 -0
  123. package/dist/memory-bounds.js +71 -0
  124. package/dist/milady-plugin.js +90 -0
  125. package/dist/models-routes.js +28 -0
  126. package/dist/onboarding-names.js +78 -0
  127. package/dist/onboarding-presets.js +922 -0
  128. package/dist/package.json +1 -0
  129. package/dist/permissions-routes.js +109 -0
  130. package/dist/plugin-validation.js +107 -0
  131. package/dist/plugins/whatsapp/actions.js +91 -0
  132. package/dist/plugins/whatsapp/index.js +16 -0
  133. package/dist/plugins/whatsapp/service.js +270 -0
  134. package/dist/provider-switch-config.js +41 -0
  135. package/dist/providers/admin-trust.js +46 -0
  136. package/dist/providers/autonomous-state.js +101 -0
  137. package/dist/providers/session-bridge.js +86 -0
  138. package/dist/providers/session-utils.js +36 -0
  139. package/dist/providers/simple-mode.js +50 -0
  140. package/dist/providers/ui-catalog.js +15 -0
  141. package/dist/providers/workspace-provider.js +93 -0
  142. package/dist/providers/workspace.js +348 -0
  143. package/dist/registry-routes.js +86 -0
  144. package/dist/registry-service.js +164 -0
  145. package/dist/restart.js +40 -0
  146. package/dist/runtime/core-plugins.js +37 -0
  147. package/dist/runtime/custom-actions.js +250 -0
  148. package/dist/runtime/eliza.js +2061 -0
  149. package/dist/runtime/embedding-manager-support.js +185 -0
  150. package/dist/runtime/embedding-manager.js +193 -0
  151. package/dist/runtime/embedding-presets.js +54 -0
  152. package/dist/runtime/embedding-state.js +8 -0
  153. package/dist/runtime/milady-plugin.js +90 -0
  154. package/dist/runtime/onboarding-names.js +78 -0
  155. package/dist/runtime/restart.js +40 -0
  156. package/dist/runtime/version.js +7 -0
  157. package/dist/sandbox-routes.js +1112 -0
  158. package/dist/security/audit-log.js +149 -0
  159. package/dist/security/network-policy.js +70 -0
  160. package/dist/server.js +7949 -0
  161. package/dist/services/agent-export.js +559 -0
  162. package/dist/services/app-manager.js +389 -0
  163. package/dist/services/browser-capture.js +86 -0
  164. package/dist/services/fallback-training-service.js +128 -0
  165. package/dist/services/mcp-marketplace.js +134 -0
  166. package/dist/services/plugin-installer.js +396 -0
  167. package/dist/services/plugin-manager-types.js +15 -0
  168. package/dist/services/registry-client-app-meta.js +144 -0
  169. package/dist/services/registry-client-endpoints.js +166 -0
  170. package/dist/services/registry-client-local.js +271 -0
  171. package/dist/services/registry-client-network.js +93 -0
  172. package/dist/services/registry-client-queries.js +70 -0
  173. package/dist/services/registry-client.js +157 -0
  174. package/dist/services/sandbox-engine.js +511 -0
  175. package/dist/services/sandbox-manager.js +297 -0
  176. package/dist/services/self-updater.js +175 -0
  177. package/dist/services/skill-catalog-client.js +119 -0
  178. package/dist/services/skill-marketplace.js +521 -0
  179. package/dist/services/stream-manager.js +236 -0
  180. package/dist/services/update-checker.js +121 -0
  181. package/dist/services/update-notifier.js +29 -0
  182. package/dist/services/version-compat.js +78 -0
  183. package/dist/services/whatsapp-pairing.js +196 -0
  184. package/dist/shared/ui-catalog-prompt.js +728 -0
  185. package/dist/subscription-routes.js +172 -0
  186. package/dist/terminal/links.js +19 -0
  187. package/dist/terminal/palette.js +14 -0
  188. package/dist/terminal/theme.js +25 -0
  189. package/dist/terminal-run-limits.js +24 -0
  190. package/dist/training-routes.js +158 -0
  191. package/dist/trajectory-routes.js +300 -0
  192. package/dist/trigger-routes.js +246 -0
  193. package/dist/triggers/action.js +218 -0
  194. package/dist/triggers/runtime.js +281 -0
  195. package/dist/triggers/scheduling.js +295 -0
  196. package/dist/triggers/types.js +5 -0
  197. package/dist/tui/components/assistant-message.js +76 -0
  198. package/dist/tui/components/chat-editor.js +34 -0
  199. package/dist/tui/components/embeddings-overlay.js +46 -0
  200. package/dist/tui/components/footer.js +60 -0
  201. package/dist/tui/components/index.js +15 -0
  202. package/dist/tui/components/modal-frame.js +45 -0
  203. package/dist/tui/components/modal-style.js +15 -0
  204. package/dist/tui/components/model-selector.js +70 -0
  205. package/dist/tui/components/pinned-chat-layout.js +46 -0
  206. package/dist/tui/components/plugins-endpoints-tab.js +196 -0
  207. package/dist/tui/components/plugins-installed-tab-view.js +69 -0
  208. package/dist/tui/components/plugins-installed-tab.js +319 -0
  209. package/dist/tui/components/plugins-overlay-catalog.js +81 -0
  210. package/dist/tui/components/plugins-overlay-data-api.js +21 -0
  211. package/dist/tui/components/plugins-overlay-data-shared.js +20 -0
  212. package/dist/tui/components/plugins-overlay-data.js +323 -0
  213. package/dist/tui/components/plugins-overlay.js +117 -0
  214. package/dist/tui/components/plugins-store-tab.js +148 -0
  215. package/dist/tui/components/settings-overlay.js +61 -0
  216. package/dist/tui/components/status-bar.js +64 -0
  217. package/dist/tui/components/tool-execution.js +68 -0
  218. package/dist/tui/components/user-message.js +22 -0
  219. package/dist/tui/eliza-tui-bridge.js +606 -0
  220. package/dist/tui/index.js +370 -0
  221. package/dist/tui/modal-presets.js +33 -0
  222. package/dist/tui/model-spec.js +46 -0
  223. package/dist/tui/sse-parser.js +78 -0
  224. package/dist/tui/theme.js +110 -0
  225. package/dist/tui/titlebar-spinner.js +62 -0
  226. package/dist/tui/tui-app.js +311 -0
  227. package/dist/tui/ws-client.js +215 -0
  228. package/dist/twitter-verify.js +134 -0
  229. package/dist/tx-service.js +108 -0
  230. package/dist/utils/exec-safety.js +17 -0
  231. package/dist/utils/globals.js +20 -0
  232. package/dist/utils/milady-root.js +61 -0
  233. package/dist/utils/number-parsing.js +37 -0
  234. package/dist/version-resolver.js +37 -0
  235. package/dist/version.js +7 -0
  236. package/dist/wallet-routes.js +266 -0
  237. package/dist/wallet.js +568 -0
  238. package/dist/whatsapp-routes.js +182 -0
  239. package/dist/zip-utils.js +109 -0
  240. package/milady.mjs +14 -0
  241. package/package.json +111 -0
@@ -0,0 +1,134 @@
1
+ import { createIntegrationTelemetrySpan } from "../diagnostics/integration-observability.js";
2
+
3
+ //#region src/services/mcp-marketplace.ts
4
+ /**
5
+ * MCP Marketplace Service
6
+ *
7
+ * Fetches MCP servers from the official registry and manages local config.
8
+ */
9
+ const MCP_REGISTRY_BASE_URL = "https://registry.modelcontextprotocol.io";
10
+ /**
11
+ * Search MCP servers from the official registry.
12
+ */
13
+ async function searchMcpMarketplace(query, limit = 30) {
14
+ const url = `${MCP_REGISTRY_BASE_URL}/v0/servers`;
15
+ const searchSpan = createIntegrationTelemetrySpan({
16
+ boundary: "mcp",
17
+ operation: "search_registry_servers"
18
+ });
19
+ let resp;
20
+ try {
21
+ resp = await fetch(url, { headers: { Accept: "application/json" } });
22
+ } catch (err) {
23
+ searchSpan.failure({ error: err });
24
+ throw err;
25
+ }
26
+ if (!resp.ok) {
27
+ searchSpan.failure({
28
+ statusCode: resp.status,
29
+ errorKind: "http_error"
30
+ });
31
+ throw new Error(`Registry API error: ${resp.status} ${resp.statusText}`);
32
+ }
33
+ let data;
34
+ try {
35
+ data = await resp.json();
36
+ } catch (err) {
37
+ searchSpan.failure({
38
+ error: err,
39
+ statusCode: resp.status
40
+ });
41
+ throw err;
42
+ }
43
+ const results = [];
44
+ const seenNames = /* @__PURE__ */ new Set();
45
+ for (const entry of data.servers) {
46
+ const server = entry.server;
47
+ const meta = entry._meta?.["io.modelcontextprotocol.registry/official"];
48
+ if (!meta?.isLatest) continue;
49
+ if (seenNames.has(server.name)) continue;
50
+ seenNames.add(server.name);
51
+ if (query) {
52
+ const q = query.toLowerCase();
53
+ const matchName = server.name.toLowerCase().includes(q);
54
+ const matchTitle = server.title?.toLowerCase().includes(q);
55
+ const matchDesc = server.description?.toLowerCase().includes(q);
56
+ if (!matchName && !matchTitle && !matchDesc) continue;
57
+ }
58
+ let connectionType = "remote";
59
+ let connectionUrl;
60
+ let npmPackage;
61
+ let dockerImage;
62
+ if (server.remotes && server.remotes.length > 0) {
63
+ connectionType = "remote";
64
+ connectionUrl = server.remotes[0].url;
65
+ } else if (server.packages && server.packages.length > 0) {
66
+ const pkg = server.packages[0];
67
+ connectionType = "stdio";
68
+ if (pkg.registryType === "npm") npmPackage = pkg.identifier;
69
+ else if (pkg.registryType === "oci") dockerImage = pkg.identifier;
70
+ }
71
+ results.push({
72
+ id: `${server.name}@${server.version}`,
73
+ name: server.name,
74
+ title: server.title || server.name.split("/").pop() || server.name,
75
+ description: server.description || "No description",
76
+ version: server.version,
77
+ connectionType,
78
+ connectionUrl,
79
+ npmPackage,
80
+ dockerImage,
81
+ repositoryUrl: server.repository?.url,
82
+ websiteUrl: server.websiteUrl,
83
+ iconUrl: server.icons?.[0]?.src,
84
+ publishedAt: meta?.publishedAt,
85
+ isLatest: true
86
+ });
87
+ if (results.length >= limit) break;
88
+ }
89
+ searchSpan.success({ statusCode: resp.status });
90
+ return { results };
91
+ }
92
+ /**
93
+ * Get detailed info for a specific MCP server from registry.
94
+ */
95
+ async function getMcpServerDetails(name) {
96
+ const url = `${MCP_REGISTRY_BASE_URL}/v0/servers/${encodeURIComponent(name)}`;
97
+ const detailsSpan = createIntegrationTelemetrySpan({
98
+ boundary: "mcp",
99
+ operation: "get_registry_server_details"
100
+ });
101
+ let resp;
102
+ try {
103
+ resp = await fetch(url, { headers: { Accept: "application/json" } });
104
+ } catch (err) {
105
+ detailsSpan.failure({ error: err });
106
+ throw err;
107
+ }
108
+ if (!resp.ok) {
109
+ if (resp.status === 404) {
110
+ detailsSpan.success({ statusCode: resp.status });
111
+ return null;
112
+ }
113
+ detailsSpan.failure({
114
+ statusCode: resp.status,
115
+ errorKind: "http_error"
116
+ });
117
+ throw new Error(`Registry API error: ${resp.status}`);
118
+ }
119
+ let data;
120
+ try {
121
+ data = await resp.json();
122
+ } catch (err) {
123
+ detailsSpan.failure({
124
+ error: err,
125
+ statusCode: resp.status
126
+ });
127
+ throw err;
128
+ }
129
+ detailsSpan.success({ statusCode: resp.status });
130
+ return data.server;
131
+ }
132
+
133
+ //#endregion
134
+ export { getMcpServerDetails, searchMcpMarketplace };
@@ -0,0 +1,396 @@
1
+ import { loadMiladyConfig, saveMiladyConfig } from "../config/config.js";
2
+ import { getPluginInfo } from "./registry-client.js";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { logger } from "@elizaos/core";
6
+ import { execFile } from "node:child_process";
7
+ import fs from "node:fs/promises";
8
+ import { promisify } from "node:util";
9
+
10
+ //#region src/services/plugin-installer.ts
11
+ /**
12
+ * Plugin Installer for Milady.
13
+ *
14
+ * Cross-platform plugin installation and lifecycle management.
15
+ *
16
+ * Install targets:
17
+ * ~/.milady/plugins/installed/<sanitised-name>/
18
+ *
19
+ * Works identically whether milady is:
20
+ * - Running from source (dev)
21
+ * - Running as a CLI install (npm global)
22
+ * - Running inside an Electron .app bundle
23
+ * - Running on macOS, Linux, or Windows
24
+ *
25
+ * Strategy:
26
+ * 1. npm/bun install to an isolated prefix directory
27
+ * 2. Fallback: git clone from the plugin's GitHub repo
28
+ * 3. Track the installation in milady.json config
29
+ * 4. Trigger agent restart to load the new plugin
30
+ *
31
+ * @module services/plugin-installer
32
+ */
33
+ const execFileAsync = promisify(execFile);
34
+ /** npm package names: @scope/name or name. No shell metacharacters. */
35
+ const VALID_PACKAGE_NAME = /^(@[a-zA-Z0-9][\w.-]*\/)?[a-zA-Z0-9][\w.-]*$/;
36
+ /** Version strings: semver, dist-tags, git refs. Conservative allowlist. */
37
+ const VALID_VERSION = /^[a-zA-Z0-9][\w.+-]*$/;
38
+ /** Git branch names: alphanumeric, hyphens, slashes, dots. No shell metacharacters. */
39
+ const VALID_BRANCH = /^[a-zA-Z0-9][\w./-]*$/;
40
+ /** Git URLs: https:// only, no shell metacharacters. */
41
+ const VALID_GIT_URL = /^https:\/\/[a-zA-Z0-9][\w./-]*\.git$/;
42
+ function assertValidPackageName(name) {
43
+ if (!VALID_PACKAGE_NAME.test(name)) throw new Error(`Invalid package name: "${name}"`);
44
+ }
45
+ function assertValidVersion(version) {
46
+ if (!VALID_VERSION.test(version)) throw new Error(`Invalid version string: "${version}"`);
47
+ }
48
+ function assertValidGitUrl(url) {
49
+ if (!VALID_GIT_URL.test(url)) throw new Error(`Invalid git URL: "${url}"`);
50
+ }
51
+ let installLock = Promise.resolve();
52
+ function serialise(fn) {
53
+ const prev = installLock;
54
+ let resolve;
55
+ installLock = new Promise((r) => {
56
+ resolve = r;
57
+ });
58
+ return prev.then(fn).finally(() => resolve?.());
59
+ }
60
+ function pluginsBaseDir() {
61
+ const base = process.env.MILADY_STATE_DIR?.trim() || path.join(os.homedir(), ".milady");
62
+ return path.join(base, "plugins", "installed");
63
+ }
64
+ function sanitisePackageName(name) {
65
+ return name.replace(/[^a-zA-Z0-9._-]/g, "_");
66
+ }
67
+ function pluginDir(pluginName) {
68
+ return path.join(pluginsBaseDir(), sanitisePackageName(pluginName));
69
+ }
70
+ async function detectPackageManager() {
71
+ for (const cmd of ["bun", "npm"]) try {
72
+ await execFileAsync(cmd, ["--version"]);
73
+ return cmd;
74
+ } catch {}
75
+ return "npm";
76
+ }
77
+ /**
78
+ * Install a plugin from the registry.
79
+ *
80
+ * 1. Resolves the plugin name in the registry.
81
+ * 2. Installs via npm/bun to ~/.milady/plugins/installed/<name>/.
82
+ * 3. Falls back to git clone if npm is not available for this package.
83
+ * 4. Writes an install record to milady.json.
84
+ * 5. Returns metadata about the installation for the caller to
85
+ * decide whether to trigger a restart.
86
+ *
87
+ * @param pluginName - The plugin name (e.g., "@elizaos/plugin-twitter")
88
+ * @param onProgress - Optional progress callback
89
+ * @param requestedVersion - Optional specific version to install (e.g., "1.2.23-alpha.0")
90
+ */
91
+ function installPlugin(pluginName, onProgress, requestedVersion) {
92
+ return serialise(() => _installPlugin(pluginName, onProgress, requestedVersion));
93
+ }
94
+ async function _installPlugin(pluginName, onProgress, requestedVersion) {
95
+ const emit = (phase, message) => onProgress?.({
96
+ phase,
97
+ pluginName,
98
+ message
99
+ });
100
+ emit("resolving", `Looking up ${pluginName} in registry...`);
101
+ const info = await getPluginInfo(pluginName);
102
+ if (!info) return {
103
+ success: false,
104
+ pluginName,
105
+ version: "",
106
+ installPath: "",
107
+ requiresRestart: false,
108
+ error: `Plugin "${pluginName}" not found in the registry`
109
+ };
110
+ const canonicalName = info.name;
111
+ const npmVersion = requestedVersion || info.npm.v2Version || info.npm.v1Version || "next";
112
+ const localPath = info.localPath;
113
+ const targetDir = pluginDir(canonicalName);
114
+ await fs.mkdir(targetDir, { recursive: true });
115
+ const targetPkgPath = path.join(targetDir, "package.json");
116
+ try {
117
+ await fs.access(targetPkgPath);
118
+ } catch {
119
+ await fs.writeFile(targetPkgPath, JSON.stringify({
120
+ private: true,
121
+ dependencies: {}
122
+ }, null, 2));
123
+ }
124
+ let installedVersion = npmVersion;
125
+ let installSource = "npm";
126
+ const pm = await detectPackageManager();
127
+ let installed = false;
128
+ if (localPath) {
129
+ emit("downloading", `Installing ${canonicalName} from local workspace...`);
130
+ try {
131
+ await runLocalPathInstall(pm, canonicalName, localPath, targetDir);
132
+ installedVersion = await readInstalledVersion(targetDir, canonicalName, npmVersion);
133
+ installSource = "path";
134
+ installed = true;
135
+ } catch (localErr) {
136
+ logger.warn(`[plugin-installer] local install failed for ${canonicalName}: ${localErr instanceof Error ? localErr.message : String(localErr)}`);
137
+ }
138
+ }
139
+ if (!installed) {
140
+ emit("downloading", `Installing ${canonicalName}@${npmVersion}...`);
141
+ try {
142
+ await runPackageInstall(pm, canonicalName, npmVersion, targetDir);
143
+ installedVersion = await readInstalledVersion(targetDir, canonicalName, npmVersion);
144
+ installSource = "npm";
145
+ installed = true;
146
+ } catch (npmErr) {
147
+ logger.warn(`[plugin-installer] npm failed for ${canonicalName}: ${npmErr instanceof Error ? npmErr.message : String(npmErr)}`);
148
+ emit("downloading", `npm failed, cloning from ${info.gitUrl}...`);
149
+ try {
150
+ await gitCloneInstall(info, targetDir, onProgress);
151
+ installedVersion = info.npm.v2Version || info.npm.v1Version || "git";
152
+ installSource = "path";
153
+ installed = true;
154
+ } catch (gitErr) {
155
+ const msg = gitErr instanceof Error ? gitErr.message : String(gitErr);
156
+ emit("error", `Installation failed: ${msg}`);
157
+ return {
158
+ success: false,
159
+ pluginName: canonicalName,
160
+ version: "",
161
+ installPath: targetDir,
162
+ requiresRestart: false,
163
+ error: msg
164
+ };
165
+ }
166
+ }
167
+ }
168
+ if (!installed) {
169
+ emit("error", "Installation failed");
170
+ return {
171
+ success: false,
172
+ pluginName: canonicalName,
173
+ version: "",
174
+ installPath: targetDir,
175
+ requiresRestart: false,
176
+ error: `Failed to install plugin "${canonicalName}"`
177
+ };
178
+ }
179
+ emit("validating", "Verifying plugin can be loaded...");
180
+ if (!await resolveEntryPoint(targetDir, canonicalName)) {
181
+ emit("error", "Plugin installed but entry point not found");
182
+ return {
183
+ success: false,
184
+ pluginName: canonicalName,
185
+ version: installedVersion,
186
+ installPath: targetDir,
187
+ requiresRestart: false,
188
+ error: "Plugin installed on disk but entry point could not be resolved"
189
+ };
190
+ }
191
+ emit("configuring", "Recording installation in config...");
192
+ recordInstallation(canonicalName, {
193
+ source: installSource,
194
+ spec: `${canonicalName}@${installedVersion}`,
195
+ installPath: targetDir,
196
+ version: installedVersion,
197
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
198
+ });
199
+ emit("complete", `${canonicalName}@${installedVersion} installed successfully`);
200
+ return {
201
+ success: true,
202
+ pluginName: canonicalName,
203
+ version: installedVersion,
204
+ installPath: targetDir,
205
+ requiresRestart: true
206
+ };
207
+ }
208
+ async function runPackageInstall(pm, packageName, version, targetDir) {
209
+ assertValidPackageName(packageName);
210
+ assertValidVersion(version);
211
+ await installSpecWithFallback(pm, `${packageName}@${version}`, targetDir);
212
+ }
213
+ async function runLocalPathInstall(pm, packageName, sourcePath, targetDir) {
214
+ assertValidPackageName(packageName);
215
+ const resolvedSourcePath = path.resolve(sourcePath);
216
+ const packageJsonPath = path.join(resolvedSourcePath, "package.json");
217
+ await fs.access(packageJsonPath);
218
+ await installSpecWithFallback(pm, `file:${resolvedSourcePath}`, targetDir);
219
+ }
220
+ async function installSpecWithFallback(pm, spec, targetDir) {
221
+ try {
222
+ await runInstallSpec(pm, spec, targetDir);
223
+ } catch (primaryErr) {
224
+ if (pm === "npm") throw primaryErr;
225
+ logger.warn(`[plugin-installer] ${pm} install failed for ${spec}; retrying with npm: ${primaryErr instanceof Error ? primaryErr.message : String(primaryErr)}`);
226
+ await runInstallSpec("npm", spec, targetDir);
227
+ }
228
+ }
229
+ async function runInstallSpec(pm, spec, targetDir) {
230
+ switch (pm) {
231
+ case "bun":
232
+ await execFileAsync("bun", ["add", spec], { cwd: targetDir });
233
+ break;
234
+ default: await execFileAsync("npm", [
235
+ "install",
236
+ spec,
237
+ "--prefix",
238
+ targetDir
239
+ ]);
240
+ }
241
+ }
242
+ async function readInstalledVersion(targetDir, packageName, fallbackVersion) {
243
+ const installedPkgPath = path.join(targetDir, "node_modules", ...packageName.split("/"), "package.json");
244
+ try {
245
+ const pkg = JSON.parse(await fs.readFile(installedPkgPath, "utf-8"));
246
+ if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
247
+ } catch {}
248
+ return fallbackVersion;
249
+ }
250
+ async function remoteBranchExists(gitUrl, branch) {
251
+ try {
252
+ const { stdout } = await execFileAsync("git", [
253
+ "ls-remote",
254
+ "--heads",
255
+ gitUrl,
256
+ branch
257
+ ], { env: {
258
+ ...process.env,
259
+ GIT_TERMINAL_PROMPT: "0"
260
+ } });
261
+ return stdout.trim().length > 0;
262
+ } catch {
263
+ return false;
264
+ }
265
+ }
266
+ async function listRemoteBranches(gitUrl) {
267
+ try {
268
+ const { stdout } = await execFileAsync("git", [
269
+ "ls-remote",
270
+ "--heads",
271
+ gitUrl
272
+ ], { env: {
273
+ ...process.env,
274
+ GIT_TERMINAL_PROMPT: "0"
275
+ } });
276
+ const branches = [];
277
+ for (const rawLine of stdout.split("\n")) {
278
+ const line = rawLine.trim();
279
+ if (!line) continue;
280
+ const parts = line.split(/\s+/);
281
+ if (parts.length < 2) continue;
282
+ const ref = parts[1];
283
+ if (!ref.startsWith("refs/heads/")) continue;
284
+ const branch = ref.replace(/^refs\/heads\//, "");
285
+ if (VALID_BRANCH.test(branch)) branches.push(branch);
286
+ }
287
+ return branches;
288
+ } catch {
289
+ return [];
290
+ }
291
+ }
292
+ async function resolveGitBranch(info) {
293
+ assertValidGitUrl(info.gitUrl);
294
+ const rawCandidates = [
295
+ info.git.v2Branch,
296
+ info.git.v1Branch,
297
+ "next",
298
+ "main",
299
+ "master"
300
+ ];
301
+ const candidates = [...new Set(rawCandidates.filter((c) => Boolean(c?.trim())))];
302
+ for (const branch of candidates) {
303
+ if (!VALID_BRANCH.test(branch)) continue;
304
+ if (await remoteBranchExists(info.gitUrl, branch)) return branch;
305
+ }
306
+ const remoteBranches = await listRemoteBranches(info.gitUrl);
307
+ if (remoteBranches.length > 0) {
308
+ for (const branch of [
309
+ "main",
310
+ "next",
311
+ "master",
312
+ "1.x",
313
+ "develop",
314
+ "dev"
315
+ ]) if (remoteBranches.includes(branch)) return branch;
316
+ return remoteBranches[0];
317
+ }
318
+ return "main";
319
+ }
320
+ async function gitCloneInstall(info, targetDir, onProgress) {
321
+ const branch = await resolveGitBranch(info);
322
+ const tempDir = path.join(path.dirname(targetDir), `temp-${Date.now()}`);
323
+ await fs.mkdir(tempDir, { recursive: true });
324
+ try {
325
+ await execFileAsync("git", [
326
+ "clone",
327
+ "--branch",
328
+ branch,
329
+ "--single-branch",
330
+ "--depth",
331
+ "1",
332
+ info.gitUrl,
333
+ tempDir
334
+ ], { env: {
335
+ ...process.env,
336
+ GIT_TERMINAL_PROMPT: "0"
337
+ } });
338
+ onProgress?.({
339
+ phase: "installing-deps",
340
+ pluginName: info.name,
341
+ message: "Installing dependencies..."
342
+ });
343
+ const pm = await detectPackageManager();
344
+ await execFileAsync(pm, ["install"], { cwd: tempDir });
345
+ const tsDir = path.join(tempDir, "typescript");
346
+ try {
347
+ await fs.access(tsDir);
348
+ } catch (err) {
349
+ if (err.code === "ENOENT") {
350
+ await fs.cp(tempDir, targetDir, { recursive: true });
351
+ return;
352
+ }
353
+ throw err;
354
+ }
355
+ await execFileAsync(pm, ["run", "build"], { cwd: tsDir }).catch((buildErr) => {
356
+ logger.warn(`[plugin-installer] build step failed for ${info.name}: ${buildErr.message}`);
357
+ });
358
+ await fs.cp(tsDir, targetDir, { recursive: true });
359
+ } finally {
360
+ await fs.rm(tempDir, {
361
+ recursive: true,
362
+ force: true
363
+ });
364
+ }
365
+ }
366
+ /**
367
+ * Resolve the importable entry point for an installed plugin.
368
+ *
369
+ * For npm-installed plugins the entry is:
370
+ * <targetDir>/node_modules/<packageName>/
371
+ *
372
+ * For git-cloned plugins the entry is the targetDir itself.
373
+ */
374
+ async function resolveEntryPoint(targetDir, packageName) {
375
+ const nmPath = path.join(targetDir, "node_modules", ...packageName.split("/"));
376
+ try {
377
+ await fs.access(nmPath);
378
+ return nmPath;
379
+ } catch {}
380
+ const pkgPath = path.join(targetDir, "package.json");
381
+ try {
382
+ await fs.access(pkgPath);
383
+ return targetDir;
384
+ } catch {}
385
+ return null;
386
+ }
387
+ function recordInstallation(pluginName, record) {
388
+ const config = loadMiladyConfig();
389
+ if (!config.plugins) config.plugins = {};
390
+ if (!config.plugins.installs) config.plugins.installs = {};
391
+ config.plugins.installs[pluginName] = record;
392
+ saveMiladyConfig(config);
393
+ }
394
+
395
+ //#endregion
396
+ export { installPlugin };
@@ -0,0 +1,15 @@
1
+ //#region src/services/plugin-manager-types.ts
2
+ function isObjectRecord(value) {
3
+ return typeof value === "object" && value !== null;
4
+ }
5
+ function isPluginManagerLike(candidate) {
6
+ if (!isObjectRecord(candidate)) return false;
7
+ return typeof candidate.refreshRegistry === "function" && typeof candidate.listInstalledPlugins === "function" && typeof candidate.getRegistryPlugin === "function" && typeof candidate.searchRegistry === "function" && typeof candidate.installPlugin === "function" && typeof candidate.uninstallPlugin === "function";
8
+ }
9
+ function isCoreManagerLike(candidate) {
10
+ if (!isObjectRecord(candidate)) return false;
11
+ return typeof candidate.getCoreStatus === "function";
12
+ }
13
+
14
+ //#endregion
15
+ export { isCoreManagerLike, isPluginManagerLike };
@@ -0,0 +1,144 @@
1
+ import { logger } from "@elizaos/core";
2
+
3
+ //#region src/services/registry-client-app-meta.ts
4
+ const LOCAL_APP_DEFAULT_SANDBOX = "allow-scripts allow-same-origin allow-popups";
5
+ const ALLOWED_SANDBOX_TOKENS = new Set([
6
+ "allow-downloads",
7
+ "allow-forms",
8
+ "allow-modals",
9
+ "allow-orientation-lock",
10
+ "allow-pointer-lock",
11
+ "allow-popups",
12
+ "allow-popups-to-escape-sandbox",
13
+ "allow-presentation",
14
+ "allow-same-origin",
15
+ "allow-scripts",
16
+ "allow-storage-access-by-user-activation",
17
+ "allow-top-navigation-by-user-activation"
18
+ ]);
19
+ const LOCAL_APP_OVERRIDES = {
20
+ "@elizaos/app-babylon": {
21
+ launchType: "url",
22
+ launchUrl: "http://localhost:3000",
23
+ viewer: {
24
+ url: "http://localhost:3000",
25
+ sandbox: "allow-scripts allow-same-origin allow-popups allow-forms"
26
+ }
27
+ },
28
+ "@elizaos/app-hyperscape": {
29
+ launchType: "connect",
30
+ launchUrl: "http://localhost:3333",
31
+ viewer: {
32
+ url: "http://localhost:3333",
33
+ embedParams: {
34
+ embedded: "true",
35
+ mode: "spectator",
36
+ quality: "medium"
37
+ },
38
+ postMessageAuth: true,
39
+ sandbox: "allow-scripts allow-same-origin allow-popups allow-forms"
40
+ }
41
+ },
42
+ "@elizaos/app-hyperfy": {
43
+ launchType: "connect",
44
+ launchUrl: "http://localhost:3003",
45
+ viewer: {
46
+ url: "http://localhost:3003",
47
+ sandbox: LOCAL_APP_DEFAULT_SANDBOX
48
+ }
49
+ },
50
+ "@elizaos/app-2004scape": {
51
+ launchType: "connect",
52
+ launchUrl: "http://localhost:8880",
53
+ viewer: {
54
+ url: "http://localhost:8880",
55
+ embedParams: { bot: "{RS_SDK_BOT_NAME}" },
56
+ postMessageAuth: true,
57
+ sandbox: "allow-scripts allow-same-origin allow-popups allow-forms"
58
+ }
59
+ },
60
+ "@elizaos/app-agent-town": {
61
+ launchType: "url",
62
+ launchUrl: "http://localhost:5173/ai-town/index.html",
63
+ viewer: {
64
+ url: "http://localhost:5173/ai-town/index.html",
65
+ sandbox: "allow-scripts allow-same-origin allow-popups allow-forms"
66
+ }
67
+ },
68
+ "@elizaos/app-dungeons": {
69
+ launchType: "local",
70
+ launchUrl: "http://localhost:3345",
71
+ viewer: {
72
+ url: "http://localhost:3345",
73
+ sandbox: "allow-scripts allow-same-origin allow-popups allow-forms"
74
+ }
75
+ }
76
+ };
77
+ function sanitizeSandbox(rawSandbox) {
78
+ if (!rawSandbox || !rawSandbox.trim()) return LOCAL_APP_DEFAULT_SANDBOX;
79
+ const tokens = rawSandbox.split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0);
80
+ if (tokens.length === 0) return LOCAL_APP_DEFAULT_SANDBOX;
81
+ for (const token of tokens) if (!ALLOWED_SANDBOX_TOKENS.has(token)) {
82
+ logger.warn(`[registry-client] rejecting untrusted sandbox token: ${token}`);
83
+ return LOCAL_APP_DEFAULT_SANDBOX;
84
+ }
85
+ return Array.from(new Set(tokens)).join(" ");
86
+ }
87
+ function normalizeViewer(viewer) {
88
+ if (!viewer) return void 0;
89
+ return {
90
+ ...viewer,
91
+ sandbox: sanitizeSandbox(viewer.sandbox)
92
+ };
93
+ }
94
+ function mergeViewer(base, patch) {
95
+ if (!base && !patch) return void 0;
96
+ if (!base) return normalizeViewer(patch);
97
+ if (!patch) return normalizeViewer(base);
98
+ return normalizeViewer({
99
+ ...base,
100
+ ...patch,
101
+ embedParams: {
102
+ ...base.embedParams ?? {},
103
+ ...patch.embedParams ?? {}
104
+ }
105
+ });
106
+ }
107
+ function mergeAppMeta(base, patch) {
108
+ if (!base && !patch) return void 0;
109
+ if (!base) return patch;
110
+ if (!patch) return base;
111
+ return {
112
+ ...base,
113
+ ...patch,
114
+ capabilities: patch.capabilities.length > 0 ? patch.capabilities : base.capabilities,
115
+ viewer: mergeViewer(base.viewer, patch.viewer)
116
+ };
117
+ }
118
+ function resolveAppOverride(packageName, appMeta) {
119
+ const override = LOCAL_APP_OVERRIDES[packageName];
120
+ if (!override) return appMeta;
121
+ const base = appMeta ?? {
122
+ displayName: override.displayName ?? packageName.replace(/^@elizaos\/app-/, ""),
123
+ category: override.category ?? "game",
124
+ launchType: override.launchType ?? "url",
125
+ launchUrl: override.launchUrl ?? null,
126
+ icon: null,
127
+ capabilities: override.capabilities ?? [],
128
+ minPlayers: null,
129
+ maxPlayers: null,
130
+ viewer: override.viewer
131
+ };
132
+ return {
133
+ ...base,
134
+ displayName: override.displayName ?? base.displayName,
135
+ category: override.category ?? base.category,
136
+ launchType: override.launchType ?? base.launchType,
137
+ launchUrl: override.launchUrl !== void 0 ? override.launchUrl : base.launchUrl,
138
+ capabilities: override.capabilities !== void 0 ? override.capabilities : base.capabilities,
139
+ viewer: mergeViewer(base.viewer, override.viewer)
140
+ };
141
+ }
142
+
143
+ //#endregion
144
+ export { mergeAppMeta, resolveAppOverride, sanitizeSandbox };