Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

llm-lean-log-cli

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

llm-lean-log-cli - npm Package Compare versions

Comparing version
0.2.2
to
0.2.4
dist/index.js

Sorry, the diff of this file is too big to display

+9
#!/usr/bin/env bun
/**
* CLI tool for llm-lean-log
*/
/**
* Main function for the CLI
*/
export declare function main(): Promise<void>;
//# sourceMappingURL=index.d.ts.map
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA;;GAEG;AA4FH;;GAEG;AACH,wBAAsB,IAAI,kBAuJzB"}
+12
-6
{
"name": "llm-lean-log-cli",
"version": "0.2.2",
"version": "0.2.4",
"description": "CLI tool for llm-lean-log",
"scripts": {
"start": "bun run src/index.ts"
"start": "bun run src/index.ts",
"build": "bun build src/index.ts --outfile dist/index.js --target node --minify && tsc --emitDeclarationOnly",
"prepublishOnly": "bun run build",
"type": "tsc --noEmit"
},

@@ -17,10 +20,10 @@ "repository": {

"homepage": "https://github.com/loclv/llm-lean-log/tree/main/packages/cli#readme",
"main": "src/index.ts",
"module": "src/index.ts",
"main": "dist/index.js",
"module": "dist/index.js",
"type": "module",
"bin": {
"l-log": "src/index.ts"
"l-log": "dist/index.js"
},
"files": [
"src",
"dist",
"README.md",

@@ -38,2 +41,5 @@ "LICENSE"

},
"peerDependencies": {
"typescript": "^5.9.3"
},
"keywords": [

@@ -40,0 +46,0 @@ "llm",

import {
afterEach,
beforeEach,
describe,
expect,
it,
mock,
spyOn,
} from "bun:test";
import * as core from "llm-lean-log-core";
import pkg from "../package.json";
import { main } from "./index";
// Mock core functions
mock.module("llm-lean-log-core", () => ({
loadLogs: mock(() => Promise.resolve([])),
saveLogs: mock(() => Promise.resolve()),
addLogEntry: mock((entries, entry) => [
...entries,
{ ...entry, id: "test-id", "created-at": "2024-01-01" },
]),
filterByTags: mock(() => []),
searchLogs: mock(() => []),
visualizeEntry: mock(() => "visualized entry"),
visualizeStats: mock(() => "visualized stats"),
visualizeTable: mock(() => "visualized table"),
}));
describe("CLI", () => {
let consoleLogSpy: any;
let consoleErrorSpy: any;
let processExitSpy: any;
let bunFileSpy: any;
let bunWriteSpy: any;
let originalArgv: string[];
beforeEach(() => {
consoleLogSpy = spyOn(console, "log").mockImplementation(() => {});
consoleErrorSpy = spyOn(console, "error").mockImplementation(() => {});
processExitSpy = spyOn(process, "exit").mockImplementation(
(code?: string | number | null | undefined) => {
throw new Error(`process.exit(${code})`);
},
);
bunFileSpy = spyOn(Bun, "file").mockImplementation(
() =>
({
exists: () => Promise.resolve(true),
text: () => Promise.resolve(""),
}) as any,
);
bunWriteSpy = spyOn(Bun, "write").mockImplementation(() =>
Promise.resolve(0),
);
originalArgv = process.argv;
});
afterEach(() => {
consoleLogSpy.mockRestore();
consoleErrorSpy.mockRestore();
processExitSpy.mockRestore();
bunFileSpy.mockRestore();
bunWriteSpy.mockRestore();
process.argv = originalArgv;
});
const runCommand = async (args: string[]) => {
process.argv = ["bun", "index.ts", ...args];
await main();
};
it("should show help message with 'help' command", async () => {
await runCommand(["help"]);
expect(consoleLogSpy).toHaveBeenCalled();
expect(consoleLogSpy.mock.calls[0][0]).toContain("l-log CLI");
});
it("should show help message with '--help' flag", async () => {
await runCommand(["--help"]);
expect(consoleLogSpy).toHaveBeenCalled();
expect(consoleLogSpy.mock.calls[0][0]).toContain("l-log CLI");
});
it("should show version with '--version' flag", async () => {
await runCommand(["--version"]);
expect(consoleLogSpy).toHaveBeenCalledWith(pkg.version);
});
it("should show version with '-v' flag", async () => {
await runCommand(["-v"]);
expect(consoleLogSpy).toHaveBeenCalledWith(pkg.version);
});
it("should show version with '-V' flag", async () => {
await runCommand(["-V"]);
expect(consoleLogSpy).toHaveBeenCalledWith(pkg.version);
});
it("should call loadLogs and visualizeTable for 'list' command", async () => {
const { loadLogs, visualizeTable } = core as any;
loadLogs.mockResolvedValueOnce([
{ id: "1", name: "test", problem: "p", "created-at": "t" },
]);
await runCommand(["list"]);
expect(loadLogs).toHaveBeenCalled();
expect(visualizeTable).toHaveBeenCalled();
expect(consoleLogSpy).toHaveBeenCalledWith("visualized table");
});
it("should work with 'ls' alias", async () => {
const { loadLogs, visualizeTable } = core as any;
await runCommand(["ls"]);
expect(loadLogs).toHaveBeenCalled();
expect(visualizeTable).toHaveBeenCalled();
});
it("should call visualizeStats for 'stats' command", async () => {
const { visualizeStats } = core as any;
await runCommand(["stats"]);
expect(visualizeStats).toHaveBeenCalled();
expect(consoleLogSpy).toHaveBeenCalledWith("visualized stats");
});
it("should add a new log entry with 'add' command", async () => {
const { saveLogs, addLogEntry } = core;
await runCommand([
"add",
"New Log",
"--problem=Test Problem",
"--tags=test,cli",
]);
expect(addLogEntry).toHaveBeenCalled();
expect(saveLogs).toHaveBeenCalled();
expect(consoleLogSpy).toHaveBeenCalledWith("Log entry added successfully");
// Verify that saveLogs was called with the expected entries
const savedEntries = (saveLogs as any).mock.calls[0][1];
const lastEntry = savedEntries[savedEntries.length - 1];
expect(lastEntry.name).toBe("New Log");
expect(lastEntry.problem).toBe("Test Problem");
expect(lastEntry.tags).toBe("test,cli");
expect(lastEntry["created-at"]).toBeDefined();
expect(lastEntry.id).toBeDefined();
});
it("should show error and exit if 'add' is missing problem", async () => {
try {
await runCommand(["add", "New Log"]);
} catch (e: any) {
expect(e.message).toBe("process.exit(1)");
}
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining("Please provide a problem description"),
);
});
it("should show error and exit if 'add' is missing name", async () => {
try {
await runCommand(["add"]);
} catch (e: any) {
expect(e.message).toBe("process.exit(1)");
}
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining("Please provide a log name"),
);
});
it("should search logs with 'search' command", async () => {
const { searchLogs, visualizeTable } = core as any;
searchLogs.mockReturnValueOnce([]);
await runCommand(["search", "query"]);
expect(searchLogs).toHaveBeenCalled();
expect(visualizeTable).toHaveBeenCalled();
});
it("should show error and exit if 'search' is missing query", async () => {
try {
await runCommand(["search"]);
} catch (e: any) {
expect(e.message).toBe("process.exit(1)");
}
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining("Please provide a search query"),
);
});
it("should filter logs by tags with 'tags' command", async () => {
const { filterByTags, visualizeTable } = core as any;
filterByTags.mockReturnValueOnce([]);
await runCommand(["tags", "tag1", "tag2"]);
expect(filterByTags).toHaveBeenCalled();
expect(visualizeTable).toHaveBeenCalled();
});
it("should show error and exit if 'tags' is missing tags", async () => {
try {
await runCommand(["tags"]);
} catch (e: any) {
expect(e.message).toBe("process.exit(1)");
}
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining("Please provide at least one tag"),
);
});
it("should view entry at index", async () => {
const { loadLogs, visualizeEntry } = core as any;
loadLogs.mockResolvedValueOnce([
{ id: "1", name: "test", problem: "p", "created-at": "t" },
]);
await runCommand(["view", "0"]);
expect(visualizeEntry).toHaveBeenCalled();
expect(consoleLogSpy).toHaveBeenCalledWith("visualized entry");
});
it("should view last entry with --last flag", async () => {
const { loadLogs, visualizeEntry } = core as any;
loadLogs.mockResolvedValueOnce([
{ id: "1", name: "test1", problem: "p1", "created-at": "t1" },
{ id: "2", name: "test2", problem: "p2", "created-at": "t2" },
]);
await runCommand(["view", "--last"]);
expect(visualizeEntry).toHaveBeenCalledWith(
expect.objectContaining({ id: "2" }),
expect.any(Object),
);
});
it("should show error and exit if 'view' has no logs", async () => {
const { loadLogs } = core as any;
loadLogs.mockResolvedValueOnce([]);
try {
await runCommand(["view", "0"]);
} catch (e: any) {
expect(e.message).toBe("process.exit(1)");
}
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining("No log entries found"),
);
});
it("should show error and exit if 'view' index is NaN", async () => {
const { loadLogs } = core as any;
loadLogs.mockResolvedValueOnce([{ id: "1" }]);
try {
await runCommand(["view", "abc"]);
} catch (e: any) {
expect(e.message).toBe("process.exit(1)");
}
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining("Please provide a valid entry index"),
);
});
it("should show error and exit if 'view' index is out of range", async () => {
const { loadLogs } = core as any;
loadLogs.mockResolvedValueOnce([{ id: "1" }]);
try {
await runCommand(["view", "10"]);
} catch (e: any) {
expect(e.message).toBe("process.exit(1)");
}
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining("out of range"),
);
});
it("should show error and exit if entry is missing at index", async () => {
const { loadLogs } = core as any;
// Create an array with a hole or undefined
loadLogs.mockResolvedValueOnce([undefined]);
try {
await runCommand(["view", "0"]);
} catch (e: any) {
expect(e.message).toBe("process.exit(1)");
}
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining("Entry not found at index 0"),
);
});
it("should show error for unknown command", async () => {
try {
await runCommand(["unknown"]);
} catch (e: any) {
expect(e.message).toBe("process.exit(1)");
}
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining('Unknown command "unknown"'),
);
});
it("should show help when no command is provided", async () => {
await runCommand([]);
expect(consoleLogSpy).toHaveBeenCalled();
expect(consoleLogSpy.mock.calls[0][0]).toContain("l-log CLI");
});
it("should use custom log file if provided", async () => {
const { loadLogs } = core as any;
await runCommand(["list", "custom.csv"]);
expect(loadLogs).toHaveBeenCalledWith("custom.csv");
});
});
#!/usr/bin/env bun
/**
* CLI tool for llm-lean-log
*/
import {
addLogEntry,
filterByTags,
loadLogs,
saveLogs,
searchLogs,
visualizeEntry,
visualizeStats,
visualizeTable,
} from "llm-lean-log-core";
import pkg from "../package.json";
/**
* Help text for LLMs, not human!
*/
const helpText = `l-log CLI
Usage: l-log <command> [log-file] [options]
Commands:
list, ls List all log entries
--compact, -c Show compact view
--human Show human-readable output (with colors)
stats Show log statistics
--human Show human-readable output (with colors)
view <index> View detailed entry at index
--last Show the last log entry
--human Show human-readable output (with colors)
search <query> Search logs by name, problem, or solution
--human Show human-readable output (with colors)
tags <tag1> [tag2] Filter logs by tags
--human Show human-readable output (with colors)
add <name> Add a new log entry
--tags=<tags> Comma-separated tags
--problem=<text> Problem description
--solution=<text> Solution description
--action=<text> Action taken
--files=<files> Comma-separated files paths
--tech-stack=<tech> Comma-separated tech stack
--model=<name> Model name
--causeIds=<ids> Comma-separated cause log row UUIDs
--effectIds=<ids> Comma-separated effect log row UUIDs
--last-commit-short-sha=<sha> Last git commit short SHA
--created-at=<time> Creation time (ISO 8601 formatted string)
--updated-at=<time> Update time (ISO 8601 formatted string)
--created-by-agent=<name> Agent model name
help, -h, --help Show this help message
-v, -V, --version Show version number
Examples for LLMs:
l-log list ./logs/example.csv
l-log stats
l-log view 0
l-log view --last
l-log search "memory"
l-log tags error api
l-log add ./logs/chat1.csv "Fix bug" --tags=bug,fix --problem="Bug description" --files="file1.ts,src/file2.ts" --tech-stack="ts,react" --causeIds="721ace2b-5e73-4901-bef9-97de16bf170f" --last-commit-short-sha="a1b2c3d" --model="gpt-4o"
`;
/**
* Main function for the CLI
*/
export async function main() {
const args = process.argv.slice(2);
const command = args[0];
// Check if second argument is a file (ends with .csv) or a parameter
const isLogFile = (arg: string) => arg?.endsWith(".csv");
const secondArg = args[1];
const hasLogFile = secondArg && isLogFile(secondArg);
const logFile: string = hasLogFile ? secondArg : "./logs/example.csv";
// Get the index where actual parameters start
const paramStart = hasLogFile ? 2 : 1;
let entries = await loadLogs(logFile);
const isHuman = args.includes("--human");
const llm = !isHuman;
switch (command) {
case "list":
case "ls": {
const compact = args.includes("--compact") || args.includes("-c") || llm;
console.log(visualizeTable(entries, { compact, llm }));
break;
}
case "stats": {
console.log(visualizeStats(entries, { llm }));
break;
}
case "view": {
const isLast = args.includes("--last");
const index = isLast
? entries.length - 1
: parseInt(args[paramStart] || "0", 10);
if (entries.length === 0) {
console.error("Error: No log entries found");
process.exit(1);
}
if (Number.isNaN(index)) {
console.error("Error: Please provide a valid entry index");
process.exit(1);
}
if (index >= entries.length || index < 0) {
console.error(
`Error: Index ${index} out of range (0-${entries.length - 1})`,
);
process.exit(1);
}
const entry = entries[index];
if (!entry) {
console.error(`Error: Entry not found at index ${index}`);
process.exit(1);
}
console.log(visualizeEntry(entry, { llm }));
break;
}
case "search": {
const query = args[paramStart];
if (!query) {
console.error("Error: Please provide a search query");
process.exit(1);
}
const results = searchLogs(entries, query);
const compact = args.includes("--compact") || args.includes("-c") || llm;
console.log(visualizeTable(results, { compact, llm }));
break;
}
case "tags": {
const tagsList = args.slice(paramStart).filter((a) => a !== "--human");
if (tagsList.length === 0) {
console.error("Error: Please provide at least one tag");
process.exit(1);
}
const results = filterByTags(entries, tagsList);
const compact = args.includes("--compact") || args.includes("-c") || llm;
console.log(visualizeTable(results, { compact, llm }));
break;
}
case "add": {
const name = args[paramStart];
if (!name) {
console.error("Error: Please provide a log name");
process.exit(1);
}
const findFlag = (flag: string): string | undefined => {
const arg = args.find((a) => a.startsWith(`${flag}=`));
return arg ? arg.split("=")[1] : undefined;
};
const problem = findFlag("--problem");
if (!problem) {
console.error(
"Error: Please provide a problem description with --problem=<text>",
);
process.exit(1);
}
entries = addLogEntry(entries, {
name,
problem,
tags: findFlag("--tags"),
solution: findFlag("--solution"),
action: findFlag("--action"),
files: findFlag("--files"),
"tech-stack": findFlag("--tech-stack"),
model: findFlag("--model"),
id: findFlag("--id"),
causeIds: findFlag("--causeIds"),
effectIds: findFlag("--effectIds"),
"last-commit-short-sha": findFlag("--last-commit-short-sha"),
"created-at": findFlag("--created-at"),
"updated-at": findFlag("--updated-at"),
"created-by-agent": findFlag("--created-by-agent"),
});
await saveLogs(logFile, entries);
console.log("Log entry added successfully");
break;
}
case "help":
case "--help":
case "-h":
console.log(helpText);
break;
case "-v":
case "-V":
case "--version":
console.log(pkg.version);
break;
default:
if (command) {
console.error(`Error: Unknown command "${command}"`);
console.log(helpText);
process.exit(1);
} else {
console.log(helpText);
}
break;
}
}
if (import.meta.main) {
main().catch((error) => {
console.error("Error:", error.message);
process.exit(1);
});
}