Skip to content

Releases: iskandarputra/zyshell

zy v1.0.46

Choose a tag to compare

@iskandarputra iskandarputra released this 14 Jun 08:53

Official binary release for zy 1.0.46.

What's changed in 1.0.46

Added

  • An interactive audit browser with analytics. Running audit on a
    terminal opens a full-screen, mouse-driven viewer of the command ledger.
    A List view: scrollable rows with risk badges and exit codes, a detail
    strip, live / search, risk and failed-only filters, in-place chain
    verification that jumps to any broken sequence, and JSON export of the current
    view. An Analysis dashboard: totals, failure rate, risk breakdown bars,
    top programs, and per-day activity. Enter (or a second click) opens a
    deep-detail overlay with every field and the full entry/prev hashes. Mouse
    wheel scrolls, clicks select and switch tabs. Scripts and zy -c audit still
    print the text status.

  • audit list and pipeable audit output. audit list [N], audit search,
    and audit risk now emit a structured table instead of plain text, so the
    ledger flows through the pipeline: audit list 500 | where exit != 0,
    audit list | where risk == destructive, audit list 50 | to csv. audit is
    now a data-aware builtin, so it stays in-process across a pipe.

  • audit prune and automatic retention with a size cap. audit prune [--days N] [--vacuum] trims old entries and optionally reclaims disk space;
    the oldest contiguous prefix is removed so the surviving chain still verifies
    from its new genesis. The shell auto-prunes once a day to [audit] retention_days (default 30) and [audit] max_entries (default 50000),
    whichever is stricter, so the database stays bounded even under heavy use
    (0 disables either limit).

  • policy rules now persist, and there's a manager TUI. Rules added with
    policy add are saved to ~/.zy/policy.conf and auto-load at startup, so they
    survive a restart (previously they vanished unless you passed --policy FILE
    every launch). New subcommands: policy remove/enable/disable <n>,
    policy save, policy test <cmd> (shows whether a command is allowed and
    which rule matched), and add now takes rate:N / priority:N. policy list
    emits a table, so policy list | where action == deny works. Running policy
    on a terminal opens a full-screen manager: a live test field (type a command,
    see allow/deny against the rules), action badges, toggle/remove, a
    restricted-mode switch, mouse + keyboard.

  • z/zi now learn the directory a terminal opens in. Before, a directory
    only entered the frecency database when you actively cd into it, so a shell
    started directly inside a project (an IDE or file manager "open terminal here",
    a new tmux or split pane) was invisible to z and zi until you navigated
    somewhere and back. The interactive shell now records its startup directory on
    launch. zy -c and scripts still do not touch the database.

Changed

  • Audit logging is now opt-in (off by default). It used to run for everyone
    and grow silently (multi-hundred-MB ledgers were common). It is a
    team/server/compliance feature, so it now stays off until you set [audit] enabled = true in ~/.zy/config.toml. A ledger you already collected stays
    readable, verifiable, and prunable after you disable it; audit with nothing
    to show points you at the switch. (--no-audit still force-disables.)
  • audit status now shows the DB size and retention window, and audit verify pinpoints the break. Verification used to just say pass/fail; it now
    reports the exact sequence number and reason (hash mismatch vs broken link)
    when the chain is bad. audit.sqlite-wal is also truncated on a clean exit so
    it stops accumulating across many shells.
  • customize's pill-shape pickers now preview the full prompt, and honor your
    merge setting.
    The Library and Mix & Match tabs used to show a fixed two-pill
    mock that always looked un-merged, no matter how your prompt was actually set
    up. They now render the same three-pill sample prompt the main menu shows, using
    the highlighted shape and your live "Merge pills" setting, so a merged ribbon
    previews as a connected ribbon and separate caps preview as separate caps.

Fixed

  • A cd visit could be silently dropped under concurrent-shell contention.
    The frecency write-through ignored SQLite's return code, so if a write lost the
    WAL lock past the busy timeout (common when several zy shells run at once), the
    visit vanished with no error. It now retries the write a few times before
    giving up.
  • jump.sqlite-wal no longer creeps up to its 4MB checkpoint threshold and
    stays there.
    With many long-lived shells the default passive auto-checkpoint
    kept losing the race to a concurrent reader, leaving a multi-megabyte WAL over a
    table of a dozen rows. The shell now truncates the WAL back into the main
    database on a clean exit.

Verify your download

curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.46/zy_1.0.46_amd64.deb
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.46/SHA256SUMS
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.46/SHA256SUMS.minisig
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.46/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/2c18944d3fc262e8564e494c943f74d16480a84ed18256d755189cf31db377e9/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.46

Install (Debian / Ubuntu)

Download zy_1.0.46_amd64.deb from the assets below, then:

sudo dpkg -i zy_1.0.46_amd64.deb
sudo apt-get install -f

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 ...
Read more

zy v1.0.44

Choose a tag to compare

@iskandarputra iskandarputra released this 10 Jun 13:04

Official binary release for zy 1.0.44.

What's changed in 1.0.44

