Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d3d84a8
feat: initial setup for "create" cmd in cli
archandatta Dec 2, 2025
cb1dde5
feat: add ts sample-app template
archandatta Dec 3, 2025
966ea89
feat: add cli prompts
archandatta Dec 3, 2025
f93d77c
feat: add file copying function into new directory
archandatta Dec 3, 2025
ee4ac92
feat: add types
archandatta Dec 4, 2025
078e7e9
feat: add types_test.go
archandatta Dec 4, 2025
fd452dd
fix: remove old files
archandatta Dec 4, 2025
628b530
feat: add testing for create_test.go
archandatta Dec 5, 2025
39f13c7
hide `create` command
archandatta Dec 5, 2025
89aa20b
self review
archandatta Dec 5, 2025
d1376a1
review: refactor prompting to use pterm
archandatta Dec 8, 2025
4db2ecd
review: add app name validation with flag
archandatta Dec 8, 2025
f54d0d4
review: update copy text
archandatta Dec 8, 2025
261ead0
refactor: fix test and refactor structure
archandatta Dec 8, 2025
3a20a69
feat: add python and ts templates
archandatta Dec 5, 2025
b5091a1
feat: add fetch templates logic
archandatta Dec 5, 2025
bd95ae9
feat: add template validation
archandatta Dec 5, 2025
9a78824
enable py templates
archandatta Dec 5, 2025
5570832
remove test
archandatta Dec 5, 2025
35220b4
fix: update ts template readme
archandatta Dec 8, 2025
ed1d016
fix: update ts package.json to new pkg versions
archandatta Dec 8, 2025
7dc6f47
fix: update py package.json to new pkg versions
archandatta Dec 8, 2025
b8dced4
fix: adding sorting for UX consistency && tests
archandatta Dec 8, 2025
97f7d38
Merge branch 'main' into archand/add-templates
archandatta Dec 9, 2025
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
2 changes: 1 addition & 1 deletion cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func runCreateApp(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to get language: %w", err)
}

template, err = create.PromptForTemplate(template)
template, err = create.PromptForTemplate(template, language)
if err != nil {
return fmt.Errorf("failed to get template: %w", err)
}
Expand Down
28 changes: 20 additions & 8 deletions pkg/create/prompts.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,21 +92,33 @@ func PromptForLanguage(providedLanguage string) (string, error) {
return l, nil
}

pterm.Warning.Printfln("Language '%s' not found. Please select from available languages.\n", providedLanguage)
return handleLanguagePrompt()
}

