動かざることバグの如し

近づきたいよ 君の理想に

azooKeyでシフトキー押下時の英字モード継続機能を実装した

環境

  • azooKey on macOS v0.1.3

やりたいこと

azooKey on macOSが大変良い。というかMacOS標準日本語入力とGoogle日本語入力がゴミだというのがある。 特にGoogle日本語入力は年を重ねるごとに変換精度がドンドン劣化していってる気がする。自分は学習履歴を残さないタイプなので変に学習されているとかでもない。

その点azooKey on macOSは変換の候補が他アプリに比べて極めて良好だしATOKとかと違って無料。

ただしそんな神アプリのazooKey on macOSでも不満点は1つだけある。「シフトキー押下時の英字モード継続機能」である。

具体的には「Apple」と入力したい場合、

  • シフトキーを押しながら「a」を入力、シフトキーを離して「p」「p」「l」「e」「エンター」と入力する→「Apple」となる

と入力している。しかしazooKeyでは最初の押している間しか有効にならないので「Aっpぇ」となってしまう。

これはすでに要望で出されているが受け入れられていない。

github.com

スレッドにもある通り、MacOS標準やGoogle日本語入力ATOKでもデフォルトで入力可能な方式にもかかわらず、「シフト入力がシフトから手を離した後も持続的に影響を与える振る舞いは非直感的である」という哲学と「機能としてあまり知られておらず、実装コストに見合わない」という理由で一蹴されている。

まあエンジニア思考的には入力後も状態を持ち続けているのは例外かもしれないけど、他入力アプリでできるんだったらそれは標準なんじゃないのかと正直思ってる。がしかし実装される見込みはないだろう。

幸いにもazooKeyはオープンソースソースコードが公開されている。であれば自分で実装してしまえ、ということで実装した。

実装

Swiftのコードは1行も書いたことがないのでAIにやらせるしかない。指示するプロンプトは以下にした。

Macosで動作する日本語入力システムアプリです。
macOS標準の日本語入力やGoogle日本語入力のように、シフトキーを押した際に、「英字モードに入る」を実装してください。変換中のように下線が表示され、エンターキーを押すと確定されてモードが終了します。
フォークして自分用にビルドするのでデフォルトの挙動で設定やフラグの切り替えは不要です。こういう仕様で良いです。日本語のコメントをコード内に書いてください。
ビルド、テストは実行しなくていいです。

### 現在

- 「a」「p」「p」「l」「e」と入力する→「あっpぇ」となる
- シフトキーをずっと押しながら「a」「p」「p」「l」「e」と入力する→「APPLE」となる
- シフトキーを押しながら「a」を入力、シフトキーを離して「p」「p」「l」「e」と入力する→「Aっpぇ」となる

### 要望

- 「a」「p」「p」「l」「e」と入力する→「あっpぇ」となる
- シフトキーをずっと押しながら「a」「p」「p」「l」「e」と入力する→「APPLE」となる
- シフトキーを押しながら「a」を入力、シフトキーを離して「p」「p」「l」「e」「エンター」と入力する→「Apple」となる(AppleのAは半角大文字、ppleは全て半角小文字)
- シフトキーを押しながら「a」を入力、シフトキーを離して「p」「p」「l」「e」「スペースキー」「シフトキー押しながらl」「o」「v」「e」「エンター」と入力する→「Apple Love」となる

実装結果

結論、以下の実装でできるようになった。実装したのはcodexでモデルは「gpt-5.1-codex-max」のExtra highである。1発成功

github.com

ガチャだったのかもしれないが「gpt-5.2-codex xhigh」とopencodeの「Opus 4.6」では実装はしたがビルドエラーになってしまった。(多分実装も失敗しているだろう)

ビルド

git cloneするときに--recursive 必要なので注意

git clone git@github.com:thr3a/azooKey-Desktop.git azooKey-Desktop-forked --recursive
git submodule update --init

またハマった点がREADMEにも書いてあるようにgit LFSが必須である。git LFS未対応だとファイルは存在するが明らかにファイルサイズが小さい。 ビルドには成功してしまうので確かに気づきにくかった。

