@lobehub/lobehub 2.0.0-next.339 → 2.0.0-next.340

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/docs/self-hosting/advanced/auth/clerk-to-betterauth.mdx +366 -0
  4. package/docs/self-hosting/advanced/auth/clerk-to-betterauth.zh-CN.mdx +360 -0
  5. package/docs/self-hosting/advanced/auth/legacy.mdx +4 -0
  6. package/docs/self-hosting/advanced/auth/legacy.zh-CN.mdx +4 -0
  7. package/docs/self-hosting/advanced/auth.mdx +55 -30
  8. package/docs/self-hosting/advanced/auth.zh-CN.mdx +55 -30
  9. package/locales/ar/auth.json +1 -1
  10. package/locales/ar/desktop-onboarding.json +1 -0
  11. package/locales/ar/metadata.json +2 -2
  12. package/locales/ar/models.json +23 -5
  13. package/locales/ar/providers.json +0 -1
  14. package/locales/ar/setting.json +19 -0
  15. package/locales/bg-BG/auth.json +1 -1
  16. package/locales/bg-BG/desktop-onboarding.json +1 -0
  17. package/locales/bg-BG/metadata.json +2 -2
  18. package/locales/bg-BG/models.json +5 -5
  19. package/locales/bg-BG/providers.json +0 -1
  20. package/locales/bg-BG/setting.json +19 -0
  21. package/locales/de-DE/auth.json +1 -1
  22. package/locales/de-DE/desktop-onboarding.json +1 -0
  23. package/locales/de-DE/metadata.json +2 -2
  24. package/locales/de-DE/models.json +31 -10
  25. package/locales/de-DE/providers.json +0 -1
  26. package/locales/de-DE/setting.json +19 -0
  27. package/locales/en-US/auth.json +3 -2
  28. package/locales/en-US/metadata.json +2 -2
  29. package/locales/en-US/models.json +10 -11
  30. package/locales/en-US/providers.json +0 -1
  31. package/locales/es-ES/auth.json +1 -1
  32. package/locales/es-ES/desktop-onboarding.json +1 -0
  33. package/locales/es-ES/metadata.json +2 -2
  34. package/locales/es-ES/models.json +32 -5
  35. package/locales/es-ES/providers.json +0 -1
  36. package/locales/es-ES/setting.json +19 -0
  37. package/locales/fa-IR/auth.json +1 -1
  38. package/locales/fa-IR/desktop-onboarding.json +1 -0
  39. package/locales/fa-IR/metadata.json +2 -2
  40. package/locales/fa-IR/models.json +35 -5
  41. package/locales/fa-IR/providers.json +0 -1
  42. package/locales/fa-IR/setting.json +19 -0
  43. package/locales/fr-FR/auth.json +1 -1
  44. package/locales/fr-FR/desktop-onboarding.json +1 -0
  45. package/locales/fr-FR/metadata.json +2 -2
  46. package/locales/fr-FR/models.json +33 -5
  47. package/locales/fr-FR/providers.json +0 -1
  48. package/locales/fr-FR/setting.json +19 -0
  49. package/locales/it-IT/auth.json +1 -1
  50. package/locales/it-IT/desktop-onboarding.json +1 -0
  51. package/locales/it-IT/metadata.json +2 -2
  52. package/locales/it-IT/models.json +3 -8
  53. package/locales/it-IT/providers.json +0 -1
  54. package/locales/it-IT/setting.json +19 -0
  55. package/locales/ja-JP/auth.json +1 -1
  56. package/locales/ja-JP/desktop-onboarding.json +1 -0
  57. package/locales/ja-JP/metadata.json +2 -2
  58. package/locales/ja-JP/models.json +32 -5
  59. package/locales/ja-JP/providers.json +0 -1
  60. package/locales/ja-JP/setting.json +19 -0
  61. package/locales/ko-KR/auth.json +1 -1
  62. package/locales/ko-KR/desktop-onboarding.json +1 -0
  63. package/locales/ko-KR/metadata.json +2 -2
  64. package/locales/ko-KR/models.json +3 -8
  65. package/locales/ko-KR/providers.json +0 -1
  66. package/locales/ko-KR/setting.json +19 -0
  67. package/locales/nl-NL/auth.json +1 -1
  68. package/locales/nl-NL/desktop-onboarding.json +1 -0
  69. package/locales/nl-NL/metadata.json +2 -2
  70. package/locales/nl-NL/models.json +45 -4
  71. package/locales/nl-NL/providers.json +0 -1
  72. package/locales/nl-NL/setting.json +19 -0
  73. package/locales/pl-PL/auth.json +1 -1
  74. package/locales/pl-PL/desktop-onboarding.json +1 -0
  75. package/locales/pl-PL/metadata.json +2 -2
  76. package/locales/pl-PL/models.json +37 -5
  77. package/locales/pl-PL/providers.json +0 -1
  78. package/locales/pl-PL/setting.json +19 -0
  79. package/locales/pt-BR/auth.json +1 -1
  80. package/locales/pt-BR/desktop-onboarding.json +1 -0
  81. package/locales/pt-BR/metadata.json +2 -2
  82. package/locales/pt-BR/models.json +28 -4
  83. package/locales/pt-BR/providers.json +0 -1
  84. package/locales/pt-BR/setting.json +19 -0
  85. package/locales/ru-RU/auth.json +1 -1
  86. package/locales/ru-RU/desktop-onboarding.json +1 -0
  87. package/locales/ru-RU/metadata.json +2 -2
  88. package/locales/ru-RU/models.json +3 -8
  89. package/locales/ru-RU/providers.json +0 -1
  90. package/locales/ru-RU/setting.json +19 -0
  91. package/locales/tr-TR/auth.json +1 -1
  92. package/locales/tr-TR/desktop-onboarding.json +1 -0
  93. package/locales/tr-TR/metadata.json +2 -2
  94. package/locales/tr-TR/models.json +26 -7
  95. package/locales/tr-TR/providers.json +0 -1
  96. package/locales/tr-TR/setting.json +19 -0
  97. package/locales/vi-VN/auth.json +1 -1
  98. package/locales/vi-VN/desktop-onboarding.json +1 -0
  99. package/locales/vi-VN/metadata.json +2 -2
  100. package/locales/vi-VN/models.json +3 -5
  101. package/locales/vi-VN/providers.json +0 -1
  102. package/locales/vi-VN/setting.json +19 -0
  103. package/locales/zh-CN/auth.json +3 -3
  104. package/locales/zh-CN/metadata.json +2 -2
  105. package/locales/zh-CN/models.json +46 -6
  106. package/locales/zh-CN/providers.json +0 -1
  107. package/locales/zh-TW/auth.json +1 -1
  108. package/locales/zh-TW/desktop-onboarding.json +1 -0
  109. package/locales/zh-TW/metadata.json +2 -2
  110. package/locales/zh-TW/models.json +39 -6
  111. package/locales/zh-TW/providers.json +0 -1
  112. package/locales/zh-TW/setting.json +19 -0
  113. package/package.json +1 -1
  114. package/packages/const/src/url.ts +1 -1
  115. package/public/og/agent-og.webp +0 -0
  116. package/public/og/mcp-og.webp +0 -0
  117. package/public/og/og.webp +0 -0
  118. package/scripts/clerk-to-betterauth/__tests__/parseCsvLine.test.ts +21 -0
  119. package/scripts/clerk-to-betterauth/_internal/config.ts +55 -0
  120. package/scripts/clerk-to-betterauth/_internal/db.ts +32 -0
  121. package/scripts/clerk-to-betterauth/_internal/env.ts +6 -0
  122. package/scripts/clerk-to-betterauth/_internal/load-data-from-files.ts +74 -0
  123. package/scripts/clerk-to-betterauth/_internal/types.ts +45 -0
  124. package/scripts/clerk-to-betterauth/_internal/utils.ts +36 -0
  125. package/scripts/clerk-to-betterauth/export-clerk-users-with-api.ts +211 -0
  126. package/scripts/clerk-to-betterauth/index.ts +314 -0
  127. package/scripts/clerk-to-betterauth/prod/put_clerk_exported_users_csv_here.txt +0 -0
  128. package/scripts/clerk-to-betterauth/test/put_clerk_exported_users_csv_here.txt +0 -0
  129. package/scripts/clerk-to-betterauth/verify.ts +275 -0
  130. package/src/app/[variants]/(auth)/signin/SignInEmailStep.tsx +30 -2
  131. package/src/app/[variants]/(auth)/signin/SignInPasswordStep.tsx +1 -1
  132. package/src/app/[variants]/(auth)/signin/page.tsx +3 -0
  133. package/src/app/[variants]/(auth)/signin/useSignIn.ts +6 -2
  134. package/src/app/[variants]/(main)/home/features/RecentResource/Item.tsx +2 -2
  135. package/src/app/[variants]/(main)/home/features/index.tsx +1 -2
  136. package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/index.tsx +0 -2
  137. package/src/app/[variants]/(main)/settings/skill/features/Actions.tsx +8 -7
  138. package/src/app/[variants]/(main)/settings/skill/features/McpSkillItem.tsx +9 -11
  139. package/src/app/manifest.ts +4 -4
  140. package/src/features/AuthCard/index.tsx +1 -1
  141. package/src/features/SkillStore/CommunityList/Item.tsx +3 -2
  142. package/src/features/SkillStore/Search/index.tsx +0 -1
  143. package/src/locales/default/auth.ts +3 -2
  144. package/src/locales/default/metadata.ts +2 -2
  145. package/src/server/ld.ts +4 -3
  146. package/src/styles/global.ts +0 -6
  147. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/apple.mdx +0 -0
  148. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/apple.zh-CN.mdx +0 -0
  149. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/auth0.mdx +0 -0
  150. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/auth0.zh-CN.mdx +0 -0
  151. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/authelia.mdx +0 -0
  152. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/authelia.zh-CN.mdx +0 -0
  153. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/authentik.mdx +0 -0
  154. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/authentik.zh-CN.mdx +0 -0
  155. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/casdoor.mdx +0 -0
  156. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/casdoor.zh-CN.mdx +0 -0
  157. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/cloudflare-zero-trust.mdx +0 -0
  158. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/cloudflare-zero-trust.zh-CN.mdx +0 -0
  159. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/cognito.mdx +0 -0
  160. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/cognito.zh-CN.mdx +0 -0
  161. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/feishu.mdx +0 -0
  162. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/feishu.zh-CN.mdx +0 -0
  163. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/generic-oidc.mdx +0 -0
  164. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/generic-oidc.zh-CN.mdx +0 -0
  165. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/github.mdx +0 -0
  166. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/github.zh-CN.mdx +0 -0
  167. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/google.mdx +0 -0
  168. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/google.zh-CN.mdx +0 -0
  169. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/keycloak.mdx +0 -0
  170. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/keycloak.zh-CN.mdx +0 -0
  171. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/logto.mdx +0 -0
  172. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/logto.zh-CN.mdx +0 -0
  173. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/microsoft.mdx +0 -0
  174. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/microsoft.zh-CN.mdx +0 -0
  175. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/okta.mdx +0 -0
  176. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/okta.zh-CN.mdx +0 -0
  177. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/wechat.mdx +0 -0
  178. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/wechat.zh-CN.mdx +0 -0
  179. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/zitadel.mdx +0 -0
  180. /package/docs/self-hosting/advanced/auth/{better-auth → providers}/zitadel.zh-CN.mdx +0 -0
