@myclaw163/clawclaw-cli 0.6.76 → 0.6.78
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 +387 -387
- 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 -116
- package/scripts/find-hide-spots.py +157 -157
- package/scripts/postinstall.mjs +20 -20
- package/scripts/sync-bundled-skill.mjs +254 -245
- package/scripts/sync-bundled-skill.test.mjs +152 -152
- package/skills/clawclaw/SKILL.md +248 -248
- package/skills/clawclaw/references/CHATTERBOX.md +141 -141
- package/skills/clawclaw/references/COMMANDS.md +160 -160
- package/skills/clawclaw/references/GAME-MECHANICS.md +188 -188
- package/skills/clawclaw/references/HUB.md +48 -48
- package/skills/clawclaw/references/KNOWLEDGE.md +42 -42
- package/skills/clawclaw/references/STRATEGIES.md +59 -59
- package/skills/clawclaw/references/STREAM.md +93 -93
- package/skills/clawclaw/references/TACTICS.md +65 -65
- package/src/assets/clawclaw-ascii-map.txt +40 -40
- package/src/cli.ts +112 -112
- package/src/commands/_schema.ts +124 -124
- package/src/commands/account.ts +209 -209
- package/src/commands/data.test.ts +33 -33
- package/src/commands/data.ts +22 -22
- package/src/commands/do.test.ts +84 -84
- package/src/commands/do.ts +130 -130
- package/src/commands/events.test.ts +100 -100
- package/src/commands/events.ts +250 -250
- 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 -1113
- 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 +153 -153
- package/src/commands/strategy.ts +183 -183
- 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 -999
- package/src/commands/watch.ts +660 -660
- package/src/lib/auth.test.ts +86 -86
- package/src/lib/auth.ts +223 -223
- package/src/lib/command-meta.ts +37 -37
- package/src/lib/game-client.ts +403 -403
- package/src/lib/game-context.ts +92 -92
- 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 +160 -160
- package/src/lib/hub-reminder.ts +78 -78
- 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 +130 -130
- 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 +240 -240
- package/src/lib/strategy-export.ts +247 -247
- 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/user-data.test.ts +96 -96
- package/src/lib/user-data.ts +400 -400
- 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 -243
- package/src/pipeline/event-format.ts +501 -501
- package/src/pipeline/event-hints.ts +195 -195
- 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 +168 -168
- package/src/pipeline/player-projection.ts +370 -370
- 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 -209
- package/src/runtime/event-daemon.ts +519 -519
- 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 -125
- package/src/runtime/ws-client.ts +287 -287
- 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 +181 -181
- 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 -15
- package/src/strategies/strategy-loop.ts +776 -776
- 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
|
@@ -1,193 +1,193 @@
|
|
|
1
|
-
import { appendFileSync, readFileSync, existsSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
2
|
-
import { dirname, join, basename } from 'path';
|
|
3
|
-
import { AuthStore } from '../lib/auth.js';
|
|
4
|
-
import { getProfileGamesDir } from '../lib/init-command.js';
|
|
5
|
-
|
|
6
|
-
export const CCL_EVENTS_CURSOR_TYPE = '_ccl_events_cursor';
|
|
7
|
-
export const CCL_EVENTS_CURSOR_NAME = 'agent_read';
|
|
8
|
-
|
|
9
|
-
interface GameSessionFile {
|
|
10
|
-
path: string;
|
|
11
|
-
sessionId: string;
|
|
12
|
-
mtimeMs: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface EventRecord {
|
|
16
|
-
line: number;
|
|
17
|
-
event: Record<string, any>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface EventsCursorRecord {
|
|
21
|
-
type: typeof CCL_EVENTS_CURSOR_TYPE;
|
|
22
|
-
cursor: typeof CCL_EVENTS_CURSOR_NAME;
|
|
23
|
-
line: number;
|
|
24
|
-
ts: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function isLocalControlEvent(event: any): boolean {
|
|
28
|
-
return !!(
|
|
29
|
-
event
|
|
30
|
-
&& typeof event === 'object'
|
|
31
|
-
&& typeof event.type === 'string'
|
|
32
|
-
&& event.type.startsWith('_ccl_')
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function extractNewEvents(source: any): Record<string, any>[] {
|
|
37
|
-
const candidates = [
|
|
38
|
-
source,
|
|
39
|
-
source?.new_events,
|
|
40
|
-
source?.data?.new_events,
|
|
41
|
-
source?.result?.new_events,
|
|
42
|
-
source?.result?.data?.new_events,
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
for (const candidate of candidates) {
|
|
46
|
-
if (Array.isArray(candidate)) {
|
|
47
|
-
return candidate.filter((evt) => evt && typeof evt === 'object');
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return [];
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export class EventStore {
|
|
55
|
-
constructor(private filePath: string) {}
|
|
56
|
-
|
|
57
|
-
get path(): string {
|
|
58
|
-
return this.filePath;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
static forActiveAccount(): EventStore {
|
|
62
|
-
return EventStore.forLatestGame();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
static forLatestGame(): EventStore {
|
|
66
|
-
const store = new AuthStore();
|
|
67
|
-
const profile = store.getActive();
|
|
68
|
-
if (!profile) throw new Error('Not logged in.');
|
|
69
|
-
|
|
70
|
-
const latest = EventStore.latestSessionFile(getProfileGamesDir(profile));
|
|
71
|
-
if (!latest) {
|
|
72
|
-
throw new Error('No game events found. Join a game first.');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return new EventStore(latest.path);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Returns the path to the latest session jsonl, or null if none exists. */
|
|
79
|
-
static latestSessionPath(): string | null {
|
|
80
|
-
const store = new AuthStore();
|
|
81
|
-
const profile = store.getActive();
|
|
82
|
-
if (!profile) return null;
|
|
83
|
-
return EventStore.latestSessionFile(getProfileGamesDir(profile))?.path ?? null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
static createSessionForActiveAccount(sessionId = EventStore.createSessionId()): EventStore {
|
|
87
|
-
const store = new AuthStore();
|
|
88
|
-
const profile = store.getActive();
|
|
89
|
-
if (!profile) throw new Error('Not logged in.');
|
|
90
|
-
|
|
91
|
-
const dir = getProfileGamesDir(profile);
|
|
92
|
-
mkdirSync(dir, { recursive: true });
|
|
93
|
-
const filePath = join(dir, `${sessionId}.jsonl`);
|
|
94
|
-
if (!existsSync(filePath)) writeFileSync(filePath, '');
|
|
95
|
-
return new EventStore(filePath);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
append(event: Record<string, any>): void {
|
|
99
|
-
mkdirSync(dirname(this.filePath), { recursive: true });
|
|
100
|
-
const line = JSON.stringify(event) + '\n';
|
|
101
|
-
appendFileSync(this.filePath, line);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
appendNewEvents(events: any): void {
|
|
105
|
-
const extracted = extractNewEvents(events);
|
|
106
|
-
if (extracted.length === 0) return;
|
|
107
|
-
for (const evt of extracted) {
|
|
108
|
-
this.append(evt);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
records(): EventRecord[] {
|
|
113
|
-
if (!existsSync(this.filePath)) return [];
|
|
114
|
-
const content = readFileSync(this.filePath, 'utf8');
|
|
115
|
-
if (!content.trim()) return [];
|
|
116
|
-
const records: EventRecord[] = [];
|
|
117
|
-
const lines = content.split('\n');
|
|
118
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
119
|
-
const line = lines[i].trim();
|
|
120
|
-
if (!line) continue;
|
|
121
|
-
try {
|
|
122
|
-
const event = JSON.parse(line);
|
|
123
|
-
if (event && typeof event === 'object') {
|
|
124
|
-
records.push({ line: i + 1, event });
|
|
125
|
-
}
|
|
126
|
-
} catch {}
|
|
127
|
-
}
|
|
128
|
-
return records;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
eventRecords(): EventRecord[] {
|
|
132
|
-
return this.records().filter((record) => !isLocalControlEvent(record.event));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
readEventsCursor(): number {
|
|
136
|
-
let cursor = 0;
|
|
137
|
-
for (const record of this.records()) {
|
|
138
|
-
const event = record.event;
|
|
139
|
-
if (
|
|
140
|
-
event.type === CCL_EVENTS_CURSOR_TYPE
|
|
141
|
-
&& event.cursor === CCL_EVENTS_CURSOR_NAME
|
|
142
|
-
&& typeof event.line === 'number'
|
|
143
|
-
&& Number.isFinite(event.line)
|
|
144
|
-
) {
|
|
145
|
-
cursor = event.line;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return cursor;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
appendEventsCursor(line: number): void {
|
|
152
|
-
if (!Number.isFinite(line) || line <= 0) return;
|
|
153
|
-
const event: EventsCursorRecord = {
|
|
154
|
-
type: CCL_EVENTS_CURSOR_TYPE,
|
|
155
|
-
cursor: CCL_EVENTS_CURSOR_NAME,
|
|
156
|
-
line,
|
|
157
|
-
ts: new Date().toISOString(),
|
|
158
|
-
};
|
|
159
|
-
this.append(event);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
tail(n: number): Record<string, any>[] {
|
|
163
|
-
return this.eventRecords().slice(-n).map((record) => record.event);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
all(): Record<string, any>[] {
|
|
167
|
-
return this.eventRecords().map((record) => record.event);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
clear(): void {
|
|
171
|
-
writeFileSync(this.filePath, '');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
private static createSessionId(): string {
|
|
175
|
-
const id = new Date().toISOString();
|
|
176
|
-
return process.platform === 'win32' ? id.replace(/:/g, '_') : id;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
private static latestSessionFile(dir: string): GameSessionFile | null {
|
|
180
|
-
if (!existsSync(dir)) return null;
|
|
181
|
-
|
|
182
|
-
const files = readdirSync(dir)
|
|
183
|
-
.filter(name => name.endsWith('.jsonl') && !name.endsWith('.raw-ws.jsonl') && !name.startsWith('index.'))
|
|
184
|
-
.map(name => {
|
|
185
|
-
const path = join(dir, name);
|
|
186
|
-
const stat = statSync(path);
|
|
187
|
-
return { path, sessionId: basename(name, '.jsonl'), mtimeMs: stat.mtimeMs };
|
|
188
|
-
})
|
|
189
|
-
.sort((a, b) => b.sessionId.localeCompare(a.sessionId) || b.mtimeMs - a.mtimeMs);
|
|
190
|
-
|
|
191
|
-
return files[0] ?? null;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
1
|
+
import { appendFileSync, readFileSync, existsSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { dirname, join, basename } from 'path';
|
|
3
|
+
import { AuthStore } from '../lib/auth.js';
|
|
4
|
+
import { getProfileGamesDir } from '../lib/init-command.js';
|
|
5
|
+
|
|
6
|
+
export const CCL_EVENTS_CURSOR_TYPE = '_ccl_events_cursor';
|
|
7
|
+
export const CCL_EVENTS_CURSOR_NAME = 'agent_read';
|
|
8
|
+
|
|
9
|
+
interface GameSessionFile {
|
|
10
|
+
path: string;
|
|
11
|
+
sessionId: string;
|
|
12
|
+
mtimeMs: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface EventRecord {
|
|
16
|
+
line: number;
|
|
17
|
+
event: Record<string, any>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface EventsCursorRecord {
|
|
21
|
+
type: typeof CCL_EVENTS_CURSOR_TYPE;
|
|
22
|
+
cursor: typeof CCL_EVENTS_CURSOR_NAME;
|
|
23
|
+
line: number;
|
|
24
|
+
ts: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isLocalControlEvent(event: any): boolean {
|
|
28
|
+
return !!(
|
|
29
|
+
event
|
|
30
|
+
&& typeof event === 'object'
|
|
31
|
+
&& typeof event.type === 'string'
|
|
32
|
+
&& event.type.startsWith('_ccl_')
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function extractNewEvents(source: any): Record<string, any>[] {
|
|
37
|
+
const candidates = [
|
|
38
|
+
source,
|
|
39
|
+
source?.new_events,
|
|
40
|
+
source?.data?.new_events,
|
|
41
|
+
source?.result?.new_events,
|
|
42
|
+
source?.result?.data?.new_events,
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
for (const candidate of candidates) {
|
|
46
|
+
if (Array.isArray(candidate)) {
|
|
47
|
+
return candidate.filter((evt) => evt && typeof evt === 'object');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class EventStore {
|
|
55
|
+
constructor(private filePath: string) {}
|
|
56
|
+
|
|
57
|
+
get path(): string {
|
|
58
|
+
return this.filePath;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static forActiveAccount(): EventStore {
|
|
62
|
+
return EventStore.forLatestGame();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static forLatestGame(): EventStore {
|
|
66
|
+
const store = new AuthStore();
|
|
67
|
+
const profile = store.getActive();
|
|
68
|
+
if (!profile) throw new Error('Not logged in.');
|
|
69
|
+
|
|
70
|
+
const latest = EventStore.latestSessionFile(getProfileGamesDir(profile));
|
|
71
|
+
if (!latest) {
|
|
72
|
+
throw new Error('No game events found. Join a game first.');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return new EventStore(latest.path);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Returns the path to the latest session jsonl, or null if none exists. */
|
|
79
|
+
static latestSessionPath(): string | null {
|
|
80
|
+
const store = new AuthStore();
|
|
81
|
+
const profile = store.getActive();
|
|
82
|
+
if (!profile) return null;
|
|
83
|
+
return EventStore.latestSessionFile(getProfileGamesDir(profile))?.path ?? null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
static createSessionForActiveAccount(sessionId = EventStore.createSessionId()): EventStore {
|
|
87
|
+
const store = new AuthStore();
|
|
88
|
+
const profile = store.getActive();
|
|
89
|
+
if (!profile) throw new Error('Not logged in.');
|
|
90
|
+
|
|
91
|
+
const dir = getProfileGamesDir(profile);
|
|
92
|
+
mkdirSync(dir, { recursive: true });
|
|
93
|
+
const filePath = join(dir, `${sessionId}.jsonl`);
|
|
94
|
+
if (!existsSync(filePath)) writeFileSync(filePath, '');
|
|
95
|
+
return new EventStore(filePath);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
append(event: Record<string, any>): void {
|
|
99
|
+
mkdirSync(dirname(this.filePath), { recursive: true });
|
|
100
|
+
const line = JSON.stringify(event) + '\n';
|
|
101
|
+
appendFileSync(this.filePath, line);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
appendNewEvents(events: any): void {
|
|
105
|
+
const extracted = extractNewEvents(events);
|
|
106
|
+
if (extracted.length === 0) return;
|
|
107
|
+
for (const evt of extracted) {
|
|
108
|
+
this.append(evt);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
records(): EventRecord[] {
|
|
113
|
+
if (!existsSync(this.filePath)) return [];
|
|
114
|
+
const content = readFileSync(this.filePath, 'utf8');
|
|
115
|
+
if (!content.trim()) return [];
|
|
116
|
+
const records: EventRecord[] = [];
|
|
117
|
+
const lines = content.split('\n');
|
|
118
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
119
|
+
const line = lines[i].trim();
|
|
120
|
+
if (!line) continue;
|
|
121
|
+
try {
|
|
122
|
+
const event = JSON.parse(line);
|
|
123
|
+
if (event && typeof event === 'object') {
|
|
124
|
+
records.push({ line: i + 1, event });
|
|
125
|
+
}
|
|
126
|
+
} catch {}
|
|
127
|
+
}
|
|
128
|
+
return records;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
eventRecords(): EventRecord[] {
|
|
132
|
+
return this.records().filter((record) => !isLocalControlEvent(record.event));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
readEventsCursor(): number {
|
|
136
|
+
let cursor = 0;
|
|
137
|
+
for (const record of this.records()) {
|
|
138
|
+
const event = record.event;
|
|
139
|
+
if (
|
|
140
|
+
event.type === CCL_EVENTS_CURSOR_TYPE
|
|
141
|
+
&& event.cursor === CCL_EVENTS_CURSOR_NAME
|
|
142
|
+
&& typeof event.line === 'number'
|
|
143
|
+
&& Number.isFinite(event.line)
|
|
144
|
+
) {
|
|
145
|
+
cursor = event.line;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return cursor;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
appendEventsCursor(line: number): void {
|
|
152
|
+
if (!Number.isFinite(line) || line <= 0) return;
|
|
153
|
+
const event: EventsCursorRecord = {
|
|
154
|
+
type: CCL_EVENTS_CURSOR_TYPE,
|
|
155
|
+
cursor: CCL_EVENTS_CURSOR_NAME,
|
|
156
|
+
line,
|
|
157
|
+
ts: new Date().toISOString(),
|
|
158
|
+
};
|
|
159
|
+
this.append(event);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
tail(n: number): Record<string, any>[] {
|
|
163
|
+
return this.eventRecords().slice(-n).map((record) => record.event);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
all(): Record<string, any>[] {
|
|
167
|
+
return this.eventRecords().map((record) => record.event);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
clear(): void {
|
|
171
|
+
writeFileSync(this.filePath, '');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private static createSessionId(): string {
|
|
175
|
+
const id = new Date().toISOString();
|
|
176
|
+
return process.platform === 'win32' ? id.replace(/:/g, '_') : id;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private static latestSessionFile(dir: string): GameSessionFile | null {
|
|
180
|
+
if (!existsSync(dir)) return null;
|
|
181
|
+
|
|
182
|
+
const files = readdirSync(dir)
|
|
183
|
+
.filter(name => name.endsWith('.jsonl') && !name.endsWith('.raw-ws.jsonl') && !name.startsWith('index.'))
|
|
184
|
+
.map(name => {
|
|
185
|
+
const path = join(dir, name);
|
|
186
|
+
const stat = statSync(path);
|
|
187
|
+
return { path, sessionId: basename(name, '.jsonl'), mtimeMs: stat.mtimeMs };
|
|
188
|
+
})
|
|
189
|
+
.sort((a, b) => b.sessionId.localeCompare(a.sessionId) || b.mtimeMs - a.mtimeMs);
|
|
190
|
+
|
|
191
|
+
return files[0] ?? null;
|
|
192
|
+
}
|
|
193
|
+
}
|
package/src/pipeline/pipeline.ts
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
import { GameClient } from '../lib/game-client.js';
|
|
2
|
-
import { EventStore } from './event-store.js';
|
|
3
|
-
|
|
4
|
-
interface PipelineAction {
|
|
5
|
-
action: Record<string, any>;
|
|
6
|
-
needsWait: boolean;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export async function runPipeline(actions: PipelineAction[], initialWaitMs?: number): Promise<void> {
|
|
10
|
-
const client = GameClient.fromAuth();
|
|
11
|
-
const store = EventStore.forActiveAccount();
|
|
12
|
-
|
|
13
|
-
if (initialWaitMs && initialWaitMs > 0) {
|
|
14
|
-
await new Promise(r => setTimeout(r, initialWaitMs));
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
for (const item of actions) {
|
|
18
|
-
const result = await client.submitAction(item.action as any);
|
|
19
|
-
store.appendNewEvents(result);
|
|
20
|
-
const durationSecs = result?.data?.duration_secs ?? result?.duration_secs ?? 0;
|
|
21
|
-
|
|
22
|
-
if (item.needsWait && durationSecs > 0) {
|
|
23
|
-
await new Promise(r => setTimeout(r, (durationSecs + 0.5) * 1000));
|
|
24
|
-
try {
|
|
25
|
-
const state = await client.getGameState();
|
|
26
|
-
if (state) store.appendNewEvents(state.new_events);
|
|
27
|
-
} catch {}
|
|
28
|
-
store.append({ type: 'pipeline', action: item.action, status: 'done' });
|
|
29
|
-
} else {
|
|
30
|
-
store.append({ type: 'pipeline', action: item.action, result });
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
store.append({ type: 'pipeline_done', count: actions.length });
|
|
35
|
-
}
|
|
1
|
+
import { GameClient } from '../lib/game-client.js';
|
|
2
|
+
import { EventStore } from './event-store.js';
|
|
3
|
+
|
|
4
|
+
interface PipelineAction {
|
|
5
|
+
action: Record<string, any>;
|
|
6
|
+
needsWait: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function runPipeline(actions: PipelineAction[], initialWaitMs?: number): Promise<void> {
|
|
10
|
+
const client = GameClient.fromAuth();
|
|
11
|
+
const store = EventStore.forActiveAccount();
|
|
12
|
+
|
|
13
|
+
if (initialWaitMs && initialWaitMs > 0) {
|
|
14
|
+
await new Promise(r => setTimeout(r, initialWaitMs));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
for (const item of actions) {
|
|
18
|
+
const result = await client.submitAction(item.action as any);
|
|
19
|
+
store.appendNewEvents(result);
|
|
20
|
+
const durationSecs = result?.data?.duration_secs ?? result?.duration_secs ?? 0;
|
|
21
|
+
|
|
22
|
+
if (item.needsWait && durationSecs > 0) {
|
|
23
|
+
await new Promise(r => setTimeout(r, (durationSecs + 0.5) * 1000));
|
|
24
|
+
try {
|
|
25
|
+
const state = await client.getGameState();
|
|
26
|
+
if (state) store.appendNewEvents(state.new_events);
|
|
27
|
+
} catch {}
|
|
28
|
+
store.append({ type: 'pipeline', action: item.action, status: 'done' });
|
|
29
|
+
} else {
|
|
30
|
+
store.append({ type: 'pipeline', action: item.action, result });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
store.append({ type: 'pipeline_done', count: actions.length });
|
|
35
|
+
}
|