mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-14 03:30:26 +00:00
cmd: add --no-skills, --skill, and --skills-dir CLI flags & config (#55)
* cmd: add --no-skills, --skill, and --skills-dir CLI flags
The pkg/kit Options struct already had full backend support for skills
control (NoSkills, Skills []string, SkillsDir) wired into loadSkills()
in pkg/kit/kit.go, but there were no corresponding CLI flags to drive
them. This commit closes that gap.
Changes in cmd/root.go:
- Add three package-level flag variables alongside the existing
noExtensionsFlag/extensionPaths group:
noSkillsFlag bool
skillsPaths []string
skillsDir string
- Register three persistent cobra flags in init():
--no-skills disable skill loading (auto-discovery and explicit)
--skill <path> load a skill file or directory (repeatable)
--skills-dir <dir> override the project-local skills directory
used for auto-discovery
- Wire all three into the kitOpts struct literal in runNormalMode()
so they flow directly into kit.New() -> loadSkills().
No changes to pkg/kit or internal/skills -- the backend was already
complete. No viper binding is needed because kit.go reads these fields
directly from opts rather than from viper (unlike NoExtensions which
uses the viper fallback path).
Example usage:
kit --no-skills "prompt"
kit --skill ./my-skill.md --skill ./other-skill.md "prompt"
kit --skills-dir /path/to/skills "prompt"
Co-authored-by: Claude <claude@anthropic.com>
* docs: document --no-skills, --skill, and --skills-dir CLI flags
Add the three new skills CLI flags to all relevant documentation:
- README.md: add Skills section under Global Flags CLI reference
- www/pages/cli/flags.md: add Skills table (mirrors Extensions section pattern)
- www/pages/cli/commands.md: expand the Skills section with usage examples
and a description of auto-discovery vs explicit loading vs --no-skills
Co-authored-by: Claude <claude@anthropic.com>
* feat: add config file support for skills options
Skills could previously only be controlled via CLI flags or SDK Options
fields. This commit wires all three skills settings into viper so they
can also be set in .kit.yml / .kit.yaml / .kit.json and via KIT_*
environment variables — matching the pattern used by no-extensions,
no-core-tools, and prompt-template.
cmd/root.go:
- Bind --no-skills, --skill, and --skills-dir flags to viper keys
(no-skills, skill, skills-dir) so config file values flow through.
pkg/kit/kit.go:
- At skill-load time, merge opts fields with viper values:
- noSkills = opts.NoSkills || v.GetBool("no-skills")
- skillPaths: opts.Skills if non-empty, else v.GetStringSlice("skill")
- skillsDir: opts.SkillsDir if non-empty, else v.GetString("skills-dir")
- Build a shallow-copied mergedOpts so loadSkills() picks up the
resolved values without mutating the original Options struct.
docs:
- README.md: add skills keys to the Basic Configuration YAML example
- www/pages/configuration.md: add no-skills, skill, skills-dir rows to
the All configuration keys table
Config file example (.kit.yml):
no-skills: false
skill:
- /path/to/skill.md
skills-dir: /path/to/skills/
Co-authored-by: Claude <claude@anthropic.com>
* config: add skills keys to default .kit.yml template
Add no-skills, skill, and skills-dir as commented-out examples in the
default config file generated by EnsureConfigExists(), alongside the
existing application settings block.
Co-authored-by: Claude <claude@anthropic.com>
* test: add test coverage for skills CLI flags and config keys
Four test locations updated:
pkg/kit/export_test.go:
- Add ConfigStringSliceForTest() helper to expose v.GetStringSlice()
from the Kit's isolated viper store, needed to assert skill list values.
pkg/kit/kit_test.go (TestNewWithSkillsOptions):
- NoSkills=true: GetSkills() returns empty slice
- SkillsDir=<empty dir>: kit.New() succeeds with zero skills
- Skills=[file]: single explicit skill file is loaded and name parsed correctly
pkg/kit/viper_isolation_test.go:
- TestSkillsViperKeys: no-API-key struct-level checks for NoSkills, Skills,
and SkillsDir fields on Options
- TestSkillsConfigFileKeys: full kit.New() round-trips via a written .kit.yml
for each of the three config keys:
no-skills: true → GetSkills() returns empty
skill: [path] → named skill loaded from config file path
skills-dir: dir → custom discovery root accepted without error
internal/config/config_test.go (TestEnsureConfigExists):
- Assert generated ~/.kit.yml template contains '# Skills configuration',
'no-skills:', and 'skills-dir:' comment blocks.
Co-authored-by: Claude <claude@anthropic.com>
---------
Co-authored-by: Claude <claude@anthropic.com>
This commit is contained in:
@@ -128,6 +128,12 @@ temperature: 0.7
|
|||||||
stream: true
|
stream: true
|
||||||
thinking-level: off # off, none, minimal, low, medium, high
|
thinking-level: off # off, none, minimal, low, medium, high
|
||||||
no-core-tools: false # set to true to disable all built-in core tools
|
no-core-tools: false # set to true to disable all built-in core tools
|
||||||
|
|
||||||
|
# Skills — all three keys are optional
|
||||||
|
no-skills: false # set to true to disable all skill loading
|
||||||
|
skill: # explicit skill files/dirs (disables auto-discovery)
|
||||||
|
- /path/to/skill.md
|
||||||
|
skills-dir: "" # override project-local directory for auto-discovery
|
||||||
```
|
```
|
||||||
|
|
||||||
All of the above keys can also be set programmatically via the SDK
|
All of the above keys can also be set programmatically via the SDK
|
||||||
@@ -203,6 +209,11 @@ mcpServers:
|
|||||||
--prompt-template Load a specific prompt template by name
|
--prompt-template Load a specific prompt template by name
|
||||||
--no-prompt-templates Disable prompt template loading
|
--no-prompt-templates Disable prompt template loading
|
||||||
|
|
||||||
|
# Skills
|
||||||
|
--skill Load skill file or directory (repeatable)
|
||||||
|
--skills-dir Override the project-local skills directory for auto-discovery
|
||||||
|
--no-skills Disable skill loading (auto-discovery and explicit)
|
||||||
|
|
||||||
# Generation parameters
|
# Generation parameters
|
||||||
--max-tokens Maximum tokens in response (default: 8192, auto-raised up to 32768 for models with larger known output limits)
|
--max-tokens Maximum tokens in response (default: 8192, auto-raised up to 32768 for models with larger known output limits)
|
||||||
--temperature Randomness 0.0-1.0 (default: 0.7)
|
--temperature Randomness 0.0-1.0 (default: 0.7)
|
||||||
|
|||||||
+19
@@ -73,6 +73,11 @@ var (
|
|||||||
noCoreToolsFlag bool
|
noCoreToolsFlag bool
|
||||||
extensionPaths []string
|
extensionPaths []string
|
||||||
|
|
||||||
|
// Skills control
|
||||||
|
noSkillsFlag bool
|
||||||
|
skillsPaths []string
|
||||||
|
skillsDir string
|
||||||
|
|
||||||
// TLS configuration
|
// TLS configuration
|
||||||
tlsSkipVerify bool
|
tlsSkipVerify bool
|
||||||
|
|
||||||
@@ -283,6 +288,14 @@ func init() {
|
|||||||
rootCmd.PersistentFlags().
|
rootCmd.PersistentFlags().
|
||||||
StringSliceVarP(&extensionPaths, "extension", "e", nil, "load additional extension file(s)")
|
StringSliceVarP(&extensionPaths, "extension", "e", nil, "load additional extension file(s)")
|
||||||
|
|
||||||
|
// Skills flags
|
||||||
|
rootCmd.PersistentFlags().
|
||||||
|
BoolVar(&noSkillsFlag, "no-skills", false, "disable skill loading (auto-discovery and explicit)")
|
||||||
|
rootCmd.PersistentFlags().
|
||||||
|
StringSliceVar(&skillsPaths, "skill", nil, "load skill file or directory (repeatable)")
|
||||||
|
rootCmd.PersistentFlags().
|
||||||
|
StringVar(&skillsDir, "skills-dir", "", "override the project-local skills directory for auto-discovery")
|
||||||
|
|
||||||
flags := rootCmd.PersistentFlags()
|
flags := rootCmd.PersistentFlags()
|
||||||
flags.StringVar(&providerURL, "provider-url", "", "base URL for the provider API (applies to OpenAI, Anthropic, Ollama, and Google)")
|
flags.StringVar(&providerURL, "provider-url", "", "base URL for the provider API (applies to OpenAI, Anthropic, Ollama, and Google)")
|
||||||
flags.StringVar(&providerAPIKey, "provider-api-key", "", "API key for the provider (applies to OpenAI, Anthropic, and Google)")
|
flags.StringVar(&providerAPIKey, "provider-api-key", "", "API key for the provider (applies to OpenAI, Anthropic, and Google)")
|
||||||
@@ -333,6 +346,9 @@ func init() {
|
|||||||
_ = viper.BindPFlag("extension", rootCmd.PersistentFlags().Lookup("extension"))
|
_ = viper.BindPFlag("extension", rootCmd.PersistentFlags().Lookup("extension"))
|
||||||
_ = viper.BindPFlag("prompt-template", rootCmd.PersistentFlags().Lookup("prompt-template"))
|
_ = viper.BindPFlag("prompt-template", rootCmd.PersistentFlags().Lookup("prompt-template"))
|
||||||
_ = viper.BindPFlag("no-prompt-templates", rootCmd.PersistentFlags().Lookup("no-prompt-templates"))
|
_ = viper.BindPFlag("no-prompt-templates", rootCmd.PersistentFlags().Lookup("no-prompt-templates"))
|
||||||
|
_ = viper.BindPFlag("no-skills", rootCmd.PersistentFlags().Lookup("no-skills"))
|
||||||
|
_ = viper.BindPFlag("skill", rootCmd.PersistentFlags().Lookup("skill"))
|
||||||
|
_ = viper.BindPFlag("skills-dir", rootCmd.PersistentFlags().Lookup("skills-dir"))
|
||||||
|
|
||||||
// Defaults are already set in flag definitions, no need to duplicate in viper
|
// Defaults are already set in flag definitions, no need to duplicate in viper
|
||||||
|
|
||||||
@@ -820,6 +836,9 @@ func runNormalMode(ctx context.Context) error {
|
|||||||
AutoCompact: autoCompactFlag,
|
AutoCompact: autoCompactFlag,
|
||||||
MCPAuthHandler: authHandler,
|
MCPAuthHandler: authHandler,
|
||||||
DisableCoreTools: viper.GetBool("no-core-tools"),
|
DisableCoreTools: viper.GetBool("no-core-tools"),
|
||||||
|
NoSkills: noSkillsFlag,
|
||||||
|
Skills: skillsPaths,
|
||||||
|
SkillsDir: skillsDir,
|
||||||
// This callback is called when each MCP server finishes loading.
|
// This callback is called when each MCP server finishes loading.
|
||||||
// We use a closure that captures appInstancePtr which is set after
|
// We use a closure that captures appInstancePtr which is set after
|
||||||
// app.New() is called below.
|
// app.New() is called below.
|
||||||
|
|||||||
@@ -493,6 +493,12 @@ mcpServers:
|
|||||||
# maxTokens: 16384
|
# maxTokens: 16384
|
||||||
# systemPrompt: "You are a deep reasoning assistant." # or a file path
|
# systemPrompt: "You are a deep reasoning assistant." # or a file path
|
||||||
|
|
||||||
|
# Skills configuration (all optional)
|
||||||
|
# no-skills: false # Set to true to disable all skill loading
|
||||||
|
# skill: # Explicit skill files/dirs (disables auto-discovery)
|
||||||
|
# - "/path/to/skill.md"
|
||||||
|
# skills-dir: "/path/to/skills" # Override project-local directory for auto-discovery
|
||||||
|
|
||||||
# API Configuration (can also use environment variables)
|
# API Configuration (can also use environment variables)
|
||||||
# provider-api-key: "your-api-key" # API key for OpenAI, Anthropic, or Google
|
# provider-api-key: "your-api-key" # API key for OpenAI, Anthropic, or Google
|
||||||
# provider-url: "https://api.openai.com/v1" # Base URL for OpenAI, Anthropic, or Ollama
|
# provider-url: "https://api.openai.com/v1" # Base URL for OpenAI, Anthropic, or Ollama
|
||||||
|
|||||||
@@ -205,6 +205,9 @@ func TestEnsureConfigExists(t *testing.T) {
|
|||||||
"type: \"local\"",
|
"type: \"local\"",
|
||||||
"type: \"remote\"",
|
"type: \"remote\"",
|
||||||
"Core tools",
|
"Core tools",
|
||||||
|
"# Skills configuration",
|
||||||
|
"no-skills:",
|
||||||
|
"skills-dir:",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, expected := range expectedSections {
|
for _, expected := range expectedSections {
|
||||||
|
|||||||
@@ -20,3 +20,9 @@ func (m *Kit) ConfigFloatForTest(key string) float64 { return m.v.GetFloat64(key
|
|||||||
// ConfigBoolForTest returns the bool value of key from this Kit's isolated
|
// ConfigBoolForTest returns the bool value of key from this Kit's isolated
|
||||||
// configuration store.
|
// configuration store.
|
||||||
func (m *Kit) ConfigBoolForTest(key string) bool { return m.v.GetBool(key) }
|
func (m *Kit) ConfigBoolForTest(key string) bool { return m.v.GetBool(key) }
|
||||||
|
|
||||||
|
// ConfigStringSliceForTest returns the string slice value of key from this
|
||||||
|
// Kit's isolated configuration store.
|
||||||
|
func (m *Kit) ConfigStringSliceForTest(key string) []string {
|
||||||
|
return m.v.GetStringSlice(key)
|
||||||
|
}
|
||||||
|
|||||||
+18
-2
@@ -1330,9 +1330,25 @@ func New(ctx context.Context, opts *Options) (*Kit, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load skills — either from explicit paths or via auto-discovery.
|
// Load skills — either from explicit paths or via auto-discovery.
|
||||||
if !opts.NoSkills {
|
// Merge viper config with opts: CLI flag / config file values are
|
||||||
|
// already bound to viper by cmd/root.go, so v.GetBool("no-skills"),
|
||||||
|
// v.GetStringSlice("skill"), and v.GetString("skills-dir") capture
|
||||||
|
// both --flag and .kit.yml keys transparently.
|
||||||
|
noSkills := opts.NoSkills || v.GetBool("no-skills")
|
||||||
|
skillPaths := opts.Skills
|
||||||
|
if len(skillPaths) == 0 {
|
||||||
|
skillPaths = v.GetStringSlice("skill")
|
||||||
|
}
|
||||||
|
skillsDir := opts.SkillsDir
|
||||||
|
if skillsDir == "" {
|
||||||
|
skillsDir = v.GetString("skills-dir")
|
||||||
|
}
|
||||||
|
if !noSkills {
|
||||||
|
mergedOpts := *opts
|
||||||
|
mergedOpts.Skills = skillPaths
|
||||||
|
mergedOpts.SkillsDir = skillsDir
|
||||||
var err error
|
var err error
|
||||||
loadedSkills, err = loadSkills(opts)
|
loadedSkills, err = loadSkills(&mergedOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load skills: %w", err)
|
return fmt.Errorf("failed to load skills: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -365,6 +365,81 @@ func TestNewSystemPromptFilePath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestNewWithSkillsOptions verifies that the three skills-related Options
|
||||||
|
// fields (NoSkills, Skills, SkillsDir) are wired correctly into kit.New().
|
||||||
|
func TestNewWithSkillsOptions(t *testing.T) {
|
||||||
|
if os.Getenv("ANTHROPIC_API_KEY") == "" {
|
||||||
|
t.Skip("Skipping test: ANTHROPIC_API_KEY not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
t.Run("NoSkills disables skill loading", func(t *testing.T) {
|
||||||
|
host, err := kit.New(ctx, &kit.Options{
|
||||||
|
Model: "anthropic/claude-sonnet-4-5-20250929",
|
||||||
|
Quiet: true,
|
||||||
|
NoSession: true,
|
||||||
|
NoSkills: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("kit.New failed: %v", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = host.Close() }()
|
||||||
|
|
||||||
|
if got := host.GetSkills(); len(got) != 0 {
|
||||||
|
t.Errorf("NoSkills=true: expected 0 skills, got %d", len(got))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("SkillsDir propagates", func(t *testing.T) {
|
||||||
|
// Use a non-existent dir — no skills will load but the option must be
|
||||||
|
// accepted without error and result in zero skills.
|
||||||
|
dir := t.TempDir()
|
||||||
|
host, err := kit.New(ctx, &kit.Options{
|
||||||
|
Model: "anthropic/claude-sonnet-4-5-20250929",
|
||||||
|
Quiet: true,
|
||||||
|
NoSession: true,
|
||||||
|
SkillsDir: dir,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("kit.New failed: %v", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = host.Close() }()
|
||||||
|
|
||||||
|
// Empty dir → no skills; the important thing is no error.
|
||||||
|
_ = host.GetSkills()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("explicit Skills paths load correctly", func(t *testing.T) {
|
||||||
|
// Write a minimal skill file to a temp dir.
|
||||||
|
dir := t.TempDir()
|
||||||
|
skillFile := dir + "/my-skill.md"
|
||||||
|
content := "---\nname: test-skill\ndescription: A test skill\n---\nDo the thing.\n"
|
||||||
|
if err := os.WriteFile(skillFile, []byte(content), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to write skill file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
host, err := kit.New(ctx, &kit.Options{
|
||||||
|
Model: "anthropic/claude-sonnet-4-5-20250929",
|
||||||
|
Quiet: true,
|
||||||
|
NoSession: true,
|
||||||
|
Skills: []string{skillFile},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("kit.New failed: %v", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = host.Close() }()
|
||||||
|
|
||||||
|
skills := host.GetSkills()
|
||||||
|
if len(skills) != 1 {
|
||||||
|
t.Fatalf("expected 1 skill, got %d", len(skills))
|
||||||
|
}
|
||||||
|
if skills[0].Name != "test-skill" {
|
||||||
|
t.Errorf("skill name = %q; want %q", skills[0].Name, "test-skill")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// TestNewSystemPromptInline confirms that inline system-prompt strings still
|
// TestNewSystemPromptInline confirms that inline system-prompt strings still
|
||||||
// flow through unchanged after the file-path resolution change.
|
// flow through unchanged after the file-path resolution change.
|
||||||
func TestNewSystemPromptInline(t *testing.T) {
|
func TestNewSystemPromptInline(t *testing.T) {
|
||||||
|
|||||||
@@ -205,6 +205,131 @@ func TestNewZeroOptionsKeepsStreamingDefault(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSkillsViperKeys verifies that the three skills config keys (no-skills,
|
||||||
|
// skill, skills-dir) flow through viper when set via a config file, matching
|
||||||
|
// the pattern used by no-extensions and no-core-tools. This test does not
|
||||||
|
// require an API key because it only exercises Options struct plumbing.
|
||||||
|
func TestSkillsViperKeys(t *testing.T) {
|
||||||
|
t.Run("NoSkills option disables skill loading", func(t *testing.T) {
|
||||||
|
o := &kit.Options{}
|
||||||
|
o.NoSkills = true
|
||||||
|
if !o.NoSkills {
|
||||||
|
t.Error("Options.NoSkills = true not reflected on struct")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Skills paths set on Options", func(t *testing.T) {
|
||||||
|
o := &kit.Options{
|
||||||
|
Skills: []string{"/a/skill.md", "/b/skill.md"},
|
||||||
|
}
|
||||||
|
if len(o.Skills) != 2 {
|
||||||
|
t.Errorf("Options.Skills: got %d paths, want 2", len(o.Skills))
|
||||||
|
}
|
||||||
|
if o.Skills[0] != "/a/skill.md" {
|
||||||
|
t.Errorf("Options.Skills[0] = %q; want %q", o.Skills[0], "/a/skill.md")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("SkillsDir set on Options", func(t *testing.T) {
|
||||||
|
o := &kit.Options{
|
||||||
|
SkillsDir: "/custom/skills",
|
||||||
|
}
|
||||||
|
if o.SkillsDir != "/custom/skills" {
|
||||||
|
t.Errorf("Options.SkillsDir = %q; want %q", o.SkillsDir, "/custom/skills")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSkillsConfigFileKeys verifies that no-skills, skill, and skills-dir
|
||||||
|
// config file keys are read via viper and applied correctly. Requires an API
|
||||||
|
// key because kit.New() is called to exercise the full config-load path.
|
||||||
|
func TestSkillsConfigFileKeys(t *testing.T) {
|
||||||
|
if os.Getenv("ANTHROPIC_API_KEY") == "" {
|
||||||
|
t.Skip("Skipping test: ANTHROPIC_API_KEY not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
t.Run("no-skills config key disables skill loading", func(t *testing.T) {
|
||||||
|
// Write a config file with no-skills: true.
|
||||||
|
cfgFile := t.TempDir() + "/.kit.yml"
|
||||||
|
if err := os.WriteFile(cfgFile, []byte("no-skills: true\n"), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to write config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
host, err := kit.New(ctx, &kit.Options{
|
||||||
|
Model: "anthropic/claude-sonnet-4-5-20250929",
|
||||||
|
Quiet: true,
|
||||||
|
NoSession: true,
|
||||||
|
ConfigFile: cfgFile,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("kit.New failed: %v", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = host.Close() }()
|
||||||
|
|
||||||
|
if got := host.GetSkills(); len(got) != 0 {
|
||||||
|
t.Errorf("no-skills:true in config: expected 0 skills, got %d", len(got))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("skill config key loads explicit skill files", func(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
skillFile := dir + "/cfg-skill.md"
|
||||||
|
if err := os.WriteFile(skillFile, []byte("---\nname: cfg-skill\ndescription: from config\n---\nContent.\n"), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to write skill file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgContent := "skill:\n - " + skillFile + "\n"
|
||||||
|
cfgFile := dir + "/.kit.yml"
|
||||||
|
if err := os.WriteFile(cfgFile, []byte(cfgContent), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to write config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
host, err := kit.New(ctx, &kit.Options{
|
||||||
|
Model: "anthropic/claude-sonnet-4-5-20250929",
|
||||||
|
Quiet: true,
|
||||||
|
NoSession: true,
|
||||||
|
ConfigFile: cfgFile,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("kit.New failed: %v", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = host.Close() }()
|
||||||
|
|
||||||
|
skills := host.GetSkills()
|
||||||
|
if len(skills) != 1 {
|
||||||
|
t.Fatalf("expected 1 skill from config, got %d", len(skills))
|
||||||
|
}
|
||||||
|
if skills[0].Name != "cfg-skill" {
|
||||||
|
t.Errorf("skill name = %q; want %q", skills[0].Name, "cfg-skill")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("skills-dir config key overrides auto-discovery root", func(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
cfgContent := "skills-dir: " + dir + "\n"
|
||||||
|
cfgFile := dir + "/.kit.yml"
|
||||||
|
if err := os.WriteFile(cfgFile, []byte(cfgContent), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to write config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
host, err := kit.New(ctx, &kit.Options{
|
||||||
|
Model: "anthropic/claude-sonnet-4-5-20250929",
|
||||||
|
Quiet: true,
|
||||||
|
NoSession: true,
|
||||||
|
ConfigFile: cfgFile,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("kit.New failed: %v", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = host.Close() }()
|
||||||
|
|
||||||
|
// Empty dir → 0 skills; the key point is no error during init.
|
||||||
|
_ = host.GetSkills()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// TestNewStreamingExplicitOptOut verifies that a raw Options can still disable
|
// TestNewStreamingExplicitOptOut verifies that a raw Options can still disable
|
||||||
// streaming by setting Streaming to a pointer to false.
|
// streaming by setting Streaming to a pointer to false.
|
||||||
func TestNewStreamingExplicitOptOut(t *testing.T) {
|
func TestNewStreamingExplicitOptOut(t *testing.T) {
|
||||||
|
|||||||
@@ -56,6 +56,26 @@ kit install --all # Install all extensions without prompting
|
|||||||
kit skill # Install the Kit extensions skill via skills.sh
|
kit skill # Install the Kit extensions skill via skills.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Skills CLI flags
|
||||||
|
|
||||||
|
Control which skills are loaded at startup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Load a specific skill file
|
||||||
|
kit --skill path/to/skill.md "prompt"
|
||||||
|
|
||||||
|
# Load multiple skill files or directories (flag is repeatable)
|
||||||
|
kit --skill ./skill1.md --skill ./skill2.md "prompt"
|
||||||
|
|
||||||
|
# Load all skills from a custom directory instead of the default locations
|
||||||
|
kit --skills-dir /path/to/skills "prompt"
|
||||||
|
|
||||||
|
# Disable all skill loading (auto-discovery and explicit)
|
||||||
|
kit --no-skills "prompt"
|
||||||
|
```
|
||||||
|
|
||||||
|
Skills are auto-discovered from `~/.config/kit/skills/`, `.kit/skills/`, and `.agents/skills/` by default. Use `--skills-dir` to override the project-local search root, or `--skill` to load files explicitly (which disables auto-discovery). `--no-skills` suppresses all skill loading regardless of other flags.
|
||||||
|
|
||||||
## Interactive slash commands
|
## Interactive slash commands
|
||||||
|
|
||||||
These commands are available inside the Kit TUI during an interactive session:
|
These commands are available inside the Kit TUI during an interactive session:
|
||||||
|
|||||||
@@ -48,6 +48,14 @@ These flags control Kit's behavior. When a prompt is passed as a positional argu
|
|||||||
| `--prompt-template` | — | — | Load a specific prompt template by name |
|
| `--prompt-template` | — | — | Load a specific prompt template by name |
|
||||||
| `--no-prompt-templates` | — | `false` | Disable prompt template loading |
|
| `--no-prompt-templates` | — | `false` | Disable prompt template loading |
|
||||||
|
|
||||||
|
## Skills
|
||||||
|
|
||||||
|
| Flag | Short | Default | Description |
|
||||||
|
|------|-------|---------|-------------|
|
||||||
|
| `--skill` | — | — | Load skill file or directory (repeatable) |
|
||||||
|
| `--skills-dir` | — | — | Override the project-local skills directory for auto-discovery |
|
||||||
|
| `--no-skills` | — | `false` | Disable skill loading (auto-discovery and explicit) |
|
||||||
|
|
||||||
## Generation parameters
|
## Generation parameters
|
||||||
|
|
||||||
| Flag | Short | Default | Description |
|
| Flag | Short | Default | Description |
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ stream: true
|
|||||||
| `theme` | object or string | — | UI theme ([inline overrides or file path](/themes)) |
|
| `theme` | object or string | — | UI theme ([inline overrides or file path](/themes)) |
|
||||||
| `prompt-templates` | bool | `true` | Enable prompt template loading |
|
| `prompt-templates` | bool | `true` | Enable prompt template loading |
|
||||||
| `prompt-template` | string | — | Specific template to load by name |
|
| `prompt-template` | string | — | Specific template to load by name |
|
||||||
|
| `no-skills` | bool | `false` | Disable skill loading (auto-discovery and explicit) |
|
||||||
|
| `skill` | list | — | Explicit skill files or directories to load (disables auto-discovery) |
|
||||||
|
| `skills-dir` | string | — | Override the project-local directory used for skill auto-discovery |
|
||||||
|
|
||||||
## Environment variables
|
## Environment variables
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user