Skip to content

Releases: vlshcc/vlsh

v1.1.7.1

06 Mar 13:30

Choose a tag to compare

2026-03-06 — version 1.1.7.1

Bug fixes

Plugin compilation uses runtime V lookup instead of compile-time path

  • plugins/plugins.v used the V compile-time constant @VEXE to locate the
    V compiler for compiling plugins. This baked the absolute path of the build
    machine's V binary (e.g. /home/sarm/repos/v/v) into the vlsh binary, so
    plugins reload failed on any other system where that path did not exist.
  • Replaced the const v_compiler = @VEXE compile-time constant with a
    find_v_compiler() function that searches $PATH at runtime, matching the
    approach already used by exec/exec.v for .vsh scripts. The lookup also
    skips directories named v to avoid false matches.
  • When v is not found in $PATH, a clear error message is printed instead
    of a cryptic "file not found" failure.

Startup no longer blocks on plugin compilation

  • plugins.load() now accepts a compile boolean parameter. On startup the
    shell passes false, so only pre-compiled plugin binaries are loaded — the
    V compiler is never invoked and the shell cannot hang waiting for it.
    Explicit operations (plugins reload, plugins enable, plugins update)
    pass true to compile outdated plugins as before.

Platform support

DragonFlyBSD compatibility

  • mux/pty_helpers.h: added __DragonFly__ to the FreeBSD preprocessor guard
    so that <libutil.h> is used for openpty/forkpty on DragonFlyBSD (same
    header as FreeBSD).
  • pkg/build.sh: added --dragonfly cross-compilation flag. The script
    automatically downloads the DragonFlyBSD 6.4.2 ISO (~260 MB), extracts the
    sysroot (headers and libraries), compiles V's bundled Boehm GC for the target,
    and cross-compiles with clang + lld. The sysroot is cached at
    ~/.vmodules/dragonflybsdroot for subsequent builds.
  • pkg/dragonfly/: added DPorts package files (Makefile, pkg-descr,
    distinfo) for building a native package directly on DragonFlyBSD, using the
    same format as FreeBSD ports.

Housekeeping

  • Maintainer email updated to sarmonsiill@tilde.guru throughout all packaging
    files (pkg/deb/control.in, pkg/build.sh, pkg/freebsd/Makefile,
    pkg/dragonfly/Makefile).
  • Repository URL updated from https://github.com/dvwallin/vlsh to
    https://github.com/vlshcc/vlsh in README.md, pkg/build.sh,
    pkg/deb/control.in, and pkg/rpm/vlsh.spec.in.

Version bumps

  • Version bumped to 1.1.7.1 in vlsh.v, v.mod, and README.md.

v1.1.7

05 Mar 15:53

Choose a tag to compare

2026-03-05 — version 1.1.7

Bug fixes

Fix interactive/TUI programs hanging the shell

  • Commands like vi, atto, nano, ssh, and other interactive programs
    would hang vlsh because exec.run() unconditionally redirected
    stdin/stdout/stderr through pipes via set_redirect_stdio(). Interactive
    programs need direct terminal (TTY) access for keyboard input and screen
    control, which pipes cannot provide.
  • exec.run() now only redirects stdio when it is actually needed: pipe
    chains, file redirection (>, >>, <), or piped stdin input from a
    previous command. Standalone commands inherit the terminal's file
    descriptors directly, allowing TUI programs to work correctly.

Version bumps

  • Version bumped to 1.1.7 in vlsh.v, v.mod, and README.md.

v1.1.6

05 Mar 15:36

Choose a tag to compare

2026-03-05 — version 1.1.6

Breaking changes

Unified environment variable handling (POSIX export/unset only)

  • The three separate mechanisms for managing environment variables —
    path= directives in ~/.vlshrc, the venv builtin, and export
    have been consolidated into the single POSIX-standard export/unset
    interface.
  • The path= config directive and the path builtin command (list / add /
    remove) have been removed. PATH is now set via export in ~/.vlshrc,
    e.g. export PATH="/usr/local/bin:/usr/bin:/bin:$PATH".
  • The venv builtin command (list / add / rm) and its __VLSH_VENV
    registry have been removed. Use export KEY=VALUE and unset KEY
    instead.

Features

~/.vlshrc now supports export and unset

  • At startup, export and unset lines in ~/.vlshrc are executed
    through the shell's main_loop, so export PATH="/opt/bin:$PATH" works
    with full $VAR expansion.
  • The config file now uses # for comments (POSIX-style) instead of ".

find_exe uses $PATH directly

  • exec/find_exe() now reads os.getenv('PATH') and splits on : to
    locate executables, instead of reading a custom cfg.Cfg.paths field.
  • When $PATH is empty, a hardcoded fallback (/usr/local/bin,
    /usr/bin, /bin) is used so the shell cannot be softlocked.

Removals

  • cfg.Cfg.paths field removed from the config struct.
  • cfg.extract_paths(), cfg.add_path(), cfg.remove_path(),
    cfg.paths(), and cfg.fallback_paths constant removed.
  • shellops.venv_registry, shellops.venv_tracked(),
    shellops.venv_track(), shellops.venv_untrack() removed.
  • exec.Cmd_object.cfg field replaced with exec.Cmd_object.aliases
    (the only config data the exec module still needs).
  • find_v_exe() no longer takes a cfg_paths parameter.

Housekeeping

  • cfg/cfg.v: alias and style parsers hardened — starts_with() checks
    replace fragile ent[0..N] slicing that could panic on short lines.
  • Default ~/.vlshrc rewritten in POSIX-style format with # comments
    and export PATH=....
  • Help system updated: path and venv entries removed; export,
    unset, and source entries added.
  • .gitignore added — ignores compiled vlsh binary, builds/, and V
    compiler artifacts.
  • builds/ directory (34 MB of release binaries) removed from git
    tracking.
  • build_deb.sh moved to pkg/build.sh alongside the packaging
    templates.
  • CHANGELOG renamed to CHANGELOG.md.
  • README updated: config example, feature bullets, built-in commands
    table, .vsh section, and POSIX compatibility section all reflect the
    new export-only approach.

Version bumps

  • Version bumped to 1.1.6 in vlsh.v, v.mod, and README.md.

v1.1.5.2

05 Mar 15:11

Choose a tag to compare

2026-03-05 — version 1.1.5.2

Bug fixes

Always capture stdout for plugin output_hooks

  • Previously, set_redirect_stdio() was only called when the command had
    piped input or intercept_stdio was set. Commands that ran directly on the
    terminal never had their stdout captured, so plugin output_hooks (e.g. the
    hist plugin) missed their output entirely.
  • run() in exec/exec.v now calls child.set_redirect_stdio() unconditionally
    and always slurps stdout. The captured output is stored in last_output and
    printed, ensuring every command's output is available to hooks.
  • The stdin pipe close (C.close(child.stdio_fd[0])) is also now unconditional,
    removing the separate branch that left it open for non-piped commands.
  • ls colour flag changed from --color=auto to --color=always so that
    colour output is preserved when stdout is captured through the redirect.

Version bumps

  • Version bumped to 1.1.5.2 in vlsh.v, v.mod, and README.md.

v1.1.5.1

05 Mar 14:39

Choose a tag to compare

2026-03-05 — version 1.1.5.1

Bug fixes

PATH failsafe to prevent shell softlock

  • Previously, if all configured path= entries in ~/.vlshrc were empty,
    non-existent, or otherwise malformed, extract_paths returned a hard error
    that prevented the config from loading. This made it impossible to run any
    external command — including text editors to fix the config — effectively
    softlocking the shell.
  • cfg.extract_paths now silently skips empty and non-existent path entries
    instead of returning an error.
  • exec.find_exe now falls back to standard system paths
    (/usr/local/bin, /usr/bin, /bin) when the configured PATH is empty,
    printing a WRN| message so the user knows the failsafe is active.
  • The external-command dispatcher in vlsh.v (both the general else branch
    and the ls fallthrough) now creates a fallback Cfg with the standard
    system paths when cfg.get() fails, instead of silently returning exit
    code 1.
  • A new cfg.fallback_paths constant centralises the list of fallback
    directories.

Version bumps

  • Version bumped to 1.1.5.1 in vlsh.v, v.mod, and README.md.

v1.1.4

20 Feb 19:41

Choose a tag to compare

2026-02-20 — version 1.1.4

Bug fixes

Aliases that expand to builtins now work correctly

  • An alias whose expansion begins with a shell builtin (e.g. ~localcd ~/.local)
    previously failed with "cd not found in defined aliases or in $PATH" because alias
    expansion inside exec.Task runs after builtin dispatch.
  • Fixed by adding one level of alias expansion at the very top of dispatch_cmd, before
    the match statement. A self-alias guard (expanded_cmd != cmd) prevents infinite
    recursion for trivially self-referential aliases.

Features added

