Ashira Pitchayapakayakul commited on
Commit
47c417c
·
1 Parent(s): 88079d2

Surrogate CLI: status line + cost meter + /undo + /history + /context (research wins #3,#4,#9)

Browse files
Files changed (1) hide show
  1. bin/surrogate +99 -2
bin/surrogate CHANGED
@@ -271,7 +271,7 @@ def tool_glob(pattern, path=None):
271
 
272
  def tool_grep(pattern, path=None, glob='*'):
273
  base = os.path.expanduser(path) if path else CWD
274
- cmd = f"grep -rn --include='{glob}' -E {subprocess.list2cmdline([pattern])} {base} 2>/dev/null | head -40"
275
  r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=15)
276
  return {'matches': r.stdout[:5000]}
277
 
@@ -474,6 +474,9 @@ ${B}Diagnostics${R}:
474
  ${CY}/cost-all${R} all provider usage breakdown
475
  ${CY}/health${R} check HF endpoint + local CLI status
476
  ${CY}/monitor${R} watch logs + system load (background)
 
 
 
477
 
478
  ${B}Hermes brain${R} (axentx/surrogate-1):
479
  ${CY}/remote${R} <task> force route via HF Space (override local)
@@ -486,6 +489,42 @@ ${B}Tips${R}:
486
  EOF
487
  }
488
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
489
  # ═══ REPL ═══
490
  repl() {
491
  banner
@@ -493,6 +532,7 @@ repl() {
493
  echo "${D}Type ${CY}/help${R}${D} for commands · ${CY}/exit${R}${D} to quit · plain text = task to Surrogate-1${R}"
494
  echo ""
495
  while true; do
 
496
  # Show project + mode in prompt (Claude-Code style)
497
  local prompt_proj="${SURROGATE_MD:+$(basename $(dirname "$SURROGATE_MD"))}"
498
  local prompt_str="${B}${BMA}▶${R}"
@@ -502,6 +542,9 @@ repl() {
502
  line="${line#"${line%%[![:space:]]*}"}" # ltrim
503
  [[ -z "$line" ]] && continue
504
 
 
 
 
505
  case "$line" in
506
  /exit|/quit|exit|quit) break ;;
507
  /help|/?) slash_help ;;
@@ -556,6 +599,60 @@ repl() {
556
  esac
557
  ;;
558
  /memory) ls -lh ~/.claude/state/surrogate-memory/ 2>&1 | head -10 ;;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
559
  /cost)
560
  bash -c 'source ~/.hermes/.env; curl -s -H "Authorization: Bearer $OPENROUTER_API_KEY" https://openrouter.ai/api/v1/auth/key' 2>&1 | python3 -c "import json,sys; d=json.load(sys.stdin).get('data',{}); print(f' OpenRouter: \${d.get(\"usage\",0):.4f}')"
561
  ;;
@@ -842,7 +939,7 @@ monitor_mode() {
842
  # If critical → spawn agent to investigate
843
  if [[ $ERR_COUNT -gt 50 ]]; then
844
  echo "${RE}⚠ elevated errors — dispatching investigator agent${R}"
845
- (run_agent "เช็ค ~/.claude/logs/ หา pattern error ที่ recur บ่อย และเสนอ fix list (ห้ามแก้เอง รายงานอย่างเดียว)" 2>&1 | head -20) &
846
  fi
847
  sleep 30
848
  done
 
271
 
272
  def tool_grep(pattern, path=None, glob='*'):
273
  base = os.path.expanduser(path) if path else CWD
274
+ cmd = f"/usr/bin/grep -rn --include='{glob}' -E {subprocess.list2cmdline([pattern])} {base} 2>/dev/null | head -40"
275
  r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=15)
276
  return {'matches': r.stdout[:5000]}
277
 
 
474
  ${CY}/cost-all${R} all provider usage breakdown
475
  ${CY}/health${R} check HF endpoint + local CLI status
476
  ${CY}/monitor${R} watch logs + system load (background)
477
+ ${CY}/history${R} last 20 prompts you ran
478
+ ${CY}/context${R} show full session context (cwd, mode, git, etc.)
479
+ ${CY}/undo${R} restore last surrogate checkpoint (git stash/reset)
480
 
481
  ${B}Hermes brain${R} (axentx/surrogate-1):
482
  ${CY}/remote${R} <task> force route via HF Space (override local)
 
489
  EOF
490
  }
491
 
492
+ # ═══ Status line — show every prompt (cost, mode, model, project) ═══
493
+ print_statusline() {
494
+ local cost_str=""
495
+ if [[ -n "${OR_KEY:-${OPENROUTER_API_KEY:-}}" ]]; then
496
+ # Cached cost lookup — fetch once per minute
497
+ local cache=~/.surrogate/.cost-cache
498
+ if [[ ! -f "$cache" ]] || [[ $(($(date +%s) - $(stat -f %m "$cache" 2>/dev/null || stat -c %Y "$cache" 2>/dev/null || echo 0))) -gt 60 ]]; then
499
+ (curl -sS -m 5 -H "Authorization: Bearer ${OPENROUTER_API_KEY:-${OR_KEY:-}}" \
500
+ https://openrouter.ai/api/v1/auth/key 2>/dev/null \
501
+ | python3 -c "import json,sys; d=json.load(sys.stdin).get('data',{}); print(f'\$OR={d.get(\"usage\",0):.3f}')" \
502
+ > "$cache") 2>/dev/null &
503
+ fi
504
+ cost_str=$(cat "$cache" 2>/dev/null | head -1)
505
+ fi
506
+ local proj_short=""
507
+ [[ -n "$SURROGATE_MD" ]] && proj_short="📁 $(basename "$(dirname "$SURROGATE_MD")") "
508
+ local model_short="${MODEL:-auto}"
509
+ local hf_brain="${USE_HF_HERMES:-1}"
510
+ local hf_str="🌐 HF" ; [[ "$hf_brain" == "0" ]] && hf_str="💻 local"
511
+ echo "${D}─── ${proj_short}${YE}${PERM_MODE}${R}${D} · ${MA}${model_short}${R}${D} · ${hf_str}${R}${D} · ${cost_str:-\$?} ───${R}"
512
+ }
513
+
514
+ # ═══ History persistence ═══
515
+ HISTORY_FILE="$SURROGATE_HOME/history.jsonl"
516
+ mkdir -p "$(dirname "$HISTORY_FILE")"
517
+ save_history() {
518
+ local prompt="$1"
519
+ python3 -c "
520
+ import json, sys, time
521
+ from pathlib import Path
522
+ Path('$HISTORY_FILE').parent.mkdir(parents=True, exist_ok=True)
523
+ with open('$HISTORY_FILE', 'a') as f:
524
+ f.write(json.dumps({'ts': time.time(), 'cwd': '$(pwd)', 'prompt': '''$prompt'''[:500]}, ensure_ascii=False) + '\n')
525
+ " 2>/dev/null
526
+ }
527
+
528
  # ═══ REPL ═══
529
  repl() {
530
  banner
 
532
  echo "${D}Type ${CY}/help${R}${D} for commands · ${CY}/exit${R}${D} to quit · plain text = task to Surrogate-1${R}"
533
  echo ""
534
  while true; do
535
+ print_statusline
536
  # Show project + mode in prompt (Claude-Code style)
537
  local prompt_proj="${SURROGATE_MD:+$(basename $(dirname "$SURROGATE_MD"))}"
538
  local prompt_str="${B}${BMA}▶${R}"
 
542
  line="${line#"${line%%[![:space:]]*}"}" # ltrim
543
  [[ -z "$line" ]] && continue
544
 
545
+ # Persist to history (skip for /exit and /clear)
546
+ case "$line" in /exit|/quit|exit|quit|/clear) ;; *) save_history "$line" ;; esac
547
+
548
  case "$line" in
549
  /exit|/quit|exit|quit) break ;;
550
  /help|/?) slash_help ;;
 
599
  esac
