Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions config/check_version.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env python3
"""Check if Test-Agent update is available. Called by Claude Code Stop hook.

Reads .version from project root, fetches remote VERSION via HTTP.
Reads VERSION from project root, fetches remote VERSION via HTTP.
Prints notification only when newer version available.
Rate-limited: checks at most once per 24h via .version_last_check timestamp.
Rate-limited: checks at most once per 24h via VERSION_last_check timestamp.
"""
import os
import time
Expand All @@ -16,13 +16,17 @@

def main():
project_root = os.getcwd()
version_file = os.path.join(project_root, ".version")
version_file = os.path.join(project_root, "VERSION")
legacy_file = os.path.join(project_root, ".version")
# Migration: rename legacy .version to VERSION if VERSION is missing
if not os.path.isfile(version_file) and os.path.isfile(legacy_file):
os.rename(legacy_file, version_file)

if not os.path.isfile(version_file):
return # Not a Test-Agent project, skip silently

# Rate limit: check at most once per CHECK_INTERVAL
last_check_file = os.path.join(project_root, ".version_last_check")
last_check_file = os.path.join(project_root, "VERSION_last_check")
now = time.time()
if os.path.isfile(last_check_file):
try:
Expand Down
14 changes: 9 additions & 5 deletions install.py
Original file line number Diff line number Diff line change
Expand Up @@ -606,8 +606,8 @@ def _read_template_version(template_dir):


def _write_local_version(project_root, version):
"""写入 .version 文件供后续更新检测。"""
vf = os.path.join(project_root, ".version")
"""写入 VERSION 文件供后续更新检测。"""
vf = os.path.join(project_root, "VERSION")
with open(vf, "w", encoding="utf-8") as f:
f.write(version + "\n")

Expand Down Expand Up @@ -654,9 +654,13 @@ def _update_deps(project_root):

def do_update():
"""轻量更新:克隆最新模板 → 比较版本 → 拷贝文件 → 更新依赖 → 保留用户数据。"""
version_file = os.path.join(PROJECT_ROOT, ".version")
version_file = os.path.join(PROJECT_ROOT, "VERSION")
legacy_file = os.path.join(PROJECT_ROOT, ".version")
# Migration: rename legacy .version to VERSION if VERSION is missing
if not os.path.isfile(version_file) and os.path.isfile(legacy_file):
os.rename(legacy_file, version_file)
if not os.path.isfile(version_file):
print(f"❌ 未找到 .version 文件")
print(f"❌ 未找到 VERSION 文件")
print(f" 当前目录: {os.getcwd()}")
print(f" 查找路径: {version_file}")
print(f" 请先执行完整安装:python install.py <目录>")
Expand Down Expand Up @@ -787,7 +791,7 @@ def main():
# 10. 恢复用户数据
restore_user_data(PROJECT_ROOT, backed)

# 11. 写入 .version 供后续更新检测
# 11. 写入 VERSION 供后续更新检测
version = _read_template_version(template_dir)
if version:
_write_local_version(PROJECT_ROOT, version)
Expand Down
18 changes: 15 additions & 3 deletions runtime/cli/interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,17 +150,25 @@ def _handle_natural_language(text: str) -> None:
console.print(f"[dim]\"{summary}\"[/]")

t0 = time.time()
_old_argv = None
try:
import sys as _sys
import sys as _sys, contextlib, io

_old_argv = _sys.argv[:]
_sys.argv = ["tagent", "run", context_input]

with console.status("[bold green]Routing...", spinner="dots"):
from runtime.cli.commands.run import run as _run
_run()
_capture = io.StringIO()
with contextlib.redirect_stdout(_capture):
_run()

elapsed = (time.time() - t0) * 1000
output = _capture.getvalue().strip()
if output:
console.print(output)
console.print(f" [dim]Completed in {elapsed:.0f}ms[/]")
mem.add("assistant", f"[Run: {text}]")
mem.add("assistant", output[:500] if output else f"[Run: {text[:100]}]")
except SystemExit:
pass
except KeyboardInterrupt:
Expand All @@ -169,6 +177,10 @@ def _handle_natural_language(text: str) -> None:
except Exception:
console.print(f" [red]Error[/] [dim]({(time.time()-t0)*1000:.0f}ms)[/]")
mem.add("assistant", "[Error: command failed]")
finally:
if _old_argv is not None:
import sys as _sys
_sys.argv = _old_argv


