From fc7d9101e9646b64c67234b0850848ffe6b3f869 Mon Sep 17 00:00:00 2001 From: mitchell Date: Tue, 16 Jun 2026 16:45:17 -0400 Subject: [PATCH 01/22] refactor(tts): remove InputError class and use native Error --- tts.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tts.ts b/tts.ts index 559ecf6..94cce7b 100644 --- a/tts.ts +++ b/tts.ts @@ -12,10 +12,6 @@ import { execa } from "execa"; import { Window } from "happy-dom"; import wrapAnsi from "wrap-ansi"; -class InputError extends Error { - override name = "InputError"; -} - async function main() { const start = Date.now(); @@ -48,9 +44,7 @@ async function main() { } if (!input) { - throw new InputError( - "No input was given. Use positional arguments or stdin.", - ); + throw new Error("No input was given. Use positional arguments or stdin."); } if (url) { @@ -61,7 +55,7 @@ async function main() { await window.happyDOM.waitUntilComplete(); const readable = new Readability(doc).parse(); if (!readable?.textContent) { - throw new InputError( + throw new Error( "Unable to read webpage. This usually means it's blocking scrapers.", ); } From f3146ab087b58f177c85eb692d0e536e316b5e28 Mon Sep 17 00:00:00 2001 From: mitchell Date: Tue, 16 Jun 2026 16:57:36 -0400 Subject: [PATCH 02/22] feat(lmc): add --git-message and --review modes, standardize args casing --- index.ts | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/index.ts b/index.ts index 45cdd57..35c5ec7 100644 --- a/index.ts +++ b/index.ts @@ -17,9 +17,14 @@ const { values: args, positionals } = util.parseArgs({ config: { type: "boolean" }, c: { type: "boolean" }, plain: { type: "boolean" }, - plainOut: { type: "boolean" }, - plainErr: { type: "boolean" }, + "plain-out": { type: "boolean" }, + "plain-err": { type: "boolean" }, + version: { type: "boolean" }, v: { type: "boolean" }, + "git-message": { type: "boolean" }, + g: { type: "boolean" }, + review: { type: "boolean" }, + r: { type: "boolean" }, }, strict: true, allowPositionals: true, @@ -27,12 +32,15 @@ const { values: args, positionals } = util.parseArgs({ async function main() { const newConvo = args.new ?? args.n; - const plainOut = args.plainOut ?? args.plain ?? !process.stdout.isTTY; - const plainErr = args.plainErr ?? args.plain ?? !process.stderr.isTTY; + const plainOut = args["plain-out"] ?? args.plain ?? !process.stdout.isTTY; + const plainErr = args["plain-err"] ?? args.plain ?? !process.stderr.isTTY; const modelFlag = args.model ?? args.m; + const versionFlag = args.version ?? args.v; const configFlag = args.config ?? args.c; + const messageFlag = args["git-message"] ?? args.g; + const reviewFlag = args.review ?? args.r; - if (args.v) { + if (versionFlag) { console.log("0.0.0"); return; } @@ -62,7 +70,23 @@ async function main() { if (modelFlag) convo.model = modelFlag; } - const prompt = positionals.join(" ") || "Introduce yourself."; + let prompt = "Introduce yourself."; + const input = positionals.join(" ").trim(); + if (messageFlag) { + prompt = `\`\`\`diff +${input} +\`\`\` + +Generate a conventional commit message for this diff. If you are unable to generate a reasonable option, ask for more context.`; + } else if (reviewFlag) { + prompt = `\`\`\`diff +${input} +\`\`\` + +Review the above diff for any potential bugs, best practices, performance issues, or security flaws. Let me know if you need any more context to compelete your review.`; + } else if (input) { + prompt = input; + } convo.messages.push({ role: "user", content: prompt }); const { fullStream, text, totalUsage } = streamText({ From 56352d87ecfb51aeebfc9efab2b5c938526bf91e Mon Sep 17 00:00:00 2001 From: mitchell Date: Tue, 16 Jun 2026 18:58:33 -0400 Subject: [PATCH 03/22] fix(entry): improve error message formatting - Relocate `util.parseArgs()` inside the main function for cleaner scoping - Update `.catch` handlers in `index.ts` and `tts.ts` to wrap text to terminal width and colorize errors in red --- index.ts | 53 +++++++++++++++++++++++++++++------------------------ tts.ts | 5 ++++- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/index.ts b/index.ts index 35c5ec7..e7c52a3 100644 --- a/index.ts +++ b/index.ts @@ -2,35 +2,37 @@ import util from "node:util"; import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; import { streamText } from "ai"; +import chalk from "chalk"; +import wrapAnsi from "wrap-ansi"; import { initConfig, loadConfig } from "./src/config"; import { Conversation } from "./src/conversation"; import Renderer from "./src/render"; -const { values: args, positionals } = util.parseArgs({ - args: process.argv.slice(2), - options: { - model: { type: "string" }, - m: { type: "string" }, - new: { type: "boolean" }, - n: { type: "boolean" }, - config: { type: "boolean" }, - c: { type: "boolean" }, - plain: { type: "boolean" }, - "plain-out": { type: "boolean" }, - "plain-err": { type: "boolean" }, - version: { type: "boolean" }, - v: { type: "boolean" }, - "git-message": { type: "boolean" }, - g: { type: "boolean" }, - review: { type: "boolean" }, - r: { type: "boolean" }, - }, - strict: true, - allowPositionals: true, -}); - async function main() { + const { values: args, positionals } = util.parseArgs({ + args: process.argv.slice(2), + options: { + model: { type: "string" }, + m: { type: "string" }, + new: { type: "boolean" }, + n: { type: "boolean" }, + config: { type: "boolean" }, + c: { type: "boolean" }, + plain: { type: "boolean" }, + "plain-out": { type: "boolean" }, + "plain-err": { type: "boolean" }, + version: { type: "boolean" }, + v: { type: "boolean" }, + "git-message": { type: "boolean" }, + g: { type: "boolean" }, + review: { type: "boolean" }, + r: { type: "boolean" }, + }, + strict: true, + allowPositionals: true, + }); + const newConvo = args.new ?? args.n; const plainOut = args["plain-out"] ?? args.plain ?? !process.stdout.isTTY; const plainErr = args["plain-err"] ?? args.plain ?? !process.stderr.isTTY; @@ -103,6 +105,9 @@ Review the above diff for any potential bugs, best practices, performance issues } main().catch((err) => { - console.error(err); + if ("message" in err) { + const [columns] = process.stderr.getWindowSize(); + console.error(wrapAnsi(chalk.red(err.message), columns)); + } else console.log(err); process.exit(1); }); diff --git a/tts.ts b/tts.ts index 94cce7b..06d3693 100644 --- a/tts.ts +++ b/tts.ts @@ -180,6 +180,9 @@ async function main() { } main().catch((err) => { - console.error(err); + if ("message" in err) { + const [columns] = process.stderr.getWindowSize(); + console.error(wrapAnsi(chalk.red(err.message), columns)); + } else console.log(err); process.exit(1); }); From f8e0f9a3682ddca20feadbc78388e14cf73dcd73 Mon Sep 17 00:00:00 2001 From: mitchell Date: Wed, 17 Jun 2026 00:48:15 -0400 Subject: [PATCH 04/22] refactor(errors): extract top-level error handling into shared utility --- index.ts | 11 ++--------- src/errors.ts | 22 ++++++++++++++++++++++ tts.ts | 9 ++------- 3 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 src/errors.ts diff --git a/index.ts b/index.ts index e7c52a3..baff42e 100644 --- a/index.ts +++ b/index.ts @@ -2,11 +2,10 @@ import util from "node:util"; import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; import { streamText } from "ai"; -import chalk from "chalk"; -import wrapAnsi from "wrap-ansi"; import { initConfig, loadConfig } from "./src/config"; import { Conversation } from "./src/conversation"; +import { handleTopLevel } from "./src/errors"; import Renderer from "./src/render"; async function main() { @@ -104,10 +103,4 @@ Review the above diff for any potential bugs, best practices, performance issues await renderer.renderStats(totalUsage); } -main().catch((err) => { - if ("message" in err) { - const [columns] = process.stderr.getWindowSize(); - console.error(wrapAnsi(chalk.red(err.message), columns)); - } else console.log(err); - process.exit(1); -}); +main().catch(handleTopLevel); diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..a8e9e05 --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,22 @@ +import { chalkStderr } from "chalk"; +import wrapAnsi from "wrap-ansi"; + +export function handleTopLevel(err: unknown) { + if ( + typeof err === "object" && + err !== null && + "message" in err && + typeof err.message === "string" + ) { + if (process.stderr.isTTY) { + const [columns] = process.stderr.getWindowSize(); + console.error(wrapAnsi(chalkStderr.red(err.message), columns)); + } else { + console.error(err.message); + } + } else { + console.error(String(err)); + } + + process.exit(1); +} diff --git a/tts.ts b/tts.ts index 06d3693..920f146 100644 --- a/tts.ts +++ b/tts.ts @@ -11,6 +11,7 @@ import chalk from "chalk"; import { execa } from "execa"; import { Window } from "happy-dom"; import wrapAnsi from "wrap-ansi"; +import { handleTopLevel } from "./src/errors"; async function main() { const start = Date.now(); @@ -179,10 +180,4 @@ async function main() { })`mpv --pause output.wav`; } -main().catch((err) => { - if ("message" in err) { - const [columns] = process.stderr.getWindowSize(); - console.error(wrapAnsi(chalk.red(err.message), columns)); - } else console.log(err); - process.exit(1); -}); +main().catch(handleTopLevel); From 3c3dab15256dde1e4d84563625cd8f6f3b53d675 Mon Sep 17 00:00:00 2001 From: mitchell Date: Wed, 17 Jun 2026 01:12:38 -0400 Subject: [PATCH 05/22] chore: move entry files to src/ directory and update paths --- package.json | 8 ++++---- index.ts => src/index.ts | 8 ++++---- tts.ts => src/tts.ts | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) rename index.ts => src/index.ts (93%) rename tts.ts => src/tts.ts (99%) diff --git a/package.json b/package.json index 23a5b72..c64f686 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lmc", - "module": "index.ts", + "module": "src/index.ts", "type": "module", "private": true, "bin": { @@ -8,11 +8,11 @@ "ttsc": "dist/tts.js" }, "scripts": { - "dev": "pnpm run bundle index.ts &>/dev/null && node dist/index.js", + "dev": "pnpm run bundle src/index.ts &>/dev/null && node dist/index.js", "check": "biome check --write && pnpm run tc", "clean": "rm -rf dist", - "build": "pnpm run check && pnpm run bundle --minify index.ts && pnpm run bundle --minify tts.ts", - "build:bin": "pnpm run check && pnpm run bundle:bin --minify index.ts && pnpm run bundle:bin --minify tts.ts && pnpm run bin", + "build": "pnpm run check && pnpm run bundle --minify src/index.ts && pnpm run bundle --minify src/tts.ts", + "build:bin": "pnpm run check && pnpm run bundle:bin --minify src/index.ts && pnpm run bundle:bin --minify src/tts.ts && pnpm run bin", "bundle": "rolldown --dir=dist --platform=node --transform.target=node22 --format=esm", "bundle:bin": "esbuild --bundle --outdir=dist --platform=node --target=node26 --format=cjs", "bin": "node --build-sea sea-config.json && node --build-sea sea-config-tts.json", diff --git a/index.ts b/src/index.ts similarity index 93% rename from index.ts rename to src/index.ts index baff42e..a99fe41 100644 --- a/index.ts +++ b/src/index.ts @@ -3,10 +3,10 @@ import util from "node:util"; import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; import { streamText } from "ai"; -import { initConfig, loadConfig } from "./src/config"; -import { Conversation } from "./src/conversation"; -import { handleTopLevel } from "./src/errors"; -import Renderer from "./src/render"; +import { initConfig, loadConfig } from "./config"; +import { Conversation } from "./conversation"; +import { handleTopLevel } from "./errors"; +import Renderer from "./render"; async function main() { const { values: args, positionals } = util.parseArgs({ diff --git a/tts.ts b/src/tts.ts similarity index 99% rename from tts.ts rename to src/tts.ts index 920f146..4ac3da5 100644 --- a/tts.ts +++ b/src/tts.ts @@ -11,7 +11,7 @@ import chalk from "chalk"; import { execa } from "execa"; import { Window } from "happy-dom"; import wrapAnsi from "wrap-ansi"; -import { handleTopLevel } from "./src/errors"; +import { handleTopLevel } from "./errors"; async function main() { const start = Date.now(); From eec90209d24cd1e9377b75b9051244a8038e3dba Mon Sep 17 00:00:00 2001 From: mitchell Date: Wed, 17 Jun 2026 01:25:27 -0400 Subject: [PATCH 06/22] refactor(prompts): extract prompt templates to dedicated module --- src/index.ts | 18 ++++-------------- src/prompts.ts | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 src/prompts.ts diff --git a/src/index.ts b/src/index.ts index a99fe41..4f7b4b4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import { streamText } from "ai"; import { initConfig, loadConfig } from "./config"; import { Conversation } from "./conversation"; import { handleTopLevel } from "./errors"; +import { Prompts } from "./prompts"; import Renderer from "./render"; async function main() { @@ -73,21 +74,10 @@ async function main() { let prompt = "Introduce yourself."; const input = positionals.join(" ").trim(); - if (messageFlag) { - prompt = `\`\`\`diff -${input} -\`\`\` + if (messageFlag) prompt = Prompts.commit(input); + else if (reviewFlag) prompt = Prompts.review(input); + else if (input) prompt = input; -Generate a conventional commit message for this diff. If you are unable to generate a reasonable option, ask for more context.`; - } else if (reviewFlag) { - prompt = `\`\`\`diff -${input} -\`\`\` - -Review the above diff for any potential bugs, best practices, performance issues, or security flaws. Let me know if you need any more context to compelete your review.`; - } else if (input) { - prompt = input; - } convo.messages.push({ role: "user", content: prompt }); const { fullStream, text, totalUsage } = streamText({ diff --git a/src/prompts.ts b/src/prompts.ts new file mode 100644 index 0000000..94969d4 --- /dev/null +++ b/src/prompts.ts @@ -0,0 +1,15 @@ +export const Prompts = { + default: "Introduce yourself.", + + commit: (input: string) => `\`\`\`diff +${input} +\`\`\` + +Generate a conventional commit message for this diff. If you are unable to generate a reasonable option, ask for more context.`, + + review: (input: string) => `\`\`\`diff +${input} +\`\`\` + +Review the above diff for any potential bugs, best practices, performance issues, or security flaws. Let me know if you need any more context to complete your review.`, +}; From 19272bc0b4060a3ada336b571f8f9f827c3c95fb Mon Sep 17 00:00:00 2001 From: mitchell Date: Wed, 17 Jun 2026 01:40:53 -0400 Subject: [PATCH 07/22] refactor: flatten source directory structure --- src/{config/index.ts => config.ts} | 0 src/{conversation/index.ts => conversation.ts} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/{config/index.ts => config.ts} (100%) rename src/{conversation/index.ts => conversation.ts} (100%) diff --git a/src/config/index.ts b/src/config.ts similarity index 100% rename from src/config/index.ts rename to src/config.ts diff --git a/src/conversation/index.ts b/src/conversation.ts similarity index 100% rename from src/conversation/index.ts rename to src/conversation.ts From 7652e62d47d2789e4a8350f943e39c4ffed82e00 Mon Sep 17 00:00:00 2001 From: mitchell Date: Wed, 17 Jun 2026 12:22:32 -0400 Subject: [PATCH 08/22] refactor(entries): simplify stdin reading with node:stream/consumers --- src/index.ts | 5 ++++- src/tts.ts | 10 ++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4f7b4b4..1fb0c4f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ #!/usr/bin/env node +import consumers from "node:stream/consumers"; import util from "node:util"; import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; import { streamText } from "ai"; @@ -72,8 +73,10 @@ async function main() { if (modelFlag) convo.model = modelFlag; } + let input = positionals.join(" ").trim(); + if (input === "-") input = await consumers.text(process.stdin); + let prompt = "Introduce yourself."; - const input = positionals.join(" ").trim(); if (messageFlag) prompt = Prompts.commit(input); else if (reviewFlag) prompt = Prompts.review(input); else if (input) prompt = input; diff --git a/src/tts.ts b/src/tts.ts index 4ac3da5..56981d7 100644 --- a/src/tts.ts +++ b/src/tts.ts @@ -1,10 +1,10 @@ #!/usr/bin/env node -import events from "node:events"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import process from "node:process"; import readline from "node:readline"; +import consumers from "node:stream/consumers"; import util from "node:util"; import { Readability } from "@mozilla/readability"; import chalk from "chalk"; @@ -36,13 +36,7 @@ async function main() { let input = positionals.join(" ").trim(); - if (!input) { - process.stdin.setEncoding("utf8"); - process.stdin.on("data", (data) => { - input += data; - }); - await events.once(process.stdin, "end"); - } + if (!input) input = await consumers.text(process.stdin); if (!input) { throw new Error("No input was given. Use positional arguments or stdin."); From 4bccbcc9ee7075f48078940738b33db6b0d734d9 Mon Sep 17 00:00:00 2001 From: mitchell Date: Wed, 17 Jun 2026 22:48:54 -0400 Subject: [PATCH 09/22] feat(cli): add --file flag for file review and refactor prompt generation - Inject current date into review prompts to improve LLM context accuracy - Remove unused `module` field from package.json --- package.json | 1 - src/index.ts | 24 +++++++++++++++++++----- src/prompts.ts | 25 ++++++++++++++++++------- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index c64f686..cda4c8c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,5 @@ { "name": "lmc", - "module": "src/index.ts", "type": "module", "private": true, "bin": { diff --git a/src/index.ts b/src/index.ts index 1fb0c4f..e3044a7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ #!/usr/bin/env node +import fs from "node:fs/promises"; import consumers from "node:stream/consumers"; import util from "node:util"; import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; @@ -7,7 +8,7 @@ import { streamText } from "ai"; import { initConfig, loadConfig } from "./config"; import { Conversation } from "./conversation"; import { handleTopLevel } from "./errors"; -import { Prompts } from "./prompts"; +import { commitDiff, file, reviewDiff, reviewFile } from "./prompts"; import Renderer from "./render"; async function main() { @@ -29,6 +30,8 @@ async function main() { g: { type: "boolean" }, review: { type: "boolean" }, r: { type: "boolean" }, + file: { type: "string" }, + f: { type: "string" }, }, strict: true, allowPositionals: true, @@ -42,6 +45,7 @@ async function main() { const configFlag = args.config ?? args.c; const messageFlag = args["git-message"] ?? args.g; const reviewFlag = args.review ?? args.r; + const fileFlag = args.file ?? args.f; if (versionFlag) { console.log("0.0.0"); @@ -76,10 +80,20 @@ async function main() { let input = positionals.join(" ").trim(); if (input === "-") input = await consumers.text(process.stdin); - let prompt = "Introduce yourself."; - if (messageFlag) prompt = Prompts.commit(input); - else if (reviewFlag) prompt = Prompts.review(input); - else if (input) prompt = input; + let prompt = input; + if (messageFlag) { + prompt = commitDiff(input); + } else if (reviewFlag && fileFlag) { + const content = await fs.readFile(fileFlag, "utf8"); + prompt = reviewFile(file(fileFlag, content)); + } else if (fileFlag) { + const content = await fs.readFile(fileFlag, "utf8"); + prompt = `${file(fileFlag, content)}\n\n${input}`; + } else if (reviewFlag) { + prompt = reviewDiff(input); + } + + if (!prompt) throw new Error("No prompt given."); convo.messages.push({ role: "user", content: prompt }); diff --git a/src/prompts.ts b/src/prompts.ts index 94969d4..daf1735 100644 --- a/src/prompts.ts +++ b/src/prompts.ts @@ -1,15 +1,26 @@ -export const Prompts = { - default: "Introduce yourself.", +export const file = (name: string, input: string) => `${name}: +\`\`\` +${input} +\`\`\``; - commit: (input: string) => `\`\`\`diff +export const commitDiff = (input: string) => `\`\`\`diff ${input} \`\`\` -Generate a conventional commit message for this diff. If you are unable to generate a reasonable option, ask for more context.`, +Generate a conventional commit message for this diff. If you are unable to generate a reasonable option, ask for more context.`; - review: (input: string) => `\`\`\`diff +export const reviewFile = (input: string) => `${input} + +Todays date is ${new Date().toISOString()}, your version info is likely out-of-date. + +Review the included file for any potential bugs, best practices, performance issues, or security flaws. +Let me know if you need any more context to complete your review.`; + +export const reviewDiff = (input: string) => `\`\`\`diff ${input} \`\`\` -Review the above diff for any potential bugs, best practices, performance issues, or security flaws. Let me know if you need any more context to complete your review.`, -}; +Todays date is ${new Date().toISOString()}, your version info is likely out-of-date. + +Review the included diff for any potential bugs, best practices, performance issues, or security flaws. +Let me know if you need any more context to complete your review.`; From 4725f148f3ef9b40fa823c3433a2e5687bb14273 Mon Sep 17 00:00:00 2001 From: mitchell Date: Thu, 18 Jun 2026 16:13:39 -0400 Subject: [PATCH 10/22] chore: migrate @cliffy/table to native jsr protocol & fix prompt typos --- .npmrc | 1 - package.json | 2 +- pnpm-lock.yaml | 2 +- src/prompts.ts | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) delete mode 100644 .npmrc diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 41583e3..0000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -@jsr:registry=https://npm.jsr.io diff --git a/package.json b/package.json index cda4c8c..4f28a1e 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "@ai-sdk/openai-compatible": "^2.0.41", "@biomejs/biome": "2.4.12", "@clack/prompts": "^1.3.0", - "@cliffy/table": "npm:@jsr/cliffy__table", + "@cliffy/table": "jsr:^1.2.1", "@mozilla/readability": "^0.6.0", "@types/node": "^25.9.2", "ai": "^6.0.161", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22f3aaa..b897059 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,7 +18,7 @@ importers: specifier: ^1.3.0 version: 1.4.0 '@cliffy/table': - specifier: npm:@jsr/cliffy__table + specifier: jsr:^1.2.1 version: '@jsr/cliffy__table@1.2.1' '@mozilla/readability': specifier: ^0.6.0 diff --git a/src/prompts.ts b/src/prompts.ts index daf1735..21f1a27 100644 --- a/src/prompts.ts +++ b/src/prompts.ts @@ -11,7 +11,7 @@ Generate a conventional commit message for this diff. If you are unable to gener export const reviewFile = (input: string) => `${input} -Todays date is ${new Date().toISOString()}, your version info is likely out-of-date. +Today's date is ${new Date().toISOString()}, your version info is likely out-of-date. Review the included file for any potential bugs, best practices, performance issues, or security flaws. Let me know if you need any more context to complete your review.`; @@ -20,7 +20,7 @@ export const reviewDiff = (input: string) => `\`\`\`diff ${input} \`\`\` -Todays date is ${new Date().toISOString()}, your version info is likely out-of-date. +Today's date is ${new Date().toISOString()}, your version info is likely out-of-date. Review the included diff for any potential bugs, best practices, performance issues, or security flaws. Let me know if you need any more context to complete your review.`; From e2253b912dd733edde380973a0c73db9fccbf347 Mon Sep 17 00:00:00 2001 From: mitchell Date: Thu, 18 Jun 2026 17:47:09 -0400 Subject: [PATCH 11/22] chore(deps): update dependencies and configure pnpm minimum release age --- package.json | 16 ++-- pnpm-lock.yaml | 197 +++++++++++++++++++++----------------------- pnpm-workspace.yaml | 1 + 3 files changed, 104 insertions(+), 110 deletions(-) diff --git a/package.json b/package.json index 4f28a1e..690ac1a 100644 --- a/package.json +++ b/package.json @@ -18,24 +18,24 @@ "tc": "tsc --noEmit --incremental" }, "devDependencies": { - "@ai-sdk/openai-compatible": "^2.0.41", + "@ai-sdk/openai-compatible": "^2.0.50", "@biomejs/biome": "2.4.12", - "@clack/prompts": "^1.3.0", + "@clack/prompts": "^1.5.1", "@cliffy/table": "jsr:^1.2.1", "@mozilla/readability": "^0.6.0", - "@types/node": "^25.9.2", - "ai": "^6.0.161", + "@types/node": "^25.9.3", + "ai": "^6.0.206", "ansi-styles": "^6.2.3", "chalk": "^5.6.2", "esbuild": "^0.28.1", "execa": "^9.6.1", - "happy-dom": "^20.10.2", - "marked": "^18.0.4", + "happy-dom": "^20.10.3", + "marked": "^18.0.5", "rolldown": "^1.1.1", - "shiki": "^4.0.2", + "shiki": "^4.2.0", "string-width": "^8.2.1", "terminal-link": "^5.0.0", - "typescript": "^5", + "typescript": "^5.9.3", "wrap-ansi": "^10.0.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b897059..d46eb49 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,14 +9,14 @@ importers: .: devDependencies: '@ai-sdk/openai-compatible': - specifier: ^2.0.41 - version: 2.0.48(zod@4.4.3) + specifier: ^2.0.50 + version: 2.0.50(zod@4.4.3) '@biomejs/biome': specifier: 2.4.12 version: 2.4.12 '@clack/prompts': - specifier: ^1.3.0 - version: 1.4.0 + specifier: ^1.5.1 + version: 1.5.1 '@cliffy/table': specifier: jsr:^1.2.1 version: '@jsr/cliffy__table@1.2.1' @@ -24,11 +24,11 @@ importers: specifier: ^0.6.0 version: 0.6.0 '@types/node': - specifier: ^25.9.2 - version: 25.9.2 + specifier: ^25.9.3 + version: 25.9.3 ai: - specifier: ^6.0.161 - version: 6.0.191(zod@4.4.3) + specifier: ^6.0.206 + version: 6.0.206(zod@4.4.3) ansi-styles: specifier: ^6.2.3 version: 6.2.3 @@ -42,17 +42,17 @@ importers: specifier: ^9.6.1 version: 9.6.1 happy-dom: - specifier: ^20.10.2 - version: 20.10.2 + specifier: ^20.10.3 + version: 20.10.3 marked: - specifier: ^18.0.4 - version: 18.0.4 + specifier: ^18.0.5 + version: 18.0.5 rolldown: specifier: ^1.1.1 version: 1.1.1 shiki: - specifier: ^4.0.2 - version: 4.1.0 + specifier: ^4.2.0 + version: 4.2.0 string-width: specifier: ^8.2.1 version: 8.2.1 @@ -60,7 +60,7 @@ importers: specifier: ^5.0.0 version: 5.0.0 typescript: - specifier: ^5 + specifier: ^5.9.3 version: 5.9.3 wrap-ansi: specifier: ^10.0.0 @@ -68,20 +68,20 @@ importers: packages: - '@ai-sdk/gateway@3.0.120': - resolution: {integrity: sha512-MYKAeD2q7/sa1ZdqtL2tw0Me0B8Tok6Q/fhkJDhJl39dG8u+VBlWO9yk9lcdm784bM418o1EKObo4aOxs6+18Q==} + '@ai-sdk/gateway@3.0.132': + resolution: {integrity: sha512-sFZFGk6aVSNKmgDrb14efaAbvM71AB3UUvaTsU+bvhWP9jrkRrwix/jFX8IoOAJk0/X7Xf7p3m0J2O2G6ljZbA==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/openai-compatible@2.0.48': - resolution: {integrity: sha512-z9MC6M4Oh/yUY/F/eszOtO8wc2nMz99XmZQKd2gWTtyIfe716xTfrKe3aYZKg20NZDtyjqPPKPSR+wqz7q1T7Q==} + '@ai-sdk/openai-compatible@2.0.50': + resolution: {integrity: sha512-HyuxddF2Yv5G8qxK/0uksAINjQ4h6TpwOqHuqzsCM0u78/JWAW2OXcIplQeB44PIAORgPjbMzrw9DhnPYHMskA==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider-utils@4.0.27': - resolution: {integrity: sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw==} + '@ai-sdk/provider-utils@4.0.29': + resolution: {integrity: sha512-uhukHaCBvqkwBHkT8C2PrnqKTCoLn3pdHXqtcR9I8ErH+flbzgW4o7VHSNIup9LRu+WBvZIZDQLsx6rwl2tiOA==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -147,12 +147,12 @@ packages: cpu: [x64] os: [win32] - '@clack/core@1.3.1': - resolution: {integrity: sha512-fT1qHVGAag4IEkrupZ6lRRbNCs1vS9P01KB/sG8zKgvUztbYtFBtQpjSITNwooDZ83tpsPzP0mRNs1/KVszCRA==} + '@clack/core@1.4.1': + resolution: {integrity: sha512-FILJa1gGKEFTGZAJE9RpVhrjKz3c3h4ar60dSv6cGuDqufQ84YEIS3GAGvZiN+H6yaLbbvTFNejjCC4tXpZEuw==} engines: {node: '>= 20.12.0'} - '@clack/prompts@1.4.0': - resolution: {integrity: sha512-S0My7XPGIgpRWMDG8uRqalbgT+a6FmCUdOW+HaIOVVpUPHOb7RrpvjTjiODadKp06fsrVDJZlIzc6yCTp4AnxA==} + '@clack/prompts@1.5.1': + resolution: {integrity: sha512-zccHj2z2oCCO4yrDiRSlFOxWerGqRiysP7a5jPK6uoI9URKAquwY42Dd/iUP8JWHxEzdRe4TlbvZCo8z1/mhrw==} engines: {node: '>= 20.12.0'} '@emnapi/core@1.11.0': @@ -444,32 +444,32 @@ packages: '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - '@shikijs/core@4.1.0': - resolution: {integrity: sha512-jLJtSJeuFffqX6/inRE1zqU5aFv2hrszvYgq3OjbAgFRZiWv7abKMDdQzYxuSDfmUPQozZvI/kuy6VMTvnvqTQ==} + '@shikijs/core@4.2.0': + resolution: {integrity: sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ==} engines: {node: '>=20'} - '@shikijs/engine-javascript@4.1.0': - resolution: {integrity: sha512-YquhawCUgaBfhsS72e2Y/dI59gCBNPHu3fEO/tvLaXrTssxZrY5ddjtNLTwndrMgPo8b3IscE+xoICDzpTmlFQ==} + '@shikijs/engine-javascript@4.2.0': + resolution: {integrity: sha512-fjETeq1k5ffyXqRgS6+3hpvqseLalp1kjNfRbXpUgWR8FpZ1CmQfiNHovc5lncYjt/Vg5JK/WJEmLahjwMa0og==} engines: {node: '>=20'} - '@shikijs/engine-oniguruma@4.1.0': - resolution: {integrity: sha512-axLpjVs45YBvvINa+dJF+NPW+KtFkNXsFr4SDw2BMj9GdeMnGxVB9PQb2xXlJYovslt/nz6giedAyOANkfc7hg==} + '@shikijs/engine-oniguruma@4.2.0': + resolution: {integrity: sha512-hTorK1dffPkpbMUk6Z+828PgRo7d07HbnizoP0hNPFjhxMHctj0Px/qoHeGMYafc6ju+u9iMldN4JbVzNQM++g==} engines: {node: '>=20'} - '@shikijs/langs@4.1.0': - resolution: {integrity: sha512-nwOMruEkbgdZfQ/b8CgpNBVOpvG1k0N5tbmgiFeqsan401+x3ILqlzZJowSla4Agmq4hG2Uf2wh5jLTEhR8VSg==} + '@shikijs/langs@4.2.0': + resolution: {integrity: sha512-bwrVRlJ0wUhZxAbVdvBbv2TTC9yLsh4C/IO5Ofz0T8MQntgDvyVnkbjw9vi50r1kx7RCIJdnJnjZAwmAsXFLZQ==} engines: {node: '>=20'} - '@shikijs/primitive@4.1.0': - resolution: {integrity: sha512-zx2/2Uwj2q9X3KSyYREEhXO23xBw5WUhP4orK2lE4r+t9JGITmEe0JH+wPmJhqHpOT2bRRs6lAL945+LDvOAGw==} + '@shikijs/primitive@4.2.0': + resolution: {integrity: sha512-NOq+DtUkVBJtZMVXL5A0vI0Xk8nvDYaXetFHSJFlOqjDZIVhIPRYFdGkSoElDqNuegikcc3A76SNUa8dTqtAYA==} engines: {node: '>=20'} - '@shikijs/themes@4.1.0': - resolution: {integrity: sha512-emCcTnUM7yO2wltYbaxm+yLvcCI4+h8XBKc4KmJ7EZUXoSGjcCHifkI//R4OFit9ewpg7H2/9tjOuXrT2v/Knw==} + '@shikijs/themes@4.2.0': + resolution: {integrity: sha512-RX8IHYeLv8Cu2W6ruc3RxUqWn0IYCqSrMBzi/uRGAmfyDNOnNO5BF/Px7o97n4XTpmFTo5GbRaazuOWj+2ak2w==} engines: {node: '>=20'} - '@shikijs/types@4.1.0': - resolution: {integrity: sha512-3EQWX54fMpniOrDblzAhiwiJwpiTMW6+B9DWyUd9ska483tbayFYuw47UxwuPknI31bKnySfVQ/QW+jFL4rFdA==} + '@shikijs/types@4.2.0': + resolution: {integrity: sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw==} engines: {node: '>=20'} '@shikijs/vscode-textmate@10.0.2': @@ -491,11 +491,8 @@ packages: '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - '@types/node@25.9.1': - resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} - - '@types/node@25.9.2': - resolution: {integrity: sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==} + '@types/node@25.9.3': + resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==} '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -513,8 +510,8 @@ packages: resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} engines: {node: '>= 20'} - ai@6.0.191: - resolution: {integrity: sha512-zAxvjKebQE7YkSyyNIl0OM7i6/zygnKeF+yNUjD4nWOelYrG+LpDd6RnH6mjySI4zUpZ7o4wbnmAy8jc6u98vQ==} + ai@6.0.206: + resolution: {integrity: sha512-hVdB5PozMMxmkPBfbCxkFOn0eVeCMi/imkfPvk65cxL4O8oIrvjUxDUX8dGkdkWG1No+R7e7D9ti2DHO61Z4YQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -575,8 +572,8 @@ packages: engines: {node: '>=18'} hasBin: true - eventsource-parser@3.0.8: - resolution: {integrity: sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==} + eventsource-parser@3.1.0: + resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} engines: {node: '>=18.0.0'} execa@9.6.1: @@ -604,8 +601,8 @@ packages: resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} engines: {node: '>=18'} - happy-dom@20.10.2: - resolution: {integrity: sha512-5p9Sxis3eowDJKqx90QCsgbNA02XXqJ59NOHvD4V6cxp+rP4d/xOyVx7uY3hS8hiUbY1VeiFH8lbJ81AyuDVLQ==} + happy-dom@20.10.3: + resolution: {integrity: sha512-Hjdiy8RziuCcn5z04QI/rlsNuQoG8P0xxjgvsSMpi89cvIXIOcucQtiHS1yHSShxoBcSCeYqAskINmTiy/mlfw==} engines: {node: '>=20.0.0'} has-flag@5.0.1: @@ -643,8 +640,8 @@ packages: json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - marked@18.0.4: - resolution: {integrity: sha512-c/BTaKzg0G6ezQx97DAkYU7k0HM6ys0FqYeKBL6hlBByZwy+ycA1+f0vDdjMHKKeEjdgkx0GOv9Il6D+85cOqA==} + marked@18.0.5: + resolution: {integrity: sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==} engines: {node: '>= 20'} hasBin: true @@ -692,8 +689,8 @@ packages: resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} engines: {node: '>=18'} - property-information@7.1.0: - resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} regex-recursion@6.0.2: resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} @@ -717,8 +714,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shiki@4.1.0: - resolution: {integrity: sha512-l/ABZPUR5v70jI10EzqfMS/I96vjSGv2y0ihUV+WYFzv0EfvW4s54m0Lg8wCrrL+2IkwBzFTuxkZjPf8b2NX9Q==} + shiki@4.2.0: + resolution: {integrity: sha512-hjNax6o/ylDy9lefQEaSDtzaT3iVNtZ3WmpQnbuQNoG4xvnSKf2kSKbihZVO4JRG1TTMejs7CmNRYlWgAL66pQ==} engines: {node: '>=20'} signal-exit@4.1.0: @@ -834,24 +831,24 @@ packages: snapshots: - '@ai-sdk/gateway@3.0.120(zod@4.4.3)': + '@ai-sdk/gateway@3.0.132(zod@4.4.3)': dependencies: '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@4.4.3) + '@ai-sdk/provider-utils': 4.0.29(zod@4.4.3) '@vercel/oidc': 3.2.0 zod: 4.4.3 - '@ai-sdk/openai-compatible@2.0.48(zod@4.4.3)': + '@ai-sdk/openai-compatible@2.0.50(zod@4.4.3)': dependencies: '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@4.4.3) + '@ai-sdk/provider-utils': 4.0.29(zod@4.4.3) zod: 4.4.3 - '@ai-sdk/provider-utils@4.0.27(zod@4.4.3)': + '@ai-sdk/provider-utils@4.0.29(zod@4.4.3)': dependencies: '@ai-sdk/provider': 3.0.10 '@standard-schema/spec': 1.1.0 - eventsource-parser: 3.0.8 + eventsource-parser: 3.1.0 zod: 4.4.3 '@ai-sdk/provider@3.0.10': @@ -893,14 +890,14 @@ snapshots: '@biomejs/cli-win32-x64@2.4.12': optional: true - '@clack/core@1.3.1': + '@clack/core@1.4.1': dependencies: fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 - '@clack/prompts@1.4.0': + '@clack/prompts@1.5.1': dependencies: - '@clack/core': 1.3.1 + '@clack/core': 1.4.1 fast-string-width: 3.0.2 fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 @@ -1071,40 +1068,40 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} - '@shikijs/core@4.1.0': + '@shikijs/core@4.2.0': dependencies: - '@shikijs/primitive': 4.1.0 - '@shikijs/types': 4.1.0 + '@shikijs/primitive': 4.2.0 + '@shikijs/types': 4.2.0 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 hast-util-to-html: 9.0.5 - '@shikijs/engine-javascript@4.1.0': + '@shikijs/engine-javascript@4.2.0': dependencies: - '@shikijs/types': 4.1.0 + '@shikijs/types': 4.2.0 '@shikijs/vscode-textmate': 10.0.2 oniguruma-to-es: 4.3.6 - '@shikijs/engine-oniguruma@4.1.0': + '@shikijs/engine-oniguruma@4.2.0': dependencies: - '@shikijs/types': 4.1.0 + '@shikijs/types': 4.2.0 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@4.1.0': + '@shikijs/langs@4.2.0': dependencies: - '@shikijs/types': 4.1.0 + '@shikijs/types': 4.2.0 - '@shikijs/primitive@4.1.0': + '@shikijs/primitive@4.2.0': dependencies: - '@shikijs/types': 4.1.0 + '@shikijs/types': 4.2.0 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 - '@shikijs/themes@4.1.0': + '@shikijs/themes@4.2.0': dependencies: - '@shikijs/types': 4.1.0 + '@shikijs/types': 4.2.0 - '@shikijs/types@4.1.0': + '@shikijs/types@4.2.0': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -1128,11 +1125,7 @@ snapshots: dependencies: '@types/unist': 3.0.3 - '@types/node@25.9.1': - dependencies: - undici-types: 7.24.6 - - '@types/node@25.9.2': + '@types/node@25.9.3': dependencies: undici-types: 7.24.6 @@ -1142,17 +1135,17 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 25.9.1 + '@types/node': 25.9.3 '@ungap/structured-clone@1.3.1': {} '@vercel/oidc@3.2.0': {} - ai@6.0.191(zod@4.4.3): + ai@6.0.206(zod@4.4.3): dependencies: - '@ai-sdk/gateway': 3.0.120(zod@4.4.3) + '@ai-sdk/gateway': 3.0.132(zod@4.4.3) '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@4.4.3) + '@ai-sdk/provider-utils': 4.0.29(zod@4.4.3) '@opentelemetry/api': 1.9.1 zod: 4.4.3 @@ -1166,7 +1159,7 @@ snapshots: buffer-image-size@0.6.4: dependencies: - '@types/node': 25.9.1 + '@types/node': 25.9.3 ccount@2.0.1: {} @@ -1223,7 +1216,7 @@ snapshots: '@esbuild/win32-ia32': 0.28.1 '@esbuild/win32-x64': 0.28.1 - eventsource-parser@3.0.8: {} + eventsource-parser@3.1.0: {} execa@9.6.1: dependencies: @@ -1261,9 +1254,9 @@ snapshots: '@sec-ant/readable-stream': 0.4.1 is-stream: 4.0.1 - happy-dom@20.10.2: + happy-dom@20.10.3: dependencies: - '@types/node': 25.9.1 + '@types/node': 25.9.3 '@types/whatwg-mimetype': 3.0.2 '@types/ws': 8.18.1 buffer-image-size: 0.6.4 @@ -1285,7 +1278,7 @@ snapshots: hast-util-whitespace: 3.0.0 html-void-elements: 3.0.0 mdast-util-to-hast: 13.2.1 - property-information: 7.1.0 + property-information: 7.2.0 space-separated-tokens: 2.0.2 stringify-entities: 4.0.4 zwitch: 2.0.4 @@ -1308,7 +1301,7 @@ snapshots: json-schema@0.4.0: {} - marked@18.0.4: {} + marked@18.0.5: {} mdast-util-to-hast@13.2.1: dependencies: @@ -1362,7 +1355,7 @@ snapshots: dependencies: parse-ms: 4.0.0 - property-information@7.1.0: {} + property-information@7.2.0: {} regex-recursion@6.0.2: dependencies: @@ -1401,14 +1394,14 @@ snapshots: shebang-regex@3.0.0: {} - shiki@4.1.0: + shiki@4.2.0: dependencies: - '@shikijs/core': 4.1.0 - '@shikijs/engine-javascript': 4.1.0 - '@shikijs/engine-oniguruma': 4.1.0 - '@shikijs/langs': 4.1.0 - '@shikijs/themes': 4.1.0 - '@shikijs/types': 4.1.0 + '@shikijs/core': 4.2.0 + '@shikijs/engine-javascript': 4.2.0 + '@shikijs/engine-oniguruma': 4.2.0 + '@shikijs/langs': 4.2.0 + '@shikijs/themes': 4.2.0 + '@shikijs/types': 4.2.0 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5ed0b5a..8ad854b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,3 @@ +minimumReleaseAge: 4320 allowBuilds: esbuild: true From d157978d52b8bfd6cea18edfb46ee322f028419b Mon Sep 17 00:00:00 2001 From: mitchell Date: Thu, 18 Jun 2026 18:24:02 -0400 Subject: [PATCH 12/22] docs: update AGENTS.md to reflect Node.js/pnpm migration --- AGENTS.md | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b0bb2e1..4f1015f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,26 +1,36 @@ # lmc -Bun CLI tool for streaming LLM chat with inline markdown rendering. +TypeScript CLI tool for streaming LLM chat with inline markdown rendering. ## Commands ``` -bun run dev # dev run -bun run prod # run bundled output -bun run check # biome lint + format + organize imports +pnpm run dev # dev run (rolldown bundle + node) +pnpm run check # biome lint + format + organize imports + typecheck +pnpm run build # check + bundle index.ts and tts.ts (minified ESM) +pnpm run build:bin # check + bundle with esbuild (CJS) + build SEA binaries +pnpm run clean # remove dist/ ``` ## Architecture -- `index.ts` — CLI entry point, orchestration +- `src/index.ts` — LMC CLI entry point, orchestration +- `src/tts.ts` — TTSC CLI entry point (text-to-speech, Kokoro pipeline via `uv run`) +- `src/config.ts` — `initConfig()`, `loadConfig()`: manages `~/.lmc_config.json` +- `src/conversation.ts` — `Conversation` class: load/save to `~/.lmc_convo.json` +- `src/errors.ts` — `handleTopLevel()`: TTY-aware error wrapping +- `src/prompts.ts` — `commitDiff()`, `reviewFile()`, `reviewDiff()`, `file()`: prompt builders - `src/render/index.ts` — `Renderer` class: streams LLM response, buffers markdown, renders to stdout/stderr -- `src/render/markdown.ts` — `renderMarkdown()`: syntax-highlighted markdown via `@comark/ansi` + Shiki +- `src/render/markdown.ts` — `renderMarkdown()`: markdown via `marked` + Shiki + `@cliffy/table` +- `src/render/codeToANSI.ts` — Shiki token output to ANSI escape sequences ## Gotchas -- **Bun only** — not Node. Use `Bun.file()`, `Bun.argv`, `Bun.sleep()`. +- **Node.js** — Use modern Node APIs (`node:fs/promises`, `node:stream/consumers`, etc.). - **No tests** — none exist. -- **Biome config**: double quotes, space indent, auto-import-organize on. Run `bun run check` before committing. +- **Biome config**: double quotes, space indent, auto-import-organize on. `pnpm run check` also runs `tsc --noEmit`. +- **Build tools**: rolldown for ESM bundles, esbuild for CJS binaries, `node --build-sea` for SEA single-executable apps. +- **TTS**: requires Python with `uv` — Kokoro pipeline lives in `tts/`. ## Style From bf5e223abf37c008a39fc6091eeeb42966133197 Mon Sep 17 00:00:00 2001 From: mitchell Date: Fri, 19 Jun 2026 14:28:16 -0400 Subject: [PATCH 13/22] refactor: rename flag variables for improved clarity and intent --- src/index.ts | 38 +++++++++++++++++++------------------- src/tts.ts | 18 +++++++++--------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/index.ts b/src/index.ts index e3044a7..f5c3ad8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,26 +40,26 @@ async function main() { const newConvo = args.new ?? args.n; const plainOut = args["plain-out"] ?? args.plain ?? !process.stdout.isTTY; const plainErr = args["plain-err"] ?? args.plain ?? !process.stderr.isTTY; - const modelFlag = args.model ?? args.m; - const versionFlag = args.version ?? args.v; - const configFlag = args.config ?? args.c; - const messageFlag = args["git-message"] ?? args.g; - const reviewFlag = args.review ?? args.r; - const fileFlag = args.file ?? args.f; + const modelOverride = args.model ?? args.m; + const showVersion = args.version ?? args.v; + const editConfig = args.config ?? args.c; + const generateGitMessage = args["git-message"] ?? args.g; + const generateReview = args.review ?? args.r; + const inputFile = args.file ?? args.f; - if (versionFlag) { + if (showVersion) { console.log("0.0.0"); return; } - const config = await loadConfig(configFlag); + const config = await loadConfig(editConfig); - if (configFlag || !config) { + if (editConfig || !config) { await initConfig(config); return; } - const model = modelFlag ?? config.model; + const model = modelOverride ?? config.model; const renderer = new Renderer(plainOut, plainErr); @@ -74,22 +74,22 @@ async function main() { if (!newConvo) { await convo.load(); - if (modelFlag) convo.model = modelFlag; + if (modelOverride) convo.model = modelOverride; } let input = positionals.join(" ").trim(); if (input === "-") input = await consumers.text(process.stdin); let prompt = input; - if (messageFlag) { + if (generateGitMessage) { prompt = commitDiff(input); - } else if (reviewFlag && fileFlag) { - const content = await fs.readFile(fileFlag, "utf8"); - prompt = reviewFile(file(fileFlag, content)); - } else if (fileFlag) { - const content = await fs.readFile(fileFlag, "utf8"); - prompt = `${file(fileFlag, content)}\n\n${input}`; - } else if (reviewFlag) { + } else if (generateReview && inputFile) { + const content = await fs.readFile(inputFile, "utf8"); + prompt = reviewFile(file(inputFile, content)); + } else if (inputFile) { + const content = await fs.readFile(inputFile, "utf8"); + prompt = `${file(inputFile, content)}\n\n${input}`; + } else if (generateReview) { prompt = reviewDiff(input); } diff --git a/src/tts.ts b/src/tts.ts index 56981d7..75f37b1 100644 --- a/src/tts.ts +++ b/src/tts.ts @@ -30,19 +30,19 @@ async function main() { allowPositionals: true, }); - const wait = args.wait ?? args.w; - const url = args.url ?? args.u; - const edit = args.edit ?? args.e; + const waitAndCombine = args.wait ?? args.w; + const readURL = args.url ?? args.u; + const editContent = args.edit ?? args.e; let input = positionals.join(" ").trim(); - if (!input) input = await consumers.text(process.stdin); + if (!input) input = (await consumers.text(process.stdin)).trim(); if (!input) { throw new Error("No input was given. Use positional arguments or stdin."); } - if (url) { + if (readURL) { const result = await fetch(input); const window = new Window({ url: input }); const doc = window.document; @@ -60,7 +60,7 @@ async function main() { const home = os.homedir(); const cwd = path.join(home, ".ttsc"); - if (edit) { + if (editContent) { const file = path.join(cwd, "input.txt"); await fs.writeFile(file, input, "utf8"); const editor = process.env.EDITOR || "nano"; @@ -90,7 +90,7 @@ async function main() { stderr: "inherit", })`uv run -- python -u main.py`; - if (!wait) { + if (!waitAndCombine) { koko.catch(() => {}); koko.then(() => { const stop = Date.now(); @@ -124,7 +124,7 @@ async function main() { const file = path.join(cwd, `${info.file}.wav`); - if (wait) { + if (waitAndCombine) { files.push(file); process.stdout.write("."); } else { @@ -141,7 +141,7 @@ async function main() { } await koko; - if (!wait) return; + if (!waitAndCombine) return; const stop = Date.now(); const duration = ((stop - start) / 1000).toFixed(2); From 5b138df664a3ab7cb63480e8b1c8988e820938b1 Mon Sep 17 00:00:00 2001 From: mitchell Date: Fri, 19 Jun 2026 14:33:56 -0400 Subject: [PATCH 14/22] refactor(errors): extract isErrorLike and use ExecaError class in tts --- src/errors.ts | 17 +++++++++++------ src/tts.ts | 4 ++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/errors.ts b/src/errors.ts index a8e9e05..2eac281 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,13 +1,18 @@ import { chalkStderr } from "chalk"; import wrapAnsi from "wrap-ansi"; +export interface ErrorLike { + message: string; +} + +export const isErrorLike = (err: unknown): err is ErrorLike => + typeof err === "object" && + err !== null && + "message" in err && + typeof err.message === "string"; + export function handleTopLevel(err: unknown) { - if ( - typeof err === "object" && - err !== null && - "message" in err && - typeof err.message === "string" - ) { + if (isErrorLike(err)) { if (process.stderr.isTTY) { const [columns] = process.stderr.getWindowSize(); console.error(wrapAnsi(chalkStderr.red(err.message), columns)); diff --git a/src/tts.ts b/src/tts.ts index 75f37b1..4e8c36c 100644 --- a/src/tts.ts +++ b/src/tts.ts @@ -8,7 +8,7 @@ import consumers from "node:stream/consumers"; import util from "node:util"; import { Readability } from "@mozilla/readability"; import chalk from "chalk"; -import { execa } from "execa"; +import { ExecaError, execa } from "execa"; import { Window } from "happy-dom"; import wrapAnsi from "wrap-ansi"; import { handleTopLevel } from "./errors"; @@ -151,7 +151,7 @@ async function main() { try { await execa({ cwd, shell: true })`sox ${files} output.wav`; } catch (err) { - if (err instanceof Object && "code" in err && err.code === "ENOENT") { + if (err instanceof ExecaError && err.code === "ENOENT") { console.error("Sox not found. Unable to combine outputs."); console.log(chalk.yellow(`Audio available in ${cwd}`)); return; From e1033398e977a4dfbd07d5ef925004f281ecb285 Mon Sep 17 00:00:00 2001 From: mitchell Date: Fri, 19 Jun 2026 14:54:03 -0400 Subject: [PATCH 15/22] fix(markdown): correct list item whitespace and indentation --- package.json | 2 +- src/render/index.ts | 2 +- src/render/markdown.ts | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 690ac1a..b27a415 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "ttsc": "dist/tts.js" }, "scripts": { - "dev": "pnpm run bundle src/index.ts &>/dev/null && node dist/index.js", + "dev": "pnpm run bundle src/index.ts >/dev/null && node dist/index.js", "check": "biome check --write && pnpm run tc", "clean": "rm -rf dist", "build": "pnpm run check && pnpm run bundle --minify src/index.ts && pnpm run bundle --minify src/tts.ts", diff --git a/src/render/index.ts b/src/render/index.ts index 01fce63..a75eee2 100644 --- a/src/render/index.ts +++ b/src/render/index.ts @@ -51,7 +51,7 @@ export default class Renderer { this.charsSinceNL = this.charsSinceNL - columns; } - if (this.lineCount >= Math.max(rows - 10, 1)) { + if (this.lineCount >= Math.max(rows - 4, 1)) { this.moveClearReset(-this.lineCount, pipe); } } diff --git a/src/render/markdown.ts b/src/render/markdown.ts index 4450ce0..ca7c59d 100644 --- a/src/render/markdown.ts +++ b/src/render/markdown.ts @@ -76,11 +76,12 @@ class LmcRenderer extends Renderer { this.listitem(item, ordered ? num + index : undefined), ) .join("\n"); + return `\n${renderedItems}\n`; } override listitem({ tokens }: Tokens.ListItem, num?: number) { - const text = this.parser.parse(tokens).trim(); + const text = this.parser.parse(tokens); let marker = ""; if (num !== undefined) { marker = `${num}.`; @@ -91,7 +92,8 @@ class LmcRenderer extends Renderer { const item = `${marker} ${text}`; return item .split("\n") - .map((line) => (line.length > 0 ? ` ${line}` : "")) + .filter((line) => line.length > 0) + .map((line) => ` ${line}`) .join("\n"); } From 49c4138a1c37d36edd669a2c775c6f41922911be Mon Sep 17 00:00:00 2001 From: mitchell Date: Fri, 19 Jun 2026 17:35:05 -0400 Subject: [PATCH 16/22] feat(tts): add --pause flag to control mpv playback --- src/tts.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/tts.ts b/src/tts.ts index 4e8c36c..e6aab05 100644 --- a/src/tts.ts +++ b/src/tts.ts @@ -21,6 +21,8 @@ async function main() { options: { wait: { type: "boolean" }, w: { type: "boolean" }, + pause: { type: "boolean" }, + p: { type: "boolean" }, url: { type: "boolean" }, u: { type: "boolean" }, edit: { type: "boolean" }, @@ -31,6 +33,7 @@ async function main() { }); const waitAndCombine = args.wait ?? args.w; + const pausePlayer = args.pause ?? args.p; const readURL = args.url ?? args.u; const editContent = args.edit ?? args.e; @@ -136,7 +139,7 @@ async function main() { stdin: "ignore", stdout: "inherit", stderr: "inherit", - })`mpv ${file}`; + })`mpv${pausePlayer ? " --pause" : ""} ${file}`; } } @@ -171,7 +174,7 @@ async function main() { stdin: "ignore", stdout: "inherit", stderr: "inherit", - })`mpv --pause output.wav`; + })`mpv${pausePlayer ? " --pause" : ""} output.wav`; } main().catch(handleTopLevel); From da317b7177e75cad4a54ef06786e8ef181f877b6 Mon Sep 17 00:00:00 2001 From: mitchell Date: Wed, 24 Jun 2026 11:54:09 -0400 Subject: [PATCH 17/22] fix(markdown): trim whitespace from parsed list item content --- src/render/markdown.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render/markdown.ts b/src/render/markdown.ts index ca7c59d..7190de8 100644 --- a/src/render/markdown.ts +++ b/src/render/markdown.ts @@ -81,7 +81,7 @@ class LmcRenderer extends Renderer { } override listitem({ tokens }: Tokens.ListItem, num?: number) { - const text = this.parser.parse(tokens); + const text = this.parser.parse(tokens).trim(); let marker = ""; if (num !== undefined) { marker = `${num}.`; From b1730bfaf47b2d6b9c3f0ecefd42a7bcc542390d Mon Sep 17 00:00:00 2001 From: mitchell Date: Fri, 26 Jun 2026 16:55:57 -0400 Subject: [PATCH 18/22] refactor(lmc): modularize prompt generation and add XML-like fences --- src/index.ts | 32 ++++++++++++++++++++------------ src/prompts.ts | 39 ++++++++++++++++++++++----------------- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/index.ts b/src/index.ts index f5c3ad8..be9e0f1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,14 @@ import { streamText } from "ai"; import { initConfig, loadConfig } from "./config"; import { Conversation } from "./conversation"; import { handleTopLevel } from "./errors"; -import { commitDiff, file, reviewDiff, reviewFile } from "./prompts"; +import { + base, + commitDiff, + contextFence, + diffFence, + fileFence, + review, +} from "./prompts"; import Renderer from "./render"; async function main() { @@ -32,6 +39,8 @@ async function main() { r: { type: "boolean" }, file: { type: "string" }, f: { type: "string" }, + stdin: { type: "boolean" }, + i: { type: "boolean" }, }, strict: true, allowPositionals: true, @@ -46,6 +55,7 @@ async function main() { const generateGitMessage = args["git-message"] ?? args.g; const generateReview = args.review ?? args.r; const inputFile = args.file ?? args.f; + const stdinContent = args.stdin ?? args.i; if (showVersion) { console.log("0.0.0"); @@ -77,20 +87,18 @@ async function main() { if (modelOverride) convo.model = modelOverride; } - let input = positionals.join(" ").trim(); - if (input === "-") input = await consumers.text(process.stdin); + const input = positionals.join(" ").trim(); + let stdinText = ""; + if (stdinContent) stdinText = await consumers.text(process.stdin); + let fileText = ""; + if (inputFile) + fileText = fileFence(inputFile, await fs.readFile(inputFile, "utf8")); - let prompt = input; + let prompt = base(input, [fileText, contextFence(stdinText)]); if (generateGitMessage) { - prompt = commitDiff(input); - } else if (generateReview && inputFile) { - const content = await fs.readFile(inputFile, "utf8"); - prompt = reviewFile(file(inputFile, content)); - } else if (inputFile) { - const content = await fs.readFile(inputFile, "utf8"); - prompt = `${file(inputFile, content)}\n\n${input}`; + prompt = commitDiff(stdinText, [fileText]); } else if (generateReview) { - prompt = reviewDiff(input); + prompt = review([fileText, stdinText ? diffFence(stdinText) : "", input]); } if (!prompt) throw new Error("No prompt given."); diff --git a/src/prompts.ts b/src/prompts.ts index 21f1a27..31fae99 100644 --- a/src/prompts.ts +++ b/src/prompts.ts @@ -1,26 +1,31 @@ -export const file = (name: string, input: string) => `${name}: -\`\`\` +const blocks = (input?: string[]) => + input?.map((block) => (block ? `${block}\n` : "")).join("") ?? ""; + +export const base = (input: string, extra?: string[]) => + `${blocks(extra)}${input}`; + +export const fileFence = (name: string, input: string) => ` ${input} -\`\`\``; + +`; -export const commitDiff = (input: string) => `\`\`\`diff +export const diffFence = (input: string) => ` ${input} -\`\`\` +`; -Generate a conventional commit message for this diff. If you are unable to generate a reasonable option, ask for more context.`; +export const contextFence = (input: string) => ` +${input} +`; -export const reviewFile = (input: string) => `${input} +export const commitDiff = ( + input: string, + extra?: string[], +) => `${blocks(extra)}${diffFence(input)} +Generate a conventional commit message for the included context. If you are unable to generate a reasonable option, ask for more context.`; + +export const review = (input: string[]) => `${blocks(input)} +Review the included context for any potential bugs, best practices, performance issues, or security flaws. Today's date is ${new Date().toISOString()}, your version info is likely out-of-date. -Review the included file for any potential bugs, best practices, performance issues, or security flaws. -Let me know if you need any more context to complete your review.`; - -export const reviewDiff = (input: string) => `\`\`\`diff -${input} -\`\`\` - -Today's date is ${new Date().toISOString()}, your version info is likely out-of-date. - -Review the included diff for any potential bugs, best practices, performance issues, or security flaws. Let me know if you need any more context to complete your review.`; From 5b6c65d30f9c9403050fdbce86a9e7d16948f3e2 Mon Sep 17 00:00:00 2001 From: mitchell Date: Fri, 26 Jun 2026 17:06:12 -0400 Subject: [PATCH 19/22] refactor(lmc): use short property for options and simplify flags --- src/index.ts | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/index.ts b/src/index.ts index be9e0f1..59e6635 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,40 +22,32 @@ async function main() { const { values: args, positionals } = util.parseArgs({ args: process.argv.slice(2), options: { - model: { type: "string" }, - m: { type: "string" }, - new: { type: "boolean" }, - n: { type: "boolean" }, - config: { type: "boolean" }, - c: { type: "boolean" }, + model: { type: "string", short: "m" }, + new: { type: "boolean", short: "n" }, + config: { type: "boolean", short: "c" }, plain: { type: "boolean" }, "plain-out": { type: "boolean" }, "plain-err": { type: "boolean" }, - version: { type: "boolean" }, - v: { type: "boolean" }, - "git-message": { type: "boolean" }, - g: { type: "boolean" }, - review: { type: "boolean" }, - r: { type: "boolean" }, - file: { type: "string" }, - f: { type: "string" }, - stdin: { type: "boolean" }, - i: { type: "boolean" }, + version: { type: "boolean", short: "v" }, + "git-message": { type: "boolean", short: "g" }, + review: { type: "boolean", short: "r" }, + file: { type: "string", short: "f" }, + stdin: { type: "boolean", short: "i" }, }, strict: true, allowPositionals: true, }); - const newConvo = args.new ?? args.n; + const newConvo = args.new; const plainOut = args["plain-out"] ?? args.plain ?? !process.stdout.isTTY; const plainErr = args["plain-err"] ?? args.plain ?? !process.stderr.isTTY; - const modelOverride = args.model ?? args.m; - const showVersion = args.version ?? args.v; - const editConfig = args.config ?? args.c; - const generateGitMessage = args["git-message"] ?? args.g; - const generateReview = args.review ?? args.r; - const inputFile = args.file ?? args.f; - const stdinContent = args.stdin ?? args.i; + const modelOverride = args.model; + const showVersion = args.version; + const editConfig = args.config; + const generateGitMessage = args["git-message"]; + const generateReview = args.review; + const inputFile = args.file; + const stdinContent = args.stdin; if (showVersion) { console.log("0.0.0"); From 4aaa80898d0e4c5c08f15ef68b0d9153b4e973fa Mon Sep 17 00:00:00 2001 From: mitchell Date: Fri, 26 Jun 2026 17:46:21 -0400 Subject: [PATCH 20/22] feat(lmc): support multiple files via -f flag and fix empty context --- src/index.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/index.ts b/src/index.ts index 59e6635..ad63ce8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,7 +31,7 @@ async function main() { version: { type: "boolean", short: "v" }, "git-message": { type: "boolean", short: "g" }, review: { type: "boolean", short: "r" }, - file: { type: "string", short: "f" }, + file: { type: "string", short: "f", multiple: true }, stdin: { type: "boolean", short: "i" }, }, strict: true, @@ -46,7 +46,7 @@ async function main() { const editConfig = args.config; const generateGitMessage = args["git-message"]; const generateReview = args.review; - const inputFile = args.file; + const inputFiles = args.file; const stdinContent = args.stdin; if (showVersion) { @@ -82,15 +82,18 @@ async function main() { const input = positionals.join(" ").trim(); let stdinText = ""; if (stdinContent) stdinText = await consumers.text(process.stdin); - let fileText = ""; - if (inputFile) - fileText = fileFence(inputFile, await fs.readFile(inputFile, "utf8")); + const files = []; + for (const file of inputFiles ?? []) + files.push(fileFence(file, await fs.readFile(file, "utf8"))); - let prompt = base(input, [fileText, contextFence(stdinText)]); + let prompt = base(input, [ + ...files, + stdinText ? contextFence(stdinText) : "", + ]); if (generateGitMessage) { - prompt = commitDiff(stdinText, [fileText]); + prompt = commitDiff(stdinText, [...files]); } else if (generateReview) { - prompt = review([fileText, stdinText ? diffFence(stdinText) : "", input]); + prompt = review([...files, stdinText ? diffFence(stdinText) : "", input]); } if (!prompt) throw new Error("No prompt given."); From a8cbfb3665e9f056dac5463738cd8eeb99455b14 Mon Sep 17 00:00:00 2001 From: mitchell Date: Sat, 27 Jun 2026 03:31:03 -0400 Subject: [PATCH 21/22] refactor(lmc): remove intermediate vars and directly access CLI args --- src/index.ts | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/index.ts b/src/index.ts index ad63ce8..dba66eb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -38,30 +38,22 @@ async function main() { allowPositionals: true, }); - const newConvo = args.new; const plainOut = args["plain-out"] ?? args.plain ?? !process.stdout.isTTY; const plainErr = args["plain-err"] ?? args.plain ?? !process.stderr.isTTY; - const modelOverride = args.model; - const showVersion = args.version; - const editConfig = args.config; - const generateGitMessage = args["git-message"]; - const generateReview = args.review; - const inputFiles = args.file; - const stdinContent = args.stdin; - if (showVersion) { + if (args.version) { console.log("0.0.0"); return; } - const config = await loadConfig(editConfig); + const config = await loadConfig(args.config); - if (editConfig || !config) { + if (args.config || !config) { await initConfig(config); return; } - const model = modelOverride ?? config.model; + const model = args.model ?? config.model; const renderer = new Renderer(plainOut, plainErr); @@ -74,25 +66,25 @@ async function main() { const convo = new Conversation(model); - if (!newConvo) { + if (!args.new) { await convo.load(); - if (modelOverride) convo.model = modelOverride; + if (args.model) convo.model = args.model; } const input = positionals.join(" ").trim(); let stdinText = ""; - if (stdinContent) stdinText = await consumers.text(process.stdin); + if (args.stdin) stdinText = await consumers.text(process.stdin); const files = []; - for (const file of inputFiles ?? []) + for (const file of args.file ?? []) files.push(fileFence(file, await fs.readFile(file, "utf8"))); let prompt = base(input, [ ...files, stdinText ? contextFence(stdinText) : "", ]); - if (generateGitMessage) { + if (args["git-message"]) { prompt = commitDiff(stdinText, [...files]); - } else if (generateReview) { + } else if (args.review) { prompt = review([...files, stdinText ? diffFence(stdinText) : "", input]); } From 159547e79958ba11a604418e44de9fa7ef0861a7 Mon Sep 17 00:00:00 2001 From: mitchell Date: Mon, 13 Jul 2026 19:30:20 -0400 Subject: [PATCH 22/22] fix(lmc): handle interrupt signal gracefully --- src/index.ts | 5 ++--- src/render/index.ts | 8 +++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index dba66eb..56c93ed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -38,9 +38,6 @@ async function main() { allowPositionals: true, }); - const plainOut = args["plain-out"] ?? args.plain ?? !process.stdout.isTTY; - const plainErr = args["plain-err"] ?? args.plain ?? !process.stderr.isTTY; - if (args.version) { console.log("0.0.0"); return; @@ -55,6 +52,8 @@ async function main() { const model = args.model ?? config.model; + const plainOut = args["plain-out"] ?? args.plain ?? !process.stdout.isTTY; + const plainErr = args["plain-err"] ?? args.plain ?? !process.stderr.isTTY; const renderer = new Renderer(plainOut, plainErr); const provider = createOpenAICompatible({ diff --git a/src/render/index.ts b/src/render/index.ts index a75eee2..2819467 100644 --- a/src/render/index.ts +++ b/src/render/index.ts @@ -84,7 +84,13 @@ export default class Renderer { async renderStream(stream: AsyncIterableStream>) { this.resetCounts(); - const spin = spinner({ withGuide: false, indicator: "timer" }); + const spin = spinner({ + withGuide: false, + indicator: "timer", + onCancel: () => { + process.exit(130); + }, + }); if (!this.isPlain) { spin.start("Waiting for response"); }