2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Claude Code】会議後の15分を0秒にした。録音→要約→Drive→Git pushが全部Enterキー1回で終わるCLIの全実装【BARAX 第2弾】

2
Last updated at Posted at 2026-03-21

スクリーンショット 2026-03-21 午前0.26.57.png

この記事は「BRAXシリーズ」の第2弾です。
前回の記事で、サイバーパンク風CLI「BARAX」の基盤(barax init / barax history / barax doctor)を作りました。
まだ読んでない方はこちらから👇

前回: 【Claude Code】 全部まとめて殴るサイバーパンクCLI「BARAX」をTypeScriptで自作した

今回はBRAXに barax meeting(会議自動化) コマンドを追加します。

この記事の登場人物
🧑‍💻 …Claude Code で自作CLI「BARAX」を育てている先輩(前回からの続き)
🔰 …「議事録って手動で書くものじゃないんですか?」レベルの後輩

会議が終わった瞬間、あなたは何をしていますか?

🔰「先輩、会議が終わると毎回こうなるんですけど…」

✅ Notion の録音を停止する
✅ 文字起こしが生成されるのを待つ
✅ AI 要約をコピーする
✅ Markdown に整形する
✅ 所定のフォルダに保存する
✅ Google Drive にアップロードする
✅ リンクを議事録に追記する
✅ Git に commit & push する

🔰「8ステップ、毎回15分。 しかもたまに忘れる」
🧑‍💻「…」
🔰「先輩は?」
🧑‍💻「Enterキー1回
🔰「は???」

$ barax meeting start --title "週次定例"

  ✔ Notion データベースを開きました
  ✔ 新規ページを作成しました
  ✔ 録音を開始しました

  ╭ RECORDING ────────────────────────────╮
  │   録音中... Enterキーで停止します。   │
  ╰───────────────────────────────────────╯

  ← ここで会議する。終わったらEnter。

  ✔ ローカル録音停止 (1823秒)
  ✔ Notion 録音を停止しました
  ✔ AI 要約が生成されました
  ✔ 議事録を保存しました
  ✔ Google Drive にアップロードしました
  ✔ Git にプッシュしました

  ┌─ 完了サマリー
  │  タイトル:     週次定例
  │  録音時間:     1823秒
  │  議事録:       ~/my-vault/meetings/2026-03-22_週次定例/minutes.md
  │  Drive 議事録:  https://drive.google.com/file/d/xxxxx/view
  │  Drive 録音:    https://drive.google.com/file/d/yyyyy/view
  │  Git:          pushed to main
  └────────────────────────────────────────

🔰「…全部終わってる」
🧑‍💻「しかも保存先は Obsidian Vault。Git管理されてるから、どの端末からでも過去の議事録を全文検索できる」
🔰「先輩、これ作るの何日かかったんですか」
🧑‍💻「Claude Code と2人で1日

🔥 この記事、こんな人に刺さります

  • 「会議後の作業、自動化できないかな…」と思ったことがある人
  • macOS の自動化に興味があるけど JXA がよくわからない人
  • Notion API じゃなくて ブラウザ操作 で自動化したい人
  • Obsidian を「ただのメモ帳」以上に使いたい人
  • Claude Code で何か面白いもの作りたい人

