Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,23 +221,35 @@ General content search across all records using the search service.
**Examples:**
```bash
# Search by record name
dirctl search --query "name=my-agent"
dirctl search --name "my-agent"

# Search by version
dirctl search --query "version=v1.0.0"
dirctl search --version "v1.0.0"

# Search by skill name
dirctl search --skill "natural_language_processing"

# Search by skill ID
dirctl search --query "skill-id=10201"
dirctl search --skill-id "10201"

# Complex search with multiple criteria
dirctl search --limit 10 --offset 0 \
--query "name=my-agent" \
--query "skill-name=Text Completion" \
--query "locator=docker-image:https://example.com/image"
--name "my-agent" \
--skill "natural_language_processing/natural_language_generation/text_completion" \
--locator "docker-image:https://example.com/image"

# Wildcard search examples
dirctl search --name "web*" --version "v1.*"
dirctl search --skill "python*" --skill "*script"
```

**Flags:**
- `--query <key=value>` - Search criteria (repeatable)
- `--name <name>` - Search by record name (repeatable)
- `--version <version>` - Search by version (repeatable)
- `--skill <skill>` - Search by skill name (repeatable)
- `--skill-id <id>` - Search by skill ID (repeatable)
- `--locator <type>` - Search by locator type (repeatable)
- `--module <module>` - Search by module (repeatable)
- `--limit <number>` - Maximum results
- `--offset <number>` - Result offset for pagination

Expand Down
24 changes: 22 additions & 2 deletions cli/cmd/search/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ type options struct {
Limit uint32
Offset uint32

Query Query
// Direct field flags (consistent with routing search)
Names []string
Versions []string
SkillIDs []string
SkillNames []string
Locators []string
Modules []string
}

