henchman-ai 0.1.13__py3-none-any.whl → 0.1.14__py3-none-any.whl

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.
henchman/cli/input.py CHANGED
@@ -210,14 +210,14 @@ class KeyMonitor:
210
210
  self._in_raw_mode = True
211
211
  with self.input.attach(lambda: None):
212
212
  while not self._stop_event.is_set() and not self._exit_event.is_set() and self._suspended.is_set():
213
- # Check for keys every 100ms
213
+ # Check for keys every 10ms
214
214
  keys = self.input.read_keys()
215
215
  for key in keys:
216
216
  if key.key == Keys.Escape:
217
217
  self._stop_event.set()
218
218
  elif key.key == Keys.ControlC:
219
219
  self._exit_event.set()
220
- await asyncio.sleep(0.1)
220
+ await asyncio.sleep(0.01)
221
221
  finally:
222
222
  self._in_raw_mode = False
223
223
  # If we were suspended, wait a bit before potentially re-entering raw mode
henchman/cli/repl.py CHANGED
@@ -198,6 +198,30 @@ class Repl:
198
198
 
199
199
  return status
200
200
 
201
+ def _get_rich_status_message(self) -> str:
202
+ """Get rich status message for persistent display."""
203
+ from henchman.utils.tokens import TokenCounter
204
+
205
+ parts = []
206
+
207
+ # Plan Mode
208
+ plan_mode = self.session.plan_mode if self.session else False
209
+ parts.append("[yellow]PLAN[/]" if plan_mode else "[blue]CHAT[/]")
210
+
211
+ # Tokens
212
+ try:
213
+ msgs = self.agent.get_messages_for_api()
214
+ tokens = TokenCounter.count_messages(msgs)
215
+ parts.append(f"Tokens: ~[cyan]{tokens}[/]")
216
+ except Exception:
217
+ pass
218
+
219
+ # RAG Status
220
+ if self.rag_system and getattr(self.rag_system, "is_indexing", False):
221
+ parts.append("[cyan]RAG: Indexing...[/]")
222
+
223
+ return " | ".join(parts)
224
+
201
225
  def _register_builtin_tools(self) -> None:
202
226
  """Register built-in tools with the registry."""
203
227
  from henchman.tools.builtins import (
@@ -209,6 +233,7 @@ class Repl:
209
233
  ReadFileTool,
210
234
  ShellTool,
211
235
  WebFetchTool,
236
+ DuckDuckGoSearchTool,
212
237
  WriteFileTool,
213
238
  )
214
239
 
@@ -222,6 +247,7 @@ class Repl:
222
247
  GrepTool(),
223
248
  ShellTool(),
224
249
  WebFetchTool(),
250
+ DuckDuckGoSearchTool(),
225
251
  ]
226
252
  for tool in tools:
227
253
  self.tool_registry.register(tool)
@@ -322,6 +348,8 @@ class Repl:
322
348
  KeyboardInterrupt: If user presses Ctrl+C.
323
349
  EOFError: If user presses Ctrl+D.
324
350
  """
351
+ # Ensure a fresh line for the prompt
352
+ self.console.print()
325
353
  return await self.prompt_session.prompt_async(self.config.prompt)
326
354
 
327
355
  async def process_input(self, user_input: str) -> bool:
@@ -417,13 +445,16 @@ class Repl:
417
445
  self.current_monitor = monitor
418
446
  monitor_task = asyncio.create_task(monitor.monitor())
419
447
 
420
- # Run the agent stream processing as a separate task so we can cancel it
421
- agent_task = asyncio.create_task(
422
- self._process_agent_stream(
423
- self.agent.run(user_input),
424
- assistant_content
448
+ from rich.status import Status
449
+ with self.console.status(self._get_rich_status_message(), spinner="dots") as status_obj:
450
+ # Run the agent stream processing as a separate task so we can cancel it
451
+ agent_task = asyncio.create_task(
452
+ self._process_agent_stream(
453
+ self.agent.run(user_input),
454
+ assistant_content,
455
+ status_obj
456
+ )
425
457
  )
426
- )
427
458
 
428
459
  try:
429
460
  while not agent_task.done():
@@ -437,7 +468,7 @@ class Repl:
437
468
  agent_task.cancel()
438
469
  break
439
470
  # Small sleep to keep the loop responsive
440
- await asyncio.sleep(0.05)
471
+ await asyncio.sleep(0)
441
472
 
442
473
  if not agent_task.done():
443
474
  try:
@@ -458,7 +489,8 @@ class Repl:
458
489
  async def _process_agent_stream(
459
490
  self,
460
491
  event_stream: AsyncIterator[AgentEvent],
461
- content_collector: list[str] | None = None
492
+ content_collector: list[str] | None = None,
493
+ status_obj: Status | None = None, # New parameter
462
494
  ) -> None:
463
495
  """Process an agent event stream, handling tool calls properly.
