Repository: JanDeDobbeleer/oh-my-posh
Pull request #5346 (2024-07-25 18:20:00)

refactor init
### Prerequisites

- [x] I have read and understood the [contributing guide][CONTRIBUTING.md].
- [x] The commit message follows the [conventional commits][cc] guidelines.
- [x] Tests for the changes have been added (for bug fixes / features).
- [x] Docs have been added/updated (for bug fixes / features).

### Description

- **fix: disable hyperlink functionality for unsupported shells**
- **refactor(color): move background Template to colors**
- **refactor(init): enable features automatically**
- **fix(zsh): correct logic to enable tooltips**
- **feat(cygwin): better detection**
- **refactor: better use of POSH_THEME**
- **feat(cache): split session and device cache**
- **feat(debug): provide shell name on debug**
- **feat(init): add --debug flag**
- **fix(windows): only reformat pwd on cygwin**
- **fix(fish): correctly export environment variables**

<!---

Tips:

If you're not comfortable with working with Git,
we're working a guide (https://ohmyposh.dev/docs/contributing/git) to help you out.
Oh My Posh advises GitKraken (https://www.gitkraken.com/invite/nQmDPR9D) as your preferred cross platform Git GUI power tool.

-->

[CONTRIBUTING.md]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CONTRIBUTING.md
[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary



Patch:
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 55697ab3..ebae0172 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -193,13 +193,14 @@
       "envFile": "${workspaceFolder}/website/.env"
     },
     {
-      "name": "Cache",
+      "name": "Cache clear",
       "type": "go",
       "request": "launch",
       "mode": "debug",
       "program": "${workspaceRoot}/src",
       "args": [
-        "cache"
+        "cache",
+        "clear"
       ]
     }
   ]
