2025年8月31日日曜日

code-server に Github copilot chat プラグインをインストールする方法

code-server に Github copilot chat プラグインをインストールする方法

概要

code-server の最新版に Github 公式の copilot-chat のプラグインがインストールできたのでインストール方法を紹介します

ポイントは code-server 自体で一度 Github アカウントにログインする必要がある点です

環境

  • Ubuntu 24.04
  • code-server v4.103.2
  • Github.copilot-chat 0.30.3
    • 執筆時点の最新バージョン 0.32 系はインストールできないのでご注意ください

code-server で Github へのログイン

  • Ctrl + p (コマンドパレットに移動)
  • > Publish to Github と入力
  • すると Github にアカウントするためのトークンが表示されるので https://github.com/login/device にアクセスし code-server が発行してくれたトークンを入力
  • code-server に戻り左メニュー下のアカウントアイコンをクリックしログインできていること確認

Github.copilot-chat のインストール

code-server のマーケットプレイスでは公開されていないので vsix ファイルを直接ダウンロードしインストールします

  • VERSION="0.30.3"
  • curl -o "copilot-chat-${VERSION}.vsix" --compressed "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/GitHub/vsextensions/copilot-chat/${VERSION}/vspackage"
  • code-server --install-extension "copilot-chat-${VERSION}.vsix"

リリースされているバージョンの一覧は Github のタグを見るのが簡単です
https://github.com/microsoft/vscode-copilot-chat/tags

注意事項

  • 最新版はそのうちインストールできるようになると思います
  • 最新版への更新はマーケットプレイスを経由していないため code-server からは行えないので手動で行う必要があります、方法はインストール時と同じです
  • 古いバージョンはプラグイン自体にログインボタンがあるがその方法は廃止されログインできても使えないので注意 (0.27 など)

最後に

直接ファイルをダウンロードしてインストールする形式なので面倒ですが一応これで動作しました

非公式の copilot-chat プラグインもあるようなのですがアカウントが必要だったりプラグインからのログインがうまくできなかったりするので無理やりでも非公式の古いバージョンを使うほうがいいのかもしれません

参考サイト

2025年8月29日金曜日

code-server は https 化しないと拡張機能が使えない

code-server は https 化しないと拡張機能が使えない

概要

RooCode などほとんどの拡張機能のページは https 化されていることが前提です
今回は code-server を https 化する方法を紹介します

環境

  • Ubuntu 24.04
  • code-server v4.103.2

A レコードの登録

名前でアクセスできる必要があるので code-server が動作しているサーバの IP の A レコードを登録します

証明書の取得

何でも OK です
certbot などを使って取得します

取得した鍵と証明書は適当なパスに配置しておきましょう

code-server の設定変更

  • vim /usr/lib/systemd/system/code-server@.service
[Unit]
Description=code-server
After=network.target

[Service]
Type=exec
# ExecStart=/usr/bin/code-server
ExecStart=/usr/bin/code-server --cert /home/hawk/certs/my_code_server.crt --cert-key /home/hawk/certs/my_code_server.key
Restart=always
User=%i

[Install]
WantedBy=default.target

/home/hawk/certs/my_code_server.crt/home/hawk/certs/my_code_server.key は先程取得した証明書と鍵へのパスになります

動作確認

  • sudo systemctl daemon-reload && sudo systemctl restart code-server@devops

これで https でアクセスできることを確認しましょう

最後に

証明書は自己証明書を使う方法がネットでよく紹介されていますが最近だと簡単に取得できます
ただドメインがそもそも必要なのでドメインがない場合には自己証明書を使う方法になるかなと思います

参考サイト

2025年8月27日水曜日

RooCode で MCP サーバ入門

RooCode で MCP サーバ入門

概要

前回 VSCode に RooCode をインストールし簡単に試してみました
今回は MCP と連携する方法を紹介します

環境

  • Windows11
  • VSCode 1.103.2
    • RooCode 3.25.23
  • node 22.15.1 (via nvm for windows)

node のインストール

MCP は今回 File System を使います
npx で動作するので nodejs を事前にインストールしておきましょう
VSCode のターミナルで node -v を実行しバージョンが表示される環境であれば何でも OK です

