diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ce50f3..7629ced 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.6.0 + +- Added `autodl-remote shutdown` with shutdown/poweroff/halt fallback and SSH disconnect-aware success handling. +- Added detached job registry commands: `job list`, `job status `, and `job tail `. +- Added `exec --script ` and `exec --stdin -- ` for complex multi-line remote commands. +- Added `put-run ... -- ` to upload selected local paths before running a remote command. +- Added `model-dir [--mkdir]` and automatic local `.autodl-remote/CONVENTIONS.md` creation for project-level remote conventions. + ## 0.5.0 Initial public package. diff --git a/README.md b/README.md index 12a156a..b693993 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,10 @@ | ⬆️ 显式上传 | 用 `put` 或 `sync-up` 把本地改过的文件送到远端 | | ⬇️ 显式下载 | 用 `get` 或 `sync-down` 只拉回需要查看或提交的结果 | | 📜 日志查看 | 用 `tail` 查看远端训练日志,不必下载大文件 | +| 🧾 Job 状态 | 用 `job status` 和 `job tail` 跟踪 `exec --detach --name` 创建的长任务 | +| 🧠 脚本执行 | 用 `exec --script` 或 `exec --stdin` 避免复杂 quoting | +| ⏻ 远端关机 | 用 `shutdown` 处理 AutoDL 关机和 SSH 断开返回码 | +| 📝 项目约定 | 首次使用自动生成 `.autodl-remote/CONVENTIONS.md` 记录远端约定 | | 🧩 Codex 友好 | 插件不替你判断同步策略,把决策权留给用户和 Codex | ## ✅ Requirements @@ -195,11 +199,14 @@ REMOTE_ROOT="/root/autodl-tmp/my-project" ```bash autodl-remote doctor +autodl-remote model-dir autodl-remote tree . --depth 2 autodl-remote exec -- pwd autodl-remote exec -- nvidia-smi ``` +`model-dir` 会输出项目默认模型目录;第一次执行项目命令时,本地还会自动生成 `.autodl-remote/CONVENTIONS.md`,用来记录模型目录、数据目录、运行命令等项目约定。 + 如果这几步正常,再开始上传代码或运行训练。 ## 🧠 工作原理 @@ -226,11 +233,18 @@ autodl-remote exec -- nvidia-smi | `autodl-remote cat -- train.py` | 查看远端文件内容 | | `autodl-remote get train.py train.py` | 把远端文件拉到本地 | | `autodl-remote put train.py train.py` | 把本地文件上传到远端 | +| `autodl-remote put-run train.py -- python train.py` | 先上传文件,再执行远端命令 | | `autodl-remote sync-up ./src src` | 上传本地目录 | | `autodl-remote sync-down outputs outputs` | 下载远端目录 | | `autodl-remote exec -- python train.py` | 在远端执行命令 | +| `autodl-remote exec --script scripts/check.sh` | 上传并运行本地脚本,减少 shell quoting 问题 | +| `cat script.sh \| autodl-remote exec --stdin -- bash` | 从 stdin 上传多行脚本并运行 | | `autodl-remote exec --detach --name train -- python train.py` | 远端后台运行长任务 | -| `autodl-remote tail -- .autodl-remote/logs/train.log` | 查看远端日志 | +| `autodl-remote job list` | 列出本地记录的远端长任务 | +| `autodl-remote job status train` | 查看长任务 PID、日志、远端状态和退出码 | +| `autodl-remote job tail train` | 查看命名长任务日志 | +| `autodl-remote model-dir --mkdir` | 输出并创建项目模型目录 | +| `autodl-remote shutdown` | 请求远端关机,并把 SSH 断开视为可能成功 | ## 📌 Usage Patterns @@ -242,8 +256,21 @@ autodl-remote cat -- train.py autodl-remote get train.py train.py # Codex 在本地修改 train.py -autodl-remote put train.py train.py -autodl-remote exec -- python train.py +autodl-remote put-run train.py -- python train.py +``` + +### 复杂脚本或 heredoc + +```bash +autodl-remote exec --script scripts/remote_metrics.sh + +cat <<'SH' | autodl-remote exec --stdin -- bash +set -euo pipefail +pwd +python - <<'PY' +print("remote python ok") +PY +SH ``` ### 本地已有项目 @@ -251,19 +278,48 @@ autodl-remote exec -- python train.py ```bash autodl-remote bind --account autodl-gpu --remote /root/autodl-tmp/my-project autodl-remote sync-up ./src src -autodl-remote put train.py train.py -autodl-remote exec -- python train.py +autodl-remote put-run train.py -- python train.py ``` ### 远端训练任务 ```bash autodl-remote exec --detach --name train -- python train.py -autodl-remote tail -- .autodl-remote/logs/train.log +autodl-remote job status train +autodl-remote job tail train autodl-remote cat -- outputs/metrics.json autodl-remote get outputs/metrics.json outputs/metrics.json ``` +### 远端关机 + +```bash +autodl-remote shutdown +``` + +`shutdown` 会先执行 `sync`,再尝试 `shutdown`、`poweroff`、`halt`,最后通过 SSH 是否还能连接来判断关机是否生效。关机导致 SSH 返回 `255` 时,会按“连接被远端关闭,可能已关机”处理。 + +### 项目模型目录约定 + +```bash +autodl-remote model-dir +autodl-remote model-dir --mkdir +``` + +默认规则: + +- 如果远端路径在 `/root/autodl-tmp` 下,默认模型目录是 `/root/autodl-tmp/models`。 +- 其他远端路径默认使用 `/models`。 +- 可在 `.autodl-remote.conf` 里设置 `MODEL_DIR="/some/path"` 覆盖。 + +首次项目命令会在本地生成: + +```text +.autodl-remote/CONVENTIONS.md +``` + +这个文件用于记录模型位置、数据位置、日志位置、常用运行命令等项目约定。 + ## ✅ What This Plugin Does Not Do AutoDL Remote 保持克制,不做这些事: diff --git a/plugins/autodl-remote/.codex-plugin/plugin.json b/plugins/autodl-remote/.codex-plugin/plugin.json index 7c9caba..1a91870 100644 --- a/plugins/autodl-remote/.codex-plugin/plugin.json +++ b/plugins/autodl-remote/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "autodl-remote", - "version": "0.5.0", - "description": "Bash-only SSH account, command, upload, and download tool layer for AutoDL.", + "version": "0.6.0", + "description": "Bash-only SSH account, command, upload, download, job, and shutdown tool layer for AutoDL.", "author": { "name": "haibarazz", "url": "https://github.com/haibarazz/AutoDL-Remote" @@ -13,6 +13,7 @@ "autodl", "ssh", "remote-execution", + "remote-jobs", "rsync", "codex" ], @@ -20,7 +21,7 @@ "interface": { "displayName": "AutoDL Remote", "shortDescription": "Control AutoDL over SSH from local Codex.", - "longDescription": "Provides SSH account management, remote command execution, and explicit file upload/download primitives. Codex decides what to inspect, push, pull, and run.", + "longDescription": "Provides SSH account management, remote command execution, explicit file upload/download primitives, remote job tracking, script execution, and shutdown handling. Codex decides what to inspect, push, pull, and run.", "developerName": "haibarazz", "category": "Developer Tools", "capabilities": [ @@ -34,7 +35,9 @@ "defaultPrompt": [ "Use AutoDL Remote to inspect my remote project.", "Upload this edited file to AutoDL and run training.", - "Tail the latest AutoDL training log." + "Tail the latest AutoDL training log.", + "Check the status of my detached AutoDL job.", + "Shut down the remote AutoDL machine when the run is finished." ], "brandColor": "#2563EB" } diff --git a/plugins/autodl-remote/README.md b/plugins/autodl-remote/README.md index 87e9323..6edd523 100644 --- a/plugins/autodl-remote/README.md +++ b/plugins/autodl-remote/README.md @@ -60,22 +60,38 @@ Inspect and run: ```bash autodl-remote doctor +autodl-remote model-dir autodl-remote tree . --depth 2 autodl-remote exec -- pwd autodl-remote exec -- python train.py autodl-remote exec --detach --name train -- python train.py -autodl-remote tail -- .autodl-remote/logs/train.log +autodl-remote job status train +autodl-remote job tail train ``` Move files only when needed: ```bash autodl-remote put ./train.py train.py +autodl-remote put-run ./train.py -- python train.py autodl-remote get outputs/result.csv ./outputs/result.csv autodl-remote sync-up ./src src autodl-remote sync-down logs/train.log ./logs/train.log ``` +Run complex local scripts without shell quoting problems: + +```bash +autodl-remote exec --script scripts/remote_check.sh +cat scripts/remote_check.sh | autodl-remote exec --stdin -- bash +``` + +Shutdown the remote machine after a run: + +```bash +autodl-remote shutdown +``` + ## Accounts List accounts: @@ -129,8 +145,10 @@ autodl-remote account password-delete autodl-remote bind [--account name] --remote /remote/project/root autodl-remote doctor +autodl-remote model-dir [--mkdir] -autodl-remote exec [--detach] [--cwd path] [--name name] -- +autodl-remote exec [--detach] [--cwd path] [--name name] [--script local_script] [--stdin] -- +autodl-remote put-run ... -- autodl-remote put [remote-path] autodl-remote get [local-path] autodl-remote sync-up [remote-path] @@ -139,6 +157,10 @@ autodl-remote tree [remote-path] [--depth 3] [--limit 500] autodl-remote ls [remote-path] autodl-remote cat [--lines 200] -- autodl-remote tail [--lines 120] [--follow] -- +autodl-remote job list +autodl-remote job status +autodl-remote job tail [--lines 120] [--follow] +autodl-remote shutdown [--wait seconds] autodl-remote shell autodl-remote history ``` @@ -152,3 +174,6 @@ autodl-remote history - No automatic push before run. - Use `put/get/sync-up/sync-down` explicitly. - Keep large models, datasets, checkpoints, and training outputs on AutoDL unless the user explicitly pulls them. +- Use `exec --script` or `exec --stdin` for multi-line shell/Python snippets. +- Use `job status` and `job tail` for detached training jobs created with `exec --detach --name`. +- Use `shutdown` instead of guessing provider-specific poweroff commands. diff --git a/plugins/autodl-remote/bin/autodl-remote b/plugins/autodl-remote/bin/autodl-remote index 2de0708..eef6767 100755 --- a/plugins/autodl-remote/bin/autodl-remote +++ b/plugins/autodl-remote/bin/autodl-remote @@ -34,9 +34,11 @@ account: project: bind [--account name] --remote /remote/project/root doctor + model-dir [--mkdir] remote: - exec [--detach] [--cwd path] [--name name] [--allow-dangerous] -- + exec [--detach] [--cwd path] [--name name] [--script local_script] [--stdin] [--allow-dangerous] -- + put-run ... -- put [remote-path] get [local-path] sync-up [remote-path] @@ -45,6 +47,10 @@ remote: ls [remote-path] cat [--lines 200] -- tail [--lines 120] [--follow] -- + job list + job status + job tail [--lines 120] [--follow] + shutdown [--wait seconds] shell history [--lines 20] @@ -140,6 +146,79 @@ write_history() { printf '{"ts":"%s","action":"%s","detail":"%s"}\n' "$(date +%Y-%m-%dT%H:%M:%S%z)" "$action" "$escaped" >> "$file" } +sanitize_name() { + local name="$1" + name="$(printf '%s' "$name" | sed 's/[^A-Za-z0-9_.-]/-/g')" + [ -n "$name" ] || name="job-$(date +%Y%m%d-%H%M%S)" + printf '%s\n' "$name" +} + +config_quote() { + sed 's/\\/\\\\/g; s/"/\\"/g' < "$file" < "$file" </dev/null } +upload_local_file() { + local local_path="$1" remote_path="$2" + [ -f "$local_path" ] || die "Local script does not exist or is not a file: $local_path" + ensure_remote_dir "$(dirname "$remote_path")" + ssh_common_args + local scp_args=(-P "$ACCOUNT_PORT") + [ -n "$ACCOUNT_KEY" ] && scp_args+=(-i "$ACCOUNT_KEY") + run_transport scp "${scp_args[@]}" "$local_path" "$ACCOUNT_TARGET:$remote_path" +} + dangerous_command_check() { local cmd="$1" case "$cmd" in @@ -527,28 +625,65 @@ cmd_doctor() { cmd_exec() { load_project - local detach=false cwd="$REMOTE_ROOT" name="" allow_dangerous=false + local detach=false cwd="$REMOTE_ROOT" name="" allow_dangerous=false script="" stdin_mode=false remote_script="" while [ $# -gt 0 ]; do case "$1" in --detach) detach=true; shift ;; --cwd) cwd="$(remote_join "$2")"; shift 2 ;; --name) name="$2"; shift 2 ;; + --script) script="$2"; shift 2 ;; + --stdin) stdin_mode=true; shift ;; --allow-dangerous) allow_dangerous=true; shift ;; --) shift; break ;; *) break ;; esac done - [ $# -gt 0 ] || die "exec requires a command" - local cmd="$*" + [ "$stdin_mode" != "true" ] || [ -z "$script" ] || die "Use either --script or --stdin, not both." + + local cmd="" tmp_name result pid status_path remote_cmd detach_rc + if [ -n "$script" ]; then + local local_script + local_script="$(local_join "$script")" + tmp_name="$(date +%Y%m%d-%H%M%S)-$(basename "$script" | sed 's/[^A-Za-z0-9_.-]/-/g')" + remote_script="$REMOTE_ROOT/.autodl-remote/tmp/$tmp_name" + upload_local_file "$local_script" "$remote_script" + if [ $# -gt 0 ]; then + cmd="$* $(shell_quote "$remote_script")" + else + cmd="bash $(shell_quote "$remote_script")" + fi + elif [ "$stdin_mode" = "true" ]; then + [ $# -gt 0 ] || die "exec --stdin requires a command after --, for example: exec --stdin -- bash" + local tmp_local + tmp_local="$(mktemp "${TMPDIR:-/tmp}/autodl-remote-stdin.XXXXXX")" || die "mktemp failed" + cat > "$tmp_local" + tmp_name="$(date +%Y%m%d-%H%M%S)-stdin.sh" + remote_script="$REMOTE_ROOT/.autodl-remote/tmp/$tmp_name" + upload_local_file "$tmp_local" "$remote_script" + rm -f "$tmp_local" + cmd="$* < $(shell_quote "$remote_script")" + else + [ $# -gt 0 ] || die "exec requires a command" + cmd="$*" + fi + if [ "$allow_dangerous" != "true" ]; then dangerous_command_check "$cmd" || die "Dangerous remote command blocked. Re-run with --allow-dangerous after explicit approval." fi [ -n "$name" ] || name="exec-$(date +%Y%m%d-%H%M%S)" - name="$(printf '%s' "$name" | sed 's/[^A-Za-z0-9_.-]/-/g')" + name="$(sanitize_name "$name")" local log="$REMOTE_ROOT/.autodl-remote/logs/$name.log" + status_path="$REMOTE_ROOT/.autodl-remote/jobs/$name.status" info "log=$log" if [ "$detach" = "true" ]; then - ssh_capture "mkdir -p $(shell_quote "$(dirname "$log")") && cd $(shell_quote "$cwd") && nohup bash -lc $(shell_quote "$cmd") > $(shell_quote "$log") 2>&1 < /dev/null & printf 'pid=%s\nlog=%s\n' \"\$!\" $(shell_quote "$log")" + remote_cmd="mkdir -p $(shell_quote "$(dirname "$log")") $(shell_quote "$(dirname "$status_path")") && ( started_at=\$(date +%Y-%m-%dT%H:%M:%S%z); pid=\$BASHPID; printf 'status=running\npid=%s\nstarted_at=%s\nlog=%s\ncwd=%s\n' \"\$pid\" \"\$started_at\" $(shell_quote "$log") $(shell_quote "$cwd") > $(shell_quote "$status_path"); cd $(shell_quote "$cwd") && bash -lc $(shell_quote "$cmd") > $(shell_quote "$log") 2>&1; rc=\$?; finished_at=\$(date +%Y-%m-%dT%H:%M:%S%z); printf 'status=exited\npid=%s\nstarted_at=%s\nfinished_at=%s\nexit_code=%s\nlog=%s\ncwd=%s\n' \"\$pid\" \"\$started_at\" \"\$finished_at\" \"\$rc\" $(shell_quote "$log") $(shell_quote "$cwd") > $(shell_quote "$status_path"); ) < /dev/null & printf 'pid=%s\nlog=%s\nstatus=%s\n' \"\$!\" $(shell_quote "$log") $(shell_quote "$status_path")" + result="$(ssh_capture "bash -lc $(shell_quote "$remote_cmd")" 2>&1)" + detach_rc=$? + printf '%s\n' "$result" + [ "$detach_rc" -eq 0 ] || return "$detach_rc" + pid="$(printf '%s\n' "$result" | awk -F= '$1=="pid"{print $2; exit}')" + [ -n "$pid" ] || die "Remote detach did not return a pid." + write_job "$name" "$pid" "$log" "$status_path" "$cwd" "$cmd" write_history exec-detach "$cmd log=$log" else local inner rc @@ -703,6 +838,159 @@ cmd_tail() { ssh_run "tail -n $lines $flag $(shell_quote "$(remote_join "$1")")" } +cmd_job_list() { + load_project + local dir file name pid started log command seen=false + dir="$(jobs_dir)" + mkdir -p "$dir" + if ls "$dir"/*.conf >/dev/null 2>&1; then + printf '%-24s %-10s %-24s %s\n' "name" "pid" "started_at" "log" + for file in "$dir"/*.conf; do + seen=true + name="$(job_get "$file" NAME)" + pid="$(job_get "$file" PID)" + started="$(job_get "$file" STARTED_AT)" + log="$(job_get "$file" LOG_PATH)" + command="$(job_get "$file" COMMAND)" + printf '%-24s %-10s %-24s %s\n' "$name" "$pid" "$started" "$log" + [ -n "$command" ] && printf ' command: %s\n' "$command" + done + fi + [ "$seen" = "true" ] || info "No jobs recorded." +} + +cmd_job_status() { + load_project + [ $# -gt 0 ] || die "job status requires a name" + local name file pid log status_path cwd command remote_status rc + name="$(sanitize_name "$1")" + file="$(job_path "$name")" + [ -f "$file" ] || die "Job not found: $name" + pid="$(job_get "$file" PID)" + log="$(job_get "$file" LOG_PATH)" + status_path="$(job_get "$file" STATUS_PATH)" + cwd="$(job_get "$file" CWD)" + command="$(job_get "$file" COMMAND)" + [ -n "$pid" ] || die "Job has no recorded pid: $name" + + info "name=$name" + info "pid=$pid" + info "cwd=$cwd" + info "log=$log" + info "status_path=$status_path" + info "command=$command" + remote_status="$(ssh_capture "if [ -f $(shell_quote "$status_path") ]; then cat $(shell_quote "$status_path"); elif kill -0 $(shell_quote "$pid") 2>/dev/null; then printf 'status=running\npid=%s\n' $(shell_quote "$pid"); else printf 'status=unknown\npid=%s\n' $(shell_quote "$pid"); fi; if kill -0 $(shell_quote "$pid") 2>/dev/null; then echo alive=yes; else echo alive=no; fi" 2>&1)" + rc=$? + if [ "$rc" -eq 0 ]; then + printf '%s\n' "$remote_status" + else + info "remote_status=unreachable" + printf '%s\n' "$remote_status" >&2 + return "$rc" + fi +} + +cmd_job_tail() { + load_project + local lines=120 follow=false + while [ $# -gt 0 ]; do + case "$1" in + --lines) lines="$2"; shift 2 ;; + --follow|-f) follow=true; shift ;; + --) shift; break ;; + *) break ;; + esac + done + [ $# -gt 0 ] || die "job tail requires a name" + local name file log flag="" + name="$(sanitize_name "$1")" + file="$(job_path "$name")" + [ -f "$file" ] || die "Job not found: $name" + log="$(job_get "$file" LOG_PATH)" + [ "$follow" = "true" ] && flag="-f" + ssh_run "tail -n $lines $flag $(shell_quote "$log")" +} + +cmd_job() { + [ $# -gt 0 ] || die "job requires a subcommand" + local sub="$1" + shift + case "$sub" in + list|ls) cmd_job_list "$@" ;; + status) cmd_job_status "$@" ;; + tail) cmd_job_tail "$@" ;; + *) die "unknown job command: $sub" ;; + esac +} + +cmd_shutdown() { + load_project + local wait_seconds=8 output rc + while [ $# -gt 0 ]; do + case "$1" in + --wait) wait_seconds="$2"; shift 2 ;; + *) die "unknown shutdown option: $1" ;; + esac + done + + info "Requesting remote shutdown for $ACCOUNT_TARGET:$ACCOUNT_PORT" + output="$(ssh_capture "sync || true; if command -v shutdown >/dev/null 2>&1; then echo method=shutdown; nohup shutdown >/dev/null 2>&1 /dev/null 2>&1; then echo method=poweroff; nohup poweroff >/dev/null 2>&1 /dev/null 2>&1; then echo method=halt; nohup halt >/dev/null 2>&1 &1)" + rc=$? + [ -n "$output" ] && printf '%s\n' "$output" + + if [ "$rc" -ne 0 ] && [ "$rc" -ne 255 ]; then + info "shutdown_request=failed rc=$rc" + return "$rc" + fi + if [ "$rc" -eq 255 ]; then + info "shutdown_request=connection_closed rc=255" + else + info "shutdown_request=sent rc=$rc" + fi + + sleep "$wait_seconds" + if ssh_capture "true" >/dev/null 2>&1; then + info "shutdown_status=still_reachable" + info "Remote is still reachable; shutdown may not have taken effect." + return 1 + fi + info "shutdown_status=disconnected" + info "SSH is disconnected; remote is likely shutting down or already powered off." + write_history shutdown "wait=$wait_seconds rc=$rc" +} + +cmd_model_dir() { + load_project + local create=false + while [ $# -gt 0 ]; do + case "$1" in + --mkdir) create=true; shift ;; + *) die "unknown model-dir option: $1" ;; + esac + done + if [ "$create" = "true" ]; then + ensure_remote_dir "$PROJECT_MODEL_DIR" + fi + info "$PROJECT_MODEL_DIR" +} + +cmd_put_run() { + local uploads=() + while [ $# -gt 0 ]; do + case "$1" in + --) shift; break ;; + *) uploads+=("$1"); shift ;; + esac + done + [ "${#uploads[@]}" -gt 0 ] || die "put-run requires at least one local path before --" + [ $# -gt 0 ] || die "put-run requires a command after --" + local path + for path in "${uploads[@]}"; do + cmd_put "$path" + done + cmd_exec -- "$@" +} + cmd_shell() { load_project ssh_common_args @@ -732,13 +1020,17 @@ main() { account) cmd_account "$@" ;; bind) cmd_bind "$@" ;; doctor) cmd_doctor "$@" ;; + model-dir) cmd_model_dir "$@" ;; exec) cmd_exec "$@" ;; + put-run) cmd_put_run "$@" ;; put|sync-up) cmd_put "$@" ;; get|sync-down) cmd_get "$@" ;; tree) cmd_tree "$@" ;; ls) cmd_ls "$@" ;; cat) cmd_cat "$@" ;; tail) cmd_tail "$@" ;; + job) cmd_job "$@" ;; + shutdown) cmd_shutdown "$@" ;; shell) cmd_shell "$@" ;; history) cmd_history "$@" ;; *) die "unknown command: $cmd" ;; diff --git a/plugins/autodl-remote/skills/autodl-remote/SKILL.md b/plugins/autodl-remote/skills/autodl-remote/SKILL.md index 49534eb..82f1e33 100644 --- a/plugins/autodl-remote/skills/autodl-remote/SKILL.md +++ b/plugins/autodl-remote/skills/autodl-remote/SKILL.md @@ -15,6 +15,8 @@ Use this skill when the user wants Codex to work locally while controlling AutoD - Codex decides the smallest useful action each time: inspect, upload, download, run, or tail logs. - Do not download LLM weights, checkpoints, datasets, or large outputs unless the user explicitly asks. - Prefer reading remote files/logs in place with `cat`, `tail`, `ls`, and `tree`. +- Use detached job commands for long training runs instead of manually guessing process state. +- Use `shutdown` when the user asks to stop the remote machine; treat SSH disconnect during shutdown as likely success. ## Setup @@ -38,12 +40,14 @@ The project config should only contain `ACCOUNT` and `REMOTE_ROOT`. ```bash autodl-remote doctor +autodl-remote model-dir autodl-remote tree . --depth 2 autodl-remote ls . autodl-remote cat -- README.md autodl-remote tail -- logs/train.log autodl-remote put ./train.py train.py +autodl-remote put-run ./train.py -- python train.py autodl-remote get outputs/result.csv ./outputs/result.csv autodl-remote sync-up ./src src autodl-remote sync-down logs/train.log ./logs/train.log @@ -51,6 +55,12 @@ autodl-remote sync-down logs/train.log ./logs/train.log autodl-remote exec -- pwd autodl-remote exec -- python train.py autodl-remote exec --detach --name train -- python train.py +autodl-remote exec --script scripts/remote_check.sh +cat scripts/remote_check.sh | autodl-remote exec --stdin -- bash +autodl-remote job list +autodl-remote job status train +autodl-remote job tail train +autodl-remote shutdown ``` ## Behavior Rules @@ -60,4 +70,9 @@ autodl-remote exec --detach --name train -- python train.py - After remote execution, inspect logs remotely first; pull only small result files when useful. - If both local and remote have similar files, do not assume one should overwrite the other. Compare or inspect first, then choose `put` or `get`. - Use `exec --detach` for long training jobs and then `tail` the log path. +- Prefer `job status ` and `job tail ` for detached jobs created with `--name`. +- Prefer `exec --script` or `exec --stdin` for complex multi-line commands, Python heredocs, env vars, JSON, awk, or sed. +- Use `put-run` when the workflow is "upload these local paths, then immediately run this remote command". +- `model-dir` prints the project model directory convention. The first project command creates `.autodl-remote/CONVENTIONS.md` locally for project notes. +- `shutdown` first syncs the remote filesystem, then tries provider-safe shutdown methods, waits, and verifies that SSH disconnects. - Treat broad destructive commands as dangerous; require explicit user approval before using `--allow-dangerous`.