@rosetears/aili-pi 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -6
- package/THIRD_PARTY_NOTICES.md +14 -3
- package/extensions/header/index.ts +92 -0
- package/extensions/matrix/index.ts +375 -0
- package/extensions/zentui/config.ts +1014 -0
- package/extensions/zentui/extension-status.ts +96 -0
- package/extensions/zentui/fixed-editor/cluster.ts +98 -0
- package/extensions/zentui/fixed-editor/compositor.ts +719 -0
- package/extensions/zentui/fixed-editor/index.ts +223 -0
- package/extensions/zentui/fixed-editor/input.ts +85 -0
- package/extensions/zentui/fixed-editor/pi-compat.ts +296 -0
- package/extensions/zentui/fixed-editor/selection.ts +217 -0
- package/extensions/zentui/fixed-editor/terminal-modes.ts +75 -0
- package/extensions/zentui/fixed-editor/types.ts +37 -0
- package/extensions/zentui/footer-format.ts +279 -0
- package/extensions/zentui/footer.ts +595 -0
- package/extensions/zentui/format.ts +434 -0
- package/extensions/zentui/git.ts +384 -0
- package/extensions/zentui/gradient.ts +70 -0
- package/extensions/zentui/icons.ts +252 -0
- package/extensions/zentui/index.ts +580 -0
- package/extensions/zentui/live-context.ts +75 -0
- package/extensions/zentui/package-version.ts +650 -0
- package/extensions/zentui/project-refresh.ts +104 -0
- package/extensions/zentui/project-state.ts +59 -0
- package/extensions/zentui/prototype-patch-registry.ts +111 -0
- package/extensions/zentui/runtime.ts +841 -0
- package/extensions/zentui/selector-border.ts +77 -0
- package/extensions/zentui/session-lifecycle.ts +60 -0
- package/extensions/zentui/settings-command.ts +897 -0
- package/extensions/zentui/state.ts +55 -0
- package/extensions/zentui/style.ts +332 -0
- package/extensions/zentui/thinking-message.ts +159 -0
- package/extensions/zentui/tool-execution.ts +189 -0
- package/extensions/zentui/ui.ts +618 -0
- package/extensions/zentui/user-message.ts +252 -0
- package/licenses/pi-sakura-cyberdeck-MIT.txt +21 -0
- package/licenses/pi-zentui-MIT.txt +21 -0
- package/manifests/capabilities.json +1 -1
- package/manifests/live-verification.json +14 -8
- package/manifests/provenance.json +18 -5
- package/manifests/sbom.json +19 -3
- package/notices/pi-sakura-cyberdeck-NOTICE.txt +6 -0
- package/package.json +12 -3
- package/scripts/local-package-e2e.ts +1 -1
- package/scripts/sync-global-skills.d.mts +13 -0
- package/scripts/sync-global-skills.mjs +134 -0
- package/src/runtime/credential-guard.ts +58 -0
- package/src/runtime/doctor.ts +1 -1
- package/src/runtime/index.ts +2 -2
- package/src/runtime/path-boundaries.ts +1 -1
- package/src/runtime/registry.ts +6 -2
- package/src/runtime/rem-head.txt +38 -0
- package/src/runtime/rose-context.ts +1 -1
- package/src/runtime/subagents.ts +74 -403
- package/templates/APPEND_SYSTEM.md +10 -8
- package/themes/rem-cyberdeck.json +32 -0
- package/upstream/opencode-global-agents.lock.json +34 -0
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import type { PolishedTuiConfig, SeparatorStyle } from "./config.js";
|
|
4
|
+
import { FOOTER_FORMAT_ALIASES } from "./config.js";
|
|
5
|
+
import { collectExtensionStatusSegments, type ExtensionStatusSegment } from "./extension-status.js";
|
|
6
|
+
import { parseFooterFormat, renderFormatSplit, stripOrphanSeparators } from "./footer-format.js";
|
|
7
|
+
import {
|
|
8
|
+
buildContextDisplayLabel,
|
|
9
|
+
buildSessionDurationLabel,
|
|
10
|
+
contextColorTier,
|
|
11
|
+
formatCwdLabel,
|
|
12
|
+
formatGitBranchText,
|
|
13
|
+
formatGitCommitSegment,
|
|
14
|
+
formatGitMetricsSegment,
|
|
15
|
+
formatOsLabel,
|
|
16
|
+
formatPackageVersionSegment,
|
|
17
|
+
formatRuntimeSegment,
|
|
18
|
+
formatTimeLabel,
|
|
19
|
+
formatUsernameHostLabel,
|
|
20
|
+
} from "./format.js";
|
|
21
|
+
import { resolveRuntimeSymbol } from "./icons.js";
|
|
22
|
+
import type { LiveContextOverride } from "./live-context.js";
|
|
23
|
+
import type { FooterState } from "./state.js";
|
|
24
|
+
import { renderStyleForSource } from "./style.js";
|
|
25
|
+
|
|
26
|
+
const separatorText: Record<SeparatorStyle, string> = {
|
|
27
|
+
pipe: " | ",
|
|
28
|
+
dot: " · ",
|
|
29
|
+
chevron: " › ",
|
|
30
|
+
none: " ",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function joinStatusTexts(statusTexts: string[], separator: string): string {
|
|
34
|
+
return statusTexts.filter(Boolean).join(separator);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function fitStatusTexts(statusTexts: string[], maxWidth: number, separator: string): string {
|
|
38
|
+
if (maxWidth <= 0) return "";
|
|
39
|
+
|
|
40
|
+
const fitted: string[] = [];
|
|
41
|
+
for (const text of statusTexts) {
|
|
42
|
+
const candidate = joinStatusTexts([...fitted, text], separator);
|
|
43
|
+
if (visibleWidth(candidate) <= maxWidth) {
|
|
44
|
+
fitted.push(text);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (fitted.length === 0) {
|
|
49
|
+
return maxWidth > 1 ? truncateToWidth(text, maxWidth, "…") : "";
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return joinStatusTexts(fitted, separator);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function appendStatusArea(base: string, statusText: string, separator: string): string {
|
|
58
|
+
if (!base) return statusText;
|
|
59
|
+
if (!statusText) return base;
|
|
60
|
+
return `${base}${separator}${statusText}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function prependStatusArea(base: string, statusText: string, separator: string): string {
|
|
64
|
+
if (!base) return statusText;
|
|
65
|
+
if (!statusText) return base;
|
|
66
|
+
return `${statusText}${separator}${base}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function composeBuiltInFooterContent(left: string, right: string, innerWidth: number): string {
|
|
70
|
+
const leftWidth = visibleWidth(left);
|
|
71
|
+
const rightWidth = visibleWidth(right);
|
|
72
|
+
return leftWidth >= innerWidth
|
|
73
|
+
? truncateToWidth(left, innerWidth, "")
|
|
74
|
+
: leftWidth + 1 + rightWidth <= innerWidth
|
|
75
|
+
? `${left}${" ".repeat(innerWidth - leftWidth - rightWidth)}${right}`
|
|
76
|
+
: truncateToWidth(left, innerWidth, "");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function composeFooterContent(
|
|
80
|
+
builtInLeft: string,
|
|
81
|
+
builtInRight: string,
|
|
82
|
+
extensionLeft: string[],
|
|
83
|
+
extensionMiddle: string[],
|
|
84
|
+
extensionRight: string[],
|
|
85
|
+
separator: string,
|
|
86
|
+
innerWidth: number,
|
|
87
|
+
): string {
|
|
88
|
+
const builtInLeftWidth = visibleWidth(builtInLeft);
|
|
89
|
+
const builtInRightWidth = visibleWidth(builtInRight);
|
|
90
|
+
const minimumGap = builtInLeft && builtInRight ? 1 : 0;
|
|
91
|
+
|
|
92
|
+
if (builtInLeftWidth + minimumGap + builtInRightWidth > innerWidth) {
|
|
93
|
+
return composeBuiltInFooterContent(builtInLeft, builtInRight, innerWidth);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const available = Math.max(0, innerWidth - builtInLeftWidth - builtInRightWidth - minimumGap);
|
|
97
|
+
let remaining = available;
|
|
98
|
+
const leftConnectorWidth = builtInLeft && extensionLeft.length > 0 ? visibleWidth(separator) : 0;
|
|
99
|
+
const rightConnectorWidth =
|
|
100
|
+
builtInRight && extensionRight.length > 0 ? visibleWidth(separator) : 0;
|
|
101
|
+
let leftStatus = "";
|
|
102
|
+
let rightStatus = "";
|
|
103
|
+
|
|
104
|
+
if (extensionLeft.length > 0 && extensionRight.length > 0) {
|
|
105
|
+
const leftBudget = Math.max(0, Math.floor(available / 2) - leftConnectorWidth);
|
|
106
|
+
leftStatus = fitStatusTexts(extensionLeft, leftBudget, separator);
|
|
107
|
+
remaining -= leftStatus ? leftConnectorWidth + visibleWidth(leftStatus) : 0;
|
|
108
|
+
|
|
109
|
+
const rightBudget = Math.max(0, remaining - rightConnectorWidth);
|
|
110
|
+
rightStatus = fitStatusTexts(extensionRight, rightBudget, separator);
|
|
111
|
+
remaining -= rightStatus ? rightConnectorWidth + visibleWidth(rightStatus) : 0;
|
|
112
|
+
|
|
113
|
+
const expandedLeftBudget = Math.max(0, remaining + visibleWidth(leftStatus));
|
|
114
|
+
const expandedLeftStatus = fitStatusTexts(extensionLeft, expandedLeftBudget, separator);
|
|
115
|
+
if (visibleWidth(expandedLeftStatus) > visibleWidth(leftStatus)) {
|
|
116
|
+
remaining += leftStatus ? leftConnectorWidth + visibleWidth(leftStatus) : 0;
|
|
117
|
+
leftStatus = expandedLeftStatus;
|
|
118
|
+
remaining -= leftStatus ? leftConnectorWidth + visibleWidth(leftStatus) : 0;
|
|
119
|
+
}
|
|
120
|
+
} else if (extensionLeft.length > 0) {
|
|
121
|
+
leftStatus = fitStatusTexts(
|
|
122
|
+
extensionLeft,
|
|
123
|
+
Math.max(0, available - leftConnectorWidth),
|
|
124
|
+
separator,
|
|
125
|
+
);
|
|
126
|
+
remaining -= leftStatus ? leftConnectorWidth + visibleWidth(leftStatus) : 0;
|
|
127
|
+
} else if (extensionRight.length > 0) {
|
|
128
|
+
rightStatus = fitStatusTexts(
|
|
129
|
+
extensionRight,
|
|
130
|
+
Math.max(0, available - rightConnectorWidth),
|
|
131
|
+
separator,
|
|
132
|
+
);
|
|
133
|
+
remaining -= rightStatus ? rightConnectorWidth + visibleWidth(rightStatus) : 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const left = appendStatusArea(builtInLeft, leftStatus, separator);
|
|
137
|
+
const right = prependStatusArea(builtInRight, rightStatus, separator);
|
|
138
|
+
const gapWidth = Math.max(0, innerWidth - visibleWidth(left) - visibleWidth(right));
|
|
139
|
+
const middle = fitStatusTexts(extensionMiddle, gapWidth, separator);
|
|
140
|
+
const middleWidth = visibleWidth(middle);
|
|
141
|
+
|
|
142
|
+
if (!middle || middleWidth <= 0) {
|
|
143
|
+
return `${left}${" ".repeat(gapWidth)}${right}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const leftPadding = Math.floor((gapWidth - middleWidth) / 2);
|
|
147
|
+
const rightPadding = gapWidth - middleWidth - leftPadding;
|
|
148
|
+
return `${left}${" ".repeat(leftPadding)}${middle}${" ".repeat(rightPadding)}${right}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function installFooter(
|
|
152
|
+
ctx: ExtensionContext,
|
|
153
|
+
state: FooterState,
|
|
154
|
+
getConfig: () => PolishedTuiConfig,
|
|
155
|
+
hooks: {
|
|
156
|
+
setRequestRender: (fn: (() => void) | undefined) => void;
|
|
157
|
+
scheduleProjectRefresh: (ctx: ExtensionContext) => void;
|
|
158
|
+
setExtensionStatusesGetter?: (fn: (() => ReadonlyMap<string, string>) | undefined) => void;
|
|
159
|
+
getLiveContext?: () => LiveContextOverride | undefined;
|
|
160
|
+
},
|
|
161
|
+
): void {
|
|
162
|
+
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
163
|
+
hooks.setRequestRender(() => tui.requestRender());
|
|
164
|
+
hooks.setExtensionStatusesGetter?.(() => footerData.getExtensionStatuses());
|
|
165
|
+
const unsubscribeBranch = footerData.onBranchChange(() => {
|
|
166
|
+
hooks.scheduleProjectRefresh(ctx);
|
|
167
|
+
tui.requestRender();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
dispose: () => {
|
|
172
|
+
unsubscribeBranch();
|
|
173
|
+
hooks.setRequestRender(undefined);
|
|
174
|
+
hooks.setExtensionStatusesGetter?.(undefined);
|
|
175
|
+
},
|
|
176
|
+
invalidate() {},
|
|
177
|
+
render(width: number): string[] {
|
|
178
|
+
if (width <= 0) return [""];
|
|
179
|
+
const config = getConfig();
|
|
180
|
+
const colorSource = config.colorSources.starship;
|
|
181
|
+
const iconMode = config.icons.mode;
|
|
182
|
+
const separator = renderStyleForSource(
|
|
183
|
+
theme,
|
|
184
|
+
colorSource,
|
|
185
|
+
config.colors.separator,
|
|
186
|
+
separatorText[config.separator],
|
|
187
|
+
);
|
|
188
|
+
const innerWidth = Math.max(1, width - 2);
|
|
189
|
+
const cwdLabel = renderStyleForSource(
|
|
190
|
+
theme,
|
|
191
|
+
colorSource,
|
|
192
|
+
config.colors.cwd,
|
|
193
|
+
formatCwdLabel(ctx.cwd, config.icons.cwd, {
|
|
194
|
+
mode: config.pathDisplay.mode,
|
|
195
|
+
depth: config.pathDisplay.depth,
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
198
|
+
const branch = state.branch;
|
|
199
|
+
const branchText = branch
|
|
200
|
+
? formatGitBranchText(branch, config.gitBranch.maxLength)
|
|
201
|
+
: undefined;
|
|
202
|
+
const contextUsage = ctx.getContextUsage();
|
|
203
|
+
const liveContext = hooks.getLiveContext?.();
|
|
204
|
+
const contextWindow = ctx.model?.contextWindow ?? contextUsage?.contextWindow;
|
|
205
|
+
const useLiveContext =
|
|
206
|
+
liveContext !== undefined && contextWindow !== undefined && contextWindow > 0;
|
|
207
|
+
const contextPercent = useLiveContext
|
|
208
|
+
? (liveContext.tokens / contextWindow) * 100
|
|
209
|
+
: contextUsage?.percent;
|
|
210
|
+
const contextLabel = buildContextDisplayLabel({
|
|
211
|
+
percent: contextPercent,
|
|
212
|
+
contextWindow,
|
|
213
|
+
style: config.contextStyle,
|
|
214
|
+
asciiGauge: iconMode === "ascii",
|
|
215
|
+
});
|
|
216
|
+
const tier = contextColorTier(contextPercent, config.contextThresholds);
|
|
217
|
+
const contextColor =
|
|
218
|
+
tier === "error"
|
|
219
|
+
? config.colors.contextError
|
|
220
|
+
: tier === "warning"
|
|
221
|
+
? config.colors.contextWarning
|
|
222
|
+
: config.colors.contextNormal;
|
|
223
|
+
const gitColor = (text: string) =>
|
|
224
|
+
renderStyleForSource(theme, colorSource, config.colors.gitBranch, text);
|
|
225
|
+
const gitStatusColor = (text: string) =>
|
|
226
|
+
renderStyleForSource(theme, colorSource, config.colors.gitStatus, text);
|
|
227
|
+
const gitIcon = config.icons.git ? gitColor(config.icons.git) : "";
|
|
228
|
+
const gitCounts = config.footerSegments.gitCounts;
|
|
229
|
+
const stashLabel =
|
|
230
|
+
state.stashed > 0
|
|
231
|
+
? gitCounts
|
|
232
|
+
? `${config.icons.stashed}${state.stashed}`
|
|
233
|
+
: config.icons.stashed
|
|
234
|
+
: "";
|
|
235
|
+
const allStatus = [
|
|
236
|
+
state.conflicted > 0 ? config.icons.conflicted : "",
|
|
237
|
+
stashLabel,
|
|
238
|
+
state.deleted > 0 ? config.icons.deleted : "",
|
|
239
|
+
state.renamed > 0 ? config.icons.renamed : "",
|
|
240
|
+
state.modified > 0 ? config.icons.modified : "",
|
|
241
|
+
state.typechanged > 0 ? config.icons.typechanged : "",
|
|
242
|
+
state.staged > 0 ? config.icons.staged : "",
|
|
243
|
+
state.untracked > 0 ? config.icons.untracked : "",
|
|
244
|
+
].join("");
|
|
245
|
+
const aheadBehind = (() => {
|
|
246
|
+
if (state.ahead > 0 && state.behind > 0) {
|
|
247
|
+
return gitCounts
|
|
248
|
+
? `${config.icons.ahead}${state.ahead}${config.icons.behind}${state.behind}`
|
|
249
|
+
: config.icons.diverged;
|
|
250
|
+
}
|
|
251
|
+
if (state.ahead > 0)
|
|
252
|
+
return gitCounts ? `${config.icons.ahead}${state.ahead}` : config.icons.ahead;
|
|
253
|
+
if (state.behind > 0)
|
|
254
|
+
return gitCounts ? `${config.icons.behind}${state.behind}` : config.icons.behind;
|
|
255
|
+
return "";
|
|
256
|
+
})();
|
|
257
|
+
const statusBlock =
|
|
258
|
+
allStatus || aheadBehind ? gitStatusColor(`[${allStatus}${aheadBehind}]`) : "";
|
|
259
|
+
const gitStateLabel = state.gitStateLabel ?? "";
|
|
260
|
+
const gitStateBlock = gitStateLabel ? gitStatusColor(gitStateLabel) : "";
|
|
261
|
+
const renderVariable = (name: string): string => {
|
|
262
|
+
const canonical = FOOTER_FORMAT_ALIASES[name] ?? name;
|
|
263
|
+
switch (canonical) {
|
|
264
|
+
case "cwd":
|
|
265
|
+
return cwdLabel;
|
|
266
|
+
case "git_branch":
|
|
267
|
+
return branchText
|
|
268
|
+
? gitIcon
|
|
269
|
+
? `${gitIcon} ${gitColor(branchText)}`
|
|
270
|
+
: gitColor(branchText)
|
|
271
|
+
: "";
|
|
272
|
+
case "git_status":
|
|
273
|
+
return statusBlock;
|
|
274
|
+
case "git_state":
|
|
275
|
+
return gitStateBlock;
|
|
276
|
+
case "runtime": {
|
|
277
|
+
if (!state.runtime) return "";
|
|
278
|
+
const symbol = resolveRuntimeSymbol(
|
|
279
|
+
state.runtime.name,
|
|
280
|
+
state.runtime.symbol,
|
|
281
|
+
iconMode,
|
|
282
|
+
);
|
|
283
|
+
const label = state.runtime.version ? `${symbol} ${state.runtime.version}` : symbol;
|
|
284
|
+
return renderStyleForSource(theme, colorSource, state.runtime.style, label);
|
|
285
|
+
}
|
|
286
|
+
case "session_duration":
|
|
287
|
+
return state.sessionStartEpoch
|
|
288
|
+
? renderStyleForSource(
|
|
289
|
+
theme,
|
|
290
|
+
colorSource,
|
|
291
|
+
config.colors.sessionDuration,
|
|
292
|
+
buildSessionDurationLabel(state.sessionStartEpoch),
|
|
293
|
+
)
|
|
294
|
+
: "";
|
|
295
|
+
case "username":
|
|
296
|
+
return renderStyleForSource(
|
|
297
|
+
theme,
|
|
298
|
+
colorSource,
|
|
299
|
+
config.colors.username,
|
|
300
|
+
formatUsernameHostLabel(config.icons.username),
|
|
301
|
+
);
|
|
302
|
+
case "os":
|
|
303
|
+
return renderStyleForSource(
|
|
304
|
+
theme,
|
|
305
|
+
colorSource,
|
|
306
|
+
config.colors.os,
|
|
307
|
+
formatOsLabel(config.icons.os, iconMode),
|
|
308
|
+
);
|
|
309
|
+
case "time":
|
|
310
|
+
return renderStyleForSource(
|
|
311
|
+
theme,
|
|
312
|
+
colorSource,
|
|
313
|
+
config.colors.time,
|
|
314
|
+
formatTimeLabel(config.icons.time),
|
|
315
|
+
);
|
|
316
|
+
case "context":
|
|
317
|
+
return renderStyleForSource(theme, colorSource, contextColor, contextLabel);
|
|
318
|
+
case "tokens":
|
|
319
|
+
return renderStyleForSource(
|
|
320
|
+
theme,
|
|
321
|
+
colorSource,
|
|
322
|
+
config.colors.tokens,
|
|
323
|
+
state.tokenLabel,
|
|
324
|
+
);
|
|
325
|
+
case "cost":
|
|
326
|
+
return renderStyleForSource(theme, colorSource, config.colors.cost, state.costLabel);
|
|
327
|
+
case "package":
|
|
328
|
+
return formatPackageVersionSegment(
|
|
329
|
+
theme,
|
|
330
|
+
state.packageVersion,
|
|
331
|
+
colorSource,
|
|
332
|
+
iconMode,
|
|
333
|
+
config.icons.package,
|
|
334
|
+
config.colors.packageVersion,
|
|
335
|
+
);
|
|
336
|
+
case "package_version":
|
|
337
|
+
return state.packageVersion?.version
|
|
338
|
+
? renderStyleForSource(
|
|
339
|
+
theme,
|
|
340
|
+
colorSource,
|
|
341
|
+
config.colors.packageVersion,
|
|
342
|
+
state.packageVersion.version,
|
|
343
|
+
)
|
|
344
|
+
: "";
|
|
345
|
+
case "sep":
|
|
346
|
+
return renderStyleForSource(theme, colorSource, config.colors.separator, " | ");
|
|
347
|
+
case "git_commit":
|
|
348
|
+
return formatGitCommitSegment(
|
|
349
|
+
theme,
|
|
350
|
+
state.commit,
|
|
351
|
+
config.gitCommit,
|
|
352
|
+
colorSource,
|
|
353
|
+
config.colors.gitCommit,
|
|
354
|
+
);
|
|
355
|
+
case "git_tag":
|
|
356
|
+
return config.gitCommit.showTag && state.commit?.tag
|
|
357
|
+
? renderStyleForSource(
|
|
358
|
+
theme,
|
|
359
|
+
colorSource,
|
|
360
|
+
config.colors.gitCommit,
|
|
361
|
+
state.commit.tag,
|
|
362
|
+
)
|
|
363
|
+
: "";
|
|
364
|
+
case "git_metrics":
|
|
365
|
+
return formatGitMetricsSegment(
|
|
366
|
+
theme,
|
|
367
|
+
state.metrics,
|
|
368
|
+
config.gitMetrics,
|
|
369
|
+
colorSource,
|
|
370
|
+
config.colors.gitMetricsAdded,
|
|
371
|
+
config.colors.gitMetricsDeleted,
|
|
372
|
+
);
|
|
373
|
+
case "git_added":
|
|
374
|
+
return state.metrics
|
|
375
|
+
? renderStyleForSource(
|
|
376
|
+
theme,
|
|
377
|
+
colorSource,
|
|
378
|
+
config.colors.gitMetricsAdded,
|
|
379
|
+
`+${state.metrics.added}`,
|
|
380
|
+
)
|
|
381
|
+
: "";
|
|
382
|
+
case "git_deleted":
|
|
383
|
+
return state.metrics
|
|
384
|
+
? renderStyleForSource(
|
|
385
|
+
theme,
|
|
386
|
+
colorSource,
|
|
387
|
+
config.colors.gitMetricsDeleted,
|
|
388
|
+
`−${state.metrics.deleted}`,
|
|
389
|
+
)
|
|
390
|
+
: "";
|
|
391
|
+
default:
|
|
392
|
+
return "";
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
const branchParts: string[] = [];
|
|
396
|
+
if (config.footerSegments.gitBranch) {
|
|
397
|
+
if (branchText) {
|
|
398
|
+
branchParts.push("on", gitIcon, gitColor(branchText));
|
|
399
|
+
} else if (state.commit?.detached) {
|
|
400
|
+
// `HEAD` uses git-branch style; `(hash)` uses git-commit style
|
|
401
|
+
// (bold green) per Starship `git_commit` format.
|
|
402
|
+
branchParts.push("on", gitIcon, gitColor("HEAD"));
|
|
403
|
+
if (config.footerSegments.gitCommit && state.commit.oid) {
|
|
404
|
+
const shortHash = state.commit.oid.slice(0, config.gitCommit.hashLength);
|
|
405
|
+
const tag = config.gitCommit.showTag && state.commit.tag ? state.commit.tag : "";
|
|
406
|
+
const inner = [shortHash, tag].filter(Boolean).join(" ");
|
|
407
|
+
branchParts.push(
|
|
408
|
+
renderStyleForSource(theme, colorSource, config.colors.gitCommit, `(${inner})`),
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
const gitStatusParts = config.footerSegments.gitStatus && statusBlock ? [statusBlock] : [];
|
|
414
|
+
const showGitState = config.footerSegments.gitBranch || config.footerSegments.gitStatus;
|
|
415
|
+
const gitStateParts = showGitState && gitStateBlock ? [gitStateBlock] : [];
|
|
416
|
+
const branchLabel = [...branchParts, ...gitStatusParts, ...gitStateParts]
|
|
417
|
+
.filter(Boolean)
|
|
418
|
+
.join(" ");
|
|
419
|
+
const runtimeLabel = config.footerSegments.runtime
|
|
420
|
+
? formatRuntimeSegment(
|
|
421
|
+
theme,
|
|
422
|
+
state.runtime,
|
|
423
|
+
config.colors.runtimePrefix,
|
|
424
|
+
colorSource,
|
|
425
|
+
iconMode,
|
|
426
|
+
)
|
|
427
|
+
: "";
|
|
428
|
+
const packageVersionLabel = config.footerSegments.packageVersion
|
|
429
|
+
? formatPackageVersionSegment(
|
|
430
|
+
theme,
|
|
431
|
+
state.packageVersion,
|
|
432
|
+
colorSource,
|
|
433
|
+
iconMode,
|
|
434
|
+
config.icons.package,
|
|
435
|
+
config.colors.packageVersion,
|
|
436
|
+
)
|
|
437
|
+
: "";
|
|
438
|
+
// Skip standalone gitCommit when hash is already folded into the
|
|
439
|
+
// branch display on detached HEAD.
|
|
440
|
+
const hashFoldedIntoBranch = state.commit?.detached && config.footerSegments.gitBranch;
|
|
441
|
+
const gitCommitLabel =
|
|
442
|
+
config.footerSegments.gitCommit && !hashFoldedIntoBranch
|
|
443
|
+
? formatGitCommitSegment(
|
|
444
|
+
theme,
|
|
445
|
+
state.commit,
|
|
446
|
+
config.gitCommit,
|
|
447
|
+
colorSource,
|
|
448
|
+
config.colors.gitCommit,
|
|
449
|
+
)
|
|
450
|
+
: "";
|
|
451
|
+
const gitMetricsLabel = config.footerSegments.gitMetrics
|
|
452
|
+
? formatGitMetricsSegment(
|
|
453
|
+
theme,
|
|
454
|
+
state.metrics,
|
|
455
|
+
config.gitMetrics,
|
|
456
|
+
colorSource,
|
|
457
|
+
config.colors.gitMetricsAdded,
|
|
458
|
+
config.colors.gitMetricsDeleted,
|
|
459
|
+
)
|
|
460
|
+
: "";
|
|
461
|
+
|
|
462
|
+
const sessionDurationSegment = (() => {
|
|
463
|
+
if (!config.footerSegments.sessionDuration || !state.sessionStartEpoch) return "";
|
|
464
|
+
const timeLabel = buildSessionDurationLabel(state.sessionStartEpoch);
|
|
465
|
+
const prefix = renderStyleForSource(theme, colorSource, "", "up for");
|
|
466
|
+
const time = renderStyleForSource(
|
|
467
|
+
theme,
|
|
468
|
+
colorSource,
|
|
469
|
+
config.colors.sessionDuration,
|
|
470
|
+
timeLabel,
|
|
471
|
+
);
|
|
472
|
+
return `${prefix} ${time}`;
|
|
473
|
+
})();
|
|
474
|
+
const usernameSegment = config.footerSegments.username
|
|
475
|
+
? renderStyleForSource(
|
|
476
|
+
theme,
|
|
477
|
+
colorSource,
|
|
478
|
+
config.colors.username,
|
|
479
|
+
formatUsernameHostLabel(config.icons.username),
|
|
480
|
+
)
|
|
481
|
+
: "";
|
|
482
|
+
const osSegment = config.footerSegments.os
|
|
483
|
+
? renderStyleForSource(
|
|
484
|
+
theme,
|
|
485
|
+
colorSource,
|
|
486
|
+
config.colors.os,
|
|
487
|
+
formatOsLabel(config.icons.os, iconMode),
|
|
488
|
+
)
|
|
489
|
+
: "";
|
|
490
|
+
const left = [
|
|
491
|
+
osSegment,
|
|
492
|
+
usernameSegment,
|
|
493
|
+
config.footerSegments.cwd ? cwdLabel : "",
|
|
494
|
+
branchLabel,
|
|
495
|
+
gitCommitLabel,
|
|
496
|
+
gitMetricsLabel,
|
|
497
|
+
packageVersionLabel,
|
|
498
|
+
runtimeLabel,
|
|
499
|
+
sessionDurationSegment,
|
|
500
|
+
]
|
|
501
|
+
.filter(Boolean)
|
|
502
|
+
.join(" ");
|
|
503
|
+
|
|
504
|
+
const timeSegment = config.footerSegments.time
|
|
505
|
+
? renderStyleForSource(
|
|
506
|
+
theme,
|
|
507
|
+
colorSource,
|
|
508
|
+
config.colors.time,
|
|
509
|
+
formatTimeLabel(config.icons.time),
|
|
510
|
+
)
|
|
511
|
+
: "";
|
|
512
|
+
const right = [
|
|
513
|
+
config.footerSegments.context
|
|
514
|
+
? renderStyleForSource(theme, colorSource, contextColor, contextLabel)
|
|
515
|
+
: "",
|
|
516
|
+
config.footerSegments.tokens
|
|
517
|
+
? renderStyleForSource(theme, colorSource, config.colors.tokens, state.tokenLabel)
|
|
518
|
+
: "",
|
|
519
|
+
config.footerSegments.cost
|
|
520
|
+
? renderStyleForSource(theme, colorSource, config.colors.cost, state.costLabel)
|
|
521
|
+
: "",
|
|
522
|
+
timeSegment,
|
|
523
|
+
]
|
|
524
|
+
.filter(Boolean)
|
|
525
|
+
.join(separator);
|
|
526
|
+
|
|
527
|
+
let contentLeft = left;
|
|
528
|
+
let contentMiddle = "";
|
|
529
|
+
let contentRight = right;
|
|
530
|
+
if (config.footerFormat) {
|
|
531
|
+
const {
|
|
532
|
+
left: fmtLeft,
|
|
533
|
+
middle: fmtMiddle,
|
|
534
|
+
right: fmtRight,
|
|
535
|
+
} = renderFormatSplit(parseFooterFormat(config.footerFormat), renderVariable);
|
|
536
|
+
contentLeft = stripOrphanSeparators(fmtLeft);
|
|
537
|
+
contentMiddle = stripOrphanSeparators(fmtMiddle);
|
|
538
|
+
contentRight = stripOrphanSeparators(fmtRight);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const extensionStatuses = collectExtensionStatusSegments(
|
|
542
|
+
footerData.getExtensionStatuses(),
|
|
543
|
+
config,
|
|
544
|
+
);
|
|
545
|
+
const renderExtensionStatus = (segment: ExtensionStatusSegment) =>
|
|
546
|
+
segment.colorMode === "original"
|
|
547
|
+
? segment.text
|
|
548
|
+
: renderStyleForSource(theme, colorSource, config.colors.extensionStatus, segment.text);
|
|
549
|
+
const extensionMiddleSegments = extensionStatuses.middle.map(renderExtensionStatus);
|
|
550
|
+
const middleSegments = contentMiddle
|
|
551
|
+
? [contentMiddle, ...extensionMiddleSegments]
|
|
552
|
+
: extensionMiddleSegments;
|
|
553
|
+
const extensionLeft = extensionStatuses.left.map(renderExtensionStatus);
|
|
554
|
+
const extensionRight = extensionStatuses.right.map(renderExtensionStatus);
|
|
555
|
+
const requestedWidth = visibleWidth(contentLeft) + visibleWidth(contentRight) + (contentLeft && contentRight ? 1 : 0);
|
|
556
|
+
|
|
557
|
+
if (requestedWidth > innerWidth) {
|
|
558
|
+
const top = composeFooterContent(
|
|
559
|
+
contentLeft,
|
|
560
|
+
"",
|
|
561
|
+
extensionLeft,
|
|
562
|
+
middleSegments,
|
|
563
|
+
[],
|
|
564
|
+
separator,
|
|
565
|
+
innerWidth,
|
|
566
|
+
);
|
|
567
|
+
const bottom = composeFooterContent(
|
|
568
|
+
"",
|
|
569
|
+
contentRight,
|
|
570
|
+
[],
|
|
571
|
+
[],
|
|
572
|
+
extensionRight,
|
|
573
|
+
separator,
|
|
574
|
+
innerWidth,
|
|
575
|
+
);
|
|
576
|
+
return [top, bottom]
|
|
577
|
+
.filter(Boolean)
|
|
578
|
+
.map((line) => truncateToWidth(width > 2 ? ` ${line} ` : line, width, ""));
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const content = composeFooterContent(
|
|
582
|
+
contentLeft,
|
|
583
|
+
contentRight,
|
|
584
|
+
extensionLeft,
|
|
585
|
+
middleSegments,
|
|
586
|
+
extensionRight,
|
|
587
|
+
separator,
|
|
588
|
+
innerWidth,
|
|
589
|
+
);
|
|
590
|
+
const framed = width > 2 ? ` ${truncateToWidth(content, width - 2, "")} ` : content;
|
|
591
|
+
return [truncateToWidth(framed, width, "")];
|
|
592
|
+
},
|
|
593
|
+
};
|
|
594
|
+
});
|
|
595
|
+
}
|