@clubs.place/cli 0.0.0-stage → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/.env.example +88 -0
  2. package/LICENSE +21 -0
  3. package/PROVENANCE.json +77 -0
  4. package/README.md +70 -2
  5. package/bin/clubs-nippo +7 -0
  6. package/bin/clubs-nippo-web.mjs +338 -0
  7. package/bin/clubs-nippo.mjs +527 -0
  8. package/docs/events-schema.md +76 -0
  9. package/examples/hermes-events.sample.jsonl +8 -0
  10. package/package.json +48 -4
  11. package/src/aggregate.mjs +105 -0
  12. package/src/autorun.mjs +25 -0
  13. package/src/autorun.test.mjs +43 -0
  14. package/src/cards.mjs +406 -0
  15. package/src/collect.mjs +111 -0
  16. package/src/config.mjs +173 -0
  17. package/src/generators/claude.mjs +31 -0
  18. package/src/generators/cli.mjs +81 -0
  19. package/src/generators/codex.mjs +28 -0
  20. package/src/generators/copilot.mjs +21 -0
  21. package/src/generators/gemini.mjs +20 -0
  22. package/src/generators/openai-compat.mjs +79 -0
  23. package/src/generators/openrouter.mjs +65 -0
  24. package/src/history.mjs +66 -0
  25. package/src/ingest.mjs +78 -0
  26. package/src/lemma.docHash.test.mjs +37 -0
  27. package/src/lemma.mjs +303 -0
  28. package/src/mask.mjs +45 -0
  29. package/src/mcp.mjs +269 -0
  30. package/src/narrative.mjs +191 -0
  31. package/src/prompt.mjs +184 -0
  32. package/src/prompt.test.mjs +146 -0
  33. package/src/remote-config.mjs +72 -0
  34. package/src/render.mjs +467 -0
  35. package/src/send.mjs +36 -0
  36. package/src/settings.mjs +54 -0
  37. package/src/sources/claude-code.mjs +174 -0
  38. package/src/sources/codex-cli.mjs +182 -0
  39. package/src/sources/copilot-cli.mjs +125 -0
  40. package/src/sources/cowork.mjs +193 -0
  41. package/src/sources/events-file.mjs +120 -0
  42. package/src/sources/gemini-cli.mjs +128 -0
  43. package/src/sources/hermes.mjs +427 -0
  44. package/src/store.mjs +227 -0
  45. package/src/tasks.mjs +229 -0
  46. package/src/tool-name.mjs +28 -0
  47. package/templates/report-v3.reference.html +162 -0
  48. package/web/assets/logo-clubs-white.png +0 -0
  49. package/web/assets/logo-clubs-white.svg +7 -0
  50. package/web/assets/logo-clubs-white.svg.tmp +30 -0
  51. package/web/assets/logo-clubs.svg +30 -0
  52. package/web/assets/qr.js +411 -0
  53. package/web/assets/sidebar.js +154 -0
  54. package/web/assets/twemoji.js +34 -0
  55. package/web/cards/achievements.html +230 -0
  56. package/web/cards/catalog.json +217 -0
  57. package/web/email/hero-v3.reference.html +123 -0
  58. package/web/home/index.html +269 -0
  59. package/web/preview.json +301 -0
  60. package/web/qr-selftest.html +46 -0
  61. package/web/reports/edit.html +255 -0
  62. package/web/reports/edit.reference.html +257 -0
  63. package/web/reports/index.html +196 -0
  64. package/web/settings/index.html +219 -0
  65. package/web/share/cards-reference.html +228 -0
  66. package/web/share/passport.html +214 -0
  67. package/web/share/teianbako.html +177 -0
  68. package/web/signup/index.html +326 -0
  69. package/web/tasks/index.html +229 -0
  70. package/web/verify/index.html +248 -0
