diff --git a/electron-builder.yml b/electron-builder.yml index f80ba20..59780ea 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -1,6 +1,6 @@ -appId: app.clawx.desktop -productName: ClawX -copyright: Copyright © 2026 ClawX +appId: app.cat89.desktop +productName: 89猫 +copyright: Copyright © 2026 89猫 compression: maximum artifactName: ${productName}-${version}-${os}-${arch}.${ext} @@ -59,7 +59,7 @@ publish: useMultipleRangeRequest: false - provider: github owner: ValueCell-ai - repo: ClawX + repo: 89猫 # macOS Configuration mac: @@ -86,8 +86,8 @@ mac: entitlementsInherit: entitlements.mac.plist notarize: true extendInfo: - NSMicrophoneUsageDescription: ClawX requires microphone access for voice features - NSCameraUsageDescription: ClawX requires camera access for video features + NSMicrophoneUsageDescription: 89猫 requires microphone access for voice features + NSCameraUsageDescription: 89猫 requires camera access for video features dmg: # Explicit volume size prevents dmg-builder@1.2.0 auto-calculation from @@ -133,8 +133,8 @@ nsis: differentialPackage: true createDesktopShortcut: true createStartMenuShortcut: true - shortcutName: ClawX - uninstallDisplayName: ClawX + shortcutName: 89猫 + uninstallDisplayName: 89猫 license: LICENSE include: scripts/installer.nsh installerIcon: resources/icons/icon.ico @@ -161,13 +161,13 @@ linux: arch: - x64 category: Utility - maintainer: ClawX Team - vendor: ClawX + maintainer: 89猫 Team + vendor: 89猫 synopsis: AI Assistant powered by OpenClaw - description: ClawX is a graphical AI assistant application that integrates with OpenClaw Gateway to provide intelligent automation and assistance across multiple messaging platforms. + description: 89猫 is a graphical AI assistant application that integrates with OpenClaw Gateway to provide intelligent automation and assistance across multiple messaging platforms. desktop: entry: - Name: ClawX + Name: 89猫 Comment: AI Assistant powered by OpenClaw Categories: Utility;Network; Keywords: ai;assistant;automation;chat; diff --git a/electron/api/routes/providers.ts b/electron/api/routes/providers.ts index 99c3074..5b20866 100644 --- a/electron/api/routes/providers.ts +++ b/electron/api/routes/providers.ts @@ -9,6 +9,7 @@ import { deviceOAuthManager, type OAuthProviderType } from '../../utils/device-o import { browserOAuthManager, type BrowserOAuthProviderType } from '../../utils/browser-oauth'; import type { HostApiContext } from '../context'; import { parseJsonBody, sendJson } from '../route-utils'; +import { getFastestBaseUrl } from '../../services/port-pinger'; import { syncDefaultProviderToRuntime, syncDeletedProviderApiKeyToRuntime, @@ -106,6 +107,22 @@ export async function handleProviderRoutes( return true; } + if (url.pathname === '/api/cat89/validate-key' && req.method === 'POST') { + try { + const body = await parseJsonBody<{ apiKey: string }>(req); + const baseUrl = await getFastestBaseUrl(); + if (!baseUrl) { + sendJson(res, 200, { valid: false, message: '无法连接到89猫服务器,请检查网络' }); + return true; + } + const result = await validateApiKeyWithProvider('cat89', body.apiKey, { baseUrl }); + sendJson(res, 200, result); + } catch (error) { + sendJson(res, 500, { valid: false, message: String(error) }); + } + return true; + } + if (url.pathname === '/api/provider-accounts/validate' && req.method === 'POST') { try { // Accept legacy `providerId` as a fallback so external clients that diff --git a/electron/main/index.ts b/electron/main/index.ts index 1fe590b..b2cf8db 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -54,7 +54,7 @@ import { browserOAuthManager } from '../utils/browser-oauth'; import { whatsAppLoginManager } from '../utils/whatsapp-login'; import { syncAllProviderAuthToRuntime } from '../services/providers/provider-runtime-sync'; -const WINDOWS_APP_USER_MODEL_ID = 'app.clawx.desktop'; +const WINDOWS_APP_USER_MODEL_ID = 'app.cat89.desktop'; const isE2EMode = process.env.CLAWX_E2E === '1'; const requestedUserDataDir = process.env.CLAWX_USER_DATA_DIR?.trim(); const requestedRemoteDebuggingPort = process.env.CLAWX_REMOTE_DEBUGGING_PORT?.trim(); @@ -88,7 +88,7 @@ app.disableHardwareAcceleration(); // on X11 it supplements the StartupWMClass matching. // Must be called before app.whenReady() / before any window is created. if (process.platform === 'linux') { - app.setDesktopName('clawx.desktop'); + app.setDesktopName('cat89.desktop'); } // Prevent multiple instances of the app from running simultaneously. diff --git a/electron/services/port-pinger.ts b/electron/services/port-pinger.ts new file mode 100644 index 0000000..04c5b87 --- /dev/null +++ b/electron/services/port-pinger.ts @@ -0,0 +1,57 @@ +/** + * Port pinger — TCP latency test for 89cat API endpoints + */ +import * as net from 'node:net'; + +const CAT89_HOST = 'api2.is89.com'; +const CAT89_PORTS = [898902, 898903, 898904, 898905, 898906, 898907, 898908, 898909]; + +export interface PingResult { + port: number; + latencyMs: number; + baseUrl: string; +} + +function pingPort(host: string, port: number, timeoutMs = 3000): Promise { + return new Promise((resolve) => { + const start = Date.now(); + const socket = new net.Socket(); + let settled = false; + + const finish = (ok: boolean) => { + if (settled) return; + settled = true; + socket.destroy(); + if (ok) { + const latencyMs = Date.now() - start; + resolve({ port, latencyMs, baseUrl: `http://${host}:${port}` }); + } else { + resolve(null); + } + }; + + socket.setTimeout(timeoutMs); + socket.on('connect', () => finish(true)); + socket.on('error', () => finish(false)); + socket.on('timeout', () => finish(false)); + + socket.connect(port, host); + }); +} + +export async function pingAllPorts(): Promise { + const results = await Promise.all( + CAT89_PORTS.map((port) => pingPort(CAT89_HOST, port)), + ); + const reachable = results.filter((r): r is PingResult => r !== null); + reachable.sort((a, b) => a.latencyMs - b.latencyMs); + return reachable; +} + +export async function getFastestBaseUrl(): Promise { + const results = await pingAllPorts(); + if (results.length === 0) return null; + return results[0].baseUrl; +} + +export { CAT89_HOST, CAT89_PORTS }; diff --git a/electron/shared/providers/registry.ts b/electron/shared/providers/registry.ts index eabf3ed..5a6f22b 100644 --- a/electron/shared/providers/registry.ts +++ b/electron/shared/providers/registry.ts @@ -364,6 +364,18 @@ export const PROVIDER_DEFINITIONS: ProviderDefinition[] = [ defaultAuthMode: 'api_key', supportsMultipleAccounts: true, }, + { + id: 'cat89', + name: '89猫', + icon: '🐱', + placeholder: '输入你的89猫 Key', + requiresApiKey: true, + category: 'official', + envVar: 'CAT89_API_KEY', + supportedAuthModes: ['api_key'], + defaultAuthMode: 'api_key', + supportsMultipleAccounts: false, + }, ]; const PROVIDER_DEFINITION_MAP = new Map( diff --git a/electron/shared/providers/types.ts b/electron/shared/providers/types.ts index 20c77b2..a0e2c12 100644 --- a/electron/shared/providers/types.ts +++ b/electron/shared/providers/types.ts @@ -12,6 +12,7 @@ export const PROVIDER_TYPES = [ 'modelstudio', 'ollama', 'custom', + 'cat89', ] as const; export const BUILTIN_PROVIDER_TYPES = [ @@ -27,6 +28,7 @@ export const BUILTIN_PROVIDER_TYPES = [ 'minimax-portal-cn', 'modelstudio', 'ollama', + 'cat89', ] as const; export type ProviderType = (typeof PROVIDER_TYPES)[number]; diff --git a/package.json b/package.json index 88dd0b1..8f273e9 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "clawx", + "name": "cat89", "version": "0.4.8-alpha.0", "pnpm": { "onlyBuiltDependencies": [ @@ -25,9 +25,9 @@ ] } }, - "description": "ClawX - Graphical AI Assistant based on OpenClaw", + "description": "89猫 - Graphical AI Assistant based on OpenClaw", "main": "dist-electron/main/index.js", - "author": "ClawX Team", + "author": "89猫 Team", "license": "MIT", "private": true, "scripts": { diff --git a/src/i18n/locales/en/skills.json b/src/i18n/locales/en/skills.json index d60be44..6d72175 100644 --- a/src/i18n/locales/en/skills.json +++ b/src/i18n/locales/en/skills.json @@ -14,7 +14,13 @@ "builtIn": "Built-in ({{count}})", "marketplace": "Marketplace ({{count}})", "enabledList": "Enabled ({{count}})", - "disabledList": "Disabled ({{count}})" + "disabledList": "Disabled ({{count}})", + "category": { + "all": "All", + "cat89": "89 Plugins", + "openclaw": "OpenClaw Skills", + "other": "Other Plugins" + } }, "search": "Search skills...", "searchMarketplace": "Search marketplace...", diff --git a/src/i18n/locales/zh/skills.json b/src/i18n/locales/zh/skills.json index c8efcd9..ec0bbd0 100644 --- a/src/i18n/locales/zh/skills.json +++ b/src/i18n/locales/zh/skills.json @@ -14,7 +14,13 @@ "builtIn": "内置 ({{count}})", "marketplace": "市场 ({{count}})", "enabledList": "已开启 ({{count}})", - "disabledList": "已禁用 ({{count}})" + "disabledList": "已禁用 ({{count}})", + "category": { + "all": "全部", + "cat89": "89插件", + "openclaw": "OpenClaw 技能", + "other": "其他插件" + } }, "search": "搜索技能...", "searchMarketplace": "搜索市场...", diff --git a/src/lib/providers.ts b/src/lib/providers.ts index c2b06f4..bc8a2ac 100644 --- a/src/lib/providers.ts +++ b/src/lib/providers.ts @@ -21,6 +21,7 @@ export const PROVIDER_TYPES = [ 'modelstudio', 'ollama', 'custom', + 'cat89', ] as const; export type ProviderType = (typeof PROVIDER_TYPES)[number]; @@ -196,6 +197,14 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [ docsUrl: 'https://icnnp7d0dymg.feishu.cn/wiki/BmiLwGBcEiloZDkdYnGc8RWnn6d#Ee1ldfvKJoVGvfxc32mcILwenth', docsUrlZh: 'https://icnnp7d0dymg.feishu.cn/wiki/BmiLwGBcEiloZDkdYnGc8RWnn6d#IWQCdfe5fobGU3xf3UGcgbLynGh', }, + { + id: 'cat89', + name: '89猫', + icon: '🐱', + placeholder: '输入你的89猫 Key', + model: '89猫 AI', + requiresApiKey: true, + }, ]; /** Get the SVG logo URL for a provider type, falls back to undefined */ diff --git a/src/pages/Dashboard/index.tsx b/src/pages/Dashboard/index.tsx index 24eb2a7..d8427dc 100644 --- a/src/pages/Dashboard/index.tsx +++ b/src/pages/Dashboard/index.tsx @@ -42,13 +42,10 @@ function ApiKeyCard() { setErrorMsg(''); try { const res = await hostApiFetch<{ valid: boolean; message?: string }>( - '/api/provider-accounts/validate', + '/api/cat89/validate-key', { method: 'POST', - body: JSON.stringify({ - vendorId: 'cat89', - apiKey: trimmed, - }), + body: JSON.stringify({ apiKey: trimmed }), }, ); if (res.valid) { diff --git a/src/pages/Skills/index.tsx b/src/pages/Skills/index.tsx index dd85ad7..6800b4a 100644 --- a/src/pages/Skills/index.tsx +++ b/src/pages/Skills/index.tsx @@ -356,7 +356,11 @@ export function Skills() { || (skill.author || '').toLowerCase().includes(q); const matchesStatus = statusFilter === 'all' || (statusFilter === 'enabled' ? skill.enabled : !skill.enabled); - return matchesSearch && matchesStatus; + const matchesCategory = categoryFilter === 'all' + || (categoryFilter === 'cat89' && ((skill as any).isCat89Exclusive || skill.source === 'cat89-plugin')) + || (categoryFilter === 'openclaw' && (skill.source || '').startsWith('openclaw-')) + || (categoryFilter === 'other' && !(skill.source || '').startsWith('openclaw-') && !((skill as any).isCat89Exclusive) && skill.source !== 'cat89-plugin'); + return matchesSearch && matchesStatus && matchesCategory; }).sort((a, b) => { if (a.enabled && !b.enabled) return -1; if (!a.enabled && b.enabled) return 1; @@ -423,6 +427,7 @@ export function Skills() { } }, [t]); + const [categoryFilter, setCategoryFilter] = useState<'all' | 'cat89' | 'openclaw' | 'other'>('all'); const [skillsDirPath, setSkillsDirPath] = useState('~/.openclaw/skills'); useEffect(() => { @@ -535,6 +540,27 @@ export function Skills() { )} + {/* Category Tabs */} +
+ {(['all', 'cat89', 'openclaw', 'other'] as const).map((cat) => ( + + ))} +
+ {/* Sub Navigation and Actions */}