refactor: replace Bun APIs with Node.js equivalents
Replace Bun.argv with process.argv, Bun.file() with fs/promises, Bun.stringWidth with string-width package, and Bun.wrapAnsi with wrap-ansi package for better compatibility.
This commit is contained in:
parent
b212fee200
commit
0c71e2cd54
7 changed files with 39 additions and 18 deletions
12
bun.lock
12
bun.lock
|
|
@ -15,7 +15,9 @@
|
||||||
"happy-dom-without-node": "^14.12.3",
|
"happy-dom-without-node": "^14.12.3",
|
||||||
"marked": "^18.0.4",
|
"marked": "^18.0.4",
|
||||||
"shiki": "^4.0.2",
|
"shiki": "^4.0.2",
|
||||||
|
"string-width": "^8.2.1",
|
||||||
"terminal-link": "^5.0.0",
|
"terminal-link": "^5.0.0",
|
||||||
|
"wrap-ansi": "^10.0.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.4.12",
|
"@biomejs/biome": "2.4.12",
|
||||||
|
|
@ -101,6 +103,8 @@
|
||||||
|
|
||||||
"ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="],
|
"ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="],
|
||||||
|
|
||||||
|
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||||
|
|
||||||
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="],
|
"bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="],
|
||||||
|
|
@ -131,6 +135,8 @@
|
||||||
|
|
||||||
"fast-wrap-ansi": ["fast-wrap-ansi@0.2.0", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w=="],
|
"fast-wrap-ansi": ["fast-wrap-ansi@0.2.0", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w=="],
|
||||||
|
|
||||||
|
"get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="],
|
||||||
|
|
||||||
"happy-dom-without-node": ["happy-dom-without-node@14.12.3", "", { "dependencies": { "entities": "^4.5.0", "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^14.0.0" } }, "sha512-1wp8+GFneT8mBjVnzancXHRuscEUH3vnb38lfCHPxuSu6OiRw1kQzHFbTGYlNCU2YXOlVIlzsS6xg9nAr7Xg6Q=="],
|
"happy-dom-without-node": ["happy-dom-without-node@14.12.3", "", { "dependencies": { "entities": "^4.5.0", "webidl-conversions": "^7.0.0", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^14.0.0" } }, "sha512-1wp8+GFneT8mBjVnzancXHRuscEUH3vnb38lfCHPxuSu6OiRw1kQzHFbTGYlNCU2YXOlVIlzsS6xg9nAr7Xg6Q=="],
|
||||||
|
|
||||||
"has-flag": ["has-flag@5.0.1", "", {}, "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA=="],
|
"has-flag": ["has-flag@5.0.1", "", {}, "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA=="],
|
||||||
|
|
@ -177,8 +183,12 @@
|
||||||
|
|
||||||
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
||||||
|
|
||||||
|
"string-width": ["string-width@8.2.1", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA=="],
|
||||||
|
|
||||||
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
|
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
|
||||||
|
|
||||||
|
"strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
||||||
|
|
||||||
"supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
|
"supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
|
||||||
|
|
||||||
"supports-hyperlinks": ["supports-hyperlinks@4.4.0", "", { "dependencies": { "has-flag": "^5.0.1", "supports-color": "^10.2.2" } }, "sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg=="],
|
"supports-hyperlinks": ["supports-hyperlinks@4.4.0", "", { "dependencies": { "has-flag": "^5.0.1", "supports-color": "^10.2.2" } }, "sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg=="],
|
||||||
|
|
@ -213,6 +223,8 @@
|
||||||
|
|
||||||
"whatwg-url": ["whatwg-url@14.2.0", "", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="],
|
"whatwg-url": ["whatwg-url@14.2.0", "", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="],
|
||||||
|
|
||||||
|
"wrap-ansi": ["wrap-ansi@10.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "string-width": "^8.2.0", "strip-ansi": "^7.1.2" } }, "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ=="],
|
||||||
|
|
||||||
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||||
|
|
||||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||||
|
|
|
||||||
2
index.ts
2
index.ts
|
|
@ -7,7 +7,7 @@ import { Conversation } from "./src/conversation";
|
||||||
import Renderer from "./src/render";
|
import Renderer from "./src/render";
|
||||||
|
|
||||||
const { values: args, positionals } = parseArgs({
|
const { values: args, positionals } = parseArgs({
|
||||||
args: Bun.argv,
|
args: process.argv,
|
||||||
options: {
|
options: {
|
||||||
model: { type: "string" },
|
model: { type: "string" },
|
||||||
m: { type: "string" },
|
m: { type: "string" },
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@
|
||||||
"happy-dom-without-node": "^14.12.3",
|
"happy-dom-without-node": "^14.12.3",
|
||||||
"marked": "^18.0.4",
|
"marked": "^18.0.4",
|
||||||
"shiki": "^4.0.2",
|
"shiki": "^4.0.2",
|
||||||
"terminal-link": "^5.0.0"
|
"string-width": "^8.2.1",
|
||||||
|
"terminal-link": "^5.0.0",
|
||||||
|
"wrap-ansi": "^10.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import { autocomplete, intro, isCancel, outro, text } from "@clack/prompts";
|
import { autocomplete, intro, isCancel, outro, text } from "@clack/prompts";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
|
|
||||||
const home = os.homedir();
|
const home = os.homedir();
|
||||||
const configFile = Bun.file(`${home}/.lmc_config.json`);
|
const configPath = `${home}/.lmc_config.json`;
|
||||||
|
|
||||||
export interface AppConfig {
|
export interface AppConfig {
|
||||||
baseURL: string;
|
baseURL: string;
|
||||||
|
|
@ -36,7 +37,7 @@ export async function initConfig(old: AppConfig | null): Promise<AppConfig> {
|
||||||
|
|
||||||
const config = { baseURL, model } satisfies AppConfig;
|
const config = { baseURL, model } satisfies AppConfig;
|
||||||
|
|
||||||
await configFile.write(JSON.stringify(config, null, " "));
|
await fs.writeFile(configPath, JSON.stringify(config, null, " "));
|
||||||
|
|
||||||
outro(chalk.green("Configuration saved!"));
|
outro(chalk.green("Configuration saved!"));
|
||||||
|
|
||||||
|
|
@ -63,10 +64,11 @@ export async function loadConfig(
|
||||||
): Promise<AppConfig | null> {
|
): Promise<AppConfig | null> {
|
||||||
let config: AppConfig;
|
let config: AppConfig;
|
||||||
try {
|
try {
|
||||||
config = await configFile.json();
|
const text = await fs.readFile(configPath);
|
||||||
|
config = JSON.parse(text.toString("utf8"));
|
||||||
} catch {
|
} catch {
|
||||||
if (configFlag) return null;
|
if (configFlag) return null;
|
||||||
console.error(`Config not found at ${configFile.name}`);
|
console.error(`Config not found at ${configPath}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import type { ModelMessage } from "ai";
|
import type { ModelMessage } from "ai";
|
||||||
|
|
||||||
const home = os.homedir();
|
const home = os.homedir();
|
||||||
const convoFile = Bun.file(`${home}/.lmc_convo.json`);
|
const convoPath = `${home}/.lmc_convo.json`;
|
||||||
|
|
||||||
export interface IConversation {
|
export interface IConversation {
|
||||||
model: string;
|
model: string;
|
||||||
|
|
@ -16,7 +17,8 @@ export class Conversation implements IConversation {
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
try {
|
try {
|
||||||
const convo: IConversation = await convoFile.json();
|
const text = await fs.readFile(convoPath);
|
||||||
|
const convo: IConversation = JSON.parse(text.toString("utf8"));
|
||||||
this.model = convo.model;
|
this.model = convo.model;
|
||||||
this.messages = convo.messages;
|
this.messages = convo.messages;
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -25,7 +27,8 @@ export class Conversation implements IConversation {
|
||||||
}
|
}
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
await convoFile.write(
|
await fs.writeFile(
|
||||||
|
convoPath,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
model: this.model,
|
model: this.model,
|
||||||
messages: this.messages,
|
messages: this.messages,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import type {
|
||||||
} from "ai";
|
} from "ai";
|
||||||
import styles from "ansi-styles";
|
import styles from "ansi-styles";
|
||||||
import { chalkStderr, supportsColorStderr } from "chalk";
|
import { chalkStderr, supportsColorStderr } from "chalk";
|
||||||
|
import stringWidth from "string-width";
|
||||||
import { renderMarkdown } from "./markdown";
|
import { renderMarkdown } from "./markdown";
|
||||||
|
|
||||||
export default class Renderer {
|
export default class Renderer {
|
||||||
|
|
@ -40,12 +41,11 @@ export default class Renderer {
|
||||||
|
|
||||||
private checkContainment(text: string, pipe: WriteStream = process.stdout) {
|
private checkContainment(text: string, pipe: WriteStream = process.stdout) {
|
||||||
const [columns, rows] = pipe.getWindowSize();
|
const [columns, rows] = pipe.getWindowSize();
|
||||||
this.charsSinceNL += Bun.stringWidth(text);
|
this.charsSinceNL += stringWidth(text);
|
||||||
if (text.includes("\n")) {
|
if (text.includes("\n")) {
|
||||||
this.lineCount += text.match(/\n/g)?.length ?? 1;
|
this.lineCount += text.match(/\n/g)?.length ?? 1;
|
||||||
this.charsSinceNL =
|
this.charsSinceNL =
|
||||||
Bun.stringWidth(text) -
|
stringWidth(text) - stringWidth(text.slice(0, text.lastIndexOf("\n")));
|
||||||
Bun.stringWidth(text.slice(0, text.lastIndexOf("\n")));
|
|
||||||
} else if (this.charsSinceNL > columns) {
|
} else if (this.charsSinceNL > columns) {
|
||||||
this.lineCount++;
|
this.lineCount++;
|
||||||
this.charsSinceNL = this.charsSinceNL - columns;
|
this.charsSinceNL = this.charsSinceNL - columns;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ import {
|
||||||
createHighlighter,
|
createHighlighter,
|
||||||
type HighlighterGeneric,
|
type HighlighterGeneric,
|
||||||
} from "shiki";
|
} from "shiki";
|
||||||
|
import stringWidth from "string-width";
|
||||||
import terminalLink from "terminal-link";
|
import terminalLink from "terminal-link";
|
||||||
|
import wrapAnsi from "wrap-ansi";
|
||||||
import { codeToANSI } from "./codeToANSI";
|
import { codeToANSI } from "./codeToANSI";
|
||||||
|
|
||||||
let highlighter: HighlighterGeneric<BundledLanguage, BundledTheme> | undefined;
|
let highlighter: HighlighterGeneric<BundledLanguage, BundledTheme> | undefined;
|
||||||
|
|
@ -15,7 +17,7 @@ let highligherLangs = "";
|
||||||
|
|
||||||
class SWCell extends Cell {
|
class SWCell extends Cell {
|
||||||
public override get length() {
|
public override get length() {
|
||||||
return Bun.stringWidth(this.toString(), { ambiguousIsNarrow: true });
|
return stringWidth(this.toString(), { ambiguousIsNarrow: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,7 +25,7 @@ class LmcRenderer extends Renderer {
|
||||||
private columns: number;
|
private columns: number;
|
||||||
|
|
||||||
constructor(columns: number) {
|
constructor(columns: number) {
|
||||||
super();
|
super({ gfm: true });
|
||||||
this.columns = columns;
|
this.columns = columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,7 +40,7 @@ class LmcRenderer extends Renderer {
|
||||||
];
|
];
|
||||||
const color = colors[depth - 1] ?? chalk.white;
|
const color = colors[depth - 1] ?? chalk.white;
|
||||||
const text = this.parser.parseInline(tokens);
|
const text = this.parser.parseInline(tokens);
|
||||||
return Bun.wrapAnsi(
|
return wrapAnsi(
|
||||||
color.bold(`${"#".repeat(depth)} ${text}\n\n`),
|
color.bold(`${"#".repeat(depth)} ${text}\n\n`),
|
||||||
this.columns,
|
this.columns,
|
||||||
{ trim: false },
|
{ trim: false },
|
||||||
|
|
@ -47,12 +49,12 @@ class LmcRenderer extends Renderer {
|
||||||
|
|
||||||
override paragraph({ tokens }: Tokens.Paragraph) {
|
override paragraph({ tokens }: Tokens.Paragraph) {
|
||||||
const text = this.parser.parseInline(tokens);
|
const text = this.parser.parseInline(tokens);
|
||||||
return Bun.wrapAnsi(`${text}\n\n`, this.columns, { trim: false });
|
return wrapAnsi(`${text}\n\n`, this.columns, { trim: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
override blockquote({ tokens }: Tokens.Blockquote) {
|
override blockquote({ tokens }: Tokens.Blockquote) {
|
||||||
const text = this.parser.parse(tokens).trim();
|
const text = this.parser.parse(tokens).trim();
|
||||||
const lines = Bun.wrapAnsi(text, this.columns - 2, { trim: false })
|
const lines = wrapAnsi(text, this.columns - 2, { trim: false })
|
||||||
.replace(/\n+/g, "\n")
|
.replace(/\n+/g, "\n")
|
||||||
.concat("\n")
|
.concat("\n")
|
||||||
.split("\n");
|
.split("\n");
|
||||||
|
|
@ -96,7 +98,7 @@ class LmcRenderer extends Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
const cols = this.columns - 2;
|
const cols = this.columns - 2;
|
||||||
return Bun.wrapAnsi(`${marker} ${text}`, cols, { trim: false })
|
return wrapAnsi(`${marker} ${text}`, cols, { trim: false })
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.map((line) => (line.length > 0 ? ` ${line}` : ""))
|
.map((line) => (line.length > 0 ? ` ${line}` : ""))
|
||||||
.join("\n")
|
.join("\n")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue