Skip to content

Conversation

@MarioCadenas
Copy link
Contributor

@MarioCadenas MarioCadenas commented Jan 11, 2026

Changes

Overview

This PR adds new AppKit development commands to databricks apps, making them first-class citizens alongside the auto-generated Apps API commands. The implementation follows the same pattern established by the pipelines commands.

New Command Structure

databricks apps now has three command groups:

Group Commands Description
Available Commands deploy, dev-remote, init, logs, run-local, validate Custom development commands
Management Commands create, delete, get, list, start, stop, etc. Auto-generated API commands
Permission Commands get-permissions, set-permissions, etc. Permission management

Key Features

  1. databricks apps init - Initialize new AppKit projects from templates with interactive prompts, but also allowing full prompt override via flags.
  2. databricks apps dev-remote - Run local Vite dev server with WebSocket bridge to remote app (this command already exists, but now has some improvements like deriving the project from the folder and reconnecting).
  3. databricks apps validate - Run validation of the app running type checking, linting and building.
  4. databricks apps deploy - Dual-mode deployment:
    • Bundle mode (when databricks.yml exists): Validates → Deploys bundle → Runs app
    • API mode: Standard API deployment with APP_NAME argument

Directory Structure

cmd/apps/                    # Custom commands
├── apps.go                  # Commands() + ManagementGroupID
├── init.go                  # Initialize projects
├── dev.go                   # Dev-remote command
├── deploy_bundle.go         # Bundle-aware deploy
├── logs.go                  # App logs
├── run_local.go               # Run locally
└── validate.go             # Validate the project compiles

cmd/workspace/apps/          # Auto-generated + overrides
├── apps.go                  # SDK commands
├── overrides.go             # Imports cmd/apps, groups commands
└── errors.go                # Error handling

libs/apps/                   # Shared libraries
├── features/                # Feature definitions
├── prompt/                  # Interactive prompts
├── validation/              # Project validation
└── vite/                    # Vite bridge

Why

Tests

@MarioCadenas MarioCadenas force-pushed the appkit-cli-commands branch 3 times, most recently from 04f4c88 to 6e23dec Compare January 14, 2026 09:01
@eng-dev-ecosystem-bot
Copy link
Collaborator

eng-dev-ecosystem-bot commented Jan 14, 2026

Commit: 81e7752

Run: 21163313341

