Skip to content

Commit eed4303

Browse files
committed
chore: release v0.6.0
1 parent 93e5169 commit eed4303

31 files changed

+1487
-298
lines changed

CHANGELOG.md

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,36 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
8+
## [0.6.0] - 2026-01-16
99

1010
### Added
11+
- `ask repo list [name]` command to inspect repository skills
1112
- Shell completion support for bash, zsh, fish, and powershell
1213
- Comprehensive test coverage for `internal/git`, `internal/skill`, `internal/deps`, and `internal/ui` packages
1314
- CI/CD quality gates: linting, test coverage reporting, and security scanning
1415
- Documentation: troubleshooting guide and architecture diagrams
1516
- Git author extraction from git config for `ask skill create` command
1617

1718
### Changed
19+
- `ask repo list` now supports optional arguments to list skills in a specific repository
1820
- Enhanced command help text with practical examples
1921
- Improved error messages with actionable suggestions
2022

21-
## [0.4.0] - 2026-01-15
23+
## [0.5.0] - 2026-01-17
2224

2325
### Added
24-
- Offline mode support with `--offline` flag
25-
- Performance benchmarking command `ask benchmark`
26-
- Search result caching leveraged for offline usage
26+
- **Multi-Tool Support** (`--agent` / `-a` flag)
27+
- Automatically detects and installs skills for: Claude Code, Cursor, OpenAI Codex, OpenCode
28+
- Supports installing to multiple agents simultaneously
29+
- **Global Installation Support** (`--global` / `-g` flag)
30+
- Install skills globally to `~/.ask/skills/` for sharing across all projects
31+
- Global configuration stored in `~/.ask/config.yaml`
32+
- Global lock file at `~/.ask/ask.lock`
33+
- `ask skill list --all` to show both project and global skills
34+
- All skill commands now support `--global` flag: `install`, `uninstall`, `list`, `update`, `outdated`, `info`
2735

2836
### Changed
29-
- `ask skill install` now prevented in offline mode
30-
- `ask skill outdated` skips remote checks in offline mode
31-
32-
## [0.3.0] - 2026-01-15
33-
34-
### Changed
35-
- Updated version structure (skipped public release of 0.3.0 features directly into 0.4.0 or this is a retrospective update if 0.3.0 was skipped)
36-
- *Note: Assuming 0.3.0 was a planned release. Adjusting to reflect current state.*
37-
38-
Actually, looking at previous steps, I implemented features on top of 0.2.0 directly to 0.4.0.
39-
Let's just release as 0.4.0.
37+
- Skill commands now display installation scope (project/global) in output messages
4038

4139
## [0.4.0] - 2026-01-15
4240

README.md

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,12 @@ Real-time progress bars during installation and updates. Clear feedback on what'
6767
### 🔌 Offline Mode
6868
Use `--offline` flag to work without network. Search uses cached results; perfect for air-gapped environments.
6969

70-
### ⏱️ Performance Benchmarking
71-
Run `ask benchmark` to measure CLI performance (cold/hot search, config loading).
70+
### 🤖 Multi-Tool Support
71+
Automatically detects and supports skills directories for **Claude Code** (`.claude/skills`), **Cursor** (`.cursor/skills`), **OpenAI Codex** (`.codex/skills`), and **OpenCode** (`.opencode/skills`).
72+
Simply run `ask skill install` and it will install to all detected tool directories.
73+
74+
### 🌍 Global Installation
75+
Install skills globally with `--global` flag to share across all projects. Local project installations take precedence over global ones.
7276

7377
---
7478

@@ -96,7 +100,8 @@ ask init # Creates ask.yaml in current directory
96100

97101
```bash
98102
ask skill search browser # Search across all sources
99-
ask skill install browser-use # Install a skill
103+
ask skill install browser-use # Install a skill
104+
ask skill install skill1 skill2 skill3 # Install multiple skills
100105
ask skill list # View installed skills
101106
```
102107

@@ -109,7 +114,7 @@ ask skill list # View installed skills
109114
| `ask init` | Initialize project, create `ask.yaml` |
110115
| **Skill Management** | |
111116
| `ask skill search <keyword>` | Search skills across all sources |
112-
| `ask skill install <skill>` | Install a skill to `./skills/` |
117+
| `ask skill install <skill...>` | Install skill(s) to `./skills/` |
113118
| `ask skill install skill@v1.0` | Install specific version |
114119
| `ask skill uninstall <skill>` | Remove a skill |
115120
| `ask skill list` | List installed skills |
@@ -118,14 +123,15 @@ ask skill list # View installed skills
118123
| `ask skill outdated` | Check for updates |
119124
| `ask skill create <name>` | Create new skill template |
120125
| **Repository Management** | |
121-
| `ask repo list` | List skill sources |
126+
| `ask repo list [name]` | List repos or skills in repo |
122127
| `ask repo add <url>` | Add custom source |
123128
| `ask repo remove <name>` | Remove a source |
124129
| **Shell Completion** | |
125130
| `ask completion <shell>` | Generate completion script |
126131
| **Utilities** | |
127132
| `ask benchmark` | Run performance benchmarks |
128133
| `--offline` | Global flag: run without network |
134+
| `--global, -g` | Global flag: use global installation (~/.ask/skills) |
129135

130136
---
131137

@@ -164,11 +170,41 @@ my-agent/
164170
├── ask.lock # Version lock file
165171
├── main.py # Your agent code
166172
└── .agent/
167-
└── skills/ # Managed skills
173+
└── skills/ # Project-level skills
168174
├── browser-use/
169175
└── web-surfer/
176+
177+
# Global skills (shared across projects)
178+
~/.ask/
179+
├── config.yaml # Global config
180+
├── ask.lock # Global version lock
181+
└── skills/ # Global skills
182+
└── shared-skill/
170183
```
171184

185+
### Installation Scopes
186+
187+
```bash
188+
# Project-level (default) - stored in .agent/skills/
189+
ask skill install browser-use
190+
191+
# Multi-Agent Installation
192+
ask skill install browser-use --agent claude --agent cursor
193+
194+
# Global - stored in ~/.ask/skills/, available to all projects
195+
ask skill install --global shared-skill
196+
ask skill install -g shared-skill
197+
198+
# Global for specific agent
199+
ask skill install browser-use --agent claude --global
200+
# Installs to ~/.claude/skills/
201+
202+
# List both
203+
ask skill list --all
204+
205+
# List for specific agent
206+
ask skill list --agent claude
207+
172208
---
173209

174210
## 🛠 Development

README_zh.md

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@
5555
### 🔌 离线模式
5656
使用 `--offline` 标志无需网络即可工作。搜索使用缓存结果,适用于无网络环境。
5757

58-
### ⏱️ 性能基准测试
59-
运行 `ask benchmark` 测量 CLI 性能(冷/热搜索、配置加载)
58+
### 🌍 全局安装
59+
使用 `--global` 标志全局安装技能,可在所有项目间共享。项目级安装优先于全局安装
6060

6161
---
6262

@@ -84,7 +84,8 @@ ask init # 在当前目录创建 ask.yaml
8484

8585
```bash
8686
ask skill search browser # 从所有来源搜索
87-
ask skill install browser-use # 安装技能
87+
ask skill install browser-use # 安装技能
88+
ask skill install skill1 skill2 skill3 # 批量安装多个技能
8889
ask skill list # 查看已安装技能
8990
```
9091

@@ -97,7 +98,7 @@ ask skill list # 查看已安装技能
9798
| `ask init` | 初始化项目,创建 `ask.yaml` |
9899
| **技能管理** | |
99100
| `ask skill search <关键词>` | 从所有来源搜索技能 |
100-
| `ask skill install <技能>` | 安装技能到 `./skills/` |
101+
| `ask skill install <技能...>` | 安装技能到 `./skills/` |
101102
| `ask skill install skill@v1.0` | 安装指定版本 |
102103
| `ask skill uninstall <技能>` | 移除技能 |
103104
| `ask skill list` | 列出已安装技能 |
@@ -113,6 +114,7 @@ ask skill list # 查看已安装技能
113114
| `ask benchmark` | 运行性能基准测试 |
114115
| `ask completion <shell>` | 生成 shell 补全脚本 |
115116
| `--offline` | 全局标志:无网络模式 |
117+
| `--global, -g` | 全局标志:使用全局安装 (~/.ask/skills) |
116118

117119
---
118120

