AIが作ったHTMLをGoogle Workspaceで社内限定公開する方法(スキル付き!)

AIが作ったHTMLをGoogle Workspaceで社内限定公開する方法(スキル付き!)

Claude Code で作成した HTML レポートを社内限定で配信する仕組みを、Google Apps Script と clasp を使って構築し、Agent Skill 化してみました。レポート作成から公開まで、AI に「社内限定で公開して」と言うだけで完結する方法について紹介します。
2026.09.24

はじめに

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回で済ませられるようになりました。社内向けのスライドやダッシュボードの配信にも同じ方法が使えます。

参考リンク

Google 公式


Claudeならクラスメソッドにお任せください

クラスメソッドは、Anthropic社とリセラー契約を締結しています。各種製品ガイドから、業種別の活用法、フェーズごとのお悩み解決などサービス支援ページにまとめております。まずはご覧いただき、お気軽にご相談ください。

サービス詳細を見る

この記事をシェアする

AI白書

関連記事