464
496
 
@@ -499,6 +531,10 @@ class Repl:
499
531
  accumulated_content: list[str] = []
500
532
 
501
533
  async for event in event_stream:
534
+ # Update status continuously
535
+ if status_obj:
536
+ status_obj.update(self._get_rich_status_message())
537
+
502
538
  if event.type == EventType.CONTENT:
503
539
  # Stream content to console
504
540
  self.console.print(event.data, end="")
henchman/core/session.py CHANGED
@@ -353,18 +353,32 @@ class SessionManager:
353
353
  """Load a session from disk.
354
354
 
355
355
  Args:
356
- session_id: ID of session to load.
356
+ session_id: ID or ID prefix of session to load.
357
357
 
358
358
  Returns:
359
359
  Loaded Session instance.
360
360
 
361
361
  Raises:
362
- FileNotFoundError: If session doesn't exist.
362
+ FileNotFoundError: If session doesn't exist or is ambiguous.
363
363
  """
364
364
  path = self._get_session_path(session_id)
365
- if not path.exists():
365
+ if path.exists():
366
+ return Session.from_json(path.read_text())
367
+
368
+ # If not found by exact ID, try as prefix
369
+ if not self.data_dir.exists():
370
+ raise FileNotFoundError(f"Session not found: {session_id}")
371
+
372
+ matches = list(self.data_dir.glob(f"{session_id}*.json"))
373
+ if not matches:
366
374
  raise FileNotFoundError(f"Session not found: {session_id}")
367
- return Session.from_json(path.read_text())
375
+
376
+ if len(matches) > 1:
377
+ # Prefer exact match if somehow multiple match prefix but one is exact
378
+ # (though glob already failed exact match if we are here)
379
+ raise ValueError(f"Ambiguous session ID prefix: {session_id}")
380
+
381
+ return Session.from_json(matches[0].read_text())
368
382
 
