OpenAI APIキー取得支援ツール(ソースコードと説明と利用ガイド)

プログラム利用ガイド

1. このプログラムの利用シーン

OpenAI APIのAPIキーを取得・設定するためのツールです。APIキーの取得からローカル環境への保存まで、一連の作業を対話式で案内し,APIキーのファイル保存等を行います。

2. 主な機能

3. 基本的な使い方

  1. ツールの起動:

    コマンドラインでプログラムを実行します。対話式メニューが表示されます。

  2. 設定の選択:

    環境変数名、保存先ファイル、ブラウザ起動方式をメニューから選択します。既定値のまま進むことも可能です。

  3. APIキーの取得:

    ブラウザが自動的に開くので、OpenAIにログインし、案内に従ってAPIキーを作成・コピーします。

  4. APIキーの入力:

    コピーしたAPIキーをツールに貼り付けます。入力内容は画面に表示されません。

  5. 保存の実行:

    確認メッセージの後、APIキーが指定した.envファイルに保存され、利用例が表示されます。

4. 便利な機能

Python 3.12 のインストール

インストール済みの場合は実行不要。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要である。

REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent
REM Python のパス設定
set "PYTHON_PATH=C:\Program Files\Python312"
set "PYTHON_SCRIPTS_PATH=C:\Program Files\Python312\Scripts"
echo "%PATH%" | find /i "%PYTHON_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_PATH%" /M >nul
echo "%PATH%" | find /i "%PYTHON_SCRIPTS_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_SCRIPTS_PATH%" /M >nul

関連する外部ページ

Python の公式ページ: https://www.python.org/

AI エディタ Windsurf のインストール

Pythonプログラムの編集・実行には、AI エディタの利用を推奨する。ここでは,Windsurfのインストールを説明する。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行して、Windsurfをシステム全体にインストールする。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要となる。

winget install --scope machine Codeium.Windsurf -e --silent

関連する外部ページ

Windsurf の公式ページ: https://windsurf.com/

OpenAI APIキー取得支援プログラム

概要

このプログラムは、OpenAI APIキーの取得と設定ファイルへの保存を支援するツールである。ブラウザでのAPIキー取得ページの自動表示と、APIキーの設定ファイル保存の機能を持つ。

主要技術

webbrowserモジュール

webbrowserモジュールは、ブラウザ制御機能を持つ[1]。デフォルトブラウザを自動検出し、指定されたURLを開く機能を持つ。

getpassモジュール

Pythonのgetpassモジュールは、パスワードやAPIキーなどを画面に表示せずに入力するためのものである[2]。

実装の特色

セキュリティ配慮の実装

APIキーの入力時にgetpass.getpass()を使用し、入力内容の画面表示を防止している。既存APIキーの表示時には部分マスク表示により、セキュリティを維持する。

既存設定の保護機能

既存の.envファイルからAPIキーを検出し、ユーザーに上書き確認を求める機能を実装している。.envファイル内のOPENAI_API_KEY行のみを更新する。

参考文献

[1] Python Software Foundation. (2025). webbrowser — Convenient web-browser controller. https://docs.python.org/3/library/webbrowser.html

[2] Python Software Foundation. (2025). getpass — Portable password input. https://docs.python.org/3/library/getpass.html

ソースコード


"""
OpenAI APIキー取得支援プログラム

特徴技術名: webbrowserモジュール
出典: Python Software Foundation. (2025). webbrowser — Convenient web-browser controller. Python 3.13.5 documentation. https://docs.python.org/3/library/webbrowser.html

特徴機能: プラットフォーム独立のブラウザ制御
webbrowserモジュールは、デフォルトブラウザを自動的に検出して使用する機能を提供する。

学習済みモデル: 使用していない

方式設計:
- 関連利用技術:
  - getpassモジュール: パスワード入力時のエコー無効化によるセキュアな入力機能
  - pathlibモジュール: オブジェクト指向ファイルパス操作とファイル読み書き機能
  - reモジュール: .env内の環境変数行の検出(export有無や空白を許容)
  - shutilモジュール: .envのバックアップ作成
- 入力と出力:
  入力:
  • 環境変数名の選択(OPENAI_API_KEY/カスタム)
  • 保存先ファイルの選択(.env/.env.development/.env.production/カスタム)
  • ブラウザの開き方の選択(既定/新しいタブ/新しいウィンドウ)
  • ユーザーによる選択操作(ブラウザでのAPIキー取得、コンソールでのAPIキー入力)
  出力: ブラウザでのOpenAI APIキーページ表示、APIキーの.envファイル保存、保存後のガイダンス表示
- 処理手順:
  1. 環境変数名と保存先ファイルを選択する
  2. 既存APIキーの確認と選択肢提示
  3. ブラウザでOpenAI APIキーページを開く
  4. ユーザーによる手動でのAPIキー取得
  5. getpassを使用したセキュアなAPIキー入力
  6. .envをバックアップし、APIキーの.envファイルへの保存
  7. 保存後の確認メッセージと利用ガイダンス表示
- 前処理: 既存.envファイルの読み込みとAPIキー存在確認
- 後処理: APIキー保存後の確認メッセージ表示
- 追加処理: APIキーの部分マスク表示によるセキュリティ配慮、バックアップ作成

- 調整を必要とする設定値:
  - var_name: 保存する環境変数名(OPENAI_API_KEY/カスタム)
  - filename: APIキーを保存するファイル名(デフォルト: .env)
  - browser_mode: ブラウザ起動モード(既定/新しいタブ/新しいウィンドウ)

将来方策: 既存の.envファイルの読み込みと同一APIキーの存在確認は現行実装で対応済み

その他の重要事項:
- getpassモジュールによりAPIキー入力時の画面表示を防止
- バックアップ作成機能により既存ファイルの安全性を確保
- 入力中断処理でユーザビリティを向上

前準備: なし(標準ライブラリのみ使用)
"""

