deepagent-code 0.1.2__tar.gz → 0.1.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepagent-code
3
- Version: 0.1.2
3
+ Version: 0.1.4
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
@@ -95,7 +95,7 @@ In the interactive loop:
95
95
 
96
96
  ```bash
97
97
  # Agent location (path/to/file.py:variable_name or module:variable)
98
- export DEEPAGENT_AGENT_SPEC="my_agent.py:graph"
98
+ export DEEPAGENT_SPEC="my_agent.py:graph"
99
99
  deepagent-code
100
100
 
101
101
  # Working directory
@@ -63,7 +63,7 @@ In the interactive loop:
63
63
 
64
64
  ```bash
65
65
  # Agent location (path/to/file.py:variable_name or module:variable)
66
- export DEEPAGENT_AGENT_SPEC="my_agent.py:graph"
66
+ export DEEPAGENT_SPEC="my_agent.py:graph"
67
67
  deepagent-code
68
68
 
69
69
  # Working directory
@@ -7,6 +7,7 @@ import importlib.util
7
7
  import json
8
8
  import os
9
9
  import re
10
+ import subprocess
10
11
  import sys
11
12
  import threading
12
13
  import time
@@ -53,7 +54,7 @@ SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇",
53
54
 
54
55
 
55
56
  # Version info
56
- __version__ = "0.1.2"
57
+ __version__ = "0.1.4"
57
58
 
58
59
 
59
60
  # Slash command registry
@@ -124,6 +125,26 @@ class CommandRegistry:
124
125
  command_registry = CommandRegistry()
125
126
 
126
127
 
