@ha7ch/mee7 0.1.0

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 (3) hide show
  1. package/README.md +18 -0
  2. package/bin.js +261 -0
  3. package/package.json +12 -0
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # @ha7ch/mee7
2
+
3
+ mee7 的主办方操作台 CLI。mee7 是 AI bouncer 活动平台:报名不是填表,guest 在微信里说服 bouncer 才能进;你(或你的 Claude Code)用这个 CLI 跑活动的全部运营。
4
+
5
+ ```bash
6
+ # 在 mee7.ha7ch.com/admin 生成 token,然后
7
+ npx @ha7ch/mee7 login <token>
8
+
9
+ npx @ha7ch/mee7 create --name "AI Builder 闭门局" --brief "最怕混进来卖课的" --seats 21
10
+ npx @ha7ch/mee7 test <event_id> "我最近在做..." # 发布前先和 bouncer 试聊几轮
11
+ npx @ha7ch/mee7 open <event_id> # 开放报名
12
+ npx @ha7ch/mee7 apps --review # 看待人工复核的报名
13
+ npx @ha7ch/mee7 transcript <event_id> <user_id> # 看完整对话和逐轮评分卡
14
+ npx @ha7ch/mee7 accept <event_id> <user_id> # 人工拍板
15
+ npx @ha7ch/mee7 help # 全部命令
16
+ ```
17
+
18
+ 接到 Claude Code 里用最顺:登录一次之后,直接对你的 Claude 说「帮我办一场 30 人的闭门局」。
package/bin.js ADDED
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env node
2
+ // mee7 CLI — the organizer's console. Zero deps, Node >= 18 (built-in fetch).
3
+ // Transport: the mee7 server's JSON-RPC endpoint (/api/mcp), one tools/call per command.
4
+ // Token: `mee7 login <token>` stores it in ~/.mee7/config.json; MEE7_TOKEN env overrides.
5
+
6
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
7
+ import { homedir } from "node:os";
8
+ import { join } from "node:path";
9
+
10
+ const API = process.env.MEE7_API || "https://mee7.ha7ch.com";
11
+ const CONFIG_DIR = join(homedir(), ".mee7");
12
+ const CONFIG_PATH = join(CONFIG_DIR, "config.json");
13
+
14
+ function loadToken() {
15
+ if (process.env.MEE7_TOKEN) return process.env.MEE7_TOKEN;
16
+ try {
17
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf8")).token || null;
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ function saveToken(token) {
24
+ mkdirSync(CONFIG_DIR, { recursive: true });
25
+ writeFileSync(CONFIG_PATH, JSON.stringify({ token, api: API }, null, 2) + "\n", { mode: 0o600 });
26
+ }
27
+
28
+ async function call(tool, args) {
29
+ const token = loadToken();
30
+ if (!token) die("还没登录。先在 mee7 后台(mee7.ha7ch.com/admin)生成 token,然后:mee7 login <token>");
31
+ const r = await fetch(`${API}/api/mcp`, {
32
+ method: "POST",
33
+ headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
34
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: tool, arguments: args } }),
35
+ });
36
+ const data = await r.json().catch(() => null);
37
+ if (!r.ok || !data) die(data?.error?.message || `HTTP ${r.status}`);
38
+ const result = data.result;
39
+ if (!result) die(data.error?.message || "服务端没有返回结果");
40
+ const text = (result.content || []).map((c) => c.text).join("\n");
41
+ if (result.isError) die(text);
42
+ return text;
43
+ }
44
+
45
+ function die(msg) {
46
+ console.error(`mee7: ${msg}`);
47
+ process.exit(1);
48
+ }
49
+
50
+ // Minimal flag parser: positional args + --key value (or --key for booleans).
51
+ function parseArgs(argv) {
52
+ const pos = [];
53
+ const flags = {};
54
+ for (let i = 0; i < argv.length; i++) {
55
+ const a = argv[i];
56
+ if (a.startsWith("--")) {
57
+ const key = a.slice(2);
58
+ const next = argv[i + 1];
59
+ if (next !== undefined && !next.startsWith("--")) {
60
+ flags[key] = next;
61
+ i++;
62
+ } else {
63
+ flags[key] = true;
64
+ }
65
+ } else {
66
+ pos.push(a);
67
+ }
68
+ }
69
+ return { pos, flags };
70
+ }
71
+
72
+ const HELP = `mee7 — AI bouncer 活动平台的主办方操作台(报名不是填表,是说服 bouncer)
73
+
74
+ 用法:npx @ha7ch/mee7 <命令> [参数]
75
+
76
+ login <token> 保存接入 token(mee7.ha7ch.com/admin 生成)
77
+ events 列出我的活动
78
+ create --name <名> [--brief 筛选侧重] [--address 地址] [--time 时间描述]
79
+ [--seats N] [--turns N] [--slug 自定义url]
80
+ 创建活动(draft 状态)
81
+ update <event_id> [--name --brief --address --time --seats --turns --status draft|open|closed]
82
+ open <event_id> 开放报名(status=open)
83
+ close <event_id> 结束活动(status=closed)
84
+ profiles 列出门规(跨活动复用的筛选配置)
85
+ profile --name <门规名> [--profile-id 更新已有] [--brand 品牌名]
86
+ [--lexicon "词1,词2,..."] [--note 一句话描述内行] [--opening 开场题]
87
+ [--strictness lenient|standard|strict] [--flags-off "investor,..."] [--flags-on "..."]
88
+ 创建/更新门规;再 update <event_id> --profile-id <id> 绑到活动
89
+ test <event_id> <说的话> [--session id]
90
+ 试聊 bouncer(draft 也能聊;续聊带 --session)
91
+ apps [--event id] [--stage 状态] [--review]
92
+ 看报名(--review 只看待人工复核)
93
+ transcript <event_id> <user_id> 看一个人的完整对话和逐轮评分卡
94
+ accept <event_id> <user_id> 人工改判:通过(先确认人选!)
95
+ waitlist <event_id> <user_id> 人工改判:候补
96
+ reject <event_id> <user_id> 人工改判:婉拒
97
+ send <user_id> <内容> [--event id] 经微信给 guest 发消息(统一发函)
98
+ cards <event_id> 拿落地页 / OG 卡 / 朋友圈导出卡链接
99
+ recap <event_id> --summary <一段话> [--quote 金句]
100
+ 写活动复盘(落地页转战报)
101
+
102
+ 环境变量:MEE7_TOKEN 覆盖已存 token;MEE7_API 覆盖服务地址。`;
103
+
104
+ function num(v) {
105
+ const n = Number(v);
106
+ return Number.isFinite(n) ? n : undefined;
107
+ }
108
+
109
+ async function main() {
110
+ const [cmd, ...rest] = process.argv.slice(2);
111
+ const { pos, flags } = parseArgs(rest);
112
+
113
+ switch (cmd) {
114
+ case "login": {
115
+ const token = pos[0];
116
+ if (!token) die("用法:mee7 login <token>");
117
+ saveToken(token);
118
+ console.log(await call("list_events", {}).then(() => "登录成功,token 已保存到 ~/.mee7/config.json"));
119
+ return;
120
+ }
121
+ case "events":
122
+ console.log(await call("list_events", {}));
123
+ return;
124
+ case "create": {
125
+ if (!flags.name) die("--name 必填");
126
+ console.log(
127
+ await call("create_event", {
128
+ name: String(flags.name),
129
+ ...(flags.brief ? { brief: String(flags.brief) } : {}),
130
+ ...(flags.address ? { address: String(flags.address) } : {}),
131
+ ...(flags.time ? { time_info: String(flags.time) } : {}),
132
+ ...(num(flags.seats) !== undefined ? { seat_total: num(flags.seats) } : {}),
133
+ ...(num(flags.turns) !== undefined ? { max_turns: num(flags.turns) } : {}),
134
+ ...(flags.slug ? { slug: String(flags.slug) } : {}),
135
+ }),
136
+ );
137
+ return;
138
+ }
139
+ case "update":
140
+ case "open":
141
+ case "close": {
142
+ const eventId = pos[0];
143
+ if (!eventId) die(`用法:mee7 ${cmd} <event_id>`);
144
+ const patch = { event_id: eventId };
145
+ if (cmd === "open") patch.status = "open";
146
+ if (cmd === "close") patch.status = "closed";
147
+ if (flags.status) patch.status = String(flags.status);
148
+ if (flags.name) patch.name = String(flags.name);
149
+ if (flags.brief) patch.brief = String(flags.brief);
150
+ if (flags.address) patch.address = String(flags.address);
151
+ if (flags.time) patch.time_info = String(flags.time);
152
+ if (flags["profile-id"]) patch.profile_id = String(flags["profile-id"]);
153
+ if (num(flags.seats) !== undefined) patch.seat_total = num(flags.seats);
154
+ if (num(flags.turns) !== undefined) patch.max_turns = num(flags.turns);
155
+ console.log(await call("update_event", patch));
156
+ return;
157
+ }
158
+ case "profiles":
159
+ console.log(await call("list_screening_profiles", {}));
160
+ return;
161
+ case "profile": {
162
+ if (!flags.name) die("--name 必填");
163
+ const args = { name: String(flags.name) };
164
+ if (flags["profile-id"]) args.profile_id = String(flags["profile-id"]);
165
+ if (flags.brand) args.brand_name = String(flags.brand);
166
+ if (flags.lexicon)
167
+ args.lexicon = String(flags.lexicon)
168
+ .split(/[,,]/)
169
+ .map((s) => s.trim())
170
+ .filter(Boolean);
171
+ if (flags.note) args.insider_note = String(flags.note);
172
+ if (flags.strictness) args.strictness = String(flags.strictness);
173
+ if (flags.opening) args.opening_question = String(flags.opening);
174
+ const redFlags = {};
175
+ for (const k of String(flags["flags-off"] || "").split(/[,,]/).map((s) => s.trim()).filter(Boolean))
176
+ redFlags[k] = false;
177
+ for (const k of String(flags["flags-on"] || "").split(/[,,]/).map((s) => s.trim()).filter(Boolean))
178
+ redFlags[k] = true;
179
+ if (Object.keys(redFlags).length > 0) args.red_flags = redFlags;
180
+ console.log(await call("set_screening_profile", args));
181
+ return;
182
+ }
183
+ case "test": {
184
+ const [eventId, ...words] = pos;
185
+ const text = words.join(" ");
186
+ if (!eventId || !text) die("用法:mee7 test <event_id> <说的话> [--session id]");
187
+ console.log(
188
+ await call("test_screening", {
189
+ event_id: eventId,
190
+ text,
191
+ ...(flags.session ? { session_id: String(flags.session) } : {}),
192
+ }),
193
+ );
194
+ return;
195
+ }
196
+ case "apps":
197
+ console.log(
198
+ await call("list_applications", {
199
+ ...(flags.event ? { event_id: String(flags.event) } : {}),
200
+ ...(flags.stage ? { stage: String(flags.stage) } : {}),
201
+ ...(flags.review ? { needs_review_only: true } : {}),
202
+ }),
203
+ );
204
+ return;
205
+ case "transcript": {
206
+ const [eventId, userId] = pos;
207
+ if (!eventId || !userId) die("用法:mee7 transcript <event_id> <user_id>");
208
+ console.log(await call("get_transcript", { event_id: eventId, user_id: userId }));
209
+ return;
210
+ }
211
+ case "accept":
212
+ case "waitlist":
213
+ case "reject": {
214
+ const [eventId, userId] = pos;
215
+ if (!eventId || !userId) die(`用法:mee7 ${cmd} <event_id> <user_id>`);
216
+ console.log(await call("override_decision", { event_id: eventId, user_id: userId, action: cmd }));
217
+ return;
218
+ }
219
+ case "send": {
220
+ const [userId, ...words] = pos;
221
+ const text = words.join(" ");
222
+ if (!userId || !text) die("用法:mee7 send <user_id> <内容> [--event id]");
223
+ console.log(
224
+ await call("send_message", {
225
+ user_id: userId,
226
+ text,
227
+ ...(flags.event ? { event_id: String(flags.event) } : {}),
228
+ }),
229
+ );
230
+ return;
231
+ }
232
+ case "cards": {
233
+ const eventId = pos[0];
234
+ if (!eventId) die("用法:mee7 cards <event_id>");
235
+ console.log(await call("generate_cards", { event_id: eventId }));
236
+ return;
237
+ }
238
+ case "recap": {
239
+ const eventId = pos[0];
240
+ if (!eventId || !flags.summary) die("用法:mee7 recap <event_id> --summary <一段话> [--quote 金句]");
241
+ console.log(
242
+ await call("write_recap", {
243
+ event_id: eventId,
244
+ summary: String(flags.summary),
245
+ ...(flags.quote ? { quote: String(flags.quote) } : {}),
246
+ }),
247
+ );
248
+ return;
249
+ }
250
+ case "help":
251
+ case undefined:
252
+ case "--help":
253
+ case "-h":
254
+ console.log(HELP);
255
+ return;
256
+ default:
257
+ die(`未知命令「${cmd}」。mee7 help 看全部命令。`);
258
+ }
259
+ }
260
+
261
+ main().catch((e) => die(e instanceof Error ? e.message : String(e)));
package/package.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "@ha7ch/mee7",
3
+ "version": "0.1.0",
4
+ "description": "mee7 CLI — AI bouncer活动平台的主办方操作台。报名不是填表,是说服 bouncer 。",
5
+ "bin": { "mee7": "./bin.js" },
6
+ "type": "module",
7
+ "engines": { "node": ">=18" },
8
+ "files": ["bin.js", "README.md"],
9
+ "keywords": ["mee7", "ha7ch", "events", "ai-native"],
10
+ "license": "UNLICENSED",
11
+ "publishConfig": { "access": "public" }
12
+ }