preppergpt 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/bin/preppergpt.js +8 -0
- package/compose/preppergpt.yaml +232 -0
- package/docs/hardware.md +15 -0
- package/docs/model-sources.md +12 -0
- package/docs/preppergpt-local-parity-map.md +16 -0
- package/docs/publishing.md +24 -0
- package/installer/cli.mjs +225 -0
- package/installer/install.sh +18 -0
- package/installer/lib/detect.mjs +128 -0
- package/installer/lib/paths.mjs +26 -0
- package/installer/lib/planner.mjs +175 -0
- package/installer/lib/render.mjs +76 -0
- package/installer/lib/util.mjs +84 -0
- package/package.json +48 -0
- package/profiles/models.json +277 -0
- package/services/comfyui/flux-kontext-edit-openwebui-nodes.json +46 -0
- package/services/comfyui/flux-kontext-edit-openwebui-workflow.json +245 -0
- package/services/comfyui/flux-kontext-mask-edit-openwebui-nodes.json +51 -0
- package/services/comfyui/flux-kontext-mask-edit-openwebui-workflow.json +322 -0
- package/services/comfyui/flux2-klein-9b-openwebui-nodes.json +58 -0
- package/services/comfyui/flux2-klein-9b-openwebui-workflow.json +141 -0
- package/services/comfyui/image-invert-edit-openwebui-nodes.json +23 -0
- package/services/comfyui/image-invert-edit-openwebui-workflow.json +52 -0
- package/services/deep-research/Dockerfile +7 -0
- package/services/deep-research/app.py +1913 -0
- package/services/local-agent/Dockerfile +17 -0
- package/services/local-agent/app.py +2311 -0
- package/services/local-scheduler/Dockerfile +8 -0
- package/services/local-scheduler/app.py +15774 -0
- package/services/local-vision/Dockerfile +11 -0
- package/services/local-vision/app.py +888 -0
- package/services/searxng/settings.yml +16 -0
- package/themes/preppergpt/custom.css +15 -0
- package/themes/preppergpt/static/favicon.svg +5 -0
- package/themes/preppergpt/static/logo.svg +6 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Teamslop
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# PrepperGPT
|
|
2
|
+
|
|
3
|
+
PrepperGPT packages a local-first ChatGPT-like experience for Linux machines.
|
|
4
|
+
It uses upstream OpenWebUI for the app shell and adds a hardware detector,
|
|
5
|
+
model planner, Docker Compose runtime, local sidecars, and a practical
|
|
6
|
+
PrepperGPT field-kit theme.
|
|
7
|
+
|
|
8
|
+
The first release targets Linux with NVIDIA GPUs first, with CPU fallback where
|
|
9
|
+
possible. It is an online installer: model and container downloads require a
|
|
10
|
+
working network during setup.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
Until the npm package is published, install from GitHub:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
git clone https://github.com/teamslop/preppergpt.git
|
|
18
|
+
cd preppergpt
|
|
19
|
+
node bin/preppergpt.js install --profile balanced
|
|
20
|
+
node bin/preppergpt.js start
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
After npm publication:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx preppergpt install --profile balanced
|
|
27
|
+
preppergpt start
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Other profiles:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
preppergpt install --profile intelligence
|
|
34
|
+
preppergpt install --profile speed
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Open the app at:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
http://127.0.0.1:8080
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Default local admin credentials are written to `~/.preppergpt/.env.preppergpt`.
|
|
44
|
+
Change them before exposing the machine to any network.
|
|
45
|
+
|
|
46
|
+
## Commands
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
preppergpt detect
|
|
50
|
+
preppergpt plan --profile balanced
|
|
51
|
+
preppergpt install --profile balanced
|
|
52
|
+
preppergpt start
|
|
53
|
+
preppergpt stop
|
|
54
|
+
preppergpt status
|
|
55
|
+
preppergpt doctor
|
|
56
|
+
preppergpt switch-profile --profile speed
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Profiles
|
|
60
|
+
|
|
61
|
+
- `intelligence`: chooses the strongest local reasoning route that fits the
|
|
62
|
+
machine, preferring GLM 5.2 Q4 and long-context coding routes when available.
|
|
63
|
+
- `speed`: chooses smaller GPU-friendly routes and makes low-latency chat the
|
|
64
|
+
default.
|
|
65
|
+
- `balanced`: uses the local auto-router as the default and keeps reasoning,
|
|
66
|
+
coding, research, vision, image, and STT routes additive.
|
|
67
|
+
|
|
68
|
+
The planner never removes existing OpenWebUI models. It writes additive defaults
|
|
69
|
+
and route ordering into the generated compose override.
|
|
70
|
+
|
|
71
|
+
## Model Assets
|
|
72
|
+
|
|
73
|
+
Some routes can be pulled by the runtime, while very large routes such as GLM
|
|
74
|
+
5.2 Q4 and Flux weights are marked as manual or external in
|
|
75
|
+
`profiles/models.json`. `preppergpt doctor` reports which selected routes still
|
|
76
|
+
need local files or endpoints.
|
|
77
|
+
|
|
78
|
+
## Publishing
|
|
79
|
+
|
|
80
|
+
The package is designed to be published as:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm publish --access public
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Publishing requires an authenticated npm account with permission to publish the
|
|
87
|
+
currently unclaimed `preppergpt` package name.
|
|
88
|
+
|
|
89
|
+
The source repository is expected at:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
https://github.com/teamslop/preppergpt
|
|
93
|
+
```
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
name: preppergpt
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
open-webui:
|
|
5
|
+
image: ghcr.io/open-webui/open-webui:main
|
|
6
|
+
container_name: preppergpt-open-webui
|
|
7
|
+
pull_policy: always
|
|
8
|
+
restart: unless-stopped
|
|
9
|
+
network_mode: host
|
|
10
|
+
ulimits:
|
|
11
|
+
nofile:
|
|
12
|
+
soft: 1048576
|
|
13
|
+
hard: 1048576
|
|
14
|
+
volumes:
|
|
15
|
+
- ${PREPPERGPT_DATA_DIR:?set PREPPERGPT_DATA_DIR}/openwebui:/app/backend/data
|
|
16
|
+
- ../services/comfyui:/app/backend/data/parity-comfyui:ro
|
|
17
|
+
- ../themes/preppergpt/static/favicon.svg:/app/backend/open_webui/static/favicon.svg:ro
|
|
18
|
+
- ../themes/preppergpt/static/logo.svg:/app/backend/open_webui/static/logo.svg:ro
|
|
19
|
+
environment:
|
|
20
|
+
WEBUI_NAME: "PrepperGPT"
|
|
21
|
+
WEBUI_AUTH: "True"
|
|
22
|
+
WEBUI_ADMIN_EMAIL: "${WEBUI_ADMIN_EMAIL:?set WEBUI_ADMIN_EMAIL}"
|
|
23
|
+
WEBUI_ADMIN_PASSWORD: "${WEBUI_ADMIN_PASSWORD:?set WEBUI_ADMIN_PASSWORD}"
|
|
24
|
+
WEBUI_ADMIN_NAME: "${WEBUI_ADMIN_NAME:-PrepperGPT Admin}"
|
|
25
|
+
WEBUI_SECRET_KEY: "${WEBUI_SECRET_KEY:?set WEBUI_SECRET_KEY}"
|
|
26
|
+
ENABLE_OLLAMA_API: "True"
|
|
27
|
+
OLLAMA_BASE_URLS: "${OLLAMA_BASE_URL:-http://127.0.0.1:11434}"
|
|
28
|
+
ENABLE_OPENAI_API: "True"
|
|
29
|
+
OPENAI_API_BASE_URLS: "${SLOCODE_BASE_URL:-http://127.0.0.1:11438/v1};${GLM52_BASE_URL:-http://127.0.0.1:11441/v1};http://127.0.0.1:18041/v1;http://127.0.0.1:18043/v1;http://127.0.0.1:18044/v1"
|
|
30
|
+
OPENAI_API_KEYS: "slopcode;glm52;deep-research;local-agent;local-vision"
|
|
31
|
+
ENABLE_DIRECT_CONNECTIONS: "True"
|
|
32
|
+
DEFAULT_MODELS: "${PREPPERGPT_DEFAULT_MODEL:-local-chatgpt-auto}"
|
|
33
|
+
MODEL_ORDER_LIST: "${PREPPERGPT_MODEL_ORDER_LIST:-[\"local-chatgpt-auto\"]}"
|
|
34
|
+
TASK_MODEL: "${PREPPERGPT_DEFAULT_MODEL:-local-chatgpt-auto}"
|
|
35
|
+
ENABLE_CHANNELS: "True"
|
|
36
|
+
ENABLE_TITLE_GENERATION: "False"
|
|
37
|
+
ENABLE_TAGS_GENERATION: "False"
|
|
38
|
+
ENABLE_FOLLOW_UP_GENERATION: "False"
|
|
39
|
+
ENABLE_SEARCH_QUERY_GENERATION: "True"
|
|
40
|
+
ENABLE_RETRIEVAL_QUERY_GENERATION: "True"
|
|
41
|
+
DEFAULT_PROMPT_SUGGESTIONS: >-
|
|
42
|
+
[
|
|
43
|
+
{"title":["Field repair","diagnose a broken water pump"],"content":"Help me troubleshoot a broken water pump with only hand tools. Ask clarifying questions first."},
|
|
44
|
+
{"title":["Food storage","build a rotation plan"],"content":"Create a practical food storage rotation plan for a household of four using shelf-stable staples."},
|
|
45
|
+
{"title":["Medical reference","triage a minor injury"],"content":"Give general first-aid information for a minor injury and clearly flag when professional care is needed."},
|
|
46
|
+
{"title":["Radio log","summarize messages"],"content":"Turn this radio message log into a concise situation report with unresolved questions."}
|
|
47
|
+
]
|
|
48
|
+
CONTENT_EXTRACTION_ENGINE: "tika"
|
|
49
|
+
TIKA_SERVER_URL: "http://127.0.0.1:9998"
|
|
50
|
+
ENABLE_RAG_HYBRID_SEARCH: "True"
|
|
51
|
+
RAG_EMBEDDING_MODEL: "BAAI/bge-m3"
|
|
52
|
+
RAG_RERANKING_MODEL: "BAAI/bge-reranker-v2-m3"
|
|
53
|
+
RAG_TOP_K: "8"
|
|
54
|
+
RAG_TOP_K_RERANKER: "4"
|
|
55
|
+
CHUNK_SIZE: "1000"
|
|
56
|
+
CHUNK_OVERLAP: "150"
|
|
57
|
+
ENABLE_WEB_SEARCH: "True"
|
|
58
|
+
WEB_SEARCH_ENGINE: "searxng"
|
|
59
|
+
SEARXNG_QUERY_URL: "http://127.0.0.1:18080/search?q=<query>&format=json"
|
|
60
|
+
WEB_SEARCH_RESULT_COUNT: "5"
|
|
61
|
+
WEB_SEARCH_CONCURRENT_REQUESTS: "3"
|
|
62
|
+
ENABLE_CODE_EXECUTION: "True"
|
|
63
|
+
CODE_EXECUTION_ENGINE: "jupyter"
|
|
64
|
+
CODE_EXECUTION_JUPYTER_URL: "http://127.0.0.1:8888"
|
|
65
|
+
CODE_EXECUTION_JUPYTER_AUTH: "token"
|
|
66
|
+
CODE_EXECUTION_JUPYTER_AUTH_TOKEN: "${JUPYTER_TOKEN:?set JUPYTER_TOKEN}"
|
|
67
|
+
ENABLE_CODE_INTERPRETER: "True"
|
|
68
|
+
CODE_INTERPRETER_ENGINE: "jupyter"
|
|
69
|
+
CODE_INTERPRETER_JUPYTER_URL: "http://127.0.0.1:8888"
|
|
70
|
+
CODE_INTERPRETER_JUPYTER_AUTH: "token"
|
|
71
|
+
CODE_INTERPRETER_JUPYTER_AUTH_TOKEN: "${JUPYTER_TOKEN:?set JUPYTER_TOKEN}"
|
|
72
|
+
WHISPER_MODEL: "large-v3"
|
|
73
|
+
WHISPER_COMPUTE_TYPE: "int8"
|
|
74
|
+
WHISPER_MODEL_AUTO_UPDATE: "True"
|
|
75
|
+
WHISPER_VAD_FILTER: "True"
|
|
76
|
+
WHISPER_MULTILINGUAL: "True"
|
|
77
|
+
ENABLE_IMAGE_GENERATION: "True"
|
|
78
|
+
IMAGE_GENERATION_ENGINE: "comfyui"
|
|
79
|
+
COMFYUI_BASE_URL: "http://127.0.0.1:8188"
|
|
80
|
+
IMAGE_GENERATION_MODEL: "flux-2-klein-9b-fp8.safetensors"
|
|
81
|
+
IMAGE_SIZE: "1024x1024"
|
|
82
|
+
IMAGE_STEPS: "20"
|
|
83
|
+
SCARF_NO_ANALYTICS: "True"
|
|
84
|
+
DO_NOT_TRACK: "True"
|
|
85
|
+
ANONYMIZED_TELEMETRY: "False"
|
|
86
|
+
PORT: "${PREPPERGPT_PORT:-8080}"
|
|
87
|
+
|
|
88
|
+
ollama:
|
|
89
|
+
image: ollama/ollama:latest
|
|
90
|
+
container_name: preppergpt-ollama
|
|
91
|
+
restart: unless-stopped
|
|
92
|
+
network_mode: host
|
|
93
|
+
volumes:
|
|
94
|
+
- ${PREPPERGPT_DATA_DIR:?set PREPPERGPT_DATA_DIR}/ollama:/root/.ollama
|
|
95
|
+
environment:
|
|
96
|
+
OLLAMA_HOST: "127.0.0.1:11434"
|
|
97
|
+
OLLAMA_MODELS: "/root/.ollama/models"
|
|
98
|
+
|
|
99
|
+
searxng:
|
|
100
|
+
image: searxng/searxng:latest
|
|
101
|
+
container_name: preppergpt-searxng
|
|
102
|
+
restart: unless-stopped
|
|
103
|
+
network_mode: host
|
|
104
|
+
volumes:
|
|
105
|
+
- ../services/searxng/settings.yml:/etc/searxng/settings.yml:ro
|
|
106
|
+
environment:
|
|
107
|
+
SEARXNG_BASE_URL: "http://127.0.0.1:18080/"
|
|
108
|
+
SEARXNG_SECRET_KEY: "${SEARXNG_SECRET_KEY:?set SEARXNG_SECRET_KEY}"
|
|
109
|
+
|
|
110
|
+
tika:
|
|
111
|
+
image: apache/tika:latest-full
|
|
112
|
+
container_name: preppergpt-tika
|
|
113
|
+
restart: unless-stopped
|
|
114
|
+
network_mode: host
|
|
115
|
+
|
|
116
|
+
jupyter:
|
|
117
|
+
image: quay.io/jupyter/minimal-notebook:latest
|
|
118
|
+
container_name: preppergpt-jupyter
|
|
119
|
+
restart: unless-stopped
|
|
120
|
+
network_mode: host
|
|
121
|
+
user: root
|
|
122
|
+
volumes:
|
|
123
|
+
- ${PREPPERGPT_DATA_DIR:?set PREPPERGPT_DATA_DIR}/jupyter:/home/jovyan/work
|
|
124
|
+
environment:
|
|
125
|
+
JUPYTER_ENABLE_LAB: "yes"
|
|
126
|
+
JUPYTER_TOKEN: "${JUPYTER_TOKEN:?set JUPYTER_TOKEN}"
|
|
127
|
+
CHOWN_HOME: "yes"
|
|
128
|
+
CHOWN_HOME_OPTS: "-R"
|
|
129
|
+
command:
|
|
130
|
+
- start-notebook.py
|
|
131
|
+
- "--ServerApp.ip=0.0.0.0"
|
|
132
|
+
- "--ServerApp.port=8888"
|
|
133
|
+
- "--ServerApp.token=${JUPYTER_TOKEN:?set JUPYTER_TOKEN}"
|
|
134
|
+
- "--ServerApp.password="
|
|
135
|
+
- "--ServerApp.allow_origin=*"
|
|
136
|
+
|
|
137
|
+
deep-research:
|
|
138
|
+
build:
|
|
139
|
+
context: ../services/deep-research
|
|
140
|
+
container_name: preppergpt-deep-research
|
|
141
|
+
restart: unless-stopped
|
|
142
|
+
network_mode: host
|
|
143
|
+
volumes:
|
|
144
|
+
- ${PREPPERGPT_DATA_DIR:?set PREPPERGPT_DATA_DIR}/deep-research:/data
|
|
145
|
+
environment:
|
|
146
|
+
DEEP_RESEARCH_HOST: "127.0.0.1"
|
|
147
|
+
DEEP_RESEARCH_PORT: "18041"
|
|
148
|
+
DEEP_RESEARCH_PUBLIC_BASE_URL: "http://127.0.0.1:18041"
|
|
149
|
+
DEEP_RESEARCH_MODEL_ID: "deep-research-glm52"
|
|
150
|
+
DEEP_RESEARCH_MODEL: "${DEEP_RESEARCH_MODEL:-glm52-q4-local}"
|
|
151
|
+
DEEP_RESEARCH_GLM_BASE_URL: "${GLM52_BASE_URL:-http://127.0.0.1:11441/v1}"
|
|
152
|
+
DEEP_RESEARCH_SEARXNG_URL: "http://127.0.0.1:18080/search"
|
|
153
|
+
DEEP_RESEARCH_TIKA_URL: "http://127.0.0.1:9998/tika"
|
|
154
|
+
DEEP_RESEARCH_LOCAL_APP_CONNECTOR_URL: "http://127.0.0.1:18042"
|
|
155
|
+
|
|
156
|
+
local-scheduler:
|
|
157
|
+
build:
|
|
158
|
+
context: ../services/local-scheduler
|
|
159
|
+
container_name: preppergpt-local-scheduler
|
|
160
|
+
restart: unless-stopped
|
|
161
|
+
network_mode: host
|
|
162
|
+
volumes:
|
|
163
|
+
- ${PREPPERGPT_DATA_DIR:?set PREPPERGPT_DATA_DIR}/local-scheduler:/data
|
|
164
|
+
- ../docs:/app/parity-docs:ro
|
|
165
|
+
environment:
|
|
166
|
+
LOCAL_SCHEDULER_HOST: "127.0.0.1"
|
|
167
|
+
LOCAL_SCHEDULER_PORT: "18042"
|
|
168
|
+
LOCAL_SCHEDULER_PUBLIC_BASE_URL: "http://127.0.0.1:18042"
|
|
169
|
+
LOCAL_SCHEDULER_DEFAULT_BASE_URL: "http://127.0.0.1:18041/v1"
|
|
170
|
+
LOCAL_SCHEDULER_DEFAULT_MODEL: "deep-research-glm52"
|
|
171
|
+
LOCAL_PARITY_DOCS_DIR: "/app/parity-docs"
|
|
172
|
+
|
|
173
|
+
playwright-agent:
|
|
174
|
+
image: mcr.microsoft.com/playwright:v1.58.0-noble
|
|
175
|
+
container_name: preppergpt-playwright-agent
|
|
176
|
+
restart: unless-stopped
|
|
177
|
+
network_mode: host
|
|
178
|
+
command: npx -y playwright@1.58.0 run-server --port 18045 --host 127.0.0.1
|
|
179
|
+
|
|
180
|
+
local-agent:
|
|
181
|
+
build:
|
|
182
|
+
context: ../services/local-agent
|
|
183
|
+
container_name: preppergpt-local-agent
|
|
184
|
+
restart: unless-stopped
|
|
185
|
+
network_mode: host
|
|
186
|
+
volumes:
|
|
187
|
+
- ${PREPPERGPT_DATA_DIR:?set PREPPERGPT_DATA_DIR}/local-agent:/data
|
|
188
|
+
- /tmp/.X11-unix:/tmp/.X11-unix:rw
|
|
189
|
+
- ${XDG_RUNTIME_DIR:-/run/user/1000}:${XDG_RUNTIME_DIR:-/run/user/1000}:rw
|
|
190
|
+
- ${XAUTHORITY:-/tmp/.preppergpt-missing-xauthority}:/tmp/.Xauthority:ro
|
|
191
|
+
environment:
|
|
192
|
+
LOCAL_AGENT_HOST: "127.0.0.1"
|
|
193
|
+
LOCAL_AGENT_PORT: "18043"
|
|
194
|
+
LOCAL_AGENT_PUBLIC_BASE_URL: "http://127.0.0.1:18043"
|
|
195
|
+
LOCAL_AGENT_MODEL_ID: "local-agent-glm52"
|
|
196
|
+
LOCAL_AGENT_GLM_MODEL: "glm52-q4-local"
|
|
197
|
+
LOCAL_AGENT_GLM_BASE_URL: "${GLM52_BASE_URL:-http://127.0.0.1:11441/v1}"
|
|
198
|
+
LOCAL_AGENT_AUTO_ROUTER_MODEL_ID: "local-auto-router"
|
|
199
|
+
LOCAL_AGENT_AUTO_ROUTER_FAST_MODEL: "gemma4:12b-256k-gpu"
|
|
200
|
+
LOCAL_AGENT_AUTO_ROUTER_FAST_BASE_URL: "http://127.0.0.1:11434/v1"
|
|
201
|
+
LOCAL_AGENT_AUTO_ROUTER_CODE_MODEL: "qwen3.6-35b-a3b:slopcode-cpu-64k"
|
|
202
|
+
LOCAL_AGENT_AUTO_ROUTER_CODE_BASE_URL: "${SLOCODE_BASE_URL:-http://127.0.0.1:11438/v1}"
|
|
203
|
+
LOCAL_AGENT_AUTO_ROUTER_RESEARCH_MODEL: "deep-research-glm52"
|
|
204
|
+
LOCAL_AGENT_AUTO_ROUTER_RESEARCH_BASE_URL: "http://127.0.0.1:18041/v1"
|
|
205
|
+
LOCAL_AGENT_AUTO_ROUTER_AGENT_MODEL: "local-agent-glm52"
|
|
206
|
+
LOCAL_AGENT_AUTO_ROUTER_AGENT_BASE_URL: "http://127.0.0.1:18043/v1"
|
|
207
|
+
LOCAL_AGENT_SEARXNG_URL: "http://127.0.0.1:18080/search"
|
|
208
|
+
LOCAL_AGENT_TIKA_URL: "http://127.0.0.1:9998/tika"
|
|
209
|
+
LOCAL_AGENT_SCHEDULER_URL: "http://127.0.0.1:18042"
|
|
210
|
+
LOCAL_AGENT_PLAYWRIGHT_WS_URL: "ws://127.0.0.1:18045"
|
|
211
|
+
LOCAL_AGENT_DESKTOP_ENABLED: "${LOCAL_AGENT_DESKTOP_ENABLED:-1}"
|
|
212
|
+
DISPLAY: "${DISPLAY:-}"
|
|
213
|
+
WAYLAND_DISPLAY: "${WAYLAND_DISPLAY:-}"
|
|
214
|
+
XDG_RUNTIME_DIR: "${XDG_RUNTIME_DIR:-/run/user/1000}"
|
|
215
|
+
XAUTHORITY: "/tmp/.Xauthority"
|
|
216
|
+
|
|
217
|
+
local-vision:
|
|
218
|
+
build:
|
|
219
|
+
context: ../services/local-vision
|
|
220
|
+
container_name: preppergpt-local-vision
|
|
221
|
+
restart: unless-stopped
|
|
222
|
+
network_mode: host
|
|
223
|
+
volumes:
|
|
224
|
+
- ${PREPPERGPT_DATA_DIR:?set PREPPERGPT_DATA_DIR}/local-vision:/data
|
|
225
|
+
environment:
|
|
226
|
+
LOCAL_VISION_HOST: "127.0.0.1"
|
|
227
|
+
LOCAL_VISION_PORT: "18044"
|
|
228
|
+
LOCAL_VISION_DEVICE: "${LOCAL_VISION_DEVICE:-cpu}"
|
|
229
|
+
LOCAL_VISION_OLLAMA_ENABLED: "1"
|
|
230
|
+
LOCAL_VISION_OLLAMA_MODEL_ID: "local-vision-gemma4-12b"
|
|
231
|
+
LOCAL_VISION_OLLAMA_MODEL: "gemma4:12b"
|
|
232
|
+
LOCAL_VISION_OLLAMA_URL: "http://127.0.0.1:11434"
|
package/docs/hardware.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Hardware Guide
|
|
2
|
+
|
|
3
|
+
PrepperGPT works best on Linux with an NVIDIA GPU and enough NVMe space for
|
|
4
|
+
model weights.
|
|
5
|
+
|
|
6
|
+
Recommended starting points:
|
|
7
|
+
|
|
8
|
+
- Speed profile: 16 GB RAM, 8-12 GB VRAM, 40 GB free disk.
|
|
9
|
+
- Balanced profile: 32-64 GB RAM, 12-24 GB VRAM, 120 GB free disk.
|
|
10
|
+
- Intelligence profile: 96 GB RAM or more, fast NVMe, and hundreds of GB free
|
|
11
|
+
for GLM 5.2 Q4 or similar large weights.
|
|
12
|
+
|
|
13
|
+
The installer reserves about 15-20% VRAM headroom when deciding whether a model
|
|
14
|
+
fits. If a large manual model is selected, `preppergpt doctor` explains the
|
|
15
|
+
endpoint or file path that must be provided.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Model Sources
|
|
2
|
+
|
|
3
|
+
PrepperGPT separates routing from model licensing and distribution.
|
|
4
|
+
|
|
5
|
+
- Ollama models are pulled by the local Ollama runtime when available.
|
|
6
|
+
- OpenWebUI STT models are downloaded by OpenWebUI/faster-whisper.
|
|
7
|
+
- Hugging Face vision models are downloaded by the local vision sidecar.
|
|
8
|
+
- Very large GLM, Slopcode, and Flux assets are marked as manual or external
|
|
9
|
+
until a license-compatible public download source is configured.
|
|
10
|
+
|
|
11
|
+
Manual routes are still added to OpenWebUI. They become live when their local
|
|
12
|
+
endpoint or files are present.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# PrepperGPT Local Parity Map
|
|
2
|
+
|
|
3
|
+
PrepperGPT packages the local ChatGPT-like stack around OpenWebUI:
|
|
4
|
+
|
|
5
|
+
- OpenWebUI UI at `http://127.0.0.1:8080`
|
|
6
|
+
- Ollama fast local models at `http://127.0.0.1:11434`
|
|
7
|
+
- Optional GLM 5.2 route at `http://127.0.0.1:11441/v1`
|
|
8
|
+
- Optional Slopcode/Qwen route at `http://127.0.0.1:11438/v1`
|
|
9
|
+
- Deep research sidecar at `http://127.0.0.1:18041/v1`
|
|
10
|
+
- Local scheduler connector at `http://127.0.0.1:18042`
|
|
11
|
+
- Local agent and auto-router at `http://127.0.0.1:18043/v1`
|
|
12
|
+
- Local vision sidecar at `http://127.0.0.1:18044/v1`
|
|
13
|
+
- SearXNG, Tika, Jupyter, and ComfyUI support services
|
|
14
|
+
|
|
15
|
+
The local goal is functional local parity for common ChatGPT workflows, not
|
|
16
|
+
hosted frontier-model quality or cloud account continuity.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Publishing
|
|
2
|
+
|
|
3
|
+
GitHub source is published at:
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
https://github.com/teamslop/preppergpt
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
To publish npm manually:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm login
|
|
13
|
+
npm whoami
|
|
14
|
+
npm run check
|
|
15
|
+
npm publish --access public
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
To publish from GitHub Actions:
|
|
19
|
+
|
|
20
|
+
1. Create an npm automation token.
|
|
21
|
+
2. Add it as the repository secret `NPM_TOKEN`.
|
|
22
|
+
3. Push a SemVer tag such as `v0.1.0`.
|
|
23
|
+
|
|
24
|
+
The release workflow runs tests and publishes `preppergpt` with npm provenance.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import http from "node:http";
|
|
3
|
+
import { detectMachine } from "./lib/detect.mjs";
|
|
4
|
+
import { buildPlan, normalizeProfile } from "./lib/planner.mjs";
|
|
5
|
+
import { packageRoot, runtimePaths } from "./lib/paths.mjs";
|
|
6
|
+
import { renderInstall } from "./lib/render.mjs";
|
|
7
|
+
import { commandResult, parseArgs, readJson, shellQuote } from "./lib/util.mjs";
|
|
8
|
+
|
|
9
|
+
const VERSION = "0.1.0";
|
|
10
|
+
|
|
11
|
+
function usage() {
|
|
12
|
+
return `PrepperGPT ${VERSION}
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
preppergpt detect [--json]
|
|
16
|
+
preppergpt plan --profile balanced|intelligence|speed [--json]
|
|
17
|
+
preppergpt install --profile balanced|intelligence|speed [--dry-run] [--home PATH]
|
|
18
|
+
preppergpt start [--home PATH]
|
|
19
|
+
preppergpt stop [--home PATH]
|
|
20
|
+
preppergpt status [--home PATH] [--json]
|
|
21
|
+
preppergpt doctor [--profile balanced|intelligence|speed] [--home PATH]
|
|
22
|
+
preppergpt switch-profile --profile balanced|intelligence|speed [--home PATH]
|
|
23
|
+
preppergpt version
|
|
24
|
+
`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function printJson(value) {
|
|
28
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function profileFrom(flags) {
|
|
32
|
+
return normalizeProfile(flags.profile || flags.mode || "balanced");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function composeArgs(paths) {
|
|
36
|
+
return ["compose", "--env-file", paths.envFile, "-f", `${packageRoot}/compose/preppergpt.yaml`, "-f", paths.generatedCompose];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function runCompose(paths, args) {
|
|
40
|
+
const result = commandResult("docker", [...composeArgs(paths), ...args], {
|
|
41
|
+
timeoutMs: 120000,
|
|
42
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
43
|
+
});
|
|
44
|
+
if (!result.ok) {
|
|
45
|
+
throw new Error(`docker compose ${args.join(" ")} failed`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function fetchStatus(url) {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
const req = http.get(url, { timeout: 4000 }, (res) => {
|
|
52
|
+
res.resume();
|
|
53
|
+
res.on("end", () => resolve({ url, ok: res.statusCode >= 200 && res.statusCode < 500, status: res.statusCode }));
|
|
54
|
+
});
|
|
55
|
+
req.on("timeout", () => {
|
|
56
|
+
req.destroy();
|
|
57
|
+
resolve({ url, ok: false, status: "timeout" });
|
|
58
|
+
});
|
|
59
|
+
req.on("error", () => resolve({ url, ok: false, status: "down" }));
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function printPlan(plan) {
|
|
64
|
+
console.log(`Profile: ${plan.profileLabel}`);
|
|
65
|
+
console.log(`Default model: ${plan.defaultModel}`);
|
|
66
|
+
console.log(`Routes: ${plan.routeIds.join(", ")}`);
|
|
67
|
+
console.log(`Context: default ${plan.estimates.defaultContextTokens}, max ${plan.estimates.maxContextTokens}`);
|
|
68
|
+
console.log(`TPS estimate: default ${plan.estimates.defaultTpsEstimate}; best ${plan.estimates.bestTpsEstimate}`);
|
|
69
|
+
if (plan.manualAssets.length) {
|
|
70
|
+
console.log("\nManual or external assets:");
|
|
71
|
+
for (const asset of plan.manualAssets) {
|
|
72
|
+
console.log(` ${asset.id}: ${asset.reason}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (plan.warnings.length) {
|
|
76
|
+
console.log("\nWarnings:");
|
|
77
|
+
for (const warning of plan.warnings) {
|
|
78
|
+
console.log(` ${warning}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function commandDetect(flags) {
|
|
84
|
+
const detection = await detectMachine();
|
|
85
|
+
if (flags.json) {
|
|
86
|
+
printJson(detection);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
console.log(`Host: ${detection.hostname} (${detection.platform}/${detection.arch})`);
|
|
90
|
+
console.log(`CPU: ${detection.cpu.cores} cores, ${detection.cpu.model}`);
|
|
91
|
+
console.log(`RAM: ${detection.memory.totalGb} GB total, ${detection.memory.freeGb} GB free`);
|
|
92
|
+
const bestDisk = detection.disks[0];
|
|
93
|
+
console.log(`Disk: ${bestDisk ? `${bestDisk.freeGb.toFixed(1)} GB free at ${bestDisk.mount}` : "not detected"}`);
|
|
94
|
+
if (detection.gpus.length) {
|
|
95
|
+
for (const gpu of detection.gpus) {
|
|
96
|
+
console.log(`GPU ${gpu.index}: ${gpu.name}, ${gpu.totalVramGb} GB VRAM, ${gpu.freeVramGb} GB free`);
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
console.log("GPU: no NVIDIA GPU detected");
|
|
100
|
+
}
|
|
101
|
+
const missing = Object.entries(detection.tools).filter(([, present]) => !present).map(([tool]) => tool);
|
|
102
|
+
console.log(`Tools: ${missing.length ? `missing ${missing.join(", ")}` : "all required tools present"}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function commandPlan(flags) {
|
|
106
|
+
const detection = await detectMachine({ skipPorts: Boolean(flags.no_ports) });
|
|
107
|
+
const plan = buildPlan(detection, profileFrom(flags));
|
|
108
|
+
if (flags.json) {
|
|
109
|
+
printJson(plan);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
printPlan(plan);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function commandInstall(flags) {
|
|
116
|
+
const home = flags.home;
|
|
117
|
+
const detection = await detectMachine();
|
|
118
|
+
const plan = buildPlan(detection, profileFrom(flags));
|
|
119
|
+
if (flags.dry_run) {
|
|
120
|
+
printPlan(plan);
|
|
121
|
+
console.log("\nDry run only. No files written.");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const paths = renderInstall(plan, detection, { home });
|
|
125
|
+
console.log(`Wrote ${paths.envFile}`);
|
|
126
|
+
console.log(`Wrote ${paths.generatedCompose}`);
|
|
127
|
+
console.log(`Wrote ${paths.modelPlan}`);
|
|
128
|
+
console.log("\nNext:");
|
|
129
|
+
console.log(` preppergpt start --home ${shellQuote(paths.root)}`);
|
|
130
|
+
console.log(" Open http://127.0.0.1:8080");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function commandSwitchProfile(flags) {
|
|
134
|
+
const paths = runtimePaths(flags.home);
|
|
135
|
+
const detection = await detectMachine();
|
|
136
|
+
const plan = buildPlan(detection, profileFrom(flags));
|
|
137
|
+
renderInstall(plan, detection, { home: paths.root });
|
|
138
|
+
console.log(`Switched PrepperGPT to ${plan.profile}.`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function commandStart(flags) {
|
|
142
|
+
const paths = runtimePaths(flags.home);
|
|
143
|
+
if (!fs.existsSync(paths.envFile) || !fs.existsSync(paths.generatedCompose)) {
|
|
144
|
+
throw new Error(`PrepperGPT is not installed at ${paths.root}. Run preppergpt install first.`);
|
|
145
|
+
}
|
|
146
|
+
runCompose(paths, ["up", "-d"]);
|
|
147
|
+
console.log("PrepperGPT start requested.");
|
|
148
|
+
console.log("Open http://127.0.0.1:8080");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function commandStop(flags) {
|
|
152
|
+
const paths = runtimePaths(flags.home);
|
|
153
|
+
runCompose(paths, ["stop"]);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function commandStatus(flags) {
|
|
157
|
+
const paths = runtimePaths(flags.home);
|
|
158
|
+
const checks = await Promise.all([
|
|
159
|
+
fetchStatus("http://127.0.0.1:8080/health"),
|
|
160
|
+
fetchStatus("http://127.0.0.1:11434/api/tags"),
|
|
161
|
+
fetchStatus("http://127.0.0.1:18041/health"),
|
|
162
|
+
fetchStatus("http://127.0.0.1:18042/health"),
|
|
163
|
+
fetchStatus("http://127.0.0.1:18043/health"),
|
|
164
|
+
fetchStatus("http://127.0.0.1:18044/health"),
|
|
165
|
+
fetchStatus("http://127.0.0.1:18080/search?q=test&format=json")
|
|
166
|
+
]);
|
|
167
|
+
let plan = null;
|
|
168
|
+
if (fs.existsSync(paths.modelPlan)) {
|
|
169
|
+
plan = readJson(paths.modelPlan);
|
|
170
|
+
}
|
|
171
|
+
const status = { home: paths.root, url: "http://127.0.0.1:8080", plan, checks };
|
|
172
|
+
if (flags.json) {
|
|
173
|
+
printJson(status);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
console.log(`PrepperGPT home: ${paths.root}`);
|
|
177
|
+
console.log(`OpenWebUI URL: ${status.url}`);
|
|
178
|
+
if (plan) {
|
|
179
|
+
console.log(`Profile: ${plan.profile}`);
|
|
180
|
+
console.log(`Default model: ${plan.defaultModel}`);
|
|
181
|
+
console.log(`Context limit estimate: ${plan.estimates.maxContextTokens}`);
|
|
182
|
+
console.log(`TPS estimate: ${plan.estimates.bestTpsEstimate}`);
|
|
183
|
+
}
|
|
184
|
+
for (const check of checks) {
|
|
185
|
+
console.log(`${check.ok ? "up " : "down"} ${check.url} (${check.status})`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function commandDoctor(flags) {
|
|
190
|
+
const detection = await detectMachine();
|
|
191
|
+
const plan = buildPlan(detection, profileFrom(flags));
|
|
192
|
+
printPlan(plan);
|
|
193
|
+
console.log("\nDoctor:");
|
|
194
|
+
const requiredTools = ["docker", "dockerCompose", "curl", "python3"];
|
|
195
|
+
for (const tool of requiredTools) {
|
|
196
|
+
console.log(` ${tool}: ${detection.tools[tool] ? "ok" : "missing"}`);
|
|
197
|
+
}
|
|
198
|
+
for (const [port, entry] of Object.entries(detection.ports)) {
|
|
199
|
+
if (!entry.free) {
|
|
200
|
+
console.log(` port ${port}: occupied`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export async function runCli(argv) {
|
|
206
|
+
const { flags, positional } = parseArgs(argv);
|
|
207
|
+
const command = positional[0] || (flags.help ? "help" : "");
|
|
208
|
+
if (!command || command === "help" || flags.help) {
|
|
209
|
+
console.log(usage());
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (command === "version" || command === "--version") {
|
|
213
|
+
console.log(VERSION);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (command === "detect") return commandDetect(flags);
|
|
217
|
+
if (command === "plan") return commandPlan(flags);
|
|
218
|
+
if (command === "install") return commandInstall(flags);
|
|
219
|
+
if (command === "start") return commandStart(flags);
|
|
220
|
+
if (command === "stop") return commandStop(flags);
|
|
221
|
+
if (command === "status") return commandStatus(flags);
|
|
222
|
+
if (command === "doctor") return commandDoctor(flags);
|
|
223
|
+
if (command === "switch-profile") return commandSwitchProfile(flags);
|
|
224
|
+
throw new Error(`Unknown command: ${command}\n\n${usage()}`);
|
|
225
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
5
|
+
printf 'PrepperGPT requires Node.js 20 or newer.\n' >&2
|
|
6
|
+
exit 1
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
if [[ -x "$(dirname "${BASH_SOURCE[0]}")/../bin/preppergpt.js" ]]; then
|
|
10
|
+
exec node "$(dirname "${BASH_SOURCE[0]}")/../bin/preppergpt.js" install "$@"
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
if ! command -v npx >/dev/null 2>&1; then
|
|
14
|
+
printf 'PrepperGPT requires npx or a cloned preppergpt checkout.\n' >&2
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
exec npx preppergpt install "$@"
|