@getkrafter/resume-toolkit 1.0.1

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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +155 -0
  3. package/dist/bin/cli.d.ts +3 -0
  4. package/dist/bin/cli.d.ts.map +1 -0
  5. package/dist/bin/cli.js +4 -0
  6. package/dist/bin/cli.js.map +1 -0
  7. package/dist/krafter/client.d.ts +54 -0
  8. package/dist/krafter/client.d.ts.map +1 -0
  9. package/dist/krafter/client.js +130 -0
  10. package/dist/krafter/client.js.map +1 -0
  11. package/dist/krafter/errors.d.ts +26 -0
  12. package/dist/krafter/errors.d.ts.map +1 -0
  13. package/dist/krafter/errors.js +45 -0
  14. package/dist/krafter/errors.js.map +1 -0
  15. package/dist/lib/ats-scorer.d.ts +12 -0
  16. package/dist/lib/ats-scorer.d.ts.map +1 -0
  17. package/dist/lib/ats-scorer.js +83 -0
  18. package/dist/lib/ats-scorer.js.map +1 -0
  19. package/dist/lib/index.d.ts +6 -0
  20. package/dist/lib/index.d.ts.map +1 -0
  21. package/dist/lib/index.js +8 -0
  22. package/dist/lib/index.js.map +1 -0
  23. package/dist/lib/resume-scorer.d.ts +62 -0
  24. package/dist/lib/resume-scorer.d.ts.map +1 -0
  25. package/dist/lib/resume-scorer.js +236 -0
  26. package/dist/lib/resume-scorer.js.map +1 -0
  27. package/dist/lib/resume-transformer.d.ts +13 -0
  28. package/dist/lib/resume-transformer.d.ts.map +1 -0
  29. package/dist/lib/resume-transformer.js +113 -0
  30. package/dist/lib/resume-transformer.js.map +1 -0
  31. package/dist/lib/text-utils.d.ts +57 -0
  32. package/dist/lib/text-utils.d.ts.map +1 -0
  33. package/dist/lib/text-utils.js +282 -0
  34. package/dist/lib/text-utils.js.map +1 -0
  35. package/dist/lib/types.d.ts +31 -0
  36. package/dist/lib/types.d.ts.map +1 -0
  37. package/dist/lib/types.js +2 -0
  38. package/dist/lib/types.js.map +1 -0
  39. package/dist/mcp/server.d.ts +31 -0
  40. package/dist/mcp/server.d.ts.map +1 -0
  41. package/dist/mcp/server.js +70 -0
  42. package/dist/mcp/server.js.map +1 -0
  43. package/dist/mcp/tools/krafter.d.ts +14 -0
  44. package/dist/mcp/tools/krafter.d.ts.map +1 -0
  45. package/dist/mcp/tools/krafter.js +228 -0
  46. package/dist/mcp/tools/krafter.js.map +1 -0
  47. package/dist/mcp/tools/scoring.d.ts +46 -0
  48. package/dist/mcp/tools/scoring.d.ts.map +1 -0
  49. package/dist/mcp/tools/scoring.js +135 -0
  50. package/dist/mcp/tools/scoring.js.map +1 -0
  51. package/package.json +67 -0
  52. package/skills/score/SKILL.md +185 -0
  53. package/skills/tailor/SKILL.md +211 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume-scorer.d.ts","sourceRoot":"","sources":["../../src/lib/resume-scorer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EAIX,QAAQ,EACT,MAAM,YAAY,CAAC;AAwDpB;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAK7D;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CASpD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAa3D;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAY9D;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAiBnE;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,UAAU,EAAE,UAAU,EACtB,MAAM,CAAC,EAAE,MAAM,GACd,WAAW,CAkHb"}