600
  ;;
601
  /memory) ls -lh ~/.claude/state/surrogate-memory/ 2>&1 | head -10 ;;
602
+ /undo)
603
+ # Restore last checkpoint (git stash if uncommitted changes from last task)
604
+ if git -C "$(pwd)" rev-parse --git-dir &>/dev/null; then
605
+ LAST_STASH=$(git -C "$(pwd)" stash list 2>/dev/null | grep "surrogate-checkpoint" | head -1)
606
+ if [[ -n "$LAST_STASH" ]]; then
607
+ STASH_REF=$(echo "$LAST_STASH" | cut -d: -f1)
608
+ echo "${YE}▸ Restoring: $LAST_STASH${R}"
609
+ git -C "$(pwd)" stash pop "$STASH_REF" 2>&1 | head -10
610
+ else
611
+ # Try undoing last commit (if surrogate-auto)
612
+ LAST_COMMIT=$(git -C "$(pwd)" log -1 --format='%s' 2>/dev/null)
613
+ if echo "$LAST_COMMIT" | grep -qiE "surrogate|auto-dev|hermes"; then
614
+ echo "${YE}▸ Undoing last commit: $LAST_COMMIT${R}"
615
+ git -C "$(pwd)" reset --soft HEAD~1 2>&1 | tail -3
616
+ else
617
+ echo "${GY} no surrogate checkpoint to undo (last commit: $LAST_COMMIT)${R}"
618
+ fi
619
+ fi
620
+ else
621
+ echo "${RE} not a git repo — /undo requires git${R}"
622
+ fi
623
+ ;;
624
+ /history)
625
+ if [[ -f "$HISTORY_FILE" ]]; then
626
+ python3 -c "
627
+ import json
628
+ from pathlib import Path
629
+ import time
630
+ lines = Path('$HISTORY_FILE').read_text().splitlines()[-20:]
631
+ for l in lines:
632
+ try:
633
+ d = json.loads(l)
634
+ age_min = int((time.time() - d.get('ts', 0)) / 60)
635
+ print(f\" [{age_min:>4}m ago] {d.get('prompt','')[:100]}\")
636
+ except: pass
637
+ "
638
+ else
639
+ echo "${GY} no history yet${R}"
640
+ fi
641
+ ;;
642
+ /context)
643
+ echo "${B}Active context${R}"
644
+ echo " cwd: $(pwd)"
645
+ echo " project: ${SURROGATE_MD:-(none)}"
646
+ echo " mode: ${PERM_MODE}"
647
+ echo " model: ${MODEL:-auto}"
648
+ echo " effort: ${EFFORT}"
649
+ echo " HF brain: ${USE_HF_HERMES:-1} (1=route via HF Space, 0=local-only)"
650
+ echo " history: $(wc -l < "$HISTORY_FILE" 2>/dev/null || echo 0) commands"
651
+ if [[ -d ".git" ]]; then
652
+ echo " git branch: $(git branch --show-current 2>/dev/null)"
653
+ echo " git status: $(git status --short 2>/dev/null | wc -l | tr -d ' ') changed files"
654
+ fi
655
+ ;;
656
  /cost)
657
  bash -c 'source ~/.hermes/.env; curl -s -H "Authorization: Bearer $OPENROUTER_API_KEY" https://openrouter.ai/api/v1/auth/key' 2>&1 | python3 -c "import json,sys; d=json.load(sys.stdin).get('data',{}); print(f' OpenRouter: \${d.get(\"usage\",0):.4f}')"
658
  ;;
 
939
  # If critical → spawn agent to investigate
940
  if [[ $ERR_COUNT -gt 50 ]]; then
941
  echo "${RE}⚠ elevated errors — dispatching investigator agent${R}"
942
+ (run_agent "เช็ค ~/.claude/logs/ หา pattern error ที่ recur บ่อย และเสนอ fix list (ห้ามแก้เอง รายงานอย่างเดียว)" 2>&1 | /usr/bin/head -20) &
943
  fi
944
  sleep 30
945
  done