Skip to content

Instantly share code, notes, and snippets.

@xtetsuji
Last active June 3, 2025 05:08
Show Gist options
  • Save xtetsuji/b12d890308b755fec77e0e816f6e457e to your computer and use it in GitHub Desktop.
Save xtetsuji/b12d890308b755fec77e0e816f6e457e to your computer and use it in GitHub Desktop.
Gen-AI IDE Chat Prettier (support: VSCode, Cursor)

ide-chat-prettier - AIエディタのチャットを見やすくする

紹介

ide-chat-prettier を使うと、AIエディタのチャットをエクスポートした内容を見やすくしてくれます。 Visual Studio Code の Copilot、Cursor に対応しています。

具体的に、ユーザとAIの一回の対話を details summary でまとめてくれます。 これによってとても長く続くチャットセッション

インストール方法

ダウンロードしたファイルに実行権限を付与してパスの通ったディレクトリにいれるだけです。 例えば /usr/local/bin にパスが通っている場合は、ダウンロードした ide-chat-prettier に対して

$ chmod +x ide-chat-prettier 
$ sudo cp ide-chat-prettier /usr/local/bin/

で行えます。 /usr/local/bin のような root のみ書き込みが許可されているディレクトリの場合は sudo の使用と、 sudo のパスワードを適宜用いて下さい。

動作環境は perl 5.10 以上です。例えば 2025年現在の各種 Linux 環境の場合、これを満たさない環境はごく稀でしょう。

利用方法

引数なしで実行するとヘルプが出てきます。

ide-chat-prettier

必須引数は、チャットを保存したファイルです。チャットを保存したファイルを第1引数に指定すると、 出力として details summary で各会話ペアを囲んだ内容が出力されます。

ide-chat-prettier cursor_export.md

結果を保存したい場合はファイルリダイレクト > を使うか、第2引数に保存したいファイル名を書きます。

VSCode の場合、ユーザ側の会話の始まりの行が GitHubユーザ名とコロンの連結となっているので、GitHub ユーザ名を指定する必要があります。

ide-chat-prettier --github-user xtetsuji vscode_export.md

もしシステムに gh コマンドが入っていてログインが通っている場合は、それを使って GitHub アカウントを割り出せるため 引数は省略できます。

ide-chat-prettier vscode_export.md

AIチャットの内容のエクスポート方法

VScode の場合

チャットウィンドウの中のなにもない箇所をクリックすると出るメニューから「すべてコピー」を選択すると クリップボードに格納されます。

なお、macOS の場合は pbpaste コマンドでクリップボードの内容が出力されるので、 出力内容を一時的にファイルとみなす bash zsh のプロセス置換 <(...) を使うことで

ide-chat-prettier <(pbpaste)

とすることで変換結果を得ることもできます。

pbpaste の逆であるコピーを行う pbcopy コマンドと組み合わせて

ide-chat-prettier <(pbpaste) | pbcopy

とすることで、クリップボードの内容を変換後のものに置き換えることもできます(冪等ではないので重複実行に注意)。

Cursor の場合

チャットウィンドウの一番上のメニューバーの ... メニューから Export Chat を選択するとファイルとして出力できます。

ライセンス

MIT

