Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ jobs:
fish fish/tests/w4b-screenshots.fish
fish fish/tests/w5a-hremote.fish
fish fish/tests/w5b-herdr-ssh.fish
fish fish/tests/w6-agent-helpers.fish

- name: Validate JSON configs
shell: bash
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,21 @@ Para validar con `HOME`, `PATH` y comandos macOS falsos aislados:
/opt/homebrew/bin/fish fish/tests/w4b-screenshots.fish
```

## Helpers Fish de agentes (W6)

**ADVERTENCIA:** por elección explícita del owner, `cc` ejecuta Claude Code con `--dangerously-skip-permissions` y `oc`/`ocb` ejecutan OpenCode con `--auto`; estos shortcuts intencionalmente omiten o autoaprueban permisos. W6 agrega `cc`, `oc`, `ocb`, `ccx`, `ccd` y `ccclip` como helpers Fish opt-in en el directorio validado; `ccx` entrega el contexto por stdin y `ccclip` escribe al clipboard solo al invocarse. No incluye `ccb`.

| Área | Owner en W6 |
| --- | --- |
| Helpers y soporte privado | `fish/functions/{_cortex_resolve_target,_cortex_run_agent,cc,oc,ocb,ccx,ccd,ccclip}.fish` |
| Home Manager | Mapea cada una de esas funciones de forma explícita; no administra binarios de agentes, clipboard, worktrees, estado, historial ni configuración de proveedores |

Para validar con agentes y clipboard falsos aislados:

```bash
/opt/homebrew/bin/fish fish/tests/w6-agent-helpers.fish
```

## Estructura

```
Expand Down
11 changes: 11 additions & 0 deletions fish/functions/_cortex_resolve_target.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
function _cortex_resolve_target
set -l target "$argv[1]"
test -n "$target"; or set target .

if not test -d "$target"
printf 'Directorio no encontrado: %s\n' "$target" >&2
return 1
end

cd "$target"; and pwd
end
11 changes: 11 additions & 0 deletions fish/functions/_cortex_run_agent.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
function _cortex_run_agent
set -l target "$argv[1]"
set -e argv[1]
set -l original_dir "$PWD"

cd "$target"; or return 1
command $argv
set -l agent_status $status
cd "$original_dir"; or return 1
return "$agent_status"
end
5 changes: 5 additions & 0 deletions fish/functions/cc.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Usage: cc [path]
function cc
set -l resolved (_cortex_resolve_target "$argv[1]"); or return 1
_cortex_run_agent "$resolved" claude --dangerously-skip-permissions
end
43 changes: 43 additions & 0 deletions fish/functions/ccclip.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Usage: ccclip <file...> [-n|--line-numbers]
function ccclip
if test (count $argv) -eq 0
printf 'Uso: ccclip <archivo1> [archivo2 ...] [-n|--line-numbers]\n' >&2
return 1
end

set -l with_numbers false
set -l files
for arg in $argv
switch "$arg"
case -n --line-numbers
set with_numbers true
case '*'
set -a files "$arg"
end
end

begin
for file in $files
if not test -f "$file"
printf 'Archivo no encontrado: %s\n' "$file" >&2
continue
end

set -l extension (path extension "$file" | string trim -c .)
printf '```%s\n// File: %s\n' "$extension" "$file"
set -l content
if test "$with_numbers" = true
set content (nl -ba "$file" | string collect)
else
set content (command cat "$file" | string collect)
end
printf '%s\n```\n\n' "$content"
end
end | pbcopy
set -l clipboard_status $pipestatus[-1]
if test "$clipboard_status" -ne 0
return "$clipboard_status"
end

printf 'Contexto copiado al clipboard (%d archivos)\n' (count $files)
end
16 changes: 16 additions & 0 deletions fish/functions/ccd.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Usage: ccd [subpath]
function ccd
set -l subpath "$argv[1]"
set -l workspace "$WORKSPACE_DIR"
test -n "$workspace"; or set workspace "$HOME/dev"
set -l target "$workspace"
test -n "$subpath"; and set target "$workspace/$subpath"

