easy-command 1.0.4 → 2.0.1

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/README.md CHANGED
@@ -1,109 +1,104 @@
1
- # easy-command
2
-
3
- One command to configure a polished Zsh terminal with Oh My Zsh, the `agnoster` theme, Git status, command suggestions, Tab completion, and syntax highlighting.
1
+ <p align="right">
2
+ <strong>ENGLISH</strong> | <a href="README_CN.md">中文</a>
3
+ </p>
4
4
 
5
- > 一条命令配置统一的 Zsh 终端:Oh My Zsh、`agnoster` 主题、Git 状态、历史命令建议、Tab 补全与命令语法高亮。
5
+ # easy-command
6
6
 
7
- ## Preview
7
+ Configure a polished Zsh terminal in one command.
8
8
 
9
- > 效果预览。
9
+ `easy-command` installs and configures Oh My Zsh, the `agnoster` theme, Git status, command suggestions, Tab completion, and syntax highlighting. It preserves your existing `.zshrc` content and creates a backup before every change.
10
10
 
11
- ### Git branch and working-tree status
11
+ ## INSTALL
12
12
 
13
- > Git 分支与工作区状态。
13
+ ### npm
14
14
 
15
- ![agnoster displays Git branch and working-tree status](assets/git-status.png)
16
-
17
- ### History-based command suggestions
15
+ ```bash
16
+ npx easy-command -y
17
+ ```
18
18
 
19
- > 历史命令自动建议。
19
+ ### GitHub
20
20
 
21
- ![A suggested history command appears while typing](assets/autosuggestions.png)
21
+ No Node.js or npm is required:
22
22
 
23
- ### Syntax highlighting and error feedback
23
+ ```bash
24
+ curl -fsSL https://raw.githubusercontent.com/UnionSchool/easy-command/main/install.sh | bash -s -- -y
25
+ ```
24
26
 
25
- > 命令语法高亮与错误提示。
27
+ When installation finishes, open a new terminal session or run:
26
28
 
27
- ![Valid commands are green and unknown commands are red](assets/syntax-highlighting.png)
29
+ ```bash
30
+ exec zsh -l
31
+ ```
28
32
 
29
- ## Quick start
33
+ ## Everyday commands
30
34
 
31
- > 快速开始。
35
+ ```bash
36
+ # Preview installation without changing your machine
37
+ easy-command --dry-run
32
38
 
33
- Install the latest version:
39
+ # Check Shell, dependencies, managed repositories, and .zshrc configuration
40
+ easy-command doctor
34
41
 
35
- > 安装最新版本:
42
+ # Restore missing managed files and configuration
43
+ easy-command repair -y
36
44
 
37
- ```bash
38
- npx easy-command --yes
45
+ # Safely update Oh My Zsh and managed plugins
46
+ easy-command update -y
39
47
  ```
40
48
 
41
- Or install directly from GitHub without Node.js/npm:
49
+ ## What you get
42
50
 
43
- > 或者无需 Node.js/npm,直接从 GitHub 安装:
51
+ - A clean `agnoster` prompt with your user, path, Git branch, and working-tree status.
52
+ - History-based command suggestions. Press `→` or `Ctrl+E` to accept one.
53
+ - Syntax highlighting: valid commands are green; unknown commands are bold red.
54
+ - Native Zsh completion. Press `Tab` to complete; press it twice to view candidates.
55
+ - Shared history across terminal sessions, retaining the latest 10,000 commands.
56
+ - Built-in preview, diagnostics, repair, and update commands for easy maintenance.
44
57
 
45
- ```bash
46
- curl -fsSL https://raw.githubusercontent.com/UnionSchool/easy-command/main/install.sh | bash -s -- --yes
47
- ```
58
+ ## Preview
48
59
 
49
- After installation, sign in again or run:
60
+ ### Git branch and working-tree status
50
61
 
51
- > 安装完成后重新登录,或运行:
62
+ ![agnoster displays Git branch and working-tree status](assets/git-status.png)
52
63
 
53
- ```bash
54
- exec zsh -l
55
- ```
64
+ ### History-based command suggestions
56
65
 
57
- ## What you get
66
+ ![A suggested history command appears while typing](assets/autosuggestions.png)
58
67
 
59
- > 提供的能力。
68
+ ### Syntax highlighting and error feedback
60
69
 
61
- - `agnoster`: shows the user, current path, Git branch, and working-tree status.<br>
62
- `agnoster`:显示用户名、路径、Git 分支和工作区状态。
63
- - `zsh-autosuggestions`: shows gray suggestions from command history; press `→` or `Ctrl+E` to accept.<br>
64
- `zsh-autosuggestions`:从历史命令显示灰色建议,按 `→` 或 `Ctrl+E` 接受。
65
- - `zsh-syntax-highlighting`: valid commands are green; unknown commands are bold red.<br>
66
- `zsh-syntax-highlighting`:有效命令绿色、未知命令红色加粗。
67
- - Native Zsh completion: press `Tab` to complete commands, paths, and Git subcommands; press it twice for candidates.<br>
68
- Zsh 原生补全:按 `Tab` 补全命令、路径和 Git 子命令,连按两次显示候选项。
69
- - Shared history: the latest 10,000 commands are shared between terminal sessions.<br>
70
- 共享历史:多个终端会话共享最近 10,000 条命令。
70
+ ![Valid commands are green and unknown commands are red](assets/syntax-highlighting.png)
71
71
 
72
72
  ## Options
73
73
 
74
- > 参数。
75
-
76
74
  ```bash
