@takara-ai/miru-code 1.0.5 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Terminal helpers: ANSI styling, display-width layout, and brand RGB colors.
3
+ * Brand colors use truecolor when COLORTERM says so; otherwise 256-color palette
4
+ * (Terminal.app often omits COLORTERM but still renders 38;5 correctly).
5
+ */
6
+ const ANSI = {
7
+ reset: "\x1b[0m",
8
+ bold: "\x1b[1m",
9
+ dim: "\x1b[2m",
10
+ cyan: "\x1b[36m",
11
+ green: "\x1b[32m",
12
+ red: "\x1b[31m",
13
+ yellow: "\x1b[33m",
14
+ magenta: "\x1b[35m",
15
+ } as const;
16
+
17
+ export function colorEnabled(stream: NodeJS.WriteStream = process.stdout): boolean {
18
+ // https://no-color.org — any NO_COLOR value disables styling.
19
+ return stream.isTTY && process.env.NO_COLOR === undefined;
20
+ }
21
+
22
+ function paint(text: string, code: string, stream?: NodeJS.WriteStream): string {
23
+ if (!colorEnabled(stream)) {
24
+ return text;
25
+ }
26
+ return `${code}${text}${ANSI.reset}`;
27
+ }
28
+
29
+ const style =
30
+ (code: string) =>
31
+ (text: string, stream?: NodeJS.WriteStream): string =>
32
+ paint(text, code, stream);
33
+
34
+ export const bold = style(ANSI.bold);
35
+ export const dim = style(ANSI.dim);
36
+ export const cyan = style(ANSI.cyan);
37
+ export const green = style(ANSI.green);
38
+ export const red = style(ANSI.red);
39
+ export const yellow = style(ANSI.yellow);
40
+ export const magenta = style(ANSI.magenta);
41
+
42
+ function charWidth(codePoint: number): number {
43
+ // East Asian and emoji runes count as two columns in most terminals.
44
+ if (codePoint === 0) {
45
+ return 0;
46
+ }
47
+ if (
48
+ (codePoint >= 0x0300 && codePoint <= 0x036f) ||
49
+ (codePoint >= 0x200b && codePoint <= 0x200f) ||
50
+ codePoint === 0xfeff
51
+ ) {
52
+ return 0;
53
+ }
54
+ if (
55
+ (codePoint >= 0x1100 && codePoint <= 0x115f) ||
56
+ (codePoint >= 0x2e80 && codePoint <= 0x303e) ||
57
+ (codePoint >= 0x3041 && codePoint <= 0x33ff) ||
58
+ (codePoint >= 0x3400 && codePoint <= 0x4dbf) ||
59
+ (codePoint >= 0x4e00 && codePoint <= 0x9fff) ||
60
+ (codePoint >= 0xa000 && codePoint <= 0xa4cf) ||
61
+ (codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
62
+ (codePoint >= 0xf900 && codePoint <= 0xfaff) ||
63
+ (codePoint >= 0xfe30 && codePoint <= 0xfe4f) ||
64
+ (codePoint >= 0xff00 && codePoint <= 0xff60) ||
65
+ (codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
66
+ (codePoint >= 0x1f300 && codePoint <= 0x1faff) ||
67
+ (codePoint >= 0x20000 && codePoint <= 0x3fffd)
68
+ ) {
69
+ return 2;
70
+ }
71
+ return 1;
72
+ }
73
+
74
+ export function displayWidth(text: string): number {
75
+ // Ignore ANSI SGR sequences when measuring layout width.
76
+ let width = 0;
77
+ let inEscape = false;
78
+ for (const char of text) {
79
+ if (inEscape) {
80
+ if (char === "m") {
81
+ inEscape = false;
82
+ }
83
+ continue;
84
+ }
85
+ if (char === "\x1b") {
86
+ inEscape = true;
87
+ continue;
88
+ }
89
+ width += charWidth(char.codePointAt(0) ?? 0);
90
+ }
91
+ return width;
92
+ }
93
+
94
+ export function padLineToWidth(text: string, width: number): string {
95
+ const pad = Math.max(0, width - displayWidth(text));
96
+ return `${text}${" ".repeat(pad)}`;
97
+ }
98
+
99
+ /** Parse `#rrggbb` into `[r, g, b]` for terminal truecolor/256-color SGR. */
100
+ export function hexToRgb(hex: string): readonly [number, number, number] {
101
+ const normalized = hex.trim().replace(/^#/, "");
102
+ if (!/^[0-9a-fA-F]{6}$/.test(normalized)) {
103
+ throw new Error(`Invalid hex color: ${hex}`);
104
+ }
105
+ return [
106
+ Number.parseInt(normalized.slice(0, 2), 16),
107
+ Number.parseInt(normalized.slice(2, 4), 16),
108
+ Number.parseInt(normalized.slice(4, 6), 16),
109
+ ];
110
+ }
111
+
112
+ export function cropCommonLeading(rows: readonly string[]): string[] {
113
+ // Trim shared left margin from sampled art without shifting rows relative to each other.
114
+ const trimmed = rows.map((row) => row.trimEnd());
115
+ let minLead = trimmed[0]?.length ?? 0;
116
+ let hasContent = false;
117
+
118
+ for (const row of trimmed) {
119
+ if (!row.trim()) {
120
+ continue;
121
+ }
122
+ hasContent = true;
123
+ minLead = Math.min(minLead, row.length - row.trimStart().length);
124
+ }
125
+
126
+ if (!hasContent) {
127
+ return [...trimmed];
128
+ }
129
+ return trimmed.map((row) => row.slice(minLead));
130
+ }
131
+
132
+ function rgbToAnsi256(r: number, g: number, b: number): number {
133
+ const avg = Math.round((r + g + b) / 3);
134
+ if (Math.abs(r - g) <= 3 && Math.abs(g - b) <= 3) {
135
+ if (avg < 8) {
136
+ return 16;
137
+ }
138
+ if (avg > 248) {
139
+ return 231;
140
+ }
141
+ return Math.round(((avg - 8) / 247) * 24) + 232;
142
+ }
143
+ return (
144
+ 16 + 36 * Math.round((r / 255) * 5) + 6 * Math.round((g / 255) * 5) + Math.round((b / 255) * 5)
145
+ );
146
+ }
147
+
148
+ function brandSgr(rgb: readonly [number, number, number]): string {
149
+ const [r, g, b] = rgb;
150
+ const colorTerm = process.env.COLORTERM?.toLowerCase() ?? "";
151
+ // termstandard/colors: COLORTERM=truecolor|24bit → 24-bit SGR; else 256-color cube.
152
+ const truecolor =
153
+ process.env.FORCE_COLOR === "3" ||
154
+ colorTerm.includes("truecolor") ||
155
+ colorTerm.includes("24bit");
156
+ if (truecolor) {
157
+ return `\x1b[1;38;2;${r};${g};${b}m`;
158
+ }
159
+ return `\x1b[1;38;5;${rgbToAnsi256(r, g, b)}m`;
160
+ }
161
+
162
+ export function brandColor(
163
+ text: string,
164
+ rgb: readonly [number, number, number],
165
+ stream: NodeJS.WriteStream = process.stdout,
166
+ ): string {
167
+ if (!colorEnabled(stream)) {
168
+ return text;
169
+ }
170
+ return `${brandSgr(rgb)}${text}${ANSI.reset}`;
171
+ }
package/src/version.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { readFile, writeFile } from "node:fs/promises";
2
1
  import { join } from "node:path";
3
2
  import packageJson from "../package.json";
4
3
  import { resolveCacheFolder } from "./cache.ts";
@@ -49,13 +48,25 @@ export function isVersionNewer(latest: string, current: string): boolean {
49
48
  return latest !== current;
50
49
  }
51
50
 
52
- for (let i = 0; i < 3; i++) {
53
- if (a[i]! > b[i]!) {
54
- return true;
55
- }
56
- if (a[i]! < b[i]!) {
57
- return false;
58
- }
51
+ const [a0, a1, a2] = a;
52
+ const [b0, b1, b2] = b;
53
+ if (a0 > b0) {
54
+ return true;
55
+ }
56
+ if (a0 < b0) {
57
+ return false;
58
+ }
59
+ if (a1 > b1) {
60
+ return true;
61
+ }
62
+ if (a1 < b1) {
63
+ return false;
64
+ }
65
+ if (a2 > b2) {
66
+ return true;
67
+ }
68
+ if (a2 < b2) {
69
+ return false;
59
70
  }
60
71
  return false;
61
72
  }
@@ -66,7 +77,7 @@ function updateCheckPath(): string {
66
77
 
67
78
  async function readUpdateCheckCache(): Promise<UpdateCheckCache | null> {
68
79
  try {
69
- const raw = await readFile(updateCheckPath(), "utf-8");
80
+ const raw = await Bun.file(updateCheckPath()).text();
70
81
  const parsed = JSON.parse(raw) as UpdateCheckCache;
71
82
  if (typeof parsed.checkedAt !== "number" || typeof parsed.latest !== "string") {
72
83
  return null;
@@ -79,7 +90,7 @@ async function readUpdateCheckCache(): Promise<UpdateCheckCache | null> {
79
90
 
80
91
  async function writeUpdateCheckCache(latest: string): Promise<void> {
81
92
  const payload: UpdateCheckCache = { checkedAt: Date.now(), latest };
82
- await writeFile(updateCheckPath(), `${JSON.stringify(payload)}\n`, "utf-8");
93
+ await Bun.write(updateCheckPath(), `${JSON.stringify(payload)}\n`);
83
94
  }
84
95
 
85
96
  export async function fetchLatestPublishedVersion(): Promise<string> {