GitHubのトレンドを眺めていたら、OfficeCLI というのが上のほうに来ていて、説明に「AIエージェント向けに作られた世界初のOfficeスイート」とありました。Word・Excel・PowerPointをコマンドラインから作ったり編集したりできるらしい。
恥ずかしながら、僕はこれまで「プログラムからExcelを作る」といえば Python の openpyxl や python-pptx くらいしか知りませんでした。あれはあれで動くんですが、スライドを1枚足すだけでも十数行書くことになって、正直ちょっと面倒だなと思っていたんですよね。
それが1コマンドで済むと書いてあったので、本当かな、と思って試してみました。しかも単一バイナリで、.NET のランタイムも埋め込み済み、Office本体のインストールも不要だと言う。ここまで言うなら動かしてみるしかないな、と。
OfficeCLIとは
OfficeCLIは、Word・Excel・PowerPointをコマンドラインから生成・編集できるC#製のCLIツールです。「AIエージェント向けに作られた世界初のOfficeスイート」を掲げており、.NETランタイムを埋め込んだ単一バイナリで動くため、Office本体のインストールは不要とされています。以下では、この単一バイナリを使い捨てのDockerコンテナに落として、インストールから実際の使い方(Word/Excel/PowerPointの生成)までを検証していきます。
試した環境
いつもどおり、ホスト(Windows)は汚したくないので、使い捨てのDockerコンテナの中だけで完結させています。コンテナを立てて → 動かして → 壊す、の流れです。
| 項目 | 値 |
|---|---|
| ベースイメージ | debian:bookworm-slim |
| OS | Debian GNU/Linux 12 (bookworm) |
| OfficeCLI | v1.0.131(officecli-linux-x64、単一バイナリ約35MB) |
| 追加で入れたもの | libicu72(72.1-3+deb12u1) |
リリースから linux-x64 のバイナリを1つ落としてくるだけ。ダウンロードと chmod +x で実測2秒でした。README には「.NET ランタイムは埋め込み済み、何もインストール不要」と書いてあったので、これで動くだろうと思ったら——最初の --version でいきなりコケました。
OfficeCLIのインストール——「何もインストール不要」のはずがlibicuで詰まった
落としてきたバイナリを叩いた最初の出力がこれです。
$ ./officecli --version
Process terminated.
Couldn't find a valid ICU package installed on the system. Please install
libicu (or icu-libs) using your package manager and try again.
最初は全然分からなくて、しばらく固まりました。「単一バイナリで依存なし」と書いてあったのに、なんで別のパッケージを要求してくるんだ、と。
調べてみたところ、.NET の自己完結バイナリでもグローバリゼーション(文字列比較や地域化)まわりは OS の ICU ライブラリを使うので、debian:bookworm-slim のような最小イメージだとそこだけ足りない、という話でした。エラーメッセージ自体は親切で、libicu を入れてくれと書いてあります。素直に従います。
$ apt-get install -y libicu72
$ ./officecli --version
1.0.131
あっさり通りました。今思えば、「ランタイム埋め込み = OSの共有ライブラリも一切要らない」ではないんですね。普段 Ubuntu や普通の Debian で動かす人はまず踏まないやつなので、スリム系イメージで動かす人だけが引っかかる罠だと思います。ここは最初に知っておきたかった。
PowerPointを1コマンドずつ組み立ててみる
環境が整ったので、READMEの最短手順どおりにスライドを作ってみます。create して、スライドを1枚足して、テキストボックスを置く、という流れです。
$ ./officecli create deck.pptx
Created: deck.pptx (kept open in background for faster subsequent commands)
totalSlides: 0
slideWidth: 960pt
slideHeight: 540pt
$ ./officecli add deck.pptx / --type slide --prop title="Q4 Report" --prop background=1A1A2E
Added slide at /slide[1]
$ ./officecli add deck.pptx "/slide[1]" --type shape \
--prop text="Revenue grew 25%" --prop x=2cm --prop y=5cm \
--prop font=Arial --prop size=24 --prop color=FFFFFF
Added shape at /slide[1]/shape[@id=100000]
create したときに「バックグラウンドで開いたままにして次のコマンドを速くする」と出るのが面白いところで、常駐セッションを裏で持っているみたいです。要素を追加すると /slide[1]/shape[@id=100000] のように XPath っぽいパスが返ってきます。この「文書をツリーとして扱う」感覚が、Officeファイルをコマンドで触るということなんだな、と少し腑に落ちました。
作ったスライドの構造は view outline で確認できます。
$ ./officecli view deck.pptx outline
File: deck.pptx | 1 slides
├── Slide 1: "Q4 Report" - 1 text box(es)
ここでひとつ想定と違ったことがありました。追加したテキストボックスの中身を見ようと get /slide[1]/shape[1] を叩いたら、返ってきたのは自分が置いた “Revenue grew 25%” ではなく、タイトルの “Q4 Report” のほうだったんです。
$ ./officecli get deck.pptx "/slide[1]/shape[1]" --json
{
"path": "/slide[1]/shape[@id=2]",
"type": "title",
"text": "Q4 Report",
...
}
shape[1] は「1番目の図形」なのでてっきり自分が足したやつだと思っていたら、スライドのタイトルプレースホルダー(@id=2)がちゃんと1番目としてカウントされていたわけです。自分が追加したテキストボックスは別 @id=100000 になっている。言われてみれば当然なんですが、最初は「あれ、消えた?」と一瞬あせりました。パスで狙い撃ちするなら @id を使うのが確実そうです。
一番驚いたのはExcel——数式がExcelなしで計算される
ここまでで「へえ、便利だな」くらいだったのが、Excelを触って一気に評価が変わりました。
まず、READMEに $Sheet:A1 という書き方でセルを指定する例があったので、そのまま真似したら弾かれました。
$ ./officecli set book.xlsx "$Sheet1:A1" --prop value="Sales"
Error: Bare selector '$Sheet1:A1' is not allowed for 'set' —
it would match across the whole document.
公式ドキュメントにはこう書いてあるんですが、実際やってみると通らない、というやつですね。ただ、エラーメッセージが「Sheet1!A1(Excel記法)か /Sheet1/A1(DOMパス)を使え」とちゃんと代替案を教えてくれたので、迷わず直せました。エラーが単なる拒否で終わらずに次の一手を示してくれるのは、地味にありがたかったです。
Sheet1!A1 の記法に直して、値を4つ入れて、合計の数式を置いてみます。
$ ./officecli set book.xlsx "Sheet1!A2" --prop value=100
$ ./officecli set book.xlsx "Sheet1!A3" --prop value=250
$ ./officecli set book.xlsx "Sheet1!A4" --prop value=75
$ ./officecli set book.xlsx "Sheet1!A5" --prop formula="=SUM(A2:A4)"
Updated Sheet1!A5: formula==SUM(A2:A4)
で、この A5 を読み出したときの出力がこれです。
$ ./officecli get book.xlsx "Sheet1!A5" --json
{
"path": "/Sheet1/A5",
"type": "cell",
"text": "425",
"format": {
"formula": "SUM(A2:A4)",
"cachedValue": "425",
"computedValue": "425",
"evaluated": true
}
}
computedValue: "425"、そして evaluated: true。つまり数式を書いた時点で、OfficeCLI が自分で =SUM(A2:A4) を計算して 425 という結果まで持っている。Excel本体もLibreOfficeも入っていないコンテナの中で、です。
openpyxl を使っていた頃、数式を書き込んでも値は「Excelで開くまで空っぽ」で、計算結果をプログラム側から読みたければ別途エンジンを噛ませる必要がありました。あの手間を思い出すと、書いた瞬間に評価済みの値が返ってくるのは素直にすごいなと思います。READMEには350以上の関数に対応と書いてあったので、ここは本気で作り込まれている部分みたいです。
日本語のWordも作れた(スタイル名だけ注意)
最後にWordも試しました。日本語がちゃんと通るのかが気になったので、見出しと本文を日本語で入れてみます。
$ ./officecli create report.docx
$ ./officecli add report.docx / --type paragraph \
--prop text="2026年 第4四半期 実績サマリ" --prop style=Heading1
WARNING: style 'Heading1' not found in styles part — will be referenced as-is
$ ./officecli add report.docx / --type paragraph \
--prop text="売上は前年同期比25%増となりました。"
見出しに style=Heading1 を指定したら「そのスタイルは見つからないけど、そのまま参照しておくね」という警告が出ました。Wordの組み込みスタイルは正確には別の名前なので、素の名前だと拾いきれないようです。とはいえ処理は止まらず、view stats を見るとスタイル配分にはちゃんと Heading1: 1 と出ていました。
$ ./officecli view report.docx text
[/body/p[@paraId=00100000]] 2026年 第4四半期 実績サマリ
[/body/p[@paraId=00100002]] 売上は前年同期比25%増となりました。
$ ./officecli view report.docx stats
Paragraphs: 2 | Words: 4 | Total Characters: 36
Style Distribution:
Heading1: 1
Normal: 1
日本語はまったく問題なく通りました。view text で中身をそのまま吸い出せるので、生成した文書を後段でチェックするのも簡単そうです。
ちなみに、スライドを画像として書き出す view screenshot も試したのですが、こちらは「ヘッドレスブラウザ(Chrome/Chromium等)を入れてくれ」と言われて、今回のスリムなコンテナには入れていなかったので断念しました。HTMLレンダリング経由で画像化する仕組みらしく、テキスト操作だけならブラウザは要らないけれど、見た目のレンダリングまでやるなら別途必要、という切り分けのようです。
触ってみての所感
短い時間でしたが、Word・Excel・PowerPointの3つとも「作る・書く・読む」を一通り動かせました。学んだことを自分用にまとめておきます。
- 単一バイナリでもスリム系Linuxでは
libicuが要る。エラーになったら素直に入れる - 要素はXPath風のパスで扱う。
shape[1]はタイトルも数に入るので、狙うなら@id指定が安全 - Excelの数式は書いた時点で評価済み。Excel本体なしで計算結果まで取れるのが一番のインパクト
- セル指定はREADMEの
$Sheet:A1ではなくSheet1!A1を使う(エラーが正しい書き方を教えてくれる) - 日本語はWord本文・見出しとも問題なし。ただし組み込みスタイル名は正確に
正直、最初は「AIエージェント向け」という売り文句を見て身構えたのですが、中身はAPIキーも何も要らない、純粋に決定的なファイル操作CLIでした。バッチでレポートを量産したり、CIの中でテスト結果からドキュメントを起こしたり、といった使い方なら、AIとは関係なく普通に便利そうです。僕はまず、いつも手作業で作っている定例の報告スライドの雛形をこれで組めないか、次に試してみたいと思っています。
もっと良い使い方があったら教えてください。