Skip to content

Commit 80cfeb4

Browse files
authored
Add TS and Python templates (#44)
## Description - adds templates from `onkernel/create-kernel-app/templates` - adds template validation, catching edge cases from the user ## Testing - uncomment line 133 in root.go - run make build && ./bin/kernel create - should walk through the prompts and create a new app with both languages - should present a set of templates respective to the language chosen <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adds comprehensive Python and TypeScript app templates (Computer Use, CUA, Stagehand, Gemini CUA, Magnitude, samples) and updates template embedding to include all assets. > > - **Templates (TypeScript)**: > - Add `computer-use` (Anthropic), `cua` (OpenAI CUA), `stagehand`, `gemini-cua` (Gemini 2.5 + Stagehand), `magnitude`, and a basic sample app with Playwright integration. > - Include project configs (`package.json`, `tsconfig.json`), lockfiles, and utility code (tools, loops, utils). > - **Templates (Python)**: > - Add `computer-use` (Anthropic) with a full sampling loop, Playwright-based computer tools, and Kernel app entrypoint. > - Add `cua` (OpenAI) with Playwright computers (local + Kernel), agent loop, and utilities. > - Include `pyproject.toml`, `uv.lock`, READMEs, and _gitignore files. > - **Template loader**: > - Change `pkg/templates/templates.go` to embed `all:*` so non-TS assets are included. > - **Misc**: > - Update TypeScript sample app deps and docs; add base TS config and .gitignore. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 97f7d38. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 0b56ae5 commit 80cfeb4

File tree

109 files changed

+15611
-56
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+15611
-56
lines changed

cmd/create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func runCreateApp(cmd *cobra.Command, args []string) error {
9090
return fmt.Errorf("failed to get language: %w", err)
9191
}
9292

93-
template, err = create.PromptForTemplate(template)
93+
template, err = create.PromptForTemplate(template, language)
9494
if err != nil {
9595
return fmt.Errorf("failed to get template: %w", err)
9696
}

pkg/create/prompts.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,21 +92,33 @@ func PromptForLanguage(providedLanguage string) (string, error) {
9292
return l, nil
9393
}
9494

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

98-
// TODO: add validation for template
99-
func PromptForTemplate(providedTemplate string) (string, error) {
100-
if providedTemplate != "" {
101-
return providedTemplate, nil
102-
}
103-
99+
func handleTemplatePrompt(templateKVs TemplateKeyValues) (string, error) {
104100
template, err := pterm.DefaultInteractiveSelect.
105-
WithOptions(GetSupportedTemplates()).
101+
WithOptions(templateKVs.GetTemplateDisplayValues()).
106102
WithDefaultText(TemplatePrompt).
107103
Show()
108104
if err != nil {
109105
return "", err
110106
}
111-
return template, nil
107+
108+
return templateKVs.GetTemplateKeyFromValue(template)
109+
}
110+
111+
func PromptForTemplate(providedTemplate string, providedLanguage string) (string, error) {
112+
templateKVs := GetSupportedTemplatesForLanguage(NormalizeLanguage(providedLanguage))
113+
114+
if providedTemplate == "" {
115+
return handleTemplatePrompt(templateKVs)
116+
}
117+
118+
if templateKVs.ContainsKey(providedTemplate) {
119+
return providedTemplate, nil
120+
}
121+
122+
pterm.Warning.Printfln("Template '%s' not found. Please select from available templates.\n", providedTemplate)
123+
return handleTemplatePrompt(templateKVs)
112124
}

pkg/create/templates.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package create
2+
3+
import (
4+
"fmt"
5+
"slices"
6+
"sort"
7+
)
8+
9+
type TemplateInfo struct {
10+
Name string
11+
Description string
12+
Languages []string
13+
}
14+
15+
type TemplateKeyValue struct {
16+
Key string
17+
Value string
18+
}
19+
20+
type TemplateKeyValues []TemplateKeyValue
21+
22+
var Templates = map[string]TemplateInfo{
23+
"sample-app": {
24+
Name: "Sample App",
25+
Description: "Implements basic Kernel apps",
26+
Languages: []string{LanguageTypeScript, LanguagePython},
27+
},
28+
"advanced-sample": {
29+
Name: "Advanced Sample",
30+
Description: "Implements sample actions with advanced Kernel configs",
31+
Languages: []string{LanguageTypeScript, LanguagePython},
32+
},
33+
"computer-use": {
34+
Name: "Computer Use",
35+
Description: "Implements the Anthropic Computer Use SDK",
36+
Languages: []string{LanguageTypeScript, LanguagePython},
37+
},
38+
"cua": {
39+
Name: "CUA Sample",
40+
Description: "Implements a Computer Use Agent (OpenAI CUA) sample",
41+
Languages: []string{LanguageTypeScript, LanguagePython},
42+
},
43+
"magnitude": {
44+
Name: "Magnitude",
45+
Description: "Implements the Magnitude.run SDK",
46+
Languages: []string{LanguageTypeScript},
47+
},
48+
"gemini-cua": {
49+
Name: "Gemini CUA",
50+
Description: "Implements Gemini 2.5 Computer Use Agent",
51+
Languages: []string{LanguageTypeScript},
52+
},
53+
"browser-use": {
54+
Name: "Browser Use",
55+
Description: "Implements Browser Use SDK",
56+
Languages: []string{LanguagePython},
57+
},
58+
"stagehand": {
59+
Name: "Stagehand",
60+
Description: "Implements the Stagehand v3 SDK",
61+
Languages: []string{LanguageTypeScript},
62+
},
63+
}
64+
65+
// GetSupportedTemplatesForLanguage returns a list of all supported template names for a given language
66+
func GetSupportedTemplatesForLanguage(language string) TemplateKeyValues {
67+
templates := make(TemplateKeyValues, 0, len(Templates))
68+
for tn := range Templates {
69+
if slices.Contains(Templates[tn].Languages, language) {
70+
templates = append(templates, TemplateKeyValue{
71+
Key: tn,
72+
Value: fmt.Sprintf("%s - %s", Templates[tn].Name, Templates[tn].Description),
73+
})
74+
}
75+
}
76+
77+
sort.Slice(templates, func(i, j int) bool {
78+
return templates[i].Key < templates[j].Key
79+
})
80+
81+
return templates
82+
}
83+
84+
// GetTemplateDisplayValues extracts display values from TemplateKeyValue slice
85+
func (tkv TemplateKeyValues) GetTemplateDisplayValues() []string {
86+
options := make([]string, len(tkv))
87+
for i, kv := range tkv {
88+
options[i] = kv.Value
89+
}
90+
return options
91+
}
92+
93+
// GetTemplateKeyFromValue maps the selected display value back to the template key
94+
func (tkv TemplateKeyValues) GetTemplateKeyFromValue(selectedValue string) (string, error) {
95+
for _, kv := range tkv {
96+
if kv.Value == selectedValue {
97+
return kv.Key, nil
98+
}
99+
}
100+
return "", fmt.Errorf("template not found: %s", selectedValue)
101+
}
102+
103+
// ContainsKey checks if a template key exists in the TemplateKeyValues
104+
func (tkv TemplateKeyValues) ContainsKey(key string) bool {
105+
for _, kv := range tkv {
106+
if kv.Key == key {
107+
return true
108+
}
109+
}
110+
return false
111+
}

pkg/create/templates_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package create
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestGetSupportedTemplatesForLanguage_Deterministic(t *testing.T) {
10+
// Run the function multiple times to ensure consistent ordering
11+
const iterations = 10
12+
language := LanguageTypeScript
13+
14+
var firstResult TemplateKeyValues
15+
for i := 0; i < iterations; i++ {
16+
result := GetSupportedTemplatesForLanguage(language)
17+
18+
if i == 0 {
19+
firstResult = result
20+
} else {
21+
// Verify that each iteration produces the same order
22+
assert.Equal(t, len(firstResult), len(result), "All iterations should return the same number of templates")
23+
for j := range result {
24+
assert.Equal(t, firstResult[j].Key, result[j].Key, "Template at index %d should be consistent across iterations", j)
25+
assert.Equal(t, firstResult[j].Value, result[j].Value, "Template value at index %d should be consistent across iterations", j)
26+
}
27+
}
28+
}
29+
}
30+
31+
func TestTemplateKeyValues_GetTemplateDisplayValues(t *testing.T) {
32+
templates := TemplateKeyValues{
33+
{Key: "sample-app", Value: "Sample App - Implements basic Kernel apps"},
34+
{Key: "advanced-sample", Value: "Advanced Sample - Implements sample actions with advanced Kernel configs"},
35+
}
36+
37+
displayValues := templates.GetTemplateDisplayValues()
38+
39+
assert.Len(t, displayValues, 2)
40+
assert.Equal(t, "Sample App - Implements basic Kernel apps", displayValues[0])
41+
assert.Equal(t, "Advanced Sample - Implements sample actions with advanced Kernel configs", displayValues[1])
42+
}
43+
44+
func TestTemplateKeyValues_GetTemplateKeyFromValue(t *testing.T) {
45+
templates := TemplateKeyValues{
46+
{Key: "sample-app", Value: "Sample App - Implements basic Kernel apps"},
47+
{Key: "advanced-sample", Value: "Advanced Sample - Implements sample actions with advanced Kernel configs"},
48+
}
49+
50+
tests := []struct {
51+
name string
52+
selectedValue string
53+
wantKey string
54+
wantErr bool
55+
}{
56+
{
57+
name: "Valid value returns correct key",
58+
selectedValue: "Sample App - Implements basic Kernel apps",
59+
wantKey: "sample-app",
60+
wantErr: false,
61+
},
62+
{
63+
name: "Invalid value returns error",
64+
selectedValue: "Non-existent template",
65+
wantKey: "",
66+
wantErr: true,
67+
},
68+
}
69+
70+
for _, tt := range tests {
71+
t.Run(tt.name, func(t *testing.T) {
72+
key, err := templates.GetTemplateKeyFromValue(tt.selectedValue)
73+
74+
if tt.wantErr {
75+
assert.Error(t, err)
76+
} else {
77+
assert.NoError(t, err)
78+
assert.Equal(t, tt.wantKey, key)
79+
}
80+
})
81+
}
82+
}
83+
84+
func TestTemplateKeyValues_ContainsKey(t *testing.T) {
85+
templates := TemplateKeyValues{
86+
{Key: "sample-app", Value: "Sample App - Implements basic Kernel apps"},
87+
{Key: "advanced-sample", Value: "Advanced Sample - Implements sample actions with advanced Kernel configs"},
88+
}
89+
90+
tests := []struct {
91+
name string
92+
key string
93+
want bool
94+
}{
95+
{
96+
name: "Existing key returns true",
97+
key: "sample-app",
98+
want: true,
99+
},
100+
{
101+
name: "Non-existing key returns false",
102+
key: "non-existent",
103+
want: false,
104+
},
105+
}
106+
107+
for _, tt := range tests {
108+
t.Run(tt.name, func(t *testing.T) {
109+
got := templates.ContainsKey(tt.key)
110+
assert.Equal(t, tt.want, got)
111+
})
112+
}
113+
}

pkg/create/types.go

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,35 +14,12 @@ const (
1414
LanguageShorthandPython = "py"
1515
)
1616

17-
type TemplateInfo struct {
18-
Name string
19-
Description string
20-
Languages []string
21-
}
22-
23-
var Templates = map[string]TemplateInfo{
24-
"sample-app": {
25-
Name: "Sample App",
26-
Description: "Implements basic Kernel apps",
27-
Languages: []string{LanguageTypeScript, LanguagePython},
28-
},
29-
}
30-
3117
// SupportedLanguages returns a list of all supported languages
3218
var SupportedLanguages = []string{
3319
LanguageTypeScript,
3420
LanguagePython,
3521
}
3622

37-
// GetSupportedTemplates returns a list of all supported template names
38-
func GetSupportedTemplates() []string {
39-
templates := make([]string, 0, len(Templates))
40-
for tn := range Templates {
41-
templates = append(templates, tn)
42-
}
43-
return templates
44-
}
45-
4623
// Helper to normalize language input (handle shorthand)
4724
func NormalizeLanguage(language string) string {
4825
switch language {

pkg/create/types_test.go

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,3 @@ func TestNormalizeLanguage(t *testing.T) {
2424
})
2525
}
2626
}
27-
28-
func TestTemplates(t *testing.T) {
29-
// Should have at least one template
30-
assert.NotEmpty(t, Templates, "Templates map should not be empty")
31-
32-
// Sample app should exist
33-
sampleApp, exists := Templates["sample-app"]
34-
assert.True(t, exists, "sample-app template should exist")
35-
36-
// Sample app should have required fields
37-
assert.NotEmpty(t, sampleApp.Name, "Template should have a name")
38-
assert.NotEmpty(t, sampleApp.Description, "Template should have a description")
39-
assert.NotEmpty(t, sampleApp.Languages, "Template should support at least one language")
40-
41-
// Should support both typescript and python
42-
assert.Contains(t, sampleApp.Languages, string(LanguageTypeScript), "sample-app should support typescript")
43-
assert.Contains(t, sampleApp.Languages, string(LanguagePython), "sample-app should support python")
44-
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.11
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Kernel Python Advanced Sample App
2+
3+
This is a collection of Kernel actions that demonstrate how to use the Kernel SDK.

0 commit comments

Comments
 (0)