recall-terminal 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.
- recall_terminal-0.1.0/PKG-INFO +7 -0
- recall_terminal-0.1.0/README.md +56 -0
- recall_terminal-0.1.0/main.py +522 -0
- recall_terminal-0.1.0/recall_terminal.egg-info/PKG-INFO +7 -0
- recall_terminal-0.1.0/recall_terminal.egg-info/SOURCES.txt +9 -0
- recall_terminal-0.1.0/recall_terminal.egg-info/dependency_links.txt +1 -0
- recall_terminal-0.1.0/recall_terminal.egg-info/entry_points.txt +2 -0
- recall_terminal-0.1.0/recall_terminal.egg-info/requires.txt +3 -0
- recall_terminal-0.1.0/recall_terminal.egg-info/top_level.txt +1 -0
- recall_terminal-0.1.0/setup.cfg +4 -0
- recall_terminal-0.1.0/setup.py +17 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Recall — The AI Brain for Developers
|
|
2
|
+
|
|
3
|
+
> Stop Googling commands. Recall remembers everything.
|
|
4
|
+
|
|
5
|
+
Recall is an AI-powered terminal tool that understands natural language, remembers your history, fixes your errors, and automates your workflows — all inside your terminal.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install recall-ai
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
recall --setup
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Natural language to command
|
|
23
|
+
recall "how to check running ports"
|
|
24
|
+
|
|
25
|
+
# See your history
|
|
26
|
+
recall --history
|
|
27
|
+
|
|
28
|
+
# Fix an error
|
|
29
|
+
recall --error "ModuleNotFoundError: No module named pandas"
|
|
30
|
+
|
|
31
|
+
# Save a workflow
|
|
32
|
+
recall --save "deploy"
|
|
33
|
+
|
|
34
|
+
# Run a workflow
|
|
35
|
+
recall --run "deploy"
|
|
36
|
+
|
|
37
|
+
# List all workflows
|
|
38
|
+
recall --list-workflows
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- Natural language → exact terminal command
|
|
44
|
+
- Persistent memory — remembers your history
|
|
45
|
+
- Context aware — learns from past queries
|
|
46
|
+
- Error intelligence — detects and fixes errors automatically
|
|
47
|
+
- Workflow automation — save and run command sequences
|
|
48
|
+
- Cross platform — Windows, Mac, Linux
|
|
49
|
+
|
|
50
|
+
## Built With
|
|
51
|
+
|
|
52
|
+
Python · FastAPI · AWS EC2 · Anthropic Claude API
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
Built by [Muhammad Bilal Ur Rehman](https://github.com/mbilalrehman)
|
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import sqlite3
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import platform
|
|
6
|
+
import requests
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
from rich import box
|
|
11
|
+
from rich.prompt import Prompt, Confirm
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
# BACKEND_URL = "http://127.0.0.1:8000"
|
|
15
|
+
BACKEND_URL = "http://100.53.1.66:8000"
|
|
16
|
+
|
|
17
|
+
# ══════════════════════════════════════
|
|
18
|
+
# TOKEN
|
|
19
|
+
# ══════════════════════════════════════
|
|
20
|
+
|
|
21
|
+
def get_token():
|
|
22
|
+
config_path = os.path.expanduser("~/.recall/config")
|
|
23
|
+
if os.path.exists(config_path):
|
|
24
|
+
with open(config_path, 'r') as f:
|
|
25
|
+
for line in f:
|
|
26
|
+
if line.startswith("TOKEN="):
|
|
27
|
+
return line.strip().split("=", 1)[1]
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
def get_os():
|
|
31
|
+
system = platform.system().lower()
|
|
32
|
+
if system == "windows":
|
|
33
|
+
return "windows"
|
|
34
|
+
elif system == "darwin":
|
|
35
|
+
return "macos"
|
|
36
|
+
else:
|
|
37
|
+
return "linux"
|
|
38
|
+
|
|
39
|
+
def get_shell():
|
|
40
|
+
os_type = get_os()
|
|
41
|
+
if os_type == "windows":
|
|
42
|
+
return "cmd/powershell"
|
|
43
|
+
elif os_type == "macos":
|
|
44
|
+
return "zsh"
|
|
45
|
+
else:
|
|
46
|
+
return "bash"
|
|
47
|
+
|
|
48
|
+
# ══════════════════════════════════════
|
|
49
|
+
# API QUERY — BACKEND SE
|
|
50
|
+
# ══════════════════════════════════════
|
|
51
|
+
|
|
52
|
+
def api_query(query_text):
|
|
53
|
+
token = get_token()
|
|
54
|
+
if not token:
|
|
55
|
+
console.print(Panel(
|
|
56
|
+
"[red]Not logged in![/red]\n\n"
|
|
57
|
+
"Run [cyan]recall --setup[/cyan] first.",
|
|
58
|
+
title="[bold red]⚠ Not Logged In[/bold red]",
|
|
59
|
+
border_style="red"
|
|
60
|
+
))
|
|
61
|
+
exit(1)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
response = requests.post(
|
|
65
|
+
f"{BACKEND_URL}/query",
|
|
66
|
+
json={"query": query_text, "os_type": platform.system()},
|
|
67
|
+
headers={"Authorization": f"Bearer {token}"}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if response.status_code == 401:
|
|
71
|
+
console.print(Panel(
|
|
72
|
+
"[red]Session expired![/red]\n\n"
|
|
73
|
+
"Run [cyan]recall --setup[/cyan] again.",
|
|
74
|
+
border_style="red"
|
|
75
|
+
))
|
|
76
|
+
exit(1)
|
|
77
|
+
|
|
78
|
+
if response.status_code == 429:
|
|
79
|
+
console.print(Panel(
|
|
80
|
+
"[red]Free limit reached! (50 queries/month)[/red]\n\n"
|
|
81
|
+
"Upgrade to Pro: [cyan]recall --upgrade[/cyan]",
|
|
82
|
+
border_style="red"
|
|
83
|
+
))
|
|
84
|
+
exit(1)
|
|
85
|
+
|
|
86
|
+
data = response.json()
|
|
87
|
+
return data.get("command", "Command not found")
|
|
88
|
+
|
|
89
|
+
except requests.exceptions.ConnectionError:
|
|
90
|
+
console.print(Panel(
|
|
91
|
+
"[red]Cannot connect to Recall server![/red]\n\n"
|
|
92
|
+
"Start backend:\n"
|
|
93
|
+
"[cyan]cd recall-backend && uvicorn main:app --reload[/cyan]",
|
|
94
|
+
border_style="red"
|
|
95
|
+
))
|
|
96
|
+
exit(1)
|
|
97
|
+
|
|
98
|
+
# ══════════════════════════════════════
|
|
99
|
+
# DATABASE — LOCAL MEMORY
|
|
100
|
+
# ══════════════════════════════════════
|
|
101
|
+
|
|
102
|
+
def get_db_path():
|
|
103
|
+
db_dir = os.path.expanduser("~/.recall")
|
|
104
|
+
os.makedirs(db_dir, exist_ok=True)
|
|
105
|
+
return os.path.join(db_dir, "memory.db")
|
|
106
|
+
|
|
107
|
+
def get_connection():
|
|
108
|
+
conn = sqlite3.connect(get_db_path())
|
|
109
|
+
conn.execute('''CREATE TABLE IF NOT EXISTS memory
|
|
110
|
+
(query TEXT, command TEXT,
|
|
111
|
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)''')
|
|
112
|
+
conn.execute('''CREATE TABLE IF NOT EXISTS workflows
|
|
113
|
+
(name TEXT UNIQUE, commands TEXT,
|
|
114
|
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)''')
|
|
115
|
+
conn.commit()
|
|
116
|
+
return conn
|
|
117
|
+
|
|
118
|
+
def save_to_memory(query, command):
|
|
119
|
+
conn = get_connection()
|
|
120
|
+
conn.execute(
|
|
121
|
+
"INSERT INTO memory VALUES (?, ?, datetime('now'))",
|
|
122
|
+
(query, command)
|
|
123
|
+
)
|
|
124
|
+
conn.commit()
|
|
125
|
+
conn.close()
|
|
126
|
+
|
|
127
|
+
def search_memory(query):
|
|
128
|
+
conn = get_connection()
|
|
129
|
+
cursor = conn.execute(
|
|
130
|
+
"SELECT command FROM memory WHERE query LIKE ? ORDER BY timestamp DESC LIMIT 1",
|
|
131
|
+
(f'%{query}%',)
|
|
132
|
+
)
|
|
133
|
+
result = cursor.fetchone()
|
|
134
|
+
conn.close()
|
|
135
|
+
return result[0] if result else None
|
|
136
|
+
|
|
137
|
+
def get_recent_history(limit=5):
|
|
138
|
+
conn = get_connection()
|
|
139
|
+
cursor = conn.execute(
|
|
140
|
+
"SELECT query, command, timestamp FROM memory ORDER BY timestamp DESC LIMIT ?",
|
|
141
|
+
(limit,)
|
|
142
|
+
)
|
|
143
|
+
results = cursor.fetchall()
|
|
144
|
+
conn.close()
|
|
145
|
+
return results
|
|
146
|
+
|
|
147
|
+
# ══════════════════════════════════════
|
|
148
|
+
# SETUP
|
|
149
|
+
# ══════════════════════════════════════
|
|
150
|
+
|
|
151
|
+
def run_setup():
|
|
152
|
+
console.print()
|
|
153
|
+
console.print(Panel(
|
|
154
|
+
"[bold cyan]Welcome to Recall![/bold cyan]\n\n"
|
|
155
|
+
"Create your account in 30 seconds.",
|
|
156
|
+
title="[bold]✦ Recall Setup[/bold]",
|
|
157
|
+
border_style="cyan"
|
|
158
|
+
))
|
|
159
|
+
console.print()
|
|
160
|
+
|
|
161
|
+
email = Prompt.ask(" [cyan]Email[/cyan]")
|
|
162
|
+
password = Prompt.ask(" [cyan]Password[/cyan]", password=True)
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
# Pehle login try karo
|
|
166
|
+
response = requests.post(
|
|
167
|
+
f"{BACKEND_URL}/login",
|
|
168
|
+
json={"email": email, "password": password}
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Login fail — naya account banao
|
|
172
|
+
if response.status_code != 200:
|
|
173
|
+
response = requests.post(
|
|
174
|
+
f"{BACKEND_URL}/signup",
|
|
175
|
+
json={"email": email, "password": password}
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
data = response.json()
|
|
179
|
+
token = data.get("token")
|
|
180
|
+
|
|
181
|
+
if not token:
|
|
182
|
+
console.print(Panel(
|
|
183
|
+
f"[red]Error: {data}[/red]",
|
|
184
|
+
border_style="red"
|
|
185
|
+
))
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
config_dir = os.path.expanduser("~/.recall")
|
|
189
|
+
os.makedirs(config_dir, exist_ok=True)
|
|
190
|
+
config_path = os.path.join(config_dir, "config")
|
|
191
|
+
|
|
192
|
+
with open(config_path, 'w') as f:
|
|
193
|
+
f.write(f"TOKEN={token}\n")
|
|
194
|
+
|
|
195
|
+
console.print()
|
|
196
|
+
console.print(Panel(
|
|
197
|
+
"[green]✓ Account ready![/green]\n\n"
|
|
198
|
+
f"Email: [cyan]{email}[/cyan]\n"
|
|
199
|
+
f"Plan: [yellow]Free[/yellow] — 50 queries/month\n\n"
|
|
200
|
+
"Try: [bold cyan]recall \"how to check ports\"[/bold cyan]",
|
|
201
|
+
title="[bold green]✓ Welcome to Recall![/bold green]",
|
|
202
|
+
border_style="green"
|
|
203
|
+
))
|
|
204
|
+
|
|
205
|
+
except requests.exceptions.ConnectionError:
|
|
206
|
+
console.print(Panel(
|
|
207
|
+
"[red]Cannot connect to server![/red]\n\n"
|
|
208
|
+
"Start backend:\n"
|
|
209
|
+
"[cyan]cd recall-backend && uvicorn main:app --reload[/cyan]",
|
|
210
|
+
border_style="red"
|
|
211
|
+
))
|
|
212
|
+
|
|
213
|
+
# ══════════════════════════════════════
|
|
214
|
+
# FEATURE 1 — HISTORY
|
|
215
|
+
# ══════════════════════════════════════
|
|
216
|
+
|
|
217
|
+
def show_history():
|
|
218
|
+
results = get_recent_history(10)
|
|
219
|
+
|
|
220
|
+
if not results:
|
|
221
|
+
console.print(Panel(
|
|
222
|
+
"[yellow]No history yet![/yellow]\n\n"
|
|
223
|
+
"Try: [cyan]recall \"how to check running ports\"[/cyan]",
|
|
224
|
+
title="[bold]📜 Recall History[/bold]",
|
|
225
|
+
border_style="yellow"
|
|
226
|
+
))
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
table = Table(
|
|
230
|
+
title="📜 Recall History",
|
|
231
|
+
box=box.ROUNDED,
|
|
232
|
+
border_style="cyan",
|
|
233
|
+
header_style="bold cyan",
|
|
234
|
+
show_lines=True
|
|
235
|
+
)
|
|
236
|
+
table.add_column("#", style="dim", width=3)
|
|
237
|
+
table.add_column("Query", style="white", min_width=30)
|
|
238
|
+
table.add_column("Command", style="green", min_width=25)
|
|
239
|
+
table.add_column("Time", style="dim", min_width=20)
|
|
240
|
+
|
|
241
|
+
for i, (query, command, timestamp) in enumerate(results, 1):
|
|
242
|
+
table.add_row(str(i), query, command, timestamp)
|
|
243
|
+
|
|
244
|
+
console.print()
|
|
245
|
+
console.print(table)
|
|
246
|
+
console.print()
|
|
247
|
+
|
|
248
|
+
# ══════════════════════════════════════
|
|
249
|
+
# FEATURE 2 — ERROR INTELLIGENCE
|
|
250
|
+
# ══════════════════════════════════════
|
|
251
|
+
|
|
252
|
+
def fix_error(error_text):
|
|
253
|
+
with console.status("[cyan]Analyzing error...[/cyan]", spinner="dots"):
|
|
254
|
+
try:
|
|
255
|
+
token = get_token()
|
|
256
|
+
response = requests.post(
|
|
257
|
+
f"{BACKEND_URL}/fix-error",
|
|
258
|
+
json={"error": error_text, "os_type": platform.system()},
|
|
259
|
+
headers={"Authorization": f"Bearer {token}"}
|
|
260
|
+
)
|
|
261
|
+
data = response.json()
|
|
262
|
+
cause = data.get("cause", "Unknown")
|
|
263
|
+
fix = data.get("fix", "No fix found")
|
|
264
|
+
confidence = data.get("confidence", "Low")
|
|
265
|
+
except:
|
|
266
|
+
cause = "Could not analyze"
|
|
267
|
+
fix = "Check the error manually"
|
|
268
|
+
confidence = "Low"
|
|
269
|
+
|
|
270
|
+
conf_color = "green" if confidence == "High" else "yellow" if confidence == "Medium" else "red"
|
|
271
|
+
|
|
272
|
+
save_to_memory(f"error: {error_text}", fix)
|
|
273
|
+
|
|
274
|
+
console.print()
|
|
275
|
+
console.print(Panel(
|
|
276
|
+
f"[bold red]✗ Error:[/bold red] {error_text}\n\n"
|
|
277
|
+
f"[yellow]● Cause:[/yellow] {cause}\n\n"
|
|
278
|
+
f"[green]● Fix:[/green] [bold cyan]{fix}[/bold cyan]\n\n"
|
|
279
|
+
f"[{conf_color}]● Confidence: {confidence}[/{conf_color}]",
|
|
280
|
+
title="[bold red]⚡ Error Intelligence[/bold red]",
|
|
281
|
+
border_style="red"
|
|
282
|
+
))
|
|
283
|
+
console.print()
|
|
284
|
+
|
|
285
|
+
# ══════════════════════════════════════
|
|
286
|
+
# FEATURE 3 — WORKFLOW SAVE
|
|
287
|
+
# ══════════════════════════════════════
|
|
288
|
+
|
|
289
|
+
def save_workflow(name):
|
|
290
|
+
console.print()
|
|
291
|
+
console.print(Panel(
|
|
292
|
+
f"Saving workflow: [bold cyan]{name}[/bold cyan]\n"
|
|
293
|
+
f"Type commands one by one.\n"
|
|
294
|
+
f"Type [bold]done[/bold] when finished.",
|
|
295
|
+
title="[bold]⚡ Save Workflow[/bold]",
|
|
296
|
+
border_style="cyan"
|
|
297
|
+
))
|
|
298
|
+
|
|
299
|
+
commands = []
|
|
300
|
+
while True:
|
|
301
|
+
cmd = Prompt.ask(f" [cyan]Command {len(commands)+1}[/cyan]")
|
|
302
|
+
if cmd.lower() == 'done':
|
|
303
|
+
break
|
|
304
|
+
if cmd.strip():
|
|
305
|
+
commands.append(cmd.strip())
|
|
306
|
+
|
|
307
|
+
if not commands:
|
|
308
|
+
console.print("[red]No commands entered. Workflow not saved.[/red]")
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
workflow = ' && '.join(commands)
|
|
312
|
+
conn = get_connection()
|
|
313
|
+
conn.execute(
|
|
314
|
+
"INSERT OR REPLACE INTO workflows VALUES (?, ?, datetime('now'))",
|
|
315
|
+
(name, workflow)
|
|
316
|
+
)
|
|
317
|
+
conn.commit()
|
|
318
|
+
conn.close()
|
|
319
|
+
|
|
320
|
+
console.print()
|
|
321
|
+
console.print(Panel(
|
|
322
|
+
f"[green]✓ Workflow saved![/green]\n\n"
|
|
323
|
+
f"[dim]Commands:[/dim]\n" +
|
|
324
|
+
"\n".join([f" {i+1}. {c}" for i, c in enumerate(commands)]) +
|
|
325
|
+
f"\n\n[dim]Run it:[/dim] [cyan]recall --run \"{name}\"[/cyan]",
|
|
326
|
+
title=f"[bold green]✓ Workflow: {name}[/bold green]",
|
|
327
|
+
border_style="green"
|
|
328
|
+
))
|
|
329
|
+
|
|
330
|
+
# ══════════════════════════════════════
|
|
331
|
+
# FEATURE 4 — WORKFLOW RUN
|
|
332
|
+
# ══════════════════════════════════════
|
|
333
|
+
|
|
334
|
+
def run_workflow(name):
|
|
335
|
+
conn = get_connection()
|
|
336
|
+
cursor = conn.execute(
|
|
337
|
+
"SELECT commands FROM workflows WHERE name=?", (name,)
|
|
338
|
+
)
|
|
339
|
+
result = cursor.fetchone()
|
|
340
|
+
conn.close()
|
|
341
|
+
|
|
342
|
+
if not result:
|
|
343
|
+
console.print(Panel(
|
|
344
|
+
f"[red]Workflow '{name}' not found![/red]\n\n"
|
|
345
|
+
f"[dim]Save it:[/dim] [cyan]recall --save \"{name}\"[/cyan]",
|
|
346
|
+
title="[bold red]✗ Not Found[/bold red]",
|
|
347
|
+
border_style="red"
|
|
348
|
+
))
|
|
349
|
+
return
|
|
350
|
+
|
|
351
|
+
commands = result[0].split(' && ')
|
|
352
|
+
|
|
353
|
+
console.print()
|
|
354
|
+
console.print(Panel(
|
|
355
|
+
f"Running [bold cyan]{len(commands)} steps[/bold cyan]",
|
|
356
|
+
title=f"[bold]⚡ Workflow: {name}[/bold]",
|
|
357
|
+
border_style="cyan"
|
|
358
|
+
))
|
|
359
|
+
|
|
360
|
+
for i, cmd in enumerate(commands, 1):
|
|
361
|
+
console.print(f"\n [dim]{i}/{len(commands)}[/dim] [cyan]▶[/cyan] {cmd}")
|
|
362
|
+
|
|
363
|
+
proc = subprocess.run(
|
|
364
|
+
cmd, shell=True,
|
|
365
|
+
capture_output=True, text=True
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
if proc.returncode == 0:
|
|
369
|
+
console.print(f" [green]✓ Done[/green]")
|
|
370
|
+
if proc.stdout.strip():
|
|
371
|
+
console.print(f" [dim]{proc.stdout.strip()[:200]}[/dim]")
|
|
372
|
+
else:
|
|
373
|
+
console.print(f" [red]✗ Failed[/red]")
|
|
374
|
+
if proc.stderr.strip():
|
|
375
|
+
fix_error(proc.stderr.strip())
|
|
376
|
+
if not Confirm.ask(" Continue workflow?"):
|
|
377
|
+
console.print("\n [yellow]⚠ Workflow stopped.[/yellow]\n")
|
|
378
|
+
return
|
|
379
|
+
|
|
380
|
+
console.print()
|
|
381
|
+
console.print(Panel(
|
|
382
|
+
"[green]All steps completed successfully![/green]",
|
|
383
|
+
title="[bold green]✓ Workflow Complete[/bold green]",
|
|
384
|
+
border_style="green"
|
|
385
|
+
))
|
|
386
|
+
|
|
387
|
+
# ══════════════════════════════════════
|
|
388
|
+
# FEATURE 5 — LIST WORKFLOWS
|
|
389
|
+
# ══════════════════════════════════════
|
|
390
|
+
|
|
391
|
+
def list_workflows():
|
|
392
|
+
conn = get_connection()
|
|
393
|
+
cursor = conn.execute(
|
|
394
|
+
"SELECT name, commands, timestamp FROM workflows ORDER BY timestamp DESC"
|
|
395
|
+
)
|
|
396
|
+
results = cursor.fetchall()
|
|
397
|
+
conn.close()
|
|
398
|
+
|
|
399
|
+
if not results:
|
|
400
|
+
console.print(Panel(
|
|
401
|
+
"[yellow]No workflows saved yet![/yellow]\n\n"
|
|
402
|
+
"Save one: [cyan]recall --save \"workflow-name\"[/cyan]",
|
|
403
|
+
title="[bold]⚡ Saved Workflows[/bold]",
|
|
404
|
+
border_style="yellow"
|
|
405
|
+
))
|
|
406
|
+
return
|
|
407
|
+
|
|
408
|
+
table = Table(
|
|
409
|
+
title="⚡ Saved Workflows",
|
|
410
|
+
box=box.ROUNDED,
|
|
411
|
+
border_style="cyan",
|
|
412
|
+
header_style="bold cyan",
|
|
413
|
+
show_lines=True
|
|
414
|
+
)
|
|
415
|
+
table.add_column("#", style="dim", width=3)
|
|
416
|
+
table.add_column("Name", style="bold white", min_width=20)
|
|
417
|
+
table.add_column("Steps", style="cyan", width=6)
|
|
418
|
+
table.add_column("Commands", style="dim", min_width=30)
|
|
419
|
+
table.add_column("Saved", style="dim", min_width=20)
|
|
420
|
+
|
|
421
|
+
for i, (name, commands, timestamp) in enumerate(results, 1):
|
|
422
|
+
cmds = commands.split(' && ')
|
|
423
|
+
table.add_row(
|
|
424
|
+
str(i), name, str(len(cmds)),
|
|
425
|
+
" → ".join(cmds[:2]) + (" → ..." if len(cmds) > 2 else ""),
|
|
426
|
+
timestamp
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
console.print()
|
|
430
|
+
console.print(table)
|
|
431
|
+
console.print()
|
|
432
|
+
|
|
433
|
+
# ══════════════════════════════════════
|
|
434
|
+
# MAIN
|
|
435
|
+
# ══════════════════════════════════════
|
|
436
|
+
|
|
437
|
+
@click.command()
|
|
438
|
+
@click.option('--setup', 'do_setup', is_flag=True, help='Setup Recall')
|
|
439
|
+
@click.option('--history', is_flag=True, help='Show past queries')
|
|
440
|
+
@click.option('--error', default=None, help='Fix an error')
|
|
441
|
+
@click.option('--save', default=None, help='Save a workflow')
|
|
442
|
+
@click.option('--run', default=None, help='Run a workflow')
|
|
443
|
+
@click.option('--list-workflows', 'list_wf', is_flag=True, help='List saved workflows')
|
|
444
|
+
@click.argument('query', nargs=-1)
|
|
445
|
+
def recall(do_setup, history, error, save, run, list_wf, query):
|
|
446
|
+
|
|
447
|
+
if not any([query, do_setup, history, error, save, run, list_wf]):
|
|
448
|
+
console.print()
|
|
449
|
+
console.print(Panel(
|
|
450
|
+
"[bold cyan]✦ RECALL — Your AI Terminal Brain[/bold cyan]\n\n"
|
|
451
|
+
"[white]recall \"your question\"[/white] [dim]→ Natural language to command[/dim]\n"
|
|
452
|
+
"[white]recall --history[/white] [dim]→ See past queries[/dim]\n"
|
|
453
|
+
"[white]recall --error \"error text\"[/white] [dim]→ Fix any error[/dim]\n"
|
|
454
|
+
"[white]recall --save \"name\"[/white] [dim]→ Save a workflow[/dim]\n"
|
|
455
|
+
"[white]recall --run \"name\"[/white] [dim]→ Run a workflow[/dim]\n"
|
|
456
|
+
"[white]recall --list-workflows[/white] [dim]→ List all workflows[/dim]\n"
|
|
457
|
+
"[white]recall --setup[/white] [dim]→ Setup your account[/dim]",
|
|
458
|
+
title="[bold]⚡ Recall Help[/bold]",
|
|
459
|
+
border_style="cyan",
|
|
460
|
+
padding=(1, 2)
|
|
461
|
+
))
|
|
462
|
+
console.print()
|
|
463
|
+
return
|
|
464
|
+
|
|
465
|
+
if do_setup:
|
|
466
|
+
run_setup()
|
|
467
|
+
return
|
|
468
|
+
|
|
469
|
+
if history:
|
|
470
|
+
show_history()
|
|
471
|
+
return
|
|
472
|
+
|
|
473
|
+
if error:
|
|
474
|
+
fix_error(error)
|
|
475
|
+
return
|
|
476
|
+
|
|
477
|
+
if save:
|
|
478
|
+
save_workflow(save)
|
|
479
|
+
return
|
|
480
|
+
|
|
481
|
+
if run:
|
|
482
|
+
run_workflow(run)
|
|
483
|
+
return
|
|
484
|
+
|
|
485
|
+
if list_wf:
|
|
486
|
+
list_workflows()
|
|
487
|
+
return
|
|
488
|
+
|
|
489
|
+
# Natural language query
|
|
490
|
+
q = ' '.join(query)
|
|
491
|
+
|
|
492
|
+
# Memory check first
|
|
493
|
+
cached = search_memory(q)
|
|
494
|
+
if cached:
|
|
495
|
+
console.print()
|
|
496
|
+
console.print(Panel(
|
|
497
|
+
f"[bold green]{cached}[/bold green]\n\n"
|
|
498
|
+
f"[dim]↳ from memory[/dim]",
|
|
499
|
+
title="[bold]✦ Recall[/bold]",
|
|
500
|
+
border_style="green"
|
|
501
|
+
))
|
|
502
|
+
console.print()
|
|
503
|
+
return
|
|
504
|
+
|
|
505
|
+
# Backend se query
|
|
506
|
+
with console.status("[cyan]Thinking...[/cyan]", spinner="dots"):
|
|
507
|
+
command = api_query(q)
|
|
508
|
+
|
|
509
|
+
save_to_memory(q, command)
|
|
510
|
+
|
|
511
|
+
console.print()
|
|
512
|
+
console.print(Panel(
|
|
513
|
+
f"[bold green]{command}[/bold green]\n\n"
|
|
514
|
+
f"[dim]↳ AI generated · {platform.system()}[/dim]",
|
|
515
|
+
title="[bold]✦ Recall[/bold]",
|
|
516
|
+
border_style="cyan"
|
|
517
|
+
))
|
|
518
|
+
console.print()
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
if __name__ == '__main__':
|
|
522
|
+
recall()
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
main.py
|
|
3
|
+
setup.py
|
|
4
|
+
recall_terminal.egg-info/PKG-INFO
|
|
5
|
+
recall_terminal.egg-info/SOURCES.txt
|
|
6
|
+
recall_terminal.egg-info/dependency_links.txt
|
|
7
|
+
recall_terminal.egg-info/entry_points.txt
|
|
8
|
+
recall_terminal.egg-info/requires.txt
|
|
9
|
+
recall_terminal.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
main
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from setuptools import setup
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name='recall-terminal',
|
|
5
|
+
version='0.1.0',
|
|
6
|
+
py_modules=['main'],
|
|
7
|
+
install_requires=[
|
|
8
|
+
'click',
|
|
9
|
+
'anthropic',
|
|
10
|
+
'rich',
|
|
11
|
+
],
|
|
12
|
+
entry_points={
|
|
13
|
+
'console_scripts': [
|
|
14
|
+
'recall=main:recall',
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
)
|