lmc/index.ts
mitchell 9d599d98bf feat: add --plain flag for unformatted terminal output
Rename StreamRenderer to Renderer and move stats rendering into the
class. Support plain mode that skips ANSI formatting and glow preview
when output is not a TTY.
2026-04-17 16:16:09 -04:00

103 lines
2.5 KiB
TypeScript

import os from "node:os";
import { parseArgs } from "node:util";
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { type ModelMessage, streamText } from "ai";
import Renderer from "./src/render";
const { values: args, positionals } = parseArgs({
args: Bun.argv,
options: {
model: { type: "string" },
m: { type: "string" },
new: { type: "boolean" },
n: { type: "boolean" },
save: { type: "boolean" },
s: { type: "boolean" },
plain: { type: "boolean" },
p: { type: "boolean" },
},
strict: true,
allowPositionals: true,
});
const home = os.homedir();
const convoFile = Bun.file(`${home}/.aicl_convo.json`);
const configFile = Bun.file(`${home}/.aicl_config.json`);
async function main() {
const newConvo = args.new ?? args.n;
const plain = args.plain ?? args.p ?? !process.stdout.isTTY;
let saveConfig = args.save ?? args.s;
let config: { model?: string };
try {
config = await configFile.json();
} catch {
config = {};
saveConfig = true;
}
config.model = args.model ?? args.m ?? config.model;
if (!config.model) {
console.error("Please specify a model with --model/-m at least one time.");
process.exit(1);
}
if (saveConfig) {
await configFile.write(JSON.stringify(config, null, " "));
}
const renderer = new Renderer(plain);
const provider = createOpenAICompatible({
name: "llama.cpp",
apiKey: "local",
baseURL: "http://127.0.0.1:8080/v1",
includeUsage: true, // Include usage information in streaming responses
});
let convo: { model: string; messages: ModelMessage[] } = {
model: config.model,
messages: [],
};
if (!newConvo) {
try {
convo = await convoFile.json();
} catch {}
if (args.model || args.m) {
convo.model = args.model ?? args.m ?? config.model;
}
}
const prompt = positionals[2] ?? "Introduce yourself.";
convo.messages.push({ role: "user", content: prompt });
const { fullStream, textStream, text, totalUsage } = streamText({
model: provider(convo.model),
messages: convo.messages,
});
const start = Date.now();
await renderer.renderStream(fullStream);
const stop = Date.now();
convo.messages.push({ role: "assistant", content: await text });
await convoFile.write(JSON.stringify(convo));
if (!plain) {
const [width] = process.stdout.getWindowSize();
await Bun.spawn({
cmd: ["glow", "-w", `${width}`, "-"],
stdin: textStream,
stdout: "inherit",
}).exited;
}
renderer.renderStats(await totalUsage, start, stop);
}
await main();