Skip to content

Commit f728683

Browse files
committed
chore: release v1.6.0
1 parent accf3eb commit f728683

20 files changed

+2144
-10
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
All notable changes to this project will be documented in this file.
44

55

6+
## [1.6.0] - 2026-02-07
7+
8+
### Added
9+
- **Internationalization**: Added complete Chinese documentation for all `docs/` files (e.g., `README_zh.md`, `commands_zh.md`).
10+
- **Prompt Integration**: New `ask skill prompt` command to generate XML skill listings for Agentic AI prompts (following [Agent Skills Spec](https://agentskills.io/specification)).
11+
- **Validation**: Enhanced `SKILL.md` validation logic to rigidly enforce spec compliance (names, descriptions).
12+
13+
### Fixed
14+
- **Cleanup**: Removed unused `formatPathForPrompt` function in `cmd/prompt.go`.
15+
616
## [1.5.1] - 2026-02-04
717

818
### Changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ ask serve
148148
| `ask skill uninstall <name>` | Remove a skill |
149149
| `ask skill update` | Update skills to latest version |
150150
| `ask skill outdated` | Check for newer versions |
151-
| `ask skill check <path>` | Security scan (Secrets, Dangerous Commands) |
151+
| `ask skill check <path>` | Security scan + SKILL.md format validation |
152+
| `ask skill prompt [paths]` | Generate XML for agent system prompts |
152153

153154
### Repository Management
154155
| Command | Description |

README_zh.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ ask serve
149149
| `ask uninstall <name>` | 卸载 Skill |
150150
| `ask update` | 更新 Skill 到最新版本 |
151151
| `ask outdated` | 检查可用更新 |
152-
| `ask check <path>` | 安全扫描 (密钥泄漏, 危险命令等) |
152+
| `ask check <path>` | 安全扫描 + SKILL.md 格式验证 |
153+
| `ask skill prompt [paths]` | 生成 XML 格式供 Agent 系统提示使用 |
153154

154155
### 仓库管理
155156
| 命令 | 说明 |

cmd/prompt.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package cmd
2+
3+
import (
4+
"encoding/xml"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
9+
"github.com/spf13/cobra"
10+
"github.com/yeasy/ask/internal/config"
11+
"github.com/yeasy/ask/internal/skill"
12+
)
13+
14+
// AvailableSkills represents the XML structure for agent prompt integration
15+
type AvailableSkills struct {
16+
XMLName xml.Name `xml:"available_skills"`
17+
Skills []SkillEntry `xml:"skill"`
18+
}
19+
20+
// SkillEntry represents a single skill in the XML output
21+
type SkillEntry struct {
22+
Name string `xml:"name"`
23+
Description string `xml:"description"`
24+
Location string `xml:"location"`
25+
}
26+
27+
var promptOutputFile string
28+
29+
// promptCmd represents the prompt command
30+
var promptCmd = &cobra.Command{
31+
Use: "prompt [paths...]",
32+
Short: "Generate XML skill listing for agent prompts",
33+
Long: `Generate <available_skills> XML block for agent system prompts.
34+
35+
This format is recommended for Anthropic's models and follows the
36+
Agent Skills specification at https://agentskills.io/specification.
37+
38+
If no paths are specified, scans installed skills in default locations.
39+
40+
Examples:
41+
ask skill prompt # Scan all installed skills
42+
ask skill prompt .agent/skills/pdf # Single skill
43+
ask skill prompt ./skills/a ./skills/b # Multiple skills`,
44+
Run: runPrompt,
45+
}
46+
47+
func init() {
48+
promptCmd.Flags().StringVarP(&promptOutputFile, "output", "o", "", "Write XML to file instead of stdout")
49+
}
50+
51+
func runPrompt(_ *cobra.Command, args []string) {
52+
var skillPaths []string
53+
54+
if len(args) > 0 {
55+
// Use provided paths
56+
for _, arg := range args {
57+
absPath, err := filepath.Abs(arg)
58+
if err != nil {
59+
fmt.Fprintf(os.Stderr, "Error resolving path %s: %v\n", arg, err)
60+
continue
61+
}
62+
if skill.FindSkillMD(absPath) {
63+
skillPaths = append(skillPaths, absPath)
64+
} else {
65+
fmt.Fprintf(os.Stderr, "Warning: %s is not a valid skill (no SKILL.md found)\n", arg)
66+
}
67+
}
68+
} else {
69+
// Scan default locations
70+
skillPaths = discoverInstalledSkills()
71+
}
72+
73+
if len(skillPaths) == 0 {
74+
fmt.Fprintln(os.Stderr, "No skills found.")
75+
os.Exit(1)
76+
}
77+
78+
// Build XML structure
79+
availableSkills := AvailableSkills{}
80+
for _, path := range skillPaths {
81+
meta, err := skill.ParseSkillMD(path)
82+
if err != nil {
83+
fmt.Fprintf(os.Stderr, "Warning: failed to parse %s: %v\n", path, err)
84+
continue
85+
}
86+
87+
entry := SkillEntry{
88+
Name: meta.Name,
89+
Description: meta.Description,
90+
Location: filepath.Join(path, "SKILL.md"),
91+
}
92+
availableSkills.Skills = append(availableSkills.Skills, entry)
93+
}
94+
95+
// Generate XML
96+
output, err := xml.MarshalIndent(availableSkills, "", " ")
97+
if err != nil {
98+
fmt.Fprintf(os.Stderr, "Error generating XML: %v\n", err)
99+
os.Exit(1)
100+
}
101+
102+
xmlContent := string(output)
103+
104+
if promptOutputFile != "" {
105+
if err := os.WriteFile(promptOutputFile, []byte(xmlContent), 0644); err != nil {
106+
fmt.Fprintf(os.Stderr, "Error writing to %s: %v\n", promptOutputFile, err)
107+
os.Exit(1)
108+
}
109+
fmt.Printf("XML written to %s\n", promptOutputFile)
110+
} else {
111+
fmt.Println(xmlContent)
112+
}
113+
}
114+
115+
// discoverInstalledSkills finds all installed skills in known locations
116+
func discoverInstalledSkills() []string {
117+
var paths []string
118+
cwd, _ := os.Getwd()
119+
home, _ := os.UserHomeDir()
120+
121+
// Locations to scan
122+
searchDirs := []string{
123+
filepath.Join(cwd, config.DefaultSkillsDir),
124+
filepath.Join(cwd, "skills"),
125+
}
126+
127+
// Add agent-specific directories
128+
for _, agentConfig := range config.SupportedAgents {
129+
searchDirs = append(searchDirs, filepath.Join(cwd, agentConfig.ProjectDir))
130+
if home != "" {
131+
searchDirs = append(searchDirs, filepath.Join(home, agentConfig.GlobalDir))
132+
}
133+
}
134+
135+
// Add global skills directory
136+
if home != "" {
137+
searchDirs = append(searchDirs, filepath.Join(home, ".ask", "skills"))
138+
}
139+
140+
// Deduplicate and scan
141+
seen := make(map[string]bool)
142+
for _, dir := range searchDirs {
143+
if seen[dir] {
144+
continue
145+
}
146+
seen[dir] = true
147+
148+
entries, err := os.ReadDir(dir)
149+
if err != nil {
150+
continue
151+
}
152+
153+
for _, entry := range entries {
154+
if !entry.IsDir() {
155+
continue
156+
}
157+
skillPath := filepath.Join(dir, entry.Name())
158+
if skill.FindSkillMD(skillPath) {
159+
// Use absolute path for deduplication
160+
absPath, _ := filepath.Abs(skillPath)
161+
if !containsPath(paths, absPath) {
162+
paths = append(paths, absPath)
163+
}
164+
}
165+
}
166+
}
167+
168+
return paths
169+
}
170+
171+
func containsPath(paths []string, target string) bool {
172+
for _, p := range paths {
173+
if p == target {
174+
return true
175+
}
176+
}
177+
return false
178+
}

cmd/prompt_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package cmd
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
"testing"
8+
)
9+
10+
func TestPromptCommand_SingleSkill(t *testing.T) {
11+
tmpDir := t.TempDir()
12+
skillDir := filepath.Join(tmpDir, "test-skill")
13+
if err := os.Mkdir(skillDir, 0755); err != nil {
14+
t.Fatal(err)
15+
}
16+
17+
// Create valid SKILL.md
18+
skillMD := `---
19+
name: test-skill
20+
description: A test skill for prompt generation
21+
---
22+
# Test Skill
23+
`
24+
if err := os.WriteFile(filepath.Join(skillDir, "SKILL.md"), []byte(skillMD), 0644); err != nil {
25+
t.Fatal(err)
26+
}
27+
28+
// Build available skills
29+
paths := []string{skillDir}
30+
availableSkills := AvailableSkills{}
31+
32+
for _, path := range paths {
33+
meta, err := parseSkillMDForTest(path)
34+
if err != nil {
35+
t.Fatalf("Failed to parse skill: %v", err)
36+
}
37+
entry := SkillEntry{
38+
Name: meta.name,
39+
Description: meta.description,
40+
Location: filepath.Join(path, "SKILL.md"),
41+
}
42+
availableSkills.Skills = append(availableSkills.Skills, entry)
43+
}
44+
45+
if len(availableSkills.Skills) != 1 {
46+
t.Errorf("Expected 1 skill, got %d", len(availableSkills.Skills))
47+
}
48+
49+
if availableSkills.Skills[0].Name != "test-skill" {
50+
t.Errorf("Expected name 'test-skill', got '%s'", availableSkills.Skills[0].Name)
51+
}
52+
}
53+
54+
// Helper to parse SKILL.md for testing
55+
type testMeta struct {
56+
name string
57+
description string
58+
}
59+
60+
func parseSkillMDForTest(skillPath string) (*testMeta, error) {
61+
content, err := os.ReadFile(filepath.Join(skillPath, "SKILL.md"))
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
meta := &testMeta{}
67+
lines := strings.Split(string(content), "\n")
68+
inFrontmatter := false
69+
70+
for _, line := range lines {
71+
line = strings.TrimSpace(line)
72+
if line == "---" {
73+
if !inFrontmatter {
74+
inFrontmatter = true
75+
continue
76+
} else {
77+
break
78+
}
79+
}
80+
if inFrontmatter {
81+
if strings.HasPrefix(line, "name:") {
82+
meta.name = strings.TrimSpace(strings.TrimPrefix(line, "name:"))
83+
}
84+
if strings.HasPrefix(line, "description:") {
85+
meta.description = strings.TrimSpace(strings.TrimPrefix(line, "description:"))
86+
}
87+
}
88+
}
89+
90+
return meta, nil
91+
}
92+
93+
func TestContainsPath(t *testing.T) {
94+
paths := []string{"/a/b/c", "/d/e/f"}
95+
96+
if !containsPath(paths, "/a/b/c") {
97+
t.Error("Expected to find /a/b/c")
98+
}
99+
100+
if containsPath(paths, "/x/y/z") {
101+
t.Error("Did not expect to find /x/y/z")
102+
}
103+
}

cmd/root.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Codex, etc.) with a familiar CLI experience, just like Homebrew or npm.`,
6161
}
6262

6363
// Version is the current version of the application
64-
const Version = "1.5.1"
64+
const Version = "1.6.0"
6565

6666
// Top-level aliases (Docker-style)
6767
var installRootCmd = &cobra.Command{
@@ -152,7 +152,7 @@ func init() {
152152
// It's likely defined in list.go.
153153
// We should probably just copy flags setup here.
154154
uninstallRootCmd.Flags().AddFlagSet(uninstallCmd.Flags())
155-
checkRootCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Write report to file (supports .md, .html/.htm, .json)")
155+
checkRootCmd.Flags().AddFlagSet(checkCmd.Flags())
156156
}
157157

158158
// initConfig reads in config file and ENV variables if set.

cmd/skill.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ Examples:
2121
func init() {
2222
rootCmd.AddCommand(skillCmd)
2323
skillCmd.AddCommand(checkCmd)
24+
skillCmd.AddCommand(promptCmd)
2425
}

docs/README_zh.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# ASK 文档
2+
3+
欢迎阅读 ASK (Agent Skills Kit) 文档。
4+
5+
## 快速导航
6+
7+
| 文档 | 描述 |
8+
|----------|-------------|
9+
| [安装指南](installation_zh.md) | 如何在您的系统上安装 ASK |
10+
| [命令参考](commands_zh.md) | 完整的命令参考手册 |
11+
| [技能源](skill-sources_zh.md) | 理解和配置技能源 |
12+
| [技能格式](skill-format_zh.md) | 使用 SKILL.md 创建技能 |
13+
| [配置指南](configuration_zh.md) | ask.yaml 和 ask.lock 配置 |
14+
| [故障排除](troubleshooting_zh.md) | 常见问题与解决方案 |
15+
| [架构设计](architecture_zh.md) | 内部设计与结构 |
16+
| [Web 界面](web-ui_zh.md) | 可视化 Web 界面指南 |
17+
18+
## 获取帮助
19+
20+
- **GitHub Issues**: [报告 Bug 或请求新功能](https://github.com/yeasy/ask/issues)
21+
- **Discussions**: [提问和分享想法](https://github.com/yeasy/ask/discussions)
22+
23+
## 贡献指南
24+
25+
请参阅我们的 [贡献指南](../CONTRIBUTING.md) 以获取有关如何为 ASK 做贡献的信息。

0 commit comments

Comments
 (0)