feat(skills): default --name to --group, --toolset, or single --prebuilt name (#3586)
## Description Makes `--name` optional for `skills-generate` whenever the command produces a single skill. When `--name` is omitted, `resolveSkillName` picks a name using the first rule that applies: 1. `--name`, if you set it explicitly 2. the `--group` name 3. the `--toolset` name 4. the config file name, when exactly one `--prebuilt` is given If none of these apply (a custom `--config`, multiple `--prebuilt` configs, or no config), `--name` is still required and the command errors clearly. So a group or toolset skill can now be generated with no naming flags: ```bash toolbox --prebuilt alloydb-postgres skills-generate --group greeting # -> skill named "greeting" ``` **Unchanged:** when no group flag is set and multiple groups exist, `--name` still acts as a prefix (`<name>-<group>`) to keep skill names from colliding. Based on #3585 --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com>
This commit is contained in:
@@ -75,7 +75,6 @@ func NewCommand(opts *internal.ToolboxOptions) *cobra.Command {
|
||||
flags.StringVar(&cmd.additionalNotes, "additional-notes", "", "Additional notes to add under the Usage section of the generated SKILL.md")
|
||||
flags.StringVar(&cmd.invocationMode, "invocation-mode", "npx", "Invocation mode for the generated scripts: 'binary' or 'npx'")
|
||||
flags.StringVar(&cmd.toolboxVersion, "toolbox-version", opts.VersionNum, "Version of @toolbox-sdk/server to use for npx approach")
|
||||
_ = cmd.MarkFlagRequired("name")
|
||||
cmd.MarkFlagsMutuallyExclusive("group", "toolset")
|
||||
return cmd.Command
|
||||
}
|
||||
@@ -100,6 +99,13 @@ func run(cmd *skillsCmd, opts *internal.ToolboxOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
name, err := resolveSkillName(cmd.name, cmd.group, cmd.toolset, opts.PrebuiltConfigs)
|
||||
if err != nil {
|
||||
opts.Logger.ErrorContext(ctx, err.Error())
|
||||
return err
|
||||
}
|
||||
cmd.name = name
|
||||
|
||||
if err := os.MkdirAll(cmd.outputDir, 0755); err != nil {
|
||||
errMsg := fmt.Errorf("error creating output directory: %w", err)
|
||||
opts.Logger.ErrorContext(ctx, errMsg.Error())
|
||||
@@ -238,6 +244,26 @@ func run(cmd *skillsCmd, opts *internal.ToolboxOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveSkillName returns the explicit --name when set. Otherwise, in the
|
||||
// single-skill modes it defaults to the --group or --toolset name, and for
|
||||
// prebuilt generation it defaults to the config name when exactly one
|
||||
// --prebuilt config is given. Any other case requires --name.
|
||||
func resolveSkillName(name, group, toolset string, prebuiltConfigs []string) (string, error) {
|
||||
if name != "" {
|
||||
return name, nil
|
||||
}
|
||||
if group != "" {
|
||||
return group, nil
|
||||
}
|
||||
if toolset != "" {
|
||||
return toolset, nil
|
||||
}
|
||||
if len(prebuiltConfigs) == 1 {
|
||||
return strings.ReplaceAll(prebuiltConfigs[0], "/", "-"), nil
|
||||
}
|
||||
return "", fmt.Errorf("--name is required unless --group or --toolset is set, or exactly one --prebuilt config is provided")
|
||||
}
|
||||
|
||||
func (c *skillsCmd) collectContents(ctx context.Context, opts *internal.ToolboxOptions) (map[string]skillContent, error) {
|
||||
// Initialize tools and groups only; skills generation does not need live
|
||||
// sources, auth services, or embedding models.
|
||||
|
||||
@@ -572,6 +572,89 @@ func TestBuildSkillContents(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveSkillName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
flagName string
|
||||
group string
|
||||
toolset string
|
||||
prebuiltConfigs []string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "explicit name wins",
|
||||
flagName: "my-skill",
|
||||
want: "my-skill",
|
||||
},
|
||||
{
|
||||
name: "explicit name wins over prebuilt",
|
||||
flagName: "my-skill",
|
||||
prebuiltConfigs: []string{"alloydb-postgres"},
|
||||
want: "my-skill",
|
||||
},
|
||||
{
|
||||
name: "explicit name wins over group",
|
||||
flagName: "my-skill",
|
||||
group: "greeting",
|
||||
want: "my-skill",
|
||||
},
|
||||
{
|
||||
name: "defaults to group",
|
||||
group: "greeting",
|
||||
want: "greeting",
|
||||
},
|
||||
{
|
||||
name: "defaults to toolset",
|
||||
toolset: "greeting",
|
||||
want: "greeting",
|
||||
},
|
||||
{
|
||||
name: "group wins over prebuilt",
|
||||
group: "greeting",
|
||||
prebuiltConfigs: []string{"alloydb-postgres"},
|
||||
want: "greeting",
|
||||
},
|
||||
{
|
||||
name: "defaults to single prebuilt",
|
||||
prebuiltConfigs: []string{"alloydb-postgres"},
|
||||
want: "alloydb-postgres",
|
||||
},
|
||||
{
|
||||
name: "sanitizes slashes in single prebuilt",
|
||||
prebuiltConfigs: []string{"alloydb-postgres/some-toolset"},
|
||||
want: "alloydb-postgres-some-toolset",
|
||||
},
|
||||
{
|
||||
name: "no name and no prebuilt errors",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no name and multiple prebuilts errors",
|
||||
prebuiltConfigs: []string{"alloydb-postgres", "cloud-sql-postgres"},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := resolveSkillName(tt.flagName, tt.group, tt.toolset, tt.prebuiltConfigs)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil (got %q)", got)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("got %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateSkill_FlagValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -34,7 +34,7 @@ toolbox <tool-source> skills-generate \
|
||||
```
|
||||
|
||||
- `<tool-source>`: Can be `--config`, `--configs`, `--config-folder`, and `--prebuilt`. See the [CLI Reference](../../../reference/cli.md) for details.
|
||||
- `--name`: Name of the generated skill. When multiple toolsets are generated because `--toolset` is omitted, this name acts as a prefix for each skill folder (e.g., `<name>-<toolset>`).
|
||||
- `--name`: (Optional) Name of the generated skill. When multiple toolsets are generated because `--toolset` is omitted, this name acts as a prefix for each skill folder (e.g., `<name>-<toolset>`). When omitted in a single-skill mode, the name defaults, in order, to: the `--group` name, then the `--toolset` name, then the single `--prebuilt` config name. Any other case (a custom `--config`, or multiple `--prebuilt` configs) requires `--name`.
|
||||
- `--description`: (Optional) Description of the generated skill. When a [group](../groups/) defines its own `description`, that takes precedence and `--description` acts as a fallback for groups without one.
|
||||
- `--group`: (Optional) Name of the [group](../groups/) to convert into a single skill. Uses the group's `description`, falling back to `--description`. Mutually exclusive with `--toolset`.
|
||||
- `--toolset`: (Optional) Name of the toolset to convert into a skill. If not provided, one skill will be generated for every custom toolset defined. If no custom toolsets are defined, it defaults to a single skill containing all tools.
|
||||
@@ -103,11 +103,10 @@ Description resolution follows the group's metadata:
|
||||
- If the group defines a `description`, the generated skill uses it — this takes **precedence** over `--description`.
|
||||
- If the group has no `description`, the `--description` flag is used as a fallback.
|
||||
|
||||
For example, to generate a skill from a `data_analyst` group:
|
||||
When `--name` is omitted, the skill is named after the group (e.g., `data_analyst`):
|
||||
|
||||
```bash
|
||||
toolbox --config tools.yaml skills-generate \
|
||||
--name "data_analyst" \
|
||||
--group "data_analyst" \
|
||||
--description "Fallback description if the group has none"
|
||||
```
|
||||
@@ -118,11 +117,10 @@ Sourcing the description from the group keeps the skill's description and the gr
|
||||
|
||||
### Example: Prebuilt Configuration
|
||||
|
||||
You can also generate skills from prebuilt toolsets:
|
||||
You can also generate skills from prebuilt configurations. When exactly one `--prebuilt` config is provided, `--name` is optional and defaults to the prebuilt config name, so no naming flags are required:
|
||||
|
||||
```bash
|
||||
toolbox --prebuilt alloydb-postgres-admin skills-generate \
|
||||
--name "alloydb-postgres-admin" \
|
||||
--description "skill for performing administrative operations on alloydb"
|
||||
```
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ toolbox skills-generate --name <name> --description <description> --toolset <too
|
||||
|
||||
**Flags:**
|
||||
|
||||
- `--name`: Name of the generated skill. When multiple toolsets are generated because `--toolset` is omitted, this name acts as a prefix for each skill folder (e.g., `<name>-<toolset>`).
|
||||
- `--name`: (Optional) Name of the generated skill. When multiple toolsets are generated because `--toolset` is omitted, this name acts as a prefix for each skill folder (e.g., `<name>-<toolset>`). When omitted in a single-skill mode, the name defaults, in order, to: the `--group` name, then the `--toolset` name, then the single `--prebuilt` config name; any other case requires `--name`.
|
||||
- `--description`: (Optional) Description of the generated skill. When a group defines its own `description`, that takes precedence and `--description` acts as a fallback.
|
||||
- `--group`: (Optional) Name of the group to convert into a single skill. Uses the group's `description`, falling back to `--description`. Mutually exclusive with `--toolset`.
|
||||
- `--toolset`: (Optional) Name of the toolset to convert into a skill. If not provided, one skill will be generated for every custom toolset defined. If no custom toolsets are defined, it defaults to a single skill containing all tools.
|
||||
|
||||
Reference in New Issue
Block a user