Skip to content
Merged
59 changes: 15 additions & 44 deletions install
Original file line number Diff line number Diff line change
Expand Up @@ -96,59 +96,30 @@ version="${version#v}"
filename="sentry-${os}-${arch}${suffix}"
url="https://github.com/getsentry/cli/releases/download/${version}/${filename}"

# Determine install directory
# Priority:
# 1. SENTRY_INSTALL_DIR environment variable (if set and writable)
# 2. ~/.local/bin (if exists AND in $PATH)
# 3. ~/bin (if exists AND in $PATH)
# 4. ~/.sentry/bin (fallback, setup command will handle PATH)
install_dir=""

if [[ -n "${SENTRY_INSTALL_DIR:-}" ]]; then
install_dir="$SENTRY_INSTALL_DIR"
if [[ ! -d "$install_dir" ]]; then
mkdir -p "$install_dir" 2>/dev/null || true
fi
if [[ ! -w "$install_dir" ]]; then
echo -e "${RED}Error: Cannot write to $install_dir${NC}"
echo -e "${MUTED}Try running with sudo or choose a different directory.${NC}"
exit 1
fi
elif [[ -d "$HOME/.local/bin" ]] && echo "$PATH" | tr ':' '\n' | grep -Fxq "$HOME/.local/bin"; then
install_dir="$HOME/.local/bin"
elif [[ -d "$HOME/bin" ]] && echo "$PATH" | tr ':' '\n' | grep -Fxq "$HOME/bin"; then
install_dir="$HOME/bin"
else
install_dir="$HOME/.sentry/bin"
fi

install_path="${install_dir}/sentry${suffix}"
# Download binary to a temp location
tmpdir="${TMPDIR:-${TMP:-${TEMP:-/tmp}}}"
tmp_binary="${tmpdir}/sentry-install-$$${suffix}"

# Create directory if needed
mkdir -p "$install_dir"
# Clean up temp binary on failure (setup handles cleanup on success)
trap 'rm -f "$tmp_binary"' EXIT

# Download binary
echo -e "${MUTED}Downloading sentry v${version}...${NC}"
curl -fsSL --progress-bar "$url" -o "$install_path"
chmod +x "$install_path"
curl -fsSL --progress-bar "$url" -o "$tmp_binary"
chmod +x "$tmp_binary"

# Run setup command to configure PATH, completions, and record install info
setup_args="--method curl"
# Delegate installation and configuration to the binary itself.
# setup --install handles: directory selection, binary placement, PATH,
# completions, agent skills, and the welcome message.
setup_args="--install --method curl"
if [[ "$no_modify_path" == "true" ]]; then
setup_args="$setup_args --no-modify-path"
fi
if [[ "$no_completions" == "true" ]]; then
setup_args="$setup_args --no-completions"
fi

# Remove trap — setup will handle temp cleanup on success
trap - EXIT

# shellcheck disable=SC2086
"$install_path" cli setup $setup_args 2>/dev/null || true

# Success message
echo ""
echo -e "Installed ${NC}sentry v${version}${MUTED} to ${NC}${install_path}"
echo ""
echo -e "${MUTED}Get started:${NC}"
echo " sentry --help"
echo ""
echo -e "${MUTED}https://cli.sentry.dev${NC}"
"$tmp_binary" cli setup $setup_args
1 change: 1 addition & 0 deletions plugins/sentry-cli/skills/sentry-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ Diagnose and repair CLI database issues
Configure shell integration

