ulnd

Small, focused TypeScript packages for building CLI tools.

$

@ulnd/log

Structured terminal logging with semantic methods and themed output. Phase markers, status messages, color themes, and unicode symbols.

bun add @ulnd/log
#

@ulnd/ranking

Generic pairwise ranking engine with pluggable storage. Elo scoring, interactive sessions, and agent voting with separate human/AI tracks.

bun add @ulnd/ranking
%

@ulnd/env

Lazy, Zod-validated environment variable access with fallbacks. Define your schema once, get typed, cached values everywhere.

bun add @ulnd/env
>

@ulnd/repo

Toolkit for working with code repositories. GitHub API with flexible auth, PR management, Dependabot automation, and git remote parsing.

bun add @ulnd/repo
*

@ulnd/op

1Password CLI wrapper and env sync. CRUD operations on vault items, .env parsing, and bidirectional environment variable synchronization.

bun add @ulnd/op
&

@ulnd/llms

Generate structured plaintext documentation from TypeScript packages. Collects metadata, README, and source into LLM-readable formats.

bun add @ulnd/llms

@ulnd/log

Replace console.log with purpose-driven output. Every method adds appropriate indentation, icons, and color so your CLI looks polished without effort.

import { log, sym, theme } from "@ulnd/log";

log.phase("Building project");
log.step("Compiling TypeScript");
log.success("Build complete");
log.fail("Lint errors found");
log.warn("Deprecated API usage");

log.raw(`PR ${theme.pr(42)} on ${theme.branch("main")}`);
log.divider("Summary");

@ulnd/ranking

Bring your own storage backend. Implement the RankingStore interface, then use interactive sessions or programmatic voting to rank anything.

import { applyRankVote, runInteractiveRank } from "@ulnd/ranking";
import type { RankingStore } from "@ulnd/ranking";

// Implement for your database
const store: RankingStore = {
  loadElo(storage, id)    { /* ... */ },
  updateElo(storage, ...) { /* ... */ },
  recordVote(storage, ..) { /* ... */ },
  resetElo(storage, ...)  { /* ... */ },
};

// Interactive TUI session
await runInteractiveRank(store, storage, items);

// Or apply a single vote
applyRankVote(store, storage, items, {
  option1Id: 1, option2Id: 2,
  choice: "1", agent: false,
});

@ulnd/env

Define your environment schema with Zod. Access is lazy — validation happens on first read and the result is cached. Invalid values fall back silently.

import { z } from "zod";
import { createEnv } from "@ulnd/env";

const env = createEnv({
  PORT: { type: z.coerce.number().int().positive(), fallback: 3000 },
  DEBUG: { type: z.coerce.boolean(), fallback: false },
  API_KEY: { type: z.string().min(1), fallback: "" },
});

env.PORT;    // number — parsed from process.env or 3000
env.DEBUG;   // boolean — lazy, cached after first access
env.API_KEY; // string — fully typed from your schema

@ulnd/repo

All GitHub operations take an explicit Octokit instance. Auth is flexible — use gh auth token, a direct token, or a custom command.

import {
  getOctokit, getPullRequestDetail,
  searchPRsForReview, getRepoInfo,
} from "@ulnd/repo";

const ok = getOctokit(); // defaults to `gh auth token`

const pr = await getPullRequestDetail(ok, "ulnd", "lib", 42);
// { title, files, additions, deletions, statusChecks }

const prs = await searchPRsForReview(ok, { org: "ulnd" });

const { owner, repo } = getRepoInfo("/path/to/repo");

@ulnd/op

Programmatic 1Password access and bidirectional .env synchronization. Store environment variables as Secure Notes with automatic merging and conflict resolution.

import {
  getItem, setField,
  getEnvEntries, saveEnvEntries,
  parseDotenv, diffEnvEntries,
} from "@ulnd/op";

// Read from 1Password
const item = getItem("my-app:env:prod", "Development");

// Sync env vars (merges global + project-specific)
const entries = getEnvEntries("myapp", "prod", {
  vault: "Development", tag: "env",
});

// Diff local .env against 1Password
const local = parseDotenv(localFile);
const diff = diffEnvEntries(local, entries);

@ulnd/llms

Collect package metadata, README, and source files into structured plaintext. Use it to generate llms.txt files for your packages.

import {
  collectPackage, formatPackageFull, formatIndex,
} from "@ulnd/llms";

// Collect a single package
const pkg = collectPackage("/path/to/my-package");
// { info, readme, sources: [{ relativePath, content }] }

// Generate full plaintext with all source code
const txt = formatPackageFull(pkg);

// Generate an index linking to per-package docs
const packages = dirs.map(d => collectPackage(d));
const index = formatIndex(packages, {
  title: "My Org",
  description: "Our packages.",
  baseUrl: "https://example.com",
});