import webbrowser
from pathlib import Path
import getpass
import re
import shutil

# 設定定数
DEFAULT_ENV_FILE = '.env'
DEFAULT_ENV_CHOICES = ['.env', '.env.development', '.env.production']
DEFAULT_KEY_CHOICES = ['OPENAI_API_KEY']
OPENAI_API_URL = 'https://platform.openai.com/api-keys'


def safe_input(prompt, default=None):
    """入力中断時の共通処理"""
    try:
        return input(prompt)
    except (KeyboardInterrupt, EOFError):
        if default is not None:
            print('入力が中断されたため既定値を用いる')
            return default
        print('入力が中断されたため処理を終了する')
        raise SystemExit(1)


def prompt_choice(header_lines, options_map, default_key):
    """数値メニューの表示と入力処理を共通化する。キーと対応値を返す。"""
    for line in header_lines:
        print(line)
    prompt = f"選択 ({'/'.join(options_map.keys())}, 既定: {default_key}): "
    key = safe_input(prompt, default=default_key).strip() or default_key
    return key, options_map.get(key)


def wait_enter(prompt):
    """Enterキー待ちの共通処理"""
    safe_input(prompt)


def sanitize_var_name(name):
    """環境変数名を検証・サニタイズする。無効な場合は既定名にフォールバックするための情報を返す。"""
    n = (name or '').strip()
    # 先頭の 'export' を除去(あれば)
    lowered = n.lower()
    if lowered.startswith('export'):
        parts = n.split(None, 1)
        n = parts[1] if len(parts) == 2 else ''
    # '=' を含む名称は不正(Windows仕様)
    if '=' in n:
        return (DEFAULT_KEY_CHOICES[0], True, "変数名に'='は使用できない")
    # POSIX慣用: 先頭は英字またはアンダースコア、その後は英数字とアンダースコアのみ
    if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', n):
        if n == '':
            return (DEFAULT_KEY_CHOICES[0], True, '空の変数名')
        return (DEFAULT_KEY_CHOICES[0], True, '許可文字は英字・数字・アンダースコア。先頭は英字またはアンダースコア')
    return (n, False, '')


def check_api_key(filename=DEFAULT_ENV_FILE, var_name='OPENAI_API_KEY'):
    """既存のAPIキーを確認する"""
    file_path = Path(filename)
    if not file_path.exists():
        return None

    try:
        content = file_path.read_text(encoding='utf-8')
        lines = content.splitlines()

        for line in lines:
            line = line.strip()
            if line.startswith(f'{var_name}='):
                api_key = line.split('=', 1)[1]
                if api_key:
                    return api_key
        return None
    except Exception as e:
        print(f'ファイル読み込みエラー: {e}')
        return None


def open_api_page(mode='default'):
    """OpenAIのAPIキーページを開く"""
    print('ブラウザでOpenAIのAPIキーページを開きます...')
    funcs = {'new_tab': webbrowser.open_new_tab, 'new_window': webbrowser.open_new}
    opened = funcs.get(mode, webbrowser.open)(OPENAI_API_URL)
    if not opened:
        print('ブラウザを開けなかった可能性がある。URLを手動で開くこと: ' + OPENAI_API_URL)
    return opened


def show_instructions():
    """手動操作の指示を表示"""
    print('\n=== 手動操作の手順 ===')
    print('1. ブラウザでOpenAIにログインしてください')
    print("2. 'Create new secret key'ボタンをクリック")
    print('3. キーの名前を入力(任意)')
    print("4. 'Create secret key'をクリック")
    print('5. 表示されたAPIキーをコピーしてください')
    print('   ※ このキーは一度しか表示されません!')
    print('=====================================\n')


def get_api_key():
    """ユーザーからAPIキーを取得"""
    print('コピーしたAPIキーを貼り付けてください')
    api_key = getpass.getpass('APIキー: ')
    return api_key.strip()


