@clubs.place/cli 0.0.0-stage → 0.1.18
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.
- package/.env.example +88 -0
- package/LICENSE +21 -0
- package/PROVENANCE.json +77 -0
- package/README.md +70 -2
- package/bin/clubs-nippo +7 -0
- package/bin/clubs-nippo-web.mjs +338 -0
- package/bin/clubs-nippo.mjs +527 -0
- package/docs/events-schema.md +76 -0
- package/examples/hermes-events.sample.jsonl +8 -0
- package/package.json +44 -4
- package/src/aggregate.mjs +105 -0
- package/src/autorun.mjs +25 -0
- package/src/autorun.test.mjs +43 -0
- package/src/cards.mjs +406 -0
- package/src/collect.mjs +111 -0
- package/src/config.mjs +173 -0
- package/src/generators/claude.mjs +31 -0
- package/src/generators/cli.mjs +81 -0
- package/src/generators/codex.mjs +28 -0
- package/src/generators/copilot.mjs +21 -0
- package/src/generators/gemini.mjs +20 -0
- package/src/generators/openai-compat.mjs +79 -0
- package/src/generators/openrouter.mjs +65 -0
- package/src/history.mjs +66 -0
- package/src/ingest.mjs +78 -0
- package/src/lemma.docHash.test.mjs +37 -0
- package/src/lemma.mjs +303 -0
- package/src/mask.mjs +45 -0
- package/src/mcp.mjs +269 -0
- package/src/narrative.mjs +191 -0
- package/src/prompt.mjs +184 -0
- package/src/prompt.test.mjs +146 -0
- package/src/remote-config.mjs +72 -0
- package/src/render.mjs +467 -0
- package/src/send.mjs +36 -0
- package/src/settings.mjs +54 -0
- package/src/sources/claude-code.mjs +174 -0
- package/src/sources/codex-cli.mjs +182 -0
- package/src/sources/copilot-cli.mjs +125 -0
- package/src/sources/cowork.mjs +193 -0
- package/src/sources/events-file.mjs +120 -0
- package/src/sources/gemini-cli.mjs +128 -0
- package/src/sources/hermes.mjs +427 -0
- package/src/store.mjs +227 -0
- package/src/tasks.mjs +229 -0
- package/src/tool-name.mjs +28 -0
- package/templates/report-v3.reference.html +162 -0
- package/web/assets/logo-clubs-white.png +0 -0
- package/web/assets/logo-clubs-white.svg +7 -0
- package/web/assets/logo-clubs-white.svg.tmp +30 -0
- package/web/assets/logo-clubs.svg +30 -0
- package/web/assets/qr.js +411 -0
- package/web/assets/sidebar.js +154 -0
- package/web/assets/twemoji.js +34 -0
- package/web/cards/achievements.html +230 -0
- package/web/cards/catalog.json +217 -0
- package/web/email/hero-v3.reference.html +123 -0
- package/web/home/index.html +269 -0
- package/web/preview.json +301 -0
- package/web/qr-selftest.html +46 -0
- package/web/reports/edit.html +255 -0
- package/web/reports/edit.reference.html +257 -0
- package/web/reports/index.html +196 -0
- package/web/settings/index.html +219 -0
- package/web/share/cards-reference.html +228 -0
- package/web/share/passport.html +214 -0
- package/web/share/teianbako.html +177 -0
- package/web/signup/index.html +326 -0
- package/web/tasks/index.html +229 -0
- package/web/verify/index.html +248 -0
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// clubs-nippo — AI業務日報 (CLUBS)
|
|
3
|
+
// Copyright (c) 2026 FRAME00 Inc.
|
|
4
|
+
// SPDX-License-Identifier: MIT
|
|
5
|
+
// https://github.com/frame00/clubs-nippo
|
|
6
|
+
|
|
7
|
+
import { writeFileSync, mkdirSync, readFileSync, existsSync } from "node:fs";
|
|
8
|
+
import { join, dirname } from "node:path";
|
|
9
|
+
import { platform, homedir } from "node:os";
|
|
10
|
+
import { execSync } from "node:child_process";
|
|
11
|
+
import { createInterface } from "node:readline";
|
|
12
|
+
import { loadConfig, APP_HOME } from "../src/config.mjs";
|
|
13
|
+
import { collect } from "../src/collect.mjs";
|
|
14
|
+
import { generateNarrative } from "../src/narrative.mjs";
|
|
15
|
+
import { registerProof, documentHash } from "../src/lemma.mjs";
|
|
16
|
+
import { pushToClubs } from "../src/ingest.mjs";
|
|
17
|
+
import { renderHtml, buildSubject } from "../src/render.mjs";
|
|
18
|
+
import { sendMail } from "../src/send.mjs";
|
|
19
|
+
import { saveDraft } from "../src/store.mjs";
|
|
20
|
+
import { appendHistory, readHistory } from "../src/history.mjs";
|
|
21
|
+
import { evaluateCards } from "../src/cards.mjs";
|
|
22
|
+
import { fetchCollectorConfig } from "../src/remote-config.mjs";
|
|
23
|
+
import { tickCommand } from "../src/autorun.mjs";
|
|
24
|
+
|
|
25
|
+
/** [Y/n] を聞く。非対話(パイプ)では既定 Yes(自動化を止めない)。 */
|
|
26
|
+
const promptYesNo = (question) =>
|
|
27
|
+
new Promise((resolve) => {
|
|
28
|
+
if (!process.stdin.isTTY) return resolve(true);
|
|
29
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
30
|
+
rl.question(`${question} `, (a) => {
|
|
31
|
+
rl.close();
|
|
32
|
+
resolve(!/^n/i.test(a.trim()));
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 毎朝の自動実行を登録する。macOS=launchd / Windows=schtasks / それ以外=crontab。
|
|
38
|
+
* cron は PATH が細いので npx はフルパスで渡す。既存の clubs-nippo 登録は入れ替える(冪等)。
|
|
39
|
+
*/
|
|
40
|
+
const registerAutorun = (hourlyLine, npx) => {
|
|
41
|
+
if (platform() === "darwin") {
|
|
42
|
+
const plistPath = join(homedir(), "Library", "LaunchAgents", "com.frame00.clubs-nippo.plist");
|
|
43
|
+
const path = `${dirname(process.execPath)}:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:${join(homedir(), ".local", "bin")}`;
|
|
44
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
45
|
+
<plist version="1.0"><dict>
|
|
46
|
+
<key>Label</key><string>com.frame00.clubs-nippo</string>
|
|
47
|
+
<key>ProgramArguments</key><array>${tickCommand(npx).map((a) => `<string>${a}</string>`).join("")}</array>
|
|
48
|
+
<key>EnvironmentVariables</key><dict><key>PATH</key><string>${path}</string></dict>
|
|
49
|
+
<key>StartCalendarInterval</key><dict><key>Minute</key><integer>0</integer></dict>
|
|
50
|
+
<key>StandardOutPath</key><string>${join(APP_HOME, "out", "cron.log")}</string>
|
|
51
|
+
<key>StandardErrorPath</key><string>${join(APP_HOME, "out", "cron.err.log")}</string>
|
|
52
|
+
</dict></plist>
|
|
53
|
+
`;
|
|
54
|
+
mkdirSync(dirname(plistPath), { recursive: true });
|
|
55
|
+
writeFileSync(plistPath, plist, "utf8");
|
|
56
|
+
try {
|
|
57
|
+
execSync(`launchctl unload ${JSON.stringify(plistPath)} 2>/dev/null`);
|
|
58
|
+
} catch {
|
|
59
|
+
/* 初回は未ロードで失敗する=無視 */
|
|
60
|
+
}
|
|
61
|
+
execSync(`launchctl load ${JSON.stringify(plistPath)}`);
|
|
62
|
+
return `launchd に登録しました(${plistPath})`;
|
|
63
|
+
}
|
|
64
|
+
if (platform() === "win32") {
|
|
65
|
+
return "Windows では自動実行を登録できません。WSL(Windows Subsystem for Linux)をお使いください。\n" +
|
|
66
|
+
" WSL 内で `clubs-nippo init` を実行すると crontab に登録されます。";
|
|
67
|
+
}
|
|
68
|
+
let current = "";
|
|
69
|
+
try {
|
|
70
|
+
current = execSync("crontab -l 2>/dev/null").toString();
|
|
71
|
+
} catch {
|
|
72
|
+
/* crontab 未設定 */
|
|
73
|
+
}
|
|
74
|
+
const kept = current.split("\n").filter((l) => l && !l.includes("clubs-nippo"));
|
|
75
|
+
execSync("crontab -", { input: [...kept, hourlyLine].join("\n") + "\n" });
|
|
76
|
+
return "crontab に登録しました";
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const parseArgs = (argv) => {
|
|
80
|
+
const out = { _: [] };
|
|
81
|
+
for (const arg of argv) {
|
|
82
|
+
if (arg === "--dry-run") out.dryRun = true;
|
|
83
|
+
else if (arg === "--draft") out.draft = true;
|
|
84
|
+
else if (arg === "--no-llm") out.noLlm = true;
|
|
85
|
+
else if (arg.startsWith("--date=")) out.date = arg.slice(7);
|
|
86
|
+
else if (arg.startsWith("--out=")) out.out = arg.slice(6);
|
|
87
|
+
else if (arg.startsWith("--source=")) out.source = arg.slice(9);
|
|
88
|
+
else if (arg.startsWith("--events=")) out.eventsFile = arg.slice(9);
|
|
89
|
+
else if (arg.startsWith("--url=")) out.url = arg.slice(6);
|
|
90
|
+
else if (arg.startsWith("--token=")) out.token = arg.slice(8);
|
|
91
|
+
else if (arg.startsWith("--code=")) out.code = arg.slice(7);
|
|
92
|
+
else if (arg.startsWith("--agent-name=")) out.agentName = arg.slice(13);
|
|
93
|
+
else if (arg.startsWith("--owner-name=")) out.ownerName = arg.slice(13);
|
|
94
|
+
else if (arg === "--help" || arg === "-h") out.help = true;
|
|
95
|
+
else out._.push(arg);
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const HELP = `clubs-nippo — Claude Code の実行ログから前日ぶんの AI業務日報メールを作って送る
|
|
101
|
+
|
|
102
|
+
使い方:
|
|
103
|
+
clubs-nippo init --url=… --code=…
|
|
104
|
+
接続設定を書く(CLUBS の接続画面が発行する
|
|
105
|
+
接続コードをトークンに引き換える・10分/1回限り)。
|
|
106
|
+
設定と履歴は ~/.clubs-nippo に置かれる。
|
|
107
|
+
--token=(再発行モーダルの直接指定)
|
|
108
|
+
--source= --agent-name= --owner-name= --events= も受ける
|
|
109
|
+
clubs-nippo 前日ぶんを生成して EMAIL_TO に送信
|
|
110
|
+
clubs-nippo --dry-run 送信せず HTML をローカルに書き出すだけ
|
|
111
|
+
clubs-nippo --draft 生成して下書き保存だけ(登録も送信もしない)
|
|
112
|
+
承認フローを使う場合はこれ。Web画面から承認すると
|
|
113
|
+
そこで初めて Lemma に登録される
|
|
114
|
+
clubs-nippo --date=2026-07-13 対象日を指定(既定=前日・JST)
|
|
115
|
+
clubs-nippo --no-llm LLM を使わず集計だけで本文を組む
|
|
116
|
+
clubs-nippo --out=path.html HTML の出力先
|
|
117
|
+
clubs-nippo tick cron の毎時起動用。CLUBS の設定(配信時刻・
|
|
118
|
+
収集元・生成器)を読み、時刻到達&本日未配信の
|
|
119
|
+
ときだけ配信する。設定はアプリ側が正——
|
|
120
|
+
crontab 行は init が表示する(npx のフルパス入り)
|
|
121
|
+
clubs-nippo collect 集計結果を JSON で表示(送信しない)
|
|
122
|
+
clubs-nippo backfill [日数] 実ログから過去ぶんの履歴を作る(既定14日)
|
|
123
|
+
clubs-nippo cards カードを評価して発行(送信はしない)
|
|
124
|
+
clubs-nippo cards --dry-run 評価するが発行しない。しきい値を試すときは必ずこちら
|
|
125
|
+
(達成カードは剥奪できないので、誤発行は取り消せない)
|
|
126
|
+
clubs-nippo mcp MCP サーバー(stdio)として起動。AIクライアント
|
|
127
|
+
(Claude Code / Cursor 等)に登録すると、対話の中で
|
|
128
|
+
CLUBS への登録(clubs_register)と疎通確認
|
|
129
|
+
(clubs_link_status)ができる。例:
|
|
130
|
+
claude mcp add clubs -- npx -y clubs-nippo@latest mcp
|
|
131
|
+
|
|
132
|
+
収集元:
|
|
133
|
+
--source=claude-code この端末の Claude Code セッション記録(既定)
|
|
134
|
+
--source=cowork Cowork(Claude のもう1つの面。claude-code と合算可)
|
|
135
|
+
--source=codex-cli Codex CLI(~/.codex/sessions)
|
|
136
|
+
--source=gemini-cli Gemini CLI(指示は logs.json、ツール実行は telemetry
|
|
137
|
+
有効時のみ=GEMINI_TELEMETRY_FILE)
|
|
138
|
+
--source=copilot-cli GitHub Copilot CLI(~/.copilot・実験的対応)
|
|
139
|
+
--source=events-file --events=path.jsonl
|
|
140
|
+
エージェントが書き出した実行記録(Hermes など)
|
|
141
|
+
形式は docs/events-schema.md を参照
|
|
142
|
+
|
|
143
|
+
環境変数は .env か通常の環境変数で渡す。README.md を参照。`;
|
|
144
|
+
|
|
145
|
+
const main = async () => {
|
|
146
|
+
const argv = parseArgs(process.argv.slice(2));
|
|
147
|
+
if (argv.help) {
|
|
148
|
+
console.log(HELP);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── mcp: MCP サーバー(stdio)。stdout はプロトコル専用になる ──────
|
|
153
|
+
if (argv._[0] === "mcp") {
|
|
154
|
+
const { runMcpServer } = await import("../src/mcp.mjs");
|
|
155
|
+
await runMcpServer();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ── init: 接続画面が発行するコマンド1行で .env を書く(npx 導入の入口)──
|
|
160
|
+
// 対話にしない。値は CLUBS の接続画面が持っているので、コピペ1発で終わるべき。
|
|
161
|
+
if (argv._[0] === "init") {
|
|
162
|
+
// 接続コード方式(v1.4 §3.2): 画面はトークンを見せず、10分・1回限りの
|
|
163
|
+
// コードだけを出す。ここで redeem してトークンに引き換える。
|
|
164
|
+
// --token 直接指定(従来・再発行モーダル)とは排他で、コードを優先する。
|
|
165
|
+
if (argv.code) {
|
|
166
|
+
if (!argv.url) {
|
|
167
|
+
console.log("使い方: clubs-nippo init --url=<取込URL> --code=<接続コード>");
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
const redeemUrl = new URL("/api/nippo/agents/redeem", argv.url).toString();
|
|
171
|
+
let res;
|
|
172
|
+
try {
|
|
173
|
+
res = await fetch(redeemUrl, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: { "content-type": "application/json" },
|
|
176
|
+
body: JSON.stringify({ code: argv.code }),
|
|
177
|
+
});
|
|
178
|
+
} catch (e) {
|
|
179
|
+
console.log(`接続先に届きませんでした(${e?.message ?? e})。URL を確認してください: ${redeemUrl}`);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
const body = await res.json().catch(() => ({}));
|
|
183
|
+
if (!res.ok || !body.token) {
|
|
184
|
+
// コードは 1 回限り・TTL 10 分。失効はエラーではなく「やり直す」導線。
|
|
185
|
+
console.log(
|
|
186
|
+
body.error ??
|
|
187
|
+
"接続コードが無効か期限切れです。CLUBS の接続画面(体の詳細)で再発行してください。",
|
|
188
|
+
);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
argv.token = body.token;
|
|
192
|
+
console.log("✓ 接続コードをトークンに引き換えました。");
|
|
193
|
+
}
|
|
194
|
+
const updates = Object.fromEntries(
|
|
195
|
+
Object.entries({
|
|
196
|
+
NIPPO_INGEST_URL: argv.url,
|
|
197
|
+
NIPPO_INGEST_TOKEN: argv.token,
|
|
198
|
+
SOURCE: argv.source,
|
|
199
|
+
EVENTS_FILE: argv.eventsFile,
|
|
200
|
+
AGENT_NAME: argv.agentName,
|
|
201
|
+
OWNER_NAME: argv.ownerName,
|
|
202
|
+
}).filter(([, v]) => v),
|
|
203
|
+
);
|
|
204
|
+
if (Object.keys(updates).length === 0) {
|
|
205
|
+
console.log(
|
|
206
|
+
"使い方: clubs-nippo init --url=<取込URL> --code=<接続コード> [--source=…]\n" +
|
|
207
|
+
"コマンドは CLUBS の接続画面からコピーできます。",
|
|
208
|
+
);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
mkdirSync(join(APP_HOME, "out"), { recursive: true });
|
|
212
|
+
const envPath = join(APP_HOME, ".env");
|
|
213
|
+
// 既存の .env は生かし、渡されたキーだけ書き換える。
|
|
214
|
+
// トークン再発行や収集元変更で再実行しても、メール設定などが消えない。
|
|
215
|
+
const lines = existsSync(envPath)
|
|
216
|
+
? readFileSync(envPath, "utf8").replace(/\n$/, "").split("\n")
|
|
217
|
+
: ["# clubs-nippo の設定。キーの一覧は https://www.npmjs.com/package/clubs-nippo を参照。"];
|
|
218
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
219
|
+
const row = `${key}=${value}`;
|
|
220
|
+
const idx = lines.findIndex((l) => l.match(new RegExp(`^\\s*${key}\\s*=`)));
|
|
221
|
+
if (idx >= 0) lines[idx] = row;
|
|
222
|
+
else lines.push(row);
|
|
223
|
+
}
|
|
224
|
+
writeFileSync(envPath, lines.join("\n") + "\n", "utf8");
|
|
225
|
+
console.log(`設定を書きました: ${envPath}`);
|
|
226
|
+
console.log(` ${Object.keys(updates).join(", ")}`);
|
|
227
|
+
// cron は PATH が細く npx が見つからないことが多い。実際に動いている node の
|
|
228
|
+
// 隣の npx をフルパスで使う(この行をそのまま登録すれば動く)。
|
|
229
|
+
const npx = join(dirname(process.execPath), "npx");
|
|
230
|
+
const hourlyLine = `0 * * * * ${tickCommand(npx).join(" ")} >> ${join(APP_HOME, "out", "cron.log")} 2>&1`;
|
|
231
|
+
console.log("");
|
|
232
|
+
const yes = await promptYesNo("毎朝の自動実行を登録しますか? [Y/n]");
|
|
233
|
+
if (yes) {
|
|
234
|
+
try {
|
|
235
|
+
const msg = registerAutorun(hourlyLine, npx);
|
|
236
|
+
console.log(`✓ ${msg}。明日の朝、最初の日報が届きます。`);
|
|
237
|
+
} catch (e) {
|
|
238
|
+
console.log(`自動登録に失敗しました(${e?.message ?? e})。うまくいかない場合は、この行を crontab -e に手で追記してください:`);
|
|
239
|
+
console.log(` ${hourlyLine}`);
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
console.log("自動登録はスキップしました。あとで登録するには、この行を crontab -e に追記:");
|
|
243
|
+
console.log(` ${hourlyLine}`);
|
|
244
|
+
}
|
|
245
|
+
console.log("");
|
|
246
|
+
console.log("今すぐ試す(前日分を発行): npx clubs-nippo@latest");
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* 前日から継続している用件(未完了の申し送り・原文)と、確定した代表表記。
|
|
251
|
+
どちらも CLUBS 側が持っているので collector-config から受け取り、生成
|
|
252
|
+
プロンプトに載せる。取れなくても生成は続ける(言い回しの揺れが残るだけで、
|
|
253
|
+
日報が出ないよりよい)。 */
|
|
254
|
+
let carryover = [];
|
|
255
|
+
let terms = [];
|
|
256
|
+
|
|
257
|
+
// ── tick: cron の毎時起動から呼ばれる(自動化レイヤ §3.3)─────────
|
|
258
|
+
// 配信ルールの正は CLUBS 設定。毎ティックで collector-config を読み、
|
|
259
|
+
// 「有効 ∧ 時刻到達 ∧ 本日未配信(サーバ側 draft の有無)」のときだけ発火する。
|
|
260
|
+
// 8時→9時に変えたら翌8時ティックはスキップ・9時ティックで発火。
|
|
261
|
+
// 取得できないティックは何もしない(次のティックで取り戻す)。
|
|
262
|
+
if (argv._[0] === "tick") {
|
|
263
|
+
const r = await fetchCollectorConfig();
|
|
264
|
+
if (!r.ok) {
|
|
265
|
+
console.log(`[tick] 設定を取得できませんでした(${r.reason})。次のティックで再試行します。`);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const c = r.config;
|
|
269
|
+
if (!c.due) {
|
|
270
|
+
const why = !c.schedule?.enabled
|
|
271
|
+
? "配信は停止中"
|
|
272
|
+
: c.delivered
|
|
273
|
+
? `本日分(${c.reportDate})は配信済み`
|
|
274
|
+
: `配信時刻 ${c.schedule.time}(${c.schedule.tz})前`;
|
|
275
|
+
console.log(`[tick] 発火しません — ${why}(現在 ${c.now?.time})`);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// アプリの設定で実行内容を上書きする(アプリ=設定の正・秘密はローカルのまま)。
|
|
280
|
+
argv.date = c.reportDate;
|
|
281
|
+
if (Array.isArray(c.carryover)) carryover = c.carryover;
|
|
282
|
+
if (Array.isArray(c.terms)) terms = c.terms;
|
|
283
|
+
const ADAPTERS = new Set([
|
|
284
|
+
"claude-code",
|
|
285
|
+
"cowork",
|
|
286
|
+
"events-file",
|
|
287
|
+
"codex-cli",
|
|
288
|
+
"gemini-cli",
|
|
289
|
+
"copilot-cli",
|
|
290
|
+
]);
|
|
291
|
+
const mapped = [
|
|
292
|
+
...new Set(
|
|
293
|
+
(c.sources ?? []).map((s) => (ADAPTERS.has(s) ? s : "events-file")),
|
|
294
|
+
),
|
|
295
|
+
];
|
|
296
|
+
if (mapped.length > 0) argv.source = mapped.join(",");
|
|
297
|
+
if (
|
|
298
|
+
mapped.includes("events-file") &&
|
|
299
|
+
!process.env.EVENTS_FILE &&
|
|
300
|
+
!argv.eventsFile
|
|
301
|
+
) {
|
|
302
|
+
// 収集できないまま毎時 exit 1 を繰り返さない(cron ノイズ防止)。
|
|
303
|
+
console.log(
|
|
304
|
+
`[tick] この接続(${c.sources?.join(",") ?? ""})はローカルの実行記録 ` +
|
|
305
|
+
`EVENTS_FILE の設定が必要です。.env に EVENTS_FILE=path.jsonl を設定して ` +
|
|
306
|
+
`ください(docs/events-schema.md)。今回は何もしません。`,
|
|
307
|
+
);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (mapped.includes("events-file") && c.agentName) {
|
|
311
|
+
// events-file 系(Hermes/OpenClaw/その他)は名義を外から与える。
|
|
312
|
+
// claude-code 等のブランド収集元は帰属の規律で名義固定(上書きしない)。
|
|
313
|
+
process.env.AGENT_NAME = c.agentName;
|
|
314
|
+
}
|
|
315
|
+
if (c.generator?.backend === "openrouter") {
|
|
316
|
+
process.env.GENERATOR = "openrouter";
|
|
317
|
+
if (c.generator.model) process.env.OPENROUTER_MODEL = c.generator.model;
|
|
318
|
+
} else if (c.generator?.backend === "openai-compat") {
|
|
319
|
+
// base URL / model は接続設定(control plane)から。API キーはローカル .env のまま。
|
|
320
|
+
process.env.GENERATOR = "openai-compat";
|
|
321
|
+
if (c.generator.baseUrl) process.env.BACKEND_BASE_URL = c.generator.baseUrl;
|
|
322
|
+
if (c.generator.model) process.env.BACKEND_MODEL = c.generator.model;
|
|
323
|
+
} else if (c.generator?.backend === "claude-cli") {
|
|
324
|
+
process.env.GENERATOR = "claude";
|
|
325
|
+
}
|
|
326
|
+
console.log(
|
|
327
|
+
`[tick] 発火: 対象日 ${c.reportDate} / source=${argv.source ?? "(ローカル設定)"} / generator=${process.env.GENERATOR ?? "claude"}`,
|
|
328
|
+
);
|
|
329
|
+
argv.tick = true; // 取込失敗時の早期打ち切り・メール未設定スキップに使う
|
|
330
|
+
argv._ = []; // 以降は通常パイプライン(収集→生成→登録→取込→送信→カード)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const config = loadConfig(argv);
|
|
334
|
+
const activity = await collect(config);
|
|
335
|
+
|
|
336
|
+
// 実ログから過去ぶんの履歴を作り直す。カードの「連続」「累計」条件は系列が要る。
|
|
337
|
+
if (argv._[0] === "backfill") {
|
|
338
|
+
const days = Number(argv._[1] ?? 14);
|
|
339
|
+
const rows = [];
|
|
340
|
+
for (let i = days; i >= 0; i -= 1) {
|
|
341
|
+
const d = new Date(Date.now() + config.tzOffsetMin * 60_000 - i * 86_400_000)
|
|
342
|
+
.toISOString()
|
|
343
|
+
.slice(0, 10);
|
|
344
|
+
const a = await collect({ ...config, date: d });
|
|
345
|
+
if ((a.aggregates?.totalCalls ?? 0) === 0) continue; // 稼働していない日は積まない
|
|
346
|
+
appendHistory(a);
|
|
347
|
+
rows.push(`${d}: 実行 ${a.aggregates?.totalCalls ?? 0} / 失敗 ${a.aggregates?.failedCalls ?? 0} / ${a.aggregates?.activeHours ?? 0}h`);
|
|
348
|
+
}
|
|
349
|
+
console.log(`履歴に ${rows.length} 日ぶんを積みました(out/history.jsonl)`);
|
|
350
|
+
for (const r of rows) console.log(" " + r);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (argv._[0] === "collect") {
|
|
355
|
+
console.log(JSON.stringify({ date: activity.date, ...activity.aggregates }, null, 2));
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (argv._[0] === "cards") {
|
|
360
|
+
appendHistory(activity);
|
|
361
|
+
// --dry-run なら保存しない。しきい値を変えて試すときは必ずこちら。
|
|
362
|
+
// 発行してしまうと、達成カードは剥奪できないので取り消せない。
|
|
363
|
+
const r = evaluateCards({
|
|
364
|
+
history: readHistory(),
|
|
365
|
+
date: activity.date,
|
|
366
|
+
config,
|
|
367
|
+
dryRun: config.dryRun,
|
|
368
|
+
});
|
|
369
|
+
if (r.dryRun) console.log("(--dry-run: 保存していません)");
|
|
370
|
+
console.log(`発行: ${r.issued.map((c) => `${c.name}(${c.rarity})`).join(", ") || "なし"}`);
|
|
371
|
+
console.log(`進行中: ${r.progress.map((p) => `${p.name} ${p.progress}%`).join(", ") || "なし"}`);
|
|
372
|
+
console.log(`判定不可: ${r.unavailable.map((u) => `${u.id}(${u.reason})`).join(", ") || "なし"}`);
|
|
373
|
+
console.log(`保有: 達成 ${r.data.achievements.length} / 称号 ${r.data.titles.length} / 解放可能な権限 ${r.data.permissionsUnlockable.length}`);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
console.log(
|
|
378
|
+
`[1/5] 収集: ${activity.date} / ${activity.agentName}(${activity.source}) — 実行 ${activity.aggregates?.totalCalls ?? 0}回 / 成功率 ${activity.aggregates?.successRate ?? "-"}% / 実働 ${activity.aggregates?.activeHours ?? 0}h`,
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
/* 手動実行(tick 以外)でも継続中の用件・確定した表記を拾う。tick で既に
|
|
382
|
+
取れていれば引き直さない。取れなくても生成は続ける。 */
|
|
383
|
+
if (
|
|
384
|
+
carryover.length === 0 &&
|
|
385
|
+
terms.length === 0 &&
|
|
386
|
+
config.ingestUrl &&
|
|
387
|
+
config.ingestToken
|
|
388
|
+
) {
|
|
389
|
+
const r = await fetchCollectorConfig();
|
|
390
|
+
if (r.ok && Array.isArray(r.config?.carryover)) carryover = r.config.carryover;
|
|
391
|
+
if (r.ok && Array.isArray(r.config?.terms)) terms = r.config.terms;
|
|
392
|
+
}
|
|
393
|
+
if (carryover.length > 0) {
|
|
394
|
+
console.log(`[1/5] 継続中の用件 ${carryover.length}件 を生成に渡します`);
|
|
395
|
+
}
|
|
396
|
+
if (terms.length > 0) {
|
|
397
|
+
console.log(`[1/5] 確定した表記 ${terms.length}件 を生成に渡します`);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const narrative = await generateNarrative(activity, {
|
|
401
|
+
...config,
|
|
402
|
+
carryover,
|
|
403
|
+
terms,
|
|
404
|
+
});
|
|
405
|
+
console.log(`[2/5] 本文生成: ${narrative.generator} — ${narrative.workBlocks.length} ブロック`);
|
|
406
|
+
|
|
407
|
+
// 承認フロー: 生成した時点でいったん下書きとして保存する。
|
|
408
|
+
// 登録も送信もここではしない。承認して初めて記録され、確定する。
|
|
409
|
+
const draft = saveDraft({ activity, narrative });
|
|
410
|
+
if (argv.draft) {
|
|
411
|
+
console.log(`[3/3] 下書き保存: out/${draft.date}.draft.json`);
|
|
412
|
+
console.log(` 承認は Web 画面から: node bin/clubs-nippo-web.mjs`);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ingest モード(CLUBS 接続済み)では証明もメールもサーバ側の仕事
|
|
417
|
+
// (接続画面デバッグリスト §-1)。ユーザーがローカルに置く鍵は OpenRouter だけで、
|
|
418
|
+
// LEMMA_API_KEY / EMAIL_API_KEY が無くてもエラーにならず正常終了する。
|
|
419
|
+
const ingestMode = Boolean(config.ingestUrl && config.ingestToken);
|
|
420
|
+
|
|
421
|
+
const proof = ingestMode
|
|
422
|
+
? {
|
|
423
|
+
status: "pending",
|
|
424
|
+
label: "照合予定",
|
|
425
|
+
docHash: documentHash(activity, narrative),
|
|
426
|
+
reason: "サーバ側で照合(取込時に CLUBS が登録)",
|
|
427
|
+
verifyUrl: null,
|
|
428
|
+
}
|
|
429
|
+
: await registerProof(activity, narrative, config);
|
|
430
|
+
console.log(
|
|
431
|
+
ingestMode
|
|
432
|
+
? `[3/5] 証明: サーバ側で照合します (${proof.docHash.slice(0, 12)}…)`
|
|
433
|
+
: proof.status === "verified"
|
|
434
|
+
? `[3/5] 証明: ✓ ログ照合済み (${proof.docHash.slice(0, 12)}…)`
|
|
435
|
+
: `[3/5] 証明: 照合予定 — ${proof.reason}`,
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
// web コンソール(clubsx)へ本文ごと取り込ませる。未設定・失敗でもここで止めない。
|
|
439
|
+
// --dry-run はサーバにも送らない: ingest 受信でサーバ側の Lemma 実登録・
|
|
440
|
+
// 日報メール実送信(当日分の送信権の消費)が走るため、dry のつもりが実行為になる。
|
|
441
|
+
const ingest = config.dryRun
|
|
442
|
+
? { ok: false, reason: "--dry-run のため取込しません" }
|
|
443
|
+
: await pushToClubs(activity, narrative, proof, config);
|
|
444
|
+
console.log(
|
|
445
|
+
ingest.quiet
|
|
446
|
+
? `[web] コンソール取込: ✓ 受け取られました(稼働なしの日として畳まれます)`
|
|
447
|
+
: ingest.ok
|
|
448
|
+
? `[web] コンソール取込: ✓ 送信しました`
|
|
449
|
+
: `[web] コンソール取込: スキップ — ${ingest.reason}`,
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
/* 稼働のなかった日(土日など)はサーバが日報にしない。ここで止める——
|
|
453
|
+
この先は描画・メール・カードで、どれも「日報が在ること」が前提になる。
|
|
454
|
+
履歴だけは積む(月次のグラフと連続日数の判定は、動かなかった日も系列として
|
|
455
|
+
要る)。tick の冪等はサーバ側の印が持つので、次のティックでは発火しない。 */
|
|
456
|
+
if (ingest.quiet) {
|
|
457
|
+
console.log(
|
|
458
|
+
`[4/5] 稼働なし: ${activity.date} は日報にしません(メールも送りません)。`,
|
|
459
|
+
);
|
|
460
|
+
appendHistory(activity);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// tick モードの冪等はサーバ側 draft(=取込の成功)が門。取込に失敗したまま
|
|
465
|
+
// 先へ進むと、メールだけ送られて delivered が立たず、次のティックで丸ごと
|
|
466
|
+
// 再実行(二重メール・生成し直しで docHash が変わり Lemma 二重登録)になる。
|
|
467
|
+
// ここで止めて、次のティックに任せる。
|
|
468
|
+
if (argv.tick && !ingest.ok) {
|
|
469
|
+
console.log(`[tick] 取込に失敗したため、ここで止めます(次のティックで再試行)。`);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const html = renderHtml({ activity, narrative, proof, config });
|
|
474
|
+
const subject = buildSubject({ activity, config, proof });
|
|
475
|
+
|
|
476
|
+
const outDir = join(APP_HOME, "out");
|
|
477
|
+
mkdirSync(outDir, { recursive: true });
|
|
478
|
+
const outPath = argv.out ?? join(outDir, `nippo-${activity.date}.html`);
|
|
479
|
+
writeFileSync(outPath, html, "utf8");
|
|
480
|
+
console.log(`[4/5] 描画: ${outPath}`);
|
|
481
|
+
|
|
482
|
+
if (config.dryRun) {
|
|
483
|
+
console.log(`[5/5] --dry-run のため送信しません。件名: ${subject}`);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (ingestMode) {
|
|
488
|
+
// ローカル送信はしない(EMAIL_API_KEY 不要)。メールは CLUBS がサーバ側で
|
|
489
|
+
// 送る(取込時・本人確認済みアドレス宛)。履歴・シール評価は続ける。
|
|
490
|
+
// 取込に失敗していたら証明もメールもどこにも届いていない——成功に見せない
|
|
491
|
+
//(tick は上で早期 return 済み=ここに来る失敗は手動実行)。
|
|
492
|
+
if (ingest.ok) {
|
|
493
|
+
console.log(`[5/6] 送信: メールは CLUBS からお届けします(ローカル送信なし)`);
|
|
494
|
+
} else {
|
|
495
|
+
console.log(
|
|
496
|
+
`[5/6] 送信: なし — 取込に失敗したため、証明もメールも届いていません。時間をおいて再実行してください。`,
|
|
497
|
+
);
|
|
498
|
+
process.exitCode = 1;
|
|
499
|
+
}
|
|
500
|
+
} else {
|
|
501
|
+
const sent = await sendMail({ subject, html, config });
|
|
502
|
+
console.log(`[5/6] 送信: ${sent.to.join(", ")} (id: ${sent.id})`);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// 集計を履歴に積み、カードを評価する。
|
|
506
|
+
// 履歴が要るのは「30日連続」のような条件が系列でしか判定できないため。
|
|
507
|
+
appendHistory(activity);
|
|
508
|
+
const cards = evaluateCards({ history: readHistory(), date: activity.date, config });
|
|
509
|
+
console.log(
|
|
510
|
+
cards.issued.length > 0
|
|
511
|
+
? `[6/6] カード: ${cards.issued.map((c) => `${c.name}(${c.rarity})`).join(", ")} を発行`
|
|
512
|
+
: `[6/6] カード: 発行なし` +
|
|
513
|
+
(cards.progress.length
|
|
514
|
+
? `(進行中: ${cards.progress.map((p) => `${p.name} ${p.progress}%`).join(", ")})`
|
|
515
|
+
: ""),
|
|
516
|
+
);
|
|
517
|
+
if (cards.unavailable.length > 0) {
|
|
518
|
+
console.log(
|
|
519
|
+
` 判定できないカード ${cards.unavailable.length} 枚: ${cards.unavailable.map((u) => u.id).join(", ")}`,
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
main().catch((e) => {
|
|
525
|
+
console.error(`エラー: ${e.message}`);
|
|
526
|
+
process.exit(1);
|
|
527
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# 実行記録スキーマ(events-file)
|
|
2
|
+
|
|
3
|
+
Claude Code 以外のエージェント — CTO のサーバで動き Telegram に常駐する Hermes など — は、
|
|
4
|
+
自分の実行記録を**この形の JSONL に吐き出すだけ**で日報を作れる。clubs-nippo 側に
|
|
5
|
+
エージェント専用のコードを足す必要はない。
|
|
6
|
+
|
|
7
|
+
## 形
|
|
8
|
+
|
|
9
|
+
1行1イベント。必須は `ts` `tool` `ok` の3つだけ。
|
|
10
|
+
|
|
11
|
+
```jsonl
|
|
12
|
+
{"ts":"2026-07-13T09:12:04+09:00","tool":"telegram.reply","ok":true,"summary":"問い合わせに一次回答","project":"サポート"}
|
|
13
|
+
{"ts":"2026-07-13T09:31:20+09:00","tool":"github.create_issue","ok":true,"summary":"バグ報告を issue 化","project":"clubsx"}
|
|
14
|
+
{"ts":"2026-07-13T10:02:11+09:00","tool":"deploy.staging","ok":false,"summary":"lint で失敗","project":"clubsx"}
|
|
15
|
+
{"ts":"2026-07-13T09:10:00+09:00","kind":"instruction","summary":"今日の問い合わせをさばいておいて"}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
| フィールド | 必須 | 意味 |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `ts` | ✅ | ISO8601。**タイムゾーン付きで書くこと**(無い場合は UTC と解釈される) |
|
|
21
|
+
| `tool` | ✅ | 実行したものの名前。日報では「ツール」として集計・表示される |
|
|
22
|
+
| `ok` | ✅ | 成否。`true` / `false`。**成功率はこれだけから出す** |
|
|
23
|
+
| `summary` | | 1行の要約。日報の本文を書く材料になる |
|
|
24
|
+
| `project` | | 案件名。日報のブロック分けに使う |
|
|
25
|
+
| `model` | | その実行に使ったモデル(例 `anthropic/claude-sonnet-4.5`)。OpenRouter 等で切り替えるエージェント向け |
|
|
26
|
+
| `kind` | | `"instruction"` ならオーナーからの指示文として扱う(`tool` / `ok` は不要) |
|
|
27
|
+
|
|
28
|
+
## model について(OpenRouter 等でモデルを切り替える場合)
|
|
29
|
+
|
|
30
|
+
**エージェント=身元、モデル=取り替え可能な部品。** Hermes が OpenRouter で5つのモデルを
|
|
31
|
+
行き来しても、パスポートを持つのは Hermes 1体で、日報も1通。**モデルは名義に一切影響しない**
|
|
32
|
+
(課金軸もエージェントであってモデルではない)。
|
|
33
|
+
|
|
34
|
+
`model` を記録すると、日報のプロフィール行が実際の内訳になる。
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
anthropic/claude-sonnet-4.5 ×3 · deepseek/deepseek-v3 ×3 · openai/gpt-5 ×1
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**静的なモデル名を1つ掲げるのは嘘になる。** 1日の半分を別のモデルがやったのに
|
|
41
|
+
「このモデルがやりました」と書くのは、ログと一致しないことを主張することであり、
|
|
42
|
+
✓ の規律に反する。`model` を記録しないエージェントは、モデル行に何も出ない
|
|
43
|
+
(無いものを在ることにしない)。
|
|
44
|
+
|
|
45
|
+
## ok を記録していない場合
|
|
46
|
+
|
|
47
|
+
`ok` の無いイベントは成功率の分母から外れ、成功率は `-` になる。数字を作らないための挙動で、
|
|
48
|
+
エラーではない。ただし成功率が出ない日報は「何回動いたか」しか言えないので、
|
|
49
|
+
成否を記録できるなら記録したほうがよい。
|
|
50
|
+
|
|
51
|
+
## 使い方
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
node bin/clubs-nippo.mjs \
|
|
55
|
+
--source=events-file \
|
|
56
|
+
--events=/path/to/hermes-events.jsonl \
|
|
57
|
+
--date=2026-07-13 \
|
|
58
|
+
--dry-run
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`.env` に書くなら:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
SOURCE=events-file
|
|
65
|
+
EVENTS_FILE=/path/to/hermes-events.jsonl
|
|
66
|
+
AGENT_NAME=Hermes
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 帰属について
|
|
70
|
+
|
|
71
|
+
`AGENT_NAME` が効くのは **`events-file` のときだけ**。`claude-code` を収集元にした日報は
|
|
72
|
+
必ず `Claude` 名義で出る(`AGENT_NAME=Hermes` を設定しても無視され、警告が出る)。
|
|
73
|
+
|
|
74
|
+
Claude Code のログから作った日報に Hermes の名前を付けられてしまうと、他のAIの作業を
|
|
75
|
+
自分の実績にできることになり、✓ の意味が崩れるため。日報のフッタには必ず
|
|
76
|
+
「収集元」が印字されるので、どこから作られたものかは後からでも分かる。
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{"ts":"2026-07-13T09:10:00+09:00","kind":"instruction","summary":"今日届いた問い合わせをさばいて、バグっぽいものは issue にしておいて"}
|
|
2
|
+
{"ts":"2026-07-13T09:12:04+09:00","tool":"telegram.reply","ok":true,"model":"anthropic/claude-sonnet-4.5","summary":"導入の相談に一次回答","project":"サポート"}
|
|
3
|
+
{"ts":"2026-07-13T09:18:41+09:00","tool":"telegram.reply","ok":true,"model":"anthropic/claude-sonnet-4.5","summary":"料金体系の質問に回答","project":"サポート"}
|
|
4
|
+
{"ts":"2026-07-13T09:31:20+09:00","tool":"github.create_issue","ok":true,"model":"deepseek/deepseek-v3","summary":"ログイン後にリダイレクトされない不具合を issue 化","project":"clubsx"}
|
|
5
|
+
{"ts":"2026-07-13T09:44:02+09:00","tool":"github.comment","ok":true,"model":"deepseek/deepseek-v3","summary":"再現手順を追記","project":"clubsx"}
|
|
6
|
+
{"ts":"2026-07-13T10:02:11+09:00","tool":"deploy.staging","ok":false,"model":"deepseek/deepseek-v3","summary":"lint に引っかかり中断","project":"clubsx"}
|
|
7
|
+
{"ts":"2026-07-13T10:09:55+09:00","tool":"deploy.staging","ok":true,"model":"openai/gpt-5","summary":"lint を直して再実行、成功","project":"clubsx"}
|
|
8
|
+
{"ts":"2026-07-13T14:20:30+09:00","tool":"telegram.reply","ok":true,"model":"anthropic/claude-sonnet-4.5","summary":"進捗を報告","project":"サポート"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clubs.place/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.18",
|
|
4
|
+
"description": "AI\u696d\u52d9\u65e5\u5831 \u2014 Claude Code \u306e\u5b9f\u884c\u30ed\u30b0\u304b\u3089\u524d\u65e5\u3076\u3093\u306e\u65e5\u5831\u30e1\u30fc\u30eb\u3092\u751f\u6210\u3057\u3066\u9001\u308b\uff08pull\u578b\u30fb\u672c\u6587\u751f\u6210\u306f\u30e6\u30fc\u30b6\u30fc\u5074LLM\u30fb\u751f\u30ed\u30b0\u306f\u7aef\u672b\u3092\u51fa\u306a\u3044\uff09",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"clubs-place": "bin/clubs-nippo",
|
|
9
|
+
"clubs-nippo": "bin/clubs-nippo",
|
|
10
|
+
"clubs-place-cli": "bin/clubs-nippo"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin",
|
|
14
|
+
"src",
|
|
15
|
+
"web",
|
|
16
|
+
"templates",
|
|
17
|
+
"examples",
|
|
18
|
+
"docs/events-schema.md",
|
|
19
|
+
".env.example",
|
|
20
|
+
"PROVENANCE.json"
|
|
21
|
+
],
|
|
22
|
+
"keywords": [
|
|
23
|
+
"clubs",
|
|
24
|
+
"nippo",
|
|
25
|
+
"ai-agent",
|
|
26
|
+
"daily-report",
|
|
27
|
+
"claude-code"
|
|
28
|
+
],
|
|
29
|
+
"homepage": "https://clubs.place",
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"nippo": "node ./bin/clubs-nippo.mjs",
|
|
35
|
+
"dry": "node ./bin/clubs-nippo.mjs --dry-run",
|
|
36
|
+
"demo": "node scripts/gen-preview.mjs && node scripts/bundle-demo.mjs",
|
|
37
|
+
"test": "node --test",
|
|
38
|
+
"prepublishOnly": "node scripts/gen-provenance.mjs"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@lemmaoracle/spec": "^0.0.37"
|
|
42
|
+
},
|
|
43
|
+
"optionalDependencies": {
|
|
44
|
+
"@lemmaoracle/sdk": "file:../../lemmaoracle/lemma/packages/sdk"
|
|
45
|
+
}
|
|
46
|
+
}
|