Added

  • customize → Show / hide pills gains bulk toggles. Press a to show every pill or d to hide every pill in one keystroke, instead of toggling 19 sections one at a time. The footer and preview pane advertise the keys.

  • New merged-segments prompt preset — the default look as one connected ribbon. prompt-preset merged-segments installs a version of the default prompt where the background pills (directory, git, languages, duration) join into a single Powerline ribbon via gradient seams. The interesting part is that it stays clean off-project: each pill is wrapped in a (…) conditional, so an absent module (no git repo, no Python, …) drops the whole pill including its seam, with no empty colored stub left behind, while the ribbon reconnects across the gap. Fg-only segments (git metrics, jobs) sit after the ribbon so they never punch a hole in it. Nerd Font required; like every preset it backs up your existing config first.

  • pill_merge = true joins your prompt pills into one connected chain. Set it at the TOML root and every run of adjacent colored pills renders as a continuous Powerline ribbon (directory git python) instead of separate floating pills, with each seam colored to flow from the previous pill into the next. The seam glyph follows pill_shape (so slant gives slant seams, not just arrows), defaulting to the solid arrow. It's the one-key version of hand-writing fg:prev_bg cap glyphs between every pill, and renders byte-for-byte the same. Toggle it live in customize under "Merge pills (connected chain)" — the preview shows the merged look. It acts on a top-level bare-pill format like [ $mod ](bg:..) (e.g. prompt-preset pastel-powerline); the shipped default prompt bakes caps inside each module, so it's a no-op there. Don't combine it with hand-written caps or they double up.

  • ast is implemented (was a "not implemented in zy" stub): it parses a pipeline string with zy's own lexer and recursive-descent parser and dumps zy's AST, the same tree the executor walks. ast 'hello' returns a {block, error} record where block is an indented tree; --json/-j returns the AST as JSON (--minify/-m for compact); --flatten/-f returns a {content, shape, span} table, one row per lexer token with its highlight shape (shape_internalcall, shape_pipe, shape_string, shape_flag, …) and byte span, matching nu's flatten view (ast "'hello'" --flatten gives shape_string at span 0..7). It dumps zy's tree rather than nu's Rust AST types, and the flatten view reports what zy's shell tokenizer actually sees (e.g. a top-level > lexes as shape_redirection).

Changed

  • The customize "Merge pills" toggle now actually merges your prompt. pill_merge is a runtime flag that only rewires a top-level bare-pill format; the default prompt bakes its caps inside each module, so enabling the toggle there used to do nothing to the live prompt (only the preview changed) and read as a bug. Now, on that per-module layout, turning the toggle on switches your prompt to the connected merged-segments ribbon while keeping your theme and pill shape (its colors already match the default), and saves a one-toggle restore point; turning it off restores your previous prompt exactly. On a format that already has top-level pills it still just sets pill_merge = true.
  • Top-level (…) conditional groups now collapse when empty, like they already do inside a module. Previously a (…) in the top-level format always rendered its contents; now it disappears when none of the $module variables inside it produce output, matching the documented (and starship) conditional semantic. This is what makes a stub-free merged ribbon possible (an absent pill drops its seam too). No shipped config used a top-level (…), so existing prompts are unaffected; if you relied on a top-level (…) rendering unconditionally, drop the parentheses.
  • make test is now the fast core (C unit tests + validation + security, ~1 minute) for a quick edit→test loop. The slow, heavy suites (shell integration, nu-parity compat, PTY end-to-end) are opt-in via make test-full (or ./tests/run_tests.sh --full), which is what CI and make test-all now run. Sanitizer runs and --filter still imply the full set so they never silently narrow coverage. The plain-run PTY timing scale defaults to 2x for explorer-render head-room.
  • The unit-test build is much faster after a source edit. About 34 unit tests link the whole object set, and each used to run its own ~20-second whole-program LTO link (the objects are slim LTO, so every link redid the codegen). The runner now does that codegen once into a single shared object blob and links each test against it in well under a second, so a cold make test dropped from ~230s to ~50s and make test-full from ~330s to ~140s, with no change to the shipped binary. Sanitizer runs keep the per-test link; pass --no-blob to fall back to the old path.

Verify your download

curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.44/zy_1.0.44_amd64.deb
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.44/SHA256SUMS
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.44/SHA256SUMS.minisig
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.44/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/388ed73080b550e29419703eca357bafb5ea39e1998d9025910d4a1b68667dd3/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.44

Install (Debian / Ubuntu)

Download zy_1.0.44_amd64.deb from the assets below, then:

sudo dpkg -i zy_1.0.44_amd64.deb
sudo apt-get install -f

zy v1.0.43

Choose a tag to compare

@iskandarputra iskandarputra released this 08 Jun 16:57

Official binary release for zy 1.0.43.

What's changed in 1.0.43

Changed

  • Filesize units now match nushell (SI/metric): 1kb is 1000 bytes (was 1024), 1mb is 10^6, and so on, while the IEC *ib forms stay binary (1kib is 1024). Display uses B/kB/MB/GB/TB/PB with the value truncated to one decimal, like nu. If you relied on kb=1024, use kib.

