77 "fmt"
88 "os"
99 "path/filepath"
10+ "sort"
11+ "strings"
1012
1113 "github.com/databricks/cli/cmd/root"
1214 "github.com/databricks/cli/experimental/apps-mcp/lib/common"
@@ -69,98 +71,150 @@ func readClaudeMd(ctx context.Context, configFile string) {
6971 cmdio .LogString (ctx , "=================\n " )
7072}
7173
72- func newInitTemplateCmd () * cobra.Command {
73- cmd := & cobra.Command {
74- Use : "init-template [TEMPLATE_PATH]" ,
75- Short : "Initialize using a bundle template" ,
76- Args : root .MaximumNArgs (1 ),
77- Long : fmt .Sprintf (`Initialize using a bundle template to get started quickly.
74+ // generateFileTree creates a tree-style visualization of the file structure.
75+ // Collapses directories with more than 10 files to avoid clutter.
76+ func generateFileTree (outputDir string ) (string , error ) {
77+ const maxFilesToShow = 10
78+
79+ // collect all files in the output directory
80+ var allFiles []string
81+ err := filepath .Walk (outputDir , func (path string , info os.FileInfo , err error ) error {
82+ if err != nil {
83+ return err
84+ }
85+ if ! info .IsDir () {
86+ relPath , err := filepath .Rel (outputDir , path )
87+ if err != nil {
88+ return err
89+ }
90+ allFiles = append (allFiles , filepath .ToSlash (relPath ))
91+ }
92+ return nil
93+ })
94+ if err != nil {
95+ return "" , err
96+ }
7897
79- TEMPLATE_PATH optionally specifies which template to use. It can be one of the following:
80- %s
81- - a local file system path with a template directory
82- - a Git repository URL, e.g. https://github.com/my/repository
98+ // build a tree structure
99+ tree := make (map [string ][]string )
83100
84- Supports the same options as 'databricks bundle init' plus:
85- --describe: Display template schema without materializing
86- --config_json: Provide config as JSON string instead of file
101+ for _ , relPath := range allFiles {
102+ parts := strings .Split (relPath , "/" )
103+
104+ if len (parts ) == 1 {
105+ // root level file
106+ tree ["" ] = append (tree ["" ], parts [0 ])
107+ } else {
108+ // file in subdirectory
109+ dir := strings .Join (parts [:len (parts )- 1 ], "/" )
110+ fileName := parts [len (parts )- 1 ]
111+ tree [dir ] = append (tree [dir ], fileName )
112+ }
113+ }
114+
115+ // format as tree
116+ var output strings.Builder
117+ var sortedDirs []string
118+ for dir := range tree {
119+ sortedDirs = append (sortedDirs , dir )
120+ }
121+ sort .Strings (sortedDirs )
122+
123+ for _ , dir := range sortedDirs {
124+ filesInDir := tree [dir ]
125+ if dir == "" {
126+ // root files - always show all
127+ for _ , file := range filesInDir {
128+ output .WriteString (file )
129+ output .WriteString ("\n " )
130+ }
131+ } else {
132+ // directory
133+ output .WriteString (dir )
134+ output .WriteString ("/\n " )
135+ if len (filesInDir ) <= maxFilesToShow {
136+ // show all files
137+ for _ , file := range filesInDir {
138+ output .WriteString (" " )
139+ output .WriteString (file )
140+ output .WriteString ("\n " )
141+ }
142+ } else {
143+ // collapse large directories
144+ output .WriteString (fmt .Sprintf (" (%d files)\n " , len (filesInDir )))
145+ }
146+ }
147+ }
148+
149+ return output .String (), nil
150+ }
151+
152+ const (
153+ defaultTemplateRepo = "https://github.com/databricks/cli"
154+ defaultTemplateDir = "experimental/apps-mcp/templates/appkit"
155+ defaultBranch = "main"
156+ templatePathEnvVar = "DATABRICKS_APPKIT_TEMPLATE_PATH"
157+ )
158+
159+ func newInitTemplateCmd () * cobra.Command {
160+ cmd := & cobra.Command {
161+ Use : "init-template" ,
162+ Short : "Initialize a Databricks App using the appkit template" ,
163+ Args : cobra .NoArgs ,
164+ Long : `Initialize a Databricks App using the appkit template.
87165
88166Examples:
89- experimental apps-mcp tools init-template # Choose from built-in templates
90- experimental apps-mcp tools init-template default-python # Python jobs and notebooks
91- experimental apps-mcp tools init-template --output-dir ./my-project
92- experimental apps-mcp tools init-template default-python --describe
93- experimental apps-mcp tools init-template default-python --config_json '{"project_name":"my-app"}'
167+ experimental apps-mcp tools init-template --name my-app
168+ experimental apps-mcp tools init-template --name my-app --warehouse abc123
169+ experimental apps-mcp tools init-template --name my-app --description "My cool app"
170+ experimental apps-mcp tools init-template --name my-app --output-dir ./projects
171+
172+ Environment variables:
173+ DATABRICKS_APPKIT_TEMPLATE_PATH Override template source with local path (for development)
94174
95175After initialization:
96176 databricks bundle deploy --target dev
97-
98- See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more information on templates.` , template .HelpDescriptions ()),
177+ ` ,
99178 }
100179
101- var configFile string
180+ var name string
181+ var warehouse string
182+ var description string
102183 var outputDir string
103- var templateDir string
104- var tag string
105- var branch string
106- var configJSON string
107184 var describe bool
108185
109- cmd .Flags ().StringVar (& configFile , "config-file" , "" , "JSON file containing key value pairs of input parameters required for template initialization." )
110- cmd .Flags ().StringVar (& templateDir , "template-dir" , "" , "Directory path within a Git repository containing the template." )
111- cmd .Flags ().StringVar (& outputDir , "output-dir" , "" , "Directory to write the initialized template to." )
112- cmd .Flags ().StringVar (& branch , "tag" , "" , "Git tag to use for template initialization" )
113- cmd .Flags ().StringVar (& tag , "branch" , "" , "Git branch to use for template initialization" )
114- cmd .Flags ().StringVar (& configJSON , "config-json" , "" , "JSON string containing key value pairs (alternative to --config-file)." )
186+ cmd .Flags ().StringVar (& name , "name" , "" , "Project name (required)" )
187+ cmd .Flags ().StringVar (& warehouse , "warehouse" , "" , "SQL warehouse ID" )
188+ cmd .Flags ().StringVar (& description , "description" , "" , "App description" )
189+ cmd .Flags ().StringVar (& outputDir , "output-dir" , "" , "Directory to write the initialized template to" )
115190 cmd .Flags ().BoolVar (& describe , "describe" , false , "Display template schema without initializing" )
116191
117192 cmd .PreRunE = root .MustWorkspaceClient
118193 cmd .RunE = func (cmd * cobra.Command , args []string ) error {
119- if tag != "" && branch != "" {
120- return errors .New ("only one of --tag or --branch can be specified" )
121- }
122-
123- if configFile != "" && configJSON != "" {
124- return errors .New ("only one of --config-file or --config-json can be specified" )
125- }
194+ ctx := cmd .Context ()
126195
127- if configFile != "" {
128- if configBytes , err := os .ReadFile (configFile ); err == nil {
129- var userConfigMap map [string ]any
130- if err := json .Unmarshal (configBytes , & userConfigMap ); err == nil {
131- if projectName , ok := userConfigMap ["project_name" ].(string ); ok {
132- if err := validateAppNameLength (projectName ); err != nil {
133- return err
134- }
135- }
136- }
137- }
138- }
196+ // Resolve template source: env var override or default remote
197+ templatePathOrUrl := os .Getenv (templatePathEnvVar )
198+ templateDir := ""
199+ branch := ""
139200
140- var templatePathOrUrl string
141- if len (args ) > 0 {
142- templatePathOrUrl = args [0 ]
201+ if templatePathOrUrl == "" {
202+ templatePathOrUrl = defaultTemplateRepo
203+ templateDir = defaultTemplateDir
204+ branch = defaultBranch
143205 }
144206
145- ctx := cmd .Context ()
146-
147- // NEW: Describe mode - show schema only
207+ // Describe mode - show schema only
148208 if describe {
149209 r := template.Resolver {
150210 TemplatePathOrUrl : templatePathOrUrl ,
151211 ConfigFile : "" ,
152212 OutputDir : outputDir ,
153213 TemplateDir : templateDir ,
154- Tag : tag ,
155214 Branch : branch ,
156215 }
157216
158217 tmpl , err := r .Resolve (ctx )
159- if errors .Is (err , template .ErrCustomSelected ) {
160- cmdio .LogString (ctx , "Please specify a path or Git repository to use a custom template." )
161- cmdio .LogString (ctx , "See https://docs.databricks.com/en/dev-tools/bundles/templates.html to learn more about custom templates." )
162- return nil
163- }
164218 if err != nil {
165219 return err
166220 }
@@ -179,55 +233,55 @@ See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more inf
179233 return nil
180234 }
181235
182- if configJSON != "" {
183- var userConfigMap map [string ]any
184- if err := json .Unmarshal ([]byte (configJSON ), & userConfigMap ); err != nil {
185- return fmt .Errorf ("invalid JSON in --config-json: %w" , err )
186- }
236+ // Validate required flag
237+ if name == "" {
238+ return errors .New ("--name is required" )
239+ }
187240
188- // Validate app name length
189- if projectName , ok := userConfigMap ["project_name" ].(string ); ok {
190- if err := validateAppNameLength (projectName ); err != nil {
191- return err
192- }
193- }
241+ if err := validateAppNameLength (name ); err != nil {
242+ return err
243+ }
194244
195- tmpFile , err := os .CreateTemp ("" , "mcp-template-config-*.json" )
196- if err != nil {
197- return fmt .Errorf ("create temp config file: %w" , err )
198- }
199- defer os .Remove (tmpFile .Name ())
245+ // Build config map from flags
246+ configMap := map [string ]any {
247+ "project_name" : name ,
248+ }
249+ if warehouse != "" {
250+ configMap ["sql_warehouse_id" ] = warehouse
251+ }
252+ if description != "" {
253+ configMap ["app_description" ] = description
254+ }
200255
201- configBytes , err := json .Marshal (userConfigMap )
202- if err != nil {
203- return fmt .Errorf ("marshal config: %w" , err )
204- }
205- if _ , err := tmpFile .Write (configBytes ); err != nil {
206- return fmt .Errorf ("write config file: %w" , err )
207- }
208- if err := tmpFile .Close (); err != nil {
209- return fmt .Errorf ("close config file: %w" , err )
210- }
256+ // Write config to temp file
257+ tmpFile , err := os .CreateTemp ("" , "mcp-template-config-*.json" )
258+ if err != nil {
259+ return fmt .Errorf ("create temp config file: %w" , err )
260+ }
261+ defer os .Remove (tmpFile .Name ())
211262
212- configFile = tmpFile .Name ()
263+ configBytes , err := json .Marshal (configMap )
264+ if err != nil {
265+ return fmt .Errorf ("marshal config: %w" , err )
266+ }
267+ if _ , err := tmpFile .Write (configBytes ); err != nil {
268+ return fmt .Errorf ("write config file: %w" , err )
213269 }
270+ if err := tmpFile .Close (); err != nil {
271+ return fmt .Errorf ("close config file: %w" , err )
272+ }
273+
274+ configFile := tmpFile .Name ()
214275
215- // Standard materialize flow (identical to bundle/init.go)
216276 r := template.Resolver {
217277 TemplatePathOrUrl : templatePathOrUrl ,
218278 ConfigFile : configFile ,
219279 OutputDir : outputDir ,
220280 TemplateDir : templateDir ,
221- Tag : tag ,
222281 Branch : branch ,
223282 }
224283
225284 tmpl , err := r .Resolve (ctx )
226- if errors .Is (err , template .ErrCustomSelected ) {
227- cmdio .LogString (ctx , "Please specify a path or Git repository to use a custom template." )
228- cmdio .LogString (ctx , "See https://docs.databricks.com/en/dev-tools/bundles/templates.html to learn more about custom templates." )
229- return nil
230- }
231285 if err != nil {
232286 return err
233287 }
@@ -239,26 +293,30 @@ See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more inf
239293 }
240294 tmpl .Writer .LogTelemetry (ctx )
241295
242- // Show branded success message
243- templateName := "bundle"
244- if templatePathOrUrl != "" {
245- templateName = filepath .Base (templatePathOrUrl )
246- }
247- outputPath := outputDir
248- if outputPath == "" {
249- outputPath = "."
296+ // Determine actual output directory (template writes to subdirectory with project name)
297+ actualOutputDir := outputDir
298+ if actualOutputDir == "" {
299+ actualOutputDir = name
250300 }
301+
251302 // Count files if we can
252303 fileCount := 0
253- if absPath , err := filepath .Abs (outputPath ); err == nil {
304+ if absPath , err := filepath .Abs (actualOutputDir ); err == nil {
254305 _ = filepath .Walk (absPath , func (path string , info os.FileInfo , err error ) error {
255306 if err == nil && ! info .IsDir () {
256307 fileCount ++
257308 }
258309 return nil
259310 })
260311 }
261- cmdio .LogString (ctx , common .FormatScaffoldSuccess (templateName , outputPath , fileCount ))
312+ cmdio .LogString (ctx , common .FormatScaffoldSuccess ("appkit" , actualOutputDir , fileCount ))
313+
314+ // Generate and print file tree structure
315+ fileTree , err := generateFileTree (actualOutputDir )
316+ if err == nil && fileTree != "" {
317+ cmdio .LogString (ctx , "\n File structure:" )
318+ cmdio .LogString (ctx , fileTree )
319+ }
262320
263321 // Try to read and display CLAUDE.md if present
264322 readClaudeMd (ctx , configFile )
0 commit comments