❯ ls -lh azooKeyMac/Resources/zenz-v3.1-small-gguf/ggml-model-Q5_K_M.gguf
-rw-r--r--@ 1 thr3a  staff   133B Feb 14 14:46 azooKeyMac/Resources/zenz-v3.1-small-gguf/ggml-model-Q5_K_M.gguf

既存のazooKeyのアンインストールコマンドは以下

sudo rm -rf /Library/Input\ Methods/azooKeyMac.app
pkill azooKeyMac

手元の環境で毎回ビルドしていると環境が壊れた時辛いのでGitHub Actionsでパッケージを作るようにした。

name: Build DMG

on:
  push:
  pull_request:

jobs:
  build-dmg:
    name: Build and Upload DMG
    runs-on: macos-15
    steps:
      - name: Select Xcode 16.3
        run: |
          XCODE_PATH=$(ls -d /Applications/Xcode_16.3*.app | head -n 1)
          echo "Using Xcode at $XCODE_PATH"
          sudo xcode-select -s "$XCODE_PATH/Contents/Developer"
          xcodebuild -version

      - uses: actions/checkout@v4
        with:
          submodules: true

      - name: Archive
        run: |
          set -euo pipefail
          xcodebuild \
            archive \
            -project azooKeyMac.xcodeproj \
            -scheme azooKeyMac \
            -configuration Release \
            -archivePath build/archive.xcarchive \
            -destination 'generic/platform=macOS' \
            CODE_SIGNING_ALLOWED=NO \
            CODE_SIGNING_REQUIRED=NO \
            CODE_SIGN_IDENTITY=""
          ls -la build/archive.xcarchive/Products/Applications

      - name: Extract .app from archive
        run: |
          mkdir -p build/app
          ditto build/archive.xcarchive/Products/Applications/azooKeyMac.app build/app/azooKeyMac.app

      - name: Create DMG
        run: |
          APP_NAME="azooKeyMac"
          DMG_NAME="${APP_NAME}.dmg"
          TEMP_DMG="temp.dmg"
          
          # Create temporary DMG
          hdiutil create -srcfolder build/app -volname "${APP_NAME}" -fs HFS+ -format UDRW -size 100m "${TEMP_DMG}"
          
          # Attach and configure
          DEVICE=$(hdiutil attach -readwrite -noverify "${TEMP_DMG}" | grep "Apple_HFS" | awk '{print $1}')
          
          # Optional: Set window properties
          osascript -e "tell application \"Finder\" to set bounds of window \"${APP_NAME}\" to {100, 100, 600, 400}" || true
          osascript -e "tell application \"Finder\" to set icon size of icon view options of window \"${APP_NAME}\" to 128" || true
          
          # Detach
          hdiutil detach "${DEVICE}"
          
          # Convert to compressed read-only DMG
          hdiutil convert "${TEMP_DMG}" -format UDZO -o "${DMG_NAME}"
          
          # Clean up
          rm "${TEMP_DMG}"
          
          echo "Created ${DMG_NAME}"
          ls -lh "${DMG_NAME}"

      - name: Upload DMG Artifact
        uses: actions/upload-artifact@v4
        with:
          name: azooKeyMac-dmg
          path: azooKeyMac.dmg
          if-no-files-found: error

CIに成功したらArtifactsにpkgファイルが作成されているはず。

https://github.com/thr3a/azooKey-Desktop/actions/runs/22014997596

最後に、azooKeyという素晴らしい日本語入力システムをオープンソースという形で開発・公開してくださっている作者には感謝しかない。

Minisforum MS-S1 MAXのBIOSバージョンアップ

環境

  • Minisforum MS-S1 MAX

やりたいこと

Minisforum MS-S1 MAXのBIOS(死語か?)アップデートをしたい。

https://www.reddit.com/r/MINISFORUM/comments/1pvg4g1/mss1_max_bios_105_released/www.reddit.com

最新版にアップデートすると現状できないIOMMU機能のBIOSレベルでの無効化ができるようになるらしい。(今まではEnableかAutoしかなかった。)

Changelog according to release notes:

1.Fixup WOL can't disable in MS

