@averagejoeslab/puppuccino-tui 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/dist/index.cjs ADDED
@@ -0,0 +1,578 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Cmd: () => import_tui2.Cmd,
24
+ DarkTheme: () => DarkTheme,
25
+ KALDI: () => KALDI,
26
+ KALDI_COLORS: () => KALDI_COLORS,
27
+ KaldiTheme: () => KaldiTheme,
28
+ Key: () => import_tui2.Key,
29
+ LightTheme: () => LightTheme,
30
+ Program: () => import_tui2.Program,
31
+ Style: () => import_style3.Style,
32
+ createApp: () => createApp,
33
+ getGoodbyeBanner: () => getGoodbyeBanner,
34
+ getKaldi: () => getKaldi,
35
+ getPrompt: () => getPrompt,
36
+ getTheme: () => getTheme,
37
+ getWelcomeBanner: () => getWelcomeBanner,
38
+ init: () => init,
39
+ renderMarkdown: () => import_markdown2.render,
40
+ themes: () => themes,
41
+ update: () => update,
42
+ view: () => view
43
+ });
44
+ module.exports = __toCommonJS(index_exports);
45
+
46
+ // src/app.ts
47
+ var import_tui = require("@averagejoeslab/tui");
48
+ var import_widgets = require("@averagejoeslab/widgets");
49
+ var import_markdown = require("@averagejoeslab/markdown");
50
+ var import_term = require("@averagejoeslab/term");
51
+
52
+ // src/themes/index.ts
53
+ var import_style = require("@averagejoeslab/style");
54
+ var KaldiTheme = {
55
+ name: "kaldi",
56
+ colors: {
57
+ primary: [255, 250, 240],
58
+ // Cream white (like Kaldi's fur)
59
+ secondary: [139, 119, 101],
60
+ // Light brown
61
+ accent: [210, 180, 140],
62
+ // Tan
63
+ text: [245, 245, 245],
64
+ // Off-white
65
+ textDim: [169, 169, 169],
66
+ // Gray
67
+ background: [30, 30, 30],
68
+ // Dark
69
+ success: [144, 238, 144],
70
+ // Light green
71
+ error: [255, 99, 71],
72
+ // Tomato red
73
+ warning: [255, 215, 0],
74
+ // Gold
75
+ info: [135, 206, 235]
76
+ // Sky blue
77
+ },
78
+ styles: {
79
+ heading: new import_style.Style().foregroundRGB(255, 250, 240).bold(),
80
+ text: new import_style.Style().foregroundRGB(245, 245, 245),
81
+ dim: new import_style.Style().foregroundRGB(169, 169, 169),
82
+ accent: new import_style.Style().foregroundRGB(210, 180, 140),
83
+ success: new import_style.Style().foregroundRGB(144, 238, 144),
84
+ error: new import_style.Style().foregroundRGB(255, 99, 71),
85
+ warning: new import_style.Style().foregroundRGB(255, 215, 0),
86
+ info: new import_style.Style().foregroundRGB(135, 206, 235),
87
+ code: new import_style.Style().foregroundRGB(255, 250, 240).background(52),
88
+ link: new import_style.Style().foregroundRGB(135, 206, 235).underline(),
89
+ prompt: new import_style.Style().foregroundRGB(210, 180, 140).bold(),
90
+ toolName: new import_style.Style().foregroundRGB(135, 206, 235).bold(),
91
+ toolOutput: new import_style.Style().foregroundRGB(169, 169, 169)
92
+ }
93
+ };
94
+ var DarkTheme = {
95
+ name: "dark",
96
+ colors: {
97
+ primary: [97, 175, 239],
98
+ // Blue
99
+ secondary: [152, 195, 121],
100
+ // Green
101
+ accent: [229, 192, 123],
102
+ // Yellow
103
+ text: [220, 220, 220],
104
+ textDim: [128, 128, 128],
105
+ background: [30, 30, 30],
106
+ success: [152, 195, 121],
107
+ error: [224, 108, 117],
108
+ warning: [229, 192, 123],
109
+ info: [97, 175, 239]
110
+ },
111
+ styles: {
112
+ heading: new import_style.Style().foregroundRGB(97, 175, 239).bold(),
113
+ text: new import_style.Style().foregroundRGB(220, 220, 220),
114
+ dim: new import_style.Style().foregroundRGB(128, 128, 128),
115
+ accent: new import_style.Style().foregroundRGB(229, 192, 123),
116
+ success: new import_style.Style().foregroundRGB(152, 195, 121),
117
+ error: new import_style.Style().foregroundRGB(224, 108, 117),
118
+ warning: new import_style.Style().foregroundRGB(229, 192, 123),
119
+ info: new import_style.Style().foregroundRGB(97, 175, 239),
120
+ code: new import_style.Style().foregroundRGB(220, 220, 220).background(236),
121
+ link: new import_style.Style().foregroundRGB(97, 175, 239).underline(),
122
+ prompt: new import_style.Style().foregroundRGB(152, 195, 121).bold(),
123
+ toolName: new import_style.Style().foregroundRGB(97, 175, 239).bold(),
124
+ toolOutput: new import_style.Style().foregroundRGB(128, 128, 128)
125
+ }
126
+ };
127
+ var LightTheme = {
128
+ name: "light",
129
+ colors: {
130
+ primary: [0, 95, 184],
131
+ // Blue
132
+ secondary: [40, 167, 69],
133
+ // Green
134
+ accent: [202, 138, 4],
135
+ // Amber
136
+ text: [33, 33, 33],
137
+ textDim: [117, 117, 117],
138
+ background: [255, 255, 255],
139
+ success: [40, 167, 69],
140
+ error: [220, 53, 69],
141
+ warning: [202, 138, 4],
142
+ info: [0, 95, 184]
143
+ },
144
+ styles: {
145
+ heading: new import_style.Style().foregroundRGB(0, 95, 184).bold(),
146
+ text: new import_style.Style().foregroundRGB(33, 33, 33),
147
+ dim: new import_style.Style().foregroundRGB(117, 117, 117),
148
+ accent: new import_style.Style().foregroundRGB(202, 138, 4),
149
+ success: new import_style.Style().foregroundRGB(40, 167, 69),
150
+ error: new import_style.Style().foregroundRGB(220, 53, 69),
151
+ warning: new import_style.Style().foregroundRGB(202, 138, 4),
152
+ info: new import_style.Style().foregroundRGB(0, 95, 184),
153
+ code: new import_style.Style().foregroundRGB(33, 33, 33).background(253),
154
+ link: new import_style.Style().foregroundRGB(0, 95, 184).underline(),
155
+ prompt: new import_style.Style().foregroundRGB(40, 167, 69).bold(),
156
+ toolName: new import_style.Style().foregroundRGB(0, 95, 184).bold(),
157
+ toolOutput: new import_style.Style().foregroundRGB(117, 117, 117)
158
+ }
159
+ };
160
+ function getTheme(name) {
161
+ switch (name) {
162
+ case "kaldi":
163
+ return KaldiTheme;
164
+ case "light":
165
+ return LightTheme;
166
+ case "dark":
167
+ default:
168
+ return DarkTheme;
169
+ }
170
+ }
171
+ var themes = {
172
+ kaldi: KaldiTheme,
173
+ dark: DarkTheme,
174
+ light: LightTheme
175
+ };
176
+
177
+ // src/components/kaldi.ts
178
+ var import_style2 = require("@averagejoeslab/style");
179
+ var KALDI = {
180
+ normal: `
181
+ / \\__
182
+ ( @\\___
183
+ / O
184
+ / (_____/
185
+ /_____/ U
186
+ `,
187
+ thinking: `
188
+ / \\__
189
+ ( @\\___ ...
190
+ / O
191
+ / (_____/
192
+ /_____/ U
193
+ `,
194
+ happy: `
195
+ / \\__
196
+ ( @\\___ woof!
197
+ / O
198
+ / (_____/
199
+ /_____/ U
200
+ `,
201
+ working: `
202
+ / \\__
203
+ ( @\\___ *typing*
204
+ / O
205
+ / (_____/
206
+ /_____/ U
207
+ `,
208
+ error: `
209
+ / \\__
210
+ ( @\\___ :(
211
+ / O
212
+ / (_____/
213
+ /_____/ U
214
+ `,
215
+ small: ` /\\_/\\
216
+ ( o.o )
217
+ > ^ <`
218
+ };
219
+ var KALDI_COLORS = {
220
+ body: [255, 250, 240],
221
+ // Cream/white
222
+ nose: [0, 0, 0],
223
+ // Black
224
+ accent: [139, 119, 101]
225
+ // Light brown
226
+ };
227
+ function getKaldi(variant = "normal") {
228
+ const art = KALDI[variant];
229
+ const style = new import_style2.Style().foregroundRGB(...KALDI_COLORS.body).bold();
230
+ return style.render(art);
231
+ }
232
+ function getWelcomeBanner(version = "0.1.0") {
233
+ const style = new import_style2.Style().foregroundRGB(...KALDI_COLORS.body);
234
+ const accentStyle = new import_style2.Style().foregroundRGB(...KALDI_COLORS.accent);
235
+ const banner = `
236
+ ${style.render(KALDI.normal)}
237
+ ${accentStyle.render(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")}
238
+ ${accentStyle.render(" \u2551")} ${style.render("Puppuccino")} ${accentStyle.render("- AI Coding Assistant")} ${accentStyle.render("\u2551")}
239
+ ${accentStyle.render(" \u2551")} ${style.render(`v${version}`)} ${accentStyle.render("| Powered by Kaldi")} ${accentStyle.render("\u2551")}
240
+ ${accentStyle.render(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")}
241
+ `;
242
+ return banner;
243
+ }
244
+ function getGoodbyeBanner() {
245
+ const style = new import_style2.Style().foregroundRGB(...KALDI_COLORS.body);
246
+ return `
247
+ ${style.render(KALDI.happy)}
248
+ ${style.render("Goodbye! Kaldi will miss you!")}
249
+ `;
250
+ }
251
+ function getPrompt() {
252
+ return "\u{1F415}\u200D\u{1F9BA}\u276F ";
253
+ }
254
+
255
+ // src/app.ts
256
+ function init(options) {
257
+ const { width, height } = (0, import_term.getTerminalSize)();
258
+ const theme = getTheme(options?.theme || "kaldi");
259
+ const model = {
260
+ input: import_widgets.TextInput.init({
261
+ placeholder: "Ask Kaldi anything...",
262
+ width: width - 4
263
+ }),
264
+ viewport: import_widgets.Viewport.init({
265
+ width: width - 2,
266
+ height: height - 6
267
+ }),
268
+ spinner: import_widgets.Spinner.init({ style: "dots" }),
269
+ status: "idle",
270
+ messages: [],
271
+ theme,
272
+ showWelcome: true,
273
+ width,
274
+ height
275
+ };
276
+ const spinnerCmd = import_widgets.Spinner.tick(model.spinner);
277
+ return [model, spinnerCmd ? () => ({ type: "spinner", msg: spinnerCmd }) : null];
278
+ }
279
+ function update(msg, model) {
280
+ switch (msg.type) {
281
+ case "key": {
282
+ if (msg.key.ctrl && msg.key.name === "c") {
283
+ return [model, () => ({ type: "quit" })];
284
+ }
285
+ if (msg.key.name === "escape") {
286
+ if (model.showWelcome) {
287
+ return [{ ...model, showWelcome: false }, null];
288
+ }
289
+ }
290
+ if (msg.key.name === "enter" && model.status === "idle") {
291
+ const text = import_widgets.TextInput.value(model.input);
292
+ if (text.trim()) {
293
+ return update({ type: "submit" }, model);
294
+ }
295
+ }
296
+ if (model.showWelcome) {
297
+ return [{ ...model, showWelcome: false }, null];
298
+ }
299
+ const inputMsg = import_widgets.TextInput.handleKey(msg.key);
300
+ if (inputMsg) {
301
+ return update({ type: "input", msg: inputMsg }, model);
302
+ }
303
+ return [model, null];
304
+ }
305
+ case "input": {
306
+ const [newInput, inputCmd] = import_widgets.TextInput.update(msg.msg, model.input);
307
+ const cmd = inputCmd ? () => ({ type: "input", msg: inputCmd }) : null;
308
+ return [{ ...model, input: newInput }, cmd];
309
+ }
310
+ case "viewport": {
311
+ const [newViewport, viewportCmd] = import_widgets.Viewport.update(msg.msg, model.viewport);
312
+ const cmd = viewportCmd ? () => ({ type: "viewport", msg: viewportCmd }) : null;
313
+ return [{ ...model, viewport: newViewport }, cmd];
314
+ }
315
+ case "spinner": {
316
+ const [newSpinner, spinnerCmd] = import_widgets.Spinner.update(msg.msg, model.spinner);
317
+ const cmd = spinnerCmd ? () => ({ type: "spinner", msg: spinnerCmd }) : null;
318
+ return [{ ...model, spinner: newSpinner }, cmd];
319
+ }
320
+ case "submit": {
321
+ const text = import_widgets.TextInput.value(model.input);
322
+ if (!text.trim()) return [model, null];
323
+ if (text.startsWith("/")) {
324
+ return handleCommand(text.slice(1), model);
325
+ }
326
+ const userMessage = {
327
+ role: "user",
328
+ content: text,
329
+ timestamp: /* @__PURE__ */ new Date()
330
+ };
331
+ return [{
332
+ ...model,
333
+ input: import_widgets.TextInput.init({
334
+ placeholder: "Ask Kaldi anything...",
335
+ width: model.width - 4
336
+ }),
337
+ messages: [...model.messages, userMessage],
338
+ status: "thinking"
339
+ }, null];
340
+ }
341
+ case "agent_event": {
342
+ return handleAgentEvent(msg.event, model);
343
+ }
344
+ case "resize": {
345
+ return [{
346
+ ...model,
347
+ width: msg.width,
348
+ height: msg.height,
349
+ input: import_widgets.TextInput.init({
350
+ placeholder: "Ask Kaldi anything...",
351
+ width: msg.width - 4,
352
+ value: import_widgets.TextInput.value(model.input)
353
+ }),
354
+ viewport: import_widgets.Viewport.init({
355
+ width: msg.width - 2,
356
+ height: msg.height - 6,
357
+ content: model.viewport.content
358
+ })
359
+ }, null];
360
+ }
361
+ case "clear": {
362
+ return [{
363
+ ...model,
364
+ messages: [],
365
+ status: "idle",
366
+ errorMessage: void 0
367
+ }, null];
368
+ }
369
+ case "error": {
370
+ return [{
371
+ ...model,
372
+ status: "error",
373
+ errorMessage: msg.error
374
+ }, null];
375
+ }
376
+ case "dismiss_welcome": {
377
+ return [{ ...model, showWelcome: false }, null];
378
+ }
379
+ case "quit": {
380
+ return [model, null];
381
+ }
382
+ default:
383
+ return [model, null];
384
+ }
385
+ }
386
+ function handleCommand(cmd, model) {
387
+ const [command, ...args] = cmd.split(/\s+/);
388
+ switch (command.toLowerCase()) {
389
+ case "clear":
390
+ case "c":
391
+ return update({ type: "clear" }, model);
392
+ case "quit":
393
+ case "q":
394
+ case "exit":
395
+ return update({ type: "quit" }, model);
396
+ case "help":
397
+ case "h":
398
+ case "?": {
399
+ const helpMessage = {
400
+ role: "assistant",
401
+ content: `## Available Commands
402
+
403
+ - \`/clear\` or \`/c\` - Clear conversation
404
+ - \`/quit\` or \`/q\` - Exit Puppuccino
405
+ - \`/help\` or \`/?\` - Show this help
406
+
407
+ Just type your message and press Enter to chat with Kaldi!`,
408
+ timestamp: /* @__PURE__ */ new Date()
409
+ };
410
+ return [{
411
+ ...model,
412
+ input: import_widgets.TextInput.init({
413
+ placeholder: "Ask Kaldi anything...",
414
+ width: model.width - 4
415
+ }),
416
+ messages: [...model.messages, helpMessage]
417
+ }, null];
418
+ }
419
+ default: {
420
+ const errorMessage = {
421
+ role: "assistant",
422
+ content: `Unknown command: \`/${command}\`. Type \`/help\` for available commands.`,
423
+ timestamp: /* @__PURE__ */ new Date()
424
+ };
425
+ return [{
426
+ ...model,
427
+ input: import_widgets.TextInput.init({
428
+ placeholder: "Ask Kaldi anything...",
429
+ width: model.width - 4
430
+ }),
431
+ messages: [...model.messages, errorMessage]
432
+ }, null];
433
+ }
434
+ }
435
+ }
436
+ function handleAgentEvent(event, model) {
437
+ switch (event.type) {
438
+ case "thinking":
439
+ return [{ ...model, status: "thinking" }, null];
440
+ case "text": {
441
+ const messages = [...model.messages];
442
+ const lastMsg = messages[messages.length - 1];
443
+ if (lastMsg && lastMsg.role === "assistant" && !lastMsg.toolName) {
444
+ lastMsg.content += event.content;
445
+ } else {
446
+ messages.push({
447
+ role: "assistant",
448
+ content: event.content,
449
+ timestamp: /* @__PURE__ */ new Date()
450
+ });
451
+ }
452
+ return [{ ...model, messages, status: "idle" }, null];
453
+ }
454
+ case "tool_start":
455
+ return [{
456
+ ...model,
457
+ status: "tool",
458
+ currentTool: event.name
459
+ }, null];
460
+ case "tool_result": {
461
+ const toolMessage = {
462
+ role: "tool",
463
+ content: event.result.success ? event.result.output : `Error: ${event.result.error}`,
464
+ toolName: event.name,
465
+ timestamp: /* @__PURE__ */ new Date()
466
+ };
467
+ return [{
468
+ ...model,
469
+ messages: [...model.messages, toolMessage],
470
+ status: "thinking",
471
+ currentTool: void 0
472
+ }, null];
473
+ }
474
+ case "tool_denied": {
475
+ const deniedMessage = {
476
+ role: "tool",
477
+ content: `Permission denied: ${event.reason}`,
478
+ toolName: event.name,
479
+ timestamp: /* @__PURE__ */ new Date()
480
+ };
481
+ return [{
482
+ ...model,
483
+ messages: [...model.messages, deniedMessage],
484
+ status: "thinking",
485
+ currentTool: void 0
486
+ }, null];
487
+ }
488
+ case "error":
489
+ return [{
490
+ ...model,
491
+ status: "error",
492
+ errorMessage: event.error
493
+ }, null];
494
+ case "done":
495
+ return [{ ...model, status: "idle" }, null];
496
+ default:
497
+ return [model, null];
498
+ }
499
+ }
500
+ function view(model) {
501
+ const { theme } = model;
502
+ const lines = [];
503
+ if (model.showWelcome) {
504
+ return getWelcomeBanner() + "\n\nPress any key to continue...";
505
+ }
506
+ lines.push(theme.styles.heading.render("\u2500".repeat(model.width)));
507
+ const chatLines = [];
508
+ for (const msg of model.messages) {
509
+ chatLines.push(renderMessage(msg, theme, model.width - 4));
510
+ chatLines.push("");
511
+ }
512
+ if (model.status === "thinking") {
513
+ const spinner = import_widgets.Spinner.view(model.spinner);
514
+ chatLines.push(`${spinner} ${theme.styles.dim.render("Kaldi is thinking...")}`);
515
+ } else if (model.status === "tool" && model.currentTool) {
516
+ const spinner = import_widgets.Spinner.view(model.spinner);
517
+ chatLines.push(`${spinner} ${theme.styles.toolName.render(model.currentTool)}`);
518
+ } else if (model.status === "error" && model.errorMessage) {
519
+ chatLines.push(theme.styles.error.render(`Error: ${model.errorMessage}`));
520
+ }
521
+ const viewportModel = import_widgets.Viewport.setContent(model.viewport, chatLines.join("\n"));
522
+ lines.push(import_widgets.Viewport.view(viewportModel));
523
+ lines.push(theme.styles.heading.render("\u2500".repeat(model.width)));
524
+ const prompt = getPrompt();
525
+ const inputView = import_widgets.TextInput.view(model.input);
526
+ lines.push(`${prompt}${inputView}`);
527
+ return lines.join("\n");
528
+ }
529
+ function renderMessage(msg, theme, width) {
530
+ const lines = [];
531
+ if (msg.role === "user") {
532
+ lines.push(theme.styles.accent.render("You:"));
533
+ lines.push(msg.content);
534
+ } else if (msg.role === "assistant") {
535
+ lines.push(theme.styles.heading.render("Kaldi:"));
536
+ lines.push((0, import_markdown.render)(msg.content, { width }));
537
+ } else if (msg.role === "tool") {
538
+ lines.push(theme.styles.toolName.render(`[${msg.toolName}]`));
539
+ const output = msg.content.length > 500 ? msg.content.slice(0, 500) + "\n...(truncated)" : msg.content;
540
+ lines.push(theme.styles.toolOutput.render(output));
541
+ }
542
+ return lines.join("\n");
543
+ }
544
+ function createApp(options) {
545
+ return new import_tui.Program({
546
+ init: () => init(options),
547
+ update,
548
+ view
549
+ });
550
+ }
551
+
552
+ // src/index.ts
553
+ var import_tui2 = require("@averagejoeslab/tui");
554
+ var import_style3 = require("@averagejoeslab/style");
555
+ var import_markdown2 = require("@averagejoeslab/markdown");
556
+ // Annotate the CommonJS export names for ESM import in node:
557
+ 0 && (module.exports = {
558
+ Cmd,
559
+ DarkTheme,
560
+ KALDI,
561
+ KALDI_COLORS,
562
+ KaldiTheme,
563
+ Key,
564
+ LightTheme,
565
+ Program,
566
+ Style,
567
+ createApp,
568
+ getGoodbyeBanner,
569
+ getKaldi,
570
+ getPrompt,
571
+ getTheme,
572
+ getWelcomeBanner,
573
+ init,
574
+ renderMarkdown,
575
+ themes,
576
+ update,
577
+ view
578
+ });
package/dist/index.js ADDED
@@ -0,0 +1,532 @@
1
+ // src/app.ts
2
+ import { Program } from "@averagejoeslab/tui";
3
+ import { Spinner, TextInput, Viewport } from "@averagejoeslab/widgets";
4
+ import { render as renderMarkdown } from "@averagejoeslab/markdown";
5
+ import { getTerminalSize } from "@averagejoeslab/term";
6
+
7
+ // src/themes/index.ts
8
+ import { Style } from "@averagejoeslab/style";
9
+ var KaldiTheme = {
10
+ name: "kaldi",
11
+ colors: {
12
+ primary: [255, 250, 240],
13
+ // Cream white (like Kaldi's fur)
14
+ secondary: [139, 119, 101],
15
+ // Light brown
16
+ accent: [210, 180, 140],
17
+ // Tan
18
+ text: [245, 245, 245],
19
+ // Off-white
20
+ textDim: [169, 169, 169],
21
+ // Gray
22
+ background: [30, 30, 30],
23
+ // Dark
24
+ success: [144, 238, 144],
25
+ // Light green
26
+ error: [255, 99, 71],
27
+ // Tomato red
28
+ warning: [255, 215, 0],
29
+ // Gold
30
+ info: [135, 206, 235]
31
+ // Sky blue
32
+ },
33
+ styles: {
34
+ heading: new Style().foregroundRGB(255, 250, 240).bold(),
35
+ text: new Style().foregroundRGB(245, 245, 245),
36
+ dim: new Style().foregroundRGB(169, 169, 169),
37
+ accent: new Style().foregroundRGB(210, 180, 140),
38
+ success: new Style().foregroundRGB(144, 238, 144),
39
+ error: new Style().foregroundRGB(255, 99, 71),
40
+ warning: new Style().foregroundRGB(255, 215, 0),
41
+ info: new Style().foregroundRGB(135, 206, 235),
42
+ code: new Style().foregroundRGB(255, 250, 240).background(52),
43
+ link: new Style().foregroundRGB(135, 206, 235).underline(),
44
+ prompt: new Style().foregroundRGB(210, 180, 140).bold(),
45
+ toolName: new Style().foregroundRGB(135, 206, 235).bold(),
46
+ toolOutput: new Style().foregroundRGB(169, 169, 169)
47
+ }
48
+ };
49
+ var DarkTheme = {
50
+ name: "dark",
51
+ colors: {
52
+ primary: [97, 175, 239],
53
+ // Blue
54
+ secondary: [152, 195, 121],
55
+ // Green
56
+ accent: [229, 192, 123],
57
+ // Yellow
58
+ text: [220, 220, 220],
59
+ textDim: [128, 128, 128],
60
+ background: [30, 30, 30],
61
+ success: [152, 195, 121],
62
+ error: [224, 108, 117],
63
+ warning: [229, 192, 123],
64
+ info: [97, 175, 239]
65
+ },
66
+ styles: {
67
+ heading: new Style().foregroundRGB(97, 175, 239).bold(),
68
+ text: new Style().foregroundRGB(220, 220, 220),
69
+ dim: new Style().foregroundRGB(128, 128, 128),
70
+ accent: new Style().foregroundRGB(229, 192, 123),
71
+ success: new Style().foregroundRGB(152, 195, 121),
72
+ error: new Style().foregroundRGB(224, 108, 117),
73
+ warning: new Style().foregroundRGB(229, 192, 123),
74
+ info: new Style().foregroundRGB(97, 175, 239),
75
+ code: new Style().foregroundRGB(220, 220, 220).background(236),
76
+ link: new Style().foregroundRGB(97, 175, 239).underline(),
77
+ prompt: new Style().foregroundRGB(152, 195, 121).bold(),
78
+ toolName: new Style().foregroundRGB(97, 175, 239).bold(),
79
+ toolOutput: new Style().foregroundRGB(128, 128, 128)
80
+ }
81
+ };
82
+ var LightTheme = {
83
+ name: "light",
84
+ colors: {
85
+ primary: [0, 95, 184],
86
+ // Blue
87
+ secondary: [40, 167, 69],
88
+ // Green
89
+ accent: [202, 138, 4],
90
+ // Amber
91
+ text: [33, 33, 33],
92
+ textDim: [117, 117, 117],
93
+ background: [255, 255, 255],
94
+ success: [40, 167, 69],
95
+ error: [220, 53, 69],
96
+ warning: [202, 138, 4],
97
+ info: [0, 95, 184]
98
+ },
99
+ styles: {
100
+ heading: new Style().foregroundRGB(0, 95, 184).bold(),
101
+ text: new Style().foregroundRGB(33, 33, 33),
102
+ dim: new Style().foregroundRGB(117, 117, 117),
103
+ accent: new Style().foregroundRGB(202, 138, 4),
104
+ success: new Style().foregroundRGB(40, 167, 69),
105
+ error: new Style().foregroundRGB(220, 53, 69),
106
+ warning: new Style().foregroundRGB(202, 138, 4),
107
+ info: new Style().foregroundRGB(0, 95, 184),
108
+ code: new Style().foregroundRGB(33, 33, 33).background(253),
109
+ link: new Style().foregroundRGB(0, 95, 184).underline(),
110
+ prompt: new Style().foregroundRGB(40, 167, 69).bold(),
111
+ toolName: new Style().foregroundRGB(0, 95, 184).bold(),
112
+ toolOutput: new Style().foregroundRGB(117, 117, 117)
113
+ }
114
+ };
115
+ function getTheme(name) {
116
+ switch (name) {
117
+ case "kaldi":
118
+ return KaldiTheme;
119
+ case "light":
120
+ return LightTheme;
121
+ case "dark":
122
+ default:
123
+ return DarkTheme;
124
+ }
125
+ }
126
+ var themes = {
127
+ kaldi: KaldiTheme,
128
+ dark: DarkTheme,
129
+ light: LightTheme
130
+ };
131
+
132
+ // src/components/kaldi.ts
133
+ import { Style as Style2 } from "@averagejoeslab/style";
134
+ var KALDI = {
135
+ normal: `
136
+ / \\__
137
+ ( @\\___
138
+ / O
139
+ / (_____/
140
+ /_____/ U
141
+ `,
142
+ thinking: `
143
+ / \\__
144
+ ( @\\___ ...
145
+ / O
146
+ / (_____/
147
+ /_____/ U
148
+ `,
149
+ happy: `
150
+ / \\__
151
+ ( @\\___ woof!
152
+ / O
153
+ / (_____/
154
+ /_____/ U
155
+ `,
156
+ working: `
157
+ / \\__
158
+ ( @\\___ *typing*
159
+ / O
160
+ / (_____/
161
+ /_____/ U
162
+ `,
163
+ error: `
164
+ / \\__
165
+ ( @\\___ :(
166
+ / O
167
+ / (_____/
168
+ /_____/ U
169
+ `,
170
+ small: ` /\\_/\\
171
+ ( o.o )
172
+ > ^ <`
173
+ };
174
+ var KALDI_COLORS = {
175
+ body: [255, 250, 240],
176
+ // Cream/white
177
+ nose: [0, 0, 0],
178
+ // Black
179
+ accent: [139, 119, 101]
180
+ // Light brown
181
+ };
182
+ function getKaldi(variant = "normal") {
183
+ const art = KALDI[variant];
184
+ const style = new Style2().foregroundRGB(...KALDI_COLORS.body).bold();
185
+ return style.render(art);
186
+ }
187
+ function getWelcomeBanner(version = "0.1.0") {
188
+ const style = new Style2().foregroundRGB(...KALDI_COLORS.body);
189
+ const accentStyle = new Style2().foregroundRGB(...KALDI_COLORS.accent);
190
+ const banner = `
191
+ ${style.render(KALDI.normal)}
192
+ ${accentStyle.render(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")}
193
+ ${accentStyle.render(" \u2551")} ${style.render("Puppuccino")} ${accentStyle.render("- AI Coding Assistant")} ${accentStyle.render("\u2551")}
194
+ ${accentStyle.render(" \u2551")} ${style.render(`v${version}`)} ${accentStyle.render("| Powered by Kaldi")} ${accentStyle.render("\u2551")}
195
+ ${accentStyle.render(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")}
196
+ `;
197
+ return banner;
198
+ }
199
+ function getGoodbyeBanner() {
200
+ const style = new Style2().foregroundRGB(...KALDI_COLORS.body);
201
+ return `
202
+ ${style.render(KALDI.happy)}
203
+ ${style.render("Goodbye! Kaldi will miss you!")}
204
+ `;
205
+ }
206
+ function getPrompt() {
207
+ return "\u{1F415}\u200D\u{1F9BA}\u276F ";
208
+ }
209
+
210
+ // src/app.ts
211
+ function init(options) {
212
+ const { width, height } = getTerminalSize();
213
+ const theme = getTheme(options?.theme || "kaldi");
214
+ const model = {
215
+ input: TextInput.init({
216
+ placeholder: "Ask Kaldi anything...",
217
+ width: width - 4
218
+ }),
219
+ viewport: Viewport.init({
220
+ width: width - 2,
221
+ height: height - 6
222
+ }),
223
+ spinner: Spinner.init({ style: "dots" }),
224
+ status: "idle",
225
+ messages: [],
226
+ theme,
227
+ showWelcome: true,
228
+ width,
229
+ height
230
+ };
231
+ const spinnerCmd = Spinner.tick(model.spinner);
232
+ return [model, spinnerCmd ? () => ({ type: "spinner", msg: spinnerCmd }) : null];
233
+ }
234
+ function update(msg, model) {
235
+ switch (msg.type) {
236
+ case "key": {
237
+ if (msg.key.ctrl && msg.key.name === "c") {
238
+ return [model, () => ({ type: "quit" })];
239
+ }
240
+ if (msg.key.name === "escape") {
241
+ if (model.showWelcome) {
242
+ return [{ ...model, showWelcome: false }, null];
243
+ }
244
+ }
245
+ if (msg.key.name === "enter" && model.status === "idle") {
246
+ const text = TextInput.value(model.input);
247
+ if (text.trim()) {
248
+ return update({ type: "submit" }, model);
249
+ }
250
+ }
251
+ if (model.showWelcome) {
252
+ return [{ ...model, showWelcome: false }, null];
253
+ }
254
+ const inputMsg = TextInput.handleKey(msg.key);
255
+ if (inputMsg) {
256
+ return update({ type: "input", msg: inputMsg }, model);
257
+ }
258
+ return [model, null];
259
+ }
260
+ case "input": {
261
+ const [newInput, inputCmd] = TextInput.update(msg.msg, model.input);
262
+ const cmd = inputCmd ? () => ({ type: "input", msg: inputCmd }) : null;
263
+ return [{ ...model, input: newInput }, cmd];
264
+ }
265
+ case "viewport": {
266
+ const [newViewport, viewportCmd] = Viewport.update(msg.msg, model.viewport);
267
+ const cmd = viewportCmd ? () => ({ type: "viewport", msg: viewportCmd }) : null;
268
+ return [{ ...model, viewport: newViewport }, cmd];
269
+ }
270
+ case "spinner": {
271
+ const [newSpinner, spinnerCmd] = Spinner.update(msg.msg, model.spinner);
272
+ const cmd = spinnerCmd ? () => ({ type: "spinner", msg: spinnerCmd }) : null;
273
+ return [{ ...model, spinner: newSpinner }, cmd];
274
+ }
275
+ case "submit": {
276
+ const text = TextInput.value(model.input);
277
+ if (!text.trim()) return [model, null];
278
+ if (text.startsWith("/")) {
279
+ return handleCommand(text.slice(1), model);
280
+ }
281
+ const userMessage = {
282
+ role: "user",
283
+ content: text,
284
+ timestamp: /* @__PURE__ */ new Date()
285
+ };
286
+ return [{
287
+ ...model,
288
+ input: TextInput.init({
289
+ placeholder: "Ask Kaldi anything...",
290
+ width: model.width - 4
291
+ }),
292
+ messages: [...model.messages, userMessage],
293
+ status: "thinking"
294
+ }, null];
295
+ }
296
+ case "agent_event": {
297
+ return handleAgentEvent(msg.event, model);
298
+ }
299
+ case "resize": {
300
+ return [{
301
+ ...model,
302
+ width: msg.width,
303
+ height: msg.height,
304
+ input: TextInput.init({
305
+ placeholder: "Ask Kaldi anything...",
306
+ width: msg.width - 4,
307
+ value: TextInput.value(model.input)
308
+ }),
309
+ viewport: Viewport.init({
310
+ width: msg.width - 2,
311
+ height: msg.height - 6,
312
+ content: model.viewport.content
313
+ })
314
+ }, null];
315
+ }
316
+ case "clear": {
317
+ return [{
318
+ ...model,
319
+ messages: [],
320
+ status: "idle",
321
+ errorMessage: void 0
322
+ }, null];
323
+ }
324
+ case "error": {
325
+ return [{
326
+ ...model,
327
+ status: "error",
328
+ errorMessage: msg.error
329
+ }, null];
330
+ }
331
+ case "dismiss_welcome": {
332
+ return [{ ...model, showWelcome: false }, null];
333
+ }
334
+ case "quit": {
335
+ return [model, null];
336
+ }
337
+ default:
338
+ return [model, null];
339
+ }
340
+ }
341
+ function handleCommand(cmd, model) {
342
+ const [command, ...args] = cmd.split(/\s+/);
343
+ switch (command.toLowerCase()) {
344
+ case "clear":
345
+ case "c":
346
+ return update({ type: "clear" }, model);
347
+ case "quit":
348
+ case "q":
349
+ case "exit":
350
+ return update({ type: "quit" }, model);
351
+ case "help":
352
+ case "h":
353
+ case "?": {
354
+ const helpMessage = {
355
+ role: "assistant",
356
+ content: `## Available Commands
357
+
358
+ - \`/clear\` or \`/c\` - Clear conversation
359
+ - \`/quit\` or \`/q\` - Exit Puppuccino
360
+ - \`/help\` or \`/?\` - Show this help
361
+
362
+ Just type your message and press Enter to chat with Kaldi!`,
363
+ timestamp: /* @__PURE__ */ new Date()
364
+ };
365
+ return [{
366
+ ...model,
367
+ input: TextInput.init({
368
+ placeholder: "Ask Kaldi anything...",
369
+ width: model.width - 4
370
+ }),
371
+ messages: [...model.messages, helpMessage]
372
+ }, null];
373
+ }
374
+ default: {
375
+ const errorMessage = {
376
+ role: "assistant",
377
+ content: `Unknown command: \`/${command}\`. Type \`/help\` for available commands.`,
378
+ timestamp: /* @__PURE__ */ new Date()
379
+ };
380
+ return [{
381
+ ...model,
382
+ input: TextInput.init({
383
+ placeholder: "Ask Kaldi anything...",
384
+ width: model.width - 4
385
+ }),
386
+ messages: [...model.messages, errorMessage]
387
+ }, null];
388
+ }
389
+ }
390
+ }
391
+ function handleAgentEvent(event, model) {
392
+ switch (event.type) {
393
+ case "thinking":
394
+ return [{ ...model, status: "thinking" }, null];
395
+ case "text": {
396
+ const messages = [...model.messages];
397
+ const lastMsg = messages[messages.length - 1];
398
+ if (lastMsg && lastMsg.role === "assistant" && !lastMsg.toolName) {
399
+ lastMsg.content += event.content;
400
+ } else {
401
+ messages.push({
402
+ role: "assistant",
403
+ content: event.content,
404
+ timestamp: /* @__PURE__ */ new Date()
405
+ });
406
+ }
407
+ return [{ ...model, messages, status: "idle" }, null];
408
+ }
409
+ case "tool_start":
410
+ return [{
411
+ ...model,
412
+ status: "tool",
413
+ currentTool: event.name
414
+ }, null];
415
+ case "tool_result": {
416
+ const toolMessage = {
417
+ role: "tool",
418
+ content: event.result.success ? event.result.output : `Error: ${event.result.error}`,
419
+ toolName: event.name,
420
+ timestamp: /* @__PURE__ */ new Date()
421
+ };
422
+ return [{
423
+ ...model,
424
+ messages: [...model.messages, toolMessage],
425
+ status: "thinking",
426
+ currentTool: void 0
427
+ }, null];
428
+ }
429
+ case "tool_denied": {
430
+ const deniedMessage = {
431
+ role: "tool",
432
+ content: `Permission denied: ${event.reason}`,
433
+ toolName: event.name,
434
+ timestamp: /* @__PURE__ */ new Date()
435
+ };
436
+ return [{
437
+ ...model,
438
+ messages: [...model.messages, deniedMessage],
439
+ status: "thinking",
440
+ currentTool: void 0
441
+ }, null];
442
+ }
443
+ case "error":
444
+ return [{
445
+ ...model,
446
+ status: "error",
447
+ errorMessage: event.error
448
+ }, null];
449
+ case "done":
450
+ return [{ ...model, status: "idle" }, null];
451
+ default:
452
+ return [model, null];
453
+ }
454
+ }
455
+ function view(model) {
456
+ const { theme } = model;
457
+ const lines = [];
458
+ if (model.showWelcome) {
459
+ return getWelcomeBanner() + "\n\nPress any key to continue...";
460
+ }
461
+ lines.push(theme.styles.heading.render("\u2500".repeat(model.width)));
462
+ const chatLines = [];
463
+ for (const msg of model.messages) {
464
+ chatLines.push(renderMessage(msg, theme, model.width - 4));
465
+ chatLines.push("");
466
+ }
467
+ if (model.status === "thinking") {
468
+ const spinner = Spinner.view(model.spinner);
469
+ chatLines.push(`${spinner} ${theme.styles.dim.render("Kaldi is thinking...")}`);
470
+ } else if (model.status === "tool" && model.currentTool) {
471
+ const spinner = Spinner.view(model.spinner);
472
+ chatLines.push(`${spinner} ${theme.styles.toolName.render(model.currentTool)}`);
473
+ } else if (model.status === "error" && model.errorMessage) {
474
+ chatLines.push(theme.styles.error.render(`Error: ${model.errorMessage}`));
475
+ }
476
+ const viewportModel = Viewport.setContent(model.viewport, chatLines.join("\n"));
477
+ lines.push(Viewport.view(viewportModel));
478
+ lines.push(theme.styles.heading.render("\u2500".repeat(model.width)));
479
+ const prompt = getPrompt();
480
+ const inputView = TextInput.view(model.input);
481
+ lines.push(`${prompt}${inputView}`);
482
+ return lines.join("\n");
483
+ }
484
+ function renderMessage(msg, theme, width) {
485
+ const lines = [];
486
+ if (msg.role === "user") {
487
+ lines.push(theme.styles.accent.render("You:"));
488
+ lines.push(msg.content);
489
+ } else if (msg.role === "assistant") {
490
+ lines.push(theme.styles.heading.render("Kaldi:"));
491
+ lines.push(renderMarkdown(msg.content, { width }));
492
+ } else if (msg.role === "tool") {
493
+ lines.push(theme.styles.toolName.render(`[${msg.toolName}]`));
494
+ const output = msg.content.length > 500 ? msg.content.slice(0, 500) + "\n...(truncated)" : msg.content;
495
+ lines.push(theme.styles.toolOutput.render(output));
496
+ }
497
+ return lines.join("\n");
498
+ }
499
+ function createApp(options) {
500
+ return new Program({
501
+ init: () => init(options),
502
+ update,
503
+ view
504
+ });
505
+ }
506
+
507
+ // src/index.ts
508
+ import { Program as Program2, Key as Key2, Cmd as Cmd2 } from "@averagejoeslab/tui";
509
+ import { Style as Style3 } from "@averagejoeslab/style";
510
+ import { render } from "@averagejoeslab/markdown";
511
+ export {
512
+ Cmd2 as Cmd,
513
+ DarkTheme,
514
+ KALDI,
515
+ KALDI_COLORS,
516
+ KaldiTheme,
517
+ Key2 as Key,
518
+ LightTheme,
519
+ Program2 as Program,
520
+ Style3 as Style,
521
+ createApp,
522
+ getGoodbyeBanner,
523
+ getKaldi,
524
+ getPrompt,
525
+ getTheme,
526
+ getWelcomeBanner,
527
+ init,
528
+ render as renderMarkdown,
529
+ themes,
530
+ update,
531
+ view
532
+ };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@averagejoeslab/puppuccino-tui",
3
+ "version": "0.1.0",
4
+ "description": "Terminal UI for Puppuccino AI coding agent",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "build": "tsup src/index.ts --format cjs,esm --dts",
18
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
19
+ "test": "vitest run",
20
+ "typecheck": "tsc --noEmit",
21
+ "clean": "rm -rf dist"
22
+ },
23
+ "dependencies": {
24
+ "@averagejoeslab/puppuccino-core": "^0.1.0",
25
+ "@averagejoeslab/ansi": "^1.0.0",
26
+ "@averagejoeslab/term": "^1.0.0",
27
+ "@averagejoeslab/style": "^1.0.0",
28
+ "@averagejoeslab/tui": "^1.0.0",
29
+ "@averagejoeslab/widgets": "^1.0.0",
30
+ "@averagejoeslab/markdown": "^1.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^20.0.0",
34
+ "tsup": "^8.0.0",
35
+ "typescript": "^5.4.0",
36
+ "vitest": "^2.0.0"
37
+ },
38
+ "files": [
39
+ "dist"
40
+ ]
41
+ }