Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env-example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
GEMINI_API_KEY="replace-this-with-your-api-key"
GEMINI_API_KEY=your-key-here


# NOTES:
# Please DO NOT add your secret here. This file is being tracked by git.
Expand Down
156 changes: 155 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,158 @@ wheels/

# Virtual environments
.venv
.ENV
.ENV
**/*.env
.env.*
!.env.example
temp.py

#macos file
**/*.DS_Store

#workspace
.vscode
temp.py

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
!*/frontend/.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

.idea/
recipe_client
example.db

# ignoring secrets file
.env

# cache
**/*.ruff_cache
**/engineering_notes.md
**/*.db
18 changes: 18 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.15.1
hooks:
# Run the linter.
- id: ruff-check
types_or: [ python, pyi ]
args: [ --fix]
# Run the formatter.
- id: ruff-format
types_or: [ python, pyi ]
31 changes: 31 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
SHELL ?= /bin/zsh
VENV ?= ".venv"
VENV_ACTIVATE ?= $(VENV)/bin/activate
PROMPT ?="Who is the current president of the United States of America?"

env:
@echo "To activate your env, run:"
@echo 'eval "$$(make -s activate)"'

activate:
@echo "source $(VENV_ACTIVATE)"

run:
uv run python main.py "$(PROMPT)"

run-with-prompt:
uv run python main.py "$(PROMPT)"

lint:
uv run pre-commit run ruff-check

fmt format:
uv run pre-commit run ruff-format

test-basic:
uv run test_get_files_info.py
make test:
uv run pytest -q

precommit: lint fmt test
@echo "✅ ready to commit!"
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,45 @@ A crude, local-first take on Claude Code, wired into Google’s Gemini API for c
## Getting started
1) Install Python 3.12+ and create a virtualenv.
2) Add your Gemini key to `.env`:
- `GOOGLE_API_KEY=your-key-here`
- `GEMINI_API_KEY=your-google-gemini-generated-api-key`

3) Install deps (uses `uv`; swap for pip if you prefer):
- `uv sync`
- `uv run python main.py`

## Makefile recipes

### `make env`
Prints the shell command to activate the virtual environment.

### `make activate`
Prints the `source` command for activating the virtual environment.

### `make run-with-prompt`
Runs `main.py` with the prompt defined by `PROMPT`.

### `make lint`
Runs the Ruff lint check via pre-commit.

### `make fmt` / `make format`
Formats code via Ruff format through pre-commit.

### `make precommit`
Runs `lint`, `fmt`, and `test` in sequence.

## Next steps
- Teach the agent to read/write files and summarize diffs.
- Add prompt presets for “explain”, “review”, “refactor”.
- Wire in tests around the Gemini client and prompt formatting.
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

## License
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

## Author
Created by [@iamserda](https://iamserda.github.io) - [GitHub](https://github.com/iamserda)

## Acknowledgments
- Powered by [Google Gemini API](https://ai.google.dev/) for code-aware responses
- Project scaffolding with [uv](https://docs.astral.sh/uv/) and [Ruff](https://docs.astral.sh/ruff/)
Empty file added calculator/__init__.py
Empty file.
1 change: 1 addition & 0 deletions calculator/lorem.txt

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions calculator/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# calculator/main.py

import sys
from pkg.calculator import Calculator
from pkg.render import format_json_output


def main():
calculator = Calculator()
if len(sys.argv) <= 1:
print("Calculator App")
print('Usage: python main.py "<expression>"')
print('Example: python main.py "3 + 5"')
return

expression = " ".join(sys.argv[1:])
try:
result = calculator.evaluate(expression)
if result is not None:
to_print = format_json_output(expression, result)
print(to_print)
else:
print("Error: Expression is empty or contains only whitespace.")
except Exception as e:
print(f"Error: {e}")


if __name__ == "__main__":
main()
62 changes: 62 additions & 0 deletions calculator/pkg/calculator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# calculator/pkg/calculator.py


class Calculator:
def __init__(self):
self.operators = {
"+": lambda a, b: a + b,
"-": lambda a, b: a - b,
"*": lambda a, b: a * b,
"/": lambda a, b: a / b,
}
self.precedence = {
"+": 1,
"-": 1,
"*": 2,
"/": 2,
}

def evaluate(self, expression):
if not expression or expression.isspace():
return None
tokens = expression.strip().split()
return self._evaluate_infix(tokens)

def _evaluate_infix(self, tokens):
values = []
operators = []

for token in tokens:
if token in self.operators:
while (
operators
and operators[-1] in self.operators
and self.precedence[operators[-1]] >= self.precedence[token]
):
self._apply_operator(operators, values)
operators.append(token)
else:
try:
values.append(float(token))
except ValueError:
raise ValueError(f"invalid token: {token}")

while operators:
self._apply_operator(operators, values)

if len(values) != 1:
raise ValueError("invalid expression")

return values[0]

def _apply_operator(self, operators, values):
if not operators:
return

operator = operators.pop()
if len(values) < 2:
raise ValueError(f"not enough operands for operator {operator}")

b = values.pop()
a = values.pop()
values.append(self.operators[operator](a, b))
16 changes: 16 additions & 0 deletions calculator/pkg/render.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# calculator/pkg/render.py

import json


def format_json_output(expression: str, result: float, indent: int = 2) -> str:
if isinstance(result, float) and result.is_integer():
result_to_dump = int(result)
else:
result_to_dump = result

output_data = {
"expression": expression,
"result": result_to_dump,
}
return json.dumps(output_data, indent=indent)
Loading
Loading