|| (OR) and ; (unconditional sequence) command chaining

  • The shell now supports three chaining operators:
    • && — run next command only if the previous one exited 0 (pre-existing)
    • || — run next command only if the previous one exited non-zero
    • ; — run next command unconditionally
  • A new split_commands(input string) []ChainPart function replaces the old
    split_and_chain. It parses the input character-by-character, correctly ignoring
    operators that appear inside single or double quotes. A lone | (pipe) is left in
    the buffer for walk_pipes to handle.
  • ChainPart { cmd string; pre_op string } struct records each segment and the operator
    that precedes it. Both have been moved to the new shellops sub-module.
  • main_loop now updates $? after every sub-command in a chain so that subsequent
    segments see the correct last exit status.

$? — last exit status tracking

  • $? is initialised to "0" in main() at shell startup.
  • After every top-level main_loop call (and after every sub-command in a chain),
    $? is updated via os.setenv('?', exit_code.str(), true).
  • Because the existing echo builtin already expands $VAR through os.getenv,
    echo $? now works as expected with no further changes required.

export builtin

  • export with no arguments prints all environment variables in KEY=VALUE format,
    sorted alphabetically.
  • export KEY=VALUE sets the variable for the current session and all child processes.
  • export KEY (without =) is accepted as a no-op (variables are already inherited).

unset builtin

  • unset KEY [KEY …] removes one or more environment variables from the session.

