elidia-agent-cli 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- elidia_agent_cli-0.3.0/CHANGELOG.md +56 -0
- {elidia_agent_cli-0.2.0/elidia_agent_cli.egg-info → elidia_agent_cli-0.3.0}/PKG-INFO +1 -1
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/docs/ARCHITECTURE_FLOWCHARTS.md +40 -0
- elidia_agent_cli-0.3.0/docs/guide/tools-mcp.md +113 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/agent/loop.py +2 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/cli/commands.py +5 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/cli/main.py +196 -6
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/cli/repl.py +69 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/permissions/manager.py +1 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/rag/chunker.py +92 -36
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/rag/engine.py +22 -9
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/tools/__init__.py +3 -0
- elidia_agent_cli-0.3.0/elidia/tools/rag.py +147 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0/elidia_agent_cli.egg-info}/PKG-INFO +1 -1
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia_agent_cli.egg-info/SOURCES.txt +1 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/pyproject.toml +1 -1
- elidia_agent_cli-0.2.0/CHANGELOG.md +0 -16
- elidia_agent_cli-0.2.0/docs/guide/tools-mcp.md +0 -86
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/LICENSE +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/MANIFEST.in +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/README.md +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/docs/AUDIT_REPORT_v0.1.0.md +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/docs/api/commands.md +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/docs/architecture/modules.md +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/docs/architecture/overview.md +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/docs/getting-started/configuration.md +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/docs/getting-started/installation.md +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/docs/getting-started/quickstart.md +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/docs/guide/chat-modes.md +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/docs/guide/creative.md +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/docs/guide/models.md +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/docs/guide/research.md +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/docs/guide/themes.md +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/docs/guide/workflows.md +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/docs/index.md +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/__main__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/agent/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/agent/personas.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/agent/portal.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/api/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/api/client.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/api/streaming.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/auth/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/auth/keychain.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/cache/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/cache/lru.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/cli/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/cli/pager.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/cli/progress.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/cli/renderer.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/cli/themes.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/config/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/config/defaults.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/config/rules.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/config/settings.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/creative/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/creative/audio.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/creative/display.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/creative/image.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/creative/local.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/creative/video.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/daemon/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/daemon/config.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/daemon/ipc.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/daemon/manager.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/daemon/process.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/daemon/worker.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/db/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/db/database.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/mcp/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/mcp/client.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/mcp/config.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/mcp/registry.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/mcp/types.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/memory/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/memory/auto.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/memory/compaction.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/memory/embeddings.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/memory/outcomes.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/memory/patterns.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/memory/store.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/models/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/models/adaptive.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/models/router.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/modes/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/modes/autonomous.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/modes/budget.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/modes/classifier.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/modes/consensus.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/modes/deep_think.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/modes/swarm.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/modes/thinking.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/permissions/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/permissions/audit.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/permissions/trust.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/rag/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/rag/ingest.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/rag/portal_bridge.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/rag/watcher.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/research/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/research/export.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/research/orchestrator.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/research/sources.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/session/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/session/history.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/session/manager.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/tools/base.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/tools/browser.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/tools/calendar.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/tools/database.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/tools/email.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/tools/fetch.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/tools/filesystem.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/tools/git.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/tools/office.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/tools/search.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/tools/terminal.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/tools/tool_router.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/widgets/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/widgets/protocol.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/widgets/renderer.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/workflow/__init__.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia/workflow/engine.py +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia_agent_cli.egg-info/dependency_links.txt +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia_agent_cli.egg-info/entry_points.txt +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia_agent_cli.egg-info/requires.txt +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/elidia_agent_cli.egg-info/top_level.txt +0 -0
- {elidia_agent_cli-0.2.0 → elidia_agent_cli-0.3.0}/setup.cfg +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Elidia CLI will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.3.0] - 2026-07-27
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- RAG reconnected to every user-facing surface: `elidia rag ingest/search/list/clear`
|
|
9
|
+
(CLI), `/rag ingest|search|list|clear` (REPL), and auto-ingest for files over
|
|
10
|
+
~8,000 chars passed via `-f/--file` (preview + index instead of blind truncation,
|
|
11
|
+
including files >1MB that were previously dropped entirely). New agent-facing
|
|
12
|
+
`rag_search`/`rag_list_sources` tools (AUTO permission tier).
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- `elidia workflow run` required an API key even for pure-shell workflows with no
|
|
16
|
+
`llm` step.
|
|
17
|
+
- Email skill used the same field for the SMTP/IMAP login and the visible `From`
|
|
18
|
+
address, which breaks for transactional relays (Zepto, SendGrid, Mailgun, SES)
|
|
19
|
+
whose AUTH username is a token distinct from the sender address. Added
|
|
20
|
+
`from_address`.
|
|
21
|
+
- RAG's FTS5 query builder crashed on any hyphenated search term (`"no such
|
|
22
|
+
column: call"` on a query containing "on-call").
|
|
23
|
+
- RAG's chunker never split a paragraph/section/line once it exceeded
|
|
24
|
+
`chunk_size` — a 21KB file with no blank lines produced 1 chunk instead of
|
|
25
|
+
~40, diluting the embedding enough that content buried mid-file became
|
|
26
|
+
unfindable by search.
|
|
27
|
+
- `elidia ask` silently dropped `-f/--file` — the subcommand never declared its
|
|
28
|
+
own option or read the parent group's.
|
|
29
|
+
|
|
30
|
+
## [0.2.0] - 2026-07-26
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
- Real background daemon (`elidia daemon start/stop/restart/status/init`),
|
|
34
|
+
`elidia mcp list/health`, `elidia workflow run`.
|
|
35
|
+
- Vision support: `--image`/`-i` flag, `/image` REPL command, S3/CDN upload.
|
|
36
|
+
- Browser, Office, Database, Email, and Calendar skills (5 new tool categories).
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
- Tool-calling never actually worked — the system prompt (and therefore every
|
|
40
|
+
tool schema) was never sent to the model; the agent could only ever respond
|
|
41
|
+
from training knowledge, never call a real tool.
|
|
42
|
+
- Permission classification for tool calls with an omitted path argument fell
|
|
43
|
+
through to the wrong (over-restrictive) tier.
|
|
44
|
+
|
|
45
|
+
## [0.1.0] - 2026-07-25
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
- Phase 0: Core CLI framework (Click + Rich + prompt_toolkit)
|
|
49
|
+
- Phase 1: MCP integration, built-in tools, model router, permissions
|
|
50
|
+
- Phase 2: Persistent memory (sqlite-vec), RAG engine, session management
|
|
51
|
+
- Phase 3: Advanced modes (classifier, consensus, deep think, autonomous, swarm)
|
|
52
|
+
- Phase 3: Research pipeline (YOYO 5-stage), creative generation, workflow engine
|
|
53
|
+
- Phase 3: Daemon mode, widget protocol, terminal image display
|
|
54
|
+
- Phase 4: Auto-pager, progress indicators, response cache, configurable themes
|
|
55
|
+
- Phase 4: Connection pooling (httpx HTTP/2), PyPI packaging, CI/CD pipeline
|
|
56
|
+
- Phase 4: Automated test suite, MkDocs documentation site
|
|
@@ -788,3 +788,43 @@ are now permanently exempt from promotion, proven with a regression test
|
|
|
788
788
|
showing 25 consecutive approvals still prompt on the 26th call. See the
|
|
789
789
|
master plan (`06_CLI_AND_DESKTOP_MASTER_PLAN.md`, §4.4) for full detail
|
|
790
790
|
and commit references.
|
|
791
|
+
|
|
792
|
+
## 22. RAG Subsystem — Reconnected (2026-07-27, AIUT-2141)
|
|
793
|
+
|
|
794
|
+
`RagEngine`/`FileIngestPipeline` existed (~1,135 LOC) but were never
|
|
795
|
+
called from any user-facing surface — confirmed via `grep -rn
|
|
796
|
+
"FileIngestPipeline(\|RagEngine(" elidia/` returning zero hits outside
|
|
797
|
+
`elidia/rag/` itself. Wired up per the requested design (all three, not
|
|
798
|
+
one-or-the-other): `elidia rag ingest/search/list/clear` (CLI), `/rag
|
|
799
|
+
...` (REPL), and auto-ingest for files over ~8,000 chars passed via
|
|
800
|
+
`-f/--file` (preview + index instead of blind truncation, including the
|
|
801
|
+
>1MB case that previously just dropped the file). The read side is a new
|
|
802
|
+
`rag_search` tool (AUTO tier, agent-invocable) — ingestion stays a
|
|
803
|
+
deliberate user action across all three entry points, never
|
|
804
|
+
agent-triggered, so an embedding-cost operation can't fire mid-conversation
|
|
805
|
+
without the user asking for it.
|
|
806
|
+
|
|
807
|
+
Live end-to-end verification surfaced and fixed three further real bugs
|
|
808
|
+
along the way, not just wiring:
|
|
809
|
+
|
|
810
|
+
1. **FTS5 query builder crashed on hyphenated terms** — `_build_fts_query`
|
|
811
|
+
escaped special characters individually but not `-`, and FTS5's query
|
|
812
|
+
grammar treats an in-word hyphen as syntax (`on-call*` → `no such
|
|
813
|
+
column: call`), not just a tokenizer boundary. Fixed by quoting every
|
|
814
|
+
term (`"on-call"*`) instead of trying to enumerate escapable characters.
|
|
815
|
+
2. **Chunker never split an oversized paragraph/section/line** — a 21KB
|
|
816
|
+
file with no blank lines produced *one* chunk containing the entire
|
|
817
|
+
file instead of ~40, diluting the embedding enough that a fact in the
|
|
818
|
+
middle became unfindable by search. Fixed for all three content types
|
|
819
|
+
(`text`, `markdown`, `code`) via a shared `_split_oversized` helper.
|
|
820
|
+
3. **`elidia ask` silently dropped `-f/--file`** — the subcommand never
|
|
821
|
+
declared its own `--file` option and never read the parent group's
|
|
822
|
+
either, so both `elidia ask -f x.txt "..."` and `elidia --file x.txt
|
|
823
|
+
ask "..."` reached the model with nothing attached. Fixed by mirroring
|
|
824
|
+
how `-i/--image` already worked (own option + parent fallback).
|
|
825
|
+
|
|
826
|
+
34 new tests added across `test_rag_tool.py` and `test_chunker.py`
|
|
827
|
+
(chunker fix verified with a real FTS5 in-memory table, not just string
|
|
828
|
+
inspection). Full live proof, not just unit tests: real `elidia rag
|
|
829
|
+
ingest` → real `elidia ask` with the agent autonomously calling
|
|
830
|
+
`rag_search` → correct grounded answer citing the ingested content.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Tools & MCP
|
|
2
|
+
|
|
3
|
+
## Built-in Tools
|
|
4
|
+
|
|
5
|
+
Elidia ships with tools in several categories:
|
|
6
|
+
|
|
7
|
+
| Category | Tools | Description |
|
|
8
|
+
|----------|-------|-------------|
|
|
9
|
+
| `filesystem` | read, write, edit, list, glob, grep | File operations |
|
|
10
|
+
| `terminal` | command_exec | Shell command execution |
|
|
11
|
+
| `git` | status, diff, log, commit, branch | Git workflow |
|
|
12
|
+
| `search` / `fetch` | web_search, http_fetch | Web requests and search |
|
|
13
|
+
| `browser` | navigate, click, type, screenshot, extract_links | Headless browser automation (Playwright) |
|
|
14
|
+
| `office` | read/write docx, read/write xlsx, read pptx | Office document parsing |
|
|
15
|
+
| `database` | connect, query, list_tables, describe_table | Read-only SQL |
|
|
16
|
+
| `email` | send, search, read | SMTP send / IMAP search+read |
|
|
17
|
+
| `calendar` | list_events, add_event, find_conflicts | Local .ics calendar |
|
|
18
|
+
| `rag` | rag_search, rag_list_sources | Search content ingested into the local RAG store |
|
|
19
|
+
|
|
20
|
+
View all tools:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
> /tools
|
|
24
|
+
> /tools filesystem # Filter by category
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## RAG (Retrieval-Augmented Search)
|
|
28
|
+
|
|
29
|
+
Large files don't need to fit in the conversation — Elidia can index them
|
|
30
|
+
into a local RAG store (sqlite-vec, hybrid BM25 + semantic search) and
|
|
31
|
+
retrieve relevant chunks on demand via the `rag_search` tool. Three ways
|
|
32
|
+
to get content in:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
elidia rag ingest <path> # explicit, works on a file or a directory
|
|
36
|
+
> /rag ingest <path> # same, from inside the REPL
|
|
37
|
+
> /rag search <query> # search without waiting for the agent to do it
|
|
38
|
+
elidia ask -f big_file.md "..." # auto-ingest: files over ~8,000 chars are
|
|
39
|
+
# previewed + indexed instead of truncated
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`elidia rag list` / `/rag list` shows how much is currently ingested;
|
|
43
|
+
`elidia rag clear` / `/rag clear` empties the store. Ingestion is always
|
|
44
|
+
a deliberate, user-triggered action (CLI command, slash command, or the
|
|
45
|
+
`-f` auto-ingest path) — the agent can search what's already ingested via
|
|
46
|
+
`rag_search`, but never triggers ingestion on its own mid-conversation.
|
|
47
|
+
|
|
48
|
+
## Permissions
|
|
49
|
+
|
|
50
|
+
Tools require user approval based on risk level:
|
|
51
|
+
|
|
52
|
+
- **Read** — auto-approved by default
|
|
53
|
+
- **Write** — requires approval
|
|
54
|
+
- **Shell** — requires approval
|
|
55
|
+
- **Web** — configurable
|
|
56
|
+
|
|
57
|
+
The trust engine learns from your decisions:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
> /trust # Show trust stats
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
After consistently approving an action, Elidia auto-promotes it (no more prompts).
|
|
64
|
+
|
|
65
|
+
## MCP Integration
|
|
66
|
+
|
|
67
|
+
Elidia supports [Model Context Protocol](https://modelcontextprotocol.io/) servers for extending tool capabilities.
|
|
68
|
+
|
|
69
|
+
### Configuration
|
|
70
|
+
|
|
71
|
+
Add MCP servers to `~/.elidia/mcp.json`:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"servers": {
|
|
76
|
+
"filesystem": {
|
|
77
|
+
"command": "npx",
|
|
78
|
+
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/user"]
|
|
79
|
+
},
|
|
80
|
+
"github": {
|
|
81
|
+
"command": "npx",
|
|
82
|
+
"args": ["-y", "@modelcontextprotocol/server-github"],
|
|
83
|
+
"env": {
|
|
84
|
+
"GITHUB_TOKEN": "ghp_..."
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Managing MCP Servers
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
> /mcp # List connected servers
|
|
95
|
+
> /mcp disconnect github # Disconnect a server
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
MCP tools appear alongside built-in tools and are invoked with the same permission system.
|
|
99
|
+
|
|
100
|
+
## Tool Execution Flow
|
|
101
|
+
|
|
102
|
+
```mermaid
|
|
103
|
+
graph TD
|
|
104
|
+
A[User Query] --> B{Agent Loop}
|
|
105
|
+
B --> C[LLM decides tool call]
|
|
106
|
+
C --> D{Permission Check}
|
|
107
|
+
D -->|Approved| E[Execute Tool]
|
|
108
|
+
D -->|Denied| F[Report to LLM]
|
|
109
|
+
E --> G[Return Result to LLM]
|
|
110
|
+
G --> B
|
|
111
|
+
F --> B
|
|
112
|
+
B --> H[Final Response]
|
|
113
|
+
```
|
|
@@ -482,6 +482,8 @@ class AgentLoop:
|
|
|
482
482
|
"calendar_list_events": "file_read",
|
|
483
483
|
"calendar_find_conflicts": "file_read",
|
|
484
484
|
"calendar_add_event": "file_write",
|
|
485
|
+
"rag_search": "rag_search",
|
|
486
|
+
"rag_list_sources": "rag_search",
|
|
485
487
|
}
|
|
486
488
|
return action_map.get(tool_name, "mcp_call_session")
|
|
487
489
|
|
|
@@ -118,5 +118,10 @@ def build_default_commands() -> CommandRegistry:
|
|
|
118
118
|
name="rules", description="Show project rules (.elidia/rules.md)",
|
|
119
119
|
usage="/rules", category="general",
|
|
120
120
|
))
|
|
121
|
+
registry.register(SlashCommand(
|
|
122
|
+
name="rag", description="Ingest files or search the local RAG store",
|
|
123
|
+
usage="/rag [ingest <path>|search <query>|list|clear]",
|
|
124
|
+
category="tools",
|
|
125
|
+
))
|
|
121
126
|
|
|
122
127
|
return registry
|
|
@@ -110,9 +110,11 @@ cli.add_command(_default_message)
|
|
|
110
110
|
@click.option("--mode", default=None, type=click.Choice(["chat", "code", "research", "think", "create"]))
|
|
111
111
|
@click.option("--image", "-i", "images", multiple=True, type=click.Path(exists=True),
|
|
112
112
|
help="Attach an image for vision analysis (repeatable, jpg/png/webp/gif)")
|
|
113
|
+
@click.option("--file", "-f", "files", multiple=True, type=click.Path(exists=True),
|
|
114
|
+
help="Include file content in context (repeatable)")
|
|
113
115
|
@click.pass_context
|
|
114
116
|
def ask(ctx: click.Context, message: tuple[str, ...], model: str | None, mode: str | None,
|
|
115
|
-
images: tuple[str, ...]) -> None:
|
|
117
|
+
images: tuple[str, ...], files: tuple[str, ...]) -> None:
|
|
116
118
|
"""Send a one-shot message: elidia ask 'your question here'"""
|
|
117
119
|
logger.debug("Entered into ask")
|
|
118
120
|
parent = ctx.parent
|
|
@@ -120,8 +122,9 @@ def ask(ctx: click.Context, message: tuple[str, ...], model: str | None, mode: s
|
|
|
120
122
|
model = model or parent_obj.get("model")
|
|
121
123
|
mode = mode or parent_obj.get("mode") or "chat"
|
|
122
124
|
images = images or parent_obj.get("images", ())
|
|
125
|
+
files = files or parent_obj.get("files", ())
|
|
123
126
|
user_msg = " ".join(message)
|
|
124
|
-
asyncio.run(_one_shot(user_msg, model=model, mode=mode, images=images))
|
|
127
|
+
asyncio.run(_one_shot(user_msg, model=model, mode=mode, images=images, files=files))
|
|
125
128
|
|
|
126
129
|
|
|
127
130
|
_IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp", ".tiff", ".heic"}
|
|
@@ -153,8 +156,47 @@ def _summarize_ics(path: Path) -> str:
|
|
|
153
156
|
return "\n".join(lines) if lines else "[No events in this calendar]"
|
|
154
157
|
|
|
155
158
|
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
_AUTO_INGEST_THRESHOLD = 8_000 # chars — beyond this, index into RAG + preview instead of blind truncation
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
async def _auto_ingest_file(path: Path, content: str) -> bool:
|
|
163
|
+
"""Best-effort: index a large file into the local RAG store so it's
|
|
164
|
+
retrievable via rag_search instead of being silently truncated.
|
|
165
|
+
Returns False (never raises) if no API key is configured or ingestion
|
|
166
|
+
fails for any reason — callers fall back to plain truncation."""
|
|
167
|
+
logger.debug(f"Entered into _auto_ingest_file: path={path}")
|
|
168
|
+
from elidia.auth.keychain import get_api_key
|
|
169
|
+
|
|
170
|
+
api_key = get_api_key()
|
|
171
|
+
if not api_key:
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
import hashlib
|
|
176
|
+
|
|
177
|
+
from elidia.memory.embeddings import EmbeddingClient
|
|
178
|
+
from elidia.rag.engine import RagEngine
|
|
179
|
+
|
|
180
|
+
engine = RagEngine(EmbeddingClient(api_key=api_key))
|
|
181
|
+
engine.open()
|
|
182
|
+
try:
|
|
183
|
+
file_hash = hashlib.sha256(content.encode()).hexdigest()[:16]
|
|
184
|
+
await engine.ingest(text=content, source=str(path.resolve()), file_hash=file_hash)
|
|
185
|
+
finally:
|
|
186
|
+
engine.close()
|
|
187
|
+
return True
|
|
188
|
+
except Exception as e:
|
|
189
|
+
logger.warning(f"Entered into _auto_ingest_file: auto-ingest failed for {path}: {e}")
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
async def _build_file_context(files: tuple[str, ...]) -> str:
|
|
194
|
+
"""Read file contents and build a context prefix for the message.
|
|
195
|
+
|
|
196
|
+
Files larger than _AUTO_INGEST_THRESHOLD are indexed into the local
|
|
197
|
+
RAG store (auto-ingest) instead of being blindly truncated — the
|
|
198
|
+
message gets a preview plus a pointer to rag_search for the rest.
|
|
199
|
+
"""
|
|
158
200
|
if not files:
|
|
159
201
|
return ""
|
|
160
202
|
parts: list[str] = []
|
|
@@ -171,7 +213,14 @@ def _build_file_context(files: tuple[str, ...]) -> str:
|
|
|
171
213
|
try:
|
|
172
214
|
size = path.stat().st_size
|
|
173
215
|
if size > 1_000_000:
|
|
174
|
-
|
|
216
|
+
ingested = await _auto_ingest_file(path, path.read_text(encoding="utf-8", errors="replace"))
|
|
217
|
+
if ingested:
|
|
218
|
+
parts.append(
|
|
219
|
+
f"[{path.name} ({size} bytes) is too large to inline but has been indexed "
|
|
220
|
+
f"into the local RAG store — use rag_search to find relevant sections]"
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
parts.append(f"[File too large to include: {path.name} ({size} bytes)]")
|
|
175
224
|
continue
|
|
176
225
|
suffix = path.suffix.lower()
|
|
177
226
|
if suffix in _OFFICE_PARSERS:
|
|
@@ -182,6 +231,15 @@ def _build_file_context(files: tuple[str, ...]) -> str:
|
|
|
182
231
|
content = _summarize_ics(path)
|
|
183
232
|
else:
|
|
184
233
|
content = path.read_text(encoding="utf-8", errors="replace")
|
|
234
|
+
|
|
235
|
+
if len(content) > _AUTO_INGEST_THRESHOLD:
|
|
236
|
+
ingested = await _auto_ingest_file(path, content)
|
|
237
|
+
if ingested:
|
|
238
|
+
preview = content[:_AUTO_INGEST_THRESHOLD] + "\n... (truncated — indexed into RAG, use rag_search for the rest)"
|
|
239
|
+
parts.append(f"--- {path.name} (preview, {len(content)} chars total, indexed into RAG) ---\n{preview}\n--- end {path.name} preview ---")
|
|
240
|
+
total += _AUTO_INGEST_THRESHOLD
|
|
241
|
+
continue
|
|
242
|
+
|
|
185
243
|
if total + len(content) > max_total:
|
|
186
244
|
content = content[:max_total - total] + "\n... (truncated)"
|
|
187
245
|
parts.append(f"--- {path.name} ---\n{content}\n--- end {path.name} ---")
|
|
@@ -200,7 +258,7 @@ async def _one_shot(message: str, model: str | None = None, mode: str = "chat",
|
|
|
200
258
|
)
|
|
201
259
|
from elidia.cli.repl import ElidiaRepl
|
|
202
260
|
|
|
203
|
-
file_ctx = _build_file_context(files)
|
|
261
|
+
file_ctx = await _build_file_context(files)
|
|
204
262
|
full_message = f"{file_ctx}{message}" if file_ctx else message
|
|
205
263
|
|
|
206
264
|
repl = ElidiaRepl(forced_model=model, mode=mode)
|
|
@@ -643,3 +701,135 @@ def workflow_run(path: str) -> None:
|
|
|
643
701
|
await client.close()
|
|
644
702
|
|
|
645
703
|
asyncio.run(_run())
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
# --- RAG subcommands ---
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
async def _open_rag_engine():
|
|
710
|
+
"""Shared setup for the rag CLI subcommands: real API key required
|
|
711
|
+
(embeddings go through the AiUtils API), console error + SystemExit(1)
|
|
712
|
+
if missing so every subcommand fails the same clear way."""
|
|
713
|
+
from elidia.auth.keychain import get_api_key
|
|
714
|
+
from elidia.memory.embeddings import EmbeddingClient
|
|
715
|
+
from elidia.rag.engine import RagEngine
|
|
716
|
+
|
|
717
|
+
api_key = get_api_key()
|
|
718
|
+
if not api_key:
|
|
719
|
+
console.print("[red]No API key configured. Run: elidia auth login[/red]")
|
|
720
|
+
console.print("[dim]RAG ingestion/search needs it for embeddings.[/dim]")
|
|
721
|
+
raise SystemExit(1)
|
|
722
|
+
|
|
723
|
+
engine = RagEngine(EmbeddingClient(api_key=api_key))
|
|
724
|
+
engine.open()
|
|
725
|
+
return engine
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
@cli.group()
|
|
729
|
+
def rag() -> None:
|
|
730
|
+
"""Manage the local RAG (retrieval-augmented search) store."""
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
@rag.command("ingest")
|
|
734
|
+
@click.argument("path", type=click.Path(exists=True))
|
|
735
|
+
@click.option("--recursive/--no-recursive", default=True, help="Recurse into subdirectories (directories only)")
|
|
736
|
+
def rag_ingest(path: str, recursive: bool) -> None:
|
|
737
|
+
"""Ingest a file or directory into the local RAG store for later rag_search."""
|
|
738
|
+
logger.debug(f"Entered into rag_ingest: path={path}, recursive={recursive}")
|
|
739
|
+
from elidia.rag.ingest import FileIngestPipeline
|
|
740
|
+
|
|
741
|
+
target = Path(path)
|
|
742
|
+
|
|
743
|
+
async def _run() -> None:
|
|
744
|
+
engine = await _open_rag_engine()
|
|
745
|
+
pipeline = FileIngestPipeline(engine)
|
|
746
|
+
try:
|
|
747
|
+
if target.is_dir():
|
|
748
|
+
console.print(f"[cyan]Ingesting directory:[/cyan] {target}")
|
|
749
|
+
result = await pipeline.ingest_directory(target, recursive=recursive)
|
|
750
|
+
console.print(
|
|
751
|
+
f"[green]v[/green] Ingested {result['files']} file(s), "
|
|
752
|
+
f"{result['chunks']} chunk(s), skipped {result['skipped']}"
|
|
753
|
+
)
|
|
754
|
+
else:
|
|
755
|
+
console.print(f"[cyan]Ingesting file:[/cyan] {target}")
|
|
756
|
+
ids = await pipeline.ingest_file(target)
|
|
757
|
+
if ids:
|
|
758
|
+
console.print(f"[green]v[/green] Ingested {len(ids)} chunk(s) from {target.name}")
|
|
759
|
+
else:
|
|
760
|
+
console.print(
|
|
761
|
+
f"[yellow]Nothing ingested from {target.name}[/yellow] "
|
|
762
|
+
"(unsupported type, empty, too large, or already ingested — same content hash)"
|
|
763
|
+
)
|
|
764
|
+
finally:
|
|
765
|
+
engine.close()
|
|
766
|
+
|
|
767
|
+
asyncio.run(_run())
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
@rag.command("search")
|
|
771
|
+
@click.argument("query")
|
|
772
|
+
@click.option("--limit", default=5, type=int, help="Max results")
|
|
773
|
+
def rag_search_cmd(query: str, limit: int) -> None:
|
|
774
|
+
"""Search the local RAG store."""
|
|
775
|
+
logger.debug(f"Entered into rag_search_cmd: query={query!r}, limit={limit}")
|
|
776
|
+
|
|
777
|
+
async def _run() -> None:
|
|
778
|
+
engine = await _open_rag_engine()
|
|
779
|
+
try:
|
|
780
|
+
results = await engine.search(query, limit=limit)
|
|
781
|
+
if not results:
|
|
782
|
+
console.print("[yellow]No matching content found.[/yellow]")
|
|
783
|
+
return
|
|
784
|
+
for r in results:
|
|
785
|
+
doc = r.document
|
|
786
|
+
console.print(
|
|
787
|
+
f"\n[cyan]{doc.source}[/cyan] "
|
|
788
|
+
f"[dim](chunk {doc.chunk_index + 1}/{doc.total_chunks}, score={r.score:.3f})[/dim]"
|
|
789
|
+
)
|
|
790
|
+
console.print(doc.content[:500] + ("..." if len(doc.content) > 500 else ""))
|
|
791
|
+
finally:
|
|
792
|
+
engine.close()
|
|
793
|
+
|
|
794
|
+
asyncio.run(_run())
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
@rag.command("list")
|
|
798
|
+
def rag_list() -> None:
|
|
799
|
+
"""Show how much content is currently ingested."""
|
|
800
|
+
logger.debug("Entered into rag_list")
|
|
801
|
+
|
|
802
|
+
async def _run() -> None:
|
|
803
|
+
engine = await _open_rag_engine()
|
|
804
|
+
try:
|
|
805
|
+
counts = engine.count_documents()
|
|
806
|
+
if counts["sources"] == 0:
|
|
807
|
+
console.print("[yellow]Nothing has been ingested yet.[/yellow] Run: elidia rag ingest <path>")
|
|
808
|
+
else:
|
|
809
|
+
console.print(f"{counts['sources']} source(s), {counts['chunks']} chunk(s) ingested.")
|
|
810
|
+
finally:
|
|
811
|
+
engine.close()
|
|
812
|
+
|
|
813
|
+
asyncio.run(_run())
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
@rag.command("clear")
|
|
817
|
+
@click.option("--source", default=None, help="Only clear entries from this source path (default: clear everything)")
|
|
818
|
+
@click.confirmation_option(prompt="This permanently deletes ingested RAG data. Continue?")
|
|
819
|
+
def rag_clear(source: str | None) -> None:
|
|
820
|
+
"""Delete ingested content from the local RAG store."""
|
|
821
|
+
logger.debug(f"Entered into rag_clear: source={source}")
|
|
822
|
+
|
|
823
|
+
async def _run() -> None:
|
|
824
|
+
engine = await _open_rag_engine()
|
|
825
|
+
try:
|
|
826
|
+
if source:
|
|
827
|
+
n = engine.delete_source(source)
|
|
828
|
+
console.print(f"[green]v[/green] Deleted {n} chunk(s) from {source}")
|
|
829
|
+
else:
|
|
830
|
+
n = engine.clear_all()
|
|
831
|
+
console.print(f"[green]v[/green] Deleted {n} chunk(s) — RAG store is now empty")
|
|
832
|
+
finally:
|
|
833
|
+
engine.close()
|
|
834
|
+
|
|
835
|
+
asyncio.run(_run())
|
|
@@ -242,8 +242,10 @@ class ElidiaRepl:
|
|
|
242
242
|
logger.debug("Entered into cleanup")
|
|
243
243
|
from elidia.tools.browser import close_browser_session
|
|
244
244
|
from elidia.tools.database import close_database_session
|
|
245
|
+
from elidia.tools.rag import close_rag_session
|
|
245
246
|
await close_browser_session()
|
|
246
247
|
close_database_session()
|
|
248
|
+
close_rag_session()
|
|
247
249
|
if self._daemon:
|
|
248
250
|
await self._daemon.stop()
|
|
249
251
|
if self._mcp_registry:
|
|
@@ -386,6 +388,8 @@ class ElidiaRepl:
|
|
|
386
388
|
return self._cmd_pager(arg)
|
|
387
389
|
if cmd == "image":
|
|
388
390
|
return await self._cmd_image(arg)
|
|
391
|
+
if cmd == "rag":
|
|
392
|
+
return await self._cmd_rag(arg)
|
|
389
393
|
if cmd in ("quit", "exit"):
|
|
390
394
|
raise EOFError()
|
|
391
395
|
|
|
@@ -471,8 +475,10 @@ class ElidiaRepl:
|
|
|
471
475
|
def _cmd_new(self) -> bool:
|
|
472
476
|
from elidia.tools.browser import close_browser_session
|
|
473
477
|
from elidia.tools.database import close_database_session
|
|
478
|
+
from elidia.tools.rag import close_rag_session
|
|
474
479
|
asyncio.ensure_future(close_browser_session())
|
|
475
480
|
close_database_session()
|
|
481
|
+
close_rag_session()
|
|
476
482
|
|
|
477
483
|
# Compact current session before starting a new one
|
|
478
484
|
if self._compactor and len(self._messages) >= 4:
|
|
@@ -934,6 +940,69 @@ class ElidiaRepl:
|
|
|
934
940
|
|
|
935
941
|
return True
|
|
936
942
|
|
|
943
|
+
async def _cmd_rag(self, arg: str) -> bool:
|
|
944
|
+
logger.debug(f"Entered into _cmd_rag: arg={arg}")
|
|
945
|
+
from elidia.tools.rag import _get_session
|
|
946
|
+
|
|
947
|
+
parts = arg.split(maxsplit=1)
|
|
948
|
+
action = parts[0] if parts else ""
|
|
949
|
+
rest = parts[1] if len(parts) > 1 else ""
|
|
950
|
+
|
|
951
|
+
if action == "ingest" and rest:
|
|
952
|
+
from pathlib import Path as _Path
|
|
953
|
+
|
|
954
|
+
from elidia.rag.ingest import FileIngestPipeline
|
|
955
|
+
from elidia.tools.rag import _ensure_engine
|
|
956
|
+
|
|
957
|
+
target = _Path(rest).expanduser()
|
|
958
|
+
if not target.exists():
|
|
959
|
+
self._console.print(f"[red]Path not found: {rest}[/red]")
|
|
960
|
+
return True
|
|
961
|
+
try:
|
|
962
|
+
engine = await _ensure_engine()
|
|
963
|
+
except RuntimeError as e:
|
|
964
|
+
self._console.print(f"[red]{e}[/red]")
|
|
965
|
+
return True
|
|
966
|
+
pipeline = FileIngestPipeline(engine)
|
|
967
|
+
if target.is_dir():
|
|
968
|
+
self._console.print(f"[cyan]Ingesting directory:[/cyan] {target}")
|
|
969
|
+
result = await pipeline.ingest_directory(target)
|
|
970
|
+
self._console.print(
|
|
971
|
+
f"[green]v[/green] Ingested {result['files']} file(s), "
|
|
972
|
+
f"{result['chunks']} chunk(s), skipped {result['skipped']}"
|
|
973
|
+
)
|
|
974
|
+
else:
|
|
975
|
+
ids = await pipeline.ingest_file(target)
|
|
976
|
+
if ids:
|
|
977
|
+
self._console.print(f"[green]v[/green] Ingested {len(ids)} chunk(s) from {target.name}")
|
|
978
|
+
else:
|
|
979
|
+
self._console.print(f"[yellow]Nothing ingested from {target.name}[/yellow]")
|
|
980
|
+
return True
|
|
981
|
+
|
|
982
|
+
if action == "search" and rest:
|
|
983
|
+
from elidia.tools.rag import _rag_search
|
|
984
|
+
result = await _rag_search(rest, limit=5)
|
|
985
|
+
self._console.print(result.content)
|
|
986
|
+
return True
|
|
987
|
+
|
|
988
|
+
if action == "list" or (not action and not arg):
|
|
989
|
+
from elidia.tools.rag import _rag_list_sources
|
|
990
|
+
result = await _rag_list_sources()
|
|
991
|
+
self._console.print(result.content)
|
|
992
|
+
return True
|
|
993
|
+
|
|
994
|
+
if action == "clear":
|
|
995
|
+
session = _get_session()
|
|
996
|
+
if session.engine is not None:
|
|
997
|
+
n = session.engine.clear_all()
|
|
998
|
+
self._console.print(f"[green]v[/green] Deleted {n} chunk(s) — RAG store is now empty")
|
|
999
|
+
else:
|
|
1000
|
+
self._console.print("[dim]RAG store not open this session.[/dim]")
|
|
1001
|
+
return True
|
|
1002
|
+
|
|
1003
|
+
self._console.print("[yellow]Usage: /rag [ingest <path>|search <query>|list|clear][/yellow]")
|
|
1004
|
+
return True
|
|
1005
|
+
|
|
937
1006
|
async def _cmd_daemon(self, arg: str) -> bool:
|
|
938
1007
|
logger.debug(f"Entered into _cmd_daemon: arg={arg}")
|
|
939
1008
|
if not self._daemon:
|
|
@@ -30,6 +30,7 @@ ACTION_TIERS: dict[str, PermissionTier] = {
|
|
|
30
30
|
"clipboard_read": PermissionTier.AUTO,
|
|
31
31
|
"mcp_call_auto": PermissionTier.AUTO,
|
|
32
32
|
"browser_read": PermissionTier.AUTO,
|
|
33
|
+
"rag_search": PermissionTier.AUTO,
|
|
33
34
|
|
|
34
35
|
"file_write_project": PermissionTier.SESSION,
|
|
35
36
|
"command_exec": PermissionTier.SESSION,
|