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,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 };
|