77
- # Configure a specific user / 指定目标用户
78
- bash install.sh --yes --user bell
75
+ # Configure a specific user
76
+ bash install.sh -y --user bell
79
77
 
80
- # Keep the current login shell / 不改变默认登录 Shell
81
- bash install.sh --yes --no-chsh
78
+ # Keep the current login shell
79
+ bash install.sh -y --no-chsh
82
80
 
83
- # Remove only the configuration block managed by easy-command / 删除本工具管理的 .zshrc 区块
81
+ # Remove only the configuration block managed by easy-command
84
82
  bash install.sh --uninstall
83
+
84
+ # Enable optional directory jumping, fuzzy search, and Git aliases
85
+ bash install.sh -y --with-zoxide --with-fzf --with-git-aliases
85
86
  ```
86
87
 
87
- ## Supported platforms
88
+ `zoxide` enables `z <keyword>` directory jumping. `fzf` adds fuzzy history and file searching. Git aliases use the non-conflicting `ec-` prefix, such as `git ec-status`; existing aliases are never overwritten.
88
89
 
89
- > 支持范围。
90
+ ## Requirements
90
91
 
91
- - macOS: Homebrew must already be installed.<br>
92
- macOS:要求已安装 Homebrew。
93
- - Ubuntu / Debian: dependencies are installed with `apt-get`.<br>
94
- Ubuntu / Debian:使用 `apt-get` 安装依赖。
92
+ - macOS: Homebrew must already be installed.
93
+ - Ubuntu / Debian: dependencies are installed with `apt-get`.
95
94
 
96
95
  The script needs `sudo` to install packages and change the default shell. Without sudo access, use `--no-chsh` and have an administrator install Zsh first.
97
96
 
98
- > 脚本需要 `sudo` 来安装软件包和切换默认 Shell。没有 sudo 权限时,可加 `--no-chsh`,并由管理员预先安装 Zsh。
99
-
100
97
  ## Configuration and rollback
101
98
 
102
- > 配置与回退。
103
-
104
- Installed files:
99
+ The installer manages only the marked `easy-command` block in `~/.zshrc`. Before each update, it creates a timestamped backup. Running `--uninstall` removes only that managed block; it does not remove Oh My Zsh, plugins, command history, or other personal settings.
105
100
 
106
- > 安装内容位于:
101
+ Installed components:
107
102
 
108
103
  ```text
109
104
  ~/.easy-command/oh-my-zsh
@@ -111,38 +106,26 @@ Installed files:
111
106
  ~/.zsh_history
112
107
  ```
113
108
 
114
- Before each `.zshrc` update, the script creates a timestamped backup. `--uninstall` removes only the configuration block managed by easy-command; it does not remove Oh My Zsh, plugins, or other user settings.
115
-
116
- > 每次更新 `.zshrc` 前,脚本会创建带时间戳的备份。`--uninstall` 只删除由 easy-command 管理的配置块,不会删除 Oh My Zsh、插件或用户自己的其他配置。
117
-
118
109
  ## Security
119
110
 
120
- > 安全说明。
121
-
122
111
  For a first-time trial, download and inspect the script before running it:
123
112
 
124
- > 首次试用建议先下载并审阅脚本:
125
-
126
113
  ```bash
127
114
  curl -fsSLO https://raw.githubusercontent.com/UnionSchool/easy-command/main/install.sh
128
115
  less install.sh
129
- bash install.sh --yes
116
+ bash install.sh -y
130
117
  ```
131
118
 
132
119
  For production, pin a release tag instead of running `main` directly:
133
120
 
134
- > 生产环境建议固定到发布标签,而不是直接使用 `main`:
135
-
136
121
  ```bash
137
- npx easy-command@1.0.4 --yes
122
+ npx easy-command@2.0.1 -y
138
123
  ```
139
124
 
140
125
  Or, without npm:
141
126
 
142
- > 或者不使用 npm:
143
-
144
127
  ```bash
