File size: 29,132 Bytes
2b69294
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e725fbe
 
 
 
2b69294
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
db81b0e
 
 
 
 
 
 
 
 
 
 
 
5ae3446
 
 
db81b0e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5ae3446
 
 
db81b0e
 
 
 
 
 
 
 
 
 
 
 
 
2b69294
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
db81b0e
 
2b69294
 
885a1fd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
#!/usr/bin/env python3
"""Basecamp wiring recipes β€” per-service knowledge for fixing and wiring
the AI services Basecamp discovers. Single source of truth for both the
stack skill (inline recipes for discovered services) and the full
references/wiring-recipes.md written into the container's HERMES_HOME.

Each recipe has:
  where   β€” where the service's configuration lives (env / file / UI / API)
  keys    β€” the exact config keys / env vars / API endpoints involved
  fix     β€” the one-line (or one-step) fix to wire it correctly
  verify  β€” how to confirm the wiring actually works
"""

WIRING_RECIPES = {
    # ── Inference engines ──
    "ollama": {
        "where": "Container env vars + CLI. Clients connect to :11434 (OpenAI-compatible /v1).",
        "keys": "OLLAMA_HOST, OLLAMA_MODELS, OLLAMA_FLASH_ATTENTION, OLLAMA_KV_CACHE_TYPE; API: GET /api/tags, POST /api/copy, /v1/chat/completions",
        "fix": "Set OLLAMA_HOST=http://0.0.0.0:11434 to accept remote clients. PERF (verified 2026-08-10): do NOT set OLLAMA_CONTEXT_LENGTH globally β€” a forced 64K pre-allocates a ~6GB KV cache on EVERY load, starving weights of VRAM and dropping speed ~9x (8B: 9 tok/s vs 80). Use OLLAMA_FLASH_ATTENTION=1 + OLLAMA_KV_CACHE_TYPE=q8_0; ollama sizes KV per-request. Hermes's context_length config is an advertised ceiling, not a per-request demand.",
        "verify": "curl http://<host>:11434/api/tags returns {\"models\": [...]}; benchmark: ollama run <model> --verbose shows eval rate (expect 60-100 tok/s for an 8B on a 12GB GPU with FA).",
        "pitfalls": "VERIFIED 2026-08-10: forced 64K = 6GB KV cache = 8B mostly on CPU (9 tok/s). Removing it + FA + q8 KV = 80 tok/s, 33/33 layers on GPU, KV 272MB. Check offload: docker logs <ollama> | grep offloaded. Keep models resident with keep_alive to avoid reload penalties.",
    },
    "tabbyapi": {
        "where": "config.yml (model_dir, model_name, max_seq_len) + api_tokens.yml (api_key, admin_key), both mounted from the host.",
        "keys": "config.yml: model.model_name, model.max_seq_len, model.cache_8bit; api_tokens.yml: api_key, admin_key; API: /v1/models, /v1/chat/completions (Bearer)",
        "fix": "Edit config.yml to point model.model_name at a model present in model_dir, restart the container. Clients use Bearer <api_key> against http://<host>:5000/v1. max_seq_len is VRAM-bound (64K KV cache needs ~16GB for 8B).",
        "verify": "curl -H 'Authorization: Bearer <api_key>' http://<host>:5000/v1/models returns the loaded model list.",
    },
    "vllm": {
        "where": "CLI launch flags (no config file).",
        "keys": "--model, --served-model-name, --host, --port, --max-model-len; API: /v1/models, /v1/chat/completions",
        "fix": "Relaunch with --host 0.0.0.0 --port 8000 --served-model-name <name> so the served name matches what clients request. Hermes needs --max-model-len >= 65536.",
        "verify": "curl http://<host>:8000/v1/models lists the served model id.",
    },
    "litellm": {
        "where": "config.yaml (model_list) + master key env var.",
        "keys": "model_list: [{model_name, litellm_params: {model, api_base, api_key}}]; env: LITELLM_MASTER_KEY; API: /v1/models, /v1/chat/completions (Bearer)",
        "fix": "Add the upstream model to model_list with correct api_base, restart. Clients use Bearer <master_key> against http://<host>:4000/v1.",
        "verify": "curl -H 'Authorization: Bearer <key>' http://<host>:4000/v1/models lists configured model_names.",
    },
    "localai": {
        "where": "Env vars + model files in the models directory.",
        "keys": "MODELS_PATH, THREADS, DEBUG; API: /v1/models, /v1/chat/completions (OpenAI-compatible)",
        "fix": "Drop GGUF models into the models dir (or set MODELS_PATH), restart. Clients use http://<host>:8080/v1 (or the mapped port).",
        "verify": "curl http://<host>:8080/v1/models lists models.",
    },
    "llamacpp": {
        "where": "CLI flags (llama-server).",
        "keys": "-m <model>, --host, --port, -c <context>, --api-key; API: /health, /v1/models, /v1/chat/completions",
        "fix": "Relaunch with --host 0.0.0.0 so other containers can reach it; -c 65536 for Hermes. If --api-key is set, clients must send it.",
        "verify": "curl http://<host>:<port>/health returns 'ok'.",
    },
    "text-generation-webui": {
        "where": "Settings files + runtime API (model loaded per-session).",
        "keys": "API: GET/POST /api/v1/model (load model), /v1/chat/completions; settings in settings.yaml",
        "fix": "POST /api/v1/model with {\"model_name\": \"<model>\"} to load a model; clients use http://<host>:5000/v1.",
        "verify": "curl http://<host>:5000/api/v1/model returns the loaded model_name.",
    },
    "koboldcpp": {
        "where": "CLI flags.",
        "keys": "--model, --host, --port, --contextsize; API: /api/v1/model, /v1/models, /v1/chat/completions",
        "fix": "Relaunch with --host 0.0.0.0 and --contextsize 65536 for Hermes. Clients use http://<host>:5001/v1.",
        "verify": "curl http://<host>:5001/api/v1/model returns a result with the model name.",
    },
    "lmstudio": {
        "where": "In-app GUI only (no server-side config file for the API).",
        "keys": "Settings β†’ Developer β†’ Local Server (port 1234); API: /v1/models, /v1/chat/completions",
        "fix": "The user must enable the local server in the app GUI (Settings β†’ Developer β†’ Start Server). No remote fix exists β€” explain the 3 clicks.",
        "verify": "curl http://<host>:1234/v1/models lists loaded models.",
    },
    "sglang": {
        "where": "CLI flags.",
        "keys": "--model-path, --host, --port, --served-model-name; API: /v1/models, /v1/chat/completions",
        "fix": "Relaunch with --host 0.0.0.0 --served-model-name <name> matching what clients request; --context-length >= 65536 for Hermes.",
        "verify": "curl http://<host>:8002/v1/models (response contains 'sglang').",
    },
    "llamafile": {
        "where": "CLI/env of the single-file binary.",
        "keys": "-ngl <layers> (GPU offload), --host, --port; API: /v1/models, /v1/chat/completions",
        "fix": "Launch with --host 0.0.0.0 (default is localhost only). Clients use http://<host>:8080/v1.",
        "verify": "curl http://<host>:8080/v1/models lists the model.",
    },
    "exo": {
        "where": "config.yaml + peer discovery.",
        "keys": "config.yaml: api_port, peers; API: /v1/models, /v1/chat/completions",
        "fix": "Ensure api_port is reachable on the docker network; add peer IPs to config.yaml. Clients use http://<host>:8000/v1.",
        "verify": "curl http://<host>:8000/v1/models lists the cluster's models.",
    },
    "text-generation-inference": {
        "where": "CLI flags (launcher).",
        "keys": "--model-id, --port, --hostname, --max-total-tokens; API: /v1/models, /info",
        "fix": "Relaunch with --hostname 0.0.0.0 --model-id <hf-repo>. Clients use http://<host>:8080/v1.",
        "verify": "curl http://<host>:8080/info returns model_id.",
    },
    "aphrodite": {
        "where": "CLI flags (vLLM-style).",
        "keys": "--model, --served-model-name, --host, --port; API: /v1/models, /v1/chat/completions",
        "fix": "Relaunch with --host 0.0.0.0 and --served-model-name matching client requests.",
        "verify": "curl http://<host>:8000/v1/models (response contains 'aphrodite').",
    },
    "llamapool": {
        "where": "config.toml.",
        "keys": "[routes] entries mapping route names to upstream endpoints",
        "fix": "Add/edit a [routes] entry pointing at the upstream inference server, restart. Clients use the route name as the model id.",
        "verify": "curl http://<host>:<port>/v1/models lists route names.",
    },

    # ── UIs / chat frontends ──
    "open-webui": {
        "where": "Container env vars (primary) + admin Settings β†’ Connections (runtime override).",
        "keys": "OLLAMA_BASE_URL, OPENAI_API_BASE_URL, OPENAI_API_KEY; admin UI: Settings β†’ Connections; API: GET /api/config (public), /api/models (auth), /api/v1/auths/signin",
        "fix": "Set OLLAMA_BASE_URL=http://ollama:11434 (and/or OPENAI_API_BASE_URL + key) in the container env, OR have the admin set it in Settings β†’ Connections. This is THE buried needle-in-a-haystack setting β€” name it explicitly.",
        "verify": "curl http://<host>:8080/api/config returns status:true; authenticated GET /api/models lists engine models.",
    },
    "sillytavern": {
        "where": "config.yaml (config dir mounted) for server auth; per-user API config in the UI.",
        "keys": "config.yaml: listen, whitelist, basicAuthUser, basicAuthPassword; UI: extensions β†’ connection settings β†’ API URL + model; API: /api/status (401 = alive behind auth)",
        "fix": "Server auth lives in config.yaml (default admin:<password>). The API URL is per-user: UI β†’ extensions β†’ connection settings β†’ set to http://<engine>:<port>/v1 and pick the model.",
        "verify": "curl -u admin:<pw> http://<host>:8000/api/status returns JSON.",
    },
    "librechat": {
        "where": "librechat.yaml (endpoints/agents) + .env.",
        "keys": "librechat.yaml: endpoints.agents.<name> {provider, baseURL, apiKey}; .env: END_USER_* vars; API: /api/health",
        "fix": "Add an endpoints.agents block with provider=openai (or custom), baseURL=http://<engine>:<port>/v1, apiKey. Restart. Set in the UI's Admin β†’ Providers as an alternative.",
        "verify": "curl http://<host>:3080/api/health returns ok; the model appears in the UI model list.",
    },
    "lobechat": {
        "where": "In-app settings (model providers) + env vars.",
        "keys": "UI: Settings β†’ Language Model β†’ providers; env: OPENAI_API_KEY etc; API: /api/status",
        "fix": "In the UI: Settings β†’ Language Model β†’ add provider with base URL http://<engine>:<port>/v1 + key (if any). No server-side config file.",
        "verify": "curl http://<host>:3001/api/status returns 200; model selectable in chat.",
    },
    "anythingllm": {
        "where": "In-app Settings (LLM provider + vector DB) + .env.",
        "keys": "UI: Settings β†’ LLM Preference β†’ provider + base URL + key; Settings β†’ Vector Database; API: /api/system/endpoints",
        "fix": "In the UI: Settings β†’ LLM Preference β†’ pick Ollama/OpenAI, set base URL to the engine, set the key. Vector DB similar (defaults to LanceDB β€” fine for noobs).",
        "verify": "curl http://<host>:3001/api/system/endpoints lists configured endpoints.",
    },
    "dify": {
        "where": ".env file (backend) + per-app model config in UI.",
        "keys": ".env: OPENAI_API_KEY, OPENAI_API_BASE; UI: Settings β†’ Model Provider; API: /health",
        "fix": "Set OPENAI_API_BASE + OPENAI_API_KEY in .env (or use the UI Model Provider page) to point at the engine, restart. Model per-app: Settings β†’ Model Provider β†’ add.",
        "verify": "curl http://<host>/health returns healthy.",
    },
    "flowise": {
        "where": "UI credentials store + env.",
        "keys": "UI: Settings β†’ Credentials; API: /api/v1/ping, /api/v1/credentials",
        "fix": "UI: Settings β†’ Credentials β†’ add the provider credential with base URL + key. Nodes then reference it. No server-side wiring file.",
        "verify": "curl http://<host>:3000/api/v1/ping returns pong.",
    },
    "n8n": {
        "where": "UI credentials store + env (auth).",
        "keys": "env: N8N_BASIC_AUTH_USER, N8N_BASIC_AUTH_PASSWORD; UI: Credentials β†’ OpenAI/Ollama; API: /healthz",
        "fix": "UI: Credentials β†’ add the AI provider credential (base URL + key) and reference it in nodes. Set basic auth envs if exposed publicly.",
        "verify": "curl http://<host>:5678/healthz returns ok.",
    },
    "langflow": {
        "where": ".env + UI components.",
        "keys": "UI: Settings β†’ Global Variables; env: OPENAI_API_KEY etc; API: /api/v1/configs",
        "fix": "UI: Settings β†’ Global Variables β†’ add the provider base URL + key; use them in component nodes.",
        "verify": "curl http://<host>:7860/api/v1/configs returns 200 (or auth wall).",
    },
    "ragflow": {
        "where": "service_conf.yaml + .env; models configured in UI.",
        "keys": ".env: MODEL providers; service_conf.yaml: es/storage; UI: Settings β†’ Model providers; API: /api/v1/version",
        "fix": "UI: Settings β†’ Model providers β†’ add the engine (base URL + key). Backend needs the configured embedding+chat models.",
        "verify": "curl http://<host>/api/v1/version returns a version.",
    },
    "koboldai": {
        "where": "Settings files in the data dir.",
        "keys": "API: /api/v1/config/status, /api/v1/model",
        "fix": "Legacy app; connect by setting the API URL in the client to http://<host>:5000/v1 and loading a model via its UI.",
        "verify": "curl http://<host>:5000/api/v1/config/status returns config.",
    },

    # ── Vector DBs ──
    "qdrant": {
        "where": "config.yaml or QDRANT__* env vars.",
        "keys": "QDRANT__SERVICE__API_KEY, QDRANT__SERVICE__GRPC_PORT; API: /readyz, /collections",
        "fix": "Set API key via env if auth needed; ensure clients use http://<host>:6333. Point your RAG app's vector DB URL here.",
        "verify": "curl http://<host>:6333/readyz returns ok.",
    },
    "milvus": {
        "where": "milvus.yaml + etcd dependency.",
        "keys": "milvus.yaml: etcd.endpoints, proxy port 19530; API: /healthz (http :9091)",
        "fix": "Milvus needs etcd running and reachable; point your RAG app at grpc://<host>:19530.",
        "verify": "curl http://<host>:9091/healthz returns {\"status\":\"OK\"}.",
    },
    "chroma": {
        "where": "Env vars.",
        "keys": "CHROMA_SERVER_HOST, CHROMA_SERVER_PORT, CHROMA_AUTH_*; API: /api/v2/health",
        "fix": "Set CHROMA_SERVER_HOST=0.0.0.0 to accept remote clients; point your RAG app at http://<host>:8000.",
        "verify": "curl http://<host>:8000/api/v2/health returns 200.",
    },
    "weaviate": {
        "where": "Env vars.",
        "keys": "PERSISTENCE_DATA_PATH, AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED, AUTHENTICATION_APIKEY_ENABLED + APIKEY_ALLOWED_KEYS; API: /v1/meta",
        "fix": "Enable anonymous access or set an API key via env; point your RAG app at http://<host>:8081.",
        "verify": "curl http://<host>:8081/v1/meta returns version + model info.",
    },

    # ── Image / media ──
    "comfyui": {
        "where": "extra_model_paths.yaml (model folder locations).",
        "keys": "extra_model_paths.yaml: checkpoints, loras, vae paths; API: /system_stats, /prompt (POST)",
        "fix": "Edit extra_model_paths.yaml to point at the real model dirs (or use the default structure), restart. Clients POST workflows to /prompt.",
        "verify": "curl http://<host>:8188/system_stats returns GPU/RAM stats.",
    },
    "stable-diffusion-webui": {
        "where": "webui-user.sh (COMMANDLINE_ARGS) + models dir.",
        "keys": "webui-user.sh: COMMANDLINE_ARGS=--api; API: /sdapi/v1/options, /sdapi/v1/txt2img",
        "fix": "Add --api to COMMANDLINE_ARGS and restart, or the API endpoints 404. Model picker via /sdapi/v1/options (sd_model_checkpoint).",
        "verify": "curl http://<host>:7860/sdapi/v1/options returns options JSON.",
    },
    "invokeai": {
        "where": "invokeai.yaml + UI model manager.",
        "keys": "invokeai.yaml: host, port; API: /api/v1/app/version",
        "fix": "Ensure host is 0.0.0.0 in invokeai.yaml; models added via the UI Model Manager (Auto-scan a folder).",
        "verify": "curl http://<host>:9090/api/v1/app/version returns a version.",
    },

    # ── Speech ──
    "whisper": {
        "where": "CLI flags of the whisper server (faster-whisper-server / whisper.cpp).",
        "keys": "--host, --port, --model (size); API: /v1/audio/transcriptions (OpenAI-compatible)",
        "fix": "Launch with --host 0.0.0.0; point clients at http://<host>:<port>/v1. Pick model size for the GPU.",
        "verify": "curl http://<host>:<port>/health returns ok.",
    },
    "piper": {
        "where": "CLI flags of the TTS server.",
        "keys": "--host, --port; API: / (synthesize GET/POST)",
        "fix": "Launch with --host 0.0.0.0; point clients at http://<host>:<port> with the voice model.",
        "verify": "curl http://<host>:<port>/health returns ok.",
    },

    # ── Gateways / proxies / hubs ──
    "openrouter": {
        "where": "Cloud API β€” key only.",
        "keys": "API key; base https://openrouter.ai/api/v1",
        "fix": "Clients send Bearer <key> to https://openrouter.ai/api/v1. No local config.",
        "verify": "curl -H 'Authorization: Bearer <key>' https://openrouter.ai/api/v1/models works.",
    },
    "kobold-horde": {
        "where": "Cloud crowd API β€” key optional.",
        "keys": "API key (optional); API: /api/v1/status, /api/v1/generate",
        "fix": "Clients set the horde URL to http://<host>:2323 (or the public horde) and optionally their kudos key.",
        "verify": "curl http://<host>:2323/api/v1/status returns queue stats.",
    },

    # ── Basecamp's own companions ──
    "mcpo": {
        "where": "config.json (mounted) β€” the MCP server list.",
        "keys": "config.json: servers [{name, transport, route, api_key}]; API: /openapi.json, /mcp (SSE)",
        "fix": "Edit mcpo/config.json to add/point MCP servers (name + route + key), restart the container. Basecamp connects to it via SSE at /mcp.",
        "verify": "curl http://<host>:8000/openapi.json lists the proxied MCP server routes.",
    },
    "searxng": {
        "where": "settings.yml (mounted) β€” search engines + formats.",
        "keys": "settings.yml: search.formats (json), engines list; API: /search?q=...&format=json",
        "fix": "Ensure 'json' is in search.formats in settings.yml or the JSON API returns 400. Engines added/removed in the engines section.",
        "verify": "curl http://<host>:8080/ returns the SearXNG page.",
    },

    # ── Coding starter pack ──
    "code-server": {
        "where": "Container env + entrypoint extension installs. The IDE itself is VS Code in the browser.",
        "keys": "env: PASSWORD (login); entrypoint: code-server --install-extension Continue.continue / TabbyML.vscode-tabby; API: /healthz, / (web UI)",
        "fix": "Login with the PASSWORD env at http://<host>:8443. Continue.dev and the Tabby extension are pre-installed in the Basecamp pack β€” configure Continue's model to http://ollama:11434/v1 (or tabbyapi:5000/v1) in its config.json, and point the Tabby extension at http://<host>:8082.",
        "verify": "curl http://<host>:8443/healthz returns 200; the browser UI loads at :8443.",
        "pitfalls": "Extension installs run on first container start β€” give it ~60s before the IDE is fully ready. Login password comes from the PASSWORD env (default basecamp) β€” change it.",
    },
    "tabby": {
        "where": "CLI flags at container start + /data volume for models.",
        "keys": "command: serve --device cpu|--device cuda --model <name> --chat-model <name>; env: TABBY_MODEL_CACHE_DIR; API: /v1/health, /v1/completions (code completion)",
        "fix": "DO NOT USE in CPU-only containers β€” BROKEN as of 2026-08-09 (verified on 20260330 pin): tabby's bundled llama-server dlopens libcuda.so.1 at startup EVEN with --device cpu (binary is CUDA-compiled), and crashes with exit 127 'cannot open shared object file' when the container lacks NVIDIA libs. Needs --gpus all + CUDA libs. Removed from the starter pack; re-add only with GPU access or a true CPU-only tabby build.",
        "verify": "curl http://<host>:8082/v1/health β€” expected to FAIL in CPU-only containers until tabby ships a CPU-only build.",
        "pitfalls": "VERIFIED 2026-08-09: (1) 'latest' images spawn Nomic-Embed with -ngl 9999 β†’ exit 127 on constrained GPUs; (2) even the 20260330 pin crash-loops in CPU-only containers: llama-server dlopens libcuda.so.1 β†’ 'cannot open shared object file' β†’ exit 127. (3) StarCoder2-3B is completion-only (no chat template) β€” a separate instruct model is required for --chat-model. Ripped out of the pack.",
    },
    "meilisearch": {
        "where": "Env vars.",
        "keys": "MEILI_MASTER_KEY, MEILI_ENV; API: /health, /indexes, /search",
        "fix": "Set MEILI_MASTER_KEY so clients can authenticate; index your code/docs via the API and search at /search with the key.",
        "verify": "curl http://<host>:7700/health returns {\"status\":\"available\"}.",
    },
    "librechat": {
        "where": "Env vars + REQUIRES MongoDB (not self-contained).",
        "keys": "MONGO_URI (required), OPENAI_API_KEY, OPENAI_API_BASE; API: /api/health",
        "fix": "LibreChat CRASHES on boot without MONGO_URI (verified 2026-08): 'Please define the MONGO_URI environment variable'. It needs a mongo container β€” the Basecamp pack ships one (starter-mongo, mongo:7) and sets MONGO_URI=mongodb://mongo:27017/LibreChat. Image lives on GHCR (ghcr.io/danny-avila/librechat), NOT Docker Hub.",
        "verify": "curl http://<host>:3080/api/health returns ok AFTER mongo is up.",
        "pitfalls": "Image is ghcr.io/danny-avila/librechat:latest β€” the Docker Hub 'danny-avila/librechat' repo 404s. First boot takes ~60s. Depends on mongo; start mongo first (depends_on in compose).",
    },
    "flowise": {
        "where": "Env vars (PORT, FLOWISE_USERNAME, FLOWISE_PASSWORD) + /root/.flowise volume.",
        "keys": "PORT=3000, FLOWISE_USERNAME, FLOWISE_PASSWORD; API: /api/v1/ping",
        "fix": "DO NOT USE β€” BROKEN UPSTREAM as of 2026-08-09 (verified on latest AND 3.1.4): crashes at boot with ERR_PACKAGE_PATH_NOT_EXPORTED β€” @langchain/langgraph-checkpoint requires './utils/uuid' which @langchain/core doesn't export. Removed from the starter pack. Re-add only when upstream fixes their dependency tree.",
        "verify": "curl http://<host>:7860/api/v1/ping β€” expected to FAIL until upstream fixes the langchain dep.",
        "pitfalls": "KNOWN BUG (verified 2026-08-09, both latest and 3.1.4): ERR_PACKAGE_PATH_NOT_EXPORTED './utils/uuid' in @langchain/core crashes node at boot (ReActAgentChat/ReActAgentLLM nodes). Not a compose problem β€” the image's dependency tree is broken. Ripped out of the pack.",
    },
    "postgres": {
        "where": "Container env (db/user/password) + data volume.",
        "keys": "POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD; port 5432; pgvector extension for embeddings",
        "fix": "This is Hermes's long-term memory backend (Postgres + pgvector). Point hermes at it: hermes memory configure --backend postgres --dsn 'postgresql://<user>:<pass>@starter-postgres:5432/<db>'. The pgvector image ships the extension β€” create it with: docker exec starter-postgres psql -U hermes -d hermes -c 'CREATE EXTENSION IF NOT EXISTS vector;'",
        "verify": "docker exec starter-postgres pg_isready -U hermes -d hermes returns 'accepting connections'.",
    },
    "mongo": {
        "where": "Container env + /data/db volume. Database for LibreChat (and other apps).",
        "keys": "MONGO_URI=mongodb://mongo:27017/<db>; port 27017; image mongo:7",
        "fix": "Point dependent apps at mongodb://<host>:27017/<dbname>. LibreChat requires MONGO_URI or it crashes on boot (verified 2026-08). No auth by default on the pack's instance β€” add a root user for anything public.",
        "verify": "docker exec starter-mongo mongosh --quiet --eval 'db.runCommand({ping:1}).ok' returns 1.",
    },
}


