@elizaos/app-core 2.0.0-alpha.69 → 2.0.0-alpha.70

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.
@@ -2,4 +2,3 @@ $ bun run build:dist
2
2
  $ test -f ../ui/dist/index.d.ts || (cd ../ui && bun run build) && rimraf dist && tsc -p tsconfig.build.json && node ../../scripts/copy-package-assets.mjs packages/app-core src/styles src/i18n/locales && node ../../scripts/prepare-package-dist.mjs packages/app-core
3
3
  error: Script not found "build"
4
4
  error: script "build:dist" exited with code 1
5
- error: script "build" exited with code 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elizaos/app-core",
3
- "version": "2.0.0-alpha.69",
3
+ "version": "2.0.0-alpha.70",
4
4
  "description": "Shared application core for Milady shells and white-label apps.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -68,8 +68,8 @@
68
68
  "@capacitor/haptics": "8.0.0",
69
69
  "@capacitor/keyboard": "8.0.0",
70
70
  "@capacitor/preferences": "^8.0.1",
71
- "@elizaos/autonomous": "2.0.0-alpha.69",
72
- "@elizaos/ui": "2.0.0-alpha.69",
71
+ "@elizaos/autonomous": "2.0.0-alpha.70",
72
+ "@elizaos/ui": "2.0.0-alpha.70",
73
73
  "@sparkjsdev/spark": "^0.1.10",
74
74
  "lucide-react": "^0.575.0",
75
75
  "three": "^0.182.0",
@@ -87,5 +87,5 @@
87
87
  "typescript": "^5.9.3",
88
88
  "vitest": "^4.0.18"
89
89
  },
90
- "gitHead": "774937c9cbefd3d95fc181ca40295bb4fd5a7d3b"
90
+ "gitHead": "db049debacc33857bcea596057f1284568a061ff"
91
91
  }
package/src/api/client.ts CHANGED
@@ -2087,6 +2087,10 @@ export class MiladyClient {
2087
2087
  }
2088
2088
  }
2089
2089
 
2090
+ getBaseUrl(): string {
2091
+ return this.baseUrl;
2092
+ }
2093
+
2090
2094
  setBaseUrl(baseUrl: string | null): void {
2091
2095
  const normalized = baseUrl?.trim().replace(/\/+$/, "") || "";
2092
2096
  this._explicitBase = normalized.length > 0;
@@ -5519,6 +5523,133 @@ export class MiladyClient {
5519
5523
  body: JSON.stringify({ settings }),
5520
5524
  });
5521
5525
  }
