@vibe-coder/cli 1.0.0 → 1.0.2

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 +79 -78
  2. package/README.zh.md +140 -0
  3. package/dist/cli.js +398 -181
  4. package/package.json +14 -3
package/README.md CHANGED
@@ -1,141 +1,142 @@
1
-
2
1
  <div align="center">
3
2
 
4
3
  # 🌊 Vibe Coding CLI
5
4
 
6
- **专为 OpenCode 打造的 vibe coding 生态构建工具**
5
+ **A vibe coding ecosystem builder tailored for OpenCode**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@vibe-coder/cli.svg?style=flat-square)](https://www.npmjs.com/package/@vibe-coder/cli)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](../../LICENSE.md)
9
+ [![Built with Bun](https://img.shields.io/badge/Bun-%23000000.svg?style=flat-square&logo=bun&logoColor=white)](https://bun.sh)
7
10
 
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)
11
+ **English** · [简体中文](https://github.com/HelloGGX/skill/blob/main/packages/vibe/README.zh.md)
11
12
 
12
- [English](#) · **简体中文**
13
+ > *Seamlessly aggregate AI tools and context rules, making your Agent truly understand your codebase.*
13
14
 
14
15
  </div>
15
16
 
16
- ## 📖 简介 (Introduction)
17
+ ## 📖 Introduction
17
18
 
18
- `vibe-coding-cli` 是一个专为 **OpenCode** 平台打造的现代命令行脚手架工具。它的核心目标是简化和自动化 Agent 技能(Skills)与底层脚本工具(Tools)的管理。
19
+ `@vibe-coder/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
20
 
20
- 通过 `vibe` 命令,你可以一键拉取远程 GitHub 仓库中的 TypeScript Python 工具脚本,自动将其注册到 OpenCode 配置中,并无缝管理其依赖环境(包括自动配置独立的 Python 虚拟环境)。
21
+ With the `vibe` command, you can pull TypeScript/Python tool scripts or Markdown rule files from remote GitHub repositories with a single click. It 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
22
 
22
- ## ✨ 核心特性 (Features)
23
+ ## ✨ Features
23
24
 
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。
25
+ * 🛠 **Fully Automated Tool Management:** Quickly parse, select, and download `.ts` / `.py` scripts from any GitHub repository straight to your local setup, ready to use out of the box.
26
+ * 📜 **Context & Capabilities United:** A unique ecosystem aggregation capability that perfectly blends the **tools/skills** needed for Agent execution with your **guidelines and best practices**. Supports on-demand installation of `.md` rule files so the AI truly understands your architectural intent.
27
+ * 📦 **Smart Configuration Injection:** Automatically intercepts and updates `.opencode/opencode.jsonc`, silently injecting tool enable-switches and Prompt instruction paths. Say goodbye to manual configuration forever.
28
+ * ⚡ **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.
29
+ * 🪄 **Standard Skills Aggregation:** Deeply integrated with Vercel's `pnpx skills` ecosystem, allowing you to manage standard Agent skill libraries alongside local extension resources within a unified CLI workflow.
29
30
 
30
31
  ---
31
32
 
32
- ## 🚀 快速开始 (Quick Start)
33
+ ## 🚀 Quick Start
33
34
 
34
- ### 安装
35
+ ### Prerequisites
35
36
 
36
- 作为全局包安装(推荐使用 npm bun):
37
+ * [Node.js](https://www.google.com/search?q=https://nodejs.org/) >= 18.0.0 or [Bun](https://bun.sh/) >= 1.0.0
37
38
 
38
- ```bash
39
- # 使用 npm
40
- npm install -g vibe-coding-cli
39
+ ### Installation
41
40
 
42
- # 使用 bun
43
- bun add -g vibe-coding-cli
41
+ Install as a global package:
44
42
 
45
- ```
43
+ ```bash
44
+ # Using npm
45
+ npm i -g @vibe-coder/cli
46
46
 
47
- *如果你在本地 monorepo 环境中开发,可以直接使用 `bun run dev` 或构建后执行 `./bin/vibe`。*
47
+ # Using bun
48
+ bun add -g @vibe-coder/cli
49
+ ```
48
50
 
49
- ### 基础用法
51
+ ### Basic Usage
50
52
 
51
- 初始化并添加一个技能库(例如本项目的 `helloggx/skill`):
53
+ Initialize and add an ecosystem library (e.g., `helloggx/skill` from this project):
52
54
 
53
55
  ```bash
54
56
  vibe add helloggx/skill
55
-
56
57
  ```
57
58
 
58
- *此命令将会弹出交互式菜单,让你选择想要安装的底层工具,并自动配置项目结构。*
59
+ *The CLI will pop up an interactive menu, allowing you to flexibly select the **Tools** and **Rules** you want to install, automatically configuring the entire environment for you.*
59
60
 
60
61
  ---
61
62
 
62
- ## 📚 命令指南 (Commands)
63
+ ## 📚 Commands
63
64
 
64
- ### 1. 添加工具与技能 (`add` / `a`)
65
+ | Command | Alias | Description |
66
+ | --- | --- | --- |
67
+ | `vibe add <repo>` | `a` | Parses the target GitHub repository, pops up a UI list, installs selected tools and rules on demand, and automatically injects configurations. |
68
+ | `vibe list` | `ls` | Prints a clear status map of all installed resources (local tools, context rules, global standard skills) in the current project. |
69
+ | `vibe update` | `up` | Concurrently pulls all source repositories recorded in `vibe-lock.json`, intelligently comparing and overwriting local scripts and rules. |
70
+ | `vibe remove [resource]` | `rm` | **No arguments:** Opens a UI multi-select list to remove local items.<br>
65
71
 
66
- ```bash
67
- vibe add <repository>
72
+ <br>**With arguments:** Quickly matches and removes specified standard skills or local tools, cleaning up configurations synchronously. |
68
73
 
69
- ```
74
+ ---
70
75
 
71
- **功能流程**:
76
+ ## 🏗️ Build Your Own Resource Repository
72
77
 
73
- 1. 调用原生能力安装目标仓库中的 Agent 技能。
74
- 2. 克隆并解析目标仓库中的 `tool` 文件夹。
75
- 3. 提供交互式多选列表,让你选择需要的具体工具。
76
- 4. 自动拷贝文件、更新 `opencode.jsonc` 并配置 Python 依赖环境(如果需要)。
78
+ We highly encourage you or your team to create a dedicated Vibe Coding resource repository on GitHub to standardize your favorite AI tools and custom coding guidelines across all your projects.
77
79
 
78
- ### 2. 查看已安装项 (`list` / `ls`)
80
+ ### Recommended Directory Structure
79
81
 
80
- ```bash
81
- vibe list
82
+ To ensure perfect compatibility with `@vibe-coder/cli`, we recommend the following convention (referencing the `helloggx/skill` repository):
83
+
84
+ ```text
85
+ your-custom-repo/
86
+ ├── skill/ # (Optional) Standard Vercel AI Agent skills
87
+ ├── tool/ # Custom TS/Python executable tools
88
+ │ ├── get_dsl.ts
89
+ │ ├── get_dsl.py # 💡 Python scripts should share the exact name as the TS tool calling them
90
+ │ └── shadcn_vue_init.ts
91
+ └── rules/ # Personalized Markdown context rules
92
+ ├── common/ # Global rules applicable to all projects
93
+ │ ├── coding-style.md
94
+ │ └── security.md
95
+ └── typescript/ # Tech-stack specific rules
96
+ └── coding-style.md # 💡 Recommended to share the name with the extended common rule
82
97
 
83
98
  ```
84
99
 
85
- **功能**:
86
- 清晰打印当前项目中安装的所有本地 Tools(来自 `vibe-lock.json`)以及已安装的标准 Skills,便于进行环境审查。
100
+ ### Organization Best Practices
87
101
 
88
- ### 3. 一键更新 (`update` / `up`)
102
+ * **Cross-Language Tool Linkage**: If your `.ts` tool script relies on an underlying `.py` script, **ensure both files share the exact same base name** (e.g., `get_dsl.ts` and `get_dsl.py`). The CLI intelligently detects and pulls both files together.
103
+ * **Rule Inheritance & Overrides**:
104
+ * Always place your global, universal coding rules in the `rules/common/` directory.
105
+ * When creating tech-stack specific rules (e.g., under `rules/typescript/`) that need to inherit from a `common` rule, it is highly recommended to **use the exact same file name** and explicitly state the inheritance at the top of the file:
106
+ *> This file extends [common/coding-style.md](https://www.google.com/search?q=../common/coding-style.md) with TypeScript specific content.*
89
107
 
90
- ```bash
91
- vibe update
92
-
93
- ```
94
108
 
95
- **功能**:
96
- 自动执行标准 Skills 的升级,并根据锁文件记录的源仓库,拉取并覆盖最新的本地工具脚本,保持生态环境最新。
97
109
 
98
110
  ---
99
111
 
100
- ## 📂 目录与配置规范 (Workspace Structure)
112
+ ## 📂 Workspace Structure
101
113
 
102
- 运行 `vibe add` 后,工具将在你的项目根目录下自动创建并维护以下结构:
114
+ After running `vibe add`, the tool will automatically take over and maintain the following structure in your project's root directory:
103
115
 
104
116
  ```text
105
117
  your-project/
106
118
  ├── .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 工具运行所需的核心依赖
114
-
119
+ │ ├── tool/ # Pulled underlying .ts / .py tool scripts
120
+ │ ├── rules/ # Pulled .md rule files (categorized by type)
121
+ ├── opencode.jsonc # Core OpenCode config (CLI auto-injects tool switches and paths)
122
+ └── vibe-lock.json # State lock file, accurately recording resource sources and versions
123
+ ├── .venv/ # (Auto-created as needed) Isolated Python virtual environment
124
+ └── requirements.txt # (Auto-maintained as needed) Python script dependencies
115
125
  ```
116
126
 
117
127
  ---
118
128
 
119
- ## 🛠️ 开发者指南 (Development)
129
+ ## 🛠️ Development
120
130
 
121
- 本项目使用 Bun 进行包管理和运行。
131
+ This project is built on top of the lightning-fast [Bun](https://bun.sh/) runtime.
122
132
 
123
133
  ```bash
124
- # 1. 安装依赖
125
- bun install
126
-
127
- # 2. 本地开发运行 CLI
128
- bun run dev --help
129
-
130
- # 3. 类型检查
131
- bun run typecheck
132
-
133
- # 4. 构建生产版本 (输出至 ./dist)
134
- bun run build
135
-
134
+ bun install # 1. Install dependencies
135
+ bun run dev --help # 2. Local debugging
136
+ bun run typecheck # 3. Type checking
137
+ bun run build # 4. Build production version (outputs to ./dist)
136
138
  ```
137
139
 
138
- ## 📄 许可证 (License)
140
+ ## 📄 License
139
141
 
140
- 本项目采用 [MIT License](https://www.google.com/search?q=../../LICENSE.md) 进行开源。
141
- © 2026 [HelloGGX](https://github.com/HelloGGX)
142
+ [MIT License](https://www.google.com/search?q=../../LICENSE.md) © 2026 [HelloGGX](https://github.com/HelloGGX)
package/README.zh.md ADDED
@@ -0,0 +1,140 @@
1
+ <div align="center">
2
+
3
+ # 🌊 Vibe Coding CLI
4
+
5
+ **专为 OpenCode 打造的 vibe coding 生态构建工具**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@vibe-coder/cli.svg?style=flat-square)](https://www.npmjs.com/package/@vibe-coder/cli)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](../../LICENSE.md)
9
+ [![Built with Bun](https://img.shields.io/badge/Bun-%23000000.svg?style=flat-square&logo=bun&logoColor=white)](https://bun.sh)
10
+
11
+ [English](https://github.com/HelloGGX/skill/blob/main/packages/vibe/README.md) · **简体中文**
12
+
13
+ > *一键聚合 AI 工具与上下文规范,让 Agent 真正懂你的代码架构。*
14
+
15
+ </div>
16
+
17
+ ## 📖 简介 (Introduction)
18
+
19
+ `@vibe-coder/cli` 是一个专为 **OpenCode** 平台打造的现代命令行脚手架工具。它的核心目标是快速搭建 Vibe Coding 的开发环境,简化规范驱动开发的资源管理。
20
+
21
+ 通过 `vibe` 命令,你可以一键拉取远程 GitHub 仓库中的 TypeScript/Python 工具脚本或 Markdown 规则文件,自动无缝注册到 OpenCode 配置中,并接管底层运行依赖,让你专注于**“与 AI 共创代码”**本身。
22
+
23
+ ## ✨ 核心特性 (Features)
24
+
25
+ - 🛠 **全自动化工具管理**: 支持从任意 GitHub 仓库快速解析、多选并下载 `.ts` / `.py` 脚本至本地开箱即用。
26
+ - 📜 **Context 与 Capabilities 完美融合**: 独创生态聚合能力,将 Agent 执行所需的**工具技能**与**行为准则**深度绑定。支持按需安装 `.md` 规则文件,让 AI 真正懂你的架构意图。
27
+ - 📦 **智能配置注入**: 自动拦截并更新 `.opencode/opencode.jsonc`,无感注入工具开关与 Prompt 指令路径,告别繁琐的手动配置。
28
+ - ⚡ **并行极速更新**: 基于并发模型设计,同时处理多个源仓库的资源对比与拉取,大幅缩短更新等待时间。
29
+ - 🪄 **标准技能聚合**: 与 Vercel 的 `pnpx skills` 生态深度集成,在统一的 CLI 流程中同时管理标准 Agent 技能库和本地扩展资源。
30
+
31
+ ---
32
+
33
+ ## 🚀 快速开始 (Quick Start)
34
+
35
+ ### 环境要求 (Prerequisites)
36
+ - [Node.js](https://nodejs.org/) >= 18.0.0 或 [Bun](https://bun.sh/) >= 1.0.0
37
+
38
+ ### 安装
39
+
40
+ 作为全局包安装:
41
+
42
+ ```bash
43
+ # 使用 npm
44
+ npm i -g @vibe-coder/cli
45
+
46
+ # 使用 bun
47
+ bun add -g @vibe-coder/cli
48
+ ```
49
+
50
+ ### 基础用法
51
+
52
+ 初始化并添加一个生态库(例如本项目的 `helloggx/skill`):
53
+
54
+ ```bash
55
+ vibe add helloggx/skill
56
+ ```
57
+
58
+ *CLI 将唤起交互式菜单,允许你灵活多选想要安装的 **Tools (工具)** 和 **Rules (规则)**,并自动为你完成所有环境配置。*
59
+
60
+ ---
61
+
62
+ ## 📚 命令指南 (Commands)
63
+
64
+ | 命令 | 别名 | 功能描述 |
65
+ | --- | --- | --- |
66
+ | `vibe add <repo>` | `a` | 解析目标 GitHub 仓库,唤起 UI 列表,按需安装工具和规则文件,并自动注入配置。 |
67
+ | `vibe list` | `ls` | 清晰打印当前项目中所有已安装资源(本地工具、上下文规则、全局标准技能)的态势图。 |
68
+ | `vibe update` | `up` | 一键并发拉取 `vibe-lock.json` 中的所有源仓库,智能比对并覆盖本地脚本与规则。 |
69
+ | `vibe remove [资源]` | `rm` | **无参运行**:唤起 UI 多选列表删除本地项。<br>
70
+
71
+ <br>**带参运行**:快捷匹配并移除指定的标准技能或本地工具,并同步清理配置。 |
72
+
73
+ ---
74
+
75
+ ## 🏗️ 构建你自己的资源仓库
76
+
77
+ 我们强烈鼓励你或团队在 GitHub 上创建专属的 Vibe Coding 资源仓库,以在所有项目中标准化团队最喜欢的 AI 工具和自定义编码规范。
78
+
79
+ ### 推荐目录结构
80
+
81
+ 为确保与 `@vibe-coder/cli` 完美兼容,推荐采用以下约定(可参考 `helloggx/skill`):
82
+
83
+ ```text
84
+ your-custom-repo/
85
+ ├── skill/ # (可选) 标准的 Vercel AI Agent 技能库
86
+ ├── tool/ # 自定义 TS/Python 可执行工具
87
+ │ ├── get_dsl.ts
88
+ │ ├── get_dsl.py # 💡 Python 脚本应与其调用的 TS 工具同名
89
+ │ └── shadcn_vue_init.ts
90
+ └── rules/ # 个性化 Markdown 上下文规则
91
+ ├── common/ # 适用于所有项目的全局通用规则
92
+ │ ├── coding-style.md
93
+ │ └── security.md
94
+ └── typescript/ # 特定技术栈规则
95
+ └── coding-style.md # 💡 建议与被扩展的通用规则同名
96
+ ```
97
+
98
+ ### 组织最佳实践
99
+
100
+ * **跨语言工具联动**:若你的 `.ts` 工具依赖底层的 `.py` 脚本,**请确保两文件基础名称完全一致**(如 `get_dsl.ts` 和 `get_dsl.py`)。CLI 会智能识别并一并拉取。
101
+ * **规则继承与扩展**:
102
+ * 全局通用规则务必放在 `rules/common/` 下。
103
+ * 为特定技术栈编写规则时(如 `rules/typescript/`),若需继承 `common` 规则,建议**保持同名**,并在文件顶部显式声明继承关系:
104
+ *> 此文件扩展了 [common/coding-style.md](https://www.google.com/search?q=../common/coding-style.md) 并增加了 TS 特定内容。*
105
+
106
+
107
+
108
+ ---
109
+
110
+ ## 📂 目录与配置规范 (Workspace Structure)
111
+
112
+ 运行 `vibe add` 后,工具将在项目根目录自动接管并维护以下结构:
113
+
114
+ ```text
115
+ your-project/
116
+ ├── .opencode/
117
+ │ ├── tool/ # 底层 .ts / .py 工具脚本
118
+ │ ├── rules/ # .md 规则文件(按类别归档)
119
+ │ ├── opencode.jsonc # OpenCode 核心配置(CLI 自动注入工具开关与指令路径)
120
+ │ └── vibe-lock.json # 状态锁文件,精准记录资源来源与版本
121
+ ├── .venv/ # (按需自动创建) 隔离的 Python 虚拟环境
122
+ └── requirements.txt # (按需自动维护) Python 脚本依赖清单
123
+ ```
124
+
125
+ ---
126
+
127
+ ## 🛠️ 开发者指南 (Development)
128
+
129
+ 本项目基于极速的 [Bun](https://bun.sh/) 运行时构建。
130
+
131
+ ```bash
132
+ bun install # 1. 安装依赖
133
+ bun run dev --help # 2. 本地调试
134
+ bun run typecheck # 3. 类型检查
135
+ bun run build # 4. 构建生产版本 (输出至 ./dist)
136
+ ```
137
+
138
+ ## 📄 许可证 (License)
139
+
140
+ [MIT License](https://www.google.com/search?q=../../LICENSE.md) © 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,9 +1,15 @@
1
1
  {
2
2
  "name": "@vibe-coder/cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Vibe Coding development tool",
5
5
  "private": false,
6
6
  "type": "module",
7
+ "author": "HelloGGX",
8
+ "license": "MIT",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/HelloGGX/skill.git"
12
+ },
7
13
  "bin": {
8
14
  "vibe": "./bin/vibe"
9
15
  },
@@ -22,7 +28,8 @@
22
28
  "keywords": [
23
29
  "cli",
24
30
  "agent-skills",
25
- "skills"
31
+ "skills",
32
+ "vibe-coding"
26
33
  ],
27
34
  "dependencies": {
28
35
  "@clack/prompts": "^1.0.1",
@@ -33,5 +40,9 @@
33
40
  "@types/bun": "1.3.9",
34
41
  "@types/node": "22.13.9",
35
42
  "typescript": "5.8.2"
36
- }
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/HelloGGX/skill/issues"
46
+ },
47
+ "homepage": "https://github.com/HelloGGX/skill#readme"
37
48
  }