feat: add fish shell and PowerShell Core plugins#2563
feat: add fish shell and PowerShell Core plugins#2563arvindcr4 wants to merge 3 commits intoantinomyhq:mainfrom
Conversation
Port the ForgeCode ZSH plugin to fish shell, providing the same `:` command interface for interacting with Forge from fish. Includes: - forge.fish: main plugin with 47 functions covering all commands (new, agent, model, provider, conversation, commit, suggest, etc.) - fish_right_prompt.fish: right prompt showing active model/agent - README.md: installation and usage documentation Uses fish-native idioms (commandline, bind, set, string match) instead of ZLE widgets. Auto-loads via conf.d/. Co-Authored-By: claude-flow <ruv@ruv.net>
- Use `string collect` to preserve blank lines in editor content, commit messages, and suggest output (prevents list-splitting) - Unquote $filtered in provider selection so all matching providers are written back, not just the first - Escape workspace_path and _FORGE_BIN in background fish -c strings to handle paths with special characters - Use `string collect` on rprompt output to prevent list-splitting - Use idiomatic fish [2] indexing instead of tail -1 for regex groups - Use $fish_key_bindings check for vi-mode instead of fragile bind test - Use `string escape --style=script` for commit message shell safety - Add comments explaining zsh subcommand usage from fish plugin Co-Authored-By: claude-flow <ruv@ruv.net>
| if not test -s "$_FORGE_COMMANDS_CACHE" | ||
| CLICOLOR_FORCE=0 command $_FORGE_BIN list commands --porcelain 2>/dev/null >"$_FORGE_COMMANDS_CACHE" | ||
| end | ||
| command cat "$_FORGE_COMMANDS_CACHE" |
There was a problem hiding this comment.
Race condition in commands cache initialization. Multiple fish sessions for the same user could simultaneously detect the cache file as missing/empty and attempt to write to it concurrently, potentially corrupting the cache file.
function _forge_get_commands --description "Lazy-load command list from forge (outputs to stdout)"
if not test -s "$_FORGE_COMMANDS_CACHE"
set -l tmpfile (mktemp "$_FORGE_COMMANDS_CACHE.XXXXXX")
CLICOLOR_FORCE=0 command $_FORGE_BIN list commands --porcelain 2>/dev/null >"$tmpfile"
command mv -f "$tmpfile" "$_FORGE_COMMANDS_CACHE" 2>/dev/null
end
command cat "$_FORGE_COMMANDS_CACHE"
endUse atomic write via temporary file + move to prevent corruption when multiple sessions initialize simultaneously.
| if not test -s "$_FORGE_COMMANDS_CACHE" | |
| CLICOLOR_FORCE=0 command $_FORGE_BIN list commands --porcelain 2>/dev/null >"$_FORGE_COMMANDS_CACHE" | |
| end | |
| command cat "$_FORGE_COMMANDS_CACHE" | |
| if not test -s "$_FORGE_COMMANDS_CACHE" | |
| set -l tmpfile (mktemp "$_FORGE_COMMANDS_CACHE.XXXXXX") | |
| CLICOLOR_FORCE=0 command $_FORGE_BIN list commands --porcelain 2>/dev/null >"$tmpfile" | |
| command mv -f "$tmpfile" "$_FORGE_COMMANDS_CACHE" 2>/dev/null | |
| end | |
| command cat "$_FORGE_COMMANDS_CACHE" | |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
Port the ForgeCode shell plugin to PowerShell Core (pwsh 7+), providing the same `:` command interface on Windows, macOS, and Linux. Includes: - ForgeCode.psm1: 1547 lines, all 30+ commands with aliases, fzf pickers, PSReadLine Enter/Tab handlers, right prompt, background sync/update via Start-Job - ForgeCode.psd1: module manifest (PowerShell 7.0+) - README.md: installation, usage, command reference, troubleshooting Uses PSReadLine RevertLine+Insert+AcceptLine pattern for Enter key interception. Cross-platform clipboard via Set-Clipboard with fallbacks. Script-scoped state variables for conversation/agent tracking. Co-Authored-By: claude-flow <ruv@ruv.net>
|
Action required: PR inactive for 5 days. |
| function _forge_find_index --description "Find 1-based position of value in porcelain output (skipping header)" | ||
| # Pipe mode: some_command | _forge_find_index <value> [field_number] | ||
| # Inline mode: _forge_find_index <multiline_string> <value> [field_number] | ||
| # Returns 1-based index after the header row. Defaults to 1 if not found. | ||
|
|
||
| set -l value_to_find | ||
| set -l field_number 1 | ||
|
|
||
| if test (count $argv) -ge 2 | ||
| # Inline: _forge_find_index "output" "value" [field] | ||
| set -l output_data $argv[1] | ||
| set value_to_find $argv[2] | ||
| if test (count $argv) -ge 3 | ||
| set field_number $argv[3] | ||
| end | ||
| set -l index 1 | ||
| set -l line_num 0 | ||
| for line in (printf '%s\n' $output_data) | ||
| set line_num (math $line_num + 1) | ||
| if test $line_num -eq 1 | ||
| continue # skip header | ||
| end | ||
| set -l field_value (printf '%s' "$line" | awk "{print \$$field_number}") | ||
| if test "$field_value" = "$value_to_find" | ||
| echo $index | ||
| return 0 | ||
| end | ||
| set index (math $index + 1) | ||
| end | ||
| echo 1 | ||
| return 0 | ||
| else | ||
| # Pipe mode: ... | _forge_find_index "value" [field] | ||
| set value_to_find $argv[1] | ||
| set -l index 1 | ||
| set -l line_num 0 | ||
| while read -l line | ||
| set line_num (math $line_num + 1) | ||
| if test $line_num -eq 1 | ||
| continue # skip header | ||
| end | ||
| set -l field_value (printf '%s' "$line" | awk "{print \$$field_number}") | ||
| if test "$field_value" = "$value_to_find" | ||
| echo $index | ||
| return 0 | ||
| end | ||
| set index (math $index + 1) | ||
| end | ||
| echo 1 | ||
| return 0 | ||
| end |
There was a problem hiding this comment.
The _forge_find_index function has a critical bug in its argument handling. When called with 2 arguments in pipe mode (e.g., cat file | _forge_find_index "value" 1), it incorrectly enters inline mode (line 136 checks if test (count $argv) -ge 2) but inline mode expects the first argument to be the output data, not the value to find.
This breaks multiple callers:
- Line 309:
command cat $tmpfile | _forge_find_index "$current_provider" 1 - Line 341:
command cat $tmpfile | _forge_find_index "$current_model" 1 - Line 609:
command cat $tmpfile | _forge_find_index "$current_id" 1
Fix by checking if stdin is available before determining mode:
function _forge_find_index
set -l value_to_find $argv[1]
set -l field_number 1
if test (count $argv) -ge 2
set field_number $argv[2]
end
set -l index 1
set -l line_num 0
while read -l line
set line_num (math $line_num + 1)
if test $line_num -eq 1
continue
end
set -l field_value (printf '%s' "$line" | awk "{print \$$field_number}")
if test "$field_value" = "$value_to_find"
echo $index
return 0
end
set index (math $index + 1)
end
echo 1
return 0
end| function _forge_find_index --description "Find 1-based position of value in porcelain output (skipping header)" | |
| # Pipe mode: some_command | _forge_find_index <value> [field_number] | |
| # Inline mode: _forge_find_index <multiline_string> <value> [field_number] | |
| # Returns 1-based index after the header row. Defaults to 1 if not found. | |
| set -l value_to_find | |
| set -l field_number 1 | |
| if test (count $argv) -ge 2 | |
| # Inline: _forge_find_index "output" "value" [field] | |
| set -l output_data $argv[1] | |
| set value_to_find $argv[2] | |
| if test (count $argv) -ge 3 | |
| set field_number $argv[3] | |
| end | |
| set -l index 1 | |
| set -l line_num 0 | |
| for line in (printf '%s\n' $output_data) | |
| set line_num (math $line_num + 1) | |
| if test $line_num -eq 1 | |
| continue # skip header | |
| end | |
| set -l field_value (printf '%s' "$line" | awk "{print \$$field_number}") | |
| if test "$field_value" = "$value_to_find" | |
| echo $index | |
| return 0 | |
| end | |
| set index (math $index + 1) | |
| end | |
| echo 1 | |
| return 0 | |
| else | |
| # Pipe mode: ... | _forge_find_index "value" [field] | |
| set value_to_find $argv[1] | |
| set -l index 1 | |
| set -l line_num 0 | |
| while read -l line | |
| set line_num (math $line_num + 1) | |
| if test $line_num -eq 1 | |
| continue # skip header | |
| end | |
| set -l field_value (printf '%s' "$line" | awk "{print \$$field_number}") | |
| if test "$field_value" = "$value_to_find" | |
| echo $index | |
| return 0 | |
| end | |
| set index (math $index + 1) | |
| end | |
| echo 1 | |
| return 0 | |
| end | |
| function _forge_find_index --description "Find 1-based position of value in porcelain output (skipping header)" | |
| # Pipe mode: some_command | _forge_find_index <value> [field_number] | |
| # Returns 1-based index after the header row. Defaults to 1 if not found. | |
| set -l value_to_find $argv[1] | |
| set -l field_number 1 | |
| if test (count $argv) -ge 2 | |
| set field_number $argv[2] | |
| end | |
| set -l index 1 | |
| set -l line_num 0 | |
| while read -l line | |
| set line_num (math $line_num + 1) | |
| if test $line_num -eq 1 | |
| continue # skip header | |
| end | |
| set -l field_value (printf '%s' "$line" | awk "{print \$$field_number}") | |
| if test "$field_value" = "$value_to_find" | |
| echo $index | |
| return 0 | |
| end | |
| set index (math $index + 1) | |
| end | |
| echo 1 | |
| return 0 | |
| end | |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
Summary
Ports the ForgeCode ZSH shell plugin to fish shell and PowerShell Core (pwsh 7+), providing the same
:command interface across all major shells.Fish Shell Plugin
:commandsand@filereferencesbind+commandlinebuiltins,string collectfor safe multi-line handlingconf.d/PowerShell Core Plugin
Start-JobSet-Clipboardwith fallbacksFiles
shell-plugin/fish/forge.fishshell-plugin/fish/fish_right_prompt.fishshell-plugin/fish/README.mdshell-plugin/powershell/ForgeCode.psm1shell-plugin/powershell/ForgeCode.psd1shell-plugin/powershell/README.mdInstallation
Fish
PowerShell
Notes
Test plan
🤖 Generated with claude-flow