5526
+
5527
+ // ── Direct Eliza Cloud Auth (no local backend required) ─────────────
5528
+
5529
+ /**
5530
+ * Initiate a direct login to Eliza Cloud without going through a local agent.
5531
+ * Used in sandbox mode when no local backend exists yet.
5532
+ */
5533
+ async cloudLoginDirect(
5534
+ cloudApiBase: string,
5535
+ ): Promise<{ ok: boolean; browserUrl?: string; sessionId?: string; error?: string }> {
5536
+ const res = await fetch(`${cloudApiBase}/api/v1/auth/login`, {
5537
+ method: "POST",
5538
+ headers: { "Content-Type": "application/json" },
5539
+ });
5540
+ if (!res.ok) {
5541
+ return { ok: false, error: `Login failed (${res.status})` };
5542
+ }
5543
+ return res.json();
5544
+ }
5545
+
5546
+ /**
5547
+ * Poll a direct Eliza Cloud login session for authentication status.
5548
+ */
5549
+ async cloudLoginPollDirect(
5550
+ cloudApiBase: string,
5551
+ sessionId: string,
5552
+ ): Promise<{
5553
+ status: "pending" | "authenticated" | "expired" | "error";
5554
+ token?: string;
5555
+ userId?: string;
5556
+ error?: string;
5557
+ }> {
5558
+ const res = await fetch(
5559
+ `${cloudApiBase}/api/v1/auth/login/status?sessionId=${encodeURIComponent(sessionId)}`,
5560
+ );
5561
+ if (!res.ok) {
5562
+ return { status: "error", error: `Poll failed (${res.status})` };
5563
+ }
5564
+ return res.json();
5565
+ }
5566
+
5567
+ // ── Eliza Cloud Sandbox Provisioning ───────────────────────────────
5568
+
5569
+ /**
5570
+ * Create a sandbox agent on Eliza Cloud and provision it.
5571
+ * Returns the bridge URL when provisioning completes.
5572
+ *
5573
+ * Flow:
5574
+ * 1. POST /api/v1/milady/agents — create agent record
5575
+ * 2. POST /api/v1/milady/agents/{id}/provision — start async provisioning
5576
+ * 3. Poll GET /api/v1/jobs/{jobId} until completed
5577
+ * 4. Return bridgeUrl from job result
5578
+ */
5579
+ async provisionCloudSandbox(options: {
5580
+ cloudApiBase: string;
5581
+ authToken: string;
5582
+ name: string;
5583
+ bio?: string[];
5584
+ onProgress?: (status: string, detail?: string) => void;
5585
+ }): Promise<{ bridgeUrl: string; agentId: string }> {
5586
+ const { cloudApiBase, authToken, name, bio, onProgress } = options;
5587
+ const headers: Record<string, string> = {
5588
+ "Content-Type": "application/json",
5589
+ Authorization: `Bearer ${authToken}`,
5590
+ };
5591
+
5592
+ onProgress?.("creating", "Creating agent...");
5593
+
5594
+ // Step 1: Create agent
5595
+ const createRes = await fetch(`${cloudApiBase}/api/v1/milady/agents`, {
5596
+ method: "POST",
5597
+ headers,
5598
+ body: JSON.stringify({ name, bio }),
5599
+ });
5600
+ if (!createRes.ok) {
5601
+ const err = await createRes.text().catch(() => "Unknown error");
5602
+ throw new Error(`Failed to create cloud agent: ${err}`);
5603
+ }
5604
+ const createData = (await createRes.json()) as { id: string };
5605
+ const agentId = createData.id;
5606
+
5607
+ onProgress?.("provisioning", "Provisioning sandbox environment...");
5608
+
5609
+ // Step 2: Start provisioning
5610
+ const provisionRes = await fetch(
5611
+ `${cloudApiBase}/api/v1/milady/agents/${agentId}/provision`,
5612
+ { method: "POST", headers },
5613
+ );
5614
+ if (!provisionRes.ok) {
5615
+ const err = await provisionRes.text().catch(() => "Unknown error");
5616
+ throw new Error(`Failed to start provisioning: ${err}`);
5617
+ }
5618
+ const provisionData = (await provisionRes.json()) as { jobId: string };
5619
+ const jobId = provisionData.jobId;
5620
+
5621
+ // Step 3: Poll job status
5622
+ const deadline = Date.now() + 120_000; // 2 minute timeout
5623
+ while (Date.now() < deadline) {
5624
+ await new Promise((r) => setTimeout(r, 2000));
5625
+
5626
+ const jobRes = await fetch(`${cloudApiBase}/api/v1/jobs/${jobId}`, {
5627
+ headers,
5628
+ });
5629
+ if (!jobRes.ok) continue;
5630
+
5631
+ const jobData = (await jobRes.json()) as {
5632
+ status: string;
5633
+ result?: { bridgeUrl?: string };
5634
+ error?: string;
5635
+ };
5636
+
5637
+ if (jobData.status === "completed" && jobData.result?.bridgeUrl) {
5638
+ onProgress?.("ready", "Sandbox ready!");
5639
+ return { bridgeUrl: jobData.result.bridgeUrl, agentId };
5640
+ }
5641
+
5642
+ if (jobData.status === "failed") {
5643
+ throw new Error(
5644
+ `Provisioning failed: ${jobData.error ?? "Unknown error"}`,
5645
+ );
5646
+ }
5647
+
5648
+ onProgress?.("provisioning", `Status: ${jobData.status}...`);
5649
+ }
5650
+
5651
+ throw new Error("Provisioning timed out after 2 minutes");
5652
+ }
5522
5653
  }
5523
5654
 
5524
5655
  // Singleton