if test -d "$target"
cd "$target"
printf 'Navegando a: %s\n' "$target"
else
printf 'Directorio no encontrado: %s\n' "$target" >&2
return 1
end
end
14 changes: 14 additions & 0 deletions fish/functions/ccx.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Usage: ccx <context> [path]
function ccx
set -l context "$argv[1]"
if test -z "$context"
printf 'Uso: ccx <contexto> [directorio]\n' >&2
return 1
end

set -l resolved (_cortex_resolve_target "$argv[2]"); or return 1
begin
printf '%s\n' "$context" | _cortex_run_agent "$resolved" claude
return $pipestatus[-1]
end
end
9 changes: 9 additions & 0 deletions fish/functions/oc.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Usage: oc [path]
function oc
set -l resolved (_cortex_resolve_target "$argv[1]"); or return 1
set -l flags
if set -q OPENCODE_DEFAULT_FLAGS; and test -n "$OPENCODE_DEFAULT_FLAGS"
set flags (string replace -ra '[[:space:]]+' \n -- "$OPENCODE_DEFAULT_FLAGS")
end
_cortex_run_agent "$resolved" opencode --auto $flags
end
4 changes: 4 additions & 0 deletions fish/functions/ocb.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Usage: ocb [path]
function ocb
oc $argv
end
139 changes: 139 additions & 0 deletions fish/tests/w6-agent-helpers.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#!/usr/bin/env fish

set -l repo_root (cd (dirname (dirname (status filename))); and pwd)
set -g functions_dir "$repo_root/functions"
set -g workspace (mktemp -d)
test -n "$workspace"; or exit 1
function _cleanup_w6 --on-event fish_exit
test -d "$workspace"; and rm -rf -- "$workspace"
end
function fail
printf 'FAIL: %s\n' "$argv" >&2
exit 1
end
set -g fish_bin (status fish-path); and test -n "$fish_bin"; or fail 'could not resolve Fish interpreter'
function contains
string match -q "*$argv[2]*" -- "$argv[1]"; or fail "$argv[3]"
end

