rubber-ducky 1.2.0__py3-none-any.whl → 1.2.1__py3-none-any.whl
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.
- ducky/ducky.py +66 -5
- {rubber_ducky-1.2.0.dist-info → rubber_ducky-1.2.1.dist-info}/METADATA +3 -3
- rubber_ducky-1.2.1.dist-info/RECORD +8 -0
- rubber_ducky-1.2.0.dist-info/RECORD +0 -8
- {rubber_ducky-1.2.0.dist-info → rubber_ducky-1.2.1.dist-info}/WHEEL +0 -0
- {rubber_ducky-1.2.0.dist-info → rubber_ducky-1.2.1.dist-info}/entry_points.txt +0 -0
- {rubber_ducky-1.2.0.dist-info → rubber_ducky-1.2.1.dist-info}/licenses/LICENSE +0 -0
- {rubber_ducky-1.2.0.dist-info → rubber_ducky-1.2.1.dist-info}/top_level.txt +0 -0
ducky/ducky.py
CHANGED
|
@@ -25,6 +25,8 @@ except ImportError: # pragma: no cover - fallback mode
|
|
|
25
25
|
|
|
26
26
|
def patch_stdout() -> nullcontext:
|
|
27
27
|
return nullcontext()
|
|
28
|
+
|
|
29
|
+
|
|
28
30
|
from rich.console import Console
|
|
29
31
|
|
|
30
32
|
|
|
@@ -110,6 +112,7 @@ async def run_shell_and_print(
|
|
|
110
112
|
assistant: RubberDuck,
|
|
111
113
|
command: str,
|
|
112
114
|
logger: ConversationLogger | None = None,
|
|
115
|
+
history: list[dict[str, str]] | None = None,
|
|
113
116
|
) -> None:
|
|
114
117
|
if not command:
|
|
115
118
|
console.print("No command provided.", style="yellow")
|
|
@@ -119,6 +122,18 @@ async def run_shell_and_print(
|
|
|
119
122
|
print_shell_result(result)
|
|
120
123
|
if logger:
|
|
121
124
|
logger.log_shell(result)
|
|
125
|
+
if history is not None:
|
|
126
|
+
history.append({"role": "user", "content": f"!{command}"})
|
|
127
|
+
combined_output: list[str] = []
|
|
128
|
+
if result.stdout.strip():
|
|
129
|
+
combined_output.append(result.stdout.rstrip())
|
|
130
|
+
if result.stderr.strip():
|
|
131
|
+
combined_output.append(f"[stderr]\n{result.stderr.rstrip()}")
|
|
132
|
+
if result.returncode != 0:
|
|
133
|
+
combined_output.append(f"(exit status {result.returncode})")
|
|
134
|
+
if not combined_output:
|
|
135
|
+
combined_output.append("(command produced no output)")
|
|
136
|
+
history.append({"role": "assistant", "content": "\n\n".join(combined_output)})
|
|
122
137
|
|
|
123
138
|
|
|
124
139
|
class RubberDuck:
|
|
@@ -255,6 +270,8 @@ class InlineInterface:
|
|
|
255
270
|
self.last_command: str | None = None
|
|
256
271
|
self.code = code
|
|
257
272
|
self._code_sent = False
|
|
273
|
+
self.last_shell_output: str | None = None
|
|
274
|
+
self.pending_command: str | None = None
|
|
258
275
|
self.session: PromptSession | None = None
|
|
259
276
|
|
|
260
277
|
if (
|
|
@@ -300,7 +317,7 @@ class InlineInterface:
|
|
|
300
317
|
return
|
|
301
318
|
|
|
302
319
|
console.print(
|
|
303
|
-
"Enter submits •
|
|
320
|
+
"Enter submits • empty Enter reruns the last suggested command (or explains the last shell output) • '!cmd' runs shell • Ctrl+D exits",
|
|
304
321
|
style="dim",
|
|
305
322
|
)
|
|
306
323
|
while True:
|
|
@@ -326,11 +343,26 @@ class InlineInterface:
|
|
|
326
343
|
if not self.last_command:
|
|
327
344
|
console.print("No suggested command available yet.", style="yellow")
|
|
328
345
|
return
|
|
329
|
-
await run_shell_and_print(
|
|
346
|
+
await run_shell_and_print(
|
|
347
|
+
self.assistant,
|
|
348
|
+
self.last_command,
|
|
349
|
+
logger=self.logger,
|
|
350
|
+
history=self.assistant.messages,
|
|
351
|
+
)
|
|
352
|
+
self.last_shell_output = True
|
|
353
|
+
self.pending_command = None
|
|
354
|
+
self.last_command = None
|
|
330
355
|
|
|
331
356
|
async def _process_text(self, text: str) -> None:
|
|
332
357
|
stripped = text.strip()
|
|
333
358
|
if not stripped:
|
|
359
|
+
if self.pending_command:
|
|
360
|
+
await self._run_last_command()
|
|
361
|
+
return
|
|
362
|
+
if self.last_shell_output:
|
|
363
|
+
await self._explain_last_command()
|
|
364
|
+
return
|
|
365
|
+
console.print("Nothing to run yet.", style="yellow")
|
|
334
366
|
return
|
|
335
367
|
|
|
336
368
|
if stripped.lower() in {":run", "/run"}:
|
|
@@ -339,8 +371,13 @@ class InlineInterface:
|
|
|
339
371
|
|
|
340
372
|
if stripped.startswith("!"):
|
|
341
373
|
await run_shell_and_print(
|
|
342
|
-
self.assistant,
|
|
374
|
+
self.assistant,
|
|
375
|
+
stripped[1:].strip(),
|
|
376
|
+
logger=self.logger,
|
|
377
|
+
history=self.assistant.messages,
|
|
343
378
|
)
|
|
379
|
+
self.last_shell_output = True
|
|
380
|
+
self.pending_command = None
|
|
344
381
|
return
|
|
345
382
|
|
|
346
383
|
result = await run_single_prompt(
|
|
@@ -352,6 +389,26 @@ class InlineInterface:
|
|
|
352
389
|
if self.code and not self._code_sent:
|
|
353
390
|
self._code_sent = True
|
|
354
391
|
self.last_command = result.command
|
|
392
|
+
self.pending_command = result.command
|
|
393
|
+
self.last_shell_output = None
|
|
394
|
+
|
|
395
|
+
async def _explain_last_command(self) -> None:
|
|
396
|
+
if not self.assistant.messages or len(self.assistant.messages) < 2:
|
|
397
|
+
console.print("No shell output to explain yet.", style="yellow")
|
|
398
|
+
return
|
|
399
|
+
last_entry = self.assistant.messages[-1]
|
|
400
|
+
if last_entry["role"] != "assistant":
|
|
401
|
+
console.print("No shell output to explain yet.", style="yellow")
|
|
402
|
+
return
|
|
403
|
+
prompt = (
|
|
404
|
+
"The user ran a shell command above. Summarize the key findings from the output, "
|
|
405
|
+
"highlight problems if any, and suggest next steps. Do NOT suggest a shell command or code snippet.\n\n"
|
|
406
|
+
f"{last_entry['content']}"
|
|
407
|
+
)
|
|
408
|
+
await run_single_prompt(
|
|
409
|
+
self.assistant, prompt, logger=self.logger, suppress_suggestion=True
|
|
410
|
+
)
|
|
411
|
+
self.last_shell_output = None
|
|
355
412
|
|
|
356
413
|
async def _run_basic_loop(self) -> None: # pragma: no cover - fallback path
|
|
357
414
|
while True:
|
|
@@ -388,6 +445,7 @@ async def run_single_prompt(
|
|
|
388
445
|
prompt: str,
|
|
389
446
|
code: str | None = None,
|
|
390
447
|
logger: ConversationLogger | None = None,
|
|
448
|
+
suppress_suggestion: bool = False,
|
|
391
449
|
) -> AssistantResult:
|
|
392
450
|
if logger:
|
|
393
451
|
logger.log_user(prompt)
|
|
@@ -396,7 +454,7 @@ async def run_single_prompt(
|
|
|
396
454
|
console.print(content, style="green", highlight=False)
|
|
397
455
|
if logger:
|
|
398
456
|
logger.log_assistant(content, result.command)
|
|
399
|
-
if result.command:
|
|
457
|
+
if result.command and not suppress_suggestion:
|
|
400
458
|
console.print("\nSuggested command:", style="cyan", highlight=False)
|
|
401
459
|
console.print(result.command, style="bold cyan", highlight=False)
|
|
402
460
|
return result
|
|
@@ -455,7 +513,10 @@ async def ducky() -> None:
|
|
|
455
513
|
and confirm("Run suggested command?")
|
|
456
514
|
):
|
|
457
515
|
await run_shell_and_print(
|
|
458
|
-
rubber_ducky,
|
|
516
|
+
rubber_ducky,
|
|
517
|
+
result.command,
|
|
518
|
+
logger=logger,
|
|
519
|
+
history=rubber_ducky.messages,
|
|
459
520
|
)
|
|
460
521
|
else:
|
|
461
522
|
console.print("No input received from stdin.", style="yellow")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rubber-ducky
|
|
3
|
-
Version: 1.2.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 1.2.1
|
|
4
|
+
Summary: Quick CLI do-it-all tool. Use natural language to spit out bash commands
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
License-File: LICENSE
|
|
@@ -43,7 +43,7 @@ Both `ducky` and `rubber-ducky` executables map to the same CLI, so `uvx rubber-
|
|
|
43
43
|
### Inline Session (default)
|
|
44
44
|
|
|
45
45
|
Launching `ducky` with no arguments opens the inline interface:
|
|
46
|
-
- **Enter** submits; **Ctrl+J** inserts a newline (helpful when crafting multi-line prompts).
|
|
46
|
+
- **Enter** submits; **Ctrl+J** inserts a newline (helpful when crafting multi-line prompts). Hitting **Enter on an empty prompt** reruns the latest suggested command; if none exists yet, it explains the most recent shell output.
|
|
47
47
|
- **Ctrl+R** re-runs the last suggested command.
|
|
48
48
|
- Prefix any line with **`!`** (e.g., `!ls -la`) to run a shell command immediately.
|
|
49
49
|
- Arrow keys browse prompt history, backed by `~/.ducky/prompt_history`.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
ducky/__init__.py,sha256=9l8SmwX0t1BmITkcrzW9fVMPvD2LfgKLZlSXWzPJFSE,25
|
|
2
|
+
ducky/ducky.py,sha256=FWGkAnyWB8k6GxsAu5WkIxJ5mlnT9ymIAJsJf8ryTts,17347
|
|
3
|
+
rubber_ducky-1.2.1.dist-info/licenses/LICENSE,sha256=gQ1rCmw18NqTk5GxG96F6vgyN70e1c4kcKUtWDwdNaE,1069
|
|
4
|
+
rubber_ducky-1.2.1.dist-info/METADATA,sha256=MDt4yR-GtzqF4bB-j8s4kXt3tNUDYJ6H_7Mr6mLUEu0,3063
|
|
5
|
+
rubber_ducky-1.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
6
|
+
rubber_ducky-1.2.1.dist-info/entry_points.txt,sha256=WPnVUUNvWdMDcBlCo8JCzkLghGllMX5QVZyQghyq85Q,75
|
|
7
|
+
rubber_ducky-1.2.1.dist-info/top_level.txt,sha256=4Q75MONDNPpQ3o17bTu7RFuKwFhTIRzlXP3_LDWQQ30,6
|
|
8
|
+
rubber_ducky-1.2.1.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
ducky/__init__.py,sha256=9l8SmwX0t1BmITkcrzW9fVMPvD2LfgKLZlSXWzPJFSE,25
|
|
2
|
-
ducky/ducky.py,sha256=9xTfSsox4Fcvrz1C90rp0CN0vSc-bEZPB-I50PvCXMM,14792
|
|
3
|
-
rubber_ducky-1.2.0.dist-info/licenses/LICENSE,sha256=gQ1rCmw18NqTk5GxG96F6vgyN70e1c4kcKUtWDwdNaE,1069
|
|
4
|
-
rubber_ducky-1.2.0.dist-info/METADATA,sha256=pQ6UzHKoaaDBq6pP51yBmMRBiyYEUGWZeu5Vj2hhmvQ,2915
|
|
5
|
-
rubber_ducky-1.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
6
|
-
rubber_ducky-1.2.0.dist-info/entry_points.txt,sha256=WPnVUUNvWdMDcBlCo8JCzkLghGllMX5QVZyQghyq85Q,75
|
|
7
|
-
rubber_ducky-1.2.0.dist-info/top_level.txt,sha256=4Q75MONDNPpQ3o17bTu7RFuKwFhTIRzlXP3_LDWQQ30,6
|
|
8
|
-
rubber_ducky-1.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|