MCP の追加

  1. 左メニューの RooCode のアイコンをクリック
  2. 右上の「・・・」のメニューをクリック
  3. 「MCP Servers」を選択
  4. 「MCP マーケットプレイス」を表示
  5. 「File System」をインストール
  6. インストール範囲の選択
  • プロジェクトだとそのプロジェクト内でのみ MCP サーバが起動します
  • グローバルだとどのプロジェクトを開いても MCP サーバが起動します
  • File System の場合はどのプロジェクトでも使うのでグローバルでもいいかなと思います
  1. Allowed Directory の設定
  • Windows であれば C:\\Users\\username などを設定します
  • バックスラッシュはエスケープが必要なので2つ入力するので注意しましょう

インストールが完了して起動していれば OK です
起動の確認は先程の「・・・」->「MCP Servers」の一覧でステータスが緑になっていることを確認するか VSCode のターミナルで

  • tasklist | Select-String -Pattern "node"

と実行してプロセスがあることを確認しましょう

mcp_settings.json の場所

グローバルの場合は以下でした

C:\Users\username\AppData\Roaming\Code\User\globalStorage\rooveterinaryinc.roo-cline\settings\mcp_settings.json

動作確認

RooCode のターミナルで動作確認します
デフォルトだとプロジェクト配下しか検索しないので Allowed Directory で指定したパスを明示的に教えたほうが確実です

また自動承認に「MCP」を追加しておくことをおすすめします

MCPを使って C:\\Users\\username から ps1 拡張子のファイルを検索してください

CPU がフル回転して MCP 経由でファイルが探索されているのが確認できると思います

最後に

RooCode で MCP サーバと連携してみました
Windows の場合アンチマルウェア対策などがされているとうまく MCP サーバが動作しないことがありそうです

ちなみにこの方法は code-server でも動作します (code-server は拡張を使うので要 https 化)

参考サイト

2025年8月26日火曜日

VSCode に RooCode をインストールしセットアップする

VSCode に RooCode をインストールしセットアップする

概要

RooCode は ChatGPT 互換のエンドポイント (ollama) などとも連携できます

環境

  • Windows11
  • VSCode 1.103.2
    • RooCode 3.25.23

RooCode インストール

VSCode を開き左メニューの拡張機能のボタンから一覧を表示し「roo」あたりで検索すればでてきます
あとはインストールすれば OK です

RooCode セットアップ

  1. VSCode の左メニューの拡張機能一覧からインストール
  2. インストールできたら左メニューの一覧に RooCode のアイコン(カンガルーのアイコン)が追加されるのでクリック
  3. 設定項目が表示されるので以下の設定(LLMの設定)を実施
  • API Provider -> OpenAI Compatible
  • Base URL -> ChatGPT 互換のエンドポイントを設定
  • API Key -> キーを入力
  • Model -> Base URL に合わせたモデルを入力 (gpt-4o など)

動作確認

あとはチャットにメッセージを送ってみましょう
基本は英語ですが必要であれば日本語でも動かせます

基本的な流れとしては

  1. 「一緒に開発しましょう」
  2. 新規 or 既存のプロジェクトを選択
  3. そこに対して何をする(読み込み or 書き込みなど)の選択
  4. 許可 or 拒否

という感じで選択していけば OK です
最終的には実際に行うタスクの一覧が決定するのでそのタスクを承認し操作対象のディレクトリを指定すれば OK です
デフォルトでは許可 or 拒否が毎回聞かれるので読み込みくらいは自動承認にしてもいいかもしれません

おまけ: Powershell の設定

  • Ctrl + p
  • Preferences: Open User Settings (JSON)
"terminal.integrated.env.windows": {
    "PSExecutionPolicyPreference": "RemoteSigned"
}

最後に

次回は MCP と連携してみます

自分で試してみた感じですが VSCode のクライアントアプリと連携したほうが簡単です
code-server でも使えるようですが code-server だと少し対処が必要な印象です

RooCode を使いこなせるようになればボタンを押すだけでコーディングできるようになるかもしれません

参考サイト

2025年8月20日水曜日

macOS 上の ollama を 0.0.0.0 で LISTEN する方法

macOS 上の ollama を 0.0.0.0 で LISTEN する方法

概要

plist を編集します

環境

  • macOS 15.6
  • ollama 0.11.4

環境変数を設定する

