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 CHANGED
@@ -1,3 +1,5 @@
1
+ """Agentman package for building MCP agents from Agentfiles."""
2
+
1
3
  import sys
2
4
 
3
5
  from agentman.cli import HelpException, init_cli, print_version
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
- " await agent()",
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 environment variables. Config file takes precedence.\n\n"
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
- if any(inst.instruction not in ["FROM", "EXPOSE", "CMD"] for inst in self.config.dockerfile_instructions):
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
- lines.extend(
253
- [
254
- "# Copy application files",
255
- "COPY agent.py .",
256
- "COPY fastagent.config.yaml .",
257
- "COPY fastagent.secrets.yaml .",
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.30",
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
- builder = AgentBuilder(config, output_dir)
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")
@@ -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 for forward compatibility
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 lines contain key-value pairs
504
- # If no key-value pairs follow, it will be treated as a simple reference
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 = parts[0].upper()
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 a key-value pair
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
- else:
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
- pass
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
- def add_argument(self, *args, help=None, default=None, completer=None, **kwargs):
41
- if help is not None:
42
- kwargs['help'] = help
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 help is not None and help != "==SUPPRESS==":
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
- # TODO: Implement runtime options configuration
83
- pass
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
- # TODO: Implement post-parse setup
89
- pass
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 Exception as e:
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 Exception as e:
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 Exception as e:
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
- return "0.1.3"
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 %s" % version())
14
+ print(f"agentman version {version()}")