Env 🟨​KNOWN 🔄​flaky 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
🟨​ aws linux 7 10 3 411 694 21:43
🟨​ aws windows 10 7 3 413 692 22:44
🟨​ aws-ucws linux 6 14 2 583 568 57:21
🟨​ aws-ucws windows 9 11 2 585 566 57:27
🟨​ azure linux 13 1 4 411 693 205:48
🟨​ azure windows 12 2 4 413 691 179:14
🟨​ azure-ucws linux 16 1 3 579 567 227:30
🟨​ azure-ucws windows 16 1 3 581 565 221:18
🟨​ gcp linux 13 1 4 400 699 203:57
🟨​ gcp windows 13 2 1 4 400 697 200:29
26 interesting tests: 22 KNOWN, 2 flaky, 1 SKIP, 1 RECOVERED
Test Name aws linux aws windows aws-ucws linux aws-ucws windows azure linux azure windows azure-ucws linux azure-ucws windows gcp linux gcp windows
🟨​ TestAccept 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K
🟨​ TestAccept/bundle/deployment/bind/alert 🙈​S 🙈​S 🙈​S 🙈​S 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K
🟨​ TestAccept/bundle/deployment/bind/alert/DATABRICKS_BUNDLE_ENGINE=direct 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K
🟨​ TestAccept/bundle/deployment/bind/alert/DATABRICKS_BUNDLE_ENGINE=terraform 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K
🟨​ TestAccept/bundle/generate/alert 💚​R 🟨​K 💚​R 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K
🟨​ TestAccept/bundle/generate/alert/DATABRICKS_BUNDLE_ENGINE=direct 💚​R 🟨​K 💚​R 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K
🟨​ TestAccept/bundle/generate/alert/DATABRICKS_BUNDLE_ENGINE=terraform 💚​R 🟨​K 💚​R 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K
🟨​ TestAccept/bundle/resources/alerts/basic 💚​R 💚​R 💚​R 💚​R 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K
🟨​ TestAccept/bundle/resources/alerts/basic/DATABRICKS_BUNDLE_ENGINE=direct 💚​R 💚​R 💚​R 💚​R 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K
🟨​ TestAccept/bundle/resources/alerts/basic/DATABRICKS_BUNDLE_ENGINE=terraform 💚​R 💚​R 💚​R 💚​R 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K
🟨​ TestAccept/bundle/resources/alerts/with_file 💚​R 💚​R 💚​R 💚​R 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K
🟨​ TestAccept/bundle/resources/alerts/with_file/DATABRICKS_BUNDLE_ENGINE=direct 💚​R 💚​R 💚​R 💚​R 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K
🟨​ TestAccept/bundle/resources/alerts/with_file/DATABRICKS_BUNDLE_ENGINE=terraform 💚​R 💚​R 💚​R 💚​R 🟨​K 💚​R 🟨​K 🟨​K 🟨​K 🟨​K
🔄​ TestAccept/bundle/resources/dashboards/detect-change ✅​p ✅​p ✅​p ✅​p ✅​p ✅​p ✅​p ✅​p ✅​p 🔄​f
🔄​ TestAccept/bundle/resources/dashboards/detect-change/DATABRICKS_BUNDLE_ENGINE=direct ✅​p ✅​p ✅​p ✅​p ✅​p ✅​p ✅​p ✅​p ✅​p 🔄​f
🙈​ TestAccept/bundle/resources/permissions 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions 🟨​K 🟨​K 🟨​K 🟨​K 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=direct 🟨​K 🟨​K 🟨​K 🟨​K
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions 🟨​K 🟨​K 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=direct 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/synced_database_tables/basic 🙈​S 🙈​S 🟨​K 🟨​K 🙈​S 🙈​S 🟨​K 🟨​K 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/synced_database_tables/basic/DATABRICKS_BUNDLE_ENGINE=direct 🟨​K 🟨​K 🟨​K 🟨​K
🟨​ TestAccept/bundle/resources/synced_database_tables/basic/DATABRICKS_BUNDLE_ENGINE=terraform 🟨​K 🟨​K 🟨​K 🟨​K
💚​ TestAccept/ssh/connection 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
Top 50 slowest tests (at least 2 minutes):
duration env testname
6:02 aws-ucws linux TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=direct
5:48 gcp windows TestAccept/ssh/connection
5:46 aws-ucws windows TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=terraform
5:46 aws-ucws windows TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=direct
5:41 aws-ucws linux TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=terraform
5:39 aws linux TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=terraform
5:38 aws windows TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=terraform
5:35 aws linux TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=direct
5:34 aws windows TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=direct
5:30 gcp windows TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=terraform
5:18 gcp linux TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=terraform
5:17 gcp windows TestSecretsPutSecretStringValue
5:12 azure windows TestAccept/ssh/connection
5:08 gcp linux TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=direct
5:06 gcp windows TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=direct
5:05 aws-ucws windows TestAccept/ssh/connection
5:04 azure windows TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=direct
5:02 aws windows TestAccept/ssh/connection
4:59 aws windows TestSecretsPutSecretStringValue
4:45 azure windows TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=terraform
4:39 gcp linux TestAccept/ssh/connection
4:32 azure-ucws linux TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=terraform
4:24 azure windows TestSecretsPutSecretStringValue
4:23 azure linux TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=terraform
4:13 azure-ucws linux TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=direct
4:04 azure-ucws windows TestAccept/ssh/connection
4:02 azure linux TestAccept/bundle/resources/clusters/deploy/update-after-create/DATABRICKS_BUNDLE_ENGINE=direct
3:57 azure-ucws windows TestSecretsPutSecretStringValue
3:46 gcp linux TestSecretsPutSecretStringValue
3:40 azure linux TestAccept/ssh/connection
3:12 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:09 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:04 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:03 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:47 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:45 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:44 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:41 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:41 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:39 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:21 azure linux TestSecretsPutSecretStringValue
2:16 azure windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:15 azure linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:14 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:10 azure linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:10 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:10 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:10 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:05 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:05 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct

@@ -0,0 +1,419 @@
package app
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check with @pietern. He wants to unify CLI UX.

Copy link
Contributor

@pietern pietern left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking merging. Need to discuss the deps first.

@keugenek keugenek self-requested a review January 16, 2026 12:02
Copy link
Contributor

@arsenyinfo arsenyinfo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those changes duplicate existing entities like template, validation etc and are not in sync with agentic prompts atm. We can't merge it unless there is a unification plan.

@keugenek
Copy link
Contributor

@MarioCadenas
Copy link
Contributor Author

Those changes duplicate existing entities like template, validation etc and are not in sync with agentic prompts atm. We can't merge it unless there is a unification plan.

the idea is to unify yes, but this comes with the approach of having this as humans first, through the apps scope, but of course, many of this should be used by the ai tools

@MarioCadenas
Copy link
Contributor Author

This PR largely duplicates cli aitools already implemented http://github.com/databricks/cli/tree/main/experimental/aitools

Also https://github.com/databricks/appkit/pull/57/changes template - another duplication https://github.com/databricks/cli/tree/main/experimental/aitools/templates/appkit

It has some similar things yes, I already mentioned this, we were doing similar things and we needed to converge into one.
The template is not really a duplication, is a much simpler one that allows configuration and goes into the scope that it should be. With ideally removing the other one

Let's talk about it in the sync

@fjakobs
Copy link
Contributor

fjakobs commented Jan 16, 2026

@arsenyinfo @keugenek this indeed duplicates functionality from aitools tools. The way I see it we are graduating these commands from the internal AI only namespace to fully supported first class commands, which will be used by humans and agents.

