replit-tools 1.2.42 → 1.2.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replit-tools",
3
- "version": "1.2.42",
3
+ "version": "1.2.43",
4
4
  "description": "DATA Tools - One command to set up Claude Code and Codex CLI on Replit with full persistence",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -41,9 +41,13 @@ LOCAL_SHARE_CLAUDE="${HOME}/.local/share/claude"
41
41
  VERSION_FILE="${REPLIT_TOOLS}/.version"
42
42
  PACKAGE_NAME="data-remote"
43
43
 
44
- # Logging helper
44
+ # Force Claude re-detection: bash setup-claude-code.sh --refresh OR CLAUDE_FORCE_REFRESH=1
45
+ CLAUDE_FORCE_REFRESH="${CLAUDE_FORCE_REFRESH:-0}"
46
+ [ "${1:-}" = "--refresh" ] && CLAUDE_FORCE_REFRESH=1
47
+
48
+ # Logging helper - prints in interactive shells, or when run manually with --refresh
45
49
  log() {
46
- if [[ $- == *i* ]]; then
50
+ if [[ $- == *i* ]] || [ "${CLAUDE_FORCE_REFRESH:-0}" = "1" ]; then
47
51
  echo "$1"
48
52
  fi
49
53
  }
@@ -314,97 +318,152 @@ if [ ! -L "${SHARE_VERSIONS}" ] || [ "$(readlink -f "${SHARE_VERSIONS}")" != "${
314
318
  fi
315
319
 
316
320
  # =============================================================================
317
- # Step 4: Find latest Claude version and create binary symlink
321
+ # Step 4: Detect newest Claude binary across ALL install locations, promote it
318
322
  # =============================================================================
323
+ # Bug history: npm installs claude to node_modules/@anthropic-ai/claude-code/
324
+ # which the old finder never checked, so ~/.local/bin/claude stayed pinned to an
325
+ # old .claude-versions entry while Replit's agent UI launched the newer
326
+ # node_modules binary directly. Now we scan everything, compare by real
327
+ # --version output (not filename), promote the winner, and GC stale copies.
328
+
329
+ CLAUDE_DETECT_CACHE="${CLAUDE_VERSIONS}/.detect-cache"
330
+
331
+ # Get semver by EXECUTING the binary (never trust filenames). Empty if invalid.
332
+ claude_bin_version() {
333
+ local bin="$1"
334
+ [ -f "$bin" ] || return 1
335
+ local v
336
+ v=$("$bin" --version 2>/dev/null | awk '{print $1}')
337
+ if [[ "$v" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
338
+ echo "$v"; return 0
339
+ fi
340
+ return 1
341
+ }
319
342
 
320
- # Check multiple locations for Claude binary (handles race conditions on container restart)
321
- find_claude_binary() {
322
- local found_binary=""
323
- local found_version=""
324
-
325
- # Priority 1: Our persistent versions directory
343
+ # All places a Claude binary might live (npm, bun, official installer, our store)
344
+ claude_candidate_paths() {
345
+ echo "${WORKSPACE}/node_modules/@anthropic-ai/claude-code/bin/claude.exe"
346
+ echo "${WORKSPACE}/node_modules/.bin/claude"
347
+ echo "${HOME}/.npm-global/bin/claude"
348
+ echo "${HOME}/.bun/bin/claude"
349
+ if [ -d "${HOME}/.local/share/claude/versions" ] && [ ! -L "${HOME}/.local/share/claude/versions" ]; then
350
+ find "${HOME}/.local/share/claude/versions" -maxdepth 1 -type f 2>/dev/null
351
+ fi
326
352
  if [ -d "${CLAUDE_VERSIONS}" ]; then
327
- local ver=$(ls -1 "${CLAUDE_VERSIONS}" 2>/dev/null | grep -v '^\.' | sort -V | tail -n1)
328
- if [ -n "${ver}" ] && [ -f "${CLAUDE_VERSIONS}/${ver}" ]; then
329
- found_binary="${CLAUDE_VERSIONS}/${ver}"
330
- found_version="${ver}"
331
- fi
353
+ find "${CLAUDE_VERSIONS}" -maxdepth 1 -type f ! -name '.*' 2>/dev/null
332
354
  fi
355
+ # command -v claude, but ONLY if it does not resolve to our own managed symlink
356
+ local cmdclaude our_target real
357
+ cmdclaude=$(command -v claude 2>/dev/null)
358
+ our_target=$(readlink -f "${LOCAL_BIN}/claude" 2>/dev/null)
359
+ if [ -n "$cmdclaude" ]; then
360
+ real=$(readlink -f "$cmdclaude" 2>/dev/null)
361
+ [ -n "$real" ] && [ "$real" != "$our_target" ] && echo "$real"
362
+ fi
363
+ }
364
+
365
+ # Cheap fingerprint of candidate paths (mtime+size) to short-circuit unchanged runs
366
+ claude_detect_fingerprint() {
367
+ {
368
+ local rp
369
+ while IFS= read -r c; do
370
+ [ -z "$c" ] && continue
371
+ rp=$(readlink -f "$c" 2>/dev/null)
372
+ [ -f "$rp" ] && stat -c '%n:%Y:%s' "$rp" 2>/dev/null
373
+ done < <(claude_candidate_paths)
374
+ readlink -f "${LOCAL_BIN}/claude" 2>/dev/null
375
+ } | md5sum 2>/dev/null | awk '{print $1}'
376
+ }
333
377
 
334
- # Priority 2: Check if claude command already works (might be from previous install)
335
- if [ -z "${found_binary}" ]; then
336
- local existing_claude=$(command -v claude 2>/dev/null)
337
- if [ -n "${existing_claude}" ] && [ -x "${existing_claude}" ]; then
338
- # Follow symlinks to find actual binary
339
- local real_path=$(readlink -f "${existing_claude}" 2>/dev/null)
340
- if [ -f "${real_path}" ]; then
341
- found_binary="${real_path}"
342
- found_version=$(basename "${real_path}")
378
+ # Detect newest version across all candidates, promote into .claude-versions, relink
379
+ detect_and_promote_claude() {
380
+ local best_ver="" best_path="" seen="" rp v
381
+ while IFS= read -r c; do
382
+ [ -z "$c" ] && continue
383
+ rp=$(readlink -f "$c" 2>/dev/null)
384
+ [ -z "$rp" ] && continue
385
+ [ -f "$rp" ] || continue
386
+ case "$seen" in *"|${rp}|"*) continue ;; esac
387
+ seen="${seen}|${rp}|"
388
+ v=$(claude_bin_version "$rp") || continue
389
+ if [ -z "$best_ver" ] || [ "$(printf '%s\n%s\n' "$best_ver" "$v" | sort -V | tail -n1)" = "$v" ]; then
390
+ if [ "$v" != "$best_ver" ] || [ -z "$best_path" ]; then
391
+ best_ver="$v"; best_path="$rp"
343
392
  fi
344
393
  fi
394
+ done < <(claude_candidate_paths)
395
+
396
+ [ -z "$best_ver" ] && return 1
397
+
398
+ mkdir -p "${CLAUDE_VERSIONS}"
399
+ if [ ! -f "${CLAUDE_VERSIONS}/${best_ver}" ]; then
400
+ cp -p "${best_path}" "${CLAUDE_VERSIONS}/${best_ver}" 2>/dev/null || true
401
+ chmod 755 "${CLAUDE_VERSIONS}/${best_ver}" 2>/dev/null || true
345
402
  fi
346
403
 
347
- # Priority 3: Default install location (not through our symlink)
348
- if [ -z "${found_binary}" ]; then
349
- local default_loc="${HOME}/.local/share/claude/versions"
350
- # Check if this is a real directory, not our symlink
351
- if [ -d "${default_loc}" ] && [ ! -L "${default_loc}" ]; then
352
- local ver=$(ls -1 "${default_loc}" 2>/dev/null | grep -v '^\.' | sort -V | tail -n1)
353
- if [ -n "${ver}" ] && [ -f "${default_loc}/${ver}" ]; then
354
- found_binary="${default_loc}/${ver}"
355
- found_version="${ver}"
356
- fi
404
+ local new_target="${CLAUDE_VERSIONS}/${best_ver}"
405
+ local cur_target
406
+ cur_target=$(readlink -f "${LOCAL_BIN}/claude" 2>/dev/null)
407
+ if [ "$cur_target" != "$new_target" ]; then
408
+ mkdir -p "${LOCAL_BIN}"
409
+ ln -sfn "${new_target}" "${LOCAL_BIN}/claude"
410
+ if [ -n "$cur_target" ] && [ -f "$cur_target" ]; then
411
+ log "✅ Claude $(basename "$cur_target") → ${best_ver}"
412
+ else
413
+ log "✅ Claude ${best_ver} active (~/.local/bin/claude)"
357
414
  fi
358
415
  fi
359
-
360
- # Return results via global variables
361
- FOUND_CLAUDE_BINARY="${found_binary}"
362
- FOUND_CLAUDE_VERSION="${found_version}"
416
+ LATEST_VERSION="${best_ver}"
417
+ return 0
363
418
  }
364
419
 
365
- # Find any existing Claude installation
366
- find_claude_binary
367
-
368
- if [ -n "${FOUND_CLAUDE_BINARY}" ]; then
369
- CLAUDE_BINARY="${FOUND_CLAUDE_BINARY}"
370
- LATEST_VERSION="${FOUND_CLAUDE_VERSION}"
420
+ # Keep only the newest 3 versions in our store (each binary is ~230 MB)
421
+ gc_claude_versions() {
422
+ [ -d "${CLAUDE_VERSIONS}" ] || return 0
423
+ local cur_target all count to_delete v p
424
+ cur_target=$(readlink -f "${LOCAL_BIN}/claude" 2>/dev/null)
425
+ all=$(find "${CLAUDE_VERSIONS}" -maxdepth 1 -type f -printf '%f\n' 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V)
426
+ count=$(printf '%s\n' "$all" | grep -c .)
427
+ [ "$count" -le 3 ] && return 0
428
+ to_delete=$(printf '%s\n' "$all" | head -n $((count - 3)))
429
+ for v in $to_delete; do
430
+ p="${CLAUDE_VERSIONS}/${v}"
431
+ [ "$(readlink -f "$p")" = "$cur_target" ] && continue
432
+ rm -f "$p" 2>/dev/null && log "🗑️ Removed old Claude ${v}"
433
+ done
434
+ }
371
435
 
372
- # Ensure binary is in our persistent directory
373
- if [ ! -f "${CLAUDE_VERSIONS}/${LATEST_VERSION}" ]; then
374
- cp -p "${CLAUDE_BINARY}" "${CLAUDE_VERSIONS}/${LATEST_VERSION}" 2>/dev/null || true
375
- chmod 755 "${CLAUDE_VERSIONS}/${LATEST_VERSION}" 2>/dev/null || true
376
- CLAUDE_BINARY="${CLAUDE_VERSIONS}/${LATEST_VERSION}"
377
- log "✅ Claude ${LATEST_VERSION} synced to persistent storage"
436
+ # Run detection (cheap short-circuit unless paths changed or --refresh given)
437
+ run_claude_detection() {
438
+ local fp cached cur
439
+ fp=$(claude_detect_fingerprint)
440
+ cached=""
441
+ [ -f "${CLAUDE_DETECT_CACHE}" ] && cached=$(cat "${CLAUDE_DETECT_CACHE}" 2>/dev/null)
442
+
443
+ if [ "${CLAUDE_FORCE_REFRESH}" != "1" ] && [ -n "$fp" ] && [ "$fp" = "$cached" ]; then
444
+ cur=$(readlink -f "${LOCAL_BIN}/claude" 2>/dev/null)
445
+ if [ -f "$cur" ]; then
446
+ LATEST_VERSION=$(basename "$cur")
447
+ return 0 # nothing changed since last run
448
+ fi
378
449
  fi
379
450
 
380
- # Create or update the binary symlink
381
- if [ ! -L "${LOCAL_BIN}/claude" ] || [ "$(readlink -f "${LOCAL_BIN}/claude")" != "${CLAUDE_VERSIONS}/${LATEST_VERSION}" ]; then
382
- rm -f "${LOCAL_BIN}/claude" 2>/dev/null || true
383
- ln -sf "${CLAUDE_VERSIONS}/${LATEST_VERSION}" "${LOCAL_BIN}/claude"
384
- log "✅ Claude binary symlink: ~/.local/bin/claude -> ${CLAUDE_VERSIONS}/${LATEST_VERSION}"
385
- fi
386
- else
387
- # Claude not installed - install it
388
- log "⚠️ Claude Code not found, installing..."
389
-
390
- # Install Claude Code using the official installer
391
- if curl -fsSL https://claude.ai/install.sh | bash 2>/dev/null; then
392
- # After install, find and sync the binary
393
- find_claude_binary
394
- if [ -n "${FOUND_CLAUDE_BINARY}" ]; then
395
- LATEST_VERSION="${FOUND_CLAUDE_VERSION}"
396
- if [ ! -f "${CLAUDE_VERSIONS}/${LATEST_VERSION}" ]; then
397
- cp -p "${FOUND_CLAUDE_BINARY}" "${CLAUDE_VERSIONS}/${LATEST_VERSION}" 2>/dev/null || true
398
- chmod 755 "${CLAUDE_VERSIONS}/${LATEST_VERSION}" 2>/dev/null || true
399
- fi
400
- ln -sf "${CLAUDE_VERSIONS}/${LATEST_VERSION}" "${LOCAL_BIN}/claude"
401
- log "✅ Claude Code ${LATEST_VERSION} installed"
402
- fi
451
+ if detect_and_promote_claude; then
452
+ gc_claude_versions
403
453
  else
404
- log " Failed to install Claude Code"
405
- log " Try running: curl -fsSL https://claude.ai/install.sh | bash"
454
+ log "⚠️ Claude Code not found, installing..."
455
+ if curl -fsSL https://claude.ai/install.sh | bash 2>/dev/null; then
456
+ detect_and_promote_claude && gc_claude_versions
457
+ else
458
+ log "❌ Failed to install Claude Code"
459
+ log " Try running: curl -fsSL https://claude.ai/install.sh | bash"
460
+ fi
406
461
  fi
407
- fi
462
+ # Persist post-promotion fingerprint so the next shell short-circuits
463
+ claude_detect_fingerprint > "${CLAUDE_DETECT_CACHE}" 2>/dev/null
464
+ }
465
+
466
+ run_claude_detection
408
467
 
409
468
  # =============================================================================
410
469
  # Step 5: Ensure PATH includes ~/.local/bin