code-puppy 0.0.53__py3-none-any.whl → 0.0.55__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.
code_puppy/__init__.py CHANGED
@@ -1,2 +1,3 @@
1
1
  import importlib.metadata
2
+
2
3
  __version__ = importlib.metadata.version("code-puppy")
code_puppy/agent.py CHANGED
@@ -18,14 +18,16 @@ from code_puppy.session_memory import SessionMemory
18
18
  MODELS_JSON_PATH = os.environ.get("MODELS_JSON_PATH", None)
19
19
 
20
20
  # Load puppy rules if provided
21
- PUPPY_RULES_PATH = Path('.puppy_rules')
21
+ PUPPY_RULES_PATH = Path(".puppy_rules")
22
22
  PUPPY_RULES = None
23
23
  if PUPPY_RULES_PATH.exists():
24
- with open(PUPPY_RULES_PATH, 'r') as f:
24
+ with open(PUPPY_RULES_PATH, "r") as f:
25
25
  PUPPY_RULES = f.read()
26
26
 
27
+
27
28
  class AgentResponse(pydantic.BaseModel):
28
29
  """Represents a response from the agent."""
30
+
29
31
  output_message: str = pydantic.Field(
30
32
  ..., description="The final output message to display to the user"
31
33
  )
@@ -33,31 +35,39 @@ class AgentResponse(pydantic.BaseModel):
33
35
  False, description="True if user input is needed to continue the task"
34
36
  )
35
37
 
38
+
36
39
  # --- NEW DYNAMIC AGENT LOGIC ---
37
40
  _LAST_MODEL_NAME = None
38
41
  _code_generation_agent = None
39
42
  _session_memory = None
40
43
 
44
+
41
45
  def session_memory():
42
- '''
46
+ """
43
47
  Returns a singleton SessionMemory instance to allow agent and tools to persist and recall context/history.
44
- '''
48
+ """
45
49
  global _session_memory
46
50
  if _session_memory is None:
47
51
  _session_memory = SessionMemory()
48
52
  return _session_memory
49
53
 
54
+
50
55
  def reload_code_generation_agent():
51
56
  """Force-reload the agent, usually after a model change."""
52
57
  global _code_generation_agent, _LAST_MODEL_NAME
53
58
  from code_puppy.config import get_model_name
59
+
54
60
  model_name = get_model_name()
55
- console.print(f'[bold cyan]Loading Model: {model_name}[/bold cyan]')
56
- models_path = Path(MODELS_JSON_PATH) if MODELS_JSON_PATH else Path(__file__).parent / "models.json"
61
+ console.print(f"[bold cyan]Loading Model: {model_name}[/bold cyan]")
62
+ models_path = (
63
+ Path(MODELS_JSON_PATH)
64
+ if MODELS_JSON_PATH
65
+ else Path(__file__).parent / "models.json"
66
+ )
57
67
  model = ModelFactory.get_model(model_name, ModelFactory.load_config(models_path))
58
68
  instructions = get_system_prompt()
59
69
  if PUPPY_RULES:
60
- instructions += f'\n{PUPPY_RULES}'
70
+ instructions += f"\n{PUPPY_RULES}"
61
71
  agent = Agent(
62
72
  model=model,
63
73
  instructions=instructions,
@@ -69,11 +79,12 @@ def reload_code_generation_agent():
69
79
  _LAST_MODEL_NAME = model_name
70
80
  # NEW: Log session event
71
81
  try:
72
- session_memory().log_task(f'Agent loaded with model: {model_name}')
82
+ session_memory().log_task(f"Agent loaded with model: {model_name}")
73
83
  except Exception:
74
84
  pass
75
85
  return _code_generation_agent
76
86
 
87
+
77
88
  def get_code_generation_agent(force_reload=False):
78
89
  """
79
90
  Retrieve the agent with the currently set MODEL_NAME.
@@ -81,6 +92,7 @@ def get_code_generation_agent(force_reload=False):
81
92
  """
82
93
  global _code_generation_agent, _LAST_MODEL_NAME
83
94
  from code_puppy.config import get_model_name
95
+
84
96
  model_name = get_model_name()
85
97
  if _code_generation_agent is None or _LAST_MODEL_NAME != model_name or force_reload:
86
98
  return reload_code_generation_agent()
@@ -106,10 +106,9 @@ Return your final response as a structured output having the following fields:
106
106
  * awaiting_user_input: True if user input is needed to continue the task. If you get an error, you might consider asking the user for help.
107
107
  """
108
108
 
109
+
109
110
  def get_system_prompt():
110
111
  """Returns the main system prompt, populated with current puppy and owner name."""
111
112
  return SYSTEM_PROMPT_TEMPLATE.format(
112
- puppy_name=get_puppy_name(),
113
- owner_name=get_owner_name()
113
+ puppy_name=get_puppy_name(), owner_name=get_owner_name()
114
114
  )
115
-
@@ -4,18 +4,23 @@ from typing import Iterable
4
4
  from prompt_toolkit.completion import Completer, Completion
5
5
  from prompt_toolkit.document import Document
6
6
 
7
+
7
8
  class FilePathCompleter(Completer):
8
9
  """A simple file path completer that works with a trigger symbol."""
10
+
9
11
  def __init__(self, symbol: str = "@"):
10
12
  self.symbol = symbol
11
- def get_completions(self, document: Document, complete_event) -> Iterable[Completion]:
13
+
14
+ def get_completions(
15
+ self, document: Document, complete_event
16
+ ) -> Iterable[Completion]:
12
17
  text = document.text
13
18
  cursor_position = document.cursor_position
14
19
  text_before_cursor = text[:cursor_position]
15
20
  if self.symbol not in text_before_cursor:
16
21
  return
17
22
  symbol_pos = text_before_cursor.rfind(self.symbol)
18
- text_after_symbol = text_before_cursor[symbol_pos + len(self.symbol):]
23
+ text_after_symbol = text_before_cursor[symbol_pos + len(self.symbol) :]
19
24
  start_position = -(len(text_after_symbol))
20
25
  try:
21
26
  pattern = text_after_symbol + "*"
@@ -36,7 +41,9 @@ class FilePathCompleter(Completer):
36
41
  else:
37
42
  paths = glob.glob(pattern)
38
43
  if not pattern.startswith(".") and not pattern.startswith("*/."):
39
- paths = [p for p in paths if not os.path.basename(p).startswith(".")]
44
+ paths = [
45
+ p for p in paths if not os.path.basename(p).startswith(".")
46
+ ]
40
47
  paths.sort()
41
48
  for path in paths:
42
49
  is_dir = os.path.isdir(path)
@@ -49,7 +56,7 @@ class FilePathCompleter(Completer):
49
56
  elif text_after_symbol.startswith("~"):
50
57
  home = os.path.expanduser("~")
51
58
  if path.startswith(home):
52
- display_path = "~" + path[len(home):]
59
+ display_path = "~" + path[len(home) :]
53
60
  else:
54
61
  display_path = path
55
62
  else:
@@ -1,4 +1,12 @@
1
- META_COMMANDS_HELP = '''
1
+ import os
2
+ from rich.console import Console
3
+ from code_puppy.command_line.model_picker_completion import (
4
+ load_model_names,
5
+ update_model_in_input,
6
+ )
7
+ from code_puppy.command_line.utils import make_directory_table
8
+
9
+ META_COMMANDS_HELP = """
2
10
  [bold magenta]Meta Commands Help[/bold magenta]
3
11
  ~help, ~h Show this help message
4
12
  ~cd [dir] Change directory or show directories
@@ -6,17 +14,14 @@ META_COMMANDS_HELP = '''
6
14
  ~m <model> Set active model
7
15
  ~show Show puppy status info
8
16
  ~<unknown> Show unknown meta command warning
9
- '''
17
+ """
10
18
 
11
- from code_puppy.command_line.model_picker_completion import update_model_in_input, load_model_names, get_active_model
12
- from rich.console import Console
13
- import os
14
- from code_puppy.command_line.utils import make_directory_table
15
19
 
16
20
  def handle_meta_command(command: str, console: Console) -> bool:
17
21
  # ~codemap (code structure visualization)
18
22
  if command.startswith("~codemap"):
19
23
  from code_puppy.tools.code_map import make_code_map
24
+
20
25
  tokens = command.split()
21
26
  if len(tokens) > 1:
22
27
  target_dir = os.path.expanduser(tokens[1])
@@ -26,7 +31,7 @@ def handle_meta_command(command: str, console: Console) -> bool:
26
31
  tree = make_code_map(target_dir, show_doc=True)
27
32
  console.print(tree)
28
33
  except Exception as e:
29
- console.print(f'[red]Error generating code map:[/red] {e}')
34
+ console.print(f"[red]Error generating code map:[/red] {e}")
30
35
  return True
31
36
  """
32
37
  Handle meta/config commands prefixed with '~'.
@@ -40,7 +45,7 @@ def handle_meta_command(command: str, console: Console) -> bool:
40
45
  table = make_directory_table()
41
46
  console.print(table)
42
47
  except Exception as e:
43
- console.print(f'[red]Error listing directory:[/red] {e}')
48
+ console.print(f"[red]Error listing directory:[/red] {e}")
44
49
  return True
45
50
  elif len(tokens) == 2:
46
51
  dirname = tokens[1]
@@ -49,36 +54,41 @@ def handle_meta_command(command: str, console: Console) -> bool:
49
54
  target = os.path.join(os.getcwd(), target)
50
55
  if os.path.isdir(target):
51
56
  os.chdir(target)
52
- console.print(f'[bold green]Changed directory to:[/bold green] [cyan]{target}[/cyan]')
57
+ console.print(
58
+ f"[bold green]Changed directory to:[/bold green] [cyan]{target}[/cyan]"
59
+ )
53
60
  else:
54
- console.print(f'[red]Not a directory:[/red] [bold]{dirname}[/bold]')
61
+ console.print(f"[red]Not a directory:[/red] [bold]{dirname}[/bold]")
55
62
  return True
56
63
 
57
64
  if command.strip().startswith("~show"):
58
- from code_puppy.config import get_puppy_name, get_owner_name
59
65
  from code_puppy.command_line.model_picker_completion import get_active_model
66
+ from code_puppy.config import get_owner_name, get_puppy_name
67
+
60
68
  puppy_name = get_puppy_name()
61
69
  owner_name = get_owner_name()
62
70
  model = get_active_model()
63
71
  from code_puppy.config import get_yolo_mode
72
+
64
73
  yolo_mode = get_yolo_mode()
65
- console.print(f'''[bold magenta]🐶 Puppy Status[/bold magenta]
74
+ console.print(f"""[bold magenta]🐶 Puppy Status[/bold magenta]
66
75
  \n[bold]puppy_name:[/bold] [cyan]{puppy_name}[/cyan]
67
76
  [bold]owner_name:[/bold] [cyan]{owner_name}[/cyan]
68
77
  [bold]model:[/bold] [green]{model}[/green]
69
- [bold]YOLO_MODE:[/bold] {'[red]ON[/red]' if yolo_mode else '[yellow]off[/yellow]'}
70
- ''')
78
+ [bold]YOLO_MODE:[/bold] {"[red]ON[/red]" if yolo_mode else "[yellow]off[/yellow]"}
79
+ """)
71
80
  return True
72
81
 
73
82
  if command.startswith("~set"):
74
83
  # Syntax: ~set KEY=VALUE or ~set KEY VALUE
75
- from code_puppy.config import set_config_value, get_config_keys
84
+ from code_puppy.config import get_config_keys, set_config_value
85
+
76
86
  tokens = command.split(None, 2)
77
- argstr = command[len('~set'):].strip()
87
+ argstr = command[len("~set") :].strip()
78
88
  key = None
79
89
  value = None
80
- if '=' in argstr:
81
- key, value = argstr.split('=', 1)
90
+ if "=" in argstr:
91
+ key, value = argstr.split("=", 1)
82
92
  key = key.strip()
83
93
  value = value.strip()
84
94
  elif len(tokens) >= 3:
@@ -86,16 +96,18 @@ def handle_meta_command(command: str, console: Console) -> bool:
86
96
  value = tokens[2]
87
97
  elif len(tokens) == 2:
88
98
  key = tokens[1]
89
- value = ''
99
+ value = ""
90
100
  else:
91
- console.print('[yellow]Usage:[/yellow] ~set KEY=VALUE or ~set KEY VALUE')
92
- console.print('Config keys: ' + ', '.join(get_config_keys()))
101
+ console.print("[yellow]Usage:[/yellow] ~set KEY=VALUE or ~set KEY VALUE")
102
+ console.print("Config keys: " + ", ".join(get_config_keys()))
93
103
  return True
94
104
  if key:
95
105
  set_config_value(key, value)
96
- console.print(f'[green]🌶 Set[/green] [cyan]{key}[/cyan] = "{value}" in puppy.cfg!')
106
+ console.print(
107
+ f'[green]🌶 Set[/green] [cyan]{key}[/cyan] = "{value}" in puppy.cfg!'
108
+ )
97
109
  else:
98
- console.print('[red]You must supply a key.[/red]')
110
+ console.print("[red]You must supply a key.[/red]")
99
111
  return True
100
112
 
101
113
  if command.startswith("~m"):
@@ -103,26 +115,34 @@ def handle_meta_command(command: str, console: Console) -> bool:
103
115
  new_input = update_model_in_input(command)
104
116
  if new_input is not None:
105
117
  from code_puppy.agent import get_code_generation_agent
118
+
106
119
  model = get_active_model()
107
120
  get_code_generation_agent(force_reload=True)
108
- console.print(f"[bold green]Active model set and loaded:[/bold green] [cyan]{model}[/cyan]")
121
+ console.print(
122
+ f"[bold green]Active model set and loaded:[/bold green] [cyan]{model}[/cyan]"
123
+ )
109
124
  return True
110
125
  # If no model matched, show available models
111
126
  model_names = load_model_names()
112
127
  console.print(f"[yellow]Available models:[/yellow] {', '.join(model_names)}")
113
- console.print(f"[yellow]Usage:[/yellow] ~m <model_name>")
128
+ console.print("[yellow]Usage:[/yellow] ~m <model_name>")
114
129
  return True
115
130
  if command in ("~help", "~h"):
116
131
  console.print(META_COMMANDS_HELP)
117
132
  return True
118
133
  if command.startswith("~"):
119
- name = command[1:].split()[0] if len(command)>1 else ""
134
+ name = command[1:].split()[0] if len(command) > 1 else ""
120
135
  if name:
121
- console.print(f"[yellow]Unknown meta command:[/yellow] {command}\n[dim]Type ~help for options.[/dim]")
136
+ console.print(
137
+ f"[yellow]Unknown meta command:[/yellow] {command}\n[dim]Type ~help for options.[/dim]"
138
+ )
122
139
  else:
123
140
  # Show current model ONLY here
124
141
  from code_puppy.command_line.model_picker_completion import get_active_model
142
+
125
143
  current_model = get_active_model()
126
- console.print(f"[bold green]Current Model:[/bold green] [cyan]{current_model}[/cyan]")
144
+ console.print(
145
+ f"[bold green]Current Model:[/bold green] [cyan]{current_model}[/cyan]"
146
+ )
127
147
  return True
128
148
  return False
@@ -9,14 +9,16 @@ from code_puppy.config import get_model_name, set_model_name
9
9
 
10
10
  MODELS_JSON_PATH = os.environ.get("MODELS_JSON_PATH")
11
11
  if not MODELS_JSON_PATH:
12
- MODELS_JSON_PATH = os.path.join(os.path.dirname(__file__), '..', 'models.json')
12
+ MODELS_JSON_PATH = os.path.join(os.path.dirname(__file__), "..", "models.json")
13
13
  MODELS_JSON_PATH = os.path.abspath(MODELS_JSON_PATH)
14
14
 
15
+
15
16
  def load_model_names():
16
- with open(MODELS_JSON_PATH, 'r') as f:
17
+ with open(MODELS_JSON_PATH, "r") as f:
17
18
  models = json.load(f)
18
19
  return list(models.keys())
19
20
 
