# ulnd > Small, focused TypeScript packages for building CLI tools. ## Packages - [@uln/cmd](https://lib.uln.industries/cmd.txt): Commander.js wrapper with styled help theming - [@uln/ranking](https://lib.uln.industries/ranking.txt): Generic pairwise ranking engine with pluggable storage. - [@uln/op](https://lib.uln.industries/op.txt): 1Password CLI wrapper and environment variable sync utilities. - [@uln/env](https://lib.uln.industries/env.txt): Lazy, Zod-validated environment variable access with fallbacks. - [@uln/hooks](https://lib.uln.industries/hooks.txt): Small, focused React hooks. - [@uln/log](https://lib.uln.industries/log.txt): Structured terminal logging with semantic methods and themed output. - [@uln/llms](https://lib.uln.industries/llms-pkg.txt): Generate structured plaintext documentation from TypeScript packages. - [@uln/repo](https://lib.uln.industries/repo.txt): Toolkit for working with code repositories. ## Using These Packages These libraries can be **installed from npm** or **copied inline** into your project. **Prefer installing over copying.** Installed packages receive updates automatically. Inline copies are frozen at the version they were copied from. ```bash bun add @uln/log ``` If you must copy code inline (e.g. to avoid a dependency), include a provenance comment at the top of each copied file: ```typescript // @uln-source { "package": "@uln/log", "version": "0.2.0", "file": "src/log.ts", "source": "https://github.com/ulnd/lib/tree/v0.2.0/packages/log/src/log.ts" } ``` The `@uln-source` tag must be a single-line JSON object with these fields: | Field | Description | |---|---| | `package` | npm package name, e.g. `@uln/env` | | `version` | Version the code was copied from, e.g. `0.2.0` | | `file` | Relative path within the package, e.g. `src/create-env.ts` | | `source` | Permalink to the source file at that version | This enables automated tooling to detect stale inline copies and suggest updates. ## @uln/cmd Commander.js wrapper with styled help theming ## @uln/ranking Generic pairwise ranking engine with pluggable storage. Currently implements Elo; designed to support alternative scoring algorithms in the future. ## Install ```bash bun add @uln/ranking ``` ## Usage ### Implement a storage adapter ```typescript 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 */ }, }; ``` ### Apply a vote programmatically ```typescript import { applyRankVote } from "@uln/ranking"; applyRankVote(store, storage, items, { option1Id: 1, option2Id: 2, choice: "1", agent: false, vote: { model: "human", votedAt: Date.now() }, }); ``` ### Run an interactive ranking session ```typescript import { runInteractiveRank } from "@uln/ranking"; await runInteractiveRank(store, storage, items, { agent: false }); ``` ### Show current rankings ```typescript import { showRankings } from "@uln/ranking"; showRankings(store, storage, items, { agent: false }); ``` ## Key concepts - **`RankingStore`** — Adapter interface for persistence. Implement this to plug in SQLite, Postgres, or anything else. - **`RankingStorage`** — Describes table/column layout so the store knows where to read and write. - **`RankedItem`** — An item with `id`, `key`, `value`, and optional `category`. - Tracks separate Elo ratings for **human** and **agent** votes. - Pair selection prioritizes items with the fewest matches to ensure even coverage. ## @uln/op 1Password CLI wrapper and environment variable sync utilities. Programmatic access to `op` CLI operations and bidirectional .env ↔ 1Password synchronization. Requires the [1Password CLI](https://developer.1password.com/docs/cli/) (`op`) to be installed and authenticated. ## Install ```bash bun add @uln/op ``` ## Usage ### 1Password operations ```typescript 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"); ``` ### .env file parsing ```typescript import { parseDotenv, serializeDotenv, diffEnvEntries } from "@uln/op"; const entries = parseDotenv("PORT=3000\nDEBUG=true"); // [{ key: "PORT", value: "3000" }, { key: "DEBUG", value: "true" }] const content = serializeDotenv(entries); // "PORT=3000\nDEBUG=true\n" const diff = diffEnvEntries(localEntries, opEntries); // { localOnly: [...], opOnly: [...], mismatched: [...] } ``` ### Environment store (1Password-backed) ```typescript import { getEnvEntries, saveEnvEntries } from "@uln/op"; // Fetch env vars (merges global + project-specific) const entries = getEnvEntries("myapp", "production", { vault: "Development", tag: "env", }); // Save env vars to 1Password const result = saveEnvEntries("myapp", "production", entries, { vault: "Development", tag: "env", }); ``` ## Storage pattern Environment variables are stored as 1Password Secure Notes with the naming convention `{prefix}:env:{key}`. Each variable is a concealed field in an "env" section. A global key (default: `"global"`) holds shared variables; project-specific keys override global values. ## @uln/env Lazy, Zod-validated environment variable access with fallbacks. ## Install ```bash bun add @uln/env ``` ## Usage ```typescript import { z } from "zod"; import { createEnv } from "@uln/env"; const env = createEnv({ PORT: { type: z.coerce.number().int().positive(), fallback: 3000 }, DATABASE_URL: { type: z.string().url(), fallback: "sqlite:///tmp/dev.db" }, DEBUG: { type: z.coerce.boolean(), fallback: false }, API_KEY: { type: z.string().min(1), fallback: "" }, }); env.PORT; // 3000 (or parsed from process.env.PORT) env.DATABASE_URL; // Lazy-parsed on first access, cached after ``` ## How it works - Values are **lazily parsed** from `process.env` on first property access - Results are **cached** — Zod validation runs only once per variable - On validation failure, the **fallback** value is used silently - The returned object is fully typed based on your Zod schemas ## @uln/hooks Small, focused React hooks. **This package is private** — it serves as the source of truth for copy-paste snippets. ## Usage Copy individual hook files into your project and add the `@uln-source` provenance comment: ```typescript // @uln-source { "package": "@uln/hooks", "version": "0.1.0", "file": "src/use-media-query.ts", "source": "https://github.com/ulnd/lib/tree/v0.1.0/packages/hooks/src/use-media-query.ts" } ``` ## Hooks | Hook | File | Description | |---|---|---| | `useForm` | `src/use-form.ts` | Zod-driven form state with per-field validation and spreadable props | | `useHover` | `src/use-hover.ts` | Hover state tracking with spreadable event handlers | | `useLocalStorage` | `src/use-local-storage.ts` | useState backed by localStorage, optional Zod validation | | `useMediaQuery` | `src/use-media-query.ts` | SSR-safe media query via useSyncExternalStore | | `useScrollPosition` | `src/use-scroll-position.ts` | Tracks scroll position of a container (up/down, at top/bottom) | | `useServerAction` | `src/use-server-action.ts` | Async action wrapper with saving/error/saved states | | `useSessionStorage` | `src/use-session-storage.ts` | useState backed by sessionStorage, optional Zod validation | ## @uln/log Structured terminal logging with semantic methods and themed output. Replaces `console.log` with purpose-driven helpers for CLI tools. ## Install ```bash bun add @uln/log ``` ## Usage ```typescript 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(); ``` ### Themed strings ```typescript log.raw(`PR ${theme.pr(42)} on ${theme.branch("main")} is ready`); log.raw(`Run ${theme.cmd("bun install")} to continue`); log.raw(`${theme.success("3 passed")}, ${theme.fail("1 failed")}`); ``` ### Symbols ```typescript console.log(`${sym.success} Done`); // ✔ Done console.log(`${sym.fail} Error`); // ✖ Error console.log(`${sym.arrow} Next`); // → Next ``` ## API **`log`** — Semantic logging methods: `phase`, `step`, `success`, `fail`, `warn`, `info`, `dim`, `item`, `detail`, `divider`, `prLine`, `summaryHeader`, `summaryLine`, `blank`, `raw`, `error`. **`theme`** — Color functions: `phase`, `step`, `success`, `fail`, `warn`, `skip`, `info`, `dim`, `pr`, `pkg`, `branch`, `severity`, `count`, `path`, `repo`, `cmd`. **`sym`** — Unicode symbols: `phase`, `success`, `fail`, `warn`, `skip`, `pending`, `arrow`, `dot`, `bar`. ## @uln/llms Generate structured plaintext documentation from TypeScript packages. Collects package metadata, README, and source files into LLM-readable formats. ## Install ```bash bun add @uln/llms ``` ## Usage ### Collect a package ```typescript import { collectPackage } from "@uln/llms"; const pkg = collectPackage("/path/to/my-package"); // { info: { name, version, dependencies }, readme, sources: [{ relativePath, content }] } ``` ### Generate full plaintext docs ```typescript import { collectPackage, formatPackageFull } from "@uln/llms"; const pkg = collectPackage("/path/to/my-package"); const txt = formatPackageFull(pkg); // Markdown document with README + all source files in fenced code blocks ``` ### Generate an index of multiple packages ```typescript import { collectPackage, formatIndex } from "@uln/llms"; const packages = dirs.map((d) => collectPackage(d)); const index = formatIndex(packages, { title: "My Org", description: "Our TypeScript packages.", baseUrl: "https://example.com", }); // Overview document with package list and per-package links to {slug}.txt ``` ## API **`collectPackage(dir)`** — Read package.json, README.md, and all source files from a directory. **`formatPackageFull(pkg)`** — Render a single package as a complete plaintext document with all source code. **`formatPackageSummary(pkg)`** — Render a brief summary (README content only, no source). **`formatIndex(packages, opts)`** — Render an overview document listing all packages with links. ## @uln/repo Toolkit for working with code repositories. GitHub API utilities with flexible Octokit authentication, git remote parsing, and PR management. ## Install ```bash bun add @uln/repo ``` ## Usage ### Authentication ```typescript 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"] }); ``` ### Pull requests ```typescript import { getOctokit, getPullRequestDetail, searchPRsForReview, mergePullRequest } from "@uln/repo"; const ok = getOctokit(); const pr = await getPullRequestDetail(ok, "owner", "repo", 42); // { title, headRefName, files, additions, deletions, statusChecks } const prs = await searchPRsForReview(ok, { org: "myorg", limit: 20 }); await mergePullRequest(ok, "owner", "repo", 42); ``` ### Git remote parsing ```typescript import { getRepoInfo } from "@uln/repo"; const { owner, repo } = getRepoInfo("/path/to/repo"); // { owner: "ulnd", repo: "lib" } ``` ### Dependabot ```typescript import { getOctokit, listDependabotPRs, listDependabotAlerts } from "@uln/repo"; const ok = getOctokit(); const prs = await listDependabotPRs(ok, "owner", "repo"); const alerts = await listDependabotAlerts(ok, "owner", "repo"); ``` ## API All GitHub operations take an explicit `Octokit` instance as the first parameter, making it easy to wrap with your own authentication logic. **Auth:** `getOctokit`, `resetOctokit` **PRs:** `getPullRequestDetail`, `getPullRequestDetails`, `getPullRequestDiff`, `createPullRequest`, `approvePullRequest`, `mergePullRequest`, `searchPRsForReview` **Branches:** `listBranches`, `deleteRemoteBranch`, `getDefaultBranch`, `getDefaultBranchForCwd` **Dependabot:** `listDependabotPRs`, `countDependabotPRs`, `listDependabotAlerts` **Repos:** `listOrgRepos`, `getRepoInfo` **Utilities:** `run` (subprocess), `parseWithSchema` (Zod validation)