ralphctl 0.1.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.
- package/CHANGELOG.md +94 -0
- package/LICENSE +21 -0
- package/README.md +189 -0
- package/bin/ralphctl +13 -0
- package/package.json +92 -0
- package/schemas/config.schema.json +20 -0
- package/schemas/ideate-output.schema.json +22 -0
- package/schemas/projects.schema.json +53 -0
- package/schemas/requirements-output.schema.json +24 -0
- package/schemas/sprint.schema.json +109 -0
- package/schemas/task-import.schema.json +49 -0
- package/schemas/tasks.schema.json +72 -0
- package/src/ai/executor.ts +973 -0
- package/src/ai/lifecycle.ts +45 -0
- package/src/ai/parser.ts +40 -0
- package/src/ai/permissions.ts +207 -0
- package/src/ai/process-manager.ts +248 -0
- package/src/ai/prompts/ideate-auto.md +144 -0
- package/src/ai/prompts/ideate.md +165 -0
- package/src/ai/prompts/index.ts +89 -0
- package/src/ai/prompts/plan-auto.md +131 -0
- package/src/ai/prompts/plan-common.md +157 -0
- package/src/ai/prompts/plan-interactive.md +190 -0
- package/src/ai/prompts/task-execution.md +159 -0
- package/src/ai/prompts/ticket-refine.md +230 -0
- package/src/ai/rate-limiter.ts +89 -0
- package/src/ai/runner.ts +478 -0
- package/src/ai/session.ts +319 -0
- package/src/ai/task-context.ts +270 -0
- package/src/cli-metadata.ts +7 -0
- package/src/cli.ts +65 -0
- package/src/commands/completion/index.ts +33 -0
- package/src/commands/config/config.ts +58 -0
- package/src/commands/config/index.ts +33 -0
- package/src/commands/dashboard/dashboard.ts +5 -0
- package/src/commands/dashboard/index.ts +6 -0
- package/src/commands/doctor/doctor.ts +271 -0
- package/src/commands/doctor/index.ts +25 -0
- package/src/commands/progress/index.ts +25 -0
- package/src/commands/progress/log.ts +64 -0
- package/src/commands/progress/show.ts +14 -0
- package/src/commands/project/add.ts +336 -0
- package/src/commands/project/index.ts +104 -0
- package/src/commands/project/list.ts +31 -0
- package/src/commands/project/remove.ts +43 -0
- package/src/commands/project/repo.ts +118 -0
- package/src/commands/project/show.ts +49 -0
- package/src/commands/sprint/close.ts +180 -0
- package/src/commands/sprint/context.ts +109 -0
- package/src/commands/sprint/create.ts +60 -0
- package/src/commands/sprint/current.ts +75 -0
- package/src/commands/sprint/delete.ts +72 -0
- package/src/commands/sprint/health.ts +229 -0
- package/src/commands/sprint/ideate.ts +496 -0
- package/src/commands/sprint/index.ts +226 -0
- package/src/commands/sprint/list.ts +86 -0
- package/src/commands/sprint/plan-utils.ts +207 -0
- package/src/commands/sprint/plan.ts +549 -0
- package/src/commands/sprint/refine.ts +359 -0
- package/src/commands/sprint/requirements.ts +58 -0
- package/src/commands/sprint/show.ts +140 -0
- package/src/commands/sprint/start.ts +119 -0
- package/src/commands/sprint/switch.ts +20 -0
- package/src/commands/task/add.ts +316 -0
- package/src/commands/task/import.ts +150 -0
- package/src/commands/task/index.ts +123 -0
- package/src/commands/task/list.ts +145 -0
- package/src/commands/task/next.ts +45 -0
- package/src/commands/task/remove.ts +47 -0
- package/src/commands/task/reorder.ts +45 -0
- package/src/commands/task/show.ts +111 -0
- package/src/commands/task/status.ts +99 -0
- package/src/commands/ticket/add.ts +265 -0
- package/src/commands/ticket/edit.ts +166 -0
- package/src/commands/ticket/index.ts +114 -0
- package/src/commands/ticket/list.ts +128 -0
- package/src/commands/ticket/refine-utils.ts +89 -0
- package/src/commands/ticket/refine.ts +268 -0
- package/src/commands/ticket/remove.ts +48 -0
- package/src/commands/ticket/show.ts +74 -0
- package/src/completion/handle.ts +30 -0
- package/src/completion/resolver.ts +241 -0
- package/src/interactive/dashboard.ts +268 -0
- package/src/interactive/escapable.ts +81 -0
- package/src/interactive/file-browser.ts +153 -0
- package/src/interactive/index.ts +429 -0
- package/src/interactive/menu.ts +403 -0
- package/src/interactive/selectors.ts +273 -0
- package/src/interactive/wizard.ts +221 -0
- package/src/providers/claude.ts +53 -0
- package/src/providers/copilot.ts +86 -0
- package/src/providers/index.ts +43 -0
- package/src/providers/types.ts +85 -0
- package/src/schemas/index.ts +130 -0
- package/src/store/config.ts +74 -0
- package/src/store/progress.ts +230 -0
- package/src/store/project.ts +276 -0
- package/src/store/sprint.ts +229 -0
- package/src/store/task.ts +443 -0
- package/src/store/ticket.ts +178 -0
- package/src/theme/index.ts +215 -0
- package/src/theme/ui.ts +872 -0
- package/src/utils/detect-scripts.ts +247 -0
- package/src/utils/editor-input.ts +41 -0
- package/src/utils/editor.ts +37 -0
- package/src/utils/exit-codes.ts +27 -0
- package/src/utils/file-lock.ts +135 -0
- package/src/utils/git.ts +185 -0
- package/src/utils/ids.ts +37 -0
- package/src/utils/issue-fetch.ts +244 -0
- package/src/utils/json-extract.ts +62 -0
- package/src/utils/multiline.ts +61 -0
- package/src/utils/path-selector.ts +236 -0
- package/src/utils/paths.ts +108 -0
- package/src/utils/provider.ts +34 -0
- package/src/utils/requirements-export.ts +63 -0
- package/src/utils/storage.ts +107 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { getSprintFilePath } from '@src/utils/paths.ts';
|
|
2
|
+
import { readValidatedJson, writeValidatedJson } from '@src/utils/storage.ts';
|
|
3
|
+
import { type Sprint, SprintSchema, type Ticket } from '@src/schemas/index.ts';
|
|
4
|
+
import { assertSprintStatus, resolveSprintId } from '@src/store/sprint.ts';
|
|
5
|
+
import { generateUuid8 } from '@src/utils/ids.ts';
|
|
6
|
+
import { getProject, ProjectNotFoundError } from '@src/store/project.ts';
|
|
7
|
+
|
|
8
|
+
export class TicketNotFoundError extends Error {
|
|
9
|
+
public readonly ticketId: string;
|
|
10
|
+
|
|
11
|
+
constructor(ticketId: string) {
|
|
12
|
+
super(`Ticket not found: ${ticketId}`);
|
|
13
|
+
this.name = 'TicketNotFoundError';
|
|
14
|
+
this.ticketId = ticketId;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function getSprintData(sprintId?: string): Promise<Sprint> {
|
|
19
|
+
const id = await resolveSprintId(sprintId);
|
|
20
|
+
return readValidatedJson(getSprintFilePath(id), SprintSchema);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function saveSprintData(sprint: Sprint): Promise<void> {
|
|
24
|
+
await writeValidatedJson(getSprintFilePath(sprint.id), sprint, SprintSchema);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AddTicketInput {
|
|
28
|
+
title: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
link?: string;
|
|
31
|
+
projectName: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function addTicket(input: AddTicketInput, sprintId?: string): Promise<Ticket> {
|
|
35
|
+
const sprint = await getSprintData(sprintId);
|
|
36
|
+
|
|
37
|
+
// Check sprint status - must be draft to add tickets
|
|
38
|
+
assertSprintStatus(sprint, ['draft'], 'add tickets');
|
|
39
|
+
|
|
40
|
+
// Validate that the project exists
|
|
41
|
+
try {
|
|
42
|
+
await getProject(input.projectName);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
if (err instanceof ProjectNotFoundError) {
|
|
45
|
+
throw new Error(`Project '${input.projectName}' does not exist. Add it first with 'ralphctl project add'.`, {
|
|
46
|
+
cause: err,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const ticket: Ticket = {
|
|
53
|
+
id: generateUuid8(),
|
|
54
|
+
title: input.title,
|
|
55
|
+
description: input.description,
|
|
56
|
+
link: input.link,
|
|
57
|
+
projectName: input.projectName,
|
|
58
|
+
requirementStatus: 'pending',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
sprint.tickets.push(ticket);
|
|
62
|
+
await saveSprintData(sprint);
|
|
63
|
+
return ticket;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface UpdateTicketInput {
|
|
67
|
+
title?: string;
|
|
68
|
+
description?: string;
|
|
69
|
+
link?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function updateTicket(ticketId: string, updates: UpdateTicketInput, sprintId?: string): Promise<Ticket> {
|
|
73
|
+
const sprint = await getSprintData(sprintId);
|
|
74
|
+
|
|
75
|
+
// Check sprint status - must be draft to update tickets
|
|
76
|
+
assertSprintStatus(sprint, ['draft'], 'update tickets');
|
|
77
|
+
|
|
78
|
+
const ticketIdx = sprint.tickets.findIndex((t) => t.id === ticketId);
|
|
79
|
+
if (ticketIdx === -1) {
|
|
80
|
+
throw new TicketNotFoundError(ticketId);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const ticket = sprint.tickets[ticketIdx];
|
|
84
|
+
if (!ticket) {
|
|
85
|
+
throw new TicketNotFoundError(ticketId);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Apply updates
|
|
89
|
+
if (updates.title !== undefined) {
|
|
90
|
+
ticket.title = updates.title;
|
|
91
|
+
}
|
|
92
|
+
if (updates.description !== undefined) {
|
|
93
|
+
ticket.description = updates.description || undefined;
|
|
94
|
+
}
|
|
95
|
+
if (updates.link !== undefined) {
|
|
96
|
+
ticket.link = updates.link || undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
await saveSprintData(sprint);
|
|
100
|
+
return ticket;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function removeTicket(ticketId: string, sprintId?: string): Promise<void> {
|
|
104
|
+
const sprint = await getSprintData(sprintId);
|
|
105
|
+
|
|
106
|
+
// Check sprint status - must be draft to remove tickets
|
|
107
|
+
assertSprintStatus(sprint, ['draft'], 'remove tickets');
|
|
108
|
+
|
|
109
|
+
const index = sprint.tickets.findIndex((t) => t.id === ticketId);
|
|
110
|
+
if (index === -1) {
|
|
111
|
+
throw new TicketNotFoundError(ticketId);
|
|
112
|
+
}
|
|
113
|
+
sprint.tickets.splice(index, 1);
|
|
114
|
+
await saveSprintData(sprint);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function listTickets(sprintId?: string): Promise<Ticket[]> {
|
|
118
|
+
const sprint = await getSprintData(sprintId);
|
|
119
|
+
return sprint.tickets;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function getTicket(ticketId: string, sprintId?: string): Promise<Ticket> {
|
|
123
|
+
const sprint = await getSprintData(sprintId);
|
|
124
|
+
const ticket = sprint.tickets.find((t) => t.id === ticketId);
|
|
125
|
+
if (!ticket) {
|
|
126
|
+
throw new TicketNotFoundError(ticketId);
|
|
127
|
+
}
|
|
128
|
+
return ticket;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get a ticket by title (for tickets without an external ID).
|
|
133
|
+
*/
|
|
134
|
+
export async function getTicketByTitle(title: string, sprintId?: string): Promise<Ticket | undefined> {
|
|
135
|
+
const sprint = await getSprintData(sprintId);
|
|
136
|
+
return sprint.tickets.find((t) => t.title === title);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Group tickets by their project name.
|
|
141
|
+
*/
|
|
142
|
+
export function groupTicketsByProject(tickets: Ticket[]): Map<string, Ticket[]> {
|
|
143
|
+
const grouped = new Map<string, Ticket[]>();
|
|
144
|
+
for (const ticket of tickets) {
|
|
145
|
+
const existing = grouped.get(ticket.projectName) ?? [];
|
|
146
|
+
existing.push(ticket);
|
|
147
|
+
grouped.set(ticket.projectName, existing);
|
|
148
|
+
}
|
|
149
|
+
return grouped;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Check if all tickets have approved requirements.
|
|
154
|
+
*/
|
|
155
|
+
export function allRequirementsApproved(tickets: Ticket[]): boolean {
|
|
156
|
+
return tickets.length > 0 && tickets.every((t) => t.requirementStatus === 'approved');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get tickets that still need requirement refinement.
|
|
161
|
+
*/
|
|
162
|
+
export function getPendingRequirements(tickets: Ticket[]): Ticket[] {
|
|
163
|
+
return tickets.filter((t) => t.requirementStatus === 'pending');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Format ticket for display: "[ID] Title"
|
|
168
|
+
*/
|
|
169
|
+
export function formatTicketDisplay(ticket: Ticket): string {
|
|
170
|
+
return `[${ticket.id}] ${ticket.title}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Format ticket ID for display.
|
|
175
|
+
*/
|
|
176
|
+
export function formatTicketId(ticket: Ticket): string {
|
|
177
|
+
return ticket.id;
|
|
178
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { bold, cyan, dim, gray, green, magenta, red, yellow } from 'colorette';
|
|
2
|
+
import gradient from 'gradient-string';
|
|
3
|
+
|
|
4
|
+
// Re-export colorette functions for direct usage
|
|
5
|
+
export { cyan, green, red, yellow, blue, gray, bold, dim, isColorSupported } from 'colorette';
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// COLOR FUNCTIONS
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Color function type (matches colorette signature)
|
|
13
|
+
*/
|
|
14
|
+
export type ColorFn = (text: string | number) => string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Theme color mappings
|
|
18
|
+
*/
|
|
19
|
+
export const colors = {
|
|
20
|
+
// Semantic colors
|
|
21
|
+
success: green,
|
|
22
|
+
error: red,
|
|
23
|
+
warning: yellow,
|
|
24
|
+
info: cyan,
|
|
25
|
+
muted: gray,
|
|
26
|
+
highlight: yellow,
|
|
27
|
+
accent: bold,
|
|
28
|
+
subtle: dim,
|
|
29
|
+
// Ralph-specific
|
|
30
|
+
primary: yellow,
|
|
31
|
+
secondary: magenta,
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
// Semantic color shortcuts (replaces utils/colors.ts)
|
|
35
|
+
export const success = (text: string): string => colors.success(text);
|
|
36
|
+
export const error = (text: string): string => colors.error(text);
|
|
37
|
+
export const warning = (text: string): string => colors.warning(text);
|
|
38
|
+
export const info = (text: string): string => colors.info(text);
|
|
39
|
+
export const muted = (text: string): string => colors.muted(text);
|
|
40
|
+
export const highlight = (text: string): string => colors.highlight(text);
|
|
41
|
+
export const accent = (text: string): string => colors.accent(text);
|
|
42
|
+
export const subtle = (text: string): string => colors.subtle(text);
|
|
43
|
+
export const primary = (text: string): string => colors.primary(text);
|
|
44
|
+
export const secondary = (text: string): string => colors.secondary(text);
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// GRADIENT RENDERING (powered by gradient-string)
|
|
48
|
+
// ============================================================================
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Built-in gradient presets for banner/header styling.
|
|
52
|
+
* Each gradient is a function: gradients.donut(text) or gradients.donut.multiline(text)
|
|
53
|
+
*/
|
|
54
|
+
export const gradients = {
|
|
55
|
+
/** Gold → Orange → Hot Pink → Orchid → Violet (Ralph's signature donut warmth) */
|
|
56
|
+
donut: gradient(['#FFD700', '#FFA500', '#FF69B4', '#DA70D6', '#9400D3'], {
|
|
57
|
+
interpolation: 'hsv',
|
|
58
|
+
hsvSpin: 'short',
|
|
59
|
+
}),
|
|
60
|
+
/** Green → Dark Cyan (success/completion) */
|
|
61
|
+
success: gradient(['#00FF00', '#00CED1']),
|
|
62
|
+
/** Orange Red → Gold (warning/attention) */
|
|
63
|
+
warning: gradient(['#FF4500', '#FFD700']),
|
|
64
|
+
} as const;
|
|
65
|
+
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// BANNER
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
const BANNER = `
|
|
71
|
+
🍩 ██████╗ █████╗ ██╗ ██████╗ ██╗ ██╗ ██████╗████████╗██╗ 🍩
|
|
72
|
+
██╔══██╗██╔══██╗██║ ██╔══██╗██║ ██║██╔════╝╚══██╔══╝██║
|
|
73
|
+
██████╔╝███████║██║ ██████╔╝███████║██║ ██║ ██║
|
|
74
|
+
██╔══██╗██╔══██║██║ ██╔═══╝ ██╔══██║██║ ██║ ██║
|
|
75
|
+
██║ ██║██║ ██║███████╗██║ ██║ ██║╚██████╗ ██║ ███████╗
|
|
76
|
+
╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
export const banner = {
|
|
80
|
+
art: BANNER,
|
|
81
|
+
tagline: "I'm helping with your sprints!",
|
|
82
|
+
} as const;
|
|
83
|
+
|
|
84
|
+
// ============================================================================
|
|
85
|
+
// QUOTES
|
|
86
|
+
// ============================================================================
|
|
87
|
+
|
|
88
|
+
export const RALPH_QUOTES = [
|
|
89
|
+
"I'm helping!",
|
|
90
|
+
"Me fail English? That's unpossible!",
|
|
91
|
+
'Go banana!',
|
|
92
|
+
'Hi, Super Nintendo Chalmers!',
|
|
93
|
+
'I bent my wookie.',
|
|
94
|
+
"My cat's breath smells like cat food.",
|
|
95
|
+
"I'm learnding!",
|
|
96
|
+
"The doctor said I wouldn't have so many nose bleeds if I kept my finger outta there.",
|
|
97
|
+
'I found a moonrock in my nose!',
|
|
98
|
+
"That's where I saw the leprechaun. He told me to burn things.",
|
|
99
|
+
"My daddy's gonna put you in jail!",
|
|
100
|
+
"I'm a unitard!",
|
|
101
|
+
'I ate the purple berries...',
|
|
102
|
+
'Tastes like burning!',
|
|
103
|
+
"My parents won't let me use scissors.",
|
|
104
|
+
'I dress myself!',
|
|
105
|
+
'Principal Skinner, I got carsick in your office.',
|
|
106
|
+
"I'm Idaho!",
|
|
107
|
+
'Mrs. Krabappel and Principal Skinner were in the closet making babies!',
|
|
108
|
+
'Even my boogers are spicy!',
|
|
109
|
+
'It smells like hot dogs.',
|
|
110
|
+
'I sleep in a drawer!',
|
|
111
|
+
'I picked the red one!',
|
|
112
|
+
'The pointy kitty took it!',
|
|
113
|
+
'When I grow up, I want to be a principal or a caterpillar.',
|
|
114
|
+
] as const;
|
|
115
|
+
|
|
116
|
+
export function getRandomQuote(): string {
|
|
117
|
+
const index = Math.floor(Math.random() * RALPH_QUOTES.length);
|
|
118
|
+
return RALPH_QUOTES[index] ?? '';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ============================================================================
|
|
122
|
+
// CONTEXT-SENSITIVE QUOTES
|
|
123
|
+
// ============================================================================
|
|
124
|
+
|
|
125
|
+
export type QuoteCategory = 'error' | 'success' | 'farewell' | 'idle';
|
|
126
|
+
|
|
127
|
+
export const QUOTES_BY_CATEGORY: Record<QuoteCategory, readonly string[]> = {
|
|
128
|
+
error: [
|
|
129
|
+
'My tummy hurts!',
|
|
130
|
+
'Tastes like burning!',
|
|
131
|
+
'I ate the purple berries...',
|
|
132
|
+
"The doctor said I wouldn't have so many nose bleeds if I kept my finger outta there.",
|
|
133
|
+
"My parents won't let me use scissors.",
|
|
134
|
+
'Principal Skinner, I got carsick in your office.',
|
|
135
|
+
'I eated the purple berries. They taste like... burning.',
|
|
136
|
+
],
|
|
137
|
+
success: [
|
|
138
|
+
"I'm helping!",
|
|
139
|
+
'Go banana!',
|
|
140
|
+
"I'm learnding!",
|
|
141
|
+
"I'm a unitard!",
|
|
142
|
+
'I dress myself!',
|
|
143
|
+
'I picked the red one!',
|
|
144
|
+
'I found a moonrock in my nose!',
|
|
145
|
+
"Yay! I'm a helper!",
|
|
146
|
+
],
|
|
147
|
+
farewell: [
|
|
148
|
+
"Bye bye! My cat's breath smells like cat food!",
|
|
149
|
+
'When I grow up, I want to be a principal or a caterpillar.',
|
|
150
|
+
'I sleep in a drawer!',
|
|
151
|
+
"I'm Idaho!",
|
|
152
|
+
'The pointy kitty took it!',
|
|
153
|
+
],
|
|
154
|
+
idle: [
|
|
155
|
+
'Hi, Super Nintendo Chalmers!',
|
|
156
|
+
'I bent my wookie.',
|
|
157
|
+
"My cat's breath smells like cat food.",
|
|
158
|
+
'It smells like hot dogs.',
|
|
159
|
+
"That's where I saw the leprechaun. He told me to burn things.",
|
|
160
|
+
"Me fail English? That's unpossible!",
|
|
161
|
+
'Even my boogers are spicy!',
|
|
162
|
+
'Mrs. Krabappel and Principal Skinner were in the closet making babies!',
|
|
163
|
+
],
|
|
164
|
+
} as const;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get a random quote appropriate for the given context category.
|
|
168
|
+
*/
|
|
169
|
+
export function getQuoteForContext(category: QuoteCategory): string {
|
|
170
|
+
const quotes = QUOTES_BY_CATEGORY[category];
|
|
171
|
+
const index = Math.floor(Math.random() * quotes.length);
|
|
172
|
+
return quotes[index] ?? '';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ============================================================================
|
|
176
|
+
// STATUS EMOJI
|
|
177
|
+
// ============================================================================
|
|
178
|
+
|
|
179
|
+
export const statusEmoji = {
|
|
180
|
+
todo: '📝',
|
|
181
|
+
in_progress: '🏃',
|
|
182
|
+
done: '✅',
|
|
183
|
+
blocked: '🚫',
|
|
184
|
+
draft: '📋',
|
|
185
|
+
active: '🎯',
|
|
186
|
+
closed: '🎉',
|
|
187
|
+
} as const;
|
|
188
|
+
|
|
189
|
+
export function getStatusEmoji(status: string): string {
|
|
190
|
+
if (status in statusEmoji) {
|
|
191
|
+
return statusEmoji[status as keyof typeof statusEmoji];
|
|
192
|
+
}
|
|
193
|
+
return status;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ============================================================================
|
|
197
|
+
// MESSAGES
|
|
198
|
+
// ============================================================================
|
|
199
|
+
|
|
200
|
+
export const messages = {
|
|
201
|
+
welcome: "Hi, Super Nintendo Chalmers! I'm ready to help!",
|
|
202
|
+
goodbye: "Bye bye! My cat's breath smells like cat food!",
|
|
203
|
+
taskComplete: "Yay! I did a task! I'm a unitard!",
|
|
204
|
+
sprintCreated: 'I made a sprint! Go banana!',
|
|
205
|
+
sprintActivated: "The sprint is awake now! It's unpossible to fail!",
|
|
206
|
+
sprintClosed: "We finished! That's where I saw the leprechaun!",
|
|
207
|
+
ticketAdded: "I added a ticket! I'm learnding!",
|
|
208
|
+
projectAdded: 'I found a project! It smells like hot dogs!',
|
|
209
|
+
error: 'My tummy hurts:',
|
|
210
|
+
confirm: 'Do you want to? I picked the red one!',
|
|
211
|
+
} as const;
|
|
212
|
+
|
|
213
|
+
export function getMessage(key: keyof typeof messages): string {
|
|
214
|
+
return messages[key];
|
|
215
|
+
}
|