この記事でわかること

  • Notion AI Meeting Notes をCLIから完全自動操作する方法
  • macOS のマイク権限問題を .app バンドルで突破する技(これが一番ヤバい
  • rclone で Google Workspace Drive に自動アップロードする方法
  • Obsidian Vault + Git で議事録をナレッジベース化する設計
  • 全工程をEnterキー1回に圧縮する 実装パターン

前回のおさらい:BRAXとは

🔰「先輩、BRAXって何でしたっけ?」
🧑‍💻「前回の記事で作った、Claude Code 日本語対応のサイバーパンクCLI」

前回作ったコマンド 機能
barax init / barax 初期化 テンプレートから対話的にプロジェクト生成
barax history search / barax 履歴 検索 セッション履歴の2層検索
barax doctor / barax 診断 10項目ヘルスチェック + スコアリング

🧑‍💻「今回はここに 4つ目のコマンド を追加する」

今回追加するコマンド 機能
barax meeting start / barax 会議 開始 会議の録音→要約→保存→Drive→Git を全自動化
barax meeting list / barax 会議 一覧 過去の会議記録を一覧表示

🔰「前回の基盤があるから、コマンドを追加するだけで済むんですね」
🧑‍💻「そう。Commander.js のサブコマンド構造のおかげで、registerMeetingCommand(program) を1行追加するだけ」


完成品:1コマンドで何が起きるか

🧑‍💻「まず全体像を見せる」

🔰「12ステップが裏で動いてるんですね」
🧑‍💻「ユーザーがやるのは コマンド実行Enterで停止 の2アクションだけ」


「Notion に録音機能あるじゃん」への完全回答

🔰「…ていうか、Notion に直接録音機能ありますよね?わざわざ CLI で自動化する意味あります?」
🧑‍💻「その質問、待ってた」
🧑‍💻「Notion 単体だとできないことが5つある

🧑‍💻「もっと根本的な話をすると、CLIは合成可能 (composable) なんだよ」
🔰「合成可能?」
🧑‍💻「パイプで繋げられる。cron で定期実行できる。他のスクリプトから呼べる。GUIアプリにはこれができない」

特性 GUI (Notion Web) CLI (BARAX)
自動化 不可 (人間がクリック) barax meeting start で全自動
再現性 人間依存 (手順忘れ) コマンドが手順そのもの
合成 不可 cron, &&, パイプで連携可能
カスタマイズ Notion の UI に制約される コード変更で自由自在
オフライン 不可 ローカル録音は Notion なしでも可
データ所有 Notion のサーバーに依存 ローカル Markdown + Git

🔰「"データ所有" って大事なんですか?」
🧑‍💻「Notion のエクスポートやったことある?大量のページを一括で Markdown にするのは結構面倒。最初からローカルに Markdown があれば、そもそもエクスポートの必要がない」

CLIの本質: 「繰り返しを消す」こと
GUIは 1回の操作 を簡単にする。CLIは 100回の操作をゼロ にする。
会議が週5回あるなら、手動の15分 × 5回 × 4週 = 月5時間が蒸発
年間にすると 60時間。1.5週分の労働時間。CLIを1日で作れば、投資回収は1ヶ月

🧑‍💻「あとClaude Codeとの相性が最高にいい。CLI のコードは Claude Code が読めるし、修正もできる。"Drive のアップロード先変えたい" って言えば、Claude が meeting.ts を直接編集してくれる」
🔰「GUI のワークフローはAIに修正させられないけど、CLIのコードならできると」
🧑‍💻「そう。コードで書かれたワークフローは、AIに最適化させ続けられる。これが2026年にCLI自動化を選ぶ最大の理由」


なぜ Obsidian Vault に保存するのか — 「検索できない議事録」に意味はない

🔰「議事録って Google Docs とか Notion にそのまま残せばよくないですか?」
🧑‍💻「3ヶ月前の会議で "認証フロー" について何を決めたか、今すぐ調べられる?」
🔰「えっと… Notion で検索して… Google Docs も見て…」
🧑‍💻「それが答え。検索がサービスに閉じてる。Obsidian Vault なら…」

🧑‍💻「ポイントは プレーンテキスト (Markdown) が真のソース だということ」

保存先 役割 なぜ必要か
Obsidian Vault ナレッジベース 全文検索、wiki link、グラフビューで議事録が知識資産になる
Git (GitHub) バージョン管理 どの端末からもアクセス、変更履歴の追跡
Google Drive チーム共有 GWS組織内でリンク1つで共有、Slack連携

🔰「3箇所に保存されるのに、ユーザーは何もしなくていいと」
🧑‍💻「そう。Markdownファイルを1つ生成すれば、あとはCLIが配る」

Vault のディレクトリ構造

~/my-vault/                              ← Obsidian Vault ルート (Git管理)
├── meetings/                            ← 議事録はここに自動生成
│   ├── 2026-03-22_週次定例/
│   │   ├── minutes.md               ← 議事録 (AI要約+文字起こし+Driveリンク)
│   │   └── audio.m4a               ← ローカル録音 (.gitignore対象)
│   ├── 2026-03-22_1on1/
│   │   ├── minutes.md
│   │   └── audio.m4a
│   └── ...
├── notes/                               ← 他のノートも同居可能
└── .gitignore                           ← *.m4a, *.wav 等を除外

🔰「音声ファイルは Git に入らないんですか?」
🧑‍💻「.gitignore で除外してる。音声は Google Drive にだけ上がる。Git にはテキストだけ — これが正しい使い分け」

生成される議事録の形式

# 週次定例

- **日付**: 2026-03-22
- **録音時間**: 30分23秒
- **生成元**: Notion AI Meeting Notes (ブラウザ録音)
- **自動化方式**: barax meeting (JXA + Chrome)
- **音声ファイル**: ~/my-vault/meetings/2026-03-22_週次定例/audio.m4a

---

## Google Drive
- [議事録 (Google Drive)](https://drive.google.com/file/d/xxxxx/view)
- [録音データ (Google Drive)](https://drive.google.com/file/d/yyyyy/view)

---

## 概要

新機能のリリーススケジュールについて議論。認証フローの改修を来週月曜に着手予定。
デザインレビューは水曜に実施。パフォーマンス改善のベンチマークは金曜までに完了見込み。

---

## 文字起こし

要約中
会話の内容
新機能のリリースについて、スケジュールを確認しました...

🔰「メタデータ、Google Driveリンク、AI要約、文字起こし、全部入ってる」
🧑‍💻「Obsidianで開けば即座に全文検索できる。Cmd+Shift+F で "認証" って検索すれば、過去に認証の話をした会議が全部ヒットする」


アーキテクチャ全体像

🧑‍💻「技術的にはこう繋がってる」


技術スタック

パッケージ / ツール 役割 なぜこれか
JXA (osascript) Chrome タブ操作 macOS ネイティブ。追加インストール不要
ffmpeg マイク録音 AVFoundation 対応。あらゆる音声フォーマットに変換可能
rclone Google Drive アップロード CLI ベース。GWS (Google Workspace) 対応
Commander.js CLI フレームワーク .alias('会議') で日本語コマンド対応
ora スピナー 各ステップの進捗表示
dayjs 日付処理 2KB。ディレクトリ名生成に使用

実装 Part 1: JXA で Chrome を操る

🔰「そもそも JXA って何ですか?」
🧑‍💻「JavaScript for Automation。macOS に最初から入ってる自動化エンジン。AppleScript の JavaScript 版」

jxa-chrome.ts — 5つの関数

// src/lib/jxa-chrome.ts

/** JXA スクリプトを実行する共通関数 */
function runJxa(script: string): string {
  return execSync(`osascript -l JavaScript -e '${escaped}'`, {
    encoding: 'utf-8',
    timeout: 10000,
  }).trim();
}

🧑‍💻「核はこの1行。osascript -l JavaScript で JXA を実行して、結果を文字列で返す」

関数 役割 使用場面
chromeOpen(url) URL を開く(既存タブ再利用) Notion DB を開く
chromeCloseDuplicates(prefix) 重複タブを閉じる 実行前のクリーンアップ
chromeExec(js) DOM 操作(戻り値不要) ボタンクリック、テキスト入力
chromeEval(js) DOM から値を取得 AI 要約テキスト抽出
chromePoll(js, opts) 条件が満たされるまでポーリング 要素の出現待ち

chromePoll — DOM が変わるまで待つ

🧑‍💻「Notion は SPA だから、ページ遷移しても DOM がいつ更新されるかわからない。だから ポーリング する」

export async function chromePoll(
  js: string,
  opts: { interval?: number; timeout?: number; label?: string } = {},
): Promise<string> {
  const interval = opts.interval ?? 2000;
  const timeout = opts.timeout ?? 120_000;
  const start = Date.now();

  while (Date.now() - start < timeout) {
    const result = chromeEval(js);
    if (result && result !== 'undefined' && result !== 'null' && result !== 'false') {
      return result;
    }
    await sleep(interval);
  }
  throw new Error(`Timeout: ${opts.label || 'chromePoll'}`);
}

🔰「何秒おきにチェックするんですか?」
🧑‍💻「デフォルト2秒。ボタン出現待ちは1〜1.5秒、AI要約待ちは3秒にしてる。Notion の負荷を考えて調整」


実装 Part 2: Notion ピークパネルという罠

🧑‍💻「Part 1 は JXA の基盤だった。ここからが 本当の戦い
🔰「え、まだ序盤なんですか」
🧑‍💻「Notion AI Meeting Notes のUIには 致命的な罠 がある。ピークパネルとフルページで全然違う」

🔰「フルページだと録音できないんですか?」
🧑‍💻「そう。フルページでは Meeting Notes テンプレートが自動適用されない。テンプレート選択画面が出るだけ。ピークパネルでしか録音ウィジェットが表示されない」

問題:ピークパネルが閉じることがある

🧑‍💻「ピークパネルは不安定で、たまに勝手に閉じる。それで録音開始に失敗する」
🔰「どう解決したんですか?」
🧑‍💻「リトライ機構 を入れた」

// Step 2: 新規ページ作成 (リトライ付き)
let peekPanelReady = false;
for (let attempt = 0; attempt < 3 && !peekPanelReady; attempt++) {
  if (attempt > 0) {
    spinner.text = `新規ページを作成中... (リトライ ${attempt + 1}/3)`;
    await sleep(1000);
  }

  // 「新規」/「New」ボタンをクリック (日英両対応)
  chromeExec(`
    var buttons = document.querySelectorAll('div[role="button"]');
    for (var i = 0; i < buttons.length; i++) {
      var t = buttons[i].textContent.trim();
      if (t === '新規' || t === 'New') { buttons[i].click(); break; }
    }
  `);

  // 文字起こしオプションの出現を待つ (10秒)
  const result = await chromePoll(`
    (function() {
      var el = document.querySelector('[aria-label="その他の文字起こしオプション"]')
            || document.querySelector('[aria-label="More transcription options"]');
      return el ? 'ready' : '';
    })()
  `, { interval: 1000, timeout: 10000 }).catch(() => '');

  if (result === 'ready') peekPanelReady = true;
}

🔰「3回リトライするんですね」
🧑‍💻「1回10秒 × 最大3回 = 30秒。パネルが閉じても再クリックする。これで成功率が体感50%から95%以上になった」

問題:UIの言語が日本語だったり英語だったりする

🔰「"その他の文字起こしオプション""More transcription options" の両方をチェックしてますよね」
🧑‍💻「Notion は組織設定やブラウザ言語によってUIの言語が変わる。同じページでもボタンが日本語だったり英語だったりする」

// 日英両対応のボタン検出パターン
const patterns = {
  newButton:     ['新規', 'New'],
  startButton:   ['ブラウザで開始', 'Start in browser'],
  stopButton:    ['停止', 'Stop'],
  transcription: ['その他の文字起こしオプション', 'More transcription options'],
};

🧑‍💻「全てのDOM操作で日本語と英語の両方を試す。これをやらないと、ある日突然動かなくなる」

Notion の DOM は言語設定で変わる。 aria-label やボタンテキストに依存する自動化は、必ず多言語対応を入れること。

🔰「ピークパネルのリトライ、DOM の多言語対応… Notion 自動化って大変ですね」
🧑‍💻「いや、まだ本当のボスが残ってる


実装 Part 3: macOS マイク権限の壁 — これが一番ヤバかった

🧑‍💻「ここで半日溶けた
🔰「何がですか?」
🧑‍💻「ターミナルから ffmpeg を起動してもマイクが使えない。声出してるのに 0KB。無音」
🔰「…怖すぎる」

macOS TCC (Transparency, Consent, and Control) の仕組み

🔰「Ghostty って何ですか?」
🧑‍💻「最近人気のターミナルアプリ。でもこれが正規の .app バンドルじゃないから、macOS の TCC (マイク権限管理) に登録できない」
🔰「つまり?」
🧑‍💻「ターミナルから直接 ffmpeg を起動すると、TCC が "このアプリにマイク権限ない" と判断して 無音データ を返す。声出してるのに 0KB」

解決策: 「自分で .app を作ればいい」

🧑‍💻「3時間悩んで、ふと思った。ターミナルにマイク権限がないなら、マイク権限のあるアプリを自分で作ればいい
🔰「え、アプリ作るんですか?」
🧑‍💻「といっても正規の .app バンドルを作って、その中から ffmpeg を呼ぶだけ」

BaraxRecorder.app/
├── Contents/
│   ├── Info.plist         ← macOS に「マイク使います」と宣言
│   └── MacOS/
│       └── barax-recorder ← ffmpeg を呼ぶ bash スクリプト

Info.plist — マイク権限の宣言

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "...">
<plist version="1.0">
<dict>
    <key>CFBundleIdentifier</key>
    <string>com.barax.recorder</string>
    <key>CFBundleExecutable</key>
    <string>barax-recorder</string>
    <key>LSUIElement</key>
    <true/>
    <key>NSMicrophoneUsageDescription</key>
    <string>barax meeting コマンドで会議音声を録音するためにマイクを使用します。</string>
</dict>
</plist>

🔰「LSUIElement って何ですか?」
🧑‍💻「Dock にアイコンを表示しない設定。バックグラウンドアプリとして動く」

barax-recorder — ffmpeg ラッパースクリプト

#!/bin/bash
OUTPUT="$1"
DURATION="${2:-0}"

FFMPEG="/opt/homebrew/bin/ffmpeg"
ARGS=(-y -f avfoundation -i :0)

if [ "$DURATION" -gt 0 ] 2>/dev/null; then
  ARGS+=(-t "$DURATION")
fi

ARGS+=(-acodec aac "$OUTPUT")
"$FFMPEG" "${ARGS[@]}"

🔰「-f avfoundation -i :0 ってなんですか?」
🧑‍💻「macOS のオーディオ入力デバイス(マイク)をキャプチャする設定」

フラグ 意味
-f avfoundation macOS AVFoundation オーディオデバイスを使用
-i :0 デフォルトマイク (映像なし:音声デバイス0)
-acodec aac AAC コーデックでエンコード
-y 既存ファイル上書き

起動方法: open コマンドが鍵

// NG — TCC がブロックする (無音)
execSync('ffmpeg -f avfoundation -i :0 -acodec aac output.m4a');

// OK — .app バンドルとして起動 → TCC がマイク権限を付与
execSync('open "BaraxRecorder.app" --args "output.m4a"');

🧑‍💻「open コマンドで .app として起動すると、macOS が正式なアプリケーションとして認識する。初回起動時にマイク権限の確認ダイアログが出て、許可すれば以降は自動で録音できる」

なぜ spawn() ではダメなのか: child_process.spawn() は子プロセスを直接 fork する。この場合 TCC は 親プロセス (ターミナル) の権限をチェックする。ターミナルにマイク権限がなければ子プロセスも録音できない。open コマンドは macOS の Launch Services 経由で 独立したアプリケーション として起動するため、TCC が .app バンドルの Info.plist を見てくれる。

🔰「.app 作って open で起動する… macOS ハックすぎません?」
🧑‍💻「ドキュメントに書いてないことは、ソースコード(OS の挙動)から逆算するしかない。これがCLI自動化の醍醐味」


実装 Part 4: 録音停止とプロセス管理

🔰「録音を止めるときはどうするんですか?」
🧑‍💻「ffmpeg は SIGINT (Ctrl+C 相当) で正常終了する。PID を追跡して kill する」

// 録音開始時: PID を取得
execSync(`open "${recorderApp}" --args "${audioPath}"`, { stdio: 'ignore' });
await sleep(1500);  // ffmpeg 起動を待つ

let recorderPid: number | null = null;
try {
  const pid = execSync(
    `pgrep -n -f "avfoundation.*${audioPath}"`,
    { encoding: 'utf-8' }
  ).trim();
  recorderPid = parseInt(pid, 10) || null;
} catch {
  // pgrep が見つからなくてもアプリは動作中
}

// ... 会議中 ...

// 録音停止: SIGINT を送信
if (recorderPid) {
  try {
    process.kill(recorderPid, 'SIGINT');
  } catch {
    // プロセスが既に終了している場合のフォールバック
    execSync(`pkill -f "avfoundation.*audio.m4a"`, { stdio: 'ignore' });
  }
  await sleep(1500);  // ファイル書き出し完了を待つ
}

🔰「pgrep で PID を取るのはなぜ直接取れないんですか?」
🧑‍💻「open コマンドは即座に返る(バックグラウンド起動)。open 自体の PID は ffmpeg の PID じゃない。だから pgrep で "avfoundation" を含むプロセスを検索する必要がある」


実装 Part 5: AI の出力を「盗む」— DOM スクレイピング

🧑‍💻「録音停止後、Notion AI が自動で要約と文字起こしを生成する。問題は Notion API からはこのデータが取れない こと」
🔰「え、APIないんですか?」
🧑‍💻「Meeting Notes の要約はAPIで取得できない。だから DOM から直接抜く

// AI要約のポーリング
const summaryText = await chromePoll(`
  (function() {
    var root = document.querySelector('[data-content-editable-root="true"]');
    if (!root) return '';
    var text = root.innerText || '';
    if (text.indexOf('まだ記録されていません') !== -1) return '';
    if (text.indexOf('要約') !== -1 && text.length > 200) return text;
    return '';
  })()
`, { interval: 3000, timeout: 180000 }).catch(() => '');

🔰「なんで data-content-editable-root を使うんですか?」
🧑‍💻「Notion のエディタ領域を特定する一番安定したセレクタ。クラス名はビルドごとに変わるけど、data- 属性は比較的安定してる」

テキスト抽出のパース戦略

// Notion の innerText をセクション分割で抽出
const extractedText = chromeEval(`
  (function() {
    var root = document.querySelector('[data-content-editable-root="true"]');
    var text = root.innerText || '';

    // 要約: 「要約を共有」の後 〜 「形式:」の前
    var summary = '';
    var shareIdx = text.indexOf('要約を共有');
    if (shareIdx !== -1) {
      var afterShare = text.substring(shareIdx + '要約を共有'.length);
      var formatIdx = afterShare.indexOf('形式:');
      summary = formatIdx !== -1
        ? afterShare.substring(0, formatIdx)
        : afterShare.substring(0, 1000);
    }

    // 文字起こし: 「文字起こし」セクション
    var transcript = '';
    var transIdx = text.indexOf('\\n文字起こし\\n');
    if (transIdx !== -1) {
      transcript = text.substring(transIdx + '\\n文字起こし\\n'.length, transIdx + 2000);
    }

    return JSON.stringify({ summary, transcript, raw: text.substring(0, 2000) });
  })()
`);

🔰「テキストの位置を文字列検索で特定してるんですね」
🧑‍💻「Notion の DOM 構造に深く依存するのは危険だから、innerText を取得して文字列レベルでパースしてる。DOM が変わっても innerText は安定する」


実装 Part 6: Google Drive — GUI を開かずにアップロードする

🔰「ここまでで録音・停止・要約抽出・保存まで来ましたね」
🧑‍💻「次は チームへの共有 。Google Drive にアップする」
🔰「drive.google.com を開いてドラッグ&ドロップ…」
🧑‍💻「それをやめるために CLI を作ってるんだが?
🔰「す、すみません」
🧑‍💻「rclone を使う。クラウドストレージの rsync。40以上のサービスに対応してる CLI ツール」

セットアップ (初回のみ)

# インストール
brew install rclone

# Google Drive の認証 (ブラウザが開く)
rclone authorize "drive"

# 設定ファイル作成 (~/.config/rclone/rclone.conf)
# [gdrive]
# type = drive
# scope = drive
# token = {"access_token":"...","refresh_token":"..."}

Google Workspace (GWS) のドライブでも動く。 rclone の drive タイプは個人 Google Drive と GWS Drive の両方に対応。組織の管理者設定で「サードパーティアプリ」が許可されている必要がある。

アップロード + リンク取得の実装

async function uploadToGDrive(
  title: string, date: string,
  minutesPath: string, audioPath: string,
): Promise<DriveLinks | null> {
  // rclone が設定済みか確認
  try {
    execSync('rclone listremotes', { stdio: ['pipe', 'pipe', 'pipe'] });
  } catch { return null; }

  const links: DriveLinks = {};
  const baseName = `${date}_${title}`;
  const driveOpts = `--drive-root-folder-id=${GDRIVE_FOLDER_ID}`;

  // 1. 議事録をアップロード
  execSync(`rclone copyto "${minutesPath}" "gdrive:/${baseName}_議事録.md" ${driveOpts}`);

  // 2. ファイルIDを取得して共有リンクを構築
  const json = execSync(`rclone lsjson "gdrive:/" ${driveOpts} --files-only`, { encoding: 'utf-8' });
  const files = JSON.parse(json) as Array<{ Name: string; ID: string }>;
  const mdFile = files.find(f => f.Name === `${baseName}_議事録.md`);
  if (mdFile) {
    links.minutes = `https://drive.google.com/file/d/${mdFile.ID}/view`;
  }

  // 3. 録音データも同様にアップロード
  if (existsSync(audioPath)) {
    execSync(`rclone copyto "${audioPath}" "gdrive:/${baseName}.m4a" ${driveOpts}`);
    // ... 同様にリンク取得
  }

  return links;
}

🔰「rclone copytorclone copy は何が違うんですか?」
🧑‍💻「copy はディレクトリ to ディレクトリ。copyto は単一ファイルのアップロード。ファイル名の指定ができる。今回は minutes.md2026-03-22_週次定例_議事録.md にリネームしたいから copyto を使ってる」

ファイル命名規則

アップロード元 Drive 上のファイル名
minutes.md 2026-03-22_週次定例_議事録.md
audio.m4a 2026-03-22_週次定例.m4a

🧑‍💻「日付とタイトルが入ってるから、Drive 上でも一目で内容がわかる」


実装 Part 7: Git — 最後のピースで完全自動化が完成する

🧑‍💻「ラスボス。議事録を Obsidian Vault の Git リポジトリに自動 push する」
🔰「git add して commit して push するだけでは?」
🧑‍💻「"するだけ" が一番危ない思考。別端末の同期でリモートが先に進んでることがある」

function gitCommitAndPush(meetingDir: string, title: string): boolean {
  const repoRoot = join(homedir(), 'my-vault');
  const gitOpts = { cwd: repoRoot, stdio: 'pipe' as const, timeout: 15000 };

  try {
    const minutesRel = meetingDir.replace(repoRoot + '/', '') + '/minutes.md';
    execSync(`git add "${minutesRel}"`, gitOpts);
    execSync(`git commit -m "会議議事録: ${title}"`, gitOpts);

    // push を試み、失敗したら stash → rebase → push
    try {
      execSync('git push origin main', gitOpts);
    } catch {
      execSync('git stash', gitOpts);
      execSync('git pull --rebase origin main', gitOpts);
      execSync('git stash pop', gitOpts);
      execSync('git push origin main', gitOpts);
    }
    return true;
  } catch {
    return false;
  }
}

コンフリクト解消フロー

🔰「push が失敗するケースってあるんですか?」
🧑‍💻「別端末で Obsidian が同期した変更があると、リモートが先に進んでる。その場合のフォールバック」

🧑‍💻「stash → pull --rebase → stash pop → push の4ステップ。これで大抵のコンフリクトは自動解消される」

なぜ stash が必要か: git pull --rebase はワーキングツリーに変更があると失敗する。Obsidian が .skill ファイル等を削除してると unstaged changes 扱いになる。stash で退避 → rebase → 復元 で安全に処理できる。


実装 Part 8: 全部つなげる — 12ステップのオーケストレーション

🔰「Part 1〜7 で全パーツが揃いました。これを繋げると…」
🧑‍💻「Enter 1回で全部動く 。最終形を見せる」

CLIコマンドの登録

// src/commands/meeting.ts
export function registerMeetingCommand(program: Command): void {
  const meeting = program
    .command('meeting')
    .alias('会議')
    .description('Notion AI Meeting Notes の自動録音・要約取得');

  meeting
    .command('start')
    .alias('開始')
    .description('会議録音を開始し、停止後に要約を取得')
    .option('-t, --title <title>', '会議タイトル')
    .option('-d, --duration <seconds>', 'ローカル録音の秒数', '0')
    .option('--no-local', 'ffmpegローカル録音を無効化')
    .action(async (opts) => {
      // 12ステップのオーケストレーション
    });

  meeting
    .command('list')
    .alias('一覧')
    .description('過去の会議記録を一覧表示')
    .action(() => { listMeetings(); });
}
barax meeting start --title "週次定例"   # 全自動フロー
barax 会議 開始 -t "週次定例"            # 日本語でも同じ
barax meeting start --no-local           # ローカル録音なし
barax meeting list                       # 過去の会議一覧
barax 会議 一覧                          # 日本語版

💀 ハマりどころ5選 — 全部実話です

🔰「実装中にハマったところありました?」
🧑‍💻「全部で丸1日溶かした。同じ轍を踏まないように全部書く」

ピークパネル vs フルページ: テンプレートが適用されない

症状: 新規ページを開いたら「テンプレート / 空白のページ」の選択画面が出て、録音ウィジェットが表示されない

原因: フルページ表示ではテンプレートが自動適用されない。DB の「新規」ボタンから開くピークパネルでのみ Meeting Notes テンプレートが自動適用される

解決: フルページ遷移を完全に廃止。ピークパネルのみで操作する

ffmpeg が無音を返す (macOS TCC)

症状: ffmpeg でマイク録音したファイルが 3KB(無音)。声を出しているのに何も入らない

原因: Ghostty ターミナルが正規 .app バンドルでないため、macOS TCC のマイク権限リストに登録できない

解決: BaraxRecorder.app を作成し、open コマンドで起動

# NG
ffmpeg -f avfoundation -i :0 output.m4a  # → 無音

# OK
open BaraxRecorder.app --args output.m4a  # → 正常録音
rclone copy vs copyto: directory not found

症状: rclone copy "file.md" "gdrive:/path" → "directory not found" エラー

原因: rclone copy はディレクトリをソースに期待する

解決: 単一ファイルのアップロードは rclone copyto を使う

# NG
rclone copy "minutes.md" "gdrive:/folder"

# OK
rclone copyto "minutes.md" "gdrive:/2026-03-22_meeting.md"
git pull --rebase が unstaged changes で失敗する

症状: Git push → 失敗 → git pull --rebase → "Cannot rebase: You have unstaged changes"

原因: Obsidian のプラグインや同期ツールがファイルを変更・削除して unstaged changes が発生

解決: push失敗 → stash → pull --rebase → stash pop → push の4段階フォールバック

Notion DOM の言語が日本語だったり英語だったりする

症状: ある日突然 querySelector('[aria-label="その他の文字起こしオプション"]') が null を返す

原因: 組織設定やブラウザ言語の変更で、Notion UI が英語に切り替わることがある

解決: 全 DOM セレクタで日本語 || 英語のフォールバック

var el = document.querySelector('[aria-label="その他の文字起こしオプション"]')
      || document.querySelector('[aria-label="More transcription options"]');

まとめ — 「Enter 1回」の裏側にあったもの

🔰「ここまで読んで思ったんですけど、"Enter 1回" の裏に 相当な仕組み がありますね」
🧑‍💻「そう。でも一度作れば 永遠に動く。会議後の作業時間がゼロになった」

Before (手動) After (BARAX)
録音停止 → コピー → 整形 → 保存 → アップロード → push Enterキー → 完了
約15分/会議 0分 (自動)
忘れることがある 忘れない (自動)
フォーマットがバラバラ 毎回統一
検索しづらい Obsidian で全文検索

🧑‍💻「週5回会議がある人なら、月5時間 が消える計算」
🔰「議事録は Obsidian で検索できるし、Drive でチームに共有もできるし、Git で履歴も残る」
🧑‍💻「プレーンテキスト (Markdown) を真のソースにして、配信先を増やしていくパターン。これが一番強い」

発展アイデア

機能 概要
Slack 通知 会議完了時に議事録リンクをチャンネルに投稿
週次サマリー 今週の会議を LLM でまとめて自動生成
参加者検出 文字起こしから話者分離 → メンション
アクションアイテム抽出 AI 要約から TODO を自動抽出 → タスク管理連携

難易度ランキング — 何が一番大変だったか

🔰「一番大変だったのは?」
🧑‍💻「ダントツで macOS のマイク権限。これだけで半日溶けた。.app バンドルを自作するなんて想定してなかった」
🔰「二番目は?」
🧑‍💻「Notion のピークパネル。フルページで動かないことに気づくまでが長かった。あと、UI の言語が日英で変わるのは罠」
🔰「逆に一番簡単だったのは?」
🧑‍💻「rclonebrew install して rclone copyto するだけ。CLIツール同士の連携は本当に楽。これがCLIの世界の強さ


おわりに — 「面倒くさい」は最高のモチベーション

🔰「これって Notion 以外でも使えますか?」
🧑‍💻「JXA 部分は Chrome で動く SPA なら何でも応用できる。Google Meet の自動操作とか、Figma の自動エクスポートとか」
🔰「応用範囲広すぎません?」
🧑‍💻「"ブラウザで手動でやってること" は、全部自動化の候補

この記事で紹介した技術

# 技術 何に使ったか 応用先
1 JXA (osascript) Chrome タブの自動操作 任意の Web アプリの自動化
2 .app バンドル + TCC ターミナルからのマイク録音 カメラ、位置情報等の権限突破
3 rclone Google Drive への CLI アップロード S3, Dropbox, OneDrive 等 40+
4 Obsidian Vault + Git 議事録のナレッジベース化 あらゆるドキュメント管理
5 Commander.js alias barax 会議 開始 日本語コマンド 多言語 CLI の実装

🧑‍💻「最後に1つ」
🔰「はい」
🧑‍💻「"面倒くさい" と感じた瞬間が、自動化のスタートライン。 その感覚を大事にしろ。忙しさに慣れるな。"面倒くさい" を解決するコードを書け」
🔰「…かっこいい」
🧑‍💻「barax 会議 開始 でどうぞ」


前回の記事はこちら👇
【Claude Code】 全部まとめて殴るサイバーパンクCLI「BARAX」をTypeScriptで自作した

いいねしてくれると第3弾のモチベーションになります 🙏

2
0
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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?