cd を賢くするツールを探すと、まず zoxide に行き着く。訪問履歴の重み付け(frecency)で cd doc から目的のディレクトリへ飛ぶ、という発想はもう定番だ。そこへ2026年8月27日、AIフォールバックを足した cd 置き換え「cdai」が現れた。売り文句は歯切れがいい——「AIには存在しないパスを作らせない(not allowed to invent a path)」。
この一文は、紹介記事では検証されないまま引用されやすい。だが幸いこれは実測できる主張である。cdaiのAIバックエンドは「JSONを標準出力に出す任意のコマンド」でしかないため、自分で嘘をつく偽AIを書いて差し替えられる。本記事ではAIバックエンドを6通りの偽物に置き換え、存在しないパス・rootの外・候補に無い実在ディレクトリを返させて、どこで止まるかを1件ずつ確かめた。
CLIツールをAIで補強する流れ全体の見取り図は、AI自動化ツール|ノーコードからコードまで2026年版の比較と選び方にまとめてある。本記事はその中の1本を、設計の当否が分かる粒度まで掘り下げる。
30秒でわかるcdai
・正体:cd を置き換えるシェル統合コマンド。ネイティブの cd をまず試し、外れたときだけ独自のインデックスとfrecencyで解決する
・zoxideとの差:zoxideが「訪問済みディレクトリの記憶」なのに対し、cdaiは未訪問のディレクトリも事前インデックスで探せる。さらに曖昧な言い回し用のAIフォールバックを持つ
・AIの権限:生成ではなく選択。決定的な処理が作った候補リストの要素以外は、実在するディレクトリであっても拒否される(本記事で6通り実測)
・AIが呼ばれない条件:端末(TTY)が無いとき、候補が0件のとき、ai.enabled が false のとき。既定経路とTab補完は常にモデル非依存
・現時点の規模:GitHub star 1、初回コミット2026年8月27日、コミット19件、タグは v0.3.1 のみ、npm未公開。本番導入を勧められる成熟度ではない
cdaiとは——zoxideと何が違うのか
cdaiはNode.js製のCLIで、cd の完全な置き換えを狙う。ライセンスはMIT、作者はFranz Enzenhofer氏。2026年9月1日時点の実測値は以下のとおりだ。
| 項目 | 実測値(2026-09-01) |
|---|---|
| GitHub star | 1 |
| fork | 0 |
| 初回コミット | 2026-08-27 |
| 最終push | 2026-08-31 |
| コミット総数 | 19 |
| タグ | v0.3.1 のみ |
| ライセンス | MIT |
| 言語 | TypeScript(配布は単一JSバンドル) |
| npmレジストリ | 未公開(private: true) |
star 1・公開5日という数字は率直に受け止めるべきで、いま乗り換えを検討する段階のツールではない。それでも取り上げる価値があるのは、後述するAIの権限設計が、LLMを既存コマンドに埋め込むときの一般解として使える形をしているからだ。
zoxideとの関係を機能軸で並べると次のようになる。cdaiのREADMEは明確にzoxideを意識しており、cdai import zoxide というzoxideのデータベース取り込みコマンドまで用意されている。
| 観点 | zoxide | cdai |
|---|---|---|
| 基本方式 | 訪問履歴のfrecency | ネイティブ cd 優先 → インデックス+frecency |
| 未訪問ディレクトリ | 原則たどれない | 事前インデックスで探せる(root指定+深さ) |
| 曖昧な言い回し | 対象外 | AIフォールバック(任意・既定は確認プロンプト付き) |
latest / 年指定 |
対象外 | モデル非依存で解釈 |
| 実装 | Rust(ネイティブバイナリ) | Node.js(単一JS・要Node 20+) |
| 実行時依存 | なし | npm依存ゼロ(標準モジュールのみ・本記事で確認) |
| 実績 | 広く使われている | star 1・公開5日 |
差が一番はっきり出るのは「未訪問ディレクトリ」の行だ。zoxideは一度 cd した場所しか覚えないので、新しく clone したリポジトリには最初は飛べない。cdaiは roots に指定したディレクトリを深さ付きで走査してインデックスを作るため、一度も訪れていない場所でも候補になる。この設計はAIとは無関係の、決定的な部分の差である。
AI無しで何ができるか——決定的機能だけを実測する
「AI搭載」を名乗るツールは、AIを切ったときに何が残るかで実力が分かる。cdaiの決定的機能だけを、AIバックエンドを呼ばせずに一通り叩いた結果が次の表である。いずれもモデルを一切経由していない。
| 入力 | 実測結果 | AI関与 |
|---|---|---|
petal |
.../clients/petalworks へ解決 |
なし |
latest petalworks folder |
petalworks-2026(更新時刻が最新の子) |
なし |
oldest petalworks folder |
petalworks-2024 |
なし |
petalworks 2025 |
petalworks-2025 |
なし |
petalworks 2024 |
petalworks-2024 |
なし |
tictac(一度も訪れていない) |
tictactoe-3d と tictactoe-web を候補提示 |
なし |
asanakti(タイポ) |
決定できずAI段へ委譲 | あり |
注目すべきは tictac の行だ。この2つのディレクトリには一度も cd していないが、roots の走査でインデックスに入っているため候補に出る。訪問履歴に依存するzoxideでは原理的にできない挙動で、ここがcdaiの実質的な差別化点である。AIではなく、事前インデックスという決定的な仕組みが効いている。
latest / oldest / 年指定も同様にモデル非依存で、更新時刻とディレクトリ名から解釈される。AIを --no-ai で完全に切っても、上表のうちタイポの1行以外はすべて動く。 逆に言えばAIが担当しているのは「決定的な処理では絞り切れなかった残り」だけで、その範囲は実測してみるとかなり狭い。
インストールと初期設定——npmレジストリには無い
READMEが推奨するのはHomebrew tapで、npmは補助経路として書かれている。ここで注意点が1つある。パッケージ cdai はnpmレジストリに存在しない。
# npmレジストリに問い合わせると Not found が返る(2026-09-01 実測)
curl -s https://registry.npmjs.org/cdai
# => {"error":"Not found"}
package.json にも "private": true が入っており、レジストリ公開を明示的に止めている。READMEの npm install -g github:franzenzenhofer/cdai はレジストリ経由ではなくGitHubリポジトリを直接指す記法で、これは正しく動く。READMEの書き方自体は誤っていないが、「npmで入る」と読み替えると npm install -g cdai で失敗するので、そこだけ補っておく。
推奨経路は次のとおり。なおこのbrewコマンドについては、tapリポジトリに Formula/cdai.rb(3,662バイト)が実在し、class Cdai < Formula として v0.3.1 のtarballを参照していることまで確認した(インストール自体は手元の環境を変更するため実行していない)。式の中では外部のNode 20+をPATHから探す独自Requirementが定義されており、READMEの「既存のNodeを再利用する」という記述と実装が一致している。
# 推奨:Homebrew tap
brew install franzenzenhofer/tap/cdai
# シェル統合を有効化(zshの例)。bashは bashrc、fishは cdai init fish | source
echo 'eval "$(cdai init zsh)"' >> ~/.zshrc
exec "$SHELL"
# 対話なしで初期設定(rootと深さを指定し、AIは切っておく)
cdai setup --root "$HOME/dev" --depth 3 --yes --no-ai
導入後の状態確認には cdai doctor がある。実測した出力は、rootごとの可否・インデックス件数と鮮度・AIバックエンドの有無・fzfとTTYの有無まで1画面に出る。設定ファイルの権限まで privacy 行で見ているのは良い設計だ。
$ cdai doctor
cdai doctor
node v22.13.1
config ok /tmp/cdai-demo/.config/cdai/config.json
roots 2
ok /private/tmp/cdai-demo/dev (depth 2)
ok /private/tmp/cdai-demo/Dropbox/clients (depth 3)
ai enabled via custom
index ok 13 dirs, 1min old
fzf miss
tty miss
privacy ok private state permissions
なお本記事の実測は、リポジトリに dist/cdai.js がコミットされている点を利用して、Homebrewもグローバルnpmも使わずに行った。検証用のHOMEを一時ディレクトリに差し替えれば、手元の環境を一切汚さずに挙動を確かめられる。
「AIに存在しないパスを作らせない」を偽バックエンド6通りで測る
ここからが本題である。cdaiの設定ファイルにおけるAIの指定は、次のように任意のコマンドでしかない。
{ "ai": { "enabled": true, "command": "/path/to/any-command", "timeoutMs": 5000 } }
そのコマンドが {"path": "...", "reason": "..."} というJSONを標準出力に出せば、cdaiはそれをAIの回答として扱う。つまりLLMを一切呼ばずに、狙った嘘を返す偽AIを書ける。そこで6通りの応答を用意し、同じクエリ(petalworkz=実在ディレクトリ petalworks のタイポ)で1件ずつ試した。
決定的に重要なのは陽性対照である。「拒否された」だけを並べても、単にAI経路が動いていないだけかもしれない。そこで最初に「正しい候補を返す偽AI」を置き、採用される経路が生きていることを先に確かめた。
| # | 偽AIが返した内容 | 実測結果 |
|---|---|---|
| 1(陽性対照) | 候補リストにある実在パス | 採用。[Y/n] 確認 → 移動。以後ローカルの別名として記憶 |
| 2 | 存在しないパス(架空のディレクトリ) | 拒否:answer was not one of the offered directories |
| 3 | /etc(実在するがrootの外) |
拒否:同上 |
| 4 | 実在しインデックス済みだが候補に無いディレクトリ | 拒否:同上 |
| 5 | JSONですらない文字列 | 拒否:unparseable answer |
| 6 | {"path": null}(AIが棄権) |
拒否:I do not know |
実際の出力はこうなる。1は確認プロンプトまで到達し、2以降は決定的な候補提示に落ちる。
# 1) 陽性対照:候補リストにあるパスを返す偽AI
cdai: thinking... (custom)
cdai: /private/tmp/cdai-demo/Dropbox/clients/petalworks (typo of petalworks) [Y/n] y
→ /private/tmp/cdai-demo/Dropbox/clients/petalworks
# 2) 存在しないパスを返す偽AI
cdai: thinking... (custom)
cdai: ai had no usable answer (answer was not one of the offered directories)
# 4) 実在しインデックス済みだが、提示された候補には無いパス
cdai: thinking... (custom)
cdai: ai had no usable answer (answer was not one of the offered directories)
注目したいのは3と4が2と同じ理由で落ちていることだ。/etc は実在するディレクトリで、4に至ってはcdai自身がインデックスに持っているディレクトリである。それでも拒否される。つまり判定は「そのパスは実在するか」でも「rootの内側か」でもなく、「今回そのAIに提示した候補リストの要素か」という集合の所属で行われている。
ソース側の該当箇所も同じことを言っている。返答は候補との完全一致に加えて、実在ディレクトリであることの両方を要求される。
・生成させない:AIはパスを書くのではなく、渡された選択肢から選ぶ
・二重条件:候補の要素であること かつ 実在すること
・失敗時は決定的側へ:どの拒否理由でも、最後は決定的な候補提示(closest:)に落ちる
この形は、LLMを既存のコマンドに足すときの一般解として使い回せる。モデルに自由記述させず、安全な選択肢を先に列挙して選ばせる——そうすればハルシネーションは「候補外の回答」という検出可能な事象に縮退する。ターミナル補完にAIを載せる別のアプローチはIRISとは|ターミナルにIntelliSense風の候補とAI補完を出すGo製シェル補完ツールで扱っている。
AIが呼ばれない2つの条件——TTYと候補ゼロ
実測中に見つかった、READMEには明示されていない挙動が2つある。どちらも「AIが動かない」と誤解しやすい。
第1に、端末が無いとAIは呼び出し自体が行われない。 パイプ経由やスクリプト内で実行すると、AIバックエンドを叩く前に辞退する。
$ echo y | cdai query -- "that client with the flowers"
cdai: AI confirmation requires a terminal [no terminal, declined]
これは「確認が取れないから実行しない」という fail-closed な作りで、CI やスクリプトの中でAIが勝手にディレクトリを決めることは構造的に起きない。裏を返せば、AI経路を試すには本物の端末が要る(本記事の実測はpty経由で行った)。
第2に、候補が0件だとAIは呼ばれない。 手元の検証環境で that client with the flowers のような完全に曖昧な言い回しを投げると、AIバックエンドが有効でも no match で終わった。決定的な処理が候補を1件も作れなかった場合、AIに渡すものが無いためである。READMEのデモGIFのように動かすには、frecencyの履歴か部分一致の手掛かりが要る。実際 petalworkz(タイポ)ではAIまで到達した。
つまりcdaiのAIは、決定的な処理が「候補は絞れたが決めきれない」状態のときだけ呼ばれる、かなり狭い担当範囲を持つ。この控えめさは、cd という間違えると困るコマンドに載せる機構としては妥当な設計判断だと言える。
一度確認したAIの答えは二度とAIに聞かない——別名の実測
実測の副産物として、READMEが1行だけ触れている挙動を確かめられた。確認プロンプトで Y を押した言い回しは、その場でローカルの別名として記録され、以後モデルを介さずに解決される。
これは意図せず発覚した。陽性対照(候補リストの正しいパスを返す偽AI)で Y を押したあと、続けて別の偽AIに差し替えて同じクエリを投げたところ、AIが一度も呼ばれずに前回のパスへ直行したのである。thinking... の表示すら出ない。
$ cdai alias list
petalworkz -> /private/tmp/cdai-demo/Dropbox/clients/petalworks
そのため本記事の6通りの比較は、各試行の前に cdai alias forget "petalworkz" を挟んで別名を消してから行っている。この手順を踏まないと、2回目以降はすべて1回目の結果を再生するだけになり、ガードレールを検証したつもりで何も検証できていない状態になる。
運用面での意味は2つある。第1に、AIの呼び出し回数は使うほど減る。曖昧な言い回しも一度確認すれば固定化されるため、AIバックエンドのコストやレイテンシは初回だけに寄る。第2に、誤って確認した別名は残り続ける。間違ったディレクトリに Y を押した場合は cdai alias forget <言い回し> で明示的に消す必要がある。
なお別名は無条件に信頼されるわけではない。ソース上、記録済みの別名は使用時に「現在の roots の内側にあるか」と「実在するディレクトリか」を再判定し、外れていればその場で破棄して通常の解決に戻る。rootの設定を変えた後に古い別名が生き残る、という壊れ方はしない作りになっている。
依存ゼロと単一バンドルの実体を確かめる
READMEは「One bundled Node 20+ executable, zero runtime npm dependencies」と書く。配布物を直接調べた結果、これは正しかった。
# バンドルが読み込む外部モジュールを列挙する
grep -o 'from *"[^"]*"' dist/cdai.js | sort -u
出力は node:child_process / node:crypto / node:fs / node:os / node:path の5行のみで、npmパッケージを指すimportは1件も無かった。ファイルサイズは109,671バイト(約107KB)の単一JSファイルである。
速度については、正直に書いておくべき留保がある。本記事の計測環境は並行して別の重い処理が走っており、絶対値は信用できない。 そのうえで、比較可能な形にするため「何もしない空のNodeスクリプト」を基準に測った。
| 計測対象 | 実測レンジ(5回・同一環境) |
|---|---|
| Node起動のみ(空スクリプト) | 0.51〜2.41 秒(初回2.41はコールドスタート。以降4回は0.51〜0.72) |
cdai query -- petal(決定的経路) |
0.67〜1.17 秒 |
| インデックス構築(13ディレクトリ) | 50 ms(cdai自身の報告値) |
読み取れるのは「支配的なコストはcdaiの処理ではなくNode本体の起動である」という点だ。cdai自身の追加分は概ね0.1〜0.4秒に収まっている。cd は1日に何十回も打つコマンドなので、Node起動を毎回挟む構造は、Rustネイティブバイナリのzoxideに対する構造的な不利になる。なおTab補完はインデックス済みの状態から引くため、この起動コストとは別経路である。CLIの実行コストを実測で比べる観点はtrippyとは|mtr コマンド相当をsudoなしで動かすRust製ネットワーク診断TUIを実測検証でも扱っている。
zoxideから移るべきか——現時点の結論
結論を先に書くと、2026年9月1日時点で乗り換えは勧めない。理由は機能ではなく成熟度にある。
・star 1・公開5日:作者以外の運用報告が実質的に存在しない。バグの発見も修正も、まだ1人に依存している
・タグは v0.3.1 の1本のみ:バージョン間の互換性方針が観測できるほどの履歴が無い
・npm未公開:導入経路がHomebrew tapとGit直参照に限られる
・Node起動コストが毎回乗る:最も打鍵回数の多いコマンドに載せる構造として不利
一方で、次の2点は今の段階でも価値がある。
・未訪問ディレクトリを引けるという決定的機能は、zoxideに無い実用差である
・AIの権限を候補選択に閉じ込める設計は、star数と無関係に参考になる
現実的な付き合い方は、zoxideを消さずに cdai import zoxide で履歴を引き継ぎ、--no-ai の決定的モードで並走させることだ。これならAI経路の不確実性を持ち込まずに、インデックス機能だけを試せる。ローカル完結のCLIツールを見極める手順としては、portlessとは|localhostのポート番号を名前付きURLに置き換えるVercel Labs製CLIを実測と同じく、「公称値を1つずつ手元で潰す」やり方が有効だった。
まとめ——検証できたことと、できなかったこと
検証できたこと
・「AIに存在しないパスを作らせない」は事実だった。偽AIバックエンド6通りのうち採用されたのは候補リストの要素だった1件のみで、実在パスであってもrootの外・候補外なら拒否された
・拒否の判定基準は「実在するか」ではなく「提示した候補リストの要素か」という集合所属である
・TTYが無い環境ではAIは呼び出し自体が行われない(fail-closed)
・実行時のnpm依存はゼロで、外部importは5つの標準モジュールのみ・単一の約107KBバンドル
・npmレジストリには未公開で、npm install -g cdai は成立しない
検証できなかったこと
・実在のLLMバックエンドとの組み合わせは試していない。本記事のAI経路はすべて自作の偽バックエンドによるもので、モデルの回答品質は測っていない
・速度の絶対値は計測環境が並行負荷下にあったため信用できない。相対比較(Node起動が支配的)のみを結論としている
・Homebrew tap経由のインストールは実行していない。tapリポジトリの実在は確認したが、導入そのものはリポジトリ同梱の dist/cdai.js を直接実行する形で代替した
star 1のツールに本番を任せる理由は無い。だが「LLMに自由記述させず、安全な候補を先に列挙して選ばせる」という設計は、規模と無関係に持ち帰る価値がある。
参照ソース
・franzenzenhofer/cdai — GitHubリポジトリ(README・src/commands/query.ts・src/ai/client.ts・package.json・docs/demo-fixture.sh を参照。2026-09-01時点)
・ajeetdsouza/zoxide — GitHubリポジトリ(比較対象の一次ソース)
・npmレジストリ cdai エンドポイント(未公開であることの確認)