source / . builtin

  • source <file> (or . <file>) reads the file line-by-line and executes each
    non-blank, non-comment (#) line through main_loop.
  • Returns the exit code of the last executed line.
  • Useful for re-reading ~/.vlshrc or loading shell scripts.

exit N — exit with status code

  • exit now accepts an optional integer argument: exit 0, exit 1, exit 42, etc.
  • Without an argument, exits with code 0 (unchanged).

cd improvements

  • cd - switches to the previous directory ($OLDPWD) and prints the new path,
    matching POSIX / bash behaviour. Returns an error when OLDPWD is not set.
  • ~ and ~/… are now expanded inside cd itself, regardless of whether the shell's
    argument parser has already performed tilde expansion.
  • $PWD is set to the resolved working directory after every successful cd.
  • $OLDPWD is set to the directory that was current before the cd.

Input redirection <

  • External commands now support < file to supply stdin from a file.
  • parse_redirect in exec/exec.v was extended to detect the < token and capture
    the following argument as stdin_file; the function now returns a 4-tuple
    ([]string, string, bool, string).
  • Cmd_object gained a stdin_file string field; walk_pipes forwards it from
    parse_redirect.
  • In run(), when stdin_file is set, the file is read and used as effective_input
    (replacing the empty piped-input default) before stdio is redirected.

New shellops sub-module

  • Pure functions that previously lived in vlsh.v (and caused root-level test
    compilation failures) have been extracted into shellops/shellops.v:
    split_commands, ChainPart, builtin_redirect, write_redirect,
    venv_tracked, venv_track, venv_untrack, and the venv_registry constant.
  • vlsh.v imports them via import shellops { … }.
  • The sub-module only depends on os and strings, keeping it free of heavy
    readline/plugin/mux dependencies so the test runner can compile it standalone.

Tests

New shellops/shellops_test.v

  • Replaces the deleted vlsh_test.v (which could not compile standalone due to V's
    module resolution for root-level module main test files).
  • 8 test groups: split_commands (single, &&, ||, ;, mixed, quoted operators,
    lone pipe), builtin_redirect, write_redirect, and venv helpers — 34 test
    functions total; all pass.

New cmds/cmds_test.v

  • 12 tests covering cd: no-args → home, explicit path, nonexistent → error,
    file → error, ~ alone, ~/ path, PWD updated, OLDPWD updated, OLDPWD
    updated on second change, cd - without OLDPWD → error, cd - returns to
    previous directory, cd - updates OLDPWD.

Updated exec/exec_test.v

  • All parse_redirect call sites updated from 3-tuple to 4-tuple destructuring.
  • New tests for <: basic stdin file, tilde in stdin path, < combined with >,
    < combined with >>, absent stdin_file returns '', both < tokens stripped
    from args.

Documentation

  • README.md updated with a ### POSIX compatibility section covering all new
    chaining operators, input redirection, $?, export/unset, source/.,
    cd improvements, exit N, and aliases expanding to builtins.
  • Built-in commands table updated: cd entry mentions cd -; exit [N]; new rows
    for export, unset, source/..
  • "Features at a glance" bullet updated to include ||, ;, and <.
  • Version bumped to 1.1.4 in vlsh.v, v.mod, and README.md.

v1.1.3

20 Feb 18:04

Choose a tag to compare

2026-02-20 — version 1.1.3

Bug fixes

Tab completion inside subdirectory paths

  • Pressing Tab after a path that ends with / (e.g. cd .config/) had no
    effect when the shell was mid-cycle from a previous Tab press at a shallower
    path level — the saved cycling prefix was used instead of the current path,
    so no new completions were generated.
  • Fixed in readline_fix.v (vlsh_completion): if r.current ends with /
    and a saved cycling prefix (last_prefix_completion) is present, it is
    cleared before the prefix is chosen for the next call. This causes the next
    Tab press to compute fresh completions rooted at the directory the user has
    navigated into, matching the behaviour expected from bash and zsh.

Features added

Fish-style inline autosuggestions

  • As the user types, vlsh_get_suggestion searches r.previous_lines (command
    history, most-recent first) for the first entry that starts with the current
    input and returns the untyped suffix as a ghost-text rune slice.
  • The suggestion is rendered as dimmed gray text (\x1b[38;5;240m) immediately
    after r.current on every vlsh_refresh_line call. The editing cursor is
    repositioned to the end of the typed text, so the ghost text is purely visual
    and cannot be edited directly.
  • Pressing (right arrow) at the end of the line or End / Ctrl+E when
    already at the end appends the full suggestion to r.current and advances
    the cursor. Any other key that changes the input automatically recomputes the
    suggestion on the next redraw.
  • On Enter (vlsh_commit_line), an \x1b[K (erase to end of line) sequence
    is emitted after the refresh and before the newline, so only the typed text
    appears in the terminal's scrollback history.
  • cd path validation — for cd history candidates the target directory
    is extracted from the history entry, tilde-expanded, and checked with
    os.is_dir. Entries whose target path no longer exists are skipped; the
    search continues to the next history entry.
  • Filesystem fallback — when no usable history entry is found,
    vlsh_get_suggestion calls r.completion_callback (the same engine that
    drives Tab completion) with the current prefix and returns the suffix of the
    first result. For cd this yields the first matching directory in the
    current working directory; for other commands it yields the first matching
    file or plugin completion.
  • Implemented entirely in readline_fix.v; no global state is required.
    vlsh_get_suggestion(r Readline) []rune is a pure read of the Readline
    struct and may be called from any context.

v1.1.2

20 Feb 10:54

Choose a tag to compare

2026-02-20 — version 1.1.2

Bug fixes

DEL key now works correctly

  • The DEL key was silently ignored because the terminal emits ESC[P (ANSI
    DCH — Delete Character) rather than the more common ESC[3~ (VT220 Delete).
    Added 'P' { return .delete_right } to vlsh_analyse_escape in
    readline_fix.v so that ESC[P is correctly mapped to a forward-delete.
  • Reverted an earlier incorrect fix that mapped byte 127 to delete_right;
    127 is the Backspace key on this system and must remain delete_left.
  • vlsh_delete_character (Backspace) simplified: the erroneous col-0
    forward-delete workaround has been removed; Backspace at column 0 now
    correctly does nothing, matching standard shell behaviour.
  • vlsh_suppr_character (forward delete) now clears completion state after
    deleting a character, preventing stale completion matches.

Plugin help command

  • Plugins can now declare a help capability; vlsh invokes
    <binary> help [command] when the user runs help <plugin-command>.
  • has_help bool added to the Plugin struct; new show_help() function
    in plugins/plugins.v dispatches to the correct plugin binary.
  • dispatch_cmd() in vlsh.v tries plugin help before falling back to
    the built-in help output.
  • examples/hello_plugin.v updated with a help handler and the help
    capability listed under capabilities.

v1.1.1

20 Feb 07:47

Choose a tag to compare

2026-02-20 — version 1.1.1

Features added

output_hook plugin capability

  • Plugins can now capture the stdout of every command by declaring the
    output_hook capability. The shell invokes the plugin binary as:
    <binary> output_hook <cmdline> <exit_code> <output>
    where <output> is the captured stdout of the command.
  • Output is captured for pipe-chain commands (the final stage that receives
    piped input) and for the built-in echo command. Direct-terminal commands
    (interactive programs such as vim or htop, and plain non-piped
    commands) pass an empty string so that TTY behaviour is never broken.
  • has_output_hook bool added to the Plugin struct in plugins/plugins.v;
    parsed in plugins.load() alongside the existing capability tokens.
  • New run_output_hooks(loaded []Plugin, cmdline string, exit_code int, output string) function in plugins/plugins.v; invoked from
    dispatch_cmd() in vlsh.v after every external command, the echo
    built-in, and the system-ls fallthrough path.
  • last_output string field added to exec.Task; populated in run() when
    the final pipe-stage output is naturally captured.
  • dispatch_cmd() in vlsh.v gains a full_cmdline string parameter so
    hooks see the original unsplit input rather than a reconstructed string.

New example plugin: examples/output_log.v

  • Demonstrates output_hook by recording a timestamped block per command
    (header + captured stdout) to ~/.vlsh/output.log.
  • Registers two shell commands:
    • output_search <pattern> — grep the log for entries matching a pattern
    • output_log_clear — wipe the log file
  • Skips empty commands and internal plugin-management invocations to keep
    the log clean.

Documentation

  • README.md updated: feature bullets, plugin-system paragraph, plugin
    protocol tables (output_hook argument and capability token rows),
    example-plugin snippet, new output_log plugin section, module API
    summary (run_output_hooks added).
  • help plugins updated in cmds/cmds.v: capabilities block added listing
    all six capability tokens including output_hook.
  • examples/hello_plugin.v protocol header updated with output_hook
    argument documentation.
  • Version bumped to 1.1.1 in vlsh.v and v.mod.

v1.1.0

20 Feb 06:42

Choose a tag to compare

2026-02-20 — version 1.1.0

Features added

Versioned remote plugin structure

  • The remote plugin repository now uses a folder-per-plugin layout:
    vlshcc/plugins/<name>/ contains a DESC metadata file (TOML: name,
    author, email, description) and one subdirectory per semver release
    (v1.0.0/, v1.1.0/, …), each holding the plugin's .v source file.
  • Locally, plugins are stored in ~/.vlsh/plugins/<name>/<version>/<name>.v
    (only the installed version is kept). Compiled binaries remain in the flat
    ~/.vlsh/plugins/.bin/ directory as before.
  • Old flat .v files in ~/.vlsh/plugins/ are silently ignored; no
    migration is required.

New and updated plugin subcommands

  • plugins list now shows the installed semver version next to each name.
  • plugins remote now marks each installed plugin with its version and flags
    when a newer version is available in the remote repository
    ([installed v1.0.0, update available: v1.1.0]).
  • plugins search <query> (new) — replaces plugins remote search; searches
    both the plugin name and the description field from the DESC file
    (case-insensitive).
  • plugins install <name> now resolves and installs the latest semver version
    and prints the installed version string (git v1.1.0 installed).
  • plugins update [name] (new) — with a name, updates that one plugin to the
    latest remote version; without a name, updates every installed plugin.
    Prints one status line per plugin (updated or already at latest). Reloads
    plugins after any successful update.
  • plugins delete <name> now removes the entire plugin folder
    (os.rmdir_all) instead of just the .v file.

New structs and helpers in plugins/plugins.v

  • PluginDesc { name, author, email, description string } — holds metadata
    parsed from a remote DESC file.
  • InstalledPlugin { name, version, src string } — holds the name, installed
    semver version, and source path of a local plugin.
  • Plugin struct gains a version string field populated at load time.
  • New public functions: installed_list(), remote_plugin_names(),
    remote_versions(), latest_remote_version(), fetch_desc(),
    update_plugin(), search_remote().
  • install(name) now returns the installed version string (!string).
  • remote_available() removed; replaced by remote_plugin_names().
  • Semver ordering: parse_semver / compare_semver helpers sort version
    strings using a [3]int fixed array.
  • TOML parsing: lightweight line-by-line parse_desc / extract_toml_val
    helpers — no external library required.

Automatic plugin directory creation

  • ~/.vlsh/plugins/ (and ~/.vlsh/plugins/.bin/) are now created
    automatically on startup if they do not exist.

Bugs fixed

GitHub API directory detection broken (plugins remote)

  • V's JSON decoder does not correctly handle @[json: 'type'] when the
    JSON key is a language keyword, leaving the mapped field always empty.
    Replaced with a check on html_url.contains('/tree/'), which is a
    guaranteed GitHub API property for directory entries (files use /blob/).
    This fixes plugins remote reporting "no remote plugins found" even when
    the remote repository is reachable and correctly structured.

Documentation

  • README.md updated: plugin feature bullet, installation section
    (v1.1.0 URLs), built-in commands table, shell-features plugins paragraph,
    "How plugins work" section, managing-plugins command block, example plugin
    install instructions, and module API summary.
  • help plugins updated: removed plugins remote list / plugins remote search; added plugins search <query> and plugins update [name];
    updated descriptions for plugins list and plugins remote.
  • Version bumped to 1.1.0 in vlsh.v and v.mod.