agentic-cli 0.4.3__tar.gz → 0.4.4__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.
Files changed (168) hide show
  1. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/CHANGELOG.md +32 -0
  2. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/CLAUDE.md +0 -1
  3. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/PKG-INFO +4 -2
  4. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/examples/research_demo/agents.py +3 -2
  5. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/examples/research_demo/settings.py +14 -6
  6. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/examples/websearch_demo.py +5 -5
  7. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/pyproject.toml +4 -2
  8. agentic_cli-0.4.4/requirements.txt +4 -0
  9. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/__init__.py +4 -3
  10. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/cli/__init__.py +1 -1
  11. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/cli/app.py +63 -21
  12. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/cli/builtin_commands.py +139 -17
  13. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/cli/commands.py +0 -25
  14. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/cli/message_processor.py +120 -82
  15. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/cli/settings_introspection.py +0 -26
  16. agentic_cli-0.4.4/src/agentic_cli/cli/usage_tracker.py +154 -0
  17. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/cli/workflow_controller.py +20 -103
  18. agentic_cli-0.4.4/src/agentic_cli/config.py +338 -0
  19. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/constants.py +1 -1
  20. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/knowledge_base/manager.py +76 -82
  21. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/knowledge_base/vector_store.py +73 -54
  22. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/logging.py +0 -14
  23. agentic_cli-0.4.4/src/agentic_cli/persistence/_utils.py +81 -0
  24. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/persistence/artifacts.py +55 -28
  25. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/persistence/session.py +61 -28
  26. agentic_cli-0.4.4/src/agentic_cli/settings_mixins.py +120 -0
  27. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/settings_persistence.py +24 -8
  28. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/__init__.py +3 -1
  29. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/arxiv_tools.py +11 -3
  30. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/execution_tools.py +1 -1
  31. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/executor.py +6 -2
  32. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/glob_tool.py +1 -7
  33. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/grep_tool.py +3 -1
  34. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/hitl_tools.py +29 -1
  35. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/interaction_tools.py +5 -3
  36. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/knowledge_tools.py +150 -87
  37. agentic_cli-0.4.4/src/agentic_cli/tools/pdf_utils.py +55 -0
  38. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/registry.py +51 -12
  39. agentic_cli-0.4.4/src/agentic_cli/tools/sandbox/__init__.py +57 -0
  40. agentic_cli-0.4.4/src/agentic_cli/tools/sandbox/backends/__init__.py +5 -0
  41. agentic_cli-0.4.4/src/agentic_cli/tools/sandbox/backends/base.py +46 -0
  42. agentic_cli-0.4.4/src/agentic_cli/tools/sandbox/backends/jupyter_local.py +238 -0
  43. agentic_cli-0.4.4/src/agentic_cli/tools/sandbox/manager.py +174 -0
  44. agentic_cli-0.4.4/src/agentic_cli/tools/sandbox/models.py +16 -0
  45. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/search.py +7 -7
  46. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/shell/executor.py +2 -64
  47. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/task_tools.py +38 -15
  48. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/webfetch/converter.py +6 -18
  49. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/webfetch_tool.py +22 -5
  50. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/__init__.py +8 -2
  51. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/adk/event_processor.py +4 -98
  52. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/adk/manager.py +184 -49
  53. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/base_manager.py +254 -64
  54. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/context.py +2 -0
  55. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/events.py +42 -47
  56. agentic_cli-0.4.4/src/agentic_cli/workflow/factory.py +112 -0
  57. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/langgraph/__init__.py +0 -2
  58. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/langgraph/graph_builder.py +44 -1
  59. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/langgraph/manager.py +142 -46
  60. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/langgraph/state.py +0 -18
  61. agentic_cli-0.4.4/src/agentic_cli/workflow/models.py +508 -0
  62. agentic_cli-0.4.4/src/agentic_cli/workflow/retry.py +47 -0
  63. agentic_cli-0.4.4/src/agentic_cli/workflow/settings.py +448 -0
  64. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/task_progress.py +1 -1
  65. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/tool_summaries.py +5 -0
  66. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/integration/test_adk_integration.py +20 -36
  67. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_commands.py +0 -49
  68. agentic_cli-0.4.4/tests/test_concurrent_stores.py +192 -0
  69. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_config.py +41 -28
  70. agentic_cli-0.4.4/tests/test_context_trimming.py +249 -0
  71. agentic_cli-0.4.4/tests/test_context_window.py +89 -0
  72. agentic_cli-0.4.4/tests/test_grep_tool_cache.py +29 -0
  73. agentic_cli-0.4.4/tests/test_input_callback.py +26 -0
  74. agentic_cli-0.4.4/tests/test_kb_helpers.py +63 -0
  75. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_knowledge_tools.py +139 -2
  76. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_langgraph.py +2 -30
  77. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_memory.py +37 -47
  78. agentic_cli-0.4.4/tests/test_model_registry.py +500 -0
  79. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_planning.py +26 -25
  80. agentic_cli-0.4.4/tests/test_session_save_resume.py +490 -0
  81. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_settings_persistence.py +35 -5
  82. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_task_tools.py +154 -154
  83. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_tool_summaries.py +10 -33
  84. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_tools.py +152 -2
  85. agentic_cli-0.4.4/tests/test_update_setting.py +21 -0
  86. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_usage_tracker.py +84 -2
  87. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_webfetch.py +35 -35
  88. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_workflow.py +0 -1
  89. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_workflow_controller.py +20 -19
  90. agentic_cli-0.4.4/tests/tools/test_sandbox.py +579 -0
  91. agentic_cli-0.4.3/requirements.txt +0 -13
  92. agentic_cli-0.4.3/src/agentic_cli/cli/settings.py +0 -58
  93. agentic_cli-0.4.3/src/agentic_cli/cli/usage_tracker.py +0 -87
  94. agentic_cli-0.4.3/src/agentic_cli/config.py +0 -610
  95. agentic_cli-0.4.3/src/agentic_cli/persistence/_utils.py +0 -33
  96. agentic_cli-0.4.3/src/agentic_cli/resolvers.py +0 -28
  97. agentic_cli-0.4.3/src/agentic_cli/workflow/settings.py +0 -153
  98. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/.gitignore +0 -0
  99. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/LICENSE +0 -0
  100. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/README.md +0 -0
  101. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/environment.yml +0 -0
  102. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/examples/arxiv_demo.py +0 -0
  103. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/examples/fileops_demo.py +0 -0
  104. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/examples/hello_agent.py +0 -0
  105. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/examples/hello_langgraph.py +0 -0
  106. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/examples/memory_demo.py +0 -0
  107. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/examples/planning_demo.py +0 -0
  108. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/examples/research_demo/__init__.py +0 -0
  109. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/examples/research_demo/__main__.py +0 -0
  110. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/examples/research_demo/app.py +0 -0
  111. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/examples/research_demo/commands.py +0 -0
  112. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/examples/shell_demo.py +0 -0
  113. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/examples/webfetch_demo.py +0 -0
  114. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/requirements-dev.txt +0 -0
  115. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/cli/settings_command.py +0 -0
  116. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/knowledge_base/__init__.py +0 -0
  117. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/knowledge_base/_mocks.py +0 -0
  118. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/knowledge_base/embeddings.py +0 -0
  119. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/knowledge_base/models.py +0 -0
  120. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/knowledge_base/sources.py +0 -0
  121. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/persistence/__init__.py +0 -0
  122. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/arxiv_source.py +0 -0
  123. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/file_read.py +0 -0
  124. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/file_write.py +0 -0
  125. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/memory_tools.py +0 -0
  126. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/planning_tools.py +0 -0
  127. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/shell/__init__.py +0 -0
  128. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/shell/audit.py +0 -0
  129. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/shell/classifier.py +0 -0
  130. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/shell/config.py +0 -0
  131. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/shell/models.py +0 -0
  132. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/shell/path_analyzer.py +0 -0
  133. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/shell/preprocessor.py +0 -0
  134. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/shell/risk_assessor.py +0 -0
  135. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/shell/sandbox.py +0 -0
  136. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/shell/tokenizer.py +0 -0
  137. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/webfetch/__init__.py +0 -0
  138. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/webfetch/fetcher.py +0 -0
  139. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/webfetch/robots.py +0 -0
  140. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/webfetch/summarizer.py +0 -0
  141. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/tools/webfetch/validator.py +0 -0
  142. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/adk/__init__.py +0 -0
  143. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/adk/llm_event_logger.py +0 -0
  144. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/config.py +0 -0
  145. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/langgraph/persistence/__init__.py +0 -0
  146. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/langgraph/persistence/checkpointers.py +0 -0
  147. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/langgraph/persistence/stores.py +0 -0
  148. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/langgraph/tools/__init__.py +0 -0
  149. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/langgraph/tools/file_search.py +0 -0
  150. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/langgraph/tools/shell.py +0 -0
  151. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/src/agentic_cli/workflow/thinking.py +0 -0
  152. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/__init__.py +0 -0
  153. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/conftest.py +0 -0
  154. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/integration/__init__.py +0 -0
  155. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/integration/conftest.py +0 -0
  156. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/integration/helpers.py +0 -0
  157. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/integration/test_langgraph_integration.py +0 -0
  158. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/integration/test_live.py +0 -0
  159. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_arxiv_tools.py +0 -0
  160. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_embeddings.py +0 -0
  161. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_hitl.py +0 -0
  162. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_knowledge_base.py +0 -0
  163. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_persistence.py +0 -0
  164. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_thinking.py +0 -0
  165. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_token_caching.py +0 -0
  166. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/test_vector_store.py +0 -0
  167. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/tools/__init__.py +0 -0
  168. {agentic_cli-0.4.3 → agentic_cli-0.4.4}/tests/tools/test_shell_security.py +0 -0
