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
79 changes: 56 additions & 23 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,40 +32,73 @@ jobs:
run: |
brew install zsh fish

- name: Setup shell environment
run: |
mkdir -p ~/.config/fish
mkdir -p ~/.zsh
touch ~/.bashrc

# Each job installs from its shell the way a user whose login shell it is
# does: `bash ./install.bash` run from that shell, with $SHELL naming it
# (the runner's login shell is bash). The home directory is the runner's
# own, not seeded with config files: macOS has no ~/.zshrc, like a fresh
# account. macOS has brew, so it takes the Homebrew path; Linux takes the
# manual one.
- name: Test install script in Zsh
if: matrix.shell == 'zsh'
run: |
bash ./install.bash
run: SHELL=$(command -v zsh) bash ./install.bash
shell: zsh {0}
- name: Test install script in Fish
if: matrix.shell == 'fish'
run: |
bash ./install.bash
run: SHELL=(command -v fish) bash ./install.bash
shell: fish {0}
- name: Test install script in Bash
if: matrix.shell == 'bash'
run: SHELL=$(command -v bash) bash ./install.bash
shell: bash {0}

- name: Verify installation
env:
LOGIN_SHELL: ${{ matrix.shell }}
run: |
if [ "${{ matrix.shell }}" = "zsh" ]; then
zsh -c "source ~/.zshrc && echo 'Zsh configuration loaded'"
elif [ "${{ matrix.shell }}" = "fish" ]; then
fish -c "source ~/.config/fish/config.fish && echo 'Fish configuration loaded'"
elif [ "${{ matrix.shell }}" = "bash" ]; then
bash -c "source ~/.bashrc && echo 'Bash configuration loaded'"
fi
[ -f ~/.shelltime/hooks/bash-preexec.sh ] || (echo "bash-preexec.sh not found in ~/.shelltime/hooks!" && exit 1)
ls ~/.shelltime
if command -v brew >/dev/null 2>&1 && brew list shelltime >/dev/null 2>&1; then
which shelltime || (echo "shelltime not in PATH via Homebrew!" && exit 1)
set -euo pipefail
hooks="$HOME/.shelltime/hooks"
case "$LOGIN_SHELL" in
zsh) rc="$HOME/.zshrc" ;;
bash) rc="$HOME/.bashrc" ;;
fish) rc="$HOME/.config/fish/config.fish" ;;
esac
ls -la "$HOME/.shelltime" "$hooks"

# Both paths download every shell's hook, and the bash-preexec.sh bash.bash sources
for f in zsh.zsh bash.bash bash-preexec.sh fish.fish; do
[ -s "$hooks/$f" ] || { echo "$hooks/$f is missing or empty"; exit 1; }
done
[ -d "$HOME/.shelltime/daemon" ] || { echo "$HOME/.shelltime/daemon is missing"; exit 1; }

# The login shell's config exists and sources its hook
[ -f "$rc" ] || { echo "$rc was not created"; exit 1; }
grep -qxF "source $hooks/$LOGIN_SHELL.$LOGIN_SHELL" "$rc" || { echo "$rc does not source the $LOGIN_SHELL hook:"; cat "$rc"; exit 1; }

if [ "$RUNNER_OS" = macOS ]; then
# Homebrew: the cask links both binaries into brew's bin dir, and
# nothing is left in ~/.shelltime/bin to shadow them
brew list --cask shelltime
bin="$(brew --prefix)/bin"
[ ! -e "$HOME/.shelltime/bin/shelltime" ] || { echo "$HOME/.shelltime/bin/shelltime shadows the cask"; exit 1; }
else
ls ~/.shelltime/bin
# Manual: both binaries go in ~/.shelltime/bin, which the config puts on PATH
bin="$HOME/.shelltime/bin"
grep -qF '$HOME/.shelltime/bin' "$rc" || { echo "$rc does not put ~/.shelltime/bin on PATH:"; cat "$rc"; exit 1; }
fi
ls ~/.shelltime/hooks
[ -x "$bin/shelltime" ] || { echo "$bin/shelltime is missing"; exit 1; }
[ -x "$bin/shelltime-daemon" ] || { echo "$bin/shelltime-daemon is missing"; exit 1; }
"$bin/shelltime" --version

