npcsh 1.1.2__tar.gz → 1.1.3__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 (55) hide show
  1. {npcsh-1.1.2 → npcsh-1.1.3}/PKG-INFO +10 -1
  2. {npcsh-1.1.2 → npcsh-1.1.3}/README.md +9 -0
  3. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/_state.py +0 -29
  4. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/alicanto.py +10 -5
  5. npcsh-1.1.3/npcsh/build.py +291 -0
  6. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/corca.py +263 -154
  7. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc.py +127 -46
  8. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/routes.py +229 -21
  9. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh.egg-info/PKG-INFO +10 -1
  10. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh.egg-info/SOURCES.txt +1 -0
  11. {npcsh-1.1.2 → npcsh-1.1.3}/setup.py +1 -1
  12. {npcsh-1.1.2 → npcsh-1.1.3}/LICENSE +0 -0
  13. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/__init__.py +0 -0
  14. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/guac.py +0 -0
  15. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/mcp_helpers.py +0 -0
  16. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/mcp_server.py +0 -0
  17. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/alicanto.npc +0 -0
  18. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/alicanto.png +0 -0
  19. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/corca.npc +0 -0
  20. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/corca.png +0 -0
  21. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/foreman.npc +0 -0
  22. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/frederic.npc +0 -0
  23. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/frederic4.png +0 -0
  24. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/guac.png +0 -0
  25. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/jinxs/bash_executer.jinx +0 -0
  26. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/jinxs/edit_file.jinx +0 -0
  27. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/jinxs/image_generation.jinx +0 -0
  28. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/jinxs/internet_search.jinx +0 -0
  29. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/jinxs/kg_search.jinx +0 -0
  30. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/jinxs/memory_search.jinx +0 -0
  31. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/jinxs/python_executor.jinx +0 -0
  32. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/jinxs/screen_cap.jinx +0 -0
  33. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/kadiefa.npc +0 -0
  34. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/kadiefa.png +0 -0
  35. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/npcsh.ctx +0 -0
  36. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/npcsh_sibiji.png +0 -0
  37. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/plonk.npc +0 -0
  38. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/plonk.png +0 -0
  39. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/plonkjr.npc +0 -0
  40. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/plonkjr.png +0 -0
  41. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/sibiji.npc +0 -0
  42. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/sibiji.png +0 -0
  43. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/spool.png +0 -0
  44. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npc_team/yap.png +0 -0
  45. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/npcsh.py +0 -0
  46. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/plonk.py +0 -0
  47. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/pti.py +0 -0
  48. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/spool.py +0 -0
  49. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/wander.py +0 -0
  50. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh/yap.py +0 -0
  51. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh.egg-info/dependency_links.txt +0 -0
  52. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh.egg-info/entry_points.txt +0 -0
  53. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh.egg-info/requires.txt +0 -0
  54. {npcsh-1.1.2 → npcsh-1.1.3}/npcsh.egg-info/top_level.txt +0 -0
  55. {npcsh-1.1.2 → npcsh-1.1.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: npcsh
3
- Version: 1.1.2
3
+ Version: 1.1.3
4
4
  Summary: npcsh is a command-line toolkit for using AI agents in novel ways.
5
5
  Home-page: https://github.com/NPC-Worldwide/npcsh
6
6
  Author: Christopher Agostino
@@ -194,6 +194,14 @@ and you will enter the NPC shell. Additionally, the pip installation includes th
194
194
  /corca --mcp-server-path /path.to.server.py
195
195
  ```
196
196
 
197
+ - **Build an NPC Team**:
198
+
199
+ ``` bash
200
+ npc build flask --output ./dist --port 5337
201
+ npc build docker --output ./deploy
202
+ npc build cli --output ./bin
203
+ npc build static --api_url https://api.example.com
204
+ ```
197
205
 
198
206
  # NPC Data Layer
199
207
 
@@ -217,6 +225,7 @@ Importantly, users can switch easily between the NPCs they are chatting with by
217
225
  - activated by invoking `/<command> ...` in `npcsh`, macros can be called in bash or through the `npc` CLI. In our examples, we provide both `npcsh` calls as well as bash calls with the `npc` cli where relevant. For converting any `/<command>` in `npcsh` to a bash version, replace the `/` with `npc ` and the macro command will be invoked as a positional argument. Some, like breathe, flush,
218
226
 
219
227
  - `/alicanto` - Conduct deep research with multiple perspectives, identifying gold insights and cliff warnings. Usage: `/alicanto 'query to be researched' --num-npcs <int> --depth <int>`
228
+ - `/build` - Builds the current npc team to an executable format . Usage: `/build <output[flask,docker,cli,static]> --options`
220
229
  - `/brainblast` - Execute an advanced chunked search on command history. Usage: `/brainblast 'query' --top_k 10`
221
230
  - `/breathe` - Condense context on a regular cadence. Usage: `/breathe -p <provider: NPCSH_CHAT_PROVIDER> -m <model: NPCSH_CHAT_MODEL>`
222
231
  - `/compile` - Compile NPC profiles. Usage: `/compile <path_to_npc> `
@@ -94,6 +94,14 @@ and you will enter the NPC shell. Additionally, the pip installation includes th
94
94
  /corca --mcp-server-path /path.to.server.py
95
95
  ```
96
96
 
97
+ - **Build an NPC Team**:
98
+
99
+ ``` bash
100
+ npc build flask --output ./dist --port 5337
101
+ npc build docker --output ./deploy
102
+ npc build cli --output ./bin
103
+ npc build static --api_url https://api.example.com
104
+ ```
97
105
 
98
106
  # NPC Data Layer
99
107
 
@@ -117,6 +125,7 @@ Importantly, users can switch easily between the NPCs they are chatting with by
117
125
  - activated by invoking `/<command> ...` in `npcsh`, macros can be called in bash or through the `npc` CLI. In our examples, we provide both `npcsh` calls as well as bash calls with the `npc` cli where relevant. For converting any `/<command>` in `npcsh` to a bash version, replace the `/` with `npc ` and the macro command will be invoked as a positional argument. Some, like breathe, flush,
118
126
 
119
127
  - `/alicanto` - Conduct deep research with multiple perspectives, identifying gold insights and cliff warnings. Usage: `/alicanto 'query to be researched' --num-npcs <int> --depth <int>`
128
+ - `/build` - Builds the current npc team to an executable format . Usage: `/build <output[flask,docker,cli,static]> --options`
120
129
  - `/brainblast` - Execute an advanced chunked search on command history. Usage: `/brainblast 'query' --top_k 10`
121
130
  - `/breathe` - Condense context on a regular cadence. Usage: `/breathe -p <provider: NPCSH_CHAT_PROVIDER> -m <model: NPCSH_CHAT_MODEL>`
122
131
  - `/compile` - Compile NPC profiles. Usage: `/compile <path_to_npc> `
@@ -645,7 +645,6 @@ BASH_COMMANDS = [
645
645
  "fg",
646
646
  "getopts",
647
647
  "hash",
648
- "help",
649
648
  "history",
650
649
  "if",
651
650
  "jobs",
@@ -2206,34 +2205,6 @@ def execute_command(
2206
2205
  npc_name = state.npc.name if isinstance(state.npc, NPC) else "__none__"
2207
2206
  team_name = state.team.name if state.team else "__none__"
2208
2207
 
2209
- if command_history:
2210
- relevant_memories = get_relevant_memories(
2211
- command_history=command_history,
2212
- npc_name=npc_name,
2213
- team_name=team_name,
2214
- path=state.current_path,
2215
- query=command,
2216
- max_memories=5,
2217
- state=state
2218
- )
2219
- print('Memory jogged...')
2220
- print(relevant_memories)
2221
-
2222
- if relevant_memories:
2223
- memory_context = "\n".join([
2224
- f"- {m.get('final_memory', '')}"
2225
- for m in relevant_memories
2226
- ])
2227
- memory_msg = {
2228
- "role": "system",
2229
- "content": f"Relevant memories:\n{memory_context}"
2230
- }
2231
- if not state.messages or \
2232
- state.messages[0].get("role") != "system":
2233
- state.messages.insert(0, memory_msg)
2234
- else:
2235
- state.messages[0]["content"] += \
2236
- f"\n\n{memory_msg['content']}"
2237
2208
 
2238
2209
  original_command_for_embedding = command
2239
2210
  commands = split_by_pipes(command)
@@ -12,6 +12,16 @@ from dataclasses import dataclass, asdict, field
12
12
  from pathlib import Path
13
13
  from concurrent.futures import ThreadPoolExecutor
14
14
 
15
+
16
+ try:
17
+ from datasets import load_dataset
18
+ except:
19
+ load_dataset = None
20
+ from sklearn.feature_extraction.text import TfidfVectorizer
21
+ from sklearn.metrics.pairwise import cosine_similarity
22
+
23
+
24
+
15
25
  from npcpy.tools import auto_tools
16
26
  from npcpy.llm_funcs import get_llm_response
17
27
  from npcpy.data.web import search_web
@@ -88,11 +98,6 @@ def list_files(directory: str = ".") -> List[str]:
88
98
  return os.listdir(directory)
89
99
 
90
100
 
91
-
92
- from datasets import load_dataset
93
- from sklearn.feature_extraction.text import TfidfVectorizer
94
- from sklearn.metrics.pairwise import cosine_similarity
95
-
96
101
  DATASET_CACHE = None
97
102
  SEARCH_INDEX = None
98
103
 
@@ -0,0 +1,291 @@
1
+ import os
2
+ import shutil
3
+ import textwrap
4
+ from pathlib import Path
5
+
6
+
7
+ def build_flask_server(config, **kwargs):
8
+ output_dir = Path(config['output_dir'])
9
+ output_dir.mkdir(parents=True, exist_ok=True)
10
+
11
+ server_script = output_dir / 'npc_server.py'
12
+
13
+ server_code = textwrap.dedent(f'''
14
+ import os
15
+ from npcpy.serve import start_flask_server
16
+ from npcpy.npc_compiler import Team
17
+ from sqlalchemy import create_engine
18
+
19
+ if __name__ == "__main__":
20
+ team_path = os.path.join(
21
+ os.path.dirname(__file__),
22
+ "npc_team"
23
+ )
24
+ db_path = os.path.expanduser("~/npcsh_history.db")
25
+
26
+ db_conn = create_engine(f'sqlite:///{{db_path}}')
27
+ team = Team(team_path=team_path, db_conn=db_conn)
28
+
29
+ start_flask_server(
30
+ port={config['port']},
31
+ cors_origins={config.get('cors_origins')},
32
+ teams={{"main": team}},
33
+ npcs=team.npcs,
34
+ db_path=db_path,
35
+ user_npc_directory=os.path.expanduser(
36
+ "~/.npcsh/npc_team"
37
+ )
38
+ )
39
+ ''')
40
+
41
+ server_script.write_text(server_code)
42
+
43
+ shutil.copytree(
44
+ config['team_path'],
45
+ output_dir / 'npc_team',
46
+ dirs_exist_ok=True
47
+ )
48
+
49
+ requirements = output_dir / 'requirements.txt'
50
+ requirements.write_text(
51
+ 'npcsh\n'
52
+ 'flask\n'
53
+ 'flask-cors\n'
54
+ 'sqlalchemy\n'
55
+ )
56
+
57
+ readme = output_dir / 'README.md'
58
+ readme.write_text(textwrap.dedent(f'''
59
+ # NPC Team Server
60
+
61
+ Run: python npc_server.py
62
+
63
+ Server will be available at http://localhost:{config['port']}
64
+
65
+ For pyinstaller standalone:
66
+ pyinstaller --onefile npc_server.py
67
+ '''))
68
+
69
+ return {
70
+ "output": f"Flask server built in {output_dir}",
71
+ "messages": kwargs.get('messages', [])
72
+ }
73
+
74
+
75
+ def build_docker_compose(config, **kwargs):
76
+ output_dir = Path(config['output_dir'])
77
+ output_dir.mkdir(parents=True, exist_ok=True)
78
+
79
+ shutil.copytree(
80
+ config['team_path'],
81
+ output_dir / 'npc_team',
82
+ dirs_exist_ok=True
83
+ )
84
+
85
+ dockerfile = output_dir / 'Dockerfile'
86
+ dockerfile.write_text(textwrap.dedent('''
87
+ FROM python:3.11-slim
88
+
89
+ WORKDIR /app
90
+
91
+ COPY requirements.txt .
92
+ RUN pip install --no-cache-dir -r requirements.txt
93
+
94
+ COPY npc_team ./npc_team
95
+ COPY npc_server.py .
96
+
97
+ EXPOSE 5337
98
+
99
+ CMD ["python", "npc_server.py"]
100
+ '''))
101
+
102
+ compose = output_dir / 'docker-compose.yml'
103
+ compose.write_text(textwrap.dedent(f'''
104
+ version: '3.8'
105
+
106
+ services:
107
+ npc-server:
108
+ build: .
109
+ ports:
110
+ - "{config['port']}:{config['port']}"
111
+ volumes:
112
+ - npc-data:/root/.npcsh
113
+ environment:
114
+ - NPCSH_DB_PATH=/root/.npcsh/npcsh_history.db
115
+
116
+ volumes:
117
+ npc-data:
118
+ '''))
119
+
120
+ build_flask_server(config, **kwargs)
121
+
122
+ return {
123
+ "output": f"Docker compose built in {output_dir}. Run: docker-compose up",
124
+ "messages": kwargs.get('messages', [])
125
+ }
126
+
127
+
128
+ def build_cli_executable(config, **kwargs):
129
+ output_dir = Path(config['output_dir'])
130
+ output_dir.mkdir(parents=True, exist_ok=True)
131
+
132
+ cli_script = output_dir / 'npc_cli.py'
133
+
134
+ cli_code = textwrap.dedent('''
135
+ import sys
136
+ from npcsh._state import setup_shell, execute_command, initial_state
137
+ from npcsh.routes import router
138
+
139
+ def main():
140
+ if len(sys.argv) < 2:
141
+ print("Usage: npc_cli <command>")
142
+ sys.exit(1)
143
+
144
+ command = " ".join(sys.argv[1:])
145
+
146
+ command_history, team, npc = setup_shell()
147
+ initial_state.npc = npc
148
+ initial_state.team = team
149
+
150
+ state, result = execute_command(
151
+ command,
152
+ initial_state,
153
+ router=router
154
+ )
155
+
156
+ output = result.get('output') if isinstance(result, dict) else result
157
+ print(output)
158
+
159
+ if __name__ == "__main__":
160
+ main()
161
+ ''')
162
+
163
+ cli_script.write_text(cli_code)
164
+
165
+ shutil.copytree(
166
+ config['team_path'],
167
+ output_dir / 'npc_team',
168
+ dirs_exist_ok=True
169
+ )
170
+
171
+ spec_file = output_dir / 'npc_cli.spec'
172
+ spec_file.write_text(textwrap.dedent('''
173
+ a = Analysis(
174
+ ['npc_cli.py'],
175
+ pathex=[],
176
+ binaries=[],
177
+ datas=[('npc_team', 'npc_team')],
178
+ hiddenimports=[],
179
+ hookspath=[],
180
+ hooksconfig={},
181
+ runtime_hooks=[],
182
+ excludes=[],
183
+ win_no_prefer_redirects=False,
184
+ win_private_assemblies=False,
185
+ cipher=None,
186
+ noarchive=False,
187
+ )
188
+ pyz = PYZ(a.pure, a.zipped_data, cipher=None)
189
+
190
+ exe = EXE(
191
+ pyz,
192
+ a.scripts,
193
+ a.binaries,
194
+ a.zipfiles,
195
+ a.datas,
196
+ [],
197
+ name='npc',
198
+ debug=False,
199
+ bootloader_ignore_signals=False,
200
+ strip=False,
201
+ upx=True,
202
+ upx_exclude=[],
203
+ runtime_tmpdir=None,
204
+ console=True,
205
+ )
206
+ '''))
207
+
208
+ return {
209
+ "output": (
210
+ f"CLI executable built in {output_dir}. "
211
+ f"Run: pyinstaller npc_cli.spec"
212
+ ),
213
+ "messages": kwargs.get('messages', [])
214
+ }
215
+
216
+
217
+ def build_static_site(config, **kwargs):
218
+ output_dir = Path(config['output_dir'])
219
+ output_dir.mkdir(parents=True, exist_ok=True)
220
+
221
+ html = output_dir / 'index.html'
222
+ html.write_text(textwrap.dedent(f'''
223
+ <!DOCTYPE html>
224
+ <html>
225
+ <head>
226
+ <title>NPC Team Interface</title>
227
+ <style>
228
+ body {{
229
+ font-family: monospace;
230
+ max-width: 800px;
231
+ margin: 50px auto;
232
+ }}
233
+ #output {{
234
+ white-space: pre-wrap;
235
+ background: #f5f5f5;
236
+ padding: 20px;
237
+ min-height: 300px;
238
+ }}
239
+ </style>
240
+ </head>
241
+ <body>
242
+ <h1>NPC Team</h1>
243
+ <input id="input" type="text"
244
+ placeholder="Enter command..."
245
+ style="width: 100%; padding: 10px;">
246
+ <div id="output"></div>
247
+
248
+ <script>
249
+ const API_URL = '{config.get("api_url", "http://localhost:5337")}';
250
+
251
+ document.getElementById('input').addEventListener('keypress',
252
+ async (e) => {{
253
+ if (e.key === 'Enter') {{
254
+ const cmd = e.target.value;
255
+ e.target.value = '';
256
+
257
+ const resp = await fetch(`${{API_URL}}/api/stream`, {{
258
+ method: 'POST',
259
+ headers: {{'Content-Type': 'application/json'}},
260
+ body: JSON.stringify({{
261
+ commandstr: cmd,
262
+ conversationId: 'web-session',
263
+ model: 'llama3.2',
264
+ provider: 'ollama'
265
+ }})
266
+ }});
267
+
268
+ const reader = resp.body.getReader();
269
+ const decoder = new TextDecoder();
270
+
271
+ while (true) {{
272
+ const {{done, value}} = await reader.read();
273
+ if (done) break;
274
+
275
+ const text = decoder.decode(value);
276
+ document.getElementById('output').textContent += text;
277
+ }}
278
+ }}
279
+ }});
280
+ </script>
281
+ </body>
282
+ </html>
283
+ '''))
284
+
285
+ return {
286
+ "output": (
287
+ f"Static site built in {output_dir}. "
288
+ f"Serve with: python -m http.server 8000"
289
+ ),
290
+ "messages": kwargs.get('messages', [])
291
+ }