rubber-ducky 1.6.2__tar.gz → 1.6.4__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.
- {rubber_ducky-1.6.2/rubber_ducky.egg-info → rubber_ducky-1.6.4}/PKG-INFO +1 -1
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4}/ducky/ducky.py +150 -51
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4}/pyproject.toml +1 -1
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4/rubber_ducky.egg-info}/PKG-INFO +1 -1
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4}/LICENSE +0 -0
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4}/MANIFEST.in +0 -0
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4}/README.md +0 -0
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4}/ducky/__init__.py +0 -0
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4}/ducky/config.py +0 -0
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4}/ducky/crumb.py +0 -0
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4}/examples/POLLING_USER_GUIDE.md +0 -0
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4}/examples/mock-logs/info.txt +0 -0
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4}/examples/mock-logs/mock-logs.sh +0 -0
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4}/rubber_ducky.egg-info/SOURCES.txt +0 -0
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4}/rubber_ducky.egg-info/dependency_links.txt +0 -0
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4}/rubber_ducky.egg-info/entry_points.txt +0 -0
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4}/rubber_ducky.egg-info/requires.txt +0 -0
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4}/rubber_ducky.egg-info/top_level.txt +0 -0
- {rubber_ducky-1.6.2 → rubber_ducky-1.6.4}/setup.cfg +0 -0
|
@@ -5,7 +5,6 @@ import asyncio
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
7
|
import re
|
|
8
|
-
import shlex
|
|
9
8
|
import subprocess
|
|
10
9
|
import sys
|
|
11
10
|
from dataclasses import dataclass
|
|
@@ -15,7 +14,7 @@ from pathlib import Path
|
|
|
15
14
|
from textwrap import dedent
|
|
16
15
|
from typing import Any, Dict, List
|
|
17
16
|
|
|
18
|
-
__version__ = "1.6.
|
|
17
|
+
__version__ = "1.6.4"
|
|
19
18
|
|
|
20
19
|
from .config import ConfigManager
|
|
21
20
|
from .crumb import CrumbManager
|
|
@@ -36,6 +35,7 @@ try: # prompt_toolkit is optional at runtime
|
|
|
36
35
|
from prompt_toolkit.patch_stdout import patch_stdout
|
|
37
36
|
from prompt_toolkit.styles import Style
|
|
38
37
|
from prompt_toolkit.widgets import Box, Button, Dialog, Label, TextArea
|
|
38
|
+
from prompt_toolkit.formatted_text import PygmentsTokens
|
|
39
39
|
except ImportError: # pragma: no cover - fallback mode
|
|
40
40
|
PromptSession = None # type: ignore[assignment]
|
|
41
41
|
FileHistory = None # type: ignore[assignment]
|
|
@@ -43,6 +43,9 @@ except ImportError: # pragma: no cover - fallback mode
|
|
|
43
43
|
|
|
44
44
|
def patch_stdout() -> nullcontext:
|
|
45
45
|
return nullcontext()
|
|
46
|
+
else:
|
|
47
|
+
def patch_stdout() -> nullcontext:
|
|
48
|
+
return nullcontext()
|
|
46
49
|
|
|
47
50
|
|
|
48
51
|
@dataclass
|
|
@@ -123,22 +126,22 @@ def print_shell_result(result: ShellResult, truncate: bool = True) -> None:
|
|
|
123
126
|
# Show first 8 lines of stdout
|
|
124
127
|
show_lines = stdout_lines[:8]
|
|
125
128
|
console.print('\n'.join(show_lines), highlight=False)
|
|
126
|
-
console.print(f"... ({len(stdout_lines) - 8} more lines, use /expand to see full output)", style="dim
|
|
129
|
+
console.print(f"... ({len(stdout_lines) - 8} more lines, use /expand to see full output)", style="dim")
|
|
127
130
|
else:
|
|
128
131
|
console.print(result.stdout.rstrip(), highlight=False)
|
|
129
132
|
|
|
130
133
|
if result.stderr.strip():
|
|
131
134
|
if result.stdout.strip():
|
|
132
135
|
console.print()
|
|
133
|
-
console.print("[stderr]", style="bold
|
|
136
|
+
console.print("[stderr]", style="bold yellow")
|
|
134
137
|
if should_truncate:
|
|
135
138
|
# Show first 5 lines of stderr
|
|
136
139
|
show_lines = stderr_lines[:5]
|
|
137
|
-
console.print('\n'.join(show_lines), style="
|
|
140
|
+
console.print('\n'.join(show_lines), style="yellow", highlight=False)
|
|
138
141
|
if len(stderr_lines) > 5:
|
|
139
|
-
console.print(f"... ({len(stderr_lines) - 5} more lines)", style="dim
|
|
142
|
+
console.print(f"... ({len(stderr_lines) - 5} more lines)", style="dim")
|
|
140
143
|
else:
|
|
141
|
-
console.print(result.stderr.rstrip(), style="
|
|
144
|
+
console.print(result.stderr.rstrip(), style="yellow", highlight=False)
|
|
142
145
|
|
|
143
146
|
if result.returncode != 0 or (not result.stdout.strip() and not result.stderr.strip()):
|
|
144
147
|
suffix = (
|
|
@@ -159,7 +162,7 @@ async def run_shell_and_print(
|
|
|
159
162
|
if not command:
|
|
160
163
|
console.print("No command provided.", style="yellow")
|
|
161
164
|
return ShellResult(command="", stdout="", stderr="", returncode=-1)
|
|
162
|
-
console.print(f"$ {command}", style="bold
|
|
165
|
+
console.print(f"$ {command}", style="bold white")
|
|
163
166
|
result = await assistant.run_shell_command(command)
|
|
164
167
|
print_shell_result(result)
|
|
165
168
|
if logger:
|
|
@@ -330,7 +333,7 @@ class RubberDuck:
|
|
|
330
333
|
models.append(m.model)
|
|
331
334
|
return models
|
|
332
335
|
except Exception as e:
|
|
333
|
-
console.print(f"Error listing models: {e}", style="
|
|
336
|
+
console.print(f"Error listing models: {e}", style="yellow")
|
|
334
337
|
return []
|
|
335
338
|
finally:
|
|
336
339
|
# Restore original host
|
|
@@ -359,13 +362,27 @@ class RubberDuck:
|
|
|
359
362
|
os.environ["OLLAMA_HOST"] = "http://localhost:11434"
|
|
360
363
|
self.client = AsyncClient()
|
|
361
364
|
|
|
362
|
-
console.print(f"Switched to model: {model_name}", style="
|
|
365
|
+
console.print(f"Switched to model: {model_name}", style="yellow")
|
|
363
366
|
|
|
364
367
|
def clear_history(self) -> None:
|
|
365
368
|
"""Reset conversation history to the initial system prompt."""
|
|
366
369
|
if self.messages:
|
|
367
370
|
self.messages = [self.messages[0]]
|
|
368
|
-
console.print("Conversation history cleared.", style="
|
|
371
|
+
console.print("Conversation history cleared.", style="yellow")
|
|
372
|
+
|
|
373
|
+
async def check_connection(self) -> tuple[bool, str]:
|
|
374
|
+
"""Check if Ollama host is reachable. Returns (is_connected, message)."""
|
|
375
|
+
try:
|
|
376
|
+
models = await self.list_models()
|
|
377
|
+
return True, f"Connected ({len(models)} models available)"
|
|
378
|
+
except Exception as e:
|
|
379
|
+
error_msg = str(e).lower()
|
|
380
|
+
if "refused" in error_msg:
|
|
381
|
+
return False, "Connection refused - is Ollama running?"
|
|
382
|
+
elif "timeout" in error_msg:
|
|
383
|
+
return False, "Connection timeout - check network/host"
|
|
384
|
+
else:
|
|
385
|
+
return False, f"Error: {e}"
|
|
369
386
|
|
|
370
387
|
|
|
371
388
|
class InlineInterface:
|
|
@@ -374,6 +391,7 @@ class InlineInterface:
|
|
|
374
391
|
assistant: RubberDuck,
|
|
375
392
|
logger: ConversationLogger | None = None,
|
|
376
393
|
code: str | None = None,
|
|
394
|
+
quiet_mode: bool = False,
|
|
377
395
|
) -> None:
|
|
378
396
|
ensure_history_dir()
|
|
379
397
|
self.assistant = assistant
|
|
@@ -387,6 +405,7 @@ class InlineInterface:
|
|
|
387
405
|
self.session: PromptSession | None = None
|
|
388
406
|
self.selected_model: str | None = None
|
|
389
407
|
self.crumb_manager = CrumbManager()
|
|
408
|
+
self.quiet_mode = quiet_mode
|
|
390
409
|
|
|
391
410
|
if (
|
|
392
411
|
PromptSession is not None
|
|
@@ -426,6 +445,36 @@ class InlineInterface:
|
|
|
426
445
|
|
|
427
446
|
return kb
|
|
428
447
|
|
|
448
|
+
def _print_banner(self) -> None:
|
|
449
|
+
"""Print the startup banner with version, model info, and crumb count."""
|
|
450
|
+
from .ducky import __version__
|
|
451
|
+
|
|
452
|
+
# Determine model display with host indicator
|
|
453
|
+
model = self.assistant.model
|
|
454
|
+
host = os.environ.get("OLLAMA_HOST", "")
|
|
455
|
+
if host == "https://ollama.com":
|
|
456
|
+
model_display = f"{model}:cloud"
|
|
457
|
+
elif "localhost" in host:
|
|
458
|
+
model_display = f"{model}:local"
|
|
459
|
+
else:
|
|
460
|
+
model_display = model
|
|
461
|
+
|
|
462
|
+
# Get crumb count
|
|
463
|
+
crumbs = self.crumb_manager.list_crumbs()
|
|
464
|
+
crumb_count = len(crumbs)
|
|
465
|
+
|
|
466
|
+
# Print banner with yellow/white color scheme
|
|
467
|
+
console.print(f"Ducky v{__version__}", style="yellow")
|
|
468
|
+
console.print(
|
|
469
|
+
f"Model: [bold white]{model_display}[/bold white] | Crumbs: {crumb_count}"
|
|
470
|
+
)
|
|
471
|
+
console.print()
|
|
472
|
+
console.print(
|
|
473
|
+
"Enter submits • !cmd=shell • Ctrl+D=exit • /help=commands",
|
|
474
|
+
style="dim",
|
|
475
|
+
)
|
|
476
|
+
console.print()
|
|
477
|
+
|
|
429
478
|
async def run(self) -> None:
|
|
430
479
|
if self.session is None:
|
|
431
480
|
console.print(
|
|
@@ -435,10 +484,10 @@ class InlineInterface:
|
|
|
435
484
|
await self._run_basic_loop()
|
|
436
485
|
return
|
|
437
486
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
487
|
+
# Print banner if not in quiet mode
|
|
488
|
+
if not self.quiet_mode:
|
|
489
|
+
self._print_banner()
|
|
490
|
+
|
|
442
491
|
while True:
|
|
443
492
|
try:
|
|
444
493
|
with patch_stdout():
|
|
@@ -497,9 +546,9 @@ class InlineInterface:
|
|
|
497
546
|
)
|
|
498
547
|
process.communicate(input=command_to_copy)
|
|
499
548
|
|
|
500
|
-
console.print(f"Copied to clipboard: {command_to_copy}", style="
|
|
549
|
+
console.print(f"Copied to clipboard: {command_to_copy}", style="yellow")
|
|
501
550
|
except Exception as e:
|
|
502
|
-
console.print(f"Failed to copy to clipboard: {e}", style="
|
|
551
|
+
console.print(f"Failed to copy to clipboard: {e}", style="yellow")
|
|
503
552
|
console.print("You can manually copy the last command:", style="dim")
|
|
504
553
|
console.print(f" {self.last_command}", style="bold")
|
|
505
554
|
|
|
@@ -533,7 +582,7 @@ class InlineInterface:
|
|
|
533
582
|
return
|
|
534
583
|
|
|
535
584
|
console.print()
|
|
536
|
-
console.print(f"[Full output for: {self.last_shell_result.command}]", style="bold
|
|
585
|
+
console.print(f"[Full output for: {self.last_shell_result.command}]", style="bold white")
|
|
537
586
|
console.print()
|
|
538
587
|
print_shell_result(self.last_shell_result, truncate=False)
|
|
539
588
|
console.print()
|
|
@@ -554,8 +603,8 @@ class InlineInterface:
|
|
|
554
603
|
first_word = stripped.split()[0].lower()
|
|
555
604
|
if self.crumb_manager.has_crumb(first_word):
|
|
556
605
|
# Extract additional arguments after the crumb name
|
|
557
|
-
parts = stripped.split()
|
|
558
|
-
args = parts[1:]
|
|
606
|
+
parts = stripped.split(maxsplit=1)
|
|
607
|
+
args = parts[1:] if len(parts) > 1 else []
|
|
559
608
|
await self._use_crumb(first_word, args)
|
|
560
609
|
return
|
|
561
610
|
|
|
@@ -636,8 +685,8 @@ class InlineInterface:
|
|
|
636
685
|
|
|
637
686
|
async def _show_help(self) -> None:
|
|
638
687
|
"""Display help information for all available commands."""
|
|
639
|
-
console.print("\nDucky CLI Help", style="bold
|
|
640
|
-
console.print("===============", style="bold
|
|
688
|
+
console.print("\nDucky CLI Help", style="bold white")
|
|
689
|
+
console.print("===============", style="bold white")
|
|
641
690
|
console.print()
|
|
642
691
|
|
|
643
692
|
commands = [
|
|
@@ -692,8 +741,8 @@ class InlineInterface:
|
|
|
692
741
|
console.print("No crumbs saved yet. Use '/crumb <name>' to save a command.", style="yellow")
|
|
693
742
|
return
|
|
694
743
|
|
|
695
|
-
console.print("\nSaved Crumbs", style="bold
|
|
696
|
-
console.print("=============", style="bold
|
|
744
|
+
console.print("\nSaved Crumbs", style="bold white")
|
|
745
|
+
console.print("=============", style="bold white")
|
|
697
746
|
console.print()
|
|
698
747
|
|
|
699
748
|
# Calculate max name length for alignment
|
|
@@ -706,7 +755,7 @@ class InlineInterface:
|
|
|
706
755
|
|
|
707
756
|
# Format: name | explanation | command
|
|
708
757
|
console.print(
|
|
709
|
-
f"[bold]{name:<{max_name_len}}[/bold] | [
|
|
758
|
+
f"[bold yellow]{name:<{max_name_len}}[/bold yellow] | [white]{explanation}[/white] | [dim]{command}[/dim]"
|
|
710
759
|
)
|
|
711
760
|
|
|
712
761
|
console.print(f"\n[dim]Total: {len(crumbs)} crumbs[/dim]")
|
|
@@ -823,7 +872,7 @@ class InlineInterface:
|
|
|
823
872
|
command=self.assistant.last_result.command,
|
|
824
873
|
)
|
|
825
874
|
|
|
826
|
-
console.print(f"Saved crumb '{name}'!", style="
|
|
875
|
+
console.print(f"Saved crumb '{name}'!", style="yellow")
|
|
827
876
|
console.print("Generating explanation...", style="dim")
|
|
828
877
|
|
|
829
878
|
# Spawn subprocess to generate explanation asynchronously
|
|
@@ -852,7 +901,7 @@ class InlineInterface:
|
|
|
852
901
|
clean_explanation = re.sub(r'\x1b\[([0-9;]*[mGK])', '', explanation)
|
|
853
902
|
|
|
854
903
|
text = Text()
|
|
855
|
-
text.append("Explanation added: ", style="
|
|
904
|
+
text.append("Explanation added: ", style="white")
|
|
856
905
|
text.append(clean_explanation)
|
|
857
906
|
console.print(text)
|
|
858
907
|
except Exception as e:
|
|
@@ -867,7 +916,7 @@ class InlineInterface:
|
|
|
867
916
|
command=command,
|
|
868
917
|
)
|
|
869
918
|
|
|
870
|
-
console.print(f"Added crumb '{name}'!", style="
|
|
919
|
+
console.print(f"Added crumb '{name}'!", style="yellow")
|
|
871
920
|
console.print("Generating explanation...", style="dim")
|
|
872
921
|
|
|
873
922
|
# Spawn subprocess to generate explanation asynchronously
|
|
@@ -876,7 +925,7 @@ class InlineInterface:
|
|
|
876
925
|
async def _delete_crumb(self, name: str) -> None:
|
|
877
926
|
"""Delete a crumb."""
|
|
878
927
|
if self.crumb_manager.delete_crumb(name):
|
|
879
|
-
console.print(f"Deleted crumb '{name}'.", style="
|
|
928
|
+
console.print(f"Deleted crumb '{name}'.", style="yellow")
|
|
880
929
|
else:
|
|
881
930
|
console.print(f"Crumb '{name}' not found.", style="yellow")
|
|
882
931
|
|
|
@@ -899,9 +948,13 @@ class InlineInterface:
|
|
|
899
948
|
if args and command != "No command":
|
|
900
949
|
command = substitute_placeholders(command, args)
|
|
901
950
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
951
|
+
from rich.text import Text
|
|
952
|
+
crumb_text = Text()
|
|
953
|
+
crumb_text.append("Crumb: ", style="bold yellow")
|
|
954
|
+
crumb_text.append(name, style="bold yellow")
|
|
955
|
+
console.print(f"\n{crumb_text}")
|
|
956
|
+
console.print(f"Explanation: {explanation}", style="yellow")
|
|
957
|
+
console.print("Command: ", style="white", end="")
|
|
905
958
|
console.print(command, highlight=False)
|
|
906
959
|
|
|
907
960
|
if command and command != "No command":
|
|
@@ -918,7 +971,7 @@ class InlineInterface:
|
|
|
918
971
|
return
|
|
919
972
|
|
|
920
973
|
# Show current model
|
|
921
|
-
console.print(f"Current model: {self.assistant.model}", style="bold
|
|
974
|
+
console.print(f"Current model: {self.assistant.model}", style="bold yellow")
|
|
922
975
|
|
|
923
976
|
# If no host specified, give user a choice between local and cloud
|
|
924
977
|
if not host:
|
|
@@ -940,17 +993,17 @@ class InlineInterface:
|
|
|
940
993
|
elif choice == "2":
|
|
941
994
|
host = "https://ollama.com"
|
|
942
995
|
else:
|
|
943
|
-
console.print("Invalid choice. Please select 1 or 2.", style="
|
|
996
|
+
console.print("Invalid choice. Please select 1 or 2.", style="yellow")
|
|
944
997
|
return
|
|
945
998
|
except (ValueError, EOFError):
|
|
946
|
-
console.print("Invalid input.", style="
|
|
999
|
+
console.print("Invalid input.", style="yellow")
|
|
947
1000
|
return
|
|
948
1001
|
|
|
949
1002
|
models = await self.assistant.list_models(host)
|
|
950
1003
|
if not models:
|
|
951
1004
|
if host == "http://localhost:11434":
|
|
952
1005
|
console.print(
|
|
953
|
-
"No local models available. Is Ollama running?", style="
|
|
1006
|
+
"No local models available. Is Ollama running?", style="yellow"
|
|
954
1007
|
)
|
|
955
1008
|
console.print("Start Ollama with: ollama serve", style="yellow")
|
|
956
1009
|
else:
|
|
@@ -966,7 +1019,7 @@ class InlineInterface:
|
|
|
966
1019
|
|
|
967
1020
|
for i, model in enumerate(models, 1):
|
|
968
1021
|
if model == self.assistant.model:
|
|
969
|
-
console.print(f"{i}. {model} (current)", style="
|
|
1022
|
+
console.print(f"{i}. [bold yellow]{model}[/bold yellow] (current)", style="yellow")
|
|
970
1023
|
else:
|
|
971
1024
|
console.print(f"{i}. {model}")
|
|
972
1025
|
|
|
@@ -985,14 +1038,14 @@ class InlineInterface:
|
|
|
985
1038
|
if 0 <= index < len(models):
|
|
986
1039
|
selected_model = models[index]
|
|
987
1040
|
else:
|
|
988
|
-
console.print("Invalid model number.", style="
|
|
1041
|
+
console.print("Invalid model number.", style="yellow")
|
|
989
1042
|
return
|
|
990
1043
|
else:
|
|
991
1044
|
# Check if it's a model name
|
|
992
1045
|
if choice in models:
|
|
993
1046
|
selected_model = choice
|
|
994
1047
|
else:
|
|
995
|
-
console.print("Invalid model name.", style="
|
|
1048
|
+
console.print("Invalid model name.", style="yellow")
|
|
996
1049
|
return
|
|
997
1050
|
|
|
998
1051
|
self.assistant.switch_model(selected_model, host)
|
|
@@ -1004,7 +1057,7 @@ class InlineInterface:
|
|
|
1004
1057
|
host or os.environ.get("OLLAMA_HOST", "http://localhost:11434"),
|
|
1005
1058
|
)
|
|
1006
1059
|
except (ValueError, EOFError):
|
|
1007
|
-
console.print("Invalid input.", style="
|
|
1060
|
+
console.print("Invalid input.", style="yellow")
|
|
1008
1061
|
|
|
1009
1062
|
async def _run_basic_loop(self) -> None: # pragma: no cover - fallback path
|
|
1010
1063
|
while True:
|
|
@@ -1062,10 +1115,10 @@ async def run_single_prompt(
|
|
|
1062
1115
|
|
|
1063
1116
|
console.print("\nOptions:", style="bold")
|
|
1064
1117
|
console.print(" 1. Use --local flag to access local models:", style="dim")
|
|
1065
|
-
console.print(" ducky --local", style="
|
|
1118
|
+
console.print(" ducky --local", style="white")
|
|
1066
1119
|
console.print(" 2. Select a local model with /local command", style="dim")
|
|
1067
1120
|
console.print(" 3. Set up Ollama cloud API credentials:", style="dim")
|
|
1068
|
-
console.print(" export OLLAMA_API_KEY='your-api-key-here'", style="
|
|
1121
|
+
console.print(" export OLLAMA_API_KEY='your-api-key-here'", style="white")
|
|
1069
1122
|
console.print("\nGet your API key from: https://ollama.com/account/api-keys", style="dim")
|
|
1070
1123
|
console.print()
|
|
1071
1124
|
raise
|
|
@@ -1073,12 +1126,12 @@ async def run_single_prompt(
|
|
|
1073
1126
|
raise
|
|
1074
1127
|
|
|
1075
1128
|
content = result.content or "(No content returned.)"
|
|
1076
|
-
console.print(content, style="
|
|
1129
|
+
console.print(content, style="dim", highlight=False)
|
|
1077
1130
|
if logger:
|
|
1078
1131
|
logger.log_assistant(content, result.command)
|
|
1079
1132
|
if result.command and not suppress_suggestion:
|
|
1080
|
-
console.print("\nSuggested command:", style="
|
|
1081
|
-
console.print(result.command, style="bold
|
|
1133
|
+
console.print("\nSuggested command:", style="yellow", highlight=False)
|
|
1134
|
+
console.print(result.command, style="bold yellow", highlight=False)
|
|
1082
1135
|
return result
|
|
1083
1136
|
|
|
1084
1137
|
|
|
@@ -1108,8 +1161,9 @@ async def interactive_session(
|
|
|
1108
1161
|
rubber_ducky: RubberDuck,
|
|
1109
1162
|
logger: ConversationLogger | None = None,
|
|
1110
1163
|
code: str | None = None,
|
|
1164
|
+
quiet_mode: bool = False,
|
|
1111
1165
|
) -> None:
|
|
1112
|
-
ui = InlineInterface(rubber_ducky, logger=logger, code=code)
|
|
1166
|
+
ui = InlineInterface(rubber_ducky, logger=logger, code=code, quiet_mode=quiet_mode)
|
|
1113
1167
|
await ui.run()
|
|
1114
1168
|
|
|
1115
1169
|
|
|
@@ -1134,6 +1188,12 @@ async def ducky() -> None:
|
|
|
1134
1188
|
action="store_true",
|
|
1135
1189
|
help=" Automatically run the suggested command without confirmation",
|
|
1136
1190
|
)
|
|
1191
|
+
parser.add_argument(
|
|
1192
|
+
"--quiet",
|
|
1193
|
+
"-q",
|
|
1194
|
+
action="store_true",
|
|
1195
|
+
help="Suppress startup messages and help text",
|
|
1196
|
+
)
|
|
1137
1197
|
parser.add_argument(
|
|
1138
1198
|
"single_prompt",
|
|
1139
1199
|
nargs="*",
|
|
@@ -1193,8 +1253,31 @@ async def ducky() -> None:
|
|
|
1193
1253
|
console.print("No input received from stdin.", style="yellow")
|
|
1194
1254
|
return
|
|
1195
1255
|
|
|
1196
|
-
# Handle crumb
|
|
1256
|
+
# Handle crumb list command
|
|
1197
1257
|
crumb_manager = CrumbManager()
|
|
1258
|
+
if args.single_prompt and args.single_prompt[0] == "crumbs":
|
|
1259
|
+
crumbs = crumb_manager.list_crumbs()
|
|
1260
|
+
|
|
1261
|
+
if not crumbs:
|
|
1262
|
+
console.print("No crumbs saved yet.", style="yellow")
|
|
1263
|
+
else:
|
|
1264
|
+
console.print("Saved Crumbs", style="bold white")
|
|
1265
|
+
console.print("=============", style="bold white")
|
|
1266
|
+
console.print()
|
|
1267
|
+
|
|
1268
|
+
max_name_len = max(len(name) for name in crumbs.keys())
|
|
1269
|
+
|
|
1270
|
+
for name, data in sorted(crumbs.items()):
|
|
1271
|
+
explanation = data.get("explanation", "") or "No explanation yet"
|
|
1272
|
+
command = data.get("command", "") or "No command"
|
|
1273
|
+
|
|
1274
|
+
console.print(
|
|
1275
|
+
f"[bold yellow]{name:<{max_name_len}}[/bold yellow] | [white]{explanation}[/white] | [dim]{command}[/dim]"
|
|
1276
|
+
)
|
|
1277
|
+
console.print(f"\n[dim]Total: {len(crumbs)} crumbs[/dim]")
|
|
1278
|
+
return
|
|
1279
|
+
|
|
1280
|
+
# Handle crumb invocation mode
|
|
1198
1281
|
if args.single_prompt:
|
|
1199
1282
|
first_arg = args.single_prompt[0]
|
|
1200
1283
|
if crumb_manager.has_crumb(first_arg):
|
|
@@ -1210,9 +1293,13 @@ async def ducky() -> None:
|
|
|
1210
1293
|
if crumb_args and command != "No command":
|
|
1211
1294
|
command = substitute_placeholders(command, crumb_args)
|
|
1212
1295
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1296
|
+
from rich.text import Text
|
|
1297
|
+
crumb_text = Text()
|
|
1298
|
+
crumb_text.append("Crumb: ", style="bold yellow")
|
|
1299
|
+
crumb_text.append(first_arg, style="bold yellow")
|
|
1300
|
+
console.print(f"\n{crumb_text}")
|
|
1301
|
+
console.print(f"Explanation: {explanation}", style="yellow")
|
|
1302
|
+
console.print("Command: ", style="white", end="")
|
|
1216
1303
|
console.print(command, highlight=False)
|
|
1217
1304
|
|
|
1218
1305
|
if command and command != "No command":
|
|
@@ -1243,7 +1330,19 @@ async def ducky() -> None:
|
|
|
1243
1330
|
console.print("\n[green]✓[/green] Command copied to clipboard")
|
|
1244
1331
|
return
|
|
1245
1332
|
|
|
1246
|
-
|
|
1333
|
+
# Validate model is available if using local
|
|
1334
|
+
if not args.single_prompt and not piped_prompt and last_host == "http://localhost:11434":
|
|
1335
|
+
connected = True
|
|
1336
|
+
try:
|
|
1337
|
+
models = await rubber_ducky.list_models()
|
|
1338
|
+
if args.model not in models:
|
|
1339
|
+
console.print(f"Model '{args.model}' not found locally.", style="yellow")
|
|
1340
|
+
console.print(f"Available: {', '.join(models[:5])}...", style="dim")
|
|
1341
|
+
console.print("Use /model to select, or run 'ollama pull <model>'", style="yellow")
|
|
1342
|
+
except Exception:
|
|
1343
|
+
pass
|
|
1344
|
+
|
|
1345
|
+
await interactive_session(rubber_ducky, logger=logger, code=code, quiet_mode=args.quiet)
|
|
1247
1346
|
|
|
1248
1347
|
|
|
1249
1348
|
def substitute_placeholders(command: str, args: list[str]) -> str:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|