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 without running setup or installing dependencies.
Commit this file so Codex can use it when creating worktrees. Existing files, including custom
configuration, are preserved unchanged; symlinked configuration paths are rejected. To regenerate,
move the existing file aside first and compare the result before replacing your custom settings.
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. 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). Projects without package.json
receive no dependency install or inferred actions.
CodeGraph setup is included only when the current project already has .codegraph/codegraph.db.
Later worktree setup uses SQLite backup from the primary worktree when available, then syncs the
independent database; if no database can be copied, it initializes one for this already-enabled
project. CodeGraph must be on PATH; missing CodeGraph prints a warning. Setup failures stop setup.
No hooks, global instructions, task orchestration, or worktree integration settings are changed.
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.
Configure project-local AI-edit hooks:
jt ai-hookThe questionnaire selects checks (Vitest, ESLint) and agent terminals (Codex). Re-running shows
the installed selection; deselecting a check removes its maintained runner. Automation can provide
the final selection without a TTY:
jt ai-hook --checks vitest,eslint --agents codexRun inside a Git repository whose root package.json directly declares tsx and each selected
tool. The command installs nothing. Workspace-package-only tooling is unsupported. jt vitest is a
reserved placeholder; old jt vitest ai-hook arguments are removed.
Installation writes maintained TypeScript runtime under .codex/hooks/jt-ai-hook/, then merges one
handler into each .codex/hooks.json stage. PreToolUse fingerprints candidate patches.
PostToolUse records content changes. Stop discovers direct .ts plugins under
stop/runner/, sorts them, and executes all concurrently. Built-in vitest.ts and eslint.ts
runners can be attached independently; custom runner files remain untouched. Existing unrelated
handlers remain, including handlers sharing a group with migrated entries. Old jt-vitest and
nlab-eslint handlers are replaced, preventing duplicate execution. Known legacy files bearing
their ownership markers are removed; unmarked, custom, and symlinked files remain. Empty legacy
directories are removed. Re-running is idempotent.
Review and trust project hooks with /hooks. Runtime does not call jt.
The Stop entry launches each tool through asynchronous child processes with shell: false. Both
runners receive isInAIHook=true and NO_COLOR=1; process, environment, exit state, Vite server,
and logger state stay isolated. One slow runner does not delay starting another.
Vitest runs related once over all AI-edited files with its native agent reporter. Coverage is
limited to edited files matching resolved project coverage.include and not matching
coverage.exclude; no match disables coverage. Provider, thresholds, and skipFull stay project
owned. A temporary coverage-summary.json is parsed into one Markdown table, then deleted. Raw
coverage output never reaches the model. ESLint checks only supported existing edited files and
returns at most 50 error diagnostics.
All runners finish before one combined result is produced. Success stays model-silent. Failures use
stable runner order and combine ESLint diagnostics with Vitest test/coverage sections. Coverage-only
failure returns the table and concise threshold conclusions. First failure blocks for repair; retry
continues once to prevent a Stop loop. Bounded details remain in /tmp/jt-ai-hook-<repo>.jsonl.
Inspect those local traces in the web console:
pnpm install
pnpm --filter ai-hook-console devOpen http://localhost:3000. The console lists sessions with status, trigger counts, context,
and code paths. Each detail page renders hook messages as Markdown, shows the session transcript,
and reconstructs apply_patch diffs from Codex session JSONL. It reads /tmp/jt-ai-hook-*.jsonl,
~/.codex/sessions, and ~/.codex/archived_sessions without modifying them. Set
AI_HOOK_LOG_DIR or CODEX_HOME to use different local directories.
On macOS, install the production server as a login service:
pnpm --filter ai-hook-console service:installThe LaunchAgent listens only on 127.0.0.1:3100, starts at login, and restarts after failure.
After changing console code, rebuild and restart it with one command:
pnpm --filter ai-hook-console service:restartUse service:status to inspect it or service:uninstall to remove the LaunchAgent. Runtime logs
are stored in ~/Library/Logs/jt-ai-hook-console.log and
~/Library/Logs/jt-ai-hook-console.error.log.
The repository is both a Cargo workspace and a pnpm/Turborepo monorepo:
apps/jt jt Rust binary and owned assets/templates
apps/ai-hook-console Next.js App Router console
apps/nlab-api-docs VitePress documentation for nlab-api
crates/nlab-api shared nlab-api library and standalone binary
packages/ai-hook-core AI-hook and Codex JSONL reader
packages/ui shared UI components and theme
Turborepo's experimental Cargo workspace support discovers jt and nlab-api 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.
Session data can contain private code and conversation text. Keep this console bound to a trusted local environment unless authentication is added.
Run the nlab-api documentation locally:
pnpm --filter @workspace/nlab-api-docs devInitialize one frontend project from its real build, TypeScript, request, response-envelope, output, and backend RPC contract layout:
The backend supplies interface directories through backend.contractRoots (or repeated
--contract-root during init). nlab-api reads interface methods from those directories, then
queries ZGateway before cross-repository and enum analysis. Only methods with a matching HTTP
route enter generated contracts; an unmatched method is dropped. Gateway lookup failure stops
generation instead of producing a pending API. The first parameter's package no longer decides
HTTP eligibility. When a method has a com.zhuanzhuan.arch.zgateway.support context parameter,
the context is excluded from the frontend request. At most one business parameter is supported.
Offline generation requires matching routes in an existing .nlab/contract-ir.json.
jt nlab-api config --runner jt --project /path/to/frontend
jt nlab-api init \
--project /path/to/frontend \
--repo-url git@example.com:team/backend.git \
--clone-dir /path/to/backend \
--branch feature-branch \
--app-name service_name
jt nlab-api generate --project /path/to/frontendGenerate semantic Mock responses directly from existing OpenAPI, without synchronizing the backend:
nlab-api mock --project /path/to/frontend --rules mock-rules.json --output-root mockThe standalone command and optional Mock phase in generate share one implementation. jt nlab-api mock follows the project runner: standalone by default, embedded when explicitly configured as jt. See Semantic Mock and native Whistle rules for one response per operation, semantic samples, fixed seeds, and independent manifests.
Both binaries write the same project-local runner config:
jt nlab-api config --runner jt --project /path/to/frontend
nlab-api config --runner nlab-api --project /path/to/frontend
jt nlab-api config --show --project /path/to/frontend
jt nlab-api config --detect --project /path/to/frontendThe preference lives in ignored .nlab/nlab-api.local.json. nlab-backend-bridge uses the configured
runner as the sole command source. When the value is missing, it invokes config --detect, which checks
jt first, then nlab-api, and persists the first available command. config --unset clears the value
for detection on next use.
The standalone binary uses the same Rust library and config:
nlab-api init \
--project /path/to/frontend \
--repo-url git@example.com:team/backend.git \
--clone-dir /path/to/backend \
--branch feature-branch \
--app-name service_name
nlab-api generate --project /path/to/frontend
nlab-api generate --project /path/to/frontend --branch another-branchinit accepts either --repo-url or an existing --repo-path. It clones missing repositories,
writes team-owned settings to .nlab/nlab-api.config.json, writes the resolved machine path to ignored
.nlab/nlab-api.local.json. frontend.aliases.enabled controls additional API/type/enum aliases:
init defaults it to true when it finds a Vite config and false otherwise. When disabled, generated
imports use the existing @/ source-root alias and init does not patch build, TypeScript, or test configs.
When enabled, init adds idempotent Vite and TypeScript aliases; Vitest configs that merge the Vite config
inherit them without a duplicate edit. An exported nlabRequest adapter is still required.
Existing src/api or src/service layout selects the
matching preset; --layout overrides it. Version 1 project config and .nlab/cli.local.json remain
read-compatible; rerun init explicitly to write the split version 2 files.
Existing configs without frontend.aliases.enabled retain the previous enabled behavior. Set it to
false in .nlab/nlab-api.config.json to opt out, including in Vite projects. Generated directories
must then remain inside frontend.sourceRoot; the project owns its existing @/ alias configuration.
generate loads both config scopes, serializes access to the shared backend checkout, clones it when
missing, rejects tracked changes, switches to the configured or one-run --branch, and
fast-forwards it from origin. It then runs codegraph init or codegraph sync once, reads the resulting
SQLite index in read-only mode, parses Java with Tree-sitter, and builds one deterministic contract IR.
Rust generates Draft OpenAPI 3.1, TypeScript DTO files, separate enum files, and API clients that reuse
the detected request adapter. Before building the IR, it queries testserver ZGateway and keeps only
interface methods with matching HTTP routes. It then
migrates business imports from the fixed previous .nlab snapshot, optionally generates Mock files
when mock.enabled is true, promotes the stable OpenAPI snapshot, and writes one final report.
By default it does not invoke Bun, Node.js, Orval, Python, frontend typecheck, tests, builds, or lint;
configured afterGenerate hooks may run project-owned commands. A DTO field uses an enum only when
code evidence confirms its association with the enum's primary value. Comments, annotations, and
linked class names remain candidates; unverified fields keep their original scalar type.
Generated request arguments and the outermost request DTO fields are optional, including inherited
fields flattened into that DTO. Nested object and array-item fields retain their original requiredness.
OpenAPI request bodies follow the same policy. This is a client
typing convention, not a claim that the backend accepts every omitted value. Response fields keep
their existing requiredness; DTOs shared by requests and responses use separate response views.
API clients return the backend response type directly, including generic responses and primitives;
the generator does not create an extra per-operation *Data alias file. Import migration recognizes
the configured API, type, and enum aliases as well as relative imports and @/.
Each API file keeps its paths in one local API_URLS object, and request functions read their paths
from that object. No per-operation URL getter functions are generated.
Each generated enum identifies its Java class and canonical primary field. Display names, descriptions,
colors, and other auxiliary properties do not produce separate enums or frontend mapping tables.
Cross-repository discovery is enabled by default, including for existing projects without discovery settings. The backend collection defaults to the entry backend repository's parent directory. A saved local collection takes precedence; override and persist it with:
jt nlab-api generate --project /path/to/frontend --repositories-root /path/to/backend-projectsinit saves the collection locally, and every generate discovers dependencies before writing
generated artifacts. Direct discover uses the same default directory. Each repository has its own
CodeGraph index, initialized or synchronized automatically. Missing services are resolved through SIC
and cloned from the company GitLab.
Every online run retries unavailable services and updates existing dependencies with fast-forward Git
pulls. Dependency branches default to master; persist an override with
jt nlab-api discover --project /path/to/frontend --service-branch service-name=feature-branch.
Branches and the latest service results live in discovery.services. Legacy allowMissing flags
are ignored and removed when discovery writes the config. Acquisition failures produce warnings and
leave related domains open; generation continues using available sources. Failed updates do not reuse
stale repository indexes. Ambiguous associations or independent index failures still block generation.
Resolved repository indexes are combined with separate node/file identities. Enum extraction follows
cross-repository requests and DTO copies. A verified same-value enum lookup or traceable DTO copy
can associate an HTTP field with a complete enum projection without proving database write invariants.
Such patches retain known status and knownValues; code-backed enum generation also requires
primaryEnumValue: true. Response-field comments may generate enums when their values cover every
resolved write and no write is unknown. Explicit null branches remain nullable. Conflicting projections,
unresolved writes, transformed values, and unrelated objects stay open and do not create orphan enum
files. enumCandidate records whether a comment candidate is verified, conflicting, unverified, or
ignored; actual code evidence takes precedence. No AI calls are used. init, discover, and generate accept --offline to use the current checkout without Git
network operations; offline discovery skips SIC queries and cloning; offline generation reuses Gateway
routes from an existing .nlab/contract-ir.json matching the current backend commit. See
repository discovery for scope and limitations.
The top-level EnumIrisable boolean in .nlab/nlab-api.config.json controls TypeScript enum syntax.
It defaults to true, including when omitted from existing configs: generation uses an as const
object and a same-name type alias. Set "EnumIrisable": false to generate native export enum
declarations instead. Both modes preserve member values, labels, source comments, and import paths.
Initialization writes the setting and preserves an existing value on subsequent runs. Native enums
require TypeScript enum transformation and cannot be used with erasableSyntaxOnly enabled.
Enum analysis indexes reverse lookups before tracing operations: a lookup must map its argument to
an enum field through a complete values() iteration, stream filter, or private map populated from
all enum constants. Method names such as valueOfType alone are not evidence. Request analysis follows
DTO getters, local aliases, and delegated parameters to those lookups; response analysis retains
setter provenance and can reuse a validated request value when it is echoed. A request domain is
closed only when unmatched values are rejected on a mandatory path. A unique, source-proven request
association can still produce an enum type with known status; rewritten or ambiguous request origins
are not associated. Semantic targets record source as
request or response; older snapshots without it mean response. Operation-specific request and
response types remain separate, including when both use the same Java DTO.
Generated output includes:
src/service/**/*.ts
src/types/service-type/**/*.ts
src/types/service-enums/**/*.ts
.nlab/nlab-api.config.json
.nlab/contract-ir.json
.nlab/openapi.json
.nlab/frontend-manifest.json
.nlab/replacement-map.json
.nlab/generate-report.json
The backend may start on another branch; nlab-api switches it when the checkout is clean. The frontend
must remain outside the backend repository. Existing tracked backend changes, concurrent
use of the same backend checkout, non-generated frontend files, symlink path escapes, config drift,
incomplete CodeGraph state, branch movement, ambiguous RPC operation identities, and incomplete schema
references stop the run. A legacy bridge manifest marked
service-paths permits its owned generated files to be replaced during the first Facade-layout
generation. Gateway errors always stop generation; unmatched routes stop it when no methods remain. Missing enums,
external value sources, removed unused operations, and Mock being disabled do not stop generation; they remain in
.nlab/generate-report.json. stdout contains one final JSON result. stderr contains only stage and
percentage progress events outside a TTY; a TTY shows one progress bar. The overall deadline defaults
to 1200 seconds.
Projects can run repository-owned commands after API, type, and enum files are written:
{
"afterGenerate": [
{
"command": "pnpm",
"args": ["exec", "eslint", "--fix"],
"includeGeneratedFiles": true
}
]
}Hooks run sequentially without a shell, from the frontend project root. Every hook receives the exact
relative file list as JSON in NLAB_API_GENERATED_FILES and the project root in
NLAB_API_PROJECT_ROOT. includeGeneratedFiles also appends each path as one literal argument. The
report records the expanded command, duration, and exit status. A non-zero status stops generation,
retains the pending artifacts, and writes a failed .nlab/generate-report.json.
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 initThis interactive command supports macOS/Linux on x86_64 and ARM64. macOS Apple Silicon needs Rosetta 2 for the Node 14 x64 runtime. 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 mirror, nrm
taobao/zz, and Vite+ shell loaders. - 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.
Install standalone nlab-api without Rust or Cargo:
curl -fsSL \
https://raw.githubusercontent.com/JacobZyy/jt-cli/main/install-nlab-api.sh \
-o /tmp/install-nlab-api.sh
sh /tmp/install-nlab-api.shnlab-api checks the latest ready release before normal commands and automatically replaces
an older installed binary. Use explicit commands when needed:
nlab-api update --check
nlab-api updateStandalone nlab-api update (alias upgrade) also updates the installed nlab-backend-bridge
through Skill Manager, including when the binary is already current. --check remains read-only.
Automatic binary upgrades attempt the same Skill sync; ordinary starts without a binary upgrade do
not. The Skill keeps its registered source and deployments; missing managers, missing skills, or
local-import sources are reported and skipped. jt upgrade does not synchronize Skills.
Pass --no-update for offline or reproducible runs. Re-run the script to install a pinned release
or change the install directory:
NLAB_API_VERSION=1.11.0 NLAB_API_INSTALL_DIR="$HOME/bin" sh /tmp/install-nlab-api.shEach GitHub Release publishes a checksum-verified nlab-api archive for macOS Apple Silicon
(aarch64-apple-darwin). The installer writes only nlab-api, defaults to ~/.local/bin,
verifies the downloaded SHA-256 checksum and binary, and refuses to replace a symlink or
non-regular target. It also writes an ownership marker used by self-update; manually copied binaries
are not replaced automatically. Generation still requires the existing git and codegraph tools.
- 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