@@ -5,6 +5,38 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.4] - 2026-03-11
9
+
10
+ ### Added
11
+ - **Session Save/Resume**: Persistent conversations across CLI exits with `/save` and `/resume`
12
+ - **Sandbox Executor**: Stateful multi-turn Python code execution with sandboxing and default deps
13
+ - **Context Window Management**: Native ADK and LangGraph context trimming with source-level detection
14
+ - **Context Window Visibility**: Trim detection, status bar token display, and `/status` breakdown
15
+ - **Dynamic Model Registry**: Replace static model lists with live API discovery from Google/Anthropic
16
+ - **Task Progress Display**: Rich-colored task progress in thinking box, persisted across turns
17
+
18
+ ### Changed
19
+ - **Architecture**: Separated workflow+tools layer from UI/CLI layer; composable settings mixins
20
+ - **DRY/SOLID Cleanup** (PR #59, #60): Deduplicated config paths, ArXiv parsing, session persistence, artifact loading; extracted `drain_trim_events()`, `format_detail_rows()`, `format_task_checklist()`; removed dead code (`get_help()`, `FinanceResearchState`, unused settings introspection); replaced dynamic `type()` with `_SessionEvent` class; moved token-drop heuristic into `UsageTracker`
21
+ - **MessageProcessor**: Removed dead code, unused params, cleaner state init
22
+ - **HITL**: Removed dead Future-based machinery, simplified callback path
23
+ - **Ripgrep**: Cache availability check with `lru_cache`
24
+
25
+ ### Fixed
26
+ - Executor import validation blocking allowed submodules
27
+ - Settings not persisting (replaced `exclude_defaults` with explicit exclusion)
28
+ - `verbose_thinking` not persisting across restarts
29
+ - Thread safety, path traversal, and TOCTOU race conditions in persistence
30
+ - VectorStore crash safety: write mappings before FAISS index
31
+ - Embeddings: update in-memory state after `ingest_document`
32
+ - `open_document`: extension allowlist, DANGEROUS permission, audit logging
33
+
34
+ ### Removed
35
+ - `FinanceResearchState` (domain-specific code in shared library)
36
+ - Dead `get_help()`, `get_ui_section`, `is_ui_hidden` methods
37
+ - Dead `generate_tool_summary`, `clear_context`/`unbind_context`, model constant re-exports
38
+ - Dead `cli/settings.py` re-export shim
39
+
8
40
  ## [0.4.3] - 2026-02-12
9
41
 
10
42
  ### Changed
@@ -20,7 +20,6 @@ agentic-cli/
20
20
  │ ├── __init__.py # Package exports, lazy imports
21
21
  │ ├── config.py # BaseSettings (pydantic-settings)
22
22
  │ ├── constants.py # Shared constants, truncate()
23
- │ ├── resolvers.py # Model/path constants (GOOGLE_MODELS, etc.)
24
23
  │ ├── settings_persistence.py
25
24
  │ ├── logging.py
26
25
  │ ├── cli/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-cli
3
- Version: 0.4.3
3
+ Version: 0.4.4
4
4
  Summary: A framework for building domain-specific agentic CLI applications
5
5
  Project-URL: Homepage, https://github.com/shoom1/agentic-cli
6
6
  Project-URL: Repository, https://github.com/shoom1/agentic-cli
@@ -17,6 +17,8 @@ Requires-Python: >=3.12
17
17
  Requires-Dist: feedparser>=6.0.0
18
18
  Requires-Dist: google-adk[genai]>=0.4.0
19
19
  Requires-Dist: httpx>=0.27.0
20
+ Requires-Dist: ipykernel>=6.0.0
21
+ Requires-Dist: jupyter-client>=8.0.0
20
22
  Requires-Dist: numpy>=1.26.0
21
23
  Requires-Dist: prompt-toolkit>=3.0.0
22
24
  Requires-Dist: pydantic-settings>=2.0.0
@@ -24,7 +26,7 @@ Requires-Dist: pydantic>=2.0.0
24
26
  Requires-Dist: pypdf>=4.0.0
25
27
  Requires-Dist: rich>=13.0.0
26
28
  Requires-Dist: structlog>=24.0.0
27
- Requires-Dist: thinking-prompt>=0.2.3
29
+ Requires-Dist: thinking-prompt>=0.2.5
28
30
  Provides-Extra: dev
29
31
  Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
30
32
  Requires-Dist: pytest-cov>=6.0.0; extra == 'dev'
@@ -108,7 +108,7 @@ When the user asks you to research something:
108
108
  6. For arXiv paper research, **delegate to arxiv_specialist**
109
109
  7. Execute ONE task at a time, updating the plan after each
110
110
  8. Use `web_fetch` to extract information from specific URLs found during research
111
- 9. Use `execute_python` for data analysis, calculations, or processing
111
+ 9. Use `execute_python` for quick calculations and data validation
112
112
  10. Use `ask_clarification` when you need user input to proceed
113
113
  11. Update the plan with `save_plan` if you discover changes are needed
114
114
  12. Store learnings with `save_memory` and share them with the user
@@ -156,7 +156,8 @@ AGENT_CONFIGS = [
156
156
  # arXiv (2 tools)
157
157
  search_arxiv,
158
158
  fetch_arxiv_paper,
159
- # Document store (3 tools)
159
+ # Document store (4 tools)
160
+ search_knowledge_base,
160
161
  ingest_document,
161
162
  list_documents,
162
163
  read_document,
@@ -2,7 +2,6 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
- from pydantic import Field
6
5
  from pydantic_settings import SettingsConfigDict
7
6
 
8
7
  from agentic_cli import BaseSettings
@@ -27,10 +26,19 @@ class ResearchDemoSettings(BaseSettings):
27
26
  extra="ignore",
28
27
  )
29
28
 
30
- app_name: str = Field(default="research_demo")
31
- workspace_dir: Path = Field(default=Path.home() / ".research_demo")
32
-
33
29
  def __init__(self, **kwargs):
34
- # Override verbose_thinking default to False (concise mode)
35
- kwargs.setdefault("verbose_thinking", False)
30
+ kwargs.setdefault("app_name", "research_demo")
31
+ kwargs.setdefault("workspace_dir", Path.home() / ".research_demo")
36
32
  super().__init__(**kwargs)
33
+
34
+ def model_post_init(self, __context):
35
+ """Override verbose_thinking default without blocking JSON persistence.
36
+
37
+ kwargs.setdefault would inject at init level (highest priority),
38
+ overriding saved JSON values. model_post_init runs after all sources
39
+ are resolved, and model_fields_set tracks which fields were explicitly
40
+ set by any source (env, JSON, init kwargs). We only apply our custom
41
+ default when no source provided a value.
42
+ """
43
+ if "verbose_thinking" not in self.model_fields_set:
44
+ object.__setattr__(self, "verbose_thinking", False)
@@ -22,7 +22,7 @@ import sys
22
22
 
23
23
  from agentic_cli.tools.search import (
24
24
  web_search,
25
- SearchResult,
25
+ WebSearchResult,
26
26
  TavilyBackend,
27
27
  BraveBackend,
28
28
  SEARCH_BACKENDS,
@@ -89,20 +89,20 @@ def demo_configuration_check():
89
89
 
90
90
 
91
91
  def demo_search_result_format():
92
- """Demo the SearchResult data structure."""
92
+ """Demo the WebSearchResult data structure."""
93
93
  print("\n" + "=" * 60)
94
- print("SearchResult Data Structure")
94
+ print("WebSearchResult Data Structure")
95
95
  print("=" * 60)
96
96
 
97
97
  # Create sample result
98
- sample = SearchResult(
98
+ sample = WebSearchResult(
99
99
  title="Example Search Result",
100
100
  url="https://example.com/article",
101
101
  snippet="This is a sample snippet showing what search results look like...",
102
102
  score=0.95,
103
103
  )
104
104
 
105
- print(" SearchResult fields:")
105
+ print(" WebSearchResult fields:")
106
106
  print(f" title: {sample.title}")
107
107
  print(f" url: {sample.url}")
108
108
  print(f" snippet: {sample.snippet[:50]}...")
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agentic-cli"
7
- version = "0.4.3"
7
+ version = "0.4.4"
8
8
  description = "A framework for building domain-specific agentic CLI applications"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -21,7 +21,7 @@ classifiers = [
21
21
  "Programming Language :: Python :: 3.13",
22
22
  ]
23
23
  dependencies = [
24
- "thinking-prompt>=0.2.3",
24
+ "thinking-prompt>=0.2.5",
25
25
  "google-adk[genai]>=0.4.0",
26
26
  "pydantic>=2.0.0",
27
27
  "pydantic-settings>=2.0.0",
@@ -32,6 +32,8 @@ dependencies = [
32
32
  "numpy>=1.26.0",
33
33
  "feedparser>=6.0.0",
34
34
  "pypdf>=4.0.0",
35
+ "jupyter_client>=8.0.0",
36
+ "ipykernel>=6.0.0",
35
37
  ]
36
38
 
37
39
  [project.optional-dependencies]
@@ -0,0 +1,4 @@
1
+ # Install from pyproject.toml (single source of truth for dependencies)
2
+ # Note: thinking-prompt is a local dependency, install separately:
3
+ # pip install -e /path/to/thinking-prompt
4
+ -e .
@@ -23,7 +23,7 @@ Note: GoogleADKWorkflowManager and LangGraphWorkflowManager are lazy-loaded to a
23
23
  """
24
24
 
25
25
  from agentic_cli.cli.app import BaseCLIApp
26
- from agentic_cli.cli.workflow_controller import create_workflow_manager_from_settings
26
+ from agentic_cli.workflow.factory import create_workflow_manager_from_settings
27
27
  from agentic_cli.cli.commands import Command, CommandRegistry
28
28
  from agentic_cli.workflow.config import AgentConfig
29
29
  from agentic_cli.workflow.events import WorkflowEvent, EventType
@@ -40,7 +40,7 @@ from agentic_cli.config import (
40
40
  )
41
41
  from agentic_cli.settings_persistence import SettingsPersistence
42
42
  from agentic_cli.workflow.settings import WorkflowSettingsMixin
43
- from agentic_cli.cli.settings import CLISettingsMixin
43
+ from agentic_cli.settings_mixins import AppSettingsMixin, CLISettingsMixin
44
44
 
45
45
  # Heavy imports - lazy loaded on first access
46
46
  _lazy_imports = {
@@ -88,7 +88,8 @@ __all__ = [
88
88
  "reload_settings",
89
89
  # Settings Mixins (organized settings by domain)
90
90
  "WorkflowSettingsMixin",
91
+ "AppSettingsMixin",
91
92
  "CLISettingsMixin",
92
93
  ]
93
94
 
94
- __version__ = "0.4.3"
95
+ __version__ = "0.4.4"
@@ -11,7 +11,7 @@ from agentic_cli.cli.commands import (
11
11
  from agentic_cli.cli.app import BaseCLIApp
12
12
  from agentic_cli.cli.message_processor import MessageProcessor, MessageHistory, MessageType
13
13
  from agentic_cli.cli.workflow_controller import WorkflowController
14
- from agentic_cli.cli.settings import CLISettingsMixin
14
+ from agentic_cli.settings_mixins import CLISettingsMixin
15
15
 
16
16
  __all__ = [
17
17
  "AppInfo",
@@ -111,6 +111,7 @@ class BaseCLIApp:
111
111
  app_info: AppInfo,
112
112
  agent_configs: list["AgentConfig"],
113
113
  settings: BaseSettings,
114
+ session_id: str | None = None,
114
115
  ) -> None:
115
116
  """Initialize the CLI application.
116
117
 
@@ -118,7 +119,10 @@ class BaseCLIApp:
118
119
  app_info: Application info (name, version, welcome message)
119
120
  agent_configs: List of agent configurations for the workflow
120
121
  settings: Application settings instance
122
+ session_id: Optional session ID for save/resume. If provided,
123
+ the session will be loaded on startup and saved on exit.
121
124
  """
125
+ self._session_id = session_id
122
126
  # === Configuration ===
123
127
  self._app_info = app_info
124
128
  self._settings = settings
@@ -282,6 +286,16 @@ class BaseCLIApp:
282
286
  """Get the application settings."""
283
287
  return self._settings
284
288
 
289
+ @property
290
+ def session_id(self) -> str | None:
291
+ """Get the persistent session ID, if any."""
292
+ return self._session_id
293
+
294
+ @session_id.setter
295
+ def session_id(self, value: str | None) -> None:
296
+ """Set the persistent session ID."""
297
+ self._session_id = value
298
+
285
299
  def stop(self) -> None:
286
300
  """Stop the application."""
287
301
  self.should_exit = True
@@ -305,32 +319,18 @@ class BaseCLIApp:
305
319
  needs_reinit = False
306
320
  new_model = changes.get("model")
307
321
 
308
- # Settings that require special handling
309
- special_settings = {"model", "thinking_effort"}
310
-
311
- # Apply model change (requires dedicated setter + reinit)
312
- if new_model:
313
- try:
314
- self._settings.set_model(new_model)
315
- needs_reinit = True
316
- except ValueError as e:
317
- self.session.add_error(f"Failed to set model: {e}")
318
- return
322
+ reinit_settings = {"model", "thinking_effort"}
319
323
 
320
- # Apply thinking effort change (requires dedicated setter + reinit)
321
- if "thinking_effort" in changes:
324
+ for key, value in changes.items():
322
325
  try:
323
- self._settings.set_thinking_effort(changes["thinking_effort"])
324
- needs_reinit = True
326
+ self._settings.update_setting(key, value)
327
+ if key in reinit_settings:
328
+ needs_reinit = True
325
329
  except ValueError as e:
326
- self.session.add_error(f"Failed to set thinking effort: {e}")
330
+ label = key.replace("_", " ").title()
331
+ self.session.add_error(f"Failed to set {label}: {e}")
327
332
  return
328
333
 
329
- # Apply all other settings directly
330
- for key, value in changes.items():
331
- if key not in special_settings:
332
- object.__setattr__(self._settings, key, value)
333
-
334
334
  # Reinitialize workflow if needed
335
335
  if needs_reinit and self._workflow_controller.is_ready:
336
336
  try:
@@ -351,7 +351,9 @@ class BaseCLIApp:
351
351
  ClearCommand,
352
352
  ExitCommand,
353
353
  StatusCommand,
354
+ SandboxCommand,
354
355
  PapersCommand,
356
+ SessionsCommand,
355
357
  )
356
358
  from agentic_cli.cli.settings_command import SettingsCommand
357
359
 
@@ -359,8 +361,10 @@ class BaseCLIApp:
359
361
  self.command_registry.register(ClearCommand())
360
362
  self.command_registry.register(ExitCommand())
361
363
  self.command_registry.register(StatusCommand())
364
+ self.command_registry.register(SandboxCommand())
362
365
  self.command_registry.register(SettingsCommand())
363
366
  self.command_registry.register(PapersCommand())
367
+ self.command_registry.register(SessionsCommand())
364
368
 
365
369
  async def process_input(self, user_input: str) -> None:
366
370
  """Process user input.
@@ -448,11 +452,45 @@ class BaseCLIApp:
448
452
  except Exception as e:
449
453
  logger.error("activity_log_save_failed", error=str(e))
450
454
 
455
+ async def _load_session_on_startup(self) -> None:
456
+ """Load a saved session after workflow initialization."""
457
+ if not self._session_id:
458
+ return
459
+
460
+ if not await self._workflow_controller.ensure_initialized(self.session):
461
+ self.session.add_warning("Cannot load session — workflow not initialized.")
462
+ return
463
+
464
+ workflow = self._workflow_controller.workflow
465
+ loaded = await workflow.load_session(self._session_id)
466
+ if loaded:
467
+ self.session.add_success(f"Session '{self._session_id}' resumed.")
468
+ else:
469
+ self.session.add_message("system", f"New session '{self._session_id}'.")
470
+
471
+ async def _save_session_on_exit(self) -> None:
472
+ """Save the current session on exit."""
473
+ if not self._session_id:
474
+ return
475
+
476
+ if not self._workflow_controller.is_ready:
477
+ return
478
+
479
+ workflow = self._workflow_controller.workflow
480
+ result = await workflow.save_session(self._session_id)
481
+ if result.get("success"):
482
+ logger.info("session_saved_on_exit", session_id=self._session_id)
483
+ else:
484
+ logger.error("session_save_on_exit_failed", error=result.get("error"))
485
+
451
486
  async def run(self) -> None:
452
487
  """Run the main application loop."""
453
488
  logger.info("repl_starting")
454
489
 
455
490
  async with self._workflow_controller.background_init(self.session):
491
+ if self._session_id:
492
+ await self._load_session_on_startup()
493
+
456
494
  # Register input handler
457
495
  @self.session.on_input
458
496
  async def handle_input(text: str) -> None:
@@ -463,6 +501,10 @@ class BaseCLIApp:
463
501
  # Run the session - user sees prompt immediately!
464
502
  await self.session.run_async()
465
503
 
504
+ # Save persistent session on exit
505
+ if self._session_id:
506
+ await self._save_session_on_exit()
507
+
466
508
  # Auto-save activity log if enabled
467
509
  if self._settings.log_activity and len(self.message_history) > 0:
468
510
  await self._save_activity_log()
@@ -108,33 +108,98 @@ class StatusCommand(Command):
108
108
  if init_error:
109
109
  table.add_row("Error", f"[red]{init_error}[/red]")
110
110
 
111
+ # Persistent session info
112
+ sid = app.session_id
113
+ if sid:
114
+ table.add_row("Session", f"{sid} (persistent)")
115
+ else:
116
+ table.add_row("Session", "ephemeral (not saved)")
117
+
111
118
  # Message history stats
112
119
  table.add_row("Messages", str(len(app.message_history)))
113
120
 
114
121
  # Token usage breakdown
115
122
  tracker = getattr(app, "usage_tracker", None)
116
- if tracker is not None and tracker.invocation_count > 0:
117
- from agentic_cli.cli.usage_tracker import format_tokens
118
-
119
- table.add_row("", "") # Spacer
120
- table.add_row("LLM Invocations", str(tracker.invocation_count))
121
- table.add_row("Input Tokens", format_tokens(tracker.prompt_tokens))
122
- table.add_row("Output Tokens", format_tokens(tracker.completion_tokens))
123
- table.add_row("Total Tokens", format_tokens(tracker.total_tokens))
124
- if tracker.cached_tokens > 0:
125
- table.add_row("Cached Tokens", format_tokens(tracker.cached_tokens))
126
- if tracker.cache_creation_tokens > 0:
127
- table.add_row("Cache Creation", format_tokens(tracker.cache_creation_tokens))
128
- if tracker.thinking_tokens > 0:
129
- table.add_row("Thinking Tokens", format_tokens(tracker.thinking_tokens))
130
- if tracker.total_latency_ms > 0:
131
- avg_ms = tracker.total_latency_ms / tracker.invocation_count
132
- table.add_row("Avg Latency", f"{avg_ms:.0f}ms")
123
+ if tracker is not None:
124
+ for label, value in tracker.format_detail_rows():
125
+ table.add_row(label, value)
133
126
 
134
127
  panel = Panel(table, title="[bold]Session Status[/bold]", border_style="cyan")
135
128
  app.session.add_rich(panel)
136
129
 
137
130
 
131
+ class SandboxCommand(Command):
132
+ """Manage sandbox sessions."""
133
+
134
+ def __init__(self) -> None:
135
+ super().__init__(
136
+ name="sandbox",
137
+ description="List and manage sandbox sessions",
138
+ aliases=["sb"],
139
+ usage="/sandbox [reset [session_id] [--all]]",
140
+ examples=["/sandbox", "/sandbox reset", "/sandbox reset my_session", "/sandbox reset --all"],
141
+ category=CommandCategory.WORKFLOW,
142
+ )
143
+
144
+ async def execute(self, args: str, app: Any) -> None:
145
+ """List or reset sandbox sessions."""
146
+ # Get sandbox manager from workflow
147
+ try:
148
+ workflow = app.workflow
149
+ manager = getattr(workflow, "sandbox_manager", None)
150
+ except (RuntimeError, AttributeError):
151
+ manager = None
152
+
153
+ if manager is None:
154
+ app.session.add_warning(
155
+ "Sandbox not available. Add sandbox tools to your agent config to enable it."
156
+ )
157
+ return
158
+
159
+ parsed = self.parse_args(args)
160
+ subcommand = parsed.positional.strip()
161
+
162
+ if subcommand.startswith("reset"):
163
+ # Parse session_id from after "reset"
164
+ rest = subcommand[len("reset"):].strip()
165
+ if parsed.has_flag("all"):
166
+ # Reset all sessions
167
+ sessions = manager.list_sessions()
168
+ if not sessions:
169
+ app.session.add_message("system", "No active sandbox sessions.")
170
+ return
171
+ for s in sessions:
172
+ manager.reset_session(s["session_id"])
173
+ app.session.add_success(f"Reset {len(sessions)} sandbox session(s).")
174
+ else:
175
+ session_id = rest if rest else "default"
176
+ was_active = manager.reset_session(session_id)
177
+ if was_active:
178
+ app.session.add_success(f"Sandbox session '{session_id}' reset.")
179
+ else:
180
+ app.session.add_warning(f"Sandbox session '{session_id}' was not active.")
181
+ else:
182
+ # List sessions
183
+ sessions = manager.list_sessions()
184
+ if not sessions:
185
+ app.session.add_message("system", "No active sandbox sessions.")
186
+ return
187
+
188
+ table = Table(title="Sandbox Sessions", show_lines=False, padding=(0, 1))
189
+ table.add_column("Session ID", style="bold cyan", no_wrap=True)
190
+ table.add_column("Working Dir", style="dim")
191
+ table.add_column("Executions", style="dim", no_wrap=True, justify="right")
192
+
193
+ for s in sessions:
194
+ table.add_row(
195
+ s["session_id"],
196
+ s["working_dir"],
197
+ str(s["execution_count"]),
198
+ )
199
+
200
+ app.session.add_rich(table)
201
+
202
+
138
203
  class PapersCommand(Command):
139
204
  """List documents in the knowledge base."""
140
205
 
@@ -227,3 +292,60 @@ class PapersCommand(Command):
227
292
  )
228
293
 
229
294
  app.session.add_rich(table)
295
+
296
+
297
+ class SessionsCommand(Command):
298
+ """List and manage saved sessions."""
299
+
300
+ def __init__(self) -> None:
301
+ super().__init__(
302
+ name="sessions",
303
+ description="List saved sessions",
304
+ aliases=["sess"],
305
+ usage="/sessions [--delete=<id>]",
306
+ examples=["/sessions", "/sessions --delete=my-research"],
307
+ category=CommandCategory.SESSION,
308
+ )
309
+
310
+ async def execute(self, args: str, app: Any) -> None:
311
+ """Display saved sessions or delete one."""
312
+ from agentic_cli.persistence.session import SessionPersistence
313
+
314
+ parsed = self.parse_args(args)
315
+ delete_id = parsed.get_option("delete", "", str) or ""
316
+
317
+ persistence = SessionPersistence(app.settings)
318
+
319
+ if delete_id:
320
+ if persistence.delete_session(delete_id):
321
+ app.session.add_success(f"Session '{delete_id}' deleted.")
322
+ else:
323
+ app.session.add_error(f"Session '{delete_id}' not found.")
324
+ return
325
+
326
+ sessions = persistence.list_sessions()
327
+ if not sessions:
328
+ app.session.add_message("system", "No saved sessions.")
329
+ return
330
+
331
+ table = Table(title="Saved Sessions", show_lines=False, padding=(0, 1))
332
+ table.add_column("Session ID", style="bold cyan")
333
+ table.add_column("Messages", style="dim", justify="right")
334
+ table.add_column("Last Saved", style="dim")
335
+ table.add_column("Created", style="dim")
336
+
337
+ current_sid = app.session_id
338
+
339
+ for s in sessions:
340
+ sid = s["session_id"]
341
+ label = f"* {sid}" if sid == current_sid else sid
342
+ saved_at = s.get("saved_at", "")[:19].replace("T", " ")
343
+ created_at = s.get("created_at", "")[:10]
344
+ table.add_row(
345
+ label,
346
+ str(s.get("message_count", 0)),
347
+ saved_at,
348
+ created_at,
349
+ )
350
+
351
+ app.session.add_rich(table)
@@ -245,31 +245,6 @@ class Command(ABC):
245
245
 
246
246
  return [strip_quotes(token) for token in tokens]
247
247
 
248
- def get_help(self) -> str:
249
- """Get detailed help text for this command.
250
-
251
- Returns:
252
- Formatted help string
253
- """
254
- lines = [
255
- f"**/{self.name}**",
256
- f" {self.description}",
257
- "",
258
- f"**Usage:** `{self.usage}`",
259
- ]
260
-
261
- if self.aliases:
262
- lines.append(f"**Aliases:** {', '.join(f'/{a}' for a in self.aliases)}")
263
-
264
- if self.examples:
265
- lines.append("")
266
- lines.append("**Examples:**")
267
- for example in self.examples:
268
- lines.append(f" `{example}`")
269
-
270
- return "\n".join(lines)
271
-
272
-
273
248
  class CommandRegistry:
274
249
  """Registry for managing slash commands.
275
250