We keep the existing tools under aitools tools until the prompts are updated and we no longer need them.

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: fixup

chore: validate command

chore: fixup

chore: remove import command

chore: fixup
@fjakobs
Copy link
Contributor

fjakobs commented Jan 19, 2026

It would be nice to make dev-local an alias to run-local so we have naming consistency between dev-local and run-local

@fjakobs fjakobs temporarily deployed to test-trigger-is January 19, 2026 09:36 — with GitHub Actions Inactive
return fmt.Errorf("failed to run app: %w. Run `databricks apps logs %s` to view logs", err, appName)
}

cmdio.LogString(ctx, "✔ Deployment complete!")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrewnester We'll need to update this after adding a "started" state to apps.

} else {
log.Debugf(ctx, "No validator found for project type, skipping validation")
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of validation happens here that doesn't happen in the regular "bundle deploy"?

We need to be careful not to make this a hard prereq for deployment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is actually to make things better, it runs validation of the app by running linting, typechecking, and building, so it verifies that everything works and the app won't fail to deploy once its running the building process inside the apps process.

Its basically to avoid the annoying behaviour of having to go into databricks or check the logs command to see why the app failed to deploy.

Right now we just have 1 validator for node apps, at some point we will probably have one for python apps too.

But if it doesn't find a validator, then it just keeps going (to avoid introducing breaking changes)


// printAnswered is an alias for internal use.
func printAnswered(title, value string) {
PrintAnswered(title, value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Print where? This should use cobra's stdout or stderr from the context or directly.


// runValidationCommand executes a shell command in the specified directory.
func runValidationCommand(ctx context.Context, workDir, command string) *ValidationDetail {
cmd := exec.CommandContext(ctx, "sh", "-c", command)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't work on Windows. Check out libs/exec for something that'll use cmd.exe if needed.

go.mod Outdated
require (
github.com/charmbracelet/huh v0.8.0
github.com/charmbracelet/lipgloss v1.1.0
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fold into the main list above and include the license as a suffix comment.

Also please include in the NOTICE file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the resulting binary size with/without these deps?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before:

Screenshot 2026-01-19 at 17 38 45

After:

image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need these direct & indirect dependencies though? We need to move toward covergence between the DABs apps tempalte, the aitools template, and the template here. And convergence in the terminal UX. I'd be okay with this only if we have a plan: we will move everything to charmbracelet in the future / we will remove charmbracelet in a followup.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the plan is the former: we move everything to the charmbracelet libs in the near future.

// substituteVars replaces template variables in a string.
func substituteVars(s string, vars templateVars) string {
s = strings.ReplaceAll(s, "{{.project_name}}", vars.ProjectName)
s = strings.ReplaceAll(s, "{{.sql_warehouse_id}}", vars.SQLWarehouseID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this low-level string mangling stuff seems incredibly unfortunate. Is the aitools template not already usable here, minus the AGENTS.md file? cc @fjakobs

go.mod Outdated
require (
github.com/charmbracelet/huh v0.8.0
github.com/charmbracelet/lipgloss v1.1.0
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need these direct & indirect dependencies though? We need to move toward covergence between the DABs apps tempalte, the aitools template, and the template here. And convergence in the terminal UX. I'd be okay with this only if we have a plan: we will move everything to charmbracelet in the future / we will remove charmbracelet in a followup.

github.com/Masterminds/semver/v3 v3.4.0 // MIT
github.com/briandowns/spinner v1.23.1 // Apache 2.0
github.com/charmbracelet/huh v0.8.0
github.com/charmbracelet/lipgloss v1.1.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also include the license like the other deps.

// detectAppNameFromBundle tries to extract the app name from a databricks.yml bundle config.
// Returns the app name if found, or empty string if no bundle or no apps found.
func detectAppNameFromBundle() string {
const bundleFile = "databricks.yml"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, this doesn't work. The app name may have a prefix, variable reference, etc.

You can look at bundle run for the right way to load/initialize a bundle.

}

return tempDir, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use git.Clone from libs/git.

profile := ""
if w := cmdctx.WorkspaceClient(ctx); w != nil && w.Config != nil {
workspaceHost = w.Config.Host
profile = w.Config.Profile
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Profile may be empty.

// runPostCreateDeploy runs the deploy command in the current directory.
func runPostCreateDeploy(ctx context.Context) error {
// Use os.Args[0] to get the path to the current executable
executable := os.Args[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incompatible with os.Chdir, unless you expect the CLI to be in $PATH.

For example, if you invoke as .databricks/databricks (snapshot build), and chdir, it won't work.

srcProjectDir = src
}

log.Debugf(context.Background(), "Copying template from: %s", srcProjectDir)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect context.


// Skip certain directories
if info.IsDir() && skipDirs[baseName] {
log.Debugf(context.Background(), "Skipping directory: %s", baseName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More incorrect context's.

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

func TestParseGitHubURL(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Candidate to move to libs/git.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants