npcpy 1.3.14__tar.gz → 1.3.16__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.
Files changed (93) hide show
  1. {npcpy-1.3.14/npcpy.egg-info → npcpy-1.3.16}/PKG-INFO +1 -1
  2. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/ft/rl.py +28 -15
  3. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/serve.py +3 -3
  4. {npcpy-1.3.14 → npcpy-1.3.16/npcpy.egg-info}/PKG-INFO +1 -1
  5. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy.egg-info/SOURCES.txt +13 -0
  6. {npcpy-1.3.14 → npcpy-1.3.16}/setup.py +1 -1
  7. npcpy-1.3.16/tests/test_browser.py +233 -0
  8. npcpy-1.3.16/tests/test_build_funcs.py +274 -0
  9. npcpy-1.3.16/tests/test_data_models.py +251 -0
  10. npcpy-1.3.16/tests/test_diff.py +360 -0
  11. npcpy-1.3.16/tests/test_genetic_evolver.py +447 -0
  12. {npcpy-1.3.14 → npcpy-1.3.16}/tests/test_load.py +3 -13
  13. npcpy-1.3.16/tests/test_memory_processor.py +268 -0
  14. npcpy-1.3.16/tests/test_ml_funcs.py +288 -0
  15. npcpy-1.3.16/tests/test_model_runner.py +329 -0
  16. npcpy-1.3.16/tests/test_npc_sysenv.py +173 -0
  17. {npcpy-1.3.14 → npcpy-1.3.16}/tests/test_serve.py +3 -0
  18. npcpy-1.3.16/tests/test_sql_adapters.py +171 -0
  19. npcpy-1.3.16/tests/test_sql_compiler.py +287 -0
  20. npcpy-1.3.16/tests/test_sql_functions.py +221 -0
  21. {npcpy-1.3.14 → npcpy-1.3.16}/tests/test_text.py +3 -4
  22. npcpy-1.3.16/tests/test_video.py +133 -0
  23. {npcpy-1.3.14 → npcpy-1.3.16}/LICENSE +0 -0
  24. {npcpy-1.3.14 → npcpy-1.3.16}/MANIFEST.in +0 -0
  25. {npcpy-1.3.14 → npcpy-1.3.16}/README.md +0 -0
  26. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/__init__.py +0 -0
  27. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/build_funcs.py +0 -0
  28. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/data/__init__.py +0 -0
  29. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/data/audio.py +0 -0
  30. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/data/data_models.py +0 -0
  31. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/data/image.py +0 -0
  32. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/data/load.py +0 -0
  33. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/data/text.py +0 -0
  34. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/data/video.py +0 -0
  35. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/data/web.py +0 -0
  36. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/ft/__init__.py +0 -0
  37. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/ft/diff.py +0 -0
  38. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/ft/ge.py +0 -0
  39. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/ft/memory_trainer.py +0 -0
  40. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/ft/model_ensembler.py +0 -0
  41. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/ft/sft.py +0 -0
  42. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/ft/usft.py +0 -0
  43. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/gen/__init__.py +0 -0
  44. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/gen/audio_gen.py +0 -0
  45. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/gen/embeddings.py +0 -0
  46. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/gen/image_gen.py +0 -0
  47. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/gen/ocr.py +0 -0
  48. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/gen/response.py +0 -0
  49. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/gen/video_gen.py +0 -0
  50. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/gen/world_gen.py +0 -0
  51. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/llm_funcs.py +0 -0
  52. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/main.py +0 -0
  53. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/memory/__init__.py +0 -0
  54. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/memory/command_history.py +0 -0
  55. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/memory/kg_vis.py +0 -0
  56. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/memory/knowledge_graph.py +0 -0
  57. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/memory/memory_processor.py +0 -0
  58. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/memory/search.py +0 -0
  59. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/mix/__init__.py +0 -0
  60. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/mix/debate.py +0 -0
  61. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/ml_funcs.py +0 -0
  62. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/npc_array.py +0 -0
  63. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/npc_compiler.py +0 -0
  64. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/npc_sysenv.py +0 -0
  65. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/npcs.py +0 -0
  66. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/sql/__init__.py +0 -0
  67. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/sql/ai_function_tools.py +0 -0
  68. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/sql/database_ai_adapters.py +0 -0
  69. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/sql/database_ai_functions.py +0 -0
  70. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/sql/model_runner.py +0 -0
  71. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/sql/npcsql.py +0 -0
  72. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/sql/sql_model_compiler.py +0 -0
  73. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/tools.py +0 -0
  74. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/work/__init__.py +0 -0
  75. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/work/browser.py +0 -0
  76. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/work/desktop.py +0 -0
  77. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/work/plan.py +0 -0
  78. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy/work/trigger.py +0 -0
  79. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy.egg-info/dependency_links.txt +0 -0
  80. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy.egg-info/requires.txt +0 -0
  81. {npcpy-1.3.14 → npcpy-1.3.16}/npcpy.egg-info/top_level.txt +0 -0
  82. {npcpy-1.3.14 → npcpy-1.3.16}/setup.cfg +0 -0
  83. {npcpy-1.3.14 → npcpy-1.3.16}/tests/test_audio.py +0 -0
  84. {npcpy-1.3.14 → npcpy-1.3.16}/tests/test_command_history.py +0 -0
  85. {npcpy-1.3.14 → npcpy-1.3.16}/tests/test_documentation_examples.py +0 -0
  86. {npcpy-1.3.14 → npcpy-1.3.16}/tests/test_image.py +0 -0
  87. {npcpy-1.3.14 → npcpy-1.3.16}/tests/test_llm_funcs.py +0 -0
  88. {npcpy-1.3.14 → npcpy-1.3.16}/tests/test_npc_array.py +0 -0
  89. {npcpy-1.3.14 → npcpy-1.3.16}/tests/test_npc_compiler.py +0 -0
  90. {npcpy-1.3.14 → npcpy-1.3.16}/tests/test_npcsql.py +0 -0
  91. {npcpy-1.3.14 → npcpy-1.3.16}/tests/test_response.py +0 -0
  92. {npcpy-1.3.14 → npcpy-1.3.16}/tests/test_tools.py +0 -0
  93. {npcpy-1.3.14 → npcpy-1.3.16}/tests/test_web.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: npcpy
3
- Version: 1.3.14
3
+ Version: 1.3.16
4
4
  Summary: npcpy is the premier open-source library for integrating LLMs and Agents into python systems.
5
5
  Home-page: https://github.com/NPC-Worldwide/npcpy
6
6
  Author: Christopher Agostino
@@ -6,27 +6,40 @@ import glob
6
6
  import json
7
7
  import os
8
8
  import pandas as pd
9
+ # Core imports that should always work
9
10
  try:
10
- from datasets import Dataset
11
-
12
- from peft import LoraConfig, PeftModel
13
11
  import torch
14
- from transformers import (
15
- AutoModelForCausalLM,
16
- AutoTokenizer,
17
- BitsAndBytesConfig
18
- )
19
- from trl import DPOTrainer, DPOConfig
20
- except:
21
- Dataset = None
22
- PeftModel = None
23
- DPOConfig = None
24
- DPOTrainer = None
12
+ except ImportError:
25
13
  torch = None
14
+
15
+ try:
16
+ from transformers import AutoModelForCausalLM, AutoTokenizer
17
+ except ImportError:
26
18
  AutoModelForCausalLM = None
27
19
  AutoTokenizer = None
20
+
21
+ try:
22
+ from transformers import BitsAndBytesConfig
23
+ except ImportError:
28
24
  BitsAndBytesConfig = None
29
25
 
26
+ try:
27
+ from datasets import Dataset
28
+ except ImportError:
29
+ Dataset = None
30
+
31
+ try:
32
+ from peft import LoraConfig, PeftModel
33
+ except ImportError:
34
+ LoraConfig = None
35
+ PeftModel = None
36
+
37
+ try:
38
+ from trl import DPOTrainer, DPOConfig
39
+ except ImportError:
40
+ DPOTrainer = None
41
+ DPOConfig = None
42
+
30
43
 
31
44
  import random
32
45
  from typing import List, Dict, Any, Optional, Callable
@@ -344,7 +357,7 @@ def train_with_dpo(
344
357
  args=training_args,
345
358
  train_dataset=preference_dataset,
346
359
  peft_config=peft_config,
347
- tokenizer=tokenizer
360
+ processing_class=tokenizer
348
361
  )
349
362
 
350
363
  print("Starting DPO training...")
@@ -5273,7 +5273,7 @@ def openai_chat_completions():
5273
5273
  current_path = request.headers.get("X-Current-Path", os.getcwd())
5274
5274
 
5275
5275
  # Load team and NPC
5276
- db_path = app.config.get('DB_PATH') or os.path.expanduser("~/.npcsh/npcsh_history.db")
5276
+ db_path = app.config.get('DB_PATH') or os.path.expanduser("~/npcsh_history.db")
5277
5277
  db_conn = create_engine(f'sqlite:///{db_path}')
5278
5278
 
5279
5279
  npc = None
@@ -6110,8 +6110,8 @@ if __name__ == "__main__":
6110
6110
 
6111
6111
  SETTINGS_FILE = Path(os.path.expanduser("~/.npcshrc"))
6112
6112
 
6113
- # Use standard npcsh paths
6114
- db_path = os.path.expanduser("~/.npcsh/npcsh_history.db")
6113
+ # Use environment variable for DB path, or fall back to home directory path (matching Electron app)
6114
+ db_path = os.environ.get('INCOGNIDE_DB_PATH', os.path.expanduser("~/npcsh_history.db"))
6115
6115
  user_npc_directory = os.path.expanduser("~/.npcsh/npc_team")
6116
6116
 
6117
6117
  # Ensure directories exist
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: npcpy
3
- Version: 1.3.14
3
+ Version: 1.3.16
4
4
  Summary: npcpy is the premier open-source library for integrating LLMs and Agents into python systems.
5
5
  Home-page: https://github.com/NPC-Worldwide/npcpy
6
6
  Author: Christopher Agostino
@@ -63,16 +63,29 @@ npcpy/work/desktop.py
63
63
  npcpy/work/plan.py
64
64
  npcpy/work/trigger.py
65
65
  tests/test_audio.py
66
+ tests/test_browser.py
67
+ tests/test_build_funcs.py
66
68
  tests/test_command_history.py
69
+ tests/test_data_models.py
70
+ tests/test_diff.py
67
71
  tests/test_documentation_examples.py
72
+ tests/test_genetic_evolver.py
68
73
  tests/test_image.py
69
74
  tests/test_llm_funcs.py
70
75
  tests/test_load.py
76
+ tests/test_memory_processor.py
77
+ tests/test_ml_funcs.py
78
+ tests/test_model_runner.py
71
79
  tests/test_npc_array.py
72
80
  tests/test_npc_compiler.py
81
+ tests/test_npc_sysenv.py
73
82
  tests/test_npcsql.py
74
83
  tests/test_response.py
75
84
  tests/test_serve.py
85
+ tests/test_sql_adapters.py
86
+ tests/test_sql_compiler.py
87
+ tests/test_sql_functions.py
76
88
  tests/test_text.py
77
89
  tests/test_tools.py
90
+ tests/test_video.py
78
91
  tests/test_web.py
@@ -83,7 +83,7 @@ extra_files = package_files("npcpy/npc_team/")
83
83
 
84
84
  setup(
85
85
  name="npcpy",
86
- version="1.3.14",
86
+ version="1.3.16",
87
87
  packages=find_packages(exclude=["tests*"]),
88
88
  install_requires=base_requirements,
89
89
  extras_require={
@@ -0,0 +1,233 @@
1
+ """Test suite for browser session storage module."""
2
+
3
+ import pytest
4
+
5
+
6
+ class TestBrowserSessions:
7
+ """Test browser session storage functions."""
8
+
9
+ def test_get_sessions_returns_dict(self):
10
+ """Test get_sessions returns a dictionary"""
11
+ from npcpy.work import browser
12
+
13
+ sessions = browser.get_sessions()
14
+
15
+ assert isinstance(sessions, dict)
16
+
17
+ def test_get_current_driver_none_initially(self):
18
+ """Test get_current_driver returns None when no session set"""
19
+ from npcpy.work import browser
20
+
21
+ # Clear any existing sessions
22
+ browser._sessions.clear()
23
+
24
+ driver = browser.get_current_driver()
25
+
26
+ assert driver is None
27
+
28
+ def test_set_driver_stores_session(self):
29
+ """Test set_driver stores the driver"""
30
+ from npcpy.work import browser
31
+
32
+ # Clear any existing sessions
33
+ browser._sessions.clear()
34
+
35
+ # Create a mock driver object
36
+ class MockDriver:
37
+ pass
38
+
39
+ mock_driver = MockDriver()
40
+
41
+ browser.set_driver("session-1", mock_driver)
42
+
43
+ assert "session-1" in browser._sessions
44
+ assert browser._sessions["session-1"] is mock_driver
45
+ assert browser._sessions["current"] == "session-1"
46
+
47
+ def test_set_driver_updates_current(self):
48
+ """Test set_driver updates current session"""
49
+ from npcpy.work import browser
50
+
51
+ browser._sessions.clear()
52
+
53
+ class MockDriver:
54
+ pass
55
+
56
+ browser.set_driver("session-1", MockDriver())
57
+ assert browser._sessions["current"] == "session-1"
58
+
59
+ browser.set_driver("session-2", MockDriver())
60
+ assert browser._sessions["current"] == "session-2"
61
+
62
+ def test_get_current_driver_returns_active_session(self):
63
+ """Test get_current_driver returns the current driver"""
64
+ from npcpy.work import browser
65
+
66
+ browser._sessions.clear()
67
+
68
+ class MockDriver:
69
+ def __init__(self, name):
70
+ self.name = name
71
+
72
+ driver1 = MockDriver("driver1")
73
+ driver2 = MockDriver("driver2")
74
+
75
+ browser.set_driver("session-1", driver1)
76
+ browser.set_driver("session-2", driver2)
77
+
78
+ current = browser.get_current_driver()
79
+
80
+ assert current is driver2
81
+ assert current.name == "driver2"
82
+
83
+ def test_close_current_removes_session(self):
84
+ """Test close_current removes the current session"""
85
+ from npcpy.work import browser
86
+
87
+ browser._sessions.clear()
88
+
89
+ class MockDriver:
90
+ def __init__(self):
91
+ self.quit_called = False
92
+
93
+ def quit(self):
94
+ self.quit_called = True
95
+
96
+ mock_driver = MockDriver()
97
+ browser.set_driver("session-1", mock_driver)
98
+
99
+ result = browser.close_current()
100
+
101
+ assert result is True
102
+ assert "session-1" not in browser._sessions
103
+ assert browser._sessions.get("current") is None
104
+
105
+ def test_close_current_calls_quit(self):
106
+ """Test close_current calls quit on driver"""
107
+ from npcpy.work import browser
108
+
109
+ browser._sessions.clear()
110
+
111
+ quit_called = {"value": False}
112
+
113
+ class MockDriver:
114
+ def quit(self):
115
+ quit_called["value"] = True
116
+
117
+ browser.set_driver("session-1", MockDriver())
118
+ browser.close_current()
119
+
120
+ assert quit_called["value"] is True
121
+
122
+ def test_close_current_handles_quit_error(self):
123
+ """Test close_current handles quit errors gracefully"""
124
+ from npcpy.work import browser
125
+
126
+ browser._sessions.clear()
127
+
128
+ class MockDriver:
129
+ def quit(self):
130
+ raise Exception("Browser already closed")
131
+
132
+ browser.set_driver("session-1", MockDriver())
133
+
134
+ # Should not raise, should return True
135
+ result = browser.close_current()
136
+
137
+ assert result is True
138
+
139
+ def test_close_current_no_session(self):
140
+ """Test close_current returns False when no session"""
141
+ from npcpy.work import browser
142
+
143
+ browser._sessions.clear()
144
+
145
+ result = browser.close_current()
146
+
147
+ assert result is False
148
+
149
+ def test_multiple_sessions_stored(self):
150
+ """Test multiple sessions can be stored"""
151
+ from npcpy.work import browser
152
+
153
+ browser._sessions.clear()
154
+
155
+ class MockDriver:
156
+ pass
157
+
158
+ browser.set_driver("session-1", MockDriver())
159
+ browser.set_driver("session-2", MockDriver())
160
+ browser.set_driver("session-3", MockDriver())
161
+
162
+ sessions = browser.get_sessions()
163
+
164
+ assert "session-1" in sessions
165
+ assert "session-2" in sessions
166
+ assert "session-3" in sessions
167
+ assert sessions["current"] == "session-3"
168
+
169
+ def test_get_current_driver_invalid_current(self):
170
+ """Test get_current_driver handles invalid current reference"""
171
+ from npcpy.work import browser
172
+
173
+ browser._sessions.clear()
174
+
175
+ # Set current to non-existent session
176
+ browser._sessions["current"] = "nonexistent"
177
+
178
+ driver = browser.get_current_driver()
179
+
180
+ assert driver is None
181
+
182
+
183
+ class TestBrowserModuleImports:
184
+ """Test browser module imports correctly."""
185
+
186
+ def test_module_imports(self):
187
+ """Test browser module can be imported"""
188
+ from npcpy.work import browser
189
+
190
+ assert hasattr(browser, "get_sessions")
191
+ assert hasattr(browser, "get_current_driver")
192
+ assert hasattr(browser, "set_driver")
193
+ assert hasattr(browser, "close_current")
194
+
195
+ def test_sessions_is_module_level(self):
196
+ """Test _sessions is module-level dict"""
197
+ from npcpy.work import browser
198
+
199
+ assert hasattr(browser, "_sessions")
200
+ assert isinstance(browser._sessions, dict)
201
+
202
+
203
+ class TestSessionIsolation:
204
+ """Test session isolation between tests."""
205
+
206
+ def test_sessions_can_be_cleared(self):
207
+ """Test sessions dict can be cleared"""
208
+ from npcpy.work import browser
209
+
210
+ browser._sessions["test"] = "value"
211
+ browser._sessions.clear()
212
+
213
+ assert len(browser._sessions) == 0
214
+
215
+ def test_sessions_persist_in_module(self):
216
+ """Test sessions persist across function calls"""
217
+ from npcpy.work import browser
218
+
219
+ browser._sessions.clear()
220
+
221
+ class MockDriver:
222
+ pass
223
+
224
+ browser.set_driver("persistent", MockDriver())
225
+
226
+ # Get sessions in separate call
227
+ sessions = browser.get_sessions()
228
+
229
+ assert "persistent" in sessions
230
+
231
+
232
+ if __name__ == "__main__":
233
+ pytest.main([__file__, "-v"])
@@ -0,0 +1,274 @@
1
+ """Test suite for build_funcs module."""
2
+
3
+ import os
4
+ import tempfile
5
+ import shutil
6
+ import pytest
7
+ import yaml
8
+
9
+
10
+ class TestGetTeamName:
11
+ """Test get_team_name function."""
12
+
13
+ def test_get_team_name_from_ctx_file(self):
14
+ """Test extracting team name from .ctx file"""
15
+ from npcpy.build_funcs import get_team_name
16
+
17
+ temp_dir = tempfile.mkdtemp()
18
+ try:
19
+ # Create a .ctx file with team name
20
+ ctx_content = {"name": "My Awesome Team", "version": "1.0"}
21
+ ctx_path = os.path.join(temp_dir, "team.ctx")
22
+ with open(ctx_path, "w") as f:
23
+ yaml.dump(ctx_content, f)
24
+
25
+ name = get_team_name(temp_dir)
26
+ assert name == "My Awesome Team"
27
+ finally:
28
+ shutil.rmtree(temp_dir)
29
+
30
+ def test_get_team_name_from_folder(self):
31
+ """Test getting team name from folder when no .ctx file"""
32
+ from npcpy.build_funcs import get_team_name
33
+
34
+ temp_dir = tempfile.mkdtemp()
35
+ try:
36
+ # Create a subfolder to test
37
+ team_dir = os.path.join(temp_dir, "my_team")
38
+ os.makedirs(team_dir)
39
+
40
+ name = get_team_name(team_dir)
41
+ assert name == "my_team"
42
+ finally:
43
+ shutil.rmtree(temp_dir)
44
+
45
+ def test_get_team_name_npc_team_fallback(self):
46
+ """Test fallback when folder is named npc_team"""
47
+ from npcpy.build_funcs import get_team_name
48
+
49
+ temp_dir = tempfile.mkdtemp()
50
+ try:
51
+ npc_team_dir = os.path.join(temp_dir, "npc_team")
52
+ os.makedirs(npc_team_dir)
53
+
54
+ name = get_team_name(npc_team_dir)
55
+ # Should use parent folder name
56
+ assert name == os.path.basename(temp_dir)
57
+ finally:
58
+ shutil.rmtree(temp_dir)
59
+
60
+
61
+ class TestBuildDockerfile:
62
+ """Test build_dockerfile function."""
63
+
64
+ def test_build_dockerfile_default(self):
65
+ """Test Dockerfile generation with defaults"""
66
+ from npcpy.build_funcs import build_dockerfile
67
+
68
+ temp_dir = tempfile.mkdtemp()
69
+ try:
70
+ team_dir = os.path.join(temp_dir, "test_team")
71
+ os.makedirs(team_dir)
72
+
73
+ config = {"team_path": team_dir}
74
+ dockerfile = build_dockerfile(config)
75
+
76
+ assert "FROM python:3.11-slim" in dockerfile
77
+ assert "pip install --no-cache-dir npcsh" in dockerfile
78
+ assert "EXPOSE 5337" in dockerfile
79
+ assert "test_team" in dockerfile
80
+ finally:
81
+ shutil.rmtree(temp_dir)
82
+
83
+ def test_build_dockerfile_custom_port(self):
84
+ """Test Dockerfile with custom port"""
85
+ from npcpy.build_funcs import build_dockerfile
86
+
87
+ temp_dir = tempfile.mkdtemp()
88
+ try:
89
+ team_dir = os.path.join(temp_dir, "my_team")
90
+ os.makedirs(team_dir)
91
+
92
+ config = {"team_path": team_dir, "port": 8080}
93
+ dockerfile = build_dockerfile(config)
94
+
95
+ assert "EXPOSE 8080" in dockerfile
96
+ assert '"8080"' in dockerfile
97
+ finally:
98
+ shutil.rmtree(temp_dir)
99
+
100
+
101
+ class TestBuildDockerCompose:
102
+ """Test build_docker_compose function."""
103
+
104
+ def test_build_docker_compose_creates_files(self):
105
+ """Test docker-compose build creates all expected files"""
106
+ from npcpy.build_funcs import build_docker_compose
107
+
108
+ temp_dir = tempfile.mkdtemp()
109
+ try:
110
+ team_dir = os.path.join(temp_dir, "test_team")
111
+ os.makedirs(team_dir)
112
+ # Create a dummy file in team dir
113
+ with open(os.path.join(team_dir, "test.npc"), "w") as f:
114
+ f.write("name: test\n")
115
+
116
+ output_dir = os.path.join(temp_dir, "build")
117
+ config = {"team_path": team_dir, "output_dir": output_dir}
118
+
119
+ result = build_docker_compose(config)
120
+
121
+ # Check files were created
122
+ assert os.path.exists(os.path.join(output_dir, "Dockerfile"))
123
+ assert os.path.exists(os.path.join(output_dir, "docker-compose.yml"))
124
+ assert os.path.exists(os.path.join(output_dir, ".env.example"))
125
+ assert os.path.exists(os.path.join(output_dir, "test_team"))
126
+
127
+ # Check output message
128
+ assert "output" in result
129
+ assert "Docker deployment created" in result["output"]
130
+ finally:
131
+ shutil.rmtree(temp_dir)
132
+
133
+ def test_build_docker_compose_with_cors(self):
134
+ """Test docker-compose with CORS origins"""
135
+ from npcpy.build_funcs import build_docker_compose
136
+
137
+ temp_dir = tempfile.mkdtemp()
138
+ try:
139
+ team_dir = os.path.join(temp_dir, "test_team")
140
+ os.makedirs(team_dir)
141
+
142
+ output_dir = os.path.join(temp_dir, "build")
143
+ config = {
144
+ "team_path": team_dir,
145
+ "output_dir": output_dir,
146
+ "cors_origins": ["http://localhost:3000", "https://example.com"],
147
+ }
148
+
149
+ build_docker_compose(config)
150
+
151
+ # Check docker-compose.yml contains CORS
152
+ compose_path = os.path.join(output_dir, "docker-compose.yml")
153
+ with open(compose_path, "r") as f:
154
+ content = f.read()
155
+
156
+ assert "CORS_ORIGINS" in content
157
+ assert "localhost:3000" in content
158
+ finally:
159
+ shutil.rmtree(temp_dir)
160
+
161
+
162
+ class TestBuildFlaskServer:
163
+ """Test build_flask_server function."""
164
+
165
+ def test_build_flask_server_creates_script(self):
166
+ """Test Flask server script generation"""
167
+ from npcpy.build_funcs import build_flask_server
168
+
169
+ temp_dir = tempfile.mkdtemp()
170
+ try:
171
+ team_dir = os.path.join(temp_dir, "test_team")
172
+ os.makedirs(team_dir)
173
+
174
+ output_dir = os.path.join(temp_dir, "build")
175
+ config = {"team_path": team_dir, "output_dir": output_dir, "port": 5000}
176
+
177
+ result = build_flask_server(config)
178
+
179
+ # Check server.py was created
180
+ server_path = os.path.join(output_dir, "server.py")
181
+ assert os.path.exists(server_path)
182
+
183
+ # Check content
184
+ with open(server_path, "r") as f:
185
+ content = f.read()
186
+
187
+ assert "from npcpy.serve import start_flask_server" in content
188
+ assert "port=5000" in content
189
+
190
+ # Check output message
191
+ assert "Flask server created" in result["output"]
192
+ finally:
193
+ shutil.rmtree(temp_dir)
194
+
195
+
196
+ class TestBuildCliExecutable:
197
+ """Test build_cli_executable function."""
198
+
199
+ def test_build_cli_executable_creates_scripts(self):
200
+ """Test CLI script generation for NPCs"""
201
+ from npcpy.build_funcs import build_cli_executable
202
+
203
+ temp_dir = tempfile.mkdtemp()
204
+ try:
205
+ team_dir = os.path.join(temp_dir, "test_team")
206
+ os.makedirs(team_dir)
207
+
208
+ # Create some .npc files
209
+ for name in ["assistant", "coder", "reviewer"]:
210
+ npc_path = os.path.join(team_dir, f"{name}.npc")
211
+ with open(npc_path, "w") as f:
212
+ f.write(f"name: {name}\n")
213
+
214
+ output_dir = os.path.join(temp_dir, "build")
215
+ config = {"team_path": team_dir, "output_dir": output_dir}
216
+
217
+ result = build_cli_executable(config)
218
+
219
+ # Check scripts were created
220
+ assert os.path.exists(os.path.join(output_dir, "assistant"))
221
+ assert os.path.exists(os.path.join(output_dir, "coder"))
222
+ assert os.path.exists(os.path.join(output_dir, "reviewer"))
223
+
224
+ # Check output message
225
+ assert "CLI scripts created" in result["output"]
226
+ assert "assistant" in result["output"]
227
+ finally:
228
+ shutil.rmtree(temp_dir)
229
+
230
+
231
+ class TestBuildStaticSite:
232
+ """Test build_static_site function."""
233
+
234
+ def test_build_static_site_creates_html(self):
235
+ """Test static site HTML generation"""
236
+ from npcpy.build_funcs import build_static_site
237
+
238
+ temp_dir = tempfile.mkdtemp()
239
+ try:
240
+ team_dir = os.path.join(temp_dir, "test_team")
241
+ os.makedirs(team_dir)
242
+
243
+ # Create .ctx file with team name
244
+ ctx_content = {"name": "Documentation Team"}
245
+ with open(os.path.join(team_dir, "team.ctx"), "w") as f:
246
+ yaml.dump(ctx_content, f)
247
+
248
+ # Create .npc files
249
+ npc_content = {"name": "writer", "primary_directive": "Write documentation"}
250
+ with open(os.path.join(team_dir, "writer.npc"), "w") as f:
251
+ yaml.dump(npc_content, f)
252
+
253
+ output_dir = os.path.join(temp_dir, "build")
254
+ config = {"team_path": team_dir, "output_dir": output_dir}
255
+
256
+ result = build_static_site(config)
257
+
258
+ # Check index.html was created
259
+ html_path = os.path.join(output_dir, "index.html")
260
+ assert os.path.exists(html_path)
261
+
262
+ # Check content
263
+ with open(html_path, "r") as f:
264
+ content = f.read()
265
+
266
+ assert "Documentation Team" in content
267
+ assert "writer" in content
268
+ assert "Write documentation" in content
269
+ finally:
270
+ shutil.rmtree(temp_dir)
271
+
272
+
273
+ if __name__ == "__main__":
274
+ pytest.main([__file__, "-v"])