diff --git a/src/cache/cache.go b/src/cache/cache.go
index 4a195198..9eaf5c9c 100644
--- a/src/cache/cache.go
+++ b/src/cache/cache.go
@@ -5,6 +5,8 @@ import (
 	"os"
 	"strconv"
 	"time"
+
+	"github.com/jandedobbeleer/oh-my-posh/src/log"
 )
 
 type Cache interface {
@@ -21,23 +23,29 @@ type Cache interface {
 	Delete(key string)
 }
 
+const (
+	FileName = "omp.cache"
+)
+
+var SessionFileName = fmt.Sprintf("%s.%s", FileName, pid())
+
 func pid() string {
 	pid := os.Getenv("POSH_PID")
 	if len(pid) == 0 {
+		log.Debug("POSH_PID not set, using process pid")
 		pid = strconv.Itoa(os.Getppid())
 	}
+
 	return pid
 }
 
-var (
-	TEMPLATECACHE    = fmt.Sprintf("template_cache_%s", pid())
-	TOGGLECACHE      = fmt.Sprintf("toggle_cache_%s", pid())
-	PROMPTCOUNTCACHE = fmt.Sprintf("prompt_count_cache_%s", pid())
-	ENGINECACHE      = fmt.Sprintf("engine_cache_%s", pid())
+const (
+	TEMPLATECACHE    = "template_cache"
+	TOGGLECACHE      = "toggle_cache"
+	PROMPTCOUNTCACHE = "prompt_count_cache"
+	ENGINECACHE      = "engine_cache"
 	FONTLISTCACHE    = "font_list_cache"
-)
 
-const (
 	ONEDAY   = 1440
 	ONEWEEK  = 10080
 	ONEMONTH = 43200
diff --git a/src/cache/file.go b/src/cache/file.go
index eb725b0c..8f66f7a7 100644
--- a/src/cache/file.go
+++ b/src/cache/file.go
@@ -3,28 +3,31 @@ package cache
 import (
 	"encoding/json"
 	"os"
-	"path/filepath"
 	"time"
 
+	"github.com/jandedobbeleer/oh-my-posh/src/log"
 	"github.com/jandedobbeleer/oh-my-posh/src/maps"
 )
 
-const (
-	CacheFile = "/omp.cache"
-)
-
 type File struct {
-	cache     *maps.Concurrent
-	cachePath string
-	dirty     bool
+	cache         *maps.Concurrent
+	cacheFilePath string
+	dirty         bool
 }
 
-func (fc *File) Init(cachePath string) {
+func (fc *File) Init(cacheFilePath string) {
+	defer log.Trace(time.Now(), cacheFilePath)
+
 	fc.cache = maps.NewConcurrent()
-	fc.cachePath = cachePath
-	cacheFilePath := filepath.Join(fc.cachePath, CacheFile)
-	content, err := os.ReadFile(cacheFilePath)
+	fc.cacheFilePath = cacheFilePath
+
+	log.Debug("loading cache file:", fc.cacheFilePath)
+
+	content, err := os.ReadFile(fc.cacheFilePath)
 	if err != nil {
+		// set to dirty so we create it on close
+		fc.dirty = true
+		log.Error(err)
 		return
 	}
 
@@ -38,6 +41,7 @@ func (fc *File) Init(cachePath string) {
 			continue
 		}
 
+		log.Debug("loading cache key:", key)
 		fc.cache.Set(key, co)
 	}
 }
@@ -50,8 +54,7 @@ func (fc *File) Close() {
 	cache := fc.cache.ToSimple()
 
 	if dump, err := json.MarshalIndent(cache, "", "    "); err == nil {
-		cacheFilePath := filepath.Join(fc.cachePath, CacheFile)
-		_ = os.WriteFile(cacheFilePath, dump, 0644)
+		_ = os.WriteFile(fc.cacheFilePath, dump, 0644)
 	}
 }
 
diff --git a/src/cache/template.go b/src/cache/template.go
index 7efdff15..a7401fd0 100644
--- a/src/cache/template.go
+++ b/src/cache/template.go
@@ -22,6 +22,7 @@ type Template struct {
 	WSL           bool
 	PromptCount   int
 	SHLVL         int
+	Jobs          int
 	Segments      *maps.Concurrent
 	SegmentsCache maps.Simple
 
diff --git a/src/cli/cache.go b/src/cli/cache.go
index 0b9f6e2f..cd9023fb 100644
--- a/src/cli/cache.go
+++ b/src/cli/cache.go
@@ -45,17 +45,11 @@ You can do the following:
 
 		switch args[0] {
 		case "path":
-			fmt.Print(env.CachePath())
+			fmt.Println(env.CachePath())
 		case "clear":
-			cacheFilePath := filepath.Join(env.CachePath(), cache.CacheFile)
-			err := os.Remove(cacheFilePath)
-			if err != nil {
-				fmt.Println(err.Error())
-				return
-			}
-			fmt.Printf("removed cache file at %s\n", cacheFilePath)
+			clear(env.CachePath())
 		case "edit":
-			cacheFilePath := filepath.Join(env.CachePath(), cache.CacheFile)
+			cacheFilePath := filepath.Join(env.CachePath(), cache.FileName)
 			editFileWithEditor(cacheFilePath)
 		}
 	},
@@ -67,16 +61,42 @@ func init() {
 
 func editFileWithEditor(file string) {
 	editor := os.Getenv("EDITOR")
+
 	var args []string
 	if strings.Contains(editor, " ") {
 		splitted := strings.Split(editor, " ")
 		editor = splitted[0]
 		args = splitted[1:]
 	}
+
 	args = append(args, file)
 	cmd := exec.Command(editor, args...)
+
 	err := cmd.Run()
 	if err != nil {
 		fmt.Println(err.Error())
 	}
 }
+
+func clear(cachePath string) {
+	// get all files in the cache directory that start with omp.cache and delete them
+	files, err := os.ReadDir(cachePath)
+	if err != nil {
+		return
+	}
+
+	for _, file := range files {
+		if file.IsDir() {
+			continue
+		}
+
+		if !strings.HasPrefix(file.Name(), cache.FileName) {
+			continue
+		}
+
+		path := filepath.Join(cachePath, file.Name())
+		if err := os.Remove(path); err == nil {
+			fmt.Println("removed cache file:", path)
+		}
+	}
+}
diff --git a/src/cli/config_export_image.go b/src/cli/config_export_image.go
index 9391b2f0..e0015613 100644
--- a/src/cli/config_export_image.go
+++ b/src/cli/config_export_image.go
@@ -70,7 +70,7 @@ Exports the config to an image file using customized output options.`,
 		env.Var = cfg.Var
 
 		terminal.Init(shell.GENERIC)
-		terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, cfg.TerminalBackground)
+		terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate(env)
 		terminal.Colors = cfg.MakeColors()
 
 		eng := &prompt.Engine{
diff --git a/src/cli/debug.go b/src/cli/debug.go
index a775f0b0..85789484 100644
--- a/src/cli/debug.go
+++ b/src/cli/debug.go
@@ -16,19 +16,25 @@ import (
 
 // debugCmd represents the prompt command
 var debugCmd = &cobra.Command{
-	Use:   "debug",
-	Short: "Print the prompt in debug mode",
-	Long:  "Print the prompt in debug mode.",
-	Args:  cobra.NoArgs,
-	Run: func(_ *cobra.Command, _ []string) {
+	Use:       "debug [bash|zsh|fish|powershell|pwsh|cmd|nu|tcsh|elvish|xonsh]",
+	Short:     "Print the prompt in debug mode",
+	Long:      "Print the prompt in debug mode.",
+	ValidArgs: supportedShells,
+	Args:      NoArgsOrOneValidArg,
+	Run: func(cmd *cobra.Command, args []string) {
 		startTime := time.Now()
 
+		if len(args) == 0 {
+			_ = cmd.Help()
+			return
+		}
+
 		env := &runtime.Terminal{
 			CmdFlags: &runtime.Flags{
 				Config: configFlag,
 				Debug:  true,
 				PWD:    pwd,
-				Shell:  shellName,
+				Shell:  args[0],
 				Plain:  plain,
 			},
 		}
@@ -42,7 +48,7 @@ var debugCmd = &cobra.Command{
 		env.Var = cfg.Var
 
 		terminal.Init(shell.GENERIC)
-		terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, cfg.TerminalBackground)
+		terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate(env)
 		terminal.Colors = cfg.MakeColors()
 		terminal.Plain = plain
 
diff --git a/src/cli/get.go b/src/cli/get.go
index ac1ed54c..5666f1af 100644
--- a/src/cli/get.go
+++ b/src/cli/get.go
@@ -65,7 +65,7 @@ This command is used to get the value of the following variables:
 			accent := color2.RGB(rgb.R, rgb.G, rgb.B)
 			fmt.Println("#" + accent.Hex())
 		case "toggles":
-			togglesCache, _ := env.Cache().Get(cache.TOGGLECACHE)
+			togglesCache, _ := env.Session().Get(cache.TOGGLECACHE)
 			var toggles []string
 			if len(togglesCache) != 0 {
 				toggles = strings.Split(togglesCache, ",")
diff --git a/src/cli/init.go b/src/cli/init.go
index f7411d45..8d465676 100644
--- a/src/cli/init.go
+++ b/src/cli/init.go
@@ -2,12 +2,11 @@ package cli
 
 import (
 	"fmt"
+	"time"
 
 	"github.com/jandedobbeleer/oh-my-posh/src/config"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
 	"github.com/jandedobbeleer/oh-my-posh/src/shell"
-	"github.com/jandedobbeleer/oh-my-posh/src/terminal"
-	"github.com/jandedobbeleer/oh-my-posh/src/upgrade"
 
 	"github.com/spf13/cobra"
 )
@@ -16,6 +15,20 @@ var (
 	printOutput bool
 	strict      bool
 	manual      bool
+	debug       bool
+
+	supportedShells = []string{
+		"bash",
+		"zsh",
+		"fish",
+		"powershell",
+		"pwsh",
+		"cmd",
+		"nu",
+		"tcsh",
+		"elvish",
+		"xonsh",
+	}
 
 	initCmd = &cobra.Command{
 		Use:   "init [bash|zsh|fish|powershell|pwsh|cmd|nu|tcsh|elvish|xonsh]",
@@ -23,19 +36,8 @@ var (
 		Long: `Initialize your shell and config.
 
 See the documentation to initialize your shell: https://ohmyposh.dev/docs/installation/prompt.`,
-		ValidArgs: []string{
-			"bash",
-			"zsh",
-			"fish",
-			"powershell",
-			"pwsh",
-			"cmd",
-			"nu",
-			"tcsh",
-			"elvish",
-			"xonsh",
-		},
-		Args: NoArgsOrOneValidArg,
+		ValidArgs: supportedShells,
+		Args:      NoArgsOrOneValidArg,
 		Run: func(cmd *cobra.Command, args []string) {
 			if len(args) == 0 {
 				_ = cmd.Help()
@@ -50,53 +52,40 @@ func init() {
 	initCmd.Flags().BoolVarP(&printOutput, "print", "p", false, "print the init script")
 	initCmd.Flags().BoolVarP(&strict, "strict", "s", false, "run in strict mode")
 	initCmd.Flags().BoolVarP(&manual, "manual", "m", false, "enable/disable manual mode")
+	initCmd.Flags().BoolVar(&debug, "debug", false, "enable/disable debug mode")
 	_ = initCmd.MarkPersistentFlagRequired("config")
 	RootCmd.AddCommand(initCmd)
 }
 
 func runInit(shellName string) {
+	var startTime time.Time
+	if debug {
+		startTime = time.Now()
+	}
+
 	env := &runtime.Terminal{
 		CmdFlags: &runtime.Flags{
 			Shell:  shellName,
 			Config: configFlag,
 			Strict: strict,
 			Manual: manual,
+			Debug:  debug,
 		},
 	}
+
 	env.Init()
 	defer env.Close()
 
 	cfg := config.Load(env)
 
-	shell.Transient = cfg.TransientPrompt != nil
-	shell.ErrorLine = cfg.ErrorLine != nil || cfg.ValidLine != nil
-	shell.Tooltips = len(cfg.Tooltips) > 0
-	shell.ShellIntegration = cfg.ShellIntegration
-	shell.PromptMark = shellName == shell.FISH && cfg.ITermFeatures != nil && cfg.ITermFeatures.Contains(terminal.PromptMark)
-	shell.AutoUpgrade = cfg.AutoUpgrade
-
-	for i, block := range cfg.Blocks {
-		// only fetch cursor position when relevant
-		if cfg.EnableCursorPositioning && (i == 0 && block.Newline) {
-			shell.CursorPositioning = true
-		}
-
-		if block.Type == config.RPrompt {
-			shell.RPrompt = true
-		}
-	}
-
-	// allow overriding the upgrade notice from the config
-	if cfg.DisableNotice || cfg.AutoUpgrade {
-		env.Cache().Set(upgrade.CACHEKEY, "disabled", -1)
-	}
+	feats := cfg.Features()
 
-	if printOutput {
-		init := shell.PrintInit(env)
+	if printOutput || debug {
+		init := shell.PrintInit(env, feats, &startTime)
 		fmt.Print(init)
 		return
 	}
 
-	init := shell.Init(env)
+	init := shell.Init(env, feats)
 	fmt.Print(init)
 }
diff --git a/src/cli/print.go b/src/cli/print.go
index b15ecd25..4eb31c4a 100644
--- a/src/cli/print.go
+++ b/src/cli/print.go
@@ -20,6 +20,7 @@ var (
 	eval          bool
 	cleared       bool
 	cached        bool
+	jobCount      int
 
 	command      string
 	shellVersion string
@@ -68,6 +69,7 @@ var printCmd = &cobra.Command{
 			Cached:        cached,
 			NoExitCode:    noStatus,
 			Column:        column,
+			JobCount:      jobCount,
 		}
 
 		eng := prompt.New(flags)
@@ -112,6 +114,7 @@ func init() {
 	printCmd.Flags().BoolVar(&cleared, "cleared", false, "do we have a clear terminal or not")
 	printCmd.Flags().BoolVar(&eval, "eval", false, "output the prompt for eval")
 	printCmd.Flags().IntVar(&column, "column", 0, "the column position of the cursor")
+	printCmd.Flags().IntVar(&jobCount, "job-count", 0, "number of background jobs")
 
 	// Deprecated flags, keep to not break CLI integration
 	printCmd.Flags().IntVarP(&status, "error", "e", 0, "last exit code")
diff --git a/src/cli/toggle.go b/src/cli/toggle.go
index 90f4ab7f..9badf5c8 100644
--- a/src/cli/toggle.go
+++ b/src/cli/toggle.go
@@ -23,7 +23,7 @@ var toggleCmd = &cobra.Command{
 		env.Init()
 		defer env.Close()
 
-		togglesCache, _ := env.Cache().Get(cache.TOGGLECACHE)
+		togglesCache, _ := env.Session().Get(cache.TOGGLECACHE)
 		var toggles []string
 		if len(togglesCache) != 0 {
 			toggles = strings.Split(togglesCache, ",")
@@ -44,7 +44,7 @@ var toggleCmd = &cobra.Command{
 			newToggles = append(newToggles, segment)
 		}
 
-		env.Cache().Set(cache.TOGGLECACHE, strings.Join(newToggles, ","), 1440)
+		env.Session().Set(cache.TOGGLECACHE, strings.Join(newToggles, ","), 1440)
 	},
 }
 
diff --git a/src/color/colors.go b/src/color/colors.go
index 60654fe1..526dfe43 100644
--- a/src/color/colors.go
+++ b/src/color/colors.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/gookit/color"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
+	"github.com/jandedobbeleer/oh-my-posh/src/template"
 )
 
 var TrueColor = true
@@ -103,6 +104,29 @@ func (c Ansi) ToForeground() Ansi {
 	return c
 }
 
+func (c Ansi) ResolveTemplate(env runtime.Environment) Ansi {
+	if c.IsEmpty() {
+		return c
+	}
+
+	if c.IsTransparent() {
+		return emptyColor
+	}
+
+	tmpl := &template.Text{
+		Template: string(c),
+		Context:  nil,
+		Env:      env,
+	}
+
+	text, err := tmpl.Render()
+	if err != nil {
+		return Transparent
+	}
+
+	return Ansi(text)
+}
+
 func (c Ansi) String() string {
 	return string(c)
 }
diff --git a/src/config/config.go b/src/config/config.go
index 48fa5bf4..ceee9e6e 100644
--- a/src/config/config.go
+++ b/src/config/config.go
@@ -3,6 +3,8 @@ package config
 import (
 	"github.com/jandedobbeleer/oh-my-posh/src/color"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
+	"github.com/jandedobbeleer/oh-my-posh/src/segments"
+	"github.com/jandedobbeleer/oh-my-posh/src/shell"
 	"github.com/jandedobbeleer/oh-my-posh/src/template"
 	"github.com/jandedobbeleer/oh-my-posh/src/terminal"
 )
@@ -74,3 +76,56 @@ func (cfg *Config) getPalette() color.Palette {
 	}
 	return cfg.Palette
 }
+
+func (cfg *Config) Features() shell.Features {
+	var feats shell.Features
+
+	if cfg.TransientPrompt != nil {
+		feats = append(feats, shell.Transient)
+	}
+
+	if cfg.ShellIntegration {
+		feats = append(feats, shell.FTCSMarks)
+	}
+
+	if !cfg.AutoUpgrade && !cfg.DisableNotice {
+		feats = append(feats, shell.Notice)
+	}
+
+	if cfg.AutoUpgrade {
+		feats = append(feats, shell.Upgrade)
+	}
+
+	if cfg.ErrorLine != nil || cfg.ValidLine != nil {
+		feats = append(feats, shell.LineError)
+	}
+
+	if len(cfg.Tooltips) > 0 {
+		feats = append(feats, shell.Tooltips)
+	}
+
+	if cfg.env.Shell() == shell.FISH && cfg.ITermFeatures != nil && cfg.ITermFeatures.Contains(terminal.PromptMark) {
+		feats = append(feats, shell.PromptMark)
+	}
+
+	for i, block := range cfg.Blocks {
+		if (i == 0 && block.Newline) && cfg.EnableCursorPositioning {
+			feats = append(feats, shell.CursorPositioning)
+		}
+
+		if block.Type == RPrompt {
+			feats = append(feats, shell.RPrompt)
+		}
+
+		for _, segment := range block.Segments {
+			if segment.Type == AZ {
+				source := segment.Properties.GetString(segments.Source, segments.FirstMatch)
+				if source == segments.Pwsh || source == segments.FirstMatch {
+					feats = append(feats, shell.Azure)
+				}
+			}
+		}
+	}
+
+	return feats
+}
diff --git a/src/config/load.go b/src/config/load.go
index 09ec9e88..fd752486 100644
--- a/src/config/load.go
+++ b/src/config/load.go
@@ -63,7 +63,7 @@ func loadConfig(env runtime.Environment) *Config {
 
 	data, err := stdOS.ReadFile(configFile)
 	if err != nil {
-		env.DebugF("error reading config file: %s", err)
+		env.Error(err)
 		return Default(env, true)
 	}
 
@@ -87,7 +87,7 @@ func loadConfig(env runtime.Environment) *Config {
 	}
 
 	if err != nil {
-		env.DebugF("error decoding config file: %s", err)
+		env.Error(err)
 		return Default(env, true)
 	}
 
diff --git a/src/config/segment.go b/src/config/segment.go
index 0681d5b4..c69fa2bb 100644
--- a/src/config/segment.go
+++ b/src/config/segment.go
@@ -112,10 +112,10 @@ func (segment *Segment) SetEnabled(env runtime.Environment) {
 		return
 	}
 
-	segment.env.DebugF("Segment: %s", segment.Name())
+	segment.env.DebugF("segment: %s", segment.Name())
 
 	// validate toggles
-	if toggles, OK := segment.env.Cache().Get(cache.TOGGLECACHE); OK && len(toggles) > 0 {
+	if toggles, OK := segment.env.Session().Get(cache.TOGGLECACHE); OK && len(toggles) > 0 {
 		list := strings.Split(toggles, ",")
 		for _, toggle := range list {
 			if SegmentType(toggle) == segment.Type || toggle == segment.Alias {
diff --git a/src/prompt/debug.go b/src/prompt/debug.go
index 1adf1548..cbd1356b 100644
--- a/src/prompt/debug.go
+++ b/src/prompt/debug.go
@@ -20,7 +20,7 @@ func (e *Engine) PrintDebug(startTime time.Time, version string) string {
 
 	// console title timing
 	titleStartTime := time.Now()
-	e.Env.Debug("Segment: Title")
+	e.Env.Debug("segment: Title")
 	title := e.getTitleTemplateText()
 	consoleTitle := &config.Segment{
 		Alias:      "ConsoleTitle",
diff --git a/src/prompt/engine.go b/src/prompt/engine.go
index 19b41181..c908e6f0 100644
--- a/src/prompt/engine.go
+++ b/src/prompt/engine.go
@@ -69,21 +69,25 @@ func (e *Engine) canWriteRightBlock(length int, rprompt bool) (int, bool) {
 }
 
 func (e *Engine) pwd() {
+	// only print when relevant
+	if len(e.Config.PWD) == 0 && !e.Config.OSC99 {
+		return
+	}
+
 	// only print when supported
 	sh := e.Env.Shell()
 	if sh == shell.ELVISH || sh == shell.XONSH {
 		return
 	}
-	// only print when relevant
-	if len(e.Config.PWD) == 0 && !e.Config.OSC99 {
-		return
-	}
 
-	cwd := e.Env.Pwd()
+	pwd := e.Env.Pwd()
+	if e.Env.IsCygwin() {
+		pwd = strings.ReplaceAll(pwd, `\`, `/`)
+	}
 
 	// Backwards compatibility for deprecated OSC99
 	if e.Config.OSC99 {
-		e.write(terminal.Pwd(terminal.OSC99, "", "", cwd))
+		e.write(terminal.Pwd(terminal.OSC99, "", "", pwd))
 		return
 	}
 
@@ -100,7 +104,7 @@ func (e *Engine) pwd() {
 
 	user := e.Env.User()
 	host, _ := e.Env.Host()
-	e.write(terminal.Pwd(pwdType, user, host, cwd))
+	e.write(terminal.Pwd(pwdType, user, host, pwd))
 }
 
 func (e *Engine) getNewline() string {
@@ -518,7 +522,7 @@ func New(flags *runtime.Flags) *Engine {
 	flags.HasTransient = cfg.TransientPrompt != nil
 
 	terminal.Init(env.Shell())
-	terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, cfg.TerminalBackground)
+	terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate(env)
 	terminal.Colors = cfg.MakeColors()
 	terminal.Plain = flags.Plain
 
diff --git a/src/prompt/extra.go b/src/prompt/extra.go
index 7cef799b..7b8f6c67 100644
--- a/src/prompt/extra.go
+++ b/src/prompt/extra.go
@@ -25,6 +25,7 @@ func (e *Engine) ExtraPrompt(promptType ExtraPromptType) string {
 	// populate env with latest context
 	e.Env.LoadTemplateCache()
 	var prompt *config.Segment
+
 	switch promptType {
 	case Debug:
 		prompt = e.Config.DebugPrompt
diff --git a/src/runtime/mock/environment.go b/src/runtime/mock/environment.go
index 1fece075..49458a16 100644
--- a/src/runtime/mock/environment.go
+++ b/src/runtime/mock/environment.go
@@ -172,6 +172,11 @@ func (env *Environment) IsWsl2() bool {
 	return args.Bool(0)
 }
 
+func (env *Environment) IsCygwin() bool {
+	args := env.Called()
+	return args.Bool(0)
+}
+
 func (env *Environment) TerminalWidth() (int, error) {
 	args := env.Called()
 	return args.Int(0), args.Error(1)
@@ -187,6 +192,11 @@ func (env *Environment) Cache() cache.Cache {
 	return args.Get(0).(cache.Cache)
 }
 
+func (env *Environment) Session() cache.Cache {
+	args := env.Called()
+	return args.Get(0).(cache.Cache)
+}
+
 func (env *Environment) Close() {
 	_ = env.Called()
 }
diff --git a/src/runtime/os.go b/src/runtime/os.go
index 213779f7..4b74c601 100644
--- a/src/runtime/os.go
+++ b/src/runtime/os.go
@@ -63,6 +63,7 @@ type Flags struct {
 	Cached        bool
 	NoExitCode    bool
 	Column        int
+	JobCount      int
 }
 
 type CommandError struct {
@@ -174,10 +175,12 @@ type Environment interface {
 	HTTPRequest(url string, body io.Reader, timeout int, requestModifiers ...http.RequestModifier) ([]byte, error)
 	IsWsl() bool
 	IsWsl2() bool
+	IsCygwin() bool
 	StackCount() int
 	TerminalWidth() (int, error)
 	CachePath() string
 	Cache() cache.Cache
+	Session() cache.Cache
 	Close()
 	Logs() string
 	InWSLSharedDrive() bool
@@ -199,10 +202,13 @@ type Terminal struct {
 	CmdFlags *Flags
 	Var      maps.Simple
 
-	cwd       string
-	host      string
-	cmdCache  *cache.Command
-	fileCache *cache.File
+	cwd      string
+	host     string
+	cmdCache *cache.Command
+
+	deviceCache  *cache.File
+	sessionCache *cache.File
+
 	tmplCache *cache.Template
 	networks  []*Connection
 
@@ -219,14 +225,23 @@ func (term *Terminal) Init() {
 
 	if term.CmdFlags.Debug {
 		log.Enable()
+		log.Debug("debug mode enabled")
 	}
 
 	if term.CmdFlags.Plain {
 		log.Plain()
+		log.Debug("dlain mode enabled")
 	}
 
-	term.fileCache = &cache.File{}
-	term.fileCache.Init(term.CachePath())
+	initCache := func(fileName string) *cache.File {
+		cache := &cache.File{}
+		cache.Init(filepath.Join(term.CachePath(), fileName))
+		return cache
+	}
+
+	term.deviceCache = initCache(cache.FileName)
+	term.sessionCache = initCache(cache.SessionFileName)
+
 	term.resolveConfigPath()
 	term.cmdCache = &cache.Command{
 		Commands: maps.NewConcurrent(),
@@ -242,12 +257,14 @@ func (term *Terminal) Init() {
 func (term *Terminal) resolveConfigPath() {
 	defer term.Trace(time.Now())
 
-	if len(term.CmdFlags.Config) == 0 {
-		term.CmdFlags.Config = term.Getenv("POSH_THEME")
+	if poshTheme := term.Getenv("POSH_THEME"); len(poshTheme) > 0 {
+		term.DebugF("config set using POSH_THEME: %s", poshTheme)
+		term.CmdFlags.Config = poshTheme
+		return
 	}
 
 	if len(term.CmdFlags.Config) == 0 {
-		term.Debug("No config set, fallback to default config")
+		term.Debug("no config set, fallback to default config")
 		return
 	}
 
@@ -263,10 +280,14 @@ func (term *Terminal) resolveConfigPath() {
 		return
 	}
 
+	isCygwin := func() bool {
+		return term.Platform() == WINDOWS && len(term.Getenv("OSTYPE")) > 0
+	}
+
 	// Cygwin path always needs the full path as we're on Windows but not really.
 	// Doing filepath actions will convert it to a Windows path and break the init script.
-	if term.Platform() == WINDOWS && term.Shell() == "bash" {
-		term.Debug("Cygwin detected, using full path for config")
+	if isCygwin() {
+		term.Debug("cygwin detected, using full path for config")
 		return
 	}
 
@@ -276,11 +297,14 @@ func (term *Terminal) resolveConfigPath() {
 		configFile = filepath.Join(term.Home(), configFile)
 	}
 
-	if !filepath.IsAbs(configFile) {
-		configFile = filepath.Join(term.Pwd(), configFile)
+	abs, err := filepath.Abs(configFile)
+	if err != nil {
+		term.Error(err)
+		term.CmdFlags.Config = filepath.Clean(configFile)
+		return
 	}
 
-	term.CmdFlags.Config = filepath.Clean(configFile)
+	term.CmdFlags.Config = abs
 }
 
 func (term *Terminal) Trace(start time.Time, args ...string) {
@@ -711,7 +735,11 @@ func (term *Terminal) StackCount() int {
 }
 
 func (term *Terminal) Cache() cache.Cache {
-	return term.fileCache
+	return term.deviceCache
+}
+
+func (term *Terminal) Session() cache.Cache {
+	return term.sessionCache
 }
 
 func (term *Terminal) saveTemplateCache() {
@@ -727,20 +755,21 @@ func (term *Terminal) saveTemplateCache() {
 
 	templateCache, err := json.Marshal(tmplCache)
 	if err == nil {
-		term.fileCache.Set(cache.TEMPLATECACHE, string(templateCache), 1440)
+		term.sessionCache.Set(cache.TEMPLATECACHE, string(templateCache), 1440)
 	}
 }
 
 func (term *Terminal) Close() {
 	defer term.Trace(time.Now())
 	term.saveTemplateCache()
-	term.fileCache.Close()
+	term.deviceCache.Close()
+	term.sessionCache.Close()
 }
 
 func (term *Terminal) LoadTemplateCache() {
 	defer term.Trace(time.Now())
 
-	val, OK := term.fileCache.Get(cache.TEMPLATECACHE)
+	val, OK := term.sessionCache.Get(cache.TEMPLATECACHE)
 	if !OK {
 		return
 	}
@@ -782,6 +811,7 @@ func (term *Terminal) TemplateCache() *cache.Template {
 	tmplCache.PromptCount = term.CmdFlags.PromptCount
 	tmplCache.Env = make(map[string]string)
 	tmplCache.Var = make(map[string]any)
+	tmplCache.Jobs = term.CmdFlags.JobCount
 
 	if term.Var != nil {
 		tmplCache.Var = term.Var
@@ -873,6 +903,8 @@ func dirMatchesOneOf(dir, home, goos string, regexes []string) bool {
 }
 
 func (term *Terminal) SetPromptCount() {
+	defer term.Trace(time.Now())
+
 	countStr := os.Getenv("POSH_PROMPT_COUNT")
 	if len(countStr) > 0 {
 		// this counter is incremented by the shell
@@ -883,13 +915,13 @@ func (term *Terminal) SetPromptCount() {
 		}
 	}
 	var count int
-	if val, found := term.Cache().Get(cache.PROMPTCOUNTCACHE); found {
+	if val, found := term.Session().Get(cache.PROMPTCOUNTCACHE); found {
 		count, _ = strconv.Atoi(val)
 	}
 	// only write to cache if we're the primary prompt
 	if term.CmdFlags.Primary {
 		count++
-		term.Cache().Set(cache.PROMPTCOUNTCACHE, strconv.Itoa(count), 1440)
+		term.Session().Set(cache.PROMPTCOUNTCACHE, strconv.Itoa(count), 1440)
 	}
 	term.CmdFlags.PromptCount = count
 }
@@ -898,9 +930,11 @@ func (term *Terminal) CursorPosition() (row, col int) {
 	if number, err := strconv.Atoi(term.Getenv("POSH_CURSOR_LINE")); err == nil {
 		row = number
 	}
+
 	if number, err := strconv.Atoi(term.Getenv("POSH_CURSOR_COLUMN")); err != nil {
 		col = number
 	}
+
 	return
 }
 
diff --git a/src/runtime/os_unix.go b/src/runtime/os_unix.go
index f095b141..a49c2a3b 100644
--- a/src/runtime/os_unix.go
+++ b/src/runtime/os_unix.go
@@ -53,6 +53,11 @@ func (term *Terminal) IsWsl2() bool {
 	return strings.Contains(uname, "WSL2")
 }
 
+func (term *Terminal) IsCygwin() bool {
+	defer term.Trace(time.Now())
+	return false
+}
+
 func (term *Terminal) TerminalWidth() (int, error) {
 	defer term.Trace(time.Now())
 
diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go
index c924355b..1976adfa 100644
--- a/src/runtime/os_windows.go
+++ b/src/runtime/os_windows.go
@@ -87,6 +87,11 @@ func (term *Terminal) IsWsl2() bool {
 	return false
 }
 
+func (term *Terminal) IsCygwin() bool {
+	defer term.Trace(time.Now())
+	return len(term.Getenv("OSTYPE")) > 0
+}
+
 func (term *Terminal) TerminalWidth() (int, error) {
 	defer term.Trace(time.Now())
 
@@ -118,10 +123,12 @@ func (term *Terminal) Platform() string {
 
 func (term *Terminal) CachePath() string {
 	defer term.Trace(time.Now())
+
 	// get LOCALAPPDATA if present
 	if cachePath := returnOrBuildCachePath(term.Getenv("LOCALAPPDATA")); len(cachePath) != 0 {
 		return cachePath
 	}
+
 	return term.Home()
 }
 
diff --git a/src/segments/az.go b/src/segments/az.go
index 66c774ed..fde2480d 100644
--- a/src/segments/az.go
+++ b/src/segments/az.go
@@ -21,9 +21,9 @@ type Az struct {
 const (
 	Source properties.Property = "source"
 
-	pwsh       = "pwsh"
-	cli        = "cli"
-	firstMatch = "first_match"
+	Pwsh       = "pwsh"
+	Cli        = "cli"
+	FirstMatch = "first_match"
 	azureEnv   = "POSH_AZURE_SUBSCRIPTION"
 )
 
@@ -82,13 +82,13 @@ func (a *Az) Init(props properties.Properties, env runtime.Environment) {
 }
 
 func (a *Az) Enabled() bool {
-	source := a.props.GetString(Source, firstMatch)
+	source := a.props.GetString(Source, FirstMatch)
 	switch source {
-	case firstMatch:
+	case FirstMatch:
 		return a.getCLISubscription() || a.getModuleSubscription()
-	case pwsh:
+	case Pwsh:
 		return a.getModuleSubscription()
-	case cli:
+	case Cli:
 		return a.getCLISubscription()
 	}
 	return false
diff --git a/src/segments/azd.go b/src/segments/azd.go
index 82bc8774..05bf8b38 100644
--- a/src/segments/azd.go
+++ b/src/segments/azd.go
@@ -42,7 +42,7 @@ func (t *Azd) Enabled() bool {
 	}
 
 	if len(parentFilePath) == 0 {
-		t.env.Debug("No .azure folder found in parent directories")
+		t.env.Debug("no .azure folder found in parent directories")
 		return false
 	}
 
diff --git a/src/segments/git.go b/src/segments/git.go
index bd7fff9a..9755582c 100644
--- a/src/segments/git.go
+++ b/src/segments/git.go
@@ -353,7 +353,7 @@ func (g *Git) hasWorktree(gitdir *runtime.FileInfo) bool {
 	matches := regex.FindNamedRegexMatch(`^gitdir: (?P<dir>.*)$`, content)
 
 	if matches == nil || len(matches["dir"]) == 0 {
-		g.env.Debug("No matches found, directory isn't a worktree")
+		g.env.Debug("no matches found, directory isn't a worktree")
 		return false
 	}
 
diff --git a/src/segments/nba.go b/src/segments/nba.go
index bca6bec9..22bcc782 100644
--- a/src/segments/nba.go
+++ b/src/segments/nba.go
@@ -368,7 +368,7 @@ func (nba *Nba) getResult() (*NBAData, error) {
 	// Cache the fact a game was not found for 30 minutes so we don't call the API too often
 	cacheNotFoundTimeout := nba.props.GetInt(properties.CacheTimeout, 30)
 
-	nba.env.Debug("Validating cache data for " + teamName)
+	nba.env.Debug("validating cache data for " + teamName)
 
 	if cacheScheduleTimeout > 0 {
 		if data, err := nba.getCachedScheduleValue(cachedScheduleKey); err == nil {
@@ -382,7 +382,7 @@ func (nba *Nba) getResult() (*NBAData, error) {
 		}
 	}
 
-	nba.env.Debug("Fetching available data for " + teamName)
+	nba.env.Debug("fetching available data for " + teamName)
 
 	data, err := nba.getAvailableGameData(teamName, httpTimeout)
 	if err != nil {
diff --git a/src/segments/path.go b/src/segments/path.go
index c6479fe0..38c44e29 100644
--- a/src/segments/path.go
+++ b/src/segments/path.go
@@ -126,8 +126,7 @@ func (pt *Path) setPaths() {
 			return false
 		}
 
-		isCygwin := pt.env.GOOS() == runtime.WINDOWS && pt.env.Shell() == shell.BASH
-		return isCygwin
+		return pt.env.IsCygwin()
 	}
 
 	pt.cygPath = displayCygpath()
diff --git a/src/segments/umbraco.go b/src/segments/umbraco.go
index 2b2a2f7d..bc8c53c2 100644
--- a/src/segments/umbraco.go
+++ b/src/segments/umbraco.go
@@ -44,7 +44,7 @@ func (u *Umbraco) Enabled() bool {
 	}
 
 	if len(location) == 0 {
-		u.env.Debug("No umbraco folder found in parent directories")
+		u.env.Debug("no umbraco folder found in parent directories")
 		return false
 	}
 
@@ -94,7 +94,7 @@ func (u *Umbraco) Init(props properties.Properties, env runtime.Environment) {
 func (u *Umbraco) TryFindModernUmbraco(configPath string) bool {
 	// Check the passed in filepath is not empty
 	if len(configPath) == 0 {
-		u.env.Debug("UMBRACO: No .CSProj file path passed in")
+		u.env.Debug("no configPath provided")
 		return false
 	}
 
@@ -111,7 +111,6 @@ func (u *Umbraco) TryFindModernUmbraco(configPath string) bool {
 	err := xml.Unmarshal([]byte(contents), &csProjPackages)
 
 	if err != nil {
-		u.env.Debug("UMBRACO: Error while trying to parse XML of .csproj file")
 		u.env.Debug(err.Error())
 	}
 
@@ -131,7 +130,7 @@ func (u *Umbraco) TryFindModernUmbraco(configPath string) bool {
 func (u *Umbraco) TryFindLegacyUmbraco(configPath string) bool {
 	// Check the passed in filepath is not empty
 	if len(configPath) == 0 {
-		u.env.Debug("UMBRACO: No web.config file path passed in")
+		u.env.Debug("no configPath provided")
 		return false
 	}
 
@@ -148,7 +147,6 @@ func (u *Umbraco) TryFindLegacyUmbraco(configPath string) bool {
 	err := xml.Unmarshal([]byte(contents), &webConfigAppSettings)
 
 	if err != nil {
-		u.env.Debug("UMBRACO: Error while trying to parse XML of web.config file")
 		u.env.Debug(err.Error())
 	}
 
diff --git a/src/segments/unity.go b/src/segments/unity.go
index 7acf3a00..209dfe2c 100644
--- a/src/segments/unity.go
+++ b/src/segments/unity.go
@@ -42,12 +42,12 @@ func (u *Unity) Enabled() bool {
 func (u *Unity) GetUnityVersion() (string, error) {
 	projectDir, err := u.env.HasParentFilePath("ProjectSettings", false)
 	if err != nil {
-		u.env.Debug("No ProjectSettings parent folder found")
+		u.env.Debug("no ProjectSettings parent folder found")
 		return "", err
 	}
 
 	if !u.env.HasFilesInDir(projectDir.Path, "ProjectVersion.txt") {
-		u.env.Debug("No ProjectVersion.txt file found")
+		u.env.Debug("no ProjectVersion.txt file found")
 		return "", err
 	}
 
diff --git a/src/shell/bash.go b/src/shell/bash.go
new file mode 100644
index 00000000..d4bd44f6
--- /dev/null
+++ b/src/shell/bash.go
@@ -0,0 +1,78 @@
+package shell
+
+import (
+	_ "embed"
+
+	"fmt"
+	"strings"
+)
+
+//go:embed scripts/omp.bash
+var bashInit string
+
+const (
+	unixUpgrade = "$_omp_executable upgrade"
+	unixNotice  = "$_omp_executable notice"
+)
+
+func (f Feature) Bash() Code {
+	switch f {
+	case CursorPositioning:
+		return "_omp_cursor_positioning=1"
+	case FTCSMarks:
+		return "_omp_ftcs_marks=1"
+	case Upgrade:
+		return unixUpgrade
+	case Notice:
+		return unixNotice
+	case PromptMark, RPrompt, PoshGit, Azure, LineError, Jobs, Tooltips, Transient:
+		fallthrough
+	default:
+		return ""
+	}
+}
+
+func quotePosixStr(str string) string {
+	if len(str) == 0 {
+		return "''"
+	}
+
+	needQuoting := false
+	var b strings.Builder
+	for _, r := range str {
+		normal := false
+		switch r {
+		case '!', ';', '"', '(', ')', '[', ']', '{', '}', '$', '|', '&', '>', '<', '`', ' ', '#', '~', '*', '?', '=':
+			b.WriteRune(r)
+		case '\\', '\'':
+			b.WriteByte('\\')
+			b.WriteRune(r)
+		case '\a':
+			b.WriteString(`\a`)
+		case '\b':
+			b.WriteString(`\b`)
+		case '\f':
+			b.WriteString(`\f`)
+		case '\n':
+			b.WriteString(`\n`)
+		case '\r':
+			b.WriteString(`\r`)
+		case '\t':
+			b.WriteString(`\t`)
+		case '\v':
+			b.WriteString(`\v`)
+		default:
+			b.WriteRune(r)
+			normal = true
+		}
+		if !normal {
+			needQuoting = true
+		}
+	}
+	// the quoting form $'...' is used for a string contains any special characters
+	if needQuoting {
+		return fmt.Sprintf("$'%s'", b.String())
+	}
+
+	return b.String()
+}
diff --git a/src/shell/cmd.go b/src/shell/cmd.go
new file mode 100644
index 00000000..e4dd5ed9
--- /dev/null
+++ b/src/shell/cmd.go
@@ -0,0 +1,38 @@
+package shell
+
+import (
+	_ "embed"
+
+	"fmt"
+	"strings"
+)
+
+//go:embed scripts/omp.lua
+var cmdInit string
+
+func (f Feature) Cmd() Code {
+	switch f {
+	case Transient:
+		return "transient_enabled = true"
+	case RPrompt:
+		return "rprompt_enabled = true"
+	case Tooltips:
+		return "enable_tooltips()"
+	case Upgrade:
+		return "os.execute(string.format('%s upgrade', omp_exe()))"
+	case Notice:
+		return "os.execute(string.format('%s notice', omp_exe()))"
+	case PromptMark, PoshGit, Azure, LineError, Jobs, FTCSMarks, CursorPositioning:
+		fallthrough
+	default:
+		return ""
+	}
+}
+
+func quoteLuaStr(str string) string {
+	if len(str) == 0 {
+		return "''"
+	}
+
+	return fmt.Sprintf("'%s'", strings.NewReplacer(`\`, `\\`, `'`, `\'`).Replace(str))
+}
diff --git a/src/shell/code.go b/src/shell/code.go
new file mode 100644
index 00000000..2dd5c2f8
--- /dev/null
+++ b/src/shell/code.go
@@ -0,0 +1,29 @@
+package shell
+
+import "strings"
+
+type Code string
+
+func (c Code) Indent(spaces int) Code {
+	return Code(strings.Repeat(" ", spaces) + string(c))
+}
+
+type Lines []Code
+
+func (l Lines) String(script string) string {
+	var builder strings.Builder
+
+	builder.WriteString(script)
+	builder.WriteString("\n")
+
+	for i, line := range l {
+		builder.WriteString(string(line))
+
+		// add newline if not last line
+		if i < len(l)-1 {
+			builder.WriteString("\n")
+		}
+	}
+
+	return builder.String()
+}
diff --git a/src/shell/elvish.go b/src/shell/elvish.go
new file mode 100644
index 00000000..9a7b2198
--- /dev/null
+++ b/src/shell/elvish.go
@@ -0,0 +1,21 @@
+package shell
+
+import (
+	_ "embed"
+)
+
+//go:embed scripts/omp.elv
+var elvishInit string
+
+func (f Feature) Elvish() Code {
+	switch f {
+	case Upgrade:
+		return unixUpgrade
+	case Notice:
+		return unixNotice
+	case PromptMark, RPrompt, PoshGit, Azure, LineError, Jobs, CursorPositioning, Tooltips, Transient, FTCSMarks:
+		fallthrough
+	default:
+		return ""
+	}
+}
diff --git a/src/shell/features.go b/src/shell/features.go
new file mode 100644
index 00000000..fdb33400
--- /dev/null
+++ b/src/shell/features.go
@@ -0,0 +1,55 @@
+package shell
+
+type Feature byte
+
+const (
+	Jobs Feature = iota
+	Azure
+	PoshGit
+	LineError
+	Tooltips
+	Transient
+	FTCSMarks
+	Upgrade
+	Notice
+	PromptMark
+	RPrompt
+	CursorPositioning
+)
+
+type Features []Feature
+
+func (f Features) Lines(shell string) Lines {
+	var lines Lines
+
+	for _, feature := range f {
+		var code Code
+
+		switch shell {
+		case PWSH, PWSH5:
+			code = feature.Pwsh()
+		case ZSH:
+			code = feature.Zsh()
+		case BASH:
+			code = feature.Bash()
+		case ELVISH:
+			code = feature.Elvish()
+		case TCSH:
+			code = feature.Tcsh()
+		case FISH:
+			code = feature.Fish()
+		case CMD:
+			code = feature.Cmd()
+		case NU:
+			code = feature.Nu()
+		case XONSH:
+			code = feature.Xonsh()
+		}
+
+		if len(code) > 0 {
+			lines = append(lines, code)
+		}
+	}
+
+	return lines
+}
diff --git a/src/shell/fish.go b/src/shell/fish.go
new file mode 100644
index 00000000..4c8e642d
--- /dev/null
+++ b/src/shell/fish.go
@@ -0,0 +1,61 @@
+package shell
+
+import (
+	_ "embed"
+
+	"fmt"
+	"strings"
+)
+
+//go:embed scripts/omp.fish
+var fishInit string
+
+func (f Feature) Fish() Code {
+	switch f {
+	case Transient:
+		return "set --global _omp_transient_prompt 1"
+	case FTCSMarks:
+		return "set --global _omp_ftcs_marks 1"
+	case PromptMark:
+		return "set --global _omp_prompt_mark 1"
+	case Tooltips:
+		return "enable_poshtooltips"
+	case Upgrade:
+		return unixUpgrade
+	case Notice:
+		return unixNotice
+	case RPrompt, PoshGit, Azure, LineError, Jobs, CursorPositioning:
+		fallthrough
+	default:
+		return ""
+	}
+}
+
+func quoteFishStr(str string) string {
+	if len(str) == 0 {
+		return "''"
+	}
+	needQuoting := false
+	var b strings.Builder
+	for _, r := range str {
+		normal := false
+		switch r {
+		case ';', '"', '(', ')', '[', ']', '{', '}', '$', '|', '&', '>', '<', ' ', '#', '~', '*', '?', '=':
+			b.WriteRune(r)
+		case '\\', '\'':
+			b.WriteByte('\\')
+			b.WriteRune(r)
+		default:
+			b.WriteRune(r)
+			normal = true
+		}
+		if !normal {
+			needQuoting = true
+		}
+	}
+	// single quotes are used when the string contains any special characters
+	if needQuoting {
+		return fmt.Sprintf("'%s'", b.String())
+	}
+	return b.String()
+}
diff --git a/src/shell/init.go b/src/shell/init.go
index 01e96af8..566f616a 100644
--- a/src/shell/init.go
+++ b/src/shell/init.go
@@ -1,171 +1,40 @@
 package shell
 
 import (
-	_ "embed"
-	"path/filepath"
-	"strconv"
-
 	"fmt"
 	"os"
 	"strings"
+	"time"
 
-	"github.com/jandedobbeleer/oh-my-posh/src/color"
+	"github.com/jandedobbeleer/oh-my-posh/src/log"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
-	"github.com/jandedobbeleer/oh-my-posh/src/template"
-	"github.com/jandedobbeleer/oh-my-posh/src/upgrade"
 )
 
-//go:embed scripts/omp.ps1
-var pwshInit string
-
-//go:embed scripts/omp.fish
-var fishInit string
-
-//go:embed scripts/omp.bash
-var bashInit string
-
-//go:embed scripts/omp.zsh
-var zshInit string
-
-//go:embed scripts/omp.lua
-var cmdInit string
-
-//go:embed scripts/omp.nu
-var nuInit string
-
-//go:embed scripts/omp.tcsh
-var tcshInit string
-
-//go:embed scripts/omp.elv
-var elvishInit string
-
-//go:embed scripts/omp.py
-var xonshInit string
-
 const (
 	noExe = "echo \"Unable to find Oh My Posh executable\""
 )
 
-var (
-	Transient         bool
-	ErrorLine         bool
-	Tooltips          bool
-	ShellIntegration  bool
-	RPrompt           bool
-	CursorPositioning bool
-	PromptMark        bool
-	AutoUpgrade       bool
-)
-
 func getExecutablePath(env runtime.Environment) (string, error) {
 	executable, err := os.Executable()
 	if err != nil {
 		return "", err
 	}
+
 	if env.Flags().Strict {
 		return runtime.Base(env, executable), nil
 	}
+
 	// On Windows, it fails when the excutable is called in MSYS2 for example
 	// which uses unix style paths to resolve the executable's location.
 	// PowerShell knows how to resolve both, so we can swap this without any issue.
 	if env.GOOS() == runtime.WINDOWS {
 		executable = strings.ReplaceAll(executable, "\\", "/")
 	}
-	return executable, nil
-}
 
-func quotePwshStr(str string) string {
-	return fmt.Sprintf("'%s'", strings.ReplaceAll(str, "'", "''"))
-}
-
-func quotePosixStr(str string) string {
-	if len(str) == 0 {
-		return "''"
-	}
-
-	needQuoting := false
-	var b strings.Builder
-	for _, r := range str {
-		normal := false
-		switch r {
-		case '!', ';', '"', '(', ')', '[', ']', '{', '}', '$', '|', '&', '>', '<', '`', ' ', '#', '~', '*', '?', '=':
-			b.WriteRune(r)
-		case '\\', '\'':
-			b.WriteByte('\\')
-			b.WriteRune(r)
-		case '\a':
-			b.WriteString(`\a`)
-		case '\b':
-			b.WriteString(`\b`)
-		case '\f':
-			b.WriteString(`\f`)
-		case '\n':
-			b.WriteString(`\n`)
-		case '\r':
-			b.WriteString(`\r`)
-		case '\t':
-			b.WriteString(`\t`)
-		case '\v':
-			b.WriteString(`\v`)
-		default:
-			b.WriteRune(r)
-			normal = true
-		}
-		if !normal {
-			needQuoting = true
-		}
-	}
-	// the quoting form $'...' is used for a string contains any special characters
-	if needQuoting {
-		return fmt.Sprintf("$'%s'", b.String())
-	}
-	return b.String()
-}
-
-func quoteFishStr(str string) string {
-	if len(str) == 0 {
-		return "''"
-	}
-	needQuoting := false
-	var b strings.Builder
-	for _, r := range str {
-		normal := false
-		switch r {
-		case ';', '"', '(', ')', '[', ']', '{', '}', '$', '|', '&', '>', '<', ' ', '#', '~', '*', '?', '=':
-			b.WriteRune(r)
-		case '\\', '\'':
-			b.WriteByte('\\')
-			b.WriteRune(r)
-		default:
-			b.WriteRune(r)
-			normal = true
-		}
-		if !normal {
-			needQuoting = true
-		}
-	}
-	// single quotes are used when the string contains any special characters
-	if needQuoting {
-		return fmt.Sprintf("'%s'", b.String())
-	}
-	return b.String()
-}
-
-func quoteLuaStr(str string) string {
-	if len(str) == 0 {
-		return "''"
-	}
-	return fmt.Sprintf("'%s'", strings.NewReplacer(`\`, `\\`, `'`, `\'`).Replace(str))
-}
-
-func quoteNuStr(str string) string {
-	if len(str) == 0 {
-		return "''"
-	}
-	return fmt.Sprintf(`"%s"`, strings.NewReplacer(`\`, `\\`, `"`, `\"`).Replace(str))
+	return executable, nil
 }
 
-func Init(env runtime.Environment) string {
+func Init(env runtime.Environment, feats Features) string {
 	shell := env.Flags().Shell
 
 	switch shell {
@@ -197,44 +66,25 @@ func Init(env runtime.Environment) string {
 
 		return fmt.Sprintf(command, executable, shell, config, additionalParams)
 	case ZSH, BASH, FISH, CMD, TCSH, XONSH:
-		return PrintInit(env)
+		return PrintInit(env, feats, nil)
 	case NU:
-		createNuInit(env)
+		createNuInit(env, feats)
 		return ""
 	default:
 		return fmt.Sprintf("echo \"No initialization script available for %s\"", shell)
 	}
 }
 
-func PrintInit(env runtime.Environment) string {
+func PrintInit(env runtime.Environment, features Features, startTime *time.Time) string {
 	executable, err := getExecutablePath(env)
 	if err != nil {
 		return noExe
 	}
 
-	toggleSetting := func(setting bool) string {
-		if env.Flags().Manual {
-			return "false"
-		}
-
-		return strconv.FormatBool(setting)
-	}
-
-	promptMark := func() string {
-		if PromptMark {
-			return "iterm2_prompt_mark"
-		}
-
-		return ""
-	}
-
 	shell := env.Flags().Shell
 	configFile := env.Flags().Config
 
-	var (
-		script, notice string
-		hasNotice      bool
-	)
+	var script string
 
 	switch shell {
 	case PWSH, PWSH5:
@@ -273,57 +123,27 @@ func PrintInit(env runtime.Environment) string {
 		return fmt.Sprintf("echo \"No initialization script available for %s\"", shell)
 	}
 
-	// only run this for shells that support
-	// injecting the notice directly
-	if shell != PWSH && shell != PWSH5 {
-		notice, hasNotice = upgrade.Notice(env, false)
-	}
-
-	return strings.NewReplacer(
+	init := strings.NewReplacer(
 		"::OMP::", executable,
 		"::CONFIG::", configFile,
 		"::SHELL::", shell,
-		"::TRANSIENT::", toggleSetting(Transient),
-		"::ERROR_LINE::", toggleSetting(ErrorLine),
-		"::TOOLTIPS::", toggleSetting(Tooltips),
-		"::FTCS_MARKS::", toggleSetting(ShellIntegration),
-		"::RPROMPT::", strconv.FormatBool(RPrompt),
-		"::CURSOR::", strconv.FormatBool(CursorPositioning),
-		"::UPGRADE::", strconv.FormatBool(hasNotice),
-		"::UPGRADENOTICE::", notice,
-		"::AUTOUPGRADE::", strconv.FormatBool(AutoUpgrade),
-		"::PROMPT_MARK::", promptMark(),
 	).Replace(script)
-}
 
-func createNuInit(env runtime.Environment) {
-	initPath := filepath.Join(env.Home(), ".oh-my-posh.nu")
-	f, err := os.OpenFile(initPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
-	if err != nil {
-		return
-	}
-	_, err = f.WriteString(PrintInit(env))
-	if err != nil {
-		return
-	}
-	_ = f.Close()
-}
+	shellScript := features.Lines(shell).String(init)
 
-func ConsoleBackgroundColor(env runtime.Environment, backgroundColorTemplate color.Ansi) color.Ansi {
-	if backgroundColorTemplate.IsEmpty() {
-		return backgroundColorTemplate
+	if !env.Flags().Debug {
+		return shellScript
 	}
 
-	tmpl := &template.Text{
-		Template: string(backgroundColorTemplate),
-		Context:  nil,
-		Env:      env,
-	}
+	var builder strings.Builder
 
-	text, err := tmpl.Render()
-	if err != nil {
-		return color.Transparent
-	}
+	builder.WriteString(fmt.Sprintf("\n%s %s\n", log.Text("Init duration:").Green().Bold().Plain(), time.Since(*startTime)))
+
+	builder.WriteString(log.Text("\nScript:\n\n").Green().Bold().Plain().String())
+	builder.WriteString(shellScript)
+
+	builder.WriteString(log.Text("\n\nLogs:\n\n").Green().Bold().Plain().String())
+	builder.WriteString(env.Logs())
 
-	return color.Ansi(text)
+	return builder.String()
 }
diff --git a/src/shell/nu.go b/src/shell/nu.go
new file mode 100644
index 00000000..40a5191a
--- /dev/null
+++ b/src/shell/nu.go
@@ -0,0 +1,52 @@
+package shell
+
+import (
+	_ "embed"
+
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
+)
+
+//go:embed scripts/omp.nu
+var nuInit string
+
+func (f Feature) Nu() Code {
+	switch f {
+	case Transient:
+		return `$env.TRANSIENT_PROMPT_COMMAND = { ^$_omp_executable print transient $"--config=($env.POSH_THEME)" --shell=nu $"--shell-version=($env.POSH_SHELL_VERSION)" $"--execution-time=(posh_cmd_duration)" $"--status=($env.LAST_EXIT_CODE)" $"--terminal-width=(posh_width)" }` //nolint: lll
+	case Upgrade:
+		return "^$_omp_executable upgrade"
+	case Notice:
+		return "^$_omp_executable notice"
+	case PromptMark, RPrompt, PoshGit, Azure, LineError, Jobs, Tooltips, FTCSMarks, CursorPositioning:
+		fallthrough
+	default:
+		return ""
+	}
+}
+
+func quoteNuStr(str string) string {
+	if len(str) == 0 {
+		return "''"
+	}
+	return fmt.Sprintf(`"%s"`, strings.NewReplacer(`\`, `\\`, `"`, `\"`).Replace(str))
+}
+
+func createNuInit(env runtime.Environment, features Features) {
+	initPath := filepath.Join(env.Home(), ".oh-my-posh.nu")
+	f, err := os.OpenFile(initPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
+	if err != nil {
+		return
+	}
+
+	_, err = f.WriteString(PrintInit(env, features, nil))
+	if err != nil {
+		return
+	}
+
+	_ = f.Close()
+}
diff --git a/src/shell/pwsh.go b/src/shell/pwsh.go
new file mode 100644
index 00000000..6da220c1
--- /dev/null
+++ b/src/shell/pwsh.go
@@ -0,0 +1,42 @@
+package shell
+
+import (
+	_ "embed"
+
+	"fmt"
+	"strings"
+)
+
+//go:embed scripts/omp.ps1
+var pwshInit string
+
+func (f Feature) Pwsh() Code {
+	switch f {
+	case Tooltips:
+		return "Enable-PoshTooltips"
+	case LineError:
+		return "Enable-PoshLineError"
+	case Transient:
+		return "Enable-PoshTransientPrompt"
+	case Jobs:
+		return "$global:_ompJobCount = $true"
+	case Azure:
+		return "$global:_ompAzure = $true"
+	case PoshGit:
+		return "$global:_ompPoshGit = $true"
+	case FTCSMarks:
+		return "$global:_ompFTCSMarks = $true"
+	case Upgrade:
+		return "& $global:_ompExecutable upgrade"
+	case Notice:
+		return "& $global:_ompExecutable notice"
+	case PromptMark, RPrompt, CursorPositioning:
+		fallthrough
+	default:
+		return ""
+	}
+}
+
+func quotePwshStr(str string) string {
+	return fmt.Sprintf("'%s'", strings.ReplaceAll(str, "'", "''"))
+}
diff --git a/src/shell/scripts/omp.bash b/src/shell/scripts/omp.bash
index 70bc90b5..f3e4f1c5 100644
--- a/src/shell/scripts/omp.bash
+++ b/src/shell/scripts/omp.bash
@@ -3,23 +3,30 @@ export POSH_SHELL_VERSION=$BASH_VERSION
 export POWERLINE_COMMAND="oh-my-posh"
 export POSH_PID=$$
 export CONDA_PROMPT_MODIFIER=false
+export OSTYPE=$OSTYPE
 
+# global variables
 _omp_start_time=""
 _omp_stack_count=0
 _omp_elapsed=-1
 _omp_no_exit_code="true"
 _omp_status_cache=0
 _omp_pipestatus_cache=0
+_omp_executable=::OMP::
+
+# switches to enable/disable features
+_omp_cursor_positioning=0
+_omp_ftcs_marks=0
 
 # start timer on command start
 PS0='${_omp_start_time:0:$((_omp_start_time="$(_omp_start_timer)",0))}$(_omp_ftcs_command_start)'
 # set secondary prompt
-PS2="$(::OMP:: print secondary --config="$POSH_THEME" --shell=bash --shell-version="$BASH_VERSION")"
+PS2="$(${_omp_executable} print secondary --shell=bash --shell-version="$BASH_VERSION")"
 
 function _omp_set_cursor_position() {
     # not supported in Midnight Commander
     # see https://github.com/JanDeDobbeleer/oh-my-posh/issues/3415
-    if [[ "::CURSOR::" != "true" ]] || [[ -v MC_SID ]]; then
+    if [[ $_omp_cursor_positioning == 0 ]] || [[ -v MC_SID ]]; then
         return
     fi
 
@@ -37,11 +44,11 @@ function _omp_set_cursor_position() {
 }
 
 function _omp_start_timer() {
-    ::OMP:: get millis
+    $_omp_executable get millis
 }
 
 function _omp_ftcs_command_start() {
-    if [[ "::FTCS_MARKS::" == "true" ]]; then
+    if [[ $_omp_ftcs_marks == 0 ]]; then
         printf "\e]133;C\a"
     fi
 }
@@ -62,7 +69,7 @@ function _omp_hook() {
     _omp_stack_count=$((${#DIRSTACK[@]} - 1))
 
     if [[ "$_omp_start_time" ]]; then
-        local omp_now=$(::OMP:: get millis --shell=bash)
+        local omp_now=$(${_omp_executable} get millis --shell=bash)
         _omp_elapsed=$((omp_now - _omp_start_time))
         _omp_start_time=""
         _omp_no_exit_code="false"
@@ -75,19 +82,11 @@ function _omp_hook() {
     set_poshcontext
     _omp_set_cursor_position
 
-    PS1="$(::OMP:: print primary --config="$POSH_THEME" --shell=bash --shell-version="$BASH_VERSION" --status="$_omp_status_cache" --pipestatus="${_omp_pipestatus_cache[*]}" --execution-time="$_omp_elapsed" --stack-count="$_omp_stack_count" --no-status="$_omp_no_exit_code" --terminal-width="${COLUMNS-0}" | tr -d '\0')"
+    PS1="$(${_omp_executable} print primary --shell=bash --shell-version="$BASH_VERSION" --status="$_omp_status_cache" --pipestatus="${_omp_pipestatus_cache[*]}" --execution-time="$_omp_elapsed" --stack-count="$_omp_stack_count" --no-status="$_omp_no_exit_code" --terminal-width="${COLUMNS-0}" | tr -d '\0')"
 
     return $_omp_status_cache
 }
 
-if [[ "$TERM" != "linux" ]] && [[ -x "$(command -v ::OMP::)" ]] && ! [[ "$PROMPT_COMMAND" =~ "_omp_hook" ]]; then
+if [[ "$TERM" != "linux" ]] && [[ -x "$(command -v $_omp_executable)" ]] && ! [[ "$PROMPT_COMMAND" =~ "_omp_hook" ]]; then
     PROMPT_COMMAND="_omp_hook; $PROMPT_COMMAND"
 fi
-
-if [[ "::UPGRADE::" == "true" ]]; then
-    echo "::UPGRADENOTICE::"
-fi
-
-if [[ "::AUTOUPGRADE::" == "true" ]]; then
-    ::OMP:: upgrade
-fi
diff --git a/src/shell/scripts/omp.elv b/src/shell/scripts/omp.elv
index daed6292..f6b94bfa 100644
--- a/src/shell/scripts/omp.elv
+++ b/src/shell/scripts/omp.elv
@@ -4,6 +4,7 @@ set-env POSH_SHELL_VERSION (elvish --version)
 set-env POWERLINE_COMMAND 'oh-my-posh'
 
 var error-code = 0
+var _omp_executable = ::OMP::
 
 fn posh-after-command-hook {|m|
     var error = $m[error]
@@ -23,18 +24,10 @@ set edit:after-command = [ $@edit:after-command $posh-after-command-hook~ ]
 
 set edit:prompt = {
     var cmd-duration = (printf "%.0f" (* $edit:command-duration 1000))
-    ::OMP:: print primary --config=$E:POSH_THEME --shell=elvish --execution-time=$cmd-duration --status=$error-code --pwd=$pwd --shell-version=$E:POSH_SHELL_VERSION
+    $_omp_executable print primary --shell=elvish --execution-time=$cmd-duration --status=$error-code --pwd=$pwd --shell-version=$E:POSH_SHELL_VERSION
 }
 
 set edit:rprompt = {
     var cmd-duration = (printf "%.0f" (* $edit:command-duration 1000))
-    ::OMP:: print right --config=$E:POSH_THEME --shell=elvish --execution-time=$cmd-duration --status=$error-code --pwd=$pwd --shell-version=$E:POSH_SHELL_VERSION
-}
-
-if (eq '::UPGRADE::' 'true') {
-    echo '::UPGRADENOTICE::'
-}
-
-if (eq '::AUTOUPGRADE::' 'true') {
-    ::OMP:: upgrade
+    $_omp_executable print right --shell=elvish --execution-time=$cmd-duration --status=$error-code --pwd=$pwd --shell-version=$E:POSH_SHELL_VERSION
 }
diff --git a/src/shell/scripts/omp.fish b/src/shell/scripts/omp.fish
index 92b0f5b1..c7745414 100644
--- a/src/shell/scripts/omp.fish
+++ b/src/shell/scripts/omp.fish
@@ -1,12 +1,17 @@
-set --export POSH_THEME ::CONFIG::
-set --export POSH_SHELL_VERSION $FISH_VERSION
-set --global POWERLINE_COMMAND oh-my-posh
-set --global POSH_PID $fish_pid
-set --global CONDA_PROMPT_MODIFIER false
+set --global --export POSH_THEME ::CONFIG::
+set --global --export POSH_SHELL_VERSION $FISH_VERSION
+set --global --export POWERLINE_COMMAND oh-my-posh
+set --global --export POSH_PID $fish_pid
+set --global --export CONDA_PROMPT_MODIFIER false
 set --global _omp_tooltip_command ''
 set --global _omp_current_rprompt ''
 set --global _omp_transient false
 
+set --global _omp_executable ::OMP::
+set --global _omp_ftcs_marks 0
+set --global _omp_transient_prompt 0
+set --global _omp_prompt_mark 0
+
 # We use this to avoid unnecessary CLI calls for prompt repaint.
 set --global _omp_new_prompt true
 
@@ -25,7 +30,7 @@ function fish_prompt
     # see https://github.com/fish-shell/fish-shell/issues/8418
     printf \e\[0J
     if test "$_omp_transient" = true
-        ::OMP:: print transient --config $POSH_THEME --shell fish --status $_omp_status_cache --pipestatus="$_omp_pipestatus_cache" --execution-time $_omp_duration --stack-count $_omp_stack_count --shell-version $FISH_VERSION --no-status=$_omp_no_exit_code
+        $_omp_executable print transient --shell fish --status $_omp_status_cache --pipestatus="$_omp_pipestatus_cache" --execution-time $_omp_duration --stack-count $_omp_stack_count --shell-version $FISH_VERSION --no-status=$_omp_no_exit_code
         return
     end
     if test "$_omp_new_prompt" = false
@@ -67,10 +72,12 @@ function fish_prompt
         set omp_cleared true
     end
 
-    ::PROMPT_MARK::
+    if test $_omp_prompt_mark = 1
+        iterm2_prompt_mark
+    end
 
     # The prompt is saved for possible reuse, typically a repaint after clearing the screen buffer.
-    set --global _omp_current_prompt (::OMP:: print primary --config $POSH_THEME --shell fish --status $_omp_status_cache --pipestatus="$_omp_pipestatus_cache" --execution-time $_omp_duration --stack-count $_omp_stack_count --shell-version $FISH_VERSION --cleared=$omp_cleared --no-status=$_omp_no_exit_code | string collect)
+    set --global _omp_current_prompt ($_omp_executable print primary --shell fish --status $_omp_status_cache --pipestatus="$_omp_pipestatus_cache" --execution-time $_omp_duration --stack-count $_omp_stack_count --shell-version $FISH_VERSION --cleared=$omp_cleared --no-status=$_omp_no_exit_code | string collect)
     echo -n "$_omp_current_prompt"
 end
 
@@ -79,13 +86,16 @@ function fish_right_prompt
         set _omp_transient false
         return
     end
+
     # Repaint an existing right prompt.
     if test "$_omp_new_prompt" = false
         echo -n "$_omp_current_rprompt"
         return
     end
+
     set _omp_new_prompt false
-    set --global _omp_current_rprompt (::OMP:: print right --config $POSH_THEME --shell fish --status $_omp_status_cache --pipestatus="$_omp_pipestatus_cache" --execution-time $_omp_duration --stack-count $_omp_stack_count --shell-version $FISH_VERSION --no-status=$_omp_no_exit_code | string join '')
+    set --global _omp_current_rprompt ($_omp_executable print right --shell fish --status $_omp_status_cache --pipestatus="$_omp_pipestatus_cache" --execution-time $_omp_duration --stack-count $_omp_stack_count --shell-version $FISH_VERSION --no-status=$_omp_no_exit_code | string join '')
+
     echo -n "$_omp_current_rprompt"
 end
 
@@ -96,7 +106,7 @@ function _omp_postexec --on-event fish_postexec
 end
 
 function _omp_preexec --on-event fish_preexec
-    if test "::FTCS_MARKS::" = true
+    if test $_omp_ftcs_marks = 1
         echo -ne "\e]133;C\a"
     end
 end
@@ -107,16 +117,19 @@ if test -n (bind \r --user 2>/dev/null | string match -e _omp_enter_key_handler)
     bind -e \r -M insert
     bind -e \r -M visual
 end
+
 if test -n (bind \n --user 2>/dev/null | string match -e _omp_enter_key_handler)
     bind -e \n -M default
     bind -e \n -M insert
     bind -e \n -M visual
 end
+
 if test -n (bind \cc --user 2>/dev/null | string match -e _omp_ctrl_c_key_handler)
     bind -e \cc -M default
     bind -e \cc -M insert
     bind -e \cc -M visual
 end
+
 if test -n (bind \x20 --user 2>/dev/null | string match -e _omp_space_key_handler)
     bind -e \x20 -M default
     bind -e \x20 -M insert
@@ -127,23 +140,28 @@ end
 function _omp_space_key_handler
     commandline --function expand-abbr
     commandline --insert ' '
+
     # Get the first word of command line as tip.
     set --local tooltip_command (commandline --current-buffer | string trim -l | string split --allow-empty -f1 ' ' | string collect)
+
     # Ignore an empty/repeated tooltip command.
     if test -z "$tooltip_command" || test "$tooltip_command" = "$_omp_tooltip_command"
         return
     end
+
     set _omp_tooltip_command $tooltip_command
-    set --local tooltip_prompt (::OMP:: print tooltip --config $POSH_THEME --shell fish --status $_omp_status_cache --pipestatus="$_omp_pipestatus_cache" --execution-time $_omp_duration --stack-count $_omp_stack_count --shell-version $FISH_VERSION --command $_omp_tooltip_command --no-status=$_omp_no_exit_code | string join '')
+    set --local tooltip_prompt ($_omp_executable print tooltip --shell fish --status $_omp_status_cache --pipestatus="$_omp_pipestatus_cache" --execution-time $_omp_duration --stack-count $_omp_stack_count --shell-version $FISH_VERSION --command $_omp_tooltip_command --no-status=$_omp_no_exit_code | string join '')
+
     if test -z "$tooltip_prompt"
         return
     end
+
     # Save the tooltip prompt to avoid unnecessary CLI calls.
     set _omp_current_rprompt $tooltip_prompt
     commandline --function repaint
 end
 
-if test "::TOOLTIPS::" = true
+function enable_poshtooltips
     bind \x20 _omp_space_key_handler -M default
     bind \x20 _omp_space_key_handler -M insert
 end
@@ -155,14 +173,17 @@ function _omp_enter_key_handler
         commandline --function accept-autosuggestion
         return
     end
+
     if commandline --is-valid || test -z (commandline --current-buffer | string trim -l | string collect)
         set _omp_new_prompt true
         set _omp_tooltip_command ''
-        if test "::TRANSIENT::" = true
+
+        if test $_omp_transient_prompt = 1
             set _omp_transient true
             commandline --function repaint
         end
     end
+
     commandline --function execute
 end
 
@@ -170,13 +191,16 @@ function _omp_ctrl_c_key_handler
     if test -z (commandline --current-buffer | string collect)
         return
     end
+
     # Render a transient prompt on Ctrl-C with non-empty command line buffer.
     set _omp_new_prompt true
     set _omp_tooltip_command ''
-    if test "::TRANSIENT::" = true
+
+    if test $_omp_transient_prompt = 1
         set _omp_transient true
         commandline --function repaint
     end
+
     commandline --function cancel-commandline
     commandline --function repaint
 end
@@ -192,17 +216,6 @@ bind \cc _omp_ctrl_c_key_handler -M insert
 bind \cc _omp_ctrl_c_key_handler -M visual
 
 # legacy functions
-function enable_poshtooltips
-    return
-end
 function enable_poshtransientprompt
     return
 end
-
-if test "::UPGRADE::" = true
-    echo "::UPGRADENOTICE::"
-end
-
-if test "::AUTOUPGRADE::" = true
-    ::OMP:: upgrade
-end
diff --git a/src/shell/scripts/omp.lua b/src/shell/scripts/omp.lua
index 4de0af3d..3cbea2f3 100644
--- a/src/shell/scripts/omp.lua
+++ b/src/shell/scripts/omp.lua
@@ -39,8 +39,8 @@ end
 
 local endedit_time = 0
 local last_duration = 0
-local tooltips_enabled = ::TOOLTIPS::
-local rprompt_enabled = ::RPROMPT::
+local rprompt_enabled = false
+local transient_enabled = false
 local no_exit_code = true
 
 local cached_prompt = {}
@@ -74,10 +74,6 @@ local function omp_exe()
     return '"'..::OMP::..'"'
 end
 
-local function omp_config()
-    return '"'..::CONFIG::..'"'
-end
-
 os.setenv("POSH_THEME", ::CONFIG::)
 os.setenv("POSH_SHELL_VERSION", string.format('clink v%s.%s.%s.%s', clink.version_major, clink.version_minor, clink.version_patch, clink.version_commit))
 
@@ -162,7 +158,7 @@ local function get_posh_prompt(rprompt)
     if rprompt then
         prompt = "right"
     end
-    local prompt_exe = string.format('%s print %s --shell=cmd --config=%s %s %s %s', omp_exe(), prompt, omp_config(), execution_time_option(), error_level_option(), no_exit_code_option())
+    local prompt_exe = string.format('%s print %s --shell=cmd %s %s %s', omp_exe(), prompt, execution_time_option(), error_level_option(), no_exit_code_option())
     return run_posh_command(prompt_exe)
 end
 
@@ -171,7 +167,7 @@ local function set_posh_tooltip(tip_command)
         -- Escape special characters properly, if any.
         local escaped_tip_command = string.gsub(tip_command, '(\\+)"', '%1%1"'):gsub('(\\+)$', '%1%1'):gsub('"', '\\"'):gsub('([&<>%(%)@%^|])', '^%1')
 
-        local prompt_exe = string.format('%s print tooltip --shell=cmd %s --config=%s --command="%s"', omp_exe(), error_level_option(), omp_config(), escaped_tip_command)
+        local prompt_exe = string.format('%s print tooltip --shell=cmd %s --command="%s"', omp_exe(), error_level_option(), escaped_tip_command)
         local tooltip = run_posh_command(prompt_exe)
         -- Do not cache an empty tooltip.
         if tooltip == "" then
@@ -260,20 +256,29 @@ function p:filter(prompt)
 
     return cached_prompt.left
 end
+
 function p:rightfilter(prompt)
     -- Return cached tooltip if available, otherwise return cached rprompt.
     -- Returning false as the second return value halts further prompt
     -- filtering, to keep other things from overriding what we generated.
     return (cached_prompt.tooltip or cached_prompt.right), false
 end
+
 function p:transientfilter(prompt)
-    local prompt_exe = string.format('%s print transient --shell=cmd --config=%s %s %s', omp_exe(), omp_config(), error_level_option(), no_exit_code_option())
+    if not transient_enabled then
+        return nil
+    end
+
+    local prompt_exe = string.format('%s print transient --shell=cmd %s %s', omp_exe(), error_level_option(), no_exit_code_option())
     prompt = run_posh_command(prompt_exe)
+
     if prompt == "" then
         prompt = nil
     end
+
     return prompt
 end
+
 function p:transientrightfilter(prompt)
     return "", false
 end
@@ -323,11 +328,10 @@ function ohmyposh_space(rl_buffer)
     end
 end
 
-if tooltips_enabled and rl.setbinding then
-    rl.setbinding(' ', [["luafunc:ohmyposh_space"]], 'emacs')
-end
+local function enable_tooltips()
+    if not rl.setbinding then
+        return
+    end
 
-if '::AUTOUPGRADE::' == 'true' then
-    local prompt_exe = string.format('%s upgrade', omp_exe())
-    os.execute(prompt_exe)
+    rl.setbinding(' ', [["luafunc:ohmyposh_space"]], 'emacs')
 end
diff --git a/src/shell/scripts/omp.nu b/src/shell/scripts/omp.nu
index b9a043d2..963ce897 100644
--- a/src/shell/scripts/omp.nu
+++ b/src/shell/scripts/omp.nu
@@ -9,6 +9,8 @@ $env.PROMPT_INDICATOR = ""
 $env.POSH_PID = (random uuid)
 $env.POSH_SHELL_VERSION = (version | get version)
 
+let _omp_executable: string = ::OMP::
+
 def posh_cmd_duration [] {
     # We have to do this because the initial value of `$env.CMD_DURATION_MS` is always `0823`,
     # which is an official setting.
@@ -21,7 +23,7 @@ def posh_width [] {
 }
 
 # PROMPTS
-$env.PROMPT_MULTILINE_INDICATOR = (^::OMP:: print secondary $"--config=($env.POSH_THEME)" --shell=nu $"--shell-version=($env.POSH_SHELL_VERSION)")
+$env.PROMPT_MULTILINE_INDICATOR = (^$_omp_executable print secondary --shell=nu $"--shell-version=($env.POSH_SHELL_VERSION)")
 
 $env.PROMPT_COMMAND = { ||
     # hack to set the cursor line to 1 when the user clears the screen
@@ -35,23 +37,9 @@ $env.PROMPT_COMMAND = { ||
         do --env $env.SET_POSHCONTEXT
     }
 
-    ^::OMP:: print primary $"--config=($env.POSH_THEME)" --shell=nu $"--shell-version=($env.POSH_SHELL_VERSION)" $"--execution-time=(posh_cmd_duration)" $"--status=($env.LAST_EXIT_CODE)" $"--terminal-width=(posh_width)" $"--cleared=($clear)"
+    ^$_omp_executable print primary --shell=nu $"--shell-version=($env.POSH_SHELL_VERSION)" $"--execution-time=(posh_cmd_duration)" $"--status=($env.LAST_EXIT_CODE)" $"--terminal-width=(posh_width)" $"--cleared=($clear)"
 }
 
 $env.PROMPT_COMMAND_RIGHT = { ||
-    ^::OMP:: print right $"--config=($env.POSH_THEME)" --shell=nu $"--shell-version=($env.POSH_SHELL_VERSION)" $"--execution-time=(posh_cmd_duration)" $"--status=($env.LAST_EXIT_CODE)" $"--terminal-width=(posh_width)"
-}
-
-if "::TRANSIENT::" == "true" {
-    $env.TRANSIENT_PROMPT_COMMAND = { ||
-        ^::OMP:: print transient $"--config=($env.POSH_THEME)" --shell=nu $"--shell-version=($env.POSH_SHELL_VERSION)" $"--execution-time=(posh_cmd_duration)" $"--status=($env.LAST_EXIT_CODE)" $"--terminal-width=(posh_width)"
-    }
-}
-
-if "::UPGRADE::" == "true" {
-    echo "::UPGRADENOTICE::"
-}
-
-if "::AUTOUPGRADE::" == "true" {
-    ^::OMP:: upgrade
+    ^$_omp_executable print right --shell=nu $"--shell-version=($env.POSH_SHELL_VERSION)" $"--execution-time=(posh_cmd_duration)" $"--status=($env.LAST_EXIT_CODE)" $"--terminal-width=(posh_width)"
 }
diff --git a/src/shell/scripts/omp.ps1 b/src/shell/scripts/omp.ps1
index ff6796f2..05c5be45 100644
--- a/src/shell/scripts/omp.ps1
+++ b/src/shell/scripts/omp.ps1
@@ -13,6 +13,13 @@ function global:Get-PoshStackCount {
     return 0
 }
 
+# global enablers
+$global:_ompJobCount = $false
+$global:_ompFTCSMarks = $false
+$global:_ompPoshGit = $false
+$global:_ompAzure = $false
+$global:_ompExecutable = ::OMP::
+
 New-Module -Name "oh-my-posh-core" -ScriptBlock {
     # Check `ConstrainedLanguage` mode.
     $script:ConstrainedLanguageMode = $ExecutionContext.SessionState.LanguageMode -eq "ConstrainedLanguage"
@@ -25,11 +32,12 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
     $script:NoExitCode = $true
     $script:ErrorCode = 0
     $script:ExecutionTime = 0
-    $script:OMPExecutable = ::OMP::
     $script:ShellName = "::SHELL::"
     $script:PSVersion = $PSVersionTable.PSVersion.ToString()
     $script:TransientPrompt = $false
     $script:TooltipCommand = ''
+    $script:JobCount = 0
+
     $env:POWERLINE_COMMAND = "oh-my-posh"
     $env:POSH_SHELL_VERSION = $script:PSVersion
     $env:POSH_PID = $PID
@@ -40,25 +48,6 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
         $env:POSH_THEME = (Resolve-Path -Path ::CONFIG::).ProviderPath
     }
 
-    # specific module support (disabled by default)
-    if (($null -eq $env:POSH_GIT_ENABLED) -or ($null -eq (Get-Module 'posh-git'))) {
-        $env:POSH_GIT_ENABLED = $false
-    }
-    if (($null -eq $env:POSH_AZURE_ENABLED) -or ($null -eq (Get-Module 'az'))) {
-        $env:POSH_AZURE_ENABLED = $false
-    }
-
-    function Initialize-ModuleSupport {
-        if ($env:POSH_GIT_ENABLED -eq $true) {
-            # We need to set the status so posh-git can facilitate autocomplete
-            $global:GitStatus = Get-GitStatus
-            $env:POSH_GIT_STATUS = $global:GitStatus | ConvertTo-Json
-        }
-        if ($env:POSH_AZURE_ENABLED -eq $true) {
-            $env:POSH_AZURE_SUBSCRIPTION = Get-AzContext | ConvertTo-Json
-        }
-    }
-
     function Start-Utf8Process {
         param(
             [string]$FileName,
@@ -94,6 +83,7 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
             }
             $StartInfo.Arguments = $escapedArgs -join ' '
         }
+
         $StartInfo.StandardErrorEncoding = $StartInfo.StandardOutputEncoding = [System.Text.Encoding]::UTF8
         $StartInfo.RedirectStandardError = $StartInfo.RedirectStandardInput = $StartInfo.RedirectStandardOutput = $true
         $StartInfo.UseShellExecute = $false
@@ -106,6 +96,7 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
             }
             $StartInfo.WorkingDirectory = $PWD.ProviderPath
         }
+
         $StartInfo.CreateNoWindow = $true
         [void]$Process.Start()
 
@@ -118,6 +109,7 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
         if ($stderr) {
             $Host.UI.WriteErrorLine($stderr)
         }
+
         $stdoutTask.Result
     }
 
@@ -140,68 +132,61 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
         $terminalWidth
     }
 
-    if (!$script:ConstrainedLanguageMode) {
-        if ('::TOOLTIPS::' -eq 'true') {
-            Set-PSReadLineKeyHandler -Key Spacebar -BriefDescription 'OhMyPoshSpaceKeyHandler' -ScriptBlock {
-                param([ConsoleKeyInfo]$key)
-                [Microsoft.PowerShell.PSConsoleReadLine]::SelfInsert($key)
-                try {
-                    $command = ''
-                    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$command, [ref]$null)
-                    # Get the first word of command line as tip.
-                    $command = $command.TrimStart().Split(' ', 2) | Select-Object -First 1
-                    # Ignore an empty/repeated tooltip command.
-                    if (!$command -or ($command -eq $script:TooltipCommand)) {
-                        return
-                    }
-
-                    $script:TooltipCommand = $command
-                    $column = $Host.UI.RawUI.CursorPosition.X
-                    $terminalWidth = Get-TerminalWidth
-                    $cleanPSWD = Get-CleanPSWD
-                    $stackCount = global:Get-PoshStackCount
-
-                    $arguments = @(
-                        "print"
-                        "tooltip"
-                        "--status=$script:ErrorCode"
-                        "--shell=$script:ShellName"
-                        "--pswd=$cleanPSWD"
-                        "--execution-time=$script:ExecutionTime"
-                        "--stack-count=$stackCount"
-                        "--config=$env:POSH_THEME"
-                        "--command=$command"
-                        "--shell-version=$script:PSVersion"
-                        "--column=$column"
-                        "--terminal-width=$terminalWidth"
-                        "--no-status=$script:NoExitCode"
-                    )
-
-                    $standardOut = (Start-Utf8Process $script:OMPExecutable $arguments) -join ''
-                    if (!$standardOut) {
-                        return
-                    }
-
-                    Write-Host $standardOut -NoNewline
-
-                    # Workaround to prevent the text after cursor from disappearing when the tooltip is printed.
-                    [Microsoft.PowerShell.PSConsoleReadLine]::Insert(' ')
-                    [Microsoft.PowerShell.PSConsoleReadLine]::Undo()
-                }
-                finally {}
-            }
+    function Enable-PoshTooltips {
+        if ($script:ConstrainedLanguageMode) {
+            return
         }
 
-        function Set-TransientPrompt {
-            $previousOutputEncoding = [Console]::OutputEncoding
+        Set-PSReadLineKeyHandler -Key Spacebar -BriefDescription 'OhMyPoshSpaceKeyHandler' -ScriptBlock {
+            param([ConsoleKeyInfo]$key)
+            [Microsoft.PowerShell.PSConsoleReadLine]::SelfInsert($key)
             try {
-                $script:TransientPrompt = $true
-                [Console]::OutputEncoding = [Text.Encoding]::UTF8
-                [Microsoft.PowerShell.PSConsoleReadLine]::InvokePrompt()
-            }
-            finally {
-                [Console]::OutputEncoding = $previousOutputEncoding
+                $command = ''
+                [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$command, [ref]$null)
+                # Get the first word of command line as tip.
+                $command = $command.TrimStart().Split(' ', 2) | Select-Object -First 1
+
+                # Ignore an empty/repeated tooltip command.
+                if (!$command -or ($command -eq $script:TooltipCommand)) {
+                    return
+                }
+
+                $script:TooltipCommand = $command
+                $column = $Host.UI.RawUI.CursorPosition.X
+                $terminalWidth = Get-TerminalWidth
+                $cleanPSWD = Get-CleanPSWD
+                $stackCount = global:Get-PoshStackCount
+
+                $standardOut = (Start-Utf8Process $global:_ompExecutable @("print", "tooltip", "--status=$script:ErrorCode", "--shell=$script:ShellName", "--pswd=$cleanPSWD", "--execution-time=$script:ExecutionTime", "--stack-count=$stackCount", "--command=$command", "--shell-version=$script:PSVersion", "--column=$column", "--terminal-width=$terminalWidth", "--no-status=$script:NoExitCode", "--job-count=$script:JobCount")) -join ''
+                if (!$standardOut) {
+                    return
+                }
+
+                Write-Host $standardOut -NoNewline
+
+                # Workaround to prevent the text after cursor from disappearing when the tooltip is printed.
+                [Microsoft.PowerShell.PSConsoleReadLine]::Insert(' ')
+                [Microsoft.PowerShell.PSConsoleReadLine]::Undo()
             }
+            finally {}
+        }
+    }
+
+    function Set-TransientPrompt {
+        $previousOutputEncoding = [Console]::OutputEncoding
+        try {
+            $script:TransientPrompt = $true
+            [Console]::OutputEncoding = [Text.Encoding]::UTF8
+            [Microsoft.PowerShell.PSConsoleReadLine]::InvokePrompt()
+        }
+        finally {
+            [Console]::OutputEncoding = $previousOutputEncoding
+        }
+    }
+
+    function Enable-PoshTransientPrompt {
+        if ($script:ConstrainedLanguageMode) {
+            return
         }
 
         Set-PSReadLineKeyHandler -Key Enter -BriefDescription 'OhMyPoshEnterKeyHandler' -ScriptBlock {
@@ -212,19 +197,18 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
                 if ($executingCommand) {
                     $script:newPrompt = $true
                     $script:TooltipCommand = ''
-                    if ('::TRANSIENT::' -eq 'true') {
-                        Set-TransientPrompt
-                    }
+                    Set-TransientPrompt
                 }
             }
             finally {
                 [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
-                if (('::FTCS_MARKS::' -eq 'true') -and $executingCommand) {
+                if ($global:_ompFTCSMarks -and $executingCommand) {
                     # Write FTCS_COMMAND_EXECUTED after accepting the input - it should still happen before execution
                     Write-Host "$([char]0x1b)]133;C`a" -NoNewline
                 }
             }
         }
+
         Set-PSReadLineKeyHandler -Key Ctrl+c -BriefDescription 'OhMyPoshCtrlCKeyHandler' -ScriptBlock {
             try {
                 $start = $null
@@ -233,9 +217,7 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
                 if ($start -eq -1) {
                     $script:newPrompt = $true
                     $script:TooltipCommand = ''
-                    if ('::TRANSIENT::' -eq 'true') {
-                        Set-TransientPrompt
-                    }
+                    Set-TransientPrompt
                 }
             }
             finally {
@@ -244,9 +226,9 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
         }
     }
 
-    if ("::ERROR_LINE::" -eq "true") {
-        $validLine = (Start-Utf8Process $script:OMPExecutable @("print", "valid", "--config=$env:POSH_THEME", "--shell=$script:ShellName")) -join "`n"
-        $errorLine = (Start-Utf8Process $script:OMPExecutable @("print", "error", "--config=$env:POSH_THEME", "--shell=$script:ShellName")) -join "`n"
+    function Enable-PoshLineError {
+        $validLine = (Start-Utf8Process $global:_ompExecutable @("print", "valid", "--shell=$script:ShellName")) -join "`n"
+        $errorLine = (Start-Utf8Process $global:_ompExecutable @("print", "error", "--shell=$script:ShellName")) -join "`n"
         Set-PSReadLineOption -PromptText $validLine, $errorLine
     }
 
@@ -285,7 +267,7 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
             $Format = 'json'
         )
 
-        $configString = Start-Utf8Process $script:OMPExecutable @("config", "export", "--config=$env:POSH_THEME", "--format=$Format")
+        $configString = Start-Utf8Process $global:_ompExecutable @("config", "export", "--format=$Format")
         # if no path, copy to clipboard by default
         if ($FilePath) {
             # https://stackoverflow.com/questions/3038337/powershell-resolve-path-that-might-not-exist
@@ -305,15 +287,18 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
             [Parameter(ValuefromPipeline = $True)]
             [string]$name
         )
+
         $esc = [char]27
         if (!$name) {
             # if name not set, uri is used as the name of the hyperlink
             $name = $uri
         }
+
         if ($null -ne $env:WSL_DISTRO_NAME) {
             # wsl conversion if needed
             $uri = &wslpath -m $uri
         }
+
         # return an ANSI formatted hyperlink
         return "$esc]8;;file://$uri$esc\$name$esc]8;;$esc\"
     }
@@ -353,7 +338,7 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
             $cleanPSWD = Get-CleanPSWD
             $themes | ForEach-Object -Process {
                 Write-Host "Theme: $(Get-FileHyperlink -uri $_.FullName -Name ($_.BaseName -replace '\.omp$', ''))`n"
-                Start-Utf8Process $script:OMPExecutable @("print", "primary", "--config=$($_.FullName)", "--pswd=$cleanPSWD", "--shell=$script:ShellName")
+                Start-Utf8Process $global:_ompExecutable @("print", "primary", "--config=$($_.FullName)", "--pswd=$cleanPSWD", "--shell=$script:ShellName")
                 Write-Host "`n"
             }
         }
@@ -374,6 +359,7 @@ Example:
             $script:TransientPrompt = $false
             return
         }
+
         # for details about the trick to detect a debugging context, see these comments:
         # 1) https://github.com/JanDeDobbeleer/oh-my-posh/issues/2483#issuecomment-1175761456
         # 2) https://github.com/JanDeDobbeleer/oh-my-posh/issues/2502#issuecomment-1179968052
@@ -382,18 +368,35 @@ Example:
             $script:PromptType = "debug"
             return
         }
+
         $script:PromptType = "primary"
-        Initialize-ModuleSupport
+
+        if ($global:_ompJobCount) {
+            $script:JobCount = (Get-Job -State Running).Count
+        }
+
+        if ($global:_ompAzure) {
+            $env:POSH_AZURE_SUBSCRIPTION = Get-AzContext | ConvertTo-Json
+        }
+
+        if ($global:_ompPoshGit) {
+            $global:GitStatus = Get-GitStatus
+            $env:POSH_GIT_STATUS = $global:GitStatus | ConvertTo-Json
+        }
+
+        Set-PoshContext
     }
 
     function Update-PoshErrorCode {
         $lastHistory = Get-History -ErrorAction Ignore -Count 1
+
         # error code should be updated only when a non-empty command is run
         if (($null -eq $lastHistory) -or ($script:LastHistoryId -eq $lastHistory.Id)) {
             $script:ExecutionTime = 0
             $script:NoExitCode = $true
             return
         }
+
         $script:NoExitCode = $false
         $script:LastHistoryId = $lastHistory.Id
         $script:ExecutionTime = ($lastHistory.EndExecutionTime - $lastHistory.StartExecutionTime).TotalMilliseconds
@@ -401,16 +404,19 @@ Example:
             $script:ErrorCode = 0
             return
         }
+
         $invocationInfo = try {
             # retrieve info of the most recent error
             $global:Error[0] | Where-Object { $_ -ne $null } | Select-Object -ExpandProperty InvocationInfo
         }
         catch { $null }
+
         # check if the last command caused the last error
         if ($null -ne $invocationInfo -and $lastHistory.CommandLine -eq $invocationInfo.Line) {
             $script:ErrorCode = 1
             return
         }
+
         if ($script:OriginalLastExitCode -is [int] -and $script:OriginalLastExitCode -ne 0) {
             # native app exit code
             $script:ErrorCode = $script:OriginalLastExitCode
@@ -432,35 +438,19 @@ Example:
 
         Set-PoshPromptType
 
-
         if ($script:PromptType -ne 'transient') {
             Update-PoshErrorCode
         }
 
         $cleanPSWD = Get-CleanPSWD
         $stackCount = global:Get-PoshStackCount
-        Set-PoshContext
         $terminalWidth = Get-TerminalWidth
 
         # set the cursor positions, they are zero based so align with other platforms
         $env:POSH_CURSOR_LINE = $Host.UI.RawUI.CursorPosition.Y + 1
         $env:POSH_CURSOR_COLUMN = $Host.UI.RawUI.CursorPosition.X + 1
 
-        $arguments = @(
-            "print"
-            $script:PromptType
-            "--status=$script:ErrorCode"
-            "--pswd=$cleanPSWD"
-            "--execution-time=$script:ExecutionTime"
-            "--stack-count=$stackCount"
-            "--config=$env:POSH_THEME"
-            "--shell-version=$script:PSVersion"
-            "--terminal-width=$terminalWidth"
-            "--shell=$script:ShellName"
-            "--no-status=$script:NoExitCode"
-        )
-
-        $standardOut = Start-Utf8Process $script:OMPExecutable $arguments
+        $standardOut = Start-Utf8Process $global:_ompExecutable @("print", $script:PromptType, "--status=$script:ErrorCode", "--pswd=$cleanPSWD", "--execution-time=$script:ExecutionTime", "--stack-count=$stackCount", "--shell-version=$script:PSVersion", "--terminal-width=$terminalWidth", "--shell=$script:ShellName", "--no-status=$script:NoExitCode", "--job-count=$script:JobCount")
         # make sure PSReadLine knows if we have a multiline prompt
         Set-PSReadLineOption -ExtraPromptLineCount (($standardOut | Measure-Object -Line).Lines - 1)
 
@@ -477,33 +467,32 @@ Example:
     $Function:prompt = $promptFunction
 
     # set secondary prompt
-    Set-PSReadLineOption -ContinuationPrompt ((Start-Utf8Process $script:OMPExecutable @("print", "secondary", "--config=$env:POSH_THEME", "--shell=$script:ShellName")) -join "`n")
-
-    # legacy functions
-    function Enable-PoshTooltips {}
-    function Enable-PoshTransientPrompt {}
-    function Enable-PoshLineError {}
+    Set-PSReadLineOption -ContinuationPrompt ((Start-Utf8Process $global:_ompExecutable @("print", "secondary", "--shell=$script:ShellName")) -join "`n")
 
     # perform cleanup on removal so a new initialization in current session works
     if (!$script:ConstrainedLanguageMode) {
         $ExecutionContext.SessionState.Module.OnRemove += {
             Remove-Item Function:Get-PoshStackCount
             $Function:prompt = $script:OriginalPromptFunction
+
             (Get-PSReadLineOption).ContinuationPrompt = $script:OriginalContinuationPrompt
             (Get-PSReadLineOption).PromptText = $script:OriginalPromptText
+
             if ((Get-PSReadLineKeyHandler Spacebar).Function -eq 'OhMyPoshSpaceKeyHandler') {
                 Remove-PSReadLineKeyHandler Spacebar
             }
+
             if ((Get-PSReadLineKeyHandler Enter).Function -eq 'OhMyPoshEnterKeyHandler') {
                 Set-PSReadLineKeyHandler Enter -Function AcceptLine
             }
+
             if ((Get-PSReadLineKeyHandler Ctrl+c).Function -eq 'OhMyPoshCtrlCKeyHandler') {
                 Set-PSReadLineKeyHandler Ctrl+c -Function CopyOrCancelLine
             }
         }
     }
 
-    $notice = Start-Utf8Process $script:OMPExecutable @("notice")
+    $notice = Start-Utf8Process $global:_ompExecutable @("notice")
     if ($notice) {
         Write-Host $notice -NoNewline
     }
@@ -519,8 +508,3 @@ Example:
         "prompt"
     )
 } | Import-Module -Global
-
-
-if ("::AUTOUPGRADE::" -eq "true") {
-    & ::OMP:: upgrade
-}
diff --git a/src/shell/scripts/omp.py b/src/shell/scripts/omp.py
index 4c33f3e5..3cc52837 100644
--- a/src/shell/scripts/omp.py
+++ b/src/shell/scripts/omp.py
@@ -4,6 +4,7 @@ $POWERLINE_COMMAND = "oh-my-posh"
 $POSH_THEME = r"::CONFIG::"
 $POSH_PID = uuid.uuid4().hex
 $POSH_SHELL_VERSION = $XONSH_VERSION
+$POSH_EXECUTABLE = r"::OMP::"
 
 def get_command_context():
     last_cmd = __xonsh__.history[-1] if __xonsh__.history else None
@@ -13,20 +14,12 @@ def get_command_context():
 
 def posh_primary():
     status, duration = get_command_context()
-    return $(::OMP:: print primary --config=@($POSH_THEME) --shell=xonsh --status=@(status) --execution-time=@(duration) --shell-version=@($POSH_SHELL_VERSION))
+    return $(@($POSH_EXECUTABLE) print primary --shell=xonsh --status=@(status) --execution-time=@(duration) --shell-version=@($POSH_SHELL_VERSION))
 
 def posh_right():
     status, duration = get_command_context()
-    return $(::OMP:: print right --config=@($POSH_THEME) --shell=xonsh --status=@(status) --execution-time=@(duration) --shell-version=@($POSH_SHELL_VERSION))
+    return $(@($POSH_EXECUTABLE) print right --shell=xonsh --status=@(status) --execution-time=@(duration) --shell-version=@($POSH_SHELL_VERSION))
 
 
 $PROMPT = posh_primary
 $RIGHT_PROMPT = posh_right
-
-notice = """::UPGRADENOTICE::"""
-
-if "::UPGRADE::" == "true":
-  print(notice)
-
-if "::AUTOUPGRADE::" == "true":
-  ::OMP:: upgrade
diff --git a/src/shell/scripts/omp.tcsh b/src/shell/scripts/omp.tcsh
index 881c22a6..ebc466fb 100644
--- a/src/shell/scripts/omp.tcsh
+++ b/src/shell/scripts/omp.tcsh
@@ -1,15 +1,15 @@
 setenv POWERLINE_COMMAND "oh-my-posh";
-setenv POSH_THEME "::CONFIG::";
+setenv POSH_THEME ::CONFIG::;
 setenv POSH_SHELL_VERSION "";
+setenv POSH_PID $$;
 
+set POSH_COMMAND = ::OMP::;
 set USER_PRECMD = "`alias precmd`";
 set USER_POSTCMD = "`alias postcmd`";
-set POSH_PRECMD = 'set POSH_CMD_STATUS = $status;set POSH_PATH = ::OMP::;set POSH_END_TIME = `$POSH_PATH get millis`;set POSH_DURATION = 0;if ( $POSH_START_TIME != -1 ) @ POSH_DURATION = $POSH_END_TIME - $POSH_START_TIME;set prompt = "`$POSH_PATH print primary --shell=tcsh --config=$POSH_THEME --status=$POSH_CMD_STATUS --execution-time=$POSH_DURATION`";set POSH_START_TIME = -1';
-set POSH_POSTCMD = 'set POSH_START_TIME = `::OMP:: get millis`';
+set POSH_PRECMD = 'set POSH_CMD_STATUS = $status;set POSH_END_TIME = `$POSH_COMMAND get millis`;set POSH_DURATION = 0;if ( $POSH_START_TIME != -1 ) @ POSH_DURATION = $POSH_END_TIME - $POSH_START_TIME;set prompt = "`$POSH_COMMAND print primary --shell=tcsh --status=$POSH_CMD_STATUS --execution-time=$POSH_DURATION`";set POSH_START_TIME = -1';
+set POSH_POSTCMD = 'set POSH_START_TIME = `$POSH_COMMAND get millis`';
+
 alias precmd "$POSH_PRECMD;$USER_PRECMD";
 alias postcmd "$POSH_POSTCMD;$USER_POSTCMD";
-set POSH_START_TIME = `::OMP:: get millis`;
-
-if ("::UPGRADE::" == "true") echo "::UPGRADENOTICE::";
 
-if ("::AUTOUPGRADE::" == "true") ::OMP:: upgrade;
+set POSH_START_TIME = `$POSH_COMMAND get millis`;
diff --git a/src/shell/scripts/omp.zsh b/src/shell/scripts/omp.zsh
index d883ca17..cd720465 100644
--- a/src/shell/scripts/omp.zsh
+++ b/src/shell/scripts/omp.zsh
@@ -6,13 +6,19 @@ export CONDA_PROMPT_MODIFIER=false
 export POSH_PROMPT_COUNT=0
 export ZLE_RPROMPT_INDENT=0
 
+_omp_executable=::OMP::
+
+# switches to enable/disable features
+_omp_cursor_positioning=0
+_omp_ftcs_marks=0
+
 # set secondary prompt
-PS2="$(::OMP:: print secondary --config="$POSH_THEME" --shell=zsh)"
+PS2="$(${_omp_executable} print secondary --shell=zsh)"
 
 function _omp_set_cursor_position() {
   # not supported in Midnight Commander
   # see https://github.com/JanDeDobbeleer/oh-my-posh/issues/3415
-  if [[ "::CURSOR::" != "true" ]] || [[ -v MC_SID ]]; then
+  if [[ $_omp_cursor_positioning == 0 ]] || [[ -v MC_SID ]]; then
     return
   fi
 
@@ -37,11 +43,11 @@ function set_poshcontext() {
 }
 
 function _omp_preexec() {
-  if [[ "::FTCS_MARKS::" = "true" ]]; then
+  if [[ $_omp_ftcs_marks == 0 ]]; then
     printf "\033]133;C\007"
   fi
 
-  _omp_start_time=$(::OMP:: get millis)
+  _omp_start_time=$(${_omp_executable} get millis)
 }
 
 function _omp_precmd() {
@@ -52,7 +58,7 @@ function _omp_precmd() {
   _omp_no_exit_code="true"
 
   if [ $_omp_start_time ]; then
-    local omp_now=$(::OMP:: get millis --shell=zsh)
+    local omp_now=$(${_omp_executable} get millis --shell=zsh)
     _omp_elapsed=$(($omp_now - $_omp_start_time))
     _omp_no_exit_code="false"
   fi
@@ -67,7 +73,7 @@ function _omp_precmd() {
   set_poshcontext
   _omp_set_cursor_position
 
-  eval "$(::OMP:: print primary --config="$POSH_THEME" --status="$_omp_status_cache" --pipestatus="${_omp_pipestatus_cache[*]}" --execution-time="$_omp_elapsed" --stack-count="$_omp_stack_count" --eval --shell=zsh --shell-version="$ZSH_VERSION" --no-status="$_omp_no_exit_code")"
+  eval "$(${_omp_executable} print primary --status="$_omp_status_cache" --pipestatus="${_omp_pipestatus_cache[*]}" --execution-time="$_omp_elapsed" --stack-count="$_omp_stack_count" --eval --shell=zsh --shell-version="$ZSH_VERSION" --no-status="$_omp_no_exit_code")"
   unset _omp_start_time
 }
 
@@ -110,7 +116,7 @@ function _omp_render_tooltip() {
   fi
 
   _omp_tooltip_command="$tooltip_command"
-  local tooltip=$(::OMP:: print tooltip --config="$POSH_THEME" --status="$_omp_status_cache" --pipestatus="${_omp_pipestatus_cache[*]}" --execution-time="$_omp_elapsed" --stack-count="$_omp_stack_count" --command="$tooltip_command" --shell=zsh --shell-version="$ZSH_VERSION" --no-status="$_omp_no_exit_code")
+  local tooltip=$(${_omp_executable} print tooltip --status="$_omp_status_cache" --pipestatus="${_omp_pipestatus_cache[*]}" --execution-time="$_omp_elapsed" --stack-count="$_omp_stack_count" --command="$tooltip_command" --shell=zsh --shell-version="$ZSH_VERSION" --no-status="$_omp_no_exit_code")
   if [[ -z "$tooltip" ]]; then
     return
   fi
@@ -129,7 +135,7 @@ function _omp_zle-line-init() {
   (( $+zle_bracketed_paste )) && print -r -n - $zle_bracketed_paste[2]
 
   _omp_tooltip_command=''
-  eval "$(::OMP:: print transient --config="$POSH_THEME" --status="$_omp_status_cache" --pipestatus="${_omp_pipestatus_cache[*]}" --execution-time="$_omp_elapsed" --stack-count="$_omp_stack_count" --eval --shell=zsh --shell-version="$ZSH_VERSION" --no-status="$_omp_no_exit_code")"
+  eval "$(${_omp_executable} print transient --status="$_omp_status_cache" --pipestatus="${_omp_pipestatus_cache[*]}" --execution-time="$_omp_elapsed" --stack-count="$_omp_stack_count" --eval --shell=zsh --shell-version="$ZSH_VERSION" --no-status="$_omp_no_exit_code")"
   zle .reset-prompt
 
   # Exit the shell if we receive EOT.
@@ -184,22 +190,15 @@ function _omp_create_widget() {
   esac
 }
 
-# legacy functions
-function enable_poshtooltips() {}
-function enable_poshtransientprompt() {}
-
-if [[ "::TOOLTIPS::" = "true" ]]; then
-  _omp_create_widget self-insert _omp_render_tooltip
-fi
+function enable_poshtooltips() {
+  widget=${$(bindkey " "):2}
 
-if [[ "::TRANSIENT::" = "true" ]]; then
-  _omp_create_widget zle-line-init _omp_zle-line-init
-fi
+  if [[ -z $widget ]]; then
+    widget=self-insert
+  fi
 
-if [[ "::UPGRADE::" = "true" ]]; then
-  echo "::UPGRADENOTICE::"
-fi
+  _omp_create_widget $widget _omp_render_tooltip
+}
 
-if [[ "::AUTOUPGRADE::" = "true" ]]; then
-  ::OMP:: upgrade
-fi
+# legacy functions
+function enable_poshtransientprompt() {}
diff --git a/src/shell/tcsh.go b/src/shell/tcsh.go
new file mode 100644
index 00000000..a6eac334
--- /dev/null
+++ b/src/shell/tcsh.go
@@ -0,0 +1,21 @@
+package shell
+
+import (
+	_ "embed"
+)
+
+//go:embed scripts/omp.tcsh
+var tcshInit string
+
+func (f Feature) Tcsh() Code {
+	switch f {
+	case Upgrade:
+		return "$POSH_COMMAND upgrade;"
+	case Notice:
+		return "$POSH_COMMAND notice;"
+	case PromptMark, RPrompt, PoshGit, Azure, LineError, Jobs, Tooltips, Transient, FTCSMarks, CursorPositioning:
+		fallthrough
+	default:
+		return ""
+	}
+}
diff --git a/src/shell/xonsh.go b/src/shell/xonsh.go
new file mode 100644
index 00000000..6aaabfd4
--- /dev/null
+++ b/src/shell/xonsh.go
@@ -0,0 +1,21 @@
+package shell
+
+import (
+	_ "embed"
+)
+
+//go:embed scripts/omp.py
+var xonshInit string
+
+func (f Feature) Xonsh() Code {
+	switch f {
+	case Upgrade:
+		return "@($POSH_EXECUTABLE) upgrade"
+	case Notice:
+		return "@($POSH_EXECUTABLE) notice"
+	case PromptMark, RPrompt, PoshGit, Azure, LineError, Jobs, Tooltips, Transient, CursorPositioning, FTCSMarks:
+		fallthrough
+	default:
+		return ""
+	}
+}
diff --git a/src/shell/zsh.go b/src/shell/zsh.go
new file mode 100644
index 00000000..6465194b
--- /dev/null
+++ b/src/shell/zsh.go
@@ -0,0 +1,29 @@
+package shell
+
+import (
+	_ "embed"
+)
+
+//go:embed scripts/omp.zsh
+var zshInit string
+
+func (f Feature) Zsh() Code {
+	switch f {
+	case CursorPositioning:
+		return "_omp_cursor_positioning=1"
+	case Tooltips:
+		return "enable_poshtooltips"
+	case Transient:
+		return "_omp_create_widget zle-line-init _omp_zle-line-init"
+	case FTCSMarks:
+		return "_omp_ftcs_marks=1"
+	case Upgrade:
+		return unixUpgrade
+	case Notice:
+		return unixNotice
+	case PromptMark, RPrompt, PoshGit, Azure, LineError, Jobs:
+		fallthrough
+	default:
+		return ""
+	}
+}
diff --git a/src/template/link.go b/src/template/link.go
index 1fef434e..3b180e54 100644
--- a/src/template/link.go
+++ b/src/template/link.go
@@ -3,10 +3,16 @@ package template
 import (
 	"fmt"
 	link "net/url"
+	"slices"
 )
 
 // url builds an hyperlink if url is not empty, otherwise returns the text only
 func url(text, url string) (string, error) {
+	unsupported := []string{elvish, xonsh}
+	if slices.Contains(unsupported, shell) {
+		return text, nil
+	}
+
 	if url == "" {
 		return text, nil
 	}
@@ -18,5 +24,10 @@ func url(text, url string) (string, error) {
 }
 
 func path(text, path string) (string, error) {
+	unsupported := []string{elvish, xonsh}
+	if slices.Contains(unsupported, shell) {
+		return text, nil
+	}
+
 	return fmt.Sprintf("<LINK>file:%s<TEXT>%s</TEXT></LINK>", path, text), nil
 }
diff --git a/src/template/text.go b/src/template/text.go
index 2671c758..c5721c44 100644
--- a/src/template/text.go
+++ b/src/template/text.go
@@ -18,6 +18,9 @@ const (
 	IncorrectTemplate = "unable to create text based on template"
 
 	globalRef = ".$"
+
+	elvish = "elvish"
+	xonsh  = "xonsh"
 )
 
 var (
@@ -40,7 +43,10 @@ var (
 		"Templates",
 		"Var",
 		"Data",
+		"Jobs",
 	}
+
+	shell string
 )
 
 type Text struct {
@@ -70,7 +76,9 @@ func (c *Context) init(t *Text) {
 }
 
 func (t *Text) Render() (string, error) {
-	t.Env.DebugF("Rendering template: %s", t.Template)
+	t.Env.DebugF("rendering template: %s", t.Template)
+
+	shell = t.Env.Flags().Shell
 
 	if !strings.Contains(t.Template, "{{") || !strings.Contains(t.Template, "}}") {
 		return t.Template, nil
diff --git a/src/terminal/writer.go b/src/terminal/writer.go
index e0de6995..617f167a 100644
--- a/src/terminal/writer.go
+++ b/src/terminal/writer.go
@@ -102,8 +102,8 @@ func Init(sh string) {
 	Shell = sh
 	Program = getTerminalName()
 
-	log.Debug("Terminal shell: %s", Shell)
-	log.Debug("Terminal program: %s", Program)
+	log.Debug("terminal program:", Program)
+	log.Debug("terminal shell:", Shell)
 
 	color.TrueColor = Program != AppleTerminal
 
@@ -166,8 +166,6 @@ func Pwd(pwdType, userName, hostName, pwd string) string {
 		pwd += `/`
 	}
 
-	pwd = strings.ReplaceAll(pwd, `\`, `/`)
-
 	switch pwdType {
 	case OSC7:
 		return fmt.Sprintf(formats.Osc7, hostName, pwd)


Test patch:
diff --git a/src/color/colors_test.go b/src/color/colors_test.go
index a1412370..db7264d1 100644
--- a/src/color/colors_test.go
+++ b/src/color/colors_test.go
@@ -5,8 +5,11 @@ import (
 	"testing"
 
 	"github.com/alecthomas/assert"
+	"github.com/jandedobbeleer/oh-my-posh/src/cache"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
+
+	testify_ "github.com/stretchr/testify/mock"
 )
 
 func TestGetAnsiFromColorString(t *testing.T) {
@@ -57,3 +60,30 @@ func TestMakeColors(t *testing.T) {
 	assert.IsType(t, &PaletteColors{}, colors.(*Cached).ansiColors)
 	assert.IsType(t, &Defaults{}, colors.(*Cached).ansiColors.(*PaletteColors).ansiColors)
 }
+
+func TestAnsiRender(t *testing.T) {
+	cases := []struct {
+		Case     string
+		Expected Ansi
+		Term     string
+	}{
+		{Case: "Inside vscode", Expected: "#123456", Term: "vscode"},
+		{Case: "Outside vscode", Expected: "", Term: "windowsterminal"},
+	}
+
+	for _, tc := range cases {
+		env := new(mock.Environment)
+		env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+		env.On("TemplateCache").Return(&cache.Template{
+			Env: map[string]string{
+				"TERM_PROGRAM": tc.Term,
+			},
+		})
+		env.On("Flags").Return(&runtime.Flags{})
+
+		ansi := Ansi("{{ if eq \"vscode\" .Env.TERM_PROGRAM }}#123456{{end}}")
+		got := ansi.ResolveTemplate(env)
+
+		assert.Equal(t, tc.Expected, got, tc.Case)
+	}
+}
diff --git a/src/config/config_test.go b/src/config/config_test.go
index b8647576..f10f27fd 100644
--- a/src/config/config_test.go
+++ b/src/config/config_test.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/jandedobbeleer/oh-my-posh/src/cache"
 	"github.com/jandedobbeleer/oh-my-posh/src/color"
+	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
 
 	"github.com/stretchr/testify/assert"
@@ -76,11 +77,14 @@ func TestGetPalette(t *testing.T) {
 			Shell: "bash",
 		})
 		env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+		env.On("Flags").Return(&runtime.Flags{})
+
 		cfg := &Config{
 			env:      env,
 			Palette:  tc.Palette,
 			Palettes: tc.Palettes,
 		}
+
 		got := cfg.getPalette()
 		assert.Equal(t, tc.ExpectedPalette, got, tc.Case)
 	}
diff --git a/src/config/segment_test.go b/src/config/segment_test.go
index a978d4db..5aa9cedc 100644
--- a/src/config/segment_test.go
+++ b/src/config/segment_test.go
@@ -149,6 +149,7 @@ func TestGetColors(t *testing.T) {
 		env.On("TemplateCache").Return(&cache.Template{
 			Env: make(map[string]string),
 		})
+		env.On("Flags").Return(&runtime.Flags{})
 
 		segment := &Segment{
 			writer: &segments.Aws{
diff --git a/src/prompt/engine_test.go b/src/prompt/engine_test.go
index 60671174..d2bc9418 100644
--- a/src/prompt/engine_test.go
+++ b/src/prompt/engine_test.go
@@ -55,22 +55,30 @@ func TestPrintPWD(t *testing.T) {
 		Config   string
 		Pwd      string
 		Shell    string
+		Cygwin   bool
 		OSC99    bool
 	}{
 		{Case: "Empty PWD"},
 		{Case: "OSC99", Config: terminal.OSC99, Expected: "\x1b]9;9;pwd\x1b\\"},
+		{Case: "OSC99 - Elvish", Config: terminal.OSC99, Shell: shell.ELVISH},
 		{Case: "OSC7", Config: terminal.OSC7, Expected: "\x1b]7;file://host/pwd\x1b\\"},
 		{Case: "OSC51", Config: terminal.OSC51, Expected: "\x1b]51;Auser@host:pwd\x1b\\"},
 		{Case: "Deprecated OSC99", OSC99: true, Expected: "\x1b]9;9;pwd\x1b\\"},
 		{Case: "Template (empty)", Config: "{{ if eq .Shell \"pwsh\" }}osc7{{ end }}"},
 		{Case: "Template (non empty)", Config: "{{ if eq .Shell \"shell\" }}osc7{{ end }}", Expected: "\x1b]7;file://host/pwd\x1b\\"},
 		{
-			Case:     "OSC99 Bash",
+			Case:     "OSC99 Cygwin",
 			Pwd:      `C:\Users\user\Documents\GitHub\oh-my-posh`,
 			Config:   terminal.OSC99,
-			Shell:    shell.BASH,
+			Cygwin:   true,
 			Expected: "\x1b]9;9;C:/Users/user/Documents/GitHub/oh-my-posh\x1b\\",
 		},
+		{
+			Case:     "OSC99 Windows",
+			Pwd:      `C:\Users\user\Documents\GitHub\oh-my-posh`,
+			Config:   terminal.OSC99,
+			Expected: "\x1b]9;9;C:\\Users\\user\\Documents\\GitHub\\oh-my-posh\x1b\\",
+		},
 	}
 
 	for _, tc := range cases {
@@ -78,15 +86,18 @@ func TestPrintPWD(t *testing.T) {
 		if len(tc.Pwd) == 0 {
 			tc.Pwd = "pwd"
 		}
+
 		env.On("Pwd").Return(tc.Pwd)
-		env.On("Shell").Return(tc.Shell)
 		env.On("User").Return("user")
+		env.On("Shell").Return(tc.Shell)
+		env.On("IsCygwin").Return(tc.Cygwin)
 		env.On("Host").Return("host", nil)
 		env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
 		env.On("TemplateCache").Return(&cache.Template{
 			Env:   make(map[string]string),
 			Shell: "shell",
 		})
+		env.On("Flags").Return(&runtime.Flags{})
 
 		terminal.Init(shell.GENERIC)
 
@@ -119,7 +130,7 @@ func engineRender() {
 	cfg := config.Load(env)
 
 	terminal.Init(shell.GENERIC)
-	terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, cfg.TerminalBackground)
+	terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate(env)
 	terminal.Colors = cfg.MakeColors()
 
 	engine := &Engine{
@@ -177,6 +188,7 @@ func TestGetTitle(t *testing.T) {
 		env.On("Home").Return("/usr/home")
 		env.On("PathSeparator").Return(tc.PathSeparator)
 		env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+		env.On("Flags").Return(&runtime.Flags{})
 		env.On("TemplateCache").Return(&cache.Template{
 			Env: map[string]string{
 				"USERDOMAIN": "MyCompany",
@@ -240,6 +252,7 @@ func TestGetConsoleTitleIfGethostnameReturnsError(t *testing.T) {
 		env.On("Pwd").Return(tc.Cwd)
 		env.On("Home").Return("/usr/home")
 		env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+		env.On("Flags").Return(&runtime.Flags{})
 		env.On("TemplateCache").Return(&cache.Template{
 			Env: map[string]string{
 				"USERDOMAIN": "MyCompany",
diff --git a/src/segments/argocd_test.go b/src/segments/argocd_test.go
index 6dc8f24f..c5cd36ff 100644
--- a/src/segments/argocd_test.go
+++ b/src/segments/argocd_test.go
@@ -6,6 +6,7 @@ import (
 	"testing"
 
 	"github.com/jandedobbeleer/oh-my-posh/src/properties"
+	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
 	"github.com/stretchr/testify/assert"
 	testify_ "github.com/stretchr/testify/mock"
@@ -251,6 +252,7 @@ servers:
 		env.On("Getenv", argocdOptsEnv).Return(tc.Opts)
 		env.On("FileContent", configFile).Return(tc.Config)
 		env.On("Error", testify_.Anything).Return()
+		env.On("Flags").Return(&runtime.Flags{})
 
 		argocd := &Argocd{
 			env:   env,
diff --git a/src/segments/aws_test.go b/src/segments/aws_test.go
index f31278e9..8f357c88 100644
--- a/src/segments/aws_test.go
+++ b/src/segments/aws_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/jandedobbeleer/oh-my-posh/src/properties"
+	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
 
 	"github.com/stretchr/testify/assert"
@@ -63,13 +64,17 @@ func TestAWSSegment(t *testing.T) {
 		props := properties.Map{
 			properties.DisplayDefault: tc.DisplayDefault,
 		}
+		env.On("Flags").Return(&runtime.Flags{})
+
 		aws := &Aws{
 			env:   env,
 			props: props,
 		}
+
 		if tc.Template == "" {
 			tc.Template = aws.Template()
 		}
+
 		assert.Equal(t, tc.ExpectedEnabled, aws.Enabled(), tc.Case)
 		assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, aws), tc.Case)
 	}
diff --git a/src/segments/az_test.go b/src/segments/az_test.go
index f205534b..05a45c0e 100644
--- a/src/segments/az_test.go
+++ b/src/segments/az_test.go
@@ -74,14 +74,14 @@ func TestAzSegment(t *testing.T) {
 			ExpectedString:  "AzureCliCloud",
 			Template:        "{{ .EnvironmentName }}",
 			HasCLI:          true,
-			Source:          cli,
+			Source:          Cli,
 		},
 		{
 			Case:            "Az CLI Profile only - disabled",
 			ExpectedEnabled: false,
 			Template:        "{{ .EnvironmentName }}",
 			HasCLI:          false,
-			Source:          cli,
+			Source:          Cli,
 		},
 		{
 			Case:            "PowerShell Profile only",
@@ -89,13 +89,13 @@ func TestAzSegment(t *testing.T) {
 			ExpectedString:  "AzurePoshCloud",
 			Template:        "{{ .EnvironmentName }}",
 			HasPowerShell:   true,
-			Source:          pwsh,
+			Source:          Pwsh,
 		},
 		{
 			Case:            "Az CLI Profile only - disabled",
 			ExpectedEnabled: false,
 			Template:        "{{ .EnvironmentName }}",
-			Source:          pwsh,
+			Source:          Pwsh,
 		},
 		{
 			Case:            "Az CLI account type",
@@ -103,13 +103,15 @@ func TestAzSegment(t *testing.T) {
 			ExpectedString:  "user",
 			Template:        "{{ .User.Type }}",
 			HasCLI:          true,
-			Source:          cli,
+			Source:          Cli,
 		},
 	}
 
 	for _, tc := range cases {
 		env := new(mock.Environment)
 		env.On("Home").Return(poshHome)
+		env.On("Flags").Return(&runtime.Flags{})
+
 		var azureProfile, azureRmContext string
 
 		if tc.HasCLI {
@@ -134,7 +136,7 @@ func TestAzSegment(t *testing.T) {
 		}
 
 		if tc.Source == "" {
-			tc.Source = firstMatch
+			tc.Source = FirstMatch
 		}
 
 		az := &Az{
diff --git a/src/segments/azd_test.go b/src/segments/azd_test.go
index 901dabc6..699f6f27 100644
--- a/src/segments/azd_test.go
+++ b/src/segments/azd_test.go
@@ -37,6 +37,7 @@ func TestAzdSegment(t *testing.T) {
 	for _, tc := range cases {
 		env := new(mock.Environment)
 		env.On("Debug", testify_.Anything)
+		env.On("Flags").Return(&runtime.Flags{})
 
 		if tc.IsInited {
 			fileInfo := &runtime.FileInfo{
diff --git a/src/segments/brewfather_test.go b/src/segments/brewfather_test.go
index d26adb97..9e8a9cbb 100644
--- a/src/segments/brewfather_test.go
+++ b/src/segments/brewfather_test.go
@@ -7,6 +7,7 @@ import (
 
 	cache_ "github.com/jandedobbeleer/oh-my-posh/src/cache/mock"
 	"github.com/jandedobbeleer/oh-my-posh/src/properties"
+	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
 
 	"github.com/stretchr/testify/assert"
@@ -160,6 +161,7 @@ func TestBrewfatherSegment(t *testing.T) {
 		env.On("HTTPRequest", BFBatchURL).Return([]byte(tc.BatchJSONResponse), tc.Error)
 		env.On("HTTPRequest", BFBatchReadingsURL).Return([]byte(tc.BatchReadingsJSONResponse), tc.Error)
 		env.On("Cache").Return(cache)
+		env.On("Flags").Return(&runtime.Flags{})
 
 		ns := &Brewfather{
 			props: props,
diff --git a/src/segments/carbon_intensity_test.go b/src/segments/carbon_intensity_test.go
index 188aadbc..125b387b 100644
--- a/src/segments/carbon_intensity_test.go
+++ b/src/segments/carbon_intensity_test.go
@@ -7,6 +7,7 @@ import (
 
 	cache_ "github.com/jandedobbeleer/oh-my-posh/src/cache/mock"
 	"github.com/jandedobbeleer/oh-my-posh/src/properties"
+	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
 
 	"github.com/stretchr/testify/assert"
@@ -228,6 +229,7 @@ func TestCarbonIntensitySegmentSingle(t *testing.T) {
 
 		env.On("HTTPRequest", CARBONINTENSITYURL).Return([]byte(jsonResponse), responseError)
 		env.On("Error", testify_.Anything)
+		env.On("Flags").Return(&runtime.Flags{})
 
 		d := &CarbonIntensity{
 			props: props,
@@ -259,6 +261,8 @@ func TestCarbonIntensitySegmentFromCache(t *testing.T) {
 		env:   env,
 	}
 
+	env.On("Flags").Return(&runtime.Flags{})
+
 	cache.On("Get", CARBONINTENSITYURL).Return(response, true)
 	cache.On("Set").Return()
 	env.On("Cache").Return(cache)
diff --git a/src/segments/language_test.go b/src/segments/language_test.go
index 4489aa45..5935739b 100644
--- a/src/segments/language_test.go
+++ b/src/segments/language_test.go
@@ -63,6 +63,8 @@ func bootStrapLanguageTest(args *languageArgs) *language {
 	env.On("Pwd").Return(cwd)
 	env.On("Home").Return(home)
 	env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+	env.On("Flags").Return(&runtime.Flags{})
+
 	env.On("TemplateCache").Return(&cache.Template{
 		Env: make(map[string]string),
 	})
@@ -583,6 +585,7 @@ func getMockedLanguageEnv(params *mockedLanguageParams) (*mock.Environment, prop
 		Env: make(map[string]string),
 	})
 	env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+	env.On("Flags").Return(&runtime.Flags{})
 	props := properties.Map{
 		properties.FetchVersion: true,
 	}
diff --git a/src/segments/path_test.go b/src/segments/path_test.go
index e118c3eb..51adda43 100644
--- a/src/segments/path_test.go
+++ b/src/segments/path_test.go
@@ -41,6 +41,8 @@ func renderTemplateNoTrimSpace(env *mock.Environment, segmentTemplate string, co
 	env.On("Error", testify_.Anything)
 	env.On("Debug", testify_.Anything)
 	env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+	env.On("Flags").Return(&runtime.Flags{})
+
 	tmpl := &template.Text{
 		Template: segmentTemplate,
 		Context:  context,
@@ -178,6 +180,7 @@ func TestAgnosterPathStyles(t *testing.T) {
 		MaxDepth            int
 		MaxWidth            int
 		HideRootLocation    bool
+		Cygwin              bool
 	}{
 		{
 			Style:               Unique,
@@ -429,6 +432,7 @@ func TestAgnosterPathStyles(t *testing.T) {
 			Pwd:                 "C:\\Users\\foo\\foobar\\man",
 			GOOS:                runtime.WINDOWS,
 			Shell:               shell.BASH,
+			Cygwin:              true,
 			Cygpath:             "/c/Users/foo/foobar/man",
 			PathSeparator:       "\\",
 			FolderSeparatorIcon: " > ",
@@ -764,6 +768,7 @@ func TestAgnosterPathStyles(t *testing.T) {
 		env.On("Home").Return(tc.HomePath)
 		env.On("Pwd").Return(tc.Pwd)
 		env.On("GOOS").Return(tc.GOOS)
+		env.On("IsCygwin").Return(tc.Cygwin)
 		env.On("StackCount").Return(0)
 		env.On("IsWsl").Return(false)
 		args := &runtime.Flags{
@@ -778,7 +783,7 @@ func TestAgnosterPathStyles(t *testing.T) {
 
 		env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
 
-		displayCygpath := tc.GOOS == runtime.WINDOWS && tc.Shell == shell.BASH
+		displayCygpath := tc.Cygwin
 		if displayCygpath {
 			env.On("RunCommand", "cygpath", []string{"-u", tc.Pwd}).Return(tc.Cygpath, tc.CygpathError)
 			env.On("RunCommand", "cygpath", testify_.Anything).Return("brrrr", nil)
@@ -1013,6 +1018,8 @@ func TestPowerlevelMappedLocations(t *testing.T) {
 		env.On("PathSeparator").Return("/")
 		env.On("Shell").Return(shell.GENERIC)
 		env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+		env.On("Flags").Return(&runtime.Flags{})
+
 		path := &Path{
 			env: env,
 			props: properties.Map{
@@ -1020,8 +1027,10 @@ func TestPowerlevelMappedLocations(t *testing.T) {
 				MappedLocations:  tc.MappedLocations,
 			},
 		}
+
 		path.setPaths()
 		path.setStyle()
+
 		got := renderTemplateNoTrimSpace(env, "{{ .Path }}", path)
 		assert.Equal(t, tc.Expected, got)
 	}
@@ -1462,6 +1471,8 @@ func TestGetFolderSeparator(t *testing.T) {
 		env.On("Error", testify_.Anything)
 		env.On("Debug", testify_.Anything)
 		env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+		env.On("Flags").Return(&runtime.Flags{})
+
 		path := &Path{
 			env:           env,
 			pathSeparator: "/",
@@ -1540,6 +1551,8 @@ func TestReplaceMappedLocations(t *testing.T) {
 		env.On("GOOS").Return(runtime.DARWIN)
 		env.On("Home").Return("/a/b/k")
 		env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+		env.On("Flags").Return(&runtime.Flags{})
+
 		path := &Path{
 			env: env,
 			props: properties.Map{
@@ -1552,6 +1565,7 @@ func TestReplaceMappedLocations(t *testing.T) {
 				},
 			},
 		}
+
 		path.setPaths()
 		assert.Equal(t, tc.Expected, path.pwd)
 	}
@@ -1659,6 +1673,7 @@ func TestGetMaxWidth(t *testing.T) {
 		env := new(mock.Environment)
 		env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
 		env.On("Error", testify_.Anything).Return(nil)
+		env.On("Flags").Return(&runtime.Flags{})
 		env.On("TemplateCache").Return(&cache.Template{
 			Env: map[string]string{
 				"MAX_WIDTH": "120",
diff --git a/src/segments/status_test.go b/src/segments/status_test.go
index 49b251bc..b18f9027 100644
--- a/src/segments/status_test.go
+++ b/src/segments/status_test.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/jandedobbeleer/oh-my-posh/src/cache"
 	"github.com/jandedobbeleer/oh-my-posh/src/properties"
+	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
 
 	"github.com/stretchr/testify/assert"
@@ -31,6 +32,7 @@ func TestStatusWriterEnabled(t *testing.T) {
 		})
 		env.On("Error", testify_.Anything).Return(nil)
 		env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+		env.On("Flags").Return(&runtime.Flags{})
 
 		props := properties.Map{}
 		if len(tc.Template) > 0 {
@@ -97,12 +99,16 @@ func TestFormatStatus(t *testing.T) {
 		})
 		env.On("Error", testify_.Anything).Return(nil)
 		env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+		env.On("Flags").Return(&runtime.Flags{})
+
 		props := properties.Map{
 			StatusTemplate:  tc.Template,
 			StatusSeparator: tc.Separator,
 		}
+
 		s := &Status{}
 		s.Init(props, env)
+
 		assert.Equal(t, tc.Expected, s.formatStatus(tc.Status, tc.PipeStatus), tc.Case)
 	}
 }
diff --git a/src/segments/wakatime_test.go b/src/segments/wakatime_test.go
index 79e17a16..e3cd053c 100644
--- a/src/segments/wakatime_test.go
+++ b/src/segments/wakatime_test.go
@@ -8,6 +8,7 @@ import (
 	"github.com/jandedobbeleer/oh-my-posh/src/cache"
 	cache_ "github.com/jandedobbeleer/oh-my-posh/src/cache/mock"
 	"github.com/jandedobbeleer/oh-my-posh/src/properties"
+	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
 
 	"github.com/stretchr/testify/assert"
@@ -83,6 +84,7 @@ func TestWTTrackedTime(t *testing.T) {
 		mockedCache.On("Set", FAKEAPIURL, response, tc.CacheTimeout).Return()
 		env.On("Cache").Return(mockedCache)
 		env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+		env.On("Flags").Return(&runtime.Flags{})
 
 		env.On("TemplateCache").Return(&cache.Template{
 			Env: map[string]string{"HELLO": "hello"},
@@ -133,6 +135,7 @@ func TestWTGetUrl(t *testing.T) {
 		env.On("TemplateCache").Return(&cache.Template{
 			Env: map[string]string{"HELLO": "hello"},
 		})
+		env.On("Flags").Return(&runtime.Flags{})
 
 		w := &Wakatime{
 			props: properties.Map{
diff --git a/src/shell/bash_test.go b/src/shell/bash_test.go
new file mode 100644
index 00000000..913f476c
--- /dev/null
+++ b/src/shell/bash_test.go
@@ -0,0 +1,19 @@
+package shell
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestBashFeatures(t *testing.T) {
+	got := allFeatures.Lines(BASH).String("// these are the features")
+
+	want := `// these are the features
+_omp_ftcs_marks=1
+$_omp_executable upgrade
+$_omp_executable notice
+_omp_cursor_positioning=1`
+
+	assert.Equal(t, want, got)
+}
diff --git a/src/shell/cmd_test.go b/src/shell/cmd_test.go
new file mode 100644
index 00000000..7a267467
--- /dev/null
+++ b/src/shell/cmd_test.go
@@ -0,0 +1,20 @@
+package shell
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCmdFeatures(t *testing.T) {
+	got := allFeatures.Lines(CMD).String("// these are the features")
+
+	want := `// these are the features
+enable_tooltips()
+transient_enabled = true
+os.execute(string.format('%s upgrade', omp_exe()))
+os.execute(string.format('%s notice', omp_exe()))
+rprompt_enabled = true`
+
+	assert.Equal(t, want, got)
+}
diff --git a/src/shell/elvish_test.go b/src/shell/elvish_test.go
new file mode 100644
index 00000000..187d77bd
--- /dev/null
+++ b/src/shell/elvish_test.go
@@ -0,0 +1,17 @@
+package shell
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestElvishFeatures(t *testing.T) {
+	got := allFeatures.Lines(ELVISH).String("// these are the features")
+
+	want := `// these are the features
+$_omp_executable upgrade
+$_omp_executable notice`
+
+	assert.Equal(t, want, got)
+}
diff --git a/src/shell/fish_test.go b/src/shell/fish_test.go
new file mode 100644
index 00000000..fcadce44
--- /dev/null
+++ b/src/shell/fish_test.go
@@ -0,0 +1,21 @@
+package shell
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestFishFeatures(t *testing.T) {
+	got := allFeatures.Lines(FISH).String("// these are the features")
+
+	want := `// these are the features
+enable_poshtooltips
+set --global _omp_transient_prompt 1
+set --global _omp_ftcs_marks 1
+$_omp_executable upgrade
+$_omp_executable notice
+set --global _omp_prompt_mark 1`
+
+	assert.Equal(t, want, got)
+}
diff --git a/src/shell/init_test.go b/src/shell/init_test.go
index 7fc00879..141456a5 100644
--- a/src/shell/init_test.go
+++ b/src/shell/init_test.go
@@ -4,37 +4,9 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/jandedobbeleer/oh-my-posh/src/cache"
-	"github.com/jandedobbeleer/oh-my-posh/src/color"
-	"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
-
 	"github.com/stretchr/testify/assert"
-	testify_ "github.com/stretchr/testify/mock"
 )
 
-func TestConsoleBackgroundColorTemplate(t *testing.T) {
-	cases := []struct {
-		Case     string
-		Expected string
-		Term     string
-	}{
-		{Case: "Inside vscode", Expected: "#123456", Term: "vscode"},
-		{Case: "Outside vscode", Expected: "", Term: "windowsterminal"},
-	}
-
-	for _, tc := range cases {
-		env := new(mock.Environment)
-		env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
-		env.On("TemplateCache").Return(&cache.Template{
-			Env: map[string]string{
-				"TERM_PROGRAM": tc.Term,
-			},
-		})
-		bgColor := ConsoleBackgroundColor(env, "{{ if eq \"vscode\" .Env.TERM_PROGRAM }}#123456{{end}}")
-		assert.Equal(t, color.Ansi(tc.Expected), bgColor, tc.Case)
-	}
-}
-
 func TestQuotePwshStr(t *testing.T) {
 	tests := []struct {
 		str      string
diff --git a/src/shell/nu_test.go b/src/shell/nu_test.go
new file mode 100644
index 00000000..d4d3b786
--- /dev/null
+++ b/src/shell/nu_test.go
@@ -0,0 +1,19 @@
+package shell
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNuFeatures(t *testing.T) {
+	got := allFeatures.Lines(NU).String("// these are the features")
+
+	//nolint: lll
+	want := `// these are the features
+$env.TRANSIENT_PROMPT_COMMAND = { ^$_omp_executable print transient $"--config=($env.POSH_THEME)" --shell=nu $"--shell-version=($env.POSH_SHELL_VERSION)" $"--execution-time=(posh_cmd_duration)" $"--status=($env.LAST_EXIT_CODE)" $"--terminal-width=(posh_width)" }
+^$_omp_executable upgrade
+^$_omp_executable notice`
+
+	assert.Equal(t, want, got)
+}
diff --git a/src/shell/pwsh_test.go b/src/shell/pwsh_test.go
new file mode 100644
index 00000000..8b8cc77c
--- /dev/null
+++ b/src/shell/pwsh_test.go
@@ -0,0 +1,26 @@
+package shell
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+var allFeatures = Features{Tooltips, LineError, Transient, Jobs, Azure, PoshGit, FTCSMarks, Upgrade, Notice, PromptMark, RPrompt, CursorPositioning}
+
+func TestPwshFeatures(t *testing.T) {
+	got := allFeatures.Lines(PWSH).String("")
+
+	want := `
+Enable-PoshTooltips
+Enable-PoshLineError
+Enable-PoshTransientPrompt
+$global:_ompJobCount = $true
+$global:_ompAzure = $true
+$global:_ompPoshGit = $true
+$global:_ompFTCSMarks = $true
+& $global:_ompExecutable upgrade
+& $global:_ompExecutable notice`
+
+	assert.Equal(t, want, got)
+}
diff --git a/src/shell/tcsh_test.go b/src/shell/tcsh_test.go
new file mode 100644
index 00000000..292b2c64
--- /dev/null
+++ b/src/shell/tcsh_test.go
@@ -0,0 +1,17 @@
+package shell
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestTcshFeatures(t *testing.T) {
+	got := allFeatures.Lines(TCSH).String("// these are the features")
+
+	want := `// these are the features
+$POSH_COMMAND upgrade;
+$POSH_COMMAND notice;`
+
+	assert.Equal(t, want, got)
+}
diff --git a/src/shell/xonsh_test.go b/src/shell/xonsh_test.go
new file mode 100644
index 00000000..321f3fcc
--- /dev/null
+++ b/src/shell/xonsh_test.go
@@ -0,0 +1,17 @@
+package shell
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestXonshFeatures(t *testing.T) {
+	got := allFeatures.Lines(XONSH).String("// these are the features")
+
+	want := `// these are the features
+@($POSH_EXECUTABLE) upgrade
+@($POSH_EXECUTABLE) notice`
+
+	assert.Equal(t, want, got)
+}
diff --git a/src/shell/zsh_test.go b/src/shell/zsh_test.go
new file mode 100644
index 00000000..592b6250
--- /dev/null
+++ b/src/shell/zsh_test.go
@@ -0,0 +1,21 @@
+package shell
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestZshFeatures(t *testing.T) {
+	got := allFeatures.Lines(ZSH).String("// these are the features")
+
+	want := `// these are the features
+enable_poshtooltips
+_omp_create_widget zle-line-init _omp_zle-line-init
+_omp_ftcs_marks=1
+$_omp_executable upgrade
+$_omp_executable notice
+_omp_cursor_positioning=1`
+
+	assert.Equal(t, want, got)
+}
diff --git a/src/template/files_test.go b/src/template/files_test.go
index f2161bdc..8e6c93c6 100644
--- a/src/template/files_test.go
+++ b/src/template/files_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/jandedobbeleer/oh-my-posh/src/cache"
+	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
 
 	"github.com/stretchr/testify/assert"
@@ -27,17 +28,21 @@ func TestGlob(t *testing.T) {
 	env.On("TemplateCache").Return(&cache.Template{
 		Env: make(map[string]string),
 	})
+	env.On("Flags").Return(&runtime.Flags{})
+
 	for _, tc := range cases {
 		tmpl := &Text{
 			Template: tc.Template,
 			Context:  nil,
 			Env:      env,
 		}
+
 		text, err := tmpl.Render()
 		if tc.ShouldError {
 			assert.Error(t, err)
 			continue
 		}
+
 		assert.Equal(t, tc.Expected, text, tc.Case)
 	}
 }
diff --git a/src/template/link_test.go b/src/template/link_test.go
index ee9cd954..adc25363 100644
--- a/src/template/link_test.go
+++ b/src/template/link_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/jandedobbeleer/oh-my-posh/src/cache"
+	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
 
 	"github.com/stretchr/testify/assert"
@@ -28,6 +29,8 @@ func TestUrl(t *testing.T) {
 	env.On("Error", testify_.Anything)
 	env.On("Debug", testify_.Anything)
 	env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+	env.On("Flags").Return(&runtime.Flags{})
+
 	for _, tc := range cases {
 		tmpl := &Text{
 			Template: tc.Template,
@@ -57,12 +60,15 @@ func TestPath(t *testing.T) {
 	env.On("TemplateCache").Return(&cache.Template{
 		Env: make(map[string]string),
 	})
+	env.On("Flags").Return(&runtime.Flags{})
+
 	for _, tc := range cases {
 		tmpl := &Text{
 			Template: tc.Template,
 			Context:  nil,
 			Env:      env,
 		}
+
 		text, _ := tmpl.Render()
 		assert.Equal(t, tc.Expected, text, tc.Case)
 	}
diff --git a/src/template/numbers_test.go b/src/template/numbers_test.go
index 932c5d03..3a4feda8 100644
--- a/src/template/numbers_test.go
+++ b/src/template/numbers_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/jandedobbeleer/oh-my-posh/src/cache"
+	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
 
 	"github.com/stretchr/testify/assert"
@@ -28,17 +29,21 @@ func TestHResult(t *testing.T) {
 	env.On("Error", testify_.Anything)
 	env.On("Debug", testify_.Anything)
 	env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+	env.On("Flags").Return(&runtime.Flags{})
+
 	for _, tc := range cases {
 		tmpl := &Text{
 			Template: tc.Template,
 			Context:  nil,
 			Env:      env,
 		}
+
 		text, err := tmpl.Render()
 		if tc.ShouldError {
 			assert.Error(t, err)
 			continue
 		}
+
 		assert.Equal(t, tc.Expected, text, tc.Case)
 	}
 }
diff --git a/src/template/round_test.go b/src/template/round_test.go
index f8840c3a..06d1ab30 100644
--- a/src/template/round_test.go
+++ b/src/template/round_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/jandedobbeleer/oh-my-posh/src/cache"
+	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
 
 	"github.com/stretchr/testify/assert"
@@ -35,17 +36,21 @@ func TestRoundSeconds(t *testing.T) {
 	env.On("Error", testify_.Anything)
 	env.On("Debug", testify_.Anything)
 	env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+	env.On("Flags").Return(&runtime.Flags{})
+
 	for _, tc := range cases {
 		tmpl := &Text{
 			Template: tc.Template,
 			Context:  nil,
 			Env:      env,
 		}
+
 		text, err := tmpl.Render()
 		if tc.ShouldError {
 			assert.Error(t, err)
 			continue
 		}
+
 		assert.Equal(t, tc.Expected, text, tc.Case)
 	}
 }
diff --git a/src/template/strings_test.go b/src/template/strings_test.go
index 9eded460..dfc0c6be 100644
--- a/src/template/strings_test.go
+++ b/src/template/strings_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/jandedobbeleer/oh-my-posh/src/cache"
+	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
 
 	"github.com/stretchr/testify/assert"
@@ -32,17 +33,21 @@ func TestTrunc(t *testing.T) {
 	env.On("Error", testify_.Anything)
 	env.On("Debug", testify_.Anything)
 	env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+	env.On("Flags").Return(&runtime.Flags{})
+
 	for _, tc := range cases {
 		tmpl := &Text{
 			Template: tc.Template,
 			Context:  nil,
 			Env:      env,
 		}
+
 		text, err := tmpl.Render()
 		if tc.ShouldError {
 			assert.Error(t, err)
 			continue
 		}
+
 		assert.Equal(t, tc.Expected, text, tc.Case)
 	}
 }
diff --git a/src/template/text_test.go b/src/template/text_test.go
index 0b0cd304..b1e8fefa 100644
--- a/src/template/text_test.go
+++ b/src/template/text_test.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/jandedobbeleer/oh-my-posh/src/cache"
 	"github.com/jandedobbeleer/oh-my-posh/src/maps"
+	"github.com/jandedobbeleer/oh-my-posh/src/runtime"
 	"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
 
 	"github.com/stretchr/testify/assert"
@@ -162,17 +163,21 @@ func TestRenderTemplate(t *testing.T) {
 	})
 	env.On("Error", testify_.Anything)
 	env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+	env.On("Flags").Return(&runtime.Flags{})
+
 	for _, tc := range cases {
 		tmpl := &Text{
 			Template: tc.Template,
 			Context:  tc.Context,
 			Env:      env,
 		}
+
 		text, err := tmpl.Render()
 		if tc.ShouldError {
 			assert.Error(t, err)
 			continue
 		}
+
 		assert.NoError(t, err)
 		assert.Equal(t, tc.Expected, text, tc.Case)
 	}
@@ -248,16 +253,20 @@ func TestRenderTemplateEnvVar(t *testing.T) {
 		})
 		env.On("Error", testify_.Anything)
 		env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
+		env.On("Flags").Return(&runtime.Flags{})
+
 		tmpl := &Text{
 			Template: tc.Template,
 			Context:  tc.Context,
 			Env:      env,
 		}
+
 		text, err := tmpl.Render()
 		if tc.ShouldError {
 			assert.Error(t, err)
 			continue
 		}
+
 		assert.Equal(t, tc.Expected, text, tc.Case)
 	}
 }
@@ -362,12 +371,15 @@ func TestSegmentContains(t *testing.T) {
 		Env:      make(map[string]string),
 		Segments: segments,
 	})
+	env.On("Flags").Return(&runtime.Flags{})
+
 	for _, tc := range cases {
 		tmpl := &Text{
 			Template: tc.Template,
 			Context:  nil,
 			Env:      env,
 		}
+
 		text, _ := tmpl.Render()
 		assert.Equal(t, tc.Expected, text, tc.Case)
 	}