1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Codex版ccresumeを作りました

Last updated at Posted at 2025-09-30

はじめに

sasazame/ccresumeは、Claude CodeのセッションをターミナルUIで管理できる便利なツールです。今回、このツールをOpenAI Codex CLI向けに移植したccresume-codexを開発しました。

本記事では、ccresume-codexがどのようなアプリなのか、実装の主要部分、そして実際の使い方を詳しく解説します。

リポジトリ: https://github.com/nogataka/ccresume-codex

画面サンプル

demo-screenshot.png

ccresume-codexとは

ccresume-codexは、OpenAI Codex CLIの会話履歴を閲覧し、過去のセッションを再開したり、新しいセッションを開始したりできるターミナルユーザーインターフェース(TUI)ツールです。

主な特徴

  • プロジェクト横断のセッション一覧: ~/.codex/sessions/配下のすべてのセッションを最終更新順に表示
  • メッセージプレビュー: ユーザー、アシスタント、ツール出力、reasoning を含む会話内容をプレビュー
  • セッション管理: 選択したセッションの再開や、特定ディレクトリでの新規セッション開始が可能
  • 高度なフィルタリング: カレントディレクトリのみに絞り込んだり、特定のメッセージタイプを非表示にしたりできる
  • コマンドエディタ: Codex CLIに渡すオプションをインタラクティブに編集可能
  • クリップボード連携: セッションUUIDをワンキーでコピー

技術スタック

  • UI Framework: Ink (React for CLIs)
  • Language: TypeScript
  • Testing: Jest + ink-testing-library
  • Build: tsc (TypeScript Compiler)

Web版 codex-viewer

実装の解説

1. エントリーポイント: cli.tsx

src/cli.tsxは、アプリケーションのエントリーポイントです。ここでコマンドライン引数の解析とInkアプリのレンダリングを行います。

主要な処理:

const args = process.argv.slice(2);
const currentDirOnly = args.includes('.');
let filteredArgs = args.filter(arg => arg !== '.');

// --hide オプションの解析
let hideOptions: string[] = [];
const hideIndex = filteredArgs.findIndex(arg => arg === '--hide');
if (hideIndex !== -1) {
  const validHideOptions = ['tool', 'thinking', 'user', 'assistant'];
  // --hide の後の引数を収集
  // ...
}

ポイント:

  • .オプションでカレントディレクトリのみにフィルタリング
  • --hideオプションで特定のメッセージタイプ(tool、thinking、user、assistant)を非表示にできる
  • Windows環境では入力が効かない問題への対処として、起動時に一時停止メッセージを表示

2. メインアプリケーション: App.tsx

src/App.tsxは、アプリケーションのコアロジックを担当します。

主要な状態管理:

const [conversations, setConversations] = useState<Conversation[]>([]);
const [selectedIndex, setSelectedIndex] = useState(0);
const [currentPage, setCurrentPage] = useState(0);
const [showCommandEditor, setShowCommandEditor] = useState(false);
const [showFullView, setShowFullView] = useState(false);

レイアウト設計:

固定レイアウトで画面を構成しています:

const ITEMS_PER_PAGE = 30;
const HEADER_HEIGHT = 2;
const LIST_MAX_HEIGHT = 9;
const MAX_VISIBLE_CONVERSATIONS = 4;

// ヘッダー + リスト + プレビューで画面を分割
const headerHeight = HEADER_HEIGHT;
const listHeight = Math.min(listMaxHeight, LIST_BASE_HEIGHT + visibleConversations);
const previewHeight = Math.max(MIN_PREVIEW_HEIGHT, dimensions.height - totalUsedHeight);

Codexコマンドの実行:

セッション再開時と新規開始時で異なるコマンドを構築します:

const buildCommandArgs = (
  conversation: Conversation,
  args: string[],
  actionType: 'resume' | 'start'
) => {
  if (actionType === 'resume') {
    const sessionIdentifier = conversation.sessionUuid ?? conversation.sessionId;
    return [...args, 'resume', sessionIdentifier];
  }
  if (args.length === 0) {
    return ['chat'];
  }
  return [...args];
};

3. セッション読み込み: conversationReader.ts

src/utils/conversationReader.tsは、Codexのログファイルを読み込み、会話データに変換します。

主要な処理フロー:

  1. listCodexSessionRecords(): ~/.codex/sessions/配下のすべてのJSONLファイルを探索
  2. parseCodexSession(): JSONLファイルをパースしてエントリに変換
  3. convertEntryToMessage(): Codexのエントリ形式を内部Message形式に変換
  4. buildConversationFromFile(): メッセージから会話オブジェクトを構築

型変換の例:

const convertEntryToMessage = (
  entry: CodexConversationEntry,
  sessionId: string,
  workspacePath: string,
): Message | null => {
  switch (entry.type) {
    case 'user':
      return {
        sessionId,
        timestamp: entry.timestamp ?? new Date().toISOString(),
        type: 'user',
        message: {
          role: 'user',
          content: [{ type: 'text', text: entry.text }],
        },
        cwd: workspacePath,
      };
    case 'assistant':
      // ...
  }
};

4. Codexセッションのパース: parseCodexSession.ts

src/codex/parseCodexSession.tsは、Codex CLIのJSONL形式ログを解析します。

対応するログタイプ:

  • session_meta: セッションのメタデータ(UUID、作業ディレクトリ、開始時刻)
  • response_item / response_message: ユーザーとアシスタントのメッセージ
  • reasoning: アシスタントの思考過程
  • function_call: ツール呼び出し
  • function_call_output: ツールの実行結果
  • event_msg: イベントメッセージ

パース処理の概要:

export const parseCodexSession = (content: string) => {
  const entries: CodexConversationEntry[] = [];
  const turns: CodexSessionTurn[] = [];
  
  const lines = content
    .split('\n')
    .map((line) => line.trim())
    .filter((line) => line.length > 0);

  for (const line of lines) {
    const parsed = JSON.parse(line);
    
    if (parsed.type === 'response_item') {
      // メッセージやreasoning、ツール呼び出しを処理
    }
    if (parsed.type === 'event_msg') {
      // イベントメッセージを処理
    }
    // ...
  }
  
  return { entries, turns, metaEvents, sessionMeta };
};

5. UIコンポーネント群

ConversationList.tsx

会話一覧を表示するコンポーネント。スクロール可能なリストを実装しています。

const visibleConversations = conversations.slice(startIndex, endIndex);
const hasMoreBelow = endIndex < conversations.length;

return (
  <Box flexDirection="column" borderStyle="single">
    {visibleConversations.map((conv, visibleIndex) => {
      const isSelected = actualIndex === safeSelectedIndex;
      const summary = generateConversationSummary(conv);
      // ...
    })}
    {hasMoreBelow && <Text> {conversations.length - endIndex} more...</Text>}
  </Box>
);

ConversationPreview.tsx

選択された会話のプレビューを表示します。メッセージのスクロール、フィルタリングに対応。

const filteredMessages = conversation.messages.filter(msg => {
  if (hideOptions.includes('tool') && content.startsWith('[Tool:')) {
    return false;
  }
  if (hideOptions.includes('thinking') && content === '[Thinking...]') {
    return false;
  }
  // ...
});

CommandEditor.tsx

Codex CLIオプションをインタラクティブに編集できるエディタ。

const codexOptions: CodexOption[] = [
  { flags: ['chat'], description: 'Start an interactive Codex chat session', hasValue: false },
  { flags: ['--model'], description: 'Specify the Codex model', hasValue: true },
  { flags: ['--sandbox'], description: 'Select sandbox mode', hasValue: true },
  // ...
];

オートコンプリート機能を実装:

useEffect(() => {
  const currentWord = getCurrentWord();
  if (currentWord.startsWith('-') || currentWord.length === 0) {
    const matching = codexOptions.filter(opt => 
      opt.flags.some(flag => flag.toLowerCase().startsWith(currentWord.toLowerCase()))
    );
    setSuggestions(matching);
  }
}, [commandLine, cursorPosition]);

6. 設定ファイルの読み込み: configLoader.ts

~/.config/ccresume/config.tomlからキーバインド設定を読み込みます。

export const loadConfig = (): Config => {
  try {
    const configContent = fs.readFileSync(configPath, 'utf-8');
    const parsed = toml.parse(configContent);
    // TOMLから設定を読み込み、デフォルトとマージ
    return mergeConfig(defaultConfig, parsed);
  } catch {
    return defaultConfig;
  }
};

使い方

📦 インストール方法

ccresume-codexは、npmパッケージとして公開されているため、インストール不要で即座に利用できます!

✨ 推奨:npxで即座に実行(インストール不要)

npx @nogataka/ccresume-codex@latest

このコマンド一つで、最新版のccresume-codexをダウンロードして実行できます。インストールや設定は不要です。

🔧 グローバルインストールして使う

頻繁に使用する場合は、グローバルインストールがおすすめです:

# グローバルインストール
npm install -g @nogataka/ccresume-codex

# インストール後は短いコマンドで実行可能
ccresume-codex
ccresume-codex .
ccresume-codex --hide tool

🛠️ 開発者向け:ソースコードから実行

開発やカスタマイズをする場合は、リポジトリをクローンして実行できます:

# リポジトリをクローン
git clone https://github.com/nogataka/ccresume-codex.git
cd ccresume-codex

# 依存関係をインストール
npm install

# 開発モードで実行
npm run dev

# ビルドして実行
npm run build
node dist/cli.js

