← Corpus / conversational-ui-and-native-shells / profile
conversational-ui-and-native-shells/profiles/profile__5ire
- Path
- profiles/Profile__5ire.md
name: 5ire Profile
slug: 5ire
upstream: https://github.com/nanbingxyz/5ire
package: “5ire” (npm-style package.json, but distributed only as electron-builder desktop binaries — macOS/Windows/Linux, via .erb scaffold)
license: Modified Apache-2.0 (“5ire Community Edition” — Apache 2.0 plus additional commercial-use and brand-protection terms; see Characterizing the license below)
maintainer: Ironben / nanbingxyz (5ire.app)
study: studies/conversational-ui-and-native-shells
profile_path: studies/conversational-ui-and-native-shells/5ire
profile_kind: Electron (React renderer), MCP client + local-first chat app mid-migration from SQLite to PGlite/Postgres
date_created: 2026-07-13
5ire — Profile
A profile of 5ire as it lives in this study (studies/conversational-ui-and-native-shells/5ire/, pinned at c7fabb9, tag-derived version v0.15.4-5-gc7fabb9). Cites pinned paths so you can jump to source rather than trust paraphrase. Included as the study’s Electron comparison point — its local-persistence story and MCP-server-marketplace maturity are the load-bearing reasons it’s here, despite the study otherwise leaning Tauri. Read alongside Profile__Dive.md (Tauri+Electron dual-shell, MCP host) and Profile__Anything-Llm.md (Electron, workspace-switching).
Characterizing the license
LICENSE:1-6 is explicit: not MIT. It’s Apache License 2.0 plus additional conditions the file calls “5ire Community Edition” terms — package.json:11 echoes this as "license": "Modified Apache-2.0". The additional terms (LICENSE:16-33) require that modified/redistributed versions not use the “5ire Brand,” and mandate a commercial license if you (a) serve enterprise clients (>10 users), (b) embed 5ire in hardware/products for sale, or (c) handle government/education procurement with sensitive data. Section 3.3 also has contributors grant 5ire “exclusive rights to dual-license” their contributions and waive patent claims against 5ire’s commercial use. This is a source-available / BUSL-adjacent license, not a permissive OSS license — treat any reuse of 5ire code accordingly.
TL;DR
5ire is a desktop Electron chat client and MCP client whose defining architectural fact right now is that it’s mid-migration between two entirely different persistence stacks, visible directly in the source tree: a legacy better-sqlite3 database (src/main/sqlite.ts) still serving conversations, messages, and bookmarks over a raw-SQL IPC bridge, alongside a new Postgres-flavored PGlite + Drizzle ORM layer (src/main/database/index.ts, src/main/database/schema/tables.ts) that has already absorbed knowledge collections, documents, document-chunk embeddings (via pgvector), and — notably — MCP server configuration, which is fully modern. A dedicated LegacyDataMigrator (src/main/services/legacy-data-migrator.ts) copies rows across on startup, one domain at a time, with idempotency tracked via legacyId unique indexes and persisted migration-completion flags — but as of this pin, it does not migrate chats, messages, or bookmarks. Those still live entirely in SQLite, and full-text search over them is a hand-rolled LIKE '%term%' scan, not FTS5 or tsvector.
On the MCP side, 5ire is one of the more complete implementations in this study: a real @modelcontextprotocol/sdk Client over StdioClientTransport/StreamableHTTPClientTransport (src/main/services/mcp-connections-manager.ts:1-3,81-96), retry-with-backoff connection logic (#connect, mcp-connections-manager.ts:104-204), and — because MCP server config already lives in PGlite — reactive reconnection driven by the database’s own live-query changefeed (driver.live.changes(...), mcp-connections-manager.ts:266-320): editing a server row in the servers table causes the manager to diff columns and reconnect/disconnect automatically, no manual “refresh servers” call needed anywhere in the app. Layered on top is a genuine community marketplace: useMCPServerMarketStore.fetchServers() (src/stores/useMCPServerMarketStore.ts:22-43) does a plain fetch('https://mcpsvr.com/servers.json') against the separate nanbingxyz/mcpsvr repo, cached client-side for 24 hours, filtered/searched in ToolMarketDrawer.tsx, and installed via a callback that writes a new row into the same servers table the connections manager is already watching.
If you want one sentence: 5ire pairs one of this study’s most complete MCP-client implementations — SDK-native transports, DB-change-driven reconnection, and a live community server marketplace fetched from mcpsvr.com — with a persistence layer caught mid-flight from raw-SQL SQLite to a PGlite/Drizzle/pgvector stack, where the newer, more capable database already owns “servers” and RAG documents but has not yet absorbed chats, messages, bookmarks, or search, which still run as string-matched LIKE scans over a legacy 5ire.db file reached through an ad hoc db-all/db-run/db-get IPC surface.
Local persistence — a database mid-migration, visible in the diff between two files
- Legacy store: one
better-sqlite3file, opened with WAL mode, exposed as raw SQL over IPC.initLegacyDatabase()(src/main/sqlite.ts:9-12) opens<userData>/5ire.dband hand-writesCREATE TABLE IF NOT EXISTSstatements forfolders,chats,messages,bookmarks,prompts,usages,knowledge_collections,knowledge_files,chat_knowledge_rels(sqlite.ts:14-200), followed by ad hocalertTableChats/alertTableMessages/alertTableBookmarks/alertTableFoldersfunctions (sqlite.ts:202-282) thatPRAGMA table_infoeach table andALTER TABLE ... ADD COLUMNwhen a column introduced in a later version is missing — a migration system built from manual, datedifchecks rather than a migration framework.database.pragma("journal_mode = WAL")(sqlite.ts:308) is the one concession to concurrent-access performance. The whole thing is reachable from the renderer only through four genericipcMain.handleverbs —db-all,db-run,db-transaction,db-get(sqlite.ts:311-373) — meaning the renderer constructs and ships raw SQL strings across the IPC boundary (seeuseBookmarkStore.tsbelow), not typed commands. - New store: PGlite (Postgres-in-WASM) + Drizzle ORM + pgvector, with live queries.
Database.#init()(src/main/database/index.ts:47-146) creates aPGliteinstance backed byNodeFSatthis.#environment.databaseDataFolder, loads thevectorandlivePGlite extensions, runsCREATE EXTENSION IF NOT EXISTS vector, defensively drops stalelive_query_%views/tables/prepared-statements left over from unclean shutdowns (index.ts:62-114— a real operational scar, not theoretical), then builds adrizzle(driver, { schema })client and calls Drizzle’s ownmigrate()against adatabaseMigrationsFolderof SQL migration files (generated viadrizzle-kit generate,package.json:26, output underdrizzle/migrations). This is a schema-versioned, tool-generated migration pipeline — the opposite of the legacy file’s manualALTER TABLEchecks. - The new schema already covers knowledge/RAG and MCP servers, not yet chat.
src/main/database/schema/tables.tsdefinescollection,document,documentChunk(with an HNSW index over a 1024-dimvectorcolumn,tables.ts:198,220—bge-m3embedding dimensionality, matching the README’s local-embedding claim),conversationCollection,prompt,project,conversation,turn,provider,usage, andserver(the MCP server table). Conspicuously,tables.ts:570-573has commented-outbookmarkColumns/bookmarktable stubs — direct evidence bookmarks are slated to move but haven’t yet. legacyIdis the migration’s join key, andonConflictDoNothingmakes re-runs idempotent. Every migrated table (collection,document,documentChunk) carries alegacyId: varchar(...)column with auniqueIndex().on(table.legacyId).where(isNotNull(...))(tables.ts:91,167,223).LegacyDataMigrator(src/main/services/legacy-data-migrator.ts) uses this for a five-stage, order-dependent migration —#migrateCollections→#migrateDocuments→#migrateDocumentChunks→#migrateTransitionalChatCollections→#migrateServersConfig(legacy-data-migrator.ts:41-433), each guarded by athis.state.migrated.<name>flag persisted viaStateful.Persistableso a completed stage is skipped on next launch (legacy-data-migrator.ts:46-48,85-90). Document-chunk vectors are pulled not from SQLite but from a third store, a legacy LanceDB table (context.legacyLanceDB.openTable("knowledge"),legacy-data-migrator.ts:202-291) — so the pre-migration app actually spanned three storage engines (SQLite for structured data, LanceDB for vectors, a JSON servers-config file) before consolidating toward PGlite+pgvector.- Chats, messages, and bookmarks are the visible gap.
LegacyDataMigrator.State["migrated"](legacy-data-migrator.ts:518-526) only tracks"collections" | "documents" | "documentChunks" | "transitionChatCollections" | "serversConfig"— there is nomessagesorbookmarkskey. Combined withuseBookmarkStore.tsandSearchDialog.tsxboth still callingwindow.electron.db.*(the legacy IPC verbs, not a Drizzle/PGlite path), this is direct proof the conversation-history domain has not crossed over yet, even though the underlying infrastructure (PGlite, migrator,legacyIdpattern) is already built and working for other domains.
Full-text search — two call sites, same technique, no FTS engine
- Global search across all chats (
src/renderer/components/SearchDialog.tsx:109-146) splits the query on whitespace intokeywords, builds one(prompt like ? OR reply like ?)clause per keyword ANDed together, wraps each keyword in%...%, and runs it viawindow.electron.db.all(sql, params)against the legacymessagestable —SELECT id, chatId, prompt, reply FROM messages WHERE ... ORDER BY messages.createdAt ASC LIMIT 10. Matched snippets are then extracted client-side byextractMatchedSnippet()(SearchDialog.tsx:32-87), which does its own substring-index math to build a highlighted window around each hit and merges overlapping windows — a hand-built excerpt/highlight system operating on the raw strings, not on any search-engine-provided ranking or offsets. - Bookmark search (
src/stores/useBookmarkStore.ts:162-194,fetchBookmarks) is the same pattern applied to a single combined keyword string rather than tokenized keywords:(prompt like ? or reply like ? or memo like ?)with one%keyword%parameter reused three times, ANDed with an optionalfavorite = ?filter,ORDER BY createdAt DESC. - No SQLite FTS5 virtual table, no Postgres
tsvector/to_tsquery, no ranking function anywhere insrc/mainorsrc/renderer. Both search paths areLIKE-based substring scans against un-indexed text columns (themessages/bookmarkstable definitions insqlite.tshave noindex/virtual-table statements forprompt/reply/memo) — correctness-first, not scale-first. This is consistent with 5ire’s position as a single-user local desktop app rather than a searchable-at-scale knowledge base; the newer PGlite side does get proper indexing (tables.tshasindex()/uniqueIndex()calls throughout), just not yet for conversation text.
Bookmarks — a durable snapshot decoupled from the source message
- Bookmarks copy content rather than reference it live. The legacy
bookmarkstable (src/main/sqlite.ts:88-109) stores its ownprompt,reply,reasoning,temperature,model,memo,citedFiles,citedChunkscolumns — a full snapshot of the turn, not a foreign key alone — with aUNIQUE ("msgId")constraint (sqlite.ts:105) that still ties it back to the originating message for de-duplication (one bookmark per message) while remaining independently readable. The README states this design intent directly: “You can bookmark each conversation, and even if the original messages are deleted, the saved bookmarked content remains unaffected” (README.md, Bookmarks section). - Bookmark store is a thin Zustand wrapper over the same raw-SQL IPC bridge as search.
useBookmarkStore.tsbuildsINSERT/UPDATE/DELETESQL by hand from whichever fields are present (createBookmark,updateBookmark,sqlite.ts:58-131— note the?.repeat(columns.length) placeholder-generation idiom atuseBookmarkStore.ts:67), with client-side state (bookmarks,favoritesarrays) kept in sync manually after each mutation rather than via any live-query mechanism — a contrast with the new PGliteMCPServersManager/MCPConnectionsManager, which get automatic UI updates for free fromdriver.live.query/driver.live.changes. - Favorites are just a
favoriteboolean column on the same table, not a separate collection —loadFavorites()calls the samefetchBookmarks({ favorite: true })path (useBookmarkStore.ts:200-208).
MCP client — SDK-native transports, DB-driven reconnection, and a community marketplace
- Two real transports, chosen by a
transportenum column, not string-sniffing a URL.serverTransport = pgEnum(..., ["stdio", "http-streamable"])(tables.ts:579) andMCPConnectionsManager.#transport()(mcp-connections-manager.ts:72-97) builds either aStdioClientTransport(parsingendpointas a shell command line viaparseCommandLine, passingconfigas env vars) or aStreamableHTTPClientTransport(treatingendpointas a URL,configas headers) — explicitly noting in a comment thatStreamableHTTPClientTransportnow subsumes what used to require a separate SSE transport (mcp-connections-manager.ts:94-96). - Connection lifecycle is a real state machine with retry, abort, and error surfacing, not fire-and-forget.
#connect()(mcp-connections-manager.ts:104-204) retries up to 3 times with a linearly increasing delay (1000 * retriesms), threads anAbortControllerthrough so a server removed mid-connect cleans up correctly, and lands in one of three states —connecting | connected | error— each carrying the data relevant to that state (Connectionunion type,mcp-connections-manager.ts:367-421).getConnectedOrThrow()(mcp-connections-manager.ts:328-348) gives callers actionable per-state error messages rather than a generic null-check. - Server config changes reconnect automatically via the database’s live-query changefeed — the clearest example in this study of persistence and MCP-connection-management being unified through one reactive primitive.
init()(mcp-connections-manager.ts:244-321) runs a Drizzle query for active servers, then wraps it indriver.live.changes(...)(PGlite’s live-query extension) keyed byid; on every emitted change it diffs__changed_columns__, decides whether the effective server snapshot actually differs (isEqual), and disconnects+reconnects only when it does. Toggling a server’sactiveflag or editing itsendpoint/configfrom the UI needs no explicit “apply” step in the connections manager — it’s driven entirely by the DB emitting a row change. - Short numeric IDs exist purely for LLM-facing tool-call ergonomics.
MCPServersManagermaintains ashortIds: Map<string, number>(mcp-servers-manager.ts:402-411) assigning small monotonic integers to each UUID server ID as servers are created — presumably so tool-call URIs or prompts shown to a model don’t need to carry full UUIDs (getShortId/getIdFromShortId,mcp-servers-manager.ts:302-323). - Tools are addressed by a custom URI scheme layering connection + tool name.
MCPToolsManager.#formatToolURI()buildstool:${connectionId}/${encodeURIComponent(tool.name)}(mcp-tools-manager.ts:30-32), with a matching#parseToolURI— giving every tool call a single string identifier that round-trips back to both which server and which tool, capped atMAX_TOOLS_PAGE = 10pages of pagination per server (mcp-tools-manager.ts:15). - The marketplace is an external static JSON feed, not an in-app registry.
useMCPServerMarketStore.fetchServers()(src/stores/useMCPServerMarketStore.ts:22-43) fetcheshttps://mcpsvr.com/servers.jsondirectly from the renderer process (a plainfetch, no IPC involved), caches the result plus anupdatedAttimestamp in Zustand state with aREMOTE_CONFIG_TTLof1000 * 60 * 60 * 24(24 hours), and only re-fetches whenforceis passed or the cache has expired.ToolMarketDrawer.tsx(src/renderer/pages/tool/MarketDrawer.tsx:36-63) does client-side filtering acrossname/descriptionagainst space-split search terms, and its “Submit” button (MarketDrawer.tsx:93-98) links out tohttps://github.com/nanbingxyz/mcpsvr— the marketplace’s own repo — for community members to add their server.onInstall(wired atsrc/renderer/pages/tool/index.tsx:149) ultimately calls the samecreateServerpath as manual configuration, writing a new row into the sharedserverstable thatMCPConnectionsManageris already watching — so “install from marketplace” and “hand-configure a server” are the exact same code path downstream of the drawer.
What’s inside this submodule
| Path | What’s there |
|---|---|
src/main/sqlite.ts | Legacy better-sqlite3 store — table DDL, ALTER TABLE-based ad hoc migrations, the 4-verb raw-SQL IPC bridge (db-all/db-run/db-transaction/db-get) |
src/main/services/legacy-data-migrator.ts | LegacyDataMigrator — five-stage SQLite/LanceDB → PGlite migration, legacyId-keyed idempotency, persisted per-stage completion flags |
src/main/database/index.ts | Database — PGlite (NodeFS, vector+live extensions) + Drizzle client construction, stale live-query cleanup, Drizzle-generated migrations |
src/main/database/schema/tables.ts | The new Postgres-flavored schema — collection/document/documentChunk (pgvector HNSW), conversation/turn, provider, server (MCP), commented-out bookmark stub |
src/main/services/mcp-connections-manager.ts | MCPConnectionsManager — SDK transports, retry/abort connection state machine, live-query-driven auto-reconnect |
src/main/services/mcp-servers-manager.ts | MCPServersManager — CRUD over the servers table, live queries for UI, short-ID assignment |
src/main/services/mcp-tools-manager.ts | MCPToolsManager — tool discovery/pagination, tool: URI scheme, tool execution |
src/main/services/mcp-prompts-manager.ts, mcp-resources-manager.ts, mcp-completion-handler.ts, mcp-content-converter.ts | Remaining MCP capability managers (prompts, resources, completions) and content-block conversion |
src/stores/useMCPServerMarketStore.ts | Marketplace client — fetch('https://mcpsvr.com/servers.json'), 24h TTL cache |
src/renderer/pages/tool/MarketDrawer.tsx, index.tsx | Marketplace browse/search/install UI; install path converges on the same createServer call as manual config |
src/stores/useBookmarkStore.ts | Bookmark CRUD — hand-built SQL over the legacy IPC bridge, favorite-as-boolean-column |
src/renderer/components/SearchDialog.tsx | Global chat search — LIKE-based, hand-rolled snippet highlighting |
src/intellichat/ | Chat-turn assembly, MCP content-block conversion (mcp/ContentBlockConverter.ts), reader/service abstractions |
LICENSE | Modified Apache-2.0 — the commercial-use and brand-protection carve-outs |
INSTALLATION.md, DEVELOPMENT.md | Setup docs — Python/Node/uv prerequisites for MCP servers, dev environment |
If you read three files: src/main/services/legacy-data-migrator.ts (the migration’s actual shape and its gaps), src/main/services/mcp-connections-manager.ts (the cleanest MCP-client + live-query integration in this study), and src/main/database/schema/tables.ts (what has and hasn’t crossed over — the commented-out bookmark stub is the tell).
Mental model for using it well
- Treat 5ire as two codebases occupying one repo. Anything touching knowledge/RAG, providers, or MCP servers is on the modern PGlite+Drizzle+live-query stack; anything touching chats, messages, bookmarks, or search is still on raw-SQL SQLite reached through a stringly-typed IPC bridge. Don’t assume a pattern found in one domain (e.g., live-query reactivity) applies to the other yet.
legacyId+onConflictDoNothingis the reusable migration idiom worth lifting. A nullable, uniquely-indexedlegacyIdcolumn on the new table lets a migration re-run safely and be resumed/interrupted without duplicate rows — cleaner than a one-shot “big bang” migration script.- MCP server config-as-database-row, watched via live query, is the reusable MCP-host idiom. Rather than an explicit “reconnect” action, treat server activation/config edits as ordinary row mutations and let a
driver.live.changessubscriber own the connect/disconnect side effects — this collapses “server management UI” and “connection manager” into one reactive pipeline. - The marketplace is intentionally dumb — a static JSON feed plus client-side filtering — and that’s the point. No backend, no server-side search index, no submission workflow inside the app; new entries land via PRs to a separate
mcpsvrrepo. Don’t over-build a marketplace surface if a static feed with a TTL cache covers the actual need. - Read the license before treating anything here as a drop-in dependency. The “Modified Apache-2.0” terms gate commercial redistribution and enterprise use — this is reference material for architecture, not a library to vendor.
When NOT to reach for this
- You want a single, finished persistence architecture to copy wholesale. 5ire’s database layer is mid-migration by its own admission (the migrator’s incomplete
migratedstate, the commented-outbookmarktable) — copy the migration technique, not the current end-state, unless you specifically want the two-tier legacy+modern split as a template for your own gradual migration. - You need FTS-quality conversation search out of the box. Both search surfaces (
SearchDialog.tsx,useBookmarkStore.ts) areLIKE-scan implementations with hand-rolled highlighting — adequate for a single user’s local chat history, not a pattern to scale past that. - You need a permissive-license reference implementation to fork freely. The Modified Apache-2.0 terms impose real constraints (brand use, enterprise-scale commercial licensing) that MIT-licensed siblings in this study (e.g. Kaas) don’t have.
- You want a Tauri-shell reference or a dual-shell (Tauri+Electron) comparison. 5ire is Electron-only via the
.erbscaffold; for shell-strategy comparisons, look todive(Tauri+Electron dual) instead.
How this compares to the rest of the study
| Axis | 5ire | dive | anything-llm |
|---|---|---|---|
| Shell | Electron (.erb scaffold) only | Tauri and Electron (dual shell) | Electron only |
| License | Modified Apache-2.0 (source-available, commercial-use carve-outs) | (see Profile__Dive.md) | (see Profile__Anything-Llm.md) |
| Persistence | Two-tier: legacy better-sqlite3 (chats/messages/bookmarks, raw-SQL IPC) + new PGlite/Drizzle/pgvector (knowledge, providers, MCP servers), migration in progress via legacyId-keyed idempotent copier | (see Profile__Dive.md) — MCP-host comparison point | (see Profile__Anything-Llm.md) — workspace-switching comparison point |
| Full-text search | Hand-rolled LIKE '%term%' scans over SQLite messages/bookmarks, client-side snippet highlighting; no FTS5/tsvector | — | — |
| Bookmarks | Full-content snapshot per message (survives source-message deletion), UNIQUE(msgId), favorite-as-boolean-column, no live-query sync | — | — |
| MCP client | SDK-native (@modelcontextprotocol/sdk), stdio + streamable-HTTP transports, retry/abort state machine, live-query-driven auto-reconnect on server-row change | MCP host (dual-shell) — see sibling profile | — |
| MCP marketplace | External static JSON feed (mcpsvr.com/servers.json, own community repo), 24h client-side TTL cache, install path converges on manual-config createServer | — | — |
| Reactive UI wiring | PGlite’s live.query/live.changes extension powers server-list and connection-state UI automatically; legacy-domain stores (bookmarks) still update state manually after each mutation | — | — |
| Best fit | Studying a live SQLite→Postgres(WASM) migration in a shipping desktop app, and/or the most complete MCP-client+marketplace pairing in this study | Cross-shell (Tauri/Electron) MCP-host comparison | Workspace-switching UX comparison |
5ire’s load-bearing contribution to this study is a real, in-progress persistence migration (SQLite/LanceDB → PGlite/pgvector) caught mid-flight in the source tree, paired with the study’s most complete MCP-client implementation — SDK-native transports, a retry/abort connection state machine, and reconnection driven entirely by the new database’s live-query changefeed — plus a working, minimal community marketplace (a static JSON feed with a TTL cache) that any MCP-client-building project could copy almost verbatim.
One-line summary
5ire is an Electron desktop MCP client caught mid-migration between two persistence stacks — a legacy
better-sqlite3file serving chats, messages, and bookmarks through a raw-SQL IPC bridge withLIKE-scan search, and a newer PGlite/Drizzle/pgvector database that already owns knowledge-base RAG and MCP server config, copied over by an idempotentlegacyId-keyed migrator that hasn’t yet reached conversation history — while its MCP-client layer is the study’s most complete: SDK-native stdio/streamable-HTTP transports, a retry-and-abort connection state machine, reconnection driven automatically by the database’s own live-query changefeed, and a real community marketplace (a staticmcpsvr.com/servers.jsonfeed, 24-hour cached) whose “install” button writes into the very sameserverstable the connection manager is already watching.