Gravifer Tokyo demonstrates the PowerShell workaround this property replaces:
https://github.com/JanDeDobbeleer/oh-my-posh/discussions/7723
The helper resolves linked-worktree indirection, runs the porcelain query, and
caches the result for the shell session. This change moves the capability behind
lazy template access and shares the cache by common repository.
The pull_request-triggered workflow gets a read-only token on fork
PRs, so posting the size comment failed with 403. Build jobs now only
upload artifacts; a separate workflow_run-triggered workflow with
write permissions downloads them and posts the comment. The PR number
travels via an untrusted artifact, so the report validates it and
checks the PR head SHA against the triggering run before commenting.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 302a325be262
A leftover `)}` outside the JSX expression rendered as literal text
below every segment doc's sample preview.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: e8444e478272
The terminal writer accepts osc99, osc7 and osc51 (anything else falls
back to osc99), but schema.json typed pwd as a bare string - so neither
the website editor nor schema-aware editors (VS Code, yaml-language-
server) could offer the values. Same lenient enum-or-string shape the
segment style property already uses.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 2b5eecac4b41
The hand-rolled completion engine could not complete YAML key
positions: its cursor-marker probe produced invalid YAML mid-document,
the parser folded the marker into the next line's key, and the popup
never opened. Instead of patching the probe, replace the editor
foundation: CodeMirror 6 provides the popup, gutter, hover, and
tooltip machinery natively.
codemirror-json-schema was evaluated for the schema features and
rejected: enum completion through $ref resolves to nothing, $ref
nodes with sibling keywords crash its resolver, and both its published
ESM and CJS builds are broken under strict module resolution. The
proven schema resolver from the previous engine stays, rewired as a
native completion/hover source that walks the lezer syntax tree
instead of scanning text - which makes mid-edit states (blank lines,
dangling keys, open strings) work in both JSON and YAML, including
per-segment-type options completion.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 9045963ce7e2
TestRestrictedFuncMapExactKeySet pinned a 190-entry hardcoded list that
just mirrors the map's own construction data; the drift-guard and
dangerous-funcs-excluded tests already cover the real invariants, so it
only added maintenance burden without extra protection.
TestStripControlRunesFastPath asserted unsafe.StringData pointer
identity to prove the no-allocation path ran — a memory-layout detail,
not behavior. The unchanged-input case is already covered functionally
by TestStripControlRunes's unicode case.
CI's fieldalignment linter flagged the anonymous test-case struct as
larger than necessary; reorder Features (a slice) after the string
fields to match its expected layout.
write's per-rune isControlRune filter only guards segment-body text
streamed through it. Pwd() and RenderItermFeatures() format the raw
current working directory (and username/hostname) directly into OSC
7/51/99/1337 sequences via fmt.Sprintf, bypassing that filter entirely.
A directory name containing ESC/ST/BEL bytes closes Oh My Posh's own
OSC sequence early and lets an independent, attacker-controlled escape
sequence stand on its own once the victim cds into it and the pwd or
iterm_features integration renders it — the same class of injection an
earlier fix closed for segment content, reopened via a sink it didn't
cover.
Add a whole-string control-rune stripper for these one-shot OSC payload
fields and apply it to pwd/userName/hostName before every OSC format
call in both functions.
Entire-Checkpoint: 97966247c0a5
context.init bound Getenv to the real environment regardless of trust
level. patchTemplate rewrites .Env.NAME into (call .Getenv "NAME"), but
that rewrite isn't the only way in: a raw {{ call .Getenv "NAME" }} in
untrusted text reaches the same binding directly, so restricting the
rewrite alone would still leave environment values readable from
attacker-controlled input (e.g. a directory name), letting secrets leak
into the rendered prompt.
Bind Getenv only when the template is trusted; untrusted renders get a
stub that always returns empty. Empty rather than nil keeps templates
that legitimately reference .Env.* from failing to render entirely.
Entire-Checkpoint: cf47aaae6d2a
RenderUntrusted's func map was built by copying every sprig function
except an explicit six-name blocklist, so getHostByName (a live DNS
lookup) and over a dozen CPU-expensive crypto functions stayed reachable
from attacker-controlled text such as a directory name. getHostByName
let a crafted folder name exfiltrate .Env values over DNS the moment the
prompt rendered; the crypto functions let the same input hang the shell
via a loop.
Block both classes in dangerousFuncs, keep them available to trusted
config templates via sharedFuncMap, and replace the untrusted map's
"copy everything except six names" construction with an explicit,
independently maintained allowlist of the sprig functions already
reviewed as safe for untrusted input. A drift-guard test now fails when
a sprig upgrade introduces a function that isn't yet triaged into either
list, so new capabilities can no longer reach untrusted templates by
default.
Entire-Checkpoint: 6beb436f6cd8
Show a description tooltip on hover for completion options (real
prose text, or a "View documentation" link for URL-only schema
descriptions), and flip the completion popup above the caret when
there isn't enough viewport room below, clamping its max-height so
it always stays fully on-screen.
Also remove the entire.json repo hook, whose sh invocation errored
on this machine and blocked every gated tool call.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: da7937bafeeb
The rebase onto origin/main dropped 4 commits whose content already
landed upstream under different hashes, taking with them style fixes that
had been folded into those commits earlier in this branch's history:
- color/shade.go, svg/svg.go, prompt/golden_test.go, terminal/writer.go:
re-apply the avoid-else refactors for in-scope hunks introduced by this
branch's styled-runs/shade/svg work.
- config/dsc_hook.go, cli/dsc/shell.go: reword the stale cobra mentions
back to the command tree/flag machinery wording used elsewhere on this
branch.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: c61e99a57d85
statusline.go's doc comment and root.go's Explorer-launch-guard comment
predate this branch and weren't touched by its cobra/pflag -> cmdtree/cmdflag
rename commits, so they were left referencing the removed dependency and its
'mousetrap' terminology. Reword them now that cmdtree/ExplorerLaunchHelpText
are the only names that exist.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: d987e21b8f4f
Builds a release-equivalent binary for every major OS we support
(linux, windows, darwin; amd64), using the same ldflags/tags/env as
src/.goreleaser.yml, each cross-compiled on ubuntu-latest with
CGO_ENABLED=0 (mirroring how build_code.yml already cross-builds every
OS via goreleaser). Each platform can pick up its own dependencies, so
each is checked independently.
Compares every build against the latest published GitHub release's
matching asset, since that mirrors what users actually download and
avoids rebuilding the base branch from source. Posts (and updates, on
re-runs, via a hidden HTML marker) a single PR comment summarizing the
baseline size, PR size, and delta in bytes/percent per OS, calling out
growth beyond 256 KiB or 2%% so an accidental heavy dependency doesn't
slip in unnoticed.
The job only fails on genuine errors (build/download failures), never
on size growth, so legitimate feature additions aren't blocked.
Triggers only on pull_request changes under src/**, since that's the
only tree the binary is built from.
The CLI framework was the largest remaining third-party chunk (545 kB)
and most of its surface went unused - completions are explicitly
disabled, templates and command groups never used. Replace both with
internal packages mirroring the exact API subset in use: a command tree
with persistent flags and nearest-hook semantics, POSIX flag parsing
(--flag=value, --flag value, shorthands and grouping, the -- terminator,
interspersed positionals, unknown-flag allowlisting for the argocd
segment), positional validators, the implicit help command, and the
Windows Explorer double-click guard.
Help, usage and error output were verified byte-identical against the
previous binary across a 47-case golden battery: every command and
subcommand help screen, error formats and exit codes, flag styles
including flags before the subcommand, and init/print rendering.
Shrinks the stripped linux/amd64 binary by 586 kB (13.50 MB -> 12.91 MB;
23.7% below the original 16.93 MB).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qiyvpiy5jR2tyzwZ3zUki5
Font installation only reads the name table of .ttf and .otf files - the
extension allowlist runs before parsing - yet ConradIrwin/font carried
WOFF/WOFF2 decoding and a brotli implementation along with it. Replace
it with a small reader for the sfnt table directory and name table,
decoding entries the same way (UTF-16BE for Unicode and Microsoft
Unicode platforms, Mac Roman for legacy Mac entries, raw bytes
otherwise).
Verified byte-identical metadata extraction (full name, preferred
family, family) against the previous library across 45 system TTF and
OTF fonts. Shrinks the stripped linux/amd64 binary by 377 kB.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qiyvpiy5jR2tyzwZ3zUki5
Four dependencies served narrow slices of their APIs:
- gopkg.in/ini.v1: the aws, gcp and git segments read sections and keys.
Replaced with a minimal src/ini package mirroring the same API subset
and ini.v1's semantics (first key wins, inline comment cutting,
surrounded-quote trimming, BOM handling).
- golang.org/x/mod: the go segment read one field, the go directive of
go.mod. Replaced with a line scan.
- gookit/goutil/jsonutil: only StripComments was used. Replaced with
text.StripJSONComments, which unlike the original is string-aware and
won't mangle values containing comment markers.
- github.com/google/uuid: the session id in cache/init.go. Replaced with
a local RFC 4122 v4 generator (uuid remains an indirect dependency of
sprig's template functions).
Prompt output verified byte-identical against the previous binary,
including the git segment against this repository's live config.
Shrinks the stripped linux/amd64 binary by 266 kB.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qiyvpiy5jR2tyzwZ3zUki5
Three call sites pulled in x/text's casing and message-formatting
machinery (with its plural and number-format tables) for ASCII-only
work: title-casing segment names and taskwarrior command keys, and
comma-grouping a number in the execution time segment. Replace them
with text.Title and a local thousands-separator helper, both verified
against the exact x/text output for the inputs these paths handle,
including the compatibility-sensitive segment template names
(nix-shell -> Nix-Shell, ui5tooling -> Ui5tooling).
x/text itself stays: the terminal writer's rune width tables use
x/text/width by design.
Shrinks the stripped linux/amd64 binary by 545 kB.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qiyvpiy5jR2tyzwZ3zUki5
Reflecting the DSC resource schemas at runtime linked invopop/jsonschema
and with it pb33f/ordered-map and a second YAML library (yaml/v4, next
to the yaml/v3 the config loader already ships) into every prompt
binary, for output that never changes between builds. Generate the
schemas ahead of time and embed them; golden tests regenerate the
schemas via the same reflection (test-only dependency, not linked) and
fail on drift. Regenerate with UPDATE_SCHEMAS=1 go test ./cli/dsc ./cli/font.
Output verified byte-identical for the shell, configuration and font
resources. Shrinks the stripped linux/amd64 binary by 438 kB.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qiyvpiy5jR2tyzwZ3zUki5
The hashicorp/hcl/v2 + zclconf/go-cty stack existed to extract a single
attribute: required_version from a top-level terraform block. Replace it
with a small scanner that tracks just enough HCL structure (comments,
strings, interpolation, heredocs, block nesting) to locate that
attribute, and fails closed to the state-file fallback on anything it
cannot make sense of - the same path a full parser failure took.
Shrinks the stripped linux/amd64 binary by 1.80 MB (16.93 MB -> 15.13 MB).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qiyvpiy5jR2tyzwZ3zUki5
The config command no longer has a migrate subcommand, but its Long
description still advertised it.
Fixes#7734
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 80471ee5a91c
Assigning \False stringifies to the literal False, which passes
conda's own truthiness check and gets Write-Host'ed on every prompt.
An empty string matches what conda's prompt wrapper actually expects.
Fixes#7733
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: ce0acf0d99c9
battery_windows_nix.go's negative build tag matched illumos/solaris
without providing a systemGetAll implementation, breaking `go build`
on those platforms. Add a dedicated no-op implementation and exclude
illumos/solaris from the fallback file.
Fixes#7730
Rasterizes the live prompt SVG to a PNG on a hidden canvas. The font
is embedded as a base64 data URI in the SVG before rasterizing, since
an Image() loaded from a blob URL cannot see the page's own
@font-face and would otherwise fall back to a generic monospace font.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 0401007e6324
The homepage hero rendered once, against the svg exporter's own fixed
dark canvas background, so it always looked like a dark-mode prompt
even when a reader had the site in light mode.
export_themes.mjs now renders the default config twice - once
unchanged for dark, once with --background-color=#ffffff (Infima's
own light background) for light - and hero.json carries both as svg/
svgLight. The homepage ships both renders in its static HTML and picks
between them with plain CSS keyed off Docusaurus's own
html[data-theme] attribute, the same convention custom.css's
--omp-card-background override already uses, so the switch is instant
and needs no client re-render.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: e4462dff49f1
The svg export's canvas background always came from the theme's own
terminal background (or a fixed dark fallback with none set) - a
caller with no way to ask for a specific canvas color at export time,
which the website needs to render the homepage hero against both a
dark and a light background for its own dark/light toggle.
The flag only ever fills in for a theme that sets no terminal
background of its own: withDefaults still prefers a real
TerminalBackground when the theme has one, so this can't paint over
how a theme would actually look in a real terminal.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 0ba07d2489e2
Redraws the terminal-window chrome to match a Windows Terminal window
instead of macOS Terminal.app: no colored traffic-light dots, a flat
title bar sharing the window's own background (no two-tone header),
thin line-drawn window controls (- box x) flush against the right
edge, and +/chevron tab controls. All chrome glyphs share one baseline
helper so the title, tab controls and window controls sit on the same
visual line regardless of their own font-size.
Adds Options.Title so a caller can label the window (e.g. the shell
name); unset renders no title, matching the prior chrome's lack of one.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 0e301f956da9
config/config.go and config/default.go referenced bare segments.CONST option keys
directly and unconditionally, pulling the entire segments package - and its
transitive HCL/go-cty/x-mod-modfile/ini/cli-auth dependency tree - into every
binary linking config, including src/wasm. Mirror the option-key strings as
local options.Option consts in config instead and drop the segments import.
src/dsc (invopop/jsonschema -> go/ast, go/parser, go/doc; spf13/cobra) was
imported unconditionally by config/dsc.go and shell/dsc.go for CLI-only DSC
(Desired State Configuration) bookkeeping never exercised by the wasm render
path. Move both DSC resource types into a new cli/dsc package where they're
actually used, and decouple config from dsc via a nil-by-default hook -
config.NewDSCTracker, set by cli/dsc's init() - so config.Parse() no-ops the
bookkeeping instead of importing dsc directly.
Combined effect on the wasm build: 21,833,910 -> 17,825,752 bytes stripped
(-4.01MB, ~18.4%). go list -deps ./wasm/ confirms segments, dsc,
invopop/jsonschema, spf13/cobra, go/ast, go/parser, and the HCL/go-cty chain
are all gone from the link graph.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 3ef09bcd218e
Move runtime.Terminal's gopsutil-backed and terminal-dimensions-backed
methods (Shell's parent-process fallback, SystemInfo, Memory, TerminalWidth,
Platform, and the remaining WSL/registry/media/connection probes) into
!js/js file pairs, mirroring the existing terminal_root_js.go /
terminal_writable_js.go split. text/template's reflect.MethodByName call
keeps every exported method of runtime.Terminal linked once it is converted
to the Environment interface (render.Config does this), so a per-method
build-tag split is the only way to drop an implementation's cost - a new,
slimmer Environment type would not help.
Retags terminal_unix.go/terminal_unix_test.go from !windows to
!windows && !js since terminal_js.go now owns those methods for js.
Verified: gopsutil (v4.26.6) already ships GOOS=js fallback stubs, so this
is not a multi-MB win - the stripped omp.wasm build drops from 21,866,822 to
21,833,786 bytes (~33 KB), and go build -ldflags=-dumpdep confirms
gopsutil/process, gopsutil/cpu, gopsutil/load, and terminal-dimensions are
fully gone from the link graph (only the type-only gopsutil/disk.IOCountersStat
used by SystemInfo.Disks remains, out of scope per environment.go).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
`npm start` now runs scripts/dev.mjs instead of `docusaurus start`
directly. It wraps the dev server with watchers that keep generated
artifacts current without a manual regen + restart:
- a /themes edit re-renders just that theme and patches it into
generated/themes.json, so <ThemeGallery/> hot-reloads it
- editing export_themes.mjs, font-metrics.mjs or segment_data.json
falls back to a full `npm run themes`, since those can change
every theme`s render
- editing a Go source file under src/ rebuilds the studio`s wasm
module (`npm run wasm`)
`npm run build` and CI are unaffected: they already run these
scripts explicitly before building and never watch anything.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Entire-Checkpoint: 23a2e79bf51a
Recorded data replayed without a segment writer diverged from the same
data replayed with one, in four ways that only surfaced once both paths
rendered the bundled themes side by side:
- Colour templates resolved against the writer rather than the data, so
every one of them fell back to the plain colour where no writer
exists - 49 of 124 themes.
- A value that is not a struct lost its methods. battery.State is an
int, and `.State.String` is what every battery theme switches on.
Method results now travel in a tree of their own, which keeps the
data itself writer-shaped: an entry whose "State" were an object no
longer unmarshals into the writer.
- A field renamed by its json tag was recorded under its Go name alone,
so encoding/json never matched it on the way back and terraform's
version restored as nil. Both names are recorded now.
- `date` fell through to the wall clock for a timestamp arriving as a
string, which is how a recorded time.Time always arrives.
The wasm build carries no zoneinfo, so its idea of local time was
whatever offset the host happened to be on and a gallery built in July
printed an hour later than the same gallery built in January. It
renders in UTC now, with recorded timestamps written to match.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Entire-Checkpoint: 707298e5cc26
Choosing a font was a five-stage terminal UI: fetch the list, scroll it,
download, unzip, install. It pulled bubbletea, lipgloss, bubbles and a
fuzzy matcher for what amounted to a non-filtering list of names with
one highlighted - and Go runs a linked package's init before main
whether or not the subcommand was invoked, so everyone typing at a
prompt paid for it on every render.
A CLI already has a way to answer "which fonts are there": print them.
oh-my-posh font list
oh-my-posh font install Meslo
The list can be grepped, piped and scripted, none of which a picker
allows, and install is what it always was underneath - resolve, fetch,
unzip - now reporting on a status line and a progress bar.
Removes 20 modules including go-runewidth, whose package init built a
2.2MB lookup table nothing in the prompt path ever read. The
atotto/clipboard fork this repo carried goes with them: bubbletea was
the only thing that needed it.
Binary 17,330,176 to 15,981,056 bytes (-7.8%). Init, median of 11 runs,
39.3ms to 6.8ms - on every invocation, prompt renders included.
BREAKING CHANGE: `oh-my-posh font install` now requires a font name; run
`oh-my-posh font list` to see them. The interactive picker is gone, and
with it the --headless flag, which existed to skip it.
Entire-Checkpoint: ef231d69c9bf
Both were display-only shells: a spinner, a status line, and in the
upgrade's case a progress bar. No list, no input, no alternate screen,
no mouse. The upgrade's business logic was already decoupled behind
plain reporter callbacks, added so the core would never import
bubbletea, so it only ever needed a different subscriber.
The auth flows had their device-code polling inside the models. They
report through the same setState calls as before; what changed is that
Run drives them synchronously instead of spawning a goroutine to keep a
message loop fed, because a status line now redraws from its own ticker.
Each flow still words its own steps - GitHub names the device code,
YouTube Music names its base URL - so message() replaces View().
The upgrade UI loses its q-to-quit key. Interrupting an upgrade midway
through replacing a binary was never a good idea, and ctrl+c still
reaches the process.
The font picker still uses the framework, so nothing is removed from
go.mod yet.
Entire-Checkpoint: 0254bffa0c17
oh-my-posh renders a prompt once or twice per keystroke and then exits.
It also ships a font installer, an upgrade UI and two OAuth flows, all
built on bubbletea - and Go runs a linked package's init before main
whether or not the subcommand that needs it was invoked. Someone typing
at a prompt pays for a font picker they are not using: 1.84MB of binary
and 44ms of every render, most of it go-runewidth building a 2.2MB
lookup table nothing in the prompt path reads.
What that framework is asked to do here is small. Across the whole
binary the interactive surface is one non-filtering select list, a
spinner, a progress bar, and quit keys. No text input, no alternate
screen, no mouse.
So: a status line that repaints with a carriage return and an erase, a
progress bar that does the same and keeps reporting to the terminal's
own taskbar indicator, and a numbered picker.
Everything writes in cooked mode. Arrow keys would need raw mode, and
raw mode is the one genuinely risky thing here - termios on POSIX,
console modes on Windows, restoring both when the process panics, and a
fallback for the ptys where stdin is a pipe and none of it applies. A
number and Enter needs none of that, and works when stdin is redirected,
which is how anyone scripts an install.
Nothing uses it yet.
Entire-Checkpoint: 040a19f91be1
Two things a segment lost when there was no writer to build it.
A segment with nothing recorded about it never turned on. Enabled was
only ever set from the writer, so with none the segment fell straight to
its fallback and drew nothing - including a text segment, which needs no
data at all and whose whole content is its template. Render already
settles this correctly for every other case, by asking whether the text
came out blank, so the answer is to let it: with no writer and nothing
recorded, the template decides.
Numbers arrived as the wrong type. JSON has one number type, so
unmarshalling into a map makes every number a float64 where the writer
struct would have produced whatever the field declared. sysinfo renders
`{{ round .PhysicalPercentUsed .Precision }}`, and round wants its
precision as an int, so a Precision of 2.0 failed a template that an int
2 rendered. Whole numbers are restored as ints, which is the guess that
still renders: a function taking a float widens an int, while one taking
an int rejects a float.
The fixture guard learns that a recorded entry is an envelope, and that
a key naming a method is not the typo it hunts for. azpwsh leaves the
fixture: it is not a registered segment type, though two bundled themes
still name it.
Entire-Checkpoint: 1478bb7e734f