// TODO: add validation for template
func PromptForTemplate(providedTemplate string) (string, error) {
if providedTemplate != "" {
return providedTemplate, nil
}

func handleTemplatePrompt(templateKVs TemplateKeyValues) (string, error) {
template, err := pterm.DefaultInteractiveSelect.
WithOptions(GetSupportedTemplates()).
WithOptions(templateKVs.GetTemplateDisplayValues()).
WithDefaultText(TemplatePrompt).
Show()
if err != nil {
return "", err
}
return template, nil

return templateKVs.GetTemplateKeyFromValue(template)
}

func PromptForTemplate(providedTemplate string, providedLanguage string) (string, error) {
templateKVs := GetSupportedTemplatesForLanguage(NormalizeLanguage(providedLanguage))

if providedTemplate == "" {
return handleTemplatePrompt(templateKVs)
}

if templateKVs.ContainsKey(providedTemplate) {
return providedTemplate, nil
}

pterm.Warning.Printfln("Template '%s' not found. Please select from available templates.\n", providedTemplate)
return handleTemplatePrompt(templateKVs)
}
111 changes: 111 additions & 0 deletions pkg/create/templates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package create

import (
"fmt"
"slices"
"sort"
)

type TemplateInfo struct {
Name string
Description string
Languages []string
}

type TemplateKeyValue struct {
Key string
Value string
}

type TemplateKeyValues []TemplateKeyValue

var Templates = map[string]TemplateInfo{
"sample-app": {
Name: "Sample App",
Description: "Implements basic Kernel apps",
Languages: []string{LanguageTypeScript, LanguagePython},
},
"advanced-sample": {
Name: "Advanced Sample",
Description: "Implements sample actions with advanced Kernel configs",
Languages: []string{LanguageTypeScript, LanguagePython},
},
"computer-use": {
Name: "Computer Use",
Description: "Implements the Anthropic Computer Use SDK",
Languages: []string{LanguageTypeScript, LanguagePython},
},
"cua": {
Name: "CUA Sample",
Description: "Implements a Computer Use Agent (OpenAI CUA) sample",
Languages: []string{LanguageTypeScript, LanguagePython},
},
"magnitude": {
Name: "Magnitude",
Description: "Implements the Magnitude.run SDK",
Languages: []string{LanguageTypeScript},
},
"gemini-cua": {
Name: "Gemini CUA",
Description: "Implements Gemini 2.5 Computer Use Agent",
Languages: []string{LanguageTypeScript},
},
"browser-use": {
Name: "Browser Use",
Description: "Implements Browser Use SDK",
Languages: []string{LanguagePython},
},
"stagehand": {
Name: "Stagehand",
Description: "Implements the Stagehand v3 SDK",
Languages: []string{LanguageTypeScript},
},
}

// GetSupportedTemplatesForLanguage returns a list of all supported template names for a given language
func GetSupportedTemplatesForLanguage(language string) TemplateKeyValues {
templates := make(TemplateKeyValues, 0, len(Templates))
for tn := range Templates {
if slices.Contains(Templates[tn].Languages, language) {
templates = append(templates, TemplateKeyValue{
Key: tn,
Value: fmt.Sprintf("%s - %s", Templates[tn].Name, Templates[tn].Description),
})
}
}

sort.Slice(templates, func(i, j int) bool {
return templates[i].Key < templates[j].Key
})

return templates
}

// GetTemplateDisplayValues extracts display values from TemplateKeyValue slice
func (tkv TemplateKeyValues) GetTemplateDisplayValues() []string {
options := make([]string, len(tkv))
for i, kv := range tkv {
options[i] = kv.Value
}
return options
}

// GetTemplateKeyFromValue maps the selected display value back to the template key
func (tkv TemplateKeyValues) GetTemplateKeyFromValue(selectedValue string) (string, error) {
for _, kv := range tkv {
if kv.Value == selectedValue {
return kv.Key, nil
}
}
return "", fmt.Errorf("template not found: %s", selectedValue)
}

// ContainsKey checks if a template key exists in the TemplateKeyValues
func (tkv TemplateKeyValues) ContainsKey(key string) bool {
for _, kv := range tkv {
if kv.Key == key {
return true
}
}
return false
}
113 changes: 113 additions & 0 deletions pkg/create/templates_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package create

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestGetSupportedTemplatesForLanguage_Deterministic(t *testing.T) {
// Run the function multiple times to ensure consistent ordering
const iterations = 10
language := LanguageTypeScript

var firstResult TemplateKeyValues
for i := 0; i < iterations; i++ {
result := GetSupportedTemplatesForLanguage(language)

if i == 0 {
firstResult = result
} else {
// Verify that each iteration produces the same order
assert.Equal(t, len(firstResult), len(result), "All iterations should return the same number of templates")
for j := range result {
assert.Equal(t, firstResult[j].Key, result[j].Key, "Template at index %d should be consistent across iterations", j)
assert.Equal(t, firstResult[j].Value, result[j].Value, "Template value at index %d should be consistent across iterations", j)
}
}
}
}

func TestTemplateKeyValues_GetTemplateDisplayValues(t *testing.T) {
templates := TemplateKeyValues{
{Key: "sample-app", Value: "Sample App - Implements basic Kernel apps"},
{Key: "advanced-sample", Value: "Advanced Sample - Implements sample actions with advanced Kernel configs"},
}

displayValues := templates.GetTemplateDisplayValues()

assert.Len(t, displayValues, 2)
assert.Equal(t, "Sample App - Implements basic Kernel apps", displayValues[0])
assert.Equal(t, "Advanced Sample - Implements sample actions with advanced Kernel configs", displayValues[1])
}

func TestTemplateKeyValues_GetTemplateKeyFromValue(t *testing.T) {
templates := TemplateKeyValues{
{Key: "sample-app", Value: "Sample App - Implements basic Kernel apps"},
{Key: "advanced-sample", Value: "Advanced Sample - Implements sample actions with advanced Kernel configs"},
}

tests := []struct {
name string
selectedValue string
wantKey string
wantErr bool
}{
{
name: "Valid value returns correct key",
selectedValue: "Sample App - Implements basic Kernel apps",
wantKey: "sample-app",
wantErr: false,
},
{
name: "Invalid value returns error",
selectedValue: "Non-existent template",
wantKey: "",
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
key, err := templates.GetTemplateKeyFromValue(tt.selectedValue)

if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.wantKey, key)
}
})
}
}

