deepagent-code 0.2.0__py3-none-any.whl → 0.3.0__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.
- deepagent_code/cli.py +165 -109
- deepagent_code/config.py +1 -0
- {deepagent_code-0.2.0.dist-info → deepagent_code-0.3.0.dist-info}/METADATA +30 -4
- deepagent_code-0.3.0.dist-info/RECORD +9 -0
- deepagent_code-0.2.0.dist-info/RECORD +0 -9
- {deepagent_code-0.2.0.dist-info → deepagent_code-0.3.0.dist-info}/WHEEL +0 -0
- {deepagent_code-0.2.0.dist-info → deepagent_code-0.3.0.dist-info}/entry_points.txt +0 -0
- {deepagent_code-0.2.0.dist-info → deepagent_code-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {deepagent_code-0.2.0.dist-info → deepagent_code-0.3.0.dist-info}/top_level.txt +0 -0
deepagent_code/cli.py
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
CLI for running arbitrary LangGraph agents from the terminal.
|
|
3
3
|
Styled after Claude Code / nanocode.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
import asyncio
|
|
6
|
-
import importlib.util
|
|
7
7
|
import json
|
|
8
8
|
import os
|
|
9
9
|
import re
|
|
@@ -15,6 +15,16 @@ import uuid
|
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
from typing import Any, Dict, List, Optional, Tuple
|
|
17
17
|
|
|
18
|
+
import click
|
|
19
|
+
|
|
20
|
+
from langgraph_stream_parser import (
|
|
21
|
+
prepare_agent_input,
|
|
22
|
+
stream_graph_updates,
|
|
23
|
+
astream_graph_updates,
|
|
24
|
+
load_agent_spec,
|
|
25
|
+
)
|
|
26
|
+
from deepagent_code import config as config_module
|
|
27
|
+
|
|
18
28
|
# Platform-specific imports for keyboard input
|
|
19
29
|
IS_WINDOWS = sys.platform == "win32"
|
|
20
30
|
if IS_WINDOWS:
|
|
@@ -23,23 +33,14 @@ else:
|
|
|
23
33
|
import termios
|
|
24
34
|
import tty
|
|
25
35
|
|
|
26
|
-
import click
|
|
27
|
-
|
|
28
36
|
# Try to import readline for tab completion (not available on all platforms)
|
|
29
37
|
try:
|
|
30
38
|
import readline
|
|
39
|
+
|
|
31
40
|
HAS_READLINE = True
|
|
32
41
|
except ImportError:
|
|
33
42
|
HAS_READLINE = False
|
|
34
43
|
|
|
35
|
-
from langgraph_stream_parser import (
|
|
36
|
-
prepare_agent_input,
|
|
37
|
-
stream_graph_updates,
|
|
38
|
-
astream_graph_updates,
|
|
39
|
-
load_agent_spec,
|
|
40
|
-
)
|
|
41
|
-
from deepagent_code import config as config_module
|
|
42
|
-
|
|
43
44
|
|
|
44
45
|
# ANSI color codes (matching nanocode style)
|
|
45
46
|
RESET, BOLD, DIM = "\033[0m", "\033[1m", "\033[2m"
|
|
@@ -154,6 +155,7 @@ def register_command(
|
|
|
154
155
|
usage: Optional[str] = None,
|
|
155
156
|
):
|
|
156
157
|
"""Decorator to register a slash command handler."""
|
|
158
|
+
|
|
157
159
|
def decorator(func):
|
|
158
160
|
command = SlashCommand(
|
|
159
161
|
name=name,
|
|
@@ -164,6 +166,7 @@ def register_command(
|
|
|
164
166
|
)
|
|
165
167
|
command_registry.register(command)
|
|
166
168
|
return func
|
|
169
|
+
|
|
167
170
|
return decorator
|
|
168
171
|
|
|
169
172
|
|
|
@@ -183,7 +186,11 @@ class Spinner:
|
|
|
183
186
|
frame = SPINNER_FRAMES[self.frame_idx % len(SPINNER_FRAMES)]
|
|
184
187
|
elapsed = time.time() - self.start_time
|
|
185
188
|
elapsed_str = f"{int(elapsed)}s"
|
|
186
|
-
print(
|
|
189
|
+
print(
|
|
190
|
+
f"\r{CYAN}{frame}{RESET} {DIM}{self.message}... {elapsed_str}{RESET}",
|
|
191
|
+
end="",
|
|
192
|
+
flush=True,
|
|
193
|
+
)
|
|
187
194
|
self.frame_idx += 1
|
|
188
195
|
time.sleep(0.08)
|
|
189
196
|
|
|
@@ -246,13 +253,13 @@ def print_goodbye():
|
|
|
246
253
|
def get_agent_name(graph) -> str:
|
|
247
254
|
"""Extract agent name from graph object, defaulting to 'AgentCode'."""
|
|
248
255
|
# Try common attribute names for agent/graph name
|
|
249
|
-
for attr in (
|
|
256
|
+
for attr in ("name", "agent_name", "_name", "__name__"):
|
|
250
257
|
if hasattr(graph, attr):
|
|
251
258
|
name = getattr(graph, attr)
|
|
252
259
|
if name and isinstance(name, str):
|
|
253
260
|
return name
|
|
254
261
|
# Check if it's a compiled graph with a name in builder
|
|
255
|
-
if hasattr(graph,
|
|
262
|
+
if hasattr(graph, "builder") and hasattr(graph.builder, "name"):
|
|
256
263
|
name = graph.builder.name
|
|
257
264
|
if name and isinstance(name, str):
|
|
258
265
|
return name
|
|
@@ -262,13 +269,13 @@ def get_agent_name(graph) -> str:
|
|
|
262
269
|
def get_agent_description(graph) -> Optional[str]:
|
|
263
270
|
"""Extract agent description from graph object, if available."""
|
|
264
271
|
# Try common attribute names for agent description
|
|
265
|
-
for attr in (
|
|
272
|
+
for attr in ("description", "agent_description", "_description", "__doc__"):
|
|
266
273
|
if hasattr(graph, attr):
|
|
267
274
|
desc = getattr(graph, attr)
|
|
268
275
|
if desc and isinstance(desc, str) and desc.strip():
|
|
269
276
|
return desc.strip()
|
|
270
277
|
# Check if it's a compiled graph with a description in builder
|
|
271
|
-
if hasattr(graph,
|
|
278
|
+
if hasattr(graph, "builder") and hasattr(graph.builder, "description"):
|
|
272
279
|
desc = graph.builder.description
|
|
273
280
|
if desc and isinstance(desc, str) and desc.strip():
|
|
274
281
|
return desc.strip()
|
|
@@ -283,59 +290,59 @@ def text_to_ascii_art(text: str) -> List[str]:
|
|
|
283
290
|
"""
|
|
284
291
|
# Clean 3-line block font - each char is exactly 3 wide
|
|
285
292
|
FONT = {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
293
|
+
"A": ["▄▀▄", "█▀█", "▀ ▀"],
|
|
294
|
+
"B": ["█▀▄", "█▀▄", "▀▀▀"],
|
|
295
|
+
"C": ["▄▀▀", "█ ", "▀▀▀"],
|
|
296
|
+
"D": ["█▀▄", "█ █", "▀▀▀"],
|
|
297
|
+
"E": ["█▀▀", "█▀▀", "▀▀▀"],
|
|
298
|
+
"F": ["█▀▀", "█▀▀", "▀ "],
|
|
299
|
+
"G": ["▄▀▀", "█▀█", "▀▀▀"],
|
|
300
|
+
"H": ["█ █", "█▀█", "▀ ▀"],
|
|
301
|
+
"I": ["▀█▀", " █ ", "▀▀▀"],
|
|
302
|
+
"J": ["▀▀█", " █", "▀▀▀"],
|
|
303
|
+
"K": ["█ █", "█▀▄", "▀ ▀"],
|
|
304
|
+
"L": ["█ ", "█ ", "▀▀▀"],
|
|
305
|
+
"M": ["█▄█", "█ █", "▀ ▀"],
|
|
306
|
+
"N": ["█▀█", "█ █", "▀ ▀"],
|
|
307
|
+
"O": ["▄▀▄", "█ █", "▀▀▀"],
|
|
308
|
+
"P": ["█▀▄", "█▀▀", "▀ "],
|
|
309
|
+
"Q": ["▄▀▄", "█ █", "▀▀█"],
|
|
310
|
+
"R": ["█▀▄", "█▀▄", "▀ ▀"],
|
|
311
|
+
"S": ["▄▀▀", "▀▀▄", "▀▀▀"],
|
|
312
|
+
"T": ["▀█▀", " █ ", " ▀ "],
|
|
313
|
+
"U": ["█ █", "█ █", "▀▀▀"],
|
|
314
|
+
"V": ["█ █", "█ █", " ▀ "],
|
|
315
|
+
"W": ["█ █", "█▀█", "▀ ▀"],
|
|
316
|
+
"X": ["▀▄▀", " █ ", "▀ ▀"],
|
|
317
|
+
"Y": ["█ █", " █ ", " ▀ "],
|
|
318
|
+
"Z": ["▀▀█", " █ ", "█▀▀"],
|
|
319
|
+
"0": ["▄▀▄", "█ █", "▀▀▀"],
|
|
320
|
+
"1": ["▄█ ", " █ ", "▀▀▀"],
|
|
321
|
+
"2": ["▀▀█", "▄▀▀", "▀▀▀"],
|
|
322
|
+
"3": ["▀▀█", " ▀█", "▀▀▀"],
|
|
323
|
+
"4": ["█ █", "▀▀█", " ▀"],
|
|
324
|
+
"5": ["█▀▀", "▀▀▄", "▀▀▀"],
|
|
325
|
+
"6": ["▄▀▀", "█▀█", "▀▀▀"],
|
|
326
|
+
"7": ["▀▀█", " █", " ▀"],
|
|
327
|
+
"8": ["▄▀▄", "█▀█", "▀▀▀"],
|
|
328
|
+
"9": ["▄▀█", "▀▀█", "▀▀▀"],
|
|
329
|
+
" ": [" ", " ", " "],
|
|
330
|
+
"-": [" ", "▀▀▀", " "],
|
|
331
|
+
"_": [" ", " ", "▀▀▀"],
|
|
332
|
+
".": [" ", " ", " ▀ "],
|
|
326
333
|
}
|
|
327
334
|
|
|
328
335
|
# Default char for unknown characters
|
|
329
|
-
DEFAULT = [
|
|
336
|
+
DEFAULT = [" ", " █ ", " "]
|
|
330
337
|
|
|
331
|
-
lines = [
|
|
338
|
+
lines = ["", "", ""]
|
|
332
339
|
for char in text.upper():
|
|
333
340
|
char_art = FONT.get(char, DEFAULT)
|
|
334
341
|
for i in range(3):
|
|
335
|
-
lines[i] += char_art[i] +
|
|
342
|
+
lines[i] += char_art[i] + " "
|
|
336
343
|
|
|
337
344
|
# Remove only the final trailing space we added (not internal spaces from chars like T, P)
|
|
338
|
-
return [line[:-1] if line.endswith(
|
|
345
|
+
return [line[:-1] if line.endswith(" ") else line for line in lines]
|
|
339
346
|
|
|
340
347
|
|
|
341
348
|
def print_header_box(agent_name: str, cwd: str, description: Optional[str] = None):
|
|
@@ -359,7 +366,7 @@ def print_header_box(agent_name: str, cwd: str, description: Optional[str] = Non
|
|
|
359
366
|
# Build cwd line with label
|
|
360
367
|
cwd_label = "cwd: "
|
|
361
368
|
max_cwd_len = inner_width - len(cwd_label)
|
|
362
|
-
cwd_display = cwd if len(cwd) <= max_cwd_len else "..." + cwd[-(max_cwd_len - 3):]
|
|
369
|
+
cwd_display = cwd if len(cwd) <= max_cwd_len else "..." + cwd[-(max_cwd_len - 3) :]
|
|
363
370
|
cwd_with_label = f"{cwd_label}{cwd_display}"
|
|
364
371
|
cwd_line = cwd_with_label.center(inner_width)
|
|
365
372
|
|
|
@@ -371,16 +378,24 @@ def print_header_box(agent_name: str, cwd: str, description: Optional[str] = Non
|
|
|
371
378
|
# Print ASCII art lines centered
|
|
372
379
|
for line in ascii_lines:
|
|
373
380
|
centered_line = line.center(inner_width)
|
|
374
|
-
print(
|
|
381
|
+
print(
|
|
382
|
+
f"{BRIGHT_CYAN}{V}{RESET} {BOLD}{BRIGHT_CYAN}{centered_line}{RESET} {BRIGHT_CYAN}{V}{RESET}"
|
|
383
|
+
)
|
|
375
384
|
else:
|
|
376
385
|
# Fall back to plain text if ASCII art doesn't fit
|
|
377
386
|
title_line = agent_name.center(inner_width)
|
|
378
|
-
print(
|
|
387
|
+
print(
|
|
388
|
+
f"{BRIGHT_CYAN}{V}{RESET} {BOLD}{BRIGHT_CYAN}{title_line}{RESET} {BRIGHT_CYAN}{V}{RESET}"
|
|
389
|
+
)
|
|
379
390
|
|
|
380
391
|
# Print description line if available
|
|
381
392
|
if description:
|
|
382
393
|
# Truncate description if too long
|
|
383
|
-
desc_display =
|
|
394
|
+
desc_display = (
|
|
395
|
+
description
|
|
396
|
+
if len(description) <= inner_width
|
|
397
|
+
else description[: inner_width - 3] + "..."
|
|
398
|
+
)
|
|
384
399
|
desc_line = desc_display.center(inner_width)
|
|
385
400
|
print(f"{CYAN}{V}{RESET} {DIM}{ITALIC}{desc_line}{RESET} {CYAN}{V}{RESET}")
|
|
386
401
|
|
|
@@ -417,17 +432,17 @@ def parse_agent_spec(agent_spec: str) -> Tuple[str, str]:
|
|
|
417
432
|
Raises:
|
|
418
433
|
ValueError: If format is invalid
|
|
419
434
|
"""
|
|
420
|
-
if
|
|
435
|
+
if ":" not in agent_spec:
|
|
421
436
|
raise ValueError(
|
|
422
437
|
f"Invalid agent spec format: '{agent_spec}'. "
|
|
423
438
|
f"Expected format: 'path/to/file.py:variable_name'"
|
|
424
439
|
)
|
|
425
440
|
|
|
426
|
-
parts = agent_spec.rsplit(
|
|
441
|
+
parts = agent_spec.rsplit(":", 1)
|
|
427
442
|
file_path = parts[0]
|
|
428
443
|
variable_name = parts[1]
|
|
429
444
|
|
|
430
|
-
if not file_path.endswith(
|
|
445
|
+
if not file_path.endswith(".py"):
|
|
431
446
|
raise ValueError(f"Agent spec file must be a .py file: {file_path}")
|
|
432
447
|
|
|
433
448
|
return file_path, variable_name
|
|
@@ -457,12 +472,12 @@ def load_graph(spec: str, default_graph_name: str = "graph"):
|
|
|
457
472
|
"""
|
|
458
473
|
path_or_module = spec
|
|
459
474
|
graph_name = default_graph_name
|
|
460
|
-
if
|
|
461
|
-
head, _, tail = spec.rpartition(
|
|
475
|
+
if ":" in spec:
|
|
476
|
+
head, _, tail = spec.rpartition(":")
|
|
462
477
|
# Only treat the trailing ':token' as a graph name if it looks like one
|
|
463
478
|
# — i.e. it has no path separators. This avoids mistaking a Windows
|
|
464
479
|
# drive-letter colon (e.g. 'C:\path\agent.py') for a name suffix.
|
|
465
|
-
if tail and
|
|
480
|
+
if tail and "/" not in tail and "\\" not in tail:
|
|
466
481
|
path_or_module = head
|
|
467
482
|
graph_name = tail or default_graph_name
|
|
468
483
|
|
|
@@ -561,8 +576,8 @@ def print_chunk(chunk: Dict[str, Any], verbose: bool = False):
|
|
|
561
576
|
print(f"\n{YELLOW}⚠ Action Required{RESET}")
|
|
562
577
|
if action_requests:
|
|
563
578
|
for i, action in enumerate(action_requests):
|
|
564
|
-
tool = action.get(
|
|
565
|
-
args_preview = get_tool_arg_preview(action.get(
|
|
579
|
+
tool = action.get("tool", "unknown")
|
|
580
|
+
args_preview = get_tool_arg_preview(action.get("args", {}))
|
|
566
581
|
print(f" {DIM}{i + 1}. {tool}{RESET}")
|
|
567
582
|
if args_preview:
|
|
568
583
|
print(f" {DIM}└─ {args_preview}{RESET}")
|
|
@@ -580,18 +595,18 @@ def get_key() -> str:
|
|
|
580
595
|
if IS_WINDOWS:
|
|
581
596
|
# Windows implementation using msvcrt
|
|
582
597
|
ch = msvcrt.getch()
|
|
583
|
-
if ch in (b
|
|
598
|
+
if ch in (b"\x00", b"\xe0"): # Special keys (arrows, function keys)
|
|
584
599
|
ch2 = msvcrt.getch()
|
|
585
|
-
if ch2 == b
|
|
586
|
-
return
|
|
587
|
-
elif ch2 == b
|
|
588
|
-
return
|
|
589
|
-
return ch2.decode(
|
|
590
|
-
elif ch == b
|
|
591
|
-
return
|
|
592
|
-
elif ch == b
|
|
593
|
-
return
|
|
594
|
-
return ch.decode(
|
|
600
|
+
if ch2 == b"H":
|
|
601
|
+
return "up"
|
|
602
|
+
elif ch2 == b"P":
|
|
603
|
+
return "down"
|
|
604
|
+
return ch2.decode("utf-8", errors="ignore")
|
|
605
|
+
elif ch == b"\r":
|
|
606
|
+
return "enter"
|
|
607
|
+
elif ch == b"\x03": # Ctrl+C
|
|
608
|
+
return "ctrl-c"
|
|
609
|
+
return ch.decode("utf-8", errors="ignore")
|
|
595
610
|
else:
|
|
596
611
|
# Unix implementation using termios/tty
|
|
597
612
|
fd = sys.stdin.fileno()
|
|
@@ -600,18 +615,18 @@ def get_key() -> str:
|
|
|
600
615
|
tty.setraw(fd)
|
|
601
616
|
ch = sys.stdin.read(1)
|
|
602
617
|
# Handle escape sequences (arrow keys)
|
|
603
|
-
if ch ==
|
|
618
|
+
if ch == "\x1b":
|
|
604
619
|
ch2 = sys.stdin.read(1)
|
|
605
|
-
if ch2 ==
|
|
620
|
+
if ch2 == "[":
|
|
606
621
|
ch3 = sys.stdin.read(1)
|
|
607
|
-
if ch3 ==
|
|
608
|
-
return
|
|
609
|
-
elif ch3 ==
|
|
610
|
-
return
|
|
611
|
-
elif ch ==
|
|
612
|
-
return
|
|
613
|
-
elif ch ==
|
|
614
|
-
return
|
|
622
|
+
if ch3 == "A":
|
|
623
|
+
return "up"
|
|
624
|
+
elif ch3 == "B":
|
|
625
|
+
return "down"
|
|
626
|
+
elif ch == "\r" or ch == "\n":
|
|
627
|
+
return "enter"
|
|
628
|
+
elif ch == "\x03": # Ctrl+C
|
|
629
|
+
return "ctrl-c"
|
|
615
630
|
return ch
|
|
616
631
|
finally:
|
|
617
632
|
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
@@ -647,13 +662,13 @@ def select_option(options: List[str], prompt: str = "Select an option:") -> int:
|
|
|
647
662
|
while True:
|
|
648
663
|
key = get_key()
|
|
649
664
|
|
|
650
|
-
if key ==
|
|
665
|
+
if key == "up" and selected > 0:
|
|
651
666
|
selected -= 1
|
|
652
|
-
elif key ==
|
|
667
|
+
elif key == "down" and selected < num_options - 1:
|
|
653
668
|
selected += 1
|
|
654
|
-
elif key ==
|
|
669
|
+
elif key == "enter":
|
|
655
670
|
break
|
|
656
|
-
elif key ==
|
|
671
|
+
elif key == "ctrl-c":
|
|
657
672
|
print("\033[?25h", end="") # Show cursor
|
|
658
673
|
sys.exit(0)
|
|
659
674
|
|
|
@@ -732,7 +747,9 @@ async def run_single_turn_async(
|
|
|
732
747
|
spinner = Spinner("Thinking")
|
|
733
748
|
spinner.start()
|
|
734
749
|
|
|
735
|
-
async for chunk in astream_graph_updates(
|
|
750
|
+
async for chunk in astream_graph_updates(
|
|
751
|
+
graph, input_data, config=config, stream_mode=stream_mode
|
|
752
|
+
):
|
|
736
753
|
# Stop spinner on first chunk
|
|
737
754
|
if first_chunk:
|
|
738
755
|
spinner.stop()
|
|
@@ -779,7 +796,9 @@ def run_single_turn_sync(
|
|
|
779
796
|
spinner = Spinner("Thinking")
|
|
780
797
|
spinner.start()
|
|
781
798
|
|
|
782
|
-
for chunk in stream_graph_updates(
|
|
799
|
+
for chunk in stream_graph_updates(
|
|
800
|
+
graph, input_data, config=config, stream_mode=stream_mode
|
|
801
|
+
):
|
|
783
802
|
# Stop spinner on first chunk
|
|
784
803
|
if first_chunk:
|
|
785
804
|
spinner.stop()
|
|
@@ -832,6 +851,7 @@ def print_help():
|
|
|
832
851
|
|
|
833
852
|
# --- Built-in Slash Commands ---
|
|
834
853
|
|
|
854
|
+
|
|
835
855
|
@register_command(
|
|
836
856
|
name="help",
|
|
837
857
|
description="Show this help message",
|
|
@@ -943,6 +963,7 @@ def cmd_config(args: str, context: Dict[str, Any]) -> Optional[str]:
|
|
|
943
963
|
# Full resolved view: each value, where it came from, and the env var
|
|
944
964
|
# / TOML key that sets it.
|
|
945
965
|
from deepagent_code.config import CodeConfig
|
|
966
|
+
|
|
946
967
|
for line in CodeConfig.resolve().describe().splitlines():
|
|
947
968
|
print(f" {line}")
|
|
948
969
|
|
|
@@ -1178,10 +1199,14 @@ def run_conversation_loop(
|
|
|
1178
1199
|
|
|
1179
1200
|
if use_async:
|
|
1180
1201
|
duration = asyncio.run(
|
|
1181
|
-
run_single_turn_async(
|
|
1202
|
+
run_single_turn_async(
|
|
1203
|
+
graph, initial_message, config, interactive, verbose, stream_mode
|
|
1204
|
+
)
|
|
1182
1205
|
)
|
|
1183
1206
|
else:
|
|
1184
|
-
duration = run_single_turn_sync(
|
|
1207
|
+
duration = run_single_turn_sync(
|
|
1208
|
+
graph, initial_message, config, interactive, verbose, stream_mode
|
|
1209
|
+
)
|
|
1185
1210
|
print_timing(duration, verbose)
|
|
1186
1211
|
print()
|
|
1187
1212
|
|
|
@@ -1252,10 +1277,14 @@ def run_conversation_loop(
|
|
|
1252
1277
|
# Run the agent
|
|
1253
1278
|
if use_async:
|
|
1254
1279
|
duration = asyncio.run(
|
|
1255
|
-
run_single_turn_async(
|
|
1280
|
+
run_single_turn_async(
|
|
1281
|
+
graph, user_input, config, interactive, verbose, stream_mode
|
|
1282
|
+
)
|
|
1256
1283
|
)
|
|
1257
1284
|
else:
|
|
1258
|
-
duration = run_single_turn_sync(
|
|
1285
|
+
duration = run_single_turn_sync(
|
|
1286
|
+
graph, user_input, config, interactive, verbose, stream_mode
|
|
1287
|
+
)
|
|
1259
1288
|
print_timing(duration, verbose)
|
|
1260
1289
|
print()
|
|
1261
1290
|
|
|
@@ -1310,6 +1339,19 @@ def run_conversation_loop(
|
|
|
1310
1339
|
default=None,
|
|
1311
1340
|
help="Show verbose output including node names",
|
|
1312
1341
|
)
|
|
1342
|
+
@click.option(
|
|
1343
|
+
"--demo",
|
|
1344
|
+
is_flag=True,
|
|
1345
|
+
default=False,
|
|
1346
|
+
help="Run with the built-in keyless demo agent — no API key needed",
|
|
1347
|
+
)
|
|
1348
|
+
@click.option(
|
|
1349
|
+
"--show-config",
|
|
1350
|
+
"show_config",
|
|
1351
|
+
is_flag=True,
|
|
1352
|
+
default=False,
|
|
1353
|
+
help="Print the resolved configuration (defaults < deepagents.toml < env < CLI) and exit",
|
|
1354
|
+
)
|
|
1313
1355
|
def main(
|
|
1314
1356
|
message: Optional[str],
|
|
1315
1357
|
agent_spec: Optional[str],
|
|
@@ -1319,6 +1361,8 @@ def main(
|
|
|
1319
1361
|
use_async: Optional[bool],
|
|
1320
1362
|
stream_mode: Optional[str],
|
|
1321
1363
|
verbose: Optional[bool],
|
|
1364
|
+
demo: bool,
|
|
1365
|
+
show_config: bool,
|
|
1322
1366
|
):
|
|
1323
1367
|
"""
|
|
1324
1368
|
Run a LangGraph agent from the command line.
|
|
@@ -1350,7 +1394,20 @@ def main(
|
|
|
1350
1394
|
deepagent-code -a my_agent.py "What can you do?"
|
|
1351
1395
|
deepagent-code -a my_agent.py:graph
|
|
1352
1396
|
deepagent-code -f ./prompt.md
|
|
1397
|
+
deepagent-code --demo "try it with no API key"
|
|
1398
|
+
deepagent-code --show-config
|
|
1353
1399
|
"""
|
|
1400
|
+
if show_config:
|
|
1401
|
+
print(config_module.CodeConfig.resolve(toml_start=Path.cwd()).describe())
|
|
1402
|
+
return
|
|
1403
|
+
|
|
1404
|
+
if demo:
|
|
1405
|
+
if agent_spec:
|
|
1406
|
+
print(f"{RED}⏺ Error: --demo and -a/--agent are mutually exclusive{RESET}")
|
|
1407
|
+
sys.exit(1)
|
|
1408
|
+
# The keyless echo agent shipped with the shared core.
|
|
1409
|
+
agent_spec = "langgraph_stream_parser.demo.stub:graph"
|
|
1410
|
+
|
|
1354
1411
|
try:
|
|
1355
1412
|
# Handle -f/--file option: read message from file
|
|
1356
1413
|
if prompt_file and message:
|
|
@@ -1359,7 +1416,7 @@ def main(
|
|
|
1359
1416
|
|
|
1360
1417
|
if prompt_file:
|
|
1361
1418
|
try:
|
|
1362
|
-
with open(prompt_file,
|
|
1419
|
+
with open(prompt_file, "r", encoding="utf-8") as f:
|
|
1363
1420
|
message = f.read().strip()
|
|
1364
1421
|
if not message:
|
|
1365
1422
|
print(f"{RED}⏺ Error: File '{prompt_file}' is empty{RESET}")
|
|
@@ -1397,9 +1454,7 @@ def main(
|
|
|
1397
1454
|
verbose = cfg.verbose
|
|
1398
1455
|
# Only change directory when a workspace root was actually configured.
|
|
1399
1456
|
workspace_root = (
|
|
1400
|
-
cfg.workspace_root
|
|
1401
|
-
if cfg.sources.get("workspace_root") != "default"
|
|
1402
|
-
else None
|
|
1457
|
+
cfg.workspace_root if cfg.sources.get("workspace_root") != "default" else None
|
|
1403
1458
|
)
|
|
1404
1459
|
|
|
1405
1460
|
# If no spec provided, try the default agent
|
|
@@ -1410,8 +1465,8 @@ def main(
|
|
|
1410
1465
|
else:
|
|
1411
1466
|
print(f"{RED}⏺ Error: No agent specified.{RESET}")
|
|
1412
1467
|
print(f"\n{DIM}Usage:{RESET}")
|
|
1413
|
-
print(
|
|
1414
|
-
print(
|
|
1468
|
+
print(" deepagent-code path/to/agent.py:graph")
|
|
1469
|
+
print(" deepagent-code mypackage.module:agent")
|
|
1415
1470
|
print(f"\n{DIM}Or set DEEPAGENT_AGENT_SPEC environment variable{RESET}")
|
|
1416
1471
|
sys.exit(1)
|
|
1417
1472
|
|
|
@@ -1472,6 +1527,7 @@ def main(
|
|
|
1472
1527
|
print(f"{RED}⏺ Error: {e}{RESET}")
|
|
1473
1528
|
if verbose:
|
|
1474
1529
|
import traceback
|
|
1530
|
+
|
|
1475
1531
|
print(traceback.format_exc())
|
|
1476
1532
|
sys.exit(1)
|
|
1477
1533
|
|
deepagent_code/config.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepagent-code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A Claude Code-style CLI for running LangGraph agents from the terminal
|
|
5
5
|
Author-email: Kedar Dabhadkar <kdabhadk@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -19,7 +19,7 @@ Requires-Python: >=3.11
|
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
21
|
Requires-Dist: langgraph>=0.2.0
|
|
22
|
-
Requires-Dist: langgraph-stream-parser<0.3,>=0.2
|
|
22
|
+
Requires-Dist: langgraph-stream-parser<0.3,>=0.2.2
|
|
23
23
|
Requires-Dist: click>=8.0.0
|
|
24
24
|
Requires-Dist: python-dotenv
|
|
25
25
|
Requires-Dist: deepagents
|
|
@@ -27,7 +27,7 @@ Provides-Extra: dev
|
|
|
27
27
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
28
28
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
29
29
|
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
30
|
-
Requires-Dist: ruff
|
|
30
|
+
Requires-Dist: ruff==0.15.15; extra == "dev"
|
|
31
31
|
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
32
32
|
Dynamic: license-file
|
|
33
33
|
|
|
@@ -37,6 +37,19 @@ A Claude Code-style CLI for running LangGraph agents from the terminal.
|
|
|
37
37
|
|
|
38
38
|

|
|
39
39
|
|
|
40
|
+
## One agent, every surface
|
|
41
|
+
|
|
42
|
+
deepagent-code is the terminal surface of the **deep-agent family**: write your agent once — any LangGraph `CompiledGraph` — and run it on every surface with the same spec string (`module:attr` or `path/to/file.py:attr`), the same `deepagents.toml` config file, and the same `DEEPAGENT_*` environment variables.
|
|
43
|
+
|
|
44
|
+
| Surface | Package | Try it |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| Web app | [cowork-dash](https://github.com/dkedar7/cowork-dash) | `cowork-dash run --agent my_agent.py:graph` |
|
|
47
|
+
| JupyterLab | [deepagent-lab](https://github.com/dkedar7/deepagent-lab) | `pip install deepagent-lab`, then the chat sidebar in `jupyter lab` |
|
|
48
|
+
| Terminal | deepagent-code | **you are here** |
|
|
49
|
+
| VS Code | [deepagent-vscode](https://github.com/dkedar7/deepagent-vscode) | chat participant + stdio sidecar |
|
|
50
|
+
| Reference agent | [deepagent-hermes](https://github.com/dkedar7/deepagent-hermes) | `DEEPAGENT_AGENT_SPEC=deepagent_hermes.agent:graph` on any surface |
|
|
51
|
+
| Shared core | [langgraph-stream-parser](https://github.com/dkedar7/langgraph-stream-parser) | typed events + config resolver behind every surface |
|
|
52
|
+
|
|
40
53
|
## Installation
|
|
41
54
|
|
|
42
55
|
```bash
|
|
@@ -50,6 +63,11 @@ pip install git+https://github.com/dkedar7/deepagent-code.git
|
|
|
50
63
|
|
|
51
64
|
## Quick Start
|
|
52
65
|
|
|
66
|
+
No agent or API key yet? See the CLI working in one command:
|
|
67
|
+
```bash
|
|
68
|
+
deepagent-code --demo "hello"
|
|
69
|
+
```
|
|
70
|
+
|
|
53
71
|
Run with the default agent (requires `ANTHROPIC_API_KEY`):
|
|
54
72
|
```bash
|
|
55
73
|
export ANTHROPIC_API_KEY="your_api_key"
|
|
@@ -86,6 +104,13 @@ deepagent-code --no-interactive
|
|
|
86
104
|
|
|
87
105
|
# Verbose output
|
|
88
106
|
deepagent-code -v
|
|
107
|
+
|
|
108
|
+
# Keyless demo agent (no API key needed)
|
|
109
|
+
deepagent-code --demo
|
|
110
|
+
|
|
111
|
+
# Print the resolved configuration: each value, its source, and the
|
|
112
|
+
# env var / deepagents.toml key that sets it
|
|
113
|
+
deepagent-code --show-config
|
|
89
114
|
```
|
|
90
115
|
|
|
91
116
|
## Commands
|
|
@@ -99,7 +124,8 @@ In the interactive loop:
|
|
|
99
124
|
|
|
100
125
|
```bash
|
|
101
126
|
# Agent location (path/to/file.py:variable_name or module:variable)
|
|
102
|
-
|
|
127
|
+
# (DEEPAGENT_SPEC is still accepted as a deprecated alias)
|
|
128
|
+
export DEEPAGENT_AGENT_SPEC="my_agent.py:graph"
|
|
103
129
|
deepagent-code
|
|
104
130
|
|
|
105
131
|
# Working directory
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
deepagent_code/__init__.py,sha256=JPFtlQaI1JlaaNwxEjHtZRrRjHnKqvIMX3hsHs_J0zc,838
|
|
2
|
+
deepagent_code/cli.py,sha256=ec_MFNyjcniFCw23NBM98gQzfm_Thl3_UsbZxU9yBCE,53091
|
|
3
|
+
deepagent_code/config.py,sha256=yS-hg488v43EHHthD1dIZjZXNLpZ6ySia7wPVlQv9l8,3900
|
|
4
|
+
deepagent_code-0.3.0.dist-info/licenses/LICENSE,sha256=qpYYHmzcrz0EHlqxI_sLsQch2b-hxbd0ck6fhdMG1Ro,1105
|
|
5
|
+
deepagent_code-0.3.0.dist-info/METADATA,sha256=p32GjWzadQ9CVHwD9Ix1rxHFG_aanV4uI8EwrW8iOe0,6482
|
|
6
|
+
deepagent_code-0.3.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
7
|
+
deepagent_code-0.3.0.dist-info/entry_points.txt,sha256=PWwiOEAJSGxXrYzQf4ZjJAvOJJu51HGjbmB9WS-YG4s,59
|
|
8
|
+
deepagent_code-0.3.0.dist-info/top_level.txt,sha256=pHYUFsYplb6AU9r59gIkiTIhB6TqKNF04ILoEkVw078,15
|
|
9
|
+
deepagent_code-0.3.0.dist-info/RECORD,,
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
deepagent_code/__init__.py,sha256=JPFtlQaI1JlaaNwxEjHtZRrRjHnKqvIMX3hsHs_J0zc,838
|
|
2
|
-
deepagent_code/cli.py,sha256=ktgiNBFzFxhcG0kWEv9OZLFj9LEOOciMjB6MnaJ6WBQ,51857
|
|
3
|
-
deepagent_code/config.py,sha256=Fg2YRSEcNWXvjzw84hCZa5rAYEcuvC0QYog-Ftw57rg,3898
|
|
4
|
-
deepagent_code-0.2.0.dist-info/licenses/LICENSE,sha256=qpYYHmzcrz0EHlqxI_sLsQch2b-hxbd0ck6fhdMG1Ro,1105
|
|
5
|
-
deepagent_code-0.2.0.dist-info/METADATA,sha256=Oc-TAbQesNOAvBk75hcgr5E2JM5z50fqSRixP1fkIh8,4958
|
|
6
|
-
deepagent_code-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
7
|
-
deepagent_code-0.2.0.dist-info/entry_points.txt,sha256=PWwiOEAJSGxXrYzQf4ZjJAvOJJu51HGjbmB9WS-YG4s,59
|
|
8
|
-
deepagent_code-0.2.0.dist-info/top_level.txt,sha256=pHYUFsYplb6AU9r59gIkiTIhB6TqKNF04ILoEkVw078,15
|
|
9
|
-
deepagent_code-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|