# A new shell (one reading the config, as a new terminal does) has the
# hook loaded and runs this shelltime. The hook may print on startup,
# so the path is the last line.
case "$LOGIN_SHELL" in
zsh) found=$(zsh -i -c '(( $+functions[preexec] && $+functions[precmd] )) && command -v shelltime' | tail -n 1) ;;
bash) found=$(bash -i -c '[[ " ${preexec_functions[*]} " == *" preexec_invoke_cmd "* && " ${precmd_functions[*]} " == *" precmd_invoke_cmd "* ]] && command -v shelltime' | tail -n 1) ;;
# fish reads config.fish in every shell, interactive or not
fish) found=$(fish -c 'functions -q fish_preexec fish_postexec; and command -v shelltime' | tail -n 1) ;;
esac || true
[ "$found" = "$bin/shelltime" ] || { echo "a new $LOGIN_SHELL shell did not load the hook and run $bin/shelltime (last line: '$found')"; exit 1; }

test-cloud:
runs-on: ${{ matrix.os }}
Expand Down
11 changes: 6 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ Run the CI workflow locally or on GitHub Actions:
bash ./install.bash
```

The CI verifies:
1. Installation script runs successfully across shells
2. Shell configuration files are properly sourced
3. Hook files are placed in `~/.shelltime/hooks/`
Each `test-shells` job runs `bash ./install.bash` from its shell (zsh, fish or bash) with `$SHELL` naming it, as for a user whose login shell it is, in the runner's own home directory (macOS has no `~/.zshrc`, like a fresh account). macOS takes the Homebrew path, Linux the manual one. The CI verifies:
1. Every hook and `bash-preexec.sh` are in `~/.shelltime/hooks/`, and `~/.shelltime/daemon/` exists
2. The login shell's config file exists and sources its hook (`install.bash` creates it for the login shell, and `config.fish` whenever fish is installed)
3. Homebrew path: the cask is installed and links `shelltime` and `shelltime-daemon` into brew's bin, with no `~/.shelltime/bin/shelltime` shadowing it. Manual path: both binaries are in `~/.shelltime/bin`, which the config puts on `PATH`
4. A new interactive shell loads the hook's functions and runs that `shelltime` (bash needs `-i`: Ubuntu's default `~/.bashrc` returns early in a non-interactive shell)

The `test-cloud` job checks that `cloud.bash` installs nothing without `CLAUDE_CODE_REMOTE=true`, then runs it twice with it on x86_64 and arm64 Linux and checks the binaries, the OTEL settings, that the SessionStart hook is registered exactly once, and that the hook prints nothing, signs in with `proxy-injected` (or `SHELLTIME_TOKEN` when set) and starts one daemon.

Expand All @@ -44,7 +45,7 @@ Bash requires the external `bash-preexec.sh` library (downloaded during installa

## Installation Paths

- CLI binary: `~/.shelltime/bin/shelltime`
- CLI binary: `~/.shelltime/bin/shelltime` (manual install), or brew's bin dir when macOS installs the `shelltime/tap/shelltime` cask
- Hook scripts: `~/.shelltime/hooks/`
- Daemon directory: `~/.shelltime/daemon/`

Expand Down
29 changes: 22 additions & 7 deletions install.bash
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,27 @@ if [[ "$OS" == "Darwin" ]] && command_exists brew; then
fi
fi

# The shell config files the PATH entry and the hooks go into, created for both
# install paths when they don't exist yet. The login shell ($SHELL, also under
# `curl | bash`) always gets one: a fresh macOS account runs zsh with no
# ~/.zshrc, and without it the shell would load no hook and the reload step
# printed at the end would fail. config.fish is created whenever fish is
# installed.
ensure_config_file() {
local file="$1"
if [ ! -f "$file" ]; then
mkdir -p "$(dirname "$file")" && touch "$file"
fi
}

case "$(basename "${SHELL:-}")" in
zsh) ensure_config_file "$HOME/.zshrc" ;;
bash) ensure_config_file "$HOME/.bashrc" ;;
esac
if command_exists fish; then
ensure_config_file "$HOME/.config/fish/config.fish"
fi

if [ "$BREW_INSTALLED" = false ]; then

CLI_FILE_NAME="https://github.com/shelltime/cli/releases/latest/download/cli_"
Expand Down Expand Up @@ -189,13 +210,7 @@ if [[ "$OS" == "Darwin" ]] || [[ "$OS" == "Linux" ]]; then
fi

# For Fish
if command_exists fish; then
if [ ! -d "$HOME/.config/fish" ]; then
mkdir -p "$HOME/.config/fish"
fi
if [ ! -f "$HOME/.config/fish/config.fish" ]; then
touch "$HOME/.config/fish/config.fish"
fi
if [ -f "$HOME/.config/fish/config.fish" ]; then
if ! grep -q '$HOME/.shelltime/bin' "$HOME/.config/fish/config.fish"; then
echo '# Added by shelltime' >> "$HOME/.config/fish/config.fish"
echo 'fish_add_path $HOME/.shelltime/bin' >> "$HOME/.config/fish/config.fish"
Expand Down
Loading