
AIが作ったHTMLをGoogle Workspaceで社内限定公開する方法(スキル付き!)
はじめに
Claude Code で調査結果や報告資料を図表つきの HTML レポートにまとめる機会が増えてきました。次に躓くのが共有。Slackに貼ったりするのめんどくさいですよね。
claude.ai の Artifact は手軽ですが、共有リンクの公開範囲を都度気にする必要があります。そもそも個人プランだと全世界に共有されてしまいます。かといって S3 + CloudFront などで配信基盤を組むのは、レポート1枚のためには大げさです。
そこで、Google Apps Script(GAS)のウェブアプリ機能で HTML レポートを社内限定で配信できるようにして、一連の流れを Claude Code の Agent Skill にしてみました。一度作れば Claude Code に「HTMLでまとめて」「社内限定で公開して」と言うだけで、レポート作成から社内限定URLの発行・更新まで済んじゃいます。
この記事は、仕組みの概要を「サマリ」に、設定やコードを「詳細」に分けて書いています。使い方だけ知りたい方はサマリだけ読めば十分です。
サマリ
スキル一式は GitHub で公開しています。CloneしてClaude Codeなどでご自身の環境に合わせてご利用ください。
何ができるようになったか
AI(Claude Code)に次のように頼むだけで、図表入りのレポートが作られ、社内の人だけが見られるURLが発行されます。
この調査結果、HTMLレポートにまとめて社内限定で公開して
受け取ったURLを Slack やメールで共有すれば、社内の人はリンクを開くだけでレポートを読めます。
以前と比べて何が変わったか
| これまで | これから | |
|---|---|---|
| レポートの作成 | AIに作らせたファイルを自分で保存する | AIに頼むだけ |
| 共有の方法 | ファイルを Slack に添付する、または公開範囲を確認しながら共有リンクを作る | AIが返すURLを貼るだけ |
| 見られる人 | 共有先の設定次第 | 会社の Google アカウントでログインしている人だけ |
| 内容を直したとき | ファイルを添付し直す | 同じURLのまま中身が新しくなる |
| 費用 | - | 追加費用なし(会社で使っている Google Workspace の機能だけで動く) |
なぜ社外に漏れないのか
レポートは、会社で使っている Google Workspace の中に置かれます。URLを開いた人が会社の Google アカウントでログインしていなければ、Google がその時点で表示を拒否します。URLが社外に転送されても、社外の人には中身が表示されません。
「この3人だけに見せたい」といった場合に備えて、見られる人をメールアドレスで指定する仕組みも入れています。ただしこの場合は、閲覧者が最初に Google の確認画面で許可を押す手間が増えます。
使うときの注意点
ブラウザで個人の Google アカウントと会社のアカウントの両方にログインしていると、個人アカウントでページが開かれて「権限がありません」と表示されることがあります。リンクを共有するときは「会社アカウントで開いてください」と一言添えています。
詳細
全体の流れ
登場するのは、Claude Code、デプロイスクリプト(deploy.sh)、Google の3つです。誰が何をするかで分けると、流れはこうなります。
[自分] 「HTMLでまとめて社内限定で公開して」
│
▼
[Claude Code] ① HTMLレポートを1ファイルで作る(report.html)
│ ② 公開の希望を確認し、deploy.sh を実行する
▼
[deploy.sh] ③ slug ごとの Apps Script プロジェクトに HTML を配置する
│ ④ clasp で push し、デプロイする
│ (2回目以降は既存デプロイを更新するので URL は変わらない)
▼
[Google] ⑤ https://script.google.com/a/macros/{ドメイン}/s/{デプロイID}/exec で配信する
│ ⑥ 社内ドメイン以外の Google アカウントはここで拒否する
▼
[社内の閲覧者] リンクを開くだけで見られる(承認画面なし)
Skill は「レポート作成」と「社内限定URL公開」の2段構成で、公開はユーザーが希望した場合だけ実行します。
状態はすべてホームディレクトリ配下に置いています。
| パス | 内容 |
|---|---|
~/.gas-reports/<slug>/ |
slug ごとの Apps Script プロジェクト(.clasp.json / .deployment / src/) |
~/.gas-reports/registry.tsv |
slug → 公開URL の台帳(slug / URL / 最終更新日 / タイトル) |
slug(weekly-report のような英小文字の識別子)は公開URLと1対1で対応しています。「前に公開したレポートを更新して」と頼まれたときは、台帳から slug を引けば同じURLのまま差し替えられます。
なぜ Google Apps Script なのか
社内共有の要件は次の4つでした。
- 社内(Google Workspace の自ドメイン)限定で閲覧できること
- 閲覧者はリンクを開くだけでよいこと(OAuth の承認画面を出さない)
- 内容を更新してもURLが変わらないこと(配布済みリンクを配り直さない)
- 追加のインフラ・ランニングコストがないこと
候補を比較するとこうでした。
| 手段 | ドメイン限定 | 閲覧の手軽さ | URL固定更新 | 追加インフラ |
|---|---|---|---|---|
| claude.ai Artifact | △(共有設定次第) | ◎ | ◎ | 不要 |
| S3 + CloudFront + 認証 | ○ | △(認証実装次第) | ◎ | 必要 |
| 社内 Wiki に貼る | ○ | ○ | ○ | 不要(ただしHTMLの表現力が失われる) |
| GAS ウェブアプリ | ◎(Google側で遮断) | ◎(リンクを開くだけ) | ◎(デプロイ更新) | 不要 |
GAS ウェブアプリは、マニフェスト(appsscript.json)の webapp.access を DOMAIN にするだけで、自ドメインの Google アカウント以外は Google 側で遮断されます。アプリのコードに認証処理を書く必要はありません。
アクセス制御は2段階
アクセス制御は「マニフェストでのドメイン制限」と「コードでのユーザー絞り込み」の2段階にしました。
1段目はマニフェストです。
{
"timeZone": "Asia/Tokyo",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"webapp": {
"access": "DOMAIN",
"executeAs": "USER_DEPLOYING"
}
}
2段目は Code.gs の ALLOWED_USERS です。空配列ならドメイン内の全員に公開、メールアドレスを列挙すればその人たちだけに絞れます(以下は読みやすさのため簡略化した抜粋です)。
// 空配列 = ドメイン内の全員に公開。絞る場合はメールアドレスを列挙する。
const ALLOWED_USERS = [];
function doGet() {
if (!isAllowed_()) {
return HtmlService.createHtmlOutput(
'<p>このページの閲覧権限がありません。</p>'
).setTitle('アクセス権限がありません');
}
return HtmlService.createHtmlOutputFromFile('index')
.setTitle('__REPORT_TITLE__')
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
function isAllowed_() {
// 絞り込みなしのときは Session を参照しない。参照すると閲覧者全員に
// OAuth 承認画面が出てしまうため。
if (ALLOWED_USERS.length === 0) return true;
return ALLOWED_USERS.indexOf(Session.getActiveUser().getEmail()) !== -1;
}
一番こだわったのは、ALLOWED_USERS が空のあいだは Session.getActiveUser() を呼ばないことです。
Session.getActiveUser() を呼ぶと userinfo.email の OAuth スコープが要求され、閲覧者全員に「未検証アプリ」の警告つき承認画面が表示されるようになります。レポートのリンクを開いただけで承認画面が出てくると、閲覧者は何を許可するのか分からないまま判断を求められます。
社内全員に見せてよいレポートでは Session を呼ばないので、閲覧者はリンクを開くだけで済みます。特定メンバーに絞りたいときだけ ALLOWED_USERS を埋めて、承認画面が出ることを受け入れます。
なお、現状の deploy.sh は実行のたびに Code.gs をテンプレートから作り直すため、ALLOWED_USERS を書き足しても次のデプロイで空に戻ります。当面は、生成された Code.gs を直接編集して clasp で反映する手順を SKILL.md に書いています。絞り込みを日常的に使うなら、deploy.sh から値を渡せるように改修が必要です。
URLが変わらない更新デプロイ
GAS ウェブアプリのURLにはデプロイIDが含まれています。
https://script.google.com/a/macros/{ドメイン}/s/{デプロイID}/exec
clasp create-deployment で毎回新しいデプロイを作ると、更新のたびにURLが変わり、配布済みのリンクの先には古い内容が残り続けます。これを避けるには、既存デプロイを clasp update-deployment(create-deployment -i <id> と同じ)で更新します。本記事は @google/clasp 3.3.0 で動作確認しています。
deploy.sh では、初回デプロイ時にIDを .deployment ファイルへ保存し、2回目以降はそのIDで更新するようにしました(エラー処理を省いた抜粋です)。
"$CLASP" push --force
if [ -f "$PROJ_DIR/.deployment" ]; then
DEPLOYMENT_ID="$(tr -d '[:space:]' < "$PROJ_DIR/.deployment")"
echo "→ 既存デプロイを更新しています(URL は変わりません): $DEPLOYMENT_ID"
"$CLASP" update-deployment "$DEPLOYMENT_ID" --description "$(date '+%Y-%m-%d') 更新"
else
echo "→ 初回デプロイを作成しています..."
OUT="$("$CLASP" create-deployment --description "$TITLE" 2>&1)"
DEPLOYMENT_ID="$(printf '%s' "$OUT" | grep -oE 'AKfyc[A-Za-z0-9_-]+' | head -1)"
printf '%s\n' "$DEPLOYMENT_ID" > "$PROJ_DIR/.deployment"
fi
これで、同じ slug で再実行すれば同じURLのまま中身だけが差し替わります。週次レポートのように繰り返し更新する用途では、この性質が重要です。
もうひとつ、<!DOCTYPE> を含まない HTML(claude.ai の Artifact 形式など)を受け取ったときは、外側の <html> と一緒に <base target="_top"> を補っています。HTML Service は配信するページをサンドボックスの iframe 内に描画するため、これがないとレポート内のリンクが iframe の中で開いてしまいます。
Claude Code Skill にする
最後に、ここまでの仕組みを Claude Code の Agent Skill にします。公開しているリポジトリの構成は次のとおりで、~/.claude/skills/html-report/ に clone して使います。
~/.claude/skills/html-report/
├── SKILL.md # スキル本体(手順・設計原則・トラブルシューティング)
├── scripts/
│ └── deploy.sh # 公開・更新スクリプト
├── templates/
│ ├── Code.gs # doGet / ALLOWED_USERS のテンプレート
│ └── appsscript.json
├── package.json # clasp を devDependency として同梱
├── pnpm-lock.yaml
├── README.md
├── LICENSE
└── .gitignore
clasp 本体は pnpm install で node_modules/ に入ります(リポジトリには含めていません)。
SKILL.md の frontmatter はこうなっています(読みやすさのため整形し、トリガーの一部を省略した抜粋です)。
---
name: html-report
description: 図・表を活用した自己完結HTMLレポートを作成し、希望があれば社内限定URL
(Google Apps Script ウェブアプリ・自社の Google Workspace ドメイン限定)として公開する。
同じレポートを更新しても公開URLは変わらない。Triggers: "HTMLでまとめて",
"HTMLレポートにして", "社内URLで共有", "社内限定で公開", "/html-report"
---
description に具体的な依頼の言い回しを書いておくと、/html-report と明示的に呼ばなくても、会話の中の「社内限定で公開して」のような依頼から Claude がこのスキルを選びます。
公開URLに入るドメインは、clasp でログインしているアカウントのメールアドレスから deploy.sh が自動で取得します。別のドメインを使う場合は環境変数 GAS_DOMAIN で指定できます。
clasp はグローバルインストールせず、スキルディレクトリの node_modules に入れました。スキル一式をコピーして pnpm install を1回実行すれば動きます(初回の clasp login だけはブラウザでの承認が必要です)。
まとめ
GAS ウェブアプリの webapp.access: DOMAIN を使うと、認証コードも追加インフラもなしで社内限定のHTML配信ができます。clasp update-deployment で既存デプロイを更新すれば公開URLも固定できるので、繰り返し更新するレポートの配布先として十分使えます。閲覧者に承認画面を出さないためには、ALLOWED_USERS が空のあいだは Session を呼ばないことだけ守ってください。
これを Claude Code の Agent Skill にしたことで、レポートの作成から配信までを依頼1回で済ませられるようになりました。社内向けのスライドやダッシュボードの配信にも同じ方法が使えます。