set -g home "$workspace/home"
set -g bin "$workspace/bin"
set -g repo "$workspace/repo"
set -g target "$workspace/target dir"
set -g workspace_dir "$workspace/workspace dir"
mkdir -p "$home/dev" "$bin" "$repo" "$target" "$workspace_dir/sub dir"
printf '#!/bin/sh\nprintf "claude|%%s|cwd=%%s|stdin=" "$*" "$PWD" >> "$W6_LOG"\nwhile IFS= read -r line; do printf "%%s\\n" "$line" >> "$W6_LOG"; done\nprintf "\\n" >> "$W6_LOG"\nexit "${W6_CLAUDE_STATUS:-0}"\n' > "$bin/claude"
printf '#!/bin/sh\nprintf "opencode|%%s|cwd=%%s\\n" "$*" "$PWD" >> "$W6_LOG"\nexit "${W6_OPENCODE_STATUS:-0}"\n' > "$bin/opencode"
printf '#!/bin/sh\nprintf "pbcopy|" >> "$W6_LOG"\nwhile IFS= read -r line; do printf "%%s\\n" "$line" >> "$W6_LOG"; done\nprintf "\\n" >> "$W6_LOG"\nexit "${W6_PBCOPY_STATUS:-0}"\n' > "$bin/pbcopy"
printf '#!/bin/sh\nfor file; do while IFS= read -r line || [ -n "$line" ]; do printf "%%s\\n" "$line"; done < "$file"; done\n' > "$bin/cat"
printf '%s\n' '#!/bin/sh' 'for file; do' ' case "$file" in -*) continue ;; esac' ' i=0' ' while IFS= read -r line || [ -n "$line" ]; do' ' i=$((i + 1))' ' printf "%6s\\t%s\\n" "$i" "$line"' ' done < "$file"' 'done' > "$bin/nl"
chmod +x "$bin"/*
printf 'alpha\n' > "$target/code file.fish"
printf 'beta\n' > "$target/other.txt"
printf '' > "$target/empty.txt"

function run_w6
env -i HOME="$home" PATH="$bin:/usr/bin:/bin" W6_LOG="$workspace/log" \
W6_CLAUDE_STATUS="$W6_CLAUDE_STATUS" W6_OPENCODE_STATUS="$W6_OPENCODE_STATUS" W6_PBCOPY_STATUS="$W6_PBCOPY_STATUS" OPENCODE_DEFAULT_FLAGS="$OPENCODE_DEFAULT_FLAGS" WORKSPACE_DIR="$WORKSPACE_DIR" \
"$fish_bin" --no-config -c 'set -gx fish_function_path $argv[1] $fish_function_path; cd "$argv[2]"; and $argv[3] $argv[4..-1]' \
"$functions_dir" "$repo" $argv
end
function run_w6_unavailable
env -i HOME="$home" PATH="/usr/bin:/bin" "$fish_bin" --no-config \
-c 'set -gx fish_function_path $argv[1] $fish_function_path; cd "$argv[2]"; and $argv[3] $argv[4..-1]' \
"$functions_dir" "$repo" $argv
end

printf '' > "$workspace/log"
run_w6 cc; or fail 'cc must use the current directory without arguments'
string match -q -- "claude|--dangerously-skip-permissions|cwd=$repo|stdin=" (string collect <"$workspace/log"); or fail 'cc default command order'
printf '' > "$workspace/log"
run_w6 cc "$target"; or fail 'cc must succeed'
set -l log (string collect <"$workspace/log")
string match -q -- "claude|--dangerously-skip-permissions|cwd=$target|stdin=" "$log"; or fail 'cc target command order'

printf '' > "$workspace/log"
run_w6 oc "$target"; or fail 'oc must succeed without defaults'
string match -q -- "opencode|--auto|cwd=$target" (string collect <"$workspace/log"); or fail 'oc default command order'
printf '' > "$workspace/log"
set -gx OPENCODE_DEFAULT_FLAGS (printf '%s\t%s\n%s' --model test --plain | string collect --no-trim-newlines)
run_w6 oc "$target"; or fail 'oc must tokenize shell whitespace in defaults'
string match -q -- "opencode|--auto --model test --plain|cwd=$target" (string collect <"$workspace/log"); or fail 'oc tokenizes defaults after one auto flag'
set -gx OPENCODE_DEFAULT_FLAGS '--model test --plain'
printf '' > "$workspace/log"
run_w6 ocb "$target"; or fail 'ocb must succeed'
string match -q -- "opencode|--auto --model test --plain|cwd=$target" (string collect <"$workspace/log"); or fail 'ocb forwards defaults after one auto flag'
set -e OPENCODE_DEFAULT_FLAGS
set -gx W6_OPENCODE_STATUS 24
run_w6 oc "$target" >/dev/null 2>&1; and fail 'opencode status must propagate'
set -e W6_OPENCODE_STATUS

printf '' > "$workspace/log"
run_w6 ccx 'initial context with spaces' "$target"; or fail 'ccx must succeed'
contains (string collect <"$workspace/log") "claude||cwd=$target|stdin=initial context with spaces" 'ccx forwards context through stdin'
if run_w6 ccx >"$workspace/ccx.out" 2>&1
fail 'ccx without context must fail'
end
contains (string collect <"$workspace/ccx.out") 'Uso: ccx' 'ccx usage'

if run_w6 cc "$workspace/missing" >"$workspace/missing.out" 2>&1
fail 'missing agent target must fail'
end
contains (string collect <"$workspace/missing.out") 'Directorio no encontrado' 'missing target error'
set -gx W6_CLAUDE_STATUS 23
run_w6 cc "$target" >/dev/null 2>&1; and fail 'claude status must propagate'
set -e W6_CLAUDE_STATUS
set -gx W6_CLAUDE_STATUS 25
run_w6 ccx context "$target" >/dev/null 2>&1; and fail 'ccx status must propagate'
set -e W6_CLAUDE_STATUS
if run_w6_unavailable cc "$target" >"$workspace/unavailable.out" 2>&1
fail 'unavailable claude must fail'
end
contains (string collect <"$workspace/unavailable.out") 'claude' 'unavailable claude identifies command'

set -gx WORKSPACE_DIR ''
set -l ccd_default (run_w6 ccd)
contains "$ccd_default" "Navegando a: $home/dev" 'ccd default directory'
set -gx WORKSPACE_DIR "$workspace_dir"
set -l ccd_output (run_w6 ccd 'sub dir')
contains "$ccd_output" "Navegando a: $workspace_dir/sub dir" 'ccd uses workspace override'
source "$functions_dir/ccd.fish"
cd "$repo"
ccd 'sub dir' >/dev/null; or fail 'ccd must change the invoking Fish process directory'
test "$PWD" = "$workspace_dir/sub dir"; or fail 'ccd persists PWD in the invoking Fish process'
if run_w6 ccd missing >"$workspace/ccd.out" 2>&1
fail 'ccd missing directory must fail'
end
contains (string collect <"$workspace/ccd.out") 'Directorio no encontrado' 'ccd validates target'
set -e WORKSPACE_DIR

printf '' > "$workspace/log"
run_w6 ccclip "$target/code file.fish" -n; or fail 'ccclip must copy content'
set log (string collect <"$workspace/log")
contains "$log" "pbcopy|```fish" 'ccclip fences extension'
contains "$log" "// File: $target/code file.fish" 'ccclip preserves spaced path'
contains "$log" '1 alpha' 'ccclip includes line numbers'
printf '' > "$workspace/log"
run_w6 ccclip "$target/code file.fish"; or fail 'ccclip must copy newline-terminated content'
set log (string collect <"$workspace/log")
set -l expected_fence (string join \n alpha '```' | string collect)
contains "$log" "$expected_fence" 'ccclip trims trailing linefeeds before the closing fence'
printf '' > "$workspace/log"
run_w6 ccclip "$target/empty.txt"; or fail 'ccclip must copy empty files'
printf '' > "$workspace/log"
run_w6 ccclip "$workspace/missing"; or fail 'ccclip empty context preserves successful clipboard status'
contains (string collect <"$workspace/log") 'pbcopy|' 'ccclip hands empty context to clipboard'
set -gx W6_PBCOPY_STATUS 31
run_w6 ccclip "$target/other.txt" >/dev/null 2>&1; and fail 'clipboard failure must propagate'
set -e W6_PBCOPY_STATUS
if run_w6 ccclip >"$workspace/clip.out" 2>&1
fail 'ccclip without files must fail'
end
contains (string collect <"$workspace/clip.out") 'Uso: ccclip' 'ccclip usage'

for file in "$functions_dir/_cortex_resolve_target.fish" "$functions_dir/_cortex_run_agent.fish" "$functions_dir/ocb.fish" "$functions_dir/ccx.fish" "$functions_dir/ccd.fish" "$functions_dir/ccclip.fish"
set -l source (string collect < "$file")
string match -rqi -- '(dangerously-skip-permissions|bypass-permissions|skip-permissions|auto-approv|--yes)' "$source"; and fail "unexpected unsafe W6 source: $file"
end

printf 'PASS: Fish W6 agent helpers\n'
8 changes: 8 additions & 0 deletions nix/home.nix
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,13 @@
"fish/functions/last.fish".source = ../fish/functions/last.fish;
"fish/functions/ssd.fish".source = ../fish/functions/ssd.fish;
"fish/functions/imgclip.fish".source = ../fish/functions/imgclip.fish;
"fish/functions/_cortex_resolve_target.fish".source = ../fish/functions/_cortex_resolve_target.fish;
"fish/functions/_cortex_run_agent.fish".source = ../fish/functions/_cortex_run_agent.fish;
"fish/functions/cc.fish".source = ../fish/functions/cc.fish;
"fish/functions/oc.fish".source = ../fish/functions/oc.fish;
"fish/functions/ocb.fish".source = ../fish/functions/ocb.fish;
"fish/functions/ccx.fish".source = ../fish/functions/ccx.fish;
"fish/functions/ccd.fish".source = ../fish/functions/ccd.fish;
"fish/functions/ccclip.fish".source = ../fish/functions/ccclip.fish;
};
}
Loading