Nushell 0.108.0
Today, we're releasing version 0.108.0 of Nu. This release adds an optional MCP server for AI agents, new experimental options (pipefail
and enforce-runtime-annotations
), smarter completions with per-command completers, clearer errors and better streaming behavior, reorder-cell-paths
enabled by default, and stronger CustomValue
support.
Where to get it
Nu 0.108.0 is available as pre-built binaries or from crates.io. If you have Rust installed you can install it using cargo install nu
.
As part of this release, we also publish a set of optional plugins you can install and use with Nushell.
Table of contents
- Highlights and themes of this release
- Changes
- Breaking changes
- Additions
- Promoted
reorder-cell-paths
to opt-out - Pipefail
- Enforce Assignment Type Annotations at Runtime
--endian
flag forformat bits
- Guess no more! More completions in built-in commands
- Simple syntax for simple completions
- Command-wide completion handler
for
loops no longer collect their source- Streams of Streams
- Arbitrary Pipeline Metadata
- HTTP Response Metadata
- Add
$nu.is-lsp
to help with printing in lsp mode - New Configuration Option:
$env.config.table.batch_duration
- New Configuration Option:
$env.config.table.stream_page_size
- Table literal columns support variables
- Add %J and %Q format specifiers for compact date/time formatting
- Add configuration for columnar menu traversal direction
- Nushell MCP Server
- Add
--chars
tostr length
command - The
to md
command now always returns valid Markdown tables - New switches are available for
to md
compact
your records- Breaking changes
collect
removessource
metadatastring
s andglob
s can now be implicitly cast between each other- Other additions
- Promoted
- Other changes
- Bug fixes
- Aliases are now expanded before being sent to external completers
- Discard
path add
input - Fixed IR evaluation error caused by redirecting output of branched blocks
- Dotnu completion refactor, fixes path with space for both dotnu module path and external executable
- boolean coloring in light theme
- changed fallback for unknown color names
- Improve collection type inference
- Using
break
/continue
outside loops raises a compile error - Windows device path fixes
- Clean up error handlers when jumping outside of
try
blocks - Other fixes
- Notes for plugin developers
- Hall of fame
- Full changelog
Highlights and themes of this release [toc]
Let AI agents use Nushell commands [toc]
Thanks to @ayax79, this release adds an MCP server for Nushell, allowing AI agents to run Nushell commands.
To use it, compile Nushell with the mcp
feature (it's not included by default), then start the server with:
nu --mcp
This is a brand new feature and will keep evolving as we refine it. You can join the discussion on our Discord server in #ai-with-nu.
More cool experiments [toc]
This release brings two new experimental options. You can read more about experimental features here.
First, pipefail
, added by @WindSoilder. When enabled, $env.LAST_EXIT_CODE
will be set to the exit code of the rightmost command in a pipeline that exited with a non-zero status, or zero if all commands succeeded. When used together with $env.config.display_errors.exit_code = true
, Nushell will also report which command failed. See some examples here.
Second, enforce-runtime-annotations
, added by @mkatychev. When enabled, Nushell will catch errors at runtime when assigning values to type-annotated variables. Without it, type annotations are only checked at parse time. This helps catch tricky type errors during execution. Read more about it here.
We also promoted one of our experimental options from opt-in to opt-out, so it’s now enabled by default: reorder-cell-paths
. It improves performance by reordering how cell-paths are evaluated, making lookups faster. If it causes any issues, you can disable it by setting reorder-cell-paths=false
in your experimental options. More info here.
Learn how to enable experimental options.
Tab, tab, tab... Aha! [toc]
Thanks to @Bahex, tab completion just got a lot smarter! A ton of built-in commands now come with handy suggestions. Custom commands can use a new, easy syntax to add their own completions. And for full control, you can now pick which completer to use by adding the @complete
attribute to your commands.
Fish out error sources [toc]
Some errors hiding inside other commands weren’t showing up quite right. @Bahex fixed that by making stream collection raise an error when the stream itself contains one. Errors inside each
calls also now keep their full context, making it easier to track down what went wrong.
Smarter CustomValue
s [toc]
CustomValue
s let plugin authors introduce their own data types without being limited to Nushell's built-in Value
s. Until now, though, they often lagged behind in functionality. Thanks to @cptpiepmatz, they’re now much closer to behaving like regular Value
s.
They now support both the $value!
syntax added by @Bahex in 0.105.0 and the older $value?
syntax. Plugin authors can now decide exactly how these should behave for their custom types.
On top of that, CustomValue
s now work with the save
command. Plugin authors can define how their custom data should be saved, which is often more complex than for built-in types.
See the notes for plugin developers for more details.
Changes [toc]
Breaking changes [toc]
into value
is now detect type
, and into value
now converts custom values to plain values (#16697) [toc]
Until 0.107, into value
tried to detect the types of table cells. It converted strings into typed values, which is where the name came from. But it didn't match the purpose of the other into
commands. This command is now called detect type
but it doesn't operate on cells anymore, so you have to call update cells {detect type}
to use that functionality again. With that we can also use that command on non-table types to try to infer the types. The into value
name is now used for converting custom values from plugins into native Nushell values.
The table
command already showed the base Nushell value:
> custom-value generate | table
I used to be a custom value! My data was (abc)
> custom-value generate | describe
CoolCustomValue
> custom-value generate | into value | describe
string
Collecting a stream that contains errors now raises an error itself (#16738) [toc]
Previously, if an error was returned as a stream item and you collected that stream into a value, you would and up with a list that has an error
item which you wouldn't know about until you interacted with that specific item.
You could even end up with a list entirely made up of error
s:
let items = 1..10 | each {|n| error make { msg: $"Error ($n)" } }
items | describe
# list<error>
With this release that's no longer the case:
let items = 1..10 | each {|n| error make { msg: $"Error ($n)" } }
Error: nu::shell::eval_block_with_input
× Eval block failed with pipeline input
╭─[entry #4:1:13]
1 │ let items = 1..10 | each {|n| error make { msg: $"Error ($n)" } }
· ──┬──
· ╰── source value
╰────
Error:
× Error 1
╭─[entry #4:1:31]
1 │ let items = 1..10 | each {|n| error make { msg: $"Error ($n)" } }
· ─────┬────
· ╰── originates from here
╰────
Simply attempting to collect such a stream immediately re-throws the first error encountered.
This also has the effect of preserving the context of errors raised some nested command calls like...
Errors raised in nested each
calls preserve their context [toc]
Previously, errors raised in nested each
calls (and other commands that returned streams) could lose their outer/caller context.
If the closure returned a stream, errors in the stream were not properly chained and lost this context. Running the following code:
0..1 | each {
0..1 | each {|e|
error make {msg: boom}
}
}
we get:
Error: nu::shell::eval_block_with_input
× Eval block failed with pipeline input
╭─[source:2:2]
1 │ 0..1 | each {
2 │ 0..1 | each {|e|
· ──┬─
· ╰── source value
3 │ error make {msg: boom}
╰────
Error:
× boom
╭─[source:3:3]
2 │ 0..1 | each {|e|
3 │ error make {msg: boom}
· ─────┬────
· ╰── originates from here
4 │ }
╰────
Error: nu::shell::eval_block_with_input
× Eval block failed with pipeline input
╭─[source:1:1]
1 │ 0..1 | each {
· ──┬─
· ╰── source value
2 │ 0..1 | each {|e|
╰────
Error: nu::shell::eval_block_with_input
× Eval block failed with pipeline input
╭─[source:2:2]
1 │ 0..1 | each {
2 │ 0..1 | each {|e|
· ──┬─
· ╰── source value
3 │ error make {msg: boom}
╰────
Error:
× boom
╭─[source:3:3]
2 │ 0..1 | each {|e|
3 │ error make {msg: boom}
· ─────┬────
· ╰── originates from here
4 │ }
╰────
Changes to EditCommand
s (#16841) [toc]
- renamed
cutinside
tocutinsidepair
- renamed
yankinside
tocopyinsidepair
- added
cutaroundpair
- added
copyaroundpair
- added
cuttextobject
- added
copytextobject
Vi Mode Text Objects (#16841) [toc]
Vi mode now supports inner and around (i
and a
) text objects for:
- word, bound to
w
(a sequence of letters, digits and underscores, separated with white space) - WORD, bound to
W
(a sequence of non-blank characters, separated with white space) - brackets, bound to
b
(any of( )
,[ ]
,{ }
) - quotes, bound to
q
(any of" "
,' '
,``
)
Existing "pair" motions for specific matching pairs (e.g. di(
or ci"
) are also extended to support "around" versions (e.g. da(
or ca"
) that cover the pair characters.
Additionally, the pair motions will now jump to the next pair if non are found within search range.
- For symmetric pairs like quotes, searching is restricted to the current line.
- For asymmetric pairs like brackets, search is multi-line across the whole buffer.
Symmetric pairs search does not do any parsing or matching of pairs based on language/groupings. It simply searches for the previous and next matching characters.
Other breaking changes [toc]
The LSP session is no longer marked as interactive (
$nu.is-interactive == false
) (#16580)The Polars plugin's value types are renamed to be prefixed with
polars_
(e.g. polars_dataframe) (#16819)
Additions [toc]
Experimental Options
Opt-in experimental options can be enabled and opt-out options can disabled using the command-line argument:
nu --experimental-options '[foo,bar=false]'
or the environment variable:
NU_EXPERIMENTAL_OPTIONS="foo,bar=false" nu
Or if you're feeling adventurous you can enable all of them with all
nu --experimental-options '[all]'
NU_EXPERIMENTAL_OPTIONS="all" nu
Promoted reorder-cell-paths
to opt-out (#16795) [toc]
In Nushell 0.106.0, experimental options were added, first of which is reorder-cell-paths
. With this release reorder-cell-paths
is promoted to being opt-out, meaning that by default this experimental option is enabled now.
This option improves cell-path accesses by reordering how the cell-path should be evaluated without modifying the output in any way.
If you experience any trouble with this, you can disable it via passing reorder-cell-paths=false
via the command-line argument or environment variable.
Pipefail (#16449) [toc]
This release adds a new experimental option: pipefail
.
When enabled, $env.LAST_EXIT_CODE
will be set to the exit code of rightmost command in the pipeline which exit with a non-zero status, or zero if all commands in the pipeline exit successfully.
> ^false | print aa
aa
> $env.LAST_EXIT_CODE
1
Using it together with $env.config.display_errors.exit_code = true
will report the command that failed:
> $env.config.display_errors.exit_code = true
> ^ls | ^false | print aa
aa
aa
Error: nu::shell::non_zero_exit_code
× External command had a non-zero exit code
╭─[entry #3:1:8]
1 │ ^ls | ^false | print aa
· ──┬──
· ╰── exited with code 1
╰────
Enforce Assignment Type Annotations at Runtime (#16079) [toc]
Nushell is perfectly capable of catching type errors at parse time...
let table1: table<a: string> = [{a: "foo"}]
let table2: table<b: string> = $table1
Error: nu::parser::type_mismatch
× Type mismatch.
╭─[entry #1:2:32]
1 │ let table1: table<a: string> = [{a: "foo"}]
2 │ let table2: table<b: string> = $table1
· ───┬───
· ╰── expected table<b: string>, found table<a: string>
╰────
But only if the relevant types are known at parse time.
let table1: table = ({a: 1} | into record | to nuon | from nuon);
let table2: table<b: string> = $table1
# * crickets *
Which can lead to some unexpected results:
let x: table<b: string> = ({a: 1} | to nuon | from nuon)
$x | describe
# record<a: int>
This release adds a new experimental option: enforce-runtime-annotations
When it's enabled, nushell can catch this class of type errors at runtime, and throw a cant_convert
error:
Error: nu::shell::cant_convert
× Can't convert to table.
╭─[entry #9:1:56]
1 │ let table1: table = ({a: 1} | into record | to nuon | from nuon);
· ────┬────
· ╰── can't convert record<a: int> to table
2 │ let table2: table<b: string> = $table1
╰────
This would be a breaking change for scripts where any
coercion/conversions previously ignored field constraints for records and tables, which is why it's being phased in with an experimental option.
Examples
mut a: record<b: int> = {b:1}; $a.b += 3; $a.b -= 2; $a.b *= 10; $a.b /= 4; $a.b
# 5.0
mut a: record<b: int> = {b:1}
$a.b += 3
$a.b -= 2
$a.b *= 10
$a.b /= 4
$a.b
Error: nu::shell::cant_convert
× Can't convert to record<b: int>.
╭─[entry #1:5:1]
4 │ $a.b *= 10
5 │ $a.b /= 4
· ─┬
· ╰── can't convert record<b: float> to record<b: int>
6 │ $a.b
╰────
let x: record<b: int> = ({a: 1} | to nuon | from nuon)
$x | describe
# record<a: int>
let x: record<b: int> = ({a: 1} | to nuon | from nuon)
$x | describe
Error: nu::shell::cant_convert
× Can't convert to record<b: int>.
╭─[entry #2:1:45]
1 │ let x: record<b: int> = ({a: 1} | to nuon | from nuon)
· ────┬────
· ╰── can't convert record<a: int> to record<b: int>
2 │ $x | describe
╰────
--endian
flag for format bits
(#16574) [toc]
format bits
used to output in native endian, until Nu 0.107.0 (#16435) changed it to big endian. Now, you will be able to choose the behavior with --endian
(use format bits --endian native
to get the original behavior from before Nu 0.107.0).
~> 258 | format bits
00000001 00000010
~> 258 | format bits --endian little # (or `--endian native` in a little endian system)
00000010 00000001
Guess no more! More completions in built-in commands (#16383) [toc]
Thanks to some recent improvements behind the scenes, nushell built-in commands that expect a specific set of values now suggests these values.
The following commands now have suggestions:
random uuid --version <...> --namespace <...>
into int --endian <...>
into duration --unit <...>
into datetime --timezone <...>
histogram --percentage-type <...>
http* --redirect-mode <...>
fill --alignment <...>
cal --week-start <...>
date to-timezone <...>
attr deprecated --report <...>
merge deep --strategy <...>
from csv
,from tsv
:--trim <...>
table --theme <...>
split list --split <...>
Simple syntax for simple completions (#16789) [toc]
Custom completions are powerful, able to provide dynamic completions based on what's written on the command line so far.
But you don't always need that power. In fact, for most cases a simple static list of options is enough.
To make it simpler and more concise to meet this need, this release adds a new way to define completions for command parameters:
def go [
direction: string@[ left up right down ]
] {
$direction
}
Completions can be provided as a list of strings inline in the command signature, or stored in a const
variable and used like that.
const directions = [ left up right down ]
def go [ direction: string@$directions ] {
$direction
}
Command-wide completion handler (#16765) [toc]
There is big difference between custom completions that can be specified in a command's signature, and the "global" external completer ($env.config.completions.external.completer
).
The former is added to individual parameters, while the latter provides completions for all arguments of (external) commands.
This makes using custom commands that wrap external commands awkward. The global external completer won't provide completions for the wrapper command and adding completions to that command uses a completely different API. It would be nice to be able to use the external completer's API for custom completions, or use the external completer for wrapper commands...
@complete external
Now it's possible to explicitly enable the global external completer for extern
and custom command def
initions:
@complete external
def --wrapped jc [...args] {
^jc ...$args | from json
}
@complete <completer>
Instead of using the external completer, command-wide custom completers can be used for specific commands:
def carapace-completer [spans: list<string>] {
^carapace ..
}
@complete carapace-completer
def --env get-env [name] { $env | get $name }
@complete carapace-completer
def --env set-env [name, value] { load-env { $name: $value } }
@complete carapace-completer
def --env unset-env [name] { hide-env $name }
Combined with extern
definitions, this makes it trivial to use specific completion sources for specific commands:
def fish-completer [spans: list<string>] {
^fish ..
}
@complete fish-completer
extern git []
@complete fish-completer
extern asdf []
Yes, you can remove the match
expression from your external completer!
{|spans: list<string>|
# ...
# no need for this anymore!
match $spans.0 {
# fish completes commits and branch names in a nicer way
git => $fish_completer
# carapace doesn't have completions for asdf
asdf => $fish_completer
_ => $carapace_completer
} | do $in $spans
Full implementation of fish-completer
from the cookbook.
def fish-completer [spans: list<string>] {
^fish --command $"complete '--do-complete=($spans | str replace --all "'" "\\'" | str join ' ')'"
| from tsv --flexible --noheaders --no-infer
| rename value description
| update value {|row|
let value = $row.value
let need_quote = ['\' ',' '[' ']' '(' ')' ' ' '\t' "'" '"' "`"] | any { $in in $value }
if ($need_quote and ($value | path exists)) {
let expanded_path = if ($value starts-with ~) { $value | path expand --no-symlink } else { $value }
$'"($expanded_path | str replace --all "\"" "\\\"")"'
} else { $value }
}
}
for
loops no longer collect their source (#16593) [toc]
Previously, for
loops only worked with values (ranges, lists, etc), so using them with streaming commands would collect the whole stream before iteration could start. Now it also works with streams too!
This makes it possible to use it with slow or unbounded streams:
for event in (watch . --glob=**/*.rs) {
cargo test
}
Streams of Streams (#16735) [toc]
When you run each
over a list (or stream), it can only return a single item for each input. When we need to create multiple values from a single input, we can just return a list in the closure and run the output through flatten
.
each { .. } | flatten
When the closure itself generates a stream however, that pattern can be inadequate, as the stream has to be collected into a list, just to be flattened afterwards. This might increase memory usage, and result in unnecessary waits.
To address this, each
has a new flag: --flatten
each --flatten
does not wait to collect closure output to ensure returning a single output item for each input, instead the stream from the closure is directly passed through. Here's a demonstration of differences:
|
|
Arbitrary Pipeline Metadata (#16821) [toc]
In addition to existing metadata fields (content_type
, source
, span
), it's now possible to attach arbitrary metadata fields to pipeline data using metadata set --merge
:
"data" | metadata set --merge {custom_key: "value"} | metadata | get custom_key
Combined with metadata access
, this makes it possible to carry extra data with streams, without impacting the stream data itself.
HTTP Response Metadata (#16821) [toc]
All http
commands now attach response data (previously only accessible with http * --full
) as metadata to their output streams. This can be accessed under the http_response
field within the pipeline metadata:
status
- HTTP status codeheaders
- Response headers as[{name, value}, ...]
urls
- Redirect history
Accessing this metadata after the response completes may not offer much benefit.
http get https://api.example.com | metadata | get http_response.status
# => 200
Where it shines is accessing it alongside the streaming response body using metadata access
:
http get --allow-errors https://api.example.com/events.jsonl
| metadata access {|meta|
if $meta.http_response.status != 200 {
error make {msg: "failed"}
} else { }
}
| lines
| each { from json }
| where event_type == "error"
Add $nu.is-lsp
to help with printing in lsp mode (#16635) [toc]
When nushell is launched with the --lsp
flag, nushell will set $nu.is-lsp
to true so that users can programmatically know when nushell is in LSP mode.
When in LSP mode, the print
command will only print to stderr. This is done to prevent the LSP from misbehaving when it sees text that isn't JSON-RPC in stdout, which is being listened to by the LSP.
New Configuration Option: $env.config.table.batch_duration
(#16629) [toc]
It is now possible to configure the batch duration for the table
command.
The table
command (and by proxy the default pipeline output) handles streaming in batches to avoid long wait times until any data is visible. With the $env.config.table.batch_duration
option, it is now possible to define this time it will collect a batch. This is by default 1 second as it was previously a hard-coded value.
Default setting and previous output:
1..=6 | each {|i| sleep 1sec; $i | into string | $"after ($in) sec"}
# ╭───┬─────────────╮
# │ 0 │ after 1 sec │
# ╰───┴─────────────╯
# ╭───┬─────────────╮
# │ 1 │ after 2 sec │
# ╰───┴─────────────╯
# ╭───┬─────────────╮
# │ 2 │ after 3 sec │
# ╰───┴─────────────╯
# ╭───┬─────────────╮
# │ 3 │ after 4 sec │
# ╰───┴─────────────╯
# ╭───┬─────────────╮
# │ 4 │ after 5 sec │
# ╰───┴─────────────╯
# ╭───┬─────────────╮
# │ 5 │ after 6 sec │
# ╰───┴─────────────╯
Configured to wait for two seconds:
$env.config.table.batch_duration = 2sec
1..=6 | each {|i| sleep 1sec; $i | into string | $"after ($in) sec"}
# ╭───┬─────────────╮
# │ 0 │ after 1 sec │
# │ 1 │ after 2 sec │
# ╰───┴─────────────╯
# ╭───┬─────────────╮
# │ 2 │ after 3 sec │
# │ 3 │ after 4 sec │
# ╰───┴─────────────╯
# ╭───┬─────────────╮
# │ 4 │ after 5 sec │
# │ 5 │ after 6 sec │
# ╰───┴─────────────╯
New Configuration Option: $env.config.table.stream_page_size
(#16629) [toc]
It is now possible to configure the maximum page size of streamed data in the table
command.
The table
command (and by proxy the default pipeline output) chunks streamed data into separate tables according to the stream page size which can now be set via $env.config.table.stream_page_size
. By default it's 1000, so (if fast enough) it will collect 1000 entries into a single table and then start a new table.
Default setting and previous output:
1..4 | each {"item " + ($in | into string)}
# ╭───┬────────╮
# │ 0 │ item 1 │
# │ 1 │ item 2 │
# │ 2 │ item 3 │
# │ 3 │ item 4 │
# ╰───┴────────╯
Configured to page at maximum 2 entries:
$env.config.table.stream_page_size = 2
1..4 | each {"item " + ($in | into string)}
# ╭───┬────────╮
# │ 0 │ item 1 │
# │ 1 │ item 2 │
# ╰───┴────────╯
# ╭───┬────────╮
# │ 2 │ item 3 │
# │ 3 │ item 4 │
# ╰───┴────────╯
Table literal columns support variables (#16669) [toc]
Table literals can now have variables in the column names list. For example, this is now allowed:
> let column_name = 'column0'
[[ $column_name column1 ];
[ foo bar ]
[ baz car ]
[ far fit ]]
╭───┬─────────┬─────────╮
│ # │ column0 │ column1 │
├───┼─────────┼─────────┤
│ 0 │ foo │ bar │
│ 1 │ baz │ car │
│ 2 │ far │ fit │
╰───┴─────────┴─────────╯
Add %J and %Q format specifiers for compact date/time formatting (#16588) [toc]
The format date
and into datetime
commands now support two new format specifiers for creating compact, sortable date and time components:
- %J produces compact dates in
YYYYMMDD
format (e.g.,20250918
) - %Q produces compact times in
HHMMSS
format (e.g.,131144
)
These can be used individually for date-only or time-only formatting, or combined for full timestamps (%J_%Q
produces 20250918_131144
), providing more flexibility than a single combined format.
Perfect for:
- Backup file naming with custom separators
- Log file timestamps with flexible formatting
- Date-only or time-only compact representations
- Any scenario requiring sortable, human-readable date/time components
Both commands use the same format specifications, ensuring consistency when parsing and formatting dates.
This addition is fully backward compatible - all existing format specifiers continue to work unchanged.
Add configuration for columnar menu traversal direction (#16724) [toc]
The feature makes it possible to choose the direction that items are placed in the columnar menu. With the default value of "horizontal"
, items are ordered left-to-right in each row, with later items appearing in rows below earlier items.
1 2 3 4
5 6 7 8
9 10
With the value set to "vertical"
, items are ordered top-to-bottom in each column, with later items appearing in columns to the right of earlier items.
1 4 7 10
2 5 8
3 6 9
The configuration option is currently called "tab_traversal" with a default value of "horizontal".
In order to toggle horizontal or vertical, add or update the completion_menu in your config.nu
# Example - Completion menu configuration
$env.config.menus ++= [{
name: completion_menu
only_buffer_difference: false # Search is done on the text written after activating the menu
marker: "| " # Indicator that appears with the menu is active
type: {
layout: columnar # Type of menu
columns: 4 # Number of columns where the options are displayed
col_width: 20 # Optional value. If missing all the screen width is used to calculate column width
col_padding: 2 # Padding between columns
tab_traversal: "horizontal" # Direction in which pressing <Tab> will cycle through options, "horizontal" or "vertical"
}
style: {
text: green # Text style
selected_text: green_reverse # Text style for selected option
description_text: yellow # Text style for description
}
}]
Nushell MCP Server (#16693, #16758, #16839, #16857) [toc]
When compiled with the "mcp" feature (not a part of the default feature set), the nushell binary can be used as a local (stdio) MCP server.
To start the nushell MCP server
nu --mcp
This will allow a local AI agent to use MCP to execute native nushell commands and external commands.
Add --chars
to str length
command (#16768) [toc]
The str length
command now has a --chars
flag to allow you to count characters.
> 'hällo' | str length --chars
5
The to md
command now always returns valid Markdown tables (#16681) [toc]
Previously, a command like this:
> [['foo' 'bar']; ['1 | 2' 'a | b']] | to md
|foo|bar|
|-|-|
|1 | 2|a | b|
would generate an invalid table, since the |
characters inside the cell content were not escaped.
Now, such special characters are automatically escaped, ensuring the output always follows proper Markdown table syntax:
> [['foo' 'bar']; ['1 | 2' 'a | b']] | to md
| foo | bar |
| --- | --- |
| 1 \| 2 | a \| b |
New switches are available for to md
(#16681) [toc]
--escape-md
|-m
escape all Markdown special characters--escape-html
|-t
escape HTML special characters--escape-all
|-a
escape both HTML and Markdown
compact
your records (#16810) [toc]
compact
can now be used to remove null
or --empty
items from records too.
Breaking changes [toc]
collect
removes source
metadata (#16821) [toc]
collect
now removes source
metadata field even when content_type
is set. Previously: both were preserved, now only content_type
is preserved.
string
s and glob
s can now be implicitly cast between each other (#16079) [toc]
Previously string
types were not able to be coerced into glob
types (and vice versa). They are now subtypes of each other.
Before:
> def spam [foo: glob] { echo $foo }; let f = 'aa'; spam $f
Error: nu::shell::cant_convert
× Can't convert to glob.
╭─[entry #109:1:56]
1 │ def spam [foo: glob] { echo $foo }; let f = 'aa'; spam $f
· ─┬
· ╰── can't convert string to glob
╰────
Now:
> def spam [foo: glob] { echo $foo }; let f = 'aa'; spam $f
aa
Other additions [toc]
Added support for exabyte and exbibyte filesize format. (#16689)
Experimental options now provide two new fields
since
andissue
which refer to when the experimental option was introduced and an issue on Github that tracks the status of that issue. You can see them viadebug experimental-options
. (#16142)New operators:
not-starts-with
andnot-ends-with
. Added for parity with other comparison operators. (#16852)Added file completions for command redirections (
o>>
,e>
, ...) (#16831)
Other changes [toc]
Add run pr
and download pr
to toolkit
(#16770) [toc]
The toolkit in the Nushell repository can now download and run PRs by downloading artifacts from CI runs. It can be run like this:
use toolkit
toolkit run pr <number>
Add network
feature to top-level crate (#16686) [toc]
Nushell can now be compiled without network related commands.
If you are manually building Nushell without default features (cargo build --no-default-features
) you will now need to pass --features network
to get access the network commands.
Improved error messages for binary literals (#16688) [toc]
Previously, a generic shell error is thrown for invalid binary strings. Now, an improve error message is thrown for invalid binary, hexadecimal, and octal strings.
0b[121]
Error: nu::parser::invalid_binary_string
× Invalid binary string.
╭─[entry #3:1:1]
1 │ 0b[121]
· ───┬───
· ╰── invalid binary string
╰────
help: binary strings may contain only 0 or 1.
Polars 50.1 upgrade (#16703) [toc]
polars fetch
has been removed due to no longer being supported on LazyFrame- introduced flag
--drop-nulls
topolars dummies
- introduced flag
--separator
topolars dummies
- introduced flag
--strict
topolars integer
- introduced flag
--base
topolars integer
- introduced flag
--dtype
topolars integer
- introduced flag
--stable
topolars pivot
- polars pivot will no longer perform a stable pivot by default, the flag
--stable
must be passed in to perform a stable pivot. - Introduced flag
--time-zone
topolars as-datetime
- Introduced flag
--time-unit
topolars as-datetime
Additional changes [toc]
- The
which
command now lists all commands (internal and external) when no argument is passed to it (#16551)
Bug fixes [toc]
Aliases are now expanded before being sent to external completers (#16640) [toc]
Before this change, when an external completer was invoked for an alias, the first argument sent to the completer would be the alias itself instead of the aliased command.
For example, with an alias defined as alias gl = git log
, typing gl branch_name
and pressing TAB
would send the arguments gl
, log
, branch_name
to the external completer.
After this change, the alias is expanded and the arguments sent to the external completer will be git
, log
, branch_name
.
Discard path add
input (#16606) [toc]
Previously path add
(from std/util
) could fail if it was fed any
input.
> use std/util 'path add'
> ls | each {} | path add
Error: nu::shell::incompatible_parameters
× Incompatible parameters.
╭─[std/util/mod.nu:18:17]
17 │ ]: [nothing -> nothing, nothing -> list<path>] {
18 │ let span = (metadata $paths).span
· ────┬─── ───┬──
· │ ╰── but a positional metadata expression was also given
· ╰── pipeline input was provided
19 │ let paths = $paths | flatten
╰────
Now any input to path add
will be discarded. It's still possible to pass it with $in
:
[a b c] | each {|p| $env.HOME | path join $p } | path add $in
Fixed IR evaluation error caused by redirecting output of branched blocks (#16676) [toc]
The following commands will no longer raise ir-eval errors:
if true { echo "hey" } out> /dev/null
if false { echo "hey" } else { echo "ho" } out> /dev/null
try { 1/0 } catch { echo "hi" } out> /dev/null
Dotnu completion refactor, fixes path with space for both dotnu module path and external executable (#16715) [toc]
Module path completion for command use
/source
now behaviors in the same manner of basic file completion, which is less error prone when dealing with paths with spaces or other special characters.
Fixed the bug of empty result of external executable path completion at the head of a pipeline when the path prefix contains spaces.
boolean coloring in light theme (#16722) [toc]
When using std/config light-theme
, booleans inside structures are now colored in dark cyan instead of white, which was very hard to read in combination with a light terminal background.
changed fallback for unknown color names (#16722) [toc]
If some named color in $env.config.color_config
is misspelled or otherwise unknown to nu, the corresponding element is now colored using the terminal's default (as in ansi reset
) instead of white.
Improve collection type inference (#16530) [toc]
Fixes order-dependent table type inference bugs and introduces similar type widening for lists:
def get_type [] { scope variables | where name == "$foo" | first | get type }
let foo = [ [bar]; [ { baz: 1 } ], [ { baz: 1, extra: true } ] ]
get_type # table<bar: record<baz: int, extra: bool>> -> table<bar: record<baz: int>>
let foo = [ [bar]; [ { baz: 1, extra: true } ], [ { baz: 1 } ] ]
get_type # table<bar: any> -> table<bar: record<baz: int>>
let foo = [ { baz: 1 }, { baz: 1, extra: true } ]
get_type # list<any> -> list<record<baz: int>>
Using break
/continue
outside loops raises a compile error (#16740) [toc]
Previously this error wasn't caught during compilation, and manifested as a runtime error when the execution reached the invalid break
/continue
call.
> let fn = { break }
> do $fn
Tried to execute 'run' for the 'break' command: this code path should never be reached in IR mode
Error: × Main thread panicked.
├─▶ at crates/nu-cmd-lang/src/core_commands/break_.rs:45:9
╰─▶ internal error: entered unreachable code
help: set the `RUST_BACKTRACE=1` environment variable to display a backtrace.
> let fn = { break }
Error: nu::compile::not_in_a_loop
× 'break' can only be used inside a loop
╭─[entry #4:1:12]
1 │ let fn = { break }
· ──┬──
· ╰── can't be used outside of a loop
╰────
Windows device path fixes (#16775) [toc]
- On Windows, UNC and device paths no longer get a trailing
\
appended when being cast topath
- On Windows,
open
,save
andsource
now work with device paths like\\.\NUL
or\\.\CON
, as well as reserved device names likeNUL
andCON
. Using full device paths is recommended.
Clean up error handlers when jumping outside of try
blocks (#16838) [toc]
Previously, jumping out of a try
block (possible with break
/continue
commands) did not clean up the error handler properly the way exiting it normally does.
So when an error was thrown afterwards, the error handler would catch it and continue execution in the catch
block...
do {
loop {
try { throwing an error jumps
┌───────────break into the catch block!!
│ } catch {◄───────────────────────────┐
break jumps out │ print 'jumped to catch block' │
of the loop │ return │
as expected │ } │
└──►} │
error make -u {msg: "success"}───────────┘
}
This was due to a miscompilation bug, which is now fixed.
Other fixes [toc]
compact --empty
now recognizes 0 byte binary values as empty (#16810)Fixed associative equality comparison for floats. (#16549)
The
**
(power) operator now parses as right-associative instead of left-associative, matching standard mathematical behavior. (#16552)port <start> <end>
on Windows now properly finds open TCP ports. (#16546)Fixed a panic when
duration
s were created with a value outside of the supported value range with certain unit suffixes. (#16594)Fixes an issue in
std help externs
- it was using a column that is no longer present. (#16590)The LSP should now work better with unsaved files and with overlays used as prefixes (ex.
overlay use foo as bar --prefix
) (#16568)Variables defined as custom command arguments show now be available to autocomplete inside that command. (#16531)
Fixed building
nu-command
by itself. (#16616)for
andwhile
loop blocks starting with a range literal no longer causes a compiler error (#16764)Some
attr
subcommands had incorrect output signatures, which are now fixed. (#16774)For commands expecting directory arguments, regular files will no longer be suggested as completions as a fallback for when no directory can be suggested. (#16846)
Notes for plugin developers [toc]
Allow saving CustomValue
s (#16692) [toc]
CustomValue
now supports a save
method. Implementors can use it to make their values savable to disk.
Pass optional and casing to follow path methods for CustomValue
(#16736) [toc]
Plugin authors that use CustomValue
can now access the optional
and casing
fields in their follow_path_int
and follow_path_string
methods to add behavior for optional access or case sensitivity.
Warning
This is a breaking change for the plugin signature so it needs an update. Simply ignoring these fields may be a valid solution.
Hall of fame [toc]
Thanks to all the contributors below for helping us solve issues, improve documentation, refactor code, and more! 🙏
author | change | link |
---|---|---|
@ayax79 | Added missing duration support | #16566 |
@fdncred | Update help to remove incorrect link | #16585 |
@Bahex | Add a Cow -like type with looser requirements that supports Serialize/Deserialize | #16382 |
@blindFS | Pitfall of redefining def | #16591 |
@blindFS | Empty span of unbalanced delimiter | #16292 |
@132ikl | Add area prefix to labeler bot labels | #16609 |
@blindFS | Duplicated testing code cleanup | #16578 |
@132ikl | Label bot: rename A:dataframe to A:plugin-polars | #16612 |
@sholderbach | Update labels in issue templates | #16610 |
@fdncred | Bump nushell to latest nu-ansi-term | #16613 |
@cptpiepmatz | Install prebuilt cargo-audit to speed up Security Audit workflow | #16615 |
@sholderbach | Rework signature flag parsing | #16617 |
@Sheape | Add integration test for associativity in power operator | #16627 |
@132ikl | Upload artifact during CI | #16633 |
@sholderbach | Bump scc to a non-yanked version | #16631 |
@132ikl | Add release notes devdocs | #16614 |
@sholderbach | Fix mismatched_lifetime_syntaxes lint | #16618 |
@Bahex | Set up ast-grep with some custom lints | #16674 |
@Sheape | Align query db description with other database commands | #16680 |
@cptpiepmatz | Bump rusqlite to 0.37 | #16684 |
@Bahex | Parser clean up. | #16668 |
@sholderbach | Update Rust to 1.88.0 and use let-chains | #16671 |
@blindFS | Check_call dedup | #16596 |
@132ikl | Nushell can now be built without pulling in SQLite as a dependency. | #16682 |
@sholderbach | Fix ast-grep config | #16691 |
@cptpiepmatz | Load experimental options before command context is loaded | #16690 |
@Bahex | Gate some plugin related tests behind the "plugin" feature | #16698 |
@Bahex | Cleaner api for embedded config files | #16626 |
@132ikl | Fix CI artifact upload on Windows runner | #16702 |
@blindFS | Add more test cases for recent fixes | #16628 |
@blindFS | Cleanup some unnecessary code introduced recently | #16696 |
@132ikl | The Nushell development toolkit is now a module rather than a single file. | #16701 |
@cptpiepmatz | Fix building cargo check -p nu-cli --features sqlite | #16707 |
@Bahex | Ast-grep rule improvements | #16699 |
@blindFS | Panic on goto std files | #16694 |
@blindFS | Missing span of incomplete math binary op | #16721 |
@ysthakur | Ignore XDG_CONFIG_HOME when getting default config path | #16595 |
@Bahex | Refactor each | #16732 |
@fdncred | Update nushell to use coreutils v0.2.2 crates | #16737 |
@WindSoilder | Set pipefail issue number | #16761 |
@Bahex | Add tracking issue of experimental reorder-cell-paths | #16767 |
@ayax79 | MCP tweaks and fixes | #16758 |
@cptpiepmatz | Stop using macos-13 in CI | #16796 |
@Bahex | Add tests for ast-grep rules | #16817 |
@Tyarel8 | ### cell-path s can now be converted into string s | #16809 |
@Bahex | Increase timeout of hover_on_external_command | #16818 |
@132ikl | Remove all usages of internal_span | #16799 |
@Bahex | Mark Value variants as #[non_exhaustive] | #16820 |
@Bahex | Big testing refactor | #16802 |
@Bahex | Remove pipeline calls added after the testing refactor branched off | #16835 |
@cablehead | Http / metadata access examples '--allow-errors' flag required | #16836 |
@cablehead | Relay rich error formatting for LLM-friendly diagnostics | #16839 |
@fdncred | Update reedline to latest commit a274653 | #16841 |
@cptpiepmatz | Bump Windows crates | #16843 |
@cablehead | Disable ANSI coloring for error messages | #16857 |
@sholderbach | Add required fields for crate publish to nu-mcp | #16862 |
Full changelog [toc]
author | title | link |
---|---|---|
@132ikl | Add area prefix to labeler bot labels | #16609 |
@132ikl | Label bot: rename A:dataframe to A:plugin-polars | #16612 |
@132ikl | Add release notes devdocs | #16614 |
@132ikl | Upload artifact during CI | #16633 |
@132ikl | Allow variables in columns of table literals | #16669 |
@132ikl | Gate SQLite history behind existing sqlite feature | #16682 |
@132ikl | Add network feature to top-level crate | #16686 |
@132ikl | Refactor toolkit.nu into module | #16701 |
@132ikl | Fix CI artifact upload on Windows runner | #16702 |
@132ikl | Add run pr and download pr to toolkit | #16770 |
@132ikl | Remove all usages of internal_span | #16799 |
@Bahex | feat: Add a Cow -like type with looser requirements that supports Serialize/Deserialize | #16382 |
@Bahex | feat: Add completions to built-in commands with a predetermined list of suggestions. | #16383 |
@Bahex | feat: for loops are lazy, don't collect their source | #16593 |
@Bahex | refactor: cleaner api for embedded config files | #16626 |
@Bahex | refactor: defer compiling blocks | #16634 |
@Bahex | Parser clean up. | #16668 |
@Bahex | Set up ast-grep with some custom lints | #16674 |
@Bahex | Gate some plugin related tests behind the "plugin" feature | #16698 |
@Bahex | ast-grep rule improvements | #16699 |
@Bahex | Refactor each | #16732 |
@Bahex | feat: each can flatten streams from the closure without collecting | #16735 |
@Bahex | refactor!: collecting a stream with an error raises an error | #16738 |
@Bahex | Using break /continue outside loops raises a compile error | #16740 |
@Bahex | Properly initialize register when compiling while , for | #16764 |
@Bahex | Command-wide completions | #16765 |
@Bahex | add tracking issue of experimental reorder-cell-paths | #16767 |
@Bahex | fix attr * command signatures | #16774 |
@Bahex | Add syntax for simple completions from const lists | #16789 |
@Bahex | big testing refactor | #16802 |
@Bahex | compact works on records too | #16810 |
@Bahex | Add tests for ast-grep rules | #16817 |
@Bahex | Increase timeout of hover_on_external_command | #16818 |
@Bahex | Mark Value variants as #[non_exhaustive] | #16820 |
@Bahex | Remove pipeline calls added after the testing refactor branched off | #16835 |
@Bahex | Clean up error handlers when jumping outside of try blocks | #16838 |
@Jan9103 | mark LSP sessions as non-interactive | #16580 |
@Sheape | Use symmetric float comparison for large floats | #16549 |
@Sheape | Fix associativity when parsing with power operator | #16552 |
@Sheape | Add integration test for associativity in power operator | #16627 |
@Sheape | Align query db description with other database commands | #16680 |
@Sheape | Improve error messages for invalid binary strings | #16688 |
@Sheape | Bump bytesize to 2.1.0 | #16689 |
@Tyarel8 | feat(into string): add cell-path input to into string | #16809 |
@Tyarel8 | feat: add not-starts-with and not-ends-with operators | #16852 |
@WindSoilder | Try to implement pipefail feature | #16449 |
@WindSoilder | set pipefail issue number | #16761 |
@Xylobyte | Escape markdown characters to always output valid table, add new switches for that | #16681 |
@andoalon | Add --endian flag to format bits | #16574 |
@app/dependabot | build(deps): bump scraper from 0.23.1 to 0.24.0 | #16479 |
@app/dependabot | build(deps): bump crate-ci/typos from 1.35.5 to 1.36.0 | #16571 |
@app/dependabot | build(deps): bump hashbrown from 0.15.2 to 0.16.0 | #16572 |
@app/dependabot | build(deps): bump shadow-rs from 1.2.0 to 1.3.0 | #16573 |
@app/dependabot | build(deps): bump actions/setup-python from 5 to 6 | #16619 |
@app/dependabot | build(deps): bump crate-ci/typos from 1.36.0 to 1.36.2 | #16620 |
@app/dependabot | build(deps): bump actions/labeler from 5 to 6 | #16621 |
@app/dependabot | build(deps): bump tempfile from 3.21.0 to 3.22.0 | #16623 |
@app/dependabot | build(deps): bump plist from 1.7.0 to 1.8.0 | #16704 |
@app/dependabot | build(deps): bump strum_macros from 0.26.4 to 0.27.2 | #16741 |
@app/dependabot | build(deps): bump tempfile from 3.22.0 to 3.23.0 | #16742 |
@app/dependabot | build(deps): bump brotli from 7.0.0 to 8.0.2 | #16743 |
@app/dependabot | build(deps): bump quick-xml from 0.37.5 to 0.38.3 | #16744 |
@app/dependabot | build(deps): bump uuid from 1.16.0 to 1.18.1 | #16745 |
@app/dependabot | build(deps): bump crate-ci/typos from 1.36.2 to 1.37.0 | #16776 |
@app/dependabot | build(deps): bump rmcp from 0.6.4 to 0.7.0 | #16777 |
@app/dependabot | build(deps): bump indicatif from 0.17.11 to 0.18.0 | #16779 |
@app/dependabot | build(deps): bump nix from 0.29.0 to 0.30.1 | #16780 |
@app/dependabot | build(deps): bump rmcp from 0.7.0 to 0.8.1 | #16823 |
@app/dependabot | build(deps): bump shadow-rs from 1.3.0 to 1.4.0 | #16824 |
@ayax79 | Added missing duration support | #16566 |
@ayax79 | Introduction of the MCP server feature. | #16693 |
@ayax79 | Polars 50.1 upgrade | #16703 |
@ayax79 | MCP tweaks and fixes | #16758 |
@ayax79 | Polars input/output types cleanup | #16819 |
@blindFS | fix(parser): empty span of unbalanced delimiter | #16292 |
@blindFS | fix(completion): missing local variables | #16531 |
@blindFS | fix(lsp): malfunctioning on unsaved files, empty workspaceFolder parameter, overlay use with prefixes | #16568 |
@blindFS | refactor(lsp): duplicated testing code cleanup | #16578 |
@blindFS | fix(parser): pitfall of redefining def | #16591 |
@blindFS | fix: panic on large duration number | #16594 |
@blindFS | refactor(parser): check_call dedup | #16596 |
@blindFS | test(lsp): add more test cases for recent fixes | #16628 |
@blindFS | fix(lsp): panic on goto std files | #16694 |
@blindFS | refactor(parser): cleanup some unnecessary code introduced recently | #16696 |
@blindFS | fix(completion): dotnu completion refactor, fixes path with space for both dotnu module path and external executable | #16715 |
@blindFS | fix(parser): missing span of incomplete math binary op | #16721 |
@blindFS | fix(completion): file completion for redirection target | #16831 |
@cablehead | feat: add custom metadata support and HTTP response metadata | #16821 |
@cablehead | fix: http / metadata access examples '--allow-errors' flag required | #16836 |
@cablehead | feat(mcp): relay rich error formatting for LLM-friendly diagnostics | #16839 |
@cablehead | fix(mcp): disable ANSI coloring for error messages | #16857 |
@cptpiepmatz | Add more infos to experimental options | #16142 |
@cptpiepmatz | Check for free TCP ports on Windows via TCP table | #16546 |
@cptpiepmatz | Install prebuilt cargo-audit to speed up Security Audit workflow | #16615 |
@cptpiepmatz | Fix building nu-command by itself | #16616 |
@cptpiepmatz | Add batch config for tables | #16629 |
@cptpiepmatz | Bump rusqlite to 0.37 | #16684 |
@cptpiepmatz | Load experimental options before command context is loaded | #16690 |
@cptpiepmatz | Allow saving CustomValue s | #16692 |
@cptpiepmatz | Replace into value with detect type | #16697 |
@cptpiepmatz | Fix building cargo check -p nu-cli --features sqlite | #16707 |
@cptpiepmatz | Pass optional and casing to follow path methods for CustomValue | #16736 |
@cptpiepmatz | Promote experimental option reorder-cell-paths to opt out | #16795 |
@cptpiepmatz | Stop using macos-13 in CI | #16796 |
@cptpiepmatz | Bump Windows crates | #16843 |
@fdncred | update help to remove incorrect link | #16585 |
@fdncred | bump nushell to latest nu-ansi-term | #16613 |
@fdncred | add $nu.is-lsp to help with printing in lsp mode | #16635 |
@fdncred | update nushell to use coreutils v0.2.2 crates | #16737 |
@fdncred | add --chars to str length command | #16768 |
@fdncred | update reedline to the latest commit | #16791 |
@fdncred | update reedline to latest commit a274653 | #16841 |
@fixerer | Fix ir eval error on branched block redirection (Fixes #16582) | #16676 |
@maxim-uvarov | Add %J and %Q format specifiers for compact date/time formatting | #16588 |
@mkatychev | fix(engine, type-system)!: enforce assignment type annotations at runtime | #16079 |
@nome | Theming fix | #16722 |
@nome | Windows device path fixes | #16775 |
@sgvictorino | improve collection type inference | #16530 |
@sholderbach | Update labels in issue templates | #16610 |
@sholderbach | Rework signature flag parsing | #16617 |
@sholderbach | Fix mismatched_lifetime_syntaxes lint | #16618 |
@sholderbach | Bump scc to a non-yanked version | #16631 |
@sholderbach | Update Rust to 1.88.0 and use let-chains | #16671 |
@sholderbach | Fix ast-grep config | #16691 |
@sholderbach | Add required fields for crate publish to nu-mcp | #16862 |
@simonborje | Expand alias when sending command to external completion | #16640 |
@simonborje | Add configuration for columnar menu traversal direction | #16724 |
@simonborje | Completions for directory s won't fallback to regular files | #16846 |
@weirdan | Fix std help externs | #16590 |
@weirdan | Discard path add input | #16606 |
@xolra0d | which without application parameter now lists all internal and external executables | #16551 |
@ysthakur | test: Ignore XDG_CONFIG_HOME when getting default config path | #16595 |