Added

  • to msgpack / from msgpack round-trip values through the MessagePack binary format ([foo, 42, false] ↔ the packed bytes).

  • encode / decode support text charsets via iconv: shift-jis, euc-jp/euc-kr, utf-16/-le/-be, big5, gb18030, latin1, windows-1252, and more, with --ignore-errors. utf-16 is decode-only.

  • nu-check validates nushell syntax with zy's own parser: '1 + 1' | nu-check is true, 'if true {' | nu-check is false; --debug prints the parse error.

  • Typed math aggregations: math sum/avg/min/max/median/mode preserve the unit, so [1min 2min 3min] | math sum is 6min and [5kb 10mb 200b] | math median is 5.0 kB; datetime supports min/max. Mixed numeric input is unchanged.

  • str length / str substring / str index-of take --grapheme-clusters (-g) to count/index by extended grapheme cluster (flags, ZWJ sequences, combining marks) instead of bytes.

  • More ansi codes: cursor_on/cursor_off, cursor_blink_*, screen/line erase, cursor movement, attr_*/reset_* aliases, and nu's single-letter aliases (r g y b m p c w k i u d h s n).

  • job namespace (nu-style closure-thread jobs): job spawn { … } runs a closure on a background thread and returns its id, with job id, job list, job kill, job send/job recv (a thread-safe per-job mailbox), and job flush. Closure execution is serialized against the main thread by a per-command executor lock (released while a worker blocks on sleep/job recv), and a worker's exit-status is thread-local, and the whole thing is TSan-clean. The bash-style jobs process control is unaffected.

  • describe -d / describe --detailed returns nu's detailed record instead of the plain type string: a nested {type, detailed_type, value, …} with length for lists and binary, columns for records, and a per-element table for lists, built recursively so each nested value is itself a detailed record. The detailed_type field matches nu's full type spelling (table<a: int> for a list of records, list<oneof<int, string>> for mixed lists, recursive record<a: record<b: int>>). nu's rust_type field is intentionally omitted (it exposes nushell's internal Rust types, meaningless in a C shell). --no-collect / -n is accepted but a no-op, since zy is eager and has no streams. The plain describe (no flag) is unchanged.

  • into sqlite <path> writes the pipeline table into a sqlite database file (default table main, --table-name to override), matching nu.

  • Timezone-aware, sub-second datetimes (ZY-062): a datetime now carries its source UTC offset and nanoseconds, so into datetime round-trips +HH:MM and fractional seconds, into record includes millisecond/microsecond/nanosecond and the source timezone, into int gives the full nanosecond count, and into string matches nu's Sat Oct 10 10:00:00 2020 form. into datetime also parses a {year..timezone} record, a list of strings, %P am/pm, %z offsets, and the European d.m.Y forms.

  • transpose -d/--as-record and the -l/--keep-last / -a/--keep-all dedup companions; rename --block {closure} renames each column by running the closure on its name.

  • Digit separators in number literals: 1_000, 1_234_567, 3_141.59.