def save_api_key(api_key, filename=DEFAULT_ENV_FILE, var_name='OPENAI_API_KEY'):
    """APIキーをファイルに保存(上書きモード)"""
    try:
        file_path = Path(filename)

        existing_lines = []
        if file_path.exists():
            # バックアップ作成
            backup_path = Path(str(file_path) + '.bak')
            try:
                shutil.copyfile(file_path, backup_path)
                print(f'バックアップを作成した: {backup_path}')
            except Exception as be:
                print(f'バックアップ作成に失敗した: {be}')

            content = file_path.read_text(encoding='utf-8')
            for line in content.splitlines():
                if not line.strip().startswith(f'{var_name}='):
                    existing_lines.append(line + '\n')

        # 新しいAPIキーを追加
        content = ''.join(existing_lines)
        if content and not content.endswith('\n'):
            content += '\n'
        content += f'{var_name}={api_key}\n'

        # 親ディレクトリがない場合は作成
        file_path.parent.mkdir(parents=True, exist_ok=True)
        file_path.write_text(content, encoding='utf-8', newline='\n')
        print(f'APIキーを {file_path.resolve()} に保存しました')
        return True
    except Exception as e:
        print(f'保存エラー: {e}')
        return False


def choose_env_file():
    """保存先のenvファイルを選択する"""
    header = ['保存先ファイルを選択する']
    header += [f'{i}. {name}' for i, name in enumerate(DEFAULT_ENV_CHOICES, 1)]
    header += ['4. カスタムパスを指定する']
    options = {'1': DEFAULT_ENV_CHOICES[0], '2': DEFAULT_ENV_CHOICES[1], '3': DEFAULT_ENV_CHOICES[2], '4': None}
    key, value = prompt_choice(header, options, default_key='1')
    if key in ('1', '2', '3'):
        return value
    else:
        custom = safe_input('保存先ファイル名を入力: ', default=DEFAULT_ENV_FILE).strip()
        return custom or DEFAULT_ENV_FILE


def choose_var_name():
    """保存する環境変数名を選択する"""
    header = ['保存する環境変数名を選択する']
    header += [f'{i}. {name}' for i, name in enumerate(DEFAULT_KEY_CHOICES, 1)]
    header += ['2. カスタム名を指定する']
    options = {'1': DEFAULT_KEY_CHOICES[0], '2': None}
    key, value = prompt_choice(header, options, default_key='1')

    if key == '1':
        return value
    else:
        custom = safe_input('変数名を入力(例: OPENAI_API_KEY): ', default=DEFAULT_KEY_CHOICES[0]).strip()
        var_name, is_fallback, reason = sanitize_var_name(custom)
        if is_fallback:
            print(f"無効な環境変数名のため既定名にフォールバック: 入力='{custom}' 理由={reason} → 使用名='{var_name}'")
        return var_name


def choose_browser_mode():
    """ブラウザの開き方を選択する"""
    header = ['ブラウザの開き方を選択する', '1. 既定モードで開く', '2. 新しいタブで開く', '3. 新しいウィンドウで開く']
    options = {'1': 'default', '2': 'new_tab', '3': 'new_window'}
    _, value = prompt_choice(header, options, default_key='1')
    return value or 'default'


def show_post_save_guidance(filepath, var_name):
    """保存後の参考情報を表示する"""
    print('\n=== 利用の参考情報 ===')
    resolved = str(Path(filepath).resolve())
    print(f'保存パス: {resolved}')
    print(f'環境変数名: {var_name}')
    print('- 使用例:')
    print(f'  export $(grep -v "^#" {filepath} | xargs)')
    print('======================\n')


def main():
    print('=== OpenAI APIキー取得支援ツール ===\n')

    # 変数名と保存先ファイルを選択
    var_name = choose_var_name()
    env_file = choose_env_file()

    # 既存のAPIキーを確認
    existing_key = check_api_key(env_file, var_name)

    if existing_key:
        print(f'既存のAPIキーが見つかりました: {existing_key[:8]}...{existing_key[-4:]}')
        print('1. 既存のAPIキーを使用する')
        print('2. 新しいAPIキーで上書きする')

        choice = safe_input('選択 (1/2, 既定: 1): ', default='1').strip() or '1'

        if choice == '1':
            print('既存のAPIキーを使用します')
            print('\n処理を終了します')
            return
        elif choice != '2':
            print('無効な選択です')
            print('\n処理を終了します')
            return

    # ブラウザを開く
    mode = choose_browser_mode()
    wait_enter('Enterキーを押すとブラウザが開きます...')
    open_api_page(mode)

    # 手順を表示
    show_instructions()

    # APIキー取得の確認
    wait_enter('APIキーをコピーしたらEnterキーを押してください...')

    # APIキーを取得
    api_key = get_api_key()

    if api_key:
        print(f'\n取得したAPIキー: {api_key[:8]}...{api_key[-4:]}')

        # ファイルに保存
        save_choice = safe_input('\nファイルに保存しますか? (y/n, 既定: y): ', default='y').strip().lower()
        if save_choice in ('y', ''):
            saved = save_api_key(api_key, env_file, var_name)
            if saved:
                show_post_save_guidance(env_file, var_name)
    else:
        print('APIキーが入力されませんでした')

    print('\n処理を終了します')


if __name__ == '__main__':
    try:
        main()
    except (KeyboardInterrupt, EOFError):
        print('\n処理を中断しました。プログラムを終了します。')