@@ -151,9 +153,30 @@ my-agent/
151153
├── ask.lock # 版本锁定文件
152154
├── main.py # 您的智能体代码
153155
└── .agent/
154-
└── skills/ # 托管技能目录
156+
└── skills/ # 项目级技能目录
155157
├── browser-use/
156158
└── web-surfer/
159+
160+
# 全局技能(跨项目共享)
161+
~/.ask/
162+
├── config.yaml # 全局配置
163+
├── ask.lock # 全局版本锁定
164+
└── skills/ # 全局技能
165+
└── shared-skill/
166+
```
167+
168+
### 安装范围
169+
170+
```bash
171+
# 项目级(默认)- 存储在 .agent/skills/
172+
ask skill install browser-use
173+
174+
# 全局 - 存储在 ~/.ask/skills/,所有项目可用
175+
ask skill install --global shared-skill
176+
ask skill install -g shared-skill
177+
178+
# 列出全部
179+
ask skill list --all
157180
```
158181

159182
---

SPEC.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,17 @@ v0.4.0 已实现:
4040
- [x] 性能基准测试 (`ask benchmark`)
4141
- [x] 离线模式 (`--offline`)
4242

43-
v0.5.0 建议功能:
43+
v0.5.0 已实现:
44+
- [x] 全局安装支持 (`--global` / `-g`)
45+
- 全局技能目录: `~/.ask/skills/`
46+
- 全局配置文件: `~/.ask/config.yaml`
47+
- 全局锁定文件: `~/.ask/ask.lock`
48+
- [x] 多 Agent 支持 (`--agent` / `-a`)
49+
- 支持将技能安装到特定 Agent 目录
50+
- 支持的 Agent: Claude, Cursor, Codex, OpenCode
51+
- 示例: `ask skill install <skill> --agent claude --agent cursor`
52+
53+
v0.6.0 建议功能:
4454
- [ ] 插件系统
4555

4656
---
@@ -256,6 +266,7 @@ ask/
256266
257267
| 日期 | 版本 | 变更 |
258268
|------|------|------|
269+
| 2026-01-16 | 0.5.0 | 新增多 Agent 支持 (`--agent`), 全局安装 (`--global`), 以及智能目录检测 |
259270
| 2026-01-16 | 0.4.0 | 新增离线模式 (`--offline`) 和性能基准测试 (`ask benchmark`) |
260271
| 2026-01-15 | 0.2.0 | CLI 重构:技能命令移至 `ask skill` 子命令;技能安装路径改为 `.agent/skills/`;新增 OpenAI 等默认仓库 |
261272
| 2026-01-15 | 0.1.0 | 初始版本,基本功能实现 |

cmd/benchmark.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ var benchmarkCmd = &cobra.Command{
2222
fmt.Println()
2323

2424
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
25-
fmt.Fprintln(w, "OPERATION\tTIME\tNOTES")
25+
_, _ = fmt.Fprintln(w, "OPERATION\tTIME\tNOTES")
2626

2727
// 1. Search (Cold) - Clear cache first
2828
c, err := cache.New("", cache.DefaultTTL)
2929
if err == nil {
30-
c.Clear()
30+
_ = c.Clear()
3131
}
3232

3333
start := time.Now()
@@ -46,27 +46,27 @@ var benchmarkCmd = &cobra.Command{
4646
// We'll search for "browser" which should trigger network requests
4747
repo := cfg.Repos[0] // Use first repo
4848
if repo.Type == "topic" {
49-
github.SearchTopic(repo.URL, "browser")
49+
_, _ = github.SearchTopic(repo.URL, "browser")
5050
}
5151
duration := time.Since(start)
52-
fmt.Fprintf(w, "Search (Cold)\t%v\tFirst repo only\n", duration.Round(time.Millisecond))
52+
_, _ = fmt.Fprintf(w, "Search (Cold)\t%v\tFirst repo only\n", duration.Round(time.Millisecond))
5353

5454
// 2. Search (Hot) - Should be cached
5555
start = time.Now()
5656
if repo.Type == "topic" {
57-
github.SearchTopic(repo.URL, "browser")
57+
_, _ = github.SearchTopic(repo.URL, "browser")
5858
}
5959
duration = time.Since(start)
60-
fmt.Fprintf(w, "Search (Hot)\t%v\tCached\n", duration.Round(time.Millisecond))
60+
_, _ = fmt.Fprintf(w, "Search (Hot)\t%v\tCached\n", duration.Round(time.Millisecond))
6161

6262
// 3. List - Local operation
6363
start = time.Now()
6464
// Simulate list parsing
65-
config.LoadConfig()
65+
_, _ = config.LoadConfig()
6666
duration = time.Since(start)
67-
fmt.Fprintf(w, "List\t%v\tConfig load\n", duration.Round(time.Millisecond))
67+
_, _ = fmt.Fprintf(w, "List\t%v\tConfig load\n", duration.Round(time.Millisecond))
6868

69-
w.Flush()
69+
_ = w.Flush()
7070
fmt.Println()
7171
fmt.Println("Done.")
7272
},

cmd/cmd_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,95 @@ func TestInitCommandHelp(t *testing.T) {
120120
t.Error("expected init help to contain 'Initialize'")
121121
}
122122
}
123+
124+
func TestParseGitHubBrowserURL(t *testing.T) {
125+
tests := []struct {
126+
name string
127+
input string
128+
wantRepoURL string
129+
wantBranch string
130+
wantSubDir string
131+
wantSkillName string
132+
wantOK bool
133+
}{
134+
{
135+
name: "full URL with subdirectory",
136+
input: "https://github.com/anthropics/skills/tree/main/skills/mcp-builder",
137+
wantRepoURL: "https://github.com/anthropics/skills.git",
138+
wantBranch: "main",
139+
wantSubDir: "skills/mcp-builder",
140+
wantSkillName: "mcp-builder",
141+
wantOK: true,
142+
},
143+
{
144+
name: "URL with different branch",
145+
input: "https://github.com/owner/repo/tree/develop/path/to/skill",
146+
wantRepoURL: "https://github.com/owner/repo.git",
147+
wantBranch: "develop",
148+
wantSubDir: "path/to/skill",
149+
wantSkillName: "skill",
150+
wantOK: true,
151+
},
152+
{
153+
name: "URL without subdirectory - just branch",
154+
input: "https://github.com/owner/repo/tree/main",
155+
wantRepoURL: "https://github.com/owner/repo.git",
156+
wantBranch: "main",
157+
wantSubDir: "",
158+
wantSkillName: "repo",
159+
wantOK: true,
160+
},
161+
{
162+
name: "URL with trailing slash",
163+
input: "https://github.com/anthropics/skills/tree/main/skills/mcp-builder/",
164+
wantRepoURL: "https://github.com/anthropics/skills.git",
165+
wantBranch: "main",
166+
wantSubDir: "skills/mcp-builder",
167+
wantSkillName: "mcp-builder",
168+
wantOK: true,
169+
},
170+
{
171+
name: "non-tree URL (regular git URL)",
172+
input: "https://github.com/owner/repo.git",
173+
wantOK: false,
174+
},
175+
{
176+
name: "shorthand format - not a browser URL",
177+
input: "owner/repo/path/to/skill",
178+
wantOK: false,
179+
},
180+
{
181+
name: "empty string",
182+
input: "",
183+
wantOK: false,
184+
},
185+
}
186+
187+
for _, tt := range tests {
188+
t.Run(tt.name, func(t *testing.T) {
189+
gotRepoURL, gotBranch, gotSubDir, gotSkillName, gotOK := parseGitHubBrowserURL(tt.input)
190+
191+
if gotOK != tt.wantOK {
192+
t.Errorf("parseGitHubBrowserURL() ok = %v, want %v", gotOK, tt.wantOK)
193+
return
194+
}
195+
196+
if !tt.wantOK {
197+
return // No need to check other fields if we expected failure
198+
}
199+
200+
if gotRepoURL != tt.wantRepoURL {
201+
t.Errorf("parseGitHubBrowserURL() repoURL = %v, want %v", gotRepoURL, tt.wantRepoURL)
202+
}
203+
if gotBranch != tt.wantBranch {
204+
t.Errorf("parseGitHubBrowserURL() branch = %v, want %v", gotBranch, tt.wantBranch)
205+
}
206+
if gotSubDir != tt.wantSubDir {
207+
t.Errorf("parseGitHubBrowserURL() subDir = %v, want %v", gotSubDir, tt.wantSubDir)
208+
}
209+
if gotSkillName != tt.wantSkillName {
210+
t.Errorf("parseGitHubBrowserURL() skillName = %v, want %v", gotSkillName, tt.wantSkillName)
211+
}
212+
})
213+
}
214+
}

0 commit comments

Comments
 (0)