Small, focused TypeScript packages for building CLI tools.
Run AI coding assistants (Claude Code, Codex) from your own CLI with automatic fallback. When one assistant hits a quota limit or service error, the next one picks up seamlessly.
bun add @uln/assistant
Commander.js wrapper with styled help theming, plus robust command execution with timeout, streaming, and signal support.
bun add @uln/cmd
Structural linter for TypeScript projects. Checks project-level conventions (package manager, monorepo layout) rather than code syntax.
bun add @uln/lint
Generic pairwise ranking engine with pluggable storage. Currently implements Elo; designed to support alternative scoring algorithms in the future.
bun add @uln/ranking
LLM prompt templates, Zod output schemas, and response parsing utilities. Everything needed to ask an LLM a structured question and validate the response.
bun add @uln/llm-prompts
1Password CLI wrapper and environment variable sync utilities. Programmatic access to `op` CLI operations and bidirectional .env ↔ 1Password synchronization.
bun add @uln/op
Extract AI usage statistics, token consumption, and MCP server configuration from VS Code, Cursor, and Claude Code.
bun add @uln/editor-stats
Lazy, Zod-validated environment variable access with fallbacks.
bun add @uln/env
Interactive CLI prompts: readline wrappers, fuzzy matching, and fzf-powered selection.
bun add @uln/prompts
Structured terminal logging with semantic methods and themed output. Replaces `console.log` with purpose-driven helpers for CLI tools.
bun add @uln/log
Generate structured plaintext documentation from TypeScript packages. Collects package metadata, README, and source files into LLM-readable formats.
bun add @uln/llms
Bundle of essential `@uln` packages for every CLI project. Install one package instead of four.
bun add @uln/essentials
Toolkit for working with code repositories. GitHub API utilities with flexible Octokit authentication, git remote parsing, and PR management.
bun add @uln/repo
Run AI coding assistants (Claude Code, Codex) from your own CLI with automatic fallback. When one assistant hits a quota limit or service error, the next one picks up seamlessly.
import { runAssistant } from "@uln/assistant";
const result = await runAssistant({
assistants: [
{ name: "claude", command: "claude" },
{ name: "codex", command: "codex" },
],
args: ["--print", "Explain this repo"],
claudeMd: "# Instructions\nUse Bun, not Node.",
});
if (result.ok) {
console.log(`Ran with ${result.assistant}`);
} else {
console.error(result.error);
}
Commander.js wrapper with styled help theming, plus robust command execution with timeout, streaming, and signal support.
import { exec, execOrThrow, execStdout, execInherit } from "@uln/cmd";
// Capture output
const result = exec(["git", "status"]);
if (result.ok) console.log(result.stdout);
// Throw on failure
const { stdout } = execOrThrow(["git", "rev-parse", "HEAD"]);
// Just the stdout string
const branch = execStdout(["git", "branch", "--show-current"]);
// Passthrough to terminal (interactive)
execInherit(["bun", "test"]);
Structural linter for TypeScript projects. Checks project-level conventions (package manager, monorepo layout) rather than code syntax.
import { createLinter } from "@uln/lint";
const linter = createLinter();
const result = linter.run(".");
for (const diagnostic of result.diagnostics) {
console.log(`${diagnostic.severity}: ${diagnostic.message}`);
}
Generic pairwise ranking engine with pluggable storage. Currently implements Elo; designed to support alternative scoring algorithms in the future.
import type { RankingStore } from "@uln/ranking";
const store: RankingStore = {
loadElo(storage, entityId) { /* load from your DB */ },
updateElo(storage, entityId, rating, matches, agent) { /* persist */ },
recordVote(storage, id1, id2, selectedId, opts) { /* save vote */ },
resetElo(storage, entityId, agent) { /* reset to defaults */ },
};
LLM prompt templates, Zod output schemas, and response parsing utilities. Everything needed to ask an LLM a structured question and validate the response.
import { composePrompt, parseLlmJsonArray, PREFERENCE_EXTRACTION, LlmPreferenceSchema } from "@uln/llm-prompts";
// Build the prompt
const prompt = composePrompt(PREFERENCE_EXTRACTION, transcriptText);
// Send to your LLM of choice, then parse
const preferences = parseLlmJsonArray(llmResponse, LlmPreferenceSchema);
// Typed array — invalid elements silently dropped
1Password CLI wrapper and environment variable sync utilities. Programmatic access to `op` CLI operations and bidirectional .env ↔ 1Password synchronization.
import { getItem, createSecureNote, setField, deleteField } from "@uln/op";
const item = getItem("my-app-config", "Development");
// { id, title, fields: [{ label, value, type, section }] }
const note = createSecureNote({
title: "my-app:env:production",
vault: "Development",
tags: ["env"],
});
setField("my-app:env:production", "Development", "env", "API_KEY", "sk-...");
deleteField("my-app:env:production", "Development", "env", "OLD_VAR");
Extract AI usage statistics, token consumption, and MCP server configuration from VS Code, Cursor, and Claude Code.
import { fetchEditorStats } from "@uln/editor-stats";
const stats = fetchEditorStats();
// AI usage from VS Code / Cursor
for (const day of stats.aiStats) {
console.log(`${day.day}: ${day.totalAccepted} accepted, ${day.totalRejected} rejected`);
}
// Claude Code daily stats
for (const day of stats.claudeCodeStats) {
console.log(`${day.day}: ${day.totalCost}`);
}
// Claude Code token usage by model
for (const usage of stats.claudeCodeTokens) {
console.log(`${usage.model}: ${usage.totalTokens} tokens, $${usage.totalCostUsd}`);
}
// Discovered MCP servers
for (const server of stats.mcpServers) {
console.log(`${server.name}: ${server.command} (${server.source})`);
}
Lazy, Zod-validated environment variable access with fallbacks.
import { z } from "zod";
import { createEnv, v } from "@uln/env";
const env = createEnv({
// Required — throws on startup if missing or invalid
DATABASE_URL: { schema: z.string().url() },
API_KEY: { schema: v.nonEmpty() },
// Optional — uses fallback when missing, warns on invalid
PORT: { schema: v.port(), fallback: 3000 },
DEBUG: { schema: v.boolean(), fallback: false },
NODE_ENV: { schema: v.oneOf(["development", "production", "test"]), fallback: "development" },
ALLOWED_ORIGINS: { schema: v.csv(), fallback: ["localhost"] },
REQUEST_TIMEOUT: { schema: v.duration(), fallback: 30_000 },
});
env.PORT; // number — 3000 or parsed from process.env.PORT
env.DATABASE_URL; // string — validated URL, throws if not set
env.DEBUG; // boolean — parsed from "true"/"false"/"1"/"0"/"yes"/"no"
Interactive CLI prompts: readline wrappers, fuzzy matching, and fzf-powered selection.
import { askLine } from "@uln/prompts";
const name = await askLine("Project name: ");
Structured terminal logging with semantic methods and themed output. Replaces `console.log` with purpose-driven helpers for CLI tools.
import { log, sym, theme } from "@uln/log";
log.phase("Building project");
log.step("Compiling TypeScript");
log.success("Build complete");
log.fail("Lint errors found");
log.warn("Deprecated API usage");
log.info("Using config from ~/.config/app");
log.dim("skipped (already up to date)");
log.item("src/index.ts");
log.detail("14 files changed");
log.divider("Summary");
log.blank();
Generate structured plaintext documentation from TypeScript packages. Collects package metadata, README, and source files into LLM-readable formats.
import { collectPackage } from "@uln/llms";
const pkg = collectPackage("/path/to/my-package");
// { info: { name, version, dependencies }, readme, sources: [{ relativePath, content }] }
Bundle of essential `@uln` packages for every CLI project. Install one package instead of four.
import { log, createEnv, exec, askLine, fzfSelect, Command } from "@uln/essentials";
Toolkit for working with code repositories. GitHub API utilities with flexible Octokit authentication, git remote parsing, and PR management.
import { getOctokit } from "@uln/repo";
// Default: runs `gh auth token` to get a token
const octokit = getOctokit();
// Or provide a token directly
const octokit = getOctokit({ token: "ghp_..." });
// Or use a custom command
const octokit = getOctokit({ tokenCommand: ["op", "read", "op://vault/github/token"] });