@@ -0,0 +1,236 @@
1
+ import { stem, tokenize, STOP_WORDS, VERB_TIERS } from './text-utils.js';
2
+ import { scoreATS } from './ats-scorer.js';
3
+ // ---------------------------------------------------------------------------
4
+ // Constants
5
+ // ---------------------------------------------------------------------------
6
+ /** Weights when a job description is provided. */
7
+ const WEIGHTS_WITH_JD = {
8
+ quantification: 0.25,
9
+ verbStrength: 0.2,
10
+ ats: 0.3,
11
+ bulletStructure: 0.15,
12
+ sectionCompleteness: 0.1,
13
+ };
14
+ /** Required resume sections, each worth 20 points. */
15
+ const REQUIRED_SECTIONS = ['experience', 'education', 'skills'];
16
+ /** Recommended resume sections, each worth ~13.33 points. */
17
+ const RECOMMENDED_SECTIONS = ['summary', 'projects', 'certifications'];
18
+ const REQUIRED_SECTION_POINTS = 20;
19
+ const RECOMMENDED_SECTION_POINTS = 100 / 3 - 13; // ~13.33
20
+ // More precise: 40 / 3 = 13.333...
21
+ const RECOMMENDED_POINTS = 40 / 3;
22
+ // ---------------------------------------------------------------------------
23
+ // Pre-computed stemmed verb tiers for fast lookup
24
+ // ---------------------------------------------------------------------------
25
+ /**
26
+ * Maps a stemmed verb to its tier number.
27
+ * Built once at module load time from VERB_TIERS.
28
+ */
29
+ const STEMMED_VERB_MAP = buildStemmedVerbMap();
30
+ function buildStemmedVerbMap() {
31
+ const map = new Map();
32
+ for (const verb of VERB_TIERS.tier1) {
33
+ map.set(stem(verb), 1);
34
+ }
35
+ for (const verb of VERB_TIERS.tier2) {
36
+ map.set(stem(verb), 2);
37
+ }
38
+ for (const verb of VERB_TIERS.tier3) {
39
+ map.set(stem(verb), 3);
40
+ }
41
+ return map;
42
+ }
43
+ // ---------------------------------------------------------------------------
44
+ // Sub-scorers
45
+ // ---------------------------------------------------------------------------
46
+ /**
47
+ * Fraction of bullets that contain at least one number or metric.
48
+ * Returns 0-100. Returns 0 for empty bullets array.
49
+ */
50
+ export function scoreQuantification(bullets) {
51
+ if (bullets.length === 0)
52
+ return 0;
53
+ const withNumbers = bullets.filter((b) => /\d+/.test(b)).length;
54
+ return (withNumbers / bullets.length) * 100;
55
+ }
56
+ /**
57
+ * Determine the verb tier for a bullet based on its first non-stop-word token.
58
+ *
59
+ * Tokenizes the bullet, skips stop words, takes the first meaningful token,
60
+ * stems it, and looks it up in the pre-computed stemmed verb map.
61
+ *
62
+ * Returns 1 (strong), 2 (solid), 3 (weak), or null (unrecognized).
63
+ */
64
+ export function getVerbTier(bullet) {
65
+ const tokens = tokenize(bullet);
66
+ // Find the first non-stop-word token
67
+ const firstMeaningful = tokens.find((t) => !STOP_WORDS.has(t));
68
+ if (!firstMeaningful)
69
+ return null;
70
+ const stemmed = stem(firstMeaningful);
71
+ return STEMMED_VERB_MAP.get(stemmed) ?? null;
72
+ }
73
+ /**
74
+ * Average verb tier quality across bullets.
75
+ * Scoring: tier1=100, tier2=60, tier3=20, null=40.
76
+ * Returns 0-100. Returns 0 for empty bullets array.
77
+ */
78
+ export function scoreVerbStrength(bullets) {
79
+ if (bullets.length === 0)
80
+ return 0;
81
+ const tierScores = { 1: 100, 2: 60, 3: 20 };
82
+ const unrecognizedScore = 40;
83
+ let total = 0;
84
+ for (const bullet of bullets) {
85
+ const tier = getVerbTier(bullet);
86
+ total += tier !== null ? tierScores[tier] : unrecognizedScore;
87
+ }
88
+ return total / bullets.length;
89
+ }
90
+ /**
91
+ * Fraction of "strong" bullets. A bullet is strong if it:
92
+ * - Starts with a recognized verb (tier 1, 2, or 3)
93
+ * - Contains a number (`/\d+/`)
94
+ * - Has 8+ words
95
+ *
96
+ * Returns 0-100. Returns 0 for empty bullets array.
97
+ */
98
+ export function scoreBulletStructure(bullets) {
99
+ if (bullets.length === 0)
100
+ return 0;
101
+ const strong = bullets.filter((bullet) => {
102
+ const tier = getVerbTier(bullet);
103
+ const hasVerb = tier !== null;
104
+ const hasNumber = /\d+/.test(bullet);
105
+ const wordCount = tokenize(bullet).length;
106
+ return hasVerb && hasNumber && wordCount >= 8;
107
+ }).length;
108
+ return (strong / bullets.length) * 100;
109
+ }
110
+ /**
111
+ * Presence of expected resume sections.
112
+ *
113
+ * Required sections ("experience", "education", "skills") are worth 20 points each.
114
+ * Recommended sections ("summary", "projects", "certifications") are worth ~13.33 points each.
115
+ * Match by substring: a section name includes the keyword.
116
+ * Capped at 100.
117
+ */
118
+ export function scoreSectionCompleteness(sections) {
119
+ const lowerSections = sections.map((s) => s.toLowerCase());
120
+ let score = 0;
121
+ for (const required of REQUIRED_SECTIONS) {
122
+ if (lowerSections.some((s) => s.includes(required))) {
123
+ score += REQUIRED_SECTION_POINTS;
124
+ }
125
+ }
126
+ for (const recommended of RECOMMENDED_SECTIONS) {
127
+ if (lowerSections.some((s) => s.includes(recommended))) {
128
+ score += RECOMMENDED_POINTS;
129
+ }
130
+ }
131
+ return Math.min(Math.round(score * 100) / 100, 100);
132
+ }
133
+ // ---------------------------------------------------------------------------
134
+ // Main scorer
135
+ // ---------------------------------------------------------------------------
136
+ /**
137
+ * Score a resume across five dimensions: quantification, verb strength,
138
+ * ATS keyword match, bullet structure, and section completeness.
139
+ *
140
+ * When a job description is provided, the ATS dimension is active and
141
+ * carries 30% weight. Without a JD, the ATS weight is redistributed
142
+ * proportionally across the other four dimensions.
143
+ *
144
+ * @param resumeData - Parsed resume data (raw text, bullets, sections)
145
+ * @param jdText - Optional job description text for ATS matching
146
+ * @returns A `ResumeScore` with total (0-100), mode, breakdown, ATS result, and flags.
147
+ */
148
+ export function scoreResume(resumeData, jdText) {
149
+ const { rawText, bullets, sections } = resumeData;
150
+ const flags = [];
151
+ // --- Determine mode ---
152
+ const hasJd = Boolean(jdText);
153
+ const mode = hasJd ? 'with-jd' : 'without-jd';
154
+ if (!hasJd) {
155
+ flags.push('No job description provided — ATS score excluded, weights redistributed.');
156
+ }
157
+ // --- Check for empty / short resume ---
158
+ if (!rawText || rawText.trim().length === 0) {
159
+ flags.push('Resume appears empty');
160
+ }
161
+ else {
162
+ const wordCount = tokenize(rawText).length;
163
+ if (wordCount < 50) {
164
+ flags.push('Resume is unusually short');
165
+ }
166
+ }
167
+ // --- Compute raw scores ---
168
+ const quantScore = scoreQuantification(bullets);
169
+ const verbScore = scoreVerbStrength(bullets);
170
+ const structScore = scoreBulletStructure(bullets);
171
+ const sectionScore = scoreSectionCompleteness(sections);
172
+ const atsResult = hasJd
173
+ ? scoreATS(rawText, jdText)
174
+ : null;
175
+ const atsScore = atsResult?.score ?? 0;
176
+ // --- Build weights ---
177
+ const weights = { ...WEIGHTS_WITH_JD };
178
+ if (!hasJd) {
179
+ // Redistribute ATS weight proportionally across the other 4 dimensions
180
+ const nonAtsTotal = 1 - weights['ats'];
181
+ weights['ats'] = 0;
182
+ for (const key of Object.keys(weights)) {
183
+ if (key !== 'ats') {
184
+ weights[key] = WEIGHTS_WITH_JD[key] / nonAtsTotal;
185
+ }
186
+ }
187
+ }
188
+ // --- Build breakdown ---
189
+ const scores = {
190
+ quantification: quantScore,
191
+ verbStrength: verbScore,
192
+ ats: atsScore,
193
+ bulletStructure: structScore,
194
+ sectionCompleteness: sectionScore,
195
+ };
196
+ const breakdown = {};
197
+ let total = 0;
198
+ for (const key of Object.keys(weights)) {
199
+ const score = scores[key];
200
+ const weight = weights[key];
201
+ const weightedScore = Math.round(score * weight * 100) / 100;
202
+ breakdown[key] = { score, weight, weightedScore };
203
+ total += weightedScore;
204
+ }
205
+ // Round total to reasonable precision
206
+ total = Math.round(total * 100) / 100;
207
+ // --- Generate diagnostic flags ---
208
+ if (quantScore < 40) {
209
+ flags.push('Fewer than 40% of bullets contain measurable results. Add numbers, percentages, or metrics.');
210
+ }
211
+ if (verbScore < 40) {
212
+ flags.push('Action verb quality is low. Replace weak/passive openers with strong action verbs.');
213
+ }
214
+ if (structScore < 40) {
215
+ flags.push('Most bullets lack the verb -> action -> outcome structure.');
216
+ }
217
+ // Check for summary/profile section
218
+ const lowerSections = sections.map((s) => s.toLowerCase());
219
+ const hasSummaryOrProfile = lowerSections.some((s) => s.includes('summary') || s.includes('profile'));
220
+ if (!hasSummaryOrProfile) {
221
+ flags.push('No summary/profile section found.');
222
+ }
223
+ // Flag low ATS score with top 5 missing terms
224
+ if (atsResult && atsResult.score < 50) {
225
+ const top5missing = atsResult.missing.slice(0, 5);
226
+ flags.push(`Low ATS match (${atsResult.score}%). Key missing terms: ${top5missing.join(', ')}`);
227
+ }
228
+ return {
229
+ total,
230
+ mode,
231
+ breakdown,
232
+ ats: atsResult,
233
+ flags,
234
+ };
235
+ }
236
+ //# sourceMappingURL=resume-scorer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume-scorer.js","sourceRoot":"","sources":["../../src/lib/resume-scorer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { ResumeData } from './types.js';
2
+ /**
3
+ * Converts a structured Krafter resume object into the flat `ResumeData` format
4
+ * used for scoring, ATS matching, and other analysis.
5
+ *
6
+ * The function is fully defensive -- it never throws on malformed input.
7
+ * Missing or unexpected fields are silently skipped.
8
+ *
9
+ * @param resume - A Krafter resume object (the shape returned by `getResume()`)
10
+ * @returns A flattened `ResumeData` with `rawText`, `bullets`, and `sections`
11
+ */
12
+ export declare function toResumeData(resume: Record<string, unknown>): ResumeData;
13
+ //# sourceMappingURL=resume-transformer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume-transformer.d.ts","sourceRoot":"","sources":["../../src/lib/resume-transformer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAwC7C;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU,CA0ExE"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Strips all HTML tags from a string, collapses whitespace, and trims.
3
+ */
4
+ function stripHtml(html) {
5
+ return html
6
+ .replace(/<[^>]*>/g, ' ')
7
+ .replace(/\s+/g, ' ')
8
+ .trim();
9
+ }
10
+ /**
11
+ * Extracts the text content of each `<li>` element from an HTML string.
12
+ * Inner HTML tags are stripped from the extracted content.
13
+ */
14
+ function extractBullets(html) {
15
+ const bullets = [];
16
+ const liRegex = /<li[^>]*>([\s\S]*?)<\/li>/gi;
17
+ let match;
18
+ while ((match = liRegex.exec(html)) !== null) {
19
+ const cleaned = stripHtml(match[1]);
20
+ if (cleaned) {
21
+ bullets.push(cleaned);
22
+ }
23
+ }
24
+ return bullets;
25
+ }
26
+ /**
27
+ * Safely reads a string property from an unknown object.
28
+ * Returns an empty string if the property is missing or not a string.
29
+ */
30
+ function getString(obj, key) {
31
+ const value = obj[key];
32
+ return typeof value === 'string' ? value : '';
33
+ }
34
+ /**
35
+ * Converts a structured Krafter resume object into the flat `ResumeData` format
36
+ * used for scoring, ATS matching, and other analysis.
37
+ *
38
+ * The function is fully defensive -- it never throws on malformed input.
39
+ * Missing or unexpected fields are silently skipped.
40
+ *
41
+ * @param resume - A Krafter resume object (the shape returned by `getResume()`)
42
+ * @returns A flattened `ResumeData` with `rawText`, `bullets`, and `sections`
43
+ */
44
+ export function toResumeData(resume) {
45
+ const textParts = [];
46
+ const allBullets = [];
47
+ const sectionNames = [];
48
+ // --- Personal fields ---
49
+ const personalFields = ['firstName', 'lastName', 'jobTitle', 'professionalSummary'];
50
+ for (const field of personalFields) {
51
+ const value = getString(resume, field);
52
+ if (value) {
53
+ textParts.push(stripHtml(value));
54
+ }
55
+ }
56
+ // --- Sections ---
57
+ const sections = resume['sections'];
58
+ if (Array.isArray(sections)) {
59
+ for (const section of sections) {
60
+ if (section == null || typeof section !== 'object')
61
+ continue;
62
+ const sec = section;
63
+ // Section name
64
+ const sectionName = getString(sec, 'name');
65
+ if (sectionName) {
66
+ sectionNames.push(sectionName.toLowerCase());
67
+ textParts.push(sectionName);
68
+ }
69
+ // Items within the section
70
+ const items = sec['items'];
71
+ if (!Array.isArray(items))
72
+ continue;
73
+ for (const item of items) {
74
+ if (item == null || typeof item !== 'object')
75
+ continue;
76
+ const it = item;
77
+ // Structured fields: title, subtitle, dates
78
+ const title = getString(it, 'title');
79
+ const subtitle = getString(it, 'subtitle');
80
+ const startDate = getString(it, 'startDate');
81
+ const endDate = getString(it, 'endDate');
82
+ if (title)
83
+ textParts.push(title);
84
+ if (subtitle)
85
+ textParts.push(subtitle);
86
+ if (startDate)
87
+ textParts.push(startDate);
88
+ if (endDate)
89
+ textParts.push(endDate);
90
+ // Skill-style items (name only)
91
+ const itemName = getString(it, 'name');
92
+ if (itemName)
93
+ textParts.push(itemName);
94
+ // Description (HTML with potential bullet points)
95
+ const description = getString(it, 'description');
96
+ if (description) {
97
+ // Extract bullets from <li> elements
98
+ const bullets = extractBullets(description);
99
+ allBullets.push(...bullets);
100
+ // Add stripped description to rawText
101
+ textParts.push(stripHtml(description));
102
+ }
103
+ }
104
+ }
105
+ }
106
+ const rawText = textParts.filter(Boolean).join(' ').replace(/\s+/g, ' ').trim();
107
+ return {
108
+ rawText,
109
+ bullets: allBullets,
110
+ sections: sectionNames,
111
+ };
112
+ }
113
+ //# sourceMappingURL=resume-transformer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume-transformer.js","sourceRoot":"","sources":["../../src/lib/resume-transformer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Comprehensive English stop words (~179 words) that carry little semantic
3
+ * meaning and should be filtered out during term extraction.
4
+ */
5
+ export declare const STOP_WORDS: Set<string>;
6
+ /**
7
+ * Resume-domain synonym map. Multi-word phrases and framework names are
8
+ * normalised to their canonical short forms. Keys are sorted by length
9
+ * descending at module load time so that longer phrases match first
10
+ * (e.g. "amazon web services" before "amazon").
11
+ */
12
+ export declare const SYNONYMS: Record<string, string>;
13
+ /**
14
+ * Action verb tiers for resume bullet quality assessment.
15
+ *
16
+ * - tier1: strong leadership / impact verbs
17
+ * - tier2: solid, active verbs
18
+ * - tier3: weak, passive, or vague verbs
19
+ */
20
+ export declare const VERB_TIERS: {
21
+ tier1: string[];
22
+ tier2: string[];
23
+ tier3: string[];
24
+ };
25
+ /**
26
+ * Stem a single word using the Porter Stemmer, with overrides for known
27
+ * problem words. Input is lowercased before stemming.
28
+ */
29
+ export declare function stem(word: string): string;
30
+ /**
31
+ * Tokenize text into an array of lowercase word tokens.
32
+ * Returns an empty array for empty / whitespace-only input.
33
+ */
34
+ export declare function tokenize(text: string): string[];
35
+ /**
36
+ * Normalise text for matching: lowercase, trim, collapse whitespace,
37
+ * and apply synonym replacement. Synonyms are applied as whole-word
38
+ * replacements on the lowercased string before any tokenization.
39
+ *
40
+ * Multi-word synonyms are matched via simple `includes` + `replaceAll`,
41
+ * while single-word synonyms use regex word-boundary matching. Keys are
42
+ * processed longest-first to prevent partial matches.
43
+ */
44
+ export declare function normalise(text: string): string;
45
+ /**
46
+ * Extract a set of search terms from text. The pipeline:
47
+ *
48
+ * 1. Normalise (lowercase + synonyms)
49
+ * 2. Tokenize
50
+ * 3. Filter out stop words
51
+ * 4. Build stemmed unigrams from the filtered list
52
+ * 5. Build raw bigrams from adjacent pairs in the filtered list
53
+ *
54
+ * Returns a Set containing both stemmed unigrams and raw bigrams.
55
+ */
56
+ export declare function extractTerms(text: string): Set<string>;
57
+ //# sourceMappingURL=text-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-utils.d.ts","sourceRoot":"","sources":["../../src/lib/text-utils.ts"],"names":[],"mappings":"AAgBA;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,GAAG,CAAC,MAAM,CAkDjC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA4B3C,CAAC;AAUF;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,EAAE;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CA+C3E,CAAC;AAuCF;;;GAGG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAezC;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAI/C;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAuB9C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAoBtD"}