ブログ
30 秒で接続するまで: `npx tacticremote` 配布パスの作り方
`npx tacticremote` の裏側にあるエンジニアリングの話。Bun でビルドしたバイナリ、薄い npm ランチャー、QR の自動保存、そしてより速い初回導線までをまとめています。
このカテゴリのプロダクト/開発アップデートを確認できます。
カテゴリを見る私たちの目標はシンプルでした。新しいユーザーが 1 つのコマンドを実行し、QR Code を読み取り、30 秒以内にスマホでライブのターミナルを見られるようにすることです。
この投稿では、その実現方法を説明します。
何が問題だったのか
npx tacticremote が登場する前、サーバーの起動はプラットフォームごとに異なる複数ステップの作業でした。
- macOS: DMG をダウンロードし、マウントし、App を Applications に移動し、起動し、Gatekeeper とやり取りする。
- Linux / CI: リポジトリを clone して
npm installを実行し、その環境でどうやってサーバーを起動し、動かし続けるかを自分で考える。
リモートの Linux VM や Docker コンテナで作業している開発者にとって、これはかなりつらい流れでした。リポジトリを clone するには git、Node.js、そして npm registry にアクセスできるネットワークが必要です。誰かにそのまま渡せる「これを実行して」で済む 1 行コマンドはありませんでした。
私たちが欲しかったのは、npx tacticremote → QR 読み取り → 完了、という流れです。
なぜそのまま npm install しないのか
サーバーの runtime 依存は多くありませんが、それでも npm install には摩擦があります。現在の runtime package には ws、bonjour-service、qrcode-terminal が含まれています。
npm の依存解決にはそれだけで時間がかかり、その前にサーバーは何も始まりません。企業ネットワークやコンテナ化された CI のような制約環境では、proxy 設定、registry mirror、権限の問題で npm install が失敗することもあります。
さらに重要なのは、CLI パス自体が軽くあるべきだということです。もし npm に公開するパッケージが runtime tree をそのまま抱えていたら、初回体験は依然として「JavaScript を大量にダウンロードしてからサーバーを起動する」になってしまいます。私たちが目指した導線にはまだ届きません。
だからこそ、npm に載せる層は極力薄く保つ必要がありました。
Bun によるビルド
私たちは Bun を使ってサーバーを単一の自己完結型バイナリにコンパイルしています。結果として得られるのは、およそ 20 MB の実行ファイルです。gzip 前の状態でもそのまま実行でき、次のものを内包しています。
- Bun の実行環境
- すべての runtime 依存
- サーバーの完全なソースコード
node_modules はありません。別途 Bun を入れる必要もありません。起動時の依存解決もありません。バイナリを受け取って、そのまま実行するだけです。
GitHub Actions(.github/workflows/build-cli.yml)は、リリースごとに次の 4 ターゲットをビルドしています。
bun-darwin-arm64- Apple Silicon Macbun-darwin-x64- Intel Macbun-linux-x64- x86_64 Linuxbun-linux-arm64- ARM Linux(Raspberry Pi、AWS Graviton など)
各バイナリは gzip 圧縮されたアセットとして GitHub Release にアップロードされます。
薄いラッパー
npmjs.com に公開されるパッケージはサーバー本体ではありません。bin/tacticremote.js という 124 行のランチャースクリプトです。実際にやっていることは次のとおりです。
process.platformとprocess.archを使って、現在の OS と CPU アーキテクチャを判定する- この環境に対応するバイナリ名を解決する
~/.tacticremote/binにある現在バージョンのキャッシュ済みバイナリを確認する- キャッシュがなければ、GitHub Releases から gzip 圧縮されたバイナリをダウンロードし、展開してディスクに書き込み、実行権限を付与する
- そのバイナリを起動し、stdio を継承しつつ
SIGINTやSIGTERMなどのシグナルを転送する
初回実行では、その release アセットをダウンロードしてローカルにキャッシュします。2 回目以降は、現在の npm パッケージ version に対応するローカルバイナリを再利用します。
このパターンは新しいものではありません。esbuild が先駆けで、turbo も使っており、prisma にも似た仕組みがあります。要するに、npm は配布チャネルであって、コンパイル環境ではないということです。依存ツリーではなく、ダウンローダーを届けるために使うのです。
package.json の分離
私たちは 2 つの package manifest を並べて管理しています。
package.json- 開発用設定。Jest、ESLint、Prettier などの devDependencies、テストスクリプト、lint スクリプト、本地開発で使う完全な依存を含みます。package.npm.json- 省いた公開用 manifest。binをtacticremote.jsに向け、runtime 依存ツリーを外しています。
この分離により、開発用と公開用の責務は明確になります。ただし当然ながら、両方の metadata を一致させておくという維持コストはあります。
現時点では、ここに対する公開時の検証はまだ十分ではありません。この切り替えが常に正しいことを保証する仕組みが repo にないので、CI の自動化が次に必要な改善です。
30 秒の導線
配布の問題を解決したあと、接続フローの各ステップを見直し、摩擦をひとつずつ潰しました。
0-5 秒。 ユーザーが端末で npx tacticremote を実行します。初回実行では、ランチャーがキャッシュ済みバイナリがないことを検知してダウンロードします。2 回目以降は、キャッシュ確認は数ミリ秒で終わります。
5-10 秒。 サーバーバイナリが起動します。ローカル IP を検出し、ランダムな API key を生成し、Unicode ブロック文字で QR Code を端末に直接表示します。ブラウザも、Web UI も不要です。
10-15 秒。 ユーザーがスマホで Tactic Remote を開きます。ホーム画面には「コンピュータを接続」カードが表示され、npx tacticremote コマンドと「QR をスキャン」ボタンがあります。ボタンを押すとカメラが開きます。
15-25 秒。 端末に表示された QR Code をスキャンします。QR にはサーバーの IP、ポート、API key を含む JSON payload が入っています。アプリはそれを解釈し、サーバー設定を自動保存します。Add Server 画面も、手動 IP 入力も、API key のコピペもありません。
25-30 秒。 WebSocket handshake が完了し、ターミナル出力がスマホへ流れ始めます。接続インジケータが緑になります。
QR の自動保存が、いちばん大きな摩擦を消しました。以前の導線では、Add Server を押して、IP を入れて、ポートを入れて、API key を入れて、保存して、接続する必要がありました。5 回のタップと 3 回の手入力です。QR スキャンは、それを 1 つのジェスチャーに圧縮します。
やり直せるならどうするか
いくつか、まだ直せていない既知の課題があります。
署名検証がまだない。 ランチャーは HTTPS でバイナリを取得しますが、公開された署名や checksum で整合性を検証してはいません。これは現行方式で最も重要な残りの完全性ギャップです。
manifest の切り替えはまだ自動化が必要。 開発用 manifest と公開用 manifest を分けるのは正しい判断ですが、両者がずれないことを保証する公開時のガードレールがまだ足りません。
バックアップミラーがない。 GitHub Releases が一時的に使えない場合、ランチャーには別の取得先がありません。二次ミラーがあれば、配布パスの信頼性はさらに上がります。
全体像
この配布パスは 1 つの原則に基づいています。ユーザーにインフラのことを考えさせないことです。Bun が何かを知る必要も、Linux マシンの CPU アーキテクチャを知る必要も、API key を手で写す必要もありません。コマンドが入口を作り、ランチャーがバイナリを取得し、QR Code が接続情報を運びます。
サーバー本体のアーキテクチャ、単一依存設計、セキュリティモデルについては、姉妹記事 「依存パッケージ1つ:ws パッケージだけで本番サーバーを構築する」 で詳しく説明しています。この記事は、そのサーバーをいかに素早くユーザーの手元に届けるかについてです。
まだ 30 秒フローを試していないなら、/download から試してみてください。
サーバーのアーキテクチャについては「依存パッケージ1つ:ws パッケージだけで本番サーバーを構築する」を参照してください。セットアップ手順はGetting Started ガイドを参照してください。
Tactic Remote を試す
スマートフォンからコーディングエージェントを操作
Mac、Windows、Linux 上の Claude Code や Codex などに接続。iPhone や iPad で進捗を確認し、次の指示を送れます。