agentman-mcp 0.1.3__py3-none-any.whl → 0.1.5__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.
- agentman/__init__.py +2 -0
- agentman/agent_builder.py +90 -26
- agentman/agentfile_parser.py +51 -16
- agentman/cli.py +56 -19
- agentman/common.py +8 -0
- agentman/version.py +7 -2
- agentman_mcp-0.1.5.dist-info/METADATA +592 -0
- agentman_mcp-0.1.5.dist-info/RECORD +13 -0
- agentman_mcp-0.1.3.dist-info/METADATA +0 -434
- agentman_mcp-0.1.3.dist-info/RECORD +0 -13
- {agentman_mcp-0.1.3.dist-info → agentman_mcp-0.1.5.dist-info}/WHEEL +0 -0
- {agentman_mcp-0.1.3.dist-info → agentman_mcp-0.1.5.dist-info}/entry_points.txt +0 -0
- {agentman_mcp-0.1.3.dist-info → agentman_mcp-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {agentman_mcp-0.1.3.dist-info → agentman_mcp-0.1.5.dist-info}/top_level.txt +0 -0
agentman/__init__.py
CHANGED
agentman/agent_builder.py
CHANGED
@@ -1,37 +1,55 @@
|
|
1
|
+
"""Agent builder module for generating files from Agentfile configuration."""
|
2
|
+
|
3
|
+
import json
|
4
|
+
import subprocess
|
1
5
|
from pathlib import Path
|
2
6
|
|
3
7
|
import yaml
|
4
8
|
|
5
|
-
from agentman.agentfile_parser import AgentfileConfig
|
9
|
+
from agentman.agentfile_parser import AgentfileConfig, AgentfileParser
|
6
10
|
|
7
11
|
|
8
12
|
class AgentBuilder:
|
9
13
|
"""Builds agent files from Agentfile configuration."""
|
10
14
|
|
11
|
-
def __init__(self, config: AgentfileConfig, output_dir: str = "output"):
|
15
|
+
def __init__(self, config: AgentfileConfig, output_dir: str = "output", source_dir: str = "."):
|
12
16
|
self.config = config
|
13
17
|
self.output_dir = Path(output_dir)
|
18
|
+
self.source_dir = Path(source_dir)
|
19
|
+
# Check if prompt.txt exists in the source directory
|
20
|
+
self.prompt_file_path = self.source_dir / "prompt.txt"
|
21
|
+
self.has_prompt_file = self.prompt_file_path.exists()
|
14
22
|
|
15
23
|
def build_all(self):
|
16
24
|
"""Build all generated files."""
|
17
25
|
self._ensure_output_dir()
|
26
|
+
self._copy_prompt_file()
|
18
27
|
self._generate_python_agent()
|
19
28
|
self._generate_config_yaml()
|
20
29
|
self._generate_secrets_yaml()
|
21
30
|
self._generate_dockerfile()
|
22
31
|
self._generate_requirements_txt()
|
23
32
|
self._generate_dockerignore()
|
33
|
+
self._validate_output()
|
24
34
|
|
25
35
|
def _ensure_output_dir(self):
|
26
36
|
"""Ensure output directory exists."""
|
27
37
|
self.output_dir.mkdir(exist_ok=True)
|
28
38
|
|
39
|
+
def _copy_prompt_file(self):
|
40
|
+
"""Copy prompt.txt to output directory if it exists."""
|
41
|
+
if self.has_prompt_file:
|
42
|
+
import shutil
|
43
|
+
|
44
|
+
dest_path = self.output_dir / "prompt.txt"
|
45
|
+
shutil.copy2(self.prompt_file_path, dest_path)
|
46
|
+
|
29
47
|
def _generate_python_agent(self):
|
30
48
|
"""Generate the main Python agent file."""
|
31
49
|
content = self._build_python_content()
|
32
50
|
|
33
51
|
agent_file = self.output_dir / "agent.py"
|
34
|
-
with open(agent_file, 'w') as f:
|
52
|
+
with open(agent_file, 'w', encoding='utf-8') as f:
|
35
53
|
f.write(content)
|
36
54
|
|
37
55
|
def _build_python_content(self) -> str:
|
@@ -71,7 +89,32 @@ class AgentBuilder:
|
|
71
89
|
[
|
72
90
|
"async def main() -> None:",
|
73
91
|
" async with fast.run() as agent:",
|
74
|
-
|
92
|
+
]
|
93
|
+
)
|
94
|
+
|
95
|
+
# Check if prompt.txt exists and add prompt loading
|
96
|
+
if self.has_prompt_file:
|
97
|
+
lines.extend(
|
98
|
+
[
|
99
|
+
" # Check if prompt.txt exists and load its content",
|
100
|
+
" import os",
|
101
|
+
" prompt_file = 'prompt.txt'",
|
102
|
+
" if os.path.exists(prompt_file):",
|
103
|
+
" with open(prompt_file, 'r', encoding='utf-8') as f:",
|
104
|
+
" prompt_content = f.read().strip()",
|
105
|
+
" if prompt_content:",
|
106
|
+
" await agent(prompt_content)",
|
107
|
+
" else:",
|
108
|
+
" await agent()",
|
109
|
+
" else:",
|
110
|
+
" await agent()",
|
111
|
+
]
|
112
|
+
)
|
113
|
+
else:
|
114
|
+
lines.extend([" await agent()"])
|
115
|
+
|
116
|
+
lines.extend(
|
117
|
+
[
|
75
118
|
"",
|
76
119
|
"",
|
77
120
|
'if __name__ == "__main__":',
|
@@ -100,7 +143,7 @@ class AgentBuilder:
|
|
100
143
|
}
|
101
144
|
|
102
145
|
config_file = self.output_dir / "fastagent.config.yaml"
|
103
|
-
with open(config_file, 'w') as f:
|
146
|
+
with open(config_file, 'w', encoding='utf-8') as f:
|
104
147
|
yaml.dump(config_data, f, default_flow_style=False, sort_keys=False)
|
105
148
|
|
106
149
|
def _generate_secrets_yaml(self):
|
@@ -125,11 +168,12 @@ class AgentBuilder:
|
|
125
168
|
secrets_data["mcp"] = {"servers": mcp_servers_env}
|
126
169
|
|
127
170
|
secrets_file = self.output_dir / "fastagent.secrets.yaml"
|
128
|
-
with open(secrets_file, 'w') as f:
|
171
|
+
with open(secrets_file, 'w', encoding='utf-8') as f:
|
129
172
|
f.write("# FastAgent Secrets Configuration\n")
|
130
173
|
f.write("# WARNING: Keep this file secure and never commit to version control\n\n")
|
131
174
|
f.write(
|
132
|
-
"# Alternatively set OPENAI_API_KEY and ANTHROPIC_API_KEY
|
175
|
+
"# Alternatively set OPENAI_API_KEY and ANTHROPIC_API_KEY "
|
176
|
+
"environment variables. Config file takes precedence.\n\n"
|
133
177
|
)
|
134
178
|
yaml.dump(secrets_data, f, default_flow_style=False, sort_keys=False)
|
135
179
|
|
@@ -240,7 +284,10 @@ class AgentBuilder:
|
|
240
284
|
lines.append(instruction.to_dockerfile_line())
|
241
285
|
|
242
286
|
# Add a blank line if we have custom instructions
|
243
|
-
|
287
|
+
custom_instructions = [
|
288
|
+
inst for inst in self.config.dockerfile_instructions if inst.instruction not in ["FROM", "EXPOSE", "CMD"]
|
289
|
+
]
|
290
|
+
if custom_instructions:
|
244
291
|
lines.append("")
|
245
292
|
|
246
293
|
# Set working directory if not already set by custom instructions
|
@@ -249,15 +296,19 @@ class AgentBuilder:
|
|
249
296
|
lines.extend(["WORKDIR /app", ""])
|
250
297
|
|
251
298
|
# Copy application files
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
299
|
+
copy_lines = [
|
300
|
+
"# Copy application files",
|
301
|
+
"COPY agent.py .",
|
302
|
+
"COPY fastagent.config.yaml .",
|
303
|
+
"COPY fastagent.secrets.yaml .",
|
304
|
+
]
|
305
|
+
|
306
|
+
# Add prompt.txt copy if it exists
|
307
|
+
if self.has_prompt_file:
|
308
|
+
copy_lines.append("COPY prompt.txt .")
|
309
|
+
|
310
|
+
copy_lines.append("")
|
311
|
+
lines.extend(copy_lines)
|
261
312
|
|
262
313
|
# Add EXPOSE instructions from custom dockerfile instructions first
|
263
314
|
expose_instructions = [inst for inst in self.config.dockerfile_instructions if inst.instruction == "EXPOSE"]
|
@@ -279,19 +330,17 @@ class AgentBuilder:
|
|
279
330
|
lines.append(instruction.to_dockerfile_line())
|
280
331
|
elif self.config.cmd:
|
281
332
|
# Default command from config
|
282
|
-
import json
|
283
|
-
|
284
333
|
cmd_str = json.dumps(self.config.cmd)
|
285
334
|
lines.append(f"CMD {cmd_str}")
|
286
335
|
|
287
336
|
dockerfile = self.output_dir / "Dockerfile"
|
288
|
-
with open(dockerfile, 'w') as f:
|
337
|
+
with open(dockerfile, 'w', encoding='utf-8') as f:
|
289
338
|
f.write("\n".join(lines))
|
290
339
|
|
291
340
|
def _generate_requirements_txt(self):
|
292
341
|
"""Generate the requirements.txt file."""
|
293
342
|
requirements = [
|
294
|
-
"fast-agent-mcp>=0.2.
|
343
|
+
"fast-agent-mcp>=0.2.31",
|
295
344
|
"deprecated>=1.2.18",
|
296
345
|
]
|
297
346
|
|
@@ -312,7 +361,7 @@ class AgentBuilder:
|
|
312
361
|
requirements = sorted(list(set(requirements)))
|
313
362
|
|
314
363
|
req_file = self.output_dir / "requirements.txt"
|
315
|
-
with open(req_file, 'w') as f:
|
364
|
+
with open(req_file, 'w', encoding='utf-8') as f:
|
316
365
|
f.write("\n".join(requirements) + "\n")
|
317
366
|
|
318
367
|
def _generate_dockerignore(self):
|
@@ -367,18 +416,29 @@ class AgentBuilder:
|
|
367
416
|
]
|
368
417
|
|
369
418
|
dockerignore = self.output_dir / ".dockerignore"
|
370
|
-
with open(dockerignore, 'w') as f:
|
419
|
+
with open(dockerignore, 'w', encoding='utf-8') as f:
|
371
420
|
f.write("\n".join(ignore_patterns))
|
372
421
|
|
422
|
+
def _validate_output(self):
|
423
|
+
"""Validate that all required files were generated."""
|
424
|
+
# Skip validation in test environments or when fast-agent is not available
|
425
|
+
try:
|
426
|
+
subprocess.run(["fast-agent", "check"], check=True, cwd=self.output_dir, capture_output=True)
|
427
|
+
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
428
|
+
# If fast-agent is not available or validation fails, just warn but don't fail
|
429
|
+
print(f"⚠️ Validation skipped: {e}")
|
430
|
+
pass
|
431
|
+
|
373
432
|
|
374
433
|
def build_from_agentfile(agentfile_path: str, output_dir: str = "output") -> None:
|
375
434
|
"""Build agent files from an Agentfile."""
|
376
|
-
from agentman.agentfile_parser import AgentfileParser
|
377
|
-
|
378
435
|
parser = AgentfileParser()
|
379
436
|
config = parser.parse_file(agentfile_path)
|
380
437
|
|
381
|
-
|
438
|
+
# Extract source directory from agentfile path
|
439
|
+
source_dir = Path(agentfile_path).parent
|
440
|
+
|
441
|
+
builder = AgentBuilder(config, output_dir, source_dir)
|
382
442
|
builder.build_all()
|
383
443
|
|
384
444
|
print(f"✅ Generated agent files in {output_dir}/")
|
@@ -388,3 +448,7 @@ def build_from_agentfile(agentfile_path: str, output_dir: str = "output") -> Non
|
|
388
448
|
print(" - Dockerfile")
|
389
449
|
print(" - requirements.txt")
|
390
450
|
print(" - .dockerignore")
|
451
|
+
|
452
|
+
# Check if prompt.txt was copied
|
453
|
+
if builder.has_prompt_file:
|
454
|
+
print(" - prompt.txt")
|
agentman/agentfile_parser.py
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
"""Agentfile parser module for parsing Agentfile configurations."""
|
2
|
+
|
3
|
+
import json
|
1
4
|
from dataclasses import dataclass, field
|
2
5
|
from typing import Any, Dict, List, Optional, Union
|
3
6
|
|
@@ -39,6 +42,7 @@ class Agent:
|
|
39
42
|
model: Optional[str] = None
|
40
43
|
use_history: bool = True
|
41
44
|
human_input: bool = False
|
45
|
+
default: bool = False
|
42
46
|
|
43
47
|
def to_decorator_string(self, default_model: Optional[str] = None) -> str:
|
44
48
|
"""Generate the @fast.agent decorator string."""
|
@@ -57,6 +61,9 @@ class Agent:
|
|
57
61
|
if self.human_input:
|
58
62
|
params.append("human_input=True")
|
59
63
|
|
64
|
+
if self.default:
|
65
|
+
params.append("default=True")
|
66
|
+
|
60
67
|
return "@fast.agent(\n " + ",\n ".join(params) + "\n)"
|
61
68
|
|
62
69
|
|
@@ -68,6 +75,7 @@ class Router:
|
|
68
75
|
agents: List[str] = field(default_factory=list)
|
69
76
|
model: Optional[str] = None
|
70
77
|
instruction: Optional[str] = None
|
78
|
+
default: bool = False
|
71
79
|
|
72
80
|
def to_decorator_string(self, default_model: Optional[str] = None) -> str:
|
73
81
|
"""Generate the @fast.router decorator string."""
|
@@ -83,6 +91,9 @@ class Router:
|
|
83
91
|
if self.instruction:
|
84
92
|
params.append(f'instruction="""{self.instruction}"""')
|
85
93
|
|
94
|
+
if self.default:
|
95
|
+
params.append("default=True")
|
96
|
+
|
86
97
|
return "@fast.router(\n " + ",\n ".join(params) + "\n)"
|
87
98
|
|
88
99
|
|
@@ -95,6 +106,7 @@ class Chain:
|
|
95
106
|
instruction: Optional[str] = None
|
96
107
|
cumulative: bool = False
|
97
108
|
continue_with_final: bool = True
|
109
|
+
default: bool = False
|
98
110
|
|
99
111
|
def to_decorator_string(self) -> str:
|
100
112
|
"""Generate the @fast.chain decorator string."""
|
@@ -113,6 +125,9 @@ class Chain:
|
|
113
125
|
if not self.continue_with_final:
|
114
126
|
params.append("continue_with_final=False")
|
115
127
|
|
128
|
+
if self.default:
|
129
|
+
params.append("default=True")
|
130
|
+
|
116
131
|
return "@fast.chain(\n " + ",\n ".join(params) + "\n)"
|
117
132
|
|
118
133
|
|
@@ -127,6 +142,7 @@ class Orchestrator:
|
|
127
142
|
plan_type: str = "full"
|
128
143
|
plan_iterations: int = 5
|
129
144
|
human_input: bool = False
|
145
|
+
default: bool = False
|
130
146
|
|
131
147
|
def to_decorator_string(self, default_model: Optional[str] = None) -> str:
|
132
148
|
"""Generate the @fast.orchestrator decorator string."""
|
@@ -153,6 +169,9 @@ class Orchestrator:
|
|
153
169
|
if self.human_input:
|
154
170
|
params.append("human_input=True")
|
155
171
|
|
172
|
+
if self.default:
|
173
|
+
params.append("default=True")
|
174
|
+
|
156
175
|
return "@fast.orchestrator(\n " + ",\n ".join(params) + "\n)"
|
157
176
|
|
158
177
|
|
@@ -187,8 +206,6 @@ class DockerfileInstruction:
|
|
187
206
|
"""Convert to Dockerfile line format."""
|
188
207
|
if self.instruction in ["CMD", "ENTRYPOINT"] and len(self.args) > 1:
|
189
208
|
# Handle array format for CMD/ENTRYPOINT
|
190
|
-
import json
|
191
|
-
|
192
209
|
args_str = json.dumps(self.args)
|
193
210
|
return f"{self.instruction} {args_str}"
|
194
211
|
return f"{self.instruction} {' '.join(self.args)}"
|
@@ -221,7 +238,7 @@ class AgentfileParser:
|
|
221
238
|
|
222
239
|
def parse_file(self, filepath: str) -> AgentfileConfig:
|
223
240
|
"""Parse an Agentfile and return the configuration."""
|
224
|
-
with open(filepath, 'r') as f:
|
241
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
225
242
|
content = f.read()
|
226
243
|
return self.parse_content(content)
|
227
244
|
|
@@ -273,7 +290,7 @@ class AgentfileParser:
|
|
273
290
|
try:
|
274
291
|
self._parse_line(line)
|
275
292
|
except Exception as e:
|
276
|
-
raise ValueError(f"Error parsing line {line_num}: {line}\n{str(e)}")
|
293
|
+
raise ValueError(f"Error parsing line {line_num}: {line}\n{str(e)}") from e
|
277
294
|
|
278
295
|
return self.config
|
279
296
|
|
@@ -353,6 +370,7 @@ class AgentfileParser:
|
|
353
370
|
"CUMULATIVE",
|
354
371
|
"API_KEY",
|
355
372
|
"BASE_URL",
|
373
|
+
"DEFAULT",
|
356
374
|
]:
|
357
375
|
self._handle_sub_instruction(instruction, parts)
|
358
376
|
# Handle ENV - could be Dockerfile instruction or sub-instruction
|
@@ -364,7 +382,8 @@ class AgentfileParser:
|
|
364
382
|
# It's a Dockerfile instruction
|
365
383
|
self._handle_dockerfile_instruction(instruction, parts)
|
366
384
|
else:
|
367
|
-
# Unknown instruction - treat as potential Dockerfile instruction
|
385
|
+
# Unknown instruction - treat as potential Dockerfile instruction
|
386
|
+
# for forward compatibility
|
368
387
|
self._handle_dockerfile_instruction(instruction, parts)
|
369
388
|
|
370
389
|
def _split_respecting_quotes(self, line: str) -> List[str]:
|
@@ -500,8 +519,9 @@ class AgentfileParser:
|
|
500
519
|
self.current_context = "secret"
|
501
520
|
self.current_item = secret_name
|
502
521
|
else:
|
503
|
-
# Create a new secret context - this will be used if subsequent
|
504
|
-
# If no key-value pairs follow,
|
522
|
+
# Create a new secret context - this will be used if subsequent
|
523
|
+
# lines contain key-value pairs. If no key-value pairs follow,
|
524
|
+
# it will be treated as a simple reference
|
505
525
|
secret = SecretContext(name=secret_name)
|
506
526
|
self.config.secrets.append(secret)
|
507
527
|
self.current_context = "secret"
|
@@ -526,7 +546,7 @@ class AgentfileParser:
|
|
526
546
|
|
527
547
|
# Handle key-value pairs like: API_KEY your_key_here
|
528
548
|
if len(parts) >= 2:
|
529
|
-
key =
|
549
|
+
key = instruction.upper()
|
530
550
|
value = ' '.join(parts[1:])
|
531
551
|
secret_context.values[key] = self._unquote(value)
|
532
552
|
else:
|
@@ -540,8 +560,8 @@ class AgentfileParser:
|
|
540
560
|
port = int(parts[1])
|
541
561
|
if port not in self.config.expose_ports:
|
542
562
|
self.config.expose_ports.append(port)
|
543
|
-
except ValueError:
|
544
|
-
raise ValueError(f"Invalid port number: {parts[1]}")
|
563
|
+
except ValueError as exc:
|
564
|
+
raise ValueError(f"Invalid port number: {parts[1]}") from exc
|
545
565
|
self.current_context = None
|
546
566
|
|
547
567
|
def _handle_cmd(self, parts: List[str]):
|
@@ -586,16 +606,15 @@ class AgentfileParser:
|
|
586
606
|
def _handle_sub_instruction(self, instruction: str, parts: List[str]):
|
587
607
|
"""Handle sub-instructions that modify the current context item."""
|
588
608
|
if not self.current_context:
|
589
|
-
# Special case: if we're not in a context but this looks like
|
590
|
-
# for a secret context, try to handle it
|
609
|
+
# Special case: if we're not in a context but this looks like
|
610
|
+
# a key-value pair for a secret context, try to handle it
|
591
611
|
if self.current_item and any(
|
592
612
|
isinstance(s, SecretContext) and s.name == self.current_item for s in self.config.secrets
|
593
613
|
):
|
594
614
|
self.current_context = "secret"
|
595
615
|
self._handle_secret_sub_instruction(instruction, parts)
|
596
616
|
return
|
597
|
-
|
598
|
-
raise ValueError(f"{instruction} can only be used within a context (SERVER, AGENT, etc.)")
|
617
|
+
raise ValueError(f"{instruction} can only be used within a context (SERVER, AGENT, etc.)")
|
599
618
|
|
600
619
|
if self.current_context == "server":
|
601
620
|
self._handle_server_sub_instruction(instruction, parts)
|
@@ -679,6 +698,10 @@ class AgentfileParser:
|
|
679
698
|
if len(parts) < 2:
|
680
699
|
raise ValueError("HUMAN_INPUT requires true/false")
|
681
700
|
agent.human_input = self._unquote(parts[1]).lower() in ['true', '1', 'yes']
|
701
|
+
elif instruction == "DEFAULT":
|
702
|
+
if len(parts) < 2:
|
703
|
+
raise ValueError("DEFAULT requires true/false")
|
704
|
+
agent.default = self._unquote(parts[1]).lower() in ['true', '1', 'yes']
|
682
705
|
|
683
706
|
def _handle_router_sub_instruction(self, instruction: str, parts: List[str]):
|
684
707
|
"""Handle sub-instructions for ROUTER context."""
|
@@ -696,6 +719,10 @@ class AgentfileParser:
|
|
696
719
|
if len(parts) < 2:
|
697
720
|
raise ValueError("INSTRUCTION requires instruction text")
|
698
721
|
router.instruction = self._unquote(' '.join(parts[1:]))
|
722
|
+
elif instruction == "DEFAULT":
|
723
|
+
if len(parts) < 2:
|
724
|
+
raise ValueError("DEFAULT requires true/false")
|
725
|
+
router.default = self._unquote(parts[1]).lower() in ['true', '1', 'yes']
|
699
726
|
|
700
727
|
def _handle_chain_sub_instruction(self, instruction: str, parts: List[str]):
|
701
728
|
"""Handle sub-instructions for CHAIN context."""
|
@@ -717,6 +744,10 @@ class AgentfileParser:
|
|
717
744
|
if len(parts) < 2:
|
718
745
|
raise ValueError("CONTINUE_WITH_FINAL requires true/false")
|
719
746
|
chain.continue_with_final = self._unquote(parts[1]).lower() in ['true', '1', 'yes']
|
747
|
+
elif instruction == "DEFAULT":
|
748
|
+
if len(parts) < 2:
|
749
|
+
raise ValueError("DEFAULT requires true/false")
|
750
|
+
chain.default = self._unquote(parts[1]).lower() in ['true', '1', 'yes']
|
720
751
|
|
721
752
|
def _handle_orchestrator_sub_instruction(self, instruction: str, parts: List[str]):
|
722
753
|
"""Handle sub-instructions for ORCHESTRATOR context."""
|
@@ -746,9 +777,13 @@ class AgentfileParser:
|
|
746
777
|
raise ValueError("PLAN_ITERATIONS requires a number")
|
747
778
|
try:
|
748
779
|
orchestrator.plan_iterations = int(parts[1])
|
749
|
-
except ValueError:
|
750
|
-
raise ValueError(f"Invalid number for PLAN_ITERATIONS: {parts[1]}")
|
780
|
+
except ValueError as exc:
|
781
|
+
raise ValueError(f"Invalid number for PLAN_ITERATIONS: {parts[1]}") from exc
|
751
782
|
elif instruction == "HUMAN_INPUT":
|
752
783
|
if len(parts) < 2:
|
753
784
|
raise ValueError("HUMAN_INPUT requires true/false")
|
754
785
|
orchestrator.human_input = self._unquote(parts[1]).lower() in ['true', '1', 'yes']
|
786
|
+
elif instruction == "DEFAULT":
|
787
|
+
if len(parts) < 2:
|
788
|
+
raise ValueError("DEFAULT requires true/false")
|
789
|
+
orchestrator.default = self._unquote(parts[1]).lower() in ['true', '1', 'yes']
|
agentman/cli.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
"""Command-line interface for Agentman."""
|
2
|
+
|
1
3
|
import argparse
|
2
4
|
import errno
|
3
5
|
import subprocess
|
@@ -10,7 +12,19 @@ from agentman.version import print_version
|
|
10
12
|
|
11
13
|
|
12
14
|
class HelpException(Exception):
|
13
|
-
|
15
|
+
"""Exception raised to trigger help display."""
|
16
|
+
|
17
|
+
|
18
|
+
class BuildArgs:
|
19
|
+
"""Arguments for the build command.
|
20
|
+
|
21
|
+
This class serves as a placeholder for build-related arguments
|
22
|
+
and may be expanded in the future to include argument validation
|
23
|
+
and processing logic.
|
24
|
+
"""
|
25
|
+
|
26
|
+
def __init__(self):
|
27
|
+
"""Initialize BuildArgs."""
|
14
28
|
|
15
29
|
|
16
30
|
def resolve_context_path(path):
|
@@ -37,12 +51,15 @@ def safe_subprocess_run(cmd_args, check=True):
|
|
37
51
|
|
38
52
|
|
39
53
|
class ArgumentParserWithDefaults(argparse.ArgumentParser):
|
40
|
-
|
41
|
-
|
42
|
-
|
54
|
+
"""Argument parser with default value handling."""
|
55
|
+
|
56
|
+
def add_argument(self, *args, help_text=None, default=None, completer=None, **kwargs):
|
57
|
+
"""Add an argument with help text and default value."""
|
58
|
+
if help_text is not None:
|
59
|
+
kwargs['help'] = help_text
|
43
60
|
if default is not None and args[0] != '-h':
|
44
61
|
kwargs['default'] = default
|
45
|
-
if
|
62
|
+
if help_text is not None and help_text != "==SUPPRESS==":
|
46
63
|
kwargs['help'] += f' (default: {default})'
|
47
64
|
action = super().add_argument(*args, **kwargs)
|
48
65
|
if completer is not None:
|
@@ -51,6 +68,7 @@ class ArgumentParserWithDefaults(argparse.ArgumentParser):
|
|
51
68
|
|
52
69
|
|
53
70
|
def get_description():
|
71
|
+
"""Get the description for the CLI tool."""
|
54
72
|
return """\
|
55
73
|
A tool for building and managing AI agents
|
56
74
|
"""
|
@@ -68,6 +86,7 @@ def configure_arguments(parser):
|
|
68
86
|
|
69
87
|
|
70
88
|
def create_argument_parser(description):
|
89
|
+
"""Create and configure the argument parser."""
|
71
90
|
parser = ArgumentParserWithDefaults(
|
72
91
|
prog="agentman",
|
73
92
|
description=description,
|
@@ -78,15 +97,24 @@ def create_argument_parser(description):
|
|
78
97
|
|
79
98
|
|
80
99
|
def runtime_options(parser, command):
|
81
|
-
"""Configure runtime options for commands.
|
82
|
-
|
83
|
-
|
100
|
+
"""Configure runtime options for commands.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
parser: The argument parser to add options to
|
104
|
+
command: The command name (currently unused but reserved for future use)
|
105
|
+
"""
|
106
|
+
# Runtime options are not yet implemented but reserved for future use
|
107
|
+
# when we need command-specific runtime configurations
|
84
108
|
|
85
109
|
|
86
110
|
def post_parse_setup(args):
|
87
|
-
"""Perform post-parse setup operations.
|
88
|
-
|
89
|
-
|
111
|
+
"""Perform post-parse setup operations.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
args: Parsed command line arguments (currently unused but reserved)
|
115
|
+
"""
|
116
|
+
# Post-parse setup is not yet implemented but reserved for future use
|
117
|
+
# when we need to perform operations after argument parsing
|
90
118
|
|
91
119
|
|
92
120
|
def build_cli(args):
|
@@ -116,12 +144,13 @@ def build_cli(args):
|
|
116
144
|
safe_subprocess_run(docker_cmd, check=True)
|
117
145
|
print(f"✅ Docker image built: {args.tag}")
|
118
146
|
|
119
|
-
except
|
147
|
+
except (subprocess.CalledProcessError, IOError, ValueError) as e:
|
120
148
|
perror(f"Build failed: {e}")
|
121
149
|
sys.exit(1)
|
122
150
|
|
123
151
|
|
124
152
|
def build_parser(subparsers):
|
153
|
+
"""Configure the build subcommand parser."""
|
125
154
|
parser = subparsers.add_parser("build", help="Build an image from a Agentfile")
|
126
155
|
parser.add_argument("-f", "--file", default="Agentfile", help="Name of the Agentfile")
|
127
156
|
parser.add_argument("-o", "--output", help="Output directory for generated files (default: agent)")
|
@@ -194,7 +223,7 @@ def run_cli(args):
|
|
194
223
|
|
195
224
|
safe_subprocess_run(run_cmd, check=True)
|
196
225
|
|
197
|
-
except
|
226
|
+
except (subprocess.CalledProcessError, IOError, ValueError) as e:
|
198
227
|
perror(f"Run failed: {e}")
|
199
228
|
sys.exit(1)
|
200
229
|
else:
|
@@ -230,32 +259,35 @@ def run_cli(args):
|
|
230
259
|
|
231
260
|
try:
|
232
261
|
safe_subprocess_run(run_cmd, check=True)
|
233
|
-
except
|
262
|
+
except (subprocess.CalledProcessError, IOError, ValueError) as e:
|
234
263
|
perror(f"Run failed: {e}")
|
235
264
|
sys.exit(1)
|
236
265
|
|
237
266
|
|
238
267
|
def run_parser(subparsers):
|
268
|
+
"""Configure the run subcommand parser."""
|
239
269
|
parser = subparsers.add_parser("run", help="Create and run a new container from an agent")
|
240
270
|
parser.add_argument("-f", "--file", default="Agentfile", help="Name of the Agentfile (when building from source)")
|
241
271
|
parser.add_argument(
|
242
|
-
"-o", "--output", help="Output directory for generated files (default: agent, when building from source)"
|
272
|
+
"-o", "--output", help="Output directory for generated files " "(default: agent, when building from source)"
|
243
273
|
)
|
244
274
|
parser.add_argument("-t", "--tag", default="agent:latest", help="Name and optionally a tag for the Docker image")
|
245
275
|
parser.add_argument(
|
246
276
|
"--from-agentfile",
|
247
277
|
action="store_true",
|
248
|
-
help="Build from Agentfile and then run (default is to run existing image)",
|
278
|
+
help="Build from Agentfile and then run " "(default is to run existing image)",
|
249
279
|
)
|
250
|
-
parser.add_argument("--path", default=".", help="Build context (directory or URL) when building from Agentfile")
|
280
|
+
parser.add_argument("--path", default=".", help="Build context (directory or URL) " "when building from Agentfile")
|
251
281
|
parser.add_argument("-i", "--interactive", action="store_true", help="Run container interactively")
|
252
282
|
parser.add_argument(
|
253
283
|
"--rm", dest="remove", action="store_true", help="Automatically remove the container when it exits"
|
254
284
|
)
|
255
285
|
parser.add_argument(
|
256
|
-
"-p", "--port", action="append", help="Publish container port(s) to the host (can be used multiple times)"
|
286
|
+
"-p", "--port", action="append", help="Publish container port(s) to the host " "(can be used multiple times)"
|
287
|
+
)
|
288
|
+
parser.add_argument(
|
289
|
+
"-e", "--env", action="append", help="Set environment variables " "(can be used multiple times)"
|
257
290
|
)
|
258
|
-
parser.add_argument("-e", "--env", action="append", help="Set environment variables (can be used multiple times)")
|
259
291
|
parser.add_argument("-v", "--volume", action="append", help="Bind mount volumes (can be used multiple times)")
|
260
292
|
parser.add_argument("command", nargs="*", help="Command to run in the container (overrides default)")
|
261
293
|
runtime_options(parser, "run")
|
@@ -263,15 +295,18 @@ def run_parser(subparsers):
|
|
263
295
|
|
264
296
|
|
265
297
|
def version_parser(subparsers):
|
298
|
+
"""Configure the version subcommand parser."""
|
266
299
|
parser = subparsers.add_parser("version", help="Show the Agentman version information")
|
267
300
|
parser.set_defaults(func=print_version)
|
268
301
|
|
269
302
|
|
270
303
|
def help_cli(args):
|
304
|
+
"""Handle the help command by raising HelpException."""
|
271
305
|
raise HelpException()
|
272
306
|
|
273
307
|
|
274
308
|
def help_parser(subparsers):
|
309
|
+
"""Configure the help subcommand parser."""
|
275
310
|
parser = subparsers.add_parser("help")
|
276
311
|
# Do not run in a container
|
277
312
|
parser.set_defaults(func=help_cli)
|
@@ -293,6 +328,7 @@ def parse_arguments(parser):
|
|
293
328
|
|
294
329
|
|
295
330
|
def init_cli():
|
331
|
+
"""Initialize the CLI by setting up argument parser and parsing arguments."""
|
296
332
|
description = get_description()
|
297
333
|
parser = create_argument_parser(description)
|
298
334
|
configure_subcommands(parser)
|
@@ -302,6 +338,7 @@ def init_cli():
|
|
302
338
|
|
303
339
|
|
304
340
|
def main():
|
341
|
+
"""Main entry point for the CLI application."""
|
305
342
|
parser, args = init_cli()
|
306
343
|
|
307
344
|
def eprint(e, exit_code):
|
agentman/common.py
CHANGED
@@ -1,5 +1,13 @@
|
|
1
|
+
"""Common utilities for Agentman."""
|
2
|
+
|
1
3
|
import sys
|
2
4
|
|
3
5
|
|
4
6
|
def perror(*args, **kwargs):
|
7
|
+
"""Print error message to stderr.
|
8
|
+
|
9
|
+
Args:
|
10
|
+
*args: Arguments to pass to print()
|
11
|
+
**kwargs: Keyword arguments to pass to print()
|
12
|
+
"""
|
5
13
|
print(*args, file=sys.stderr, **kwargs)
|
agentman/version.py
CHANGED
@@ -1,9 +1,14 @@
|
|
1
|
+
"""Version information for Agentman."""
|
2
|
+
|
3
|
+
|
1
4
|
def version():
|
2
|
-
|
5
|
+
"""Return the current version of Agentman."""
|
6
|
+
return "0.1.5"
|
3
7
|
|
4
8
|
|
5
9
|
def print_version(args):
|
10
|
+
"""Print the version information."""
|
6
11
|
if args.quiet:
|
7
12
|
print(version())
|
8
13
|
else:
|
9
|
-
print("agentman version
|
14
|
+
print(f"agentman version {version()}")
|