zy v1.0.45
Official binary release for zy 1.0.45.
What's changed in 1.0.45
Added
explore's AI (Ctrl-Q) now researches your files itself. With
[ai] provider = "anthropic"set, asking a question no longer just sends
the focused file's head: the model searches the tree on its own (live
grep, file-name lookup, and multi-pattern search, all run inside zy
against the warm index and scoped to the directory you're in), then
answers with path:line citations. You watch each search happen, it stops after six
rounds, and the privacy default is unchanged: provider starts "off" and
nothing ever leaves the machine until you opt in. Other providers keep
the existing one-shot behavior.explorebrowses inside archives and SQLite databases. Enter on a
zip/tar/7z (any libarchive format) opens its contents like a folder:
members grouped by directory with sizes, and Enter on one extracts just
that member safely to /tmp and opens it in the viewer. Enter on a
.sqlite/.dbfile lists its tables with row counts, and Enter on a
table shows its rows as a real typed table. Everything inside a
container is read-only; Esc returns to the listing.explorereads an[explore]section from~/.zy/config.toml.
Set your startup defaults once:show_hidden = true,sort = "size"
(name/size/date/type),sort_reverse,columns = 1(the metadata
columns,cycles),max_entries, andgit_timeout_ms. Bad values
never break anything, they fall back or clamp with a one-line note
before the explorer opens. The existing[explore.keys]bindings and
environment variables keep working (env wins over config).explore's diff viewer stages hunks. Inside the diff view,a
stages the hunk you're looking at,runstages it, andX(with a
confirm) throws it away from the working tree. A lazygit-grade review
loop without leaving the file manager. The patch piped to git is a
byte-exact slice of git's own output, so it always applies cleanly; the
view refreshes after each action and closes itself when the last hunk
is dealt with.exploreexports a whole-directory (or whole-selection) HTML report.
Mark a set of entries (or just put the cursor on a folder) and press
H: one self-contained offline page with every file in the set, each
rendered the right way (syntax-highlighted source, CSV as a real table,
images inline, binaries as a size note), directories walked recursively,
with a sidebar of per-file links and open-in-editor links. A single
focused file keeps the existing rich document page.explore's grep pane jumps symbols across naming conventions. With
grep results open,*expands the pattern into its case variants
(snake_case, kebab-case, camelCase, PascalCase, SCREAMING_SNAKE) and
re-greps them all in one pass, so searchingparse_stringalso surfaces
everyparseString,ParseString, andPARSE_STRINGcallsite. Scope
chips likeext:care kept.explorecan diff any two things. Mark exactly two entries (two
files, two directories, in a repo or not) and press\: the side-by-side
diff viewer opens on the pair, with the same syntax highlighting,
word-level diff, andn/Nchange navigation the git diff viewer has,
andhexports the self-contained HTML diff page for the pair. Identical
files say so in one line. With anything other than two entries marked,
\keeps toggling the diff preview as before.exploregets a;command line: pipe the selection through real zy
builtins, or hand a command off to the prompt. Press;and type
| where size > 10mb | sort-by modifiedto run that pipeline over the
marked files in-process (they flow as the same typed name/type/size/modified
tablelsproduces, so every filter works verbatim), with the result
rendered right there. Type anything else as a template (%sthe selected
paths,%dthe directory,%tthe focused entry, all safely quoted) and
the explorer exits with the composed command waiting pre-filled at the zy
prompt, ready to edit before running. Works in grep-results mode over the
selected hit files too. Rebindable ascommand_linein[explore.keys].- Ranges are lazy.
1.. | first 3,seq 4 600000000 | first 5, and
1.. | skip 5 | first 2answer instantly at flat memory instead of
freezing the terminal or hitting the old "exceeds the 10000000 limit"
error.first,take,skip,last,length, andmath sumstream
(length/math sumgive the true answer for any bounded range, like
1..600000000 | math sum→180000000300000000);skipon a range
stays a range so laziness composes;describesaysrangeandto nuon
prints the1..10literal, both matching nu. Anything else sees the
range as a bounded list, so no command can be surprised into an OOM.
Unbounded ranges (1..) now parse. Smallseqoutput is unchanged. - The Nushell
exportkeyword family works.export def,export const,
export alias,export use,export module, andexport externparse as
module-member declarations, so the canonical nu module pattern runs:
module m { export def hello [] { "hi" } }; use m hello; helloprintshi,
andexport const X = 99imports cleanly.export-env { $env.FOO = "bar" }
is a new builtin whose block's$envchanges persist (top level and inside
modules).externsignature declarations parse and no-op instead of
erroring a module load.use mod *imports every member (the*is no
longer glob-expanded into filenames), and trailing names after a file
module are member selectors. Plain POSIXexport VAR=valis unchanged. fff-findcan filter by git status:git:modified,git:untracked,
git:conflicted,git:clean, andgit:dirty(anything but clean) work as
query constraints alongsideext:/path:/glob:/name:, with bare
aliases likeuntracked:and!git:...negation. So
fff-find 'git:modified'lists just the files you have touched since the
last commit, andfff-find --pick 'git:dirty'opens the picker on them.
Resolved from the in-process.git/indexcache (nogitsubprocess).fff-servegrep responses carry anexthint: a one-sentence
follow-up in plain language (read the top match at its line for context,
raisemax_resultsortime_budget_mswhen a limit tripped, try
find_fileson zero matches), so AI clients get the obvious next step
without re-deriving it from the truncation flags. Applies to bothgrep
andmulti_grep.- The interactive picker tolerates typos. When the live filter in any
zyfuzz picker (fff-find --pick,fzf, history search) matches nothing,
fuzzy terms of 3+ characters get a second pass that forgives one mistyped
character (two for 6+ character terms), sowhreestill narrows to
where.cinstead of emptying the list. Correctly-typed queries rank
exactly as before, and exact ('foo), prefix, suffix, and negated terms
are never typo-tolerated.
Changed
lslists entries sorted by name (plain byte-wise compare, the same
order nu uses) instead of raw directory order, sols | first,
ls | where …, and every other pipeline overlsis deterministic and
matches nu.ls -ashows dotfiles but no longer lists the.and..
pseudo-entries, and thecreated/accessed/modifiedcolumns now carry
nanosecond precision (2026-06-10T22:17:07.126711977+08:00in nuon,
byte-identical to nu).
Fixed
matchon a record no longer corrupts memory (ZY-070). Matching a
record pattern (match $x { {k: $val} => … }) freed each field it
examined even though the record still owned it, a latent use-after-free
that AddressSanitizer caught while gating the lazy-ranges work. The
field is a borrowed view now, freed exactly once with its record.- More Nushell-parity gaps, conversions and paths:
into binarymaps
over list input (ls | get size | into binary→ a list of binaries, not
the bytes of the rendered[…]text) and converts a filesize to its
8-byte count like nu (it used to stringify1.0 kB);path expandkeeps
the list shape (ls | get name | path expandstayslist<string>even
for one row);lsemits null for a non-symlink'stargetso
default 'nothing' targetfills it; and the bare$envrecord holds
PATHas a list of directories ($env | get home! path!matches nu
byte for byte). - A batch of Nushell-parity gaps in the filter family, each verified
against nu live:where <col> >= <value>no longer errors (the split
>=operator is re-joined, and a substituted(date now) - 2wkvalue
evaluates as datetime arithmetic);uniq-by --keep-last/-lreturns the
last occurrence per key;get name.2indexes the collected column and
get 2.nameworks; multi-keygroup-by lang year --to-tableproduces
nu's flat shape with key-named columns ([[lang, year, items]; …]);
barelaston a table returns the final row as a record likefirst;
eachover nothing passes the nothing through ($env.name? | each {…} | default "bye"→bye); a zero-row table serializes as[]in nuon; and
a nothing-valued interpolation segment renders as empty instead of the
literalnull($"a(if false {'x'})b"→ab). - Quoted table cells keep parens and brackets (ZY-069).
[[name]; ['(((((']]used to come out as garbage (name: ((((("" ]),
and'abc)'or'abc]'lost their last character, because the table
parser couldn't tell quoted content from the literal's own brackets.
Tokens now carry the quoted span's offsets, so anything inside quotes
stays data no matter how structural it looks. joinhonors--suffix/--prefixafter the positionals (ZY-067).
join <table> <key> --suffix _r, the way nu usually spells it, silently
dropped the flag and the colliding column came out asa_(the collision
fallback) instead ofa_r. Flags now parse before and after the
positionals, in both the inline table-literal and file forms.zi/z"Visits" counts no longer balloon into the tens of thousands
(ZY-066). The frecency store re-saved every cached directory on each shell
exit through an UPSERT that incremented on conflict, so every tracked
directory gained a phantom +1 per session whether or not you went there (even
/boot/efiand~/Picturesread ~60-95k visits). Now only a real visit
increments a directory; the exit-time flush just persists the entry, it never
bumps the count. Counts that are already inflated need a one-timez --clear
to reset.- "Merge pills" no longer truncates the directory to the repo name. Turning
on Merge pills incustomizeinstalls the merged-segments ribbon, whose
[directory]section left outtruncate_to_repoandtruncation_length, so
the directory module fell back to its own defaults (the repo's basename
alone) instead of the full path the default prompt shows. The preset now
pins both to the default's values (truncate_to_repo = false,
truncation_length = 0), so a merged prompt shows the same full~/pathas
before merging.
Verify your download
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.45/zy_1.0.45_amd64.deb
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.45/SHA256SUMS
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.45/SHA256SUMS.minisig
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.45/zy-release.pub
minisign -Vm SHA256SUMS -p zy-release.pub # confirms maintainer signature
sha256sum -c SHA256SUMS --ignore-missing # confirms the .deb hash matchesFull signing policy: docs/ops/RELEASE_SIGNING.md
VirusTotal scan
This release was uploaded to VirusTotal at publish time. Click through to see the live verdict from 65+ AV engines:
(Scans run within ~5 minutes of upload. If the page shows "Item not found", wait a moment and refresh.)
Try without installing
docker run --rm -it ghcr.io/iskandarputra/zyshell:1.0.45Install (Debian / Ubuntu)
Download zy_1.0.45_amd64.deb from the assets below, then:
sudo dpkg -i zy_1.0.45_amd64.deb
sudo apt-get install -f