# AGENTS.md - JRedisDesktop v2 ## Project identity Fork of [AnotherRedisDesktopManager](https://github.com/qishibo/AnotherRedisDesktopManager). The legacy Vue 2 codebase lives in `legacy/` and is gitignored. Work only in the root project. ## Stack - **Shell:** Electron 33 (`src/main/index.ts`) - **Preload:** `src/preload/index.ts` (contextBridge, typed API) - **Renderer:** Vue 3 + Pinia + Element Plus + Vue Router (`src/renderer/src/`) - **IPC types:** `src/preload/index.d.ts` (ElectronAPI interface, global Window augmentation) - **Build:** electron-vite + Vite 6 + TypeScript 5 - **Module system:** `"type": "module"` (ESM) — main outputs `.js`, preload outputs `.mjs` - **Redis client:** ioredis 5 (main process only) - **Persistence:** electron-store + safeStorage (credentials) ## Commands ```bash npm run dev # electron-vite dev (HMR, renderer on :5173) npm run build # production build -> out/ npm start # preview built app npm run build:unpack # build + electron-builder --dir npm run build:win # build + electron-builder --win npm run build:mac # build + electron-builder --mac npm run build:linux # build + electron-builder --linux npm run lint # eslint --fix ``` ## Architecture ``` Electron main (src/main/) ├── index.ts # Window creation, app lifecycle ├── ipc-handlers.ts # all IPC channel handlers ├── store.ts # electron-store (connections, settings) ├── credential.ts # safeStorage encrypt/decrypt ├── win-state.ts # window position persistence ├── updater.ts # electron-updater (check/download/install) └── redis/ # Redis service modules ├── index.ts # Unified export ├── connection.ts # connect, disconnect, isClientConnected ├── keys.ts # scan, delete, rename, ttl, batchDelete ├── string.ts # get, set ├── hash.ts # hscan, hset, hdel ├── list.ts # range, push, set, remove ├── set.ts # members, add, remove ├── zset.ts # range, add, remove ├── stream.ts # info, range, add, trim ├── server.ts # info, select, ping, slowlog ├── pubsub.ts # SUBSCRIBE/UNSUBSCRIBE ├── commandLogger.ts # command execution log └── format.ts # value decode/format detection Preload (src/preload/) ├── index.ts # contextBridge.exposeInMainWorld └── index.d.ts # ElectronAPI type declarations Renderer (src/renderer/) ├── index.html # Vite entry HTML └── src/ ├── main.ts # Vue 3 bootstrap (Pinia, Element Plus) ├── App.vue # Shell layout (TitleBar + Sidebar + MainArea + StatusBar) ├── env.d.ts # Vite client + *.vue / *.css module declarations ├── stores/ │ ├── app.ts # theme, fontSize, scanCount │ ├── connection.ts # connections CRUD, connect/disconnect, health check │ └── key.ts # key list, scan, tree view, selected key data ├── components/ │ ├── TitleBar.vue # window controls │ ├── StatusBar.vue # connection status, DB selector │ ├── Sidebar.vue # keyboard shortcuts, dialog host │ ├── ConnectionList.vue # search, import/export, new button │ ├── ConnectionCard.vue # single connection card │ ├── NewConnectionDialog.vue # create/edit/test connection │ ├── DbSelector.vue # database 0-15 dropdown │ ├── MainArea.vue # tab host (Status/Keys/CLI/SlowLog/Settings) │ ├── StatusView.vue # INFO dashboard with stat cards │ ├── KeyBrowser.vue # split pane (KeyList + KeyDetail) │ ├── KeyList.vue # pattern search, filter, SCAN, multi-select │ ├── KeyDetail.vue # type dispatch, TTL, rename, copy │ ├── StringEditor.vue # view/edit with JSON format │ ├── HashEditor.vue # field table CRUD │ ├── ListEditor.vue # paginated elements │ ├── SetEditor.vue # member list │ ├── ZsetEditor.vue # sorted set with scores │ ├── StreamEditor.vue # stream entries │ ├── CliView.vue # interactive terminal │ ├── SlowLogView.vue # slow log table │ ├── SettingsView.vue # theme, language, scan count │ ├── MemoryAnalysis.vue # MEMORY USAGE scan & sort │ ├── CommandLog.vue # command execution history │ └── ReJsonEditor.vue # JSON.GET / JSON.SET editing ├── i18n/ │ ├── index.ts # useI18n composable │ └── locales/ │ ├── en.ts # English │ ├── zh-CN.ts # 中文 │ ├── ja.ts # 日本語 │ ├── ko.ts # 한국어 │ └── de.ts # Deutsch ├── composables/ │ └── useKeyboard.ts # keyboard shortcut handler ├── styles/ │ ├── variables.css # CSS custom properties │ └── global.css # global overrides ├── data/ │ └── commands.ts # Redis command definitions └── utils/ └── binary.ts # hex ↔ buffer conversion ``` ## IPC Channels | Channel | Type | Purpose | |---------|------|---------| | `redis:connect` | invoke | Create Redis connection | | `redis:disconnect` | invoke | Close connection | | `redis:execute` | invoke | Execute arbitrary command | | `redis:getInfo` | invoke | INFO server info | | `redis:ping` | invoke | Health check ping | | `redis:isConnected` | invoke | Check connection status | | `redis:scanKeys` | invoke | SCAN with pattern | | `redis:getKeyType` | invoke | TYPE of a key | | `redis:getKeyTTL` | invoke | TTL of a key | | `redis:deleteKey` | invoke | DEL a single key | | `redis:existsKey` | invoke | EXISTS check | | `redis:getString` | invoke | GET string value | | `redis:setString` | invoke | SET string value | | `redis:hashScan` | invoke | HSCAN fields | | `redis:hashSet` | invoke | HSET field | | `redis:hashDel` | invoke | HDEL field | | `redis:listRange` | invoke | LRANGE elements | | `redis:listLength` | invoke | LLEN | | `redis:listPush` | invoke | RPUSH elements | | `redis:listSet` | invoke | LSET element | | `redis:listRemove` | invoke | LREM elements | | `redis:setMembers` | invoke | SMEMBERS | | `redis:setSize` | invoke | SCARD | | `redis:setAdd` | invoke | SADD members | | `redis:setRemove` | invoke | SREM members | | `redis:zsetRange` | invoke | ZRANGE with scores | | `redis:zsetSize` | invoke | ZCARD | | `redis:zsetAdd` | invoke | ZADD member | | `redis:zsetRemove` | invoke | ZREM members | | `redis:streamInfo` | invoke | XINFO STREAM | | `redis:streamRange` | invoke | XRANGE entries | | `redis:streamAdd` | invoke | XADD entry | | `redis:streamTrim` | invoke | XTRIM | | `redis:streamDel` | invoke | XDEL entries | | `redis:streamGroups` | invoke | XINFO GROUPS | | `redis:streamConsumers` | invoke | XINFO CONSUMERS | | `redis:streamGroupCreate` | invoke | XGROUP CREATE | | `redis:streamGroupDestroy` | invoke | XGROUP DESTROY | | `redis:streamAck` | invoke | XACK messages | | `redis:batchDelete` | invoke | Pipeline DEL multiple keys | | `redis:renameKey` | invoke | RENAME key | | `redis:setTTL` | invoke | EXPIRE/PERSIST | | `redis:selectDb` | invoke | SELECT database | | `redis:dbSize` | invoke | DBSIZE | | `redis:slowLogGet` | invoke | SLOWLOG GET | | `redis:slowLogLen` | invoke | SLOWLOG LEN | | `redis:memoryUsage` | invoke | MEMORY USAGE | | `redis:subscribe` | invoke | SUBSCRIBE to channels | | `redis:unsubscribe` | invoke | UNSUBSCRIBE from channels | | `redis:subscribeMessage` | send | Push subscribed message to renderer | | `redis:monitor` | invoke | Start MONITOR stream | | `redis:monitorStop` | invoke | Stop MONITOR stream | | `redis:monitorMessage` | send | Push monitored command to renderer | | `redis:getCommandLog` | invoke | Get command execution log | | `redis:clearCommandLog` | invoke | Clear command log | | `redis:decodeValue` | invoke | Decode value by format | | `redis:detectFormat` | invoke | Auto-detect value format | | `redis:customFormat` | invoke | Custom external formatter | | `redis:rejsonGet` | invoke | JSON.GET value | | `redis:rejsonSet` | invoke | JSON.SET value | | `storage:getConnections` | invoke | Read connections | | `storage:saveConnection` | invoke | Save connection | | `storage:deleteConnection` | invoke | Delete connection | | `storage:reorderConnections` | invoke | Reorder connections | | `storage:getSettings` | invoke | Read settings | | `storage:saveSettings` | invoke | Save settings | | `credential:encrypt` | invoke | safeStorage encrypt | | `credential:decrypt` | invoke | safeStorage decrypt | | `dialog:openFile` | invoke | Open file picker | | `theme:get` | invoke | Get OS dark mode | | `theme:set` | send | Set theme source | | `theme:os-updated` | send | Push OS theme change to renderer | | `window:minimize` | send | Minimize window | | `window:maximize` | send | Toggle maximize | | `window:close` | send | Close window | | `updater:check` | invoke | Check for updates | | `updater:download` | invoke | Download update | | `updater:install` | invoke | Quit and install update | | `updater:status` | send | Push updater status to renderer | ## Keyboard Shortcuts | Shortcut | Action | Scope | |----------|--------|-------| | `Ctrl+N` | New connection | Global | | `Ctrl+R` | Refresh keys | Global | | `F5` | Refresh keys | Global | | `Delete` | Delete selected key | Global | | `Escape` | Deselect key | Global | ## Build config - `electron.vite.config.ts` defines three build targets: main, preload, renderer - Entry points auto-detected (zero-config): `src/main/index.ts`, `src/preload/index.ts`, `src/renderer/index.html` - Build output: `out/main/index.js`, `out/preload/index.mjs`, `out/renderer/` (index.html + assets/) - `package.json` `"main"` points to `./out/main/index.js` - electron-builder packages into `release/` - Renderer dev server runs on port 5173 - Main/preload use `externalizeDepsPlugin()` to keep node_modules external - `@renderer` alias resolves to `src/renderer/src` ### Runtime paths in main process `src/main/index.ts` uses `__dirname` (resolved to `out/main/` at runtime) to locate siblings: | Target | Expression | |--------|-----------| | Preload script | `join(__dirname, '../preload/index.mjs')` | | Renderer HTML | `join(__dirname, '../renderer/index.html')` | | App icon | `join(__dirname, '../../build/icons/icon_256.png')` | If output directory or file extensions change, update these paths accordingly. ## TypeScript - Root `tsconfig.json` references `tsconfig.node.json` (main + preload) and `tsconfig.web.json` (renderer) - Node target (`tsconfig.node.json`) includes `src/main/**/*`, `src/preload/**/*.ts`, `electron.vite.config.ts` - Web target (`tsconfig.web.json`) includes `src/renderer/src/**/*`, `src/preload/**/*.d.ts` - Both use `verbatimModuleSyntax` (type-only imports required) and `moduleResolution: "bundler"` - `src/preload/index.d.ts` declares the `ElectronAPI` interface and augments global `Window` ## Security - `contextIsolation: true` - renderer has zero Node.js access - `nodeIntegration: false` - no Node globals in renderer - `safeStorage` - OS-level encryption for credentials - All Redis operations go through typed IPC to main process ## Legacy The `legacy/` directory contains the old Vue 2 + Webpack 4 codebase. It is gitignored and not part of the v2 build. See `legacy/AGENTS.md` for its documentation. ## Known issues ### Electron binary not installed after `npm install` The `allowScripts` field in `package.json` blocks electron's postinstall script (`node install.js`), so `npm install` only fetches the npm package without downloading the ~100 MB binary. `npm run dev` then fails with `Error: Electron failed to install correctly` or `spawn ... ENOENT`. **Fix:** Run the install script manually after `npm install`: ```bash node node_modules/electron/install.js ``` If the download is slow (GitHub releases), use a mirror: ```bash ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/" node node_modules/electron/install.js ``` > `package.json` references `scripts/fix-electron.sh` (`npm run fix:electron`) but the script does not exist yet.