🕹️

ターミナルでチャットできるGoベースの対話型シェル「chatsh」を作った

に公開

はじめに

開発作業をしているときにせんようClientに移らずに「ターミナル自体がチャットルームになったらどうだろう?」という発想から、Goでファイルシステムに似せたなリアルタイムチャットシェルのchatshを作りました。

chatsh demo movie

https://github.com/ponyo877/chatsh

chatshとは

chatshは、ターミナル内でファイルシステム風の操作でチャットルームを管理できる対話型シェルです。

主な特徴:

  • ls, cd, mkdirといった標準的なシェルコマンドでチャットルームを操作
  • vimコマンドでTUIベースのリアルタイムチャット
  • gRPCの双方向ストリーミングによる低遅延通信
  • ファイルシステムの階層構造でチャットルームを管理
# インストール
$ brew install ponyo877/tap/chatsh

# 使用例
$ chatsh
❯ mkdir project-team
❯ cd project-team  
❯ touch daily-standup
❯ vim daily-standup  # チャットルーム開始

技術選定

Go + gRPC

リアルタイム性を重視して、gRPCの双方向ストリーミングRPCを採用しました。WebSocketと比較してプロトコルレベルでの型安全性と効率的なシリアライゼーションが決め手でした。

service ChatshService {
  rpc StreamMessage(stream ClientMessage) returns (stream ServerMessage);
}

message ClientMessage {
  oneof payload {
    Join join = 1;
    Chat chat = 2;
    Tail tail = 3;
  }
}

ファイルシステムメタファー

チャットルームの管理にファイルシステムの概念を導入。直感的な操作と学習コストの削減を狙いました。

SQLiteで階層構造を実現:

-- ディレクトリ構造
CREATE TABLE directories (
    id          INTEGER  PRIMARY KEY AUTOINCREMENT,
    name        TEXT     NOT NULL,
    parent_id   INTEGER  NOT NULL REFERENCES directories(id),  -- 親Dir
    owner_token TEXT     NOT NULL REFERENCES users(token),
    path        TEXT     NOT NULL,  -- CLI高速化ための絶対PATH
    created_at  DATETIME NOT NULL,
    UNIQUE (parent_id, name),
    UNIQUE (path)
);

-- チャットルーム(ファイルを模している)
CREATE TABLE rooms (
    id           INTEGER  PRIMARY KEY AUTOINCREMENT,
    name         TEXT     NOT NULL,
    directory_id INTEGER  NOT NULL REFERENCES directories(id),  -- 親Dir
    owner_token  TEXT     NOT NULL REFERENCES users(token),
    path         TEXT     NOT NULL,  -- CLI高速化ための絶対PATH
    created_at   DATETIME NOT NULL,
    UNIQUE (directory_id, name),
    UNIQUE (path)
);

Cloud Run + Litestream

データ永続化にLitestreamを使用してSQLiteファイルをGoogle Cloud Storageにレプリケーション。Cloud SQLを使わずコストを大幅削減しました。
終始以下のkokiさんの記事を参考にさせていただきました。
https://zenn.dev/kou_pg_0131/articles/google-cloudrun-litestream

実装のポイント

双方向ストリーミング通信

クライアント側ではgRPCストリームを確立し、goroutineで非同期にメッセージを受信:

func runChatUI(client pb.ChatshServiceClient, roomPath string) error {
    stream, err := client.StreamMessage(ctx)
    if err != nil {
        return err
    }

    // メッセージ受信用goroutine
    go func() {
        for {
            msg, err := stream.Recv()
            if err == io.EOF {
                return
            }
            // UI更新
            updateChatView(msg)
        }
    }()

    // メッセージ送信処理
    // ...
}

TUIの実装

tviewを使用してリッチなターミナルUIを構築
https://github.com/rivo/tview

textView := tview.NewTextView().
    SetDynamicColors(true).
    SetScrollable(true)

inputField := tview.NewInputField().
    SetLabel("❯ ").
    SetAcceptanceFunc(tview.InputFieldMaxLength(256))

flex := tview.NewFlex().
    SetDirection(tview.FlexRow).
    AddItem(textView, 0, 1, false).
    AddItem(inputField, 1, 0, true)

CLI框架

cobraでサブコマンドを構築し、go-promptで対話型モードとTAB補完を実装
https://github.com/spf13/cobra
https://github.com/c-bata/go-prompt

func completer(d prompt.Document) []prompt.Suggest {
    // gRPCでディレクトリ情報取得
    res, _ := client.ListNodes(ctx, &pb.ListNodesRequest{Path: currentPath})
    
    var suggestions []prompt.Suggest
    for _, entry := range res.Entries {
        suggestions = append(suggestions, prompt.Suggest{
            Text: entry.Name,
        })
    }
    return suggestions
}

自動リリース

GoReleaserでGitHubリリースとHomebrew Tapを自動化:

# .goreleaser.yaml
builds:
  - binary: chatsh
    goos: [linux, darwin, windows]
    goarch: [amd64, arm64]

brews:
  - repository:
      owner: ponyo877
      name: homebrew-tap
    homepage: https://github.com/ponyo877/chatsh

こちらもkokiさんの記事を参考にしました、もうkokiさん信者です。
https://zenn.dev/kou_pg_0131/articles/goreleaser-usage

まとめ

chatshを通じて、以下の技術を実践的に学べました:

  • gRPCの双方向ストリーミング
  • Goでのリアルタイム通信
  • TUIライブラリの活用
  • ファイルシステムメタファーを用いたUX設計
  • Cloud Run + Litestreamによる低コスト構成

ターミナルネイティブなコミュニケーション体験として、新しい開発ワークフローの可能性を提示できたのではないかと思います。

興味のある方はぜひ試してみてください:

brew install ponyo877/tap/chatsh

また、Asakusa.goでも発表させていただきました!
https://speakerdeck.com/ponyo877/filesystemfeng-tiyatutochatshkai-fa-texue-ntagonobian-li-packageshao-jie

会場を提供してくださっているドクターズプライムさんのイベントレポートでも取り上げていただけました!
ありがとうございます!
https://note.com/drsprime/n/n9f21a7eea007

Discussion