Fixed

  • A bare builtin statement before ; no longer prints its value (job id; 99, whoami; 99 → just 99, matching nu): nu_output now honors force-capture like into_output.

  • to json of a binary emits nu's byte-int array (0x[ff aa 01][255, 170, 1], indent-aware), not a quoted "0x[ff]" string.

  • to nuon of a duration and binary match nu: a duration serializes as its raw nanosecond count (1day86400000000000ns) and a binary as uppercase no-space hex (0x[ff]0x[FF]), so they round-trip and describe -d of a duration/binary matches nu in every field.

  • Duration and filesize arithmetic keep their unit: (500day + 4hr + 5sec) is a duration (was a float), so into record breaks it into week/day/hour; dur / dur is a float ratio.

  • flatten --all recurses into nested list/table columns; a nested [[ ]] table literal inside a cell now parses.

  • join accepts an inline [ … ] table as its right side and supports --suffix/--prefix for colliding columns.

  • insert/upsert with a dotted cell-path key (a.0.b) descend instead of making a literal column; group-by --to-table emits [{group, items}, …]; find matches list elements by value for numeric/filesize terms; seq honours an explicit 3-arg step like nu (ZY-061); inspect as the last stage forwards its value to stdout (ZY-060).

  • Empty quoted strings survive in records and tables (['', 'a'], {a: '', b: 1}, {a:'' b:1}, [[a]; ['']]), and a $"…" value in a brace block (if c { $"label: (x)" }) no longer mis-parses as a record.

  • to nuon quotes digit/dot/space-bearing strings like nu ({c1: 1}{"c1": 1}).

  • Cell-paths on list and record literals (ZY-053): [10 20 30].1 → 20, {a:1}.a → 1, {a:{b:2}}.a.b → 2.

  • $nu.os-info is a record (ZY-056) with name/arch/family/kernel_version, so | get name and .arch work.

  • String interpolation inside a record value (ZY-054): {a: $"x ($n)"} evaluates the ($n) block.

  • bits shr --signed sign-extends and the width auto-sizes (ZY-044): -2 | bits shr 1 --signed → -1
    (was 127), and ints over 255 no longer wrap (256 | bits shr 1 → 128). -s/-n short flags now parse (ZY-039).

  • const accepts a list or record value (ZY-038): const x = [1 2 3] binds the whole list, not just [1].

  • The fff MCP server advertises usage instructions so a fresh client knows the grep/find_files/multi_grep tools.

  • Whole-valued floats print with .0 (ZY-057), matching nu: 10 / 25.0, 9 | math sqrt3.0,
    string interpolation $"(10 / 2)"5.0. into string still drops it (2.0 | into string2).
    math abs of an int returns an int; math median of an even-length list returns a float.

  • const accepts both const X=42 (POSIX) and const x = [1 2 3] (nu) forms again. The fused form had
    regressed to "not a valid identifier".

  • A def function now returns an integer value (ZY-042). def double [x] { return ($x * 2) }; double 5
    gave nothing because an integer return was always read as a POSIX exit code. A def now puts the value
    on the pipeline like nu, while a POSIX name() { … } still uses the exit code.

  • select / reject / update error on a missing column (ZY-045), like nu, instead of silently dropping it
    or creating it. A trailing ? marks a column optional (select a?); use upsert to add a column.

  • where handles column arithmetic (ZY-055): where a mod 2 == 0, where a + b > 6 and similar now
    filter per row instead of returning nothing.

  • A cell-path on a record literal works (ZY-053): {a:1}.a → 1, {a:{b:2}}.a.b → 2.

  • sort compares by value type (ZY-043): a list of genuine strings sorts lexicographically like nu
    ("2 10 1" | split row " " | sort → 1, 10, 2) instead of coercing to numbers.

  • split chars and str reverse are UTF-8 aware (ZY-040): 'café' | split chars → c a f é and
    'café' | str reverseéfac, no more byte mojibake.

  • Records can mix spaced and compact colon entries (ZY-059): {b: 4, a: 3, c:1} parses (and so
    … | sort -v), in any order of b: 4 / c:1 style.

  • A huge seq or range no longer freezes the terminal. seq 4 600000000 and a
    bare 1..600000000 used to allocate the whole list and exhaust memory; they now
    error with a clear hint past 10M elements. This also removes the old silent
    truncation of a range literal at 100k, so ranges up to 10M materialise fully
    (1..200000 | length is 200000, not 100000). Lazy streaming (so big ranges
    don't materialise at all, like nu) is planned separately.

  • where shorthand matches Nushell. where a > 1 and b > 1 now honours the
    whole and/or chain instead of just the first comparison; where p.age > 18
    follows a dotted column path; where c in ["red" "green"] tests membership; and
    where a >= 2 / <= 25 / == 2 / != 2 work (the two-char operators were being
    split). where $it mod 2 == 0 over a list evaluates with full precedence.

  • More commands take closures and map over lists. sort-by {|x| …} and
    group-by {|x| …} work on a plain list of values; into bool / into string /
    into filesize / into record map element-wise over a list (and into record
    reads a list of [key value] pairs); parse --regex returns every match per line
    (not just the first) and names unnamed groups capture0….

  • Records, ranges, and control flow. let r = try { … } catch { … } assigns the
    evaluated value; a bare (1 + 2) at the prompt prints 3; a quoted string that
    matches a command name (if false {"a"} else {"z"}) stays a string;
    error make {msg: ("a" + "b")} evaluates the field; a runtime error binds
    catch {|e| $e.msg}; the range 1..3..9 reads 3 as the second element (step 2)
    like nu; match arm bodies run a nested match or a ("x" | cmd) pipeline;
    [[a b]] is a list-of-lists; and def --wrapped foo […] defines foo.

  • String and value fixes. mut s = "a"; $s = $s + "b" / += / ++=
    concatenate; str kebab-case / snake-case no longer double the separator on
    CONST_NAME; $env.HOME? drops the optional marker; an empty string keeps its
    type in a list/record/fold; `str su...

Read more

zy v1.0.42

Choose a tag to compare

@iskandarputra iskandarputra released this 06 Jun 07:44

Official binary release for zy 1.0.42.

What's changed in 1.0.42

Added

  • explore can grep the whole tree into a navigable pane (press L). Type a
    pattern and instead of a throwaway popup you get a results pane: every hit grouped
    under its file, each line shown with its line:col (right-aligned so the snippets
    line up) and a snippet, that you scroll like a directory (j/k/g/G). Press
    Enter on a hit to open it at that line in $EDITOR, Space to mark hits, Y to
    copy their paths, and H to export the whole result set as a self-contained
    offline HTML report (hits grouped by file, syntax-highlighted, with a file-tree
    sidebar and click-to-editor line links); Esc returns to the file list. You can
    scope the search with
    leading chips, e.g. ext:c path:src !path:vendor TODO greps TODO only in .c
    files under src and outside vendor. File operations (delete, rename, move,
    chmod) are disabled in the grep pane on purpose, since a hit is a line in a file
    rather than a thing to delete, so a stray keystroke can't touch your files.
  • explore has a regex bulk-rename with a live preview (press Ctrl-R). Type a
    s/pat/repl/flags expression and watch a before/after list of every selected file
    update as you type; Enter applies it. It's real PCRE2, so you get $1 backrefs,
    \U/\L case changes, /g and /i flags, and a {n} 1-based counter for
    numbering. Renames happen in place through the same collision-safe two-pass staging
    the editor bulk-rename uses, and the modal refuses to apply if two files would land
    on the same name.
  • explore keys are remappable now. Add an [explore.keys] table to
    ~/.zy/config.toml mapping an action to a key, e.g. info = "Z" or
    compress_zip = "ctrl-z", and that key triggers the action while the default
    still works. About 30 actions are bindable (the full list is in the action
    registry); press ? in explore for the syntax. Single-character and ctrl-x
    style keys are supported.
  • explore previews PDFs as images now (and PostScript / video where the tools
    are installed).
    Move the cursor onto a .pdf and the preview pane shows the
    first page rendered as a picture instead of a wall of extracted text; PostScript
    and .eps go through ghostscript and videos through ffmpeg, each rendered to a
    thumbnail and cached so a parked cursor doesn't re-run the tool. If the tool
    isn't installed it quietly falls back to the old text preview.
  • explore's sidebar now lists your mounted disks. A DISKS section shows each
    real mounted filesystem (skipping snap/loop images) with a usage-percent chip
    coloured green/amber/red by how full it is; Enter jumps to the mount. The sidebar
    also scrolls now, so a long list of bookmarks, pinned folders, disks, and recents
    stays reachable instead of clipping at the bottom.
  • explore can show detail columns: press , to cycle name-only → size →
    +age → +permissions.
    The columns are right-aligned next to each row and
    auto-drop when a pane gets too narrow to keep the filename readable, so the
    list never looks cramped.
  • explore can create archives now: c zips the selection, C makes a
    .tar.zst.
    It compresses the marked files (or the focused one) into a new
    archive in the current directory, named after the focused entry and never
    overwriting an existing file. Directories are included recursively, with
    permissions and symlinks preserved. (Needs a build with libarchive, same as the
    existing extract; without it the keys are a no-op.)
  • explore has a rich file-info card now (press I). It shows owner and
    group, permissions (rwx + octal), size (computed recursively for a directory,
    with the item count), modified/accessed/changed times, inode and link count, a
    symlink's target, the binary format and architecture for executables (ELF /
    PE / Mach-O, e.g. "ELF 64-bit x86-64"), the Linux inode attributes (lsattr),
    and a SHA-256 for files up to 64 MiB. Read-only; j/k scroll a tall card and
    any other key closes it.
  • explore listing got three upgrades: natural sort, a fuzzy filter, and a
    sort picker.
    Files now sort in natural order, so img2.png comes before
    img10.png instead of after it. The in-panel filter ('/') is now fuzzy: the
    letters you type only have to appear in order, so mtc finds my_test.c, and
    the closest matches rank to the top. And s opens a small sort menu (Name /
    Size / Modified / Type plus a direction toggle) instead of blind-cycling, so you
    can see and pick the sort. n/s/m/t in that menu jump straight to a mode,
    and r flips the direction.
  • explore now remembers your cursor position per directory. Scroll down to
    a file deep in a long listing, step into a subdirectory, come back, and the
    cursor is right where you left it instead of jumping to the top or landing on a
    stale row. The position is kept for every directory you visit (and even across
    separate explore runs in the same shell session). Picking a specific file from
    the Ctrl-K / Ctrl-P / Ctrl-G finders still lands you on that file, and each tab
    keeps its own independent position.
  • explore diffs now label each hunk with its enclosing function, and the
    HTML diff pairs changed lines by similarity
    (first slice of a delta-inspired
    pass, ZY-033). For code files, every @@ … @@ hunk header, in both the
    in-terminal diff viewer and the HTML page, now shows the enclosing function it
    falls in (from git's hunk-header context), styled distinctly. And the HTML
    side-by-side page pairs a
    deletion with the most similar following addition (a width-weighted token
    distance, ported from delta) instead of pairing by position, so unrelated lines
    are no longer word-diffed against each other and genuine edits line up.
  • The in-terminal explore diff viewer got the same delta-style upgrades
    (ZY-033 cont.): the code is now syntax-highlighted (colours layered over the
    red/green change tint), n/N jump to the next/previous change, the
    filename in the header is a clickable link to the file, and the line-number
    gutter auto-sizes to the file (tighter for small files, correct for large
    ones).
  • explore diffs highlight merge-conflict markers, and the terminal viewer can
    wrap long lines
    (ZY-033 final). <<<<<<< / ======= / >>>>>>> conflict
    markers are styled distinctly in both the terminal viewer and the HTML page; and
    w in the terminal diff viewer toggles soft-wrapping of long lines (with the
    syntax colours carried across the wrap) instead of truncating them.
  • The HTML diff page's file list is now a collapsible directory tree
    (github.dev-style) instead of a flat list of basenames. Files nest under their
    folders, single-child folder chains collapse into one row
    (zy_src/08_ui/src/explorer), each folder shows its combined +/- count, and
    clicking a folder folds it.
  • explore "open as HTML" can now render fully offline. The viewer's h
    (and the file list's H) write a standalone HTML page and open it in your
    browser. Previously every asset (highlight.js, KaTeX, Mermaid, web fonts) was
    pulled from a CDN, so the page lost all syntax colour, math, and diagrams with
    no network. zy now prefers a local bundle, resolved from ~/.zy/assets,
    /usr/share/zy/assets (the .deb), or the dev tree, and falls back to the CDN
    only when no bundle is present. Populate the bundle once with
    scripts/dev/fetch-html-assets.sh. Set ZY_HTML_ASSETS=bundled to never touch
    the network, or cdn to force the old behaviour.
  • The HTML markdown renderer gained real GFM coverage: nested ordered/unordered
    lists (by indentation), task lists (- [ ] / - [x]), strikethrough
    (~~text~~), bare and <angle> autolinks (including <email>), hard line
    breaks (two trailing spaces), + bullets, consumed YAML frontmatter, setext
    headings (=== / --- underlines), reference-style links ([text][ref] and
    collapsed [text][]), and footnotes ([^id] references with a back-linked
    notes section). Raw inline HTML stays escaped on purpose, since the source file
    is untrusted. The header docstring previously claimed features that did not
    exist; it now matches what the renderer actually does.
  • explore's "open as HTML" now has dedicated rich renderers for more file
    types instead of dumping everything into a <pre>: images show the actual
    picture (with pixel dimensions) on a transparency checkerboard; SVGs render as
    an image with a RENDER/SOURCE toggle (embedded scripts never execute); CSV/TSV
    become a sortable, filterable table; JSON becomes a collapsible tree with a RAW
    toggle; and PDFs are handed straight to the browser, which renders them
    natively.
  • The HTML diff page is now a whole-changeset view with a file-tree
    sidebar
    (like github.dev "Files changed"): pressing h/H exports every
    changed file, not just the focused one. The sidebar lists each file with its
    +/ counts, jumps to it, marks it viewed (collapsing it, remembered in your
    browser), and tracks the active file as you scroll; each file gets a sticky
    in-table header you can collapse, and the page auto-scrolls to the file you
    invoked it on. Plus inline comment threads: double-click a line to attach
    review comments (multiple per line, stored locally per file+line, deletable).
  • The HTML diff page caught up to github.dev / VS Code. New: intra-line
    word/character diff (only the changed tokens are tinted, on top of syntax
    colour); jump to next/previous change with n/N and an "i of N" counter;
    a change-density minimap rail; find-in-diff (/); sticky hunk headers;
    collapse/expand of unchanged context (the export now fetches a wide 25-line
    band so you can reveal surrounding lines in-page); horizontal scrolling for
    long lines (the side-by-side table grows to the widest line instead of
    clipping it); ...
Read more

zy v1.0.41

Choose a tag to compare

@iskandarputra iskandarputra released this 04 Jun 15:52

Official binary release for zy 1.0.41.

What's changed in 1.0.41

Added

  • spinner builtin and a reusable spinner engine. A new spinner command
    drives a terminal spinner for long-running work. Wrap a command and it animates
    until the command exits, then prints a green check or red cross and passes the
    command's exit status through: spinner -s moon "Building" -- make -j8. It can
    also spin for a fixed time (spinner -d 3 "Warming up"), list the presets
    (spinner --list), or preview them animating (spinner --demo). The animation
    draws to stderr, so a wrapped command's stdout stays clean for a pipe.
    Forty-two presets ship across all six families: single-glyph spinners (dots,
    braille, moon, dice, …), scrolling "linear" rows, sweeping "bar" arcs (including a
    two-row Zed/Claude-style bar), and the 2D braille rings (a rotating arc around a
    circle, or a filled square or rectangle). The bar, linear, and ring families render
    live instead of from a fixed frame table, so the bar fills the terminal width (a
    full-width sweep, re-filling on resize) and you can reshape any of them per run:
    --width, --height (multi-row bar), --spin cw|ccw,
    --motion bounce|loop|squeeze|radiate, --bar-style (sixteen glyph sets),
    --track, --radius/--size/--arc/--fade, --centre filled|empty,
    --direction/--flow, --slots/--lit, and --phase for a flux travelling-wave.
    The rings rotate smoothly (frames that would not change the rasterized output are
    collapsed). spinner --list groups them by family. They
    ship in /usr/share/zy/spinners.toml and are also compiled into the binary, so
    spinners work even without the file; a ~/.zy/spinners.toml can override or add to
    them, and scripts/dev/gen_spinners.py is the single source that generates both.
    The families are modeled on the MIT-licensed tui-spinner crate. The engine
    (08_ui/src/spinner.c plus the pure renderer spinner_render.c) is shared, so
    other zy TUIs can use it too.
  • Redesigned install banner. The post-install experience moved to a bolder,
    on-brand look: the same braille spinner runs the setup steps, sections are drawn
    as colored "pills" built from block glyphs (so they render without a Nerd Font,
    which may not be installed yet), and the Terms and Setup screens share that
    vocabulary. No prompts or behavior changed, just the presentation.
  • fff-grep inline path scoping. A leading ext:/path:/glob:/name:
    constraint (with ! to negate) in the pattern now scopes the search to
    matching files: fff-grep "ext:rs TODO" greps TODO only in .rs files,
    fff-grep "!ext:md TODO" skips .md. A plain pattern is unchanged. This is
    what AI/MCP callers reach for to avoid grepping the whole tree.
  • fff-grep context lines (-A/-B/-C). fff-grep -C 2 foo now returns the
    2 lines before and after each match (or -A/-B for one side), like grep.
    Context lines are tagged match_type = "context" so structured/MCP consumers
    can distinguish them, and they're captured from the full file so a match near a
    scan-chunk boundary still gets its surrounding lines.
  • fff-find query: name: filter + case-insensitive ext:. name:foo hard-
    filters to files whose basename contains foo; ext: now matches
    case-insensitively (ext:RS finds .rs files), and !ext:rs excludes them.
  • fff differential rebuild (L.3). When a file is deleted or a new subdirectory
    appears, the fff index used to re-walk the entire tree to refresh. It now re-walks
    only the changed directory and copies the rest of the entries from the previous
    snapshot, so a localized change no longer pays for a full-tree readdir/stat. The
    result is published as a fresh immutable snapshot through the same path as a full
    rebuild, so search results are identical. Renames and .gitignore edits still do
    a full rebuild (they can change filtering tree-wide). Covered by a new
    thread-sanitizer gate, tests/unit/0714_fff_differential_rebuild.c.
  • libzy_fff.so — the fff engine as a shared library (N). make libzy_fff.so
    builds the recursive index + AI-grade grep + watcher into a self-contained shared
    object that other tools can link, exporting just the fff_* engine API (the
    shell-coupled CLI shims are left out). Output is byte-identical to the in-binary
    engine, checked by tests/validation/fff_libzy_so.sh.
  • The github_pr pill is now recolorable per state. New [github_pr] keys
    open_color, draft_color, merged_color, closed_color, and approved_color
    set the pill background for each PR state. Each takes a palette slot name like
    color4 or a hex like #9d7cd8, resolved against the active theme. To restyle
    the whole pill at once, override style as before.
  • customize gained a "GitHub PR colors" picker. Under Appearance, it lets you
    set each PR state's background with arrow keys (←/→ cycles the theme palette, d
    resets to the default), with a live swatch and sample pill. It writes the
    *_color keys above, so it is the first per-pill color editor in the wizard.

Changed

  • fff-find ranking is sharper. A query that matches a file's name now ranks
    that file above one where the match is buried in a parent directory, with an
    exact-filename match (useruser.rs) weighted highest, and a gentle
    path-depth tiebreaker that prefers the shallower of two otherwise-equal hits.
  • The fff frecency database now self-maintains. On first use per process it
    ages out access history older than 90 days and hard-caps the table, so the
    ~/.zy/fff.db access log can't grow without bound or accumulate dead rows for
    long-deleted files over a long-lived install. Ranking is unchanged (the score
    already decays old entries to ~1%).
  • fff-grep is now ReDoS-resistant and emits less fuzzy noise. Regex matching
    is bounded by PCRE2 match + backtracking-depth limits, so a pathological pattern
    (e.g. (a+)+$ on a long line) can no longer hang the grep — it caps the work and
    reports no match for that line instead. And the fuzzy grep path now rejects
    widely-scattered matches (a needle whose characters are sprinkled across a wide
    span), so low-signal noise doesn't flood results.

Fixed

  • fff git-status now works in repos using index format v4. The .git/index
    parser previously bailed on version 4 (prefix-compressed path names) and left the
    status cache empty, so the file explorer's git chips were blank for any repo that
    enabled v4 (index.version=4 / feature.manyFiles). It now decodes v4's varint
    path compression, so modified/untracked/clean status resolves correctly. See
    KNOWN_ISSUES ZY-028.
  • fff now honors nested .gitignore files. Previously only the .gitignores
    from the repo root down to the indexed directory were applied, so a .gitignore
    inside a subdirectory was ignored. The walker now loads each directory's
    .gitignore as it descends and matches rules relative to that directory (the way
    git does), in both the single-threaded and parallel walkers — so sub/.gitignore
    hides files under sub/ without affecting siblings, and root rules still apply
    throughout. See KNOWN_ISSUES ZY-026.
  • fff: four correctness fixes surfaced by an architecture review against fff.nvim.
    (1) glob: in a find query now actually globs via fnmatch (glob:*.rs matches .rs
    files by basename) instead of literal substring matching, which matched nothing.
    (2) fff-grep with a regex no longer drops matches past byte 8191 on a long/minified
    line — the regex runs over the whole line, and a match-centered window shows long-line
    hits correctly. (3) The file watcher now recovers from IN_Q_OVERFLOW (a dropped-events
    burst, e.g. a big git checkout) by forcing a full rebuild instead of going silently
    stale. (4) fff-find results are now deterministic on score ties (stable path
    tiebreaker). See KNOWN_ISSUES ZY-023..ZY-025, ZY-027.
  • The github_pr pill now actually changes color with PR state. It pushed the
    state color into the runtime palette by its slot name (color8, color2, ...),
    but the palette setter only accepts a hex literal, so the push was silently
    dropped and the pill fell back to the terminal background, looking the same dark
    color for every state. The color is now resolved to a hex first, so draft, open,
    merged, and closed render distinct backgrounds.
  • Release notes now carry the "What's changed" block, and re-publishing refreshes
    them.
    The release publisher read the CHANGELOG section after its orphan-branch
    step had already wiped the working tree, so the GitHub release body fell back to
    bare boilerplate. v1.0.40 hit this first and its notes were patched in afterward.
    The section is now captured before the wipe. Re-publishing an existing release also
    updates its notes and title now, instead of only replacing the assets.

Verify your download

curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.41/zy_1.0.41_amd64.deb
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.41/SHA256SUMS
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.41/SHA256SUMS.minisig
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.41/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/338ee07e6c27a04612111b6708daaa69ad437b700763a0b2091afdbdc9169228/detection](https://www.virustotal.com/gui/file/338ee07e6c27a04612111b6708daaa69ad437b700763a0b2091af...

Read more

zy v1.0.40

Choose a tag to compare

@iskandarputra iskandarputra released this 03 Jun 16:55

Official binary release for zy 1.0.40.

What's changed in 1.0.40

Added

  • Lower glibc floor for the .deb. Building the release package with
    make deb-bookworm (Debian bookworm toolchain) drops the install floor to
    GLIBC 2.35, so the .deb installs on older distributions (Ubuntu 22.04,
    Debian 12, and similar). Fixed several headers that were not self-contained
    and blocked the older toolchain.
  • Changelog discipline. A user-visible change or resolved defect now gets a
    [Unreleased] bullet in the same change, citing its tracker ID. This is documented
    in CONTRIBUTING.md and CLAUDE.md, and make bump rolls [Unreleased] into the
    dated release section.
  • Release notes now carry "what changed." publish-release.sh injects the
    CHANGELOG [VERSION] section into the GitHub release notes, above the
    verify/install boilerplate. Passing --notes "..." still overrides it.
  • check_changelog_public_safe.sh guard. Since the [Unreleased] section ships
    verbatim into the public release notes, this fails CI if it contains internal
    source paths, commit hashes, archived-doc references, the private repo name, or
    secrets (pre-commit, on CHANGELOG.md).
  • fff: streaming index publish. A large cold index walk now serves partial
    search results as soon as it has indexed a checkpoint of entries, instead of
    blocking until the whole tree is walked. The not-yet-released tail stays invisible
    behind the count fence, and small or medium trees still publish once at the end as
    before. Verified under ThreadSanitizer and the fff validation suites.
  • ZY_FFF_STREAM_CHECKPOINT env knob. Tunes the entry count at which a cold
    index walk starts streaming partial results (default 100,000). Lower it on a huge
    monorepo to surface results sooner. It also lets the test suite exercise the
    streaming path without a 100k-file corpus.

Changed

  • check_doc_version.sh also guards the CHANGELOG. It fails CI unless the
    released ZYSHELL_VERSION has a matching ## [x.y.z] section and an
    ## [Unreleased] buffer exists, so a version can't ship without its changelog entry.
  • System /etc/zy/zyrc is now single-sourced. The .deb ships the default
    system rc as /usr/share/zy/zyrc.system and its post-install step installs that
    file to /etc/zy/zyrc, instead of carrying a second embedded copy of the contents.
    (make install already copied the same file.) The installed /etc/zy/zyrc is
    byte-for-byte identical to before, verified by a real install, upgrade, and purge of
    the package in a Debian bookworm container. The old byte-compare drift guard is now
    a single-source wiring guard.

Fixed

  • fff: data race in the concurrent reader path. Under concurrent searches with
    the file watcher live (notably the fff-serve MCP server while files are being
    created), reader threads read the index's entry count without synchronisation
    while the watcher appended new entries. Readers now observe entries through a
    release/acquire count fence, so only fully-written entries are ever visible, with no
    torn reads. This was the groundwork for streaming index publish, verified race-free
    under ThreadSanitizer with a new concurrent-reader test.
  • fff: data race in the streaming index publish. When a freshly-walked index
    was published, the file watcher was already armed on the still-live snapshot, so
    the publish step's read of the entry count could race the watcher's concurrent
    append. The publish now reads the count through the same release/acquire fence as
    the rest of the read path. Caught by the concurrent-rebuild gate under
    ThreadSanitizer, which now passes.
  • Test suite is now clean under ThreadSanitizer and AddressSanitizer (no
    quarantine flag). Two fixes. First, the audit-policy tests run against an isolated,
    throwaway $HOME instead of the real ~/.zy ledger, so running the suite no longer
    writes test entries into (or races a live shell on) your actual audit database.
    Second, the interactive PTY tests now scale their timing windows under a
    sanitizer-slowed shell (ZY_TEST_TIMING_SCALE), which removes the timing flakes that
    previously only appeared under sanitizers.

Security

  • fff: use-after-free in the concurrent index acquire (ZY-022). Under
    concurrent searches while the indexed tree changed (e.g. the fff-serve MCP
    server), a reader could dereference an index snapshot that another thread freed
    in the same instant, because the snapshot was validated before its reference was
    taken. Acquisition now takes the reference under a publish lock held across the
    swap, so a snapshot stays alive until the reader owns a reference. Found and
    regression-guarded by new concurrent-reader ThreadSanitizer tests.
  • migrate import rejects non-regular tar members (ZY-014 follow-up). On top of
    refusing absolute or ..-traversing paths, an imported archive that contains a
    symlink, hardlink, or device/fifo/socket member is now refused before any
    extraction or privileged copy. Only regular files and directories are accepted.
    This closes the symlink-extraction and planted-link half of the import attack
    surface that was noted when ZY-014 was first fixed.

Verify your download

curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.40/zy_1.0.40_amd64.deb
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.40/SHA256SUMS
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.40/SHA256SUMS.minisig
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.40/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/cfe17d44dececa1fa2b56b8994a1dc72806e8d671fecc6be678fc77acb4aaf3e/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.40

Install (Debian / Ubuntu)

The recommended path is the zy APT repository which handles upgrades automatically:

curl -1sLf 'https://dl.cloudsmith.io/public/iskandarputra/zyshell/setup.deb.sh' | sudo -E bash
sudo apt install zy

Or direct .deb install:

sudo dpkg -i zy_1.0.40_amd64.deb
sudo apt-get install -f

zy v1.0.39

Choose a tag to compare

@iskandarputra iskandarputra released this 02 Jun 16:28

Official binary release for zy 1.0.39.

What's changed

Reliability & correctness

  • Resolved 23 defects found in a full source-code review (ZY-001–021): memory-safety overflows in the parser and stor create, a data race in the fff grep engine, git prompt modules not rendering with async = true, the two-argument slice <start> <end> form, a JSON out-of-bounds read, with-env exit-status propagation, a safer cp, valid YAML string escaping, and more.

Security

  • Hardened sys migrate import against shell-injection via crafted archive or path names.
  • Crash reports (~/.zy/crash.log, owner-only) now capture recent log context for faster diagnosis — no additional data is exposed.

Packaging

  • The Debian package now declares its complete runtime dependencies (derived from the binary with dpkg-shlibdeps), so a clean dpkg -i or apt install pulls every library zy needs.

Under the hood

  • Central structured logging with crash-context capture, a leaner documentation tree, and new maintainer release tooling.

Verify your download

curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.39/zy_1.0.39_amd64.deb
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.39/SHA256SUMS
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.39/SHA256SUMS.minisig
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.39/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/7058f5f9e03db3f125fcb9f8465987682e2a15b93a3da58c16981cf6c5016fc8/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.39

Install (Debian / Ubuntu)

The recommended path is the zy APT repository which handles upgrades automatically:

curl -1sLf 'https://dl.cloudsmith.io/public/iskandarputra/zyshell/setup.deb.sh' | sudo -E bash
sudo apt install zy

Or direct .deb install:

sudo dpkg -i zy_1.0.39_amd64.deb
sudo apt-get install -f

zy v1.0.38

Choose a tag to compare

@iskandarputra iskandarputra released this 29 May 17:59

Official binary release for zy 1.0.38.

Verify your download

curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.38/zy_1.0.38_amd64.deb
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.38/SHA256SUMS
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.38/SHA256SUMS.minisig
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.38/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/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/af2761feb8486ae281fb13b92388a27488c80e87df3be603c7f8138ad576dac3/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.38

Install (Debian / Ubuntu)

The recommended path is the zy APT repository which handles upgrades automatically:

curl -1sLf 'https://dl.cloudsmith.io/public/iskandarputra/zyshell/setup.deb.sh' | sudo -E bash
sudo apt install zy

Or direct .deb install:

sudo dpkg -i zy_1.0.38_amd64.deb
sudo apt-get install -f

zy v1.0.37

Choose a tag to compare

@iskandarputra iskandarputra released this 24 May 20:38

Initial public release. Signed manifest (key D108A9B9CA11369C), Cloudsmith APT repo, VirusTotal scanned, GHCR container image.