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,157 @@
|
|
|
1
|
+
import { loadMiladyConfig, saveMiladyConfig } from "../config/config.js";
|
|
2
|
+
import { sanitizeSandbox } from "./registry-client-app-meta.js";
|
|
3
|
+
import { isDefaultEndpoint as isDefaultEndpoint$1, mergeCustomEndpoints, normaliseEndpointUrl, parseRegistryEndpointUrl } from "./registry-client-endpoints.js";
|
|
4
|
+
import { applyLocalWorkspaceApps, applyNodeModulePlugins } from "./registry-client-local.js";
|
|
5
|
+
import { fetchFromNetwork as fetchFromNetwork$1 } from "./registry-client-network.js";
|
|
6
|
+
import { getPluginInfoFromRegistry, normalizePluginLookupAlias, scoreEntries, toPluginListItem } from "./registry-client-queries.js";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { logger } from "@elizaos/core";
|
|
10
|
+
import fs from "node:fs/promises";
|
|
11
|
+
|
|
12
|
+
//#region src/services/registry-client.ts
|
|
13
|
+
/**
|
|
14
|
+
* Registry Client for Milady.
|
|
15
|
+
*
|
|
16
|
+
* Provides a 3-tier cached registry (memory → file → network) that works
|
|
17
|
+
* offline, in .app bundles, and in dev. Fetches from the next branch.
|
|
18
|
+
*
|
|
19
|
+
* @module services/registry-client
|
|
20
|
+
*/
|
|
21
|
+
const GENERATED_REGISTRY_URL = "https://raw.githubusercontent.com/elizaos-plugins/registry/next/generated-registry.json";
|
|
22
|
+
const INDEX_REGISTRY_URL = "https://raw.githubusercontent.com/elizaos-plugins/registry/next/index.json";
|
|
23
|
+
const CACHE_TTL_MS = 36e5;
|
|
24
|
+
let memoryCache = null;
|
|
25
|
+
async function fetchFromNetwork() {
|
|
26
|
+
try {
|
|
27
|
+
return await fetchFromNetwork$1({
|
|
28
|
+
generatedRegistryUrl: GENERATED_REGISTRY_URL,
|
|
29
|
+
indexRegistryUrl: INDEX_REGISTRY_URL,
|
|
30
|
+
applyLocalWorkspaceApps,
|
|
31
|
+
applyNodeModulePlugins,
|
|
32
|
+
sanitizeSandbox
|
|
33
|
+
});
|
|
34
|
+
} catch (err) {
|
|
35
|
+
logger.warn(`[registry-client] generated-registry/index fallback failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function cacheFilePath() {
|
|
40
|
+
const base = process.env.MILADY_STATE_DIR?.trim() || path.join(os.homedir(), ".milady");
|
|
41
|
+
return path.join(base, "cache", "registry.json");
|
|
42
|
+
}
|
|
43
|
+
async function readFileCache() {
|
|
44
|
+
try {
|
|
45
|
+
const raw = await fs.readFile(cacheFilePath(), "utf-8");
|
|
46
|
+
const parsed = JSON.parse(raw);
|
|
47
|
+
if (typeof parsed.fetchedAt !== "number" || !Array.isArray(parsed.plugins)) return null;
|
|
48
|
+
if (Date.now() - parsed.fetchedAt > CACHE_TTL_MS) return null;
|
|
49
|
+
return new Map(parsed.plugins);
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function writeFileCache(plugins) {
|
|
55
|
+
const filePath = cacheFilePath();
|
|
56
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
57
|
+
await fs.writeFile(filePath, JSON.stringify({
|
|
58
|
+
fetchedAt: Date.now(),
|
|
59
|
+
plugins: [...plugins.entries()]
|
|
60
|
+
}), "utf-8");
|
|
61
|
+
}
|
|
62
|
+
/** Return the list of custom registry endpoints from config. */
|
|
63
|
+
function getConfiguredEndpoints() {
|
|
64
|
+
try {
|
|
65
|
+
return loadMiladyConfig().plugins?.registryEndpoints ?? [];
|
|
66
|
+
} catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Add a custom registry endpoint. Blocks duplicate URLs. */
|
|
71
|
+
function addRegistryEndpoint(label, url) {
|
|
72
|
+
const normalised = normaliseEndpointUrl(parseRegistryEndpointUrl(url).toString());
|
|
73
|
+
if (isDefaultEndpoint(normalised)) throw new Error("Cannot add the default registry as a custom endpoint.");
|
|
74
|
+
const cfg = loadMiladyConfig();
|
|
75
|
+
const endpoints = cfg.plugins?.registryEndpoints ?? [];
|
|
76
|
+
if (endpoints.some((ep) => normaliseEndpointUrl(ep.url) === normalised)) throw new Error(`Endpoint already exists: ${url}`);
|
|
77
|
+
if (!cfg.plugins) cfg.plugins = {};
|
|
78
|
+
cfg.plugins.registryEndpoints = [...endpoints, {
|
|
79
|
+
label,
|
|
80
|
+
url: normalised,
|
|
81
|
+
enabled: true
|
|
82
|
+
}];
|
|
83
|
+
saveMiladyConfig(cfg);
|
|
84
|
+
memoryCache = null;
|
|
85
|
+
}
|
|
86
|
+
/** Remove a custom registry endpoint by URL. Cannot remove the default. */
|
|
87
|
+
function removeRegistryEndpoint(url) {
|
|
88
|
+
const normalised = normaliseEndpointUrl(url);
|
|
89
|
+
if (isDefaultEndpoint(normalised)) throw new Error("Cannot remove the default ElizaOS registry.");
|
|
90
|
+
const cfg = loadMiladyConfig();
|
|
91
|
+
const endpoints = cfg.plugins?.registryEndpoints ?? [];
|
|
92
|
+
const updated = endpoints.filter((ep) => normaliseEndpointUrl(ep.url) !== normalised);
|
|
93
|
+
if (updated.length === endpoints.length) throw new Error(`Endpoint not found: ${url}`);
|
|
94
|
+
if (!cfg.plugins) cfg.plugins = {};
|
|
95
|
+
cfg.plugins.registryEndpoints = updated;
|
|
96
|
+
saveMiladyConfig(cfg);
|
|
97
|
+
memoryCache = null;
|
|
98
|
+
}
|
|
99
|
+
/** Toggle an endpoint's enabled status. */
|
|
100
|
+
function toggleRegistryEndpoint(url, enabled) {
|
|
101
|
+
const normalised = normaliseEndpointUrl(url);
|
|
102
|
+
const cfg = loadMiladyConfig();
|
|
103
|
+
const endpoints = cfg.plugins?.registryEndpoints ?? [];
|
|
104
|
+
const ep = endpoints.find((e) => normaliseEndpointUrl(e.url) === normalised);
|
|
105
|
+
if (!ep) throw new Error(`Endpoint not found: ${url}`);
|
|
106
|
+
ep.enabled = enabled;
|
|
107
|
+
if (!cfg.plugins) cfg.plugins = {};
|
|
108
|
+
cfg.plugins.registryEndpoints = endpoints;
|
|
109
|
+
saveMiladyConfig(cfg);
|
|
110
|
+
memoryCache = null;
|
|
111
|
+
}
|
|
112
|
+
function isDefaultEndpoint(url) {
|
|
113
|
+
return isDefaultEndpoint$1(url, GENERATED_REGISTRY_URL);
|
|
114
|
+
}
|
|
115
|
+
/** Get all plugins. Resolution: memory → file → network. */
|
|
116
|
+
async function getRegistryPlugins() {
|
|
117
|
+
if (memoryCache && Date.now() - memoryCache.fetchedAt < CACHE_TTL_MS) return memoryCache.plugins;
|
|
118
|
+
const fromFile = await readFileCache();
|
|
119
|
+
if (fromFile) {
|
|
120
|
+
await applyLocalWorkspaceApps(fromFile);
|
|
121
|
+
await applyNodeModulePlugins(fromFile);
|
|
122
|
+
await mergeCustomEndpoints(fromFile, getConfiguredEndpoints());
|
|
123
|
+
memoryCache = {
|
|
124
|
+
plugins: fromFile,
|
|
125
|
+
fetchedAt: Date.now()
|
|
126
|
+
};
|
|
127
|
+
return fromFile;
|
|
128
|
+
}
|
|
129
|
+
logger.info("[registry-client] Fetching plugin registry from next branch...");
|
|
130
|
+
const plugins = await fetchFromNetwork();
|
|
131
|
+
await mergeCustomEndpoints(plugins, getConfiguredEndpoints());
|
|
132
|
+
logger.info(`[registry-client] Loaded ${plugins.size} plugins`);
|
|
133
|
+
memoryCache = {
|
|
134
|
+
plugins,
|
|
135
|
+
fetchedAt: Date.now()
|
|
136
|
+
};
|
|
137
|
+
writeFileCache(plugins).catch((err) => logger.warn(`[registry-client] Cache write failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
138
|
+
return plugins;
|
|
139
|
+
}
|
|
140
|
+
/** Look up a plugin by name (exact → @elizaos/ prefix → bare suffix). */
|
|
141
|
+
async function getPluginInfo(name) {
|
|
142
|
+
const registry = await getRegistryPlugins();
|
|
143
|
+
const normalizedName = normalizePluginLookupAlias(name);
|
|
144
|
+
const candidates = Array.from(new Set([normalizedName, name]));
|
|
145
|
+
for (const candidate of candidates) {
|
|
146
|
+
const info = getPluginInfoFromRegistry(registry, candidate);
|
|
147
|
+
if (info) return info;
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
/** Search non-app plugins by query. */
|
|
152
|
+
async function searchNonAppPlugins(query, limit = 15) {
|
|
153
|
+
return scoreEntries([...(await getRegistryPlugins()).values()].filter((p) => p.kind !== "app"), query, limit).map(({ p }) => toPluginListItem(p));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
//#endregion
|
|
157
|
+
export { addRegistryEndpoint, getConfiguredEndpoints, getPluginInfo, getRegistryPlugins, isDefaultEndpoint, removeRegistryEndpoint, searchNonAppPlugins, toggleRegistryEndpoint };
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import { arch, platform } from "node:os";
|
|
2
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
3
|
+
|
|
4
|
+
//#region src/services/sandbox-engine.ts
|
|
5
|
+
/** Cross-platform sandbox engine: Docker, Apple Container, auto-detect. */
|
|
6
|
+
function appendMountArgs(args, mounts) {
|
|
7
|
+
for (const mount of mounts) if (mount.readonly) args.push("--mount", `type=bind,source=${mount.host},target=${mount.container},readonly`);
|
|
8
|
+
else args.push("-v", `${mount.host}:${mount.container}`);
|
|
9
|
+
}
|
|
10
|
+
function appendEnvArgs(args, env) {
|
|
11
|
+
for (const [key, value] of Object.entries(env)) args.push("-e", `${key}=${value}`);
|
|
12
|
+
}
|
|
13
|
+
function listContainersFromBinary(binary, prefix) {
|
|
14
|
+
try {
|
|
15
|
+
return execFileSync(binary, [
|
|
16
|
+
"ps",
|
|
17
|
+
"-a",
|
|
18
|
+
"--filter",
|
|
19
|
+
`name=${prefix}`,
|
|
20
|
+
"--format",
|
|
21
|
+
"{{.ID}}"
|
|
22
|
+
], {
|
|
23
|
+
encoding: "utf-8",
|
|
24
|
+
timeout: 1e4,
|
|
25
|
+
stdio: [
|
|
26
|
+
"ignore",
|
|
27
|
+
"pipe",
|
|
28
|
+
"ignore"
|
|
29
|
+
]
|
|
30
|
+
}).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
31
|
+
} catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function checkHealthWithBinary(binary, id) {
|
|
36
|
+
try {
|
|
37
|
+
const result = execFileSync(binary, [
|
|
38
|
+
"exec",
|
|
39
|
+
id,
|
|
40
|
+
"echo",
|
|
41
|
+
"healthy"
|
|
42
|
+
], {
|
|
43
|
+
encoding: "utf-8",
|
|
44
|
+
timeout: 5e3,
|
|
45
|
+
stdio: [
|
|
46
|
+
"ignore",
|
|
47
|
+
"pipe",
|
|
48
|
+
"ignore"
|
|
49
|
+
]
|
|
50
|
+
}).trim();
|
|
51
|
+
return Promise.resolve(result === "healthy");
|
|
52
|
+
} catch {
|
|
53
|
+
return Promise.resolve(false);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function getChildProcessErrorText(error) {
|
|
57
|
+
const execError = error;
|
|
58
|
+
return [
|
|
59
|
+
execError.message,
|
|
60
|
+
execError.stderr,
|
|
61
|
+
execError.stdout
|
|
62
|
+
].map((value) => {
|
|
63
|
+
if (value === void 0 || value === null) return "";
|
|
64
|
+
if (typeof value === "string") return value;
|
|
65
|
+
if (typeof value === "object" && "toString" in value) return value.toString();
|
|
66
|
+
return "";
|
|
67
|
+
}).filter(Boolean).join(" ").toLowerCase();
|
|
68
|
+
}
|
|
69
|
+
function isContainerVersionUnsupported(error) {
|
|
70
|
+
const errorText = getChildProcessErrorText(error);
|
|
71
|
+
return errorText.includes("unknown option") || errorText.includes("unrecognized option") || errorText.includes("invalid option") || errorText.includes("unknown flag") || errorText.includes("no such option");
|
|
72
|
+
}
|
|
73
|
+
async function runExecInContainer(opts) {
|
|
74
|
+
const { binary, args, timeoutMs, stdin } = opts;
|
|
75
|
+
const start = Date.now();
|
|
76
|
+
return new Promise((resolve) => {
|
|
77
|
+
const proc = spawn(binary, args, { stdio: [
|
|
78
|
+
"pipe",
|
|
79
|
+
"pipe",
|
|
80
|
+
"pipe"
|
|
81
|
+
] });
|
|
82
|
+
let stdout = "";
|
|
83
|
+
let stderr = "";
|
|
84
|
+
const timeout = setTimeout(() => proc.kill("SIGKILL"), timeoutMs ?? 3e4);
|
|
85
|
+
proc.stdout.on("data", (data) => {
|
|
86
|
+
stdout += data.toString();
|
|
87
|
+
});
|
|
88
|
+
proc.stderr.on("data", (data) => {
|
|
89
|
+
stderr += data.toString();
|
|
90
|
+
});
|
|
91
|
+
if (stdin) {
|
|
92
|
+
proc.stdin.write(stdin);
|
|
93
|
+
proc.stdin.end();
|
|
94
|
+
}
|
|
95
|
+
proc.on("close", (code) => {
|
|
96
|
+
clearTimeout(timeout);
|
|
97
|
+
resolve({
|
|
98
|
+
exitCode: code ?? 1,
|
|
99
|
+
stdout,
|
|
100
|
+
stderr,
|
|
101
|
+
durationMs: Date.now() - start
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
proc.on("error", (err) => {
|
|
105
|
+
clearTimeout(timeout);
|
|
106
|
+
resolve({
|
|
107
|
+
exitCode: 1,
|
|
108
|
+
stdout,
|
|
109
|
+
stderr: `Exec error: ${err.message}`,
|
|
110
|
+
durationMs: Date.now() - start
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function parseContainerCommand(command) {
|
|
116
|
+
const args = [];
|
|
117
|
+
let current = "";
|
|
118
|
+
let inSingleQuote = false;
|
|
119
|
+
let inDoubleQuote = false;
|
|
120
|
+
let escaping = false;
|
|
121
|
+
let tokenStarted = false;
|
|
122
|
+
const emitCurrent = () => {
|
|
123
|
+
if (tokenStarted) {
|
|
124
|
+
args.push(current);
|
|
125
|
+
current = "";
|
|
126
|
+
tokenStarted = false;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const trimmed = command.trim();
|
|
130
|
+
if (trimmed.length === 0) throw new Error("Container exec command is required");
|
|
131
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
132
|
+
const char = trimmed[i];
|
|
133
|
+
if (escaping) {
|
|
134
|
+
current += char;
|
|
135
|
+
escaping = false;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (inSingleQuote) {
|
|
139
|
+
if (char === "'") inSingleQuote = false;
|
|
140
|
+
else {
|
|
141
|
+
current += char;
|
|
142
|
+
tokenStarted = true;
|
|
143
|
+
}
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (inDoubleQuote) {
|
|
147
|
+
if (char === "\"") inDoubleQuote = false;
|
|
148
|
+
else if (char === "\\") {
|
|
149
|
+
const next = trimmed[i + 1];
|
|
150
|
+
if (next === "\\" || next === "\"" || next === "$" || next === "`") {
|
|
151
|
+
i += 1;
|
|
152
|
+
current += trimmed[i];
|
|
153
|
+
} else current += char;
|
|
154
|
+
} else current += char;
|
|
155
|
+
tokenStarted = true;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (char === "'") {
|
|
159
|
+
inSingleQuote = true;
|
|
160
|
+
tokenStarted = true;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (char === "\"") {
|
|
164
|
+
inDoubleQuote = true;
|
|
165
|
+
tokenStarted = true;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (char === "\\") {
|
|
169
|
+
if (i + 1 >= trimmed.length) throw new Error("Container exec command cannot end with dangling escape");
|
|
170
|
+
escaping = true;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (/\s/.test(char)) {
|
|
174
|
+
emitCurrent();
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (char === "&" || char === "|" || char === ";" || char === "<" || char === ">" || char === "$" || char === "`" || char === "(" || char === ")" || char === "{" || char === "}" || char === "\n" || char === "\r") throw new Error("Container exec command contains unsupported shell syntax");
|
|
178
|
+
current += char;
|
|
179
|
+
tokenStarted = true;
|
|
180
|
+
}
|
|
181
|
+
if (inSingleQuote || inDoubleQuote) throw new Error("Container exec command has unterminated quotes");
|
|
182
|
+
if (escaping) throw new Error("Container exec command has trailing escape");
|
|
183
|
+
emitCurrent();
|
|
184
|
+
if (args.length === 0) throw new Error("Container exec command is required");
|
|
185
|
+
return args;
|
|
186
|
+
}
|
|
187
|
+
var DockerEngine = class {
|
|
188
|
+
constructor() {
|
|
189
|
+
this.engineType = "docker";
|
|
190
|
+
}
|
|
191
|
+
isAvailable() {
|
|
192
|
+
try {
|
|
193
|
+
execFileSync("docker", ["info"], {
|
|
194
|
+
stdio: "ignore",
|
|
195
|
+
timeout: 1e4
|
|
196
|
+
});
|
|
197
|
+
return true;
|
|
198
|
+
} catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
getInfo() {
|
|
203
|
+
let version = "unknown";
|
|
204
|
+
try {
|
|
205
|
+
version = execFileSync("docker", ["--version"], {
|
|
206
|
+
encoding: "utf-8",
|
|
207
|
+
timeout: 5e3
|
|
208
|
+
}).trim();
|
|
209
|
+
} catch {}
|
|
210
|
+
return {
|
|
211
|
+
type: "docker",
|
|
212
|
+
available: this.isAvailable(),
|
|
213
|
+
version,
|
|
214
|
+
platform: platform(),
|
|
215
|
+
arch: arch(),
|
|
216
|
+
details: this.getDockerContext()
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
async runContainer(opts) {
|
|
220
|
+
const args = ["run"];
|
|
221
|
+
if (opts.detach) args.push("-d");
|
|
222
|
+
args.push("--name", opts.name);
|
|
223
|
+
if (opts.network) args.push("--network", opts.network);
|
|
224
|
+
if (opts.user) args.push("--user", opts.user);
|
|
225
|
+
if (opts.memory) args.push("--memory", opts.memory);
|
|
226
|
+
if (opts.cpus) args.push("--cpus", String(opts.cpus));
|
|
227
|
+
if (opts.pidsLimit) args.push("--pids-limit", String(opts.pidsLimit));
|
|
228
|
+
if (opts.readOnlyRoot) args.push("--read-only");
|
|
229
|
+
for (const cap of opts.capDrop) args.push("--cap-drop", cap);
|
|
230
|
+
appendMountArgs(args, opts.mounts);
|
|
231
|
+
appendEnvArgs(args, opts.env);
|
|
232
|
+
if (opts.ports) for (const p of opts.ports) args.push("-p", `${p.host}:${p.container}`);
|
|
233
|
+
if (opts.dns) for (const d of opts.dns) args.push("--dns", d);
|
|
234
|
+
args.push(opts.image);
|
|
235
|
+
return execFileSync("docker", args, {
|
|
236
|
+
encoding: "utf-8",
|
|
237
|
+
timeout: 6e4
|
|
238
|
+
}).trim().substring(0, 12);
|
|
239
|
+
}
|
|
240
|
+
async execInContainer(opts) {
|
|
241
|
+
const args = ["exec"];
|
|
242
|
+
if (opts.workdir) args.push("-w", opts.workdir);
|
|
243
|
+
if (opts.env) appendEnvArgs(args, opts.env);
|
|
244
|
+
const commandArgs = parseContainerCommand(opts.command);
|
|
245
|
+
args.push(opts.containerId, ...commandArgs);
|
|
246
|
+
return runExecInContainer({
|
|
247
|
+
binary: "docker",
|
|
248
|
+
args,
|
|
249
|
+
timeoutMs: opts.timeoutMs,
|
|
250
|
+
stdin: opts.stdin
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
async stopContainer(id) {
|
|
254
|
+
try {
|
|
255
|
+
execFileSync("docker", ["stop", id], {
|
|
256
|
+
timeout: 15e3,
|
|
257
|
+
stdio: "ignore"
|
|
258
|
+
});
|
|
259
|
+
} catch {}
|
|
260
|
+
}
|
|
261
|
+
async removeContainer(id) {
|
|
262
|
+
try {
|
|
263
|
+
execFileSync("docker", [
|
|
264
|
+
"rm",
|
|
265
|
+
"-f",
|
|
266
|
+
id
|
|
267
|
+
], {
|
|
268
|
+
timeout: 1e4,
|
|
269
|
+
stdio: "ignore"
|
|
270
|
+
});
|
|
271
|
+
} catch {}
|
|
272
|
+
}
|
|
273
|
+
isContainerRunning(id) {
|
|
274
|
+
try {
|
|
275
|
+
return execFileSync("docker", [
|
|
276
|
+
"inspect",
|
|
277
|
+
"-f",
|
|
278
|
+
"{{.State.Running}}",
|
|
279
|
+
id
|
|
280
|
+
], {
|
|
281
|
+
encoding: "utf-8",
|
|
282
|
+
timeout: 5e3,
|
|
283
|
+
stdio: [
|
|
284
|
+
"ignore",
|
|
285
|
+
"pipe",
|
|
286
|
+
"ignore"
|
|
287
|
+
]
|
|
288
|
+
}).trim() === "true";
|
|
289
|
+
} catch {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
imageExists(image) {
|
|
294
|
+
try {
|
|
295
|
+
execFileSync("docker", [
|
|
296
|
+
"image",
|
|
297
|
+
"inspect",
|
|
298
|
+
image
|
|
299
|
+
], {
|
|
300
|
+
stdio: "ignore",
|
|
301
|
+
timeout: 1e4
|
|
302
|
+
});
|
|
303
|
+
return true;
|
|
304
|
+
} catch {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async pullImage(image) {
|
|
309
|
+
execFileSync("docker", ["pull", image], {
|
|
310
|
+
stdio: [
|
|
311
|
+
"ignore",
|
|
312
|
+
"pipe",
|
|
313
|
+
"pipe"
|
|
314
|
+
],
|
|
315
|
+
timeout: 3e5
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
listContainers(prefix) {
|
|
319
|
+
return listContainersFromBinary("docker", prefix);
|
|
320
|
+
}
|
|
321
|
+
async healthCheck(id) {
|
|
322
|
+
return checkHealthWithBinary("docker", id);
|
|
323
|
+
}
|
|
324
|
+
getDockerContext() {
|
|
325
|
+
try {
|
|
326
|
+
return execFileSync("docker", ["context", "show"], {
|
|
327
|
+
encoding: "utf-8",
|
|
328
|
+
timeout: 5e3,
|
|
329
|
+
stdio: [
|
|
330
|
+
"ignore",
|
|
331
|
+
"pipe",
|
|
332
|
+
"ignore"
|
|
333
|
+
]
|
|
334
|
+
}).trim();
|
|
335
|
+
} catch {
|
|
336
|
+
return "default";
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
var AppleContainerEngine = class {
|
|
341
|
+
constructor() {
|
|
342
|
+
this.engineType = "apple-container";
|
|
343
|
+
}
|
|
344
|
+
isAvailable() {
|
|
345
|
+
try {
|
|
346
|
+
execFileSync("container", ["--version"], {
|
|
347
|
+
stdio: "ignore",
|
|
348
|
+
timeout: 5e3
|
|
349
|
+
});
|
|
350
|
+
return true;
|
|
351
|
+
} catch (error) {
|
|
352
|
+
if (!isContainerVersionUnsupported(error)) return false;
|
|
353
|
+
try {
|
|
354
|
+
execFileSync("container", ["help"], {
|
|
355
|
+
stdio: "ignore",
|
|
356
|
+
timeout: 5e3
|
|
357
|
+
});
|
|
358
|
+
return true;
|
|
359
|
+
} catch {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
getInfo() {
|
|
365
|
+
let version = "unknown";
|
|
366
|
+
try {
|
|
367
|
+
version = execFileSync("container", ["--version"], {
|
|
368
|
+
encoding: "utf-8",
|
|
369
|
+
timeout: 5e3
|
|
370
|
+
}).trim();
|
|
371
|
+
} catch {}
|
|
372
|
+
return {
|
|
373
|
+
type: "apple-container",
|
|
374
|
+
available: this.isAvailable(),
|
|
375
|
+
version,
|
|
376
|
+
platform: "darwin",
|
|
377
|
+
arch: arch(),
|
|
378
|
+
details: `Apple Silicon: ${arch() === "arm64" ? "yes" : "no"}`
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
async runContainer(opts) {
|
|
382
|
+
const args = [
|
|
383
|
+
"run",
|
|
384
|
+
"--name",
|
|
385
|
+
opts.name
|
|
386
|
+
];
|
|
387
|
+
appendMountArgs(args, opts.mounts);
|
|
388
|
+
appendEnvArgs(args, opts.env);
|
|
389
|
+
args.push(opts.image);
|
|
390
|
+
return new Promise((resolve, reject) => {
|
|
391
|
+
const proc = spawn("container", args, {
|
|
392
|
+
stdio: [
|
|
393
|
+
"pipe",
|
|
394
|
+
"pipe",
|
|
395
|
+
"pipe"
|
|
396
|
+
],
|
|
397
|
+
detached: true
|
|
398
|
+
});
|
|
399
|
+
let stderr = "";
|
|
400
|
+
proc.stderr.on("data", (chunk) => {
|
|
401
|
+
stderr += chunk.toString();
|
|
402
|
+
});
|
|
403
|
+
const checkTimer = setTimeout(() => {
|
|
404
|
+
if (proc.exitCode !== null) reject(/* @__PURE__ */ new Error(`Apple Container exited immediately: ${stderr}`));
|
|
405
|
+
else {
|
|
406
|
+
proc.unref();
|
|
407
|
+
resolve(opts.name);
|
|
408
|
+
}
|
|
409
|
+
}, 2e3);
|
|
410
|
+
proc.on("error", (err) => {
|
|
411
|
+
clearTimeout(checkTimer);
|
|
412
|
+
reject(/* @__PURE__ */ new Error(`Apple Container spawn failed: ${err.message}`));
|
|
413
|
+
});
|
|
414
|
+
proc.on("exit", (code) => {
|
|
415
|
+
if (code !== null && code !== 0) {
|
|
416
|
+
clearTimeout(checkTimer);
|
|
417
|
+
reject(/* @__PURE__ */ new Error(`Apple Container exited with code ${code}: ${stderr}`));
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
async execInContainer(opts) {
|
|
423
|
+
const args = ["exec"];
|
|
424
|
+
if (opts.workdir) args.push("-w", opts.workdir);
|
|
425
|
+
const commandArgs = parseContainerCommand(opts.command);
|
|
426
|
+
args.push(opts.containerId, ...commandArgs);
|
|
427
|
+
return runExecInContainer({
|
|
428
|
+
binary: "container",
|
|
429
|
+
args,
|
|
430
|
+
timeoutMs: opts.timeoutMs,
|
|
431
|
+
stdin: opts.stdin
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
async stopContainer(id) {
|
|
435
|
+
try {
|
|
436
|
+
execFileSync("container", ["stop", id], {
|
|
437
|
+
timeout: 15e3,
|
|
438
|
+
stdio: "ignore"
|
|
439
|
+
});
|
|
440
|
+
} catch {}
|
|
441
|
+
}
|
|
442
|
+
async removeContainer(id) {
|
|
443
|
+
try {
|
|
444
|
+
execFileSync("container", ["rm", id], {
|
|
445
|
+
timeout: 1e4,
|
|
446
|
+
stdio: "ignore"
|
|
447
|
+
});
|
|
448
|
+
} catch {}
|
|
449
|
+
}
|
|
450
|
+
isContainerRunning(id) {
|
|
451
|
+
try {
|
|
452
|
+
execFileSync("container", ["inspect", id], {
|
|
453
|
+
stdio: "ignore",
|
|
454
|
+
timeout: 5e3
|
|
455
|
+
});
|
|
456
|
+
return true;
|
|
457
|
+
} catch {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
imageExists(image) {
|
|
462
|
+
try {
|
|
463
|
+
execFileSync("container", [
|
|
464
|
+
"image",
|
|
465
|
+
"inspect",
|
|
466
|
+
image
|
|
467
|
+
], {
|
|
468
|
+
stdio: "ignore",
|
|
469
|
+
timeout: 1e4
|
|
470
|
+
});
|
|
471
|
+
return true;
|
|
472
|
+
} catch {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
async pullImage(image) {
|
|
477
|
+
execFileSync("container", ["pull", image], {
|
|
478
|
+
stdio: [
|
|
479
|
+
"ignore",
|
|
480
|
+
"pipe",
|
|
481
|
+
"pipe"
|
|
482
|
+
],
|
|
483
|
+
timeout: 3e5
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
listContainers(prefix) {
|
|
487
|
+
return listContainersFromBinary("container", prefix);
|
|
488
|
+
}
|
|
489
|
+
async healthCheck(id) {
|
|
490
|
+
return checkHealthWithBinary("container", id);
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
/** Auto-detect: prefer Apple Container on ARM Mac, else Docker. */
|
|
494
|
+
function detectBestEngine() {
|
|
495
|
+
if (platform() === "darwin" && arch() === "arm64") {
|
|
496
|
+
const apple = new AppleContainerEngine();
|
|
497
|
+
if (apple.isAvailable()) return apple;
|
|
498
|
+
}
|
|
499
|
+
return new DockerEngine();
|
|
500
|
+
}
|
|
501
|
+
function createEngine(type) {
|
|
502
|
+
switch (type) {
|
|
503
|
+
case "apple-container": return new AppleContainerEngine();
|
|
504
|
+
case "docker": return new DockerEngine();
|
|
505
|
+
case "auto": return detectBestEngine();
|
|
506
|
+
default: return new DockerEngine();
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
//#endregion
|
|
511
|
+
export { createEngine, detectBestEngine };
|