OLLAMA_HOST と OLLAMA_ORIGINS を追加します

  • vim /opt/homebrew/opt/ollama/homebrew.mxcl.ollama.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>EnvironmentVariables</key>
        <dict>
                <key>OLLAMA_FLASH_ATTENTION</key>
                <string>1</string>
                <key>OLLAMA_KV_CACHE_TYPE</key>
                <string>q8_0</string>
                <key>OLLAMA_HOST</key>
                <string>0.0.0.0</string>
                <key>OLLAMA_ORIGINS</key>
                <string>192.168.*</string>
        </dict>
        <key>KeepAlive</key>
        <true/>
        <key>Label</key>
        <string>homebrew.mxcl.ollama</string>
        <key>LimitLoadToSessionType</key>
        <array>
                <string>Aqua</string>
                <string>Background</string>
                <string>LoginWindow</string>
                <string>StandardIO</string>
                <string>System</string>
        </array>
        <key>ProgramArguments</key>
        <array>
                <string>/opt/homebrew/opt/ollama/bin/ollama</string>
                <string>serve</string>
        </array>
        <key>RunAtLoad</key>
        <true/>
        <key>StandardErrorPath</key>
        <string>/opt/homebrew/var/log/ollama.log</string>
        <key>StandardOutPath</key>
        <string>/opt/homebrew/var/log/ollama.log</string>
        <key>WorkingDirectory</key>
        <string>/opt/homebrew/var</string>
</dict>
</plist>

動作確認

  • brew services restart ollama
  • ollama run gemma3

最後に

基本認証はないので外部からはアクセスできないようにしておきましょう

参考サイト

2025年8月19日火曜日

Ubuntu に code-server (vscode) をインストールする方法

Ubuntu に code-server (vscode) をインストールする方法

概要

code-server はブラウザ版 vscode になります
Ubuntu24.04 にインストールしたのでコマンドをメモしておきます

環境

  • Ubuntu 24.04
  • code-server 4.102.3

インストール

  • curl -fsSL https://code-server.dev/install.sh | sh

systemctl 有効化

  • sudo systemctl enable --now code-server@$USER

起動確認

  • sudo systemctl status code-server@$USER

起動ポート変更

  • vim /home/devops/.config/code-server/config.yaml
bind-addr: 0.0.0.0:8081
  • sudo systemctl restart code-server@$USEr

動作確認

IP:8081 でアクセスして vscode が表示されることを確認しましょう

最後に

code-server でも mcp は使えるのだろうか

参考サイト

2025年8月16日土曜日

Mineflayer と ollama を連携して自然言語でボットを制御してみた

Mineflayer と ollama を連携して自然言語でボットを制御してみた

概要

前回 Mineflayer 上で JavaScript の eval を使って動的にコードを動かすことに成功しました
今度はその応用として AI (ollama) にコードを生成してもらい生成したコードをボットに実行してもらうようにしてみました

環境

  • macOS 15.6
  • Minecraft 1.21.4
  • nodejs 22.15.1
  • mineflayer 4.31.0

lib/command/commands.js

useAiCodeCmd を追加しています

node-fetch を使って ollama の API をコールします
コールしたあとでレスポンスはそのまま使わずに少し加工しています
サンプルでは gemma3 を使っていますがこのあたりのレスポンスの処理はモデルによって変わってくるので適宜変更してください

const { VM } = require("vm2");
const fetch = require("node-fetch");
const { goals, Movements } = require("mineflayer-pathfinder");
const { GoalBlock } = goals;
const {
  buildBaseBlock,
  buildFloorFromBase,
  buildWallsFromBase,
  buildRoofFlatFromBase,
  moveBotToCenter,
} = require("./build/house");
const { tryRescue } = require("./dig/low_level_api");

const mcDataLoader = require("minecraft-data");

// 指定の座標に移動するコマンド
function moveCmd(bot, username, args) {
  if (args.length < 3) {
    bot.chat(`Usage: move <x> <y> <z>`);
    return;
  }

  const [x, y, z] = args.map(Number);
  if ([x, y, z].some(isNaN)) {
    bot.chat("座標は数値で指定してください");
    return;
  }

  bot.chat(`${username} の指示で (${x}, ${y}, ${z}) に移動します`);
  bot.pathfinder.setGoal(new GoalBlock(x, y, z));
}

