superskill 0.2.5 → 0.2.7
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/CHANGELOG.md +32 -0
- package/README.md +118 -134
- package/dist/commands/skill/activate.d.ts +23 -0
- package/dist/commands/skill/activate.js +170 -0
- package/dist/commands/skill/activate.js.map +1 -0
- package/dist/commands/skill/generate.d.ts +28 -0
- package/dist/commands/skill/generate.js +180 -0
- package/dist/commands/skill/generate.js.map +1 -0
- package/dist/commands/skill/helpers.d.ts +14 -0
- package/dist/commands/skill/helpers.js +82 -0
- package/dist/commands/skill/helpers.js.map +1 -0
- package/dist/commands/skill/manifest.d.ts +26 -0
- package/dist/commands/skill/manifest.js +90 -0
- package/dist/commands/skill/manifest.js.map +1 -0
- package/dist/commands/skill/marketplace.d.ts +8 -122
- package/dist/commands/skill/marketplace.js +12 -590
- package/dist/commands/skill/marketplace.js.map +1 -1
- package/dist/commands/skill/resolve.d.ts +47 -0
- package/dist/commands/skill/resolve.js +101 -0
- package/dist/commands/skill/resolve.js.map +1 -0
- package/dist/commands/skill/web-discovery.d.ts +36 -4
- package/dist/commands/skill/web-discovery.js +160 -5
- package/dist/commands/skill/web-discovery.js.map +1 -1
- package/dist/lib/analytics.d.ts +55 -0
- package/dist/lib/analytics.js +167 -0
- package/dist/lib/analytics.js.map +1 -0
- package/dist/lib/skill-cache.d.ts +34 -0
- package/dist/lib/skill-cache.js +150 -0
- package/dist/lib/skill-cache.js.map +1 -0
- package/dist/setup/postinstall.js +21 -0
- package/dist/setup/postinstall.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later OR Commercial
|
|
2
|
+
import { readFile, writeFile, mkdir, rename } from "fs/promises";
|
|
3
|
+
import { resolve, dirname } from "path";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
import { randomBytes } from "crypto";
|
|
6
|
+
// ── Constants ────────────────────────────────────────
|
|
7
|
+
const MAX_ACTIVATIONS = 1000;
|
|
8
|
+
const MAX_FAILED_SEARCHES = 1000;
|
|
9
|
+
const ANALYTICS_DIR = resolve(process.env.HOME ?? process.env.USERPROFILE ?? tmpdir(), ".superskill");
|
|
10
|
+
const ANALYTICS_PATH = resolve(ANALYTICS_DIR, "analytics.json");
|
|
11
|
+
/** Override path for testing. */
|
|
12
|
+
let analyticsPathOverride = null;
|
|
13
|
+
export function setAnalyticsPath(path) {
|
|
14
|
+
analyticsPathOverride = path;
|
|
15
|
+
}
|
|
16
|
+
function getAnalyticsPath() {
|
|
17
|
+
return analyticsPathOverride ?? ANALYTICS_PATH;
|
|
18
|
+
}
|
|
19
|
+
// ── Storage ──────────────────────────────────────────
|
|
20
|
+
function emptyData() {
|
|
21
|
+
return {
|
|
22
|
+
activations: [],
|
|
23
|
+
failed_searches: [],
|
|
24
|
+
summary: {
|
|
25
|
+
total_activations: 0,
|
|
26
|
+
total_failed_searches: 0,
|
|
27
|
+
most_used_skills: [],
|
|
28
|
+
first_seen: null,
|
|
29
|
+
last_seen: null,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async function readAnalytics() {
|
|
34
|
+
try {
|
|
35
|
+
const raw = await readFile(getAnalyticsPath(), "utf-8");
|
|
36
|
+
const data = JSON.parse(raw);
|
|
37
|
+
return {
|
|
38
|
+
activations: Array.isArray(data.activations) ? data.activations : [],
|
|
39
|
+
failed_searches: Array.isArray(data.failed_searches) ? data.failed_searches : [],
|
|
40
|
+
summary: data.summary ?? emptyData().summary,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return emptyData();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function computeSummary(data) {
|
|
48
|
+
const counts = new Map();
|
|
49
|
+
for (const a of data.activations) {
|
|
50
|
+
counts.set(a.skill_id, (counts.get(a.skill_id) ?? 0) + 1);
|
|
51
|
+
}
|
|
52
|
+
const most_used_skills = Array.from(counts.entries())
|
|
53
|
+
.map(([id, count]) => ({ id, count }))
|
|
54
|
+
.sort((a, b) => b.count - a.count)
|
|
55
|
+
.slice(0, 20);
|
|
56
|
+
const allTimestamps = [
|
|
57
|
+
...data.activations.map((a) => a.timestamp),
|
|
58
|
+
...data.failed_searches.map((f) => f.timestamp),
|
|
59
|
+
].filter(Boolean).sort();
|
|
60
|
+
return {
|
|
61
|
+
total_activations: data.activations.length,
|
|
62
|
+
total_failed_searches: data.failed_searches.length,
|
|
63
|
+
most_used_skills,
|
|
64
|
+
first_seen: allTimestamps[0] ?? null,
|
|
65
|
+
last_seen: allTimestamps[allTimestamps.length - 1] ?? null,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function rotateIfNeeded(data) {
|
|
69
|
+
if (data.activations.length > MAX_ACTIVATIONS) {
|
|
70
|
+
data.activations = data.activations.slice(-MAX_ACTIVATIONS);
|
|
71
|
+
}
|
|
72
|
+
if (data.failed_searches.length > MAX_FAILED_SEARCHES) {
|
|
73
|
+
data.failed_searches = data.failed_searches.slice(-MAX_FAILED_SEARCHES);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function writeAnalytics(data) {
|
|
77
|
+
const filePath = getAnalyticsPath();
|
|
78
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
79
|
+
// Atomic write: write to temp file, then rename
|
|
80
|
+
const tempPath = filePath + "." + randomBytes(6).toString("hex") + ".tmp";
|
|
81
|
+
const json = JSON.stringify(data, null, 2);
|
|
82
|
+
await writeFile(tempPath, json, "utf-8");
|
|
83
|
+
await rename(tempPath, filePath);
|
|
84
|
+
}
|
|
85
|
+
// ── Public API ───────────────────────────────────────
|
|
86
|
+
/**
|
|
87
|
+
* Track a skill activation event.
|
|
88
|
+
* Never throws — analytics failures must not block skill activation.
|
|
89
|
+
*/
|
|
90
|
+
export async function trackActivation(activation) {
|
|
91
|
+
try {
|
|
92
|
+
const data = await readAnalytics();
|
|
93
|
+
data.activations.push({
|
|
94
|
+
...activation,
|
|
95
|
+
timestamp: new Date().toISOString(),
|
|
96
|
+
});
|
|
97
|
+
rotateIfNeeded(data);
|
|
98
|
+
data.summary = computeSummary(data);
|
|
99
|
+
await writeAnalytics(data);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Never crash — analytics failure must never block skill activation
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Track a failed search event.
|
|
107
|
+
* Never throws — analytics failures must not block skill activation.
|
|
108
|
+
*/
|
|
109
|
+
export async function trackFailedSearch(query, resultCount = 0) {
|
|
110
|
+
try {
|
|
111
|
+
const data = await readAnalytics();
|
|
112
|
+
data.failed_searches.push({
|
|
113
|
+
query,
|
|
114
|
+
timestamp: new Date().toISOString(),
|
|
115
|
+
result_count: resultCount,
|
|
116
|
+
});
|
|
117
|
+
rotateIfNeeded(data);
|
|
118
|
+
data.summary = computeSummary(data);
|
|
119
|
+
await writeAnalytics(data);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Never crash — analytics failure must never block skill activation
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Track a web discovery activation (convenience wrapper).
|
|
127
|
+
* Never throws.
|
|
128
|
+
*/
|
|
129
|
+
export async function trackWebDiscovery(taskQuery, matched) {
|
|
130
|
+
try {
|
|
131
|
+
await trackActivation({
|
|
132
|
+
skill_id: "web_discovery",
|
|
133
|
+
match_method: "web_discovery",
|
|
134
|
+
task_query: taskQuery,
|
|
135
|
+
matched,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Never crash
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get the current analytics summary.
|
|
144
|
+
* Never throws — returns empty summary on failure.
|
|
145
|
+
*/
|
|
146
|
+
export async function getAnalyticsSummary() {
|
|
147
|
+
try {
|
|
148
|
+
const data = await readAnalytics();
|
|
149
|
+
return computeSummary(data);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return emptyData().summary;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get the full analytics data (for debugging / display).
|
|
157
|
+
* Never throws — returns empty data on failure.
|
|
158
|
+
*/
|
|
159
|
+
export async function getAnalyticsData() {
|
|
160
|
+
try {
|
|
161
|
+
return await readAnalytics();
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return emptyData();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=analytics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../src/lib/analytics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** The 8 core skills that are prefetched on install */
|
|
2
|
+
export declare const PREFETCH_SKILL_IDS: string[];
|
|
3
|
+
export declare function setCacheDir(dir: string): void;
|
|
4
|
+
export declare function getCacheDir(): string;
|
|
5
|
+
/**
|
|
6
|
+
* Get cached skill content. Returns null if not cached.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getCachedSkill(skillId: string): Promise<string | null>;
|
|
9
|
+
/**
|
|
10
|
+
* Write skill content to cache. Uses atomic write (temp + rename).
|
|
11
|
+
*/
|
|
12
|
+
export declare function cacheSkill(skillId: string, content: string): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Check if a skill is cached.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isSkillCached(skillId: string): Promise<boolean>;
|
|
17
|
+
/**
|
|
18
|
+
* Prefetch core skills from their source repos.
|
|
19
|
+
* Called during postinstall. Non-fatal — warns on failure, never blocks install.
|
|
20
|
+
* Returns count of successfully cached skills.
|
|
21
|
+
*/
|
|
22
|
+
export declare function prefetchCoreSkills(options?: {
|
|
23
|
+
concurrency?: number;
|
|
24
|
+
onProgress?: (skill: string, status: "cached" | "fetched" | "failed") => void;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
fetched: number;
|
|
27
|
+
cached: number;
|
|
28
|
+
failed: number;
|
|
29
|
+
errors: string[];
|
|
30
|
+
}>;
|
|
31
|
+
/**
|
|
32
|
+
* List all cached skills.
|
|
33
|
+
*/
|
|
34
|
+
export declare function listCachedSkills(): Promise<string[]>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later OR Commercial
|
|
2
|
+
/**
|
|
3
|
+
* Skill cache — local disk cache for fetched skill content.
|
|
4
|
+
* Skills are cached at ~/.superskill/cache/{source}/{name}@{version}.md
|
|
5
|
+
* Prefetched skills are downloaded on install for offline availability.
|
|
6
|
+
*/
|
|
7
|
+
import { readFile, writeFile, mkdir, rename, readdir } from "fs/promises";
|
|
8
|
+
import { resolve, dirname, join } from "path";
|
|
9
|
+
import { homedir, tmpdir } from "os";
|
|
10
|
+
import { randomBytes } from "crypto";
|
|
11
|
+
import { CATALOG } from "../commands/skill/catalog.js";
|
|
12
|
+
// ── Configuration ────────────────────────────────────
|
|
13
|
+
const CACHE_DIR = resolve(homedir(), ".superskill", "cache");
|
|
14
|
+
/** The 8 core skills that are prefetched on install */
|
|
15
|
+
export const PREFETCH_SKILL_IDS = [
|
|
16
|
+
"superpowers/brainstorming",
|
|
17
|
+
"ecc/plan",
|
|
18
|
+
"ecc/tdd-workflow",
|
|
19
|
+
"ecc/go-review",
|
|
20
|
+
"superpowers/systematic-debugging",
|
|
21
|
+
"ecc/security-review",
|
|
22
|
+
"ecc/verification-loop",
|
|
23
|
+
"ecc/deployment-patterns",
|
|
24
|
+
];
|
|
25
|
+
// Allow tests to override the cache dir
|
|
26
|
+
let cacheDir = CACHE_DIR;
|
|
27
|
+
export function setCacheDir(dir) { cacheDir = dir; }
|
|
28
|
+
export function getCacheDir() { return cacheDir; }
|
|
29
|
+
// ── Cache Operations ─────────────────────────────────
|
|
30
|
+
/**
|
|
31
|
+
* Get cached skill content. Returns null if not cached.
|
|
32
|
+
*/
|
|
33
|
+
export async function getCachedSkill(skillId) {
|
|
34
|
+
try {
|
|
35
|
+
const path = skillCachePath(skillId);
|
|
36
|
+
return await readFile(path, "utf-8");
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Write skill content to cache. Uses atomic write (temp + rename).
|
|
44
|
+
*/
|
|
45
|
+
export async function cacheSkill(skillId, content) {
|
|
46
|
+
try {
|
|
47
|
+
const path = skillCachePath(skillId);
|
|
48
|
+
await mkdir(dirname(path), { recursive: true });
|
|
49
|
+
const tmp = join(tmpdir(), `superskill-cache-${randomBytes(8).toString("hex")}`);
|
|
50
|
+
await writeFile(tmp, content, "utf-8");
|
|
51
|
+
await rename(tmp, path);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Cache write failure is non-fatal
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Check if a skill is cached.
|
|
59
|
+
*/
|
|
60
|
+
export async function isSkillCached(skillId) {
|
|
61
|
+
try {
|
|
62
|
+
await readFile(skillCachePath(skillId), "utf-8");
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Prefetch core skills from their source repos.
|
|
71
|
+
* Called during postinstall. Non-fatal — warns on failure, never blocks install.
|
|
72
|
+
* Returns count of successfully cached skills.
|
|
73
|
+
*/
|
|
74
|
+
export async function prefetchCoreSkills(options) {
|
|
75
|
+
const concurrency = options?.concurrency ?? 4;
|
|
76
|
+
const onProgress = options?.onProgress;
|
|
77
|
+
const results = { fetched: 0, cached: 0, failed: 0, errors: [] };
|
|
78
|
+
// Resolve skill IDs to catalog entries
|
|
79
|
+
const skills = PREFETCH_SKILL_IDS
|
|
80
|
+
.map((id) => CATALOG.find((s) => s.id === id))
|
|
81
|
+
.filter((s) => s !== undefined);
|
|
82
|
+
// Process in batches for bounded concurrency
|
|
83
|
+
for (let i = 0; i < skills.length; i += concurrency) {
|
|
84
|
+
const batch = skills.slice(i, i + concurrency);
|
|
85
|
+
const batchResults = await Promise.allSettled(batch.map(async (skill) => {
|
|
86
|
+
// Check if already cached
|
|
87
|
+
if (await isSkillCached(skill.id)) {
|
|
88
|
+
onProgress?.(skill.id, "cached");
|
|
89
|
+
results.cached++;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Fetch from source
|
|
93
|
+
const content = await fetchSkillFromSource(skill.source);
|
|
94
|
+
await cacheSkill(skill.id, content);
|
|
95
|
+
onProgress?.(skill.id, "fetched");
|
|
96
|
+
results.fetched++;
|
|
97
|
+
}));
|
|
98
|
+
for (let j = 0; j < batchResults.length; j++) {
|
|
99
|
+
const result = batchResults[j];
|
|
100
|
+
if (result.status === "rejected") {
|
|
101
|
+
const skillId = batch[j].id;
|
|
102
|
+
const error = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
103
|
+
results.errors.push(`${skillId}: ${error}`);
|
|
104
|
+
results.failed++;
|
|
105
|
+
onProgress?.(skillId, "failed");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return results;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* List all cached skills.
|
|
113
|
+
*/
|
|
114
|
+
export async function listCachedSkills() {
|
|
115
|
+
try {
|
|
116
|
+
const cached = [];
|
|
117
|
+
const repos = await readdir(cacheDir).catch(() => []);
|
|
118
|
+
for (const repo of repos) {
|
|
119
|
+
const repoDir = join(cacheDir, repo);
|
|
120
|
+
const files = await readdir(repoDir).catch(() => []);
|
|
121
|
+
for (const file of files) {
|
|
122
|
+
if (file.endsWith(".md")) {
|
|
123
|
+
const name = file.replace(".md", "");
|
|
124
|
+
cached.push(`${repo}/${name}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return cached;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// ── Helpers ──────────────────────────────────────────
|
|
135
|
+
function skillCachePath(skillId) {
|
|
136
|
+
// "ecc/tdd-workflow" → ~/.superskill/cache/ecc/tdd-workflow.md
|
|
137
|
+
const [repo, ...rest] = skillId.split("/");
|
|
138
|
+
const name = rest.join("/");
|
|
139
|
+
return resolve(cacheDir, repo, `${name}.md`);
|
|
140
|
+
}
|
|
141
|
+
async function fetchSkillFromSource(source) {
|
|
142
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
143
|
+
const res = await fetch(source);
|
|
144
|
+
if (!res.ok)
|
|
145
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
146
|
+
return res.text();
|
|
147
|
+
}
|
|
148
|
+
return readFile(source, "utf-8");
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=skill-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-cache.js","sourceRoot":"","sources":["../../src/lib/skill-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-or-later OR Commercial
|
|
3
3
|
try {
|
|
4
4
|
const { detectClients } = await import("./detect.js");
|
|
5
|
+
const { prefetchCoreSkills, PREFETCH_SKILL_IDS } = await import("../lib/skill-cache.js");
|
|
5
6
|
if (!process.stdout.isTTY)
|
|
6
7
|
process.exit(0);
|
|
7
8
|
const detected = detectClients();
|
|
@@ -14,8 +15,28 @@ try {
|
|
|
14
15
|
console.log(" No AI clients detected.");
|
|
15
16
|
}
|
|
16
17
|
console.log(' Run "superskill-cli setup --all" to configure all 8 supported clients.\n');
|
|
18
|
+
// Prefetch core skills for offline availability
|
|
19
|
+
console.log(` Prefetching ${PREFETCH_SKILL_IDS.length} core skills...`);
|
|
20
|
+
const result = await prefetchCoreSkills({
|
|
21
|
+
onProgress: (skill, status) => {
|
|
22
|
+
const icon = status === "fetched" ? "+" : status === "cached" ? "=" : "x";
|
|
23
|
+
console.log(` [${icon}] ${skill}`);
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
const parts = [];
|
|
27
|
+
if (result.fetched > 0)
|
|
28
|
+
parts.push(`${result.fetched} fetched`);
|
|
29
|
+
if (result.cached > 0)
|
|
30
|
+
parts.push(`${result.cached} already cached`);
|
|
31
|
+
if (result.failed > 0)
|
|
32
|
+
parts.push(`${result.failed} failed`);
|
|
33
|
+
console.log(` Done: ${parts.join(", ")}.\n`);
|
|
34
|
+
if (result.failed > 0) {
|
|
35
|
+
console.log(" Some skills could not be prefetched. They will be fetched on first use.\n");
|
|
36
|
+
}
|
|
17
37
|
}
|
|
18
38
|
catch {
|
|
39
|
+
// Postinstall must never fail the install
|
|
19
40
|
process.exit(0);
|
|
20
41
|
}
|
|
21
42
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postinstall.js","sourceRoot":"","sources":["../../src/setup/postinstall.ts"],"names":[],"mappings":";AACA,2DAA2D;AAC3D,IAAI,CAAC;IACH,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"postinstall.js","sourceRoot":"","sources":["../../src/setup/postinstall.ts"],"names":[],"mappings":";AACA,2DAA2D;AAC3D,IAAI,CAAC;IACH,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IACtD,MAAM,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;IAEzF,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAE3C,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAC;IAEjC,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;IAE3C,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,eAAe,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5E,OAAO,CAAC,GAAG,CAAC,6EAA6E,CAAC,CAAC;IAC7F,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;IAC3C,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,4EAA4E,CAAC,CAAC;IAE1F,gDAAgD;IAChD,OAAO,CAAC,GAAG,CAAC,iBAAiB,kBAAkB,CAAC,MAAM,iBAAiB,CAAC,CAAC;IAEzE,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC;QACtC,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YAC5B,MAAM,IAAI,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;YAC1E,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC;QACxC,CAAC;KACF,CAAC,CAAC;IAEH,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,MAAM,CAAC,OAAO,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;IAChE,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,iBAAiB,CAAC,CAAC;IACrE,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,SAAS,CAAC,CAAC;IAC7D,OAAO,CAAC,GAAG,CAAC,WAAW,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAE9C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,6EAA6E,CAAC,CAAC;IAC7F,CAAC;AACH,CAAC;AAAC,MAAM,CAAC;IACP,0CAA0C;IAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC"}
|