369
383
  def load_by_tag(
370
384
  self,
@@ -10,6 +10,7 @@ from henchman.tools.builtins.ls import LsTool
10
10
  from henchman.tools.builtins.rag_search import RagSearchTool
11
11
  from henchman.tools.builtins.shell import ShellTool
12
12
  from henchman.tools.builtins.web_fetch import WebFetchTool
13
+ from henchman.tools.builtins.web_search import DuckDuckGoSearchTool
13
14
 
14
15
  __all__ = [
15
16
  "AskUserTool",
@@ -21,5 +22,6 @@ __all__ = [
21
22
  "ReadFileTool",
22
23
  "ShellTool",
23
24
  "WebFetchTool",
25
+ "DuckDuckGoSearchTool",
24
26
  "WriteFileTool",
25
27
  ]
@@ -101,6 +101,10 @@ class ShellTool(Tool):
101
101
  success=False,
102
102
  error=f"Timeout after {timeout} seconds",
103
103
  )
104
+ except asyncio.CancelledError:
105
+ process.kill()
106
+ await process.wait()
107
+ raise
104
108
 
105
109
  # Decode output
106
110
  stdout_text = stdout.decode("utf-8", errors="replace")
@@ -0,0 +1,129 @@
1
+ """Web search tool implementation using DuckDuckGo."""
2
+
3
+ import re
4
+ from typing import Any
5
+
6
+ from henchman.tools.base import Tool, ToolKind, ToolResult
7
+
8
+
9
+ class DuckDuckGoSearchTool(Tool):
10
+ """Search the web using DuckDuckGo.
11
+
12
+ This tool searches DuckDuckGo and returns a summary of the results.
13
+ """
14
+
15
+ @property
16
+ def name(self) -> str:
17
+ """Tool name."""
18
+ return "web_search"
19
+
20
+ @property
21
+ def description(self) -> str:
22
+ """Tool description."""
23
+ return "Search the web for information."
24
+
25
+ @property
26
+ def parameters(self) -> dict[str, object]:
27
+ """JSON Schema for parameters."""
28
+ return {
29
+ "type": "object",
30
+ "properties": {
31
+ "query": {
32
+ "type": "string",
33
+ "description": "The search query",
34
+ },
35
+ "max_results": {
36
+ "type": "integer",
37
+ "description": "Maximum number of results to return",
38
+ "default": 5,
39
+ },
40
+ },
41
+ "required": ["query"],
42
+ }
43
+
44
+ @property
45
+ def kind(self) -> ToolKind:
46
+ """Tool kind - NETWORK requires confirmation."""
47
+ return ToolKind.NETWORK
48
+
49
+ async def execute( # type: ignore[override]
50
+
51
+ self,
52
+ query: str = "",
53
+ max_results: int = 5,
54
+ **kwargs: object, # noqa: ARG002
55
+ ) -> ToolResult:
56
+ """Execute web search.
57
+
58
+ Args:
59
+ query: Search query.
60
+ max_results: Maximum results to return.
61
+ **kwargs: Additional arguments (ignored).
62
+
63
+ Returns:
64
+ ToolResult with search results.
65
+ """
66
+ try:
67
+ import aiohttp
68
+ from bs4 import BeautifulSoup
69
+ except ImportError:
70
+ return ToolResult(
71
+ content="Error: aiohttp and beautifulsoup4 are required for web_search",
72
+ success=False,
73
+ error="Dependencies not installed. Please run: pip install aiohttp beautifulsoup4",
74
+ )
75
+
76
+ try:
77
+ # DuckDuckGo HTML search URL
78
+ url = f"https://html.duckduckgo.com/html/?q={query}"
79
+ headers = {
80
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
81
+ }
82
+
83
+ async with (
84
+ aiohttp.ClientSession(headers=headers) as session,
85
+ session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as response,
86
+ ):
87
+ if response.status >= 400:
88
+ return ToolResult(
89
+ content=f"Search failed with HTTP {response.status}",
90
+ success=False,
91
+ error=f"HTTP {response.status}",
92
+ )
93
+
94
+ html = await response.text()
95
+ soup = BeautifulSoup(html, "html.parser")
96
+
97
+ results = []
98
+ # DuckDuckGo HTML results are usually in 'result' class divs
99
+ for i, result in enumerate(soup.find_all("div", class_="result")):
100
+ if i >= max_results:
101
+ break
102
+
103
+ title_elem = result.find("a", class_="result__a")
104
+ snippet_elem = result.find("a", class_="result__snippet")
105
+
106
+ if title_elem:
107
+ title = title_elem.get_text(strip=True)
108
+ link = title_elem.get("href", "")
109
+ snippet = snippet_elem.get_text(strip=True) if snippet_elem else ""
110
+
111
+ results.append(f"### {title}\nURL: {link}\n{snippet}\n")
112
+
113
+ if not results:
114
+ return ToolResult(
115
+ content="No results found.",
116
+ success=True,
117
+ )
118
+
119
+ return ToolResult(
120
+ content="\n".join(results),
121
+ success=True,
122
+ )
123
+
124
+ except Exception as e:
125
+ return ToolResult(
126
+ content=f"Error performing search: {e}",
127
+ success=False,
128
+ error=str(e),
129
+ )
@@ -0,0 +1,317 @@
1
+ Metadata-Version: 2.4
2
+ Name: henchman-ai
3
+ Version: 0.1.14
4
+ Summary: A model-agnostic AI agent CLI - your AI henchman for the terminal
5
+ Project-URL: Homepage, https://github.com/MGPowerlytics/henchman-ai
6
+ Project-URL: Repository, https://github.com/MGPowerlytics/henchman-ai
7
+ Project-URL: Documentation, https://github.com/MGPowerlytics/henchman-ai/tree/main/docs
8
+ Project-URL: Changelog, https://github.com/MGPowerlytics/henchman-ai/blob/main/CHANGELOG.md
9
+ Project-URL: Bug Tracker, https://github.com/MGPowerlytics/henchman-ai/issues
10
+ Project-URL: Discussions, https://github.com/MGPowerlytics/henchman-ai/discussions
11
+ Author-email: Matthew <matthew@example.com>
12
+ License-Expression: MIT
13
+ License-File: LICENSE
14
+ Keywords: agent,ai,anthropic,assistant,cli,deepseek,henchman,llm,openai,terminal
15
+ Classifier: Development Status :: 4 - Beta
16
+ Classifier: Environment :: Console
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: Intended Audience :: Science/Research
19
+ Classifier: License :: OSI Approved :: MIT License
20
+ Classifier: Operating System :: OS Independent
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
27
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
28
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
29
+ Classifier: Topic :: Text Processing :: Linguistic
30
+ Classifier: Topic :: Utilities
31
+ Classifier: Typing :: Typed
32
+ Requires-Python: >=3.10
33
+ Requires-Dist: aiohttp>=3.9
34
+ Requires-Dist: anthropic>=0.40
35
+ Requires-Dist: anyio>=4.0
36
+ Requires-Dist: beautifulsoup4>=4.12
37
+ Requires-Dist: chromadb>=0.4
38
+ Requires-Dist: click>=8.0
39
+ Requires-Dist: fastembed>=0.3
40
+ Requires-Dist: httpx>=0.27
41
+ Requires-Dist: mcp>=1.0
42
+ Requires-Dist: openai>=1.40
43
+ Requires-Dist: prompt-toolkit>=3.0
44
+ Requires-Dist: pydantic>=2.0
45
+ Requires-Dist: pyyaml>=6.0
46
+ Requires-Dist: rich>=13.0
47
+ Requires-Dist: tiktoken>=0.5
48
+ Provides-Extra: dev
49
+ Requires-Dist: mypy>=1.10; extra == 'dev'
50
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
51
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
52
+ Requires-Dist: pytest-mock>=3.0; extra == 'dev'
53
+ Requires-Dist: pytest>=8.0; extra == 'dev'
54
+ Requires-Dist: ruff>=0.4; extra == 'dev'
55
+ Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
56
+ Description-Content-Type: text/markdown
57
+
58
+ # Henchman-AI
59
+
60
+ > Your AI Henchman for the Terminal - A Model-Agnostic AI Agent CLI
61
+
62
+ [![PyPI version](https://img.shields.io/pypi/v/henchman-ai.svg)](https://pypi.org/project/henchman-ai/)
63
+ [![Python versions](https://img.shields.io/pypi/pyversions/henchman-ai.svg)](https://pypi.org/project/henchman-ai/)
64
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
65
+
66
+ Henchman-AI is a powerful, terminal-based AI agent that supports multiple LLM providers (DeepSeek, OpenAI, Anthropic, Ollama, and more) through a unified interface. Inspired by gemini-cli, built for extensibility and production use.
67
+
68
+ ## ✨ Features
69
+
70
+ - 🔄 **Model-Agnostic**: Support any LLM provider through a unified abstraction layer
71
+ - 🐍 **Pythonic**: Leverages Python's async ecosystem and rich libraries for optimal performance
72
+ - 🔌 **Extensible**: Plugin system for tools, providers, and custom commands
73
+ - 🚀 **Production-Ready**: Proper error handling, comprehensive testing, and semantic versioning
74
+ - 🛠️ **Tool Integration**: Built-in support for file operations, web search, code execution, and more
75
+ - ⚡ **Fast & Efficient**: Async-first design with intelligent caching and rate limiting
76
+ - 🔒 **Secure**: Environment-based configuration and safe execution sandboxing
77
+
78
+ ## 📦 Installation
79
+
80
+ ### From PyPI (Recommended)
81
+ ```bash
82
+ pip install henchman-ai
83
+ ```
84
+
85
+ ### From Source
86
+ ```bash
87
+ git clone https://github.com/MGPowerlytics/henchman-ai.git
88
+ cd henchman-ai
89
+ pip install -e ".[dev]"
90
+ ```
91
+
92
+ ### With uv (Fastest)
93
+ ```bash
94
+ uv pip install henchman-ai
95
+ ```
96
+
97
+ ## 🚀 Quick Start
98
+
99
+ 1. **Set your API key** (choose your preferred provider):
100
+ ```bash
101
+ export DEEPSEEK_API_KEY="your-api-key-here"
102
+ # or
103
+ export OPENAI_API_KEY="your-api-key-here"
104
+ # or
105
+ export ANTHROPIC_API_KEY="your-api-key-here"
106
+ ```
107
+
108
+ 2. **Start the CLI**:
109
+ ```bash
110
+ henchman
111
+ ```
112
+
113
+ 3. **Or run with a prompt directly**:
114
+ ```bash
115
+ henchman --prompt "Explain this Python code" < example.py
116
+ ```
117
+
118
+ ## 📖 Usage Examples
119
+
120
+ ### Basic Commands
121
+ ```bash
122
+ # Show version
123
+ henchman --version
124
+
125
+ # Show help
126
+ henchman --help
127
+
128
+ # Interactive mode (default)
129
+ henchman
130
+
131
+ # Headless mode with prompt
132
+ henchman -p "Summarize the key points from README.md"
133
+
134
+ # Specify a provider
135
+ henchman --provider openai -p "Write a Python function to calculate fibonacci"
136
+
137
+ # Use a specific model
138
+ henchman --model gpt-4-turbo -p "Analyze this code for security issues"
139
+ ```
140
+
141
+ ### File Operations
142
+ ```bash
143
+ # Read and analyze a file
144
+ henchman -p "Review this code for bugs" < script.py
145
+
146
+ # Process multiple files
147
+ cat *.py | henchman -p "Find common patterns in these files"
148
+
149
+ # Generate documentation
150
+ henchman -p "Create API documentation for this module" < module.py > docs.md
151
+ ```
152
+
153
+ ## ⚙️ Configuration
154
+
155
+ Henchman-AI uses hierarchical configuration (later settings override earlier ones):
156
+
157
+ 1. **Default settings** (built-in sensible defaults)
158
+ 2. **User settings**: `~/.henchman/settings.yaml`
159
+ 3. **Workspace settings**: `.henchman/settings.yaml` (project-specific)
160
+ 4. **Environment variables** (highest priority)
161
+
162
+ ### Example `settings.yaml`
163
+ ```yaml
164
+ # Provider configuration
165
+ providers:
166
+ default: deepseek # or openai, anthropic, ollama
167
+ deepseek:
168
+ model: deepseek-chat
169
+ base_url: "https://api.deepseek.com"
170
+ temperature: 0.7
171
+ openai:
172
+ model: gpt-4-turbo-preview
173
+ organization: "org-xxx"
174
+
175
+ # Tool settings
176
+ tools:
177
+ auto_accept_read: true
178
+ shell_timeout: 60
179
+ web_search_max_results: 5
180
+
181
+ # UI settings
182
+ ui:
183
+ theme: "monokai"
184
+ show_tokens: true
185
+ streaming: true
186
+
187
+ # System settings
188
+ system:
189
+ cache_enabled: true
190
+ cache_ttl: 3600
191
+ max_tokens: 4096
192
+ ```
193
+
194
+ ### Environment Variables
195
+ ```bash
196
+ # Provider API keys
197
+ export DEEPSEEK_API_KEY="sk-xxx"
198
+ export OPENAI_API_KEY="sk-xxx"
199
+ export ANTHROPIC_API_KEY="sk-xxx"
200
+
201
+ # Configuration overrides
202
+ export HENCHMAN_DEFAULT_PROVIDER="openai"
203
+ export HENCHMAN_DEFAULT_MODEL="gpt-4"
204
+ export HENCHMAN_TEMPERATURE="0.5"
205
+ ```
206
+
207
+ ## 🔌 Supported Providers
208
+
209
+ | Provider | Models | Features |
210
+ |----------|--------|----------|
211
+ | **DeepSeek** | deepseek-chat, deepseek-coder | Free tier, Code completion |
212
+ | **OpenAI** | gpt-4, gpt-3.5-turbo, etc. | Function calling, JSON mode |
213
+ | **Anthropic** | claude-3-opus, claude-3-sonnet | Long context, Constitutional AI |
214
+ | **Ollama** | llama2, mistral, codellama | Local models, Custom models |
215
+ | **Custom** | Any OpenAI-compatible API | Self-hosted, Local inference |
216
+
217
+ ## 🛠️ Development
218
+
219
+ ### Setup Development Environment
220
+ ```bash
221
+ # Clone and install
222
+ git clone https://github.com/MGPowerlytics/henchman-ai.git
223
+ cd henchman-ai
224
+ python -m venv venv
225
+ source venv/bin/activate # On Windows: venv\Scripts\activate
226
+ pip install -e ".[dev]"
227
+ ```
228
+
229
+ ### Running Tests
230
+ ```bash
231
+ # Run all tests
232
+ pytest
233
+
234
+ # Run with coverage
235
+ pytest --cov=henchman --cov-report=html
236
+
237
+ # Run specific test categories
238
+ pytest tests/unit/ -v
239
+ pytest tests/integration/ -v
240
+ ```
241
+
242
+ ### Code Quality
243
+ ```bash
244
+ # Linting
245
+ ruff check src/ tests/
246
+ ruff format src/ tests/
247
+
248
+ # Type checking
249
+ mypy src/
250
+
251
+ # Security scanning
252
+ bandit -r src/
253
+ ```
254
+
255
+ ### Building and Publishing
256
+ ```bash
257
+ # Build package
258
+ hatch build
259
+
260
+ # Test build
261
+ hatch run test
262
+
263
+ # Publish to PyPI (requires credentials)
264
+ hatch publish
265
+ ```
266
+
267
+ ## 📚 Documentation
268
+
269
+ ### Online Documentation
270
+ For detailed documentation, see the [docs directory](docs/) in this repository:
271
+
272
+ - [Getting Started](docs/getting-started.md)
273
+ - [Configuration Guide](docs/configuration.md)
274
+ - [API Reference](docs/api.md)
275
+ - [Tool Development](docs/tools.md)
276
+ - [Provider Integration](docs/providers.md)
277
+ - [MCP Integration](docs/mcp.md)
278
+ - [Extensions](docs/extensions.md)
279
+
280
+ ### Building Documentation Locally
281
+ You can build and view the documentation locally:
282
+
283
+ ```bash
284
+ # Install documentation dependencies
285
+ pip install mkdocs mkdocs-material mkdocstrings[python]
286
+
287
+ # Build static HTML documentation
288
+ python scripts/build_docs.py
289
+
290
+ # Or serve documentation locally (live preview)
291
+ mkdocs serve
292
+ ```
293
+
294
+ The documentation will be available at `http://localhost:8000` when served locally.
295
+
296
+ ## 🤝 Contributing
297
+
298
+ We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
299
+
300
+ ## 🐛 Reporting Issues
301
+
302
+ Found a bug or have a feature request? Please [open an issue](https://github.com/MGPowerlytics/henchman-ai/issues) on GitHub.
303
+
304
+ ## 📄 License
305
+
306
+ Henchman-AI is released under the MIT License. See the [LICENSE](LICENSE) file for details.
307
+
308
+ ## 🙏 Acknowledgments
309
+
310
+ - Inspired by [gemini-cli](https://github.com/google/gemini-cli)
311
+ - Built with [Rich](https://github.com/Textualize/rich) for beautiful terminal output
312
+ - Uses [Pydantic](https://docs.pydantic.dev/) for data validation
313
+ - Powered by the Python async ecosystem
314
+
315
+ ---
316
+
317
+ **Happy coding with your AI Henchman!** 🦸‍♂️🤖
@@ -4,10 +4,10 @@ henchman/version.py,sha256=g1Eesg-ffAqlRayLSNQqW5rBPWm91ti9pjNfGtTq5W4,161
4
4
  henchman/cli/__init__.py,sha256=Gv86a_heuBLqUd-y46JZUyzUaDl5H-9RtcWGr3rMwBw,673
5
5
  henchman/cli/app.py,sha256=ausKDDrRJ5KgiespK2P9vhX1yn-DxdJhYyBJ6tB5sb4,11507
6
6
  henchman/cli/console.py,sha256=S4Jvq0UTmu9KtOkLNsIsvG_8X9eg1Guc6NAh8T_JeNI,8017
7
- henchman/cli/input.py,sha256=oMKMF1CQCZrON5gqy8mtbYqIoGUvXcBEiDZeTxC9B6s,7129
7
+ henchman/cli/input.py,sha256=1-dz9mg3IvCtKMJbXzstxNbJYpwU1pGr-a0wJ0gqUik,7129
8
8
  henchman/cli/json_output.py,sha256=9kP9S5q0xBgP4HQGTT4P6DDT76F9VVTdEY_KiEpoZnI,2669
9
9
  henchman/cli/prompts.py,sha256=m3Velzi2tXBIHinN9jIpU9kDMYL80ngYQsv2EYo7IZU,6647
10
- henchman/cli/repl.py,sha256=fkeaMEGnFaFZ-HIjBLI1DUfos9ebGkHrPUUNMWS_LLU,26535
10
+ henchman/cli/repl.py,sha256=YLV1oVfGASYWLux4G5_I88RCaGk7y97um3B6uRxAacg,27844
11
11
  henchman/cli/repl.py.backup,sha256=3iagruUgsvtcfpDv1mTAYg4I14X4CaNSEeMQjj91src,15638
12
12
  henchman/cli/repl.py.backup2,sha256=-zgSUrnobd_sHq3jG-8NbwPTVlPc3FaqSkv32gAFdPo,11328
13
13
  henchman/cli/commands/__init__.py,sha256=8s6NBCPlc4jKTCdvnKJCmdLwRCQ4QLCARjQbr7ICipw,3828
@@ -27,7 +27,7 @@ henchman/core/__init__.py,sha256=BtOHfsJB6eW-QhtK4sbqOzzO6GLqWeVLkewOA-wuqL0,521
27
27
  henchman/core/agent.py,sha256=l9BJO8Zw4bMdUyTDjcZKG84WdZ1Kndm3Y09oUAZFYp0,13475
28
28
  henchman/core/agent.py.backup,sha256=Tq0IhWAPMRQTxjETeH7WTosEmzuUVz7um0YbCnuNbLQ,7417
29
29
  henchman/core/events.py,sha256=Uijv3NGNV8yJnQfY48u0pBBvEauAEczAbkGARJy8mfI,1423
30
- henchman/core/session.py,sha256=NkwEG2ZS2uh2ZeX8_LkSN7MwQlBxwhTXjx0BoNUZLDw,12475
30
+ henchman/core/session.py,sha256=1KNcH2xkDrDhbnzdRd_IZjeii5annY8Deqz-om7NJbY,13093
31
31
  henchman/core/turn.py,sha256=iRaTr_8hGSGQUt0vqmUE8w5D-drvkLeZlNEHmNUfX0M,8617
32
32
  henchman/extensions/__init__.py,sha256=C7LrK50uiHwmLlOGkQyngbFvuUYdCcZEb6ucOklY_ws,310
33
33
  henchman/extensions/base.py,sha256=cHUzWu4OGFju9Wr1xAiGHZOOW7eQbcC1-dqEd2Oe3QM,2290
@@ -61,7 +61,7 @@ henchman/skills/store.py,sha256=z_qHnVdyHAm-jtGNy5b33d9jQ-3pN-mkjrBAzc7CA5U,5331
61
61
  henchman/tools/__init__.py,sha256=NLQuYRtPECju8TZHg-ZgVUrtdlHdMTu7P8C3D4Nhdqg,440
62
62
  henchman/tools/base.py,sha256=PPeKS6jOCv3jFSgjd5j3kzgjTzw7VpBAY3ikmeDlIcI,4820
63
63
  henchman/tools/registry.py,sha256=YrDz29mwMGPGdJB5RD0QlakHiJ-24p3d_jnIaMY05hI,10228
64
- henchman/tools/builtins/__init__.py,sha256=VceFN4VY4DMEyXIzQ86yZ-_-m75wHCyKYQ89ex2VB4Q,797
64
+ henchman/tools/builtins/__init__.py,sha256=g6znk4vJ9rNAUzu9G4pf407sjs2lphL4fYyes7se-KI,893
65
65
  henchman/tools/builtins/ask_user.py,sha256=xPu74cB0rYahZHajVdjKgdmKU121SWyAgZSkU_wBYjQ,3007
66
66
  henchman/tools/builtins/file_edit.py,sha256=VjfpYVZulpIBufRSIsTx9eD5gYGnSybksyo5vGCL4wo,3709
67
67
  henchman/tools/builtins/file_read.py,sha256=RJCsK9Y-M2bd4IB8hnGaMjdzl62WSq7wOS9apcA3thA,4173
@@ -70,15 +70,16 @@ henchman/tools/builtins/glob_tool.py,sha256=7NAlan5A6v-RWAIUj8ID78aYRSvXe9Jtt2I6
70
70
  henchman/tools/builtins/grep.py,sha256=PV8X2ydnAutrWCS5VR9lABFpfSv0Olzsqa1Ktb5X4z0,5321
71
71
  henchman/tools/builtins/ls.py,sha256=5iSqHilrEiZ8ziOG4nKwC90fuLEx01V_0BzfS2PNAro,4167
72
72
  henchman/tools/builtins/rag_search.py,sha256=yk0z0mIVRH-yl47uteNXTy76aXP8PLxBq512juAfIuU,4568
73
- henchman/tools/builtins/shell.py,sha256=Gx8x1jBq1NvERFnc-kUNMovFoWg_i4IrV_askSECfEM,4134
73
+ henchman/tools/builtins/shell.py,sha256=MJEZzWcIm71ofq7739QBik6ihJBypHZ9TvWXfjFOwus,4267
74
74
  henchman/tools/builtins/web_fetch.py,sha256=uwgZm0ye3yDuS2U2DPV4D-8bjviYDTKN-cNi7mCMRpw,3370
75
+ henchman/tools/builtins/web_search.py,sha256=dapmhN5Yf_WYJT5bnwkkhyDe1n2aDmT-mU9ZY1BC6Sw,4265
75
76
  henchman/utils/__init__.py,sha256=ayu2XRNx3Fw0z8vbIne63A3gBjxu779QE8sUQsjNnm4,240
76
77
  henchman/utils/compaction.py,sha256=ARS0jUDI2adsoCTfJjygRom31N16QtWbRzNXDKzX6cA,22871
77
78
  henchman/utils/retry.py,sha256=sobZk9LLGxglSJw_jeNaBYCrvH14YNFrBVyp_OwLWcw,4993
78
79
  henchman/utils/tokens.py,sha256=D9H4ciFNH7l1b05IGbw0U0tmy2yF5aItFZyDufGF53k,5665
79
80
  henchman/utils/validation.py,sha256=moj4LQXVXt2J-3_pWVH_0-EabyRYApOU2Oh5JSTIua8,4146
80
- henchman_ai-0.1.13.dist-info/METADATA,sha256=Gf-ViR-NIQx7-rnxdm0Otk39hZmRrfoFlShbOFSalkQ,3552
81
- henchman_ai-0.1.13.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
82
- henchman_ai-0.1.13.dist-info/entry_points.txt,sha256=dtPyd6BzK3A8lmrj1KXTFlHBplIWcWMdryjtR0jw5iU,51
83
- henchman_ai-0.1.13.dist-info/licenses/LICENSE,sha256=TMoSCCG1I1vCMK-Bjtvxe80E8kIdSdrtuQXYHc_ahqg,1064
84
- henchman_ai-0.1.13.dist-info/RECORD,,
81
+ henchman_ai-0.1.14.dist-info/METADATA,sha256=B3V44ITO-lpcEVr-cJTdAunvIgMii_OTJZkkXLRzaxk,9186
82
+ henchman_ai-0.1.14.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
83
+ henchman_ai-0.1.14.dist-info/entry_points.txt,sha256=dtPyd6BzK3A8lmrj1KXTFlHBplIWcWMdryjtR0jw5iU,51
84
+ henchman_ai-0.1.14.dist-info/licenses/LICENSE,sha256=TMoSCCG1I1vCMK-Bjtvxe80E8kIdSdrtuQXYHc_ahqg,1064
85
+ henchman_ai-0.1.14.dist-info/RECORD,,
@@ -1,144 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: henchman-ai
3
- Version: 0.1.13
4
- Summary: A model-agnostic AI agent CLI - your AI henchman for the terminal
5
- Project-URL: Homepage, https://github.com/MGPowerlytics/henchman-ai
6
- Project-URL: Repository, https://github.com/MGPowerlytics/henchman-ai
7
- Project-URL: Documentation, https://github.com/MGPowerlytics/henchman-ai#readme
8
- Author-email: Matthew <matthew@example.com>
9
- License-Expression: MIT
10
- License-File: LICENSE
11
- Keywords: agent,ai,anthropic,assistant,cli,deepseek,henchman,llm,openai,terminal
12
- Classifier: Development Status :: 4 - Beta
13
- Classifier: Environment :: Console
14
- Classifier: Intended Audience :: Developers
15
- Classifier: License :: OSI Approved :: MIT License
16
- Classifier: Operating System :: OS Independent
17
- Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.10
19
- Classifier: Programming Language :: Python :: 3.11
20
- Classifier: Programming Language :: Python :: 3.12
21
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
- Classifier: Topic :: Utilities
23
- Classifier: Typing :: Typed
24
- Requires-Python: >=3.10
25
- Requires-Dist: aiohttp>=3.9
26
- Requires-Dist: anthropic>=0.40
27
- Requires-Dist: anyio>=4.0
28
- Requires-Dist: chromadb>=0.4
29
- Requires-Dist: click>=8.0
30
- Requires-Dist: fastembed>=0.3
31
- Requires-Dist: httpx>=0.27
32
- Requires-Dist: mcp>=1.0
33
- Requires-Dist: openai>=1.40
34
- Requires-Dist: prompt-toolkit>=3.0
35
- Requires-Dist: pydantic>=2.0
36
- Requires-Dist: pyyaml>=6.0
37
- Requires-Dist: rich>=13.0
38
- Requires-Dist: tiktoken>=0.5
39
- Provides-Extra: dev
40
- Requires-Dist: mypy>=1.10; extra == 'dev'
41
- Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
42
- Requires-Dist: pytest-cov>=4.0; extra == 'dev'
43
- Requires-Dist: pytest>=8.0; extra == 'dev'
44
- Requires-Dist: ruff>=0.4; extra == 'dev'
45
- Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
46
- Description-Content-Type: text/markdown
47
-
48
- # Henchman-AI
49
-
50
- > A Model-Agnostic AI Agent CLI in Python
51
-
52
- Henchman-AI is a terminal-based AI agent that supports multiple LLM providers (DeepSeek, OpenAI, Anthropic, Ollama) through a unified interface. Inspired by gemini-cli, built for extensibility.
53
-
54
- ## Features
55
-
56
- - 🔄 **Model-Agnostic**: Support any LLM provider through a unified abstraction
57
- - 🐍 **Pythonic**: Leverages Python's async ecosystem and rich libraries
58
- - 🔌 **Extensible**: Plugin system for tools, providers, and commands
59
- - 🚀 **Production-Ready**: Proper error handling, testing, and packaging
60
-
61
- ## Installation
62
-
63
- ```bash
64
- pip install henchman-ai
65
- ```
66
-
67
- Or install from source:
68
-
69
- ```bash
70
- git clone https://github.com/matthew/henchman-ai.git
71
- cd henchman-ai
72
- pip install -e ".[dev]"
73
- ```
74
-
75
- ## Quick Start
76
-
77
- ```bash
78
- # Set your API key
79
- export DEEPSEEK_API_KEY="your-api-key"
80
-
81
- # Start the CLI
82
- henchman
83
-
84
- # Or run with a prompt directly
85
- henchman --prompt "Explain this code" < file.py
86
- ```
87
-
88
- ## Usage
89
-
90
- ```bash
91
- # Show version
92
- henchman --version
93
-
94
- # Show help
95
- henchman --help
96
-
97
- # Interactive mode (default)
98
- henchman
99
-
100
- # Headless mode with prompt
101
- henchman -p "Summarize README.md"
102
- ```
103
-
104
- ## Configuration
105
-
106
- Henchman-AI uses hierarchical configuration:
107
-
108
- 1. Default settings
109
- 2. User settings: `~/.henchman/settings.yaml`
110
- 3. Workspace settings: `.henchman/settings.yaml`
111
- 4. Environment variables
112
-
113
- Example `settings.yaml`:
114
-
115
- ```yaml
116
- providers:
117
- default: deepseek
118
- deepseek:
119
- model: deepseek-chat
120
-
121
- tools:
122
- auto_accept_read: true
123
- shell_timeout: 60
124
- ```
125
-
126
- ## Development
127
-
128
- ```bash
129
- # Install dev dependencies
130
- pip install -e ".[dev]"
131
-
132
- # Run tests
133
- pytest
134
-
135
- # Linting
136
- ruff check src/ tests/
137
-
138
- # Type checking
139
- mypy src/
140
- ```
141
-
142
- ## License
143
-
144
- MIT License - see [LICENSE](LICENSE) for details.