hitmos 0.0.1__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.
- hitmos-0.0.1/.github/workflows/release.yml +63 -0
- hitmos-0.0.1/.gitignore +10 -0
- hitmos-0.0.1/.python-version +1 -0
- hitmos-0.0.1/CITATION.cff +25 -0
- hitmos-0.0.1/PKG-INFO +196 -0
- hitmos-0.0.1/README.md +163 -0
- hitmos-0.0.1/hitmos/__init__.py +1 -0
- hitmos-0.0.1/hitmos/app.py +163 -0
- hitmos-0.0.1/hitmos/chat.py +34 -0
- hitmos-0.0.1/hitmos/cli.py +46 -0
- hitmos-0.0.1/hitmos/client.py +131 -0
- hitmos-0.0.1/hitmos/commands.py +37 -0
- hitmos-0.0.1/hitmos/config.py +67 -0
- hitmos-0.0.1/hitmos/constants.py +41 -0
- hitmos-0.0.1/hitmos/context.py +119 -0
- hitmos-0.0.1/hitmos/exceptions.py +22 -0
- hitmos-0.0.1/hitmos/methods/__init__.py +45 -0
- hitmos-0.0.1/hitmos/methods/base.py +33 -0
- hitmos-0.0.1/hitmos/methods/edit_file.py +23 -0
- hitmos-0.0.1/hitmos/methods/list_directory.py +20 -0
- hitmos-0.0.1/hitmos/methods/read_file.py +17 -0
- hitmos-0.0.1/hitmos/methods/run_command.py +31 -0
- hitmos-0.0.1/hitmos/methods/write_file.py +18 -0
- hitmos-0.0.1/hitmos/ui.py +217 -0
- hitmos-0.0.1/hitmos/utils.py +10 -0
- hitmos-0.0.1/main.py +8 -0
- hitmos-0.0.1/pyproject.toml +60 -0
- hitmos-0.0.1/uv.lock +425 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
with:
|
|
17
|
+
fetch-depth: 0
|
|
18
|
+
|
|
19
|
+
- name: Read version
|
|
20
|
+
id: ver
|
|
21
|
+
run: |
|
|
22
|
+
VERSION=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
23
|
+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
24
|
+
|
|
25
|
+
- name: Check if tag already exists
|
|
26
|
+
id: tag
|
|
27
|
+
run: |
|
|
28
|
+
if git rev-parse "v${{ steps.ver.outputs.version }}" >/dev/null 2>&1; then
|
|
29
|
+
echo "exists=true" >> "$GITHUB_OUTPUT"
|
|
30
|
+
echo "Tag v${{ steps.ver.outputs.version }} already exists — skipping release."
|
|
31
|
+
else
|
|
32
|
+
echo "exists=false" >> "$GITHUB_OUTPUT"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
- uses: actions/setup-python@v5
|
|
36
|
+
if: steps.tag.outputs.exists == 'false'
|
|
37
|
+
with:
|
|
38
|
+
python-version: "3.13"
|
|
39
|
+
|
|
40
|
+
- name: Build
|
|
41
|
+
if: steps.tag.outputs.exists == 'false'
|
|
42
|
+
run: |
|
|
43
|
+
pip install build
|
|
44
|
+
python -m build
|
|
45
|
+
|
|
46
|
+
- name: Publish to PyPI
|
|
47
|
+
if: steps.tag.outputs.exists == 'false'
|
|
48
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
49
|
+
with:
|
|
50
|
+
password: ${{ secrets.PYPI_TOKEN }}
|
|
51
|
+
|
|
52
|
+
- name: Create tag and GitHub release
|
|
53
|
+
if: steps.tag.outputs.exists == 'false'
|
|
54
|
+
env:
|
|
55
|
+
GH_TOKEN: ${{ github.token }}
|
|
56
|
+
run: |
|
|
57
|
+
git config user.name "github-actions[bot]"
|
|
58
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
59
|
+
git tag -a "v${{ steps.ver.outputs.version }}" -m "Release v${{ steps.ver.outputs.version }}"
|
|
60
|
+
git push origin "v${{ steps.ver.outputs.version }}"
|
|
61
|
+
gh release create "v${{ steps.ver.outputs.version }}" dist/* \
|
|
62
|
+
--title "v${{ steps.ver.outputs.version }}" \
|
|
63
|
+
--generate-notes
|
hitmos-0.0.1/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use this software, please cite it as below."
|
|
3
|
+
type: software
|
|
4
|
+
title: "Hitmos"
|
|
5
|
+
abstract: "AI terminal assistant powered by OpenRouter — minimal, fast, developer UX."
|
|
6
|
+
authors:
|
|
7
|
+
- family-names: "NEFOR"
|
|
8
|
+
given-names: "White"
|
|
9
|
+
email: n7for8572@gmail.com
|
|
10
|
+
repository-code: "https://github.com/ndugram/hitmos"
|
|
11
|
+
url: "https://github.com/ndugram/hitmos"
|
|
12
|
+
license: MIT
|
|
13
|
+
version: "0.1.0"
|
|
14
|
+
date-released: "2026-06-06"
|
|
15
|
+
keywords:
|
|
16
|
+
- ai
|
|
17
|
+
- cli
|
|
18
|
+
- assistant
|
|
19
|
+
- openrouter
|
|
20
|
+
- llm
|
|
21
|
+
- terminal
|
|
22
|
+
- chat
|
|
23
|
+
- deepseek
|
|
24
|
+
- developer-tools
|
|
25
|
+
- python
|
hitmos-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hitmos
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: AI terminal assistant powered by OpenRouter — minimal, fast, developer UX
|
|
5
|
+
Project-URL: Homepage, https://github.com/ndugram/hitmos
|
|
6
|
+
Project-URL: Documentation, https://github.com/ndugram/hitmos
|
|
7
|
+
Project-URL: Repository, https://github.com/ndugram/hitmos
|
|
8
|
+
Project-URL: Issues, https://github.com/ndugram/hitmos/issues
|
|
9
|
+
Author-email: White NEFOR <n7for8572@gmail.com>
|
|
10
|
+
Maintainer-email: White NEFOR <n7for8572@gmail.com>
|
|
11
|
+
License: MIT
|
|
12
|
+
Keywords: ai,assistant,chat,cli,deepseek,developer-tools,llm,openrouter,python,terminal
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: Terminals
|
|
23
|
+
Classifier: Topic :: Utilities
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.13
|
|
26
|
+
Requires-Dist: fasthttp-client>=1.3.13
|
|
27
|
+
Requires-Dist: orjson>=3.9.0
|
|
28
|
+
Requires-Dist: pydantic-settings>=2.0.0
|
|
29
|
+
Requires-Dist: questionary>=2.1.1
|
|
30
|
+
Requires-Dist: rich>=15.0.0
|
|
31
|
+
Requires-Dist: typer>=0.26.7
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
<p align="center">
|
|
35
|
+
<img src="https://raw.githubusercontent.com/ndugram/hitmos/master/docs/logo.png" style="background:white; padding:12px; border-radius:10px; width:300">
|
|
36
|
+
</p>
|
|
37
|
+
<p align="center">
|
|
38
|
+
<em>AI terminal assistant powered by OpenRouter — minimal, fast, developer UX.</em>
|
|
39
|
+
</p>
|
|
40
|
+
<p align="center">
|
|
41
|
+
<a href="https://pypi.org/project/hitmos" target="_blank">
|
|
42
|
+
<img src="https://img.shields.io/pypi/v/hitmos?color=%2300d7af&label=pypi%20package" alt="Package version">
|
|
43
|
+
</a>
|
|
44
|
+
<a href="https://pypi.org/project/hitmos" target="_blank">
|
|
45
|
+
<img src="https://img.shields.io/pypi/pyversions/hitmos.svg?color=%2300d7af" alt="Supported Python versions">
|
|
46
|
+
</a>
|
|
47
|
+
<a href="https://pypi.org/project/hitmos" target="_blank">
|
|
48
|
+
<img src="https://img.shields.io/pypi/dm/hitmos?color=%2300d7af&label=downloads" alt="Monthly downloads">
|
|
49
|
+
</a>
|
|
50
|
+
<a href="https://pepy.tech/projects/hitmos" target="_blank">
|
|
51
|
+
<img src="https://img.shields.io/pepy/dt/hitmos?color=%2300d7af&label=total%20downloads" alt="Total downloads">
|
|
52
|
+
</a>
|
|
53
|
+
<a href="https://github.com/ndugram/hitmos" target="_blank">
|
|
54
|
+
<img src="https://img.shields.io/github/stars/ndugram/hitmos?style=social" alt="GitHub Stars">
|
|
55
|
+
</a>
|
|
56
|
+
</p>
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
**Source Code**: <a href="https://github.com/ndugram/hitmos" target="_blank">https://github.com/ndugram/hitmos</a>
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
Hitmos is a modern **AI terminal assistant** for developers, powered by <a href="https://openrouter.ai" target="_blank">OpenRouter</a>. It brings a Claude Code–styled UX to your terminal — live markdown streaming, interactive model switching, project context injection, and Tab completion, all with zero latency overhead.
|
|
65
|
+
|
|
66
|
+
Key features:
|
|
67
|
+
|
|
68
|
+
- **Streaming** — responses stream token-by-token with live Markdown rendering via <a href="https://github.com/Textualize/rich" target="_blank">rich</a>.
|
|
69
|
+
- **Multi-model** — switch between DeepSeek, Gemini, Claude, GPT-4o, Llama, and more with an interactive arrow-key picker.
|
|
70
|
+
- **Project context** — automatically injects your project files into the system prompt so the model can answer project-specific questions.
|
|
71
|
+
- **Tab completion** — `/model <Tab>` lists all available models; `/` completes all commands.
|
|
72
|
+
- **Minimal UX** — Claude Code–inspired welcome panel, `◆` status lines, clean `>` prompt.
|
|
73
|
+
- **Fast** — built on <a href="https://github.com/ndugram/fasthttp" target="_blank">fasthttp-client</a> with full async SSE streaming.
|
|
74
|
+
- **Configurable** — API key via env (`OPEN_TOKEN`, `OPENROUTER_API_KEY`) or `~/.hitmos/config.toml`.
|
|
75
|
+
|
|
76
|
+
## Requirements
|
|
77
|
+
|
|
78
|
+
Python 3.13+
|
|
79
|
+
|
|
80
|
+
Hitmos depends on:
|
|
81
|
+
|
|
82
|
+
- <a href="https://github.com/ndugram/fasthttp" target="_blank"><code>fasthttp-client</code></a> — async HTTP transport with SSE streaming.
|
|
83
|
+
- <a href="https://github.com/Textualize/rich" target="_blank"><code>rich</code></a> — live Markdown rendering and styled console output.
|
|
84
|
+
- <a href="https://typer.tiangolo.com/" target="_blank"><code>typer</code></a> — CLI interface.
|
|
85
|
+
- <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> — fast JSON parsing.
|
|
86
|
+
- <a href="https://github.com/prompt-toolkit/python-prompt-toolkit" target="_blank"><code>prompt-toolkit</code></a> — async input with Tab completion (bundled with questionary).
|
|
87
|
+
- <a href="https://github.com/tmbo/questionary" target="_blank"><code>questionary</code></a> — interactive model picker.
|
|
88
|
+
- <a href="https://docs.pydantic.dev/" target="_blank"><code>pydantic-settings</code></a> — config management.
|
|
89
|
+
|
|
90
|
+
## Installation
|
|
91
|
+
|
|
92
|
+
```console
|
|
93
|
+
$ pip install hitmos
|
|
94
|
+
|
|
95
|
+
---> 100%
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Quickstart
|
|
99
|
+
|
|
100
|
+
### Save your API key
|
|
101
|
+
|
|
102
|
+
```console
|
|
103
|
+
$ hitmos login
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Or set an environment variable:
|
|
107
|
+
|
|
108
|
+
```console
|
|
109
|
+
$ export OPEN_TOKEN=sk-or-...
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Start chatting
|
|
113
|
+
|
|
114
|
+
```console
|
|
115
|
+
$ hitmos
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
You will see:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
╭─────────────────────────────────────────────────────╮
|
|
122
|
+
│ │
|
|
123
|
+
│ ✻ Welcome to Hitmos │
|
|
124
|
+
│ │
|
|
125
|
+
│ /help for commands │
|
|
126
|
+
│ │
|
|
127
|
+
╰─────────────────────────────────────────────────────╯
|
|
128
|
+
|
|
129
|
+
◆ ~/my-project
|
|
130
|
+
◆ deepseek/deepseek-chat
|
|
131
|
+
◆ context 12 KB
|
|
132
|
+
|
|
133
|
+
>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Ask anything about your project:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
> what does this project do?
|
|
140
|
+
> add error handling to client.py
|
|
141
|
+
> explain the streaming logic
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Commands
|
|
145
|
+
|
|
146
|
+
| Command | Description |
|
|
147
|
+
|---|---|
|
|
148
|
+
| `/help` | Show available commands |
|
|
149
|
+
| `/model` | Interactive model picker (↑↓ + Enter) |
|
|
150
|
+
| `/model <name>` | Switch model directly |
|
|
151
|
+
| `/clear` | Clear conversation history |
|
|
152
|
+
| `/reset` | Reset context |
|
|
153
|
+
| `/exit` | Exit Hitmos |
|
|
154
|
+
|
|
155
|
+
### Switch model interactively
|
|
156
|
+
|
|
157
|
+
Type `/model` and press Enter to open the arrow-key picker:
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
? Select model (↑↓ navigate, Enter confirm, Ctrl+C cancel)
|
|
161
|
+
❯ deepseek/deepseek-chat DeepSeek Chat · fast, cheap, great for code
|
|
162
|
+
deepseek/deepseek-r1 DeepSeek R1 · reasoning model
|
|
163
|
+
google/gemini-2.5-flash Gemini 2.5 Flash · fast multimodal
|
|
164
|
+
google/gemini-2.5-pro Gemini 2.5 Pro · powerful multimodal
|
|
165
|
+
anthropic/claude-3.5-haiku Claude 3.5 Haiku · fast Anthropic
|
|
166
|
+
anthropic/claude-sonnet-4-5 Claude Sonnet 4.5 · balanced Anthropic
|
|
167
|
+
openai/gpt-4o-mini GPT-4o Mini · OpenAI fast
|
|
168
|
+
openai/o4-mini o4-mini · OpenAI reasoning
|
|
169
|
+
meta-llama/llama-3.3-70b-instruct Llama 3.3 70B · open source
|
|
170
|
+
mistralai/mistral-small-3.2-24b-instruct Mistral Small 3.2 · lightweight
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Or use Tab completion: type `/model deep` then press `Tab`.
|
|
174
|
+
|
|
175
|
+
## Configuration
|
|
176
|
+
|
|
177
|
+
Hitmos resolves the API key in this order:
|
|
178
|
+
|
|
179
|
+
1. `OPEN_TOKEN` environment variable
|
|
180
|
+
2. `OPENROUTER_API_KEY` environment variable
|
|
181
|
+
3. `~/.hitmos/config.toml`
|
|
182
|
+
|
|
183
|
+
Config file format:
|
|
184
|
+
|
|
185
|
+
```toml
|
|
186
|
+
token = "sk-or-..."
|
|
187
|
+
model = "deepseek/deepseek-chat"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Contributing
|
|
191
|
+
|
|
192
|
+
Contributions are welcome! Please open an issue or pull request on <a href="https://github.com/ndugram/hitmos" target="_blank">GitHub</a>.
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
This project is licensed under the terms of the <a href="https://github.com/ndugram/hitmos/blob/master/LICENSE" target="_blank">MIT license</a>.
|
hitmos-0.0.1/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/ndugram/hitmos/master/docs/logo.png" style="background:white; padding:12px; border-radius:10px; width:300">
|
|
3
|
+
</p>
|
|
4
|
+
<p align="center">
|
|
5
|
+
<em>AI terminal assistant powered by OpenRouter — minimal, fast, developer UX.</em>
|
|
6
|
+
</p>
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://pypi.org/project/hitmos" target="_blank">
|
|
9
|
+
<img src="https://img.shields.io/pypi/v/hitmos?color=%2300d7af&label=pypi%20package" alt="Package version">
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://pypi.org/project/hitmos" target="_blank">
|
|
12
|
+
<img src="https://img.shields.io/pypi/pyversions/hitmos.svg?color=%2300d7af" alt="Supported Python versions">
|
|
13
|
+
</a>
|
|
14
|
+
<a href="https://pypi.org/project/hitmos" target="_blank">
|
|
15
|
+
<img src="https://img.shields.io/pypi/dm/hitmos?color=%2300d7af&label=downloads" alt="Monthly downloads">
|
|
16
|
+
</a>
|
|
17
|
+
<a href="https://pepy.tech/projects/hitmos" target="_blank">
|
|
18
|
+
<img src="https://img.shields.io/pepy/dt/hitmos?color=%2300d7af&label=total%20downloads" alt="Total downloads">
|
|
19
|
+
</a>
|
|
20
|
+
<a href="https://github.com/ndugram/hitmos" target="_blank">
|
|
21
|
+
<img src="https://img.shields.io/github/stars/ndugram/hitmos?style=social" alt="GitHub Stars">
|
|
22
|
+
</a>
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
**Source Code**: <a href="https://github.com/ndugram/hitmos" target="_blank">https://github.com/ndugram/hitmos</a>
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
Hitmos is a modern **AI terminal assistant** for developers, powered by <a href="https://openrouter.ai" target="_blank">OpenRouter</a>. It brings a Claude Code–styled UX to your terminal — live markdown streaming, interactive model switching, project context injection, and Tab completion, all with zero latency overhead.
|
|
32
|
+
|
|
33
|
+
Key features:
|
|
34
|
+
|
|
35
|
+
- **Streaming** — responses stream token-by-token with live Markdown rendering via <a href="https://github.com/Textualize/rich" target="_blank">rich</a>.
|
|
36
|
+
- **Multi-model** — switch between DeepSeek, Gemini, Claude, GPT-4o, Llama, and more with an interactive arrow-key picker.
|
|
37
|
+
- **Project context** — automatically injects your project files into the system prompt so the model can answer project-specific questions.
|
|
38
|
+
- **Tab completion** — `/model <Tab>` lists all available models; `/` completes all commands.
|
|
39
|
+
- **Minimal UX** — Claude Code–inspired welcome panel, `◆` status lines, clean `>` prompt.
|
|
40
|
+
- **Fast** — built on <a href="https://github.com/ndugram/fasthttp" target="_blank">fasthttp-client</a> with full async SSE streaming.
|
|
41
|
+
- **Configurable** — API key via env (`OPEN_TOKEN`, `OPENROUTER_API_KEY`) or `~/.hitmos/config.toml`.
|
|
42
|
+
|
|
43
|
+
## Requirements
|
|
44
|
+
|
|
45
|
+
Python 3.13+
|
|
46
|
+
|
|
47
|
+
Hitmos depends on:
|
|
48
|
+
|
|
49
|
+
- <a href="https://github.com/ndugram/fasthttp" target="_blank"><code>fasthttp-client</code></a> — async HTTP transport with SSE streaming.
|
|
50
|
+
- <a href="https://github.com/Textualize/rich" target="_blank"><code>rich</code></a> — live Markdown rendering and styled console output.
|
|
51
|
+
- <a href="https://typer.tiangolo.com/" target="_blank"><code>typer</code></a> — CLI interface.
|
|
52
|
+
- <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> — fast JSON parsing.
|
|
53
|
+
- <a href="https://github.com/prompt-toolkit/python-prompt-toolkit" target="_blank"><code>prompt-toolkit</code></a> — async input with Tab completion (bundled with questionary).
|
|
54
|
+
- <a href="https://github.com/tmbo/questionary" target="_blank"><code>questionary</code></a> — interactive model picker.
|
|
55
|
+
- <a href="https://docs.pydantic.dev/" target="_blank"><code>pydantic-settings</code></a> — config management.
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```console
|
|
60
|
+
$ pip install hitmos
|
|
61
|
+
|
|
62
|
+
---> 100%
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Quickstart
|
|
66
|
+
|
|
67
|
+
### Save your API key
|
|
68
|
+
|
|
69
|
+
```console
|
|
70
|
+
$ hitmos login
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Or set an environment variable:
|
|
74
|
+
|
|
75
|
+
```console
|
|
76
|
+
$ export OPEN_TOKEN=sk-or-...
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Start chatting
|
|
80
|
+
|
|
81
|
+
```console
|
|
82
|
+
$ hitmos
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
You will see:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
╭─────────────────────────────────────────────────────╮
|
|
89
|
+
│ │
|
|
90
|
+
│ ✻ Welcome to Hitmos │
|
|
91
|
+
│ │
|
|
92
|
+
│ /help for commands │
|
|
93
|
+
│ │
|
|
94
|
+
╰─────────────────────────────────────────────────────╯
|
|
95
|
+
|
|
96
|
+
◆ ~/my-project
|
|
97
|
+
◆ deepseek/deepseek-chat
|
|
98
|
+
◆ context 12 KB
|
|
99
|
+
|
|
100
|
+
>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Ask anything about your project:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
> what does this project do?
|
|
107
|
+
> add error handling to client.py
|
|
108
|
+
> explain the streaming logic
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Commands
|
|
112
|
+
|
|
113
|
+
| Command | Description |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `/help` | Show available commands |
|
|
116
|
+
| `/model` | Interactive model picker (↑↓ + Enter) |
|
|
117
|
+
| `/model <name>` | Switch model directly |
|
|
118
|
+
| `/clear` | Clear conversation history |
|
|
119
|
+
| `/reset` | Reset context |
|
|
120
|
+
| `/exit` | Exit Hitmos |
|
|
121
|
+
|
|
122
|
+
### Switch model interactively
|
|
123
|
+
|
|
124
|
+
Type `/model` and press Enter to open the arrow-key picker:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
? Select model (↑↓ navigate, Enter confirm, Ctrl+C cancel)
|
|
128
|
+
❯ deepseek/deepseek-chat DeepSeek Chat · fast, cheap, great for code
|
|
129
|
+
deepseek/deepseek-r1 DeepSeek R1 · reasoning model
|
|
130
|
+
google/gemini-2.5-flash Gemini 2.5 Flash · fast multimodal
|
|
131
|
+
google/gemini-2.5-pro Gemini 2.5 Pro · powerful multimodal
|
|
132
|
+
anthropic/claude-3.5-haiku Claude 3.5 Haiku · fast Anthropic
|
|
133
|
+
anthropic/claude-sonnet-4-5 Claude Sonnet 4.5 · balanced Anthropic
|
|
134
|
+
openai/gpt-4o-mini GPT-4o Mini · OpenAI fast
|
|
135
|
+
openai/o4-mini o4-mini · OpenAI reasoning
|
|
136
|
+
meta-llama/llama-3.3-70b-instruct Llama 3.3 70B · open source
|
|
137
|
+
mistralai/mistral-small-3.2-24b-instruct Mistral Small 3.2 · lightweight
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Or use Tab completion: type `/model deep` then press `Tab`.
|
|
141
|
+
|
|
142
|
+
## Configuration
|
|
143
|
+
|
|
144
|
+
Hitmos resolves the API key in this order:
|
|
145
|
+
|
|
146
|
+
1. `OPEN_TOKEN` environment variable
|
|
147
|
+
2. `OPENROUTER_API_KEY` environment variable
|
|
148
|
+
3. `~/.hitmos/config.toml`
|
|
149
|
+
|
|
150
|
+
Config file format:
|
|
151
|
+
|
|
152
|
+
```toml
|
|
153
|
+
token = "sk-or-..."
|
|
154
|
+
model = "deepseek/deepseek-chat"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Contributing
|
|
158
|
+
|
|
159
|
+
Contributions are welcome! Please open an issue or pull request on <a href="https://github.com/ndugram/hitmos" target="_blank">GitHub</a>.
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
This project is licensed under the terms of the <a href="https://github.com/ndugram/hitmos/blob/master/LICENSE" target="_blank">MIT license</a>.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from collections.abc import AsyncGenerator
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from .chat import ChatSession
|
|
6
|
+
from .client import OpenRouterClient, ToolCallRequest
|
|
7
|
+
from .commands import CommandHandler, CommandResult, CommandType
|
|
8
|
+
from .config import ConfigManager
|
|
9
|
+
from .constants import SYSTEM_PROMPT
|
|
10
|
+
from .context import ProjectContext
|
|
11
|
+
from .exceptions import AuthError, HitmosError
|
|
12
|
+
from .methods import dispatch
|
|
13
|
+
from .ui import ConsoleUI
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HitmosApp:
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
self._config = ConfigManager()
|
|
19
|
+
self._ui = ConsoleUI()
|
|
20
|
+
self._commands = CommandHandler()
|
|
21
|
+
self._session: ChatSession | None = None
|
|
22
|
+
self._client: OpenRouterClient | None = None
|
|
23
|
+
|
|
24
|
+
def run(self) -> None:
|
|
25
|
+
try:
|
|
26
|
+
token = self._config.resolve_token()
|
|
27
|
+
except AuthError as e:
|
|
28
|
+
self._ui.show_error(str(e))
|
|
29
|
+
raise SystemExit(1)
|
|
30
|
+
|
|
31
|
+
model = self._config.get_model()
|
|
32
|
+
self._client = OpenRouterClient(token, model)
|
|
33
|
+
|
|
34
|
+
system_prompt, ctx_kb = self._build_system_prompt()
|
|
35
|
+
self._session = ChatSession(system_prompt=system_prompt)
|
|
36
|
+
|
|
37
|
+
self._ui.show_welcome(model, ctx_kb)
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
asyncio.run(self._loop())
|
|
41
|
+
except KeyboardInterrupt:
|
|
42
|
+
self._ui.show_exit()
|
|
43
|
+
|
|
44
|
+
async def _loop(self) -> None:
|
|
45
|
+
assert self._client is not None
|
|
46
|
+
assert self._session is not None
|
|
47
|
+
|
|
48
|
+
while True:
|
|
49
|
+
try:
|
|
50
|
+
text = await self._ui.get_input()
|
|
51
|
+
except (KeyboardInterrupt, EOFError):
|
|
52
|
+
self._ui.show_exit()
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
text = text.strip()
|
|
56
|
+
if not text:
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
result = self._commands.parse(text)
|
|
60
|
+
if result is not None:
|
|
61
|
+
should_exit = await self._handle_command(result)
|
|
62
|
+
if should_exit:
|
|
63
|
+
return
|
|
64
|
+
else:
|
|
65
|
+
await self._handle_message(text)
|
|
66
|
+
|
|
67
|
+
def _build_system_prompt(self) -> tuple[str, int]:
|
|
68
|
+
ctx, kb = ProjectContext(Path.cwd()).build()
|
|
69
|
+
if not ctx:
|
|
70
|
+
return SYSTEM_PROMPT, 0
|
|
71
|
+
return (
|
|
72
|
+
"You are Hitmos, a helpful AI coding assistant. "
|
|
73
|
+
"Be concise, accurate, and developer-focused.\n\n"
|
|
74
|
+
"You have access to the user's project files below. "
|
|
75
|
+
"Use this context to give accurate, project-specific answers.\n\n"
|
|
76
|
+
+ ctx,
|
|
77
|
+
kb,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
async def _handle_command(self, result: CommandResult) -> bool:
|
|
81
|
+
assert self._client is not None
|
|
82
|
+
assert self._session is not None
|
|
83
|
+
|
|
84
|
+
match result.type:
|
|
85
|
+
case CommandType.HELP:
|
|
86
|
+
self._ui.show_help()
|
|
87
|
+
case CommandType.CLEAR:
|
|
88
|
+
self._session.clear()
|
|
89
|
+
self._ui.show_info("History cleared.")
|
|
90
|
+
case CommandType.RESET:
|
|
91
|
+
self._session.reset()
|
|
92
|
+
self._ui.show_info("Context reset.")
|
|
93
|
+
case CommandType.MODEL:
|
|
94
|
+
if result.arg:
|
|
95
|
+
self._client.model = result.arg
|
|
96
|
+
self._config.save_model(result.arg)
|
|
97
|
+
self._ui.show_success(f"Model: {result.arg}")
|
|
98
|
+
else:
|
|
99
|
+
selected = await self._ui.show_model_picker(self._client.model)
|
|
100
|
+
if selected:
|
|
101
|
+
self._client.model = selected
|
|
102
|
+
self._config.save_model(selected)
|
|
103
|
+
self._ui.show_success(f"Model: {selected}")
|
|
104
|
+
case CommandType.EXIT:
|
|
105
|
+
self._ui.show_exit()
|
|
106
|
+
return True
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
async def _handle_message(self, text: str) -> None:
|
|
110
|
+
assert self._client is not None
|
|
111
|
+
assert self._session is not None
|
|
112
|
+
|
|
113
|
+
self._session.add_user(text)
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
while True:
|
|
117
|
+
tool_calls: list[ToolCallRequest] = []
|
|
118
|
+
|
|
119
|
+
async def _text_gen() -> AsyncGenerator[str, None]:
|
|
120
|
+
async for item in self._client.stream_chat( # type: ignore[union-attr]
|
|
121
|
+
self._session.messages # type: ignore[union-attr]
|
|
122
|
+
):
|
|
123
|
+
if isinstance(item, list):
|
|
124
|
+
tool_calls.extend(item)
|
|
125
|
+
else:
|
|
126
|
+
yield item
|
|
127
|
+
|
|
128
|
+
full_response = await self._ui.stream_response(_text_gen())
|
|
129
|
+
|
|
130
|
+
if not tool_calls:
|
|
131
|
+
if full_response:
|
|
132
|
+
self._session.add_assistant(full_response)
|
|
133
|
+
break
|
|
134
|
+
|
|
135
|
+
self._session.add_raw({
|
|
136
|
+
"role": "assistant",
|
|
137
|
+
"content": full_response or None,
|
|
138
|
+
"tool_calls": [
|
|
139
|
+
{
|
|
140
|
+
"id": tc.id,
|
|
141
|
+
"type": "function",
|
|
142
|
+
"function": {"name": tc.name, "arguments": tc.arguments},
|
|
143
|
+
}
|
|
144
|
+
for tc in tool_calls
|
|
145
|
+
],
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
for tc in tool_calls:
|
|
149
|
+
self._ui.show_tool_call(tc.name, tc.arguments)
|
|
150
|
+
result = dispatch(tc.name, tc.arguments)
|
|
151
|
+
self._ui.show_tool_result(result)
|
|
152
|
+
self._session.add_raw({
|
|
153
|
+
"role": "tool",
|
|
154
|
+
"tool_call_id": tc.id,
|
|
155
|
+
"content": result,
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
except HitmosError as e:
|
|
159
|
+
self._session.pop_last()
|
|
160
|
+
self._ui.show_error(str(e))
|
|
161
|
+
except Exception as e:
|
|
162
|
+
self._session.pop_last()
|
|
163
|
+
self._ui.show_error(f"Unexpected error: {e}")
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from .constants import SYSTEM_PROMPT
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ChatSession:
|
|
5
|
+
def __init__(self, system_prompt: str | None = None) -> None:
|
|
6
|
+
self._messages: list[dict] = []
|
|
7
|
+
self._system = system_prompt or SYSTEM_PROMPT
|
|
8
|
+
|
|
9
|
+
def add_user(self, content: str) -> None:
|
|
10
|
+
self._messages.append({"role": "user", "content": content})
|
|
11
|
+
|
|
12
|
+
def add_assistant(self, content: str) -> None:
|
|
13
|
+
self._messages.append({"role": "assistant", "content": content})
|
|
14
|
+
|
|
15
|
+
def add_raw(self, message: dict) -> None:
|
|
16
|
+
self._messages.append(message)
|
|
17
|
+
|
|
18
|
+
def clear(self) -> None:
|
|
19
|
+
self._messages.clear()
|
|
20
|
+
|
|
21
|
+
def reset(self) -> None:
|
|
22
|
+
self._messages.clear()
|
|
23
|
+
|
|
24
|
+
def pop_last(self) -> None:
|
|
25
|
+
if self._messages:
|
|
26
|
+
self._messages.pop()
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def messages(self) -> list[dict]:
|
|
30
|
+
return [{"role": "system", "content": self._system}] + self._messages
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def is_empty(self) -> bool:
|
|
34
|
+
return len(self._messages) == 0
|