Javis OS
Javis OS는 기업가를 돕는 에이전시 AI로, 업무를 지원하고 함께 성장합니다
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
社内向けの Web チャットアプリ。ログインした社員が Claude API と会話しながら週間の行動計画を作る。 会話ログを記録(後の改善・人事評価用)、API キーはサーバ側のみ。 ローカル起動 1. npm install 2. cp .env.example .env.local して ANTHROPIC_API_KEY と ACCESS_CODE を設定 3. npm run dev → http://localhost:3000 Vercel デプロイ 1. このリポジトリを Vercel で Import 2. 環境変数を設定: ANTHROPIC_API_KEY / ACCESS_CODE /(任意)AGENT_MODEL 3. Deploy → 発行 URL を社内共有README 전문 보기 ↗
社内向けの Web チャットアプリ。ログインした社員が Claude API と会話しながら週間の行動計画を作る。 会話ログを記録(後の改善・人事評価用)、API キーはサーバ側のみ。
スプレッドシート「2026小中等部問合せ管理」(校舎ごとのタブ)を置き換える台帳アプリ。
URL は /inquiry-board。会議DXのメニューには載せず、URL を直接共有して使う
(ログインは会議DXと共通。ログアウト状態で開くとログイン画面を経て戻ってくる)。
問合せ管理の「AIに聞く」(問い合わせQA)と会議DXの目標管理は、この台帳のデータを参照する(下記「問い合わせQA」)。
INQUIRY_DB_ID に入れる
(閲覧できる人をそのファイルの共有設定だけで絞れる)INQUIRY_BOARD_ID に旧シートの ID が入っている状態で、
GAS エディタから previewLegacyDiff()(確認のみ)→ applyLegacyDiff()(反映)の順に実行する。
importLegacyInquiryBoard() は applyLegacyDiff() と同じ。初回の一括取り込みも、並行運用中の差分取り込みも同じ手順
LEGACY_START_YEAR(期の開始年)で補う。
「5/21.6/29」のような複数日は文字のまま移し、画面では文字入力として出る(消さない)/inquiry-board を小中等部の担当者に共有するAPPS_SCRIPT_URL が無いローカル開発では .data/inquiry-board.json に保存される(git には入らない)。
Vercel 本番で未設定なら画面に「未設定」と出る。
WordPress の問い合わせフォームの送信先(Webhook)にこのアプリを指定すると、メールを介さず台帳に1件登録される。
POST https://<このアプリ>/api/inquiry-board/intake(app/api/inquiry-board/intake/route.ts)INQUIRY_INTAKE_TOKEN に合言葉を入れ、WordPress 側から
ヘッダ X-Intake-Token: <合言葉>(ヘッダを付けられないプラグインなら URL に ?token=<合言葉>)で送る。
未設定のあいだ受け口は閉じている(503)FIELD_ALIASES。通知メールと同じ項目名(お子様名・ふりがな・保護者名・
保護者名ふりがな・郵便番号・住所・電話番号・メールアドレス・学校名・学年・希望コース・受講校舎・相談事項・
お問い合わせ内容)と、英語名(student_name / student_kana / guardian_name / guardian_kana / postal / address /
phone / email / school / grade / course / campus / consult / message / submission_id)のどちらでも受け付ける。
JSON・form-urlencoded・multipart のいずれでもよいWordPress 側の設定(Contact Form 7 の場合。フォームごとに設定する):
https://<このアプリ>/api/inquiry-board/intake?token=<合言葉>&form=<フォーム名> を入れる
(このプラグインはヘッダを付けられないため URL に付ける)。form= はフォームごとに変える(例 &form=資料請求
&form=テスト対策)。備考の1行に「HPフォーム:資料請求」のように出て、どのフォームから来たか分かる[hidden your-campus "日宇校"] [hidden your-course "中学生向け定期テスト対策"] のような hidden タグを足す
(複数校舎で共用するフォームなら [select* your-campus "日野校" "駅前校" "大野校" "日宇校" "県中" "オンライン"] を置く)。
無ければ「その他」に入り、備考に【校舎不明】が付くFIELD_ALIASES にその名前を足して push する/inquiry-board に出ることを確認。Vercel のログに [INQUIRY_INTAKE] が残る{ ok:false, reason } を返す。
CF7 to Webhook は 200 番台以外が返るとフォームの送信結果を「送信に失敗しました」に変えてしまうため。
失敗は Vercel のログに [INQUIRY_INTAKE_ERROR] で残るので、台帳に出ないときはそこを見る
(reason: invalid_token なら URL の token と Vercel の INQUIRY_INTAKE_TOKEN が一致していない)取り込みの決まり(lib/inquiryIntake.ts の decideIntake):
小中等部の問合せ管理を読み、各校舎の状況を AI が答える(問合せ管理アプリの「AIに聞く」・/inquiry-board/qa。テスト中)。
旧 URL の /inquiry-qa は /inquiry-board/qa へ転送する。
職員からのアプリへの意見・不具合の報告(ヘルプデスクの「ご意見・不具合の報告」)とは別物。
読む先は 問合せ管理アプリの台帳「問合せ台帳」を優先し、台帳が空のあいだは旧スプレッドシート (INQUIRY_BOARD_ID)を校舎タブごとに読む(apps_script/Code.gs の listInquiryBoard_)。 どちらから読んでも同じ形(校舎・No.・日付・…・結果・入塾日・備考)で返り、個人情報は同じように落とす。
INQUIRY_BOARD_ID に問合せ管理スプレッドシートの ID を入れる
(URL の /d/ と /edit の間)。空のままだと画面に「未設定」と出る。当月カードに「実績/目標」を重ねる。目標は中等部会議議事録の**タブ「秋~冬行動計画」**から読む。
タブは名前で引くこと。位置や順番で探してはいけない。 旧タブに 「このシート内容を『秋~冬行動計画』に移行しました。こちらのシートは削除します。(小山)」 と注記があり、タブの並びが変わる前提のため。
実績の出所は指標ごとに変える:
| 指標 | 実績 | 理由 |
|---|---|---|
| 体験授業 | 問合せ管理から自動計算(体験日の月) | 体験日の列がある |
| 今月入会 | 行動計画の手入力値 | 問合せ管理に入塾日の列が無い(下記) |
| サイトク・模試 等 | 行動計画の手入力値 | 問合せ管理に該当データが無い |
「今月入会」を自動計算してはいけない(当面)。 旧シートの日付列は問い合わせ日であって入塾日ではない。「結果=入塾」を問い合わせ日の月で 数えると、行動計画の「今月入会」とは別物になる。実データでは入塾42件の問い合わせ日が 5月7件・6月13件・7月15件に集中し、9月に問い合わせた行の入塾は0件だった (9月に入会した人の行は5〜7月にある)。備考に「6/2入会」とある行は42件中1件だけで抽出もできない。 → 問合せ管理アプリには「入塾日」列を設けた。AI には「入塾日から数えた入会(参考)」として 入塾日が記入された分だけの件数と、入塾日が未記入の入塾行の数を渡している(lib/inquiryBoard.ts の enrollmentsInMonth)。旧シートから移した行は入塾日が空なので、担当者が埋め終わってから lib/goals.ts の AUTO_METRICS に「入会」を足して自動計算へ切り替える。
体験は逆に、問い合わせ日ではなく体験日の月で数える。 8月に問い合わせて9月に体験した人を8月側に入れると行動計画とずれるため(trialsInMonth)。
画面では指標名にカーソルを合わせると出所が出る。AIも回答時に出所を明示する。 数字の出所が分からないまま判断されるのを防ぐため。
※「中等部」(4校舎合計)の行は校舎カードが無いため画面には出ない。チャットでは答えられる。
Claude は音声を直接読めないため、文字起こしだけ Google AI Studio(Gemini API) に任せ、 その結果を Claude に渡して議事録化している。
GEMINI_API_KEY(必須)/
GEMINI_API_KEY_2〜_5(任意・予備キー)/GEMINI_RPM(任意・既定 5)/
GEMINI_MODEL(任意・既定 gemini-3.6-flash)/GEMINI_API_URL(任意・通常は不要)GEMINI_RPM)— 無料枠で詰まらせないために無料枠を止めるのは1日の総量ではなく 1分あたりの回数(RPM) のほう。
gemini-3.6-flash の無料枠は 5 RPM しかない。1区間はおよそ10秒で終わるので、
間隔をあけずに続けて送ると 6回/分 になり、必ず 429(TooManyRequests)で弾かれる。
そこで画面は GEMINI_RPM(キー1本あたり・既定 5)から送信間隔を決め、
はじめから上限を超えない速さで送る。断られてから待ち直すより速く、確実に終わる。
RPM は Google のプロジェクト単位なので、別アカウント(別プロジェクト)のキーを足すと
全体の上限は本数ぶん増える。画面は「いちばん早く空くキー」を選んで順番に使い、
どのキーを使うかを keyNo でサーバへ渡す。
| APIキー | 1分あたり | 1時間の会議(40区間)の目安 |
|---|---|---|
| 1本 | 5回 | 約9分 |
| 2本(別アカウント) | 10回 | 約5分 |
7 / 5 のように出る)GEMINI_RPM を上げてはいけない★どのキーを使うかはサーバではなく画面が決めている。 サーバの関数は呼び出しごとに別の実体で動くことがあり、そのたびに「前に通ったキー」の記憶が 消えて1本目に偏る。1つのタブが順番に送る画面側で決めることで、本数ぶんを均等に使える。
無料枠のモデルは混雑すると 503(The model is overloaded) を返す。これは利用枠の問題ではないので、 待っても同じモデルが空くまで直らない。
そこで 503(または存在しないモデル名の 404)が返ったときは、同じ音声を別のモデルで送り直す。
GEMINI_MODEL_FALLBACK(カンマ区切り)。空のままでよい★503 はモデル側の混雑なので、キーを替えても直らない(キーの切り替えは枠切れ・制限・無効のときだけ)。
GEMINI_API_KEY_2〜_5 を入れておくと、1本目が 1日の利用枠を使い切った/短時間の送りすぎで
断られた/キーが無効 のときに、その区間をそのまま次のキーで送り直す。利用者側の操作は要らない。
GEMINI_API_KEY にカンマ区切りで複数まとめて書く形でもよい★無料枠は Google のプロジェクト単位なので、予備キーは別の Google アカウントで発行すること。
同じアカウントで2本目を作っても、同じ枠を2本で分け合うだけで総量は増えない。
Google AI Studio の「使用量」画面でキーごとの内訳が見えるが、同じプロジェクトに並んでいるキーは
枠を共有しているので、切り替えても 429 は減らない。その場合は上の GEMINI_RPM で間隔をあけるか、
課金を有効にして上限そのものを上げる。
Gemini が受け付ける音声は WAV / MP3 / AIFF / AAC / OGG / FLAC で、
ブラウザ録音の webm は受け付けない。そのため録音・添付ファイルのどちらも、
送信前に lib/audioChunk.ts で 16kHz モノラルの WAV に変換している。
あわせて、長い会議は90秒ごとの区間に分けて順番に送る(1区間およそ2.9MB)。
WAV は圧縮しないので通信量は増える(1時間の会議でおよそ110MB=2.9MB×40回)。
その場で録音する場合は会議中に少しずつ送るので気になりにくいが、
長い録音ファイルを後から添付する場合は回線に余裕のある場所で行うこと。
サーバ関数の実行時間・リクエストサイズの上限に収めるための分割なので、
区間の長さを変えるときは lib/audioChunk.ts の SEGMENT_SECONDS を直し、
app/api/dept-minutes/transcribe/route.ts の実行時間上限に収まるか必ず確認すること
(録音の区切りは components/DeptMinutesUI.tsx で SEGMENT_SECONDS に合わせてある)。
10/31(土)全体研修会の事前アンケート。総合メニューの独立アプリで、全部門が利用できる。 質問は2つだけの自由記述で、この1年で「仕事」「人生・プライベート」それぞれで達成したいこと・やってみたいことを、 1行に1つずつ、いくつでも書く。
/kenshu を職員に案内する困ったときに開く画面をまとめた、総合メニューのアプリ(URL は /helpdesk)。全部門が利用できる(どれも個人情報を含まないため)。
上のタブで「要項QA」「マニュアル」「ご意見・不具合の報告」を切り替える。要項QAとご意見・不具合の報告は、v0.16 までは会議DXの左メニューにあった。
| タブ | URL | 中身 |
|---|---|---|
| 要項QA | /helpdesk | 下の「要項QA」の節 |
| マニュアル | /helpdesk/manual | 操作マニュアル(PDF)と動画マニュアルのダウンロード・再生。並べる資料は lib/manuals.ts、ファイルは public/manual/ |
| ご意見・不具合の報告 | /helpdesk/feedback | アプリへの意見・不具合の報告と、管理部門の回答(components/InquiryUI.tsx、API は app/api/inquiry/route.ts) |
/yoko-qa は /helpdesk へ、/inquiry は /helpdesk/feedback へ転送する(API の URL は変えていない)講習会・イベントの実施要項について、申込方法・申込期限・受講料・支払い方法・日程などを
AI が答える(ヘルプデスクの「要項QA」・/helpdesk)。全部門が利用できる(個人情報を含まないため)。
要項ドキュメントには編集中の案が混ざっている(「原案)」「(案)」、小等部夏期は1案・第2案・ 3案が並立)。案ごとに受講料や期限が違うため、そのまま読ませると矛盾した回答になる。 保護者に誤った金額を伝える事故につながるので、担当者が確認したものだけを git に置いて読む。
documents.get の JSON
(Google Docs コネクタの read_doc でも取れる)を node scripts/export-yoko-doc.mjs <JSON> <出力先>
に渡すと、タブごとの Markdown になる(--list でタブ名・講座名・ステータスの一覧だけ出す)。
テンプレート本体のタブと、複製時に残った冒頭の「使い方」は自動で落とす。node scripts/import-yoko.mjs <書き出したファイル> を実行
# 見出し(ドキュメントのタブ見出し)を区切りとして1件1ファイルに割るstatus: 下書きstatus: 確定 に変え、owner に名前を入れるstatus: 確定 以外は AI に本文を渡さない。画面の「未確定の要項」に名前だけ出るので、
聞かれた場合 AI は「まだ確定していないため回答できません」と答える。
要項は Google ドキュメントのテンプレートで書く。有料講座と無料講座で2種類あり、 Google ドライブの「14_教務運営 > 04要項」フォルダに置いている(担当者への共有用)。 https://drive.google.com/drive/folders/1IwwmmjAqh7yznmtOgFSkTDZrNS3_x-L5
| 種類 | Google ドキュメント | リポジトリ側の控え |
|---|---|---|
| 有料講座 | 「有料講座_要項テンプレート(記入用)」 https://docs.google.com/document/d/1ZoGnYxV424TelGRnZuAEmCgVIDt5nKXH1d7-OAYc5wc/edit | knowledge/40_要項/_TEMPLATE_有料講座.md |
| 無料講座(無料体験・説明会・セミナー・無料模試など) | 「無料講座_要項テンプレート(記入用)」 https://docs.google.com/document/d/1MoDgNhn0i5kUV0D_qaUBuf5GevbX5AG_QTxGCVApYL0/edit | knowledge/40_要項/_TEMPLATE_無料講座.md |
テンプレートのタブを複製 → 講座名にリネーム → 上から埋める →
<基本情報>の「ステータス」を「確定」にする。
_ で始まるファイルは要項QAに読み込まれない。取り込み時は、タブ名が _ で始まるものと
「テンプレート」を含むものを飛ばすので、テンプレート本体のタブは取り込まれない。
旧テンプレート(https://docs.google.com/document/d/1zCh_tpiwfJBbru0Df3T5VFLsn4AQ66RDdmzuAW0BJHQ/edit)は 2026-09-16 に上の2種類へ置き換えた。旧テンプレートで書いた要項も取り込みはできる。
新テンプレートで変わった点:
<基本情報>は front matter に転記される(取り込みスクリプトが読む):
| ドキュメントの項目 | front matter |
|---|---|
| 講座名 | title |
| 作成者 | owner |
| ステータス | status(「確定」だけがAIに渡る) |
| 作成日 | updated |
| 部門 | dept |
書くときの原則(テンプレート内にも記載):
#)を使わない。取り込み時に別の要項として切り出されてしまう取り込み時は次を自動で行う:
status: を「確定」と完全一致にし、updated を直した日にする。確定・金額訂正・経理記入完了を経理へ自動で知らせる。実体は apps_script/YokoNotify.gs。
要項テンプレートの Google ドキュメント(04要項 フォルダの2つ)に、コンテナバインドの Apps Script として貼る。
| 出来事 | 検知のしかた | 通知 |
|---|---|---|
| 確定 | ステータスが「確定」になった | 経理へ「経理記入欄の記入をお願いします」+担当者記入の経理連絡事項 |
| 金額訂正 | 確定済みのまま、<受講料>・<申込>の支払い方法・<経理連絡事項>の担当者記入が変わった | 経理へ【至急】。変わった行を「− 前/+ 後」で示し、更新履歴を引用 |
| 経理記入完了 | 経理記入欄の「経理記入日」が入った | 講座担当へ「経理側の記入が終わった。取り込みを依頼してください」 |
| 確定取り下げ | 確定 → 下書き に戻った | 経理へ「請求処理を止めてください」 |
| リマインド | 経理記入待ちのまま 20 時間(REMIND_HOURS)経過 | 経理へ再通知(走査のたび) |
経理が経理記入欄を書いても「金額訂正」にはならない(経理記入欄より前だけを比べる)。
動かし方:
dailyScan が 04要項 フォルダの全ドキュメント・全タブを走査する。
setupDailyTrigger を1回実行すると毎日 9〜10 時のトリガーができる。どちらか片方のドキュメントにだけ設定する。removeDailyTrigger を実行する。セットアップ(初回のみ):
bot を付け、Client ID / Client Secret / Service Account / Private Key を控える。YokoNotify.gs を貼り、
「プロジェクトの設定 > スクリプト プロパティ」に LW_CLIENT_ID LW_CLIENT_SECRET LW_SERVICE_ACCOUNT
LW_PRIVATE_KEY LW_BOT_ID LW_CHANNEL_ID を入れる(両方のドキュメントに同じ値)。
LW_PRIVATE_KEY は Developer Console からダウンロードした private_xxxx.key の中身
(-----BEGIN PRIVATE KEY----- で始まる)をそのまま貼る。入力欄で改行が消えて1行になっても、
改行を文字の \n で書いても、スクリプト側で整形するので動く。
エラーが出るときは Apps Script エディタで checkPrivateKey を実行し、実行ログの診断を見る。seedLedger を実行する。台帳に現在の状態が記録されるだけで通知は出ない。
これを飛ばすと、次の走査で既に「確定」の要項がすべて「確定」として通知される。setupDailyTrigger を実行する。純粋関数(解析・差分・出来事判定・通知文)は node scripts/test-yoko-notify.mjs で確認できる。
node scripts/check-yoko.mjs # 全件
node scripts/check-yoko.mjs --confirmed # 確定のものだけ(不足があれば exit 1)
必須項目(対象・日程・受講料・申込開始日・申込期限・申込方法・支払い方法・連絡先・ 経理連絡事項(経理記入欄))が書かれているかを見る。あわせて「記入例」「(例:…)」の消し忘れと、 確定済みなのに経理記入欄の「経理記入日」が空のものを指摘する。 要項QAは書かれていることしか答えられないため、確定にする前に通す。
outputFileTracingIncludes も直すこと。
Next はコードから辿れないファイルをサーバ関数に含めないため、変え忘れると本番だけ 0 件になる。status を戻して外すこと。現行の「塾講師適性検査」(Zinzycle社の受託システム。100問・はい/いいえ・A/B/C判定)を移したもの。 講師(学生パート講師)だけでなく、事務スタッフ・社員も判定できる。設計の詳細は docs/superpowers/specs/2026-10-08-aptitude-test-design.md。
/aptitude(メニューの「適性検査」カード)/受検者側 /exam/<トークン>(ログイン不要)npm test)/aptitude/master)