@myclaw163/clawclaw-cli 0.6.71 → 0.6.74
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 +377 -427
- package/bin/clawclaw-cli.mjs +3 -3
- package/package.json +48 -48
- package/personas//347/220/206/346/231/272/346/270/251/345/222/214.md +23 -23
- package/personas//350/200/201/350/260/213/346/267/261/347/256/227.md +22 -22
- package/personas//350/257/232/346/201/263/347/233/264/347/216/207.md +22 -22
- package/personas//350/275/273/346/235/276/346/264/273/346/263/274.md +22 -22
- package/personas//351/207/216/346/200/247/345/217/233/351/200/206.md +23 -23
- package/scripts/check-skill-command-surface.mjs +116 -0
- package/scripts/find-hide-spots.py +157 -157
- package/scripts/postinstall.mjs +20 -20
- package/scripts/sync-bundled-skill.mjs +245 -245
- package/scripts/sync-bundled-skill.test.mjs +152 -152
- package/skills/clawclaw/SKILL.md +246 -244
- package/skills/clawclaw/references/CHATTERBOX.md +141 -142
- package/skills/clawclaw/references/COMMANDS.md +155 -148
- package/skills/clawclaw/references/GAME-MECHANICS.md +188 -188
- package/skills/clawclaw/references/HUB.md +48 -48
- package/skills/clawclaw/references/KNOWLEDGE.md +42 -45
- package/skills/clawclaw/references/STRATEGIES.md +59 -59
- package/skills/clawclaw/references/STREAM.md +93 -91
- package/skills/clawclaw/references/TACTICS.md +65 -65
- package/src/assets/clawclaw-ascii-map.txt +40 -40
- package/src/cli.ts +110 -110
- package/src/commands/_schema.ts +124 -109
- package/src/commands/account.ts +209 -209
- package/src/commands/do.test.ts +84 -73
- package/src/commands/do.ts +130 -126
- package/src/commands/events.test.ts +71 -71
- package/src/commands/events.ts +221 -155
- package/src/commands/game-map.test.ts +28 -28
- package/src/commands/game-start-plan.test.ts +84 -84
- package/src/commands/game.ts +1113 -1047
- package/src/commands/history-player.test.ts +102 -102
- package/src/commands/history.ts +573 -573
- package/src/commands/hub.test.ts +96 -96
- package/src/commands/hub.ts +234 -234
- package/src/commands/knowledge.test.ts +13 -13
- package/src/commands/knowledge.ts +139 -139
- package/src/commands/load.test.ts +51 -51
- package/src/commands/load.ts +13 -13
- package/src/commands/meeting-history.test.ts +106 -106
- package/src/commands/memory.ts +40 -40
- package/src/commands/peek.ts +45 -45
- package/src/commands/persona.ts +57 -57
- package/src/commands/setup/codex.ts +266 -266
- package/src/commands/skill.ts +128 -128
- package/src/commands/state.ts +46 -46
- package/src/commands/strategy.test.ts +145 -145
- package/src/commands/strategy.ts +181 -181
- package/src/commands/tts.ts +128 -128
- package/src/commands/upgrade.test.ts +82 -82
- package/src/commands/upgrade.ts +148 -148
- package/src/commands/watch.test.ts +999 -977
- package/src/commands/watch.ts +660 -658
- package/src/lib/auth.test.ts +74 -74
- package/src/lib/auth.ts +186 -186
- package/src/lib/command-meta.ts +37 -37
- package/src/lib/game-client.ts +403 -391
- package/src/lib/game-context.ts +92 -0
- package/src/lib/http-keepalive.ts +15 -15
- package/src/lib/http-transport.test.ts +42 -42
- package/src/lib/http-transport.ts +113 -113
- package/src/lib/hub-client.test.ts +56 -56
- package/src/lib/hub-client.ts +88 -88
- package/src/lib/hub-install.test.ts +98 -98
- package/src/lib/hub-install.ts +121 -121
- package/src/lib/hub-reminder.ts +75 -75
- package/src/lib/hub-unzip.test.ts +69 -69
- package/src/lib/hub-unzip.ts +62 -62
- package/src/lib/init-command.test.ts +75 -75
- package/src/lib/init-command.ts +120 -120
- package/src/lib/knowledge-store.test.ts +170 -170
- package/src/lib/knowledge-store.ts +369 -369
- package/src/lib/load-context.test.ts +52 -52
- package/src/lib/load-context.ts +52 -52
- package/src/lib/match-state.test.ts +134 -134
- package/src/lib/match-state.ts +94 -94
- package/src/lib/netease-tts.ts +83 -83
- package/src/lib/normalize.ts +42 -42
- package/src/lib/persona.test.ts +41 -41
- package/src/lib/persona.ts +72 -72
- package/src/lib/server-registry.ts +152 -152
- package/src/lib/skill-version.test.ts +48 -48
- package/src/lib/skill-version.ts +19 -19
- package/src/lib/strategy-export.test.ts +232 -232
- package/src/lib/strategy-export.ts +242 -242
- package/src/lib/tts-keys.ts +7 -7
- package/src/lib/tts-speech.test.ts +63 -63
- package/src/lib/tts-speech.ts +76 -76
- package/src/lib/workspace-argv.test.ts +49 -49
- package/src/lib/workspace-argv.ts +44 -44
- package/src/perception/player-history-store.test.ts +87 -87
- package/src/perception/player-history-store.ts +194 -194
- package/src/pipeline/event-format.test.ts +243 -215
- package/src/pipeline/event-format.ts +501 -485
- package/src/pipeline/event-hints.ts +195 -190
- package/src/pipeline/event-store.test.ts +28 -28
- package/src/pipeline/event-store.ts +193 -193
- package/src/pipeline/pipeline.ts +35 -35
- package/src/pipeline/player-projection.test.ts +119 -0
- package/src/pipeline/player-projection.ts +380 -0
- package/src/runtime/auto-upgrade.test.ts +66 -66
- package/src/runtime/auto-upgrade.ts +31 -31
- package/src/runtime/event-daemon.test.ts +209 -141
- package/src/runtime/event-daemon.ts +519 -457
- package/src/runtime/owner-control.ts +150 -150
- package/src/runtime/raw-ws-log.test.ts +33 -33
- package/src/runtime/raw-ws-log.ts +32 -32
- package/src/runtime/runtime-logger.ts +107 -107
- package/src/runtime/ws-client.test.ts +125 -104
- package/src/runtime/ws-client.ts +287 -272
- package/src/sdk/action.ts +166 -166
- package/src/sdk/index.ts +110 -110
- package/src/sdk/types.ts +161 -161
- package/src/strategies/avoid-lone.ts +12 -12
- package/src/strategies/avoid-players.knowledge.md +19 -19
- package/src/strategies/avoid-players.ts +16 -16
- package/src/strategies/corpse-patrol.ts +23 -23
- package/src/strategies/crab-sabotage.ts +22 -22
- package/src/strategies/custom-module.test.ts +270 -270
- package/src/strategies/find-player.ts +17 -17
- package/src/strategies/game-utils.test.ts +242 -242
- package/src/strategies/game-utils.ts +846 -846
- package/src/strategies/goals/anchor-linger.ts +77 -77
- package/src/strategies/goals/avoid-lone-top.ts +168 -168
- package/src/strategies/goals/avoid-players-top.test.ts +83 -83
- package/src/strategies/goals/avoid-players-top.ts +121 -121
- package/src/strategies/goals/conversation-goal.ts +51 -51
- package/src/strategies/goals/corpse-patrol-top.ts +113 -113
- package/src/strategies/goals/crab-octopus-reflexes.ts +101 -101
- package/src/strategies/goals/crab-sabotage-top.ts +197 -197
- package/src/strategies/goals/emergency-hunt-goal.ts +28 -28
- package/src/strategies/goals/find-player-top.ts +93 -93
- package/src/strategies/goals/flee-players-goal.ts +53 -53
- package/src/strategies/goals/follow-companion-goal.ts +106 -106
- package/src/strategies/goals/goal-manager.ts +41 -41
- package/src/strategies/goals/goal-root-strategy.ts +49 -49
- package/src/strategies/goals/goal.ts +28 -28
- package/src/strategies/goals/hide-top.ts +197 -197
- package/src/strategies/goals/keep-away-goal.ts +221 -221
- package/src/strategies/goals/kill-frenzy-top.ts +80 -80
- package/src/strategies/goals/kill-lone-top.ts +160 -160
- package/src/strategies/goals/kill-target-goal.ts +59 -59
- package/src/strategies/goals/kill-target-top.ts +109 -109
- package/src/strategies/goals/leaf-goal.ts +27 -27
- package/src/strategies/goals/linger-corpse-goal.ts +35 -35
- package/src/strategies/goals/lone-kill-core.ts +82 -82
- package/src/strategies/goals/lone-kill-goal.ts +24 -24
- package/src/strategies/goals/lone-kill-task-top.test.ts +85 -85
- package/src/strategies/goals/lone-kill-task-top.ts +133 -133
- package/src/strategies/goals/move-room-goal.ts +60 -60
- package/src/strategies/goals/normal-shrimp-top.test.ts +80 -80
- package/src/strategies/goals/normal-shrimp-top.ts +242 -242
- package/src/strategies/goals/paradise-fish-top.test.ts +126 -126
- package/src/strategies/goals/paradise-fish-top.ts +224 -224
- package/src/strategies/goals/patrol-top.ts +57 -57
- package/src/strategies/goals/report-patrol-top.ts +80 -80
- package/src/strategies/goals/safe-task-goal.ts +102 -102
- package/src/strategies/goals/social-task-top.ts +161 -161
- package/src/strategies/goals/task-kill-report-top.ts +163 -163
- package/src/strategies/goals/task-only-top.ts +57 -57
- package/src/strategies/goals/task-or-patrol-goal.ts +41 -41
- package/src/strategies/goals/task-report-top.ts +57 -57
- package/src/strategies/goals/wander-task-goal.ts +33 -33
- package/src/strategies/goals/warrior-shrimp-top.test.ts +87 -87
- package/src/strategies/goals/warrior-shrimp-top.ts +267 -267
- package/src/strategies/greeting.ts +53 -53
- package/src/strategies/hide-spots.ts +59 -59
- package/src/strategies/hide.ts +24 -24
- package/src/strategies/kill-frenzy.ts +13 -13
- package/src/strategies/kill-lone.knowledge.md +17 -17
- package/src/strategies/kill-lone.ts +14 -14
- package/src/strategies/kill-target.ts +19 -19
- package/src/strategies/loader.test.ts +678 -678
- package/src/strategies/loader.ts +179 -179
- package/src/strategies/lone-kill-task.ts +22 -22
- package/src/strategies/meeting-gate.test.ts +59 -59
- package/src/strategies/meeting-gate.ts +23 -23
- package/src/strategies/move-room.ts +16 -16
- package/src/strategies/new-events-backfill.ts +98 -98
- package/src/strategies/off-route-points.ts +105 -105
- package/src/strategies/paradise-fish.knowledge.md +19 -19
- package/src/strategies/paradise-fish.ts +26 -26
- package/src/strategies/pathfind/distance-field.ts +150 -150
- package/src/strategies/pathfind/escape-planner.test.ts +197 -197
- package/src/strategies/pathfind/escape-planner.ts +355 -355
- package/src/strategies/pathfind/walkable-grid.ts +117 -117
- package/src/strategies/patrol.ts +12 -12
- package/src/strategies/player-targets.ts +13 -13
- package/src/strategies/report-patrol.ts +12 -12
- package/src/strategies/shrimp-memory.knowledge.md +19 -19
- package/src/strategies/shrimp-memory.ts +26 -26
- package/src/strategies/social-task.test.ts +28 -28
- package/src/strategies/social-task.ts +50 -50
- package/src/strategies/spawn.ts +82 -82
- package/src/strategies/speech-module.ts +123 -123
- package/src/strategies/strategy-loop.test.ts +15 -0
- package/src/strategies/strategy-loop.ts +776 -771
- package/src/strategies/task-kill-report.ts +18 -18
- package/src/strategies/task-only.ts +12 -12
- package/src/strategies/task-report.ts +23 -23
- package/src/strategies/types.ts +109 -109
- package/src/strategies/warrior-memory.knowledge.md +21 -21
- package/src/strategies/warrior-memory.ts +17 -17
package/src/lib/init-command.ts
CHANGED
|
@@ -1,120 +1,120 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
|
-
import { isAbsolute, join, resolve, win32 } from 'path';
|
|
3
|
-
import { homedir } from 'os';
|
|
4
|
-
import type { AuthProfile } from './auth.js';
|
|
5
|
-
|
|
6
|
-
const ACCOUNT_DIRS = ['state', 'logs', 'games'];
|
|
7
|
-
|
|
8
|
-
export const CLAWCLAW_WORKSPACE_DIR = 'CLAWCLAW_WORKSPACE_DIR';
|
|
9
|
-
|
|
10
|
-
export type ResolveWorkspaceDirOptions = {
|
|
11
|
-
env?: Record<string, string | undefined>;
|
|
12
|
-
platform?: NodeJS.Platform;
|
|
13
|
-
homeDir?: string;
|
|
14
|
-
cwd?: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export function resolveWorkspaceDir({
|
|
18
|
-
env = process.env,
|
|
19
|
-
platform = process.platform,
|
|
20
|
-
homeDir = homedir(),
|
|
21
|
-
cwd = process.cwd(),
|
|
22
|
-
}: ResolveWorkspaceDirOptions = {}): string {
|
|
23
|
-
const rawWorkspaceDir = env[CLAWCLAW_WORKSPACE_DIR];
|
|
24
|
-
const pathApi = platform === 'win32' ? win32 : { isAbsolute, join, resolve };
|
|
25
|
-
if (typeof rawWorkspaceDir === 'string') {
|
|
26
|
-
const trimmedWorkspaceDir = rawWorkspaceDir.trim();
|
|
27
|
-
if (trimmedWorkspaceDir !== '') {
|
|
28
|
-
return pathApi.isAbsolute(trimmedWorkspaceDir)
|
|
29
|
-
? trimmedWorkspaceDir
|
|
30
|
-
: pathApi.resolve(cwd, trimmedWorkspaceDir);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (platform === 'win32') {
|
|
35
|
-
const appData = env.APPDATA ?? win32.join(homeDir, 'AppData', 'Roaming');
|
|
36
|
-
return win32.join(appData, 'clawclaw');
|
|
37
|
-
}
|
|
38
|
-
return join(homeDir, '.clawclaw');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function getWorkspaceDir(): string {
|
|
42
|
-
return resolveWorkspaceDir();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function cmdInit(force = false): { created: boolean; path: string } {
|
|
46
|
-
const workspaceDir = getWorkspaceDir();
|
|
47
|
-
const alreadyExists = existsSync(join(workspaceDir, 'config.json'));
|
|
48
|
-
|
|
49
|
-
if (alreadyExists && !force) {
|
|
50
|
-
return { created: false, path: workspaceDir };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
mkdirSync(workspaceDir, { recursive: true });
|
|
54
|
-
mkdirSync(join(workspaceDir, 'accounts'), { recursive: true });
|
|
55
|
-
|
|
56
|
-
if (!alreadyExists) {
|
|
57
|
-
writeFileSync(
|
|
58
|
-
join(workspaceDir, 'config.json'),
|
|
59
|
-
JSON.stringify({}, null, 2),
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return { created: !alreadyExists, path: workspaceDir };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function getAccountDir(accountId: string): string {
|
|
67
|
-
return join(getWorkspaceDir(), 'accounts', accountId);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function createAccountDirs(accountId: string): void {
|
|
71
|
-
const accountDir = getAccountDir(accountId);
|
|
72
|
-
for (const sub of ACCOUNT_DIRS) {
|
|
73
|
-
mkdirSync(join(accountDir, sub), { recursive: true });
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function getAccountId(profile: Pick<AuthProfile, 'apiKey'>): string {
|
|
78
|
-
return profile.apiKey;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function createProfileDirs(profile: Pick<AuthProfile, 'apiKey'>): void {
|
|
82
|
-
createAccountDirs(getAccountId(profile));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function getStateDir(accountId: string): string {
|
|
86
|
-
return join(getAccountDir(accountId), 'state');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function getLogsDir(accountId: string): string {
|
|
90
|
-
return join(getAccountDir(accountId), 'logs');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function getGamesDir(accountId: string): string {
|
|
94
|
-
return join(getAccountDir(accountId), 'games');
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function getProfileStateDir(profile: Pick<AuthProfile, 'apiKey'>): string {
|
|
98
|
-
createProfileDirs(profile);
|
|
99
|
-
return getStateDir(getAccountId(profile));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function getProfileLogsDir(profile: Pick<AuthProfile, 'apiKey'>): string {
|
|
103
|
-
createProfileDirs(profile);
|
|
104
|
-
return getLogsDir(getAccountId(profile));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function getProfileGamesDir(profile: Pick<AuthProfile, 'apiKey'>): string {
|
|
108
|
-
createProfileDirs(profile);
|
|
109
|
-
return getGamesDir(getAccountId(profile));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function getProfileMemoryFile(profile: Pick<AuthProfile, 'apiKey'>): string {
|
|
113
|
-
createProfileDirs(profile);
|
|
114
|
-
return join(getAccountDir(getAccountId(profile)), 'memory.md');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export function getProfilePersonaFile(profile: Pick<AuthProfile, 'apiKey'>): string {
|
|
118
|
-
createProfileDirs(profile);
|
|
119
|
-
return join(getAccountDir(getAccountId(profile)), 'persona.md');
|
|
120
|
-
}
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import { isAbsolute, join, resolve, win32 } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import type { AuthProfile } from './auth.js';
|
|
5
|
+
|
|
6
|
+
const ACCOUNT_DIRS = ['state', 'logs', 'games'];
|
|
7
|
+
|
|
8
|
+
export const CLAWCLAW_WORKSPACE_DIR = 'CLAWCLAW_WORKSPACE_DIR';
|
|
9
|
+
|
|
10
|
+
export type ResolveWorkspaceDirOptions = {
|
|
11
|
+
env?: Record<string, string | undefined>;
|
|
12
|
+
platform?: NodeJS.Platform;
|
|
13
|
+
homeDir?: string;
|
|
14
|
+
cwd?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function resolveWorkspaceDir({
|
|
18
|
+
env = process.env,
|
|
19
|
+
platform = process.platform,
|
|
20
|
+
homeDir = homedir(),
|
|
21
|
+
cwd = process.cwd(),
|
|
22
|
+
}: ResolveWorkspaceDirOptions = {}): string {
|
|
23
|
+
const rawWorkspaceDir = env[CLAWCLAW_WORKSPACE_DIR];
|
|
24
|
+
const pathApi = platform === 'win32' ? win32 : { isAbsolute, join, resolve };
|
|
25
|
+
if (typeof rawWorkspaceDir === 'string') {
|
|
26
|
+
const trimmedWorkspaceDir = rawWorkspaceDir.trim();
|
|
27
|
+
if (trimmedWorkspaceDir !== '') {
|
|
28
|
+
return pathApi.isAbsolute(trimmedWorkspaceDir)
|
|
29
|
+
? trimmedWorkspaceDir
|
|
30
|
+
: pathApi.resolve(cwd, trimmedWorkspaceDir);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (platform === 'win32') {
|
|
35
|
+
const appData = env.APPDATA ?? win32.join(homeDir, 'AppData', 'Roaming');
|
|
36
|
+
return win32.join(appData, 'clawclaw');
|
|
37
|
+
}
|
|
38
|
+
return join(homeDir, '.clawclaw');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getWorkspaceDir(): string {
|
|
42
|
+
return resolveWorkspaceDir();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function cmdInit(force = false): { created: boolean; path: string } {
|
|
46
|
+
const workspaceDir = getWorkspaceDir();
|
|
47
|
+
const alreadyExists = existsSync(join(workspaceDir, 'config.json'));
|
|
48
|
+
|
|
49
|
+
if (alreadyExists && !force) {
|
|
50
|
+
return { created: false, path: workspaceDir };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
mkdirSync(workspaceDir, { recursive: true });
|
|
54
|
+
mkdirSync(join(workspaceDir, 'accounts'), { recursive: true });
|
|
55
|
+
|
|
56
|
+
if (!alreadyExists) {
|
|
57
|
+
writeFileSync(
|
|
58
|
+
join(workspaceDir, 'config.json'),
|
|
59
|
+
JSON.stringify({}, null, 2),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { created: !alreadyExists, path: workspaceDir };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getAccountDir(accountId: string): string {
|
|
67
|
+
return join(getWorkspaceDir(), 'accounts', accountId);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function createAccountDirs(accountId: string): void {
|
|
71
|
+
const accountDir = getAccountDir(accountId);
|
|
72
|
+
for (const sub of ACCOUNT_DIRS) {
|
|
73
|
+
mkdirSync(join(accountDir, sub), { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getAccountId(profile: Pick<AuthProfile, 'apiKey'>): string {
|
|
78
|
+
return profile.apiKey;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function createProfileDirs(profile: Pick<AuthProfile, 'apiKey'>): void {
|
|
82
|
+
createAccountDirs(getAccountId(profile));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function getStateDir(accountId: string): string {
|
|
86
|
+
return join(getAccountDir(accountId), 'state');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getLogsDir(accountId: string): string {
|
|
90
|
+
return join(getAccountDir(accountId), 'logs');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function getGamesDir(accountId: string): string {
|
|
94
|
+
return join(getAccountDir(accountId), 'games');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function getProfileStateDir(profile: Pick<AuthProfile, 'apiKey'>): string {
|
|
98
|
+
createProfileDirs(profile);
|
|
99
|
+
return getStateDir(getAccountId(profile));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getProfileLogsDir(profile: Pick<AuthProfile, 'apiKey'>): string {
|
|
103
|
+
createProfileDirs(profile);
|
|
104
|
+
return getLogsDir(getAccountId(profile));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getProfileGamesDir(profile: Pick<AuthProfile, 'apiKey'>): string {
|
|
108
|
+
createProfileDirs(profile);
|
|
109
|
+
return getGamesDir(getAccountId(profile));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function getProfileMemoryFile(profile: Pick<AuthProfile, 'apiKey'>): string {
|
|
113
|
+
createProfileDirs(profile);
|
|
114
|
+
return join(getAccountDir(getAccountId(profile)), 'memory.md');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function getProfilePersonaFile(profile: Pick<AuthProfile, 'apiKey'>): string {
|
|
118
|
+
createProfileDirs(profile);
|
|
119
|
+
return join(getAccountDir(getAccountId(profile)), 'persona.md');
|
|
120
|
+
}
|
|
@@ -1,170 +1,170 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { mkdtempSync, rmSync, writeFileSync } from 'fs';
|
|
3
|
-
import { tmpdir } from 'os';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
import {
|
|
6
|
-
buildKnowledgeView,
|
|
7
|
-
KnowledgeStore,
|
|
8
|
-
readKnowledgeFile,
|
|
9
|
-
readKnowledgeFileResult,
|
|
10
|
-
} from './knowledge-store.js';
|
|
11
|
-
|
|
12
|
-
describe('KnowledgeStore', () => {
|
|
13
|
-
let dir: string;
|
|
14
|
-
let path: string;
|
|
15
|
-
let now: Date;
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
dir = mkdtempSync(join(tmpdir(), 'clawclaw-knowledge-'));
|
|
19
|
-
path = join(dir, 'knowledge.json');
|
|
20
|
-
now = new Date('2026-06-05T00:00:00.000Z');
|
|
21
|
-
vi.useFakeTimers();
|
|
22
|
-
vi.setSystemTime(now);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
afterEach(() => {
|
|
26
|
-
vi.useRealTimers();
|
|
27
|
-
rmSync(dir, { recursive: true, force: true });
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
function tick(ms = 1000): void {
|
|
31
|
-
now = new Date(now.getTime() + ms);
|
|
32
|
-
vi.setSystemTime(now);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function store(gameId = 'game-1'): KnowledgeStore {
|
|
36
|
-
return new KnowledgeStore(path, gameId);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function view(playerNamesBySeat: Record<string, string> = {}) {
|
|
40
|
-
return buildKnowledgeView(readKnowledgeFile(path), { playerNamesBySeat });
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
it('writes facts and tags under the current game id', () => {
|
|
44
|
-
store().set({
|
|
45
|
-
type: 'player',
|
|
46
|
-
selector: '5',
|
|
47
|
-
key: 'role',
|
|
48
|
-
value: 'impostor',
|
|
49
|
-
tags: ['kill_if_armed'],
|
|
50
|
-
note: 'vent read',
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const file = readKnowledgeFile(path);
|
|
54
|
-
|
|
55
|
-
expect(file?.gameId).toBe('game-1');
|
|
56
|
-
expect(file?.subjects['player:5']?.facts?.role.value).toBe('impostor');
|
|
57
|
-
expect(file?.subjects['player:5']?.tags?.kill_if_armed.note).toBe('vent read');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('preserves explicit null fact values', () => {
|
|
61
|
-
store().set({
|
|
62
|
-
type: 'player',
|
|
63
|
-
selector: '5',
|
|
64
|
-
key: 'role',
|
|
65
|
-
value: null,
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
expect(readKnowledgeFile(path)?.subjects['player:5']?.facts?.role.value).toBeNull();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('distinguishes invalid files from missing files', () => {
|
|
72
|
-
expect(readKnowledgeFileResult(path)).toEqual({ status: 'missing' });
|
|
73
|
-
|
|
74
|
-
writeFileSync(path, '{bad json');
|
|
75
|
-
|
|
76
|
-
expect(readKnowledgeFileResult(path).status).toBe('invalid');
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('returns an empty file when loading knowledge from another game', () => {
|
|
80
|
-
store('old-game').set({ type: 'player', selector: '5', tags: ['hostile'] });
|
|
81
|
-
|
|
82
|
-
expect(store('new-game').list()).toEqual({
|
|
83
|
-
version: 1,
|
|
84
|
-
gameId: 'new-game',
|
|
85
|
-
subjects: {},
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('treats unmarked players as suspected by default', () => {
|
|
90
|
-
const s = store();
|
|
91
|
-
s.set({ type: 'player', selector: '5', tags: ['hostile'] });
|
|
92
|
-
|
|
93
|
-
const v = view({ '5': 'Bob', '9': 'Zed' });
|
|
94
|
-
|
|
95
|
-
// 显式标记的两档
|
|
96
|
-
expect(v.isHostile({ seat: 5 })).toBe(true);
|
|
97
|
-
expect(v.isSuspect({ seat: 5 })).toBe(false);
|
|
98
|
-
// 完全没标记的人 = 被怀疑(markOf 返回 undefined)
|
|
99
|
-
expect(v.markOf({ seat: 9 })).toBeUndefined();
|
|
100
|
-
expect(v.isSuspect({ name: 'Zed' })).toBe(true);
|
|
101
|
-
expect(v.isHostile({ seat: 9 })).toBe(false);
|
|
102
|
-
expect(v.isTrusted({ seat: 9 })).toBe(false);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('aggregates player marks written by seat and by name', () => {
|
|
106
|
-
const s = store();
|
|
107
|
-
s.set({ type: 'player', selector: '5', tags: ['hostile'] });
|
|
108
|
-
|
|
109
|
-
const v = view({ '5': 'Bob' });
|
|
110
|
-
|
|
111
|
-
expect(v.isHostile({ name: 'Bob' })).toBe(true);
|
|
112
|
-
expect(v.markOf({ seat: 5 })).toBe('hostile');
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('uses the latest mark across seat and name selectors', () => {
|
|
116
|
-
const s = store();
|
|
117
|
-
s.set({ type: 'player', selector: '5', tags: ['hostile'] });
|
|
118
|
-
tick();
|
|
119
|
-
s.set({ type: 'player', selector: 'Bob', tags: ['trusted'] });
|
|
120
|
-
|
|
121
|
-
const v = view({ '5': 'Bob' });
|
|
122
|
-
|
|
123
|
-
expect(v.markOf({ name: 'Bob' })).toBe('trusted');
|
|
124
|
-
expect(v.isHostile({ name: 'Bob' })).toBe(false);
|
|
125
|
-
expect(v.isTrusted({ name: 'Bob' })).toBe(true);
|
|
126
|
-
expect(v.hasTag({ seat: 5 }, 'hostile')).toBe(false);
|
|
127
|
-
expect(v.hasTag({ seat: 5 }, 'trusted')).toBe(true);
|
|
128
|
-
expect(v.selectorsWithTag('player', 'hostile')).toEqual([]);
|
|
129
|
-
expect(v.selectorsWithTag('player', 'trusted')).toEqual(['Bob']);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('keeps role facts independent from strategy marks', () => {
|
|
133
|
-
const s = store();
|
|
134
|
-
s.set({ type: 'player', selector: '5', key: 'role', value: 'impostor' });
|
|
135
|
-
s.set({ type: 'player', selector: '5', tags: ['trusted'] });
|
|
136
|
-
|
|
137
|
-
const v = view();
|
|
138
|
-
|
|
139
|
-
expect(v.roleOf({ seat: 5 })).toBe('impostor');
|
|
140
|
-
expect(v.markOf({ seat: 5 })).toBe('trusted');
|
|
141
|
-
expect(v.isHostile({ seat: 5 })).toBe(false);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('can flip player marks', () => {
|
|
145
|
-
const s = store();
|
|
146
|
-
s.set({ type: 'player', selector: '5', tags: ['trusted'] });
|
|
147
|
-
tick();
|
|
148
|
-
s.set({ type: 'player', selector: 'Bob', tags: ['hostile'] });
|
|
149
|
-
|
|
150
|
-
const v = view({ '5': 'Bob' });
|
|
151
|
-
|
|
152
|
-
expect(v.isHostile({ seat: 5 })).toBe(true);
|
|
153
|
-
expect(v.isTrusted({ seat: 5 })).toBe(false);
|
|
154
|
-
expect(v.selectorsWithTag('player', 'trusted')).toEqual([]);
|
|
155
|
-
expect(v.selectorsWithTag('player', 'hostile')).toEqual(['Bob']);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('maps legacy tags onto the two canonical marks', () => {
|
|
159
|
-
const s = store();
|
|
160
|
-
s.set({ type: 'player', selector: '6', tags: ['kill_if_armed'] });
|
|
161
|
-
s.set({ type: 'player', selector: '7', tags: ['protected'] });
|
|
162
|
-
|
|
163
|
-
const v = view();
|
|
164
|
-
|
|
165
|
-
expect(v.markOf({ seat: 6 })).toBe('hostile');
|
|
166
|
-
expect(v.markOf({ seat: 7 })).toBe('trusted');
|
|
167
|
-
expect(v.hasTag({ seat: 6 }, 'hostile')).toBe(true);
|
|
168
|
-
expect(v.hasTag({ seat: 7 }, 'trusted')).toBe(true);
|
|
169
|
-
});
|
|
170
|
-
});
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'fs';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import {
|
|
6
|
+
buildKnowledgeView,
|
|
7
|
+
KnowledgeStore,
|
|
8
|
+
readKnowledgeFile,
|
|
9
|
+
readKnowledgeFileResult,
|
|
10
|
+
} from './knowledge-store.js';
|
|
11
|
+
|
|
12
|
+
describe('KnowledgeStore', () => {
|
|
13
|
+
let dir: string;
|
|
14
|
+
let path: string;
|
|
15
|
+
let now: Date;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
dir = mkdtempSync(join(tmpdir(), 'clawclaw-knowledge-'));
|
|
19
|
+
path = join(dir, 'knowledge.json');
|
|
20
|
+
now = new Date('2026-06-05T00:00:00.000Z');
|
|
21
|
+
vi.useFakeTimers();
|
|
22
|
+
vi.setSystemTime(now);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
vi.useRealTimers();
|
|
27
|
+
rmSync(dir, { recursive: true, force: true });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
function tick(ms = 1000): void {
|
|
31
|
+
now = new Date(now.getTime() + ms);
|
|
32
|
+
vi.setSystemTime(now);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function store(gameId = 'game-1'): KnowledgeStore {
|
|
36
|
+
return new KnowledgeStore(path, gameId);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function view(playerNamesBySeat: Record<string, string> = {}) {
|
|
40
|
+
return buildKnowledgeView(readKnowledgeFile(path), { playerNamesBySeat });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
it('writes facts and tags under the current game id', () => {
|
|
44
|
+
store().set({
|
|
45
|
+
type: 'player',
|
|
46
|
+
selector: '5',
|
|
47
|
+
key: 'role',
|
|
48
|
+
value: 'impostor',
|
|
49
|
+
tags: ['kill_if_armed'],
|
|
50
|
+
note: 'vent read',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const file = readKnowledgeFile(path);
|
|
54
|
+
|
|
55
|
+
expect(file?.gameId).toBe('game-1');
|
|
56
|
+
expect(file?.subjects['player:5']?.facts?.role.value).toBe('impostor');
|
|
57
|
+
expect(file?.subjects['player:5']?.tags?.kill_if_armed.note).toBe('vent read');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('preserves explicit null fact values', () => {
|
|
61
|
+
store().set({
|
|
62
|
+
type: 'player',
|
|
63
|
+
selector: '5',
|
|
64
|
+
key: 'role',
|
|
65
|
+
value: null,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(readKnowledgeFile(path)?.subjects['player:5']?.facts?.role.value).toBeNull();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('distinguishes invalid files from missing files', () => {
|
|
72
|
+
expect(readKnowledgeFileResult(path)).toEqual({ status: 'missing' });
|
|
73
|
+
|
|
74
|
+
writeFileSync(path, '{bad json');
|
|
75
|
+
|
|
76
|
+
expect(readKnowledgeFileResult(path).status).toBe('invalid');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('returns an empty file when loading knowledge from another game', () => {
|
|
80
|
+
store('old-game').set({ type: 'player', selector: '5', tags: ['hostile'] });
|
|
81
|
+
|
|
82
|
+
expect(store('new-game').list()).toEqual({
|
|
83
|
+
version: 1,
|
|
84
|
+
gameId: 'new-game',
|
|
85
|
+
subjects: {},
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('treats unmarked players as suspected by default', () => {
|
|
90
|
+
const s = store();
|
|
91
|
+
s.set({ type: 'player', selector: '5', tags: ['hostile'] });
|
|
92
|
+
|
|
93
|
+
const v = view({ '5': 'Bob', '9': 'Zed' });
|
|
94
|
+
|
|
95
|
+
// 显式标记的两档
|
|
96
|
+
expect(v.isHostile({ seat: 5 })).toBe(true);
|
|
97
|
+
expect(v.isSuspect({ seat: 5 })).toBe(false);
|
|
98
|
+
// 完全没标记的人 = 被怀疑(markOf 返回 undefined)
|
|
99
|
+
expect(v.markOf({ seat: 9 })).toBeUndefined();
|
|
100
|
+
expect(v.isSuspect({ name: 'Zed' })).toBe(true);
|
|
101
|
+
expect(v.isHostile({ seat: 9 })).toBe(false);
|
|
102
|
+
expect(v.isTrusted({ seat: 9 })).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('aggregates player marks written by seat and by name', () => {
|
|
106
|
+
const s = store();
|
|
107
|
+
s.set({ type: 'player', selector: '5', tags: ['hostile'] });
|
|
108
|
+
|
|
109
|
+
const v = view({ '5': 'Bob' });
|
|
110
|
+
|
|
111
|
+
expect(v.isHostile({ name: 'Bob' })).toBe(true);
|
|
112
|
+
expect(v.markOf({ seat: 5 })).toBe('hostile');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('uses the latest mark across seat and name selectors', () => {
|
|
116
|
+
const s = store();
|
|
117
|
+
s.set({ type: 'player', selector: '5', tags: ['hostile'] });
|
|
118
|
+
tick();
|
|
119
|
+
s.set({ type: 'player', selector: 'Bob', tags: ['trusted'] });
|
|
120
|
+
|
|
121
|
+
const v = view({ '5': 'Bob' });
|
|
122
|
+
|
|
123
|
+
expect(v.markOf({ name: 'Bob' })).toBe('trusted');
|
|
124
|
+
expect(v.isHostile({ name: 'Bob' })).toBe(false);
|
|
125
|
+
expect(v.isTrusted({ name: 'Bob' })).toBe(true);
|
|
126
|
+
expect(v.hasTag({ seat: 5 }, 'hostile')).toBe(false);
|
|
127
|
+
expect(v.hasTag({ seat: 5 }, 'trusted')).toBe(true);
|
|
128
|
+
expect(v.selectorsWithTag('player', 'hostile')).toEqual([]);
|
|
129
|
+
expect(v.selectorsWithTag('player', 'trusted')).toEqual(['Bob']);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('keeps role facts independent from strategy marks', () => {
|
|
133
|
+
const s = store();
|
|
134
|
+
s.set({ type: 'player', selector: '5', key: 'role', value: 'impostor' });
|
|
135
|
+
s.set({ type: 'player', selector: '5', tags: ['trusted'] });
|
|
136
|
+
|
|
137
|
+
const v = view();
|
|
138
|
+
|
|
139
|
+
expect(v.roleOf({ seat: 5 })).toBe('impostor');
|
|
140
|
+
expect(v.markOf({ seat: 5 })).toBe('trusted');
|
|
141
|
+
expect(v.isHostile({ seat: 5 })).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('can flip player marks', () => {
|
|
145
|
+
const s = store();
|
|
146
|
+
s.set({ type: 'player', selector: '5', tags: ['trusted'] });
|
|
147
|
+
tick();
|
|
148
|
+
s.set({ type: 'player', selector: 'Bob', tags: ['hostile'] });
|
|
149
|
+
|
|
150
|
+
const v = view({ '5': 'Bob' });
|
|
151
|
+
|
|
152
|
+
expect(v.isHostile({ seat: 5 })).toBe(true);
|
|
153
|
+
expect(v.isTrusted({ seat: 5 })).toBe(false);
|
|
154
|
+
expect(v.selectorsWithTag('player', 'trusted')).toEqual([]);
|
|
155
|
+
expect(v.selectorsWithTag('player', 'hostile')).toEqual(['Bob']);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('maps legacy tags onto the two canonical marks', () => {
|
|
159
|
+
const s = store();
|
|
160
|
+
s.set({ type: 'player', selector: '6', tags: ['kill_if_armed'] });
|
|
161
|
+
s.set({ type: 'player', selector: '7', tags: ['protected'] });
|
|
162
|
+
|
|
163
|
+
const v = view();
|
|
164
|
+
|
|
165
|
+
expect(v.markOf({ seat: 6 })).toBe('hostile');
|
|
166
|
+
expect(v.markOf({ seat: 7 })).toBe('trusted');
|
|
167
|
+
expect(v.hasTag({ seat: 6 }, 'hostile')).toBe(true);
|
|
168
|
+
expect(v.hasTag({ seat: 7 }, 'trusted')).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
});
|