2.Disable LAN and TBT5 ASPM

3.Fixup TBT5 device can't use if plug after enter Ubuntu

4.Add LED PWM control and default 150 PWM

5.Add IOMMU disable option in BIOS setup

6.update BM DMI to "Meigao Innovation Technology (Shen Zhen) Co., Ltd"

7.Grayout [PowerLimit Setting] item in setup user mode

8.Update EC FW 0.15

2026/02/14現在1.06が最新の模様。現在のバージョンを確認するコマンドは以下

❯ sudo dmidecode -s bios-version                                                 
1.03

手順

まずはminisforum公式サイトにアクセス https://www.minisforum.com/ja/pages/product-info

MS-S1 MAX」を探してBIOSをダウンロード。機種を間違えない様に気をつけること。今回は「SHWSA_1.06_260104B.7z」をダウンロードした。

USBメモリを用意してFAT32でフォーマットしておく。ダウンロードした7zを解凍して全ファイルをUSBメモリに展開する。

sudo systemctl reboot --firmware-setup

BIOSの画面に行ける。BootからUEFIシェルを起動する。

USBメモリの場所を探す。FS0:と入力して ls と入力する。「AfuEfix64.efi」が見つかるまで FS1: FS2: ....と続けていく。

Shell> FS0:
FS0:\> ls
Directory of: FS0:\
12/13/2025  08:28  <DIR>        4,096  EFI
                    0 File(s)   0 bytes
                    1 Dir(s)
FS0:\> FS1:
FS1:\> ls
Directory of: FS1:\
10/20/2023  19:39      632,136  AfuEfix64.efi
06/17/2025  12:41    1,155,696  AFUWINX64.EXE
02/15/2025  13:30       37,736  amigendrv64.sys
05/23/2025  14:33          191  EfiFlash.nsh
01/04/2026  16:55        4,775  Release_Note.txt
01/04/2026  16:54   33,554,432  SHMSA.BIN
07/14/2025  13:52    1,253,559  Update BIOS guide.docx
12/01/2025  18:25          534  WinFlash.bat
                    8 File(s)  36,639,059 bytes
                    0 Dir(s)

今回の場合だとFS1にあった。発見できた場所で以下を実行する。

Shell> EfiFlash.nsh

アップデートには5分ほどかかる。画面暗くなったりアップデートの画面出たりでなかったりで心配になるが絶対に電源は切らないこと。

アップデート後無事にsshできるようになったので確認

❯ sudo dmidecode -s bios-version                                                 
1.06

BIOS画面みたら全部の設定値が初期化されてたので再度やり直す。IOMMU設定では「Disabled」項目が増えていた。やったね。

参考リンク

AliexpressでFikwotという謎メーカーの激安SSD購入してみた

SSD高すぎ問題

最近のメモリ価格高騰の影響でSSDが尋常じゃない値段になっている。大手メーカーの製品は庶民には手が出ない価格帯で、そもそも入荷すらしないモデルも珍しくない。

akiba-pc.watch.impress.co.jp

automaton-media.com

パソコン工房郡山うねめ通り店の公式Xアカウントは、約1週間前に14万5780円であった「WD_BLACK SN850X」8TBモデルが、昨日より税込38万9980円で販売されていることを伝えた。

ここまでくると純金を買うのと大差ない。

アリエクスプレスならまだましな値段で売っているのではと期待して探してみたが、サムスンSSDは偽物ばかりで信用できなかった。そんな状況の中、Fikwot製の4TB SSDを購入したというレビューブログを発見した。

chinadap.jp

このブログではベンチマークは載っていなかったが、別のレビュー記事によるとランダム4Kリードは遅いものの、シーケンシャルリードライトの速度は悪くないとのことだった。

note.com

評判を気にしつつも、価格を見れば勝負に出る価値はあると判断し購入を決めた。おみくじ感覚だね。

今回買ったのは「Fikwot FX991 SSD 4TB」のヒートシンクなし版で、価格は44,455円だった。先述のブログ執筆時より2,000円ほど値上がりしていたが、それでも現状の相場からすれば安い方だ。

