replit-tools 1.2.41 → 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.41",
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
  }
@@ -225,18 +229,27 @@ if command -v node &>/dev/null; then
225
229
  if (fs.existsSync(codexConfigPath)) c = fs.readFileSync(codexConfigPath, "utf8");
226
230
  const desired = String(codexMaxBytes);
227
231
  let updated = false;
232
+ // Codex requires persistence field when [history] section is present
228
233
  if (!/\[history\]/.test(c)) {
229
- c = (c.trimEnd() + "\n\n[history]\nmax_bytes = " + desired + "\n").trimStart();
234
+ c = (c.trimEnd() + "\n\n[history]\npersistence = \"save-all\"\nmax_bytes = " + desired + "\n").trimStart();
230
235
  updated = true;
231
- } else if (/max_bytes\s*=\s*(\d+)/.test(c)) {
232
- const cur = c.match(/max_bytes\s*=\s*(\d+)/)[1];
233
- if (cur !== desired) {
234
- c = c.replace(/(\[history\][\s\S]*?max_bytes\s*=\s*)\d+/, "$1" + desired);
236
+ } else {
237
+ // Ensure persistence field exists
238
+ if (!/(\[history\][\s\S]*?)persistence\s*=/.test(c)) {
239
+ c = c.replace(/\[history\](\s*)/, "[history]$1persistence = \"save-all\"\n");
240
+ updated = true;
241
+ }
242
+ // Ensure max_bytes is set correctly
243
+ if (/max_bytes\s*=\s*(\d+)/.test(c)) {
244
+ const cur = c.match(/max_bytes\s*=\s*(\d+)/)[1];
245
+ if (cur !== desired) {
246
+ c = c.replace(/(\[history\][\s\S]*?max_bytes\s*=\s*)\d+/, "$1" + desired);
247
+ updated = true;
248
+ }
249
+ } else {
250
+ c = c.replace(/(\[history\][\s\S]*?persistence\s*=\s*"[^"]*"\s*\n)/, "$1max_bytes = " + desired + "\n");
235
251
  updated = true;
236
252
  }
237
- } else {
238
- c = c.replace(/\[history\](\s*)/, "[history]$1max_bytes = " + desired + "\n");
239
- updated = true;
240
253
  }
241
254
  if (updated) {
242
255
  fs.writeFileSync(codexConfigPath, c);
@@ -305,97 +318,152 @@ if [ ! -L "${SHARE_VERSIONS}" ] || [ "$(readlink -f "${SHARE_VERSIONS}")" != "${
305
318
  fi
306
319
 
307
320
  # =============================================================================
308
- # Step 4: Find latest Claude version and create binary symlink
321
+ # Step 4: Detect newest Claude binary across ALL install locations, promote it
309
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
+ }
310
342
 
311
- # Check multiple locations for Claude binary (handles race conditions on container restart)
312
- find_claude_binary() {
313
- local found_binary=""
314
- local found_version=""
315
-
316
- # 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
317
352
  if [ -d "${CLAUDE_VERSIONS}" ]; then
318
- local ver=$(ls -1 "${CLAUDE_VERSIONS}" 2>/dev/null | grep -v '^\.' | sort -V | tail -n1)
319
- if [ -n "${ver}" ] && [ -f "${CLAUDE_VERSIONS}/${ver}" ]; then
320
- found_binary="${CLAUDE_VERSIONS}/${ver}"
321
- found_version="${ver}"
322
- fi
353
+ find "${CLAUDE_VERSIONS}" -maxdepth 1 -type f ! -name '.*' 2>/dev/null
323
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
+ }
324
377
 
325
- # Priority 2: Check if claude command already works (might be from previous install)
326
- if [ -z "${found_binary}" ]; then
327
- local existing_claude=$(command -v claude 2>/dev/null)
328
- if [ -n "${existing_claude}" ] && [ -x "${existing_claude}" ]; then
329
- # Follow symlinks to find actual binary
330
- local real_path=$(readlink -f "${existing_claude}" 2>/dev/null)
331
- if [ -f "${real_path}" ]; then
332
- found_binary="${real_path}"
333
- 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"
334
392
  fi
335
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
336
402
  fi
337
403
 
338
- # Priority 3: Default install location (not through our symlink)
339
- if [ -z "${found_binary}" ]; then
340
- local default_loc="${HOME}/.local/share/claude/versions"
341
- # Check if this is a real directory, not our symlink
342
- if [ -d "${default_loc}" ] && [ ! -L "${default_loc}" ]; then
343
- local ver=$(ls -1 "${default_loc}" 2>/dev/null | grep -v '^\.' | sort -V | tail -n1)
344
- if [ -n "${ver}" ] && [ -f "${default_loc}/${ver}" ]; then
345
- found_binary="${default_loc}/${ver}"
346
- found_version="${ver}"
347
- 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)"
348
414
  fi
349
415
  fi
350
-
351
- # Return results via global variables
352
- FOUND_CLAUDE_BINARY="${found_binary}"
353
- FOUND_CLAUDE_VERSION="${found_version}"
416
+ LATEST_VERSION="${best_ver}"
417
+ return 0
354
418
  }
355
419
 
356
- # Find any existing Claude installation
357
- find_claude_binary
358
-
359
- if [ -n "${FOUND_CLAUDE_BINARY}" ]; then
360
- CLAUDE_BINARY="${FOUND_CLAUDE_BINARY}"
361
- 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
+ }
362
435
 