// プレイヤーの目の前に移動するコマンド(canDig + 自動救出版)
function comeOnCmd(bot, username) {
  function getScaffoldBlocks(bot, mcData) {
    const usableItemIds = [
      mcData.itemsByName.dirt.id,
      mcData.itemsByName.cobblestone.id,
      mcData.itemsByName.sand.id,
    ];
    return bot.inventory
      .items()
      .filter((item) => usableItemIds.includes(item.type))
      .map((item) => item.type);
  }

  const player = bot.players[username]?.entity;
  if (!player) {
    bot.chat(`プレイヤー ${username} が見つかりません`);
    return;
  }

  const mcData = mcDataLoader(bot.version);
  const movements = new Movements(bot, mcData);

  // 各種設定
  movements.allowParkour = true;
  movements.allow1by1towers = true;
  movements.canDig = true;
  movements.scafoldingBlocks = getScaffoldBlocks(bot, mcData);

  bot.pathfinder.setMovements(movements);

  // プレイヤーの目の前の座標
  const pos = player.position.clone();
  const dx = Math.round(Math.cos(player.yaw));
  const dz = Math.round(Math.sin(player.yaw));
  const targetPos = pos.offset(dx, 0, dz);

  bot.chat(`${username} の所に行きます!`);
  bot.pathfinder.setGoal(new GoalBlock(targetPos.x, targetPos.y, targetPos.z));

  // 自動救出ループ
  let lastPos = bot.entity.position.clone();
  const checkInterval = setInterval(() => {
    const dist = bot.entity.position.distanceTo(targetPos);
    if (dist <= 1.5) {
      bot.chat(`到着しました!(距離 ${dist.toFixed(2)})`);
      clearInterval(checkInterval);
      return;
    }

    if (bot.entity.position.distanceTo(lastPos) < 0.01) {
      tryRescue(bot, targetPos);
    }
    lastPos = bot.entity.position.clone();
  }, 1500);
}

// ボットのインベントリーの中身を確認するコマンド
function inventoryCmd(bot, username) {
  const items = bot.inventory.items();
  if (items.length === 0) {
    bot.chat(`${username}、インベントリは空です。`);
    return;
  }
  const itemList = items
    .map((item) => `${item.count}x${item.name}(${item.type})`)
    .join(", ");
  bot.chat(`${username} の所持品: ${itemList}`);
}

// 四角形の家を建築するコマンド
async function buildEasyHouseCmd(bot, username) {
  const baseBlockPos = await buildBaseBlock(bot, "dirt");
  if (baseBlockPos) {
    await buildFloorFromBase(bot, baseBlockPos, "dirt");
    await moveBotToCenter(bot, baseBlockPos, 5);
    bot.chat("床を作りました!");
    await buildWallsFromBase(bot, baseBlockPos, "dirt", 5, 3);
    bot.chat("壁を作りました");
    await buildRoofFlatFromBase(bot, baseBlockPos, "dirt", 5, 3);
    bot.chat("屋根を作りました");
  }
}

// チャットから指定したボットへの命令を動的に実行する
function evalCmd(bot, username, code) {
  // VM2 サンドボックス作成
  const vm = new VM({
    timeout: 1000, // 無限ループ防止
    sandbox: {
      bot, // bot API を利用可能にする
      Vec3: require("vec3"), // 位置操作用
      console: { log: (...args) => bot.chat(args.join(" ")) }, // console.log をチャットに出力
    },
  });

  try {
    const result = vm.run(new String(code)); // コード実行
    if (result !== undefined) {
      bot.chat(`実行結果: ${result}`);
    }
  } catch (err) {
    bot.chat(`エラー: ${err.message}`);
  }
}

// AI に問い合わせてコードを生成してもらいそれを eval する
async function useAiCodeCmd(bot, username, prompt) {
  bot.chat(`AIにリクエスト中: "${prompt}"`);

  try {
    // 1. Ollama にリクエスト
    const response = await fetch("http://localhost:11434/api/generate", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        model: "gemma3", // 使うモデル名(Ollamaにあるもの)
        prompt: `
あなたはmineflayerのJavaScriptコードを書くアシスタントです。
ユーザーの指示に従い、mineflayer用のJavaScriptコードだけを出力してください。
bot オブジェクトは使えるので bot オブジェクトに対する操作をしてください。
説明文や余計な文章は書かず、コードのみを出力してください。
ユーザーの指示: ${prompt}
        `,
        stream: false,
      }),
    });

    const data = await response.json();
    let aiCode = data.response.trim();
    // Markdown のコードブロック記法を削除
    aiCode = aiCode
      .replace(/```(?:javascript|js)?\n?/gi, "")
      .replace(/```/g, "")
      .trim();

    console.log(aiCode);
    bot.chat("AIコードを受信、実行します…");

    // 2. サンドボックスで実行
    const vm = new VM({
      timeout: 2000,
      sandbox: {
        bot,
        Vec3: require("vec3"),
        console: { log: (...args) => bot.chat(args.join(" ")) },
      },
    });

    try {
      vm.run(aiCode);
      bot.chat("AIコードの実行が完了しました");
    } catch (err) {
      bot.chat(`コード実行エラー: ${err.message}`);
    }
  } catch (err) {
    bot.chat(`AI呼び出しエラー: ${err.message}`);
  }
}

