Skip to content

zy v1.0.45

Choose a tag to compare

@iskandarputra iskandarputra released this 11 Jun 14:20

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.
  • explore browses 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/.db file 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.
  • explore reads 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, and git_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, r unstages it, and X (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.
  • explore exports 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 searching parse_string also surfaces
    every parseString, ParseString, and PARSE_STRING callsite. Scope
    chips like ext:c are kept.
  • explore can 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, and n/N change navigation the git diff viewer has,
    and h exports 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.
  • explore gets 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 modified to run that pipeline over the
    marked files in-process (they flow as the same typed name/type/size/modified
    table ls produces, so every filter works verbatim), with the result
    rendered right there. Type anything else as a template (%s the selected
    paths, %d the directory, %t the 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 as command_line in [explore.keys].
  • Ranges are lazy. 1.. | first 3, seq 4 600000000 | first 5, and
    1.. | skip 5 | first 2 answer instantly at flat memory instead of
    freezing the terminal or hitting the old "exceeds the 10000000 limit"
    error. first, take, skip, last, length, and math sum stream
    (length/math sum give the true answer for any bounded range, like
    1..600000000 | math sum180000000300000000); skip on a range
    stays a range so laziness composes; describe says range and to nuon
    prints the 1..10 literal, 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. Small seq output is unchanged.
  • The Nushell export keyword family works. export def, export const,
    export alias, export use, export module, and export extern parse as
    module-member declarations, so the canonical nu module pattern runs:
    module m { export def hello [] { "hi" } }; use m hello; hello prints hi,
    and export const X = 99 imports cleanly. export-env { $env.FOO = "bar" }
    is a new builtin whose block's $env changes persist (top level and inside
    modules). extern signature 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 POSIX export VAR=val is unchanged.
  • fff-find can filter by git status: git:modified, git:untracked,
    git:conflicted, git:clean, and git:dirty (anything but clean) work as
    query constraints alongside ext:/path:/glob:/name:, with bare
    aliases like untracked: and !git:... negation. So
    fff-find 'git:modified' lists just the files you have touched since the
    last commit, and fff-find --pick 'git:dirty' opens the picker on them.
    Resolved from the in-process .git/index cache (no git subprocess).
  • fff-serve grep responses carry a next hint: a one-sentence
    follow-up in plain language (read the top match at its line for context,
    raise max_results or time_budget_ms when a limit tripped, try
    find_files on zero matches), so AI clients get the obvious next step
    without re-deriving it from the truncation flags. Applies to both grep
    and multi_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), so whree still narrows to
    where.c instead of emptying the list. Correctly-typed queries rank
    exactly as before, and exact ('foo), prefix, suffix, and negated terms
    are never typo-tolerated.

Changed

  • ls lists entries sorted by name (plain byte-wise compare, the same
    order nu uses) instead of raw directory order, so ls | first,
    ls | where …, and every other pipeline over ls is deterministic and
    matches nu. ls -a shows dotfiles but no longer lists the . and ..
    pseudo-entries, and the created/accessed/modified columns now carry
    nanosecond precision (2026-06-10T22:17:07.126711977+08:00 in nuon,
    byte-identical to nu).

Fixed

  • match on 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 binary maps
    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 stringify 1.0 kB); path expand keeps
    the list shape (ls | get name | path expand stays list<string> even
    for one row); ls emits null for a non-symlink's target so
    default 'nothing' target fills it; and the bare $env record holds
    PATH as 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) - 2wk value
    evaluates as datetime arithmetic); uniq-by --keep-last/-l returns the
    last occurrence per key; get name.2 indexes the collected column and
    get 2.name works; multi-key group-by lang year --to-table produces
    nu's flat shape with key-named columns ([[lang, year, items]; …]);
    bare last on a table returns the final row as a record like first;
    each over 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
    literal null ($"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.
  • join honors --suffix/--prefix after 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 as a_ (the collision
    fallback) instead of a_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/efi and ~/Pictures read ~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-time z --clear
    to reset.
  • "Merge pills" no longer truncates the directory to the repo name. Turning
    on Merge pills in customize installs the merged-segments ribbon, whose
    [directory] section left out truncate_to_repo and truncation_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 ~/path as
    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 matches

Full 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:

https://www.virustotal.com/gui/file/bf41736e605cae3af7d712f9e6f55ca74106911020dc1d14b4bb614f9853ba14/detection

(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.45

Install (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