128
+ def rl_wrap(code: str) -> str:
129
+ """Wrap ANSI escape code for readline to ignore in length calculations.
130
+
131
+ On terminals, ANSI codes are invisible but counted in string length.
132
+ This causes issues with line wrapping when using input().
133
+ Wrapping with \\001 and \\002 tells readline to ignore these characters.
134
+ """
135
+ if HAS_READLINE:
136
+ return f"\001{code}\002"
137
+ return code
138
+
139
+
140
+ def make_prompt(text: str = "❯", color: str = BRIGHT_BLUE) -> str:
141
+ """Create a prompt string with proper readline escaping for ANSI codes.
142
+
143
+ This prevents line wrapping issues on Windows and other terminals.
144
+ """
145
+ return f"{rl_wrap(BOLD)}{rl_wrap(color)}{text}{rl_wrap(RESET)} "
146
+
147
+
127
148
  def register_command(
128
149
  name: str,
129
150
  description: str,
@@ -236,8 +257,87 @@ def get_agent_name(graph) -> str:
236
257
  return "AgentCode"
237
258
 
238
259
 
239
- def print_header_box(agent_name: str, cwd: str):
240
- """Print an elegant header with the agent name and version."""
260
+ def get_agent_description(graph) -> Optional[str]:
261
+ """Extract agent description from graph object, if available."""
262
+ # Try common attribute names for agent description
263
+ for attr in ('description', 'agent_description', '_description', '__doc__'):
264
+ if hasattr(graph, attr):
265
+ desc = getattr(graph, attr)
266
+ if desc and isinstance(desc, str) and desc.strip():
267
+ return desc.strip()
268
+ # Check if it's a compiled graph with a description in builder
269
+ if hasattr(graph, 'builder') and hasattr(graph.builder, 'description'):
270
+ desc = graph.builder.description
271
+ if desc and isinstance(desc, str) and desc.strip():
272
+ return desc.strip()
273
+ return None
274
+
275
+
276
+ def text_to_ascii_art(text: str) -> List[str]:
277
+ """Convert text to ASCII art using a clean block font.
278
+
279
+ Returns a list of strings, one per line of the ASCII art.
280
+ All characters are exactly 3 chars wide for consistent spacing.
281
+ """
282
+ # Clean 3-line block font - each char is exactly 3 wide
283
+ FONT = {
284
+ 'A': ['▄▀▄', '█▀█', '▀ ▀'],
285
+ 'B': ['█▀▄', '█▀▄', '▀▀▀'],
286
+ 'C': ['▄▀▀', '█ ', '▀▀▀'],
287
+ 'D': ['█▀▄', '█ █', '▀▀▀'],
288
+ 'E': ['█▀▀', '█▀▀', '▀▀▀'],
289
+ 'F': ['█▀▀', '█▀▀', '▀ '],
290
+ 'G': ['▄▀▀', '█▀█', '▀▀▀'],
291
+ 'H': ['█ █', '█▀█', '▀ ▀'],
292
+ 'I': ['▀█▀', ' █ ', '▀▀▀'],
293
+ 'J': ['▀▀█', ' █', '▀▀▀'],
294
+ 'K': ['█ █', '█▀▄', '▀ ▀'],
295
+ 'L': ['█ ', '█ ', '▀▀▀'],
296
+ 'M': ['█▄█', '█ █', '▀ ▀'],
297
+ 'N': ['█▀█', '█ █', '▀ ▀'],
298
+ 'O': ['▄▀▄', '█ █', '▀▀▀'],
299
+ 'P': ['█▀▄', '█▀▀', '▀ '],
300
+ 'Q': ['▄▀▄', '█ █', '▀▀█'],
301
+ 'R': ['█▀▄', '█▀▄', '▀ ▀'],
302
+ 'S': ['▄▀▀', '▀▀▄', '▀▀▀'],
303
+ 'T': ['▀█▀', ' █ ', ' ▀ '],
304
+ 'U': ['█ █', '█ █', '▀▀▀'],
305
+ 'V': ['█ █', '█ █', ' ▀ '],
306
+ 'W': ['█ █', '█▀█', '▀ ▀'],
307
+ 'X': ['▀▄▀', ' █ ', '▀ ▀'],
308
+ 'Y': ['█ █', ' █ ', ' ▀ '],
309
+ 'Z': ['▀▀█', ' █ ', '█▀▀'],
310
+ '0': ['▄▀▄', '█ █', '▀▀▀'],
311
+ '1': ['▄█ ', ' █ ', '▀▀▀'],
312
+ '2': ['▀▀█', '▄▀▀', '▀▀▀'],
313
+ '3': ['▀▀█', ' ▀█', '▀▀▀'],
314
+ '4': ['█ █', '▀▀█', ' ▀'],
315
+ '5': ['█▀▀', '▀▀▄', '▀▀▀'],
316
+ '6': ['▄▀▀', '█▀█', '▀▀▀'],
317
+ '7': ['▀▀█', ' █', ' ▀'],
318
+ '8': ['▄▀▄', '█▀█', '▀▀▀'],
319
+ '9': ['▄▀█', '▀▀█', '▀▀▀'],
320
+ ' ': [' ', ' ', ' '],
321
+ '-': [' ', '▀▀▀', ' '],
322
+ '_': [' ', ' ', '▀▀▀'],
323
+ '.': [' ', ' ', ' ▀ '],
324
+ }
325
+
326
+ # Default char for unknown characters
327
+ DEFAULT = [' ', ' █ ', ' ']
328
+
329
+ lines = ['', '', '']
330
+ for char in text.upper():
331
+ char_art = FONT.get(char, DEFAULT)
332
+ for i in range(3):
333
+ lines[i] += char_art[i] + ' '
334
+
335
+ # Remove only the final trailing space we added (not internal spaces from chars like T, P)
336
+ return [line[:-1] if line.endswith(' ') else line for line in lines]
337
+
338
+
339
+ def print_header_box(agent_name: str, cwd: str, description: Optional[str] = None):
340
+ """Print an elegant header with ASCII art agent name, optional description, and cwd."""
241
341
  term_width = get_terminal_width()
242
342
 
243
343
  # Box drawing characters
@@ -247,15 +347,41 @@ def print_header_box(agent_name: str, cwd: str):
247
347
  # Calculate inner width (accounting for borders and padding)
248
348
  inner_width = term_width - 4 # 2 for borders, 2 for padding
249
349
 
250
- # Build the header content
251
- title_line = agent_name.center(inner_width)
252
- cwd_display = cwd if len(cwd) <= inner_width else "..." + cwd[-(inner_width - 3):]
253
- cwd_line = cwd_display.center(inner_width)
350
+ # Generate ASCII art for agent name
351
+ ascii_lines = text_to_ascii_art(agent_name)
352
+ ascii_width = max(len(line) for line in ascii_lines) if ascii_lines else 0
353
+
354
+ # Use ASCII art if it fits in terminal width
355
+ use_ascii = ascii_width <= inner_width
356
+
357
+ # Build cwd line with label
358
+ cwd_label = "cwd: "
359
+ max_cwd_len = inner_width - len(cwd_label)
360
+ cwd_display = cwd if len(cwd) <= max_cwd_len else "..." + cwd[-(max_cwd_len - 3):]
361
+ cwd_with_label = f"{cwd_label}{cwd_display}"
362
+ cwd_line = cwd_with_label.center(inner_width)
254
363
 
255
364
  # Print the box with gradient-style coloring
256
365
  print()
257
366
  print(f"{BRIGHT_CYAN}{TL}{H * (term_width - 2)}{TR}{RESET}")
258
- print(f"{BRIGHT_CYAN}{V}{RESET} {BOLD}{BRIGHT_CYAN}{title_line}{RESET} {BRIGHT_CYAN}{V}{RESET}")
367
+
368
+ if use_ascii:
369
+ # Print ASCII art lines centered
370
+ for line in ascii_lines:
371
+ centered_line = line.center(inner_width)
372
+ print(f"{BRIGHT_CYAN}{V}{RESET} {BOLD}{BRIGHT_CYAN}{centered_line}{RESET} {BRIGHT_CYAN}{V}{RESET}")
373
+ else:
374
+ # Fall back to plain text if ASCII art doesn't fit
375
+ title_line = agent_name.center(inner_width)
376
+ print(f"{BRIGHT_CYAN}{V}{RESET} {BOLD}{BRIGHT_CYAN}{title_line}{RESET} {BRIGHT_CYAN}{V}{RESET}")
377
+
378
+ # Print description line if available
379
+ if description:
380
+ # Truncate description if too long
381
+ desc_display = description if len(description) <= inner_width else description[:inner_width - 3] + "..."
382
+ desc_line = desc_display.center(inner_width)
383
+ print(f"{CYAN}{V}{RESET} {DIM}{ITALIC}{desc_line}{RESET} {CYAN}{V}{RESET}")
384
+
259
385
  print(f"{CYAN}{V}{RESET} {DIM}{cwd_line}{RESET} {CYAN}{V}{RESET}")
260
386
  print(f"{CYAN}{BL}{H * (term_width - 2)}{BR}{RESET}")
261
387
 
@@ -278,7 +404,7 @@ def render_markdown(text: str) -> str:
278
404
 
279
405
  def parse_agent_spec(agent_spec: str) -> Tuple[str, str]:
280
406
  """
281
- Parse DEEPAGENT_AGENT_SPEC format: path/to/file.py:variable_name.
407
+ Parse agent spec format: path/to/file.py:variable_name.
282
408
 
283
409
  Args:
284
410
  agent_spec: Agent specification string
@@ -647,7 +773,7 @@ def handle_interrupt_input(num_actions: int = 1) -> List[Dict[str, Any]]:
647
773
  return [{"type": "reject"} for _ in range(num_actions)]
648
774
  elif choice == 2:
649
775
  print("Enter your decision as JSON (will be applied to all actions):")
650
- json_str = input(f"{BOLD}{BLUE}{RESET} ").strip()
776
+ json_str = input(make_prompt("❯", BLUE)).strip()
651
777
  try:
652
778
  decision = json.loads(json_str)
653
779
  return [decision for _ in range(num_actions)]
@@ -1070,6 +1196,7 @@ def run_conversation_loop(
1070
1196
  graph,
1071
1197
  config: Dict[str, Any],
1072
1198
  agent_name: str = "AgentCode",
1199
+ agent_description: Optional[str] = None,
1073
1200
  use_async: bool = False,
1074
1201
  interactive: bool = True,
1075
1202
  verbose: bool = False,
@@ -1083,8 +1210,8 @@ def run_conversation_loop(
1083
1210
  # Set up tab completion for slash commands
1084
1211
  setup_readline_completion()
1085
1212
 
1086
- # Print box-drawn header with agent name
1087
- print_header_box(agent_name, os.getcwd())
1213
+ # Print box-drawn header with agent name and description
1214
+ print_header_box(agent_name, os.getcwd(), agent_description)
1088
1215
 
1089
1216
  # Print welcome message with tips
1090
1217
  print_welcome()
@@ -1119,7 +1246,7 @@ def run_conversation_loop(
1119
1246
  while True:
1120
1247
  try:
1121
1248
  print(separator("dots"))
1122
- user_input = input(f"{BOLD}{BRIGHT_BLUE}❯{RESET} ").strip()
1249
+ user_input = input(make_prompt()).strip()
1123
1250
 
1124
1251
  if not user_input:
1125
1252
  continue
@@ -1147,6 +1274,28 @@ def run_conversation_loop(
1147
1274
  print(f"{DIM}Type /help to see available commands{RESET}")
1148
1275
  continue
1149
1276
 
1277
+ # Handle bang commands (!) - execute bash directly
1278
+ if user_input.startswith("!"):
1279
+ bash_cmd = user_input[1:].strip()
1280
+ if bash_cmd:
1281
+ print()
1282
+ try:
1283
+ result = subprocess.run(
1284
+ bash_cmd,
1285
+ shell=True,
1286
+ capture_output=True,
1287
+ text=True,
1288
+ )
1289
+ if result.stdout:
1290
+ print(result.stdout, end="")
1291
+ if result.stderr:
1292
+ print(f"{RED}{result.stderr}{RESET}", end="")
1293
+ if result.returncode != 0:
1294
+ print(f"{DIM}Exit code: {result.returncode}{RESET}")
1295
+ except Exception as e:
1296
+ print(f"{RED}Error executing command: {e}{RESET}")
1297
+ continue
1298
+
1150
1299
  # Handle "exit" as a special case (without slash)
1151
1300
  if user_input.lower() == "exit":
1152
1301
  break
@@ -1233,7 +1382,7 @@ def main(
1233
1382
  Supports environment variables for configuration:
1234
1383
 
1235
1384
  \b
1236
- - DEEPAGENT_AGENT_SPEC: Agent location (same formats as above)
1385
+ - DEEPAGENT_SPEC: Agent location (same formats as above)
1237
1386
  - DEEPAGENT_WORKSPACE_ROOT: Working directory for the agent
1238
1387
  - DEEPAGENT_CONFIG: Configuration JSON string or path to JSON file
1239
1388
  - DEEPAGENT_STREAM_MODE: Stream mode for LangGraph (updates or values)
@@ -1248,8 +1397,8 @@ def main(
1248
1397
  deepagent-code -m "Hello, agent!"
1249
1398
  """
1250
1399
  try:
1251
- # Get environment variables
1252
- env_agent_spec = os.getenv('DEEPAGENT_AGENT_SPEC')
1400
+ # Get environment variables (DEEPAGENT_SPEC preferred, DEEPAGENT_AGENT_SPEC for backwards compat)
1401
+ env_agent_spec = os.getenv('DEEPAGENT_SPEC') or os.getenv('DEEPAGENT_AGENT_SPEC')
1253
1402
  env_workspace_root = os.getenv('DEEPAGENT_WORKSPACE_ROOT')
1254
1403
  env_config = os.getenv('DEEPAGENT_CONFIG')
1255
1404
  env_stream_mode = os.getenv('DEEPAGENT_STREAM_MODE', 'updates')
@@ -1268,7 +1417,7 @@ def main(
1268
1417
  print(f"\n{DIM}Usage:{RESET}")
1269
1418
  print(f" deepagent-code path/to/agent.py:graph")
1270
1419
  print(f" deepagent-code mypackage.module:agent")
1271
- print(f"\n{DIM}Or set DEEPAGENT_AGENT_SPEC environment variable{RESET}")
1420
+ print(f"\n{DIM}Or set DEEPAGENT_SPEC environment variable{RESET}")
1272
1421
  sys.exit(1)
1273
1422
 
1274
1423
  # Change to workspace root if specified
@@ -1311,14 +1460,16 @@ def main(
1311
1460
  if "thread_id" not in config_dict["configurable"]:
1312
1461
  config_dict["configurable"]["thread_id"] = str(uuid.uuid4())
1313
1462
 
1314
- # Extract agent name from graph object
1463
+ # Extract agent name and description from graph object
1315
1464
  agent_name = get_agent_name(graph)
1465
+ agent_description = get_agent_description(graph)
1316
1466
 
1317
1467
  # Run the conversation loop
1318
1468
  run_conversation_loop(
1319
1469
  graph=graph,
1320
1470
  config=config_dict,
1321
1471
  agent_name=agent_name,
1472
+ agent_description=agent_description,
1322
1473
  use_async=use_async,
1323
1474
  interactive=interactive,
1324
1475
  verbose=verbose,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepagent-code
3
- Version: 0.1.2
3
+ Version: 0.1.4
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
@@ -95,7 +95,7 @@ In the interactive loop:
95
95
 
96
96
  ```bash
97
97
  # Agent location (path/to/file.py:variable_name or module:variable)
98
- export DEEPAGENT_AGENT_SPEC="my_agent.py:graph"
98
+ export DEEPAGENT_SPEC="my_agent.py:graph"
99
99
  deepagent-code
100
100
 
101
101
  # Working directory
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deepagent-code"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  description = "A Claude Code-style CLI for running LangGraph agents from the terminal"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
File without changes
File without changes