module.exports = {
  moveCmd,
  comeOnCmd,
  inventoryCmd,
  buildEasyHouseCmd,
  evalCmd,
  useAiCodeCmd,
};

lib/command/parser.js

isUseAiCodeCommand を追加します

class CommandParser {
  constructor(message) {
    this.message = message.trim();
    this.parts = this.message.split(/\s+/);
  }

  isMoveCommand() {
    return this.parts[0].toLowerCase() === "move";
  }

  isComeOnCommand() {
    return this.parts[0].toLowerCase() === "comeon";
  }

  isInventoryCommand() {
    return this.parts[0].toLowerCase() === "inv";
  }

  isBuildEasyHouseCommand() {
    return this.parts[0].toLowerCase() === "build_easy_house";
  }

  isEvalCommand() {
    return this.parts[0].toLowerCase() === "eval";
  }

  isUseAiCodeCommand() {
    return this.parts[0].toLowerCase() === "use_ai_code";
  }

  getArgs() {
    return this.parts.slice(1);
  }
}

module.exports = CommandParser;

index.js

メインの処理を追記します
onChat の分岐処理を追記します

const mineflayer = require("mineflayer");
const { pathfinder, Movements } = require("mineflayer-pathfinder");
const CommandParser = require("./lib/command/parser");
const {
  moveCmd,
  comeOnCmd,
  inventoryCmd,
  buildEasyHouseCmd,
  evalCmd,
  useAiCodeCmd,
} = require("./lib/command/commands");

class MinecraftBot {
  constructor(host, port, username) {
    this.bot = mineflayer.createBot({
      host,
      port,
      username,
      version: false,
    });

    this.bot.loadPlugin(pathfinder);
    this.registerEvents();
  }

  registerEvents() {
    this.bot.once("spawn", () => this.onSpawn());
    this.bot.on("chat", (username, message) => this.onChat(username, message));
    this.bot.on("kicked", (reason) =>
      console.error("キックされました:", reason),
    );
    this.bot.on("error", (err) => console.error("エラー:", err));
    this.bot.on("end", () => console.log("Bot が切断されました"));
  }

  onSpawn() {
    console.log("Bot がログインしました!");
    this.bot.chat("こんにちは!Botクラスです!");

    const defaultMovements = new Movements(this.bot, this.bot.registry);
    this.bot.pathfinder.setMovements(defaultMovements);
  }

  onChat(username, message) {
    if (username === this.bot.username) return;
    console.log(`[${username}]: ${message}`);

    const parser = new CommandParser(message);

    if (parser.isMoveCommand()) {
      moveCmd(this.bot, username, parser.getArgs());
    } else if (parser.isComeOnCommand()) {
      comeOnCmd(this.bot, username);
    } else if (parser.isInventoryCommand()) {
      inventoryCmd(this.bot, username);
    } else if (parser.isBuildEasyHouseCommand()) {
      buildEasyHouseCmd(this.bot, username);
    } else if (parser.isEvalCommand()) {
      evalCmd(this.bot, username, parser.getArgs());
    } else if (parser.isUseAiCodeCommand()) {
      useAiCodeCmd(this.bot, username, parser.getArgs());
    }
  }
}

const myBot = new MinecraftBot("localhost", 25565, "bot");

動作確認

  • npx node index.js

チャットに「use_ai_code ハローとチャットして」と入力すると少し間を置いてからちゃんとチャットが表示されることが確認できます

最後に

Mineflayer と ollama を連携してボットを AI に動かしてもらうことに挑戦してみました

内部的には JavaScript を eval しているので eval で実行できないコードはそもそも実行できません

これ以上複雑なことをやろうとするならばやはりスクラッチですべて実装するかもしくは minecraft-mcp-server などを使うことになるかなと思います

2025年8月15日金曜日

Mineflayer で eval を使ってボットに動的にコードを実行させてみた

Mineflayer で eval を使ってボットに動的にコードを実行させてみた

