mindcraft 0.1.4-0 → 0.1.4-2

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/README.md CHANGED
@@ -1,60 +1,50 @@
1
- <h1 align="center">🧠mindcraft⛏️</h1>
2
- <h1 align="center">
3
- <a href="https://trendshift.io/repositories/9163" target="_blank"><img src="https://trendshift.io/api/badge/repositories/9163" alt="kolbytn%2Fmindcraft | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
4
- </h1>
5
-
6
- <p align="center">Crafting minds for Minecraft with LLMs and <a href="https://prismarinejs.github.io/mineflayer/#/">Mineflayer!</a></p>
7
-
8
- <p align="center">
9
- <a href="https://github.com/mindcraft-bots/mindcraft/blob/main/FAQ.md">FAQ</a> |
10
- <a href="https://discord.gg/mp73p35dzC">Discord Support</a> |
11
- <a href="https://www.youtube.com/watch?v=gRotoL8P8D8">Video Tutorial</a> |
12
- <a href="https://kolbynottingham.com/mindcraft/">Blog Post</a> |
13
- <a href="https://mindcraft-minecollab.github.io/index.html">Paper Website</a> |
14
- <a href="https://github.com/mindcraft-bots/mindcraft/blob/main/minecollab.md">MineCollab</a>
15
- </p>
1
+ # mindcraft
16
2
 
