@myclaw163/clawclaw-cli 0.6.76 → 0.6.77

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 (209) hide show
  1. package/README.md +387 -387
  2. package/bin/clawclaw-cli.mjs +3 -3
  3. package/package.json +48 -48
  4. package/personas//347/220/206/346/231/272/346/270/251/345/222/214.md +23 -23
  5. package/personas//350/200/201/350/260/213/346/267/261/347/256/227.md +22 -22
  6. package/personas//350/257/232/346/201/263/347/233/264/347/216/207.md +22 -22
  7. package/personas//350/275/273/346/235/276/346/264/273/346/263/274.md +22 -22
  8. package/personas//351/207/216/346/200/247/345/217/233/351/200/206.md +23 -23
  9. package/scripts/check-skill-command-surface.mjs +116 -116
  10. package/scripts/find-hide-spots.py +157 -157
  11. package/scripts/postinstall.mjs +20 -20
  12. package/scripts/sync-bundled-skill.mjs +254 -245
  13. package/scripts/sync-bundled-skill.test.mjs +152 -152
  14. package/skills/clawclaw/SKILL.md +248 -248
  15. package/skills/clawclaw/references/CHATTERBOX.md +141 -141
  16. package/skills/clawclaw/references/COMMANDS.md +160 -160
  17. package/skills/clawclaw/references/GAME-MECHANICS.md +188 -188
  18. package/skills/clawclaw/references/HUB.md +48 -48
  19. package/skills/clawclaw/references/KNOWLEDGE.md +42 -42
  20. package/skills/clawclaw/references/STRATEGIES.md +59 -59
  21. package/skills/clawclaw/references/STREAM.md +93 -93
  22. package/skills/clawclaw/references/TACTICS.md +65 -65
  23. package/src/assets/clawclaw-ascii-map.txt +40 -40
  24. package/src/cli.ts +112 -112
  25. package/src/commands/_schema.ts +124 -124
  26. package/src/commands/account.ts +209 -209
  27. package/src/commands/data.test.ts +33 -33
  28. package/src/commands/data.ts +22 -22
  29. package/src/commands/do.test.ts +84 -84
  30. package/src/commands/do.ts +130 -130
  31. package/src/commands/events.test.ts +100 -100
  32. package/src/commands/events.ts +250 -250
  33. package/src/commands/game-map.test.ts +28 -28
  34. package/src/commands/game-start-plan.test.ts +84 -84
  35. package/src/commands/game.ts +1113 -1113
  36. package/src/commands/history-player.test.ts +102 -102
  37. package/src/commands/history.ts +573 -573
  38. package/src/commands/hub.test.ts +96 -96
  39. package/src/commands/hub.ts +234 -234
  40. package/src/commands/knowledge.test.ts +13 -13
  41. package/src/commands/knowledge.ts +139 -139
  42. package/src/commands/load.test.ts +51 -51
  43. package/src/commands/load.ts +13 -13
  44. package/src/commands/meeting-history.test.ts +106 -106
  45. package/src/commands/memory.ts +40 -40
  46. package/src/commands/peek.ts +45 -45
  47. package/src/commands/persona.ts +57 -57
  48. package/src/commands/setup/codex.ts +266 -266
  49. package/src/commands/skill.ts +128 -128
  50. package/src/commands/state.ts +46 -46
  51. package/src/commands/strategy.test.ts +153 -153
  52. package/src/commands/strategy.ts +183 -183
  53. package/src/commands/tts.ts +128 -128
  54. package/src/commands/upgrade.test.ts +82 -82
  55. package/src/commands/upgrade.ts +148 -148
  56. package/src/commands/watch.test.ts +999 -999
  57. package/src/commands/watch.ts +660 -660
  58. package/src/lib/auth.test.ts +86 -86
  59. package/src/lib/auth.ts +223 -223
  60. package/src/lib/command-meta.ts +37 -37
  61. package/src/lib/game-client.ts +403 -403
  62. package/src/lib/game-context.ts +92 -92
  63. package/src/lib/http-keepalive.ts +15 -15
  64. package/src/lib/http-transport.test.ts +42 -42
  65. package/src/lib/http-transport.ts +113 -113
  66. package/src/lib/hub-client.test.ts +56 -56
  67. package/src/lib/hub-client.ts +88 -88
  68. package/src/lib/hub-install.test.ts +98 -98
  69. package/src/lib/hub-install.ts +160 -160
  70. package/src/lib/hub-reminder.ts +78 -78
  71. package/src/lib/hub-unzip.test.ts +69 -69
  72. package/src/lib/hub-unzip.ts +62 -62
  73. package/src/lib/init-command.test.ts +75 -75
  74. package/src/lib/init-command.ts +130 -130
  75. package/src/lib/knowledge-store.test.ts +170 -170
  76. package/src/lib/knowledge-store.ts +369 -369
  77. package/src/lib/load-context.test.ts +52 -52
  78. package/src/lib/load-context.ts +52 -52
  79. package/src/lib/match-state.test.ts +134 -134
  80. package/src/lib/match-state.ts +94 -94
  81. package/src/lib/netease-tts.ts +83 -83
  82. package/src/lib/normalize.ts +42 -42
  83. package/src/lib/persona.test.ts +41 -41
  84. package/src/lib/persona.ts +72 -72
  85. package/src/lib/server-registry.ts +152 -152
  86. package/src/lib/skill-version.test.ts +48 -48
  87. package/src/lib/skill-version.ts +19 -19
  88. package/src/lib/strategy-export.test.ts +240 -240
  89. package/src/lib/strategy-export.ts +247 -247
  90. package/src/lib/tts-keys.ts +7 -7
  91. package/src/lib/tts-speech.test.ts +63 -63
  92. package/src/lib/tts-speech.ts +76 -76
  93. package/src/lib/user-data.test.ts +96 -96
  94. package/src/lib/user-data.ts +400 -400
  95. package/src/lib/workspace-argv.test.ts +49 -49
  96. package/src/lib/workspace-argv.ts +44 -44
  97. package/src/perception/player-history-store.test.ts +87 -87
  98. package/src/perception/player-history-store.ts +194 -194
  99. package/src/pipeline/event-format.test.ts +243 -243
  100. package/src/pipeline/event-format.ts +501 -501
  101. package/src/pipeline/event-hints.ts +195 -195
  102. package/src/pipeline/event-store.test.ts +28 -28
  103. package/src/pipeline/event-store.ts +193 -193
  104. package/src/pipeline/pipeline.ts +35 -35
  105. package/src/pipeline/player-projection.test.ts +168 -168
  106. package/src/pipeline/player-projection.ts +370 -370
  107. package/src/runtime/auto-upgrade.test.ts +66 -66
  108. package/src/runtime/auto-upgrade.ts +31 -31
  109. package/src/runtime/event-daemon.test.ts +209 -209
  110. package/src/runtime/event-daemon.ts +519 -519
  111. package/src/runtime/owner-control.ts +150 -150
  112. package/src/runtime/raw-ws-log.test.ts +33 -33
  113. package/src/runtime/raw-ws-log.ts +32 -32
  114. package/src/runtime/runtime-logger.ts +107 -107
  115. package/src/runtime/ws-client.test.ts +125 -125
  116. package/src/runtime/ws-client.ts +287 -287
  117. package/src/sdk/action.ts +166 -166
  118. package/src/sdk/index.ts +110 -110
  119. package/src/sdk/types.ts +161 -161
  120. package/src/strategies/avoid-lone.ts +12 -12
  121. package/src/strategies/avoid-players.knowledge.md +19 -19
  122. package/src/strategies/avoid-players.ts +16 -16
  123. package/src/strategies/corpse-patrol.ts +23 -23
  124. package/src/strategies/crab-sabotage.ts +22 -22
  125. package/src/strategies/custom-module.test.ts +270 -270
  126. package/src/strategies/find-player.ts +17 -17
  127. package/src/strategies/game-utils.test.ts +242 -242
  128. package/src/strategies/game-utils.ts +846 -846
  129. package/src/strategies/goals/anchor-linger.ts +77 -77
  130. package/src/strategies/goals/avoid-lone-top.ts +168 -168
  131. package/src/strategies/goals/avoid-players-top.test.ts +83 -83
  132. package/src/strategies/goals/avoid-players-top.ts +121 -121
  133. package/src/strategies/goals/conversation-goal.ts +51 -51
  134. package/src/strategies/goals/corpse-patrol-top.ts +113 -113
  135. package/src/strategies/goals/crab-octopus-reflexes.ts +101 -101
  136. package/src/strategies/goals/crab-sabotage-top.ts +197 -197
  137. package/src/strategies/goals/emergency-hunt-goal.ts +28 -28
  138. package/src/strategies/goals/find-player-top.ts +93 -93
  139. package/src/strategies/goals/flee-players-goal.ts +53 -53
  140. package/src/strategies/goals/follow-companion-goal.ts +106 -106
  141. package/src/strategies/goals/goal-manager.ts +41 -41
  142. package/src/strategies/goals/goal-root-strategy.ts +49 -49
  143. package/src/strategies/goals/goal.ts +28 -28
  144. package/src/strategies/goals/hide-top.ts +197 -197
  145. package/src/strategies/goals/keep-away-goal.ts +221 -221
  146. package/src/strategies/goals/kill-frenzy-top.ts +80 -80
  147. package/src/strategies/goals/kill-lone-top.ts +160 -160
  148. package/src/strategies/goals/kill-target-goal.ts +59 -59
  149. package/src/strategies/goals/kill-target-top.ts +109 -109
  150. package/src/strategies/goals/leaf-goal.ts +27 -27
  151. package/src/strategies/goals/linger-corpse-goal.ts +35 -35
  152. package/src/strategies/goals/lone-kill-core.ts +82 -82
  153. package/src/strategies/goals/lone-kill-goal.ts +24 -24
  154. package/src/strategies/goals/lone-kill-task-top.test.ts +85 -85
  155. package/src/strategies/goals/lone-kill-task-top.ts +133 -133
  156. package/src/strategies/goals/move-room-goal.ts +60 -60
  157. package/src/strategies/goals/normal-shrimp-top.test.ts +80 -80
  158. package/src/strategies/goals/normal-shrimp-top.ts +242 -242
  159. package/src/strategies/goals/paradise-fish-top.test.ts +126 -126
  160. package/src/strategies/goals/paradise-fish-top.ts +224 -224
  161. package/src/strategies/goals/patrol-top.ts +57 -57
  162. package/src/strategies/goals/report-patrol-top.ts +80 -80
  163. package/src/strategies/goals/safe-task-goal.ts +102 -102
  164. package/src/strategies/goals/social-task-top.ts +161 -161
  165. package/src/strategies/goals/task-kill-report-top.ts +163 -163
  166. package/src/strategies/goals/task-only-top.ts +57 -57
  167. package/src/strategies/goals/task-or-patrol-goal.ts +41 -41
  168. package/src/strategies/goals/task-report-top.ts +57 -57
  169. package/src/strategies/goals/wander-task-goal.ts +33 -33
  170. package/src/strategies/goals/warrior-shrimp-top.test.ts +87 -87
  171. package/src/strategies/goals/warrior-shrimp-top.ts +267 -267
  172. package/src/strategies/greeting.ts +53 -53
  173. package/src/strategies/hide-spots.ts +59 -59
  174. package/src/strategies/hide.ts +24 -24
  175. package/src/strategies/kill-frenzy.ts +13 -13
  176. package/src/strategies/kill-lone.knowledge.md +17 -17
  177. package/src/strategies/kill-lone.ts +14 -14
  178. package/src/strategies/kill-target.ts +19 -19
  179. package/src/strategies/loader.test.ts +678 -678
  180. package/src/strategies/loader.ts +181 -181
  181. package/src/strategies/lone-kill-task.ts +22 -22
  182. package/src/strategies/meeting-gate.test.ts +59 -59
  183. package/src/strategies/meeting-gate.ts +23 -23
  184. package/src/strategies/move-room.ts +16 -16
  185. package/src/strategies/new-events-backfill.ts +98 -98
  186. package/src/strategies/off-route-points.ts +105 -105
  187. package/src/strategies/paradise-fish.knowledge.md +19 -19
  188. package/src/strategies/paradise-fish.ts +26 -26
  189. package/src/strategies/pathfind/distance-field.ts +150 -150
  190. package/src/strategies/pathfind/escape-planner.test.ts +197 -197
  191. package/src/strategies/pathfind/escape-planner.ts +355 -355
  192. package/src/strategies/pathfind/walkable-grid.ts +117 -117
  193. package/src/strategies/patrol.ts +12 -12
  194. package/src/strategies/player-targets.ts +13 -13
  195. package/src/strategies/report-patrol.ts +12 -12
  196. package/src/strategies/shrimp-memory.knowledge.md +19 -19
  197. package/src/strategies/shrimp-memory.ts +26 -26
  198. package/src/strategies/social-task.test.ts +28 -28
  199. package/src/strategies/social-task.ts +50 -50
  200. package/src/strategies/spawn.ts +82 -82
  201. package/src/strategies/speech-module.ts +123 -123
  202. package/src/strategies/strategy-loop.test.ts +15 -15
  203. package/src/strategies/strategy-loop.ts +776 -776
  204. package/src/strategies/task-kill-report.ts +18 -18
  205. package/src/strategies/task-only.ts +12 -12
  206. package/src/strategies/task-report.ts +23 -23
  207. package/src/strategies/types.ts +109 -109
  208. package/src/strategies/warrior-memory.knowledge.md +21 -21
  209. package/src/strategies/warrior-memory.ts +17 -17