**Flags:**
- `--install - Install the binary from a temp location to the system path`
- `--method <value> - Installation method (curl, npm, pnpm, bun, yarn)`
- `--no-modify-path - Skip PATH modification`
- `--no-completions - Skip shell completion installation`
Expand Down
4 changes: 2 additions & 2 deletions src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { AuthError, formatError, getExitCode } from "./lib/errors.js";
import { error } from "./lib/formatters/colors.js";
import { runInteractiveLogin } from "./lib/interactive-login.js";
import { withTelemetry } from "./lib/telemetry.js";
import { cleanupOldBinary } from "./lib/upgrade.js";
import { startCleanupOldBinary } from "./lib/upgrade.js";
import {
abortPendingVersionCheck,
getUpdateNotification,
Expand Down Expand Up @@ -71,7 +71,7 @@ async function executeWithAutoAuth(args: string[]): Promise<void> {

async function main(): Promise<void> {
// Clean up old binary from previous Windows upgrade (no-op if file doesn't exist)
cleanupOldBinary();
startCleanupOldBinary();

const args = process.argv.slice(2);
const suppressNotification = shouldSuppressNotification(args);
Expand Down
97 changes: 92 additions & 5 deletions src/commands/cli/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
* sentry cli setup
*
* Configure shell integration: PATH, completions, and install metadata.
* This command is called by the install script and can also be run manually.
* With --install, also handles binary placement (used by the install script
* and the upgrade command for curl-based installs).
*/

import { unlinkSync } from "node:fs";
import { dirname } from "node:path";
import type { SentryContext } from "../../context.js";
import { installAgentSkills } from "../../lib/agent-skills.js";
import { determineInstallDir, installBinary } from "../../lib/binary.js";
import { buildCommand } from "../../lib/command.js";
import { installCompletions } from "../../lib/completions.js";
import { CLI_VERSION } from "../../lib/constants.js";
Expand All @@ -26,6 +29,7 @@ import {
} from "../../lib/upgrade.js";

type SetupFlags = {
readonly install: boolean;
readonly method?: InstallationMethod;
readonly "no-modify-path": boolean;
readonly "no-completions": boolean;
Expand All @@ -35,6 +39,43 @@ type SetupFlags = {

type Logger = (msg: string) => void;

/**
* Handle binary installation from a temp location.
*
* Determines the target install directory, copies the running binary
* (which is at a temp path) to the install location, then cleans up
* the temp binary on Posix (safe because the running process's inode
* stays alive until exit).
*
* On Windows, the temp binary cannot be deleted while running. It will
* be cleaned up by the OS when the temp directory is purged.
*
* @returns The absolute path of the installed binary and its directory
*/
async function handleInstall(
execPath: string,
homeDir: string,
env: NodeJS.ProcessEnv,
log: Logger
): Promise<{ binaryPath: string; binaryDir: string }> {
const installDir = determineInstallDir(homeDir, env);
const binaryPath = await installBinary(execPath, installDir);
const binaryDir = dirname(binaryPath);

log(`Binary: Installed to ${binaryPath}`);

// Clean up temp binary (Posix only — the inode stays alive for the running process)
if (process.platform !== "win32") {
try {
unlinkSync(execPath);
} catch {
// Ignore — temp file may already be gone or we lack permissions
}
}

return { binaryPath, binaryDir };
}

/**
* Handle PATH modification for a directory.
*/
Expand Down Expand Up @@ -118,6 +159,24 @@ async function handleAgentSkills(homeDir: string, log: Logger): Promise<void> {
}
}

/**
* Print a rich welcome message after fresh install.
*/
function printWelcomeMessage(
log: Logger,
version: string,
binaryPath: string
): void {
log("");
log(`Installed sentry v${version} to ${binaryPath}`);
log("");
log("Get started:");
log(" sentry login Authenticate with Sentry");
log(" sentry --help See all available commands");
log("");
log("https://cli.sentry.dev");
}

export const setupCommand = buildCommand({
docs: {
brief: "Configure shell integration",
Expand All @@ -127,17 +186,25 @@ export const setupCommand = buildCommand({
"- Installs shell completions (bash, zsh, fish)\n" +
"- Installs agent skills for AI coding assistants (e.g., Claude Code)\n" +
"- Records installation metadata for upgrades\n\n" +
"With --install, also handles binary placement from a temporary\n" +
"download location (used by the install script and upgrade command).\n\n" +
"This command is called automatically by the install script,\n" +
"but can also be run manually after downloading the binary.\n\n" +
"Examples:\n" +
" sentry cli setup # Auto-detect and configure\n" +
" sentry cli setup --method curl # Record install method\n" +
" sentry cli setup --install # Place binary and configure\n" +
" sentry cli setup --no-modify-path # Skip PATH modification\n" +
" sentry cli setup --no-completions # Skip shell completions\n" +
" sentry cli setup --no-agent-skills # Skip agent skill installation",
},
parameters: {
flags: {
install: {
kind: "boolean",
brief: "Install the binary from a temp location to the system path",
default: false,
},
method: {
kind: "parsed",
parse: parseInstallationMethod,
Expand Down Expand Up @@ -177,8 +244,21 @@ export const setupCommand = buildCommand({
}
};

const binaryPath = process.execPath;
const binaryDir = dirname(binaryPath);
let binaryPath = process.execPath;
let binaryDir = dirname(binaryPath);

// 0. Install binary from temp location (when --install is set)
if (flags.install) {
const result = await handleInstall(
process.execPath,
homeDir,
process.env,
log
);
binaryPath = result.binaryPath;
binaryDir = result.binaryDir;
}

const shell = detectShell(
process.env.SHELL,
homeDir,
Expand All @@ -192,7 +272,9 @@ export const setupCommand = buildCommand({
path: binaryPath,
version: CLI_VERSION,
});
log(`Recorded installation method: ${flags.method}`);
if (!flags.install) {
log(`Recorded installation method: ${flags.method}`);
}
}

// 2. Handle PATH modification
Expand All @@ -210,8 +292,13 @@ export const setupCommand = buildCommand({
await handleAgentSkills(homeDir, log);
}

// 5. Print welcome message (fresh install) or completion message
if (!flags.quiet) {
stdout.write("\nSetup complete!\n");
if (flags.install) {
printWelcomeMessage(log, CLI_VERSION, binaryPath);
} else {
stdout.write("\nSetup complete!\n");
}
}
},
});
Loading
Loading