363
- # Ensure binary is in our persistent directory
364
- if [ ! -f "${CLAUDE_VERSIONS}/${LATEST_VERSION}" ]; then
365
- cp -p "${CLAUDE_BINARY}" "${CLAUDE_VERSIONS}/${LATEST_VERSION}" 2>/dev/null || true
366
- chmod 755 "${CLAUDE_VERSIONS}/${LATEST_VERSION}" 2>/dev/null || true
367
- CLAUDE_BINARY="${CLAUDE_VERSIONS}/${LATEST_VERSION}"
368
- 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
369
449
  fi
370
450
 
371
- # Create or update the binary symlink
372
- if [ ! -L "${LOCAL_BIN}/claude" ] || [ "$(readlink -f "${LOCAL_BIN}/claude")" != "${CLAUDE_VERSIONS}/${LATEST_VERSION}" ]; then
373
- rm -f "${LOCAL_BIN}/claude" 2>/dev/null || true
374
- ln -sf "${CLAUDE_VERSIONS}/${LATEST_VERSION}" "${LOCAL_BIN}/claude"
375
- log "✅ Claude binary symlink: ~/.local/bin/claude -> ${CLAUDE_VERSIONS}/${LATEST_VERSION}"
376
- fi
377
- else
378
- # Claude not installed - install it
379
- log "⚠️ Claude Code not found, installing..."
380
-
381
- # Install Claude Code using the official installer
382
- if curl -fsSL https://claude.ai/install.sh | bash 2>/dev/null; then
383
- # After install, find and sync the binary
384
- find_claude_binary
385
- if [ -n "${FOUND_CLAUDE_BINARY}" ]; then
386
- LATEST_VERSION="${FOUND_CLAUDE_VERSION}"
387
- if [ ! -f "${CLAUDE_VERSIONS}/${LATEST_VERSION}" ]; then
388
- cp -p "${FOUND_CLAUDE_BINARY}" "${CLAUDE_VERSIONS}/${LATEST_VERSION}" 2>/dev/null || true
389
- chmod 755 "${CLAUDE_VERSIONS}/${LATEST_VERSION}" 2>/dev/null || true
390
- fi
391
- ln -sf "${CLAUDE_VERSIONS}/${LATEST_VERSION}" "${LOCAL_BIN}/claude"
392
- log "✅ Claude Code ${LATEST_VERSION} installed"
393
- fi
451
+ if detect_and_promote_claude; then
452
+ gc_claude_versions
394
453
  else
395
- log " Failed to install Claude Code"
396
- 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
397
461
  fi
398
- 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
399
467
 
400
468
  # =============================================================================
401
469
  # Step 5: Ensure PATH includes ~/.local/bin