# ── Fuzzy matching (thefuck-style) ─────────────────────────────────
Expand Down
43 changes: 19 additions & 24 deletions runtime/cli/slash_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ def all_commands() -> list[CommandDef]:
return list(COMMAND_REGISTRY)


def _run_with_argv(cmd: list[str], fn) -> None:
"""Execute fn with temporary sys.argv, restoring afterward."""
import sys
old = sys.argv[:]
try:
sys.argv = cmd
fn()
finally:
sys.argv = old


# ── Short, memorable commands ────────────────────────────────────


Expand Down Expand Up @@ -96,10 +107,8 @@ def _cmd_run(args: str) -> None:
from runtime.cli._shared import console
console.print("[red]Usage: /run <path|URL|text>[/]")
return
import sys as _sys
_sys.argv = ["tagent", "run"] + args.split()
from runtime.cli.commands.run import run
run()
_run_with_argv(["tagent", "run"] + args.split(), run)


@register("plan", "Plan only", aliases=["p"], args_hint="<target>")
Expand All @@ -108,55 +117,41 @@ def _cmd_plan(args: str) -> None:
from runtime.cli._shared import console
console.print("[red]Usage: /plan <path|URL|text>[/]")
return
import sys as _sys
_sys.argv = ["tagent", "plan"] + args.split()
from runtime.cli.commands.run import plan
plan()
_run_with_argv(["tagent", "plan"] + args.split(), plan)


@register("doctor", "Health check", aliases=["health"], args_hint="[--agents] [--probe]")
def _cmd_doctor(args: str) -> None:
import sys as _sys
_sys.argv = ["tagent", "doctor"] + (args.split() if args.strip() else [])
from runtime.cli.commands.doctor import doctor
doctor()
_run_with_argv(["tagent", "doctor"] + (args.split() if args.strip() else []), doctor)


@register("ls", "List experts + skills", aliases=["list", "catalog"])
def _cmd_ls(args: str) -> None:
import sys as _sys
_sys.argv = ["tagent", "catalog"]
from runtime.cli.commands.catalog import catalog
catalog()
_run_with_argv(["tagent", "catalog"], catalog)


@register("setup", "Generate config", aliases=["init"], args_hint="[--preset ...]")
def _cmd_setup(args: str) -> None:
import sys as _sys
_sys.argv = ["tagent", "init"] + (args.split() if args.strip() else [])
from runtime.cli.commands.init import init_project
init_project()
_run_with_argv(["tagent", "init"] + (args.split() if args.strip() else []), init_project)


@register("ready", "Release readiness", aliases=["readiness"])
def _cmd_ready(args: str) -> None:
import sys as _sys
_sys.argv = ["tagent", "readiness"] + (args.split() if args.strip() else [])
from runtime.cli.commands.readiness import readiness
readiness()
_run_with_argv(["tagent", "readiness"] + (args.split() if args.strip() else []), readiness)


@register("check", "Framework self-test", aliases=["selftest"], args_hint="[--e2e] [--strict]")
def _cmd_check(args: str) -> None:
import sys as _sys
_sys.argv = ["tagent", "selftest"] + (args.split() if args.strip() else [])
from runtime.cli.commands.selftest import selftest
selftest()
_run_with_argv(["tagent", "selftest"] + (args.split() if args.strip() else []), selftest)


@register("demo", "Quick demo", args_hint="[--real-llm]")
def _cmd_demo(args: str) -> None:
import sys as _sys
_sys.argv = ["tagent", "demo"] + (args.split() if args.strip() else [])
from runtime.cli.commands.demo import demo
demo()
_run_with_argv(["tagent", "demo"] + (args.split() if args.strip() else []), demo)