cli-dev-tip 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ # Claude Code
2
+ .claude
3
+
4
+ # VS Code
5
+ .vscode/
6
+ *.code-workspace
7
+
8
+ # ChatGPT / OpenAI
9
+ .chatgpt/
10
+ .openai/
11
+
12
+ # GitHub Copilot
13
+ .copilot/
14
+
15
+ # OS files
16
+ .DS_Store
17
+ Thumbs.db
18
+
19
+ # Environment & secrets
20
+ .env
21
+ .env.*
22
+ !.env.example
23
+
24
+ # Python
25
+ __pycache__/
26
+ *.py[cod]
27
+ *$py.class
28
+ *.egg-info/
29
+ dist/
30
+ build/
31
+ *.egg
32
+ .venv/
33
+ venv/
34
+ .python-version
35
+
36
+ # Node
37
+ node_modules/
38
+ npm-debug.log*
39
+ yarn-debug.log*
40
+ yarn-error.log*
41
+ .pnpm-debug.log*
42
+
43
+ # IDE / Editor
44
+ *.swp
45
+ *.swo
46
+ *~
47
+ .idea/
48
+ *.iml
49
+
50
+ # Logs
51
+ *.log
52
+
53
+ # Coverage & testing
54
+ .coverage
55
+ htmlcov/
56
+ .pytest_cache/
57
+ .mypy_cache/
58
+ .ruff_cache/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 My-CD-ROM
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,223 @@
1
+ Metadata-Version: 2.4
2
+ Name: cli-dev-tip
3
+ Version: 0.1.0
4
+ Summary: Bite-sized developer tips in your terminal
5
+ Project-URL: Homepage, https://github.com/My-CD-ROM/cli-dev-tip
6
+ Project-URL: Repository, https://github.com/My-CD-ROM/cli-dev-tip
7
+ Project-URL: Issues, https://github.com/My-CD-ROM/cli-dev-tip/issues
8
+ Author: My-CD-ROM
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: cli,developer,learning,terminal,tips
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development
22
+ Classifier: Topic :: Utilities
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: pyyaml
25
+ Requires-Dist: rich
26
+ Requires-Dist: typer
27
+ Description-Content-Type: text/markdown
28
+
29
+ # dev-tip
30
+
31
+ A terminal tool that helps developers learn continuously by showing bite-sized technical tips — right in the terminal, between commands.
32
+
33
+ ## What it does
34
+
35
+ - Shows tips periodically during your shell session (every N commands or M minutes)
36
+ - First tip appears immediately when you open a terminal
37
+ - Covers general IT topics by default — Python, Git, Docker, Linux, Kubernetes, and more
38
+ - Filters by topic or difficulty level
39
+ - Remembers what you've seen so you don't get repeats
40
+ - Optional AI-powered tip generation via Gemini or OpenRouter (free, no extra packages needed)
41
+ - Zero prompt latency — all periodic logic runs as pure shell code, Python only invoked when showing a tip
42
+ - Pause/resume tips without removing the hook
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ uv tool install cli-dev-tip
48
+ ```
49
+
50
+ This makes `dev-tip` available globally from any terminal. No extra packages needed.
51
+
52
+ ## Quick start
53
+
54
+ The recommended setup — AI-generated tips covering general IT topics, appearing every 15 commands or 30 minutes:
55
+
56
+ ```bash
57
+ dev-tip enable --provider gemini --key YOUR_GEMINI_API_KEY
58
+ ```
59
+
60
+ Get a free Gemini API key at https://aistudio.google.com or a free OpenRouter key at https://openrouter.ai/keys.
61
+
62
+ More examples:
63
+
64
+ ```bash
65
+ # Static tips only (125 built in, works offline)
66
+ dev-tip enable
67
+
68
+ # AI tips, focused on Python, advanced level
69
+ dev-tip enable --provider gemini --key YOUR_KEY --topic python --level advanced
70
+
71
+ # Show tips more frequently — every 5 commands or every 10 minutes
72
+ dev-tip enable --provider gemini --key YOUR_KEY --every-commands 5 --every-minutes 10
73
+
74
+ # Quiet mode — body only, no header/emoji
75
+ dev-tip enable --quiet
76
+
77
+ # Disable
78
+ dev-tip disable
79
+ ```
80
+
81
+ ### `enable` options
82
+
83
+ | Option | Short | Description | Default |
84
+ |---|---|---|---|
85
+ | `--provider` | `-p` | AI provider (gemini, openrouter) | None (static tips) |
86
+ | `--key` | `-k` | API key for the AI provider | Config or env var |
87
+ | `--topic` | `-t` | Filter tips by topic | General IT topics |
88
+ | `--level` | `-l` | Filter tips by difficulty | All levels |
89
+ | `--every-commands` | | Show a tip every N commands | 15 |
90
+ | `--every-minutes` | | Show a tip every N minutes | 30 |
91
+ | `--quiet` | `-q` | Show tip body only, no header | false |
92
+
93
+ Tips appear when either threshold is reached — whichever comes first. The first tip always shows immediately on shell startup.
94
+
95
+ ## Usage
96
+
97
+ One-off tips from the command line:
98
+
99
+ ```bash
100
+ # Show a random tip (general IT topics, all levels)
101
+ dev-tip
102
+
103
+ # Filter by topic
104
+ dev-tip --topic python
105
+ dev-tip -t git
106
+
107
+ # Filter by difficulty level
108
+ dev-tip --level beginner
109
+ dev-tip -l advanced
110
+
111
+ # Combine filters
112
+ dev-tip --topic docker --level intermediate
113
+
114
+ # Quiet mode — body only, no header
115
+ dev-tip --quiet
116
+
117
+ # Use AI provider for a one-off tip
118
+ dev-tip --provider gemini --key YOUR_KEY
119
+ ```
120
+
121
+ ### One-off options
122
+
123
+ | Option | Short | Description | Default |
124
+ |---|---|---|---|
125
+ | `--topic` | `-t` | Filter tips by topic | General IT topics |
126
+ | `--level` | `-l` | Filter tips by difficulty | All levels |
127
+ | `--provider` | `-p` | AI provider (gemini, openrouter) | None |
128
+ | `--key` | `-k` | API key for the AI provider | Config or env var |
129
+ | `--quiet` | `-q` | Show tip body only, no header | false |
130
+
131
+ Unknown topics produce a warning when using static tips (AI can handle any topic). Unknown levels always warn.
132
+
133
+ ### Available topics
134
+
135
+ | Topic | Count |
136
+ |---|---|
137
+ | `python` | 15 |
138
+ | `git` | 15 |
139
+ | `docker` | 15 |
140
+ | `sql` | 15 |
141
+ | `linux` | 15 |
142
+ | `kubernetes` | 10 |
143
+ | `vim` | 10 |
144
+ | `javascript` | 10 |
145
+ | `terraform` | 10 |
146
+ | `rust` | 10 |
147
+
148
+ 125 tips built in. With AI enabled, any topic works and tips are generated fresh.
149
+
150
+ ### Difficulty levels
151
+
152
+ | Level | Description |
153
+ |---|---|
154
+ | `beginner` | Fundamentals and common patterns |
155
+ | `intermediate` | Deeper concepts and useful tricks |
156
+ | `advanced` | Expert techniques and edge cases |
157
+
158
+ ## Commands
159
+
160
+ ### `dev-tip pause` / `dev-tip resume`
161
+
162
+ Temporarily stop tips without removing the hook:
163
+
164
+ ```bash
165
+ dev-tip pause # stops tips, hook stays installed
166
+ dev-tip resume # starts tips again
167
+ ```
168
+
169
+ This creates/removes a `.paused` file in `~/.dev-tip/`. The shell hook checks for it with a fast `[ -f ]` test — no Python invoked when paused.
170
+
171
+ ### `dev-tip status`
172
+
173
+ Show current configuration and diagnostics:
174
+
175
+ ```bash
176
+ dev-tip status
177
+ ```
178
+
179
+ Displays hook state, pause status, config values, AI provider info, cache stats, and tip history count.
180
+
181
+ ### `dev-tip clear-cache`
182
+
183
+ Clear cached AI tips to force fresh generation:
184
+
185
+ ```bash
186
+ dev-tip clear-cache
187
+ ```
188
+
189
+ ## AI-powered tips
190
+
191
+ Generate fresh tips dynamically instead of using the built-in collection. Both providers are free.
192
+
193
+ | Provider | Default model | Free key |
194
+ |---|---|---|
195
+ | `gemini` | `gemini-2.0-flash` | https://aistudio.google.com |
196
+ | `openrouter` | `google/gemini-2.0-flash-exp:free` | https://openrouter.ai/keys |
197
+
198
+ ### How it works
199
+
200
+ - Generates 10 tips per API call and caches them locally (`~/.dev-tip/ai_cache.json`)
201
+ - Cache never expires — use `dev-tip clear-cache` to force refresh
202
+ - Cache is keyed by topic+level combination
203
+ - Falls back to static tips silently on any error (bad key, network failure, rate limit)
204
+
205
+ ## Configuration
206
+
207
+ Settings are stored in `~/.dev-tip/config.toml`:
208
+
209
+ ```toml
210
+ # topic = "python"
211
+ # level = "beginner"
212
+ # ai_provider = "gemini"
213
+ # ai_model = "gemini-2.0-flash"
214
+ # every_commands = 15
215
+ # every_minutes = 30
216
+ # quiet = false
217
+ ```
218
+
219
+ Values passed via `dev-tip enable` flags are saved here automatically. Comments in the config file are preserved when values are updated.
220
+
221
+ ## Status
222
+
223
+ Early development — not yet published to PyPI.
@@ -0,0 +1,195 @@
1
+ # dev-tip
2
+
3
+ A terminal tool that helps developers learn continuously by showing bite-sized technical tips — right in the terminal, between commands.
4
+
5
+ ## What it does
6
+
7
+ - Shows tips periodically during your shell session (every N commands or M minutes)
8
+ - First tip appears immediately when you open a terminal
9
+ - Covers general IT topics by default — Python, Git, Docker, Linux, Kubernetes, and more
10
+ - Filters by topic or difficulty level
11
+ - Remembers what you've seen so you don't get repeats
12
+ - Optional AI-powered tip generation via Gemini or OpenRouter (free, no extra packages needed)
13
+ - Zero prompt latency — all periodic logic runs as pure shell code, Python only invoked when showing a tip
14
+ - Pause/resume tips without removing the hook
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ uv tool install cli-dev-tip
20
+ ```
21
+
22
+ This makes `dev-tip` available globally from any terminal. No extra packages needed.
23
+
24
+ ## Quick start
25
+
26
+ The recommended setup — AI-generated tips covering general IT topics, appearing every 15 commands or 30 minutes:
27
+
28
+ ```bash
29
+ dev-tip enable --provider gemini --key YOUR_GEMINI_API_KEY
30
+ ```
31
+
32
+ Get a free Gemini API key at https://aistudio.google.com or a free OpenRouter key at https://openrouter.ai/keys.
33
+
34
+ More examples:
35
+
36
+ ```bash
37
+ # Static tips only (125 built in, works offline)
38
+ dev-tip enable
39
+
40
+ # AI tips, focused on Python, advanced level
41
+ dev-tip enable --provider gemini --key YOUR_KEY --topic python --level advanced
42
+
43
+ # Show tips more frequently — every 5 commands or every 10 minutes
44
+ dev-tip enable --provider gemini --key YOUR_KEY --every-commands 5 --every-minutes 10
45
+
46
+ # Quiet mode — body only, no header/emoji
47
+ dev-tip enable --quiet
48
+
49
+ # Disable
50
+ dev-tip disable
51
+ ```
52
+
53
+ ### `enable` options
54
+
55
+ | Option | Short | Description | Default |
56
+ |---|---|---|---|
57
+ | `--provider` | `-p` | AI provider (gemini, openrouter) | None (static tips) |
58
+ | `--key` | `-k` | API key for the AI provider | Config or env var |
59
+ | `--topic` | `-t` | Filter tips by topic | General IT topics |
60
+ | `--level` | `-l` | Filter tips by difficulty | All levels |
61
+ | `--every-commands` | | Show a tip every N commands | 15 |
62
+ | `--every-minutes` | | Show a tip every N minutes | 30 |
63
+ | `--quiet` | `-q` | Show tip body only, no header | false |
64
+
65
+ Tips appear when either threshold is reached — whichever comes first. The first tip always shows immediately on shell startup.
66
+
67
+ ## Usage
68
+
69
+ One-off tips from the command line:
70
+
71
+ ```bash
72
+ # Show a random tip (general IT topics, all levels)
73
+ dev-tip
74
+
75
+ # Filter by topic
76
+ dev-tip --topic python
77
+ dev-tip -t git
78
+
79
+ # Filter by difficulty level
80
+ dev-tip --level beginner
81
+ dev-tip -l advanced
82
+
83
+ # Combine filters
84
+ dev-tip --topic docker --level intermediate
85
+
86
+ # Quiet mode — body only, no header
87
+ dev-tip --quiet
88
+
89
+ # Use AI provider for a one-off tip
90
+ dev-tip --provider gemini --key YOUR_KEY
91
+ ```
92
+
93
+ ### One-off options
94
+
95
+ | Option | Short | Description | Default |
96
+ |---|---|---|---|
97
+ | `--topic` | `-t` | Filter tips by topic | General IT topics |
98
+ | `--level` | `-l` | Filter tips by difficulty | All levels |
99
+ | `--provider` | `-p` | AI provider (gemini, openrouter) | None |
100
+ | `--key` | `-k` | API key for the AI provider | Config or env var |
101
+ | `--quiet` | `-q` | Show tip body only, no header | false |
102
+
103
+ Unknown topics produce a warning when using static tips (AI can handle any topic). Unknown levels always warn.
104
+
105
+ ### Available topics
106
+
107
+ | Topic | Count |
108
+ |---|---|
109
+ | `python` | 15 |
110
+ | `git` | 15 |
111
+ | `docker` | 15 |
112
+ | `sql` | 15 |
113
+ | `linux` | 15 |
114
+ | `kubernetes` | 10 |
115
+ | `vim` | 10 |
116
+ | `javascript` | 10 |
117
+ | `terraform` | 10 |
118
+ | `rust` | 10 |
119
+
120
+ 125 tips built in. With AI enabled, any topic works and tips are generated fresh.
121
+
122
+ ### Difficulty levels
123
+
124
+ | Level | Description |
125
+ |---|---|
126
+ | `beginner` | Fundamentals and common patterns |
127
+ | `intermediate` | Deeper concepts and useful tricks |
128
+ | `advanced` | Expert techniques and edge cases |
129
+
130
+ ## Commands
131
+
132
+ ### `dev-tip pause` / `dev-tip resume`
133
+
134
+ Temporarily stop tips without removing the hook:
135
+
136
+ ```bash
137
+ dev-tip pause # stops tips, hook stays installed
138
+ dev-tip resume # starts tips again
139
+ ```
140
+
141
+ This creates/removes a `.paused` file in `~/.dev-tip/`. The shell hook checks for it with a fast `[ -f ]` test — no Python invoked when paused.
142
+
143
+ ### `dev-tip status`
144
+
145
+ Show current configuration and diagnostics:
146
+
147
+ ```bash
148
+ dev-tip status
149
+ ```
150
+
151
+ Displays hook state, pause status, config values, AI provider info, cache stats, and tip history count.
152
+
153
+ ### `dev-tip clear-cache`
154
+
155
+ Clear cached AI tips to force fresh generation:
156
+
157
+ ```bash
158
+ dev-tip clear-cache
159
+ ```
160
+
161
+ ## AI-powered tips
162
+
163
+ Generate fresh tips dynamically instead of using the built-in collection. Both providers are free.
164
+
165
+ | Provider | Default model | Free key |
166
+ |---|---|---|
167
+ | `gemini` | `gemini-2.0-flash` | https://aistudio.google.com |
168
+ | `openrouter` | `google/gemini-2.0-flash-exp:free` | https://openrouter.ai/keys |
169
+
170
+ ### How it works
171
+
172
+ - Generates 10 tips per API call and caches them locally (`~/.dev-tip/ai_cache.json`)
173
+ - Cache never expires — use `dev-tip clear-cache` to force refresh
174
+ - Cache is keyed by topic+level combination
175
+ - Falls back to static tips silently on any error (bad key, network failure, rate limit)
176
+
177
+ ## Configuration
178
+
179
+ Settings are stored in `~/.dev-tip/config.toml`:
180
+
181
+ ```toml
182
+ # topic = "python"
183
+ # level = "beginner"
184
+ # ai_provider = "gemini"
185
+ # ai_model = "gemini-2.0-flash"
186
+ # every_commands = 15
187
+ # every_minutes = 30
188
+ # quiet = false
189
+ ```
190
+
191
+ Values passed via `dev-tip enable` flags are saved here automatically. Comments in the config file are preserved when values are updated.
192
+
193
+ ## Status
194
+
195
+ Early development — not yet published to PyPI.
File without changes
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import random
5
+
6
+ from dev_tip.ai.cache import is_on_cooldown, load_cache, mark_failure, save_cache
7
+ from dev_tip.ai.provider import create_provider
8
+ from dev_tip.history import get_unseen
9
+
10
+ _ENV_KEYS = {
11
+ "gemini": "GEMINI_API_KEY",
12
+ "openrouter": "OPENROUTER_API_KEY",
13
+ }
14
+
15
+ BATCH_SIZE = 10
16
+
17
+
18
+ def get_ai_tip(
19
+ topic: str | None, level: str | None, config: dict
20
+ ) -> tuple[dict | None, int]:
21
+ """Return (tip, unseen_count) or (None, 0) on any failure."""
22
+ try:
23
+ provider_name = config.get("ai_provider")
24
+ if not provider_name:
25
+ return None, 0
26
+
27
+ api_key = config.get("ai_key")
28
+ if not api_key:
29
+ env_var = _ENV_KEYS.get(provider_name)
30
+ if env_var:
31
+ api_key = os.environ.get(env_var)
32
+ if not api_key:
33
+ return None, 0
34
+
35
+ # Try cache first
36
+ tips = load_cache(topic, level)
37
+
38
+ if not tips:
39
+ if is_on_cooldown():
40
+ return None, 0
41
+ try:
42
+ provider = create_provider(
43
+ provider_name, api_key, model=config.get("ai_model")
44
+ )
45
+ tips = provider.generate_tips(topic, level, BATCH_SIZE)
46
+ save_cache(tips, topic, level)
47
+ except Exception:
48
+ mark_failure()
49
+ return None, 0
50
+
51
+ unseen = get_unseen(tips)
52
+ return random.choice(unseen), len(unseen)
53
+
54
+ except Exception:
55
+ return None, 0
@@ -0,0 +1,114 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import time
5
+ from pathlib import Path
6
+
7
+ CACHE_DIR = Path.home() / ".dev-tip"
8
+ CACHE_FILE = CACHE_DIR / "ai_cache.json"
9
+ COOLDOWN_SECONDS = 5 * 60 # 5 min backoff after API failure
10
+
11
+
12
+ def _cache_key(topic: str | None, level: str | None) -> str:
13
+ """Build a cache key like 'python:beginner' or 'None:None'."""
14
+ return f"{topic}:{level}"
15
+
16
+
17
+ def _load_all() -> dict:
18
+ """Load full cache, auto-migrating v1 single-key format to v2."""
19
+ if not CACHE_FILE.exists():
20
+ return {"version": 2, "keys": {}}
21
+
22
+ data = json.loads(CACHE_FILE.read_text())
23
+
24
+ # v1 migration: old format had top-level topic/level/tips/generated_at
25
+ if "version" not in data and "tips" in data:
26
+ key = _cache_key(data.get("topic"), data.get("level"))
27
+ migrated = {
28
+ "version": 2,
29
+ "keys": {
30
+ key: {
31
+ "generated_at": data.get("generated_at", 0.0),
32
+ "tips": data.get("tips", []),
33
+ }
34
+ },
35
+ }
36
+ _save_all(migrated)
37
+ return migrated
38
+
39
+ return data
40
+
41
+
42
+ def _save_all(data: dict) -> None:
43
+ """Write full cache to disk."""
44
+ CACHE_DIR.mkdir(parents=True, exist_ok=True)
45
+ CACHE_FILE.write_text(json.dumps(data, indent=2))
46
+
47
+
48
+ def load_cache(topic: str | None, level: str | None) -> list[dict]:
49
+ """Return all cached tips for a topic+level combo (never expires)."""
50
+ data = _load_all()
51
+ key = _cache_key(topic, level)
52
+ entry = data.get("keys", {}).get(key)
53
+ if not entry:
54
+ return []
55
+ return entry.get("tips", [])
56
+
57
+
58
+ def save_cache(tips: list[dict], topic: str | None, level: str | None) -> None:
59
+ """Merge tips into the multi-key cache structure."""
60
+ data = _load_all()
61
+ key = _cache_key(topic, level)
62
+ existing = data.get("keys", {}).get(key, {}).get("tips", [])
63
+
64
+ # Deduplicate by tip id
65
+ seen_ids = {t["id"] for t in existing}
66
+ merged = existing + [t for t in tips if t["id"] not in seen_ids]
67
+
68
+ data.setdefault("keys", {})[key] = {
69
+ "generated_at": time.time(),
70
+ "tips": merged,
71
+ }
72
+ data["version"] = 2
73
+ _save_all(data)
74
+
75
+
76
+ def cache_needs_refill(topic: str | None, level: str | None, unseen_count: int) -> bool:
77
+ """Return True when unseen tips are running low and cache entry exists."""
78
+ if unseen_count > 3:
79
+ return False
80
+ data = _load_all()
81
+ key = _cache_key(topic, level)
82
+ return key in data.get("keys", {})
83
+
84
+
85
+ def is_on_cooldown() -> bool:
86
+ """Return True if a recent API failure means we should skip retrying."""
87
+ data = _load_all()
88
+ failed_at = data.get("last_failure", 0)
89
+ return time.time() - failed_at < COOLDOWN_SECONDS
90
+
91
+
92
+ def mark_failure() -> None:
93
+ """Record an API failure timestamp for cooldown backoff."""
94
+ data = _load_all()
95
+ data["last_failure"] = time.time()
96
+ _save_all(data)
97
+
98
+
99
+ def clear_cache() -> None:
100
+ """Delete the AI cache file."""
101
+ if CACHE_FILE.exists():
102
+ CACHE_FILE.unlink()
103
+
104
+
105
+ def get_cache_stats() -> dict:
106
+ """Return cache statistics for the status command."""
107
+ data = _load_all()
108
+ keys = data.get("keys", {})
109
+ total_tips = sum(len(entry.get("tips", [])) for entry in keys.values())
110
+ return {
111
+ "keys": len(keys),
112
+ "total_tips": total_tips,
113
+ "cooldown_active": is_on_cooldown(),
114
+ }
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import urllib.request
5
+
6
+ from dev_tip.ai.prompt import build_prompt, parse_response
7
+ from dev_tip.ai.provider import AIProvider
8
+
9
+ DEFAULT_MODEL = "gemini-2.0-flash"
10
+ _ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={api_key}"
11
+
12
+
13
+ class GeminiProvider(AIProvider):
14
+ def __init__(self, api_key: str, model: str | None = None) -> None:
15
+ self._api_key = api_key
16
+ self._model = model or DEFAULT_MODEL
17
+
18
+ def generate_tips(self, topic: str | None, level: str | None, count: int) -> list[dict]:
19
+ prompt = build_prompt(topic, level, count)
20
+ url = _ENDPOINT.format(model=self._model, api_key=self._api_key)
21
+ body = json.dumps({
22
+ "contents": [{"parts": [{"text": prompt}]}],
23
+ }).encode()
24
+ req = urllib.request.Request(url, data=body, headers={"Content-Type": "application/json"})
25
+ with urllib.request.urlopen(req, timeout=30) as resp:
26
+ data = json.loads(resp.read())
27
+ text = data["candidates"][0]["content"]["parts"][0]["text"]
28
+ return parse_response(text)