pnpfucius 2.0.0

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.
@@ -0,0 +1,418 @@
1
+ // SQLite storage for market tracking
2
+ // Uses better-sqlite3 for synchronous, fast SQLite operations
3
+
4
+ import Database from 'better-sqlite3';
5
+ import { mkdirSync, existsSync } from 'fs';
6
+ import { dirname } from 'path';
7
+ import { agentEvents, AgentEvents } from '../events/emitter.js';
8
+
9
+ export class MarketStore {
10
+ constructor(dbPath = null) {
11
+ this.dbPath = dbPath;
12
+
13
+ if (dbPath && dbPath !== ':memory:') {
14
+ const dir = dirname(dbPath);
15
+ if (!existsSync(dir)) {
16
+ mkdirSync(dir, { recursive: true });
17
+ }
18
+ }
19
+
20
+ this.db = new Database(dbPath || ':memory:');
21
+ this.db.pragma('journal_mode = WAL');
22
+ this._initSchema();
23
+ }
24
+
25
+ _initSchema() {
26
+ this.db.exec(`
27
+ CREATE TABLE IF NOT EXISTS markets (
28
+ address TEXT PRIMARY KEY,
29
+ question TEXT NOT NULL,
30
+ category TEXT,
31
+ category_key TEXT,
32
+ creation_time INTEGER,
33
+ creation_signature TEXT,
34
+ initial_liquidity TEXT,
35
+ duration_days INTEGER,
36
+ end_time INTEGER,
37
+ status TEXT DEFAULT 'active',
38
+ outcome TEXT,
39
+ volume TEXT,
40
+ resolution_time INTEGER,
41
+ metadata TEXT
42
+ );
43
+
44
+ CREATE TABLE IF NOT EXISTS daemon_state (
45
+ key TEXT PRIMARY KEY,
46
+ value TEXT,
47
+ updated_at INTEGER
48
+ );
49
+
50
+ CREATE TABLE IF NOT EXISTS webhook_events (
51
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
52
+ event_type TEXT,
53
+ signature TEXT,
54
+ data TEXT,
55
+ processed INTEGER DEFAULT 0,
56
+ timestamp INTEGER
57
+ );
58
+
59
+ CREATE INDEX IF NOT EXISTS idx_markets_status ON markets(status);
60
+ CREATE INDEX IF NOT EXISTS idx_markets_category ON markets(category_key);
61
+ CREATE INDEX IF NOT EXISTS idx_markets_creation ON markets(creation_time);
62
+ CREATE INDEX IF NOT EXISTS idx_webhook_processed ON webhook_events(processed);
63
+ `);
64
+ }
65
+
66
+ // Initialize (for API compatibility)
67
+ async initialize() {
68
+ return this;
69
+ }
70
+
71
+ // Save a market record
72
+ saveMarket(record) {
73
+ const endTime = record.endTime || (record.creationTime + (record.durationDays * 24 * 60 * 60 * 1000));
74
+
75
+ const stmt = this.db.prepare(`
76
+ INSERT OR REPLACE INTO markets
77
+ (address, question, category, category_key, creation_time, creation_signature,
78
+ initial_liquidity, duration_days, end_time, status, outcome, volume, resolution_time, metadata)
79
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
80
+ `);
81
+
82
+ stmt.run(
83
+ record.address,
84
+ record.question,
85
+ record.category || null,
86
+ record.categoryKey || null,
87
+ record.creationTime || Date.now(),
88
+ record.creationSignature || null,
89
+ record.initialLiquidity?.toString() || null,
90
+ record.durationDays || null,
91
+ endTime,
92
+ record.status || 'active',
93
+ record.outcome || null,
94
+ record.volume?.toString() || null,
95
+ record.resolutionTime || null,
96
+ JSON.stringify(record.metadata || {})
97
+ );
98
+
99
+ agentEvents.emitTyped(AgentEvents.MARKET_UPDATED, { address: record.address });
100
+
101
+ return this.getMarket(record.address);
102
+ }
103
+
104
+ // Get a single market by address
105
+ getMarket(address) {
106
+ const stmt = this.db.prepare('SELECT * FROM markets WHERE address = ?');
107
+ const row = stmt.get(address);
108
+
109
+ if (!row) return null;
110
+
111
+ return this._rowToMarket(row);
112
+ }
113
+
114
+ // Convert database row to market object
115
+ _rowToMarket(row) {
116
+ return {
117
+ address: row.address,
118
+ question: row.question,
119
+ category: row.category,
120
+ categoryKey: row.category_key,
121
+ creationTime: row.creation_time,
122
+ creationSignature: row.creation_signature,
123
+ initialLiquidity: row.initial_liquidity,
124
+ durationDays: row.duration_days,
125
+ endTime: row.end_time,
126
+ status: row.status,
127
+ outcome: row.outcome,
128
+ volume: row.volume,
129
+ resolutionTime: row.resolution_time,
130
+ metadata: row.metadata ? JSON.parse(row.metadata) : {}
131
+ };
132
+ }
133
+
134
+ // Get all markets with optional filters
135
+ getAllMarkets(options = {}) {
136
+ let sql = 'SELECT * FROM markets WHERE 1=1';
137
+ const params = [];
138
+
139
+ if (options.status) {
140
+ sql += ' AND status = ?';
141
+ params.push(options.status);
142
+ }
143
+
144
+ if (options.category) {
145
+ sql += ' AND category_key = ?';
146
+ params.push(options.category);
147
+ }
148
+
149
+ if (options.since) {
150
+ sql += ' AND creation_time >= ?';
151
+ params.push(options.since);
152
+ }
153
+
154
+ if (options.until) {
155
+ sql += ' AND creation_time <= ?';
156
+ params.push(options.until);
157
+ }
158
+
159
+ sql += ' ORDER BY creation_time DESC';
160
+
161
+ if (options.limit) {
162
+ sql += ' LIMIT ?';
163
+ params.push(options.limit);
164
+
165
+ if (options.offset) {
166
+ sql += ' OFFSET ?';
167
+ params.push(options.offset);
168
+ }
169
+ }
170
+
171
+ const stmt = this.db.prepare(sql);
172
+ const rows = stmt.all(...params);
173
+
174
+ return rows.map(row => this._rowToMarket(row));
175
+ }
176
+
177
+ // Update a market
178
+ updateMarket(address, updates) {
179
+ const market = this.getMarket(address);
180
+ if (!market) return null;
181
+
182
+ const fieldMap = {
183
+ status: 'status',
184
+ outcome: 'outcome',
185
+ volume: 'volume',
186
+ resolutionTime: 'resolution_time',
187
+ resolution_time: 'resolution_time'
188
+ };
189
+
190
+ const setClauses = [];
191
+ const params = [];
192
+
193
+ for (const [key, value] of Object.entries(updates)) {
194
+ const dbField = fieldMap[key] || key;
195
+ setClauses.push(`${dbField} = ?`);
196
+ params.push(value);
197
+ }
198
+
199
+ if (setClauses.length > 0) {
200
+ params.push(address);
201
+ const stmt = this.db.prepare(`UPDATE markets SET ${setClauses.join(', ')} WHERE address = ?`);
202
+ stmt.run(...params);
203
+ }
204
+
205
+ agentEvents.emitTyped(AgentEvents.MARKET_UPDATED, { address, updates });
206
+
207
+ return this.getMarket(address);
208
+ }
209
+
210
+ // Delete a market
211
+ deleteMarket(address) {
212
+ const stmt = this.db.prepare('DELETE FROM markets WHERE address = ?');
213
+ const result = stmt.run(address);
214
+ return result.changes > 0;
215
+ }
216
+
217
+ // Get statistics
218
+ getStats() {
219
+ const now = Date.now();
220
+ const weekAgo = now - (7 * 24 * 60 * 60 * 1000);
221
+
222
+ const total = this.db.prepare('SELECT COUNT(*) as count FROM markets').get().count;
223
+ const active = this.db.prepare('SELECT COUNT(*) as count FROM markets WHERE status = ?').get('active').count;
224
+ const resolved = this.db.prepare('SELECT COUNT(*) as count FROM markets WHERE status = ?').get('resolved').count;
225
+ const cancelled = this.db.prepare('SELECT COUNT(*) as count FROM markets WHERE status = ?').get('cancelled').count;
226
+ const recentCount = this.db.prepare('SELECT COUNT(*) as count FROM markets WHERE creation_time >= ?').get(weekAgo).count;
227
+
228
+ const byCategory = this.db.prepare(`
229
+ SELECT category, category_key, COUNT(*) as count
230
+ FROM markets
231
+ GROUP BY category_key
232
+ `).all();
233
+
234
+ return {
235
+ total,
236
+ active,
237
+ resolved,
238
+ cancelled,
239
+ recentCount,
240
+ byCategory,
241
+ lastUpdated: Date.now()
242
+ };
243
+ }
244
+
245
+ // Get performance metrics
246
+ getPerformanceMetrics() {
247
+ const resolved = this.db.prepare('SELECT * FROM markets WHERE status = ?').all('resolved');
248
+
249
+ const volumeResult = this.db.prepare('SELECT SUM(CAST(volume AS INTEGER)) as total FROM markets').get();
250
+ const totalVolume = BigInt(volumeResult.total || 0);
251
+
252
+ let averageDuration = 0;
253
+ if (resolved.length > 0) {
254
+ const totalDays = resolved.reduce((sum, m) => sum + (m.duration_days || 0), 0);
255
+ averageDuration = totalDays / resolved.length;
256
+ }
257
+
258
+ const total = this.db.prepare('SELECT COUNT(*) as count FROM markets').get().count;
259
+ const resolutionRate = total > 0 ? resolved.length / total : 0;
260
+
261
+ return {
262
+ totalVolume: totalVolume.toString(),
263
+ averageDuration: Math.round(averageDuration),
264
+ resolutionRate: Math.round(resolutionRate * 100) / 100,
265
+ marketCount: resolved.length
266
+ };
267
+ }
268
+
269
+ // Save daemon state
270
+ saveState(keyOrState, value) {
271
+ const stmt = this.db.prepare(`
272
+ INSERT OR REPLACE INTO daemon_state (key, value, updated_at)
273
+ VALUES (?, ?, ?)
274
+ `);
275
+
276
+ if (typeof keyOrState === 'string') {
277
+ stmt.run(keyOrState, JSON.stringify(value), Date.now());
278
+ } else {
279
+ // Old API: saveState(stateObject)
280
+ const stateObj = { ...keyOrState, savedAt: Date.now() };
281
+ stmt.run('__full_state__', JSON.stringify(stateObj), Date.now());
282
+ }
283
+
284
+ agentEvents.emitTyped(AgentEvents.STATE_SAVED, {});
285
+ }
286
+
287
+ // Get daemon state
288
+ getState(key) {
289
+ if (key) {
290
+ const stmt = this.db.prepare('SELECT value FROM daemon_state WHERE key = ?');
291
+ const row = stmt.get(key);
292
+ return row ? JSON.parse(row.value) : null;
293
+ }
294
+
295
+ // Return full state object for old API
296
+ const stmt = this.db.prepare('SELECT value FROM daemon_state WHERE key = ?');
297
+ const row = stmt.get('__full_state__');
298
+ return row ? JSON.parse(row.value) : null;
299
+ }
300
+
301
+ // Get all state
302
+ getAllState() {
303
+ const stmt = this.db.prepare('SELECT key, value FROM daemon_state');
304
+ const rows = stmt.all();
305
+
306
+ const state = {};
307
+ for (const row of rows) {
308
+ if (row.key === '__full_state__') {
309
+ Object.assign(state, JSON.parse(row.value));
310
+ } else {
311
+ state[row.key] = JSON.parse(row.value);
312
+ }
313
+ }
314
+
315
+ return state;
316
+ }
317
+
318
+ // Save webhook event
319
+ saveWebhookEvent(event) {
320
+ const stmt = this.db.prepare(`
321
+ INSERT INTO webhook_events (event_type, signature, data, processed, timestamp)
322
+ VALUES (?, ?, ?, 0, ?)
323
+ `);
324
+
325
+ stmt.run(
326
+ event.type,
327
+ event.signature,
328
+ JSON.stringify(event.data),
329
+ Date.now()
330
+ );
331
+ }
332
+
333
+ // Log webhook event (alias)
334
+ logWebhookEvent(eventType, signature, data) {
335
+ this.saveWebhookEvent({ type: eventType, signature, data });
336
+ }
337
+
338
+ // Get webhook events
339
+ getWebhookEvents(filtersOrLimit = {}) {
340
+ if (typeof filtersOrLimit === 'number') {
341
+ const stmt = this.db.prepare('SELECT * FROM webhook_events ORDER BY timestamp DESC LIMIT ?');
342
+ return stmt.all(filtersOrLimit);
343
+ }
344
+
345
+ const filters = filtersOrLimit;
346
+ let sql = 'SELECT * FROM webhook_events WHERE 1=1';
347
+ const params = [];
348
+
349
+ if (filters.eventType) {
350
+ sql += ' AND event_type = ?';
351
+ params.push(filters.eventType);
352
+ }
353
+
354
+ if (filters.processed !== undefined) {
355
+ sql += ' AND processed = ?';
356
+ params.push(filters.processed ? 1 : 0);
357
+ }
358
+
359
+ sql += ' ORDER BY timestamp DESC';
360
+
361
+ if (filters.limit) {
362
+ sql += ' LIMIT ?';
363
+ params.push(filters.limit);
364
+ }
365
+
366
+ const stmt = this.db.prepare(sql);
367
+ return stmt.all(...params);
368
+ }
369
+
370
+ // Mark webhook as processed
371
+ markWebhookProcessed(id) {
372
+ const stmt = this.db.prepare('UPDATE webhook_events SET processed = 1 WHERE id = ?');
373
+ stmt.run(id);
374
+ }
375
+
376
+ // Close database
377
+ close() {
378
+ this.db.close();
379
+ }
380
+
381
+ // Clear all data
382
+ clear() {
383
+ this.db.exec('DELETE FROM markets');
384
+ this.db.exec('DELETE FROM daemon_state');
385
+ this.db.exec('DELETE FROM webhook_events');
386
+ }
387
+
388
+ // Export data as JSON string
389
+ export() {
390
+ const markets = this.getAllMarkets();
391
+ const state = this.getAllState();
392
+ const events = this.getWebhookEvents(1000);
393
+
394
+ return JSON.stringify({ markets, state, events }, null, 2);
395
+ }
396
+
397
+ // Import data from JSON string
398
+ import(jsonString) {
399
+ try {
400
+ const data = JSON.parse(jsonString);
401
+
402
+ if (data.markets) {
403
+ for (const market of data.markets) {
404
+ this.saveMarket(market);
405
+ }
406
+ }
407
+
408
+ return true;
409
+ } catch {
410
+ return false;
411
+ }
412
+ }
413
+ }
414
+
415
+ // Factory function
416
+ export function createMarketStore(dbPath = null) {
417
+ return new MarketStore(dbPath);
418
+ }
@@ -0,0 +1,172 @@
1
+ // CLI spinner and progress utilities
2
+ // Provides elegant loading indicators for long operations
3
+
4
+ import ora from 'ora';
5
+
6
+ // Default spinner configuration
7
+ const DEFAULT_CONFIG = {
8
+ spinner: 'dots',
9
+ color: 'cyan'
10
+ };
11
+
12
+ // Create a new spinner
13
+ export function createSpinner(text, config = {}) {
14
+ return ora({
15
+ text,
16
+ ...DEFAULT_CONFIG,
17
+ ...config
18
+ });
19
+ }
20
+
21
+ // Execute a task with a spinner
22
+ export async function withSpinner(text, task, options = {}) {
23
+ const spinner = createSpinner(text, options);
24
+ spinner.start();
25
+
26
+ try {
27
+ const result = await task(spinner);
28
+ spinner.succeed(options.successText || text);
29
+ return result;
30
+ } catch (error) {
31
+ spinner.fail(options.failText || `${text} - ${error.message}`);
32
+ if (!options.silent) {
33
+ throw error;
34
+ }
35
+ return null;
36
+ }
37
+ }
38
+
39
+ // Execute multiple tasks with progress
40
+ export async function withProgress(tasks, options = {}) {
41
+ const results = [];
42
+ const total = tasks.length;
43
+ const spinner = createSpinner(`Processing 0/${total}...`, options);
44
+
45
+ spinner.start();
46
+
47
+ for (let i = 0; i < tasks.length; i++) {
48
+ const { name, task } = tasks[i];
49
+ spinner.text = `${name} (${i + 1}/${total})...`;
50
+
51
+ try {
52
+ const result = await task();
53
+ results.push({ name, success: true, result });
54
+ } catch (error) {
55
+ results.push({ name, success: false, error: error.message });
56
+
57
+ if (options.stopOnError) {
58
+ spinner.fail(`Failed at: ${name}`);
59
+ throw error;
60
+ }
61
+ }
62
+ }
63
+
64
+ const successCount = results.filter(r => r.success).length;
65
+ spinner.succeed(`Completed ${successCount}/${total} tasks`);
66
+
67
+ return results;
68
+ }
69
+
70
+ // Indeterminate progress for unknown duration tasks
71
+ export function createIndeterminateProgress(text) {
72
+ const spinner = createSpinner(text);
73
+ let elapsed = 0;
74
+ let timer = null;
75
+
76
+ return {
77
+ start() {
78
+ spinner.start();
79
+ timer = setInterval(() => {
80
+ elapsed++;
81
+ spinner.text = `${text} (${elapsed}s)`;
82
+ }, 1000);
83
+ return this;
84
+ },
85
+
86
+ update(newText) {
87
+ spinner.text = `${newText} (${elapsed}s)`;
88
+ return this;
89
+ },
90
+
91
+ succeed(successText) {
92
+ if (timer) clearInterval(timer);
93
+ spinner.succeed(successText || `${text} (${elapsed}s)`);
94
+ return this;
95
+ },
96
+
97
+ fail(failText) {
98
+ if (timer) clearInterval(timer);
99
+ spinner.fail(failText || `${text} failed after ${elapsed}s`);
100
+ return this;
101
+ },
102
+
103
+ stop() {
104
+ if (timer) clearInterval(timer);
105
+ spinner.stop();
106
+ return this;
107
+ }
108
+ };
109
+ }
110
+
111
+ // Step-by-step progress indicator
112
+ export class StepProgress {
113
+ constructor(steps, options = {}) {
114
+ this.steps = steps;
115
+ this.currentStep = 0;
116
+ this.spinner = createSpinner('', options);
117
+ this.startTime = null;
118
+ }
119
+
120
+ start() {
121
+ this.startTime = Date.now();
122
+ this.updateSpinner();
123
+ this.spinner.start();
124
+ return this;
125
+ }
126
+
127
+ updateSpinner() {
128
+ const step = this.steps[this.currentStep];
129
+ const progress = `[${this.currentStep + 1}/${this.steps.length}]`;
130
+ this.spinner.text = `${progress} ${step}`;
131
+ }
132
+
133
+ next(customText) {
134
+ if (this.currentStep < this.steps.length - 1) {
135
+ this.currentStep++;
136
+ if (customText) {
137
+ this.steps[this.currentStep] = customText;
138
+ }
139
+ this.updateSpinner();
140
+ }
141
+ return this;
142
+ }
143
+
144
+ succeed(text) {
145
+ const elapsed = Math.round((Date.now() - this.startTime) / 1000);
146
+ this.spinner.succeed(text || `Completed ${this.steps.length} steps in ${elapsed}s`);
147
+ return this;
148
+ }
149
+
150
+ fail(text) {
151
+ const elapsed = Math.round((Date.now() - this.startTime) / 1000);
152
+ this.spinner.fail(text || `Failed at step ${this.currentStep + 1} after ${elapsed}s`);
153
+ return this;
154
+ }
155
+ }
156
+
157
+ // Simple text status updates (no spinner)
158
+ export function statusLine(text, symbol = '-') {
159
+ console.log(` ${symbol} ${text}`);
160
+ }
161
+
162
+ export function successLine(text) {
163
+ statusLine(text, '\u2713');
164
+ }
165
+
166
+ export function errorLine(text) {
167
+ statusLine(text, '\u2717');
168
+ }
169
+
170
+ export function infoLine(text) {
171
+ statusLine(text, '\u2022');
172
+ }