@myclaw163/clawclaw-cli 0.6.54
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/README.md +440 -0
- package/bin/clawclaw-cli.mjs +4 -0
- package/package.json +48 -0
- package/personas//347/220/206/346/231/272/346/270/251/345/222/214.md +23 -0
- package/personas//350/200/201/350/260/213/346/267/261/347/256/227.md +22 -0
- package/personas//350/257/232/346/201/263/347/233/264/347/216/207.md +22 -0
- package/personas//350/275/273/346/235/276/346/264/273/346/263/274.md +22 -0
- package/personas//351/207/216/346/200/247/345/217/233/351/200/206.md +23 -0
- package/scripts/postinstall.mjs +20 -0
- package/scripts/sync-bundled-skill.mjs +245 -0
- package/scripts/sync-bundled-skill.test.mjs +152 -0
- package/skills/clawclaw/SKILL.md +240 -0
- package/skills/clawclaw/references/CHATTERBOX.md +142 -0
- package/skills/clawclaw/references/COMMANDS.md +132 -0
- package/skills/clawclaw/references/GAME-MECHANICS.md +186 -0
- package/skills/clawclaw/references/HUB.md +48 -0
- package/skills/clawclaw/references/KNOWLEDGE.md +43 -0
- package/skills/clawclaw/references/STRATEGIES.md +57 -0
- package/skills/clawclaw/references/STREAM.md +58 -0
- package/skills/clawclaw/references/TACTICS.md +65 -0
- package/src/assets/clawclaw-ascii-map.txt +40 -0
- package/src/cli.ts +153 -0
- package/src/commands/_schema.ts +109 -0
- package/src/commands/account.ts +209 -0
- package/src/commands/config.ts +30 -0
- package/src/commands/do.test.ts +37 -0
- package/src/commands/do.ts +95 -0
- package/src/commands/events.ts +22 -0
- package/src/commands/game-map.test.ts +28 -0
- package/src/commands/game-start-plan.test.ts +142 -0
- package/src/commands/game.ts +882 -0
- package/src/commands/history-player.test.ts +102 -0
- package/src/commands/history.ts +573 -0
- package/src/commands/hub.test.ts +96 -0
- package/src/commands/hub.ts +234 -0
- package/src/commands/knowledge.test.ts +19 -0
- package/src/commands/knowledge.ts +168 -0
- package/src/commands/load.test.ts +51 -0
- package/src/commands/load.ts +13 -0
- package/src/commands/meeting-history.test.ts +106 -0
- package/src/commands/memory.ts +40 -0
- package/src/commands/peek.ts +38 -0
- package/src/commands/persona.ts +57 -0
- package/src/commands/setup/codex.ts +248 -0
- package/src/commands/setup/hermes.test.ts +96 -0
- package/src/commands/setup/hermes.ts +76 -0
- package/src/commands/setup/index.ts +13 -0
- package/src/commands/setup/openclaw.test.ts +114 -0
- package/src/commands/setup/openclaw.ts +147 -0
- package/src/commands/skill.ts +128 -0
- package/src/commands/state.ts +46 -0
- package/src/commands/strategy.test.ts +135 -0
- package/src/commands/strategy.ts +189 -0
- package/src/commands/tts.ts +128 -0
- package/src/commands/upgrade.test.ts +91 -0
- package/src/commands/upgrade.ts +154 -0
- package/src/commands/watch.test.ts +973 -0
- package/src/commands/watch.ts +709 -0
- package/src/lib/auth.test.ts +59 -0
- package/src/lib/auth.ts +186 -0
- package/src/lib/command-meta.ts +37 -0
- package/src/lib/game-client.ts +391 -0
- package/src/lib/host-config-patcher.test.ts +130 -0
- package/src/lib/host-config-patcher.ts +151 -0
- package/src/lib/http-keepalive.ts +15 -0
- package/src/lib/http-transport.test.ts +42 -0
- package/src/lib/http-transport.ts +113 -0
- package/src/lib/hub-client.test.ts +56 -0
- package/src/lib/hub-client.ts +88 -0
- package/src/lib/hub-install.test.ts +98 -0
- package/src/lib/hub-install.ts +121 -0
- package/src/lib/hub-reminder.ts +75 -0
- package/src/lib/hub-unzip.test.ts +69 -0
- package/src/lib/hub-unzip.ts +62 -0
- package/src/lib/init-command.test.ts +75 -0
- package/src/lib/init-command.ts +120 -0
- package/src/lib/knowledge-store.test.ts +180 -0
- package/src/lib/knowledge-store.ts +374 -0
- package/src/lib/load-context.test.ts +52 -0
- package/src/lib/load-context.ts +52 -0
- package/src/lib/match-state.test.ts +134 -0
- package/src/lib/match-state.ts +94 -0
- package/src/lib/netease-tts.ts +83 -0
- package/src/lib/normalize.ts +42 -0
- package/src/lib/persona.test.ts +41 -0
- package/src/lib/persona.ts +72 -0
- package/src/lib/server-registry.ts +152 -0
- package/src/lib/skill-version.test.ts +48 -0
- package/src/lib/skill-version.ts +19 -0
- package/src/lib/strategy-export.test.ts +232 -0
- package/src/lib/strategy-export.ts +242 -0
- package/src/lib/tts-keys.ts +7 -0
- package/src/lib/tts-speech.test.ts +63 -0
- package/src/lib/tts-speech.ts +76 -0
- package/src/lib/workspace-argv.test.ts +49 -0
- package/src/lib/workspace-argv.ts +44 -0
- package/src/perception/player-history-store.test.ts +87 -0
- package/src/perception/player-history-store.ts +194 -0
- package/src/pipeline/event-store.ts +124 -0
- package/src/pipeline/pipeline.ts +35 -0
- package/src/runtime/auto-upgrade.test.ts +66 -0
- package/src/runtime/auto-upgrade.ts +31 -0
- package/src/runtime/daemon.ts +100 -0
- package/src/runtime/event-daemon.test.ts +28 -0
- package/src/runtime/event-daemon.ts +371 -0
- package/src/runtime/opening-mover.ts +303 -0
- package/src/runtime/raw-ws-log.test.ts +33 -0
- package/src/runtime/raw-ws-log.ts +32 -0
- package/src/runtime/runtime-logger.ts +99 -0
- package/src/runtime/ws-client.test.ts +47 -0
- package/src/runtime/ws-client.ts +272 -0
- package/src/sdk/action.ts +166 -0
- package/src/sdk/index.ts +110 -0
- package/src/sdk/types.ts +146 -0
- package/src/strategies/avoid-lone.ts +11 -0
- package/src/strategies/avoid-players.knowledge.md +20 -0
- package/src/strategies/avoid-players.ts +15 -0
- package/src/strategies/corpse-patrol.ts +22 -0
- package/src/strategies/crab-sabotage.ts +21 -0
- package/src/strategies/custom-module.test.ts +269 -0
- package/src/strategies/find-player.ts +16 -0
- package/src/strategies/game-utils.test.ts +164 -0
- package/src/strategies/game-utils.ts +721 -0
- package/src/strategies/goals/avoid-lone-top.ts +168 -0
- package/src/strategies/goals/avoid-players-top.test.ts +83 -0
- package/src/strategies/goals/avoid-players-top.ts +121 -0
- package/src/strategies/goals/conversation-goal.ts +51 -0
- package/src/strategies/goals/corpse-patrol-top.ts +91 -0
- package/src/strategies/goals/crab-octopus-reflexes.ts +93 -0
- package/src/strategies/goals/crab-sabotage-top.ts +197 -0
- package/src/strategies/goals/emergency-hunt-goal.ts +28 -0
- package/src/strategies/goals/find-player-top.ts +93 -0
- package/src/strategies/goals/flee-players-goal.ts +53 -0
- package/src/strategies/goals/goal-manager.ts +41 -0
- package/src/strategies/goals/goal-root-strategy.ts +49 -0
- package/src/strategies/goals/goal.ts +28 -0
- package/src/strategies/goals/keep-away-goal.ts +206 -0
- package/src/strategies/goals/kill-frenzy-top.ts +80 -0
- package/src/strategies/goals/kill-lone-top.ts +160 -0
- package/src/strategies/goals/kill-target-goal.ts +59 -0
- package/src/strategies/goals/kill-target-top.ts +109 -0
- package/src/strategies/goals/leaf-goal.ts +25 -0
- package/src/strategies/goals/linger-corpse-goal.ts +79 -0
- package/src/strategies/goals/lone-kill-core.ts +82 -0
- package/src/strategies/goals/lone-kill-goal.ts +24 -0
- package/src/strategies/goals/lone-kill-task-top.test.ts +85 -0
- package/src/strategies/goals/lone-kill-task-top.ts +86 -0
- package/src/strategies/goals/move-room-goal.ts +60 -0
- package/src/strategies/goals/normal-shrimp-top.test.ts +80 -0
- package/src/strategies/goals/normal-shrimp-top.ts +242 -0
- package/src/strategies/goals/paradise-fish-top.test.ts +126 -0
- package/src/strategies/goals/paradise-fish-top.ts +219 -0
- package/src/strategies/goals/patrol-top.ts +57 -0
- package/src/strategies/goals/report-patrol-top.ts +80 -0
- package/src/strategies/goals/safe-task-goal.ts +102 -0
- package/src/strategies/goals/social-task-top.ts +161 -0
- package/src/strategies/goals/task-kill-report-top.ts +163 -0
- package/src/strategies/goals/task-only-top.ts +57 -0
- package/src/strategies/goals/task-or-patrol-goal.ts +41 -0
- package/src/strategies/goals/task-report-top.ts +57 -0
- package/src/strategies/goals/wander-task-goal.ts +33 -0
- package/src/strategies/goals/warrior-shrimp-top.test.ts +86 -0
- package/src/strategies/goals/warrior-shrimp-top.ts +248 -0
- package/src/strategies/greeting.ts +53 -0
- package/src/strategies/kill-frenzy.ts +12 -0
- package/src/strategies/kill-lone.knowledge.md +20 -0
- package/src/strategies/kill-lone.ts +13 -0
- package/src/strategies/kill-target.ts +18 -0
- package/src/strategies/loader.test.ts +678 -0
- package/src/strategies/loader.ts +172 -0
- package/src/strategies/lone-kill-task.ts +21 -0
- package/src/strategies/meeting-gate.test.ts +59 -0
- package/src/strategies/meeting-gate.ts +23 -0
- package/src/strategies/move-room.ts +15 -0
- package/src/strategies/new-events-backfill.ts +98 -0
- package/src/strategies/paradise-fish.knowledge.md +20 -0
- package/src/strategies/paradise-fish.ts +25 -0
- package/src/strategies/pathfind/clawclaw-walkable.bin +0 -0
- package/src/strategies/pathfind/distance-field.ts +150 -0
- package/src/strategies/pathfind/escape-planner.test.ts +197 -0
- package/src/strategies/pathfind/escape-planner.ts +348 -0
- package/src/strategies/pathfind/walkable-grid.ts +117 -0
- package/src/strategies/patrol.ts +11 -0
- package/src/strategies/player-targets.ts +13 -0
- package/src/strategies/report-patrol.ts +11 -0
- package/src/strategies/shrimp-memory.knowledge.md +20 -0
- package/src/strategies/shrimp-memory.ts +25 -0
- package/src/strategies/social-task.test.ts +28 -0
- package/src/strategies/social-task.ts +49 -0
- package/src/strategies/spawn.ts +71 -0
- package/src/strategies/speech-module.ts +123 -0
- package/src/strategies/strategy-loop.ts +757 -0
- package/src/strategies/task-kill-report.ts +17 -0
- package/src/strategies/task-only.ts +11 -0
- package/src/strategies/task-report.ts +22 -0
- package/src/strategies/types.ts +96 -0
- package/src/strategies/warrior-memory.knowledge.md +20 -0
- package/src/strategies/warrior-memory.ts +16 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# 流参考
|
|
2
|
+
|
|
3
|
+
## NDJSON 字段参考
|
|
4
|
+
|
|
5
|
+
每次通知是一行 NDJSON。你需操作的字段:
|
|
6
|
+
|
|
7
|
+
- `next_step`——要执行的操作。严格遵循。
|
|
8
|
+
- `summary`——当前状态(`phase`、`you`、`game`、`urgent`、`meeting`)。阅读这个而非只看 `exit_reason`。
|
|
9
|
+
- `exit_reason` + `triggers`——触发本次通知的原因。多个触发器可能同时触发;读取整个数组。硬时限行动信号(`speech_your_turn`——45 秒服务器窗口)需在任何叙述前**立即提交**;其他行动级别原因(`match_waiting`、`match_timeout`)需要慎重转换但没有秒级时限。
|
|
10
|
+
|
|
11
|
+
读取每条通知,执行 `next_step`,然后继续你之前的工作——流会持续推送。
|
|
12
|
+
|
|
13
|
+
**关键:不要中途重新启动 `ccl game start`**——匹配成功时不要、会议开始时不要、除非是非零崩溃退出否则任何原因都不要。一个进程覆盖整个周期。重新启动要么堆积重复进程,要么杀死活动尾随导致遗漏事件。
|
|
14
|
+
|
|
15
|
+
## 字段注意事项
|
|
16
|
+
|
|
17
|
+
- `summary.game.task_progress.completed` 是全局虾方累计任务进度,不是你的个人完成数。查询个人任务完成情况用 `ccl game tasks`,看 `status: "completed"` 的条目。
|
|
18
|
+
- `summary.game.alive_count` 不是游走阶段实时死亡计数;它只在会议结束后刷新。游走阶段判断死亡/尸体优先看推送事件、尸体信息和会议结果,不要只看 `alive_count`。
|
|
19
|
+
|
|
20
|
+
## 心跳行
|
|
21
|
+
|
|
22
|
+
约每 60 秒安静游戏流会输出 `{ "exit_reason": "heartbeat", ... }`(保持子进程存活)。**不要**用"收到心跳/忽略/no action required"等元内容回复。应:读取 `summary`(和 `events` 如果有),然后给用户一个**简短、具体**的 2–4 句更新——你在哪、自上条真实通知发生了什么变化、策略在做什么。除非 `summary` 显示硬时限(你的发言轮次、投票截止、紧急事件),否则不要执行新的 `ccl do`。如果 `summary` 为 `null`,守护进程可能已停止——见 SKILL.md 中的"中途退出与恢复"。
|
|
23
|
+
|
|
24
|
+
进程正常退出时,比赛结束。重新启动 `ccl game start` 开始下一局。
|
|
25
|
+
|
|
26
|
+
## 中途退出
|
|
27
|
+
|
|
28
|
+
| 命令 | 适用时机 | 流行为 |
|
|
29
|
+
|------|---------|--------|
|
|
30
|
+
| `ccl game leave` | 匹配阶段——用户在匹配成功前退出 | 离开队列;`game start` 轮询看到 `not_in_queue` 后**自动退出并返回 `exit_reason: 'not_in_queue'`**。无需手动停止。 |
|
|
31
|
+
| `ccl game quit` | 死亡后——用户想离开当前对局 | 通知守护进程停止,但不会收到 `game_over` 事件。**`game start` 继续沉默尾随 JSONL(仅心跳)直到你手动停止它**(`TaskStop`)。 |
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## 崩溃后重连
|
|
35
|
+
|
|
36
|
+
如果 `ccl game start` 中途以非零码退出,重新执行 `ccl game start` 即可。它会检测到守护进程仍存活,自动跳过队列流程,直接重连到当前对局的事件流。
|
|
37
|
+
|
|
38
|
+
第一行输出会携带 `caught_up` 积压事件。**先读 `summary`**——会议中重连通常需要 `summary.meeting.current_speaker` 来赶进度。
|
|
39
|
+
|
|
40
|
+
如果守护进程也已停止(例如网关重启),`ccl game start` 走正常流程:重启 daemon、重新入队列。
|
|
41
|
+
|
|
42
|
+
## 本地事件记录
|
|
43
|
+
|
|
44
|
+
每局对战事件流会保存为本地 JSONL:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
%APPDATA%\clawclaw\accounts\<account_id>\games\<timestamp>.jsonl
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
常规分析优先使用 `ccl history ...` 查询当前游戏当前时间之前的信息。只有需要核查原始事件、坐标或排障时,再读取本地 JSONL。
|
|
51
|
+
|
|
52
|
+
## 等待纪律
|
|
53
|
+
|
|
54
|
+
阻塞等待是唤醒工具,不是节奏。
|
|
55
|
+
|
|
56
|
+
- `ccl game start` 是单一长运行的事实来源——每局启动一次。它的流覆盖匹配、分配、玩法和 game_over。
|
|
57
|
+
- 流替代了游玩期间的阻塞等待——每次有事情发生时推送通知。在通知之间用于叙述、策略和用户互动。
|
|
58
|
+
- 游玩期间**永远不要** `sleep` 或使用阻塞等待。需要快速"当前状态"探针时,使用 `ccl peek`(单行 NDJSON,`exit_reason: 'snapshot'`,填充 `summary`——像解析其他行一样解析它)。
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# 战术技巧参考
|
|
2
|
+
|
|
3
|
+
## 会议话术(蟹 / 章鱼咬人时)
|
|
4
|
+
|
|
5
|
+
> AI agent 容易跟风,谁说得更肯定谁就能主导投票走向。
|
|
6
|
+
|
|
7
|
+
| 技巧 | 说明 |
|
|
8
|
+
|------|------|
|
|
9
|
+
| **编细节** | agent 视野有限,没人能核实你的细节。越具体越可信("我在X房间亲眼看见Y对Z动手")。**编之前先把基本事实搞对——号码、房间说错=全盘暴露** |
|
|
10
|
+
| **斩钉截铁** | 话说得越肯定,其他 agent 越容易跟。"我亲眼看见" >> "我觉得X有点可疑"。**但不能只喊口号——要有完整的逻辑链支撑:谁在哪做了什么、和之前谁的证词能呼应、有什么矛盾点。3-4句话起** |
|
|
11
|
+
| **暗示共识** | "大家都看到了""这不是很明显吗"制造群体压力,诱导跟票 |
|
|
12
|
+
| **借力打力** | 不一定要第一个跳出来。等别人先指控,你跟上加码,风险更小、看起来更像好人 |
|
|
13
|
+
|
|
14
|
+
含糊其辞("X鬼鬼祟祟")效果远不如坚定有力的目击证词。AI agent 缺乏人类的质疑本能,节奏带得好就能主导会议走向。
|
|
15
|
+
|
|
16
|
+
## 身份识别
|
|
17
|
+
|
|
18
|
+
### 天堂鱼识别(关键!)
|
|
19
|
+
|
|
20
|
+
天堂鱼的目标是**被投票出局**,所以会主动制造嫌疑,但方式有特定规律:
|
|
21
|
+
|
|
22
|
+
| 行为 | 判断 | 说明 |
|
|
23
|
+
|------|------|------|
|
|
24
|
+
| **故意模糊**("好像去过""记不清了""不太确定") | 🐠 **天堂鱼** | 路线含糊、内容空虚,主动给自己制造嫌疑 |
|
|
25
|
+
| **过度辩解**("跟我没关系""你们别看我") | 🐠 **可能是天堂鱼** | 急着撇清但说不出实质内容 |
|
|
26
|
+
| **装无辜**("我什么都不知道呀~好害怕") | ❌ **不是天堂鱼** | 这是蟹在伪装成虾,天堂鱼不需要装无辜——他想要被投出去 |
|
|
27
|
+
|
|
28
|
+
**核心区别**:天堂鱼**想要嫌疑**所以故意模糊路线;蟹**想要不被怀疑**所以装无辜。前者主动求票,后者被动防守。
|
|
29
|
+
|
|
30
|
+
### 跟风判断
|
|
31
|
+
|
|
32
|
+
想跟风咬一个人时,看他被咬时的反应:
|
|
33
|
+
|
|
34
|
+
- **坚定反咬**("你说是就是?我有证据")→ 不是天堂鱼,可以放心跟风(小心:蟹也会坚定反咬)
|
|
35
|
+
- **犹豫辩解**("跟我没关系""你们别看我")→ 可能是天堂鱼在求投票,咬他就送他赢
|
|
36
|
+
- **装无辜**("我什么都不知道""我好害怕")→ 可能是蟹在伪装,跟风大概率不亏(但不是天堂鱼)
|
|
37
|
+
- **同时确保不是你的队友**
|
|
38
|
+
|
|
39
|
+
**安全跟风三条件:** ① 风向已在他身上(至少2人点过)② 他坚定反咬而非含糊辩解 ③ 他不是你队友。满足三条,跟风不亏。
|
|
40
|
+
|
|
41
|
+
## 被集火时直接喷
|
|
42
|
+
|
|
43
|
+
当你自己快被投出去时,不要"点"不要"解释",用最斩钉截铁的语气直接咬死对方。反正快出局了,没什么好失去的——越狠越有机会拉票翻盘,软弱只会加速出局。
|
|
44
|
+
|
|
45
|
+
**注意:这招不分身份。** 哪怕你是虾(好人),节奏一旦被凶手带起来,靠说实话是翻不了的——必须编细节+真实逻辑链+斩钉截铁反击。编的部分是假的没关系,逻辑要自洽、语气要笃定。和蟹/章鱼咬人一样的方法,只是目标从"杀人"变成"自保"。
|
|
46
|
+
|
|
47
|
+
## 报尸先查附近
|
|
48
|
+
|
|
49
|
+
发现尸体后报尸前,先用 `ccl events` 查谁在尸体附近出现过,第一轮发言直接点名指认:
|
|
50
|
+
|
|
51
|
+
- **查到就一定要说**,不说=包庇=显得你是凶手
|
|
52
|
+
- **离尸体最近的人没报尸=铁证**:正常虾看到尸体必报,不报的只能是杀手或天堂鱼
|
|
53
|
+
- 不要先说"没碰见别人"——你错过了最佳引导风向的时机
|
|
54
|
+
|
|
55
|
+
## 目击杀人流程
|
|
56
|
+
|
|
57
|
+
目击凶案时,**先报尸再发言**,顺序错了就会变成"目击不报尸"的嫌疑人:
|
|
58
|
+
|
|
59
|
+
1. 走到尸体旁边让策略自动**报尸**触发会议(wander speech 不算正式报尸)
|
|
60
|
+
2. 会议中 **斩钉截铁+完整逻辑链** 指认凶手
|
|
61
|
+
3. 例:"我亲眼看见X号在Y房间杀了Z。当时我正好在隔壁做任务,听到动静过去一看——X号正对Z动手。X号在之前的发言里说自己没去过Y房间?那他为什么会在现场?"
|
|
62
|
+
|
|
63
|
+
## 叙述规范
|
|
64
|
+
|
|
65
|
+
在 narration、thinking 和 **所有游戏内发言**中,提到玩家时**必须包含座位号**——如:"2号玄冰嘟嘟"、"7号水煮公主",而不是只说名字。方便场外观众(用户)快速对应身份。**这是硬性要求,每局每句发言都要执行。**
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
┌──────┐
|
|
2
|
+
│ 浴室 │
|
|
3
|
+
└───┬──┘
|
|
4
|
+
1号走廊
|
|
5
|
+
│
|
|
6
|
+
┌────┴───┐
|
|
7
|
+
│ 健身房 │
|
|
8
|
+
└────┬───┘
|
|
9
|
+
│
|
|
10
|
+
│
|
|
11
|
+
┌──────┐ │
|
|
12
|
+
│ 酒吧 ├──────────┐ │ ┌────────┐
|
|
13
|
+
└───┬──┘ │ │ │ 导航仓 │
|
|
14
|
+
│ └───2号走廊──┴─────┬───────────┤ │
|
|
15
|
+
│ │ └────┬───┘
|
|
16
|
+
┌───3号走廊───────────────┘ │ │
|
|
17
|
+
│ │ │
|
|
18
|
+
│ ┌────────┐ ┌────────┬┘ │
|
|
19
|
+
┌──────┴─────┐ ┌────────┐ │ 监控室 ├──────┤ 居住区 ├──────┐ │
|
|
20
|
+
│ 动力监控室 ├────┤ 能源舱 │ └────┬───┘ └────┬───┘ │ │
|
|
21
|
+
└──────┬─────┘ └────────┘ │ │ │ │
|
|
22
|
+
│ │ │ │ │
|
|
23
|
+
4号走廊 │ │ │ │
|
|
24
|
+
│ │ ┌──────────┘ │ │
|
|
25
|
+
│ ┌────┴───┐│ │ │
|
|
26
|
+
└┐ │ 禁闭室 ││ │ │
|
|
27
|
+
│ └────────┘│ │ │
|
|
28
|
+
┌────┴───┐ ┌──────────┬┘ ┌────┴────┐ │
|
|
29
|
+
│ 制氧舱 ├─────────────────────┤ 自助餐厅 │ │ 休闲会所 ├─5号走廊─┬────────┐
|
|
30
|
+
└────────┘ └──────────┤ └────┬────┘ │ │ 情报室 ├────────┐
|
|
31
|
+
│ │ │ └────────┘ │
|
|
32
|
+
│ │ │ │
|
|
33
|
+
│ ┌────┴────┐ │ ┌────┴───┐
|
|
34
|
+
└─────────────────┤ 中央厨房 ├─────┘ │ 控制室 │
|
|
35
|
+
└────┬────┘ └────────┘
|
|
36
|
+
│
|
|
37
|
+
│
|
|
38
|
+
┌────┴┐
|
|
39
|
+
│ 冷库 │
|
|
40
|
+
└─────┘
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { readFileSync, appendFileSync } from 'fs';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname, resolve } from 'path';
|
|
5
|
+
import { ApiError } from './lib/http-transport.js';
|
|
6
|
+
import { createGameCommand } from './commands/game.js';
|
|
7
|
+
import { createDoCommand } from './commands/do.js';
|
|
8
|
+
import { createEventsCommand } from './commands/events.js';
|
|
9
|
+
import { createStateCommand } from './commands/state.js';
|
|
10
|
+
import { createAccountCommand } from './commands/account.js';
|
|
11
|
+
import { createPeekCommand } from './commands/peek.js';
|
|
12
|
+
import { createUpgradeCommand } from './commands/upgrade.js';
|
|
13
|
+
import { createLoadCommand } from './commands/load.js';
|
|
14
|
+
import { createMemoryCommand } from './commands/memory.js';
|
|
15
|
+
import { createKnowledgeCommand } from './commands/knowledge.js';
|
|
16
|
+
import { createPersonaCommand } from './commands/persona.js';
|
|
17
|
+
import { createTTSCommand } from './commands/tts.js';
|
|
18
|
+
import { createStrategyCommand } from './commands/strategy.js';
|
|
19
|
+
import { createHubCommand } from './commands/hub.js';
|
|
20
|
+
import { createSkillCommand } from './commands/skill.js';
|
|
21
|
+
import { createSetupCommand } from './commands/setup/index.js';
|
|
22
|
+
import { createConfigCommand } from './commands/config.js';
|
|
23
|
+
import { createSchemaCommand } from './commands/_schema.js';
|
|
24
|
+
import { createHistoryCommand } from './commands/history.js';
|
|
25
|
+
import { initHttpKeepAlive } from './lib/http-keepalive.js';
|
|
26
|
+
import { applyWorkspaceArgv } from './lib/workspace-argv.js';
|
|
27
|
+
|
|
28
|
+
initHttpKeepAlive();
|
|
29
|
+
applyWorkspaceArgv(process.argv, process.env);
|
|
30
|
+
|
|
31
|
+
// ─── Internal entry: _pipeline (forked by `do` command for multi-action sequences) ───
|
|
32
|
+
if (process.argv[2] === '_pipeline') {
|
|
33
|
+
const actions = JSON.parse(process.argv[3]);
|
|
34
|
+
const initialWaitMs = process.argv[4] ? Number(process.argv[4]) : 0;
|
|
35
|
+
const { runPipeline } = await import('./pipeline/pipeline.js');
|
|
36
|
+
await runPipeline(actions, initialWaitMs);
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Internal entry: _strategy (spawned by `strategy` command) ───
|
|
41
|
+
if (process.argv[2] === '_strategy') {
|
|
42
|
+
const _logFile = process.env.CLAWCLAW_LOG_FILE;
|
|
43
|
+
const _earlyLog = (label: string, err: any) => {
|
|
44
|
+
const msg = `[${new Date().toISOString()}] [${label}] ${err?.stack ?? err}\n`;
|
|
45
|
+
if (_logFile) try { appendFileSync(_logFile, msg); } catch {}
|
|
46
|
+
try { process.stderr.write(msg); } catch {}
|
|
47
|
+
};
|
|
48
|
+
process.on('uncaughtException', (err) => { _earlyLog('FATAL', err); process.exit(1); });
|
|
49
|
+
process.on('unhandledRejection', (reason) => { _earlyLog('FATAL:unhandledRejection', reason); process.exit(1); });
|
|
50
|
+
|
|
51
|
+
const strategyId = process.argv[3];
|
|
52
|
+
const dashDashIdx = process.argv.indexOf('--');
|
|
53
|
+
const strategyArgs = dashDashIdx >= 0 ? process.argv.slice(dashDashIdx + 1) : [];
|
|
54
|
+
const { runStrategyLoop } = await import('./strategies/strategy-loop.js');
|
|
55
|
+
try {
|
|
56
|
+
await runStrategyLoop(strategyId, strategyArgs.length > 0 ? strategyArgs : undefined);
|
|
57
|
+
} catch (e: any) {
|
|
58
|
+
const { EventStore } = await import('./pipeline/event-store.js');
|
|
59
|
+
try { EventStore.forActiveAccount().append({ type: 'auto_fatal', error: e?.message ?? String(e), stack: e?.stack }); } catch {}
|
|
60
|
+
}
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Internal entry: _daemon (spawned by `game start`) ───
|
|
65
|
+
if (process.argv[2] === '_daemon') {
|
|
66
|
+
const _logFile = process.env.CLAWCLAW_LOG_FILE;
|
|
67
|
+
const _earlyLog = (label: string, err: any) => {
|
|
68
|
+
const msg = `[${new Date().toISOString()}] [${label}] ${err?.stack ?? err}\n`;
|
|
69
|
+
if (_logFile) try { appendFileSync(_logFile, msg); } catch {}
|
|
70
|
+
try { process.stderr.write(msg); } catch {}
|
|
71
|
+
};
|
|
72
|
+
process.on('uncaughtException', (err) => { _earlyLog('FATAL', err); process.exit(1); });
|
|
73
|
+
process.on('unhandledRejection', (reason) => { _earlyLog('FATAL:unhandledRejection', reason); process.exit(1); });
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const { AuthStore } = await import('./lib/auth.js');
|
|
77
|
+
const { startEventDaemon } = await import('./runtime/event-daemon.js');
|
|
78
|
+
await startEventDaemon(new AuthStore());
|
|
79
|
+
await new Promise(() => {});
|
|
80
|
+
} catch (err) {
|
|
81
|
+
_earlyLog('FATAL', err);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ─── Internal entry: _opener (short-lived opening movement helper) ───
|
|
87
|
+
if (process.argv[2] === '_opener') {
|
|
88
|
+
const _logFile = process.env.CLAWCLAW_LOG_FILE;
|
|
89
|
+
const _earlyLog = (label: string, err: any) => {
|
|
90
|
+
const msg = `[${new Date().toISOString()}] [${label}] ${err?.stack ?? err}\n`;
|
|
91
|
+
if (_logFile) try { appendFileSync(_logFile, msg); } catch {}
|
|
92
|
+
try { process.stderr.write(msg); } catch {}
|
|
93
|
+
};
|
|
94
|
+
process.on('uncaughtException', (err) => { _earlyLog('FATAL', err); process.exit(1); });
|
|
95
|
+
process.on('unhandledRejection', (reason) => { _earlyLog('FATAL:unhandledRejection', reason); process.exit(1); });
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const { runOpeningMover } = await import('./runtime/opening-mover.js');
|
|
99
|
+
await runOpeningMover();
|
|
100
|
+
} catch (err) {
|
|
101
|
+
_earlyLog('FATAL', err);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
process.exit(0);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ─── Normal CLI ───
|
|
108
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
109
|
+
const __dirname = dirname(__filename);
|
|
110
|
+
const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8'));
|
|
111
|
+
|
|
112
|
+
const program = new Command()
|
|
113
|
+
.name('clawclaw-cli')
|
|
114
|
+
.description('ClawClaw social deduction game CLI')
|
|
115
|
+
.version(pkg.version)
|
|
116
|
+
.option('--workspace-dir <dir>', 'workspace directory');
|
|
117
|
+
|
|
118
|
+
program.addCommand(createDoCommand());
|
|
119
|
+
program.addCommand(createStateCommand());
|
|
120
|
+
program.addCommand(createEventsCommand());
|
|
121
|
+
program.addCommand(createGameCommand());
|
|
122
|
+
program.addCommand(createAccountCommand());
|
|
123
|
+
program.addCommand(createLoadCommand());
|
|
124
|
+
program.addCommand(createMemoryCommand());
|
|
125
|
+
program.addCommand(createKnowledgeCommand());
|
|
126
|
+
program.addCommand(createPersonaCommand());
|
|
127
|
+
program.addCommand(createUpgradeCommand());
|
|
128
|
+
program.addCommand(createPeekCommand());
|
|
129
|
+
program.addCommand(createHistoryCommand());
|
|
130
|
+
program.addCommand(createTTSCommand());
|
|
131
|
+
program.addCommand(createStrategyCommand());
|
|
132
|
+
program.addCommand(createSkillCommand());
|
|
133
|
+
program.addCommand(createHubCommand());
|
|
134
|
+
program.addCommand(createSetupCommand());
|
|
135
|
+
program.addCommand(createConfigCommand());
|
|
136
|
+
program.addCommand(createSchemaCommand(() => program), { hidden: true });
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
await program.parseAsync();
|
|
140
|
+
} catch (err) {
|
|
141
|
+
if (err instanceof ApiError) {
|
|
142
|
+
if (err.status === 422) {
|
|
143
|
+
console.info(`API key is missing or malformed. Run: clawclaw-cli account register`);
|
|
144
|
+
} else if (err.status === 401) {
|
|
145
|
+
console.info(`Account not found or API key expired. Run: clawclaw-cli account register`);
|
|
146
|
+
} else {
|
|
147
|
+
console.info(`Request failed (${err.status}): ${err.body}`);
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
151
|
+
}
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `_schema` — internal hidden command exposing the entire commander tree as JSON.
|
|
3
|
+
*
|
|
4
|
+
* Adapter consumers (e.g. `openclaw-clawclaw`) call this to auto-generate one tool
|
|
5
|
+
* per leaf subcommand without having to hand-translate each `ccl` command.
|
|
6
|
+
*
|
|
7
|
+
* Hidden by convention: name starts with `_` so the walker excludes it from output,
|
|
8
|
+
* and `--help` listings of regular tooling generally skip underscore-prefixed entries.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Command, Option, Argument } from 'commander';
|
|
12
|
+
import { getMeta, type CommandMeta } from '../lib/command-meta.js';
|
|
13
|
+
|
|
14
|
+
interface OptionSchema {
|
|
15
|
+
flags: string;
|
|
16
|
+
long?: string | null;
|
|
17
|
+
short?: string | null;
|
|
18
|
+
attributeName: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
defaultValue?: unknown;
|
|
21
|
+
/** option marked with `<value>` — value required */
|
|
22
|
+
required: boolean;
|
|
23
|
+
/** option marked with `[value]` — value optional */
|
|
24
|
+
optional: boolean;
|
|
25
|
+
/** option marked with `.makeOptionMandatory()` — option itself required */
|
|
26
|
+
mandatory: boolean;
|
|
27
|
+
negate: boolean;
|
|
28
|
+
takesValue: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ArgumentSchema {
|
|
32
|
+
name: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
required: boolean;
|
|
35
|
+
variadic: boolean;
|
|
36
|
+
defaultValue?: unknown;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface CommandSchema {
|
|
40
|
+
name: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
aliases: string[];
|
|
43
|
+
options: OptionSchema[];
|
|
44
|
+
arguments: ArgumentSchema[];
|
|
45
|
+
subcommands: CommandSchema[];
|
|
46
|
+
/** path from root, joined by space, e.g. "account leaderboard" */
|
|
47
|
+
path: string;
|
|
48
|
+
/** true when the command has no subcommands and is therefore directly invocable */
|
|
49
|
+
leaf: boolean;
|
|
50
|
+
/** Adapter-facing metadata (omitted if no metadata is set on this command). */
|
|
51
|
+
meta?: CommandMeta;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function dumpOption(o: Option): OptionSchema {
|
|
55
|
+
const flags = o.flags;
|
|
56
|
+
const takesValue = flags.includes('<') || flags.includes('[');
|
|
57
|
+
return {
|
|
58
|
+
flags,
|
|
59
|
+
long: o.long ?? null,
|
|
60
|
+
short: o.short ?? null,
|
|
61
|
+
attributeName: o.attributeName(),
|
|
62
|
+
description: o.description || undefined,
|
|
63
|
+
defaultValue: o.defaultValue,
|
|
64
|
+
required: o.required,
|
|
65
|
+
optional: o.optional,
|
|
66
|
+
mandatory: o.mandatory,
|
|
67
|
+
negate: o.negate,
|
|
68
|
+
takesValue,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function dumpArgument(a: Argument): ArgumentSchema {
|
|
73
|
+
return {
|
|
74
|
+
name: a.name(),
|
|
75
|
+
description: a.description || undefined,
|
|
76
|
+
required: a.required,
|
|
77
|
+
variadic: a.variadic,
|
|
78
|
+
defaultValue: a.defaultValue,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function walkCommand(cmd: Command, parentPath: string[] = []): CommandSchema {
|
|
83
|
+
const path = [...parentPath, cmd.name()];
|
|
84
|
+
const visibleSubs = (cmd.commands || []).filter((c) => !c.name().startsWith('_'));
|
|
85
|
+
const meta = getMeta(cmd);
|
|
86
|
+
return {
|
|
87
|
+
name: cmd.name(),
|
|
88
|
+
description: cmd.description() || undefined,
|
|
89
|
+
aliases: cmd.aliases() ?? [],
|
|
90
|
+
options: (cmd.options || []).map(dumpOption),
|
|
91
|
+
arguments: (cmd.registeredArguments || []).map(dumpArgument),
|
|
92
|
+
subcommands: visibleSubs.map((c) => walkCommand(c, path)),
|
|
93
|
+
path: path.slice(1).join(' '), // exclude root program name from path
|
|
94
|
+
leaf: visibleSubs.length === 0,
|
|
95
|
+
...(meta && Object.keys(meta).length > 0 ? { meta } : {}),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function createSchemaCommand(getProgram: () => Command): Command {
|
|
100
|
+
return new Command('_schema')
|
|
101
|
+
.description('[internal] dump full command tree as JSON for adapter auto-generation')
|
|
102
|
+
.option('--pretty', 'Indent JSON output')
|
|
103
|
+
.action((opts: { pretty?: boolean }) => {
|
|
104
|
+
const tree = walkCommand(getProgram());
|
|
105
|
+
console.log(JSON.stringify(tree, null, opts.pretty ? 2 : 0));
|
|
106
|
+
});
|
|
107
|
+
// Note: to hide from `ccl --help`, the parent passes { hidden: true } to addCommand().
|
|
108
|
+
// See cli.ts registration site.
|
|
109
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { AuthStore } from '../lib/auth.js';
|
|
3
|
+
import type { AuthProfile } from '../lib/auth.js';
|
|
4
|
+
import { GameClient, ApiError } from '../lib/game-client.js';
|
|
5
|
+
import { createProfileDirs, cmdInit } from '../lib/init-command.js';
|
|
6
|
+
import { setMeta } from '../lib/command-meta.js';
|
|
7
|
+
import { hubReminder, readCachedGamesPlayed, writeGamesPlayedCache } from '../lib/hub-reminder.js';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_SERVER_URL = 'https://myclaw.163.com/claw';
|
|
10
|
+
const SERVER_URL_ENV = 'CLAWCLAW_SERVER_URL';
|
|
11
|
+
|
|
12
|
+
function createClientForProfile(profile: AuthProfile): GameClient {
|
|
13
|
+
return new GameClient({
|
|
14
|
+
lobbyUrl: profile.serverUrl,
|
|
15
|
+
gameServerUrl: profile.gameServerUrl,
|
|
16
|
+
apiKey: profile.apiKey,
|
|
17
|
+
agentName: profile.agentName,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function extractServerAccountName(payload: any): string | undefined {
|
|
22
|
+
const data = payload?.data ?? payload;
|
|
23
|
+
return data?.agent_name ?? data?.agentName ?? data?.name;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getRegisterServerUrl(env: NodeJS.ProcessEnv = process.env): string {
|
|
27
|
+
const configured = env[SERVER_URL_ENV]?.trim();
|
|
28
|
+
return configured || DEFAULT_SERVER_URL;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createAccountCommand(): Command {
|
|
32
|
+
const account = new Command('account');
|
|
33
|
+
account.description('Account management & game data');
|
|
34
|
+
|
|
35
|
+
// ─── Auth ───
|
|
36
|
+
|
|
37
|
+
account
|
|
38
|
+
.command('register')
|
|
39
|
+
.description('Register new agent')
|
|
40
|
+
.option('-n, --name <name>', 'Agent name (max 16 chars); omit to let the server choose randomly')
|
|
41
|
+
.option('-d, --desc <text>', 'Agent description (max 500 chars)')
|
|
42
|
+
.option('--invite-code <code>', 'Invite code (required during beta)')
|
|
43
|
+
.option('--avatar-url <url>', 'Avatar image URL')
|
|
44
|
+
.option('--avatar-file <path>', 'Avatar image file (jpeg/png/gif/webp, max 1MB)')
|
|
45
|
+
.action(async (opts: { name?: string; desc?: string; inviteCode?: string; avatarUrl?: string; avatarFile?: string }) => {
|
|
46
|
+
const authStore = new AuthStore();
|
|
47
|
+
const { created, path: wsPath } = cmdInit();
|
|
48
|
+
if (created) console.log(`[ClawClaw] Workspace initialized at ${wsPath}`);
|
|
49
|
+
const serverUrl = getRegisterServerUrl();
|
|
50
|
+
|
|
51
|
+
const tmpClient = new GameClient({ lobbyUrl: serverUrl, apiKey: '' });
|
|
52
|
+
const result = await tmpClient.registerAgent({
|
|
53
|
+
name: opts.name,
|
|
54
|
+
description: opts.desc,
|
|
55
|
+
inviteCode: opts.inviteCode,
|
|
56
|
+
avatarUrl: opts.avatarUrl,
|
|
57
|
+
avatarFile: opts.avatarFile,
|
|
58
|
+
});
|
|
59
|
+
const d = result.data ?? result;
|
|
60
|
+
const apiKey = d.api_key ?? d.apiKey ?? '';
|
|
61
|
+
if (!apiKey) throw new Error('Registration failed: server did not return an API key.');
|
|
62
|
+
const agentName = d.agent_name ?? '';
|
|
63
|
+
if (!agentName) throw new Error('Registration failed: server did not return an agent name.');
|
|
64
|
+
|
|
65
|
+
const profile: AuthProfile = {
|
|
66
|
+
agentName,
|
|
67
|
+
apiKey,
|
|
68
|
+
serverUrl,
|
|
69
|
+
};
|
|
70
|
+
authStore.addProfile(profile);
|
|
71
|
+
try { createProfileDirs(profile); } catch {}
|
|
72
|
+
console.log(JSON.stringify({ ...result, message: `Registered and saved as profile "${agentName}"` }, null, 2));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
account
|
|
76
|
+
.command('rename <name>')
|
|
77
|
+
.description('Rename agent (max once/month, max 2/year, not during a game)')
|
|
78
|
+
.action(async (newName: string) => {
|
|
79
|
+
const authStore = new AuthStore();
|
|
80
|
+
const profile = authStore.getActive();
|
|
81
|
+
if (!profile) throw new Error('Not logged in.');
|
|
82
|
+
const client = GameClient.fromAuth();
|
|
83
|
+
const result = await client.renameAgent(newName);
|
|
84
|
+
const oldName = profile.agentName;
|
|
85
|
+
profile.agentName = newName;
|
|
86
|
+
authStore.removeProfile(oldName);
|
|
87
|
+
authStore.addProfile(profile);
|
|
88
|
+
authStore.switchProfile(newName);
|
|
89
|
+
console.log(JSON.stringify({ ...result, message: `Renamed "${oldName}" → "${newName}"` }, null, 2));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
account
|
|
93
|
+
.command('switch <apiKey>')
|
|
94
|
+
.description('Switch active profile by API key')
|
|
95
|
+
.action(async (apiKey: string) => {
|
|
96
|
+
const authStore = new AuthStore();
|
|
97
|
+
const data = authStore.load();
|
|
98
|
+
const profileKey = Object.keys(data.profiles).find(key => data.profiles[key]?.apiKey === apiKey);
|
|
99
|
+
if (!profileKey) {
|
|
100
|
+
throw new Error(`Profile with apiKey "${apiKey}" not found. Use \`clawclaw-cli account list\` to inspect saved accounts.`);
|
|
101
|
+
}
|
|
102
|
+
authStore.switchProfile(profileKey);
|
|
103
|
+
console.log(JSON.stringify({ message: 'Switched active profile', apiKey }, null, 2));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
account
|
|
107
|
+
.command('list')
|
|
108
|
+
.description('List all saved profiles with current server names')
|
|
109
|
+
.action(async () => {
|
|
110
|
+
const authStore = new AuthStore();
|
|
111
|
+
const active = authStore.getActive();
|
|
112
|
+
const data = authStore.load();
|
|
113
|
+
const result = await Promise.all(Object.values(data.profiles).map(async (profile) => {
|
|
114
|
+
try {
|
|
115
|
+
const me = await createClientForProfile(profile).getMe();
|
|
116
|
+
return {
|
|
117
|
+
name: extractServerAccountName(me) ?? profile.agentName,
|
|
118
|
+
apiKey: profile.apiKey,
|
|
119
|
+
active: profile.apiKey === active?.apiKey,
|
|
120
|
+
};
|
|
121
|
+
} catch {
|
|
122
|
+
return {
|
|
123
|
+
name: profile.agentName,
|
|
124
|
+
apiKey: profile.apiKey,
|
|
125
|
+
active: profile.apiKey === active?.apiKey,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}));
|
|
129
|
+
console.log(JSON.stringify(result, null, 2));
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// ─── Game Data ───
|
|
133
|
+
|
|
134
|
+
account
|
|
135
|
+
.command('leaderboard')
|
|
136
|
+
.description('Show leaderboard')
|
|
137
|
+
.option('-p, --page <number>', 'Page number')
|
|
138
|
+
.option('-l, --limit <number>', 'Items per page (max 100)', '20')
|
|
139
|
+
.action(async (opts: { page?: string; limit: string }) => {
|
|
140
|
+
const client = GameClient.fromAuth();
|
|
141
|
+
const page = opts.page ? Number(opts.page) : undefined;
|
|
142
|
+
const result = await client.getLeaderboard(page, Number(opts.limit));
|
|
143
|
+
console.log(JSON.stringify(result, null, 2));
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
account
|
|
147
|
+
.command('settlement')
|
|
148
|
+
.description('Show settlement data from last game')
|
|
149
|
+
.action(async () => {
|
|
150
|
+
const client = GameClient.fromAuth();
|
|
151
|
+
try {
|
|
152
|
+
const result = await client.getSettlement();
|
|
153
|
+
// `ccl account settlement` is the last command the agent runs before
|
|
154
|
+
// writing the debrief close, and its `data` block is long enough to bury
|
|
155
|
+
// the reminder that rode in on the `game_over` push. Re-attach it here as
|
|
156
|
+
// the final fields (after `data`) so the nudge is the freshest thing in
|
|
157
|
+
// context the moment the agent writes the close.
|
|
158
|
+
const reminder = hubReminder(readCachedGamesPlayed());
|
|
159
|
+
const out: Record<string, any> = (result && typeof result === 'object' && !Array.isArray(result))
|
|
160
|
+
? { ...result }
|
|
161
|
+
: { result };
|
|
162
|
+
if (reminder) {
|
|
163
|
+
out.hub_reminder = reminder;
|
|
164
|
+
out.next_step = `Write the post-game debrief with your user from this settlement, then close out. ${reminder}`;
|
|
165
|
+
}
|
|
166
|
+
console.log(JSON.stringify(out, null, 2));
|
|
167
|
+
} catch (err: any) {
|
|
168
|
+
if (err instanceof ApiError && err.body?.includes('NO_SETTLEMENT')) {
|
|
169
|
+
console.info('No settlement data yet. The game may still be in progress, or you quit early after dying; you can still give a brief recap from known events so far.');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
account
|
|
177
|
+
.command('history')
|
|
178
|
+
.description('Show game history')
|
|
179
|
+
.option('-p, --page <number>', 'Page number', '1')
|
|
180
|
+
.option('-l, --limit <number>', 'Items per page (max 100)', '20')
|
|
181
|
+
.action(async (opts: { page: string; limit: string }) => {
|
|
182
|
+
const client = GameClient.fromAuth();
|
|
183
|
+
const result = await client.getGameHistory(Number(opts.page), Number(opts.limit));
|
|
184
|
+
console.log(JSON.stringify(result, null, 2));
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
account
|
|
188
|
+
.command('info')
|
|
189
|
+
.description('Print account name, Claw Key, and spectate URL')
|
|
190
|
+
.action(async () => {
|
|
191
|
+
const authStore = new AuthStore();
|
|
192
|
+
const profile = authStore.getActive();
|
|
193
|
+
if (!profile) throw new Error('Not logged in.');
|
|
194
|
+
const client = GameClient.fromAuth();
|
|
195
|
+
const result = await client.getMe();
|
|
196
|
+
if (result?.data) delete result.data.tutorial;
|
|
197
|
+
const gp = result?.data?.games_played;
|
|
198
|
+
if (typeof gp === 'number') writeGamesPlayedCache(gp);
|
|
199
|
+
const origin = new URL(profile.serverUrl).origin.replace(/^http:\/\//, 'https://');
|
|
200
|
+
const url = `${origin}/lobby?token=${encodeURIComponent(profile.apiKey)}`;
|
|
201
|
+
console.log(JSON.stringify({ ...result, clawKey: profile.apiKey, spectateUrl: url }, null, 2));
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// ── Adapter metadata (consumed by ccl _schema, applied by openclaw-clawclaw L2 generator) ──
|
|
205
|
+
setMeta(account.commands.find((c) => c.name() === 'register')!, { requiresConfirm: true });
|
|
206
|
+
setMeta(account.commands.find((c) => c.name() === 'rename')!, { requiresConfirm: true });
|
|
207
|
+
|
|
208
|
+
return account;
|
|
209
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getWorkspaceDir } from '../lib/init-command.js';
|
|
3
|
+
import { AuthStore } from '../lib/auth.js';
|
|
4
|
+
|
|
5
|
+
export function createWorkspaceSubcommand(): Command {
|
|
6
|
+
return new Command('workspace')
|
|
7
|
+
.description('Print the workspace directory path')
|
|
8
|
+
.action(() => {
|
|
9
|
+
console.log(getWorkspaceDir());
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createApikeySubcommand(): Command {
|
|
14
|
+
return new Command('apikey')
|
|
15
|
+
.description('Print the active account API key')
|
|
16
|
+
.action(() => {
|
|
17
|
+
const store = new AuthStore();
|
|
18
|
+
const profile = store.getActive();
|
|
19
|
+
if (!profile) throw new Error('Not logged in. Run: clawclaw-cli account register');
|
|
20
|
+
console.log(profile.apiKey);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createConfigCommand(): Command {
|
|
25
|
+
const config = new Command('config');
|
|
26
|
+
config.description('Query ClawClaw CLI configuration.');
|
|
27
|
+
config.addCommand(createWorkspaceSubcommand());
|
|
28
|
+
config.addCommand(createApikeySubcommand());
|
|
29
|
+
return config;
|
|
30
|
+
}
|