casabot 1.1.11 → 1.1.13

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.
@@ -0,0 +1,2 @@
1
+ export declare function renderBrailleLogo(logoPath: string, maxWidth: number): Promise<string>;
2
+ //# sourceMappingURL=braille-logo.d.ts.map
@@ -0,0 +1,59 @@
1
+ import { Jimp, intToRGBA } from "jimp";
2
+ import chalk from "chalk";
3
+ const BRAILLE_BASE = 0x2800;
4
+ const BRAILLE_MAP = [
5
+ [0x01, 0x08],
6
+ [0x02, 0x10],
7
+ [0x04, 0x20],
8
+ [0x40, 0x80],
9
+ ];
10
+ export async function renderBrailleLogo(logoPath, maxWidth) {
11
+ const image = await Jimp.read(logoPath);
12
+ const targetPixelWidth = maxWidth * 2;
13
+ image.resize({ w: targetPixelWidth });
14
+ const { width, height } = image.bitmap;
15
+ const lines = [];
16
+ for (let y = 0; y < height; y += 4) {
17
+ let line = "";
18
+ for (let x = 0; x < width; x += 2) {
19
+ let pattern = 0;
20
+ let rSum = 0;
21
+ let gSum = 0;
22
+ let bSum = 0;
23
+ let onCount = 0;
24
+ for (let row = 0; row < 4; row++) {
25
+ for (let col = 0; col < 2; col++) {
26
+ const px = x + col;
27
+ const py = y + row;
28
+ if (px >= width || py >= height)
29
+ continue;
30
+ const { r, g, b, a } = intToRGBA(image.getPixelColor(px, py));
31
+ const brightness = 0.299 * r + 0.587 * g + 0.114 * b;
32
+ if (a > 20 && brightness > 30) {
33
+ pattern |= BRAILLE_MAP[row][col];
34
+ rSum += r;
35
+ gSum += g;
36
+ bSum += b;
37
+ onCount++;
38
+ }
39
+ }
40
+ }
41
+ if (onCount > 0) {
42
+ const ch = String.fromCharCode(BRAILLE_BASE + pattern);
43
+ const avgR = Math.round(rSum / onCount);
44
+ const avgG = Math.round(gSum / onCount);
45
+ const avgB = Math.round(bSum / onCount);
46
+ line += chalk.rgb(avgR, avgG, avgB)(ch);
47
+ }
48
+ else {
49
+ line += " ";
50
+ }
51
+ }
52
+ lines.push(line);
53
+ }
54
+ while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
55
+ lines.pop();
56
+ }
57
+ return lines.join("\n");
58
+ }
59
+ //# sourceMappingURL=braille-logo.js.map
package/dist/cli/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from "module";
3
2
  import { fileURLToPath } from "url";
4
3
  import { dirname, join } from "path";
5
4
  import { Command } from "commander";
@@ -9,18 +8,14 @@ import { loadSkills } from "../skills/loader.js";
9
8
  import { createConversation } from "../history/store.js";
10
9
  import { startTUI } from "../tui/app.js";
11
10
  import { setupWizard } from "./setup.js";