21
+
20
22
  def get_active_model():
21
23
  """
22
24
  Returns the active model from the config using get_model_name().
@@ -24,37 +26,43 @@ def get_active_model():
24
26
  """
25
27
  return get_model_name()
26
28
 
29
+
27
30
  def set_active_model(model_name: str):
28
31
  """
29
32
  Sets the active model name by updating both config (for persistence)
30
33
  and env (for process lifetime override).
31
34
  """
32
35
  set_model_name(model_name)
33
- os.environ['MODEL_NAME'] = model_name.strip()
36
+ os.environ["MODEL_NAME"] = model_name.strip()
34
37
  # Reload agent globally
35
38
  try:
36
- from code_puppy.agent import reload_code_generation_agent, get_code_generation_agent
37
- reload_code_generation_agent() # This will reload dynamically everywhere
38
- except Exception as e:
39
+ from code_puppy.agent import reload_code_generation_agent
40
+
41
+ reload_code_generation_agent() # This will reload dynamically everywhere
42
+ except Exception:
39
43
  pass # If reload fails, agent will still be switched next interpreter run
40
44
 
45
+
41
46
  class ModelNameCompleter(Completer):
42
47
  """
43
48
  A completer that triggers on '~m' to show available models from models.json.
44
49
  Only '~m' (not just '~') will trigger the dropdown.
45
50
  """
51
+
46
52
  def __init__(self, trigger: str = "~m"):
47
53
  self.trigger = trigger
48
54
  self.model_names = load_model_names()
49
55
 
50
- def get_completions(self, document: Document, complete_event) -> Iterable[Completion]:
56
+ def get_completions(
57
+ self, document: Document, complete_event
58
+ ) -> Iterable[Completion]:
51
59
  text = document.text
52
60
  cursor_position = document.cursor_position
53
61
  text_before_cursor = text[:cursor_position]
54
62
  if self.trigger not in text_before_cursor:
55
63
  return
56
64
  symbol_pos = text_before_cursor.rfind(self.trigger)
57
- text_after_trigger = text_before_cursor[symbol_pos + len(self.trigger):]
65
+ text_after_trigger = text_before_cursor[symbol_pos + len(self.trigger) :]
58
66
  start_position = -(len(text_after_trigger))
59
67
  for model_name in self.model_names:
60
68
  meta = "Model (selected)" if model_name == get_active_model() else "Model"
@@ -65,25 +73,31 @@ class ModelNameCompleter(Completer):
65
73
  display_meta=meta,
66
74
  )
67
75
 
76
+
68
77
  def update_model_in_input(text: str) -> Optional[str]:
69
78
  # If input starts with ~m and a model name, set model and strip it out
70
79
  content = text.strip()
71
80
  if content.startswith("~m"):
72
81
  rest = content[2:].strip()
73
82
  for model in load_model_names():
74
- if rest.startswith(model):
83
+ if rest == model:
75
84
  set_active_model(model)
76
85
  # Remove ~mmodel from the input
77
- idx = text.find("~m"+model)
86
+ idx = text.find("~m" + model)
78
87
  if idx != -1:
79
- new_text = (text[:idx] + text[idx+len("~m"+model):]).strip()
88
+ new_text = (text[:idx] + text[idx + len("~m" + model) :]).strip()
80
89
  return new_text
81
90
  return None
82
91
 
83
- async def get_input_with_model_completion(prompt_str: str = ">>> ", trigger: str = "~m", history_file: Optional[str] = None) -> str:
92
+
93
+ async def get_input_with_model_completion(
94
+ prompt_str: str = ">>> ", trigger: str = "~m", history_file: Optional[str] = None
95
+ ) -> str:
84
96
  history = FileHistory(os.path.expanduser(history_file)) if history_file else None
85
97
  session = PromptSession(
86
- completer=ModelNameCompleter(trigger), history=history, complete_while_typing=True
98
+ completer=ModelNameCompleter(trigger),
99
+ history=history,
100
+ complete_while_typing=True,
87
101
  )
88
102
  text = await session.prompt_async(prompt_str)
89
103
  possibly_stripped = update_model_in_input(text)