← back to keywaysh__cli

Function bodies 191 total

All specs Real LLM only Function bodies
env.CalculatePushDiff function · go · L18-L45 (28 LOC)
internal/env/diff.go
func CalculatePushDiff(local, vault map[string]string) *PushDiff {
	diff := &PushDiff{}

	// Check local secrets against vault
	for key, localVal := range local {
		if vaultVal, exists := vault[key]; exists {
			if localVal != vaultVal {
				diff.Changed = append(diff.Changed, key)
			}
		} else {
			diff.Added = append(diff.Added, key)
		}
	}

	// Find vault-only secrets (will be removed)
	for key := range vault {
		if _, exists := local[key]; !exists {
			diff.Removed = append(diff.Removed, key)
		}
	}

	// Sort for deterministic output
	sort.Strings(diff.Added)
	sort.Strings(diff.Changed)
	sort.Strings(diff.Removed)

	return diff
}
env.CalculatePullDiff function · go · L61-L91 (31 LOC)
internal/env/diff.go
func CalculatePullDiff(local, vault map[string]string) *PullDiff {
	diff := &PullDiff{}

	// Check vault secrets against local
	for key, vaultVal := range vault {
		if localVal, exists := local[key]; exists {
			if localVal != vaultVal {
				diff.Changed = append(diff.Changed, key)
			} else {
				diff.Unchanged = append(diff.Unchanged, key)
			}
		} else {
			diff.Added = append(diff.Added, key)
		}
	}

	// Find local-only secrets
	for key := range local {
		if _, exists := vault[key]; !exists {
			diff.LocalOnly = append(diff.LocalOnly, key)
		}
	}

	// Sort for deterministic output
	sort.Strings(diff.Added)
	sort.Strings(diff.Changed)
	sort.Strings(diff.LocalOnly)
	sort.Strings(diff.Unchanged)

	return diff
}
env.Discover function · go · L17-L41 (25 LOC)
internal/env/discover.go
func Discover() []Candidate {
	entries, err := os.ReadDir(".")
	if err != nil {
		return nil
	}

	// Template files to exclude (not real secrets)
	excludeFiles := map[string]bool{
		".env.example":  true, // Template files
		".env.sample":   true,
		".env.template": true,
	}

	var candidates []Candidate
	for _, entry := range entries {
		name := entry.Name()
		if strings.HasPrefix(name, ".env") && !excludeFiles[name] && !entry.IsDir() {
			candidates = append(candidates, Candidate{
				File: name,
				Env:  DeriveEnvFromFile(name),
			})
		}
	}
	return candidates
}
env.DeriveEnvFromFile function · go · L48-L57 (10 LOC)
internal/env/discover.go
func DeriveEnvFromFile(file string) string {
	base := filepath.Base(file)
	if base == ".env" {
		return "development"
	}
	if strings.HasPrefix(base, ".env.") {
		return strings.TrimPrefix(base, ".env.")
	}
	return "development"
}
env.Parse function · go · L11-L38 (28 LOC)
internal/env/parser.go
func Parse(content string) map[string]string {
	result := make(map[string]string)
	for _, line := range strings.Split(content, "\n") {
		line = strings.TrimSpace(line)
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		idx := strings.Index(line, "=")
		if idx == -1 {
			continue
		}
		key := strings.TrimSpace(line[:idx])
		value := line[idx+1:]

		// Remove surrounding quotes
		if len(value) >= 2 {
			if (value[0] == '"' && value[len(value)-1] == '"') ||
				(value[0] == '\'' && value[len(value)-1] == '\'') {
				value = value[1 : len(value)-1]
			}
		}

		if key != "" {
			result[key] = value
		}
	}
	return result
}
env.CountLines function · go · L41-L50 (10 LOC)
internal/env/parser.go
func CountLines(content string) int {
	count := 0
	for _, line := range strings.Split(content, "\n") {
		line = strings.TrimSpace(line)
		if line != "" && !strings.HasPrefix(line, "#") {
			count++
		}
	}
	return count
}
env.Merge function · go · L54-L79 (26 LOC)
internal/env/parser.go
func Merge(vaultContent string, local, vault map[string]string) string {
	// Start with vault content
	result := strings.TrimRight(vaultContent, "\n")

	// Find local-only secrets and collect keys for sorting
	var localOnlyKeys []string
	for key := range local {
		if _, exists := vault[key]; !exists {
			localOnlyKeys = append(localOnlyKeys, key)
		}
	}

	if len(localOnlyKeys) > 0 {
		// Sort keys for deterministic output
		sort.Strings(localOnlyKeys)

		result += "\n\n# Local variables (not in vault)\n"
		for _, key := range localOnlyKeys {
			result += key + "=" + local[key] + "\n"
		}
	} else {
		result += "\n"
	}

	return result
}
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
git.IsGitRepository function · go · L20-L25 (6 LOC)
internal/git/repo.go
func IsGitRepository() bool {
	cmd := exec.Command("git", "rev-parse", "--is-inside-work-tree")
	cmd.Stderr = nil
	cmd.Stdout = nil
	return cmd.Run() == nil
}
git.DetectRepo function · go · L28-L42 (15 LOC)
internal/git/repo.go
func DetectRepo() (string, error) {
	if !IsGitRepository() {
		return "", fmt.Errorf("not in a git repository")
	}

	cmd := exec.Command("git", "remote", "get-url", "origin")
	cmd.Stderr = nil
	output, err := cmd.Output()
	if err != nil {
		return "", fmt.Errorf("no remote origin configured")
	}

	remoteURL := strings.TrimSpace(string(output))
	return ParseGitHubURL(remoteURL)
}
git.ParseGitHubURL function · go · L45-L57 (13 LOC)
internal/git/repo.go
func ParseGitHubURL(url string) (string, error) {
	// Try SSH format
	if matches := sshRegex.FindStringSubmatch(url); matches != nil {
		return fmt.Sprintf("%s/%s", matches[1], matches[2]), nil
	}

	// Try HTTPS format
	if matches := httpsRegex.FindStringSubmatch(url); matches != nil {
		return fmt.Sprintf("%s/%s", matches[1], matches[2]), nil
	}

	return "", fmt.Errorf("not a GitHub URL: %s", url)
}
git.GetGitRoot function · go · L60-L68 (9 LOC)
internal/git/repo.go
func GetGitRoot() (string, error) {
	cmd := exec.Command("git", "rev-parse", "--show-toplevel")
	cmd.Stderr = nil
	output, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(output)), nil
}
git.CheckEnvGitignore function · go · L71-L99 (29 LOC)
internal/git/repo.go
func CheckEnvGitignore() bool {
	gitRoot, err := GetGitRoot()
	if err != nil {
		return true // Not a git repo, don't warn
	}

	gitignorePath := filepath.Join(gitRoot, ".gitignore")
	content, err := os.ReadFile(gitignorePath)
	if err != nil {
		return false // No .gitignore file
	}

	lines := strings.Split(string(content), "\n")
	patterns := []string{".env", ".env*", ".env.*", "*.env"}

	for _, line := range lines {
		line = strings.TrimSpace(line)
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		for _, pattern := range patterns {
			if line == pattern {
				return true
			}
		}
	}

	return false
}
git.AddEnvToGitignore function · go · L102-L120 (19 LOC)
internal/git/repo.go
func AddEnvToGitignore() error {
	gitRoot, err := GetGitRoot()
	if err != nil {
		return err
	}

	gitignorePath := filepath.Join(gitRoot, ".gitignore")

	// Read existing content
	content, _ := os.ReadFile(gitignorePath)

	newContent := string(content)
	if len(newContent) > 0 && !strings.HasSuffix(newContent, "\n") {
		newContent += "\n"
	}
	newContent += ".env*\n"

	return os.WriteFile(gitignorePath, []byte(newContent), 0644)
}
git.DetectMonorepo function · go · L130-L165 (36 LOC)
internal/git/repo.go
func DetectMonorepo() MonorepoInfo {
	gitRoot, err := GetGitRoot()
	if err != nil {
		return MonorepoInfo{IsMonorepo: false}
	}

	// Check for monorepo tool config files (in order of popularity)
	monorepoIndicators := []struct {
		file string
		tool string
	}{
		{"turbo.json", "Turborepo"},
		{"nx.json", "Nx"},
		{"pnpm-workspace.yaml", "pnpm workspaces"},
		{"lerna.json", "Lerna"},
		{"rush.json", "Rush"},
	}

	for _, indicator := range monorepoIndicators {
		if _, err := os.Stat(filepath.Join(gitRoot, indicator.file)); err == nil {
			return MonorepoInfo{IsMonorepo: true, Tool: indicator.tool}
		}
	}

	// Check package.json for workspaces field (npm/yarn workspaces)
	packageJSONPath := filepath.Join(gitRoot, "package.json")
	if content, err := os.ReadFile(packageJSONPath); err == nil {
		contentStr := string(content)
		// Simple check for "workspaces" field in package.json
		if strings.Contains(contentStr, `"workspaces"`) {
			return MonorepoInfo{IsMonorepo: true, Tool: "npm/yarn wor
injector.RunCommand function · go · L15-L67 (53 LOC)
internal/injector/injector.go
func RunCommand(command string, args []string, secrets map[string]string) error {
	// Prepare the command
	cmd := exec.Command(command, args...)

	// Connect standard input/output
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	// Build the environment
	// Start with current environment
	currentEnv := os.Environ()
	newEnv := make([]string, 0, len(currentEnv)+len(secrets))
	newEnv = append(newEnv, currentEnv...)

	// Append secrets
	for k, v := range secrets {
		newEnv = append(newEnv, fmt.Sprintf("%s=%s", k, v))
	}
	cmd.Env = newEnv

	// Handle signals
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, signals...)

	// Start the command
	if err := cmd.Start(); err != nil {
		return fmt.Errorf("failed to start command: %w", err)
	}

	// Forward signals to the child process
	go func() {
		for sig := range sigs {
			if cmd.Process != nil {
				_ = cmd.Process.Signal(sig)
			}
		}
	}()

	// Wait for the command to finish
	err := cmd.Wait()

	// Handle exit code
	
All rows scored by the Repobility analyzer (https://repobility.com)
ui.Confirm function · go · L92-L104 (13 LOC)
internal/ui/ui.go
func Confirm(message string, defaultValue bool) (bool, error) {
	result := defaultValue
	err := huh.NewConfirm().
		Title(message).
		Value(&result).
		Affirmative("Yes").
		Negative("No").
		Run()
	if err != nil {
		return defaultValue, err
	}
	return result, nil
}
ui.Select function · go · L107-L120 (14 LOC)
internal/ui/ui.go
func Select(message string, options []string) (string, error) {
	var result string
	opts := make([]huh.Option[string], len(options))
	for i, opt := range options {
		opts[i] = huh.NewOption(opt, opt)
	}

	err := huh.NewSelect[string]().
		Title(message).
		Options(opts...).
		Value(&result).
		Run()
	return result, err
}
ui.Password function · go · L123-L131 (9 LOC)
internal/ui/ui.go
func Password(message string) (string, error) {
	var result string
	err := huh.NewInput().
		Title(message).
		EchoMode(huh.EchoModePassword).
		Value(&result).
		Run()
	return result, err
}
ui.Spin function · go · L134-L146 (13 LOC)
internal/ui/ui.go
func Spin(message string, fn func() error) error {
	var err error
	spinErr := spinner.New().
		Title(message).
		Action(func() {
			err = fn()
		}).
		Run()
	if spinErr != nil {
		return spinErr
	}
	return err
}
ui.IsInteractive function · go · L149-L160 (12 LOC)
internal/ui/ui.go
func IsInteractive() bool {
	// Check CI environment
	if ci := os.Getenv("CI"); ci == "true" || ci == "1" {
		return false
	}
	// Check if stdin is a terminal
	fi, err := os.Stdin.Stat()
	if err != nil {
		return false
	}
	return (fi.Mode() & os.ModeCharDevice) != 0
}
version.getCacheFilePath function · go · L18-L24 (7 LOC)
internal/version/cache.go
func getCacheFilePath() (string, error) {
	home, err := os.UserHomeDir()
	if err != nil {
		return "", err
	}
	return filepath.Join(home, ".config", "keyway", "update-check.json"), nil
}
version.LoadCache function · go · L27-L44 (18 LOC)
internal/version/cache.go
func LoadCache() (*CacheData, error) {
	path, err := getCacheFilePath()
	if err != nil {
		return nil, err
	}

	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}

	var cache CacheData
	if err := json.Unmarshal(data, &cache); err != nil {
		return nil, err
	}

	return &cache, nil
}
version.SaveCache function · go · L47-L64 (18 LOC)
internal/version/cache.go
func SaveCache(cache *CacheData) error {
	path, err := getCacheFilePath()
	if err != nil {
		return err
	}

	// Ensure directory exists
	if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
		return err
	}

	data, err := json.MarshalIndent(cache, "", "  ")
	if err != nil {
		return err
	}

	return os.WriteFile(path, data, 0600)
}
Repobility · code-quality intelligence platform · https://repobility.com
version.DetectInstallMethod function · go · L21-L57 (37 LOC)
internal/version/detect.go
func DetectInstallMethod() InstallMethod {
	execPath, err := os.Executable()
	if err != nil {
		return InstallMethodBinary
	}

	// Resolve symlinks to get the real path
	realPath, err := filepath.EvalSymlinks(execPath)
	if err != nil {
		realPath = execPath
	}

	pathLower := strings.ToLower(realPath)

	// Check for npx (temporary cache)
	if strings.Contains(pathLower, "_npx") ||
		strings.Contains(pathLower, "npx-") {
		return InstallMethodNPX
	}

	// Check for npm global installation
	if strings.Contains(pathLower, "node_modules") ||
		strings.Contains(pathLower, "@keywaysh") {
		return InstallMethodNPM
	}

	// Check for Homebrew installation (macOS and Linux)
	if runtime.GOOS == "darwin" || runtime.GOOS == "linux" {
		if strings.Contains(pathLower, "/cellar/") ||
			strings.Contains(pathLower, "/homebrew/") ||
			strings.Contains(pathLower, "/linuxbrew/") {
			return InstallMethodHomebrew
		}
	}

	return InstallMethodBinary
}
version.FetchLatestVersion function · go · L23-L54 (32 LOC)
internal/version/github.go
func FetchLatestVersion(ctx context.Context) (string, error) {
	// Skip update checks for self-hosted instances
	if config.IsCustomAPIURL() {
		return "", fmt.Errorf("update checks disabled for self-hosted instances")
	}

	req, err := http.NewRequestWithContext(ctx, "GET", defaultGitHubReleasesURL, nil)
	if err != nil {
		return "", err
	}

	req.Header.Set("Accept", "application/vnd.github.v3+json")
	req.Header.Set("User-Agent", "keyway-cli")

	client := &http.Client{Timeout: CheckTimeout}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("GitHub API returned %d", resp.StatusCode)
	}

	var release githubRelease
	if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
		return "", err
	}

	return release.TagName, nil
}
version.CheckForUpdate function · go · L30-L73 (44 LOC)
internal/version/version.go
func CheckForUpdate(ctx context.Context, currentVersion string) *UpdateInfo {
	if IsUpdateCheckDisabled() {
		return nil
	}

	// Skip update check for self-hosted instances
	if config.IsCustomAPIURL() {
		return nil
	}

	// Skip check for dev builds
	if currentVersion == "dev" || currentVersion == "" {
		return nil
	}

	// Detect install method once
	method := DetectInstallMethod()

	// Skip check for npx (always fetches latest)
	if method == InstallMethodNPX {
		return nil
	}

	// Check cache first
	cached, err := LoadCache()
	if err == nil && cached != nil && time.Since(cached.LastCheck) < CacheDuration {
		return buildUpdateInfo(currentVersion, cached.LatestVersion, cached.InstallMethod)
	}

	// Fetch from GitHub
	latest, err := FetchLatestVersion(ctx)
	if err != nil {
		return nil // Silent failure
	}

	// Save to cache (ignore errors)
	_ = SaveCache(&CacheData{
		LastCheck:     time.Now(),
		LatestVersion: latest,
		InstallMethod: method,
	})

	return buildUpdateInfo(currentVersio
version.buildUpdateInfo function · go · L75-L87 (13 LOC)
internal/version/version.go
func buildUpdateInfo(current, latest string, method InstallMethod) *UpdateInfo {
	if !IsNewerVersion(latest, current) {
		return nil
	}

	return &UpdateInfo{
		Available:      true,
		CurrentVersion: current,
		LatestVersion:  latest,
		InstallMethod:  method,
		UpdateCommand:  GetUpdateCommand(method),
	}
}
version.IsNewerVersion function · go · L109-L138 (30 LOC)
internal/version/version.go
func IsNewerVersion(latest, current string) bool {
	latestParts := parseVersion(latest)
	currentParts := parseVersion(current)

	if len(latestParts) == 0 || len(currentParts) == 0 {
		return false
	}

	// Compare major, minor, patch
	for i := 0; i < 3; i++ {
		latestPart := 0
		currentPart := 0

		if i < len(latestParts) {
			latestPart = latestParts[i]
		}
		if i < len(currentParts) {
			currentPart = currentParts[i]
		}

		if latestPart > currentPart {
			return true
		}
		if latestPart < currentPart {
			return false
		}
	}

	return false
}
version.parseVersion function · go · L141-L162 (22 LOC)
internal/version/version.go
func parseVersion(v string) []int {
	// Strip 'v' prefix
	v = strings.TrimPrefix(v, "v")

	// Handle dirty/dev suffixes
	if idx := strings.IndexAny(v, "-+"); idx != -1 {
		v = v[:idx]
	}

	parts := strings.Split(v, ".")
	result := make([]int, 0, 3)

	for _, part := range parts {
		n, err := strconv.Atoi(part)
		if err != nil {
			break
		}
		result = append(result, n)
	}

	return result
}
getBinaryPath function · javascript · L27-L32 (6 LOC)
npm/scripts/install.js
function getBinaryPath() {
  const binDir = path.join(__dirname, "..", "bin");
  // Use keyway-bin to avoid conflict with the wrapper script
  const binaryName = process.platform === "win32" ? `${BINARY_NAME}-bin.exe` : `${BINARY_NAME}-bin`;
  return path.join(binDir, binaryName);
}
fetch function · javascript · L34-L60 (27 LOC)
npm/scripts/install.js
function fetch(url) {
  return new Promise((resolve, reject) => {
    const request = https.get(url, (response) => {
      if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
        // Follow redirect
        fetch(response.headers.location).then(resolve).catch(reject);
        return;
      }

      if (response.statusCode !== 200) {
        reject(new Error(`Failed to download: ${response.statusCode}`));
        return;
      }

      const chunks = [];
      response.on("data", (chunk) => chunks.push(chunk));
      response.on("end", () => resolve(Buffer.concat(chunks)));
      response.on("error", reject);
    });

    request.on("error", reject);
    request.setTimeout(60000, () => {
      request.destroy();
      reject(new Error("Request timeout"));
    });
  });
}
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
downloadChecksum function · javascript · L62-L71 (10 LOC)
npm/scripts/install.js
async function downloadChecksum() {
  const url = `https://github.com/${REPO}/releases/download/v${VERSION}/checksums.txt`;
  try {
    const data = await fetch(url);
    return data.toString();
  } catch (error) {
    console.warn("Warning: Could not download checksums file");
    return null;
  }
}
verifyChecksum function · javascript · L73-L90 (18 LOC)
npm/scripts/install.js
function verifyChecksum(buffer, checksums, filename) {
  if (!checksums) return true;

  const hash = crypto.createHash("sha256").update(buffer).digest("hex");
  const expectedLine = checksums.split("\n").find((line) => line.includes(filename));

  if (!expectedLine) {
    console.warn(`Warning: No checksum found for ${filename}`);
    return true;
  }

  const expectedHash = expectedLine.split(/\s+/)[0];
  if (hash !== expectedHash) {
    throw new Error(`Checksum mismatch for ${filename}\nExpected: ${expectedHash}\nGot: ${hash}`);
  }

  return true;
}
extractTarGz function · javascript · L92-L101 (10 LOC)
npm/scripts/install.js
async function extractTarGz(buffer, destDir) {
  const tmpFile = path.join(os.tmpdir(), `keyway-${Date.now()}.tar.gz`);
  fs.writeFileSync(tmpFile, buffer);

  try {
    execSync(`tar -xzf "${tmpFile}" -C "${destDir}"`, { stdio: "pipe" });
  } finally {
    fs.unlinkSync(tmpFile);
  }
}
extractZip function · javascript · L103-L120 (18 LOC)
npm/scripts/install.js
async function extractZip(buffer, destDir) {
  const tmpFile = path.join(os.tmpdir(), `keyway-${Date.now()}.zip`);
  fs.writeFileSync(tmpFile, buffer);

  try {
    // Try powershell on Windows
    if (process.platform === "win32") {
      execSync(
        `powershell -Command "Expand-Archive -Path '${tmpFile}' -DestinationPath '${destDir}' -Force"`,
        { stdio: "pipe" }
      );
    } else {
      execSync(`unzip -o "${tmpFile}" -d "${destDir}"`, { stdio: "pipe" });
    }
  } finally {
    fs.unlinkSync(tmpFile);
  }
}
install function · javascript · L122-L209 (88 LOC)
npm/scripts/install.js
async function install() {
  const platformKey = getPlatformKey();
  const target = PLATFORMS[platformKey];

  if (!target) {
    console.error(`\nUnsupported platform: ${platformKey}`);
    console.error(`Supported platforms: ${Object.keys(PLATFORMS).join(", ")}`);
    console.error(`\nYou can install manually from: https://github.com/${REPO}/releases`);
    process.exit(1);
  }

  const binDir = path.join(__dirname, "..", "bin");
  const binaryPath = getBinaryPath();

  // Check if binary already exists and is correct version
  if (fs.existsSync(binaryPath)) {
    try {
      const result = spawnSync(binaryPath, ["--version"], { encoding: "utf8" });
      if (result.stdout && result.stdout.includes(VERSION)) {
        console.log(`keyway v${VERSION} already installed`);
        return;
      }
    } catch (e) {
      // Binary exists but couldn't run, reinstall
    }
  }

  const isWindows = process.platform === "win32";
  const ext = isWindows ? "zip" : "tar.gz";
  const filename = 
getPlatform function · javascript · L26-L32 (7 LOC)
npm/scripts/postinstall.js
function getPlatform() {
  const platform = PLATFORM_MAP[process.platform];
  if (!platform) {
    throw new Error(`Unsupported platform: ${process.platform}`);
  }
  return platform;
}
getArch function · javascript · L34-L40 (7 LOC)
npm/scripts/postinstall.js
function getArch() {
  const arch = ARCH_MAP[process.arch];
  if (!arch) {
    throw new Error(`Unsupported architecture: ${process.arch}`);
  }
  return arch;
}
httpsGet function · javascript · L47-L65 (19 LOC)
npm/scripts/postinstall.js
function httpsGet(url) {
  return new Promise((resolve, reject) => {
    https
      .get(url, { headers: { "User-Agent": "keyway-npm-installer" } }, (res) => {
        if (res.statusCode === 301 || res.statusCode === 302) {
          return httpsGet(res.headers.location).then(resolve).catch(reject);
        }
        if (res.statusCode !== 200) {
          reject(new Error(`HTTP ${res.statusCode}: ${url}`));
          return;
        }
        const chunks = [];
        res.on("data", (chunk) => chunks.push(chunk));
        res.on("end", () => resolve(Buffer.concat(chunks)));
        res.on("error", reject);
      })
      .on("error", reject);
  });
}
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
downloadAndExtract function · javascript · L67-L121 (55 LOC)
npm/scripts/postinstall.js
async function downloadAndExtract(url, destPath) {
  console.log(`Downloading ${url}`);
  const data = await httpsGet(url);

  // Create bin directory if it doesn't exist
  if (!fs.existsSync(BIN_DIR)) {
    fs.mkdirSync(BIN_DIR, { recursive: true });
  }

  if (url.endsWith(".tar.gz")) {
    // Extract tar.gz
    const tmpDir = path.join(__dirname, "..", "tmp");
    if (!fs.existsSync(tmpDir)) {
      fs.mkdirSync(tmpDir, { recursive: true });
    }

    const tarPath = path.join(tmpDir, "keyway.tar.gz");
    fs.writeFileSync(tarPath, data);

    // Use tar command to extract
    execSync(`tar -xzf "${tarPath}" -C "${tmpDir}"`, { stdio: "pipe" });

    // Find and move binary
    const extractedBinary = path.join(tmpDir, "keyway");
    if (fs.existsSync(extractedBinary)) {
      fs.copyFileSync(extractedBinary, destPath);
      fs.chmodSync(destPath, 0o755);
    }

    // Cleanup
    fs.rmSync(tmpDir, { recursive: true, force: true });
  } else if (url.endsWith(".zip")) {
    // For W
main function · javascript · L123-L155 (33 LOC)
npm/scripts/postinstall.js
async function main() {
  try {
    const platform = getPlatform();
    const arch = getArch();
    const version = getVersion();

    // Skip download for version 0.0.0 (development)
    if (version === "0.0.0") {
      console.log("Development version detected, skipping binary download");
      // Create a placeholder script
      const placeholder = `#!/bin/sh
echo "Keyway CLI not installed. Run 'make build' in the cli directory."
exit 1
`;
      fs.writeFileSync(path.join(BIN_DIR, "keyway"), placeholder);
      fs.chmodSync(path.join(BIN_DIR, "keyway"), 0o755);
      return;
    }

    const ext = platform === "windows" ? "zip" : "tar.gz";
    const filename = `keyway_${version}_${platform}_${arch}.${ext}`;
    const url = `https://github.com/${REPO}/releases/download/v${version}/${filename}`;

    const destPath = path.join(BIN_DIR, BINARY_NAME);
    await downloadAndExtract(url, destPath);

    console.log(`Keyway CLI v${version} installed successfully!`);
  } catch (error) {
   
‹ prevpage 4 / 4