@@ -90,7 +90,7 @@ export function CharacterRoster({
90
90
 
91
91
  return (
92
92
  <div
93
- className="flex flex-wrap items-start justify-center gap-y-1"
93
+ className="ce-roster"
94
94
  data-testid={`${testIdPrefix}-roster-grid`}
95
95
  >
96
96
  {entries.map((entry) => {
@@ -100,26 +100,16 @@ export function CharacterRoster({
100
100
  <button
101
101
  key={entry.id}
102
102
  type="button"
103
- className={`group relative -mx-3 min-w-0 w-[9.75rem] text-center transition-all duration-300 ease-out ${
104
- isSelected
105
- ? "z-100 scale-[1.00] opacity-100"
106
- : "scale-[1.00] opacity-70 hover:scale-[1.00] hover:opacity-100"
107
- }`}
103
+ className={`ce-roster-card ${isSelected ? "ce-roster-card--active" : ""}`}
108
104
  onClick={() => onSelect(entry)}
109
105
  data-testid={`${testIdPrefix}-preset-${entry.id}`}
110
106
  >
111
107
  <div
112
- className={`relative h-[10rem] w-full p-[2px] transition-all duration-300 ${
113
- isSelected
114
- ? "bg-yellow-400 shadow-[0_0_28px_rgba(250,204,21,0.32)]"
115
- : useWhiteBorders
116
- ? "bg-white/10 hover:bg-white/35"
117
- : "bg-border/20 hover:bg-border/60"
118
- }`}
108
+ className={`ce-roster-card-frame ${isSelected ? "ce-roster-card-frame--active" : ""}`}
119
109
  style={{ clipPath: SLANT_CLIP }}
120
110
  >
121
111
  <div
122
- className="relative h-full w-full overflow-hidden"
112
+ className="ce-roster-card-inner"
123
113
  style={{ clipPath: SLANT_CLIP }}
124
114
  >
125
115
  {isSelected && (
@@ -132,19 +122,11 @@ export function CharacterRoster({
132
122
  src={getVrmPreviewUrl(entry.avatarIndex)}
133
123
  alt={entry.name}
134
124
  draggable={false}
135
- className={`h-full w-full object-cover transition-transform duration-300 ease-out ${
136
- isSelected
137
- ? "scale-[1.04]"
138
- : "scale-100 group-hover:scale-[1.02]"
139
- }`}
125
+ className={`ce-roster-card-img ${isSelected ? "ce-roster-card-img--active" : ""}`}
140
126
  />
141
- <div className="absolute inset-x-0 bottom-0">
127
+ <div className="ce-roster-card-label">
142
128
  <div
143
- className={`px-2 py-1 text-sm font-semibold text-white transition-all ${
144
- isSelected
145
- ? "bg-black/78 shadow-[inset_0_1px_0_rgba(255,255,255,0.08)]"
146
- : "bg-black/62"
147
- }`}
129
+ className={`ce-roster-card-name ${isSelected ? "ce-roster-card-name--active" : ""}`}
148
130
  style={{ clipPath: INSET_CLIP }}
149
131
  >
150
132
  {entry.name}
@@ -1,5 +1,4 @@
1
1
  import { Button } from "@elizaos/ui";
2
- import { useBranding } from "../config/branding";
3
2
  import { useApp } from "../state";
4
3
 
5
4
  export type CloudSourceMode = "cloud" | "own-key";
@@ -7,7 +6,7 @@ export type CloudSourceMode = "cloud" | "own-key";
7
6
  export function CloudSourceModeToggle({
8
7
  mode,
9
8
  onChange,
10
- cloudLabel,
9
+ cloudLabel = "Eliza Cloud",
11
10
  ownKeyLabel = "Own API Key",
12
11
  }: {
13
12
  mode: CloudSourceMode;
@@ -15,8 +14,7 @@ export function CloudSourceModeToggle({
15
14
  cloudLabel?: string;
16
15
  ownKeyLabel?: string;
17
16
  }) {
18
- const branding = useBranding();
19
- const resolvedCloudLabel = cloudLabel ?? branding.cloudName;
17
+ const resolvedCloudLabel = cloudLabel;
20
18
  return (
21
19
  <div className="inline-flex overflow-hidden rounded-lg border border-[var(--border)] bg-[var(--card)] shadow-sm">
22
20
  <Button
@@ -59,9 +57,8 @@ export function CloudConnectionStatus({
59
57
  disconnectedText: string;
60
58
  }) {
61
59
  const { t } = useApp();
62
- const branding = useBranding();
63
60
  const resolvedConnectedText =
64
- connectedText ?? `Connected to ${branding.cloudName}`;
61
+ connectedText ?? "Connected to Eliza Cloud";
65
62
  return (
66
63
  <div className="flex items-center justify-between py-2.5 px-3 border border-[var(--border)] bg-[var(--bg-muted)]">
67
64
  {connected ? (
@@ -415,6 +415,19 @@ function CompanionSceneSurface({
415
415
  });
416
416
  }, [tab]);
417
417
 
418
+ /* ── Preload all VRM files into browser cache for instant character swaps ── */
419
+ const preloadedRef = useRef(false);
420
+ useEffect(() => {
421
+ if (preloadedRef.current || preloadAvatars.length === 0) return;
422
+ preloadedRef.current = true;
423
+ for (const entry of preloadAvatars) {
424
+ // Fire-and-forget fetch to warm browser cache; low priority.
425
+ void fetch(entry.vrmPath, { priority: "low" } as RequestInit).catch(
426
+ () => {},
427
+ );
428
+ }
429
+ }, [preloadAvatars]);
430
+
418
431
  return (
419
432
  <div
420
433
  ref={rootRef}
@@ -17,7 +17,6 @@ import {
17
17
  defaultRegistry,
18
18
  type JsonSchemaObject,
19
19
  } from "../config";
20
- import { DEFAULT_BRANDING, useBranding } from "../config/branding";
21
20
  import { useApp } from "../state";
22
21
  import type { ConfigUiHint } from "../types";
23
22
  import {
@@ -64,13 +63,12 @@ function CloudRpcStatus({
64
63
  onLogin,
65
64
  }: CloudRpcStatusProps) {
66
65
  const { t, setState, setTab } = useApp();
67
- const branding = useBranding();
68
66
  if (connected) {
69
67
  return (
70
68
  <div className="flex items-center gap-2 text-xs">
71
69
  <span className="inline-block w-2 h-2 rounded-full bg-[var(--ok,#16a34a)]" />
72
70
  <span className="font-semibold">
73
- {`Connected to ${branding.cloudName}`}
71
+ Connected to Eliza Cloud
74
72
  </span>
75
73
  {credits !== null && (
76
74
  <span className="text-[var(--muted)] ml-auto">
@@ -107,7 +105,7 @@ function CloudRpcStatus({
107
105
  <div className="flex items-center gap-2 text-xs">
108
106
  <span className="inline-block w-2 h-2 rounded-full bg-[var(--muted)]" />
109
107
  <span className="text-[var(--muted)]">
110
- {`Requires ${branding.cloudName}`}
108
+ Requires Eliza Cloud
111
109
  </span>
112
110
  </div>
113
111
  <button
@@ -197,7 +195,6 @@ function RpcConfigSection<T extends string>({
197
195
  cloud,
198
196
  containerClassName,
199
197
  }: RpcSectionProps<T>) {
200
- const branding = useBranding();
201
198
  const rpcConfig = buildRpcRendererConfig(
202
199
  selectedProvider,
203
200
  providerConfigs,
@@ -217,7 +214,7 @@ function RpcConfigSection<T extends string>({
217
214
  (key: string) => {
218
215
  // hack to get t function without breaking hook rules
219
216
  return key === "elizaclouddashboard.ElizaCloud"
220
- ? branding.cloudName
217
+ ? "Eliza Cloud"
221
218
  : key;
222
219
  },
223
220
  )}
@@ -294,27 +291,27 @@ const CLOUD_SERVICE_DEFS: {
294
291
  {
295
292
  key: "inference",
296
293
  label: "Model Inference",
297
- description: `Use ${DEFAULT_BRANDING.cloudName} for LLM calls. Turn off to use your own API keys (Anthropic, OpenAI, etc.)`,
294
+ description: `Use Eliza Cloud for LLM calls. Turn off to use your own API keys (Anthropic, OpenAI, etc.)`,
298
295
  },
299
296
  {
300
297
  key: "rpc",
301
298
  label: "Blockchain RPC",
302
- description: `Use ${DEFAULT_BRANDING.cloudName} RPC endpoints for EVM, BSC, and Solana`,
299
+ description: `Use Eliza Cloud RPC endpoints for EVM, BSC, and Solana`,
303
300
  },
304
301
  {
305
302
  key: "media",
306
303
  label: "Media Generation",
307
- description: `Use ${DEFAULT_BRANDING.cloudName} for image, video, audio, and vision`,
304
+ description: `Use Eliza Cloud for image, video, audio, and vision`,
308
305
  },
309
306
  {
310
307
  key: "tts",
311
308
  label: "Text-to-Speech",
312
- description: `Use ${DEFAULT_BRANDING.cloudName} for TTS voice synthesis`,
309
+ description: `Use Eliza Cloud for TTS voice synthesis`,
313
310
  },
314
311
  {
315
312
  key: "embeddings",
316
313
  label: "Embeddings",
317
- description: `Use ${DEFAULT_BRANDING.cloudName} for text embedding generation`,
314
+ description: `Use Eliza Cloud for text embedding generation`,
318
315
  },
319
316
  ];
320
317
 
@@ -83,6 +83,8 @@ export const VrmStage = memo(function VrmStage({
83
83
  const [loaderFading, setLoaderFading] = useState(false);
84
84
  const [loaderHidden, setLoaderHidden] = useState(false);
85
85
  const loaderFadingStartedRef = useRef(false);
86
+ /** After the first successful VRM load, suppress the loader on subsequent swaps. */
87
+ const hasLoadedFirstVrmRef = useRef(false);
86
88
 
87
89
  const chatAvatarVoice = useChatAvatarVoiceState();
88
90
 
@@ -142,6 +144,7 @@ export const VrmStage = memo(function VrmStage({
142
144
  if (state.vrmLoaded) {
143
145
  setVrmLoaded(true);
144
146
  setShowVrmFallback(false);
147
+ hasLoadedFirstVrmRef.current = true;
145
148
  if (!loaderFadingStartedRef.current) {
146
149
  loaderFadingStartedRef.current = true;
147
150
  setLoaderFading(true);
@@ -173,13 +176,17 @@ export const VrmStage = memo(function VrmStage({
173
176
  if (vrmPath === prevVrmPathRef.current && hasMountedRef.current) return;
174
177
  prevVrmPathRef.current = vrmPath;
175
178
  if (hasMountedRef.current) {
176
- // Avatar changed — reset loading state but NOT the world
177
- setVrmLoaded(false);
178
- setShowVrmFallback(false);
179
- setLoadingProgress(undefined);
180
- setLoaderFading(false);
181
- setLoaderHidden(false);
182
- loaderFadingStartedRef.current = false;
179
+ // Avatar changed — reset loading state but NOT the world.
180
+ // After the first successful VRM load, keep the loader hidden so
181
+ // subsequent character swaps feel instant (no flash of loading bar).
182
+ if (!hasLoadedFirstVrmRef.current) {
183
+ setVrmLoaded(false);
184
+ setShowVrmFallback(false);
185
+ setLoadingProgress(undefined);
186
+ setLoaderFading(false);
187
+ setLoaderHidden(false);
188
+ loaderFadingStartedRef.current = false;
189
+ }
183
190
  }
184
191
  hasMountedRef.current = true;
185
192
  }, [vrmPath]);
@@ -4,11 +4,12 @@ import type {
4
4
  ProviderOption,
5
5
  } from "@elizaos/app-core/api";
6
6
  import { client } from "@elizaos/app-core/api";
7
+ import { useBranding } from "@elizaos/app-core/config";
7
8
  import { isNative } from "@elizaos/app-core/platform";
8
9
  import { getProviderLogo } from "@elizaos/app-core/providers";
9
10
  import { useApp } from "@elizaos/app-core/state";
10
11
  import { openExternalUrl } from "@elizaos/app-core/utils";
11
- import { useState } from "react";
12
+ import { useEffect, useState } from "react";
12
13
 
13
14
  function formatRequestError(err: unknown): string {
14
15
  if (err instanceof Error) {
@@ -46,6 +47,8 @@ export function ConnectionStep() {
46
47
  t,
47
48
  } = useApp();
48
49
 
50
+ const branding = useBranding();
51
+
49
52
  const [openaiOAuthStarted, setOpenaiOAuthStarted] = useState(false);
50
53
  const [openaiCallbackUrl, setOpenaiCallbackUrl] = useState("");
51
54
  const [openaiConnected, setOpenaiConnected] = useState(false);
@@ -148,7 +151,22 @@ export function ConnectionStep() {
148
151
  setState("onboardingOpenRouterModel", modelId);
149
152
  };
150
153
 
151
- const providers = onboardingOptions?.providers ?? [];
154
+ const catalogProviders = onboardingOptions?.providers ?? [];
155
+ // Merge custom providers from branding (app-injected) with the catalog.
156
+ // Custom providers are appended; duplicates (by id) are skipped.
157
+ const customProviders = branding.customProviders ?? [];
158
+ const catalogIds = new Set(catalogProviders.map((p: ProviderOption) => p.id));
159
+ const providers = [
160
+ ...catalogProviders,
161
+ ...customProviders.filter((cp) => !catalogIds.has(cp.id as never)),
162
+ ] as ProviderOption[];
163
+ // Build a map of custom provider logos for getProviderLogo lookups.
164
+ const customLogoMap = new Map(
165
+ customProviders
166
+ .filter((cp) => cp.logoDark || cp.logoLight)
167
+ .map((cp) => [cp.id, { logoDark: cp.logoDark, logoLight: cp.logoLight }]),
168
+ );
169
+ const getCustomLogo = (id: string) => customLogoMap.get(id);
152
170
  const elizaCloudReady =
153
171
  elizaCloudConnected ||
154
172
  (onboardingRunMode === "cloud" &&
@@ -275,6 +293,18 @@ export function ConnectionStep() {
275
293
  }
276
294
  };
277
295
 
296
+ // On native (mobile), force sandbox mode — skip straight to Eliza Cloud login.
297
+ // Mobile cannot run a local backend, so cloud is the only option.
298
+ useEffect(() => {
299
+ if (isNative && !onboardingRunMode) {
300
+ setState("onboardingRunMode", "cloud");
301
+ setState("onboardingCloudProvider", "elizacloud");
302
+ setState("onboardingProvider", "");
303
+ setState("onboardingApiKey", "");
304
+ setState("onboardingPrimaryModel", "");
305
+ }
306
+ }, [isNative, onboardingRunMode, setState]);
307
+
278
308
  if (!showProviderSelection) {
279
309
  if (!onboardingRunMode) {
280
310
  return (
@@ -730,7 +760,7 @@ export function ConnectionStep() {
730
760
  onClick={() => handleProviderSelect(p.id)}
731
761
  >
732
762
  <img
733
- src={getProviderLogo(p.id, false)}
763
+ src={getProviderLogo(p.id, false, getCustomLogo(p.id))}
734
764
  alt={display.name}
735
765
  className="onboarding-provider-icon"
736
766
  />
@@ -795,7 +825,7 @@ export function ConnectionStep() {
795
825
  >
796
826
  {selectedProvider && (
797
827
  <img
798
- src={getProviderLogo(selectedProvider.id, false)}
828
+ src={getProviderLogo(selectedProvider.id, false, getCustomLogo(selectedProvider.id))}
799
829
  alt={selectedDisplay.name}
800
830
  className="onboarding-provider-icon"
801
831
  style={{ width: "1.5rem", height: "1.5rem" }}
@@ -1,10 +1,30 @@
1
1
  import { createContext, useContext } from "react";
2
2
 
3
+ /**
4
+ * Custom provider that apps can inject into the onboarding flow.
5
+ * Uses `string` for id/family so apps aren't restricted to the built-in union.
6
+ */
7
+ export interface CustomProviderOption {
8
+ id: string;
9
+ name: string;
10
+ envKey: string | null;
11
+ pluginName: string;
12
+ keyPrefix: string | null;
13
+ description: string;
14
+ family: string;
15
+ authMode: "api-key" | "cloud" | "credentials" | "local" | "subscription";
16
+ group: "cloud" | "local" | "subscription";
17
+ order: number;
18
+ recommended?: boolean;
19
+ /** Dark-mode logo path (e.g. "/logos/my-provider.png") */
20
+ logoDark?: string;
21
+ /** Light-mode logo path */
22
+ logoLight?: string;
23
+ }
24
+
3
25
  export interface BrandingConfig {
4
26
  /** Product name shown in UI ("Eliza" | "Milady") */
5
27
  appName: string;
6
- /** Cloud service name ("Eliza Cloud" | "Milady Cloud") */
7
- cloudName: string;
8
28
  /** GitHub org ("elizaos" | "milady-ai") */
9
29
  orgName: string;
10
30
  /** GitHub repo name ("eliza" | "milady") */
@@ -21,11 +41,12 @@ export interface BrandingConfig {
21
41
  fileExtension: string;
22
42
  /** npm package scope ("elizaos" | "miladyai") */
23
43
  packageScope: string;
44
+ /** Custom providers injected by the app into the onboarding flow */
45
+ customProviders?: CustomProviderOption[];
24
46
  }
25
47
 
26
48
  export const DEFAULT_BRANDING: BrandingConfig = {
27
49
  appName: "Eliza",
28
- cloudName: "Eliza Cloud",
29
50
  orgName: "elizaos",
30
51
  repoName: "eliza",
31
52
  docsUrl: "https://docs.elizaos.ai",