Personal Rust CLI. Name joins Jacob and Taotao.
Create a Codex local environment from the current project directory:
jt codex initWrites .codex/environments/environment.toml and updates .worktreeinclude without running setup
or installing project dependencies. Commit both files so new worktrees receive the setup and
local configuration copy rules. Existing environment files, including custom
configuration, are automatically backed up and overwritten; identical content is left unchanged.
Symlinked configuration paths are rejected. No AGENTS.md files are written.
jt must be available on PATH when the generated setup runs.
Initialization also extends the project's existing Lefthook or simple-git-hooks configuration with
a post-checkout task and runs that manager's installer from the primary worktree. Existing business
hooks and local overrides are preserved. If no configuration exists, a declared manager dependency
selects the manager. JT does not add dependencies. Conflicting managers, unavailable executables,
foreign installed hooks, or failed installation are reported; automatic native Git inheritance is
enabled only after installation is verified. Commit the updated manager configuration so later
manager reinstalls retain the task.
Nonempty Lefthook YAML flow mappings and ESM default re-exports from another file require a
supported local configuration form first; JT reports the unsupported form without rewriting it.
.worktreeinclude remains the only copy list. Defaults select .env.local, .env.*.local,
.nlab/*.local.json, .codex/config.toml, .codex/hooks.json, .claude/settings.local.json,
CLAUDE.local.md, AGENTS.override.md, and the selected manager's local override files. Custom
entries remain intact. JT does not select entire configuration directories, locks, logs, reports,
or indexes. Codex uses its native copy support; generated setup fills missing configuration before
dependency preparation. Ordinary git worktree add uses the manager's post-checkout task, which
only handles initial linked-worktree checkout and never installs dependencies or indexes.
Missing files are copied once from the same repository's primary worktree. Existing target
configuration takes precedence; each worktree can then change independently. JT's copy logic also
preserves valid legacy NLab settings and existing Lefthook override aliases. Codex's native copy
list only protects exact existing paths; it cannot express priority between alternate filenames.
JT never deletes existing configuration or guesses which files Codex copied. For an old worktree,
git worktree add --no-checkout, or creation with hooks disabled, fill missing configuration explicitly:
jt codex inheritOptionally preview an update without replacing the existing configuration:
jt codex init --check--check writes a separate candidate when the generated content differs and reports whether the
existing file is an unchanged managed template. Rerun jt codex init to apply the update; --update
is also accepted and behaves the same as the default. Before replacement, the original bytes are
saved in the reported environment.toml.backup-*/environment.toml path; copy that file back to restore.
Neither command runs setup. --check does not update the copy list or manager configuration and
does not install hooks. Candidate and recovery files should stay out of commits.
Node projects support npm and pnpm. packageManager selects the manager when declared; otherwise
the lockfile selects it (npm when neither exists). Unsupported or ambiguous lockfiles fail before
writing. Setup uses npm ci or pnpm install --frozen-lockfile --prefer-offline when the matching
lockfile exists, otherwise a normal install. Setup first checks the exact dependency inputs,
manager version, Node runtime/ABI, OS/architecture, install options, configuration, and local
dependency layout. A successful installation records a runtime stamp in node_modules.
An exact declared packageManager version must match the available manager; a mismatch stops setup.
Compatible worktrees clone these stamped dependencies using filesystem copy-on-write, preserve
relative symlinks, and adjust package-manager launchers to the new root. Dependency writes then
remain local to the worktree. Repeated setup preserves a compatible stamped environment.
Concurrent setup is serialized by an owner-marked lock; after an interrupted process, verify that
its owner has stopped before removing the reported lock.
Missing installation evidence, changed lockfiles/configuration, external/absolute dependency links,
install lifecycle scripts, incompatible local packages, or unavailable copy-on-write trigger normal
installation. Workspace and internal file:/link: dependencies require matching project contents
and links confined to the new root; workspace source changes trigger installation.
Root and local package installation lifecycle scripts always run normal installation, including repeated setup,
because arbitrary ignored generated outputs cannot be proven valid. Ignored local package directories
whose full layout is unknown also use normal installation without a reusable stamp. Existing
unverified dependencies are not deleted to make cloning possible. Run the generated setup once in
the primary environment to establish an installation baseline; jt codex init only installs the
selected Git hook manager's hooks. Actions use only nonempty scripts present in
package.json: Dev (dev, then start), Unit tests (test:unit:run, test:unit, then test),
and Type check (type-check, typecheck, then type:check). Test scripts and arguments are preserved.
Projects with Cargo.toml receive Cargo Build and Rust tests actions. These actual commands set
CARGO_TARGET_DIR to a common Git cache outside the primary checkout, reusing Cargo's locked build
cache across worktrees; an existing environment variable takes precedence. Cargo.lock enables
--locked. jt codex init --lightweight additionally sets debug output and incremental compilation
defaults to zero in those action commands. This option applies only to Cargo projects and does not
change test selection or features. Setup exports are not relied on, and ordinary terminal commands
outside these actions keep their original environment. Projects without a supported manifest get
no inferred build/test actions.
CodeGraph setup is included only when the current or primary worktree already has
.codegraph/codegraph.db. SQLite online backup produces a consistent snapshot including committed
WAL pages; setup caches one snapshot in the common Git directory and tries to clone that stable
file with copy-on-write. Unsupported filesystems use an independent ordinary copy. It never copies
the live DB main file directly or copies daemon PID/socket/lock files. Existing worktree indexes
are retained and incrementally synced. Shared DB symlinks/hard links are rejected. Projects that
have not enabled CodeGraph are not indexed. If the optional source snapshot cannot be backed up,
setup reports the SQLite error and rebuilds the local index without modifying the source database.
CodeGraph must be on PATH; index initialization, sync, and other setup failures still stop setup.
Logical directory sizes from du do not measure physical copy-on-write savings.
Inspect registered worktrees without deleting historical directories:
jt codex worktrees
jt codex worktrees --check /path/to/evidence.jsonThe check requires explicit, fresh lifecycle facts from the current Codex task/attachment inventory:
{
"version": 1,
"root": "/exact/worktree",
"head": "WORKTREE_HEAD",
"primary_head": "CURRENT_PRIMARY_HEAD",
"attachment_identity_key": "/exact/worktree",
"all_tasks_known": true,
"managed": true,
"pinned": false,
"permanent": false,
"shared": false,
"tasks": [{ "id": "CHAT_ID", "complete": true, "running": false, "retain": false }],
"validation": { "head": "CURRENT_PRIMARY_HEAD", "command": "cargo test --locked", "exit_code": 0 },
"ignored_files_reviewed": true,
"ignored_backups": [{ "source": ".env", "backup": "/outside/worktree/saved.env" }]
}Task completion, running state, ownership, and validation are caller assertions, not inferred from
directory age or private Codex databases. The CLI verifies current Git HEADs, integration into a
clean primary branch, and listed ignored-file backups. Use an empty backup list only after reviewing
that no necessary ignored files need preserving. Necessary directory contents must be listed as
individual files. Primary, locked, pinned, permanent, shared, active, retained, or unknown worktrees
remain protected. Passing checks produces the exact supported Codex archive_worktree invocation
for review; refresh native task/attachment state before executing it. Native archival preserves
uncommitted and unpushed work; ignored files need those external backups.
The CLI does not automatically archive worktrees: Codex exposes no supported completion hook that
also proves integration, successful validation, and complete task ownership. SessionEnd can fire
on exit or idle and cannot establish these facts. Codex's existing native archive/keep-count lifecycle
remains responsible for automatic snapshots and reclamation; existing user settings are preserved.
Historical directories with unknown attachment ownership are inventory-only. git worktree prune
cleans stale registration and does not reclaim real directories. See the official
Codex worktree lifecycle and
hook events.
Validate the generated dependency runtime with node --test apps/jt/src/codex/dependencies.test.mjs
alongside the Rust checks. The suite exercises real local npm/pnpm installs, clone isolation,
incompatible inputs, repeated setup, lifecycle scripts, and concurrent setup protection.
Bootstrap terminal tooling:
jt cli bootstrapInteractive bootstrap supports macOS, Debian/Ubuntu, and WSL on x86_64 and ARM64. It installs:
- Fish or Zsh and Starship with Catppuccin Mocha.
- bat, eza, fd, ripgrep, fzf, btop, zoxide, jq, tldr, git-delta, and lazygit.
- Fish Git abbreviations/functions plus
proxy-onandproxy-off. - Zellij when selected.
The command asks before mutation, changes the default shell, writes only jt-managed shell files, backs up replaced files, and configures git-delta globally. Fish users can explicitly enable proxy-on at shell startup; this option defaults to No and validates GitHub through http://127.0.0.1:7890 before exporting proxy variables. Ghostty, fonts, Node.js, fnm, and pnpm stay outside this bootstrap.
Bootstrap automatically loads jt completions in interactive Fish and Zsh shells when jt is on PATH. Load them manually when needed:
jt completions fish | sourcesource <(jt completions zsh)Install Ghostty on macOS:
jt ghostty installThis separate interactive command installs Ghostty and Maple Mono NF CN through Homebrew, then writes a jt-managed Ghostty configuration. It backs up replaced files and asks before mutation. Linux and WSL servers are rejected without installation.
Write project Zed settings from the live repository template:
jt zed-confThe command finds the current Git repository root and writes .zed/settings.json. Existing
different content is backed up beside the file before an atomic update. The template is fetched
over HTTPS from apps/jt/templates/zed/settings.json on main, so merging a
template-only change updates future command runs without releasing a new jt version.
Find statically unused functions, variables, and files:
jt code unused
jt code unused /path/to/project --kind function,variable,file
jt code unused /path/to/project --mode library --json
# compatibility alias
jt unusedPATH can be a project root or a nested source directory/file. jt walks upward to find the nearest
JavaScript/TypeScript project (package.json, tsconfig.json, or jsconfig.json), builds one reference
graph, and reports findings inside the requested scope. The scan is read-only: it does not run
codegraph init, install packages, or write an index.
Projects can commit .nlab/unused.config.json to control the graph:
{
"version": 2,
"roots": ["src"],
"entrypoints": ["src/main.ts"],
"exclude": [
"src/types/service-type/**",
"src/types/service-enums/**",
"src/mock/**"
]
}roots contains project-relative files or directories; an omitted or empty list means the project
root. Version 1 remains readable. Version 2 adds entrypoints, used when HTML/package scripts cannot
prove the complete runtime roots. exclude accepts project-relative gitignore-style patterns without
negation. Excluded files do not produce findings, but still provide consumer evidence so generated
bootstrap code cannot make a live hand-written target look unused. Test files remain a hard boundary.
An explicit PATH only narrows output inside configured roots. Absolute paths, .., unknown fields,
unsupported versions, and symlinked roots fail before scanning. JSON reports effective scanRoots
and exclude values.
The scanner uses Oxc for JavaScript/TypeScript syntax and the project's TypeScript/Volar installation
for cross-file and Vue template references. It reports only function/method, variable binding, and file
candidates; parameters, catch/import bindings, class fields, abstract methods, and overload signatures
are excluded. Owner-aware reachability prevents dead function/file cycles from protecting themselves.
--mode app is the default: an export declaration alone and a barrel re-export alone are not symbol
usage. --mode library protects only package public-entry closure, not every internal export. Static and
bounded dynamic imports, import.meta.glob, CommonJS require/export, type usage, initializer side effects,
and unresolved runtime boundaries retain exact/potential/unknown distinctions. Missing TypeScript/Volar
dependencies are not installed; affected semantic coverage appears in diagnostics/unknown.
The semantic phase loads the trusted project's installed typescript/vue-tsc packages in a bounded
Node.js helper (120-second timeout). Run it only in workspaces whose dependencies you trust.
Entrypoints come from version 2 config, literal HTML module scripts, supported package scripts, and
package entry fields; a filename named main is not enough. Entrypoint files are excluded only at file
level—their internal declarations remain candidates. Declaration files (*.d.ts) provide type-consumer
evidence without becoming findings. Test files (tests/, __tests__/, *.test.*, *.spec.*) are fully
filtered. Results are sorted by path and source position. --json writes pure JSON to stdout; errors
retain the normal error: stderr format. Unsupported framework auto-imports, reflection, string-based
registries, virtual modules, and unbounded runtime loading become diagnostics/unknown rather than false
unused findings.
Regression coverage includes projects with known answers under apps/jt/tests/fixtures.
unused-golden locks the Oxc fallback; unused-semantic-golden locks TypeScript/Volar, Vue
template, and namespace-import behavior when workspace Node.js dependencies are installed.
Write a self-contained interactive call graph:
jt call-graph
jt call-graph src/views --focus queryQcTemplate --depth 3
jt call-graph --output .nlab/call-graph.html --max-nodes 800
jt call-graph --database .nlab/unused-graph.db
jt call-graph --openThe command reuses the same .nlab/unused.config.json roots and exclusions, Oxc graph, and
TypeScript/Volar semantic pass as jt unused. It merges repeated call sites into directed edges,
keeps import/type-import/re-export/dynamic-import edges distinct, and writes one offline HTML file with no CDN,
server, telemetry, or source-code upload. The Canvas view supports pan, zoom, node dragging, search,
kind filters, caller/callee focus, configurable depth, and call-site details. Output defaults to
.nlab/call-graph.html; jt-generated files can be regenerated, while replacing another HTML file
requires --force. The HTML embeds the complete graph and initially displays at most 400 nodes;
search and focus can reveal the rest. The same run atomically writes .nlab/unused-graph.db, a
read-only SQLite input for @jacob-z/unused-graph-inspector. A 20,000-node/80,000-edge safety ceiling requires narrowing
.nlab/unused.config.json for exceptionally large repositories.
Run the Nuxt SSR inspector from this workspace:
NUXT_GRAPH_DATABASE=/path/to/project/.nlab/unused-graph.db \
pnpm --filter @jacob-z/unused-graph-inspector devIts SSR routes query SQLite on the server. The client renders an aggregated project-area overview,
then uses Cytoscape.js and ELK for focused directed call flows. The npm executable and jt graph
launcher remain deferred.
The graph follows Graphy's split between language analysis, a renderer-independent node/edge model, and an interactive focused view. It does not invent edges for unresolved reflection or string-based dispatch; those gaps remain diagnostics rather than false exact calls.
AI edit hook capabilities are now maintained by nlab. See the jt 4 migration guide for removed commands and retained project data.
The repository is both a Cargo workspace and a pnpm/Turborepo monorepo:
apps/jt jt Rust binary and owned assets/templates
apps/unused-graph-inspector Nuxt graph inspection app
Turborepo's experimental Cargo workspace support discovers jt through
cargo metadata. pnpm build, pnpm check, pnpm lint, and pnpm test therefore orchestrate both
Node packages and Rust crates from one task graph. Cargo and pnpm keep their own manifests and
lockfiles as ecosystem dependency sources.
API contract generation is now provided by nlab api. Existing .nlab configuration and generated artifacts remain in use.
Configure Node.js and Rust package release automation:
jt repo cicdCommand operates on current project root. It discovers and validates publishable Node.js packages
and Cargo workspace members before making changes. With an existing GitHub origin, behavior stays
non-interactive. When Git or origin is missing, an interactive terminal can create one public
GitHub repository through an installed, authenticated GitHub CLI (gh). Run gh auth login first
when needed.
Repository creation asks twice, defaults both confirmations to No, and validates selected
OWNER/REPO against package metadata. It runs git init only after final confirmation and only
when needed, then calls gh repo create without committing or pushing. A failed creation or later
initialization keeps any local Git repository, GitHub repository, and origin already created so
re-running command can continue safely.
After origin validation, command creates:
.github/workflows/npm-release.yml
release-please-config.json
.release-please-manifest.json
The generated caller invokes the versioned reusable workflow in this repository. release-please
owns versions, changelogs, Git tags, and GitHub Releases. npm packages publish through npm Trusted
Publishing; Rust crates publish to crates.io through Trusted Publishing. jt never versions,
tags, or publishes locally and never stores npm or crates.io tokens.
Existing release-please-config.json and .release-please-manifest.json files are preserved so a
repository can own custom release configuration. An existing different caller workflow is never
overwritten.
Initialize global Node/pnpm environment through Vite+:
jt node init
# Use the official npm registry instead of the default npmmirror:
jt node init --npmregister https://registry.npmjs.orgThis interactive command supports macOS/Linux on x86_64 and ARM64. Every Node version is installed through vp env install. On macOS Apple Silicon, a missing arm64 archive triggers installation through a matching x64 Vite+ CLI and requires Rosetta 2. It:
- Installs or reuses Vite+, then prepares Node
14.21.3,16.19.0,20.11.0,22.21.0, and24.15.0. - Installs default pnpm, prewarms pnpm
7.4.1,9.7.0, and10.12.4, then installs nrm. - Configures npm registry, nrm sources, and Vite+ shell loaders.
--npmregister URLdefaults tohttps://registry.npmmirror.com/. Existing nrm sources are reused by URL; new URLs usejt. Built-in source overrides are preserved, and thezzregistry remains configured. The URL must be HTTP(S), without credentials, query, fragment, or whitespace. This controls package installation, migration, and pnpm prewarming. Vite+ itself is always downloaded from npmmirror; Node binaries use their independent download mirror. - Migrates globals whose npm inventory proves an npmjs/npmmirror registry source to Vite+.
- Freshly scans old nvm/fnm/Homebrew/pnpm/Bun state. It shows every cleanup action and asks again before deleting anything.
Both confirmations default to No. Canceling first confirmation makes no changes. Incomplete inventory blocks cleanup. Existing Vite+ installation and additive changes remain when a later stage fails or cleanup is declined.
PNPM/Bun package manifests do not prove install provenance. jt reports those globals as unreconstructable and retains PNPM_HOME/Bun targets instead of guessing a registry package or Bun global directory. After cleanup confirmation, jt fresh-checks both legacy inventory and every target Vite+ global before deleting old providers.
Recursive deletion is limited to exact known dedicated nvm/fnm roots. Custom manager roots and unverified fnm wrappers are reported and retained. Shell cleanup follows actual removable providers; a failed shell write stops all later provider deletion.
Write one JT icon into ./public:
jt icon 64
jt icon svg
jt icon animatedPass a directory as the fourth shell argument to override the destination:
jt icon 64 ./assets
jt icon svg ./assets
jt icon animated ./assetsPNG sizes are 16, 24, 32, 48, 64, 128, 256, 512, and 1024. A PNG selector writes the matching original jt-<size>.png; svg creates jt.svg; animated creates jt-animated.svg. Commands use embedded sources, work offline, create the destination directory, and refuse to overwrite an existing file.
./install.shThe script builds the current source with cargo build --release --locked, installs jt to ~/.local/bin, and verifies the installed binary. Override the directory when needed:
JT_INSTALL_DIR=/path/to/bin ./install.shInstall a published release, replacing vX.Y.Z with a tag from GitHub Releases:
cargo install --git https://github.com/JacobZyy/jt-cli --tag vX.Y.Z --locked --root "$HOME/.local" jtUpgrade an existing user-owned installation:
jt upgrade --check
jt upgrade
jt upgrade --dry-run --forcejt upgrade resolves an exact published GitHub Release and pins its Git commit, asks Cargo to build it in a temporary directory, verifies the staged binary, then atomically replaces ~/.local/bin/jt. A failed post-install version check restores the previous binary. --force reinstalls the current version. The command never runs a shell or sudo. Other paths and package-manager shims must use their original installer or manager. Cargo and Git remain required for jt; persistent rollback is not implemented.
- GitHub repository containing Node.js packages, Rust crates, or both
- Standalone package, Turborepo, npm/pnpm workspace, or Cargo workspace
- Turborepo validation runs
turbo run test build; Cargo workspace validation also runs directly - npm or pnpm; pnpm projects declare an exact
packageManager, such aspnpm@10.15.0 - Lockfile optional for npm; an explicit
packageManagerresolves repositories with stale or multiple lockfiles - Publishable Node.js packages use the npm registry, with
publishConfig.registryabsent orhttps://registry.npmjs.org - Publishable Rust packages use crates.io, with Cargo
publishabsent or including onlycrates-io - Every publishable package declares same GitHub repository, matching existing
originor selectedOWNER/REPO
Private Turborepo roots, private Node.js workspace packages, and Cargo packages with
publish = false are allowed and skipped. Yarn, Bun, Deno, GitLab, non-npm Node registries, other
Cargo registries, and ecosystems other than Node.js/Rust report an explicit unsupported error.
Turborepo is the monorepo build system supported here. Turbopack is a Next.js bundler, not a monorepo manager.
- Run
jt repo cicd, commit generated workflow on a feature branch, then merge it through a PR. - In GitHub Actions settings, grant workflows read/write permission and allow GitHub Actions to create pull requests.
- For every npm package, add a GitHub Actions Trusted Publisher in npm package settings:
- organization or user: package repository owner
- repository: package repository name
- workflow filename:
npm-release.yml - allowed action:
npm publish
- Publish every new crate manually once, then configure its crates.io Trusted Publisher for the
same repository and
.github/workflows/npm-release.yml. crates.io cannot use Trusted Publishing for a crate's first release. - Use Conventional Commits on the repository default branch, for example
feat: add export command.
npm and crates.io authenticate through OIDC on GitHub-hosted runners. npm validates the caller workflow in the consuming repository, not the central reusable workflow. The workflow uses Node.js 24 and no persistent publish token.
Release flow:
- Merge a Conventional Commit PR into the default branch.
- Install, test, and build every detected ecosystem. Turborepo orchestrates Node packages and the Cargo workspace through one task graph.
- release-please creates or updates one manifest Release PR for all changed packages.
- Merge Release PR.
- Same workflow validates again; release-please creates versions,
CHANGELOG.mdfiles, tags, and GitHub Releases. - Released npm packages publish to npm. Released Cargo workspace packages publish to crates.io in dependency order through release-plz.
Validation produces npm tarballs before release. Only isolated publish jobs receive OIDC permission; npm package lifecycle scripts are disabled during packing and publishing. If registry publishing fails after GitHub Release creation, use Re-run failed jobs.
Generated workflow pins reusable workflow to @v1.4.0; consuming repositories never execute
mutable main with write and OIDC permissions.
Push features through a Conventional Commit PR. .github/workflows/release.yml runs release-please on main; its Release PR owns Cargo.toml, Cargo.lock, CHANGELOG.md, the Git tag, and the GitHub Release. Never version or tag jt locally.
pnpm check
pnpm lint
pnpm test
pnpm build
cargo fmt --all --check
cargo clippy --locked --workspace --all-targets --all-features -- -D warningsThe pnpm commands are the normal repository entrypoints. Direct Cargo formatting and strict Clippy remain explicit release gates while Turbo's Cargo integration is experimental.
Activate repository pre-commit hook once per clone:
git config --local core.hooksPath .githooksEvery commit runs cargo test --locked --quiet. Failed tests abort commit. GitHub CI remains final required check because local hooks can be bypassed.
Use Conventional Commits because release-please derives versions and changelog entries from commits on main. Git does not activate repository hooks automatically, and --no-verify can bypass them. For squash merges, enforce Conventional Commit PR titles with GitHub rules or a server-side PR-title check.
release-please currently uses built-in GITHUB_TOKEN. GitHub suppresses workflows triggered by its Release PR. Repositories requiring PR checks need a bot bypass until optional GitHub App token support is added.
MIT