11
+ import { renderBrailleLogo } from "./braille-logo.js";
12
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
13
13
  async function displayLogo() {
14
14
  try {
15
- const require = createRequire(import.meta.url);
16
- const asciify = require("asciify-image");
17
15
  const logoPath = join(__dirname, "..", "..", "Logo2.png");
18
- const ascii = await asciify(logoPath, {
19
- fit: "box",
20
- width: "50%",
21
- color: true,
22
- });
23
- console.log(ascii);
16
+ const maxWidth = Math.min(process.stdout.columns || 80, 160);
17
+ const art = await renderBrailleLogo(logoPath, maxWidth);
18
+ console.log(art);
24
19
  console.log("");
25
20
  }
26
21
  catch {
package/dist/tui/app.js CHANGED
@@ -38,9 +38,6 @@ function getPreview(conv, maxLen = 50) {
38
38
  function HRule({ columns }) {
39
39
  return (_jsx(Box, { paddingX: 1, width: columns, children: _jsx(Text, { dimColor: true, children: "─".repeat(Math.max(columns - 4, 1)) }) }));
40
40
  }
41
- function HeaderBlock({ columns }) {
42
- return (_jsxs(Box, { flexDirection: "column", paddingTop: 1, width: columns, children: [_jsx(Box, { paddingX: 2, children: _jsx(Gradient, { colors: [BRAND_RED, BRAND_BLUE], children: _jsx(Text, { bold: true, children: "✦ CasAbot" }) }) }), _jsx(Box, { paddingX: 2, children: _jsx(Text, { wrap: "wrap", dimColor: true, children: "Cassiopeia A — Freely creates everything, like a supernova explosion." }) }), _jsx(HRule, { columns: columns })] }));
43
- }
44
41
  function UserMessageView({ content, columns }) {
45
42
  return (_jsxs(Box, { flexDirection: "column", paddingX: 2, marginTop: 1, width: columns, children: [_jsx(Text, { color: BRAND_RED, bold: true, children: "▶ You" }), _jsx(Box, { marginLeft: 2, width: Math.max(columns - 6, 10), children: _jsx(Text, { wrap: "wrap", children: content }) })] }));
46
43
  }
@@ -228,23 +225,14 @@ function App({ provider, conversation: initialConversation, skills, }) {
228
225
  });
229
226
  const userCount = messages.filter((m) => m.role === "user").length;
230
227
  const convId = conversationRef.current.id;
231
- const items = useMemo(() => [
232
- { key: `header-${convId}`, type: "header" },
233
- ...messages.map((msg, i) => ({
234
- key: `${convId}-msg-${i}`,
235
- type: "message",
236
- message: msg,
237
- })),
238
- ], [messages, convId]);
228
+ const items = useMemo(() => messages.map((msg, i) => ({
229
+ key: `${convId}-msg-${i}`,
230
+ message: msg,
231
+ })), [messages, convId]);
239
232
  if (mode === "history") {
240
233
  return (_jsx(HistoryBrowser, { columns: columns, currentId: conversationRef.current.id, onSelect: handleHistorySelect, onBack: () => setMode("chat") }));
241
234
  }
242
- return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(Static, { items: items, children: (item) => {
243
- if (item.type === "header") {
244
- return (_jsx(Box, { flexDirection: "column", width: columns, children: _jsx(HeaderBlock, { columns: columns }) }, item.key));
245
- }
246
- return (_jsx(Box, { flexDirection: "column", width: columns, children: _jsx(MessageView, { message: item.message, columns: columns }) }, item.key));
247
- } }), messages.length === 0 && !isProcessing && _jsx(WelcomeHint, { columns: columns }), isProcessing && _jsx(ProcessingIndicator, { columns: columns }), _jsx(HRule, { columns: columns }), _jsx(Box, { paddingX: 1, width: columns, children: _jsxs(Box, { borderStyle: "round", borderColor: isProcessing ? "gray" : BRAND_BLUE, paddingX: 1, width: Math.max(columns - 2, 10), overflow: "hidden", children: [_jsx(Text, { color: BRAND_BLUE, bold: true, children: "❯ " }), _jsx(TextInput, { value: input, onChange: setInput, onSubmit: (val) => {
235
+ return (_jsxs(Box, { flexDirection: "column", width: columns, children: [_jsx(Static, { items: items, children: (item) => (_jsx(Box, { flexDirection: "column", width: columns, children: _jsx(MessageView, { message: item.message, columns: columns }) }, item.key)) }), messages.length === 0 && !isProcessing && _jsx(WelcomeHint, { columns: columns }), isProcessing && _jsx(ProcessingIndicator, { columns: columns }), _jsx(HRule, { columns: columns }), _jsx(Box, { paddingX: 1, width: columns, children: _jsxs(Box, { borderStyle: "round", borderColor: isProcessing ? "gray" : BRAND_BLUE, paddingX: 1, width: Math.max(columns - 2, 10), overflow: "hidden", children: [_jsx(Text, { color: BRAND_BLUE, bold: true, children: "❯ " }), _jsx(TextInput, { value: input, onChange: setInput, onSubmit: (val) => {
248
236
  handleSubmit(val).catch(() => { });
249
237
  }, placeholder: "Type your message\u2026", focus: !isProcessing, showCursor: true })] }) }), _jsxs(Box, { paddingX: 2, width: columns, justifyContent: "space-between", children: [_jsx(Text, { dimColor: true, children: isProcessing
250
238
  ? "ESC / Ctrl+C cancel"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "casabot",
3
- "version": "1.1.11",
3
+ "version": "1.1.13",
4
4
  "description": "CasAbot — Skill-driven multi-agent orchestrator system",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,7 +28,6 @@
28
28
  "license": "Apache-2.0",
29
29
  "dependencies": {
30
30
  "@anthropic-ai/sdk": "^0.52.0",
31
- "asciify-image": "^0.1.10",
32
31
  "chalk": "^5.4.1",
33
32
  "commander": "^13.1.0",
34
33
  "gray-matter": "^4.0.3",
@@ -36,6 +35,7 @@
36
35
  "ink-gradient": "^4.0.0",
37
36
  "ink-spinner": "^5.0.0",
38
37
  "ink-text-input": "^6.0.0",
38
+ "jimp": "^1.6.0",
39
39
  "marked": "^15.0.12",
40
40
  "marked-terminal": "^7.3.0",
41
41
  "openai": "^5.1.0",
@@ -0,0 +1,72 @@
1
+ import { Jimp, intToRGBA } from "jimp";
2
+ import chalk from "chalk";
3
+
4
+ const BRAILLE_BASE = 0x2800;
5
+
6
+ const BRAILLE_MAP = [
7
+ [0x01, 0x08],
8
+ [0x02, 0x10],
9
+ [0x04, 0x20],
10
+ [0x40, 0x80],
11
+ ] as const;
12
+
13
+ export async function renderBrailleLogo(
14
+ logoPath: string,
15
+ maxWidth: number,
16
+ ): Promise<string> {
17
+ const image = await Jimp.read(logoPath);
18
+ const targetPixelWidth = maxWidth * 2;
19
+ image.resize({ w: targetPixelWidth });
20
+
21
+ const { width, height } = image.bitmap;
22
+ const lines: string[] = [];
23
+
24
+ for (let y = 0; y < height; y += 4) {
25
+ let line = "";
26
+
27
+ for (let x = 0; x < width; x += 2) {
28
+ let pattern = 0;
29
+ let rSum = 0;
30
+ let gSum = 0;
31
+ let bSum = 0;
32
+ let onCount = 0;
33
+
34
+ for (let row = 0; row < 4; row++) {
35
+ for (let col = 0; col < 2; col++) {
36
+ const px = x + col;
37
+ const py = y + row;
38
+ if (px >= width || py >= height) continue;
39
+
40
+ const { r, g, b, a } = intToRGBA(image.getPixelColor(px, py));
41
+ const brightness = 0.299 * r + 0.587 * g + 0.114 * b;
42
+
43
+ if (a > 20 && brightness > 30) {
44
+ pattern |= BRAILLE_MAP[row][col];
45
+ rSum += r;
46
+ gSum += g;
47
+ bSum += b;
48
+ onCount++;
49
+ }
50
+ }
51
+ }
52
+
53
+ if (onCount > 0) {
54
+ const ch = String.fromCharCode(BRAILLE_BASE + pattern);
55
+ const avgR = Math.round(rSum / onCount);
56
+ const avgG = Math.round(gSum / onCount);
57
+ const avgB = Math.round(bSum / onCount);
58
+ line += chalk.rgb(avgR, avgG, avgB)(ch);
59
+ } else {
60
+ line += " ";
61
+ }
62
+ }
63
+
64
+ lines.push(line);
65
+ }
66
+
67
+ while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
68
+ lines.pop();
69
+ }
70
+
71
+ return lines.join("\n");
72
+ }
package/src/cli/index.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { createRequire } from "module";
4
3
  import { fileURLToPath } from "url";
5
4
  import { dirname, join } from "path";
6
5
  import { Command } from "commander";
@@ -10,23 +9,16 @@ import { loadSkills } from "../skills/loader.js";
10
9
  import { createConversation } from "../history/store.js";
11
10
  import { startTUI } from "../tui/app.js";
12
11
  import { setupWizard } from "./setup.js";
12
+ import { renderBrailleLogo } from "./braille-logo.js";
13
13
 
14
14
  const __dirname = dirname(fileURLToPath(import.meta.url));
15
15
 
16
16
  async function displayLogo(): Promise<void> {
17
17
  try {
18
- const require = createRequire(import.meta.url);
19
- const asciify = require("asciify-image") as (
20
- path: string,
21
- opts: Record<string, unknown>,
22
- ) => Promise<string>;
23
18
  const logoPath = join(__dirname, "..", "..", "Logo2.png");
24
- const ascii = await asciify(logoPath, {
25
- fit: "box",
26
- width: "50%",
27
- color: true,
28
- });
29
- console.log(ascii);
19
+ const maxWidth = Math.min(process.stdout.columns || 80, 160);
20
+ const art = await renderBrailleLogo(logoPath, maxWidth);
21
+ console.log(art);
30
22
  console.log("");
31
23
  } catch {
32
24
  // Logo display is non-critical; silently skip on failure
package/src/tui/app.tsx CHANGED
@@ -56,24 +56,6 @@ function HRule({ columns }: { columns: number }): React.ReactElement {
56
56
  );
57
57
  }
58
58
 
59
- function HeaderBlock({ columns }: { columns: number }): React.ReactElement {
60
- return (
61
- <Box flexDirection="column" paddingTop={1} width={columns}>
62
- <Box paddingX={2}>
63
- <Gradient colors={[BRAND_RED, BRAND_BLUE]}>
64
- <Text bold>{"✦ CasAbot"}</Text>
65
- </Gradient>
66
- </Box>
67
- <Box paddingX={2}>
68
- <Text wrap="wrap" dimColor>
69
- {"Cassiopeia A — Freely creates everything, like a supernova explosion."}
70
- </Text>
71
- </Box>
72
- <HRule columns={columns} />
73
- </Box>
74
- );
75
- }
76
-
77
59
  function UserMessageView({ content, columns }: { content: string; columns: number }): React.ReactElement {
78
60
  return (
79
61
  <Box flexDirection="column" paddingX={2} marginTop={1} width={columns}>
@@ -364,9 +346,7 @@ function HistoryBrowser({
364
346
  );
365
347
  }
366
348
 
367
- type DisplayItem =
368
- | { key: string; type: "header" }
369
- | { key: string; type: "message"; message: Message };
349
+ type DisplayItem = { key: string; message: Message };
370
350
 
371
351
  type AppMode = "chat" | "history";
372
352
 
@@ -498,16 +478,13 @@ function App({
498
478
  const convId = conversationRef.current.id;
499
479
 
500
480
  const items = useMemo(
501
- (): DisplayItem[] => [
502
- { key: `header-${convId}`, type: "header" },
503
- ...messages.map(
481
+ (): DisplayItem[] =>
482
+ messages.map(
504
483
  (msg, i): DisplayItem => ({
505
484
  key: `${convId}-msg-${i}`,
506
- type: "message",
507
485
  message: msg,
508
486
  }),
509
487
  ),
510
- ],
511
488
  [messages, convId],
512
489
  );
513
490
 
@@ -525,20 +502,11 @@ function App({
525
502
  return (
526
503
  <Box flexDirection="column" width={columns}>
527
504
  <Static items={items}>
528
- {(item) => {
529
- if (item.type === "header") {
530
- return (
531
- <Box key={item.key} flexDirection="column" width={columns}>
532
- <HeaderBlock columns={columns} />
533
- </Box>
534
- );
535
- }
536
- return (
537
- <Box key={item.key} flexDirection="column" width={columns}>
538
- <MessageView message={item.message} columns={columns} />
539
- </Box>
540
- );
541
- }}
505
+ {(item) => (
506
+ <Box key={item.key} flexDirection="column" width={columns}>
507
+ <MessageView message={item.message} columns={columns} />
508
+ </Box>
509
+ )}
542
510
  </Static>
543
511
 
544
512
  {messages.length === 0 && !isProcessing && <WelcomeHint columns={columns} />}