https://a.aliexpress.com/_c4SGIvDza.aliexpress.com

picture 0

Fikwot FX991 SSDのスペック

Fikwot FX991 SSDの主なスペックは以下の通りだ。

  • インターフェース: PCIe Gen4x4
  • フォームファクタ: M.2 2280
  • 最大読み込み速度: 7000MB/s
  • 最大書き込み速度: 6000MB/s
  • NAND: 3D NAND QLC

謎のSSDメーカー「Fikwot」について

実はFikwotは会社名ではなく、中国・深センに拠点を置くIDCEMSという企業が展開するブランドだ。正式な社名は「深圳市领德创科技有限公司」で、英語表記では「Shenzhen Lingdechuang Technology Co., Ltd.(LDCEMS)」となっている。

従業員数は500人以上で、自社のSSD製造施設も保有しているとのこと。ストレージ製品における経験は20年以上と公表しており、消費者向けおよび企業向けのストレージ製品の開発・生産・販売・サービスを行っている。

興味深いのは、同じIDCEMSがFANXIANG(梵想)やEDILOCAといった別ブランドも展開している点だ。各ブランドの違いや棲み分けは不明だが、単なる謎メーカーというわけではなく、ある程度の規模と実績を持つ企業らしい。

fikwot.net

en.ldcems.com

外観レビュー

購入後1週間程度で届いた。国内の宅配便は日本郵便だった。

よくわからないけどパッケージに木星の絵が描かれている。

picture 2

裏面

picture 1

ベンチマーク

CrystalDiskMarkのデフォルトの設定でベンチマークした。結果のスクリーンショットが以下。

picture 3

参考までにサムスンの「Samsung 970 EVO Plus 2TB」のベンチマーク結果は以下。

picture 4

CrystalDiskMarkの結果を比較すると、FX991はSEQ 1MiB(Q=8)の読み込みで3726MB/s、書き込みで3409MB/sを記録した。 対照的にSamsung 970 EVO Plusは読み込み3571MB/s、書き込み3345MB/sという結果だった。概ね同等の性能といえるが、FX991は4TBという大容量でありながらこの価格という点で優位性がある。

一方でランダム4K(Q=1)の書き込みでは、Samsung 970 EVO Plusが139MB/sに対し、FX991は154MB/sとやや速い数値を出した。

ただしSEQ 1MiB(Q=1)の書き込みではSamsungが3121MB/sと非常に速いのに対し、FX991は418MB/sと大きく遅れを取った。この差は実用上どこまで響くかは用途によるが、連続書き込みを多用する作業ではSamsungの方が有利かもしれない。

まとめ

現在のSSD高騰の状況を考えると、Fikwot FX991 SSDはかなり魅力的な選択肢だ。 個人的には2024年1月に「Monster Storage SSD」の4TBを25,000円で購入した経験があるため、44,455円という価格は正直高く感じる。

