bun-sticky 1.0.3 → 1.0.5

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/index.ts DELETED
@@ -1,349 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * 🥐 Bun Sticky - Fastest bun under the sum.
4
- *
5
- * Built the Anthropic way:
6
- * - First principles
7
- * - Zero dependencies
8
- * - Native Bun APIs
9
- * - TypeScript native
10
- *
11
- * Wolfejam slot-based scoring (NOT Elon weights).
12
- * For Claude Codesters.
13
- */
14
-
15
- import { parseYaml, getNestedValue } from "./lib/parser.ts";
16
- import { calculateScore, FafScore } from "./lib/scorer.ts";
17
- import { getTier } from "./lib/tier.ts";
18
-
19
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
20
- // CONSTANTS
21
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
22
-
23
- const VERSION = "1.0.3";
24
-
25
- // Standard colors only (B/W version - color reserved for ZIG poster child)
26
- const GREEN = "\x1b[32m";
27
- const YELLOW = "\x1b[33m";
28
- const RED = "\x1b[31m";
29
- const BOLD = "\x1b[1m";
30
- const DIM = "\x1b[2m";
31
- const RESET = "\x1b[0m";
32
-
33
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
34
- // ASCII ART BANNER
35
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
36
-
37
- const BANNER = `
38
- ────────────────────────────────────────────────
39
-
40
- ▄▄ ▄▀▀▀ ▀█▀ █ ▄▀▀ █▄▀ █ █
41
- ████ ▀▀█▄ █ █ █ █▀▄ █
42
- ██████ ▄▄▄▀ █ █ ▀▀▀ █ █ █
43
- ████████
44
- ████████ █▀▄ █ █ █▀▄
45
- ██████ ██▀ █ █ █ █
46
- ████ █▄▀ ▀▄▀ █ █
47
- ▀▀
48
-
49
- 🥐 Bun Sticky v${VERSION} .faf CLI
50
- Fastest bun under the sum.
51
-
52
- ────────────────────────────────────────────────
53
- `;
54
-
55
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
56
- // COMMANDS
57
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
58
-
59
- async function cmdScore(): Promise<void> {
60
- const file = Bun.file("project.faf");
61
-
62
- if (!(await file.exists())) {
63
- console.log(`${RED}No project.faf found${RESET}`);
64
- console.log(`${DIM}Run: bun-sticky init <name>${RESET}`);
65
- process.exit(1);
66
- }
67
-
68
- const content = await file.text();
69
- const faf = parseYaml(content);
70
- const result = calculateScore(faf);
71
- const tier = getTier(result.score);
72
-
73
- console.log(BANNER);
74
-
75
- // Project name & type
76
- const name = (getNestedValue(faf, "project.name") as string) || "Unknown";
77
- console.log(` Project: ${BOLD}${name}${RESET}`);
78
- console.log(` Type: ${DIM}${result.projectType}${RESET}`);
79
- console.log();
80
-
81
- // Section breakdown (only show applicable sections)
82
- const { sections } = result;
83
-
84
- if (sections.project.total > 0) {
85
- console.log(` ${DIM}Project${RESET} ${formatBar(sections.project.percentage)} ${sections.project.filled}/${sections.project.total}`);
86
- }
87
- if (sections.frontend.total > 0) {
88
- console.log(` ${DIM}Frontend${RESET} ${formatBar(sections.frontend.percentage)} ${sections.frontend.filled}/${sections.frontend.total}`);
89
- }
90
- if (sections.backend.total > 0) {
91
- console.log(` ${DIM}Backend${RESET} ${formatBar(sections.backend.percentage)} ${sections.backend.filled}/${sections.backend.total}`);
92
- }
93
- if (sections.universal.total > 0) {
94
- console.log(` ${DIM}Universal${RESET} ${formatBar(sections.universal.percentage)} ${sections.universal.filled}/${sections.universal.total}`);
95
- }
96
- if (sections.human.total > 0) {
97
- console.log(` ${DIM}Human${RESET} ${formatBar(sections.human.percentage)} ${sections.human.filled}/${sections.human.total}`);
98
- }
99
- console.log();
100
-
101
- // Total
102
- console.log(` ${tier.color}${tier.emoji} ${BOLD}${result.score}%${RESET} ${tier.color}${tier.name}${RESET}`);
103
- console.log(` ${DIM}Filled: ${result.filled}/${result.total} slots${RESET}`);
104
- console.log();
105
-
106
- // Show missing slots with copy-paste YAML
107
- if (result.missing.length > 0) {
108
- console.log(` ${YELLOW}Add to project.faf:${RESET}`);
109
- console.log();
110
-
111
- // Group by section
112
- const projectMissing = result.missing.filter(s => s.startsWith("project."));
113
- const humanMissing = result.missing.filter(s => s.startsWith("human_context."));
114
- const stackMissing = result.missing.filter(s => s.startsWith("stack."));
115
-
116
- if (projectMissing.length > 0) {
117
- console.log(` ${DIM}project:${RESET}`);
118
- for (const slot of projectMissing) {
119
- const field = slot.replace("project.", "");
120
- console.log(` ${DIM}${field}:${RESET} "${getHint(field)}"`);
121
- }
122
- }
123
-
124
- if (stackMissing.length > 0) {
125
- console.log(` ${DIM}stack:${RESET}`);
126
- for (const slot of stackMissing) {
127
- const field = slot.replace("stack.", "");
128
- console.log(` ${DIM}${field}:${RESET} "${getHint(field)}"`);
129
- }
130
- }
131
-
132
- if (humanMissing.length > 0) {
133
- console.log(` ${DIM}human_context:${RESET}`);
134
- for (const slot of humanMissing) {
135
- const field = slot.replace("human_context.", "");
136
- console.log(` ${DIM}${field}:${RESET} "${getHint(field)}"`);
137
- }
138
- }
139
- console.log();
140
- }
141
- }
142
-
143
- function getHint(field: string): string {
144
- const hints: Record<string, string> = {
145
- // Project
146
- name: "Project name",
147
- goal: "What problem does this solve?",
148
- main_language: "TypeScript",
149
- // Human context - questions that make you think
150
- who: "Who is it for?",
151
- what: "What does it do?",
152
- why: "Why does it exist?",
153
- where: "Where is it deployed/used?",
154
- when: "When is it due/released?",
155
- how: "How is it built?",
156
- // Stack
157
- frontend: "React",
158
- css_framework: "Tailwind",
159
- ui_library: "shadcn",
160
- state_management: "zustand",
161
- backend: "Node.js",
162
- api_type: "REST",
163
- runtime: "Bun",
164
- database: "PostgreSQL",
165
- connection: "prisma",
166
- hosting: "Vercel",
167
- build: "vite",
168
- cicd: "GitHub Actions",
169
- };
170
- return hints[field] || "";
171
- }
172
-
173
- function formatBar(percent: number): string {
174
- const width = 12;
175
- const filled = Math.round((percent / 100) * width);
176
- const empty = width - filled;
177
- const bar = "█".repeat(filled) + "░".repeat(empty);
178
-
179
- if (percent >= 85) return `${GREEN}${bar}${RESET}`;
180
- if (percent >= 70) return `${GREEN}${bar}${RESET}`;
181
- if (percent >= 55) return `${YELLOW}${bar}${RESET}`;
182
- return `${RED}${bar}${RESET}`;
183
- }
184
-
185
- async function cmdInit(name: string): Promise<void> {
186
- const file = Bun.file("project.faf");
187
-
188
- if (await file.exists()) {
189
- console.log(`${YELLOW}project.faf already exists${RESET}`);
190
- process.exit(1);
191
- }
192
-
193
- const template = `# ${name} - Project DNA
194
- # Generated by Bun Sticky
195
-
196
- faf_version: 2.5.0
197
-
198
- project:
199
- name: ${name}
200
- goal: Define your project goal here
201
- main_language: TypeScript
202
- type: cli
203
- version: 0.1.0
204
-
205
- human_context:
206
- who: Your target users
207
- what: What this project does
208
- why: Why it exists
209
- where: Where it runs
210
- when: When to use it
211
- how: How to get started
212
-
213
- stack:
214
- runtime: Bun
215
- build: bun build
216
- `;
217
-
218
- await Bun.write("project.faf", template);
219
- console.log(BANNER);
220
- console.log(` ${GREEN}Created${RESET} project.faf`);
221
- console.log(` ${DIM}Run: bun-sticky score${RESET}`);
222
- console.log();
223
- }
224
-
225
- async function cmdSync(): Promise<void> {
226
- const fafFile = Bun.file("project.faf");
227
-
228
- if (!(await fafFile.exists())) {
229
- console.log(`${RED}No project.faf found${RESET}`);
230
- process.exit(1);
231
- }
232
-
233
- const content = await fafFile.text();
234
- const faf = parseYaml(content);
235
- const name = (getNestedValue(faf, "project.name") as string) || "Project";
236
- const goal = (getNestedValue(faf, "project.goal") as string) || "";
237
- const result = calculateScore(faf);
238
- const tier = getTier(result.score);
239
-
240
- const scoreBadge = `**${tier.emoji} ${result.score}% ${tier.name}** - ${result.filled}/${result.total} slots filled`;
241
- const claudeFile = Bun.file("CLAUDE.md");
242
-
243
- if (await claudeFile.exists()) {
244
- // Update existing CLAUDE.md - preserve content, update/insert score badge
245
- let existing = await claudeFile.text();
246
- const badgePattern = /^\*\*[🏆🥇🥈🥉🟢🟡🔴⚪🍊]\s*\d+%.*\*\*.*slots filled$/mu;
247
-
248
- if (badgePattern.test(existing)) {
249
- // Replace existing badge
250
- existing = existing.replace(badgePattern, scoreBadge);
251
- } else {
252
- // Insert badge after first paragraph (after title + description)
253
- const lines = existing.split("\n");
254
- let insertIndex = 1;
255
-
256
- // Find first empty line after title
257
- for (let i = 1; i < lines.length; i++) {
258
- if (lines[i].trim() === "") {
259
- insertIndex = i + 1;
260
- // Skip consecutive empty lines
261
- while (insertIndex < lines.length && lines[insertIndex].trim() === "") {
262
- insertIndex++;
263
- }
264
- break;
265
- }
266
- }
267
-
268
- // Check if next line is already a heading, insert before it
269
- if (lines[insertIndex]?.startsWith("#")) {
270
- lines.splice(insertIndex, 0, scoreBadge, "");
271
- } else {
272
- lines.splice(insertIndex, 0, "", scoreBadge);
273
- }
274
- existing = lines.join("\n");
275
- }
276
-
277
- await Bun.write("CLAUDE.md", existing);
278
- } else {
279
- // Create new minimal CLAUDE.md
280
- const claudeMd = `# ${name}
281
-
282
- ${goal}
283
-
284
- ${scoreBadge}
285
-
286
- ---
287
- *Synced by Bun Sticky*
288
- `;
289
- await Bun.write("CLAUDE.md", claudeMd);
290
- }
291
-
292
- console.log(BANNER);
293
- console.log(` ${GREEN}Synced${RESET} project.faf → CLAUDE.md`);
294
- console.log();
295
- }
296
-
297
- function cmdHelp(): void {
298
- console.log(BANNER);
299
- console.log(` ${BOLD}Commands${RESET}`);
300
- console.log();
301
- console.log(` score Show FAF score + tier`);
302
- console.log(` init <n> Create project.faf`);
303
- console.log(` sync Sync to CLAUDE.md`);
304
- console.log(` version Show version`);
305
- console.log(` help Show this help`);
306
- console.log();
307
- console.log(` ${DIM}Zero dependencies. Pure Bun.${RESET}`);
308
- console.log(` ${DIM}Wolfejam slot-based scoring.${RESET}`);
309
- console.log();
310
- }
311
-
312
- function cmdVersion(): void {
313
- console.log(`bun-sticky v${VERSION}`);
314
- }
315
-
316
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
317
- // MAIN
318
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
319
-
320
- const args = process.argv.slice(2);
321
- const cmd = args[0] || "help";
322
-
323
- switch (cmd) {
324
- case "score":
325
- await cmdScore();
326
- break;
327
- case "init":
328
- const name = args[1];
329
- if (!name) {
330
- console.log(`${RED}Usage: bun-sticky init <name>${RESET}`);
331
- process.exit(1);
332
- }
333
- await cmdInit(name);
334
- break;
335
- case "sync":
336
- await cmdSync();
337
- break;
338
- case "version":
339
- case "-v":
340
- case "--version":
341
- cmdVersion();
342
- break;
343
- case "help":
344
- case "-h":
345
- case "--help":
346
- default:
347
- cmdHelp();
348
- break;
349
- }
package/lib/parser.ts DELETED
@@ -1,75 +0,0 @@
1
- /**
2
- * Minimal YAML Parser for .faf files
3
- *
4
- * Zero dependencies. Pure Bun.
5
- * Handles: scalars, nested objects, arrays
6
- */
7
-
8
- export function parseYaml(content: string): Record<string, unknown> {
9
- const result: Record<string, unknown> = {};
10
- const lines = content.split("\n");
11
- const stack: { indent: number; obj: Record<string, unknown> }[] = [
12
- { indent: -1, obj: result },
13
- ];
14
-
15
- for (const line of lines) {
16
- // Skip comments and empty lines
17
- if (line.trim().startsWith("#") || line.trim() === "") continue;
18
-
19
- const indent = line.search(/\S/);
20
- const trimmed = line.trim();
21
-
22
- // Handle key: value pairs
23
- const colonIndex = trimmed.indexOf(":");
24
- if (colonIndex === -1) continue;
25
-
26
- const key = trimmed.slice(0, colonIndex).trim();
27
- const value = trimmed.slice(colonIndex + 1).trim();
28
-
29
- // Pop stack to find parent
30
- while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
31
- stack.pop();
32
- }
33
-
34
- const parent = stack[stack.length - 1].obj;
35
-
36
- if (value === "" || value === "|" || value === ">") {
37
- // Nested object or multiline
38
- const newObj: Record<string, unknown> = {};
39
- parent[key] = newObj;
40
- stack.push({ indent, obj: newObj });
41
- } else if (value.startsWith("[") && value.endsWith("]")) {
42
- // Inline array
43
- parent[key] = value
44
- .slice(1, -1)
45
- .split(",")
46
- .map((s) => s.trim().replace(/^["']|["']$/g, ""));
47
- } else if (value.startsWith("- ")) {
48
- // Array item (simple case)
49
- parent[key] = [value.slice(2).trim()];
50
- } else {
51
- // Scalar value
52
- parent[key] = value.replace(/^["']|["']$/g, "");
53
- }
54
- }
55
-
56
- return result;
57
- }
58
-
59
- export function getNestedValue(obj: Record<string, unknown>, path: string): unknown {
60
- const parts = path.split(".");
61
- let current: unknown = obj;
62
- for (const part of parts) {
63
- if (current === null || current === undefined) return undefined;
64
- if (typeof current !== "object") return undefined;
65
- current = (current as Record<string, unknown>)[part];
66
- }
67
- return current;
68
- }
69
-
70
- export function hasValue(obj: Record<string, unknown>, path: string): boolean {
71
- const value = getNestedValue(obj, path);
72
- if (value === undefined || value === null) return false;
73
- if (typeof value === "string" && value.trim() === "") return false;
74
- return true;
75
- }
package/lib/scorer.ts DELETED
@@ -1,237 +0,0 @@
1
- /**
2
- * 🥐 Bun Sticky Scorer - Wolfejam Slot-Based Scoring
3
- *
4
- * Score = (Filled slots / Applicable slots) × 100
5
- *
6
- * 21 total slots, type-aware scoring.
7
- * Zero dependencies. Pure Bun.
8
- */
9
-
10
- import { hasValue, getNestedValue } from "./parser.ts";
11
-
12
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
13
- // SLOT DEFINITIONS - 21 Slots Total
14
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
15
-
16
- export const SLOTS = {
17
- // Project slots (3)
18
- project: [
19
- "project.name",
20
- "project.goal",
21
- "project.main_language",
22
- ],
23
- // Frontend slots (4)
24
- frontend: [
25
- "stack.frontend",
26
- "stack.css_framework",
27
- "stack.ui_library",
28
- "stack.state_management",
29
- ],
30
- // Backend slots (5)
31
- backend: [
32
- "stack.backend",
33
- "stack.api_type",
34
- "stack.runtime",
35
- "stack.database",
36
- "stack.connection",
37
- ],
38
- // Universal slots (3)
39
- universal: [
40
- "stack.hosting",
41
- "stack.build",
42
- "stack.cicd",
43
- ],
44
- // Human context slots (6)
45
- human: [
46
- "human_context.who",
47
- "human_context.what",
48
- "human_context.why",
49
- "human_context.where",
50
- "human_context.when",
51
- "human_context.how",
52
- ],
53
- } as const;
54
-
55
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
56
- // TYPE DEFINITIONS - Which slots apply to each type
57
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
58
-
59
- export type ProjectType =
60
- | "cli"
61
- | "library"
62
- | "api"
63
- | "webapp"
64
- | "fullstack"
65
- | "mobile"
66
- | "unknown";
67
-
68
- export const TYPE_CATEGORIES: Record<ProjectType, (keyof typeof SLOTS)[]> = {
69
- // CLI/Tool: 9 slots (project + human)
70
- cli: ["project", "human"],
71
-
72
- // Library/Package: 9 slots (project + human)
73
- library: ["project", "human"],
74
-
75
- // API/Backend: 17 slots (project + backend + universal + human)
76
- api: ["project", "backend", "universal", "human"],
77
-
78
- // Web App: 16 slots (project + frontend + universal + human)
79
- webapp: ["project", "frontend", "universal", "human"],
80
-
81
- // Fullstack: 21 slots (all)
82
- fullstack: ["project", "frontend", "backend", "universal", "human"],
83
-
84
- // Mobile: 9 slots (project + human) - simplified
85
- mobile: ["project", "human"],
86
-
87
- // Unknown: 9 slots (project + human) - safe default
88
- unknown: ["project", "human"],
89
- };
90
-
91
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
92
- // SCORE INTERFACE
93
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
94
-
95
- export interface SlotSection {
96
- filled: number;
97
- total: number;
98
- percentage: number;
99
- }
100
-
101
- export interface FafScore {
102
- projectType: ProjectType;
103
- sections: {
104
- project: SlotSection;
105
- frontend: SlotSection;
106
- backend: SlotSection;
107
- universal: SlotSection;
108
- human: SlotSection;
109
- };
110
- filled: number;
111
- total: number;
112
- score: number;
113
- missing: string[];
114
- }
115
-
116
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
117
- // SCORING FUNCTIONS
118
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
119
-
120
- /**
121
- * Detect project type from .faf content
122
- */
123
- export function detectProjectType(faf: Record<string, unknown>): ProjectType {
124
- const type = getNestedValue(faf, "project.type") as string;
125
-
126
- if (type) {
127
- const typeLower = type.toLowerCase();
128
- if (typeLower.includes("cli")) return "cli";
129
- if (typeLower.includes("lib") || typeLower.includes("package")) return "library";
130
- if (typeLower.includes("api") || typeLower.includes("backend")) return "api";
131
- if (typeLower.includes("web") || typeLower.includes("frontend")) return "webapp";
132
- if (typeLower.includes("full")) return "fullstack";
133
- if (typeLower.includes("mobile") || typeLower.includes("app")) return "mobile";
134
- }
135
-
136
- // Infer from stack
137
- const hasFrontend = hasValue(faf, "stack.frontend");
138
- const hasBackend = hasValue(faf, "stack.backend") || hasValue(faf, "stack.database");
139
-
140
- if (hasFrontend && hasBackend) return "fullstack";
141
- if (hasFrontend) return "webapp";
142
- if (hasBackend) return "api";
143
-
144
- return "unknown";
145
- }
146
-
147
- /**
148
- * Count filled slots in a section
149
- */
150
- function countSection(
151
- faf: Record<string, unknown>,
152
- slots: readonly string[],
153
- applies: boolean
154
- ): SlotSection {
155
- if (!applies) {
156
- return { filled: 0, total: 0, percentage: 0 };
157
- }
158
-
159
- let filled = 0;
160
- for (const slot of slots) {
161
- if (hasValue(faf, slot)) filled++;
162
- }
163
-
164
- const total = slots.length;
165
- const percentage = total > 0 ? Math.round((filled / total) * 100) : 0;
166
-
167
- return { filled, total, percentage };
168
- }
169
-
170
- /**
171
- * Calculate score using wolfejam slot-based system
172
- * Score = (Filled slots / Applicable slots) × 100
173
- */
174
- export function calculateScore(faf: Record<string, unknown>): FafScore {
175
- const projectType = detectProjectType(faf);
176
- const applicableCategories = TYPE_CATEGORIES[projectType];
177
-
178
- // Count each section
179
- const sections = {
180
- project: countSection(
181
- faf,
182
- SLOTS.project,
183
- applicableCategories.includes("project")
184
- ),
185
- frontend: countSection(
186
- faf,
187
- SLOTS.frontend,
188
- applicableCategories.includes("frontend")
189
- ),
190
- backend: countSection(
191
- faf,
192
- SLOTS.backend,
193
- applicableCategories.includes("backend")
194
- ),
195
- universal: countSection(
196
- faf,
197
- SLOTS.universal,
198
- applicableCategories.includes("universal")
199
- ),
200
- human: countSection(
201
- faf,
202
- SLOTS.human,
203
- applicableCategories.includes("human")
204
- ),
205
- };
206
-
207
- // Sum totals
208
- let filled = 0;
209
- let total = 0;
210
-
211
- for (const section of Object.values(sections)) {
212
- filled += section.filled;
213
- total += section.total;
214
- }
215
-
216
- // Calculate final score
217
- const score = total > 0 ? Math.round((filled / total) * 100) : 0;
218
-
219
- // Find missing slots
220
- const missing: string[] = [];
221
- for (const category of applicableCategories) {
222
- for (const slot of SLOTS[category]) {
223
- if (!hasValue(faf, slot)) {
224
- missing.push(slot);
225
- }
226
- }
227
- }
228
-
229
- return {
230
- projectType,
231
- sections,
232
- filled,
233
- total,
234
- score,
235
- missing,
236
- };
237
- }
package/lib/tier.ts DELETED
@@ -1,46 +0,0 @@
1
- /**
2
- * Tier System
3
- *
4
- * The medal hierarchy for FAF scores.
5
- * Zero dependencies. Pure Bun.
6
- */
7
-
8
- export interface Tier {
9
- emoji: string;
10
- name: string;
11
- color: string;
12
- }
13
-
14
- // ANSI colors
15
- const YELLOW = "\x1b[33m";
16
- const GREEN = "\x1b[32m";
17
- const RED = "\x1b[31m";
18
- const DIM = "\x1b[2m";
19
- const ORANGE = "\x1b[38;5;208m";
20
- const WHITE = "\x1b[37m";
21
-
22
- export const TIERS: Tier[] = [
23
- { emoji: "🏆", name: "Trophy", color: YELLOW },
24
- { emoji: "🥇", name: "Gold", color: YELLOW },
25
- { emoji: "🥈", name: "Silver", color: WHITE },
26
- { emoji: "🥉", name: "Bronze", color: ORANGE },
27
- { emoji: "🟢", name: "Green", color: GREEN },
28
- { emoji: "🟡", name: "Yellow", color: YELLOW },
29
- { emoji: "🔴", name: "Red", color: RED },
30
- ];
31
-
32
- export function getTier(score: number): Tier {
33
- if (score >= 105) return { emoji: "🍊", name: "Big Orange", color: ORANGE };
34
- if (score >= 100) return { emoji: "🏆", name: "Trophy", color: YELLOW };
35
- if (score >= 99) return { emoji: "🥇", name: "Gold", color: YELLOW };
36
- if (score >= 95) return { emoji: "🥈", name: "Silver", color: WHITE };
37
- if (score >= 85) return { emoji: "🥉", name: "Bronze", color: ORANGE };
38
- if (score >= 70) return { emoji: "🟢", name: "Green", color: GREEN };
39
- if (score >= 55) return { emoji: "🟡", name: "Yellow", color: YELLOW };
40
- if (score > 0) return { emoji: "🔴", name: "Red", color: RED };
41
- return { emoji: "⚪", name: "Empty", color: DIM };
42
- }
43
-
44
- export function isLaunchReady(score: number): boolean {
45
- return score >= 85;
46
- }