@@ -0,0 +1,275 @@
1
+ /* eslint-disable unicorn/prefer-top-level-await */
2
+ import { getMigrationMode, resolveDataPaths } from './_internal/config';
3
+ import { db, pool, schema } from './_internal/db';
4
+ import { loadCSVData, loadClerkUsersFromFile } from './_internal/load-data-from-files';
5
+ import { ClerkExternalAccount, ClerkUser } from './_internal/types';
6
+
7
+ type ExpectedAccount = {
8
+ accountId?: string;
9
+ providerId: string;
10
+ scope?: string;
11
+ userId: string;
12
+ };
13
+
14
+ type ActualAccount = {
15
+ accountId: string | null;
16
+ providerId: string;
17
+ scope: string | null;
18
+ userId: string;
19
+ };
20
+
21
+ const MAX_SAMPLES = 5;
22
+
23
+ const formatDuration = (ms: number) => `${(ms / 1000).toFixed(1)}s`;
24
+
25
+ function providerIdFromExternal(external: ClerkExternalAccount): string {
26
+ return external.provider === 'credential'
27
+ ? 'credential'
28
+ : external.provider.replace('oauth_', '');
29
+ }
30
+
31
+ function buildExpectedAccounts(
32
+ csvUsers: Awaited<ReturnType<typeof loadCSVData>>,
33
+ clerkUsers: ClerkUser[],
34
+ ) {
35
+ const clerkMap = new Map(clerkUsers.map((u) => [u.id, u]));
36
+
37
+ const expectedAccounts: ExpectedAccount[] = [];
38
+ const expectedTwoFactorUsers = new Set<string>();
39
+ let passwordEnabledWithoutDigest = 0;
40
+ const passwordEnabledWithoutDigestSamples: string[] = [];
41
+
42
+ for (const user of csvUsers) {
43
+ const clerkUser = clerkMap.get(user.id);
44
+ const externalAccounts = clerkUser?.external_accounts as ClerkExternalAccount[] | undefined;
45
+
46
+ if (externalAccounts) {
47
+ for (const external of externalAccounts) {
48
+ expectedAccounts.push({
49
+ accountId: external.provider_user_id,
50
+ providerId: providerIdFromExternal(external),
51
+ scope: external.approved_scopes?.replace(/\s+/g, ','),
52
+ userId: user.id,
53
+ });
54
+ }
55
+ }
56
+
57
+ const passwordEnabled = Boolean(clerkUser?.password_enabled);
58
+ if (passwordEnabled && user.password_digest) {
59
+ expectedAccounts.push({
60
+ accountId: user.id,
61
+ providerId: 'credential',
62
+ scope: undefined,
63
+ userId: user.id,
64
+ });
65
+ } else if (passwordEnabled && !user.password_digest) {
66
+ passwordEnabledWithoutDigest += 1;
67
+ if (passwordEnabledWithoutDigestSamples.length < MAX_SAMPLES) {
68
+ passwordEnabledWithoutDigestSamples.push(user.id);
69
+ }
70
+ }
71
+
72
+ if (user.totp_secret) {
73
+ expectedTwoFactorUsers.add(user.id);
74
+ }
75
+ }
76
+
77
+ return {
78
+ expectedAccounts,
79
+ expectedTwoFactorUsers,
80
+ passwordEnabledWithoutDigest,
81
+ passwordEnabledWithoutDigestSamples,
82
+ };
83
+ }
84
+
85
+ function buildAccountKey(account: {
86
+ accountId?: string | null;
87
+ providerId: string;
88
+ userId: string;
89
+ }) {
90
+ return `${account.userId}__${account.providerId}__${account.accountId ?? ''}`;
91
+ }
92
+
93
+ async function loadActualAccounts() {
94
+ const rows = await db
95
+ .select({
96
+ accountId: schema.account.accountId,
97
+ providerId: schema.account.providerId,
98
+ scope: schema.account.scope,
99
+ userId: schema.account.userId,
100
+ })
101
+ .from(schema.account);
102
+
103
+ return rows as ActualAccount[];
104
+ }
105
+
106
+ async function loadActualTwoFactorUserIds() {
107
+ const rows = await db.select({ userId: schema.twoFactor.userId }).from(schema.twoFactor);
108
+ return new Set(rows.map((row) => row.userId));
109
+ }
110
+
111
+ async function loadActualUserIds() {
112
+ const rows = await db.select({ id: schema.users.id }).from(schema.users);
113
+ return new Set(rows.map((row) => row.id));
114
+ }
115
+
116
+ async function main() {
117
+ const startedAt = Date.now();
118
+ const mode = getMigrationMode();
119
+ const { clerkCsvPath, clerkUsersPath } = resolveDataPaths(mode);
120
+
121
+ console.log('');
122
+ console.log('╔════════════════════════════════════════════════════════════╗');
123
+ console.log('║ Migration Verification Script ║');
124
+ console.log('╠════════════════════════════════════════════════════════════╣');
125
+ console.log(`║ Mode: ${mode.padEnd(48)}║`);
126
+ console.log(`║ CSV: ${clerkCsvPath.padEnd(48)}║`);
127
+ console.log(`║ JSON: ${clerkUsersPath.padEnd(48)}║`);
128
+ console.log('╚════════════════════════════════════════════════════════════╝');
129
+ console.log('');
130
+
131
+ const [csvUsers, clerkUsers] = await Promise.all([
132
+ loadCSVData(clerkCsvPath),
133
+ loadClerkUsersFromFile(clerkUsersPath),
134
+ ]);
135
+
136
+ console.log(
137
+ `📦 [verify] Loaded csvUsers=${csvUsers.length}, clerkUsers=${clerkUsers.length} (unique ids=${
138
+ new Set(clerkUsers.map((u) => u.id)).size
139
+ })`,
140
+ );
141
+
142
+ const {
143
+ expectedAccounts,
144
+ expectedTwoFactorUsers,
145
+ passwordEnabledWithoutDigest,
146
+ passwordEnabledWithoutDigestSamples,
147
+ } = buildExpectedAccounts(csvUsers, clerkUsers);
148
+
149
+ console.log(
150
+ `🧮 [verify] Expected accounts=${expectedAccounts.length}, expected 2FA users=${expectedTwoFactorUsers.size}, passwordEnabledWithoutDigest=${passwordEnabledWithoutDigest} sample=${
151
+ passwordEnabledWithoutDigestSamples.join(', ') || 'n/a'
152
+ }`,
153
+ );
154
+
155
+ const [actualAccounts, actualTwoFactorUserIds, actualUserIds] = await Promise.all([
156
+ loadActualAccounts(),
157
+ loadActualTwoFactorUserIds(),
158
+ loadActualUserIds(),
159
+ ]);
160
+
161
+ console.log(
162
+ `🗄️ [verify] DB snapshot: users=${actualUserIds.size}, accounts=${actualAccounts.length}, twoFactor=${actualTwoFactorUserIds.size}`,
163
+ );
164
+
165
+ let missingUsers = 0;
166
+ const missingUserSamples: string[] = [];
167
+ for (const user of csvUsers) {
168
+ if (!actualUserIds.has(user.id)) {
169
+ missingUsers += 1;
170
+ if (missingUserSamples.length < MAX_SAMPLES) missingUserSamples.push(user.id);
171
+ }
172
+ }
173
+
174
+ const expectedAccountSet = new Set(expectedAccounts.map(buildAccountKey));
175
+ const actualAccountSet = new Set(actualAccounts.map(buildAccountKey));
176
+
177
+ let missingAccounts = 0;
178
+ const missingAccountSamples: string[] = [];
179
+ for (const account of expectedAccounts) {
180
+ const key = buildAccountKey(account);
181
+ if (!actualAccountSet.has(key)) {
182
+ missingAccounts += 1;
183
+ if (missingAccountSamples.length < MAX_SAMPLES) missingAccountSamples.push(account.userId);
184
+ }
185
+ }
186
+
187
+ let unexpectedAccounts = 0;
188
+ const unexpectedAccountSamples: string[] = [];
189
+ for (const account of actualAccounts) {
190
+ const key = buildAccountKey(account);
191
+ if (!expectedAccountSet.has(key)) {
192
+ unexpectedAccounts += 1;
193
+ if (unexpectedAccountSamples.length < MAX_SAMPLES)
194
+ unexpectedAccountSamples.push(account.userId);
195
+ }
196
+ }
197
+
198
+ let missingTwoFactor = 0;
199
+ const missingTwoFactorSamples: string[] = [];
200
+ for (const userId of expectedTwoFactorUsers) {
201
+ if (!actualTwoFactorUserIds.has(userId)) {
202
+ missingTwoFactor += 1;
203
+ if (missingTwoFactorSamples.length < MAX_SAMPLES) missingTwoFactorSamples.push(userId);
204
+ }
205
+ }
206
+
207
+ let missingScopeNonCredential = 0;
208
+ let missingAccountIdNonCredential = 0;
209
+ const sampleMissingScope: string[] = [];
210
+ const sampleMissingAccountId: string[] = [];
211
+
212
+ for (const account of actualAccounts) {
213
+ if (account.providerId !== 'credential') {
214
+ if (!account.scope) {
215
+ missingScopeNonCredential += 1;
216
+ if (sampleMissingScope.length < MAX_SAMPLES) sampleMissingScope.push(account.userId);
217
+ }
218
+ if (!account.accountId) {
219
+ missingAccountIdNonCredential += 1;
220
+ if (sampleMissingAccountId.length < MAX_SAMPLES)
221
+ sampleMissingAccountId.push(account.userId);
222
+ }
223
+ }
224
+ }
225
+
226
+ const expectedProviderCounts: Record<string, number> = {};
227
+ const actualProviderCounts: Record<string, number> = {};
228
+
229
+ for (const account of expectedAccounts) {
230
+ expectedProviderCounts[account.providerId] =
231
+ (expectedProviderCounts[account.providerId] ?? 0) + 1;
232
+ }
233
+
234
+ for (const account of actualAccounts) {
235
+ actualProviderCounts[account.providerId] = (actualProviderCounts[account.providerId] ?? 0) + 1;
236
+ }
237
+
238
+ const formatCounts = (counts: Record<string, number>) =>
239
+ Object.entries(counts)
240
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
241
+ .map(([providerId, count]) => `${providerId}=${count}`)
242
+ .join(', ');
243
+
244
+ console.log(
245
+ `📊 [verify] Expected provider counts: ${formatCounts(expectedProviderCounts) || 'n/a'}`,
246
+ );
247
+ console.log(
248
+ `📊 [verify] Actual provider counts: ${formatCounts(actualProviderCounts) || 'n/a'}`,
249
+ );
250
+
251
+ console.log(
252
+ `✅ [verify] Missing users=${missingUsers} sample=${missingUserSamples.join(', ') || 'n/a'}, missing accounts=${missingAccounts} sample=${missingAccountSamples.join(', ') || 'n/a'}, unexpected accounts=${unexpectedAccounts} sample=${unexpectedAccountSamples.join(', ') || 'n/a'}`,
253
+ );
254
+
255
+ console.log(
256
+ `🔐 [verify] Two-factor missing=${missingTwoFactor} sample=${missingTwoFactorSamples.join(', ') || 'n/a'}`,
257
+ );
258
+
259
+ console.log(
260
+ `⚠️ [verify] Non-credential missing scope=${missingScopeNonCredential} sample=${sampleMissingScope.join(', ') || 'n/a'}, missing account_id=${missingAccountIdNonCredential} sample=${sampleMissingAccountId.join(', ') || 'n/a'}`,
261
+ );
262
+
263
+ console.log('');
264
+ console.log(`✅ Verification success! (${formatDuration(Date.now() - startedAt)})`);
265
+ }
266
+
267
+ void main()
268
+ .catch((error) => {
269
+ console.log('');
270
+ console.error('❌ Verification failed:', error);
271
+ process.exitCode = 1;
272
+ })
273
+ .finally(async () => {
274
+ await pool.end();
275
+ });
@@ -1,7 +1,8 @@
1
1
  import { BRANDING_NAME } from '@lobechat/business-const';
