@myclaw163/clawclaw-cli 0.6.66 → 0.6.67
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 +427 -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/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 +244 -245
- package/skills/clawclaw/references/CHATTERBOX.md +142 -142
- package/skills/clawclaw/references/COMMANDS.md +148 -148
- package/skills/clawclaw/references/GAME-MECHANICS.md +188 -188
- package/skills/clawclaw/references/HUB.md +48 -48
- package/skills/clawclaw/references/KNOWLEDGE.md +43 -43
- package/skills/clawclaw/references/STRATEGIES.md +57 -57
- package/skills/clawclaw/references/STREAM.md +91 -92
- 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 +109 -109
- package/src/commands/account.ts +209 -209
- package/src/commands/do.test.ts +73 -73
- package/src/commands/do.ts +126 -126
- package/src/commands/events.test.ts +71 -71
- package/src/commands/events.ts +155 -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 +1027 -1027
- 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 +19 -19
- package/src/commands/knowledge.ts +168 -168
- 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 -248
- package/src/commands/skill.ts +128 -128
- package/src/commands/state.ts +46 -46
- package/src/commands/strategy.test.ts +135 -135
- package/src/commands/strategy.ts +180 -180
- 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 +966 -966
- package/src/commands/watch.ts +659 -659
- package/src/lib/auth.test.ts +59 -59
- package/src/lib/auth.ts +186 -186
- package/src/lib/command-meta.ts +37 -37
- package/src/lib/game-client.ts +391 -391
- 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 +180 -180
- package/src/lib/knowledge-store.ts +374 -374
- 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 +135 -135
- package/src/pipeline/event-format.ts +376 -376
- package/src/pipeline/event-hints.ts +173 -173
- 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/runtime/auto-upgrade.test.ts +66 -66
- package/src/runtime/auto-upgrade.ts +31 -31
- package/src/runtime/event-daemon.test.ts +107 -107
- package/src/runtime/event-daemon.ts +409 -409
- 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 +104 -104
- package/src/runtime/ws-client.ts +272 -272
- package/src/sdk/action.ts +166 -166
- package/src/sdk/index.ts +111 -111
- package/src/sdk/types.ts +159 -159
- package/src/strategies/avoid-lone.ts +11 -11
- package/src/strategies/avoid-players.knowledge.md +20 -20
- package/src/strategies/avoid-players.ts +15 -15
- package/src/strategies/corpse-patrol.ts +22 -22
- package/src/strategies/crab-sabotage.ts +21 -21
- package/src/strategies/custom-module.test.ts +269 -269
- package/src/strategies/find-player.ts +16 -16
- package/src/strategies/game-utils.test.ts +190 -190
- package/src/strategies/game-utils.ts +782 -782
- 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 +91 -91
- package/src/strategies/goals/crab-octopus-reflexes.ts +93 -93
- 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 +217 -217
- 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 +25 -25
- 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 +86 -86
- 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 +207 -207
- 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 +86 -86
- package/src/strategies/goals/warrior-shrimp-top.ts +500 -500
- package/src/strategies/greeting.ts +53 -53
- package/src/strategies/hide-spots.ts +123 -123
- package/src/strategies/hide.ts +23 -23
- package/src/strategies/kill-frenzy.ts +12 -12
- package/src/strategies/kill-lone.knowledge.md +20 -20
- package/src/strategies/kill-lone.ts +13 -13
- package/src/strategies/kill-target.ts +18 -18
- package/src/strategies/loader.test.ts +678 -678
- package/src/strategies/loader.ts +172 -172
- package/src/strategies/lone-kill-task.ts +21 -21
- package/src/strategies/meeting-gate.test.ts +59 -59
- package/src/strategies/meeting-gate.ts +23 -23
- package/src/strategies/move-room.ts +15 -15
- package/src/strategies/new-events-backfill.ts +98 -98
- package/src/strategies/paradise-fish.knowledge.md +20 -20
- package/src/strategies/paradise-fish.ts +25 -25
- 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 +11 -11
- package/src/strategies/player-targets.ts +13 -13
- package/src/strategies/report-patrol.ts +11 -11
- package/src/strategies/shrimp-memory.knowledge.md +20 -20
- package/src/strategies/shrimp-memory.ts +25 -25
- package/src/strategies/social-task.test.ts +28 -28
- package/src/strategies/social-task.ts +49 -49
- package/src/strategies/spawn.ts +82 -82
- package/src/strategies/speech-module.ts +123 -123
- package/src/strategies/strategy-loop.ts +771 -771
- package/src/strategies/task-kill-report.ts +17 -17
- package/src/strategies/task-only.ts +11 -11
- package/src/strategies/task-report.ts +22 -22
- package/src/strategies/types.ts +102 -102
- package/src/strategies/warrior-memory.knowledge.md +22 -22
- package/src/strategies/warrior-memory.ts +16 -16
|
@@ -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
|
+
}
|
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
|
|
3
|
-
vi.mock('../commands/upgrade.js', () => ({
|
|
4
|
-
runDefaultUpgrade: vi.fn(),
|
|
5
|
-
}));
|
|
6
|
-
|
|
7
|
-
import { runDefaultUpgrade } from '../commands/upgrade.js';
|
|
8
|
-
import { runAutoUpgradeOnLoad, shouldSkipAutoUpgrade } from './auto-upgrade.js';
|
|
9
|
-
|
|
10
|
-
describe('runAutoUpgradeOnLoad', () => {
|
|
11
|
-
const prev = process.env.CLAWCLAW_SKIP_AUTO_UPGRADE;
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
vi.mocked(runDefaultUpgrade).mockReset();
|
|
15
|
-
delete process.env.CLAWCLAW_SKIP_AUTO_UPGRADE;
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
if (prev === undefined) delete process.env.CLAWCLAW_SKIP_AUTO_UPGRADE;
|
|
20
|
-
else process.env.CLAWCLAW_SKIP_AUTO_UPGRADE = prev;
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('skips when CLAWCLAW_SKIP_AUTO_UPGRADE=1', () => {
|
|
24
|
-
process.env.CLAWCLAW_SKIP_AUTO_UPGRADE = '1';
|
|
25
|
-
runAutoUpgradeOnLoad({ packageVersion: '0.5.25' });
|
|
26
|
-
expect(runDefaultUpgrade).not.toHaveBeenCalled();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('skips prerelease/test package versions', () => {
|
|
30
|
-
runAutoUpgradeOnLoad({ packageVersion: '0.5.25-test' });
|
|
31
|
-
expect(runDefaultUpgrade).not.toHaveBeenCalled();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('runs default upgrade when not skipped', () => {
|
|
35
|
-
vi.mocked(runDefaultUpgrade).mockReturnValue({ version: '0.4.8' });
|
|
36
|
-
runAutoUpgradeOnLoad({ packageVersion: '0.5.25' });
|
|
37
|
-
expect(runDefaultUpgrade).toHaveBeenCalledOnce();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('does not write upgrade progress to stdout', () => {
|
|
41
|
-
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
42
|
-
vi.mocked(runDefaultUpgrade).mockReturnValue({ version: '0.4.8' });
|
|
43
|
-
try {
|
|
44
|
-
runAutoUpgradeOnLoad({ packageVersion: '0.5.25' });
|
|
45
|
-
expect(logSpy).not.toHaveBeenCalled();
|
|
46
|
-
} finally {
|
|
47
|
-
logSpy.mockRestore();
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('notifies on upgrade success and failure', () => {
|
|
52
|
-
const notify = vi.fn();
|
|
53
|
-
vi.mocked(runDefaultUpgrade).mockReturnValueOnce({ version: '0.4.8' });
|
|
54
|
-
runAutoUpgradeOnLoad({ packageVersion: '0.5.25', notify });
|
|
55
|
-
expect(notify).toHaveBeenCalledWith('[clawclaw-cli] auto-upgrade installed 0.4.8');
|
|
56
|
-
|
|
57
|
-
vi.mocked(runDefaultUpgrade).mockReturnValueOnce({ error: 'npm install failed: EACCES' });
|
|
58
|
-
runAutoUpgradeOnLoad({ packageVersion: '0.5.25', notify });
|
|
59
|
-
expect(notify).toHaveBeenCalledWith('[clawclaw-cli] auto-upgrade failed: npm install failed: EACCES');
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('treats prerelease versions as skip-worthy', () => {
|
|
63
|
-
expect(shouldSkipAutoUpgrade('0.5.25-test')).toBe(true);
|
|
64
|
-
expect(shouldSkipAutoUpgrade('0.5.25')).toBe(false);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
1
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
vi.mock('../commands/upgrade.js', () => ({
|
|
4
|
+
runDefaultUpgrade: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
import { runDefaultUpgrade } from '../commands/upgrade.js';
|
|
8
|
+
import { runAutoUpgradeOnLoad, shouldSkipAutoUpgrade } from './auto-upgrade.js';
|
|
9
|
+
|
|
10
|
+
describe('runAutoUpgradeOnLoad', () => {
|
|
11
|
+
const prev = process.env.CLAWCLAW_SKIP_AUTO_UPGRADE;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.mocked(runDefaultUpgrade).mockReset();
|
|
15
|
+
delete process.env.CLAWCLAW_SKIP_AUTO_UPGRADE;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
if (prev === undefined) delete process.env.CLAWCLAW_SKIP_AUTO_UPGRADE;
|
|
20
|
+
else process.env.CLAWCLAW_SKIP_AUTO_UPGRADE = prev;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('skips when CLAWCLAW_SKIP_AUTO_UPGRADE=1', () => {
|
|
24
|
+
process.env.CLAWCLAW_SKIP_AUTO_UPGRADE = '1';
|
|
25
|
+
runAutoUpgradeOnLoad({ packageVersion: '0.5.25' });
|
|
26
|
+
expect(runDefaultUpgrade).not.toHaveBeenCalled();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('skips prerelease/test package versions', () => {
|
|
30
|
+
runAutoUpgradeOnLoad({ packageVersion: '0.5.25-test' });
|
|
31
|
+
expect(runDefaultUpgrade).not.toHaveBeenCalled();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('runs default upgrade when not skipped', () => {
|
|
35
|
+
vi.mocked(runDefaultUpgrade).mockReturnValue({ version: '0.4.8' });
|
|
36
|
+
runAutoUpgradeOnLoad({ packageVersion: '0.5.25' });
|
|
37
|
+
expect(runDefaultUpgrade).toHaveBeenCalledOnce();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('does not write upgrade progress to stdout', () => {
|
|
41
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
42
|
+
vi.mocked(runDefaultUpgrade).mockReturnValue({ version: '0.4.8' });
|
|
43
|
+
try {
|
|
44
|
+
runAutoUpgradeOnLoad({ packageVersion: '0.5.25' });
|
|
45
|
+
expect(logSpy).not.toHaveBeenCalled();
|
|
46
|
+
} finally {
|
|
47
|
+
logSpy.mockRestore();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('notifies on upgrade success and failure', () => {
|
|
52
|
+
const notify = vi.fn();
|
|
53
|
+
vi.mocked(runDefaultUpgrade).mockReturnValueOnce({ version: '0.4.8' });
|
|
54
|
+
runAutoUpgradeOnLoad({ packageVersion: '0.5.25', notify });
|
|
55
|
+
expect(notify).toHaveBeenCalledWith('[clawclaw-cli] auto-upgrade installed 0.4.8');
|
|
56
|
+
|
|
57
|
+
vi.mocked(runDefaultUpgrade).mockReturnValueOnce({ error: 'npm install failed: EACCES' });
|
|
58
|
+
runAutoUpgradeOnLoad({ packageVersion: '0.5.25', notify });
|
|
59
|
+
expect(notify).toHaveBeenCalledWith('[clawclaw-cli] auto-upgrade failed: npm install failed: EACCES');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('treats prerelease versions as skip-worthy', () => {
|
|
63
|
+
expect(shouldSkipAutoUpgrade('0.5.25-test')).toBe(true);
|
|
64
|
+
expect(shouldSkipAutoUpgrade('0.5.25')).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import { readFileSync } from 'fs';
|
|
2
|
-
import { runDefaultUpgrade } from '../commands/upgrade.js';
|
|
3
|
-
|
|
4
|
-
function readPackageVersion(): string | null {
|
|
5
|
-
try {
|
|
6
|
-
const pkg = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
|
|
7
|
-
return typeof pkg?.version === 'string' ? pkg.version : null;
|
|
8
|
-
} catch {
|
|
9
|
-
return null;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function shouldSkipAutoUpgrade(packageVersion = readPackageVersion()): boolean {
|
|
14
|
-
if (process.env.CLAWCLAW_SKIP_AUTO_UPGRADE === '1') return true;
|
|
15
|
-
return Boolean(packageVersion?.includes('-'));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** Run during `ccl load`, before game start and event streaming begin. */
|
|
19
|
-
export function runAutoUpgradeOnLoad(options?: { packageVersion?: string; notify?: (message: string) => void }): void {
|
|
20
|
-
if (shouldSkipAutoUpgrade(options?.packageVersion)) return;
|
|
21
|
-
|
|
22
|
-
const result = runDefaultUpgrade();
|
|
23
|
-
if (result.error) {
|
|
24
|
-
const msg = result.version ? `${result.version} (${result.error})` : result.error;
|
|
25
|
-
options?.notify?.(`[clawclaw-cli] auto-upgrade failed: ${msg}`);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
if (result.version) {
|
|
29
|
-
options?.notify?.(`[clawclaw-cli] auto-upgrade installed ${result.version}`);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { runDefaultUpgrade } from '../commands/upgrade.js';
|
|
3
|
+
|
|
4
|
+
function readPackageVersion(): string | null {
|
|
5
|
+
try {
|
|
6
|
+
const pkg = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
|
|
7
|
+
return typeof pkg?.version === 'string' ? pkg.version : null;
|
|
8
|
+
} catch {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function shouldSkipAutoUpgrade(packageVersion = readPackageVersion()): boolean {
|
|
14
|
+
if (process.env.CLAWCLAW_SKIP_AUTO_UPGRADE === '1') return true;
|
|
15
|
+
return Boolean(packageVersion?.includes('-'));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Run during `ccl load`, before game start and event streaming begin. */
|
|
19
|
+
export function runAutoUpgradeOnLoad(options?: { packageVersion?: string; notify?: (message: string) => void }): void {
|
|
20
|
+
if (shouldSkipAutoUpgrade(options?.packageVersion)) return;
|
|
21
|
+
|
|
22
|
+
const result = runDefaultUpgrade();
|
|
23
|
+
if (result.error) {
|
|
24
|
+
const msg = result.version ? `${result.version} (${result.error})` : result.error;
|
|
25
|
+
options?.notify?.(`[clawclaw-cli] auto-upgrade failed: ${msg}`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (result.version) {
|
|
29
|
+
options?.notify?.(`[clawclaw-cli] auto-upgrade installed ${result.version}`);
|
|
30
|
+
}
|
|
31
|
+
}
|