@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
@@ -0,0 +1,248 @@
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>日報の検証 — CLUBS</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Noto+Sans+JP:wght@400;500;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet" />
10
+ <style>
11
+ :root{
12
+ --ink:#161A26; --ink-2:#3A4155; --muted:#6B7280;
13
+ --line:#E7E9F2; --bg:#F6F7FB; --card:#FFFFFF;
14
+ --indigo:#4F6BF6; --violet:#8B5CF6;
15
+ --green:#0A8F66; --green-2:#17A45F; --green-bg:#E7F5EF; --green-line:#BFE6D6;
16
+ --gray:#6B7280; --gray-bg:#F3F4F6; --gray-line:#E5E7EB;
17
+ --radius:16px;
18
+ }
19
+ *{box-sizing:border-box}
20
+ body{margin:0; background:var(--bg); color:var(--ink); min-height:100vh;
21
+ font-family:"Inter","Noto Sans JP",system-ui,-apple-system,"Segoe UI",sans-serif;
22
+ line-height:1.6; -webkit-font-smoothing:antialiased;
23
+ display:flex; flex-direction:column; align-items:center}
24
+ .mono{font-family:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,monospace}
25
+
26
+ /* DEMO 用(?demo=1 のときだけ JS が表示する。本番URLでは出ない) */
27
+ .demobar{display:none; width:100%; background:#0F1220; color:#C9CEDC; font-size:12px}
28
+ .demobar.show{display:block}
29
+ .demobar .in{max-width:520px; margin:0 auto; padding:0 20px; height:40px;
30
+ display:flex; align-items:center; gap:8px; flex-wrap:wrap}
31
+ .demobar b{color:#fff; font-weight:600}
32
+ .demobar button{font:inherit; color:#C9CEDC; background:transparent; border:1px solid #2A2F45;
33
+ padding:3px 10px; border-radius:999px; cursor:pointer}
34
+ .demobar button.on{color:#fff; background:#242A42; border-color:#3A4160}
35
+
36
+ .top{margin:28px 0 18px; display:flex; align-items:center; gap:9px; font-weight:700; letter-spacing:.02em}
37
+ .top .dot{width:22px; height:22px; border-radius:7px; background:linear-gradient(135deg,var(--indigo),var(--violet))}
38
+ .top .chip{font-size:11px; font-weight:600; color:var(--indigo); background:#EEF1FE;
39
+ border:1px solid #DCE3FD; padding:2px 8px; border-radius:999px}
40
+
41
+ .cert{width:min(92vw,460px); background:var(--card); border:1px solid var(--line);
42
+ border-radius:var(--radius); box-shadow:0 4px 20px rgba(20,26,38,.06);
43
+ padding:34px 30px 26px; text-align:center; margin-bottom:22px}
44
+ .seal{width:64px; height:64px; margin:0 auto 16px; border-radius:50%;
45
+ display:flex; align-items:center; justify-content:center; border:2px solid}
46
+ .seal svg{width:32px; height:32px}
47
+ .seal.registered,.seal.anchored{color:var(--green); background:var(--green-bg); border-color:var(--green-line)}
48
+ .seal.pending,.seal.not_found{color:var(--gray); background:var(--gray-bg); border-color:var(--gray-line)}
49
+
50
+ .verdict{font-size:20px; font-weight:700; margin:0 0 8px; letter-spacing:.01em}
51
+ .sub{font-size:14px; color:var(--ink-2); margin:0 auto; max-width:340px}
52
+
53
+ .facts{margin:22px 0 0; border-top:1px solid var(--line); padding-top:18px;
54
+ display:grid; grid-template-columns:1fr 1fr; gap:14px 10px; text-align:left}
55
+ .facts .k{font-size:11px; color:var(--muted); font-weight:600; margin-bottom:2px}
56
+ .facts .v{font-size:14px; color:var(--ink)}
57
+
58
+ .chain{margin-top:16px}
59
+ .chain a{display:inline-flex; align-items:center; gap:7px; font-size:13.5px; font-weight:600;
60
+ color:var(--indigo); text-decoration:none; background:#EEF1FE; border:1px solid #DCE3FD;
61
+ padding:8px 14px; border-radius:10px}
62
+ .chain a.disabled{color:var(--muted); background:var(--gray-bg); border-color:var(--gray-line); pointer-events:none}
63
+ .chain svg{width:15px; height:15px}
64
+
65
+ .cta{margin-top:18px}
66
+ .cta a{font-size:14px; font-weight:600; color:var(--indigo); text-decoration:none}
67
+
68
+ .more{margin-top:18px; border-top:1px solid var(--line); padding-top:14px; text-align:left}
69
+ details{font-size:13px}
70
+ summary{cursor:pointer; color:var(--muted); font-weight:600; list-style:none; display:flex; align-items:center; gap:6px}
71
+ summary::-webkit-details-marker{display:none}
72
+ summary .caret{transition:transform .15s; font-size:11px}
73
+ details[open] summary .caret{transform:rotate(90deg)}
74
+ .more .k{font-size:11px; color:var(--muted); font-weight:600; margin:12px 0 3px}
75
+ .hashrow{display:flex; align-items:center; gap:8px}
76
+ .hash{font-size:11.5px; color:var(--ink-2); word-break:break-all}
77
+ .copy{font:inherit; font-size:11px; color:var(--indigo); background:#EEF1FE;
78
+ border:1px solid #DCE3FD; border-radius:7px; padding:2px 8px; cursor:pointer; white-space:nowrap}
79
+ .meaningtxt{color:var(--ink-2); margin:8px 0 0}
80
+
81
+ .foot{font-size:12px; color:var(--muted); max-width:min(92vw,460px); text-align:center;
82
+ padding:0 8px 40px; margin-top:2px}
83
+ .foot a{color:var(--indigo); text-decoration:none}
84
+ </style>
85
+ </head>
86
+ <body>
87
+
88
+ <div class="demobar" id="demobar"><div class="in">
89
+ <b>DEMO</b>
90
+ <button data-state="registered" class="on">無料・照合済み</button>
91
+ <button data-state="anchored">有償・オンチェーン</button>
92
+ <button data-state="pending">照合予定</button>
93
+ <button data-state="not_found">未登録</button>
94
+ </div></div>
95
+
96
+ <div class="top"><img src="../assets/logo-clubs.svg" alt="CLUBS" style="width:78px;height:20px;vertical-align:middle;margin-right:6px" /><span class="chip">検証</span></div>
97
+
98
+ <div id="view"></div>
99
+ <div class="foot" id="foot"></div>
100
+
101
+ <script>
102
+ /* ============================================================
103
+ ■ 差し替え箇所 1 / 2 : LEMMA_API_BASE
104
+ Lemma の参照系エンドポイント(GET /v1/documents/:docHash)が
105
+ 出たら、この base を設定する。空のままだと実照会はせず、
106
+ ?demo=1 のときだけモックを表示する(本番URLでは「未登録」)。
107
+ ------------------------------------------------------------
108
+ ■ 差し替え箇所 2 / 2 : EXPLORER_TX_BASE
109
+ 使用チェーンのブロックエクスプローラが決まったら設定する。
110
+ 例: "https://basescan.org/tx/"(Base メインネット)
111
+ 空の間は anchored でもリンクを無効表示にする。
112
+ ============================================================ */
113
+ const LEMMA_API_BASE = ""; // 例: "https://lemma.workers.dev"
114
+ const EXPLORER_TX_BASE = ""; // 例: "https://basescan.org/tx/"
115
+
116
+ /* getDocument: docHash から検証記録を引く。
117
+ 返り値の形:
118
+ { status:"registered"|"anchored"|"pending",
119
+ registeredAt, issuerId, subjectId, docHash, onchainTxHash?, reportUrl? }
120
+ ※ 本文(workBlocks 等)はこのページでは扱わない。中身は reportUrl 経由。 */
121
+ async function getDocument(docHash){
122
+ if (LEMMA_API_BASE){
123
+ try{
124
+ const r = await fetch(`${LEMMA_API_BASE}/v1/documents/${docHash}`);
125
+ if (r.status === 404) return { status:"not_found" };
126
+ if (!r.ok) return { status:"not_found" };
127
+ return await r.json();
128
+ }catch{ return { status:"not_found" }; }
129
+ }
130
+ // コンソール(ローカルサーバ)から開いたとき。
131
+ // bin/clubs-nippo-web.mjs が GET /api/documents/:hash を返す(Lemma の参照系が出るまでの代替)。
132
+ // ここを見ていなかったので、実在する docHash でも「記録が見つかりません」になっていた。
133
+ try{
134
+ const r = await fetch(`/api/documents/${docHash}`);
135
+ if (r.ok) return await r.json();
136
+ }catch{
137
+ /* 静的配信には /api が無い。次へ。 */
138
+ }
139
+ if (DEMO_ON) return MOCK[DEMO] ?? MOCK.not_found;
140
+ return { status:"not_found" };
141
+ }
142
+
143
+ /* ---- DEMO(?demo=1 のときだけ有効) ---- */
144
+ const DEMO_ON = new URLSearchParams(location.search).get("demo") === "1";
145
+ const BASE_SAMPLE = {
146
+ docHash:"0x9f2c4a7be1d83056c1a0f4e8b7d2593a8c6f0b41d7e9a2c5b830f16e4d7a9c02",
147
+ issuerId:"Hermes", subjectId:"aggre", date:"2026-07-13", reportUrl:"https://clubs.place/r/demo"
148
+ };
149
+ const MOCK = {
150
+ registered:{ ...BASE_SAMPLE, status:"registered", registeredAt:"2026-07-14 08:00 JST" },
151
+ anchored:{ ...BASE_SAMPLE, status:"anchored", registeredAt:"2026-07-14 08:00 JST",
152
+ onchainTxHash:"0x4b1e9d0a7c3f5628ba90e1d4f7c2a6539e8b0d31f6a4c92e5730b18d6c4a9f10" },
153
+ pending:{ ...BASE_SAMPLE, status:"pending" },
154
+ not_found:{ status:"not_found" }
155
+ };
156
+ let DEMO = "registered";
157
+
158
+ /* ---- アイコン ---- */
159
+ const svg=(d)=>`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">${d}</svg>`;
160
+ const I={
161
+ check:'<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5"/>',
162
+ clock:'<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6l4 2M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>',
163
+ q:'<path stroke-linecap="round" stroke-linejoin="round" d="M9.88 9.5a2.12 2.12 0 1 1 3.04 2.02c-.7.34-1.42.9-1.42 1.73v.5M12 17h.01"/><circle cx="12" cy="12" r="9"/>',
164
+ ext:'<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5M15 3h6m0 0v6m0-6-9 9"/>'
165
+ };
166
+ const STATES={
167
+ registered:{ cls:"registered", icon:I.check, verdict:"改ざんされていません",
168
+ sub:(d)=>`${d.issuerId} が作成した ${d.date} の日報です。CLUBS に記録された時点から内容は変わっていません。` },
169
+ anchored:{ cls:"anchored", icon:I.check, verdict:"改ざんされていません",
170
+ sub:(d)=>`${d.issuerId} が作成した ${d.date} の日報です。この記録は Base 上にも刻まれており、誰でも確認できます。` },
171
+ pending:{ cls:"pending", icon:I.clock, verdict:"照合を待っています",
172
+ sub:(d)=>`${d.issuerId} の ${d.date} の日報です。まだ検証記録への登録が済んでいません。` },
173
+ };
174
+ const esc=(s)=>String(s??"").replace(/[&<>"]/g,c=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c]));
175
+
176
+ function render(doc){
177
+ const view=document.getElementById("view"), foot=document.getElementById("foot");
178
+ foot.innerHTML = `この検証は <b>Lemma</b>(中立な検証層)の記録に基づいています。✓ が示すのは日報が改ざんされていないことで、書かれた内容の正しさそのものは保証しません。<br>あなたのAIの日報 → <a href="https://clubs.place">clubs.place</a>`;
179
+
180
+ if(!doc || doc.status==="not_found"){
181
+ view.innerHTML = `<div class="cert">
182
+ <div class="seal not_found">${svg(I.q)}</div>
183
+ <h1 class="verdict">記録が見つかりません</h1>
184
+ <p class="sub">この日報は登録されていないか、リンクが正しくない可能性があります。</p>
185
+ </div>`;
186
+ return;
187
+ }
188
+ const st=STATES[doc.status]||STATES.pending;
189
+
190
+ const chain = doc.status==="anchored" && doc.onchainTxHash
191
+ ? `<div class="chain">${
192
+ EXPLORER_TX_BASE
193
+ ? `<a href="${EXPLORER_TX_BASE}${esc(doc.onchainTxHash)}" target="_blank" rel="noopener">${svg(I.ext)} オンチェーンの記録を確認</a>`
194
+ : `<a class="disabled">${svg(I.ext)} オンチェーン記録あり(エクスプローラ確定待ち)</a>`
195
+ }</div>` : "";
196
+
197
+ const facts = [
198
+ ["発行者(AI)", esc(doc.issuerId)],
199
+ ["対象", esc(doc.subjectId)],
200
+ doc.registeredAt ? ["記録日時", esc(doc.registeredAt)] : null,
201
+ ["日報の日付", esc(doc.date)]
202
+ ].filter(Boolean).map(([k,v])=>`<div><div class="k">${k}</div><div class="v">${v}</div></div>`).join("");
203
+
204
+ const cta = doc.reportUrl
205
+ ? `<div class="cta"><a href="${esc(doc.reportUrl)}">この日報の中身を CLUBS で見る →</a></div>` : "";
206
+
207
+ view.innerHTML = `<div class="cert">
208
+ <div class="seal ${st.cls}">${svg(st.icon)}</div>
209
+ <h1 class="verdict">${st.verdict}</h1>
210
+ <p class="sub">${st.sub(doc)}</p>
211
+ <div class="facts">${facts}</div>
212
+ ${chain}
213
+ ${cta}
214
+ <div class="more"><details>
215
+ <summary><span class="caret">▶</span>検証コードと詳しい意味</summary>
216
+ <div class="k">検証コード(docHash)</div>
217
+ <div class="hashrow">
218
+ <div class="hash mono">${esc(doc.docHash)}</div>
219
+ <button class="copy" id="copyBtn">コピー</button>
220
+ </div>
221
+ <p class="meaningtxt">CLUBS に登録したときの日報と、いま照会している記録が一致しているという意味です。日報を後から書き換えるとこのコードが変わり、一致しなくなります。</p>
222
+ </details></div>
223
+ </div>`;
224
+
225
+ const btn=document.getElementById("copyBtn");
226
+ if(btn) btn.onclick=()=>{ navigator.clipboard?.writeText(doc.docHash);
227
+ btn.textContent="コピーしました"; setTimeout(()=>btn.textContent="コピー",1400); };
228
+ }
229
+
230
+ function currentDocHash(){
231
+ const q=new URLSearchParams(location.search).get("doc");
232
+ if(q) return q;
233
+ const m=location.pathname.match(/0x[0-9a-fA-F]{8,}/);
234
+ return m?m[0]:(DEMO_ON?BASE_SAMPLE.docHash:"");
235
+ }
236
+ async function load(){ render(await getDocument(currentDocHash())); }
237
+
238
+ if(DEMO_ON){
239
+ const bar=document.getElementById("demobar"); bar.classList.add("show");
240
+ bar.querySelectorAll("button").forEach(b=>{
241
+ b.onclick=()=>{ bar.querySelectorAll("button").forEach(x=>x.classList.remove("on"));
242
+ b.classList.add("on"); DEMO=b.dataset.state; load(); };
243
+ });
244
+ }
245
+ load();
246
+ </script>
247
+ </body>
248
+ </html>