17
- > [!Caution]
18
- Do not connect this bot to public servers with coding enabled. This project allows an LLM to write/execute code on your computer. The code is sandboxed, but still vulnerable to injection attacks. Code writing is disabled by default, you can enable it by setting `allow_insecure_coding` to `true` in `settings.js`. Ye be warned.
3
+ LLM agents that play Minecraft, built on [mineflayer](https://prismarinejs.github.io/mineflayer/#/).
19
4
 
20
- # Getting Started
21
- ## Requirements
5
+ > [!Note]
6
+ > This is an **unofficial fork** of [mindcraft-bots/mindcraft](https://github.com/mindcraft-bots/mindcraft) that packages it as an npm CLI with a browser-based setup. See [upstream issue #758](https://github.com/mindcraft-bots/mindcraft/issues/758) for context. All credit for the agent itself goes to the upstream authors.
22
7
 
23
- - [Minecraft Java Edition](https://www.minecraft.net/en-us/store/minecraft-java-bedrock-edition-pc) (up to v1.21.11, recommend v1.21.6)
24
- - [Node.js Installed](https://nodejs.org/) (Node v18 or v20 LTS recommended. Node v24+ may cause issues with native dependencies)
25
- - At least one API key from a supported API provider. See [supported APIs](#model-customization). OpenAI is the default.
8
+ > [!Caution]
9
+ > Don't connect to public servers with `allow_insecure_coding` enabled it lets the LLM write and run code on your machine. It's off by default.
26
10
 
27
- > [!Important]
28
- > If installing node on windows, ensure you check `Automatically install the necessary tools`
29
- >
30
- > If you encounter `npm install` errors on macOS, see the [FAQ](FAQ.md#common-issues) for troubleshooting native module build issues
11
+ ## Quick start
31
12
 
32
- ## Install and Run
13
+ ```bash
14
+ npx mindcraft ui
15
+ ```
33
16
 
34
- 1. Make sure you have the requirements above.
17
+ This opens a web UI where you set your Minecraft server address, add an API key, and create an agent. Settings are saved to `~/.config/mindcraft/` so subsequent runs pick up where you left off.
35
18
 
36
- 2. Download the [latest release](https://github.com/mindcraft-bots/mindcraft/releases/latest) and unzip it, or clone the repository.
19
+ You'll need:
20
+ - [Minecraft Java Edition](https://www.minecraft.net/en-us/store/minecraft-java-bedrock-edition-pc) (up to v1.21.11; v1.21.6 recommended), with a world open to LAN
21
+ - [Node.js](https://nodejs.org/) 18 or 20 (v24+ may have native-dep issues)
22
+ - An API key for one of the [supported providers](#supported-providers) (or a local Ollama model)
37
23
 
38
- 3. Rename `keys.example.json` to `keys.json` and fill in your API keys (you only need one). The desired model is set in `andy.json` or other profiles. For other models refer to the table below.
24
+ For programmatic use:
39
25
 
40
- 4. In terminal/command prompt, run `npm install` from the installed directory
26
+ ```js
27
+ import { init, createAgent } from 'mindcraft';
28
+ await init();
29
+ await createAgent({ host: 'localhost', port: 25565, profile: { name: 'andy', model: 'gpt-4o-mini' } });
30
+ ```
41
31
 
42
- 5. Start a minecraft world and open it to LAN on localhost port `55916`
32
+ ## Running from a checkout
43
33
 
44
- 6. Run `node main.js` from the installed directory
34
+ If you want to hack on it, clone and `npm install`, start a Minecraft world open to LAN, then `node main.js`. Put API keys in `keys.json` (copy `keys.example.json`) and pick a profile in `settings.js`. The [FAQ](FAQ.md) covers common install errors.
45
35
 
46
- If you encounter issues, check the [FAQ](https://github.com/mindcraft-bots/mindcraft/blob/main/FAQ.md) or find support on [discord](https://discord.gg/mp73p35dzC). We are currently not very responsive to github issues. To run tasks please refer to [Minecollab Instructions](minecollab.md#installation)
36
+ To run benchmark tasks: `node main.js --task_path tasks/basic/single_agent.json --task_id gather_oak_logs` see [MineCollab](minecollab.md).
47
37
 
38
+ ## Configuration
48
39
 
49
- # Configuration
50
- ## Model Customization
40
+ The `npx mindcraft` UI writes config to `~/.config/mindcraft/config.json` and `keys.json`. Don't edit `settings.js` — it's defaults only. To override from the shell, use `SETTINGS_JSON='{"host":"..."}' node main.js` or `--profiles ./foo.json`.
51
41
 
52
- You can configure project details in `settings.js`. [See file.](settings.js)
42
+ Agent name, model and prompts live in a profile JSON (e.g. `andy.json`). The model can be a string like `"gpt-4o-mini"` or `"anthropic/claude-sonnet-4-6"`, or an object — see [Model Specifications](#model-specifications).
53
43
 
54
- You can configure the agent's name, model, and prompts in their profile like `andy.json`. The model can be specified with the `model` field, with values like `model: "gemini-2.5-pro"`. You will need the correct API key for the API provider you choose. See all supported APIs below.
44
+ ## Supported providers
55
45
 
56
46
  <details>
57
- <summary><strong>⭐ VIEW SUPPORTED APIs ⭐</strong></summary>
47
+ <summary>All supported APIs</summary>
58
48
 
59
49
  | API Name | Config Variable| Docs |
60
50
  |------|------|------|
@@ -87,59 +77,11 @@ To install our models, install ollama and run the following terminal command:
87
77
  ollama pull sweaterdog/andy-4:micro-q8_0 && ollama pull embeddinggemma
88
78
  ```
89
79
 
90
- ## Online Servers
91
- To connect to online servers your bot will need an official Microsoft/Minecraft account. You can use your own personal one, but will need another account if you want to connect too and play with it. To connect, change these lines in `settings.js`:
92
- ```javascript
93
- "host": "111.222.333.444",
94
- "port": 55920,
95
- "auth": "microsoft",
96
-
97
- // rest is same...
98
- ```
99
- > [!Important]
100
- > The bot's name in the profile.json must exactly match the Minecraft profile name! Otherwise the bot will spam talk to itself.
80
+ ## Online servers
101
81
 
102
- To use different accounts, Mindcraft will connect with the account that the Minecraft launcher is currently using. You can switch accounts in the launcher, then run `node main.js`, then switch to your main account after the bot has connected.
82
+ To connect to an online server the agent needs a real Microsoft/Minecraft account. Set `auth: "microsoft"` in the Server panel (or your config). The agent's profile name must exactly match the Minecraft account's username, or it will talk to itself. Mindcraft uses whichever account is active in the Minecraft launcher; switch accounts there, start the agent, then switch back.
103
83
 
104
- ## Tasks
105
-
106
- Tasks automatically start the bot with a prompt and a goal item to acquire or blueprint to construct. To run a simple task that involves collecting 4 oak_logs run
107
-
108
- `node main.js --task_path tasks/basic/single_agent.json --task_id gather_oak_logs`
109
-
110
- Here is an example task json format:
111
-
112
- ```
113
- {
114
- "gather_oak_logs": {
115
- "goal": "Collect at least four logs",
116
- "initial_inventory": {
117
- "0": {
118
- "wooden_axe": 1
119
- }
120
- },
121
- "agent_count": 1,
122
- "target": "oak_log",
123
- "number_of_target": 4,
124
- "type": "techtree",
125
- "max_depth": 1,
126
- "depth": 0,
127
- "timeout": 300,
128
- "blocked_actions": {
129
- "0": [],
130
- "1": []
131
- },
132
- "missing_items": [],
133
- "requires_ctable": false
134
- }
135
- }
136
- ```
137
-
138
- The `initial_inventory` is what the bot will have at the start of the episode, `target` refers to the target item and `number_of_target` refers to the number of target items the agent needs to collect to successfully complete the task.
139
-
140
- If you want more optimization and automatic launching of the minecraft world, you will need to follow the instructions in [Minecollab Instructions](minecollab.md#installation)
141
-
142
- ## Docker Container
84
+ ## Docker
143
85
 
144
86
  If you intend to `allow_insecure_coding`, it is a good idea to run the app in a docker container to reduce risks of running unknown code. This is strongly recommended before connecting to remote servers, although still does not guarantee complete safety.
145
87
 
@@ -151,21 +93,11 @@ or simply
151
93
  docker-compose up --build
152
94
  ```
153
95
 
154
- When running in docker, if you want the bot to join your local minecraft server, you have to use a special host address `host.docker.internal` to call your localhost from inside your docker container. Put this into your [settings.js](settings.js):
155
-
156
- ```javascript
157
- "host": "host.docker.internal", // instead of "localhost", to join your local minecraft from inside the docker container
158
- ```
159
-
160
- To connect to an unsupported minecraft version, you can try to use [viaproxy](services/viaproxy/README.md)
161
-
162
- # Bot Profiles
96
+ When running in docker, use `host.docker.internal` instead of `localhost` to reach a Minecraft server on your host machine. For unsupported Minecraft versions, try [viaproxy](services/viaproxy/README.md).
163
97
 
164
- Bot profiles are json files (such as `andy.json`) that define:
98
+ # Profiles
165
99
 
166
- 1. Bot backend LLMs to use for talking, coding, and embedding.
167
- 2. Prompts used to influence the bot's behavior.
168
- 3. Examples help the bot perform tasks.
100
+ A profile JSON (e.g. `andy.json`) defines the agent's name, which LLMs it uses for chat/coding/embedding, its prompts, and example conversations.
169
101
 
170
102
  ## Model Specifications
171
103
 
@@ -217,16 +149,9 @@ If you try to use an unsupported model, then it will default to a simple word-ov
217
149
 
218
150
  Voice synthesis models are used to narrate bot responses and specified with `speak_model`. This field is parsed differently than other models and only supports strings formatted as `"{api}/{model}/{voice}"`, like `"openai/tts-1/echo"`. We only support `openai` and `google` for voice synthesis.
219
151
 
220
- ## Specifying Profiles via Command Line
221
-
222
- By default, the program will use the profiles specified in `settings.js`. You can specify one or more agent profiles using the `--profiles` argument: `node main.js --profiles ./profiles/andy.json ./profiles/jill.json`
223
-
224
-
225
152
  # Contributing
226
153
 
227
- We welcome contributions to the project! We are generally less responsive to github issues, and more responsive to pull requests. Join the [discord](https://discord.gg/mp73p35dzC) for more active support and direction.
228
-
229
- While AI generated code is allowed, please vet it carefully. Submitting tons of sloppy code and documentation actively harms development.
154
+ Please contribute upstream at [mindcraft-bots/mindcraft](https://github.com/mindcraft-bots/mindcraft) this fork tracks it. Join the upstream [discord](https://discord.gg/mp73p35dzC) for support.
230
155
 
231
156
  ## Patches
232
157
 
package/bin/mindcraft.js CHANGED
@@ -46,34 +46,39 @@ async function main() {
46
46
  if (opts.cmd !== 'ui') return;
47
47
 
48
48
  // Load persisted config from ~/.config/mindcraft so a restart picks up where the
49
- // wizard left off: keys → process.env, server settings → spec defaults,
50
- // saved bots → recreated.
49
+ // setup panels left off: keys → process.env, server + settings → spec defaults,
50
+ // saved agents → recreated. Merge order is settings_spec.json defaults
51
+ // ← config.settings ← config.server ← per-agent overrides.
51
52
  userconfig.loadKeysIntoEnv();
52
53
  const config = userconfig.getConfig();
53
54
  overrideSpecDefaults({
54
55
  data_dir: opts.dataDir,
56
+ ...(config?.settings || {}),
55
57
  ...(config?.server || {}),
56
58
  });
57
59
 
58
60
  await init(false, opts.port, opts.open);
59
61
  console.log(`\nMindcraft UI: http://localhost:${opts.port}`);
60
- console.log(`Bot data dir: ${opts.dataDir}`);
62
+ console.log(`Agent data dir: ${opts.dataDir}`);
61
63
 
62
- if (config?.bots?.length) {
64
+ const agents = config?.agents || config?.bots; // .bots = legacy key
65
+ if (agents?.length) {
63
66
  const profiles = Object.fromEntries(userconfig.listProfiles().map(p => [p.name, p]));
64
- for (const bot of config.bots) {
65
- const profile = profiles[bot.profile];
66
- if (!profile) { console.warn(`Skipping bot "${bot.profile}": profile not found`); continue; }
67
- console.log(`Restoring bot: ${bot.profile}`);
67
+ for (const a of agents) {
68
+ const profile = profiles[a.profile];
69
+ if (!profile) { console.warn(`Skipping agent "${a.profile}": profile not found`); continue; }
70
+ console.log(`Restoring agent: ${a.profile}`);
68
71
  await createAgent({
72
+ ...(config.settings || {}),
69
73
  ...(config.server || {}),
74
+ ...(a.settings || {}),
70
75
  data_dir: opts.dataDir,
71
- base_profile: bot.base_profile || 'assistant',
76
+ base_profile: a.base_profile || 'assistant',
72
77
  profile,
73
78
  });
74
79
  }
75
80
  } else {
76
- console.log(`No saved bots — open the UI to run setup.\n`);
81
+ console.log(`No saved agents — open the UI to run setup.\n`);
77
82
  }
78
83
  }
79
84
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindcraft",
3
- "version": "0.1.4-0",
3
+ "version": "0.1.4-2",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "tag": "latest"
@@ -65,8 +65,8 @@
65
65
  "node-canvas-webgl": "^0.3.0"
66
66
  },
67
67
  "overrides": {
68
- "canvas": "^3.1.0",
69
- "gl": "^8.1.6"
68
+ "canvas": "^3.1.0",
69
+ "gl": "^8.1.6"
70
70
  },
71
71
  "scripts": {
72
72
  "postinstall": "patch-package",
package/settings.js CHANGED
@@ -1,3 +1,13 @@
1
+ // Default settings for the `node main.js` entrypoint.
2
+ //
3
+ // DON'T EDIT THIS FILE for local config — that just dirties your git checkout.
4
+ // Instead, override via:
5
+ // - `npx mindcraft ui` (writes ~/.config/mindcraft/config.json), or
6
+ // - the SETTINGS_JSON env var: SETTINGS_JSON='{"host":"..."}' node main.js, or
7
+ // - CLI flags: node main.js --profiles ./foo.json
8
+ //
9
+ // The web UI / `npx mindcraft` flow does not read this file at all; it uses
10
+ // settings_spec.json defaults overlaid with ~/.config/mindcraft/config.json.
1
11
  const settings = {
2
12
  "minecraft_version": "auto", // or specific version like "1.21.6"
3
13
  "host": "127.0.0.1", // or "localhost", "your.ip.address.here"
@@ -11,7 +11,7 @@ import { SelfPrompter } from './self_prompter.js';
11
11
  import convoManager from './conversation.js';
12
12
  import { handleTranslation, handleEnglishTranslation } from '../utils/translator.js';
13
13
  import { addBrowserViewer } from './vision/browser_viewer.js';
14
- import { serverProxy, sendOutputToServer } from './mindserver_proxy.js';
14
+ import { serverProxy, sendOutputToServer, sendLogToServer } from './mindserver_proxy.js';
15
15
  import settings from './settings.js';
16
16
  import { Task } from './tasks/tasks.js';
17
17
  import { speak } from './speak.js';
@@ -108,7 +108,11 @@ export class Agent {
108
108
  this.bot.once('spawn', async () => {
109
109
  try {
110
110
  clearTimeout(spawnTimeout);
111
- addBrowserViewer(this.bot, count_id);
111
+ const viewer = await addBrowserViewer(this.bot, count_id);
112
+ if (viewer.error) {
113
+ serverProxy.getSocket()?.emit('viewer-status', this.name, { error: viewer.error });
114
+ sendOutputToServer(this.name, `Viewer unavailable: ${viewer.error}`);
115
+ }
112
116
  console.log('Initializing vision intepreter...');
113
117
  // VisionInterpreter -> camera.js -> node-canvas-webgl, whose native
114
118
  // deps (gl, canvas) are optionalDependencies and may not have built.
@@ -303,6 +307,7 @@ export class Agent {
303
307
  // Now translate the message
304
308
  message = await handleEnglishTranslation(message);
305
309
  console.log('received message from', source, ':', message);
310
+ sendLogToServer(this.name, self_prompt ? 'thinking' : 'user', `${source}: ${message}`);
306
311
 
307
312
  const checkInterrupt = () => this.self_prompter.shouldInterrupt(self_prompt) || this.shut_up || convoManager.responseScheduledFor(source);
308
313
 
@@ -328,6 +333,7 @@ export class Agent {
328
333
  let res = await this.prompter.promptConvo(history);
329
334
 
330
335
  console.log(`${this.name} full response to ${source}: ""${res}""`);
336
+ sendLogToServer(this.name, 'thinking', res);
331
337
 
332
338
  if (res.trim().length === 0) {
333
339
  console.warn('no response')
@@ -35,7 +35,7 @@ export const actionsList = [
35
35
  perform: async function(agent, prompt) {
36
36
  // just ignore prompt - it is now in context in chat history
37
37
  if (!settings.allow_insecure_coding) {
38
- agent.openChat('newAction is disabled. Enable with allow_insecure_coding=true in settings.js');
38
+ agent.openChat('newAction is disabled. Enable with allow_insecure_coding=true in agent settings.');
39
39
  return "newAction not allowed! Code writing is disabled in settings. Notify the user.";
40
40
  }
41
41
  let result = "";
@@ -134,3 +134,9 @@ export function sendBotChatToServer(agentName, json) {
134
134
  export function sendOutputToServer(agentName, message) {
135
135
  serverProxy.getSocket().emit('bot-output', agentName, message);
136
136
  }
137
+
138
+ // structured conversation log for the UI's per-agent log panel
139
+ // role: 'user' | 'bot' | 'thinking' | 'error'
140
+ export function sendLogToServer(agentName, role, text) {
141
+ serverProxy.getSocket()?.emit('agent-log', agentName, { role, text });
142
+ }
@@ -1,8 +1,31 @@
1
1
  import settings from '../settings.js';
2
- import prismarineViewer from 'prismarine-viewer';
3
- const mineflayerViewer = prismarineViewer.mineflayer;
2
+ import net from 'net';
4
3
 
5
- export function addBrowserViewer(bot, count_id) {
6
- if (settings.render_bot_view)
7
- mineflayerViewer(bot, { port: 3000+count_id, firstPerson: true, });
8
- }
4
+ // prismarine-viewer starts an express server on a fixed port and doesn't expose
5
+ // the http.Server, so an EADDRINUSE becomes an uncaught exception that kills
6
+ // the agent process. Probe the port first, and lazy-import so missing optional
7
+ // native deps also degrade to "no viewer" instead of crashing.
8
+ function portFree(port) {
9
+ return new Promise((resolve) => {
10
+ const s = net.createServer()
11
+ .once('error', () => resolve(false))
12
+ .once('listening', () => s.close(() => resolve(true)))
13
+ .listen(port, '127.0.0.1');
14
+ });
15
+ }
16
+
17
+ export async function addBrowserViewer(bot, count_id) {
18
+ if (!settings.render_bot_view) return { ok: false, disabled: true };
19
+ const port = 3000 + count_id;
20
+ try {
21
+ if (!(await portFree(port))) {
22
+ throw new Error(`port ${port} is already in use`);
23
+ }
24
+ const { default: prismarineViewer } = await import('prismarine-viewer');
25
+ prismarineViewer.mineflayer(bot, { port, firstPerson: true });
26
+ return { ok: true, port };
27
+ } catch (err) {
28
+ console.warn(`prismarine-viewer unavailable (port ${port}):`, err.message);
29
+ return { ok: false, port, error: err.message };
30
+ }
31
+ }
@@ -131,7 +131,7 @@ export async function getServer(host, port, version) {
131
131
 
132
132
  // Server not found
133
133
  if (server == null)
134
- throw new Error(`MC server not found. (Host: ${host}, Port: ${port}) Check the host and port in settings.js, and ensure the server is running and open to public or LAN.`);
134
+ throw new Error(`MC server not found. (Host: ${host}, Port: ${port}) Check the host and port in your server config, and ensure the server is running and open to public or LAN.`);
135
135
 
136
136
  serverString = `(Host: ${server.host}, Port: ${server.port}, Version: ${server.version})`;
137
137
 
@@ -146,7 +146,7 @@ export async function getServer(host, port, version) {
146
146
  if (!isSupported)
147
147
  throw new Error(`MC server was found ${serverString}, but version is unsupported. Supported versions are: ${mc.supportedVersions.join(", ")}.`);
148
148
  else if (version !== "auto" && server.version !== version)
149
- throw new Error(`MC server was found ${serverString}, but version is incorrect. Expected ${version}, but found ${server.version}. Check the server version in settings.js.`);
149
+ throw new Error(`MC server was found ${serverString}, but version is incorrect. Expected ${version}, but found ${server.version}. Check the server version in your config.`);
150
150
  else
151
151
  console.log(`MC server found. ${serverString}`);
152
152
 
@@ -5,6 +5,7 @@ import path from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import * as mindcraft from './mindcraft.js';
7
7
  import * as userconfig from './userconfig.js';
8
+ import { configuredProviders } from './providers.js';
8
9
  import { readFileSync } from 'fs';
9
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
11
 
@@ -35,10 +36,16 @@ class AgentConnection {
35
36
  this.in_game = false;
36
37
  this.full_state = null;
37
38
  this.viewer_port = viewer_port;
39
+ this.viewer_error = null;
40
+ this.log = [];
38
41
  }
39
42
  setSettings(settings) {
40
43
  this.settings = settings;
41
44
  }
45
+ appendLog(entry) {
46
+ this.log.push({ ...entry, ts: Date.now() });
47
+ if (this.log.length > 500) this.log.shift();
48
+ }
42
49
  }
43
50
 
44
51
  export function registerAgent(settings, viewer_port) {
@@ -218,21 +225,40 @@ export function createMindServer(host_public = false, port = 8080) {
218
225
  });
219
226
 
220
227
  socket.on('bot-output', (agentName, message) => {
228
+ agent_connections[agentName]?.appendLog({ role: 'bot', text: message });
221
229
  io.emit('bot-output', agentName, message);
222
230
  });
223
231
 
232
+ socket.on('agent-log', (agentName, entry) => {
233
+ agent_connections[agentName]?.appendLog(entry);
234
+ io.emit('agent-log', agentName, entry);
235
+ });
236
+
237
+ socket.on('get-agent-log', (agentName, callback) => {
238
+ callback({ log: agent_connections[agentName]?.log || [] });
239
+ });
240
+
241
+ socket.on('viewer-status', (agentName, status) => {
242
+ if (agent_connections[agentName]) {
243
+ agent_connections[agentName].viewer_error = status.error || null;
244
+ agentsStatusUpdate();
245
+ }
246
+ });
247
+
224
248
  socket.on('listen-to-agents', () => {
225
249
  addListener(socket);
226
250
  });
227
251
 
228
- // ---- setup wizard / persistence ----
252
+ // ---- setup panels / persistence ----
229
253
 
230
254
  socket.on('get-config', (callback) => {
231
255
  callback({
232
256
  config: userconfig.getConfig(),
233
257
  hasKeys: userconfig.hasKeys(),
258
+ providers: configuredProviders(userconfig.getKeys()),
234
259
  profiles: userconfig.listProfiles(),
235
260
  settingsSpec: settings_spec,
261
+ paths: { root: userconfig.paths.ROOT },
236
262
  });
237
263
  });
238
264
 
@@ -241,11 +267,12 @@ export function createMindServer(host_public = false, port = 8080) {
241
267
  catch (e) { callback({ success: false, error: e.message }); }
242
268
  });
243
269
 
244
- socket.on('save-config', (config, callback) => {
270
+ socket.on('save-config', (partial, callback) => {
245
271
  try {
246
- userconfig.saveConfig(config);
247
- if (config.server) overrideSpecDefaults(config.server);
248
- callback({ success: true });
272
+ const merged = userconfig.mergeConfig(partial);
273
+ if (partial.server) overrideSpecDefaults(partial.server);
274
+ if (partial.settings) overrideSpecDefaults(partial.settings);
275
+ callback({ success: true, config: merged });
249
276
  } catch (e) { callback({ success: false, error: e.message }); }
250
277
  });
251
278
 
@@ -254,6 +281,18 @@ export function createMindServer(host_public = false, port = 8080) {
254
281
  catch (e) { callback({ success: false, error: e.message }); }
255
282
  });
256
283
 
284
+ socket.on('delete-profile', (name, callback) => {
285
+ try {
286
+ userconfig.deleteProfile(name);
287
+ const cfg = userconfig.getConfig() || {};
288
+ if (cfg.agents) {
289
+ cfg.agents = cfg.agents.filter(a => a.profile !== name);
290
+ userconfig.saveConfig(cfg);
291
+ }
292
+ callback({ success: true });
293
+ } catch (e) { callback({ success: false, error: e.message }); }
294
+ });
295
+
257
296
  socket.on('list-profiles', (callback) => {
258
297
  callback({ profiles: userconfig.listProfiles() });
259
298
  });
@@ -278,9 +317,10 @@ function agentsStatusUpdate(socket) {
278
317
  for (let agentName in agent_connections) {
279
318
  const conn = agent_connections[agentName];
280
319
  agents.push({
281
- name: agentName,
320
+ name: agentName,
282
321
  in_game: conn.in_game,
283
322
  viewerPort: conn.viewer_port,
323
+ viewerError: conn.viewer_error,
284
324
  socket_connected: !!conn.socket
285
325
  });
286
326
  };
@@ -0,0 +1,38 @@
1
+ // Metadata for the API-providers setup panel: maps each provider (the `api`
2
+ // prefix used in profiles, see src/models/*.js) to its env-var key name and a
3
+ // short list of suggested models. Suggested models are just defaults for the
4
+ // dropdown — any model string the provider accepts will work.
5
+ //
6
+ // Order matters: the first entry is the wizard's default selection, matching
7
+ // upstream's default (OpenAI / andy.json uses gpt-4o-mini).
8
+
9
+ export const PROVIDERS = [
10
+ { api: 'openai', label: 'OpenAI', key: 'OPENAI_API_KEY', models: ['gpt-5.4-mini', 'gpt-5.4', 'gpt-5.4-nano', 'o3'] },
11
+ { api: 'anthropic', label: 'Anthropic', key: 'ANTHROPIC_API_KEY', models: ['claude-sonnet-4-6', 'claude-opus-4-6', 'claude-haiku-4-5'] },
12
+ { api: 'google', label: 'Google Gemini', key: 'GEMINI_API_KEY', models: ['gemini-3.1-pro', 'gemini-2.5-pro', 'gemini-2.5-flash'] },
13
+ { api: 'xai', label: 'xAI (Grok)', key: 'XAI_API_KEY', models: ['grok-4', 'grok-code-fast-1'] },
14
+ { api: 'deepseek', label: 'DeepSeek', key: 'DEEPSEEK_API_KEY', models: ['deepseek-chat', 'deepseek-reasoner'] },
15
+ { api: 'mistral', label: 'Mistral', key: 'MISTRAL_API_KEY', models: ['mistral-large-latest', 'mistral-small-latest'] },
16
+ { api: 'groq', label: 'Groq', key: 'GROQCLOUD_API_KEY', models: ['groq/llama-3.3-70b-versatile', 'groq/openai/gpt-oss-120b'] },
17
+ { api: 'qwen', label: 'Qwen', key: 'QWEN_API_KEY', models: ['qwen-max', 'qwen-plus'] },
18
+ { api: 'replicate', label: 'Replicate', key: 'REPLICATE_API_KEY', models: ['replicate/meta/meta-llama-3-70b-instruct'] },
19
+ { api: 'openrouter', label: 'OpenRouter', key: 'OPENROUTER_API_KEY', models: ['openrouter/anthropic/claude-sonnet-4-6', 'openrouter/openai/gpt-5.4'] },
20
+ { api: 'huggingface', label: 'Hugging Face', key: 'HUGGINGFACE_API_KEY',models: ['huggingface/meta-llama/Llama-3.3-70B-Instruct'] },
21
+ { api: 'novita', label: 'Novita', key: 'NOVITA_API_KEY', models: ['novita/meta-llama/llama-3.3-70b-instruct'] },
22
+ { api: 'hyperbolic', label: 'Hyperbolic', key: 'HYPERBOLIC_API_KEY', models: ['hyperbolic/meta-llama/Llama-3.3-70B-Instruct'] },
23
+ { api: 'cerebras', label: 'Cerebras', key: 'CEREBRAS_API_KEY', models: ['cerebras/llama-3.3-70b'] },
24
+ { api: 'mercury', label: 'Mercury', key: 'MERCURY_API_KEY', models: ['mercury/mercury-coder-small'] },
25
+ { api: 'glhf', label: 'glhf.chat', key: 'GHLF_API_KEY', models: ['glhf/hf:meta-llama/Llama-3.3-70B-Instruct'] },
26
+ { api: 'azure', label: 'Azure OpenAI', key: 'OPENAI_API_KEY', models: ['azure/gpt-5.4'] },
27
+ { api: 'ollama', label: 'Ollama (local)', key: null, models: ['ollama/sweaterdog/andy-4', 'ollama/llama3.3'] },
28
+ { api: 'lmstudio', label: 'LM Studio (local)',key: null, models: ['lmstudio/default'] },
29
+ { api: 'vllm', label: 'vLLM (local)', key: null, models: ['vllm/default'] },
30
+ ];
31
+
32
+ export function configuredProviders(keys) {
33
+ const have = new Set(Object.entries(keys).filter(([, v]) => v).map(([k]) => k));
34
+ return PROVIDERS.map(p => ({
35
+ ...p,
36
+ configured: p.key === null || have.has(p.key) || !!process.env[p.key],
37
+ }));
38
+ }