145
- curl -fsSL https://raw.githubusercontent.com/UnionSchool/easy-command/v1.0.4/install.sh | bash -s -- --yes
128
+ curl -fsSL https://raw.githubusercontent.com/UnionSchool/easy-command/v2.0.1/install.sh | bash -s -- -y
146
129
  ```
147
130
 
148
131
  ## License
package/README_CN.md ADDED
@@ -0,0 +1,133 @@
1
+ <p align="right">
2
+ <a href="README.md">ENGLISH</a> | <strong>中文</strong>
3
+ </p>
4
+
5
+ # easy-command
6
+
7
+ 一条命令配置更顺手的 Zsh 终端。
8
+
9
+ `easy-command` 会安装并配置 Oh My Zsh、`agnoster` 主题、Git 状态、命令建议、Tab 补全和语法高亮。每次修改 `.zshrc` 前都会备份,并保留用户已有的配置内容。
10
+
11
+ ## 安装
12
+
13
+ ### npm 安装
14
+
15
+ ```bash
16
+ npx easy-command -y
17
+ ```
18
+
19
+ ### GitHub 安装
20
+
21
+ 无需安装 Node.js 或 npm:
22
+
23
+ ```bash
24
+ curl -fsSL https://raw.githubusercontent.com/UnionSchool/easy-command/main/install.sh | bash -s -- -y
25
+ ```
26
+
27
+ 安装完成后,重新打开终端,或执行:
28
+
29
+ ```bash
30
+ exec zsh -l
31
+ ```
32
+
33
+ ## 日常命令
34
+
35
+ ```bash
36
+ # 预览安装过程,不修改本机环境
37
+ easy-command --dry-run
38
+
39
+ # 检查 Shell、依赖、托管仓库和 .zshrc 配置
40
+ easy-command doctor
41
+
42
+ # 恢复缺失的托管文件和配置
43
+ easy-command repair -y
44
+
45
+ # 安全更新 Oh My Zsh 和托管插件
46
+ easy-command update -y
47
+ ```
48
+
49
+ ## 安装后即可使用
50
+
51
+ - `agnoster` 提示符:显示用户名、当前路径、Git 分支和工作区状态。
52
+ - 历史命令建议:输入时显示灰色建议,按 `→` 或 `Ctrl+E` 接受。
53
+ - 语法高亮:有效命令为绿色,未知命令为红色加粗。
54
+ - 原生 Zsh 补全:按 `Tab` 补全命令、路径和 Git 子命令;连按两次显示候选项。
55
+ - 多终端共享历史:保留最近 10,000 条命令。
56
+ - 内置预览、诊断、修复和更新命令,后续维护更简单。
57
+
58
+ ## 效果预览
59
+
60
+ ### Git 分支与工作区状态
61
+
62
+ ![agnoster displays Git branch and working-tree status](assets/git-status.png)
63
+
64
+ ### 历史命令自动建议
65
+
66
+ ![A suggested history command appears while typing](assets/autosuggestions.png)
67
+
68
+ ### 命令语法高亮与错误提示
69
+
70
+ ![Valid commands are green and unknown commands are red](assets/syntax-highlighting.png)
71
+
72
+ ## 常用参数
73
+
74
+ ```bash
75
+ # 指定目标用户
76
+ bash install.sh -y --user bell
77
+
78
+ # 不修改默认登录 Shell
79
+ bash install.sh -y --no-chsh
80
+
81
+ # 仅删除 easy-command 管理的 .zshrc 配置区块
82
+ bash install.sh --uninstall
83
+
84
+ # 启用可选的目录跳转、模糊搜索和 Git 快捷操作
85
+ bash install.sh -y --with-zoxide --with-fzf --with-git-aliases
86
+ ```
87
+
88
+ `zoxide` 提供 `z <关键词>` 目录跳转;`fzf` 提供历史命令和文件的模糊搜索;Git 快捷操作使用 `ec-` 前缀,例如 `git ec-status`,不会覆盖已有别名。
89
+
90
+ ## 系统要求
91
+
92
+ - macOS:需预先安装 Homebrew。
93
+ - Ubuntu / Debian:脚本会使用 `apt-get` 安装依赖。
94
+
95
+ 脚本需要 `sudo` 权限安装软件包和切换默认 Shell。没有 sudo 权限时,请使用 `--no-chsh`,并由管理员预先安装 Zsh。
96
+
97
+ ## 配置与回退
98
+
99
+ 安装器只管理 `~/.zshrc` 中带有 `easy-command` 标记的配置区块。每次更新前会创建带时间戳的备份。执行 `--uninstall` 仅删除该托管区块,不会删除 Oh My Zsh、插件、命令历史或其他个人配置。
100
+
101
+ 安装内容:
102
+
103
+ ```text
104
+ ~/.easy-command/oh-my-zsh
105
+ ~/.zshrc
106
+ ~/.zsh_history
107
+ ```
108
+
109
+ ## 安全说明
110
+
111
+ 首次使用时,建议先下载并审阅脚本:
112
+
113
+ ```bash
114
+ curl -fsSLO https://raw.githubusercontent.com/UnionSchool/easy-command/main/install.sh
115
+ less install.sh
116
+ bash install.sh -y
117
+ ```
118
+
119
+ 生产环境建议固定到发布标签,而不是直接使用 `main`:
120
+
121
+ ```bash
122
+ npx easy-command@2.0.1 -y
123
+ ```
124
+
125
+ 或者不使用 npm:
126
+
127
+ ```bash
128
+ curl -fsSL https://raw.githubusercontent.com/UnionSchool/easy-command/v2.0.1/install.sh | bash -s -- -y
129
+ ```
130
+
131
+ ## 许可
132
+
133
+ [MIT](LICENSE)
Binary file
Binary file
Binary file
package/install.sh CHANGED
@@ -2,59 +2,68 @@
2
2
 
3
3
  set -Eeuo pipefail
4
4
 
5
- readonly VERSION='1.0.4'
5
+ readonly VERSION='2.0.1'
6
6
  readonly BEGIN_MARKER='# >>> easy-command zsh >>>'
7
7
  readonly END_MARKER='# <<< easy-command zsh <<<'
8
+ readonly BASE_DIR_NAME='.easy-command'
8
9
 
10
+ ACTION='install'
9
11
  ASSUME_YES=false
10
12
  CHANGE_LOGIN_SHELL=true
11
- UNINSTALL=false
13
+ DRY_RUN=false
14
+ ENABLE_ZOXIDE='auto'
15
+ ENABLE_FZF='auto'
16
+ ENABLE_GIT_ALIASES='auto'
12
17
  TARGET_USER="${SUDO_USER:-${USER}}"
18
+ TARGET_HOME=''
13
19
 
14
20
  info() { printf '\033[1;32m[easy-command]\033[0m %s\n' "$*"; }
15
21
  warn() { printf '\033[1;33m[easy-command]\033[0m %s\n' "$*" >&2; }
16
22
  fail() { printf '\033[1;31m[easy-command]\033[0m %s\n' "$*" >&2; exit 1; }
23
+ ok() { printf '\033[1;32m[ok]\033[0m %s\n' "$*"; }
24
+ check_warning() { printf '\033[1;33m[warn]\033[0m %s\n' "$*"; }
25
+ check_error() { printf '\033[1;31m[error]\033[0m %s\n' "$*"; }
17
26
 
18
27
  usage() {
19
28
  cat <<'EOF'
20
- Usage: install.sh [options]
29
+ Usage: easy-command [command] [options]
30
+
31
+ Commands:
32
+ install Install and configure easy-command (default).
33
+ doctor Check the Shell, dependencies, repositories and .zshrc block.
34
+ repair Restore missing dependencies, repositories and managed configuration.
35
+ update Safely update managed repositories and refresh managed configuration.
21
36
 
22
37
  Options:
23
- --yes, -y Run without confirmation.
24
- --user USER Configure this user. Defaults to the invoking user.
25
- --no-chsh Do not change the user's default login shell to zsh.
26
- --uninstall Remove only the easy-command managed .zshrc block.
27
- --help, -h Show this help.
38
+ --yes, -y Run without confirmation.
39
+ --user USER Configure this user. Defaults to the invoking user.
40
+ --no-chsh Do not change the user's default login shell to zsh.
41
+ --dry-run Preview changes without installing, writing files, or using sudo.
42
+ --with-zoxide Enable optional zoxide directory jumping.
43
+ --without-zoxide Disable zoxide in the managed configuration.
44
+ --with-fzf Enable optional fzf history and file searching.
45
+ --without-fzf Disable fzf in the managed configuration.
46
+ --with-git-aliases Add non-conflicting global Git aliases prefixed with ec-.
47
+ --without-git-aliases
48
+ Remove global Git aliases managed by easy-command.
49
+ --uninstall Remove only the easy-command managed .zshrc block.
50
+ --help, -h Show this help.
28
51
  EOF
29
52
  }
30
53
 
31
- while (($#)); do
32
- case "$1" in
33
- --yes|-y) ASSUME_YES=true ;;
34
- --user)
35
- shift
36
- (($#)) || fail '--user requires a value.'
37
- TARGET_USER="$1"
38
- ;;
39
- --no-chsh) CHANGE_LOGIN_SHELL=false ;;
40
- --uninstall) UNINSTALL=true ;;
41
- --help|-h)
42
- usage
43
- exit 0
44
- ;;
45
- *) fail "Unknown option: $1" ;;
46
- esac
47
- shift
48
- done
49
-
50
- TARGET_HOME="$(getent passwd "$TARGET_USER" | cut -d: -f6 2>/dev/null || true)"
51
- if [[ -z "$TARGET_HOME" && "$(uname -s)" == 'Darwin' ]]; then
52
- TARGET_HOME="$(dscl . -read "/Users/$TARGET_USER" NFSHomeDirectory | awk '{print $2}')"
53
- fi
54
- [[ -n "$TARGET_HOME" && -d "$TARGET_HOME" ]] || fail "Cannot determine home directory for $TARGET_USER."
54
+ command_preview() {
55
+ local output=''
56
+ local part
57
+ for part in "$@"; do
58
+ output+="$(printf '%q' "$part") "
59
+ done
60
+ info "[dry-run] ${output% }"
61
+ }
55
62
 
56
63
  run_as_root() {
57
- if [[ "$EUID" -eq 0 ]]; then
64
+ if "$DRY_RUN"; then
65
+ command_preview sudo "$@"
66
+ elif [[ "$EUID" -eq 0 ]]; then
58
67
  "$@"
59
68
  elif command -v sudo >/dev/null 2>&1; then
60
69
  sudo "$@"
@@ -64,64 +73,195 @@ run_as_root() {
64
73
  }
65
74
 
66
75
  run_as_user() {
67
- if [[ "$EUID" -eq 0 && "$TARGET_USER" != 'root' ]]; then
68
- runuser -u "$TARGET_USER" -- "$@"
76
+ if "$DRY_RUN"; then
77
+ if [[ "$EUID" -eq 0 && "$TARGET_USER" != 'root' ]]; then
78
+ if command -v runuser >/dev/null 2>&1; then
79
+ command_preview runuser -u "$TARGET_USER" -- "$@"
80
+ else
81
+ command_preview sudo -u "$TARGET_USER" -- "$@"
82
+ fi
83
+ else
84
+ command_preview "$@"
85
+ fi
86
+ elif [[ "$EUID" -eq 0 && "$TARGET_USER" != 'root' ]]; then
87
+ if command -v runuser >/dev/null 2>&1; then
88
+ runuser -u "$TARGET_USER" -- "$@"
89
+ elif command -v sudo >/dev/null 2>&1; then
90
+ sudo -u "$TARGET_USER" -- "$@"
91
+ else
92
+ fail 'runuser or sudo is required to run commands as the target user.'
93
+ fi
69
94
  else
70
95
  "$@"
71
96
  fi
72
97
  }
73
98
 
99
+ write_file() {
100
+ local source_file="$1"
101
+ local destination_file="$2"
102
+
103
+ if "$DRY_RUN"; then
104
+ command_preview cp "$source_file" "$destination_file"
105
+ return
106
+ fi
107
+
108
+ cp "$source_file" "$destination_file"
109
+ chown "$TARGET_USER" "$destination_file" 2>/dev/null || true
110
+ }
111
+
112
+ determine_target_home() {
113
+ TARGET_HOME="$(getent passwd "$TARGET_USER" | cut -d: -f6 2>/dev/null || true)"
114
+ if [[ -z "$TARGET_HOME" && "$(uname -s)" == 'Darwin' ]]; then
115
+ TARGET_HOME="$(dscl . -read "/Users/$TARGET_USER" NFSHomeDirectory | awk '{print $2}')"
116
+ fi
117
+ [[ -n "$TARGET_HOME" && -d "$TARGET_HOME" ]] || fail "Cannot determine home directory for $TARGET_USER."
118
+ }
119
+
74
120
  backup_file() {
75
121
  local file="$1"
76
122
  [[ -f "$file" ]] || return 0
77
123
 
78
124
  local backup="${file}.easy-command-backup-$(date +%Y%m%d%H%M%S)"
79
- cp "$file" "$backup"
125
+ local sequence=1
126
+ while [[ -e "$backup" ]]; do
127
+ backup="${file}.easy-command-backup-$(date +%Y%m%d%H%M%S)-${sequence}"
128
+ sequence=$((sequence + 1))
129
+ done
130
+ if "$DRY_RUN"; then
131
+ command_preview cp "$file" "$backup"
132
+ else
133
+ cp "$file" "$backup"
134
+ chown "$TARGET_USER" "$backup" 2>/dev/null || true
135
+ fi
80
136
  info "Backed up $file to $backup"
81
137
  }
82
138
 
83
- remove_managed_block() {
139
+ managed_block_is_valid() {
84
140
  local zshrc="$TARGET_HOME/.zshrc"
85
141
  [[ -f "$zshrc" ]] || return 0
86
142
 
87
- local temp_file
88
- temp_file="$(mktemp)"
89
- awk -v begin="$BEGIN_MARKER" -v end="$END_MARKER" '
90
- $0 == begin { skipping = 1; next }
91
- $0 == end { skipping = 0; next }
92
- !skipping { print }
93
- ' "$zshrc" > "$temp_file"
94
- cat "$temp_file" > "$zshrc"
95
- rm -f "$temp_file"
96
- chown "$TARGET_USER" "$zshrc" 2>/dev/null || true
143
+ local begin_count end_count
144
+ begin_count="$(grep -Fxc "$BEGIN_MARKER" "$zshrc" || true)"
145
+ end_count="$(grep -Fxc "$END_MARKER" "$zshrc" || true)"
146
+ [[ "$begin_count" == '0' && "$end_count" == '0' ]] || [[ "$begin_count" == '1' && "$end_count" == '1' ]]
147
+ }
148
+
149
+ managed_option_enabled() {
150
+ local option="$1"
151
+ local zshrc="$TARGET_HOME/.zshrc"
152
+
153
+ [[ -f "$zshrc" ]] && grep -Fqx "# easy-command: ${option}" "$zshrc"
154
+ }
155
+
156
+ resolve_option() {
157
+ local requested="$1"
158
+ local marker="$2"
159
+
160
+ if [[ "$requested" == 'auto' ]]; then
161
+ if managed_option_enabled "$marker"; then
162
+ printf 'true'
163
+ else
164
+ printf 'false'
165
+ fi
166
+ else
167
+ printf '%s' "$requested"
168
+ fi
97
169
  }
98
170
 
99
171
  install_packages() {
172
+ local packages=(zsh git)
173
+ [[ "$(resolve_option "$ENABLE_ZOXIDE" 'zoxide')" == 'true' ]] && packages+=(zoxide)
174
+ [[ "$(resolve_option "$ENABLE_FZF" 'fzf')" == 'true' ]] && packages+=(fzf)
175
+
100
176
  case "$(uname -s)" in
101
177
  Darwin)
102
178
  command -v brew >/dev/null 2>&1 || fail 'Homebrew is required on macOS: https://brew.sh'
103
- brew install zsh git
179
+ run_as_user brew install "${packages[@]}"
104
180
  ;;
105
181
  Linux)
106
182
  command -v apt-get >/dev/null 2>&1 || fail 'Only Ubuntu/Debian (apt-get) is supported on Linux.'
107
183
  run_as_root apt-get update
108
- run_as_root env DEBIAN_FRONTEND=noninteractive apt-get install -y zsh git
184
+ run_as_root env DEBIAN_FRONTEND=noninteractive apt-get install -y "${packages[@]}"
109
185
  ;;
110
186
  *) fail "Unsupported operating system: $(uname -s)" ;;
111
187
  esac
112
188
  }
113
189
 
114
- install_repository() {
190
+ ensure_repository() {
115
191
  local repository="$1"
116
192
  local destination="$2"
117
- [[ -d "$destination/.git" ]] && return 0
118
193
 
119
- mkdir -p "$(dirname "$destination")"
194
+ if [[ -d "$destination/.git" ]]; then
195
+ return 0
196
+ fi
197
+ if [[ -e "$destination" ]]; then
198
+ fail "Expected Git repository at $destination, but the path is not a repository. Run repair after moving the path aside."
199
+ fi
200
+
201
+ if "$DRY_RUN"; then
202
+ command_preview mkdir -p "$(dirname "$destination")"
203
+ else
204
+ mkdir -p "$(dirname "$destination")"
205
+ chown "$TARGET_USER" "$(dirname "$destination")" 2>/dev/null || true
206
+ fi
120
207
  run_as_user git clone --depth 1 "$repository" "$destination"
121
208
  }
122
209
 
210
+ update_repository() {
211
+ local destination="$1"
212
+ [[ -d "$destination/.git" ]] || return 0
213
+
214
+ if ! run_as_user git -C "$destination" diff --quiet --ignore-submodules --; then
215
+ warn "Skipped $destination because it has local changes."
216
+ return 0
217
+ fi
218
+ run_as_user git -C "$destination" pull --ff-only
219
+ }
220
+
221
+ ensure_repositories() {
222
+ local base_dir="$TARGET_HOME/$BASE_DIR_NAME"
223
+ ensure_repository 'https://github.com/ohmyzsh/ohmyzsh.git' "$base_dir/oh-my-zsh"
224
+ ensure_repository 'https://github.com/zsh-users/zsh-autosuggestions.git' "$base_dir/oh-my-zsh/custom/plugins/zsh-autosuggestions"
225
+ ensure_repository 'https://github.com/zsh-users/zsh-syntax-highlighting.git' "$base_dir/oh-my-zsh/custom/plugins/zsh-syntax-highlighting"
226
+ }
227
+
228
+ update_repositories() {
229
+ local base_dir="$TARGET_HOME/$BASE_DIR_NAME/oh-my-zsh"
230
+ update_repository "$base_dir"
231
+ update_repository "$base_dir/custom/plugins/zsh-autosuggestions"
232
+ update_repository "$base_dir/custom/plugins/zsh-syntax-highlighting"
233
+ }
234
+
235
+ remove_managed_block() {
236
+ local zshrc="$TARGET_HOME/.zshrc"
237
+ [[ -f "$zshrc" ]] || return 0
238
+ managed_block_is_valid || fail "Managed .zshrc block is incomplete or duplicated. Refusing to change $zshrc; restore a backup or repair the markers manually."
239
+
240
+ if "$DRY_RUN"; then
241
+ info "[dry-run] Would remove the managed block from $zshrc"
242
+ return 0
243
+ fi
244
+
245
+ local temp_file
246
+ temp_file="$(mktemp)"
247
+ awk -v begin="$BEGIN_MARKER" -v end="$END_MARKER" '
248
+ $0 == begin { skipping = 1; next }
249
+ $0 == end { skipping = 0; next }
250
+ !skipping { print }
251
+ ' "$zshrc" > "$temp_file"
252
+ write_file "$temp_file" "$zshrc"
253
+ rm -f "$temp_file"
254
+ }
255
+
123
256
  write_zshrc_block() {
124
257
  local zshrc="$TARGET_HOME/.zshrc"
258
+ managed_block_is_valid || fail "Managed .zshrc block is incomplete or duplicated. Refusing to change $zshrc; restore a backup or repair the markers manually."
259
+
260
+ if "$DRY_RUN"; then
261
+ info "[dry-run] Would write the managed configuration block to $zshrc"
262
+ return 0
263
+ fi
264
+
125
265
  local temp_file
126
266
  temp_file="$(mktemp)"
127
267
 
@@ -131,35 +271,92 @@ write_zshrc_block() {
131
271
  !skipping { print }
132
272
  ' "$zshrc" > "$temp_file"
133
273
 
134
- cat >> "$temp_file" <<'EOF'
274
+ cat >> "$temp_file" <<EOF
135
275
 
136
276
  # >>> easy-command zsh >>>
137
277
  # Managed by easy-command. Re-run the installer to update this block.
138
- export ZSH="$HOME/.easy-command/oh-my-zsh"
278
+ export ZSH="\$HOME/.easy-command/oh-my-zsh"
139
279
  ZSH_THEME="agnoster"
140
280
  plugins=(git zsh-autosuggestions zsh-syntax-highlighting)
141
281
 
142
- HISTFILE="$HOME/.zsh_history"
282
+ HISTFILE="\$HOME/.zsh_history"
143
283
  HISTSIZE=10000
144
284
  SAVEHIST=10000
145
285
  setopt append_history share_history hist_ignore_dups
146
286
  ZSH_AUTOSUGGEST_STRATEGY=(history completion)
147
287
  ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'
148
288
 
149
- source "$ZSH/oh-my-zsh.sh"
289
+ source "\$ZSH/oh-my-zsh.sh"
150
290
 
151
291
  ZSH_HIGHLIGHT_STYLES[command]='fg=green'
152
292
  ZSH_HIGHLIGHT_STYLES[builtin]='fg=green'
153
293
  ZSH_HIGHLIGHT_STYLES[function]='fg=green'
154
294
  ZSH_HIGHLIGHT_STYLES[alias]='fg=green'
155
295
  ZSH_HIGHLIGHT_STYLES[unknown-token]='fg=red,bold'
296
+ EOF
297
+
298
+ if [[ "$(resolve_option "$ENABLE_ZOXIDE" 'zoxide')" == 'true' ]]; then
299
+ cat >> "$temp_file" <<'EOF'
300
+
301
+ # easy-command: zoxide
302
+ if command -v zoxide >/dev/null 2>&1; then
303
+ eval "$(zoxide init zsh)"
304
+ fi
305
+ EOF
306
+ fi
307
+
308
+ if [[ "$(resolve_option "$ENABLE_FZF" 'fzf')" == 'true' ]]; then
309
+ cat >> "$temp_file" <<'EOF'
310
+
311
+ # easy-command: fzf
312
+ if [[ -r /usr/share/doc/fzf/examples/key-bindings.zsh ]]; then
313
+ source /usr/share/doc/fzf/examples/key-bindings.zsh
314
+ elif command -v brew >/dev/null 2>&1 && [[ -r "$(brew --prefix)/opt/fzf/shell/key-bindings.zsh" ]]; then
315
+ source "$(brew --prefix)/opt/fzf/shell/key-bindings.zsh"
316
+ fi
317
+ EOF
318
+ fi
319
+
320
+ cat >> "$temp_file" <<'EOF'
156
321
  # <<< easy-command zsh <<<
157
322
  EOF
158
323
 
159
324
  backup_file "$zshrc"
160
- cat "$temp_file" > "$zshrc"
325
+ write_file "$temp_file" "$zshrc"
161
326
  rm -f "$temp_file"
162
- chown "$TARGET_USER" "$zshrc" 2>/dev/null || true
327
+ }
328
+
329
+ configure_git_aliases() {
330
+ local enabled
331
+ enabled="$(resolve_option "$ENABLE_GIT_ALIASES" 'git-aliases')"
332
+
333
+ local aliases=(
334
+ 'ec-status=status --short --branch'
335
+ 'ec-log=log --oneline --graph --decorate -12'
336
+ 'ec-last=log -1 --stat'
337
+ )
338
+ local entry name value
339
+ for entry in "${aliases[@]}"; do
340
+ name="${entry%%=*}"
341
+ value="${entry#*=}"
342
+ if "$DRY_RUN"; then
343
+ if [[ "$enabled" == 'true' ]]; then
344
+ command_preview git config --global "alias.$name" "$value"
345
+ elif [[ "$ENABLE_GIT_ALIASES" == 'false' ]]; then
346
+ command_preview git config --global --unset "alias.$name"
347
+ fi
348
+ continue
349
+ fi
350
+ if [[ "$enabled" == 'true' ]]; then
351
+ if run_as_user git config --global --get "alias.$name" >/dev/null 2>&1; then
352
+ check_warning "Git alias $name already exists; leaving it unchanged."
353
+ else
354
+ run_as_user git config --global "alias.$name" "$value"
355
+ fi
356
+ elif [[ "$ENABLE_GIT_ALIASES" == 'false' ]]; then
357
+ run_as_user git config --global --unset "alias.$name" 2>/dev/null || true
358
+ fi
359
+ done
163
360
  }
164
361
 
165
362
  change_login_shell() {
@@ -171,30 +368,151 @@ change_login_shell() {
171
368
  run_as_root chsh -s "$zsh_path" "$TARGET_USER"
172
369
  }
173
370
 
174
- main() {
175
- if "$UNINSTALL"; then
176
- backup_file "$TARGET_HOME/.zshrc"
177
- remove_managed_block
178
- info 'Removed the easy-command managed .zshrc block.'
179
- exit 0
371
+ confirm() {
372
+ "$DRY_RUN" && return 0
373
+ "$ASSUME_YES" && return 0
374
+
375
+ local answer
376
+ read -r -p "$1 [y/N] " answer
377
+ [[ "$answer" =~ ^[Yy]$ ]] || exit 0
378
+ }
379
+
380
+ doctor() {
381
+ local errors=0
382
+ local zshrc="$TARGET_HOME/.zshrc"
383
+ local base_dir="$TARGET_HOME/$BASE_DIR_NAME/oh-my-zsh"
384
+ local configured_shell=''
385
+
386
+ info "Checking easy-command for $TARGET_USER"
387
+ configured_shell="$(getent passwd "$TARGET_USER" | cut -d: -f7 2>/dev/null || true)"
388
+ if [[ -z "$configured_shell" && "$(uname -s)" == 'Darwin' ]]; then
389
+ configured_shell="$(dscl . -read "/Users/$TARGET_USER" UserShell | awk '{print $2}')"
390
+ fi
391
+ if [[ "$configured_shell" == *zsh ]]; then
392
+ ok "Login shell: $configured_shell"
393
+ else
394
+ check_warning "Login shell is ${configured_shell:-unknown}; run install without --no-chsh to switch to zsh."
395
+ fi
396
+
397
+ local command_name
398
+ for command_name in zsh git; do
399
+ if command -v "$command_name" >/dev/null 2>&1; then
400
+ ok "Command available: $command_name"
401
+ else
402
+ check_error "Missing command: $command_name"
403
+ errors=$((errors + 1))
404
+ fi
405
+ done
406
+
407
+ local repository
408
+ for repository in "$base_dir" "$base_dir/custom/plugins/zsh-autosuggestions" "$base_dir/custom/plugins/zsh-syntax-highlighting"; do
409
+ if [[ -d "$repository/.git" ]]; then
410
+ ok "Repository available: $repository"
411
+ else
412
+ check_error "Missing repository: $repository"
413
+ errors=$((errors + 1))
414
+ fi
415
+ done
416
+
417
+ local begin_count=0 end_count=0
418
+ if [[ -f "$zshrc" ]]; then
419
+ begin_count="$(grep -Fxc "$BEGIN_MARKER" "$zshrc" || true)"
420
+ end_count="$(grep -Fxc "$END_MARKER" "$zshrc" || true)"
421
+ fi
422
+ if [[ "$begin_count" == '1' && "$end_count" == '1' ]]; then
423
+ ok 'Managed .zshrc block is complete.'
424
+ else
425
+ check_error "Managed .zshrc block is invalid (begin: $begin_count, end: $end_count). Restore a backup or repair the markers manually."
426
+ errors=$((errors + 1))
180
427
  fi
181
428
 
182
- if ! "$ASSUME_YES"; then
183
- read -r -p "Install easy-command for $TARGET_USER? [y/N] " answer
184
- [[ "$answer" =~ ^[Yy]$ ]] || exit 0
429
+ if command -v zsh >/dev/null 2>&1 && [[ -f "$zshrc" ]]; then
430
+ if zsh -n "$zshrc"; then
431
+ ok '.zshrc syntax check passed.'
432
+ else
433
+ check_error '.zshrc syntax check failed.'
434
+ errors=$((errors + 1))
435
+ fi
185
436
  fi
186
437
 
187
- install_packages
438
+ if ((errors > 0)); then
439
+ return 1
440
+ fi
441
+ }
188
442
 
189
- local base_dir="$TARGET_HOME/.easy-command"
190
- install_repository 'https://github.com/ohmyzsh/ohmyzsh.git' "$base_dir/oh-my-zsh"
191
- install_repository 'https://github.com/zsh-users/zsh-autosuggestions.git' "$base_dir/oh-my-zsh/custom/plugins/zsh-autosuggestions"
192
- install_repository 'https://github.com/zsh-users/zsh-syntax-highlighting.git' "$base_dir/oh-my-zsh/custom/plugins/zsh-syntax-highlighting"
443
+ parse_args() {
444
+ while (($#)); do
445
+ case "$1" in
446
+ install|doctor|repair|update)
447
+ ACTION="$1"
448
+ ;;
449
+ --yes|-y) ASSUME_YES=true ;;
450
+ --user)
451
+ shift
452
+ (($#)) || fail '--user requires a value.'
453
+ TARGET_USER="$1"
454
+ ;;
455
+ --no-chsh) CHANGE_LOGIN_SHELL=false ;;
456
+ --dry-run) DRY_RUN=true ;;
457
+ --with-zoxide) ENABLE_ZOXIDE=true ;;
458
+ --without-zoxide) ENABLE_ZOXIDE=false ;;
459
+ --with-fzf) ENABLE_FZF=true ;;
460
+ --without-fzf) ENABLE_FZF=false ;;
461
+ --with-git-aliases) ENABLE_GIT_ALIASES=true ;;
462
+ --without-git-aliases) ENABLE_GIT_ALIASES=false ;;
463
+ --uninstall) ACTION='uninstall' ;;
464
+ --help|-h)
465
+ usage
466
+ exit 0
467
+ ;;
468
+ *) fail "Unknown option or command: $1" ;;
469
+ esac
470
+ shift
471
+ done
472
+ }
193
473
 
194
- write_zshrc_block
195
- change_login_shell
474
+ main() {
475
+ parse_args "$@"
476
+ determine_target_home
196
477
 
197
- info "Installed easy-command $VERSION for $TARGET_USER. Reconnect or run: exec zsh -l"
478
+ case "$ACTION" in
479
+ doctor)
480
+ doctor
481
+ ;;
482
+ uninstall)
483
+ confirm "Remove the easy-command managed configuration for $TARGET_USER?"
484
+ backup_file "$TARGET_HOME/.zshrc"
485
+ remove_managed_block
486
+ info 'Removed the easy-command managed .zshrc block.'
487
+ ;;
488
+ install)
489
+ confirm "Install easy-command $VERSION for $TARGET_USER?"
490
+ install_packages
491
+ ensure_repositories
492
+ write_zshrc_block
493
+ configure_git_aliases
494
+ change_login_shell
495
+ info "Installed easy-command $VERSION for $TARGET_USER. Reconnect or run: exec zsh -l"
496
+ ;;
497
+ repair)
498
+ CHANGE_LOGIN_SHELL=false
499
+ confirm "Repair easy-command for $TARGET_USER?"
500
+ install_packages
501
+ ensure_repositories
502
+ write_zshrc_block
503
+ configure_git_aliases
504
+ info "Repaired easy-command for $TARGET_USER. Reconnect or run: exec zsh -l"
505
+ ;;
506
+ update)
507
+ CHANGE_LOGIN_SHELL=false
508
+ confirm "Update easy-command repositories for $TARGET_USER?"
509
+ ensure_repositories
510
+ update_repositories
511
+ write_zshrc_block
512
+ configure_git_aliases
513
+ info "Updated easy-command for $TARGET_USER. Reconnect or run: exec zsh -l"
514
+ ;;
515
+ esac
198
516
  }
199
517
 
200
- main
518
+ main "$@"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easy-command",
3
- "version": "1.0.4",
3
+ "version": "2.0.1",
4
4
  "description": "Configure a polished Zsh terminal with one command.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -8,8 +8,10 @@
8
8
  },
9
9
  "files": [
10
10
  "bin",
11
+ "assets",
11
12
  "install.sh",
12
13
  "README.md",
14
+ "README_CN.md",
13
15
  "LICENSE"
14
16
  ],
15
17
  "repository": {