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.
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: recall-terminal
3
+ Version: 0.1.0
4
+ Requires-Dist: click
5
+ Requires-Dist: anthropic
6
+ Requires-Dist: rich
7
+ Dynamic: requires-dist
@@ -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,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: recall-terminal
3
+ Version: 0.1.0
4
+ Requires-Dist: click
5
+ Requires-Dist: anthropic
6
+ Requires-Dist: rich
7
+ Dynamic: requires-dist
@@ -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,2 @@
1
+ [console_scripts]
2
+ recall = main:recall
@@ -0,0 +1,3 @@
1
+ click
2
+ anthropic
3
+ rich
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ )