-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaskc
More file actions
executable file
·153 lines (134 loc) · 5.19 KB
/
askc
File metadata and controls
executable file
·153 lines (134 loc) · 5.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#!/usr/bin/env python3
"""Context-aware terminal assistant. Keeps conversation history + reads shell history."""
import sys
import os
import json
import urllib.request
import subprocess
import tempfile
import hashlib
SYSTEM = """You are a terminal command assistant with context of the user's terminal session.
You can see their recent shell history and prior conversation.
Rules:
- Default: give ONLY the command. No explanation, no markdown fences, no preamble.
- If no single command answers it, use a short bulleted list (2-5 items max).
- Never say "sure", "here you go", or any filler.
- Only explain if the user explicitly says "explain" or "why".
- Prefer one-liners. Be terse.
- Use the shell history context to give relevant answers (e.g. "fix that" = fix the last failed command)."""
MODEL = "gemini-3.1-flash-lite-preview"
def get_session_file():
"""Unique history file per terminal session, stored in tmp."""
# Use parent PID (the shell) so all askc calls in one terminal share history
ppid = os.getppid()
h = hashlib.md5(str(ppid).encode()).hexdigest()[:8]
return os.path.join(tempfile.gettempdir(), f"askc_{h}.json")
def load_history(path):
try:
with open(path) as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return []
def save_history(path, history):
# Keep last 20 turns to avoid bloating context
with open(path, "w") as f:
json.dump(history[-40:], f)
def get_shell_history(n=30):
"""Grab last N commands from shell history."""
# Try zsh first, then bash
hist_file = os.environ.get("HISTFILE")
if not hist_file:
for candidate in ["~/.zsh_history", "~/.bash_history"]:
expanded = os.path.expanduser(candidate)
if os.path.exists(expanded):
hist_file = expanded
break
if not hist_file or not os.path.exists(hist_file):
return ""
try:
with open(hist_file, "rb") as f:
raw = f.read()
# zsh history has metadata prefixed with ': timestamp:0;'
lines = []
for line in raw.decode("utf-8", errors="replace").splitlines():
if line.startswith(": "):
cmd = line.split(";", 1)[-1] if ";" in line else line
lines.append(cmd)
elif line.strip():
lines.append(line.strip())
return "\n".join(lines[-n:])
except Exception:
return ""
def stream(url, body):
req = urllib.request.Request(url, data=body, headers={"Content-Type": "application/json"})
full = []
first = True
try:
with urllib.request.urlopen(req) as resp:
for line in resp:
line = line.decode().strip()
if not line.startswith("data: "):
continue
chunk = json.loads(line[6:])
parts = chunk.get("candidates", [{}])[0].get("content", {}).get("parts", [])
for part in parts:
text = part.get("text", "")
if text:
if first:
text = text.lstrip()
first = False
sys.stdout.write(text)
sys.stdout.flush()
full.append(text)
if not first:
print()
except urllib.error.HTTPError as e:
err = e.read().decode()
try:
msg = json.loads(err)["error"]["message"]
except Exception:
msg = err
print(f"Error: {msg}", file=sys.stderr)
sys.exit(1)
return "".join(full).strip()
def main():
if len(sys.argv) < 2:
if len(sys.argv) == 1:
print("Usage: askc <question> (context-aware, keeps history)")
print(" askc --clear (clear conversation history)")
sys.exit(1)
api_key = os.environ.get("GEMINI_API_KEY")
if not api_key:
print("Set GEMINI_API_KEY in your environment.")
sys.exit(1)
session_file = get_session_file()
if sys.argv[1] == "--clear":
try:
os.remove(session_file)
except FileNotFoundError:
pass
print("Conversation cleared.")
return
query = " ".join(sys.argv[1:])
history = load_history(session_file)
# Build context: shell history on first message
shell_hist = get_shell_history()
system_with_context = SYSTEM
if shell_hist:
system_with_context += f"\n\nRecent shell history:\n{shell_hist}"
# Build conversation contents
contents = list(history)
contents.append({"role": "user", "parts": [{"text": query}]})
url = f"https://generativelanguage.googleapis.com/v1beta/models/{MODEL}:streamGenerateContent?alt=sse&key={api_key}"
body = json.dumps({
"system_instruction": {"parts": [{"text": system_with_context}]},
"contents": contents,
"generationConfig": {"temperature": 0.1},
}).encode()
response_text = stream(url, body)
# Save to history
history.append({"role": "user", "parts": [{"text": query}]})
history.append({"role": "model", "parts": [{"text": response_text}]})
save_history(session_file, history)
if __name__ == "__main__":
main()