@vibe-coder/cli 1.0.0 → 1.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.
Files changed (4) hide show
  1. package/README.md +76 -56
  2. package/README.zh.md +157 -0
  3. package/dist/cli.js +398 -181
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -1,141 +1,161 @@
1
-
2
1
  <div align="center">
3
2
 
4
3
  # 🌊 Vibe Coding CLI
5
4
 
6
- **专为 OpenCode 打造的 vibe coding 生态构建工具**
7
-
8
- [![Version](https://img.shields.io/badge/version-1.0.0-blue.svg)](./package.json)
9
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](../../LICENSE.md)
10
- [![Built with Bun](https://img.shields.io/badge/Bun-%23000000.svg?logo=bun&logoColor=white)](https://bun.sh)
5
+ **A vibe coding ecosystem builder tailored for OpenCode**
11
6
 
12
- [English](#) · **简体中文**
7
+ **English** · [简体中文](https://github.com/HelloGGX/skill/blob/main/packages/vibe/README.zh.md)
13
8
 
14
9
  </div>
15
10
 
16
- ## 📖 简介 (Introduction)
11
+ ## 📖 Introduction
17
12
 
18
- `vibe-coding-cli` 是一个专为 **OpenCode** 平台打造的现代命令行脚手架工具。它的核心目标是简化和自动化 Agent 技能(Skills)与底层脚本工具(Tools)的管理。
13
+ `vibe-coding-cli` is a modern command-line scaffolding tool built specifically for the **OpenCode** platform. Its core objective is to quickly set up a Vibe Coding development environment and simplify resource management for specification-driven development.
19
14
 
20
- 通过 `vibe` 命令,你可以一键拉取远程 GitHub 仓库中的 TypeScript Python 工具脚本,自动将其注册到 OpenCode 配置中,并无缝管理其依赖环境(包括自动配置独立的 Python 虚拟环境)。
15
+ With the `vibe` command, you can pull TypeScript/Python tool scripts or Markdown rule files from remote GitHub repositories with a single click. It automatically and seamlessly registers them into your OpenCode configuration and manages the underlying runtime dependency environments, allowing you to focus entirely on "co-creating code with AI".
21
16
 
22
- ## ✨ 核心特性 (Features)
17
+ ## ✨ Features
23
18
 
24
- - 🛠 **全自动化工具管理**: 支持从任意 GitHub 仓库快速解析、选择并下载 `.ts` / `.py` 工具至本地 `.opencode/tool/` 目录。
25
- - 📦 **智能配置注入**: 自动维护状态锁文件 (`vibe-lock.json`) 并向 `.opencode/opencode.jsonc` 中无感注入工具注册信息,告别手动配置。
26
- - 🐍 **自动 Python 环境集成**: 侦测到 Python 工具时,自动在项目根目录创建 `.venv` 虚拟环境,并安装必要的 `requests`、`dotenv` 等基础依赖。
27
- - 🪄 **标准技能聚合**: `pnpx skills` 生态深度集成,统一管理标准技能库和本地化扩展工具。
28
- - **极致性能**: 基于 [Bun](https://bun.sh/) 编写与构建,极速执行代码和处理文件 IO。
19
+ * 🛠 **Fully Automated Tool Management:** Quickly parse, select, and download `.ts` / `.py` scripts from any GitHub repository straight to your local `.opencode/tool/` directory, ready to use out of the box.
20
+ * 📜 **Integrates Everything You Need for Vibe Coding:** A unique ecosystem aggregation capability that perfectly blends the **Capabilities (tools and skills)** needed for Agent execution with the **Context (guidelines and best practices)**. Supports on-demand installation of `.md` rule files so the AI truly understands your architectural intent and coding standards.
21
+ * 📦 **Smart Configuration Injection:** Automatically intercepts and updates `.opencode/opencode.jsonc`, silently injecting tool enable-switches and Prompt instruction paths. Say goodbye to tedious manual configuration forever.
22
+ * ⚡ **Lightning-Fast Parallel Updates:** Designed with a concurrency model to simultaneously handle resource comparison and pulling from multiple source repositories, drastically reducing update wait times in multi-dependency scenarios.
23
+ * 🪄 **Standard Skills Aggregation:** Deeply integrated with Vercel's `pnpx skills` ecosystem, allowing you to manage standard Agent skill libraries alongside localized extension resources within a unified CLI workflow.
29
24
 
30
25
  ---
31
26
 
32
- ## 🚀 快速开始 (Quick Start)
27
+ ## 🚀 Quick Start
33
28
 
34
- ### 安装
29
+ ### Installation
35
30
 
36
- 作为全局包安装(推荐使用 npm bun):
31
+ Install as a global package (npm or bun recommended):
37
32
 
38
33
  ```bash
39
- # 使用 npm
34
+ # Using npm
40
35
  npm install -g vibe-coding-cli
41
36
 
42
- # 使用 bun
37
+ # Using bun
43
38
  bun add -g vibe-coding-cli
44
39
 
45
40
  ```
46
41
 
47
- *如果你在本地 monorepo 环境中开发,可以直接使用 `bun run dev` 或构建后执行 `./bin/vibe`。*
48
-
49
- ### 基础用法
42
+ ### Basic Usage
50
43
 
51
- 初始化并添加一个技能库(例如本项目的 `helloggx/skill`):
44
+ Initialize and add an ecosystem library (e.g., `helloggx/skill` from this project):
52
45
 
53
46
  ```bash
54
47
  vibe add helloggx/skill
55
48
 
56
49
  ```
57
50
 
58
- *此命令将会弹出交互式菜单,让你选择想要安装的底层工具,并自动配置项目结构。*
51
+ *This command will pop up an interactive menu, allowing you to flexibly select the **Tools** and **Rules** you want to install. The CLI will automatically configure the entire environment for you.*
59
52
 
60
53
  ---
61
54
 
62
- ## 📚 命令指南 (Commands)
55
+ ## 📚 Commands
63
56
 
64
- ### 1. 添加工具与技能 (`add` / `a`)
57
+ ### 1. Add Resources (`add` / `a`)
65
58
 
66
59
  ```bash
67
60
  vibe add <repository>
68
61
 
69
62
  ```
70
63
 
71
- **功能流程**:
64
+ **Execution Flow**:
72
65
 
73
- 1. 调用原生能力安装目标仓库中的 Agent 技能。
74
- 2. 克隆并解析目标仓库中的 `tool` 文件夹。
75
- 3. 提供交互式多选列表,让你选择需要的具体工具。
76
- 4. 自动拷贝文件、更新 `opencode.jsonc` 并配置 Python 依赖环境(如果需要)。
66
+ 1. Invokes native capabilities to install basic Agent skills from the target repository.
67
+ 2. Clones and parses the `skill`, `tool`, and `rules` asset directories in the target repository.
68
+ 3. Triggers an interactive multi-select list to pick specific tool scripts and rule documents on demand.
69
+ 4. Automatically executes file copying, intelligently merges common rules, updates `opencode.jsonc`, and configures the Python dependency environment (if needed).
77
70
 
78
- ### 2. 查看已安装项 (`list` / `ls`)
71
+ ### 2. List Installed Items (`list` / `ls`)
79
72
 
80
73
  ```bash
81
74
  vibe list
82
75
 
83
76
  ```
84
77
 
85
- **功能**:
86
- 清晰打印当前项目中安装的所有本地 Tools(来自 `vibe-lock.json`)以及已安装的标准 Skills,便于进行环境审查。
78
+ **Function**:
79
+ Clearly prints a status map of all installed resources in the current project, including:
87
80
 
88
- ### 3. 一键更新 (`update` / `up`)
81
+ * 🛠️ Local Tools
82
+ * 📜 Injected Context Rules (Local Rules)
83
+ * 🪄 Global Standard Skills
84
+
85
+ ### 3. One-Click Sync & Update (`update` / `up`)
89
86
 
90
87
  ```bash
91
88
  vibe update
92
89
 
93
90
  ```
94
91
 
95
- **功能**:
96
- 自动执行标准 Skills 的升级,并根据锁文件记录的源仓库,拉取并覆盖最新的本地工具脚本,保持生态环境最新。
92
+ **Function**:
93
+ Executes a full workspace update with one click. The CLI will concurrently pull all source repositories recorded in `vibe-lock.json`, intelligently compare and overwrite the latest local scripts and rule files, and simultaneously trigger an upgrade of the standard skills library.
94
+
95
+ ### 4. Remove Resources (`remove` / `rm`)
96
+
97
+ ```bash
98
+ # Interactive mode: Pops up a UI list to select tools and rules to remove
99
+ vibe remove
100
+
101
+ # Shortcut mode: Directly specify the resource to remove (supports standard skills and local tools/rules)
102
+ vibe remove <resource>
103
+
104
+ ```
105
+
106
+ **Execution Flow**:
107
+
108
+ 1. Invokes native capabilities to remove standard Agent skills from the target repository (via `pnpx skills remove`).
109
+ 2. Parses the list of installed resources in the local `vibe-lock.json`.
110
+ 3. **Interactive Mode**: Pops up a multi-select list, allowing you to choose specific tools and rule categories for removal.
111
+ 4. **Shortcut Mode**: Directly matches and removes local tool/rule files based on the passed arguments.
112
+ 5. Automatically cleans up the corresponding physical files (`.opencode/tool/` and `.opencode/rules/`) and synchronously updates the `opencode.jsonc` configuration.
97
113
 
98
114
  ---
99
115
 
100
- ## 📂 目录与配置规范 (Workspace Structure)
116
+ ## 📂 Workspace Structure
101
117
 
102
- 运行 `vibe add` 后,工具将在你的项目根目录下自动创建并维护以下结构:
118
+ After running `vibe add`, the tool will automatically create and maintain the following standard Vibe Coding structure in your project's root directory:
103
119
 
104
120
  ```text
105
121
  your-project/
106
122
  ├── .opencode/
107
- ├── tool/ # 存放被拉取下来的底层 .ts / .py 工具脚本
108
- ├── get_dsl.ts
109
- └── ...
110
- ├── opencode.jsonc # OpenCode 核心配置(vibe 会自动向 "tools" 字段注入注册信息)
111
- └── vibe-lock.json # Vibe 状态锁文件,记录工具来源和版本时间戳
112
- ├── .venv/ # (自动创建) Python 虚拟环境,为 .py 工具提供隔离环境
113
- └── requirements.txt # (自动维护) 补充 Python 工具运行所需的核心依赖
123
+   ├── tool/                   # Stores the pulled underlying .ts / .py tool scripts
124
+     ├── get_dsl.ts
125
+     └── ...
126
+   ├── rules/                  # Stores the pulled .md rule files (categorized by type)
127
+   │   ├── common/
128
+ │   │   └── typescript/
129
+ │   ├── opencode.jsonc          # Core OpenCode configuration file
130
+ │   │                           # (vibe automatically manages "tools": {...} and "instructions": [...] inside)
131
+ │   └── vibe-lock.json          # Internal state lock file, accurately recording resource sources, versions, and update timestamps
132
+ ├── .venv/                      # (Auto-created as needed) Isolated Python virtual environment
133
+ └── requirements.txt            # (Auto-maintained as needed) Dependency list required for Python scripts
114
134
 
115
135
  ```
116
136
 
117
137
  ---
118
138
 
119
- ## 🛠️ 开发者指南 (Development)
139
+ ## 🛠️ Development
120
140
 
121
- 本项目使用 Bun 进行包管理和运行。
141
+ This project is built on top of [Bun](https://bun.sh/), offering a lightning-fast execution and bundling experience.
122
142
 
123
143
  ```bash
124
- # 1. 安装依赖
144
+ # 1. Install project dependencies
125
145
  bun install
126
146
 
127
- # 2. 本地开发运行 CLI
147
+ # 2. Run CLI locally for debugging
128
148
  bun run dev --help
129
149
 
130
- # 3. 类型检查
150
+ # 3. Strict type checking
131
151
  bun run typecheck
132
152
 
133
- # 4. 构建生产版本 (输出至 ./dist)
153
+ # 4. Build production version (outputs to ./dist)
134
154
  bun run build
135
155
 
136
156
  ```
137
157
 
138
- ## 📄 许可证 (License)
158
+ ## 📄 License
139
159
 
140
- 本项目采用 [MIT License](https://www.google.com/search?q=../../LICENSE.md) 进行开源。
160
+ This project is open-sourced under the [MIT License](https://www.google.com/search?q=../../LICENSE.md).
141
161
  © 2026 [HelloGGX](https://github.com/HelloGGX)
package/README.zh.md ADDED
@@ -0,0 +1,157 @@
1
+ <div align="center">
2
+
3
+ # 🌊 Vibe Coding CLI
4
+
5
+ **专为 OpenCode 打造的 vibe coding 生态构建工具**
6
+
7
+ [![Version](https://img.shields.io/badge/version-1.0.1-blue.svg)](./package.json)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](../../LICENSE.md)
9
+ [![Built with Bun](https://img.shields.io/badge/Bun-%23000000.svg?logo=bun&logoColor=white)](https://bun.sh)
10
+
11
+ [English](https://github.com/HelloGGX/skill/blob/main/packages/vibe/README.md) · **简体中文**
12
+
13
+ </div>
14
+
15
+ ## 📖 简介 (Introduction)
16
+
17
+ `vibe-coding-cli` 是一个专为 **OpenCode** 平台打造的现代命令行脚手架工具。它的核心目标是快速搭建 Vibe Coding 的开发环境,简化规范驱动开发的资源管理。
18
+
19
+ 通过 `vibe` 命令,你可以一键拉取远程 GitHub 仓库中的 TypeScript/Python 工具脚本或 Markdown 规则文件,自动将其无缝注册到 OpenCode 配置中,并接管其底层的运行依赖环境,让你专注于“与 AI 共创代码”本身。
20
+
21
+ ## ✨ 核心特性 (Features)
22
+
23
+ - 🛠 **全自动化工具管理**: 支持从任意 GitHub 仓库快速解析、选择并下载 `.ts` / `.py` 脚本至本地 `.opencode/tool/` 目录,开箱即用。
24
+ - 📜 **集成 Vibe Coding 所需的一切**: 独创的生态聚合能力,将 Agent 执行所需的 **Capabilities(工具与技能)** 与 **Context(行为准则与最佳实践)** 完美融合。支持按需安装 `.md` 规则文件,让 AI 真正懂你的架构意图与代码规范。
25
+ - 📦 **智能配置注入**: 自动拦截并更新 `.opencode/opencode.jsonc`,无感注入工具的启用开关与 Prompt 指令(instructions)路径,彻底告别繁琐的手动配置。
26
+ - ⚡ **并行极速更新**: 基于并发模型设计,同时处理多个源仓库的资源对比与拉取,大幅缩短多依赖场景下的更新等待时间。
27
+ - 🪄 **标准技能聚合**: 与 Vercel 的 `pnpx skills` 生态深度集成,在统一的 CLI 流程中同时管理标准 Agent 技能库和本地化扩展资源。
28
+
29
+ ---
30
+
31
+ ## 🚀 快速开始 (Quick Start)
32
+
33
+ ### 安装
34
+
35
+ 作为全局包安装(推荐使用 npm 或 bun):
36
+
37
+ ```bash
38
+ # 使用 npm
39
+ npm install -g vibe-coding-cli
40
+
41
+ # 使用 bun
42
+ bun add -g vibe-coding-cli
43
+ ```
44
+
45
+ ### 基础用法
46
+
47
+ 初始化并添加一个生态库(例如本项目的 `helloggx/skill`):
48
+
49
+ ```bash
50
+ vibe add helloggx/skill
51
+ ```
52
+
53
+ *此命令将会弹出交互式菜单,允许你灵活多选想要安装的 **Tools (工具)** 和 **Rules (规则)**,CLI 将会自动为你完成所有环境的配置。*
54
+
55
+ ---
56
+
57
+ ## 📚 命令指南 (Commands)
58
+
59
+ ### 1. 添加资源 (`add` / `a`)
60
+
61
+ ```bash
62
+ vibe add <repository>
63
+ ```
64
+
65
+ **执行流程**:
66
+
67
+ 1. 调用原生能力,安装目标仓库中的基础 Agent 技能。
68
+ 2. 克隆并解析目标仓库中的 `skill`、`tool` 和 `rules` 资产目录。
69
+ 3. 唤起交互式多选列表,按需挑选具体的工具脚本和规则文档。
70
+ 4. 自动执行文件拷贝、智能合并公共规则、更新 `opencode.jsonc` 并配置 Python 依赖环境(如需)。
71
+
72
+ ### 2. 查看已安装项 (`list` / `ls`)
73
+
74
+ ```bash
75
+ vibe list
76
+ ```
77
+
78
+ **功能**:
79
+ 清晰打印当前项目中安装的所有资源态势图,包含:
80
+
81
+ * 🛠️ 本地扩展工具 (Local Tools)
82
+ * 📜 注入的上下文规则 (Local Rules)
83
+ * 🪄 全局标准技能 (Standard Skills)
84
+
85
+ ### 3. 一键同步更新 (`update` / `up`)
86
+
87
+ ```bash
88
+ vibe update
89
+ ```
90
+
91
+ **功能**:
92
+ 一键执行工作区全量更新。CLI 会并发拉取 `vibe-lock.json` 中记录的所有源仓库,智能比对并覆盖最新的本地脚本和规则文件,同时触发标准技能库的升级。
93
+
94
+ ### 4. 移除资源 (`remove` / `rm`)
95
+
96
+ ```bash
97
+ # 交互式模式:弹出 UI 列表选择要移除的工具和规则
98
+ vibe remove
99
+
100
+ # 快捷模式:直接指定要移除的资源(支持标准 skills 和本地工具/规则)
101
+ vibe remove <resource>
102
+ ```
103
+
104
+ **执行流程**:
105
+
106
+ 1. 调用原生能力,移除目标仓库中的标准 Agent 技能(通过 `pnpx skills remove`)。
107
+ 2. 解析本地 `vibe-lock.json` 中的已安装资源列表。
108
+ 3. **交互模式**:弹出多选列表,允许你选择具体的工具和规则分类进行移除。
109
+ 4. **快捷模式**:根据传入的参数直接匹配并移除本地工具/规则文件。
110
+ 5. 自动清理对应的物理文件(`.opencode/tool/` 和 `.opencode/rules/`),并同步更新 `opencode.jsonc` 配置。
111
+
112
+ ---
113
+
114
+ ## 📂 目录与配置规范 (Workspace Structure)
115
+
116
+ 运行 `vibe add` 后,工具将在你的项目根目录下自动创建并维护以下标准 Vibe Coding 结构:
117
+
118
+ ```text
119
+ your-project/
120
+ ├── .opencode/
121
+ │ ├── tool/ # 存放被拉取下来的底层 .ts / .py 工具脚本
122
+ │ │ ├── get_dsl.ts
123
+ │ │ └── ...
124
+ │ ├── rules/ # 存放被拉取下来的 .md 规则文件(按类别分类归档)
125
+ │ │ ├── common/
126
+ │ │ └── typescript/
127
+ │ ├── opencode.jsonc # OpenCode 核心配置文件
128
+ │ │ # (vibe 会自动管理其中的 "tools": {...} 和 "instructions": [...])
129
+ │ └── vibe-lock.json # 内部状态锁文件,精准记录资源来源仓库、版本与更新时间戳
130
+ ├── .venv/ # (按需自动创建) 隔离的 Python 虚拟环境
131
+ └── requirements.txt # (按需自动维护) Python 脚本所需的依赖清单
132
+ ```
133
+
134
+ ---
135
+
136
+ ## 🛠️ 开发者指南 (Development)
137
+
138
+ 本项目底层基于 [Bun](https://bun.sh/) 构建,拥有极速的执行与打包体验。
139
+
140
+ ```bash
141
+ # 1. 安装项目依赖
142
+ bun install
143
+
144
+ # 2. 本地调试运行 CLI
145
+ bun run dev --help
146
+
147
+ # 3. 严格类型检查
148
+ bun run typecheck
149
+
150
+ # 4. 构建生产版本 (输出至 ./dist)
151
+ bun run build
152
+ ```
153
+
154
+ ## 📄 许可证 (License)
155
+
156
+ 本项目采用 [MIT License](https://www.google.com/search?q=../../LICENSE.md) 进行开源。
157
+ © 2026 [HelloGGX](https://github.com/HelloGGX)
package/dist/cli.js CHANGED
@@ -1317,6 +1317,25 @@ class Vt extends x {
1317
1317
  }
1318
1318
  }
1319
1319
  }
1320
+
1321
+ class kt extends x {
1322
+ get cursor() {
1323
+ return this.value ? 0 : 1;
1324
+ }
1325
+ get _value() {
1326
+ return this.cursor === 0;
1327
+ }
1328
+ constructor(e) {
1329
+ super(e, false), this.value = !!e.initialValue, this.on("userInput", () => {
1330
+ this.value = this._value;
1331
+ }), this.on("confirm", (s) => {
1332
+ this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = s, this.state = "submit", this.close();
1333
+ }), this.on("cursor", () => {
1334
+ this.value = !this.value;
1335
+ });
1336
+ }
1337
+ }
1338
+
1320
1339
  class yt extends x {
1321
1340
  options;
1322
1341
  cursor = 0;
@@ -1689,6 +1708,33 @@ var X2 = (t) => {
1689
1708
  B2.push(w);
1690
1709
  return h && B2.push(g), B2;
1691
1710
  };
1711
+ var Re = (t) => {
1712
+ const r = t.active ?? "Yes", s = t.inactive ?? "No";
1713
+ return new kt({ active: r, inactive: s, signal: t.signal, input: t.input, output: t.output, initialValue: t.initialValue ?? true, render() {
1714
+ const i = t.withGuide ?? _.withGuide, a = `${i ? `${import_picocolors2.default.gray(d)}
1715
+ ` : ""}${W2(this.state)} ${t.message}
1716
+ `, o = this.value ? r : s;
1717
+ switch (this.state) {
1718
+ case "submit": {
1719
+ const u = i ? `${import_picocolors2.default.gray(d)} ` : "";
1720
+ return `${a}${u}${import_picocolors2.default.dim(o)}`;
1721
+ }
1722
+ case "cancel": {
1723
+ const u = i ? `${import_picocolors2.default.gray(d)} ` : "";
1724
+ return `${a}${u}${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(o))}${i ? `
1725
+ ${import_picocolors2.default.gray(d)}` : ""}`;
1726
+ }
1727
+ default: {
1728
+ const u = i ? `${import_picocolors2.default.cyan(d)} ` : "", l = i ? import_picocolors2.default.cyan(x2) : "";
1729
+ return `${a}${u}${this.value ? `${import_picocolors2.default.green(Q2)} ${r}` : `${import_picocolors2.default.dim(H2)} ${import_picocolors2.default.dim(r)}`}${t.vertical ? i ? `
1730
+ ${import_picocolors2.default.cyan(d)} ` : `
1731
+ ` : ` ${import_picocolors2.default.dim("/")} `}${this.value ? `${import_picocolors2.default.dim(H2)} ${import_picocolors2.default.dim(s)}` : `${import_picocolors2.default.green(Q2)} ${s}`}
1732
+ ${l}
1733
+ `;
1734
+ }
1735
+ }
1736
+ } }).prompt();
1737
+ };
1692
1738
  var R2 = { message: (t = [], { symbol: r = import_picocolors2.default.gray(d), secondarySymbol: s = import_picocolors2.default.gray(d), output: i = process.stdout, spacing: a = 1, withGuide: o } = {}) => {
1693
1739
  const u = [], l = o ?? _.withGuide, n = l ? s : "", c = l ? `${r} ` : "", g = l ? `${s} ` : "";
1694
1740
  for (let p = 0;p < a; p++)
@@ -1868,10 +1914,10 @@ var bt2 = ({ indicator: t = "dots", onCancel: r, output: s = process.stdout, can
1868
1914
  var zt = { light: C("─", "-"), heavy: C("━", "="), block: C("█", "#") };
1869
1915
  var Qt = `${import_picocolors2.default.gray(d)} `;
1870
1916
 
1871
- // src/add.ts
1872
- import { execSync } from "child_process";
1873
- import { existsSync, mkdirSync, cpSync, writeFileSync, readFileSync, readdirSync } from "fs";
1874
- import path from "path";
1917
+ // src/commands/add.ts
1918
+ import { execSync as execSync2 } from "child_process";
1919
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync2 } from "fs";
1920
+ import path4 from "path";
1875
1921
 
1876
1922
  // ../../node_modules/.bun/simple-git@3.32.2/node_modules/simple-git/dist/esm/index.js
1877
1923
  var import_file_exists = __toESM(require_dist(), 1);
@@ -5958,6 +6004,10 @@ async function cleanupTempDir(dir) {
5958
6004
  await rm(dir, { recursive: true, force: true });
5959
6005
  }
5960
6006
 
6007
+ // src/utils/config.ts
6008
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
6009
+ import path from "path";
6010
+
5961
6011
  // src/constants.ts
5962
6012
  var OPENCODE_DIR = ".opencode";
5963
6013
  var TOOL_SUBDIR = "tool";
@@ -5973,7 +6023,29 @@ var YELLOW = "\x1B[33m";
5973
6023
  var DIM = "\x1B[38;5;102m";
5974
6024
  var TEXT = "\x1B[38;5;145m";
5975
6025
 
5976
- // src/add.ts
6026
+ // src/utils/error.ts
6027
+ function handleExecError(error, context, severity = "warn" /* WARN */) {
6028
+ const message = error instanceof Error ? error.message : String(error);
6029
+ const formatted = `${context}
6030
+ ${YELLOW}${message}${RESET}`;
6031
+ switch (severity) {
6032
+ case "error" /* ERROR */:
6033
+ R2.error(formatted);
6034
+ process.exit(1);
6035
+ case "warn" /* WARN */:
6036
+ R2.warn(formatted);
6037
+ break;
6038
+ default:
6039
+ R2.info(formatted);
6040
+ }
6041
+ }
6042
+
6043
+ // src/utils/config.ts
6044
+ function parseJsonc(content) {
6045
+ let safeJsonStr = content.replace(/"(?:\\.|[^"\\])*"|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, (m) => m.startsWith('"') ? m : "");
6046
+ safeJsonStr = safeJsonStr.replace(/,\s*([\]}])/g, "$1");
6047
+ return JSON.parse(safeJsonStr);
6048
+ }
5977
6049
  function getLockFilePath() {
5978
6050
  return path.join(process.cwd(), OPENCODE_DIR, LOCK_FILE);
5979
6051
  }
@@ -5984,6 +6056,8 @@ function readLockFile() {
5984
6056
  const parsed = JSON.parse(readFileSync(lockPath, "utf-8"));
5985
6057
  if (!parsed.rules)
5986
6058
  parsed.rules = {};
6059
+ if (!parsed.tools)
6060
+ parsed.tools = {};
5987
6061
  return parsed;
5988
6062
  }
5989
6063
  } catch (e2) {}
@@ -6016,138 +6090,174 @@ function ensureOpencodeConfig() {
6016
6090
  writeFileSync(configPath, jsoncContent, "utf-8");
6017
6091
  }
6018
6092
  }
6019
- function updateOpencodeConfigTools(newTools) {
6020
- if (newTools.length === 0)
6093
+ function updateOpencodeConfig(newTools, newRulePaths) {
6094
+ if (newTools.length === 0 && newRulePaths.length === 0)
6021
6095
  return;
6022
6096
  const configPath = path.join(process.cwd(), OPENCODE_DIR, CONFIG_FILE);
6023
6097
  if (!existsSync(configPath))
6024
6098
  return;
6025
6099
  try {
6026
6100
  const content = readFileSync(configPath, "utf-8");
6027
- let safeJsonStr = content.replace(/"(?:\\.|[^"\\])*"|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, (m) => m.startsWith('"') ? m : "");
6028
- safeJsonStr = safeJsonStr.replace(/,\s*([\]}])/g, "$1");
6029
- const config = JSON.parse(safeJsonStr);
6030
- config.tools = config.tools || {};
6101
+ const config = parseJsonc(content);
6031
6102
  let updated = false;
6032
- for (const tool of newTools) {
6033
- if (!config.tools[tool]) {
6034
- config.tools[tool] = true;
6035
- updated = true;
6103
+ if (newTools.length > 0) {
6104
+ config.tools = config.tools || {};
6105
+ for (const tool of newTools) {
6106
+ if (!config.tools[tool]) {
6107
+ config.tools[tool] = true;
6108
+ updated = true;
6109
+ }
6110
+ }
6111
+ }
6112
+ if (newRulePaths.length > 0) {
6113
+ config.instructions = config.instructions || [];
6114
+ for (const rulePath of newRulePaths) {
6115
+ if (!config.instructions.includes(rulePath)) {
6116
+ config.instructions.push(rulePath);
6117
+ updated = true;
6118
+ }
6036
6119
  }
6037
6120
  }
6038
6121
  if (updated)
6039
6122
  writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
6040
6123
  } catch (e2) {
6041
- console.error(`
6042
- ${YELLOW}Warning: Failed to inject tools into opencode.jsonc. ${e2.message}${RESET}`);
6124
+ handleExecError(e2, "Failed to update opencode.jsonc", "warn" /* WARN */);
6043
6125
  }
6044
6126
  }
6045
- function updateOpencodeConfigInstructions(rulePaths) {
6046
- if (rulePaths.length === 0)
6127
+ function removeOpencodeConfig(toolsToRemove, rulesToRemove) {
6128
+ if (toolsToRemove.length === 0 && rulesToRemove.length === 0)
6047
6129
  return;
6048
6130
  const configPath = path.join(process.cwd(), OPENCODE_DIR, CONFIG_FILE);
6049
6131
  if (!existsSync(configPath))
6050
6132
  return;
6051
6133
  try {
6052
6134
  const content = readFileSync(configPath, "utf-8");
6053
- let safeJsonStr = content.replace(/"(?:\\.|[^"\\])*"|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, (m) => m.startsWith('"') ? m : "");
6054
- safeJsonStr = safeJsonStr.replace(/,\s*([\]}])/g, "$1");
6055
- const config = JSON.parse(safeJsonStr);
6056
- config.instructions = config.instructions || [];
6135
+ const config = parseJsonc(content);
6057
6136
  let updated = false;
6058
- for (const rulePath of rulePaths) {
6059
- if (!config.instructions.includes(rulePath)) {
6060
- config.instructions.push(rulePath);
6137
+ if (toolsToRemove.length > 0 && config.tools) {
6138
+ for (const tool of toolsToRemove) {
6139
+ if (tool in config.tools) {
6140
+ delete config.tools[tool];
6141
+ updated = true;
6142
+ }
6143
+ }
6144
+ }
6145
+ if (rulesToRemove.length > 0 && config.instructions) {
6146
+ const originalLength = config.instructions.length;
6147
+ config.instructions = config.instructions.filter((inst) => {
6148
+ return !rulesToRemove.some((rule) => inst.includes(`/${RULES_SUBDIR}/${rule}/`));
6149
+ });
6150
+ if (config.instructions.length !== originalLength) {
6061
6151
  updated = true;
6062
6152
  }
6063
6153
  }
6064
6154
  if (updated)
6065
6155
  writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
6066
6156
  } catch (e2) {
6067
- console.error(`
6068
- ${YELLOW}Warning: Failed to inject instructions into opencode.jsonc. ${e2.message}${RESET}`);
6157
+ handleExecError(e2, "Failed to remove items from opencode.jsonc", "warn" /* WARN */);
6069
6158
  }
6070
6159
  }
6160
+
6161
+ // src/utils/file.ts
6162
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, cpSync, readdirSync, rmSync } from "fs";
6163
+ import path2 from "path";
6071
6164
  function copyToolFiles(toolName, sourceDir, targetDir) {
6072
6165
  let hasPython = false;
6073
6166
  const tsFile = `${toolName}.ts`;
6074
6167
  const pyFile = `${toolName}.py`;
6075
- const srcTs = path.join(sourceDir, tsFile);
6076
- if (existsSync(srcTs))
6077
- cpSync(srcTs, path.join(targetDir, tsFile), { recursive: true });
6078
- const srcPy = path.join(sourceDir, pyFile);
6079
- if (existsSync(srcPy)) {
6080
- cpSync(srcPy, path.join(targetDir, pyFile), { recursive: true });
6168
+ const srcTs = path2.join(sourceDir, tsFile);
6169
+ if (existsSync2(srcTs))
6170
+ cpSync(srcTs, path2.join(targetDir, tsFile), { recursive: true });
6171
+ const srcPy = path2.join(sourceDir, pyFile);
6172
+ if (existsSync2(srcPy)) {
6173
+ cpSync(srcPy, path2.join(targetDir, pyFile), { recursive: true });
6081
6174
  hasPython = true;
6082
6175
  }
6083
6176
  return hasPython;
6084
6177
  }
6085
6178
  function installRules(categories, rulesSourceDir, targetRulesDir) {
6086
- if (!existsSync(targetRulesDir))
6087
- mkdirSync(targetRulesDir, { recursive: true });
6179
+ if (!existsSync2(targetRulesDir))
6180
+ mkdirSync2(targetRulesDir, { recursive: true });
6088
6181
  const installedRulePaths = [];
6089
- const commonSource = path.join(rulesSourceDir, "common");
6090
- const commonTarget = path.join(targetRulesDir, "common");
6091
- if (existsSync(commonSource)) {
6092
- if (!existsSync(commonTarget))
6093
- mkdirSync(commonTarget, { recursive: true });
6094
- const files = readdirSync(commonSource).filter((f) => f.endsWith(".md"));
6095
- for (const file of files) {
6096
- cpSync(path.join(commonSource, file), path.join(commonTarget, file));
6097
- installedRulePaths.push(`./${RULES_SUBDIR}/common/${file}`);
6098
- }
6099
- }
6100
- for (const category of categories) {
6101
- const catSource = path.join(rulesSourceDir, category);
6102
- const catTarget = path.join(targetRulesDir, category);
6103
- if (existsSync(catSource)) {
6104
- if (!existsSync(catTarget))
6105
- mkdirSync(catTarget, { recursive: true });
6106
- const files = readdirSync(catSource).filter((f) => f.endsWith(".md"));
6182
+ const copyDir = (srcFolder, targetFolder, relativePathPrefix) => {
6183
+ if (existsSync2(srcFolder)) {
6184
+ if (!existsSync2(targetFolder))
6185
+ mkdirSync2(targetFolder, { recursive: true });
6186
+ const files = readdirSync(srcFolder).filter((f) => f.endsWith(".md"));
6107
6187
  for (const file of files) {
6108
- cpSync(path.join(catSource, file), path.join(catTarget, file));
6109
- installedRulePaths.push(`./${RULES_SUBDIR}/${category}/${file}`);
6188
+ cpSync(path2.join(srcFolder, file), path2.join(targetFolder, file));
6189
+ installedRulePaths.push(`./${RULES_SUBDIR}/${relativePathPrefix}/${file}`);
6110
6190
  }
6111
6191
  }
6192
+ };
6193
+ copyDir(path2.join(rulesSourceDir, "common"), path2.join(targetRulesDir, "common"), "common");
6194
+ for (const category of categories) {
6195
+ copyDir(path2.join(rulesSourceDir, category), path2.join(targetRulesDir, category), category);
6112
6196
  }
6113
6197
  return installedRulePaths;
6114
6198
  }
6199
+ function removeToolFiles(toolName, targetDir) {
6200
+ const tsFile = path2.join(targetDir, `${toolName}.ts`);
6201
+ const pyFile = path2.join(targetDir, `${toolName}.py`);
6202
+ if (existsSync2(tsFile))
6203
+ rmSync(tsFile, { force: true });
6204
+ if (existsSync2(pyFile))
6205
+ rmSync(pyFile, { force: true });
6206
+ }
6207
+ function removeRuleCategory(category, targetRulesDir) {
6208
+ const catDir = path2.join(targetRulesDir, category);
6209
+ if (existsSync2(catDir)) {
6210
+ rmSync(catDir, { recursive: true, force: true });
6211
+ }
6212
+ }
6213
+
6214
+ // src/utils/python.ts
6215
+ import { execSync } from "child_process";
6216
+ import { existsSync as existsSync3, writeFileSync as writeFileSync2, readFileSync as readFileSync2 } from "fs";
6217
+ import path3 from "path";
6115
6218
  function setupPythonEnvironment(rootDir, spinner) {
6116
6219
  spinner.message(`Initializing Python environment in ./.venv ...`);
6117
6220
  try {
6118
- const reqPath = path.join(rootDir, "requirements.txt");
6221
+ const reqPath = path3.join(rootDir, "requirements.txt");
6119
6222
  const reqContent = `# 核心依赖
6120
6223
  requests>=2.28.0
6121
6224
  urllib3>=1.26.0
6122
6225
  python-dotenv>=0.19.0
6123
6226
  `;
6124
- if (!existsSync(reqPath)) {
6125
- writeFileSync(reqPath, reqContent, "utf-8");
6227
+ if (!existsSync3(reqPath)) {
6228
+ writeFileSync2(reqPath, reqContent, "utf-8");
6126
6229
  } else {
6127
- const existingReq = readFileSync(reqPath, "utf-8");
6230
+ const existingReq = readFileSync2(reqPath, "utf-8");
6128
6231
  if (!existingReq.includes("requests>="))
6129
- writeFileSync(reqPath, existingReq + `
6232
+ writeFileSync2(reqPath, existingReq + `
6130
6233
  ` + reqContent, "utf-8");
6131
6234
  }
6132
- const venvPath = path.join(rootDir, ".venv");
6133
- if (!existsSync(venvPath)) {
6235
+ const venvPath = path3.join(rootDir, ".venv");
6236
+ if (!existsSync3(venvPath)) {
6134
6237
  try {
6135
6238
  execSync(`python3 -m venv "${venvPath}"`, { stdio: "ignore" });
6136
6239
  } catch {
6137
6240
  execSync(`python -m venv "${venvPath}"`, { stdio: "ignore" });
6138
6241
  }
6139
6242
  }
6140
- const pipCmd = process.platform === "win32" ? path.join(venvPath, "Scripts", "pip") : path.join(venvPath, "bin", "pip");
6243
+ const pipCmd = process.platform === "win32" ? path3.join(venvPath, "Scripts", "pip") : path3.join(venvPath, "bin", "pip");
6141
6244
  spinner.message(`Installing Python dependencies...`);
6142
6245
  execSync(`"${pipCmd}" install -r "${reqPath}"`, { stdio: "ignore" });
6143
6246
  } catch (pyError) {
6144
- R2.warn(`⚠️ Failed to initialize Python environment. Manual setup required.`);
6247
+ spinner.stop();
6248
+ handleExecError(pyError, "Failed to initialize Python environment. Manual setup required.", "warn" /* WARN */);
6145
6249
  }
6146
6250
  }
6251
+ function getPythonActivationCmd() {
6252
+ const isWin = process.platform === "win32";
6253
+ return isWin ? ".\\.venv\\Scripts\\Activate.ps1" : "source .venv/bin/activate";
6254
+ }
6255
+
6256
+ // src/commands/add.ts
6147
6257
  async function runAdd(args) {
6148
6258
  const repository = args[0];
6149
6259
  if (!repository) {
6150
- console.error(`${CYAN}Error:${RESET} Repository name required.`);
6260
+ handleExecError(new Error("Repository name is missing"), "Argument Error", "error" /* ERROR */);
6151
6261
  process.exit(1);
6152
6262
  }
6153
6263
  const repoUrl = `https://github.com/${repository}.git`;
@@ -6156,9 +6266,9 @@ async function runAdd(args) {
6156
6266
  Target: ${CYAN}${OPENCODE_DIR}${RESET}`, "Initializing");
6157
6267
  R2.step("Executing standard skills installer (pnpx skills add)...");
6158
6268
  try {
6159
- execSync(`pnpx skills add ${repository} --agent opencode`, { stdio: "inherit" });
6160
- } catch {
6161
- R2.warn("Skills installer finished with warnings.");
6269
+ execSync2(`pnpx skills add ${repository} --agent opencode`, { stdio: "inherit" });
6270
+ } catch (err) {
6271
+ handleExecError(err, "Skills installer finished with warnings", "warn" /* WARN */);
6162
6272
  }
6163
6273
  const s = bt2();
6164
6274
  s.start("Fetching remote repository...");
@@ -6166,89 +6276,85 @@ Target: ${CYAN}${OPENCODE_DIR}${RESET}`, "Initializing");
6166
6276
  try {
6167
6277
  tempDir = await cloneRepo(repoUrl);
6168
6278
  s.stop("Remote repository parsed.");
6169
- const toolDirPath = path.join(tempDir, "tool");
6170
- const rulesDirPath = path.join(tempDir, "rules");
6171
- const hasTools = existsSync(toolDirPath);
6172
- const hasRules = existsSync(rulesDirPath);
6173
- if (!hasTools && !hasRules) {
6174
- return R2.warn('Neither "tool" nor "rules" directory found in repository.');
6175
- }
6279
+ const toolDirPath = path4.join(tempDir, "tool");
6280
+ const rulesDirPath = path4.join(tempDir, "rules");
6281
+ const hasTools = existsSync4(toolDirPath);
6282
+ const hasRules = existsSync4(rulesDirPath);
6283
+ if (!hasTools && !hasRules)
6284
+ return handleExecError(new Error(`Neither "tool' nor "rules' directory found in repository.`), "warn" /* WARN */);
6176
6285
  let selectedTools = [];
6177
6286
  let selectedRules = [];
6178
6287
  if (hasTools) {
6179
- const availableTools = readdirSync(toolDirPath).filter((f) => f.endsWith(".ts")).map((f) => f.replace(/\.ts$/, ""));
6180
- if (availableTools.length > 0) {
6181
- const result = await je({
6288
+ const opts = readdirSync2(toolDirPath).filter((f) => f.endsWith(".ts")).map((f) => f.replace(/\.ts$/, ""));
6289
+ if (opts.length > 0) {
6290
+ const res = await je({
6182
6291
  message: "Select tools to install (space to toggle)",
6183
- options: availableTools.map((t) => ({ value: t, label: t })),
6292
+ options: opts.map((t) => ({ value: t, label: t })),
6184
6293
  required: false
6185
6294
  });
6186
- if (Ct(result))
6295
+ if (Ct(res))
6187
6296
  return Ne("Installation cancelled.");
6188
- if (Array.isArray(result))
6189
- selectedTools = result;
6297
+ if (Array.isArray(res))
6298
+ selectedTools = res;
6190
6299
  }
6191
6300
  }
6192
6301
  if (hasRules) {
6193
- const availableRules = readdirSync(rulesDirPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory() && dirent.name !== "common").map((dirent) => dirent.name);
6194
- if (availableRules.length > 0) {
6195
- const result = await je({
6196
- message: "Select rule categories to install (space to toggle)",
6197
- options: availableRules.map((r) => ({ value: r, label: r })),
6302
+ const opts = readdirSync2(rulesDirPath, { withFileTypes: true }).filter((d2) => d2.isDirectory() && d2.name !== "common").map((d2) => d2.name);
6303
+ if (opts.length > 0) {
6304
+ const res = await je({
6305
+ message: "Select rule categories to install",
6306
+ options: opts.map((r) => ({ value: r, label: r })),
6198
6307
  required: false
6199
6308
  });
6200
- if (Ct(result))
6309
+ if (Ct(res))
6201
6310
  return Ne("Installation cancelled.");
6202
- if (Array.isArray(result))
6203
- selectedRules = result;
6311
+ if (Array.isArray(res))
6312
+ selectedRules = res;
6204
6313
  }
6205
6314
  }
6206
- if (selectedTools.length === 0 && selectedRules.length === 0) {
6207
- Ne("No tools or rules selected.");
6208
- return;
6209
- }
6315
+ if (selectedTools.length === 0 && selectedRules.length === 0)
6316
+ return Ne("No tools or rules selected.");
6210
6317
  const installSpinner = bt2();
6211
6318
  installSpinner.start(`Installing to ${OPENCODE_DIR}/ ...`);
6319
+ ensureOpencodeConfig();
6212
6320
  const lockData = readLockFile();
6213
6321
  const now = new Date().toISOString();
6214
6322
  let requiresPython = false;
6215
- ensureOpencodeConfig();
6323
+ let installedRulePaths = [];
6216
6324
  if (selectedTools.length > 0) {
6217
- const targetToolDir = path.join(process.cwd(), OPENCODE_DIR, TOOL_SUBDIR);
6218
- if (!existsSync(targetToolDir))
6219
- mkdirSync(targetToolDir, { recursive: true });
6325
+ const targetToolDir = path4.join(process.cwd(), OPENCODE_DIR, TOOL_SUBDIR);
6326
+ if (!existsSync4(targetToolDir))
6327
+ mkdirSync3(targetToolDir, { recursive: true });
6220
6328
  for (const tool of selectedTools) {
6221
- const hasPy = copyToolFiles(tool, toolDirPath, targetToolDir);
6222
- if (hasPy)
6329
+ if (copyToolFiles(tool, toolDirPath, targetToolDir))
6223
6330
  requiresPython = true;
6224
6331
  if (lockData.tools)
6225
6332
  lockData.tools[tool] = { source: repoUrl, installedAt: now };
6226
6333
  }
6227
- updateOpencodeConfigTools(selectedTools);
6228
6334
  }
6229
6335
  if (selectedRules.length > 0) {
6230
- const targetRulesDir = path.join(process.cwd(), OPENCODE_DIR, RULES_SUBDIR);
6231
- const installedRulePaths = installRules(selectedRules, rulesDirPath, targetRulesDir);
6336
+ const targetRulesDir = path4.join(process.cwd(), OPENCODE_DIR, RULES_SUBDIR);
6337
+ installedRulePaths = installRules(selectedRules, rulesDirPath, targetRulesDir);
6232
6338
  for (const rule of selectedRules) {
6233
6339
  if (!lockData.rules)
6234
6340
  lockData.rules = {};
6235
6341
  lockData.rules[rule] = { source: repoUrl, installedAt: now };
6236
6342
  }
6237
- updateOpencodeConfigInstructions(installedRulePaths);
6238
6343
  }
6344
+ updateOpencodeConfig(selectedTools, installedRulePaths);
6239
6345
  writeLockFile(lockData);
6240
- if (requiresPython) {
6346
+ if (requiresPython)
6241
6347
  setupPythonEnvironment(process.cwd(), installSpinner);
6348
+ await new Promise((r) => setTimeout(r, 400));
6349
+ installSpinner.stop(`${GREEN}Successfully installed ${selectedTools.length + selectedRules.length} items.${RESET}`);
6350
+ if (requiresPython) {
6351
+ Ve(`Your Python tools are ready. Run:
6352
+
6353
+ ${CYAN}${getPythonActivationCmd()}${RESET}`, "\uD83D\uDC0D Python Environment");
6242
6354
  }
6243
- const totalInstalled = selectedTools.length + selectedRules.length;
6244
- installSpinner.stop(`${GREEN}Successfully installed ${totalInstalled} items.${RESET}`);
6245
6355
  } catch (error) {
6246
6356
  s.stop("Failed to fetch repository.");
6247
- if (error instanceof GitCloneError)
6248
- R2.error(`${YELLOW}Git Error:${RESET}
6249
- ${error.message}`);
6250
- else
6251
- R2.error(`Error: ${error.message}`);
6357
+ handleExecError(error, "Repository Fetch Error", "error" /* ERROR */);
6252
6358
  } finally {
6253
6359
  if (tempDir)
6254
6360
  await cleanupTempDir(tempDir).catch(() => {});
@@ -6256,57 +6362,48 @@ ${error.message}`);
6256
6362
  Le(`✨ Workspace updated for ${CYAN}${OPENCODE_DIR}${RESET}`);
6257
6363
  }
6258
6364
 
6259
- // src/list.ts
6260
- import { execSync as execSync2 } from "child_process";
6365
+ // src/commands/list.ts
6366
+ import { execSync as execSync3 } from "child_process";
6261
6367
  async function runList(args) {
6262
6368
  const lockData = readLockFile();
6263
6369
  console.log(`
6264
6370
  ${BOLD}\uD83D\uDEE0️ Installed Tools (${OPENCODE_DIR}/${TOOL_SUBDIR}):${RESET}
6265
6371
  `);
6266
6372
  const tools = Object.keys(lockData.tools || {});
6267
- if (tools.length === 0) {
6373
+ if (tools.length === 0)
6268
6374
  console.log(` ${DIM}No tools installed yet.${RESET}`);
6269
- } else {
6270
- tools.forEach((t) => {
6271
- const source = lockData.tools[t]?.source || "unknown";
6272
- console.log(` ${CYAN}◆${RESET} ${t} ${DIM}(${source})${RESET}`);
6273
- });
6274
- }
6375
+ else
6376
+ tools.forEach((t) => console.log(` ${CYAN}◆${RESET} ${t} ${DIM}(${lockData.tools[t]?.source || "unknown"})${RESET}`));
6275
6377
  console.log(`
6276
6378
  ${BOLD}\uD83D\uDCDC Installed Rules (${OPENCODE_DIR}/${RULES_SUBDIR}):${RESET}
6277
6379
  `);
6278
6380
  const rules = Object.keys(lockData.rules || {});
6279
- if (rules.length === 0) {
6381
+ if (rules.length === 0)
6280
6382
  console.log(` ${DIM}No rules installed yet.${RESET}`);
6281
- } else {
6282
- rules.forEach((r) => {
6283
- const source = lockData.rules[r]?.source || "unknown";
6284
- console.log(` ${CYAN}◆${RESET} ${r} ${DIM}(${source})${RESET}`);
6285
- });
6286
- }
6383
+ else
6384
+ rules.forEach((r) => console.log(` ${CYAN}◆${RESET} ${r} ${DIM}(${lockData.rules[r]?.source || "unknown"})${RESET}`));
6287
6385
  console.log(`
6288
6386
  ${BOLD}\uD83E\uDE84 Installed Skills (Standard):${RESET}
6289
6387
  `);
6290
6388
  try {
6291
- execSync2("pnpx skills ls", { stdio: "inherit" });
6389
+ execSync3("pnpx skills ls", { stdio: "inherit" });
6292
6390
  } catch (error) {
6293
- console.log(` ${YELLOW}No standard skills found or failed to fetch.${RESET}`);
6391
+ handleExecError(error, "No standard skills found or failed to fetch", "warn" /* WARN */);
6294
6392
  }
6295
- console.log();
6296
6393
  }
6297
6394
 
6298
- // src/update.ts
6299
- import { execSync as execSync3 } from "child_process";
6300
- import { existsSync as existsSync2 } from "fs";
6301
- import path2 from "path";
6395
+ // src/commands/update.ts
6396
+ import { execSync as execSync4 } from "child_process";
6397
+ import { existsSync as existsSync5 } from "fs";
6398
+ import path5 from "path";
6302
6399
  async function runUpdate(args) {
6303
6400
  console.log(`
6304
6401
  ${BOLD}\uD83E\uDE84 Updating Standard Skills...${RESET}
6305
6402
  `);
6306
6403
  try {
6307
- execSync3("pnpx skills update", { stdio: "inherit" });
6404
+ execSync4("pnpx skills update", { stdio: "inherit" });
6308
6405
  } catch (error) {
6309
- console.log(` ${YELLOW}Failed to update standard skills or none installed.${RESET}`);
6406
+ handleExecError(error, "Failed to update standard skills", "warn" /* WARN */);
6310
6407
  }
6311
6408
  console.log(`
6312
6409
  ${BOLD}\uD83D\uDCE6 Updating Local Tools & Rules...${RESET}
@@ -6315,73 +6412,174 @@ ${BOLD}\uD83D\uDCE6 Updating Local Tools & Rules...${RESET}
6315
6412
  const toolNames = Object.keys(lockData.tools || {});
6316
6413
  const ruleNames = Object.keys(lockData.rules || {});
6317
6414
  if (toolNames.length === 0 && ruleNames.length === 0) {
6318
- console.log(` ${DIM}No local tools or rules tracked in lock file.${RESET}
6415
+ return console.log(` ${DIM}No local items to update.${RESET}
6319
6416
  `);
6320
- return;
6321
6417
  }
6322
6418
  const itemsBySource = {};
6323
6419
  toolNames.forEach((t) => {
6324
- const src = lockData.tools[t]?.source;
6325
- if (src) {
6326
- itemsBySource[src] = itemsBySource[src] || { tools: [], rules: [] };
6327
- itemsBySource[src].tools.push(t);
6420
+ const s2 = lockData.tools[t]?.source;
6421
+ if (s2) {
6422
+ itemsBySource[s2] = itemsBySource[s2] || { tools: [], rules: [] };
6423
+ itemsBySource[s2].tools.push(t);
6328
6424
  }
6329
6425
  });
6330
6426
  ruleNames.forEach((r) => {
6331
- const src = lockData.rules[r]?.source;
6332
- if (src) {
6333
- itemsBySource[src] = itemsBySource[src] || { tools: [], rules: [] };
6334
- itemsBySource[src].rules.push(r);
6427
+ const s2 = lockData.rules[r]?.source;
6428
+ if (s2) {
6429
+ itemsBySource[s2] = itemsBySource[s2] || { tools: [], rules: [] };
6430
+ itemsBySource[s2].rules.push(r);
6335
6431
  }
6336
6432
  });
6337
- const targetToolDir = path2.join(process.cwd(), OPENCODE_DIR, TOOL_SUBDIR);
6338
- const targetRulesDir = path2.join(process.cwd(), OPENCODE_DIR, RULES_SUBDIR);
6433
+ const targetToolDir = path5.join(process.cwd(), OPENCODE_DIR, TOOL_SUBDIR);
6434
+ const targetRulesDir = path5.join(process.cwd(), OPENCODE_DIR, RULES_SUBDIR);
6339
6435
  const now = new Date().toISOString();
6340
- let successCount = 0;
6341
- for (const [source, items] of Object.entries(itemsBySource)) {
6342
- const s = bt2();
6343
- s.start(`Fetching from ${CYAN}${source}${RESET}...`);
6436
+ const sourcesCount = Object.keys(itemsBySource).length;
6437
+ const s = bt2();
6438
+ s.start(`Fetching from ${CYAN}${sourcesCount}${RESET} source(s) concurrently...`);
6439
+ const updatePromises = Object.entries(itemsBySource).map(async ([source, items]) => {
6344
6440
  let tempDir = null;
6441
+ let successCount = 0;
6442
+ const logs = [];
6345
6443
  try {
6346
6444
  tempDir = await cloneRepo(source);
6347
- if (items.tools.length > 0) {
6348
- const toolDirPath = path2.join(tempDir, "tool");
6349
- if (existsSync2(toolDirPath)) {
6350
- for (const tool of items.tools) {
6351
- copyToolFiles(tool, toolDirPath, targetToolDir);
6352
- if (lockData.tools[tool])
6353
- lockData.tools[tool].installedAt = now;
6354
- successCount++;
6355
- console.log(` ${GREEN}✓${RESET} Updated tool: ${tool}`);
6356
- }
6445
+ if (items.tools.length > 0 && existsSync5(path5.join(tempDir, "tool"))) {
6446
+ for (const tool of items.tools) {
6447
+ copyToolFiles(tool, path5.join(tempDir, "tool"), targetToolDir);
6448
+ if (lockData.tools[tool])
6449
+ lockData.tools[tool].installedAt = now;
6450
+ successCount++;
6451
+ logs.push(` ${GREEN}✓${RESET} Updated tool: ${tool}`);
6357
6452
  }
6358
6453
  }
6359
- if (items.rules.length > 0) {
6360
- const rulesDirPath = path2.join(tempDir, "rules");
6361
- if (existsSync2(rulesDirPath)) {
6362
- installRules(items.rules, rulesDirPath, targetRulesDir);
6363
- for (const rule of items.rules) {
6364
- if (lockData.rules[rule])
6365
- lockData.rules[rule].installedAt = now;
6366
- successCount++;
6367
- console.log(` ${GREEN}✓${RESET} Updated rule: ${rule}`);
6368
- }
6454
+ if (items.rules.length > 0 && existsSync5(path5.join(tempDir, "rules"))) {
6455
+ installRules(items.rules, path5.join(tempDir, "rules"), targetRulesDir);
6456
+ for (const rule of items.rules) {
6457
+ if (lockData.rules[rule])
6458
+ lockData.rules[rule].installedAt = now;
6459
+ successCount++;
6460
+ logs.push(` ${GREEN}✓${RESET} Updated rule: ${rule}`);
6369
6461
  }
6370
6462
  }
6371
- s.stop(`Done with ${source}`);
6463
+ return { source, success: true, count: successCount, logs };
6372
6464
  } catch (err) {
6373
- s.stop(` ${YELLOW}✗ Failed to fetch from ${source}${RESET}`);
6465
+ return { source, success: false, count: 0, error: err, logs: [] };
6374
6466
  } finally {
6375
6467
  if (tempDir)
6376
6468
  await cleanupTempDir(tempDir).catch(() => {});
6377
6469
  }
6470
+ });
6471
+ const results = await Promise.allSettled(updatePromises);
6472
+ s.stop(`Finished fetching from ${sourcesCount} source(s).`);
6473
+ let totalSuccessCount = 0;
6474
+ for (const result of results) {
6475
+ if (result.status === "fulfilled") {
6476
+ const { source, success, count, logs, error } = result.value;
6477
+ if (success) {
6478
+ totalSuccessCount += count;
6479
+ logs.forEach((log) => console.log(log));
6480
+ } else {
6481
+ handleExecError(error, `Failed to fetch from ${source}`, "warn" /* WARN */);
6482
+ }
6483
+ } else {
6484
+ handleExecError(result.reason, "Unexpected error during concurrent update", "error" /* ERROR */);
6485
+ }
6378
6486
  }
6379
6487
  writeLockFile(lockData);
6380
- console.log();
6381
- if (successCount > 0) {
6382
- console.log(`${TEXT}✓ Successfully updated ${successCount} local item(s)${RESET}`);
6488
+ if (totalSuccessCount > 0) {
6489
+ console.log(`${TEXT}✓ Successfully updated ${totalSuccessCount} local item(s)${RESET}
6490
+ `);
6383
6491
  }
6384
- console.log();
6492
+ }
6493
+
6494
+ // src/commands/remove.ts
6495
+ import { execSync as execSync5 } from "child_process";
6496
+ import path6 from "path";
6497
+ async function runRemove(args) {
6498
+ We(`${BG_CYAN} vibe cli ${RESET}`);
6499
+ R2.step("Executing standard skills remover (pnpx skills remove)...");
6500
+ try {
6501
+ const cmdArgs = args.length > 0 ? args.join(" ") : "";
6502
+ execSync5(`pnpx skills remove ${cmdArgs}`.trim(), { stdio: "inherit" });
6503
+ } catch (err) {
6504
+ R2.warn("Skills remover finished with warnings or nothing to remove.");
6505
+ }
6506
+ const lockData = readLockFile();
6507
+ const installedTools = Object.keys(lockData.tools || {});
6508
+ const installedRules = Object.keys(lockData.rules || {});
6509
+ if (installedTools.length === 0 && installedRules.length === 0) {
6510
+ R2.info(`No local tools or rules found in ${OPENCODE_DIR}.`);
6511
+ return Le(`✨ Removal process completed.`);
6512
+ }
6513
+ let toolsToRemove = [];
6514
+ let rulesToRemove = [];
6515
+ if (args.length > 0) {
6516
+ for (const arg of args) {
6517
+ if (installedTools.includes(arg))
6518
+ toolsToRemove.push(arg);
6519
+ else if (installedRules.includes(arg))
6520
+ rulesToRemove.push(arg);
6521
+ else {
6522
+ R2.info(`Local item '${arg}' not found, skipping local cleanup.`);
6523
+ }
6524
+ }
6525
+ } else {
6526
+ if (installedTools.length > 0) {
6527
+ const res = await je({
6528
+ message: "Select local tools to remove (space to toggle)",
6529
+ options: installedTools.map((t) => ({ value: t, label: t })),
6530
+ required: false
6531
+ });
6532
+ if (Ct(res))
6533
+ return Ne("Operation cancelled.");
6534
+ if (Array.isArray(res))
6535
+ toolsToRemove = res;
6536
+ }
6537
+ if (installedRules.length > 0) {
6538
+ const res = await je({
6539
+ message: "Select rule categories to remove (space to toggle)",
6540
+ options: installedRules.map((r) => ({ value: r, label: r })),
6541
+ required: false
6542
+ });
6543
+ if (Ct(res))
6544
+ return Ne("Operation cancelled.");
6545
+ if (Array.isArray(res))
6546
+ rulesToRemove = res;
6547
+ }
6548
+ }
6549
+ if (toolsToRemove.length === 0 && rulesToRemove.length === 0) {
6550
+ if (args.length === 0)
6551
+ Ne("No local items selected for removal.");
6552
+ else
6553
+ Le(`✨ Removal process completed.`);
6554
+ return;
6555
+ }
6556
+ const confirm = await Re({
6557
+ message: `Are you sure you want to completely remove ${toolsToRemove.length} local tool(s) and ${rulesToRemove.length} local rule(s)?`
6558
+ });
6559
+ if (Ct(confirm) || !confirm) {
6560
+ return Ne("Operation cancelled.");
6561
+ }
6562
+ const s = bt2();
6563
+ s.start(`Cleaning up local workspace...`);
6564
+ try {
6565
+ const targetToolDir = path6.join(process.cwd(), OPENCODE_DIR, TOOL_SUBDIR);
6566
+ const targetRulesDir = path6.join(process.cwd(), OPENCODE_DIR, RULES_SUBDIR);
6567
+ for (const tool of toolsToRemove) {
6568
+ removeToolFiles(tool, targetToolDir);
6569
+ delete lockData.tools[tool];
6570
+ }
6571
+ for (const rule of rulesToRemove) {
6572
+ removeRuleCategory(rule, targetRulesDir);
6573
+ delete lockData.rules[rule];
6574
+ }
6575
+ removeOpencodeConfig(toolsToRemove, rulesToRemove);
6576
+ writeLockFile(lockData);
6577
+ s.stop(`${GREEN}Successfully removed selected local items.${RESET}`);
6578
+ } catch (e2) {
6579
+ s.stop("Failed to complete local removal.");
6580
+ handleExecError(e2, "Removal Error", "error" /* ERROR */);
6581
+ }
6582
+ Le(`✨ Workspace cleaned for ${CYAN}${OPENCODE_DIR}${RESET}`);
6385
6583
  }
6386
6584
 
6387
6585
  // src/cli.ts
@@ -6393,9 +6591,20 @@ var VIBE_LOGO = [
6393
6591
  " ╚████╔╝ ██║██████╔╝███████╗",
6394
6592
  " ╚═══╝ ╚═╝╚═════╝ ╚══════╝"
6395
6593
  ];
6594
+ var GRAYS = [
6595
+ "\x1B[38;5;250m",
6596
+ "\x1B[38;5;248m",
6597
+ "\x1B[38;5;245m",
6598
+ "\x1B[38;5;243m",
6599
+ "\x1B[38;5;240m",
6600
+ "\x1B[38;5;238m"
6601
+ ];
6396
6602
  function showLogo() {
6397
6603
  console.log();
6398
- VIBE_LOGO.forEach((line) => console.log(`${CYAN}${line}${RESET}`));
6604
+ VIBE_LOGO.forEach((line, i) => {
6605
+ const color = GRAYS[i] || GRAYS[GRAYS.length - 1];
6606
+ console.log(`${color}${line}${RESET}`);
6607
+ });
6399
6608
  console.log();
6400
6609
  }
6401
6610
  function showBanner() {
@@ -6405,6 +6614,7 @@ function showBanner() {
6405
6614
  console.log(` ${DIM}$${RESET} ${TEXT}vibe add <repository>${RESET} ${DIM}Add skills & tools${RESET}`);
6406
6615
  console.log(` ${DIM}$${RESET} ${TEXT}vibe list${RESET} ${DIM}List installed tools & skills${RESET}`);
6407
6616
  console.log(` ${DIM}$${RESET} ${TEXT}vibe update${RESET} ${DIM}Update installed tools & skills${RESET}`);
6617
+ console.log(` ${DIM}$${RESET} ${TEXT}vibe remove${RESET} ${DIM}Remove tools & rules${RESET}`);
6408
6618
  console.log();
6409
6619
  console.log(`${DIM}Example:${RESET} vibe add helloggx/skill`);
6410
6620
  console.log();
@@ -6419,6 +6629,7 @@ ${BOLD}Manage Tools & Skills:${RESET}
6419
6629
  https://github.com/helloggx/skill
6420
6630
  list, ls List installed tools & skills for .opencode
6421
6631
  update, up Update all local tools and standard skills
6632
+ remove, rm Remove selected tools & rules from .opencode
6422
6633
 
6423
6634
  ${BOLD}Options:${RESET}
6424
6635
  --help, -h Show this help message
@@ -6427,6 +6638,7 @@ ${BOLD}Examples:${RESET}
6427
6638
  ${DIM}$${RESET} vibe add helloggx/skill
6428
6639
  ${DIM}$${RESET} vibe list ${DIM}# list installed tools and skills${RESET}
6429
6640
  ${DIM}$${RESET} vibe update ${DIM}# check and update all items${RESET}
6641
+ ${DIM}$${RESET} vibe remove ${DIM}# remove selected tools and rules${RESET}
6430
6642
 
6431
6643
  The Design-Driven Agent Skills Ecosystem
6432
6644
  `);
@@ -6445,6 +6657,11 @@ async function main() {
6445
6657
  showLogo();
6446
6658
  await runAdd(args.slice(1));
6447
6659
  break;
6660
+ case "rm":
6661
+ case "remove":
6662
+ console.clear();
6663
+ await runRemove(args.slice(1));
6664
+ break;
6448
6665
  case "ls":
6449
6666
  case "list":
6450
6667
  await runList(args.slice(1));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-coder/cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Vibe Coding development tool",
5
5
  "private": false,
6
6
  "type": "module",