概要

JavaScript には eval という文字列からコードを実行する仕組みがあります
今回はそれと Mineflayer を組み合わせてチャットに入力されたボット制御するコードを実行するような仕組みを作ってみました

注意点としては eval は非常に危険なコードなので使用する際には十分に注意してください
なお今回はサンドボックス化できる vm2 という仕組みを使って eval を使用します

環境

  • macOS 15.6
  • Minecraft 1.21.4
  • nodejs 22.15.1
  • mineflayer 4.31.0

vm2 のインストール

  • npm install vm2

lib/command/commands.js

vm2 を使って eval する処理を commands.js に追記します
サンドボックスなので VM オブジェクト生成時に渡したオブジェクトだけ eval 内で使えるようになります

const { VM } = require("vm2");
const { goals, Movements } = require("mineflayer-pathfinder");
const { GoalBlock } = goals;
const {
  buildBaseBlock,
  buildFloorFromBase,
  buildWallsFromBase,
  buildRoofFlatFromBase,
  moveBotToCenter,
} = require("./build/house");
const { tryRescue } = require("./dig/low_level_api");

const mcDataLoader = require("minecraft-data");

// 指定の座標に移動するコマンド
function moveCmd(bot, username, args) {
  if (args.length < 3) {
    bot.chat(`Usage: move <x> <y> <z>`);
    return;
  }

  const [x, y, z] = args.map(Number);
  if ([x, y, z].some(isNaN)) {
    bot.chat("座標は数値で指定してください");
    return;
  }

  bot.chat(`${username} の指示で (${x}, ${y}, ${z}) に移動します`);
  bot.pathfinder.setGoal(new GoalBlock(x, y, z));
}

// プレイヤーの目の前に移動するコマンド(canDig + 自動救出版)
function comeOnCmd(bot, username) {
  function getScaffoldBlocks(bot, mcData) {
    const usableItemIds = [
      mcData.itemsByName.dirt.id,
      mcData.itemsByName.cobblestone.id,
      mcData.itemsByName.sand.id,
    ];
    return bot.inventory
      .items()
      .filter((item) => usableItemIds.includes(item.type))
      .map((item) => item.type);
  }

  const player = bot.players[username]?.entity;
  if (!player) {
    bot.chat(`プレイヤー ${username} が見つかりません`);
    return;
  }

  const mcData = mcDataLoader(bot.version);
  const movements = new Movements(bot, mcData);

  // 各種設定
  movements.allowParkour = true;
  movements.allow1by1towers = true;
  movements.canDig = true;
  movements.scafoldingBlocks = getScaffoldBlocks(bot, mcData);

  bot.pathfinder.setMovements(movements);

  // プレイヤーの目の前の座標
  const pos = player.position.clone();
  const dx = Math.round(Math.cos(player.yaw));
  const dz = Math.round(Math.sin(player.yaw));
  const targetPos = pos.offset(dx, 0, dz);

  bot.chat(`${username} の所に行きます!`);
  bot.pathfinder.setGoal(new GoalBlock(targetPos.x, targetPos.y, targetPos.z));

  // 自動救出ループ
  let lastPos = bot.entity.position.clone();
  const checkInterval = setInterval(() => {
    const dist = bot.entity.position.distanceTo(targetPos);
    if (dist <= 1.5) {
      bot.chat(`到着しました!(距離 ${dist.toFixed(2)})`);
      clearInterval(checkInterval);
      return;
    }

    if (bot.entity.position.distanceTo(lastPos) < 0.01) {
      tryRescue(bot, targetPos);
    }
    lastPos = bot.entity.position.clone();
  }, 1500);
}

// ボットのインベントリーの中身を確認するコマンド
function inventoryCmd(bot, username) {
  const items = bot.inventory.items();
  if (items.length === 0) {
    bot.chat(`${username}、インベントリは空です。`);
    return;
  }
  const itemList = items
    .map((item) => `${item.count}x${item.name}(${item.type})`)
    .join(", ");
  bot.chat(`${username} の所持品: ${itemList}`);
}

// 四角形の家を建築するコマンド
async function buildEasyHouseCmd(bot, username) {
  const baseBlockPos = await buildBaseBlock(bot, "dirt");
  if (baseBlockPos) {
    await buildFloorFromBase(bot, baseBlockPos, "dirt");
    await moveBotToCenter(bot, baseBlockPos, 5);
    bot.chat("床を作りました!");
    await buildWallsFromBase(bot, baseBlockPos, "dirt", 5, 3);
    bot.chat("壁を作りました");
    await buildRoofFlatFromBase(bot, baseBlockPos, "dirt", 5, 3);
    bot.chat("屋根を作りました");
  }
}