基本的な使い方

# 基本的な起動
npx @nogataka/ccresume-codex@latest
# または(グローバルインストール済みの場合)
ccresume-codex

# カレントディレクトリのセッションのみ表示
npx @nogataka/ccresume-codex@latest .

# toolとthinkingメッセージを非表示
npx @nogataka/ccresume-codex@latest --hide tool thinking

# Codex CLIにオプションを渡す
npx @nogataka/ccresume-codex@latest -- --model o1-mini --json

# 組み合わせ
npx @nogataka/ccresume-codex@latest . --hide tool -- --sandbox workspace-write

💡 Tip: グローバルインストールした場合は、npx @nogataka/ccresume-codex@latestccresume-codexに置き換えて実行できます。

キーバインド

キー 機能
q 終了
↑/↓ 会話リストの上下移動
←/→ ページ移動
Enter 選択したセッションを再開
n 選択したディレクトリで新規セッション開始
- コマンドエディタを開く
c セッションUUIDをクリップボードにコピー
j/k メッセージ履歴のスクロール(1行)
d/u メッセージ履歴のスクロール(ページ単位)
g/G メッセージ履歴の先頭/末尾へ移動
f フルビュー表示の切り替え

コマンドエディタの使い方

-キーを押すとコマンドエディタが開きます。

  1. テキストを入力すると、候補が表示される
  2. ↑/↓で候補を選択
  3. TabまたはEnterで候補を挿入
  4. オプションの入力が完了したらEnterで確定
  5. Escでキャンセル

編集したオプションは、その後のEnter(セッション再開)やn(新規開始)で使用されます。

設定ファイルのカスタマイズ

~/.config/ccresume/config.tomlでキーバインドをカスタマイズできます:

[keybindings]
quit = ["q"]
selectPrevious = ["up"]
selectNext = ["down"]
pageNext = ["right", "pagedown"]
pagePrevious = ["left", "pageup"]
confirm = ["return"]
copySessionId = ["c"]
startNewSession = ["n"]
openCommandEditor = ["-"]
scrollUp = ["k"]
scrollDown = ["j"]
scrollPageUp = ["u"]
scrollPageDown = ["d"]
scrollTop = ["g"]
scrollBottom = ["G"]
toggleFullView = ["f"]

移植時の工夫と課題

Claude CodeからCodexへの主な変更点

  1. ログファイルの場所

    • Claude Code: ~/.claude/sessions/
    • Codex: ~/.codex/sessions/
  2. 履歴ファイルの形式

    • Claude Code: history.jsonlに履歴情報
    • Codex: セッションファイル自体にメタデータが含まれる
  3. CLIコマンド

    • Claude Code: claude コマンド
    • Codex: codex コマンド
  4. セッション識別子

    • 両方ともUUIDを使用するが、フォールバックとしてファイルパスからエンコードしたIDも使用

Windows環境への対応

Windows環境では、Codex CLI起動直後に入力が効かなくなる問題があります。これに対して、起動時に一時停止メッセージを表示し、ユーザーにEnterを押すよう促す対策を実装しました。

if (process.platform === 'win32') {
  console.log('📝 Windows ユーザー向けの注意: Codex CLI 起動後に入力できない場合は ENTER を押してください。');
  const pause = spawn('cmd.exe', ['/c', 'pause'], { stdio: 'inherit' });
  await new Promise((resolve) => {
    pause.on('close', resolve);
  });
}

型安全性の確保

TypeScriptの型システムを活用し、Codexのログ形式を厳密に型定義しました。これにより、ログ形式の変更に早期に気づくことができます。

export type CodexConversationEntry =
  | { type: 'user'; id: string; timestamp: string | null; text: string; source: string }
  | { type: 'assistant'; id: string; timestamp: string | null; text: string; source: string }
  | { type: 'assistant-reasoning'; id: string; timestamp: string | null; summary: string | null; text: string; encrypted: boolean }
  | { type: 'tool-call'; id: string; timestamp: string | null; name: string; arguments: string | null; callId: string | null }
  | { type: 'tool-result'; id: string; timestamp: string | null; callId: string | null; output: string | null }
  | { type: 'system'; id: string; timestamp: string | null; subtype: string; text: string | null };

まとめ

ccresume-codexは、Claude Code向けのccresumeをOpenAI Codex向けに移植したTUIツールです。InkとReactを使用した豊富なインタラクティブ機能、TypeScriptによる型安全性、柔軟なカスタマイズ性が特徴です。

Codex CLIを日常的に使用している方にとって、過去のセッションを素早く確認・再開できるこのツールは作業効率の向上に貢献するはずです。

ぜひお試しいただき、フィードバックやコントリビューションをお待ちしております!

リポジトリ: https://github.com/nogataka/ccresume-codex

参考リンク

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?