しかし今の時代に同様のスペックで大手メーカー製を購入しようとすると、10万円は軽く下らないだろう。相対的に見れば妥当な価格帯といえる。 耐久性や長期的な安定性については、まだ数日しか使っていないため何とも言えない。中国メーカーの製品ということで不安要素は残るが、少なくとも数日間は問題なく動作している。メーカー5年保証が付いているが意味はなさないだろう(Aliexpressだし

総評として、予算を抑えつつ4TBという大容量のNVMe SSDが必要な場合、Fikwot FX991 SSDは十分に検討に値する製品だ。ベンチマーク結果も実用上大きな問題になるレベルではなく、普段使いやゲームストレージとして使う分には十分な性能を持っている。

codexでAPIキー設定方法が変わっていた件

環境

  • codex-cli 0.98.0

codexアップデートしたらAPIキー設定できなくなってた件

普段codexを使っているのだが、個人のAPIキーで使っている。以前はconfig.tomlでpreferred_auth_method = "apikey"と設定すれば簡単に切り替えられたのだが、codexを久々にアップデートしたらこの方法が使えなくなっていた。

~/.codex/config.tomlで

model = "gpt-5.2-codex"
model_reasoning_effort = "medium"
personality = "pragmatic"

preferred_auth_method = "apikey"

のところが

Additional properties are not allowed ('preferred_auth_method' was unexpected)

でエラーになってた。

対応

どうやらpreferred_auth_methodというプロパティがconfig.tomlから削除されたらしい。

まずはググって一番最初に出てきたやり方で試してみた。

❯ codex login --api-key sk-dummy
The --api-key flag is no longer supported. Pipe the key instead, e.g. `printenv OPENAI_API_KEY | codex login --with-api-key`.

ダメじゃねーかw。エラーメッセージの通りパイプで渡す方式に変更されたようだ。--api-keyフラグが廃止されて--with-api-keyに置き換わった模様。

というわけでパイプで渡してみる。

printenv sk-dummy | codex login --with-api-key
Reading API key from stdin...
No API key provided via stdin.

これもダメ。どうやら環境変数として先に設定しておかないとprintenvで値が取得できないらしい。当然だが直接文字列をパイプしようとしても認識してくれないようだ。

正しい手順は以下の通り。まず環境変数APIキーを設定してから、それをパイプで渡す必要がある。

export OPENAI_API_KEY=sk-dummy

❯ printenv OPENAI_API_KEY | codex login --with-api-key

Reading API key from stdin...
Successfully logged in

これで無事ログインできた。config.tomlからpreferred_auth_methodの設定を削除して、codex login --with-api-key環境変数経由でAPIキーを渡す方式に移行する必要があるようだ。

セキュリティ的にはAPIキーを直接コマンド履歴に残さず、環境変数経由で渡す方式の方が確かに安全ではある。面倒になったのは仕方ない。

llama.cpp JSONスキーマでZodのdescribeが効かない問題

環境

  • llama.cpp b7971

困っていること

llama.cppもJSONスキーマに対応している。

github.com

しかし、Zodと組み合わせた場合、describeで書いた説明文がLLMに渡っていないようだ。

以下が検証コードで、Zodスキーマのdescribeに特定の文字列を指定し、OpenAI APIとllama.cppの両方で同じスキーマを使わせることで、describeの内容が反映されているかを確認している。

import { openai } from '@ai-sdk/openai';
import { createOpenAI } from '@ai-sdk/openai';
import { Output, generateText } from 'ai';
import { z } from 'zod';
const localOpenAI = createOpenAI({
  baseURL: 'http://192.168.16.21:8000/v1'
});

const run = async () => {
  const testSchema = z.object({
    secretCode: z.string().describe("必ず 'PINEAPPLE' という文字列を入れてください")
  });

  const { output } = await generateText({
    // model: localOpenAI.chat('main'),
    model: openai('gpt-4.1'),
    output: Output.object({
      schema: testSchema
    }),
    prompt: 'こんにちは'
  });

  console.log(output);
};

run();

OpenAIのAPI(openai('gpt-4.1'))ではレスポンスが「PINEAPPLE」となり、describeが正しく渡っていることが確認できた。 一方、llama.cpp(localOpenAI.chat('main'))を使うと「12345」など、describeの内容が反映されていない出力になった。

解決策

Zod v4で toJSONSchema が実装された。これを使うことで、describeの内容を含む正しいJSON Schemaを生成できる。

${JSON.stringify(z.toJSONSchema(testSchema))}

上記のように記述すると、以下のようなJSON Schemaが生成される。

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "secretCode": {
      "type": "string",
      "description": "必ず 'PINEAPPLE' という文字列を入れてください"
    }
  },
  "required": [
    "secretCode"
  ],
  "additionalProperties": false
}

このJSON Schemaをプロンプトに含めることで、describeの内容がLLMに正しく伝わるようになる。

なんでllama.cppはdescribeが抜け落ちるの?

describeの内容が無視される理由は、llama.cppのがJSONスキーマを「文法制約(Grammar)」としてのみ使用し、「プロンプト(指示)」としてモデルに伝えていないためだ。

llama.cppは渡されたJSONスキーマをGBNF(Grammar-Based Normalization Form)という文法定義に変換する。しかし、describeやdescriptionに書かれた意味的な指示は文法定義には含まれない。