@agentlensai/server 0.2.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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/dist/__tests__/agents-stats.test.d.ts +5 -0
  3. package/dist/__tests__/agents-stats.test.d.ts.map +1 -0
  4. package/dist/__tests__/agents-stats.test.js +134 -0
  5. package/dist/__tests__/agents-stats.test.js.map +1 -0
  6. package/dist/__tests__/api-keys.test.d.ts +5 -0
  7. package/dist/__tests__/api-keys.test.d.ts.map +1 -0
  8. package/dist/__tests__/api-keys.test.js +118 -0
  9. package/dist/__tests__/api-keys.test.js.map +1 -0
  10. package/dist/__tests__/auth-no-db.test.d.ts +2 -0
  11. package/dist/__tests__/auth-no-db.test.d.ts.map +1 -0
  12. package/dist/__tests__/auth-no-db.test.js +43 -0
  13. package/dist/__tests__/auth-no-db.test.js.map +1 -0
  14. package/dist/__tests__/auth.test.d.ts +5 -0
  15. package/dist/__tests__/auth.test.d.ts.map +1 -0
  16. package/dist/__tests__/auth.test.js +86 -0
  17. package/dist/__tests__/auth.test.js.map +1 -0
  18. package/dist/__tests__/config.test.d.ts +2 -0
  19. package/dist/__tests__/config.test.d.ts.map +1 -0
  20. package/dist/__tests__/config.test.js +37 -0
  21. package/dist/__tests__/config.test.js.map +1 -0
  22. package/dist/__tests__/events-ingest.test.d.ts +5 -0
  23. package/dist/__tests__/events-ingest.test.d.ts.map +1 -0
  24. package/dist/__tests__/events-ingest.test.js +248 -0
  25. package/dist/__tests__/events-ingest.test.js.map +1 -0
  26. package/dist/__tests__/events-query.test.d.ts +5 -0
  27. package/dist/__tests__/events-query.test.d.ts.map +1 -0
  28. package/dist/__tests__/events-query.test.js +205 -0
  29. package/dist/__tests__/events-query.test.js.map +1 -0
  30. package/dist/__tests__/health.test.d.ts +5 -0
  31. package/dist/__tests__/health.test.d.ts.map +1 -0
  32. package/dist/__tests__/health.test.js +40 -0
  33. package/dist/__tests__/health.test.js.map +1 -0
  34. package/dist/__tests__/sessions.test.d.ts +5 -0
  35. package/dist/__tests__/sessions.test.d.ts.map +1 -0
  36. package/dist/__tests__/sessions.test.js +176 -0
  37. package/dist/__tests__/sessions.test.js.map +1 -0
  38. package/dist/__tests__/test-helpers.d.ts +24 -0
  39. package/dist/__tests__/test-helpers.d.ts.map +1 -0
  40. package/dist/__tests__/test-helpers.js +45 -0
  41. package/dist/__tests__/test-helpers.js.map +1 -0
  42. package/dist/config.d.ts +20 -0
  43. package/dist/config.d.ts.map +1 -0
  44. package/dist/config.js +20 -0
  45. package/dist/config.js.map +1 -0
  46. package/dist/db/__tests__/init.test.d.ts +2 -0
  47. package/dist/db/__tests__/init.test.d.ts.map +1 -0
  48. package/dist/db/__tests__/init.test.js +73 -0
  49. package/dist/db/__tests__/init.test.js.map +1 -0
  50. package/dist/db/__tests__/sqlite-store-read.test.d.ts +2 -0
  51. package/dist/db/__tests__/sqlite-store-read.test.d.ts.map +1 -0
  52. package/dist/db/__tests__/sqlite-store-read.test.js +749 -0
  53. package/dist/db/__tests__/sqlite-store-read.test.js.map +1 -0
  54. package/dist/db/__tests__/sqlite-store-write.test.d.ts +2 -0
  55. package/dist/db/__tests__/sqlite-store-write.test.d.ts.map +1 -0
  56. package/dist/db/__tests__/sqlite-store-write.test.js +418 -0
  57. package/dist/db/__tests__/sqlite-store-write.test.js.map +1 -0
  58. package/dist/db/errors.d.ts +16 -0
  59. package/dist/db/errors.d.ts.map +1 -0
  60. package/dist/db/errors.js +22 -0
  61. package/dist/db/errors.js.map +1 -0
  62. package/dist/db/index.d.ts +33 -0
  63. package/dist/db/index.d.ts.map +1 -0
  64. package/dist/db/index.js +44 -0
  65. package/dist/db/index.js.map +1 -0
  66. package/dist/db/migrate.d.ts +26 -0
  67. package/dist/db/migrate.d.ts.map +1 -0
  68. package/dist/db/migrate.js +128 -0
  69. package/dist/db/migrate.js.map +1 -0
  70. package/dist/db/schema.sqlite.d.ts +1009 -0
  71. package/dist/db/schema.sqlite.d.ts.map +1 -0
  72. package/dist/db/schema.sqlite.js +96 -0
  73. package/dist/db/schema.sqlite.js.map +1 -0
  74. package/dist/db/sqlite-store.d.ts +68 -0
  75. package/dist/db/sqlite-store.d.ts.map +1 -0
  76. package/dist/db/sqlite-store.js +753 -0
  77. package/dist/db/sqlite-store.js.map +1 -0
  78. package/dist/index.d.ts +45 -0
  79. package/dist/index.d.ts.map +1 -0
  80. package/dist/index.js +182 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/lib/__tests__/retention.test.d.ts +2 -0
  83. package/dist/lib/__tests__/retention.test.d.ts.map +1 -0
  84. package/dist/lib/__tests__/retention.test.js +238 -0
  85. package/dist/lib/__tests__/retention.test.js.map +1 -0
  86. package/dist/lib/retention.d.ts +31 -0
  87. package/dist/lib/retention.d.ts.map +1 -0
  88. package/dist/lib/retention.js +47 -0
  89. package/dist/lib/retention.js.map +1 -0
  90. package/dist/middleware/auth.d.ts +37 -0
  91. package/dist/middleware/auth.d.ts.map +1 -0
  92. package/dist/middleware/auth.js +78 -0
  93. package/dist/middleware/auth.js.map +1 -0
  94. package/dist/routes/agents.d.ts +13 -0
  95. package/dist/routes/agents.d.ts.map +1 -0
  96. package/dist/routes/agents.js +34 -0
  97. package/dist/routes/agents.js.map +1 -0
  98. package/dist/routes/api-keys.d.ts +14 -0
  99. package/dist/routes/api-keys.d.ts.map +1 -0
  100. package/dist/routes/api-keys.js +81 -0
  101. package/dist/routes/api-keys.js.map +1 -0
  102. package/dist/routes/config.d.ts +39 -0
  103. package/dist/routes/config.d.ts.map +1 -0
  104. package/dist/routes/config.js +97 -0
  105. package/dist/routes/config.js.map +1 -0
  106. package/dist/routes/events.d.ts +14 -0
  107. package/dist/routes/events.d.ts.map +1 -0
  108. package/dist/routes/events.js +164 -0
  109. package/dist/routes/events.js.map +1 -0
  110. package/dist/routes/sessions.d.ts +14 -0
  111. package/dist/routes/sessions.d.ts.map +1 -0
  112. package/dist/routes/sessions.js +72 -0
  113. package/dist/routes/sessions.js.map +1 -0
  114. package/dist/routes/stats.d.ts +12 -0
  115. package/dist/routes/stats.d.ts.map +1 -0
  116. package/dist/routes/stats.js +16 -0
  117. package/dist/routes/stats.js.map +1 -0
  118. package/package.json +61 -0
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Database initialization — dialect selector.
3
+ *
4
+ * Creates the appropriate Drizzle ORM instance based on DB_DIALECT env var.
5
+ * SQLite (default) uses WAL mode with tuned pragmas.
6
+ */
7
+ import Database from 'better-sqlite3';
8
+ import { drizzle } from 'drizzle-orm/better-sqlite3';
9
+ import * as schema from './schema.sqlite.js';
10
+ /**
11
+ * Create a database connection with the appropriate dialect.
12
+ *
13
+ * For SQLite, applies performance pragmas:
14
+ * - journal_mode=WAL (concurrent read/write)
15
+ * - synchronous=NORMAL (durability/performance balance)
16
+ * - cache_size=-64000 (64MB page cache)
17
+ * - busy_timeout=5000 (5s wait on locks)
18
+ */
19
+ export function createDb(options = {}) {
20
+ const dialect = options.dialect ??
21
+ process.env['DB_DIALECT'] ??
22
+ 'sqlite';
23
+ if (dialect === 'postgresql') {
24
+ // PostgreSQL support is deferred — interface defined, implementation later
25
+ throw new Error('PostgreSQL dialect is not yet implemented. Use DB_DIALECT=sqlite (default).');
26
+ }
27
+ const dbPath = options.databasePath ?? process.env['DATABASE_PATH'] ?? './agentlens.db';
28
+ const sqlite = new Database(dbPath);
29
+ // Apply SQLite performance pragmas per Architecture §6.4
30
+ sqlite.pragma('journal_mode = WAL');
31
+ sqlite.pragma('synchronous = NORMAL');
32
+ sqlite.pragma('cache_size = -64000');
33
+ sqlite.pragma('busy_timeout = 5000');
34
+ sqlite.pragma('foreign_keys = ON');
35
+ return drizzle(sqlite, { schema });
36
+ }
37
+ /**
38
+ * Create an in-memory SQLite database for testing.
39
+ * Tables are created via push (no migration files needed).
40
+ */
41
+ export function createTestDb() {
42
+ return createDb({ databasePath: ':memory:' });
43
+ }
44
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,OAAO,EAA8B,MAAM,4BAA4B,CAAC;AACjF,OAAO,KAAK,MAAM,MAAM,oBAAoB,CAAC;AAa7C;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ,CAAC,UAA2B,EAAE;IACpD,MAAM,OAAO,GACX,OAAO,CAAC,OAAO;QACd,OAAO,CAAC,GAAG,CAAC,YAAY,CAAyC;QAClE,QAAQ,CAAC;IAEX,IAAI,OAAO,KAAK,YAAY,EAAE,CAAC;QAC7B,2EAA2E;QAC3E,MAAM,IAAI,KAAK,CAAC,6EAA6E,CAAC,CAAC;IACjG,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,gBAAgB,CAAC;IACxF,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;IAEpC,yDAAyD;IACzD,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IACpC,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;IACtC,MAAM,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC;IACrC,MAAM,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC;IACrC,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAEnC,OAAO,OAAO,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,QAAQ,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC,CAAC;AAChD,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Database migration runner.
3
+ *
4
+ * For SQLite, uses Drizzle's push-based approach to create/update tables
5
+ * on first start. For production, Drizzle Kit migrations can be used.
6
+ */
7
+ import type { SqliteDb } from './index.js';
8
+ /**
9
+ * Run migrations: create all tables and indexes if they don't exist.
10
+ *
11
+ * Uses CREATE TABLE IF NOT EXISTS for idempotent startup.
12
+ * This approach is simpler than file-based migrations for an
13
+ * embedded SQLite database that auto-creates on first start.
14
+ */
15
+ export declare function runMigrations(db: SqliteDb): void;
16
+ /**
17
+ * Verify that WAL mode is enabled.
18
+ */
19
+ export declare function verifyPragmas(db: SqliteDb): {
20
+ journalMode: string;
21
+ synchronous: string;
22
+ cacheSize: number;
23
+ busyTimeout: number;
24
+ foreignKeys: boolean;
25
+ };
26
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/db/migrate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAoGhD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,GAAG;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;CACtB,CAoBA"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Database migration runner.
3
+ *
4
+ * For SQLite, uses Drizzle's push-based approach to create/update tables
5
+ * on first start. For production, Drizzle Kit migrations can be used.
6
+ */
7
+ import { sql } from 'drizzle-orm';
8
+ /**
9
+ * Run migrations: create all tables and indexes if they don't exist.
10
+ *
11
+ * Uses CREATE TABLE IF NOT EXISTS for idempotent startup.
12
+ * This approach is simpler than file-based migrations for an
13
+ * embedded SQLite database that auto-creates on first start.
14
+ */
15
+ export function runMigrations(db) {
16
+ // Create all tables using raw SQL for CREATE IF NOT EXISTS
17
+ // Drizzle doesn't have a built-in "push" that works at runtime,
18
+ // so we use the schema definitions to generate DDL.
19
+ db.run(sql `
20
+ CREATE TABLE IF NOT EXISTS events (
21
+ id TEXT PRIMARY KEY,
22
+ timestamp TEXT NOT NULL,
23
+ session_id TEXT NOT NULL,
24
+ agent_id TEXT NOT NULL,
25
+ event_type TEXT NOT NULL,
26
+ severity TEXT NOT NULL DEFAULT 'info',
27
+ payload TEXT NOT NULL,
28
+ metadata TEXT NOT NULL DEFAULT '{}',
29
+ prev_hash TEXT,
30
+ hash TEXT NOT NULL
31
+ )
32
+ `);
33
+ db.run(sql `
34
+ CREATE TABLE IF NOT EXISTS sessions (
35
+ id TEXT PRIMARY KEY,
36
+ agent_id TEXT NOT NULL,
37
+ agent_name TEXT,
38
+ started_at TEXT NOT NULL,
39
+ ended_at TEXT,
40
+ status TEXT NOT NULL DEFAULT 'active',
41
+ event_count INTEGER NOT NULL DEFAULT 0,
42
+ tool_call_count INTEGER NOT NULL DEFAULT 0,
43
+ error_count INTEGER NOT NULL DEFAULT 0,
44
+ total_cost_usd REAL NOT NULL DEFAULT 0,
45
+ tags TEXT NOT NULL DEFAULT '[]'
46
+ )
47
+ `);
48
+ db.run(sql `
49
+ CREATE TABLE IF NOT EXISTS agents (
50
+ id TEXT PRIMARY KEY,
51
+ name TEXT NOT NULL,
52
+ description TEXT,
53
+ first_seen_at TEXT NOT NULL,
54
+ last_seen_at TEXT NOT NULL,
55
+ session_count INTEGER NOT NULL DEFAULT 0
56
+ )
57
+ `);
58
+ db.run(sql `
59
+ CREATE TABLE IF NOT EXISTS alert_rules (
60
+ id TEXT PRIMARY KEY,
61
+ name TEXT NOT NULL,
62
+ enabled INTEGER NOT NULL DEFAULT 1,
63
+ condition TEXT NOT NULL,
64
+ threshold REAL NOT NULL,
65
+ window_minutes INTEGER NOT NULL,
66
+ scope TEXT NOT NULL DEFAULT '{}',
67
+ notify_channels TEXT NOT NULL DEFAULT '[]',
68
+ created_at TEXT NOT NULL,
69
+ updated_at TEXT NOT NULL
70
+ )
71
+ `);
72
+ db.run(sql `
73
+ CREATE TABLE IF NOT EXISTS alert_history (
74
+ id TEXT PRIMARY KEY,
75
+ rule_id TEXT NOT NULL REFERENCES alert_rules(id),
76
+ triggered_at TEXT NOT NULL,
77
+ resolved_at TEXT,
78
+ current_value REAL NOT NULL,
79
+ threshold REAL NOT NULL,
80
+ message TEXT NOT NULL
81
+ )
82
+ `);
83
+ db.run(sql `
84
+ CREATE TABLE IF NOT EXISTS api_keys (
85
+ id TEXT PRIMARY KEY,
86
+ key_hash TEXT NOT NULL,
87
+ name TEXT NOT NULL,
88
+ scopes TEXT NOT NULL,
89
+ created_at INTEGER NOT NULL,
90
+ last_used_at INTEGER,
91
+ revoked_at INTEGER,
92
+ rate_limit INTEGER
93
+ )
94
+ `);
95
+ // Create indexes (IF NOT EXISTS)
96
+ db.run(sql `CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp)`);
97
+ db.run(sql `CREATE INDEX IF NOT EXISTS idx_events_session_id ON events(session_id)`);
98
+ db.run(sql `CREATE INDEX IF NOT EXISTS idx_events_agent_id ON events(agent_id)`);
99
+ db.run(sql `CREATE INDEX IF NOT EXISTS idx_events_type ON events(event_type)`);
100
+ db.run(sql `CREATE INDEX IF NOT EXISTS idx_events_session_ts ON events(session_id, timestamp)`);
101
+ db.run(sql `CREATE INDEX IF NOT EXISTS idx_events_agent_type_ts ON events(agent_id, event_type, timestamp)`);
102
+ db.run(sql `CREATE INDEX IF NOT EXISTS idx_sessions_agent_id ON sessions(agent_id)`);
103
+ db.run(sql `CREATE INDEX IF NOT EXISTS idx_sessions_started_at ON sessions(started_at)`);
104
+ db.run(sql `CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status)`);
105
+ db.run(sql `CREATE INDEX IF NOT EXISTS idx_api_keys_hash ON api_keys(key_hash)`);
106
+ db.run(sql `CREATE INDEX IF NOT EXISTS idx_alert_history_rule_id ON alert_history(rule_id)`);
107
+ }
108
+ /**
109
+ * Verify that WAL mode is enabled.
110
+ */
111
+ export function verifyPragmas(db) {
112
+ const journalMode = db.get(sql `PRAGMA journal_mode`)?.journal_mode ?? '';
113
+ const synchronous = db.get(sql `PRAGMA synchronous`)?.synchronous ?? -1;
114
+ const cacheSize = db.get(sql `PRAGMA cache_size`)?.cache_size ?? 0;
115
+ // SQLite returns { timeout: N } for PRAGMA busy_timeout
116
+ const busyTimeout = db.get(sql `PRAGMA busy_timeout`)?.timeout ?? 0;
117
+ const foreignKeys = db.get(sql `PRAGMA foreign_keys`)?.foreign_keys === 1;
118
+ // synchronous: 0=OFF, 1=NORMAL, 2=FULL, 3=EXTRA
119
+ const syncNames = { 0: 'OFF', 1: 'NORMAL', 2: 'FULL', 3: 'EXTRA' };
120
+ return {
121
+ journalMode,
122
+ synchronous: syncNames[synchronous] ?? String(synchronous),
123
+ cacheSize,
124
+ busyTimeout,
125
+ foreignKeys,
126
+ };
127
+ }
128
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../src/db/migrate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAElC;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,EAAY;IACxC,2DAA2D;IAC3D,gEAAgE;IAChE,oDAAoD;IACpD,EAAE,CAAC,GAAG,CAAC,GAAG,CAAA;;;;;;;;;;;;;GAaT,CAAC,CAAC;IAEH,EAAE,CAAC,GAAG,CAAC,GAAG,CAAA;;;;;;;;;;;;;;GAcT,CAAC,CAAC;IAEH,EAAE,CAAC,GAAG,CAAC,GAAG,CAAA;;;;;;;;;GAST,CAAC,CAAC;IAEH,EAAE,CAAC,GAAG,CAAC,GAAG,CAAA;;;;;;;;;;;;;GAaT,CAAC,CAAC;IAEH,EAAE,CAAC,GAAG,CAAC,GAAG,CAAA;;;;;;;;;;GAUT,CAAC,CAAC;IAEH,EAAE,CAAC,GAAG,CAAC,GAAG,CAAA;;;;;;;;;;;GAWT,CAAC,CAAC;IAEH,iCAAiC;IACjC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAA,sEAAsE,CAAC,CAAC;IAClF,EAAE,CAAC,GAAG,CAAC,GAAG,CAAA,wEAAwE,CAAC,CAAC;IACpF,EAAE,CAAC,GAAG,CAAC,GAAG,CAAA,oEAAoE,CAAC,CAAC;IAChF,EAAE,CAAC,GAAG,CAAC,GAAG,CAAA,kEAAkE,CAAC,CAAC;IAC9E,EAAE,CAAC,GAAG,CAAC,GAAG,CAAA,mFAAmF,CAAC,CAAC;IAC/F,EAAE,CAAC,GAAG,CACJ,GAAG,CAAA,gGAAgG,CACpG,CAAC;IACF,EAAE,CAAC,GAAG,CAAC,GAAG,CAAA,wEAAwE,CAAC,CAAC;IACpF,EAAE,CAAC,GAAG,CAAC,GAAG,CAAA,4EAA4E,CAAC,CAAC;IACxF,EAAE,CAAC,GAAG,CAAC,GAAG,CAAA,oEAAoE,CAAC,CAAC;IAChF,EAAE,CAAC,GAAG,CAAC,GAAG,CAAA,oEAAoE,CAAC,CAAC;IAChF,EAAE,CAAC,GAAG,CAAC,GAAG,CAAA,gFAAgF,CAAC,CAAC;AAC9F,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,EAAY;IAOxC,MAAM,WAAW,GACf,EAAE,CAAC,GAAG,CAA2B,GAAG,CAAA,qBAAqB,CAAC,EAAE,YAAY,IAAI,EAAE,CAAC;IACjF,MAAM,WAAW,GAAG,EAAE,CAAC,GAAG,CAA0B,GAAG,CAAA,oBAAoB,CAAC,EAAE,WAAW,IAAI,CAAC,CAAC,CAAC;IAChG,MAAM,SAAS,GAAG,EAAE,CAAC,GAAG,CAAyB,GAAG,CAAA,mBAAmB,CAAC,EAAE,UAAU,IAAI,CAAC,CAAC;IAC1F,wDAAwD;IACxD,MAAM,WAAW,GAAG,EAAE,CAAC,GAAG,CAAsB,GAAG,CAAA,qBAAqB,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC;IACxF,MAAM,WAAW,GACf,EAAE,CAAC,GAAG,CAA2B,GAAG,CAAA,qBAAqB,CAAC,EAAE,YAAY,KAAK,CAAC,CAAC;IAEjF,gDAAgD;IAChD,MAAM,SAAS,GAA2B,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC;IAE3F,OAAO;QACL,WAAW;QACX,WAAW,EAAE,SAAS,CAAC,WAAW,CAAC,IAAI,MAAM,CAAC,WAAW,CAAC;QAC1D,SAAS;QACT,WAAW;QACX,WAAW;KACZ,CAAC;AACJ,CAAC"}