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.
- {deepagent_code-0.1.2 → deepagent_code-0.1.4}/PKG-INFO +2 -2
- {deepagent_code-0.1.2 → deepagent_code-0.1.4}/README.md +1 -1
- {deepagent_code-0.1.2 → deepagent_code-0.1.4}/deepagent_code/cli.py +169 -18
- {deepagent_code-0.1.2 → deepagent_code-0.1.4}/deepagent_code.egg-info/PKG-INFO +2 -2
- {deepagent_code-0.1.2 → deepagent_code-0.1.4}/pyproject.toml +1 -1
- {deepagent_code-0.1.2 → deepagent_code-0.1.4}/LICENSE +0 -0
- {deepagent_code-0.1.2 → deepagent_code-0.1.4}/deepagent_code/__init__.py +0 -0
- {deepagent_code-0.1.2 → deepagent_code-0.1.4}/deepagent_code/utils.py +0 -0
- {deepagent_code-0.1.2 → deepagent_code-0.1.4}/deepagent_code.egg-info/SOURCES.txt +0 -0
- {deepagent_code-0.1.2 → deepagent_code-0.1.4}/deepagent_code.egg-info/dependency_links.txt +0 -0
- {deepagent_code-0.1.2 → deepagent_code-0.1.4}/deepagent_code.egg-info/entry_points.txt +0 -0
- {deepagent_code-0.1.2 → deepagent_code-0.1.4}/deepagent_code.egg-info/requires.txt +0 -0
- {deepagent_code-0.1.2 → deepagent_code-0.1.4}/deepagent_code.egg-info/top_level.txt +0 -0
- {deepagent_code-0.1.2 → deepagent_code-0.1.4}/setup.cfg +0 -0
- {deepagent_code-0.1.2 → deepagent_code-0.1.4}/tests/test_cli.py +0 -0
- {deepagent_code-0.1.2 → deepagent_code-0.1.4}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepagent-code
|
|
3
|
-
Version: 0.1.
|
|
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
|
|
98
|
+
export DEEPAGENT_SPEC="my_agent.py:graph"
|
|
99
99
|
deepagent-code
|
|
100
100
|
|
|
101
101
|
# 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.
|
|
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
|
|
240
|
-
"""
|
|
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
|
-
#
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
-
|
|
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
|
|
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.
|
|
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
|
|
98
|
+
export DEEPAGENT_SPEC="my_agent.py:graph"
|
|
99
99
|
deepagent-code
|
|
100
100
|
|
|
101
101
|
# Working directory
|
|
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
|