func TestTemplateKeyValues_ContainsKey(t *testing.T) {
templates := TemplateKeyValues{
{Key: "sample-app", Value: "Sample App - Implements basic Kernel apps"},
{Key: "advanced-sample", Value: "Advanced Sample - Implements sample actions with advanced Kernel configs"},
}

tests := []struct {
name string
key string
want bool
}{
{
name: "Existing key returns true",
key: "sample-app",
want: true,
},
{
name: "Non-existing key returns false",
key: "non-existent",
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := templates.ContainsKey(tt.key)
assert.Equal(t, tt.want, got)
})
}
}
23 changes: 0 additions & 23 deletions pkg/create/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,12 @@ const (
LanguageShorthandPython = "py"
)

type TemplateInfo struct {
Name string
Description string
Languages []string
}

var Templates = map[string]TemplateInfo{
"sample-app": {
Name: "Sample App",
Description: "Implements basic Kernel apps",
Languages: []string{LanguageTypeScript, LanguagePython},
},
}

// SupportedLanguages returns a list of all supported languages
var SupportedLanguages = []string{
LanguageTypeScript,
LanguagePython,
}

// GetSupportedTemplates returns a list of all supported template names
func GetSupportedTemplates() []string {
templates := make([]string, 0, len(Templates))
for tn := range Templates {
templates = append(templates, tn)
}
return templates
}

// Helper to normalize language input (handle shorthand)
func NormalizeLanguage(language string) string {
switch language {
Expand Down
18 changes: 0 additions & 18 deletions pkg/create/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,3 @@ func TestNormalizeLanguage(t *testing.T) {
})
}
}

func TestTemplates(t *testing.T) {
// Should have at least one template
assert.NotEmpty(t, Templates, "Templates map should not be empty")

// Sample app should exist
sampleApp, exists := Templates["sample-app"]
assert.True(t, exists, "sample-app template should exist")

// Sample app should have required fields
assert.NotEmpty(t, sampleApp.Name, "Template should have a name")
assert.NotEmpty(t, sampleApp.Description, "Template should have a description")
assert.NotEmpty(t, sampleApp.Languages, "Template should support at least one language")

// Should support both typescript and python
assert.Contains(t, sampleApp.Languages, string(LanguageTypeScript), "sample-app should support typescript")
assert.Contains(t, sampleApp.Languages, string(LanguagePython), "sample-app should support python")
}
1 change: 1 addition & 0 deletions pkg/templates/python/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.11
3 changes: 3 additions & 0 deletions pkg/templates/python/advanced-sample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Kernel Python Advanced Sample App

This is a collection of Kernel actions that demonstrate how to use the Kernel SDK.
Loading