2
- import { Button, Flexbox, Icon, Input, Skeleton, Text } from '@lobehub/ui';
2
+ import { Alert, Button, Flexbox, Icon, Input, Skeleton, Text } from '@lobehub/ui';
3
3
  import { Divider, Form } from 'antd';
4
4
  import type { FormInstance, InputRef } from 'antd';
5
+ import { createStaticStyles } from 'antd-style';
5
6
  import { ChevronRight, Mail } from 'lucide-react';
6
7
  import { useEffect, useRef } from 'react';
7
8
  import { Trans, useTranslation } from 'react-i18next';
@@ -11,14 +12,24 @@ import { PRIVACY_URL, TERMS_URL } from '@/const/url';
11
12
 
12
13
  import AuthCard from '../../../../features/AuthCard';
13
14
 
15
+ const styles = createStaticStyles(({ css, cssVar }) => ({
16
+ setPasswordLink: css`
17
+ cursor: pointer;
18
+ color: ${cssVar.colorPrimary};
19
+ text-decoration: underline;
20
+ `,
21
+ }));
22
+
14
23
  export const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
15
24
  export const USERNAME_REGEX = /^\w+$/;
16
25
 
17
26
  export interface SignInEmailStepProps {
18
27
  form: FormInstance<{ email: string }>;
28
+ isSocialOnly: boolean;
19
29
  loading: boolean;
20
30
  oAuthSSOProviders: string[];
21
31
  onCheckUser: (values: { email: string }) => Promise<void>;
32
+ onSetPassword: () => void;
22
33
  onSocialSignIn: (provider: string) => void;
23
34
  serverConfigInit: boolean;
24
35
  socialLoading: string | null;
@@ -26,11 +37,13 @@ export interface SignInEmailStepProps {
26
37
 
27
38
  export const SignInEmailStep = ({
28
39
  form,
40
+ isSocialOnly,
29
41
  loading,
30
42
  oAuthSSOProviders,
31
43
  serverConfigInit,
32
44
  socialLoading,
33
45
  onCheckUser,
46
+ onSetPassword,
34
47
  onSocialSignIn,
35
48
  }: SignInEmailStepProps) => {
36
49
  const { t } = useTranslation('auth');
@@ -88,7 +101,7 @@ export const SignInEmailStep = ({
88
101
  <AuthCard
89
102
  footer={footer}
90
103
  subtitle={t('signin.subtitle', { appName: BRANDING_NAME })}
91
- title={t('signin.title')}
104
+ title={'Agent teams that grow with you'}
92
105
  >
93
106
  {!serverConfigInit && (
94
107
  <Flexbox gap={12}>
@@ -172,6 +185,21 @@ export const SignInEmailStep = ({
172
185
  />
173
186
  </Form.Item>
174
187
  </Form>
188
+ {isSocialOnly && (
189
+ <Alert
190
+ description={
191
+ <>
192
+ {t('betterAuth.signin.socialOnlyHint')}{' '}
193
+ <a className={styles.setPasswordLink} onClick={onSetPassword}>
194
+ {t('betterAuth.signin.setPassword')}
195
+ </a>
196
+ </>
197
+ }
198
+ showIcon
199
+ style={{ marginTop: 12 }}
200
+ type="info"
201
+ />
202
+ )}
175
203
  </AuthCard>
176
204
  );
177
205
  };
@@ -55,7 +55,7 @@ export const SignInPasswordStep = ({
55
55
  </>
56
56
  }
57
57
  subtitle={t('betterAuth.signin.passwordStep.subtitle')}
58
- title={t('signin.title')}
58
+ title={'Agent teams that grow with you'}
59
59
  >
60
60
  <Text fontSize={20}>{email}</Text>
61
61
  <Form
@@ -17,6 +17,7 @@ const SignInPage = () => {
17
17
  handleForgotPassword,
18
18
  handleSignIn,
19
19
  handleSocialSignIn,
20
+ isSocialOnly,
20
21
  loading,
21
22
  oAuthSSOProviders,
22
23
  serverConfigInit,
@@ -29,9 +30,11 @@ const SignInPage = () => {
29
30
  {step === 'email' ? (
30
31
  <SignInEmailStep
31
32
  form={form as any}
33
+ isSocialOnly={isSocialOnly}
32
34
  loading={loading}
33
35
  oAuthSSOProviders={oAuthSSOProviders}
34
36
  onCheckUser={handleCheckUser}
37
+ onSetPassword={handleForgotPassword}
35
38
  onSocialSignIn={handleSocialSignIn}
36
39
  serverConfigInit={serverConfigInit}
37
40
  socialLoading={socialLoading}
@@ -1,6 +1,5 @@
1
1
  import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
2
2
  import { Form } from 'antd';
3
- import { useRouter, useSearchParams } from '@/libs/next/navigation';
4
3
  import { useEffect, useState } from 'react';
5
4
  import { useTranslation } from 'react-i18next';
6
5
 
@@ -10,6 +9,7 @@ import { useBusinessSignin } from '@/business/client/hooks/useBusinessSignin';
10
9
  import { message } from '@/components/AntdStaticMethods';
11
10
  import { requestPasswordReset, signIn } from '@/libs/better-auth/auth-client';
12
11
  import { isBuiltinProvider, normalizeProviderId } from '@/libs/better-auth/utils/client';
12
+ import { useRouter, useSearchParams } from '@/libs/next/navigation';
13
13
  import { useServerConfigStore } from '@/store/serverConfig';
14
14
  import { serverConfigSelectors } from '@/store/serverConfig/selectors';
15
15
 
@@ -37,6 +37,7 @@ export const useSignIn = () => {
37
37
  const [socialLoading, setSocialLoading] = useState<string | null>(null);
38
38
  const [step, setStep] = useState<Step>('email');
39
39
  const [email, setEmail] = useState('');
40
+ const [isSocialOnly, setIsSocialOnly] = useState(false);
40
41
  const serverConfigInit = useServerConfigStore((s) => s.serverConfigInit);
41
42
  const oAuthSSOProviders = useServerConfigStore((s) => s.serverConfig.oAuthSSOProviders) || [];
42
43
  const { ssoProviders, preSocialSigninCheck, getAdditionalData } = useBusinessSignin();
@@ -142,7 +143,8 @@ export const useSignIn = () => {
142
143
  return;
143
144
  }
144
145
 
145
- message.info(t('betterAuth.signin.socialOnlyHint'));
146
+ // User has no password and magic link is disabled, they can only sign in via social
147
+ setIsSocialOnly(true);
146
148
  } catch (error) {
147
149
  console.error('Error checking user:', error);
148
150
  message.error(t('betterAuth.signin.error'));
@@ -215,6 +217,7 @@ export const useSignIn = () => {
215
217
  const handleBackToEmail = () => {
216
218
  setStep('email');
217
219
  setEmail('');
220
+ setIsSocialOnly(false);
218
221
  };
219
222
 
220
223
  const handleGoToSignup = () => {
@@ -247,6 +250,7 @@ export const useSignIn = () => {
247
250
  handleGoToSignup,
248
251
  handleSignIn,
249
252
  handleSocialSignIn,
253
+ isSocialOnly,
250
254
  loading,
251
255
  oAuthSSOProviders: ENABLE_BUSINESS_FEATURES ? ssoProviders : oAuthSSOProviders,
252
256
  serverConfigInit: ENABLE_BUSINESS_FEATURES ? true : serverConfigInit,
@@ -40,7 +40,7 @@ const RecentResourceItem = memo<RecentResourceItemProps>(({ file }) => {
40
40
  >
41
41
  <Center
42
42
  flex={'none'}
43
- height={126}
43
+ height={148}
44
44
  style={{ background: cssVar.colorFillTertiary, overflow: 'hidden' }}
45
45
  >
46
46
  {isImage && file.url ? (
@@ -63,7 +63,7 @@ const RecentResourceItem = memo<RecentResourceItemProps>(({ file }) => {
63
63
 
64
64
  {/* File Info */}
65
65
  <Flexbox flex={1} gap={6} justify={'space-between'} padding={12}>
66
- <Text ellipsis={{ rows: 2 }} fontSize={13} style={{ lineHeight: 1.4 }} weight={500}>
66
+ <Text ellipsis fontSize={13} style={{ lineHeight: 1.4 }} weight={500}>
67
67
  {file.name}
68
68
  </Text>
69
69
  <Flexbox align={'center'} gap={8} horizontal>
@@ -8,7 +8,6 @@ import { useUserStore } from '@/store/user';
8
8
  import { authSelectors } from '@/store/user/slices/auth/selectors';
9
9
 
10
10
  import CommunityAgents from './CommunityAgents';
11
- import FeaturedPlugins from './FeaturedPlugins';
12
11
  import InputArea from './InputArea';
13
12
  import RecentPage from './RecentPage';
14
13
  import RecentResource from './RecentResource';
@@ -32,7 +31,7 @@ const Home = memo(() => {
32
31
  </>
33
32
  )}
34
33
  <CommunityAgents />
35
- <FeaturedPlugins />
34
+ {/*<FeaturedPlugins />*/}
36
35
  {isLogin && <RecentResource />}
37
36
  </Flexbox>
38
37
  );
@@ -62,8 +62,6 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
62
62
  form: css`
63
63
  .${prefixCls}-form-item-control:has(.${prefixCls}-input,.${prefixCls}-select) {
64
64
  flex: none;
65
- width: min(70%, 800px);
66
- min-width: min(70%, 800px) !important;
67
65
  }
68
66
  ${responsive.sm} {
69
67
  width: 100%;
@@ -1,12 +1,11 @@
1
- import { ActionIcon, Button, DropdownMenu, Flexbox, Icon } from '@lobehub/ui';
2
- import { App } from 'antd';
3
- import { MoreVerticalIcon, Trash2 } from 'lucide-react';
1
+ import { Button, DropdownMenu, Flexbox, Icon } from '@lobehub/ui';
2
+ import { App, Space } from 'antd';
3
+ import { MoreHorizontalIcon, Trash2 } from 'lucide-react';
4
4
  import { memo, useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
7
  import McpSettingsModal from '@/features/MCP/MCPSettings/McpSettingsModal';
8
8
  import PluginDetailModal from '@/features/PluginDetailModal';
9
- import EditCustomPlugin from './EditCustomPlugin';
10
9
  import { useAgentStore } from '@/store/agent';
11
10
  import { agentSelectors } from '@/store/agent/selectors';
12
11
  import { useServerConfigStore } from '@/store/serverConfig';
@@ -14,6 +13,8 @@ import { pluginHelpers, useToolStore } from '@/store/tool';
14
13
  import { pluginSelectors, pluginStoreSelectors } from '@/store/tool/selectors';
15
14
  import { type LobeToolType } from '@/types/tool/tool';
16
15
 
16
+ import EditCustomPlugin from './EditCustomPlugin';
17
+
17
18
  interface ActionsProps {
18
19
  identifier: string;
19
20
  isMCP?: boolean;
@@ -73,7 +74,7 @@ const Actions = memo<ActionsProps>(({ identifier, type, isMCP }) => {
73
74
  <>
74
75
  <Flexbox align={'center'} gap={8} horizontal onClick={(e) => e.stopPropagation()}>
75
76
  {installed ? (
76
- <>
77
+ <Space.Compact>
77
78
  {showConfigureButton &&
78
79
  (isCustomPlugin ? (
79
80
  <EditCustomPlugin identifier={identifier} onOpenChange={setModal} open={showModal}>
@@ -108,9 +109,9 @@ const Actions = memo<ActionsProps>(({ identifier, type, isMCP }) => {
108
109
  ]}
109
110
  placement="bottomRight"
110
111
  >
111
- <ActionIcon icon={MoreVerticalIcon} loading={installing} />
112
+ <Button icon={MoreHorizontalIcon} loading={installing} />
112
113
  </DropdownMenu>
113
- </>
114
+ </Space.Compact>
114
115
  ) : (
115
116
  <Button
116
117
  loading={installing}
@@ -1,19 +1,20 @@
1
1
  'use client';
2
2
 
3
- import { Flexbox, Modal } from '@lobehub/ui';
3
+ import { Block, Flexbox, Modal } from '@lobehub/ui';
4
4
  import { createStyles } from 'antd-style';
5
5
  import { memo, useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
8
  import PluginAvatar from '@/components/Plugins/PluginAvatar';
9
9
  import PluginTag from '@/components/Plugins/PluginTag';
10
- import PluginDetailModal from '@/features/PluginDetailModal';
11
- import Actions from './Actions';
12
10
  import McpDetail from '@/features/MCP/MCPDetail';
11
+ import PluginDetailModal from '@/features/PluginDetailModal';
13
12
  import { useToolStore } from '@/store/tool';
14
13
  import { pluginSelectors } from '@/store/tool/selectors';
15
14
  import { type LobeToolType } from '@/types/tool/tool';
16
15
 
16
+ import Actions from './Actions';
17
+
17
18
  const useStyles = createStyles(({ css, token }) => ({
18
19
  container: css`
19
20
  padding-block: 12px;
@@ -25,11 +26,8 @@ const useStyles = createStyles(({ css, token }) => ({
25
26
  align-items: center;
26
27
  justify-content: center;
27
28
 
28
- width: 48px;
29
- height: 48px;
30
- border-radius: 12px;
31
-
32
- background: ${token.colorFillTertiary};
29
+ width: 40px;
30
+ height: 40px;
33
31
  `,
34
32
  title: css`
35
33
  cursor: pointer;
@@ -72,10 +70,10 @@ const McpSkillItem = memo<McpSkillItemProps>(
72
70
  horizontal
73
71
  justify="space-between"
74
72
  >
75
- <Flexbox align="center" gap={16} horizontal style={{ flex: 1, overflow: 'hidden' }}>
76
- <div className={styles.icon}>
73
+ <Flexbox align="center" gap={12} horizontal style={{ flex: 1, overflow: 'hidden' }}>
74
+ <Block className={styles.icon} variant={'outlined'}>
77
75
  <PluginAvatar avatar={avatar} size={32} />
78
- </div>
76
+ </Block>
79
77
  <Flexbox align="center" gap={8} horizontal style={{ overflow: 'hidden' }}>
80
78
  <span className={styles.title} onClick={() => setDetailOpen(true)}>
81
79
  {title}
@@ -5,7 +5,7 @@ const manifest = async (): Promise<MetadataRoute.Manifest> => {
5
5
  if (process.env.NODE_ENV === 'development') {
6
6
  return {
7
7
  background_color: '#000000',
8
- description: 'LobeChat Development',
8
+ description: 'LobeHub Development',
9
9
  display: 'standalone',
10
10
  icons: [
11
11
  {
@@ -14,8 +14,8 @@ const manifest = async (): Promise<MetadataRoute.Manifest> => {
14
14
  type: 'image/png',
15
15
  },
16
16
  ],
17
- name: 'LobeChat',
18
- short_name: 'LobeChat',
17
+ name: 'LobeHub',
18
+ short_name: 'LobeHub',
19
19
  start_url: '/',
20
20
  theme_color: '#000000',
21
21
  };
@@ -30,7 +30,7 @@ const manifest = async (): Promise<MetadataRoute.Manifest> => {
30
30
 
31
31
  // @ts-expect-error - manifestModule.generate returns extended manifest with custom properties
32
32
  return manifestModule.generate({
33
- description: `${BRANDING_NAME} brings you the best UI experience for ChatGPT, Claude, Gemini, and OLLaMA.`,
33
+ description: `${BRANDING_NAME} is a work-and-lifestyle space to find, build, and collaborate with agent teams that grow with you.`,
34
34
  icons: [
35
35
  {
36
36
  purpose: 'any',
@@ -11,7 +11,7 @@ export interface AuthCardProps extends Omit<FlexboxProps, 'title'> {
11
11
 
12
12
  export const AuthCard = memo<AuthCardProps>(({ children, title, subtitle, footer, ...rest }) => {
13
13
  return (
14
- <Flexbox width={'min(100%,400px)'} {...rest}>
14
+ <Flexbox width={'min(100%,440px)'} {...rest}>
15
15
  <Flexbox gap={16}>
16
16
  {title && (
17
17
  <Text fontSize={28} style={{ lineHeight: 1.4 }} weight={'bold'}>
@@ -8,8 +8,8 @@ import React, { memo, useState } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
9
 
10
10
  import PluginAvatar from '@/components/Plugins/PluginAvatar';
11
- import MCPInstallProgress from '@/features/MCP/MCPInstallProgress';
12
11
  import McpDetail from '@/features/MCP/MCPDetail';
12
+ import MCPInstallProgress from '@/features/MCP/MCPInstallProgress';
13
13
  import { useMarketAuth } from '@/layout/AuthProvider/MarketAuth';
14
14
  import { useAgentStore } from '@/store/agent';
15
15
  import { agentSelectors } from '@/store/agent/selectors';
@@ -117,13 +117,14 @@ const Item = memo<DiscoverMcpItem>(({ name, description, icon, identifier }) =>
117
117
  <Flexbox className={styles.container} gap={0}>
118
118
  <Block
119
119
  align={'center'}
120
+ clickable
120
121
  gap={12}
121
122
  horizontal
122
123
  onClick={() => setDetailOpen(true)}
123
124
  paddingBlock={12}
124
125
  paddingInline={12}
125
126
  style={{ cursor: 'pointer' }}
126
- variant={'filled'}
127
+ variant={'outlined'}
127
128
  >
128
129
  <PluginAvatar avatar={icon} size={40} />
129
130
  <Flexbox flex={1} gap={4} style={{ minWidth: 0, overflow: 'hidden' }}>
@@ -33,7 +33,6 @@ export const Search = memo<SearchProps>(({ activeTab, onLobeHubSearch }) => {
33
33
  }
34
34
  }}
35
35
  placeholder={t('skillStore.search')}
36
- variant={'borderless'}
37
36
  />
38
37
  </Flexbox>
39
38
  </Flexbox>
@@ -97,10 +97,11 @@ export default {
97
97
  'betterAuth.signin.orContinueWith': 'OR',
98
98
  'betterAuth.signin.passwordPlaceholder': 'Enter your password',
99
99
  'betterAuth.signin.passwordStep.subtitle': 'Enter your password to continue',
100
+ 'betterAuth.signin.setPassword': 'set a password',
100
101
  'betterAuth.signin.signupLink': 'Sign up now',
101
102
  'betterAuth.signin.socialError': 'Social sign in failed, please try again',
102
103
  'betterAuth.signin.socialOnlyHint':
103
- 'This email was registered using a social account. Please sign in using the corresponding social provider.',
104
+ 'This email was registered via a third-party social account. Sign in with that provider, or',
104
105
  'betterAuth.signin.submit': 'Sign In',
105
106
  'betterAuth.signup.confirmPasswordPlaceholder': 'Confirm your password',
106
107
  'betterAuth.signup.emailPlaceholder': 'Enter your email address',
@@ -200,7 +201,7 @@ export default {
200
201
  'profile.usernameRule': 'Username can only contain letters, numbers, or underscores',
201
202
  'profile.usernameUpdateFailed': 'Failed to update username, please try again later',
202
203
  'signin.subtitle': 'Sign up or log in to your {{appName}} account',
203
- 'signin.title': 'Where Agents Collaborate',
204
+ 'signin.title': 'Agent teams that grow with you',
204
205
  'signout': 'Log Out',
205
206
  'signup': 'Sign Up',
206
207
  'stats.aiheatmaps': 'Activity Index',