Author: OGATA Tetsuji @xtetsuji

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use Getopt::Long qw(:config no_ignore_case bundling);
# UTF-8での入出力を有効にする
binmode(STDIN, ':utf8');
binmode(STDOUT, ':utf8');
binmode(STDERR, ':utf8');
# バージョン情報
my $VERSION = '1.0.0';
sub show_help {
print <<'EOF';
IDE Chat Export Formatter
Usage:
ide-chat-prettier [options] <input-file> [output-file]
Examples:
ide-chat-prettier cursor_export.md
ide-chat-prettier cursor_export.md formatted_output.md
ide-chat-prettier --github-user myusername vscode_export.md
Options:
-h, --help Show this help message
-v, --version Show version information
-g, --github-user USER Specify GitHub username for VS Code exports
Notes:
- For VS Code exports, the tool will attempt to detect your GitHub username
automatically using 'gh api /user' if GitHub CLI is available
- You can override this with the --github-user option
EOF
}
sub show_version {
print "ide-chat-prettier version $VERSION\n";
}
sub get_github_username {
my ($explicit_user) = @_;
# 明示的にユーザー名が指定された場合はそれを使用
return $explicit_user if $explicit_user;
# GitHub CLIを使ってユーザー名を取得
my $gh_user = `gh api /user 2>/dev/null | jq -r '.login' 2>/dev/null`;
chomp $gh_user if defined $gh_user;
if ($gh_user && $gh_user ne 'null' && $gh_user ne '') {
return $gh_user;
}
# git config からユーザー名を推定(フォールバック)
my $git_user = `git config --get user.name 2>/dev/null`;
chomp $git_user if defined $git_user;
if ($git_user && $git_user ne '') {
# スペースを除去してGitHubユーザー名っぽくする
$git_user =~ s/\s+//g;
return lc($git_user);
}
return undef;
}
sub detect_format {
my ($content, $github_user) = @_;
# Cursor形式の特徴を検出
if ($content =~ /^_Exported on.*?_/m && $content =~ /\*\*User\*\*/ && $content =~ /\*\*Cursor\*\*/) {
return 'cursor';
}
# VS Code形式の特徴を検出
elsif ($content =~ /GitHub Copilot:/ ||
($github_user && $content =~ /^$github_user:\s/m) ||
$content =~ /^[a-zA-Z0-9_-]+:\s/m) {
return 'vscode';
}
return 'unknown';
}
sub format_cursor_export {
my ($content, $github_user) = @_;
my $format = detect_format($content, $github_user);
if ($format eq 'cursor') {
return format_cursor_style($content);
} elsif ($format eq 'vscode') {
return format_vscode_style($content, $github_user);
} else {
die "Error: Unsupported chat export format. This tool supports Cursor and VS Code chat exports.\n" .
"For VS Code exports, make sure your GitHub username is detectable or specify it with --github-user.\n";
}
}
sub format_cursor_style {
my ($content) = @_;
# エクスポート情報行(_Exported on...)を削除
$content =~ s/^_Exported on.*?_\s*\n//gm;
# セクション区切り(---)で分割
my @sections = split(/\n---\n/, $content);
# 最初のセクションはタイトル部分
my $title = shift @sections || '';
$title =~ s/^\s+|\s+$//g; # 前後の空白を削除
my $result = "$title\n\n";
# User/Cursorのやり取りをペアで処理
for (my $i = 0; $i < @sections; $i += 2) {
my $user_section = $sections[$i] // '';
my $cursor_section = $sections[$i + 1] // '';
# セクションが空の場合はスキップ
next if !$user_section || !$cursor_section;
# Userセクションから**User**を削除してクリーンアップ
$user_section =~ s/^\s*\*\*User\*\*\s*\n?//;
$user_section =~ s/^\s+|\s+$//g;
# Cursorセクションから**Cursor**を削除してクリーンアップ
$cursor_section =~ s/^\s*\*\*Cursor\*\*\s*\n?//;
$cursor_section =~ s/^\s+|\s+$//g;
# セクションが空の場合はスキップ
next if !$user_section || !$cursor_section;
# Userコメントの最初の行をサマリとして使用
my ($summary) = split(/\n/, $user_section, 2);
$summary =~ s/^\s+|\s+$//g;
# 長すぎるサマリは単語境界で切り詰める
if (length($summary) > 100) {
$summary = substr($summary, 0, 97);
# 単語境界で切る
$summary =~ s/\s+\S*$//;
$summary .= '...';
}
# HTMLエスケープ
$summary = escape_html($summary);
# detailsブロックを構築
$result .= "<details>\n";
$result .= "<summary>$summary</summary>\n\n";
$result .= "**User:**\n\n";
$result .= "$user_section\n\n";
$result .= "**Cursor:**\n\n";
$result .= "$cursor_section\n\n";
$result .= "</details>\n\n";
}
return $result;
}
sub format_vscode_style {
my ($content, $github_user) = @_;
# GitHubユーザー名が指定されていない場合はエラー
unless ($github_user) {
die "Error: GitHub username is required for VS Code export format.\n" .
"Use --github-user option or ensure 'gh' CLI is available and authenticated.\n";
}
# ファイルパス行を削除
$content =~ s/^<!-- filepath:.*?-->\s*\n//gm;
my $result = "";
my @conversations = ();
# VS Codeの会話を解析(発話者: 内容の形式)
my @lines = split(/\n/, $content);
my $current_speaker = '';
my $current_content = '';
for my $line (@lines) {
# GitHubユーザー名またはGitHub Copilotを発話者として認識
if ($line =~ /^($github_user|GitHub Copilot|VS Code):\s*(.*)$/i) {
# 新しい発話者
if ($current_speaker && $current_content) {
push @conversations, {
speaker => $current_speaker,
content => $current_content
};
}
$current_speaker = $1;
$current_content = $2 || '';
} else {
# 継続行
if ($current_speaker) {
$current_content .= ($current_content ? "\n" : '') . $line;
}
}
}
# 最後の会話を追加
if ($current_speaker && $current_content) {
push @conversations, {
speaker => $current_speaker,
content => $current_content
};
}
# デバッグ情報(開発時のみ)
# print STDERR "Found " . scalar(@conversations) . " conversation parts\n";
# タイトルを生成(最初の質問から)
my $title = "# Chat Session";
if (@conversations && $conversations[0]->{content}) {
my ($first_line) = split(/\n/, $conversations[0]->{content}, 2);
$first_line =~ s/^\s+|\s+$//g;
if (length($first_line) > 50) {
$first_line = substr($first_line, 0, 47) . '...';
}
$title = "# $first_line" if $first_line;
}
$result .= "$title\n\n";
# 会話をペアで処理
for (my $i = 0; $i < @conversations; $i += 2) {
my $user_conv = $conversations[$i];
my $assistant_conv = $conversations[$i + 1];
next unless $user_conv && $assistant_conv;
my $user_content = $user_conv->{content};
my $assistant_content = $assistant_conv->{content};
$user_content =~ s/^\s+|\s+$//g;
$assistant_content =~ s/^\s+|\s+$//g;
next if !$user_content || !$assistant_content;
# サマリーを生成
my ($summary) = split(/\n/, $user_content, 2);
$summary =~ s/^\s+|\s+$//g;
if (length($summary) > 100) {
$summary = substr($summary, 0, 97);
$summary =~ s/\s+\S*$//;
$summary .= '...';
}
$summary = escape_html($summary);
$result .= "<details>\n";
$result .= "<summary>$summary</summary>\n\n";
$result .= "**$user_conv->{speaker}:**\n\n";
$result .= "$user_content\n\n";
$result .= "**$assistant_conv->{speaker}:**\n\n";
$result .= "$assistant_content\n\n";
$result .= "</details>\n\n";
}
return $result;
}
sub escape_html {
my ($text) = @_;
$text =~ s/&/&amp;/g;
$text =~ s/</&lt;/g;
$text =~ s/>/&gt;/g;
$text =~ s/"/&quot;/g;
$text =~ s/'/&#39;/g;
return $text;
}
# メイン処理
sub main {
my $help = 0;
my $version = 0;
my $github_user = '';
# コマンドラインオプションの解析
my $result = GetOptions(
'help|h' => \$help,
'version|v' => \$version,
'github-user|g=s' => \$github_user,
);
if (!$result) {
print STDERR "Error: Invalid command line options.\n\n";
show_help();
exit 1;
}
if ($help) {
show_help();
exit 0;
}
if ($version) {
show_version();
exit 0;
}
# 残りの引数(ファイル名)を取得
my $input_file = shift @ARGV;
my $output_file = shift @ARGV;
# 余分な引数があるかチェック
if (@ARGV) {
print STDERR "Error: Too many arguments.\n\n";
show_help();
exit 1;
}
# 引数チェック
if (!$input_file) {
print STDERR "Error: Input file is required.\n\n";
show_help();
exit 1;
}
# 入力ファイルの存在チェック
if (!-f $input_file) {
print STDERR "Error: Input file '$input_file' not found.\n";
exit 1;
}
# 入力ファイルを読み込み
open my $fh, '<:utf8', $input_file or die "Cannot open '$input_file': $!";
my $content = do { local $/; <$fh> };
close $fh;
# GitHubユーザー名を取得(必要に応じて)
my $detected_user = get_github_username($github_user);
if (!$github_user && $detected_user) {
$github_user = $detected_user;
print STDERR "Detected GitHub username: $github_user\n";
}
# フォーマット処理
my $formatted = format_cursor_export($content, $github_user);
# 出力
if ($output_file) {
open my $out_fh, '>:utf8', $output_file or die "Cannot create '$output_file': $!";
print $out_fh $formatted;
close $out_fh;
print "Formatted output written to: $output_file\n";
} else {
print $formatted;
}
return 0;
}
# スクリプトとして実行された場合のみmainを呼び出し
exit main() unless caller;
__END__
=head1 NAME
ide-chat-prettier - Format IDE chat exports for GitHub Issues/PRs
=head1 SYNOPSIS
ide-chat-prettier [options] input-file [output-file]
=head1 DESCRIPTION
This tool converts IDE chat export files (Cursor, VS Code) into a more readable format
suitable for GitHub Issues and Pull Requests. It transforms User/AI interactions
into collapsible details sections with summaries.
=head1 OPTIONS
=over 4
=item B<-h, --help>
Show help message and exit.
=item B<-v, --version>
Show version information and exit.
=item B<-g, --github-user USER>
Specify GitHub username for VS Code exports.
=back
=head1 EXAMPLES
# Basic usage - output to stdout
ide-chat-prettier cursor_export.md
# Specify output file
ide-chat-prettier cursor_export.md formatted_output.md
# With GitHub username for VS Code exports
ide-chat-prettier --github-user myusername vscode_export.md
=head1 AUTHOR
Created for formatting IDE chat exports.
=cut
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment