mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2026-08-24 02:34:19 -05:00
fix(data): render a segment the same with or without its writer
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
This commit is contained in:
committed by
Jan De Dobbeleer
co-authored by
Claude Opus 5
parent
e4ac930b0c
commit
47f2228931
@@ -156,12 +156,12 @@ func buildDataDocument(cfg *config.Config) ([]byte, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
raw, err := recordSegmentData(writer)
|
||||
raw, methods, err := recordSegmentData(writer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal segment %s: %w", segment.DataKey(), err)
|
||||
}
|
||||
|
||||
recorded := config.RecordedSegment{Data: raw, Enabled: segment.Enabled}
|
||||
recorded := config.RecordedSegment{Data: raw, Methods: methods, Enabled: segment.Enabled}
|
||||
|
||||
recordedRaw, err := json.Marshal(recorded)
|
||||
if err != nil {
|
||||
|
||||
+121
-22
@@ -4,6 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/config"
|
||||
)
|
||||
|
||||
// interfaceMethods are the SegmentWriter methods every writer has to satisfy. They describe how a
|
||||
@@ -36,18 +38,48 @@ const maxMethodDepth = 3
|
||||
//
|
||||
// Fields win over methods on a name collision: the field is what json.Marshal chose to call the
|
||||
// value, and a method shadowing it would change what replay sees.
|
||||
func recordSegmentData(writer any) ([]byte, error) {
|
||||
data, err := asDataMap(reflect.ValueOf(writer), 0)
|
||||
//
|
||||
// The methods a struct field carries in its own right are the exception, and they are why this
|
||||
// returns two trees rather than one. battery.State is an int, and every battery theme switches on
|
||||
// `.State.String`; a map holding the number it marshals to has nothing under that name. Its method
|
||||
// results have to go somewhere, and they cannot go where the number is - a file whose "State" is
|
||||
// an object no longer unmarshals into the writer, which is the other half of what a data file is
|
||||
// for. So the number stays in data, the methods go in a parallel methods tree shaped like it, and
|
||||
// whoever renders without a writer merges the second over the first (config.MergeRecordedMethods).
|
||||
func recordSegmentData(writer any) (data, methods []byte, err error) {
|
||||
recorded, err := asDataMap(reflect.ValueOf(writer), 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return json.Marshal(data)
|
||||
data, err = json.Marshal(recorded.data)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if recorded.methods == nil {
|
||||
return data, nil, nil
|
||||
}
|
||||
|
||||
methods, err = json.Marshal(recorded.methods)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return data, methods, nil
|
||||
}
|
||||
|
||||
func asDataMap(value reflect.Value, depth int) (any, error) {
|
||||
// recorded is one value's two trees: what it marshals to, and the method results that have no
|
||||
// place in that marshalling. methods is nil for everything that needs no overlay, which is most of
|
||||
// what a writer holds.
|
||||
type recorded struct {
|
||||
data any
|
||||
methods any
|
||||
}
|
||||
|
||||
func asDataMap(value reflect.Value, depth int) (recorded, error) {
|
||||
if !value.IsValid() {
|
||||
return nil, nil
|
||||
return recorded{}, nil
|
||||
}
|
||||
|
||||
structValue := reflect.Indirect(value)
|
||||
@@ -58,20 +90,32 @@ func asDataMap(value reflect.Value, depth int) (any, error) {
|
||||
if structValue.Kind() != reflect.Struct || marshalsItself(value) {
|
||||
raw, err := json.Marshal(value.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return recorded{}, err
|
||||
}
|
||||
|
||||
return json.RawMessage(raw), nil
|
||||
// A type with its own MarshalJSON gets no overlay: that representation is the one templates
|
||||
// consume, and time.Time's methods would bury the timestamp `date` parses.
|
||||
var methods any
|
||||
if overlay := methodResults(value, depth); len(overlay) != 0 {
|
||||
methods = overlay
|
||||
}
|
||||
|
||||
return recorded{data: json.RawMessage(raw), methods: methods}, nil
|
||||
}
|
||||
|
||||
fields := make(map[string]any)
|
||||
overlay := make(map[string]any)
|
||||
|
||||
if depth < maxMethodDepth {
|
||||
addStructFields(structValue, fields, depth)
|
||||
addMethodResults(value, fields, depth)
|
||||
addStructFields(structValue, fields, overlay, depth)
|
||||
addMethodResults(value, fields, overlay, depth)
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
if len(overlay) == 0 {
|
||||
return recorded{data: fields}, nil
|
||||
}
|
||||
|
||||
return recorded{data: fields, methods: overlay}, nil
|
||||
}
|
||||
|
||||
// marshalsItself reports whether a type defines its own JSON representation, in which case taking
|
||||
@@ -87,16 +131,20 @@ func marshalsItself(value reflect.Value) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// addStructFields records a struct's exported fields keyed by their Go names rather than their
|
||||
// json tags.
|
||||
// addStructFields records a struct's exported fields under their Go names, and again under a json
|
||||
// tag that renames them.
|
||||
//
|
||||
// This is what a template reads. `{{ .Name }}` resolves against a field called Name; the az
|
||||
// The Go name is what a template reads. `{{ .Name }}` resolves against a field called Name; the az
|
||||
// segment's json tag for it is "name", and wakatime's CumulativeTotal is tagged
|
||||
// "cumulative_total". A struct bridges the two because encoding/json knows the tag - a map has no
|
||||
// tags, so a recorded file keyed by tags leaves every such template unable to find anything. Since
|
||||
// the whole point of recording is to be replayed where no struct exists, the keys have to be the
|
||||
// names the templates use.
|
||||
func addStructFields(structValue reflect.Value, fields map[string]any, depth int) {
|
||||
// tags, so a recorded file keyed by tags leaves every such template unable to find anything.
|
||||
//
|
||||
// The tag is what a writer reads. encoding/json matches a tagged field by its tag and by nothing
|
||||
// else, so a file keyed only by Go names restores every plainly-named field and silently skips the
|
||||
// renamed ones - terraform's Version, tagged "terraform_version", came back nil and took the
|
||||
// version out of the prompt. Both keys carry the same value, so whichever side reads the file
|
||||
// finds what it is looking for under the name it knows.
|
||||
func addStructFields(structValue reflect.Value, fields, overlay map[string]any, depth int) {
|
||||
for i := range structValue.NumField() {
|
||||
field := structValue.Type().Field(i)
|
||||
|
||||
@@ -115,7 +163,7 @@ func addStructFields(structValue reflect.Value, fields map[string]any, depth int
|
||||
// concerned, so they are flattened rather than nested under the type name.
|
||||
embedded := reflect.Indirect(structValue.Field(i))
|
||||
if embedded.Kind() == reflect.Struct {
|
||||
addStructFields(embedded, fields, depth)
|
||||
addStructFields(embedded, fields, overlay, depth)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -125,11 +173,58 @@ func addStructFields(structValue reflect.Value, fields map[string]any, depth int
|
||||
continue
|
||||
}
|
||||
|
||||
fields[field.Name] = nested
|
||||
fields[field.Name] = nested.data
|
||||
|
||||
if name := taggedName(&field); name != "" {
|
||||
fields[name] = nested.data
|
||||
}
|
||||
|
||||
// The overlay is read only where there is no writer, so it needs the template's name and
|
||||
// not the writer's.
|
||||
if nested.methods != nil {
|
||||
overlay[field.Name] = nested.methods
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addMethodResults(value reflect.Value, fields map[string]any, depth int) {
|
||||
// taggedName is the name encoding/json will look for when it differs from the field's own, and ""
|
||||
// when the two agree or the field carries no tag at all.
|
||||
func taggedName(field *reflect.StructField) string {
|
||||
tag, tagged := field.Tag.Lookup("json")
|
||||
if !tagged {
|
||||
return ""
|
||||
}
|
||||
|
||||
name, _, _ := strings.Cut(tag, ",")
|
||||
if name == "" || name == field.Name {
|
||||
return ""
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
// methodResults records a value's methods on their own, for the non-struct case that has no
|
||||
// fields to merge them with. What comes back is the whole of what replaces the value, so each
|
||||
// method's own overlay is folded in here rather than left for the merge at restore to find - the
|
||||
// merge stops descending the moment one side is not a map, and a scalar's data is never one.
|
||||
func methodResults(value reflect.Value, depth int) map[string]any {
|
||||
if depth >= maxMethodDepth || marshalsItself(value) {
|
||||
return nil
|
||||
}
|
||||
|
||||
fields := make(map[string]any)
|
||||
overlay := make(map[string]any)
|
||||
|
||||
addMethodResults(value, fields, overlay, depth)
|
||||
|
||||
for name, methods := range overlay {
|
||||
fields[name] = config.MergeRecordedMethods(fields[name], methods)
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
func addMethodResults(value reflect.Value, fields, overlay map[string]any, depth int) {
|
||||
for i := range value.NumMethod() {
|
||||
name := value.Type().Method(i).Name
|
||||
|
||||
@@ -158,7 +253,11 @@ func addMethodResults(value reflect.Value, fields map[string]any, depth int) {
|
||||
continue
|
||||
}
|
||||
|
||||
fields[name] = nested
|
||||
fields[name] = nested.data
|
||||
|
||||
if nested.methods != nil {
|
||||
overlay[name] = nested.methods
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+30
-1
@@ -76,10 +76,39 @@ func ThemeFiles(dir string) ([]string, error) {
|
||||
// version marker. A hand-written file skips this wrapper and stores a segment's
|
||||
// raw writer fields directly, as it always has.
|
||||
type RecordedSegment struct {
|
||||
Data json.RawMessage `json:"data"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
// Methods holds the method results that have no room in Data, shaped like it. A recorded
|
||||
// value's methods normally sit alongside its fields, but a value that is not a struct has no
|
||||
// fields to sit alongside: battery.State is an int, and an entry whose "State" were the object
|
||||
// its String() belongs in would no longer unmarshal into the writer. Kept apart, Data stays
|
||||
// writer-shaped and a render with no writer merges this over it - see MergeRecordedMethods.
|
||||
Methods json.RawMessage `json:"methods,omitempty"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// MergeRecordedMethods overlays a recorded segment's Methods tree on its Data tree. Two maps are
|
||||
// merged key by key; anything else is replaced outright, which is what puts a scalar's method
|
||||
// results in the place of the scalar.
|
||||
func MergeRecordedMethods(data, methods any) any {
|
||||
dataMap, isDataMap := data.(map[string]any)
|
||||
|
||||
methodsMap, isMethodsMap := methods.(map[string]any)
|
||||
if !isDataMap || !isMethodsMap {
|
||||
return methods
|
||||
}
|
||||
|
||||
for key, value := range methodsMap {
|
||||
if existing, ok := dataMap[key]; ok {
|
||||
dataMap[key] = MergeRecordedMethods(existing, value)
|
||||
continue
|
||||
}
|
||||
|
||||
dataMap[key] = value
|
||||
}
|
||||
|
||||
return dataMap
|
||||
}
|
||||
|
||||
// Data holds template data supplied via the --data flag, used to render a
|
||||
// prompt deterministically without querying the real runtime.
|
||||
type Data struct {
|
||||
|
||||
+28
-9
@@ -417,7 +417,7 @@ func (segment *Segment) ResolveForeground() color.Ansi {
|
||||
}
|
||||
|
||||
if len(segment.ForegroundTemplates) != 0 {
|
||||
match := segment.ForegroundTemplates.FirstMatch(segment.writer, segment.Foreground.String())
|
||||
match := segment.ForegroundTemplates.FirstMatch(segment.templateContext(), segment.Foreground.String())
|
||||
segment.Foreground = color.Ansi(match)
|
||||
}
|
||||
|
||||
@@ -433,7 +433,7 @@ func (segment *Segment) ResolveBackground() color.Ansi {
|
||||
}
|
||||
|
||||
if len(segment.BackgroundTemplates) != 0 {
|
||||
match := segment.BackgroundTemplates.FirstMatch(segment.writer, segment.Background.String())
|
||||
match := segment.BackgroundTemplates.FirstMatch(segment.templateContext(), segment.Background.String())
|
||||
segment.Background = color.Ansi(match)
|
||||
}
|
||||
|
||||
@@ -611,7 +611,7 @@ func (segment *Segment) restoreData() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if err := segment.restoreInto(recorded.Data); err != nil {
|
||||
if err := segment.restoreInto(recorded.Data, recorded.Methods); err != nil {
|
||||
log.Error(err)
|
||||
return false
|
||||
}
|
||||
@@ -631,7 +631,11 @@ func (segment *Segment) restoreData() bool {
|
||||
// degraded form - a template resolves a name against a map key exactly as it resolves it against
|
||||
// a struct field, so both carry the same recorded values to the same templates. What a map cannot
|
||||
// carry is a method result, which is why the recorder writes those out as data too.
|
||||
func (segment *Segment) restoreInto(raw json.RawMessage) error {
|
||||
//
|
||||
// methods is the overlay for the values whose method results could not be written as data without
|
||||
// changing what the writer sees (RecordedSegment.Methods explains which). It is read here and
|
||||
// nowhere else: a writer brings its own methods, so only the map ever needs it.
|
||||
func (segment *Segment) restoreInto(raw, methods json.RawMessage) error {
|
||||
if segment.writer != nil {
|
||||
return json.Unmarshal(raw, &segment.writer)
|
||||
}
|
||||
@@ -641,6 +645,15 @@ func (segment *Segment) restoreInto(raw json.RawMessage) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(methods) != 0 {
|
||||
overlay := make(map[string]any)
|
||||
if err := json.Unmarshal(methods, &overlay); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
MergeRecordedMethods(data, overlay)
|
||||
}
|
||||
|
||||
segment.data = normalizeNumbers(data).(map[string]any)
|
||||
|
||||
return nil
|
||||
@@ -688,12 +701,12 @@ func normalizeNumbers(value any) any {
|
||||
}
|
||||
|
||||
// decodeRecordedSegment reports whether raw is exactly a RecordedSegment
|
||||
// envelope: a JSON object with only "enabled" and "data" keys, nothing else.
|
||||
// Anything short of that - a flat hand-written entry, or malformed JSON - is
|
||||
// left for the caller to treat as unmarked data.
|
||||
// envelope: a JSON object holding "enabled" and "data", and nothing beyond an
|
||||
// optional "methods". Anything short of that - a flat hand-written entry, or
|
||||
// malformed JSON - is left for the caller to treat as unmarked data.
|
||||
func decodeRecordedSegment(raw json.RawMessage) (RecordedSegment, bool) {
|
||||
var fields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &fields); err != nil || len(fields) != 2 {
|
||||
if err := json.Unmarshal(raw, &fields); err != nil {
|
||||
return RecordedSegment{}, false
|
||||
}
|
||||
|
||||
@@ -703,12 +716,18 @@ func decodeRecordedSegment(raw json.RawMessage) (RecordedSegment, bool) {
|
||||
return RecordedSegment{}, false
|
||||
}
|
||||
|
||||
methodsRaw, hasMethods := fields["methods"]
|
||||
|
||||
if len(fields) != 2 && (len(fields) != 3 || !hasMethods) {
|
||||
return RecordedSegment{}, false
|
||||
}
|
||||
|
||||
var enabled bool
|
||||
if err := json.Unmarshal(enabledRaw, &enabled); err != nil {
|
||||
return RecordedSegment{}, false
|
||||
}
|
||||
|
||||
return RecordedSegment{Data: dataRaw, Enabled: enabled}, true
|
||||
return RecordedSegment{Data: dataRaw, Methods: methodsRaw, Enabled: enabled}, true
|
||||
}
|
||||
|
||||
func (segment *Segment) setCache() {
|
||||
|
||||
@@ -213,6 +213,15 @@ func reachableNames(value reflect.Value) (map[string]bool, map[string]reflect.Va
|
||||
|
||||
names[field.Name] = true
|
||||
fields[field.Name] = sv.Field(i)
|
||||
|
||||
// The recorder writes a renamed field under both names, so a tag is just as legitimate
|
||||
// a key as the Go name - it is the one the writer itself reads back.
|
||||
if tag, tagged := field.Tag.Lookup("json"); tagged {
|
||||
if name, _, _ := strings.Cut(tag, ","); name != "" && name != "-" {
|
||||
names[name] = true
|
||||
fields[name] = sv.Field(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,15 @@ func (n Number) String() string {
|
||||
|
||||
type Index string
|
||||
|
||||
// String is what a template compares an index against. `{{ eq "moderate" .Index }}` reads the
|
||||
// value itself and works the same, but only where a segment renders from its writer: a segment
|
||||
// rendered from recorded data has no Go type to carry Icon, so the recorder stores an index as
|
||||
// its method results, and a template reaching past them for the raw string finds nothing. Naming
|
||||
// the string as a method keeps both readings available wherever a segment renders from.
|
||||
func (i Index) String() string {
|
||||
return string(i)
|
||||
}
|
||||
|
||||
func (i Index) Icon() string {
|
||||
switch i {
|
||||
case "very low":
|
||||
|
||||
+18
-5
@@ -24,11 +24,7 @@ func dateInZone(fmt string, date any, zone string) string {
|
||||
case int32:
|
||||
t = time.Unix(int64(v), 0)
|
||||
case string:
|
||||
if epoch, err := strconv.ParseInt(v, 10, 64); err == nil {
|
||||
t = time.Unix(epoch, 0)
|
||||
} else {
|
||||
t = time.Now()
|
||||
}
|
||||
t = parseDateString(v)
|
||||
default:
|
||||
t = time.Now()
|
||||
}
|
||||
@@ -41,6 +37,23 @@ func dateInZone(fmt string, date any, zone string) string {
|
||||
return t.In(loc).Format(fmt)
|
||||
}
|
||||
|
||||
// parseDateString reads the two shapes a date reaches a template as text in: a Unix epoch, which
|
||||
// is what sprig's own unixEpoch hands on, and an RFC 3339 timestamp, which is how a time.Time
|
||||
// marshals. The second matters for a segment rendered from recorded data rather than from its
|
||||
// writer: JSON has no time type, so a recorded date arrives as the string it marshalled to, and
|
||||
// falling through to time.Now() would quietly replace it with the wall clock.
|
||||
func parseDateString(value string) time.Time {
|
||||
if epoch, err := strconv.ParseInt(value, 10, 64); err == nil {
|
||||
return time.Unix(epoch, 0)
|
||||
}
|
||||
|
||||
if parsed, err := time.Parse(time.RFC3339Nano, value); err == nil {
|
||||
return parsed
|
||||
}
|
||||
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func ompDate(fmt string, date any) string {
|
||||
return dateInZone(fmt, date, "Local")
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"syscall/js"
|
||||
"time"
|
||||
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/config"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/render"
|
||||
@@ -31,6 +32,15 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Every render here is meant to be reproducible: the same config and the same recorded data
|
||||
// give the same SVG, whoever builds the site and whenever. A recorded date is the one thing
|
||||
// that would not have been - `{{ .CurrentDate | date .Format }}` converts to the local zone,
|
||||
// and this build carries no zoneinfo, so "local" is whatever fixed offset the host happened to
|
||||
// be on. A gallery built in July would print an hour later than the same gallery built in
|
||||
// January. Pinning the zone takes the host back out of it; recorded timestamps are written in
|
||||
// UTC to match (website/segment_data.json).
|
||||
time.Local = time.UTC
|
||||
|
||||
js.Global().Set("render", js.FuncOf(renderJS))
|
||||
|
||||
// A wasm_exec.js-hosted module's main must never return: the host glue
|
||||
|
||||
@@ -28,11 +28,11 @@ import Config from "@site/src/components/Config.js";
|
||||
foreground: "#000000",
|
||||
background: "#ffffff",
|
||||
background_templates: [
|
||||
'{{if eq "very low" .Index}}#a3e635{{end}}',
|
||||
'{{if eq "low" .Index}}#bef264{{end}}',
|
||||
'{{if eq "moderate" .Index}}#fbbf24{{end}}',
|
||||
'{{if eq "high" .Index}}#ef4444{{end}}',
|
||||
'{{if eq "very high" .Index}}#dc2626{{end}}',
|
||||
'{{if eq "very low" .Index.String}}#a3e635{{end}}',
|
||||
'{{if eq "low" .Index.String}}#bef264{{end}}',
|
||||
'{{if eq "moderate" .Index.String}}#fbbf24{{end}}',
|
||||
'{{if eq "high" .Index.String}}#ef4444{{end}}',
|
||||
'{{if eq "very high" .Index.String}}#dc2626{{end}}',
|
||||
],
|
||||
template:
|
||||
" CO₂ {{ .Index.Icon }}{{ .Actual.String }} {{ .TrendIcon }} {{ .Forecast.String }} ",
|
||||
|
||||
+401
-41
@@ -126,7 +126,17 @@
|
||||
"TenantDisplayName": "",
|
||||
"TenantID": "00000000-0000-0000-0000-000000000000",
|
||||
"Text": " x ",
|
||||
"User": null
|
||||
"User": null,
|
||||
"environmentName": "AzureCloud",
|
||||
"homeTenantId": "00000000-0000-0000-0000-000000000000",
|
||||
"id": "00000000-0000-0000-0000-000000000000",
|
||||
"isDefault": false,
|
||||
"managedByTenants": null,
|
||||
"name": "Contoso Production",
|
||||
"state": "",
|
||||
"tenantDisplayName": "",
|
||||
"tenantId": "00000000-0000-0000-0000-000000000000",
|
||||
"user": null
|
||||
},
|
||||
"enabled": true
|
||||
},
|
||||
@@ -138,7 +148,9 @@
|
||||
"Text": " x "
|
||||
},
|
||||
"Text": " x ",
|
||||
"Version": 1
|
||||
"Version": 1,
|
||||
"defaultEnvironment": "zava-prod",
|
||||
"version": 1
|
||||
},
|
||||
"enabled": true
|
||||
},
|
||||
@@ -181,7 +193,17 @@
|
||||
"TenantDisplayName": "",
|
||||
"TenantID": "00000000-0000-0000-0000-000000000000",
|
||||
"Text": " x ",
|
||||
"User": null
|
||||
"User": null,
|
||||
"environmentName": "AzureCloud",
|
||||
"homeTenantId": "00000000-0000-0000-0000-000000000000",
|
||||
"id": "00000000-0000-0000-0000-000000000000",
|
||||
"isDefault": false,
|
||||
"managedByTenants": null,
|
||||
"name": "Contoso Production",
|
||||
"state": "",
|
||||
"tenantDisplayName": "",
|
||||
"tenantId": "00000000-0000-0000-0000-000000000000",
|
||||
"user": null
|
||||
},
|
||||
"enabled": true
|
||||
},
|
||||
@@ -197,7 +219,12 @@
|
||||
"State": 3,
|
||||
"Text": " x "
|
||||
},
|
||||
"enabled": true
|
||||
"enabled": true,
|
||||
"methods": {
|
||||
"State": {
|
||||
"String": "Charging"
|
||||
}
|
||||
}
|
||||
},
|
||||
"bazel": {
|
||||
"data": {
|
||||
@@ -239,7 +266,8 @@
|
||||
"Reading": null,
|
||||
"ReadingAge": 3,
|
||||
"Recipe": {
|
||||
"Name": "Contoso Pale Ale"
|
||||
"Name": "Contoso Pale Ale",
|
||||
"name": "Contoso Pale Ale"
|
||||
},
|
||||
"Segment": {
|
||||
"Index": 10,
|
||||
@@ -250,7 +278,20 @@
|
||||
"TemperatureTrend": 0,
|
||||
"TemperatureTrendIcon": "→",
|
||||
"Text": " x ",
|
||||
"URL": ""
|
||||
"URL": "",
|
||||
"batchNo": 42,
|
||||
"bottlingDate": 0,
|
||||
"brewDate": 0,
|
||||
"fermentationStartDate": 0,
|
||||
"measuredAbv": 5.4,
|
||||
"measuredFg": 0,
|
||||
"measuredOg": 0,
|
||||
"name": "Batch 42",
|
||||
"recipe": {
|
||||
"Name": "Contoso Pale Ale",
|
||||
"name": "Contoso Pale Ale"
|
||||
},
|
||||
"status": "Fermenting"
|
||||
},
|
||||
"enabled": true
|
||||
},
|
||||
@@ -308,9 +349,24 @@
|
||||
"Text": " x "
|
||||
},
|
||||
"Text": " x ",
|
||||
"TrendIcon": "↘"
|
||||
"TrendIcon": "↘",
|
||||
"actual": 132,
|
||||
"forecast": 148,
|
||||
"index": "moderate"
|
||||
},
|
||||
"enabled": true
|
||||
"enabled": true,
|
||||
"methods": {
|
||||
"Actual": {
|
||||
"String": "132"
|
||||
},
|
||||
"Forecast": {
|
||||
"String": "148"
|
||||
},
|
||||
"Index": {
|
||||
"Icon": "•",
|
||||
"String": "moderate"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cds": {
|
||||
"data": {
|
||||
@@ -381,14 +437,25 @@
|
||||
"RemainingPercentage": null,
|
||||
"TotalInputTokens": 68000,
|
||||
"TotalOutputTokens": 4200,
|
||||
"UsedPercentage": 42
|
||||
"UsedPercentage": 42,
|
||||
"context_window_size": 200000,
|
||||
"current_usage": null,
|
||||
"remaining_percentage": null,
|
||||
"total_input_tokens": 68000,
|
||||
"total_output_tokens": 4200,
|
||||
"used_percentage": 42
|
||||
},
|
||||
"Cost": {
|
||||
"TotalAPIDurationMS": 0,
|
||||
"TotalCostUSD": 1.82,
|
||||
"TotalDurationMS": 734000,
|
||||
"TotalLinesAdded": 118,
|
||||
"TotalLinesRemoved": 26
|
||||
"TotalLinesRemoved": 26,
|
||||
"total_api_duration_ms": 0,
|
||||
"total_cost_usd": 1.82,
|
||||
"total_duration_ms": 734000,
|
||||
"total_lines_added": 118,
|
||||
"total_lines_removed": 26
|
||||
},
|
||||
"Effort": null,
|
||||
"Exceeds200KTokens": false,
|
||||
@@ -403,7 +470,9 @@
|
||||
"FormattedTokens": "72.2K",
|
||||
"Model": {
|
||||
"DisplayName": "Opus 4.5",
|
||||
"ID": "claude-opus-4-5"
|
||||
"ID": "claude-opus-4-5",
|
||||
"display_name": "Opus 4.5",
|
||||
"id": "claude-opus-4-5"
|
||||
},
|
||||
"OutputStyle": null,
|
||||
"PR": null,
|
||||
@@ -432,9 +501,74 @@
|
||||
"CurrentDir": "~/dev/oh-my-posh",
|
||||
"GitWorktree": "",
|
||||
"ProjectDir": "~/dev/oh-my-posh",
|
||||
"Repo": null
|
||||
"Repo": null,
|
||||
"added_dirs": null,
|
||||
"current_dir": "~/dev/oh-my-posh",
|
||||
"git_worktree": "",
|
||||
"project_dir": "~/dev/oh-my-posh",
|
||||
"repo": null
|
||||
},
|
||||
"Worktree": null
|
||||
"Worktree": null,
|
||||
"agent": null,
|
||||
"context_window": {
|
||||
"ContextWindowSize": 200000,
|
||||
"CurrentUsage": null,
|
||||
"RemainingPercentage": null,
|
||||
"TotalInputTokens": 68000,
|
||||
"TotalOutputTokens": 4200,
|
||||
"UsedPercentage": 42,
|
||||
"context_window_size": 200000,
|
||||
"current_usage": null,
|
||||
"remaining_percentage": null,
|
||||
"total_input_tokens": 68000,
|
||||
"total_output_tokens": 4200,
|
||||
"used_percentage": 42
|
||||
},
|
||||
"cost": {
|
||||
"TotalAPIDurationMS": 0,
|
||||
"TotalCostUSD": 1.82,
|
||||
"TotalDurationMS": 734000,
|
||||
"TotalLinesAdded": 118,
|
||||
"TotalLinesRemoved": 26,
|
||||
"total_api_duration_ms": 0,
|
||||
"total_cost_usd": 1.82,
|
||||
"total_duration_ms": 734000,
|
||||
"total_lines_added": 118,
|
||||
"total_lines_removed": 26
|
||||
},
|
||||
"cwd": "~/dev/oh-my-posh",
|
||||
"effort": null,
|
||||
"exceeds_200k_tokens": false,
|
||||
"fast_mode": false,
|
||||
"model": {
|
||||
"DisplayName": "Opus 4.5",
|
||||
"ID": "claude-opus-4-5",
|
||||
"display_name": "Opus 4.5",
|
||||
"id": "claude-opus-4-5"
|
||||
},
|
||||
"output_style": null,
|
||||
"pr": null,
|
||||
"prompt_id": "",
|
||||
"rate_limits": null,
|
||||
"session_id": "a1b2c3d4-e5f6-4789-b012-3456789abcde",
|
||||
"session_name": "oh-my-posh",
|
||||
"thinking": null,
|
||||
"transcript_path": "",
|
||||
"version": "2.1.0",
|
||||
"vim": null,
|
||||
"workspace": {
|
||||
"AddedDirs": null,
|
||||
"CurrentDir": "~/dev/oh-my-posh",
|
||||
"GitWorktree": "",
|
||||
"ProjectDir": "~/dev/oh-my-posh",
|
||||
"Repo": null,
|
||||
"added_dirs": null,
|
||||
"current_dir": "~/dev/oh-my-posh",
|
||||
"git_worktree": "",
|
||||
"project_dir": "~/dev/oh-my-posh",
|
||||
"repo": null
|
||||
},
|
||||
"worktree": null
|
||||
},
|
||||
"enabled": true
|
||||
},
|
||||
@@ -505,29 +639,119 @@
|
||||
"Percent": 34,
|
||||
"Remaining": 66,
|
||||
"Unlimited": false,
|
||||
"Used": 34
|
||||
"Used": 34,
|
||||
"limit": 100,
|
||||
"percent": 34,
|
||||
"remaining": 66,
|
||||
"unlimited": false,
|
||||
"used": 34
|
||||
},
|
||||
"Inline": {
|
||||
"Limit": 0,
|
||||
"Percent": 0,
|
||||
"Remaining": 100,
|
||||
"Unlimited": true,
|
||||
"Used": 0
|
||||
"Used": 0,
|
||||
"limit": 0,
|
||||
"percent": 0,
|
||||
"remaining": 100,
|
||||
"unlimited": true,
|
||||
"used": 0
|
||||
},
|
||||
"Premium": {
|
||||
"Limit": 300,
|
||||
"Percent": 40,
|
||||
"Remaining": 60,
|
||||
"Unlimited": false,
|
||||
"Used": 120
|
||||
"Used": 120,
|
||||
"limit": 300,
|
||||
"percent": 40,
|
||||
"remaining": 60,
|
||||
"unlimited": false,
|
||||
"used": 120
|
||||
},
|
||||
"Segment": {
|
||||
"Index": 21,
|
||||
"Text": " x "
|
||||
},
|
||||
"Text": " x "
|
||||
"Text": " x ",
|
||||
"billing_cycle_end": "2026-08-01",
|
||||
"chat": {
|
||||
"Limit": 100,
|
||||
"Percent": 34,
|
||||
"Remaining": 66,
|
||||
"Unlimited": false,
|
||||
"Used": 34,
|
||||
"limit": 100,
|
||||
"percent": 34,
|
||||
"remaining": 66,
|
||||
"unlimited": false,
|
||||
"used": 34
|
||||
},
|
||||
"inline": {
|
||||
"Limit": 0,
|
||||
"Percent": 0,
|
||||
"Remaining": 100,
|
||||
"Unlimited": true,
|
||||
"Used": 0,
|
||||
"limit": 0,
|
||||
"percent": 0,
|
||||
"remaining": 100,
|
||||
"unlimited": true,
|
||||
"used": 0
|
||||
},
|
||||
"premium": {
|
||||
"Limit": 300,
|
||||
"Percent": 40,
|
||||
"Remaining": 60,
|
||||
"Unlimited": false,
|
||||
"Used": 120,
|
||||
"limit": 300,
|
||||
"percent": 40,
|
||||
"remaining": 60,
|
||||
"unlimited": false,
|
||||
"used": 120
|
||||
}
|
||||
},
|
||||
"enabled": true
|
||||
"enabled": true,
|
||||
"methods": {
|
||||
"Chat": {
|
||||
"Percent": {
|
||||
"Gauge": "▰▰▰▱▱",
|
||||
"GaugeUsed": "▰▱▱▱▱",
|
||||
"String": "34"
|
||||
},
|
||||
"Remaining": {
|
||||
"Gauge": "▰▱▱▱▱",
|
||||
"GaugeUsed": "▰▰▰▱▱",
|
||||
"String": "66"
|
||||
}
|
||||
},
|
||||
"Inline": {
|
||||
"Percent": {
|
||||
"Gauge": "▰▰▰▰▰",
|
||||
"GaugeUsed": "▱▱▱▱▱",
|
||||
"String": "0"
|
||||
},
|
||||
"Remaining": {
|
||||
"Gauge": "▱▱▱▱▱",
|
||||
"GaugeUsed": "▰▰▰▰▰",
|
||||
"String": "100"
|
||||
}
|
||||
},
|
||||
"Premium": {
|
||||
"Percent": {
|
||||
"Gauge": "▰▰▰▱▱",
|
||||
"GaugeUsed": "▰▰▱▱▱",
|
||||
"String": "40"
|
||||
},
|
||||
"Remaining": {
|
||||
"Gauge": "▰▰▱▱▱",
|
||||
"GaugeUsed": "▰▰▰▱▱",
|
||||
"String": "60"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"copilot_cli": {
|
||||
"data": {
|
||||
@@ -545,26 +769,47 @@
|
||||
"TotalOutputTokens": 0,
|
||||
"TotalReasoningTokens": 0,
|
||||
"TotalTokens": 36000,
|
||||
"UsedPercentage": 18
|
||||
"UsedPercentage": 18,
|
||||
"context_window_size": 200000,
|
||||
"current_context_tokens": 36000,
|
||||
"last_call_input_tokens": 0,
|
||||
"last_call_output_tokens": 0,
|
||||
"remaining_percentage": null,
|
||||
"remaining_tokens": null,
|
||||
"total_cache_read_tokens": 0,
|
||||
"total_cache_write_tokens": 0,
|
||||
"total_input_tokens": 0,
|
||||
"total_output_tokens": 0,
|
||||
"total_reasoning_tokens": 0,
|
||||
"total_tokens": 36000,
|
||||
"used_percentage": 18
|
||||
},
|
||||
"Cost": {
|
||||
"TotalAPIDurationMS": 0,
|
||||
"TotalDurationMS": 214000,
|
||||
"TotalLinesAdded": 9,
|
||||
"TotalLinesRemoved": 2,
|
||||
"TotalPremiumRequests": 3
|
||||
"TotalPremiumRequests": 3,
|
||||
"total_api_duration_ms": 0,
|
||||
"total_duration_ms": 214000,
|
||||
"total_lines_added": 9,
|
||||
"total_lines_removed": 2,
|
||||
"total_premium_requests": 3
|
||||
},
|
||||
"FormattedAPIDuration": "0m 0s",
|
||||
"FormattedDuration": "3m 34s",
|
||||
"FormattedTokens": "36.0K",
|
||||
"Model": {
|
||||
"DisplayName": "Sonnet 4.5",
|
||||
"ID": "claude-sonnet-4-5"
|
||||
"ID": "claude-sonnet-4-5",
|
||||
"display_name": "Sonnet 4.5",
|
||||
"id": "claude-sonnet-4-5"
|
||||
},
|
||||
"RemainingPercent": 82,
|
||||
"RemainingTokensCount": 164000,
|
||||
"Remote": {
|
||||
"Connected": false
|
||||
"Connected": false,
|
||||
"connected": false
|
||||
},
|
||||
"Segment": {
|
||||
"Index": 22,
|
||||
@@ -580,7 +825,68 @@
|
||||
"Username": "alice",
|
||||
"Version": "0.0.339",
|
||||
"Workspace": {
|
||||
"CurrentDir": "~/dev/oh-my-posh"
|
||||
"CurrentDir": "~/dev/oh-my-posh",
|
||||
"current_dir": "~/dev/oh-my-posh"
|
||||
},
|
||||
"context_window": {
|
||||
"ContextWindowSize": 200000,
|
||||
"CurrentContextTokens": 36000,
|
||||
"LastCallInputTokens": 0,
|
||||
"LastCallOutputTokens": 0,
|
||||
"RemainingPercentage": null,
|
||||
"RemainingTokens": null,
|
||||
"TotalCacheReadTokens": 0,
|
||||
"TotalCacheWriteTokens": 0,
|
||||
"TotalInputTokens": 0,
|
||||
"TotalOutputTokens": 0,
|
||||
"TotalReasoningTokens": 0,
|
||||
"TotalTokens": 36000,
|
||||
"UsedPercentage": 18,
|
||||
"context_window_size": 200000,
|
||||
"current_context_tokens": 36000,
|
||||
"last_call_input_tokens": 0,
|
||||
"last_call_output_tokens": 0,
|
||||
"remaining_percentage": null,
|
||||
"remaining_tokens": null,
|
||||
"total_cache_read_tokens": 0,
|
||||
"total_cache_write_tokens": 0,
|
||||
"total_input_tokens": 0,
|
||||
"total_output_tokens": 0,
|
||||
"total_reasoning_tokens": 0,
|
||||
"total_tokens": 36000,
|
||||
"used_percentage": 18
|
||||
},
|
||||
"cost": {
|
||||
"TotalAPIDurationMS": 0,
|
||||
"TotalDurationMS": 214000,
|
||||
"TotalLinesAdded": 9,
|
||||
"TotalLinesRemoved": 2,
|
||||
"TotalPremiumRequests": 3,
|
||||
"total_api_duration_ms": 0,
|
||||
"total_duration_ms": 214000,
|
||||
"total_lines_added": 9,
|
||||
"total_lines_removed": 2,
|
||||
"total_premium_requests": 3
|
||||
},
|
||||
"cwd": "~/dev/oh-my-posh",
|
||||
"model": {
|
||||
"DisplayName": "Sonnet 4.5",
|
||||
"ID": "claude-sonnet-4-5",
|
||||
"display_name": "Sonnet 4.5",
|
||||
"id": "claude-sonnet-4-5"
|
||||
},
|
||||
"remote": {
|
||||
"Connected": false,
|
||||
"connected": false
|
||||
},
|
||||
"session_id": "f6e5d4c3-b2a1-4098-9876-543210fedcba",
|
||||
"session_name": "oh-my-posh",
|
||||
"transcript_path": "",
|
||||
"username": "alice",
|
||||
"version": "0.0.339",
|
||||
"workspace": {
|
||||
"CurrentDir": "~/dev/oh-my-posh",
|
||||
"current_dir": "~/dev/oh-my-posh"
|
||||
}
|
||||
},
|
||||
"enabled": true
|
||||
@@ -1375,7 +1681,14 @@
|
||||
"Text": " x ",
|
||||
"Time": "Q3 6:42"
|
||||
},
|
||||
"enabled": true
|
||||
"enabled": true,
|
||||
"methods": {
|
||||
"GameStatus": {
|
||||
"Int": 2,
|
||||
"String": "In Progress",
|
||||
"Valid": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"nbgv": {
|
||||
"data": {
|
||||
@@ -1413,7 +1726,18 @@
|
||||
"Trend": 4,
|
||||
"TrendIcon": "",
|
||||
"Type": "sgv",
|
||||
"UtcOffset": 60
|
||||
"UtcOffset": 60,
|
||||
"_id": "",
|
||||
"date": 1782500000000,
|
||||
"dateString": "2026-07-28T09:33:20Z",
|
||||
"device": "xDrip-DexcomG6",
|
||||
"direction": "Flat",
|
||||
"mills": 1782500000000,
|
||||
"sgv": 118,
|
||||
"sysTime": "2026-07-28T09:33:20Z",
|
||||
"trend": 4,
|
||||
"type": "sgv",
|
||||
"utcOffset": 60
|
||||
},
|
||||
"enabled": true
|
||||
},
|
||||
@@ -1595,7 +1919,12 @@
|
||||
"Text": " x ",
|
||||
"Writable": true
|
||||
},
|
||||
"enabled": true
|
||||
"enabled": true,
|
||||
"methods": {
|
||||
"Folders": {
|
||||
"List": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"perl": {
|
||||
"data": {
|
||||
@@ -1722,7 +2051,9 @@
|
||||
"Stack": "contoso-prod",
|
||||
"Text": " x ",
|
||||
"URL": "app.pulumi.com/contoso",
|
||||
"User": "alice"
|
||||
"User": "alice",
|
||||
"url": "app.pulumi.com/contoso",
|
||||
"user": "alice"
|
||||
},
|
||||
"enabled": true
|
||||
},
|
||||
@@ -1753,7 +2084,9 @@
|
||||
"data": {
|
||||
"AppVite": {
|
||||
"Dev": true,
|
||||
"Version": "1.9.3"
|
||||
"Version": "1.9.3",
|
||||
"dev": true,
|
||||
"version": "1.9.3"
|
||||
},
|
||||
"BuildMetadata": "",
|
||||
"Error": "",
|
||||
@@ -1775,7 +2108,9 @@
|
||||
"URL": "",
|
||||
"Vite": {
|
||||
"Dev": true,
|
||||
"Version": "5.4.11"
|
||||
"Version": "5.4.11",
|
||||
"dev": true,
|
||||
"version": "5.4.11"
|
||||
}
|
||||
},
|
||||
"enabled": true
|
||||
@@ -2019,7 +2354,19 @@
|
||||
"Text": " x ",
|
||||
"Type": "Ride",
|
||||
"URL": "",
|
||||
"WeightedAverageWatts": 0
|
||||
"WeightedAverageWatts": 0,
|
||||
"average_heartrate": 0,
|
||||
"average_watts": 0,
|
||||
"device_watts": false,
|
||||
"distance": 0,
|
||||
"id": 0,
|
||||
"kudos_count": 0,
|
||||
"max_heartrate": 0,
|
||||
"moving_time": 0,
|
||||
"name": "Morning Ride",
|
||||
"start_date": "0001-01-01T00:00:00Z",
|
||||
"type": "Ride",
|
||||
"weighted_average_watts": 0
|
||||
},
|
||||
"enabled": true
|
||||
},
|
||||
@@ -2181,7 +2528,8 @@
|
||||
},
|
||||
"Text": " x ",
|
||||
"Version": "1.9.0",
|
||||
"WorkspaceName": "example_corp-prod"
|
||||
"WorkspaceName": "example_corp-prod",
|
||||
"terraform_version": "1.9.0"
|
||||
},
|
||||
"enabled": true
|
||||
},
|
||||
@@ -2197,7 +2545,7 @@
|
||||
},
|
||||
"time": {
|
||||
"data": {
|
||||
"CurrentDate": "2026-03-17T09:41:00+01:00",
|
||||
"CurrentDate": "2026-03-17T09:41:00Z",
|
||||
"Format": "",
|
||||
"Segment": {
|
||||
"Index": 98,
|
||||
@@ -2286,7 +2634,9 @@
|
||||
"Text": " x "
|
||||
},
|
||||
"Text": " x ",
|
||||
"Version": ""
|
||||
"Version": "",
|
||||
"current": "26.7.1",
|
||||
"latest": "26.8.0"
|
||||
},
|
||||
"enabled": true
|
||||
},
|
||||
@@ -2350,7 +2700,9 @@
|
||||
"data": {
|
||||
"CumulativeTotal": {
|
||||
"Seconds": 16200,
|
||||
"Text": "4 hrs 30 mins"
|
||||
"Text": "4 hrs 30 mins",
|
||||
"seconds": 16200,
|
||||
"text": "4 hrs 30 mins"
|
||||
},
|
||||
"End": "2026-07-09T23:59:59Z",
|
||||
"Segment": {
|
||||
@@ -2358,7 +2710,15 @@
|
||||
"Text": " x "
|
||||
},
|
||||
"Start": "2026-07-09T00:00:00Z",
|
||||
"Text": " x "
|
||||
"Text": " x ",
|
||||
"cumulative_total": {
|
||||
"Seconds": 16200,
|
||||
"Text": "4 hrs 30 mins",
|
||||
"seconds": 16200,
|
||||
"text": "4 hrs 30 mins"
|
||||
},
|
||||
"end": "2026-07-09T23:59:59Z",
|
||||
"start": "2026-07-09T00:00:00Z"
|
||||
},
|
||||
"enabled": true
|
||||
},
|
||||
@@ -2372,16 +2732,16 @@
|
||||
"UpdateCount": 2,
|
||||
"Updates": [
|
||||
{
|
||||
"Name": "Microsoft Edge",
|
||||
"ID": "Microsoft.Edge",
|
||||
"Available": "131.0.2903.51",
|
||||
"Current": "130.0.2849.68",
|
||||
"Available": "131.0.2903.51"
|
||||
"ID": "Microsoft.Edge",
|
||||
"Name": "Microsoft Edge"
|
||||
},
|
||||
{
|
||||
"Name": "Git",
|
||||
"ID": "Git.Git",
|
||||
"Available": "2.47.1",
|
||||
"Current": "2.47.0",
|
||||
"Available": "2.47.1"
|
||||
"ID": "Git.Git",
|
||||
"Name": "Git"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user