@@ -1,139 +1,139 @@
1
- import { Command } from 'commander';
2
- import { DROPPED_PLAYER_MARK_TAGS, KnowledgeStore, type KnowledgeSetInput, type PlayerMark } from '../lib/knowledge-store.js';
3
-
4
- // 玩家只标两档:hostile(坏人)/ trusted(好人)。未标记的默认「被怀疑」,无需也无法显式标。
5
- const PLAYER_MARKS = new Set<PlayerMark>(['hostile', 'trusted']);
6
- const LEGACY_MARK_TAGS = ['kill_if_armed', 'cleared', 'protected', 'do_not_kill'];
7
-
8
- export function normalizePlayerMark(mark?: string): PlayerMark | null {
9
- const normalized = mark?.trim().toLowerCase() as PlayerMark | undefined;
10
- return normalized && PLAYER_MARKS.has(normalized) ? normalized : null;
11
- }
12
-
13
- /** "true"/"false"/数字 自动转类型,其余按字符串。 */
14
- function coerceValue(raw: string | undefined): unknown {
15
- if (raw == null) return true;
16
- if (raw === 'true') return true;
17
- if (raw === 'false') return false;
18
- if (raw !== '' && !Number.isNaN(Number(raw))) return Number(raw);
19
- return raw;
20
- }
21
-
22
- function collectTag(value: string, prev: string[]): string[] {
23
- return [...prev, value];
24
- }
25
-
26
- function print(data: unknown): void {
27
- console.log(JSON.stringify(data, null, 2));
28
- }
29
-
30
- function fail(error: string, message: string): never {
31
- console.error(JSON.stringify({ error, message }, null, 2));
32
- process.exit(1);
33
- }
34
-
35
- function setPlayerMark(store: KnowledgeStore, player: string, mark: PlayerMark, opts: { note?: string }) {
36
- const removeTags = [
37
- ...[...PLAYER_MARKS].filter(candidate => candidate !== mark),
38
- ...LEGACY_MARK_TAGS,
39
- ...DROPPED_PLAYER_MARK_TAGS, // 顺手清掉旧数据残留的 suspect/avoid raw tag,别再从 get/subject() 漏出去
40
- ];
41
- return store.set({
42
- type: 'player',
43
- selector: player,
44
- tags: [mark],
45
- removeTags,
46
- note: opts.note,
47
- });
48
- }
49
-
50
- export function createKnowledgeCommand(): Command {
51
- const cmd = new Command('knowledge')
52
- .alias('kn')
53
- .description('Per-game structured knowledge that a running strategy can query (no restart needed)');
54
-
55
- cmd
56
- .command('set [type] [selector] [key] [value]', { hidden: true })
57
- .description('Advanced: upsert arbitrary facts/tags. Use `knowledge mark` for player strategy behavior.')
58
- .option('--tag <tag>', 'Custom tag, repeatable', collectTag, [])
59
- .option('--note <text>', 'Free-text reason')
60
- .option('--json <json>', 'Provide the whole input object as JSON instead of positional args')
61
- .action((type: string | undefined, selector: string | undefined, key: string | undefined, value: string | undefined, opts: {
62
- tag: string[]; note?: string; json?: string;
63
- }) => {
64
- try {
65
- const store = KnowledgeStore.forActiveAccount();
66
- let input: KnowledgeSetInput;
67
- if (opts.json) {
68
- input = JSON.parse(opts.json) as KnowledgeSetInput;
69
- } else {
70
- if (!type || !selector) {
71
- fail('missing_args', 'Usage: ccl knowledge set <type> <selector> [key] [value] [--tag ...] — or: ccl knowledge set --json "{...}"');
72
- }
73
- input = {
74
- type,
75
- selector,
76
- key,
77
- value: key != null ? coerceValue(value) : undefined,
78
- tags: opts.tag.length > 0 ? opts.tag : undefined,
79
- note: opts.note,
80
- };
81
- }
82
- const entry = store.set(input);
83
- print({ ok: true, gameId: store.gameId ?? null, subject: entry });
84
- } catch (e) {
85
- fail('knowledge_set_failed', e instanceof Error ? e.message : String(e));
86
- }
87
- });
88
-
89
- cmd
90
- .command('mark <player> <mark>')
91
- .description('Set the strategy mark for a player: hostile | trusted (unmarked players are suspected by default)')
92
- .option('--note <text>', 'Free-text reason')
93
- .action((player: string, rawMark: string, opts: { note?: string }) => {
94
- const mark = normalizePlayerMark(rawMark);
95
- if (!mark) fail('invalid_mark', `Unknown mark '${rawMark}'. Use: hostile | trusted. (Unmarked = suspected by default.)`);
96
- try {
97
- const store = KnowledgeStore.forActiveAccount();
98
- const entry = setPlayerMark(store, player, mark, opts);
99
- print({ ok: true, gameId: store.gameId ?? null, mark, subject: entry });
100
- } catch (e) {
101
- fail('knowledge_mark_failed', e instanceof Error ? e.message : String(e));
102
- }
103
- });
104
-
105
- cmd
106
- .command('get [type] [selector]')
107
- .description('Read knowledge: all, all of a type, or one subject')
108
- .action((type: string | undefined, selector: string | undefined) => {
109
- const store = KnowledgeStore.forActiveAccount();
110
- const file = store.list();
111
- if (type && selector) {
112
- print({ gameId: file.gameId ?? null, subject: file.subjects[`${type}:${selector}`] ?? null });
113
- return;
114
- }
115
- const subjects = Object.values(file.subjects).filter((s) => !type || s.type === type);
116
- print({ gameId: file.gameId ?? null, count: subjects.length, subjects });
117
- });
118
-
119
- cmd
120
- .command('del <type> <selector> [key]')
121
- .description('Delete a subject, or just one fact (key) / tag (--tag)')
122
- .option('--tag <tag>', 'Delete only this tag')
123
- .action((type: string, selector: string, key: string | undefined, opts: { tag?: string }) => {
124
- const store = KnowledgeStore.forActiveAccount();
125
- const removed = store.del(type, selector, { key, tag: opts.tag });
126
- print({ ok: removed, gameId: store.gameId ?? null });
127
- });
128
-
129
- cmd
130
- .command('clear')
131
- .description('Clear all knowledge for the current game')
132
- .action(() => {
133
- const store = KnowledgeStore.forActiveAccount();
134
- store.clear();
135
- print({ ok: true, gameId: store.gameId ?? null });
136
- });
137
-
138
- return cmd;
139
- }
1
+ import { Command } from 'commander';
2
+ import { DROPPED_PLAYER_MARK_TAGS, KnowledgeStore, type KnowledgeSetInput, type PlayerMark } from '../lib/knowledge-store.js';
3
+
4
+ // 玩家只标两档:hostile(坏人)/ trusted(好人)。未标记的默认「被怀疑」,无需也无法显式标。
5
+ const PLAYER_MARKS = new Set<PlayerMark>(['hostile', 'trusted']);
6
+ const LEGACY_MARK_TAGS = ['kill_if_armed', 'cleared', 'protected', 'do_not_kill'];
7
+
8
+ export function normalizePlayerMark(mark?: string): PlayerMark | null {
9
+ const normalized = mark?.trim().toLowerCase() as PlayerMark | undefined;
10
+ return normalized && PLAYER_MARKS.has(normalized) ? normalized : null;
11
+ }
12
+
13
+ /** "true"/"false"/数字 自动转类型,其余按字符串。 */
14
+ function coerceValue(raw: string | undefined): unknown {
15
+ if (raw == null) return true;
16
+ if (raw === 'true') return true;
17
+ if (raw === 'false') return false;
18
+ if (raw !== '' && !Number.isNaN(Number(raw))) return Number(raw);
19
+ return raw;
20
+ }
21
+
22
+ function collectTag(value: string, prev: string[]): string[] {
23
+ return [...prev, value];
24
+ }
25
+
26
+ function print(data: unknown): void {
27
+ console.log(JSON.stringify(data, null, 2));
28
+ }
29
+
30
+ function fail(error: string, message: string): never {
31
+ console.error(JSON.stringify({ error, message }, null, 2));
32
+ process.exit(1);
33
+ }
34
+
35
+ function setPlayerMark(store: KnowledgeStore, player: string, mark: PlayerMark, opts: { note?: string }) {
36
+ const removeTags = [
37
+ ...[...PLAYER_MARKS].filter(candidate => candidate !== mark),
38
+ ...LEGACY_MARK_TAGS,
39
+ ...DROPPED_PLAYER_MARK_TAGS, // 顺手清掉旧数据残留的 suspect/avoid raw tag,别再从 get/subject() 漏出去
40
+ ];
41
+ return store.set({
42
+ type: 'player',
43
+ selector: player,
44
+ tags: [mark],
45
+ removeTags,
46
+ note: opts.note,
47
+ });
48
+ }
49
+
50
+ export function createKnowledgeCommand(): Command {
51
+ const cmd = new Command('knowledge')
52
+ .alias('kn')
53
+ .description('Per-game structured knowledge that a running strategy can query (no restart needed)');
54
+
55
+ cmd
56
+ .command('set [type] [selector] [key] [value]', { hidden: true })
57
+ .description('Advanced: upsert arbitrary facts/tags. Use `knowledge mark` for player strategy behavior.')
58
+ .option('--tag <tag>', 'Custom tag, repeatable', collectTag, [])
59
+ .option('--note <text>', 'Free-text reason')
60
+ .option('--json <json>', 'Provide the whole input object as JSON instead of positional args')
61
+ .action((type: string | undefined, selector: string | undefined, key: string | undefined, value: string | undefined, opts: {
62
+ tag: string[]; note?: string; json?: string;
63
+ }) => {
64
+ try {
65
+ const store = KnowledgeStore.forActiveAccount();
66
+ let input: KnowledgeSetInput;
67
+ if (opts.json) {
68
+ input = JSON.parse(opts.json) as KnowledgeSetInput;
69
+ } else {
70
+ if (!type || !selector) {
71
+ fail('missing_args', 'Usage: ccl knowledge set <type> <selector> [key] [value] [--tag ...] — or: ccl knowledge set --json "{...}"');
72
+ }
73
+ input = {
74
+ type,
75
+ selector,
76
+ key,
77
+ value: key != null ? coerceValue(value) : undefined,
78
+ tags: opts.tag.length > 0 ? opts.tag : undefined,
79
+ note: opts.note,
80
+ };
81
+ }
82
+ const entry = store.set(input);
83
+ print({ ok: true, gameId: store.gameId ?? null, subject: entry });
84
+ } catch (e) {
85
+ fail('knowledge_set_failed', e instanceof Error ? e.message : String(e));
86
+ }
87
+ });
88
+
89
+ cmd
90
+ .command('mark <player> <mark>')
91
+ .description('Set the strategy mark for a player: hostile | trusted (unmarked players are suspected by default)')
92
+ .option('--note <text>', 'Free-text reason')
93
+ .action((player: string, rawMark: string, opts: { note?: string }) => {
94
+ const mark = normalizePlayerMark(rawMark);
95
+ if (!mark) fail('invalid_mark', `Unknown mark '${rawMark}'. Use: hostile | trusted. (Unmarked = suspected by default.)`);
96
+ try {
97
+ const store = KnowledgeStore.forActiveAccount();
98
+ const entry = setPlayerMark(store, player, mark, opts);
99
+ print({ ok: true, gameId: store.gameId ?? null, mark, subject: entry });
100
+ } catch (e) {
101
+ fail('knowledge_mark_failed', e instanceof Error ? e.message : String(e));
102
+ }
103
+ });
104
+
105
+ cmd
106
+ .command('get [type] [selector]')
107
+ .description('Read knowledge: all, all of a type, or one subject')
108
+ .action((type: string | undefined, selector: string | undefined) => {
109
+ const store = KnowledgeStore.forActiveAccount();
110
+ const file = store.list();
111
+ if (type && selector) {
112
+ print({ gameId: file.gameId ?? null, subject: file.subjects[`${type}:${selector}`] ?? null });
113
+ return;
114
+ }
115
+ const subjects = Object.values(file.subjects).filter((s) => !type || s.type === type);
116
+ print({ gameId: file.gameId ?? null, count: subjects.length, subjects });
117
+ });
118
+
119
+ cmd
120
+ .command('del <type> <selector> [key]')
121
+ .description('Delete a subject, or just one fact (key) / tag (--tag)')
122
+ .option('--tag <tag>', 'Delete only this tag')
123
+ .action((type: string, selector: string, key: string | undefined, opts: { tag?: string }) => {
124
+ const store = KnowledgeStore.forActiveAccount();
125
+ const removed = store.del(type, selector, { key, tag: opts.tag });
126
+ print({ ok: removed, gameId: store.gameId ?? null });
127
+ });
128
+
129
+ cmd
130
+ .command('clear')
131
+ .description('Clear all knowledge for the current game')
132
+ .action(() => {
133
+ const store = KnowledgeStore.forActiveAccount();
134
+ store.clear();
135
+ print({ ok: true, gameId: store.gameId ?? null });
136
+ });
137
+
138
+ return cmd;
139
+ }
@@ -1,51 +1,51 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
-
3
- vi.mock('../lib/load-context.js', () => ({
4
- loadContext: vi.fn(() => ({ persona: { exists: true }, memory: { exists: false } })),
5
- }));
6
-
7
- vi.mock('../runtime/auto-upgrade.js', () => ({
8
- runAutoUpgradeOnLoad: vi.fn(),
9
- }));
10
-
11
- import { runAutoUpgradeOnLoad } from '../runtime/auto-upgrade.js';
12
- import { createLoadCommand } from './load.js';
13
-
14
- describe('load command', () => {
15
- let logSpy: ReturnType<typeof vi.spyOn>;
16
- let errorSpy: ReturnType<typeof vi.spyOn>;
17
-
18
- beforeEach(() => {
19
- vi.mocked(runAutoUpgradeOnLoad).mockClear();
20
- logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
21
- errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
22
- });
23
-
24
- afterEach(() => {
25
- logSpy.mockRestore();
26
- errorSpy.mockRestore();
27
- });
28
-
29
- it('runs auto-upgrade check before printing JSON load context', async () => {
30
- const cmd = createLoadCommand();
31
- await cmd.parseAsync(['node', 'load'], { from: 'node' });
32
-
33
- expect(runAutoUpgradeOnLoad).toHaveBeenCalledOnce();
34
- expect(logSpy).toHaveBeenCalledOnce();
35
- expect(JSON.parse(String(logSpy.mock.calls[0][0]))).toEqual({
36
- persona: { exists: true },
37
- memory: { exists: false },
38
- });
39
- });
40
-
41
- it('routes auto-upgrade notifications to stderr', async () => {
42
- vi.mocked(runAutoUpgradeOnLoad).mockImplementation(({ notify }: any) => {
43
- notify('[clawclaw-cli] auto-upgrade installed 0.6.39');
44
- });
45
-
46
- const cmd = createLoadCommand();
47
- await cmd.parseAsync(['node', 'load'], { from: 'node' });
48
-
49
- expect(errorSpy).toHaveBeenCalledWith('[clawclaw-cli] auto-upgrade installed 0.6.39');
50
- });
51
- });
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ vi.mock('../lib/load-context.js', () => ({
4
+ loadContext: vi.fn(() => ({ persona: { exists: true }, memory: { exists: false } })),
5
+ }));
6
+
7
+ vi.mock('../runtime/auto-upgrade.js', () => ({
8
+ runAutoUpgradeOnLoad: vi.fn(),
9
+ }));
10
+
11
+ import { runAutoUpgradeOnLoad } from '../runtime/auto-upgrade.js';
12
+ import { createLoadCommand } from './load.js';
13
+
14
+ describe('load command', () => {
15
+ let logSpy: ReturnType<typeof vi.spyOn>;
16
+ let errorSpy: ReturnType<typeof vi.spyOn>;
17
+
18
+ beforeEach(() => {
19
+ vi.mocked(runAutoUpgradeOnLoad).mockClear();
20
+ logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
21
+ errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
22
+ });
23
+
24
+ afterEach(() => {
25
+ logSpy.mockRestore();
26
+ errorSpy.mockRestore();
27
+ });
28
+
29
+ it('runs auto-upgrade check before printing JSON load context', async () => {
30
+ const cmd = createLoadCommand();
31
+ await cmd.parseAsync(['node', 'load'], { from: 'node' });
32
+
33
+ expect(runAutoUpgradeOnLoad).toHaveBeenCalledOnce();
34
+ expect(logSpy).toHaveBeenCalledOnce();
35
+ expect(JSON.parse(String(logSpy.mock.calls[0][0]))).toEqual({
36
+ persona: { exists: true },
37
+ memory: { exists: false },
38
+ });
39
+ });
40
+
41
+ it('routes auto-upgrade notifications to stderr', async () => {
42
+ vi.mocked(runAutoUpgradeOnLoad).mockImplementation(({ notify }: any) => {
43
+ notify('[clawclaw-cli] auto-upgrade installed 0.6.39');
44
+ });
45
+
46
+ const cmd = createLoadCommand();
47
+ await cmd.parseAsync(['node', 'load'], { from: 'node' });
48
+
49
+ expect(errorSpy).toHaveBeenCalledWith('[clawclaw-cli] auto-upgrade installed 0.6.39');
50
+ });
51
+ });
@@ -1,13 +1,13 @@
1
- import { Command } from 'commander';
2
- import { loadContext } from '../lib/load-context.js';
3
- import { runAutoUpgradeOnLoad } from '../runtime/auto-upgrade.js';
4
-
5
- export function createLoadCommand(): Command {
6
- return new Command('load')
7
- .description('Read active account persona and memory files')
8
- .action(() => {
9
- runAutoUpgradeOnLoad({ notify: (message) => console.error(message) });
10
- const snapshot = loadContext();
11
- console.log(JSON.stringify(snapshot, null, 2));
12
- });
13
- }
1
+ import { Command } from 'commander';
2
+ import { loadContext } from '../lib/load-context.js';
3
+ import { runAutoUpgradeOnLoad } from '../runtime/auto-upgrade.js';
4
+
5
+ export function createLoadCommand(): Command {
6
+ return new Command('load')
7
+ .description('Read active account persona and memory files')
8
+ .action(() => {
9
+ runAutoUpgradeOnLoad({ notify: (message) => console.error(message) });
10
+ const snapshot = loadContext();
11
+ console.log(JSON.stringify(snapshot, null, 2));
12
+ });
13
+ }
@@ -1,106 +1,106 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { meetingHistoryCommandInternals } from './history.js';
3
-
4
- const allSeats = [
5
- { name: 'A', seat: 1 },
6
- { name: 'B', seat: 2 },
7
- { name: 'C', seat: 3 },
8
- ];
9
-
10
- describe('meeting history builder', () => {
11
- it('outputs the compact meeting shape with found-in-meeting deaths and ordered speeches', () => {
12
- const records = meetingHistoryCommandInternals.buildMeetingRecords([
13
- { type: 'meeting_start', tick: 10, actor_name: 'A' },
14
- { type: 'meeting_briefing', tick: 10, all_seats: allSeats },
15
- {
16
- type: 'meeting_state',
17
- round: 1,
18
- tick: 10,
19
- sub_phase: 'entry',
20
- speech_order: ['A', 'B'],
21
- alive_players: ['A', 'B'],
22
- },
23
- {
24
- type: 'meeting_state',
25
- round: 1,
26
- tick: 20,
27
- sub_phase: 'vote',
28
- speech_order: ['A', 'B'],
29
- speeches: { B: '我没问题', A: '' },
30
- speech_skipped: ['A'],
31
- votes_submitted: ['A'],
32
- alive_players: ['A', 'B'],
33
- },
34
- { type: 'no_exile', tick: 30, votes: { A: 'skip', B: 'skip' } },
35
- { type: 'meeting_ended', tick: 31, result: 'skip', result_target: null },
36
- ]);
37
- const [meeting] = meetingHistoryCommandInternals.allMeetingOutputs(records);
38
- expect(Object.keys(meeting)).toEqual([
39
- 'round',
40
- 'tick',
41
- 'caller',
42
- 'corpses_found',
43
- 'state',
44
- 'exiled_player',
45
- 'players',
46
- 'votes',
47
- 'speeches',
48
- ]);
49
- expect(meeting.corpses_found).toBeNull();
50
- expect(meeting.state).toBe('ended');
51
- expect(meeting.players).toEqual([
52
- { name: 'A', seat: 1, status: 'alive', death: null },
53
- { name: 'B', seat: 2, status: 'alive', death: null },
54
- { name: 'C', seat: 3, status: 'dead', death: { type: 'found_in_meeting', round: 1 } },
55
- ]);
56
- expect(meeting.votes).toEqual({ A: 'skip', B: 'skip' });
57
- expect(meeting.speeches).toEqual([
58
- { speaker: 'A', text: '', skipped: true },
59
- { speaker: 'B', text: '我没问题', skipped: false },
60
- ]);
61
- });
62
-
63
- it('marks exiled players and carries death status into later meetings', () => {
64
- const records = meetingHistoryCommandInternals.buildMeetingRecords([
65
- { type: 'meeting_start', tick: 10, actor_name: 'A' },
66
- { type: 'meeting_briefing', tick: 10, all_seats: allSeats },
67
- { type: 'meeting_state', round: 1, tick: 10, sub_phase: 'vote', alive_players: ['A', 'B', 'C'] },
68
- { type: 'exile', tick: 20, actor_name: 'B', votes: { A: 'B', C: 'B' } },
69
- { type: 'meeting_ended', tick: 21, result: 'exiled', result_target: 'B' },
70
- { type: 'meeting_start', tick: 100, actor_name: 'A' },
71
- { type: 'meeting_briefing', tick: 100, all_seats: allSeats },
72
- { type: 'meeting_state', round: 2, tick: 100, sub_phase: 'entry', alive_players: ['A', 'C'] },
73
- ]);
74
- const meetings = meetingHistoryCommandInternals.allMeetingOutputs(records);
75
- expect(meetings[0].players.find((player: any) => player.name === 'B')).toEqual({
76
- name: 'B',
77
- seat: 2,
78
- status: 'dead',
79
- death: { type: 'exiled', round: 1 },
80
- });
81
- expect(meetings[1].players.find((player: any) => player.name === 'B')?.death).toEqual({ type: 'exiled', round: 1 });
82
- });
83
-
84
- it('keeps current unfinished meeting state from meeting_state', () => {
85
- const records = meetingHistoryCommandInternals.buildMeetingRecords([
86
- { type: 'meeting_start', tick: 10, actor_name: 'A' },
87
- { type: 'meeting_briefing', tick: 10, all_seats: allSeats },
88
- { type: 'meeting_state', round: 1, tick: 11, sub_phase: 'speech', current_speaker: 'B', alive_players: ['A', 'B', 'C'] },
89
- ]);
90
- const [meeting] = meetingHistoryCommandInternals.allMeetingOutputs(records);
91
- expect(meeting.state).toBe('speech');
92
- expect(meeting.speeches).toEqual([]);
93
- });
94
-
95
- it('marks an unfinished previous meeting interrupted when a new meeting starts', () => {
96
- const records = meetingHistoryCommandInternals.buildMeetingRecords([
97
- { type: 'meeting_start', tick: 10, actor_name: 'A' },
98
- { type: 'meeting_briefing', tick: 10, all_seats: allSeats },
99
- { type: 'meeting_start', tick: 100, actor_name: 'C' },
100
- { type: 'meeting_briefing', tick: 100, all_seats: allSeats },
101
- ]);
102
- const meetings = meetingHistoryCommandInternals.allMeetingOutputs(records);
103
- expect(meetings[0].state).toBe('interrupted_or_missing_end');
104
- expect(meetings[1].state).toBe('interrupted_or_missing_end');
105
- });
106
- });
1
+ import { describe, expect, it } from 'vitest';
2
+ import { meetingHistoryCommandInternals } from './history.js';
3
+
4
+ const allSeats = [
5
+ { name: 'A', seat: 1 },
6
+ { name: 'B', seat: 2 },
7
+ { name: 'C', seat: 3 },
8
+ ];
9
+
10
+ describe('meeting history builder', () => {
11
+ it('outputs the compact meeting shape with found-in-meeting deaths and ordered speeches', () => {
12
+ const records = meetingHistoryCommandInternals.buildMeetingRecords([
13
+ { type: 'meeting_start', tick: 10, actor_name: 'A' },
14
+ { type: 'meeting_briefing', tick: 10, all_seats: allSeats },
15
+ {
16
+ type: 'meeting_state',
17
+ round: 1,
18
+ tick: 10,
19
+ sub_phase: 'entry',
20
+ speech_order: ['A', 'B'],
21
+ alive_players: ['A', 'B'],
22
+ },
23
+ {
24
+ type: 'meeting_state',
25
+ round: 1,
26
+ tick: 20,
27
+ sub_phase: 'vote',
28
+ speech_order: ['A', 'B'],
29
+ speeches: { B: '我没问题', A: '' },
30
+ speech_skipped: ['A'],
31
+ votes_submitted: ['A'],
32
+ alive_players: ['A', 'B'],
33
+ },
34
+ { type: 'no_exile', tick: 30, votes: { A: 'skip', B: 'skip' } },
35
+ { type: 'meeting_ended', tick: 31, result: 'skip', result_target: null },
36
+ ]);
37
+ const [meeting] = meetingHistoryCommandInternals.allMeetingOutputs(records);
38
+ expect(Object.keys(meeting)).toEqual([
39
+ 'round',
40
+ 'tick',
41
+ 'caller',
42
+ 'corpses_found',
43
+ 'state',
44
+ 'exiled_player',
45
+ 'players',
46
+ 'votes',
47
+ 'speeches',
48
+ ]);
49
+ expect(meeting.corpses_found).toBeNull();
50
+ expect(meeting.state).toBe('ended');
51
+ expect(meeting.players).toEqual([
52
+ { name: 'A', seat: 1, status: 'alive', death: null },
53
+ { name: 'B', seat: 2, status: 'alive', death: null },
54
+ { name: 'C', seat: 3, status: 'dead', death: { type: 'found_in_meeting', round: 1 } },
55
+ ]);
56
+ expect(meeting.votes).toEqual({ A: 'skip', B: 'skip' });
57
+ expect(meeting.speeches).toEqual([
58
+ { speaker: 'A', text: '', skipped: true },
59
+ { speaker: 'B', text: '我没问题', skipped: false },
60
+ ]);
61
+ });
62
+
63
+ it('marks exiled players and carries death status into later meetings', () => {
64
+ const records = meetingHistoryCommandInternals.buildMeetingRecords([
65
+ { type: 'meeting_start', tick: 10, actor_name: 'A' },
66
+ { type: 'meeting_briefing', tick: 10, all_seats: allSeats },
67
+ { type: 'meeting_state', round: 1, tick: 10, sub_phase: 'vote', alive_players: ['A', 'B', 'C'] },
68
+ { type: 'exile', tick: 20, actor_name: 'B', votes: { A: 'B', C: 'B' } },
69
+ { type: 'meeting_ended', tick: 21, result: 'exiled', result_target: 'B' },
70
+ { type: 'meeting_start', tick: 100, actor_name: 'A' },
71
+ { type: 'meeting_briefing', tick: 100, all_seats: allSeats },
72
+ { type: 'meeting_state', round: 2, tick: 100, sub_phase: 'entry', alive_players: ['A', 'C'] },
73
+ ]);
74
+ const meetings = meetingHistoryCommandInternals.allMeetingOutputs(records);
75
+ expect(meetings[0].players.find((player: any) => player.name === 'B')).toEqual({
76
+ name: 'B',
77
+ seat: 2,
78
+ status: 'dead',
79
+ death: { type: 'exiled', round: 1 },
80
+ });
81
+ expect(meetings[1].players.find((player: any) => player.name === 'B')?.death).toEqual({ type: 'exiled', round: 1 });
82
+ });
83
+
84
+ it('keeps current unfinished meeting state from meeting_state', () => {
85
+ const records = meetingHistoryCommandInternals.buildMeetingRecords([
86
+ { type: 'meeting_start', tick: 10, actor_name: 'A' },
87
+ { type: 'meeting_briefing', tick: 10, all_seats: allSeats },
88
+ { type: 'meeting_state', round: 1, tick: 11, sub_phase: 'speech', current_speaker: 'B', alive_players: ['A', 'B', 'C'] },
89
+ ]);
90
+ const [meeting] = meetingHistoryCommandInternals.allMeetingOutputs(records);
91
+ expect(meeting.state).toBe('speech');
92
+ expect(meeting.speeches).toEqual([]);
93
+ });
94
+
95
+ it('marks an unfinished previous meeting interrupted when a new meeting starts', () => {
96
+ const records = meetingHistoryCommandInternals.buildMeetingRecords([
97
+ { type: 'meeting_start', tick: 10, actor_name: 'A' },
98
+ { type: 'meeting_briefing', tick: 10, all_seats: allSeats },
99
+ { type: 'meeting_start', tick: 100, actor_name: 'C' },
100
+ { type: 'meeting_briefing', tick: 100, all_seats: allSeats },
101
+ ]);
102
+ const meetings = meetingHistoryCommandInternals.allMeetingOutputs(records);
103
+ expect(meetings[0].state).toBe('interrupted_or_missing_end');
104
+ expect(meetings[1].state).toBe('interrupted_or_missing_end');
105
+ });
106
+ });