def wiring_recipe_markdown(service_types=None):
    """Render WIRING_RECIPES as markdown, optionally filtered to a type list.

    Returns a list of markdown lines (no trailing newline joining) ready to
    be embedded in a SKILL.md or written as a standalone reference file.
    """
    types = service_types or list(WIRING_RECIPES.keys())
    lines = []
    for t in sorted(types):
        r = WIRING_RECIPES.get(t)
        if not r:
            continue
        lines.append(f"### {t}")
        lines.append(f"- **Config lives in:** {r['where']}")
        lines.append(f"- **Keys/endpoints:** {r['keys']}")
        lines.append(f"- **Fix:** {r['fix']}")
        lines.append(f"- **Verify:** {r['verify']}")
        if r.get("pitfalls"):
            lines.append(f"- **Pitfalls (verified):** {r['pitfalls']}")
        lines.append("")
    return lines


# ── KNOWN CREDENTIALS & DEFAULTS (overhaul 2026-08-10) ────────────────────
# Public-knowledge / stack-default credentials only. These let the box's
# Hermes answer "what's the default login for X?" with an EXACT value
# instead of guessing. Custom user keys are NEVER listed here β€” the agent
# must ask the user for those. Keep this list honest: only creds we've
# verified on the reference stack.
KNOWN_CREDENTIALS = [
    {
        "service": "SillyTavern",
        "username": "admin",
        "password": "tabby",
        "url": "http://<host>:8000",
        "note": "Default admin account created on first run in the reference stack.",
    },
    {
        "service": "TabbyAPI",
        "username": None,
        "password": None,
        "url": "http://<host>:5000",
        "note": "API key + admin key live in api_tokens.yml on the HOST (mounted "
                "config). Ask the user for the key β€” it is generated at first "
                "start, not a public default.",
    },
    {
        "service": "Ollama",
        "username": None,
        "password": None,
        "url": "http://<host>:11434",
        "note": "No auth by default (OpenAI-compatible /v1 uses API key 'ollama' "
                "when the client requires one).",
    },
    {
        "service": "LibreChat",
        "username": None,
        "password": None,
        "url": "http://<host>:3080",
        "note": "First registered user becomes admin. No default creds β€” point "
                "the user at the signup page.",
    },
    {
        "service": "Open WebUI",
        "username": None,
        "password": None,
        "url": "http://<host>:3000",
        "note": "First registered user becomes admin. No default creds.",
    },
    {
        "service": "LobeChat",
        "username": None,
        "password": None,
        "url": "http://<host>:3210",
        "note": "First registered user becomes admin. No default creds.",
    },
    {
        "service": "AnythingLLM",
        "username": None,
        "password": None,
        "url": "http://<host>:3001",
        "note": "Multi-user mode: first user is admin. Single-user mode: no login.",
    },
    {
        "service": "PostgreSQL",
        "username": "postgres",
        "password": "postgres",
        "url": "http://<host>:5432",
        "note": "Stack default for the starter pack's pgvector memory DB "
                "(docker-compose.starter.yml).",
    },
    {
        "service": "MongoDB",
        "username": None,
        "password": None,
        "url": "http://<host>:27017",
        "note": "No auth inside the starter pack's private network. Exposing it "
                "to the host requires enabling auth.",
    },
    {
        "service": "Meilisearch",
        "username": None,
        "password": "master-key",
        "url": "http://<host>:7700",
        "note": "Default master key 'master-key' in the starter pack compose. "
                "Real deployments must change it.",
    },
    {
        "service": "n8n",
        "username": None,
        "password": None,
        "url": "http://<host>:5678",
        "note": "First run creates the owner account. No default creds.",
    },
    {
        "service": "code-server",
        "username": None,
        "password": None,
        "url": "http://<host>:8443",
        "note": "Password set via PASSWORD env var in compose; if unset, code-server "
                "generates one and prints it in the container logs (docker logs).",
    },
    {
        "service": "Qdrant",
        "username": None,
        "password": None,
        "url": "http://<host>:6333",
        "note": "No auth by default; API key optional via QDRANT__SERVICE__API_KEY.",
    },
    {
        "service": "Chroma",
        "username": None,
        "password": None,
        "url": "http://<host>:8000",
        "note": "No auth by default; tenant/database isolation only.",
    },
]


def known_credentials_markdown():
    """Render KNOWN_CREDENTIALS as markdown lines for the stack skill."""
    lines = []
    for c in KNOWN_CREDENTIALS:
        svc = c["service"]
        if c["username"] and c["password"]:
            cred = f"`{c['username']}` / `{c['password']}`"
        else:
            cred = "no public default (see note)"
        lines.append(f"- **{svc}** ({c['url']}): {cred} β€” {c['note']}")
    lines.append("")
    lines.append("Rule: if a service's real credentials are NOT in this list, the")
    lines.append("user must provide them β€” never invent a password or key.")
    lines.append("")
    return lines