package/src/store.mjs ADDED
@@ -0,0 +1,227 @@
1
+ // clubs-nippo — AI業務日報 (CLUBS)
2
+ // Copyright (c) 2026 FRAME00 Inc.
3
+ // SPDX-License-Identifier: MIT
4
+ // https://github.com/frame00/clubs-nippo
5
+
6
+ import { readFileSync, writeFileSync, readdirSync, mkdirSync, existsSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { APP_HOME } from "./config.mjs";
9
+
10
+ /**
11
+ * 下書きと承認済み日報の置き場。out/ に日付ごとの JSON を置くだけ。
12
+ * DB は使わない(依存を増やさない)。
13
+ *
14
+ * out/2026-07-13.draft.json … 生成しただけ。未承認
15
+ * out/2026-07-13.approved.json … 承認して Lemma に登録した
16
+ *
17
+ * 集計値(aggregates)は下書きに焼き込む。これが「ロック値」で、
18
+ * 承認時にクライアントから送られてきた数字は一切使わない。
19
+ * 数字を受け取れるようにすると「盛れない」が崩れる。
20
+ */
21
+
22
+ const DIR = join(APP_HOME, "out");
23
+
24
+ const ensureDir = () => mkdirSync(DIR, { recursive: true });
25
+
26
+ const draftPath = (date) => join(DIR, `${date}.draft.json`);
27
+ const approvedPath = (date) => join(DIR, `${date}.approved.json`);
28
+
29
+ const readJson = (path) => {
30
+ try {
31
+ return JSON.parse(readFileSync(path, "utf8"));
32
+ } catch {
33
+ return null;
34
+ }
35
+ };
36
+
37
+ /** 生成した日報を下書きとして保存する。承認するまで登録も送信もしない。 */
38
+ export const saveDraft = ({ activity, narrative }) => {
39
+ ensureDir();
40
+ const draft = {
41
+ id: activity.date,
42
+ date: activity.date,
43
+ agentName: activity.agentName,
44
+ displayName: activity.displayName,
45
+ source: activity.source,
46
+ origin: activity.origin,
47
+ // ロック値。ログ由来で、編集させない。
48
+ aggregates: activity.aggregates,
49
+ // 編集可。文章だけ。
50
+ // handoff は編集画面の契約に合わせて「文字列」で持つ。見出しは編集させないので別に置く。
51
+ workBlocks: narrative.workBlocks,
52
+ handoff: narrative.handoff?.text ?? "",
53
+ handoffTitle: narrative.handoff?.title ?? "明日にまわすもの",
54
+ selfNote: narrative.selfNote,
55
+ insights: narrative.insights ?? [],
56
+ generator: narrative.generator,
57
+ savedAt: new Date().toISOString(),
58
+ };
59
+ writeFileSync(draftPath(activity.date), JSON.stringify(draft, null, 2), "utf8");
60
+ return draft;
61
+ };
62
+
63
+ export const loadDraft = (date) => readJson(draftPath(date));
64
+ export const loadApproved = (date) => readJson(approvedPath(date));
65
+
66
+ export const saveApproved = (record) => {
67
+ ensureDir();
68
+ writeFileSync(
69
+ approvedPath(record.date),
70
+ JSON.stringify(record, null, 2),
71
+ "utf8",
72
+ );
73
+ return record;
74
+ };
75
+
76
+ /**
77
+ * 一覧用。承認済みが在ればそれを、無ければ下書きを、その日の状態として返す。
78
+ *
79
+ * status:
80
+ * draft … 生成しただけ。未承認
81
+ * recorded … 承認して Lemma に登録した(オフチェーン)
82
+ * anchored … オンチェーンにも記録された(txHash あり)
83
+ * pending … 承認したが Lemma に登録できていない(鍵未設定・到達不可)
84
+ */
85
+ export const listReports = () => {
86
+ if (!existsSync(DIR)) return [];
87
+
88
+ const dates = new Set();
89
+ for (const name of readdirSync(DIR)) {
90
+ const m = name.match(/^(\d{4}-\d{2}-\d{2})\.(draft|approved)\.json$/);
91
+ if (m) dates.add(m[1]);
92
+ }
93
+
94
+ return [...dates]
95
+ .sort()
96
+ .reverse()
97
+ .map((date) => {
98
+ const approved = loadApproved(date);
99
+ const draft = loadDraft(date);
100
+ const base = approved ?? draft;
101
+ if (!base) return null;
102
+
103
+ const status = !approved
104
+ ? "draft"
105
+ : approved.proof?.status !== "verified"
106
+ ? "pending"
107
+ : approved.proof?.txHash
108
+ ? "anchored"
109
+ : "recorded";
110
+
111
+ const a = base.aggregates ?? {};
112
+ return {
113
+ date,
114
+ w: ["日", "月", "火", "水", "木", "金", "土"][
115
+ new Date(`${date}T00:00:00Z`).getUTCDay()
116
+ ],
117
+ agent: base.displayName ?? base.agentName,
118
+ status,
119
+ activeHours: a.activeHours ?? 0,
120
+ totalCalls: a.totalCalls ?? 0,
121
+ successRate: a.successRate,
122
+ firstAt: a.firstAt ?? null,
123
+ lastAt: a.lastAt ?? null,
124
+ docHash: approved?.proof?.docHash ?? null,
125
+ href:
126
+ status === "draft"
127
+ ? `/reports/edit.html?date=${date}`
128
+ : approved?.proof?.docHash
129
+ ? `/verify/?doc=${approved.proof.docHash}`
130
+ : `/reports/edit.html?date=${date}`,
131
+ };
132
+ })
133
+ .filter(Boolean);
134
+ };
135
+
136
+ /**
137
+ * 今月の累計。連続承認日数・承認数・実働・達成カード枚数。
138
+ *
139
+ * cards は out/cards.json(src/cards.mjs が発行)から数える。
140
+ * 以前はカード未実装のため 0 を決め打ちしていたが、実装された今それは嘘になる。
141
+ */
142
+ export const monthSummary = (reports, today, cards = { achievements: [], titles: [] }) => {
143
+ const month = today.slice(0, 7);
144
+ const inMonth = reports.filter((r) => r.date.startsWith(month));
145
+ const approved = inMonth.filter((r) => r.status !== "draft");
146
+
147
+ // 直近から遡って、承認が途切れるまでの日数。
148
+ let streak = 0;
149
+ for (const r of reports) {
150
+ if (r.status === "draft") break;
151
+ streak += 1;
152
+ }
153
+
154
+ // month は上の inMonth 判定で既に宣言済み。使い回す。
155
+ const cardsThisMonth =
156
+ (cards.achievements ?? []).filter((a) => a.date?.startsWith(month)).length +
157
+ (cards.titles ?? []).filter((t) => t.earnedAt?.startsWith(month)).length;
158
+
159
+ return {
160
+ streak,
161
+ approved: approved.length,
162
+ activeHours: Math.round(approved.reduce((s, r) => s + r.activeHours, 0) * 10) / 10,
163
+ cards: cardsThisMonth,
164
+ };
165
+ };
166
+
167
+ /**
168
+ * パスポート用の累計。日報の積み重ねから出す。
169
+ *
170
+ * 「注目の達成」は無いものを在ることにしない。条件を満たしたときだけ出す。
171
+ */
172
+ export const passport = (reports, displayName, agentName) => {
173
+ const approved = reports.filter((r) => r.status !== "draft");
174
+
175
+ let streak = 0;
176
+ for (const r of reports) {
177
+ if (r.status === "draft") break;
178
+ streak += 1;
179
+ }
180
+
181
+ const rates = approved.map((r) => r.successRate).filter((x) => typeof x === "number");
182
+ const avgSuccess = rates.length
183
+ ? Math.round(rates.reduce((a, b) => a + b, 0) / rates.length)
184
+ : null;
185
+
186
+ const totalHours = Math.round(approved.reduce((s, r) => s + r.activeHours, 0) * 10) / 10;
187
+ const totalCalls = approved.reduce((s, r) => s + r.totalCalls, 0);
188
+
189
+ // 達成は実績から導く。該当が無ければ空(作らない)。
190
+ const achievements = [];
191
+ if (rates.length >= 3 && rates.every((x) => x === 100)) {
192
+ achievements.push({ emoji: "🛡️", name: "無失敗の連続", note: `${rates.length}日連続で失敗ゼロ` });
193
+ }
194
+ if (streak >= 7) {
195
+ achievements.push({ emoji: "🔥", name: "継続", note: `${streak}日連続で承認` });
196
+ }
197
+ if (totalCalls >= 100) {
198
+ achievements.push({ emoji: "🎯", name: "実行の積み重ね", note: `累計 ${totalCalls} 回` });
199
+ }
200
+
201
+ // 「型」はログから導ける事実だけ。性格や地位を発明しない。
202
+ // 該当が無ければ空(無いものを在ることにしない)。
203
+ const types = [];
204
+ const lateDays = approved.filter((r) => (r.lastAt ?? "") >= "22:00").length;
205
+ const earlyDays = approved.filter((r) => (r.firstAt ?? "99:99") <= "08:00").length;
206
+ if (approved.length >= 3 && lateDays / approved.length >= 0.5) {
207
+ types.push({ emoji: "🦉", name: "夜型", why: `${lateDays}/${approved.length} 日は22時以降も稼働` });
208
+ }
209
+ if (approved.length >= 3 && earlyDays / approved.length >= 0.5) {
210
+ types.push({ emoji: "🌅", name: "朝型", why: `${earlyDays}/${approved.length} 日は8時前から稼働` });
211
+ }
212
+ if (avgSuccess === 100 && approved.length >= 3) {
213
+ types.push({ emoji: "🎯", name: "一発職人", why: "平均成功率 100%" });
214
+ }
215
+
216
+ return {
217
+ agentName,
218
+ displayName,
219
+ streak,
220
+ approved: approved.length,
221
+ avgSuccess,
222
+ totalHours,
223
+ totalCalls,
224
+ types,
225
+ achievements,
226
+ };
227
+ };
package/src/tasks.mjs ADDED
@@ -0,0 +1,229 @@
1
+ // clubs-nippo — AI業務日報 (CLUBS)
2
+ // Copyright (c) 2026 FRAME00 Inc.
3
+ // SPDX-License-Identifier: MIT
4
+ // https://github.com/frame00/clubs-nippo
5
+
6
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { createHash } from "node:crypto";
9
+ import { APP_HOME } from "./config.mjs";
10
+
11
+ /**
12
+ * 日報の「申し送り(handoff)」と「気づき・提案(insights)」を、
13
+ * 流さずにコンソールへ蓄積する。日報から派生した**ビュー**であって、
14
+ * 集計(aggregates)ではない。
15
+ *
16
+ * 規律:
17
+ * - 蓄積するのは承認済み本文から抜いた**項目テキスト+メタ**だけ。
18
+ * 作業の中身(コマンド全文・出力・指示文)は持たない。
19
+ * - 件数・採用率・streak は必ずここ(out/ の確定データ)から出す。
20
+ * クライアントから数字は受けない。盛れない。
21
+ * - 提案の「承認」は**決定の記録**でしかない。実行はしない。
22
+ * - ✓(Lemma)は「日報が改ざんされていない」ことの証明であって、
23
+ * タスク/提案の当否を保証しない。
24
+ */
25
+
26
+ const DIR = join(APP_HOME, "out");
27
+ const TASKS_PATH = join(DIR, "tasks.json");
28
+ const PROPS_PATH = join(DIR, "proposals.json");
29
+
30
+ const load = (path) => {
31
+ try {
32
+ const d = JSON.parse(readFileSync(path, "utf8"));
33
+ return Array.isArray(d?.items) ? d : { items: [] };
34
+ } catch {
35
+ return { items: [] };
36
+ }
37
+ };
38
+ const save = (path, data) => {
39
+ mkdirSync(DIR, { recursive: true });
40
+ writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf8");
41
+ };
42
+
43
+ /**
44
+ * 繰越同定のための安定 ID。項目テキストを正規化してハッシュにする。
45
+ * 言い回しが変わると別 item になるが、それは誤マージより安全側(許容)。
46
+ */
47
+ export const fingerprint = (text) =>
48
+ createHash("sha256")
49
+ .update(
50
+ String(text ?? "")
51
+ .normalize("NFKC")
52
+ .toLowerCase()
53
+ .replace(/\s+/g, "") // 空白畳み
54
+ .replace(/[\p{P}\p{S}]/gu, ""), // 記号除去(句読点・約物・記号)
55
+ )
56
+ .digest("hex")
57
+ .slice(0, 16);
58
+
59
+ // 「特にありません」系は項目にしない。
60
+ const isEmptyNote = (s) =>
61
+ /^(特にありません|特に無し|特になし|とくにありません|なし|ありません|無し|none|n\/a)$/i.test(
62
+ String(s ?? "").replace(/\s+/g, "").replace(/[。..]$/u, ""),
63
+ );
64
+
65
+ /**
66
+ * 申し送りテキストを箇条化する。改行と行頭の箇条書き記号で割る。
67
+ * 構造が無ければ全体を1件にする(今回は文単位までは割らない)。
68
+ */
69
+ export const bulletize = (text) => {
70
+ const t = String(text ?? "").trim();
71
+ if (!t || isEmptyNote(t)) return [];
72
+ const lines = t
73
+ .split(/\r?\n/)
74
+ .map((line) => line.replace(/^\s*(?:[-•・●*▪]|\d+[..)、])\s*/u, "").trim())
75
+ .filter((line) => line && !isEmptyNote(line));
76
+ return lines.length ? lines : [t];
77
+ };
78
+
79
+ /** narrative から tasks / proposals の素を取り出す(ID 付与済み)。 */
80
+ export const extract = (narrative, date) => {
81
+ const tasks = bulletize(narrative?.handoff?.text).map((text) => ({
82
+ id: fingerprint(text),
83
+ text,
84
+ sourceDate: date,
85
+ }));
86
+
87
+ const insights = Array.isArray(narrative?.insights) ? narrative.insights : [];
88
+ const proposals = insights
89
+ .map((ins) => ({
90
+ title: String(ins?.title ?? "").trim(),
91
+ desc: String(ins?.desc ?? "").trim(),
92
+ icon: ins?.icon || "💡",
93
+ }))
94
+ .filter((p) => p.title || p.desc)
95
+ .map((p) => ({ id: fingerprint(`${p.title} ${p.desc}`), ...p, sourceDate: date }));
96
+
97
+ return { tasks, proposals };
98
+ };
99
+
100
+ /**
101
+ * 繰越同定してマージする(§2)。
102
+ * 同一 ID が既にあれば新規作成せず、別日で再登場したときだけ carryDays を +1 し
103
+ * status は引き継ぐ(done を open に戻さない・承認済みを未対応に戻さない)。
104
+ */
105
+ const mergeItems = (store, incoming, date, make) => {
106
+ const byId = new Map(store.items.map((i) => [i.id, i]));
107
+ for (const ex of incoming) {
108
+ const cur = byId.get(ex.id);
109
+ if (cur) {
110
+ if (cur.lastSeen !== date) {
111
+ cur.carryDays = (cur.carryDays ?? 1) + 1; // 別日で再登場=繰越
112
+ cur.lastSeen = date;
113
+ cur.history.push({ date, event: "seen" });
114
+ }
115
+ cur.sourceDate = date; // 最後に日報から拾った日
116
+ } else {
117
+ const item = make(ex, date);
118
+ store.items.push(item);
119
+ byId.set(ex.id, item);
120
+ }
121
+ }
122
+ return store;
123
+ };
124
+
125
+ const makeTask = (ex, date) => ({
126
+ id: ex.id,
127
+ text: ex.text,
128
+ status: "open",
129
+ firstSeen: date,
130
+ lastSeen: date,
131
+ carryDays: 1, // 登場した日数。>=2 で「繰越 N日」表示
132
+ sourceDate: date,
133
+ assignee: "PRO",
134
+ history: [{ date, event: "created" }],
135
+ });
136
+
137
+ const makeProposal = (ex, date) => ({
138
+ id: ex.id,
139
+ icon: ex.icon,
140
+ title: ex.title,
141
+ desc: ex.desc,
142
+ status: "open",
143
+ firstSeen: date,
144
+ lastSeen: date,
145
+ carryDays: 1,
146
+ sourceDate: date,
147
+ decidedBy: null,
148
+ decidedAt: null,
149
+ history: [{ date, event: "created" }],
150
+ });
151
+
152
+ /**
153
+ * 日報記録時に呼ぶ。handoff→tasks / insights→proposals を同定マージして保存する。
154
+ * 冪等: 同じ日に二度記録しても carryDays は増えない(lastSeen で判定)。
155
+ */
156
+ export const accumulateFromNarrative = (narrative, date) => {
157
+ const { tasks, proposals } = extract(narrative, date);
158
+ const t = mergeItems(load(TASKS_PATH), tasks, date, makeTask);
159
+ save(TASKS_PATH, t);
160
+ const p = mergeItems(load(PROPS_PATH), proposals, date, makeProposal);
161
+ save(PROPS_PATH, p);
162
+ return { tasks: t.items.length, proposals: p.items.length };
163
+ };
164
+
165
+ /**
166
+ * 提案の集計。シェア素材が使う。**必ずここで出す**(クライアント計算にしない)。
167
+ * total … 累計提案数
168
+ * approved … 承認(採用)数
169
+ * approvalRate … approved / total(%・四捨五入)
170
+ * streakDays … 直近から遡って、決定が記録された連続日数
171
+ */
172
+ export const proposalStats = (items) => {
173
+ const total = items.length;
174
+ const approved = items.filter((p) => p.status === "approved").length;
175
+ const approvalRate = total ? Math.round((approved / total) * 100) : 0;
176
+
177
+ const days = [
178
+ ...new Set(items.filter((p) => p.decidedAt).map((p) => p.decidedAt.slice(0, 10))),
179
+ ].sort().reverse();
180
+ let streakDays = 0;
181
+ for (let i = 0; i < days.length; i++) {
182
+ if (i === 0) {
183
+ streakDays = 1;
184
+ continue;
185
+ }
186
+ const prev = Date.parse(`${days[i - 1]}T00:00:00Z`);
187
+ const cur = Date.parse(`${days[i]}T00:00:00Z`);
188
+ if (prev - cur === 86_400_000) streakDays++;
189
+ else break;
190
+ }
191
+ return { total, approved, approvalRate, streakDays };
192
+ };
193
+
194
+ export const listTasks = () => load(TASKS_PATH).items;
195
+ export const listProposals = () => {
196
+ const items = load(PROPS_PATH).items;
197
+ return { items, stats: proposalStats(items) };
198
+ };
199
+
200
+ /** タスクの状態を open/done で切り替える。決定を履歴に残す。 */
201
+ export const setTaskStatus = (id, status, date) => {
202
+ if (status !== "open" && status !== "done") return null;
203
+ const store = load(TASKS_PATH);
204
+ const item = store.items.find((i) => i.id === id);
205
+ if (!item) return null;
206
+ if (item.status !== status) {
207
+ item.status = status;
208
+ item.history.push({ date, event: status === "done" ? "done" : "reopen" });
209
+ }
210
+ save(TASKS_PATH, store);
211
+ return item;
212
+ };
213
+
214
+ /**
215
+ * 提案の決定を記録する。**決定の記録のみ。実行はしない。**
216
+ * decidedBy(誰が)・decidedAt(いつ)を監査用に残す。
217
+ */
218
+ export const setProposalDecision = (id, decision, by, atIso) => {
219
+ if (!["approved", "rejected", "hold"].includes(decision)) return null;
220
+ const store = load(PROPS_PATH);
221
+ const item = store.items.find((i) => i.id === id);
222
+ if (!item) return null;
223
+ item.status = decision;
224
+ item.decidedBy = by ?? "PRO";
225
+ item.decidedAt = atIso;
226
+ item.history.push({ date: atIso.slice(0, 10), event: decision });
227
+ save(PROPS_PATH, store);
228
+ return item;
229
+ };
@@ -0,0 +1,28 @@
1
+ // clubs-nippo — AI業務日報 (CLUBS)
2
+ // Copyright (c) 2026 FRAME00 Inc.
3
+ // SPDX-License-Identifier: MIT
4
+ // https://github.com/frame00/clubs-nippo
5
+
6
+ /**
7
+ * ツール名を人が読める形にする。
8
+ *
9
+ * MCP のツールは `mcp__<server>__<tool>` という形で記録される。server がUUIDのことがあり
10
+ * (例: mcp__a3f41ac2-9d0b-4c19-a110-8471149ab932__create_draft)、そのまま日報に出すと読めない。
11
+ * 日報で意味を持つのは「何をしたか」=ツール名の方なので、そこだけ残す。
12
+ *
13
+ * server が人の付けた名前(workspace など)なら、どこ経由かも情報なので残す。
14
+ */
15
+ const looksLikeUuid = (s) =>
16
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s);
17
+
18
+ export const normalizeToolName = (name) => {
19
+ if (typeof name !== "string" || !name.startsWith("mcp__")) return name;
20
+
21
+ const parts = name.split("__");
22
+ if (parts.length < 3) return name;
23
+
24
+ const server = parts[1];
25
+ const tool = parts.slice(2).join("__");
26
+
27
+ return looksLikeUuid(server) ? tool : `${server}.${tool}`;
28
+ };
@@ -0,0 +1,162 @@
1
+ <!DOCTYPE html>
2
+ <html lang="ja">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>{{TITLE}}</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Noto+Sans+JP:wght@400;500;700;900&family=IBM+Plex+Mono:wght@500;600&family=Yomogi&display=swap" rel="stylesheet">
9
+ <style>
10
+ :root{
11
+ --bg:#F5F6FB; --paper:#FFFFFF; --ink:#151827; --ink2:#5B607A; --line:#E7E9F2;
12
+ --brand1:#4F6BF6; --brand2:#8B5CF6; --ok:#0A8F66; --ok-t:#E4F5EE;
13
+ --gold:#B7862B; --gold-t:#F7EFDD; --tealAv1:#66B9D8; --tealAv2:#3E93B5;
14
+ --r:14px;
15
+ --body:'Inter','Noto Sans JP','Hiragino Sans','Yu Gothic',system-ui,sans-serif;
16
+ --mono:'IBM Plex Mono',ui-monospace,monospace; --hand:'Yomogi',cursive;
17
+ }
18
+ *{margin:0;padding:0;box-sizing:border-box}
19
+ body{font-family:var(--body);background:var(--bg);color:var(--ink);line-height:1.7;font-size:14.5px}
20
+ .col{max-width:600px;margin:0 auto;padding:0 18px 44px}
21
+
22
+ .ptitle{padding:24px 0 6px;text-align:center}
23
+ .ptitle .d{font-size:12px;font-weight:600;color:var(--ink2);letter-spacing:.06em}
24
+ .ptitle h1{font-size:21px;font-weight:800;margin-top:2px;letter-spacing:-.01em}
25
+
26
+ .card{background:var(--paper);border:1px solid var(--line);border-radius:var(--r);box-shadow:0 1px 2px rgba(18,22,45,.05)}
27
+
28
+ /* profile header */
29
+ .phead{margin-top:8px;border-radius:16px;overflow:hidden;background:var(--paper);border:1px solid var(--line);box-shadow:0 8px 24px rgba(27,35,52,.07)}
30
+ .pcover{height:60px;background:linear-gradient(120deg,var(--brand1),var(--brand2));position:relative}
31
+ .pcover .seal{position:absolute;right:12px;top:11px;background:rgba(255,255,255,.95);color:var(--ok);border-radius:999px;padding:4px 11px;font-size:10.5px;font-weight:800;display:flex;align-items:center;gap:5px}
32
+ .pcover .seal.pending{color:var(--gold)}
33
+ .pcover .seal .mk{width:14px;height:14px;border-radius:50%;background:var(--ok);color:#fff;display:flex;align-items:center;justify-content:center;font-size:8px}
34
+ .pcover .seal.pending .mk{background:var(--gold)}
35
+ .pbody{padding:0 18px 16px}
36
+ .pav{position:relative;z-index:2;width:60px;height:60px;border-radius:16px;background:linear-gradient(160deg,var(--tealAv1),var(--tealAv2));display:flex;align-items:center;justify-content:center;color:#fff;font-size:25px;font-weight:800;margin-top:-30px;border:3px solid #fff;box-shadow:0 3px 10px rgba(27,35,52,.15)}
37
+ .pname{font-size:19px;font-weight:800;margin-top:8px}
38
+ .pmeta{font-family:var(--mono);font-size:11px;color:var(--ink2);margin-top:1px;word-break:break-all}
39
+ .pseat{display:inline-block;margin-top:8px;font-size:10.5px;font-weight:700;color:var(--brand1);background:#EEF1FE;border-radius:999px;padding:3px 10px}
40
+ .pstats{display:flex;gap:20px;margin-top:13px;border-top:1px solid var(--line);padding-top:12px}
41
+ .pstats .n{font-weight:800;font-size:16px;line-height:1}
42
+ .pstats .l{font-size:9.5px;color:var(--ink2);font-weight:600;margin-top:2px}
43
+ .pconn{display:flex;gap:6px;margin-top:11px;flex-wrap:wrap}
44
+ .pconn .c{font-size:9.5px;color:var(--ink2);background:var(--bg);border:1px solid var(--line);border-radius:999px;padding:2px 9px;font-weight:600}
45
+ .annot{font-size:10.5px;color:var(--ink2);margin-top:11px;padding:0 4px;line-height:1.65}
46
+
47
+ .strip{margin-top:14px;display:flex;gap:9px}
48
+ .stat{flex:1;background:var(--paper);border:1px solid var(--line);border-radius:12px;padding:11px 6px;text-align:center}
49
+ .stat .n{font-weight:800;font-size:17px;color:var(--ink);line-height:1.1}
50
+ .stat .n small{font-size:11px;font-weight:700}
51
+ .stat .l{font-size:9px;font-weight:600;color:var(--ink2);margin-top:3px;letter-spacing:.02em}
52
+
53
+ .sec{margin-top:18px}
54
+ .sec-l{font-size:11px;letter-spacing:.04em;font-weight:800;color:var(--ink);margin-bottom:9px;display:flex;align-items:center;justify-content:space-between}
55
+ .badge-ok{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;font-weight:800;color:var(--ok)}
56
+ .badge-ok .mk{width:15px;height:15px;border-radius:50%;background:var(--ok);color:#fff;display:flex;align-items:center;justify-content:center;font-size:9px}
57
+ .badge-pending{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;font-weight:800;color:var(--gold);background:var(--gold-t);border-radius:999px;padding:2px 9px}
58
+
59
+ .wblock{padding:14px 16px;margin-bottom:10px}
60
+ .wh{display:flex;align-items:center;gap:8px;margin-bottom:5px;flex-wrap:wrap}
61
+ .wt{font-weight:800;font-size:14px}
62
+ .wv{font-size:10px;color:var(--ink2);background:var(--bg);border:1px solid var(--line);border-radius:999px;padding:1px 8px;font-weight:600}
63
+ .wok{margin-left:auto;display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:800;color:var(--ok);white-space:nowrap}
64
+ .wok .mk{width:14px;height:14px;border-radius:50%;background:var(--ok);color:#fff;display:flex;align-items:center;justify-content:center;font-size:8px}
65
+ .wpending{margin-left:auto;font-size:10px;font-weight:800;color:var(--gold);background:var(--gold-t);border-radius:999px;padding:1px 8px;white-space:nowrap}
66
+ .wtext{font-size:13.5px;color:var(--ink);line-height:1.85}
67
+ .wmetric{margin-top:6px;font-family:var(--mono);font-size:11px;color:var(--brand1);border-top:1px dashed var(--line);padding-top:6px}
68
+ .redact{background:#2A3145;color:#2A3145;border-radius:4px;padding:0 5px;letter-spacing:-1px;user-select:none}
69
+ .wmask{margin-top:6px;font-size:10.5px;color:var(--ink2)}
70
+ .wmask b{color:var(--gold)}
71
+
72
+ .handoff{background:var(--bg);border:1px solid var(--line);border-radius:14px;padding:14px 16px}
73
+ .handoff .t{font-weight:800;font-size:13.5px;margin-bottom:4px}
74
+ .handoff .x{font-size:13px;color:var(--ink);line-height:1.85}
75
+
76
+ .propo{border:1.5px solid #D7DEFB;border-radius:16px;padding:16px 18px;background:linear-gradient(180deg,#F4F6FF,#FFFFFF)}
77
+ .propo .ph{display:flex;align-items:center;gap:9px;flex-wrap:wrap;margin-bottom:4px}
78
+ .propo .pt{font-weight:800;font-size:14px}
79
+ .chip{font-size:9.5px;font-weight:800;border-radius:999px;padding:2px 8px}
80
+ .chip.opt{background:#E7ECFE;color:var(--brand1)}
81
+ .chip.wait{background:var(--gold-t);color:var(--gold)}
82
+ .propo .pintro{font-size:11.5px;color:var(--ink2);margin-bottom:12px}
83
+ .pitem{background:var(--paper);border:1px solid var(--line);border-radius:12px;padding:12px 13px;margin-bottom:9px}
84
+ .pitem:last-child{margin-bottom:0}
85
+ .pi-top{display:flex;align-items:flex-start;gap:10px}
86
+ .pi-ic{width:32px;height:32px;border-radius:9px;display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0;background:#EAF4F8}
87
+ .pi-t{font-weight:800;font-size:13px;line-height:1.35}
88
+ .pi-d{font-size:12px;color:var(--ink2);margin-top:2px;line-height:1.6}
89
+ .pi-save{font-family:var(--mono);font-size:11px;color:var(--ok);font-weight:600;margin-top:3px}
90
+ .propo .pfoot{font-size:10.5px;color:var(--ink2);margin-top:11px;border-top:1px solid var(--line);padding-top:9px}
91
+ .propo .pfoot b{color:var(--brand1)}
92
+
93
+ .self{background:#F6F7FB;border:1px solid var(--line);border-radius:14px;padding:15px 16px 13px;position:relative}
94
+ .self::before{content:attr(data-label);position:absolute;top:-9px;left:16px;font-size:9.5px;font-weight:800;background:var(--ink2);color:#fff;padding:2px 10px;border-radius:999px}
95
+ .self .tx{font-family:var(--body);font-size:13.5px;color:var(--ink);line-height:1.85}
96
+
97
+ .proof{margin-top:18px;background:var(--paper);border:1px solid var(--line);border-radius:14px;padding:13px 15px}
98
+ .proof .t{font-size:11px;font-weight:800;margin-bottom:5px}
99
+ .proof .h{font-family:var(--mono);font-size:10.5px;color:var(--ink2);word-break:break-all}
100
+ .proof a{color:var(--brand1);font-weight:700}
101
+
102
+ .foot{text-align:center;padding:22px 0 8px;font-size:11px;color:var(--ink2);line-height:1.7}
103
+ .foot a{color:var(--brand1);font-weight:700}
104
+ </style>
105
+ </head>
106
+ <body>
107
+
108
+ <div class="col">
109
+
110
+ <div class="ptitle"><div class="d">{{DATE_LINE}}</div><h1>AI業務日報</h1></div>
111
+
112
+ <div class="phead">
113
+ <div class="pcover">{{PROFILE_SEAL}}</div>
114
+ <div class="pbody">
115
+ <div class="pav">{{AGENT_INITIAL}}</div>
116
+ <div class="pname">{{AGENT}}</div>
117
+ <div class="pmeta">{{MODEL_LINE}}</div>
118
+ <div class="pseat">{{SEAT}}</div>
119
+ <div class="pstats">{{PROFILE_STATS}}</div>
120
+ <div class="pconn">{{CONNECTIONS}}</div>
121
+ </div>
122
+ </div>
123
+
124
+ <div class="sec">
125
+ <div class="strip">
126
+ <div class="stat"><div class="n">{{STAT_HOURS}}<small>h</small></div><div class="l">実働(推定)</div></div>
127
+ <div class="stat"><div class="n">{{STAT_CALLS}}</div><div class="l">実行回数</div></div>
128
+ <div class="stat"><div class="n">{{STAT_SUCCESS}}<small>%</small></div><div class="l">成功率</div></div>
129
+ </div>
130
+ <div class="annot">{{ANNOT}}</div>
131
+ </div>
132
+
133
+ <div class="sec">
134
+ <div class="sec-l">本日の業務 {{SECTION_BADGE}}</div>
135
+ {{WORK_BLOCKS}}
136
+ </div>
137
+
138
+ <div class="sec">
139
+ <div class="sec-l">進捗と申し送り</div>
140
+ <div class="handoff">
141
+ <div class="t">{{HANDOFF_TITLE}}</div>
142
+ <div class="x">{{HANDOFF_TEXT}}</div>
143
+ </div>
144
+ </div>
145
+
146
+ {{INSIGHTS_SECTION}}
147
+
148
+ <div class="sec">
149
+ <div class="self" data-label="{{AGENT}} のひとこと(✓なし)"><div class="tx">{{SELF_NOTE}}</div></div>
150
+ </div>
151
+
152
+ {{PROOF_SECTION}}
153
+
154
+ <div class="foot">
155
+ この日報は手元({{HOST}})で生成しています。実行ログ本体は送信していません。<br>
156
+ 収集元: {{ORIGIN}}<br>
157
+ 生成: {{GENERATOR}} / 対象: {{DATE}}
158
+ </div>
159
+
160
+ </div>
161
+ </body>
162
+ </html>
Binary file
@@ -0,0 +1,7 @@
1
+ <svg width="670" height="159" viewBox="0 0 670 159" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M615.749 0C604.58 0 594.328 2.85094 585.499 8.35326C576.83 13.756 571.008 20.789 567.217 27.0965C563.481 33.3105 561.453 39.2358 560.341 43.4064C559.77 45.5482 559.4 47.3852 559.161 48.7801C559.041 49.4816 558.953 50.0829 558.888 50.5679C558.856 50.8108 558.83 51.0257 558.808 51.2105C558.798 51.303 558.788 51.3881 558.78 51.4655L558.768 51.576L558.76 51.6513L558.756 51.6986L558.748 51.774L558.738 51.8844C558.733 51.9447 558.727 52.0096 558.721 52.079L558.716 52.1394C558.701 52.3243 558.684 52.5391 558.668 52.782C558.636 53.2671 558.605 53.8683 558.586 54.5698C558.547 55.9647 558.553 57.8018 558.699 59.9436C558.982 64.1141 559.833 70.0395 562.334 76.2535C564.873 82.561 569.297 89.5939 576.894 94.9967C584.63 100.499 594.315 103.35 605.484 103.35C607.077 103.35 607.905 103.554 608.248 103.677C608.294 103.758 608.359 103.886 608.437 104.079C608.735 104.821 608.938 105.852 609.01 106.898C609.02 107.056 609.027 107.2 609.031 107.325C609.002 107.45 608.967 107.593 608.925 107.752C608.646 108.798 608.238 109.829 607.792 110.571C607.676 110.764 607.586 110.892 607.523 110.973C607.156 111.096 606.288 111.3 604.694 111.3H552.836L548.098 159H599.956C611.125 159 621.376 156.149 630.206 150.647C638.875 145.244 644.697 138.211 648.488 131.904C652.223 125.689 654.252 119.764 655.363 115.594C655.934 113.452 656.305 111.615 656.544 110.22C656.664 109.518 656.752 108.917 656.817 108.432C656.849 108.189 656.875 107.974 656.897 107.789C656.907 107.697 656.917 107.612 656.925 107.534L656.937 107.424L656.944 107.349L656.949 107.301L656.956 107.226L656.967 107.116C656.974 107.038 656.981 106.953 656.989 106.861C657.004 106.676 657.021 106.461 657.037 106.218C657.069 105.733 657.099 105.132 657.119 104.43C657.157 103.035 657.151 101.198 657.006 99.0564C656.723 94.8858 655.871 88.9605 653.371 82.7464C650.832 76.439 646.408 69.406 638.811 64.0032C631.075 58.5009 621.39 55.65 610.221 55.65C608.627 55.65 607.8 55.4457 607.457 55.3233C607.411 55.2424 607.346 55.1138 607.268 54.9214C606.969 54.1793 606.766 53.1483 606.695 52.1017C606.685 51.9435 606.678 51.8002 606.674 51.675C606.703 51.5498 606.738 51.4065 606.78 51.2482C607.059 50.2016 607.467 49.1707 607.913 48.4285C608.029 48.2361 608.119 48.1076 608.182 48.0267C608.549 47.9043 609.417 47.7 611.011 47.7H662.869L667.607 0H615.749ZM608.444 103.766C608.442 103.768 608.41 103.755 608.356 103.719C608.419 103.746 608.446 103.764 608.444 103.766ZM608.177 103.57C608.18 103.574 608.184 103.578 608.189 103.584C608.177 103.567 608.171 103.561 608.171 103.561C608.171 103.561 608.173 103.564 608.177 103.57ZM607.423 111.089C607.423 111.089 607.431 111.08 607.446 111.066C607.431 111.083 607.423 111.089 607.423 111.089ZM607.737 110.884C607.735 110.882 607.701 110.895 607.639 110.931C607.677 110.916 607.703 110.904 607.719 110.896C607.732 110.889 607.737 110.884 607.737 110.884ZM607.261 55.2338C607.263 55.2317 607.295 55.2453 607.349 55.2812C607.286 55.2539 607.259 55.2359 607.261 55.2338ZM607.526 55.4284C607.523 55.4249 607.52 55.4208 607.515 55.416C607.527 55.4326 607.534 55.4389 607.534 55.4389C607.534 55.4389 607.532 55.435 607.526 55.4284ZM608.282 47.9111C608.282 47.9111 608.274 47.92 608.259 47.9339C608.274 47.9174 608.282 47.9111 608.282 47.9111ZM607.968 48.1161C607.97 48.1183 608.004 48.1046 608.066 48.0688C608.035 48.0808 608.013 48.0911 607.997 48.0989C607.976 48.1088 607.968 48.1161 607.968 48.1161Z" fill="white"/>
3
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M475.334 0H427.465L411.673 159H459.541H478.689L482.637 119.25H463.489L465.463 99.375H484.611L488.559 59.625H469.412L471.386 39.75H490.533L494.481 0H475.334ZM488.559 59.625C490.131 59.625 491.712 59.3679 493.214 58.8685C494.715 58.3691 496.107 57.6371 497.31 56.7144C498.513 55.7916 499.504 54.6961 500.225 53.4904C500.946 52.2847 501.384 50.9925 501.513 49.6875C501.643 48.3825 501.462 47.0902 500.98 45.8846C500.498 44.6789 499.726 43.5834 498.706 42.6606C497.687 41.7378 496.44 41.0058 495.038 40.5064C493.635 40.007 492.105 39.75 490.533 39.75L494.481 0C502.339 0 509.992 1.2852 517.004 3.78223C524.015 6.27926 530.248 9.93922 535.346 14.5531C540.444 19.167 544.308 24.6445 546.716 30.6729C549.124 36.7013 550.03 43.1624 549.382 49.6875C548.734 56.2125 546.545 62.6737 542.939 68.7021C540.635 72.5534 537.782 76.1799 534.454 79.5C537.123 82.8201 539.255 86.4466 540.794 90.2979C543.202 96.3262 544.108 102.787 543.46 109.312C542.812 115.838 540.623 122.299 537.017 128.327C533.411 134.355 528.459 139.833 522.445 144.447C516.43 149.061 509.471 152.721 501.963 155.218C494.455 157.715 486.547 159 478.689 159L482.637 119.25C484.209 119.25 485.79 118.993 487.292 118.494C488.793 117.994 490.185 117.262 491.388 116.339C492.591 115.417 493.581 114.321 494.303 113.115C495.024 111.91 495.462 110.617 495.591 109.312C495.721 108.007 495.54 106.715 495.058 105.51C494.576 104.304 493.804 103.208 492.784 102.286C491.764 101.363 490.518 100.631 489.116 100.131C487.713 99.632 486.183 99.375 484.611 99.375L488.559 59.625Z" fill="white"/>
4
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M282.263 0H330.132L321.051 91.425L321.052 91.425C320.793 94.0348 321.052 96.6191 321.815 99.0303C322.578 101.442 323.829 103.632 325.498 105.478C327.167 107.323 329.22 108.787 331.54 109.786C333.861 110.785 336.403 111.299 339.022 111.299C341.642 111.299 344.286 110.785 346.805 109.786C349.324 108.787 351.668 107.323 353.703 105.478C355.738 103.632 357.425 101.442 358.667 99.0303C359.909 96.6191 360.681 94.0348 360.94 91.425H360.942L370.022 0H417.891L408.811 91.425C407.929 100.299 405.302 109.086 401.08 117.285C396.858 125.483 391.123 132.933 384.202 139.208C377.282 145.483 369.312 150.46 360.747 153.856C352.182 157.252 343.19 159 334.285 159C325.379 159 316.734 157.252 308.844 153.856C300.954 150.46 293.972 145.483 288.299 139.208C282.625 132.933 278.369 125.483 275.776 117.285C273.182 109.086 272.301 100.299 273.182 91.425L282.263 0Z" fill="white"/>
5
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M216.842 0H168.973L153.18 159H201.049H272.852L277.59 111.3L205.787 111.3L216.842 0Z" fill="white"/>
6
+ <path d="M160.719 39.75C153.443 24.594 141.064 12.7488 125.504 6.0516C109.943 -0.645614 92.07 -1.82058 74.6559 2.70893C57.2418 7.23844 41.2602 17.2193 29.1898 31.1035C17.1193 44.9877 9.63453 61.9993 7.89632 79.5C6.1581 97.0007 10.2636 114.012 19.576 127.897C28.8884 141.781 42.8874 151.762 59.4017 156.291C75.916 160.821 94.0227 159.646 110.914 152.948C127.805 146.251 142.536 134.406 152.822 119.25L113.736 95.4C109.621 101.462 103.728 106.2 96.9721 108.879C90.2157 111.558 82.973 112.028 76.3673 110.216C69.7616 108.405 64.162 104.412 60.437 98.8586C56.7121 93.3049 55.0699 86.5003 55.7652 79.5C56.4605 72.4997 59.4544 65.6951 64.2825 60.1414C69.1107 54.5877 75.5034 50.5954 82.469 48.7836C89.4346 46.9718 96.584 47.4418 102.808 50.1206C109.032 52.7995 113.984 57.5376 116.894 63.6L160.719 39.75Z" fill="white"/>
7
+ </svg>