// チャットから指定したボットへの命令を動的に実行する
function evalCmd(bot, username, code) {
  // VM2 サンドボックス作成
  const vm = new VM({
    timeout: 1000, // 無限ループ防止
    sandbox: {
      bot, // bot API を利用可能にする
      Vec3: require("vec3"), // 位置操作用
      console: { log: (...args) => bot.chat(args.join(" ")) }, // console.log をチャットに出力
    },
  });

  try {
    const result = vm.run(new String(code)); // コード実行
    if (result !== undefined) {
      bot.chat(`実行結果: ${result}`);
    }
  } catch (err) {
    bot.chat(`エラー: ${err.message}`);
  }
}

module.exports = {
  moveCmd,
  comeOnCmd,
  inventoryCmd,
  buildEasyHouseCmd,
  evalCmd,
};

lib/command/parser.js

isEvalCommand を追加します

class CommandParser {
  constructor(message) {
    this.message = message.trim();
    this.parts = this.message.split(/\s+/);
  }

  isMoveCommand() {
    return this.parts[0].toLowerCase() === "move";
  }

  isComeOnCommand() {
    return this.parts[0].toLowerCase() === "comeon";
  }

  isInventoryCommand() {
    return this.parts[0].toLowerCase() === "inv";
  }

  isBuildEasyHouseCommand() {
    return this.parts[0].toLowerCase() === "build_easy_house";
  }

  isEvalCommand() {
    return this.parts[0].toLowerCase() === "eval";
  }

  getArgs() {
    return this.parts.slice(1);
  }
}

module.exports = CommandParser;

index.js

メインの処理を追記します
onChat の分岐処理を追記します

const mineflayer = require("mineflayer");
const { pathfinder, Movements } = require("mineflayer-pathfinder");
const CommandParser = require("./lib/command/parser");
const {
  moveCmd,
  comeOnCmd,
  inventoryCmd,
  buildEasyHouseCmd,
  evalCmd,
} = require("./lib/command/commands");

class MinecraftBot {
  constructor(host, port, username) {
    this.bot = mineflayer.createBot({
      host,
      port,
      username,
      version: false,
    });

    this.bot.loadPlugin(pathfinder);
    this.registerEvents();
  }

  registerEvents() {
    this.bot.once("spawn", () => this.onSpawn());
    this.bot.on("chat", (username, message) => this.onChat(username, message));
    this.bot.on("kicked", (reason) =>
      console.error("キックされました:", reason),
    );
    this.bot.on("error", (err) => console.error("エラー:", err));
    this.bot.on("end", () => console.log("Bot が切断されました"));
  }

  onSpawn() {
    console.log("Bot がログインしました!");
    this.bot.chat("こんにちは!Botクラスです!");

    const defaultMovements = new Movements(this.bot, this.bot.registry);
    this.bot.pathfinder.setMovements(defaultMovements);
  }

  onChat(username, message) {
    if (username === this.bot.username) return;
    console.log(`[${username}]: ${message}`);

    const parser = new CommandParser(message);

    if (parser.isMoveCommand()) {
      moveCmd(this.bot, username, parser.getArgs());
    } else if (parser.isComeOnCommand()) {
      comeOnCmd(this.bot, username);
    } else if (parser.isInventoryCommand()) {
      inventoryCmd(this.bot, username);
    } else if (parser.isBuildEasyHouseCommand()) {
      buildEasyHouseCmd(this.bot, username);
    } else if (parser.isEvalCommand()) {
      evalCmd(this.bot, username, parser.getArgs());
    }
  }
}

const myBot = new MinecraftBot("localhost", 25565, "bot");

動作確認

  • npx node index.js

チャットを開いて eval bot.chat('hello'); と入力するとボットがチャットすることが確認できます
これで毎回チャットで別の命令をすることができます

最後に

eval を使ってボットに動的に命令する方法を実装してみました
VM2 でオブジェクトをサンドボックス化していますが bot オブジェクトはそのまま使えてしまうのでセキュリティには注意しましょう

また現状だと複雑なコードは実行不可なので単純な命令のみ実行できるレベルです

参考サイト