diff --git a/.gitignore b/.gitignore index 5d42b0d..6d8608f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,11 +11,12 @@ node_modules dist dist-ssr server/dist +packages/server/dist *.local ROADMAP.md # Server data -server/data/ -server/node_modules/ +packages/server/data/ +packages/server/node_modules/ .hermes-web-ui/ # Editor directories and files diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..10f833a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,452 @@ +# CLAUDE.md — Hermes Web UI Development Guide + +## Project Overview + +Hermes Web UI is a web dashboard for [Hermes Agent](https://github.com/EKKOLearnAI/hermes-web-ui), a multi-platform AI chat system. It provides session management, scheduled jobs, usage analytics, model configuration, channel management (Telegram, Discord, Slack, WhatsApp, etc.), an integrated terminal, and a streaming chat interface. + +The project is designed for **multi-agent extensibility** — Hermes is the first agent integration. All agent-specific code is namespaced under `hermes/` directories, so future agents can be added alongside without conflicts. + +**Tech stack:** + +- **Frontend:** Vue 3 (Composition API, ` + + + + +``` + +Key patterns: +- Import Naive UI components directly from `naive-ui` +- Use `useMessage()` for toast notifications +- Use `useI18n()` for translations, access via `t('key.path')` +- Scoped SCSS with `@use '@/styles/variables' as *` + +### Pinia Stores + +Use setup store syntax (function passed to `defineStore`): + +```ts +import { defineStore } from 'pinia' +import { ref } from 'vue' + +export const useMyStore = defineStore('myStore', () => { + const items = ref([]) + const loading = ref(false) + + async function fetchItems() { + loading.value = true + try { + items.value = await apiCall() + } finally { + loading.value = false + } + } + + return { items, loading, fetchItems } +}) +``` + +Existing stores in `packages/client/src/stores/hermes/`: `app`, `chat`, `jobs`, `models`, `settings`, `usage`. + +### API Layer + +Agent-specific API modules live in `api/{agent}/`. The shared base `api/client.ts` provides: + +- `request(path, options)` — typed fetch wrapper with automatic `Authorization: Bearer` header and global 401 handling (clears token, redirects to login) +- `getApiKey()` / `setApiKey()` / `clearApiKey()` — token management via `localStorage` +- `getBaseUrlValue()` — configurable server URL from `localStorage` + +```ts +// packages/client/src/api/hermes/sessions.ts +import { request } from '../client' + +export async function fetchSessions(source?: string, limit?: number): Promise { + const params = new URLSearchParams() + if (source) params.set('source', source) + if (limit) params.set('limit', String(limit)) + const query = params.toString() + const res = await request<{ sessions: SessionSummary[] }>(`/api/hermes/sessions${query ? `?${query}` : ''}`) + return res.sessions +} +``` + +**API path rules:** +- Local BFF endpoints: `/api/hermes/{resource}` — handled by Koa routes, call Hermes CLI directly +- Gateway proxy endpoints: `/api/hermes/v1/*`, `/api/hermes/jobs/*` — forwarded to upstream Hermes gateway +- Shared endpoints: `/health`, `/upload`, `/webhook` — no agent prefix + +### i18n + +Two locales: `en.ts` and `zh.ts` in `packages/client/src/i18n/locales/`. Flat nested object structure organized by feature section: + +```ts +// en.ts +export default { + chat: { + emptyState: 'Start a conversation with Hermes Agent', + inputPlaceholder: 'Type a message...', + sessions: 'Sessions', + // ... + }, + common: { + save: 'Save', + cancel: 'Cancel', + delete: 'Delete', + // ... + }, +} +``` + +When adding new strings, always add to both `en.ts` and `zh.ts`. + +### SCSS Styling + +- Global variables in `packages/client/src/styles/variables.scss` — import with `@use '@/styles/variables' as *` +- Theme: "Pure Ink" (monochrome black/white/gray), no color accent +- Mobile breakpoint: `$breakpoint-mobile: 768px` +- Global resets and shared classes in `packages/client/src/styles/global.scss` +- Component styles are always `