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.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -0
  3. package/bin/preppergpt.js +8 -0
  4. package/compose/preppergpt.yaml +232 -0
  5. package/docs/hardware.md +15 -0
  6. package/docs/model-sources.md +12 -0
  7. package/docs/preppergpt-local-parity-map.md +16 -0
  8. package/docs/publishing.md +24 -0
  9. package/installer/cli.mjs +225 -0
  10. package/installer/install.sh +18 -0
  11. package/installer/lib/detect.mjs +128 -0
  12. package/installer/lib/paths.mjs +26 -0
  13. package/installer/lib/planner.mjs +175 -0
  14. package/installer/lib/render.mjs +76 -0
  15. package/installer/lib/util.mjs +84 -0
  16. package/package.json +48 -0
  17. package/profiles/models.json +277 -0
  18. package/services/comfyui/flux-kontext-edit-openwebui-nodes.json +46 -0
  19. package/services/comfyui/flux-kontext-edit-openwebui-workflow.json +245 -0
  20. package/services/comfyui/flux-kontext-mask-edit-openwebui-nodes.json +51 -0
  21. package/services/comfyui/flux-kontext-mask-edit-openwebui-workflow.json +322 -0
  22. package/services/comfyui/flux2-klein-9b-openwebui-nodes.json +58 -0
  23. package/services/comfyui/flux2-klein-9b-openwebui-workflow.json +141 -0
  24. package/services/comfyui/image-invert-edit-openwebui-nodes.json +23 -0
  25. package/services/comfyui/image-invert-edit-openwebui-workflow.json +52 -0
  26. package/services/deep-research/Dockerfile +7 -0
  27. package/services/deep-research/app.py +1913 -0
  28. package/services/local-agent/Dockerfile +17 -0
  29. package/services/local-agent/app.py +2311 -0
  30. package/services/local-scheduler/Dockerfile +8 -0
  31. package/services/local-scheduler/app.py +15774 -0
  32. package/services/local-vision/Dockerfile +11 -0
  33. package/services/local-vision/app.py +888 -0
  34. package/services/searxng/settings.yml +16 -0
  35. package/themes/preppergpt/custom.css +15 -0
  36. package/themes/preppergpt/static/favicon.svg +5 -0
  37. 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,8 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from "../installer/cli.mjs";
3
+
4
+ runCli(process.argv.slice(2)).catch((error) => {
5
+ const message = error && error.stack ? error.stack : String(error);
6
+ console.error(message);
7
+ process.exitCode = 1;
8
+ });
@@ -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"
@@ -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 "$@"