func init() {
Expand All @@ -20,7 +26,21 @@ func init() {
flags.Uint32Var(&opts.Limit, "limit", 100, "Maximum number of results to return (default: 100)") //nolint:mnd
flags.Uint32Var(&opts.Offset, "offset", 0, "Pagination offset (default: 0)")

flags.VarP(&opts.Query, "query", "q", "Search query terms")
// Direct field flags
flags.StringArrayVar(&opts.Names, "name", nil, "Search for records with specific name (can be repeated)")
flags.StringArrayVar(&opts.Versions, "version", nil, "Search for records with specific version (can be repeated)")
flags.StringArrayVar(&opts.SkillIDs, "skill-id", nil, "Search for records with specific skill ID (can be repeated)")
flags.StringArrayVar(&opts.SkillNames, "skill", nil, "Search for records with specific skill name (can be repeated)")
flags.StringArrayVar(&opts.Locators, "locator", nil, "Search for records with specific locator type (can be repeated)")
flags.StringArrayVar(&opts.Modules, "module", nil, "Search for records with specific module (can be repeated)")

// Add examples in flag help
flags.Lookup("name").Usage = "Search for records with specific name (e.g., --name 'my-agent' --name 'web-*')"
flags.Lookup("version").Usage = "Search for records with specific version (e.g., --version 'v1.0.0' --version 'v1.*')"
flags.Lookup("skill-id").Usage = "Search for records with specific skill ID (e.g., --skill-id '10201')"
flags.Lookup("skill").Usage = "Search for records with specific skill name (e.g., --skill 'natural_language_processing' --skill 'audio')"
flags.Lookup("locator").Usage = "Search for records with specific locator type (e.g., --locator 'docker-image')"
flags.Lookup("module").Usage = "Search for records with specific module (e.g., --module 'runtime/language')"

// Add output format flags
presenter.AddOutputFlags(Command)
Expand Down
79 changes: 0 additions & 79 deletions cli/cmd/search/query.go

This file was deleted.

108 changes: 85 additions & 23 deletions cli/cmd/search/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,74 +19,76 @@ var Command = &cobra.Command{
Short: "Search for records",
Long: `Search for records in the directory using various filters and options.

This command provides a consistent interface with routing search commands.

Usage examples:

1. Basic search with specific filters and limit:

dirctl search --limit 10 \
--offset 0 \
--query "name=my-agent-name" \
--query "version=v1.0.0" \
--query "skill-id=10201" \
--query "skill-name=Text Completion" \
--query "locator=docker-image:https://example.com/docker-image" \
--query "module=my-custom-module-name"
--name "my-agent-name" \
--version "v1.0.0" \
--skill-id "10201" \
--skill "Text Completion" \
--locator "docker-image:https://example.com/docker-image" \
--module "my-custom-module-name"

2. Wildcard search examples:

# Find all web-related agents
dirctl search --query "name=web*"
dirctl search --name "web*"

# Find all v1.x versions
dirctl search --query "version=v1.*"
dirctl search --version "v1.*"

# Find agents with Python or JavaScript skills
dirctl search --query "skill-name=python*" --query "skill-name=*script"
dirctl search --skill "python*" --skill "*script"

# Find agents with HTTP-based locators
dirctl search --query "locator=http*"
dirctl search --locator "http*"

# Find agents with plugin modules
dirctl search --query "module=*-plugin*"
dirctl search --module "*-plugin*"

3. Question mark wildcard (? matches exactly one character):

# Find version v1.0.x where x is any single digit
dirctl search --query "version=v1.0.?"
dirctl search --version "v1.0.?"

# Find agents with 3-character names ending in "api"
dirctl search --query "name=???api"
dirctl search --name "???api"

# Find skills with single character variations
dirctl search --query "skill-name=Pytho?"
dirctl search --skill "Pytho?"

4. List wildcards ([] matches any character within brackets):

# Find agents with numeric suffixes
dirctl search --query "name=agent-[0-9]"
dirctl search --name "agent-[0-9]"

# Find versions starting with v followed by any digit
dirctl search --query "version=v[0-9].*"
dirctl search --version "v[0-9].*"

# Find skills starting with uppercase letters A-M
dirctl search --query "skill-name=[A-M]*"
dirctl search --skill "[A-M]*"

# Find locators with specific protocols
dirctl search --query "locator=[hf]tt[ps]*"
dirctl search --locator "[hf]tt[ps]*"

5. Complex wildcard patterns:

# Find API services with v2 versions
dirctl search --query "name=api-*-service" --query "version=v2.*"
dirctl search --name "api-*-service" --version "v2.*"

# Find machine learning agents
dirctl search --query "skill-name=*machine*learning*"
dirctl search --skill "*machine*learning*"

# Find agents with container locators
dirctl search --query "locator=*docker*" --query "locator=*container*"
dirctl search --locator "*docker*" --locator "*container*"

# Combine different wildcard types
dirctl search --query "name=web-[0-9]?" --query "version=v?.*.?"
dirctl search --name "web-[0-9]?" --version "v?.*.?"

`,
RunE: func(cmd *cobra.Command, _ []string) error {
Expand All @@ -100,10 +102,13 @@ func runCommand(cmd *cobra.Command) error {
return errors.New("failed to get client from context")
}

// Build queries from direct field flags
queries := buildQueriesFromFlags()

ch, err := c.Search(cmd.Context(), &searchv1.SearchRequest{
Limit: &opts.Limit,
Offset: &opts.Offset,
Queries: opts.Query.ToAPIQueries(),
Queries: queries,
})
if err != nil {
return fmt.Errorf("failed to search: %w", err)
Expand All @@ -122,3 +127,60 @@ func runCommand(cmd *cobra.Command) error {

return presenter.PrintMessage(cmd, "record CIDs", "Record CIDs found", results)
}

// buildQueriesFromFlags builds API queries.
func buildQueriesFromFlags() []*searchv1.RecordQuery {
queries := make([]*searchv1.RecordQuery, 0,
len(opts.Names)+len(opts.Versions)+len(opts.SkillIDs)+
len(opts.SkillNames)+len(opts.Locators)+len(opts.Modules))

// Add name queries
for _, name := range opts.Names {
queries = append(queries, &searchv1.RecordQuery{
Type: searchv1.RecordQueryType_RECORD_QUERY_TYPE_NAME,
Value: name,
})
}

// Add version queries
for _, version := range opts.Versions {
queries = append(queries, &searchv1.RecordQuery{
Type: searchv1.RecordQueryType_RECORD_QUERY_TYPE_VERSION,
Value: version,
})
}

// Add skill-id queries
for _, skillID := range opts.SkillIDs {
queries = append(queries, &searchv1.RecordQuery{
Type: searchv1.RecordQueryType_RECORD_QUERY_TYPE_SKILL_ID,
Value: skillID,
})
}

// Add skill-name queries
for _, skillName := range opts.SkillNames {
queries = append(queries, &searchv1.RecordQuery{
Type: searchv1.RecordQueryType_RECORD_QUERY_TYPE_SKILL_NAME,
Value: skillName,
})
}

// Add locator queries
for _, locator := range opts.Locators {
queries = append(queries, &searchv1.RecordQuery{
Type: searchv1.RecordQueryType_RECORD_QUERY_TYPE_LOCATOR,
Value: locator,
})
}

// Add module queries
for _, module := range opts.Modules {
queries = append(queries, &searchv1.RecordQuery{
Type: searchv1.RecordQueryType_RECORD_QUERY_TYPE_MODULE,
Value: module,
})
}

return queries
}
25 changes: 12 additions & 13 deletions e2e/local/01_storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package local

import (
_ "embed"
"fmt"
"os"
"path/filepath"
"time"
Expand Down Expand Up @@ -128,19 +127,19 @@ var _ = ginkgo.Describe("Running dirctl end-to-end tests using a local single no
WithLimit(10).
WithOffset(0).
WithArgs("--raw").
WithQuery("name", version.expectedAgentName). // Use version-specific record name to prevent conflicts between V1/V2/V3 tests
WithQuery("skill-id", version.expectedSkillIDs[0]).
WithQuery("skill-name", version.expectedSkillNames[0])
WithName(version.expectedAgentName). // Use version-specific record name to prevent conflicts between V1/V2/V3 tests
WithSkillID(version.expectedSkillIDs[0]).
WithSkillName(version.expectedSkillNames[0])

// Add locator and module queries only if they exist (not empty for minimal test)
if version.expectedLocator != "" {
search = search.WithQuery("locator", version.expectedLocator)
search = search.WithLocator(version.expectedLocator)
}
if version.expectedModule != "" {
search = search.WithQuery("module", version.expectedModule)
search = search.WithModule(version.expectedModule)
}

search.ShouldReturn(fmt.Sprintf("[%s]", cid))
search.ShouldContain(cid)
})

// Step 6: Search by second skill (depends on push)
Expand All @@ -155,19 +154,19 @@ var _ = ginkgo.Describe("Running dirctl end-to-end tests using a local single no
WithLimit(10).
WithOffset(0).
WithArgs("--raw").
WithQuery("name", version.expectedAgentName). // Use version-specific record name to prevent conflicts between V1/V2/V3 tests
WithQuery("skill-id", version.expectedSkillIDs[1]).
WithQuery("skill-name", version.expectedSkillNames[1])
WithName(version.expectedAgentName). // Use version-specific record name to prevent conflicts between V1/V2/V3 tests
WithSkillID(version.expectedSkillIDs[1]).
WithSkillName(version.expectedSkillNames[1])

// Add locator and module queries only if they exist (not empty for minimal test)
if version.expectedLocator != "" {
search = search.WithQuery("locator", version.expectedLocator)
search = search.WithLocator(version.expectedLocator)
}
if version.expectedModule != "" {
search = search.WithQuery("module", version.expectedModule)
search = search.WithModule(version.expectedModule)
}

search.ShouldReturn(fmt.Sprintf("[%s]", cid))
search.ShouldContain(cid)
})

// Step 7: Test non-existent pull (independent test)
Expand Down
Loading
Loading