@groeponline/pi-wishcraft 0.17.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/AGENTS.md +68 -0
- package/CHANGELOG.md +724 -0
- package/CONTRIBUTING.md +37 -0
- package/README.md +648 -0
- package/RELEASE.md +117 -0
- package/ROADMAP.md +52 -0
- package/bash-mode/completion-providers.ts +269 -0
- package/bash-mode/completion.ts +416 -0
- package/bash-mode/editor-ghost.ts +40 -0
- package/bash-mode/editor-input.ts +80 -0
- package/bash-mode/editor.ts +437 -0
- package/bash-mode/history.ts +263 -0
- package/bash-mode/shell-session.ts +286 -0
- package/bash-mode/transcript.ts +108 -0
- package/bash-mode/types.ts +80 -0
- package/index.ts +6 -0
- package/package.json +55 -0
- package/queue/store.ts +443 -0
- package/queue/types.ts +54 -0
- package/src/config/custom-items.ts +182 -0
- package/src/config/extension-statuses.ts +51 -0
- package/src/config/layout.ts +60 -0
- package/src/config/parse.ts +127 -0
- package/src/config/powerline-config.ts +18 -0
- package/src/config/presets.ts +245 -0
- package/src/config/primitives.ts +117 -0
- package/src/config/segment-ids.ts +114 -0
- package/src/config/segment-options.ts +128 -0
- package/src/config/settings-patch.ts +26 -0
- package/src/config/types.ts +277 -0
- package/src/core/frontmatter.ts +40 -0
- package/src/editor/autocomplete-chain.ts +41 -0
- package/src/extension/activate.ts +28 -0
- package/src/extension/bash-mode-actions.ts +104 -0
- package/src/extension/commands.ts +268 -0
- package/src/extension/constants.ts +46 -0
- package/src/extension/custom-editor.ts +406 -0
- package/src/extension/git-invalidation.ts +40 -0
- package/src/extension/layout.ts +160 -0
- package/src/extension/menu-views.ts +393 -0
- package/src/extension/powerline-widgets.ts +95 -0
- package/src/extension/prompt-history.ts +219 -0
- package/src/extension/queue-commands.ts +245 -0
- package/src/extension/queue-context.ts +12 -0
- package/src/extension/queue-integration.ts +434 -0
- package/src/extension/segment-context.ts +212 -0
- package/src/extension/session-lifecycle.ts +373 -0
- package/src/extension/settings-io.ts +202 -0
- package/src/extension/shortcuts-config.ts +357 -0
- package/src/extension/shortcuts-router.ts +383 -0
- package/src/extension/skills/inline-invocation.ts +174 -0
- package/src/extension/skills/ook.md +6 -0
- package/src/extension/skills/test.md +6 -0
- package/src/extension/stale-context.ts +10 -0
- package/src/extension/stash-history.ts +103 -0
- package/src/extension/state.ts +159 -0
- package/src/extension/status-line-renderers.ts +222 -0
- package/src/extension/types.ts +97 -0
- package/src/extension/vibe-command.ts +160 -0
- package/src/extension/welcome-control.ts +27 -0
- package/src/extension/welcome-integration.ts +153 -0
- package/src/git/status.ts +332 -0
- package/src/paths/agent-dirs.ts +67 -0
- package/src/render/timer.ts +46 -0
- package/src/segments/core.ts +256 -0
- package/src/segments/custom.ts +114 -0
- package/src/segments/index.ts +3 -0
- package/src/segments/registry.ts +87 -0
- package/src/segments/shared.ts +36 -0
- package/src/segments/system.ts +235 -0
- package/src/segments/usage.ts +178 -0
- package/src/shell/cd-command.ts +190 -0
- package/src/shortcuts/matching.ts +61 -0
- package/src/theme/colors.ts +60 -0
- package/src/theme/icons.ts +175 -0
- package/src/theme/separators.ts +41 -0
- package/src/theme/theme.ts +211 -0
- package/src/tools/graph.ts +75 -0
- package/src/tools/patch.ts +179 -0
- package/src/tools/ripgrep.ts +104 -0
- package/src/usage/context.ts +97 -0
- package/src/usage/ledger.ts +293 -0
- package/src/usage/rates.ts +155 -0
- package/src/welcome/auto-dismiss.ts +43 -0
- package/src/welcome/banner.ts +68 -0
- package/src/welcome/discover.ts +234 -0
- package/src/welcome/format.ts +18 -0
- package/src/welcome/index.ts +5 -0
- package/src/welcome/layout.ts +36 -0
- package/src/welcome/overlay.ts +80 -0
- package/src/welcome/renderer.ts +157 -0
- package/src/welcome/sessions.ts +107 -0
- package/src/welcome/types.ts +41 -0
- package/src/welcome/widgets/graph-widget.ts +25 -0
- package/src/welcome/widgets/index.ts +20 -0
- package/src/welcome/widgets/queue-widget.ts +26 -0
- package/src/welcome/widgets/sessions-widget.ts +23 -0
- package/src/welcome/widgets/shortcuts-widget.ts +17 -0
- package/src/welcome/widgets/system-widget.ts +29 -0
- package/src/working-vibes/generate.ts +144 -0
- package/src/working-vibes/index.ts +24 -0
- package/src/working-vibes/manager.ts +198 -0
- package/src/working-vibes/provider.ts +163 -0
- package/src/working-vibes/storage.ts +357 -0
- package/theme.example.json +24 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { hostname as osHostname } from "node:os";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import type {
|
|
4
|
+
SegmentContext,
|
|
5
|
+
SemanticColor,
|
|
6
|
+
StatusLineSegment,
|
|
7
|
+
} from "../config/types.ts";
|
|
8
|
+
import { applyColor } from "../theme/theme.ts";
|
|
9
|
+
import { getIcons, SEP_DOT, getThinkingText } from "../theme/icons.ts";
|
|
10
|
+
import type { IconSet } from "../theme/icons.ts";
|
|
11
|
+
import { getGitRemoteHost } from "../git/status.ts";
|
|
12
|
+
import type { GitHost } from "../git/status.ts";
|
|
13
|
+
import { color, withIcon, formatDuration } from "./shared.ts";
|
|
14
|
+
|
|
15
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
16
|
+
// Segment Implementations
|
|
17
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
18
|
+
|
|
19
|
+
export const modelSegment: StatusLineSegment = {
|
|
20
|
+
id: "model",
|
|
21
|
+
render(ctx) {
|
|
22
|
+
const icons = getIcons();
|
|
23
|
+
const opts = ctx.options.model ?? {};
|
|
24
|
+
|
|
25
|
+
let modelName = ctx.model?.name || ctx.model?.id || "no-model";
|
|
26
|
+
if (opts.display === "qualified" && ctx.model?.id) {
|
|
27
|
+
const provider =
|
|
28
|
+
ctx.model.provider || ctx.model.providerId || ctx.model.providerName;
|
|
29
|
+
modelName =
|
|
30
|
+
provider && !ctx.model.id.includes("/")
|
|
31
|
+
? `${provider}/${ctx.model.id}`
|
|
32
|
+
: ctx.model.id;
|
|
33
|
+
} else if (modelName.startsWith("Claude ")) {
|
|
34
|
+
modelName = modelName.slice(7);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let content = withIcon(icons.model, modelName);
|
|
38
|
+
|
|
39
|
+
if (opts.showThinkingLevel !== false && ctx.model?.reasoning) {
|
|
40
|
+
const level = ctx.thinkingLevel || "off";
|
|
41
|
+
if (level !== "off") {
|
|
42
|
+
const thinkingText = getThinkingText(level);
|
|
43
|
+
if (thinkingText) {
|
|
44
|
+
content += `${SEP_DOT}${thinkingText}`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { content: color(ctx, "model", content), visible: true };
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const shellModeSegment: StatusLineSegment = {
|
|
54
|
+
id: "shell_mode",
|
|
55
|
+
render(ctx) {
|
|
56
|
+
if (!ctx.shellModeActive) {
|
|
57
|
+
return { content: "", visible: false };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const shellName = ctx.shellName ?? "shell";
|
|
61
|
+
const state = ctx.shellRunning ? "run" : "idle";
|
|
62
|
+
const cwd = ctx.shellCwd ? basename(ctx.shellCwd) : null;
|
|
63
|
+
const parts = [shellName, state];
|
|
64
|
+
if (cwd) {
|
|
65
|
+
parts.push(cwd);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
content: color(ctx, "shellMode", parts.join(SEP_DOT)),
|
|
70
|
+
visible: true,
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const pathSegment: StatusLineSegment = {
|
|
76
|
+
id: "path",
|
|
77
|
+
render(ctx) {
|
|
78
|
+
const icons = getIcons();
|
|
79
|
+
const opts = ctx.options.path ?? {};
|
|
80
|
+
const mode = opts.mode ?? "basename";
|
|
81
|
+
|
|
82
|
+
let pwd =
|
|
83
|
+
ctx.shellModeActive && ctx.shellCwd
|
|
84
|
+
? ctx.shellCwd
|
|
85
|
+
: (ctx.cwd ?? process.cwd());
|
|
86
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
87
|
+
|
|
88
|
+
if (mode === "basename") {
|
|
89
|
+
// Just the last directory component (cross-platform)
|
|
90
|
+
pwd = basename(pwd) || pwd;
|
|
91
|
+
} else {
|
|
92
|
+
// Abbreviate home directory for abbreviated/full modes
|
|
93
|
+
if (home && pwd.startsWith(home)) {
|
|
94
|
+
pwd = `~${pwd.slice(home.length)}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Strip /work/ prefix (common in containers)
|
|
98
|
+
if (pwd.startsWith("/work/")) {
|
|
99
|
+
pwd = pwd.slice(6);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Truncate if too long (only for abbreviated mode)
|
|
103
|
+
if (mode === "abbreviated") {
|
|
104
|
+
const maxLen = opts.maxLength ?? 40;
|
|
105
|
+
if (pwd.length > maxLen) {
|
|
106
|
+
pwd = `…${pwd.slice(-(maxLen - 1))}`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const content = withIcon(icons.folder, pwd);
|
|
112
|
+
return { content: color(ctx, "path", content), visible: true };
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Icon for the branch label: the origin remote's host logo when hostIcon is
|
|
118
|
+
* enabled and a remote is known, otherwise the plain branch icon. An
|
|
119
|
+
* unrecognized remote falls back to the generic git logo.
|
|
120
|
+
*/
|
|
121
|
+
function resolveBranchIcon(icons: IconSet, hostIcon: boolean): string {
|
|
122
|
+
if (!hostIcon) return icons.branch;
|
|
123
|
+
const host = getGitRemoteHost();
|
|
124
|
+
const byHost: Record<GitHost, string> = {
|
|
125
|
+
github: icons.github,
|
|
126
|
+
gitlab: icons.gitlab,
|
|
127
|
+
bitbucket: icons.bitbucket,
|
|
128
|
+
other: icons.git,
|
|
129
|
+
};
|
|
130
|
+
return host ? byHost[host] : icons.branch;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const gitSegment: StatusLineSegment = {
|
|
134
|
+
id: "git",
|
|
135
|
+
render(ctx) {
|
|
136
|
+
const icons = getIcons();
|
|
137
|
+
const opts = ctx.options.git ?? {};
|
|
138
|
+
const { branch, staged, unstaged, untracked } = ctx.git;
|
|
139
|
+
const gitStatus =
|
|
140
|
+
staged > 0 || unstaged > 0 || untracked > 0
|
|
141
|
+
? { staged, unstaged, untracked }
|
|
142
|
+
: null;
|
|
143
|
+
|
|
144
|
+
if (!branch && !gitStatus) return { content: "", visible: false };
|
|
145
|
+
|
|
146
|
+
const isDirty =
|
|
147
|
+
gitStatus &&
|
|
148
|
+
(gitStatus.staged > 0 ||
|
|
149
|
+
gitStatus.unstaged > 0 ||
|
|
150
|
+
gitStatus.untracked > 0);
|
|
151
|
+
const showBranch = opts.showBranch !== false;
|
|
152
|
+
const branchColor: SemanticColor = isDirty ? "gitDirty" : "gitClean";
|
|
153
|
+
|
|
154
|
+
// Build content - color branch separately from indicators
|
|
155
|
+
let content = "";
|
|
156
|
+
if (showBranch && branch) {
|
|
157
|
+
// Color just the branch name (icon + branch text)
|
|
158
|
+
const branchIcon = resolveBranchIcon(icons, opts.hostIcon === true);
|
|
159
|
+
content = color(ctx, branchColor, withIcon(branchIcon, branch));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Add status indicators (each with their own color, not wrapped)
|
|
163
|
+
if (gitStatus) {
|
|
164
|
+
const indicators: string[] = [];
|
|
165
|
+
if (opts.showUnstaged !== false && gitStatus.unstaged > 0) {
|
|
166
|
+
indicators.push(
|
|
167
|
+
applyColor(ctx.theme, "warning", `*${gitStatus.unstaged}`),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
if (opts.showStaged !== false && gitStatus.staged > 0) {
|
|
171
|
+
indicators.push(
|
|
172
|
+
applyColor(ctx.theme, "success", `+${gitStatus.staged}`),
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
if (opts.showUntracked !== false && gitStatus.untracked > 0) {
|
|
176
|
+
indicators.push(
|
|
177
|
+
applyColor(ctx.theme, "muted", `?${gitStatus.untracked}`),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
if (indicators.length > 0) {
|
|
181
|
+
const indicatorText = indicators.join(" ");
|
|
182
|
+
if (!content && showBranch === false) {
|
|
183
|
+
// No branch shown, color the git icon with branch color
|
|
184
|
+
content =
|
|
185
|
+
color(ctx, branchColor, icons.git ? `${icons.git} ` : "") +
|
|
186
|
+
indicatorText;
|
|
187
|
+
} else {
|
|
188
|
+
content += content ? ` ${indicatorText}` : indicatorText;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!content) return { content: "", visible: false };
|
|
194
|
+
|
|
195
|
+
return { content, visible: true };
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export const timeSpentSegment: StatusLineSegment = {
|
|
200
|
+
id: "time_spent",
|
|
201
|
+
render(ctx) {
|
|
202
|
+
const icons = getIcons();
|
|
203
|
+
const elapsed = Date.now() - ctx.sessionStartTime;
|
|
204
|
+
if (elapsed < 1000) return { content: "", visible: false };
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
content: withIcon(icons.time, formatDuration(elapsed)),
|
|
208
|
+
visible: true,
|
|
209
|
+
};
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export const timeSegment: StatusLineSegment = {
|
|
214
|
+
id: "time",
|
|
215
|
+
render(ctx) {
|
|
216
|
+
const icons = getIcons();
|
|
217
|
+
const opts = ctx.options.time ?? {};
|
|
218
|
+
const now = new Date();
|
|
219
|
+
|
|
220
|
+
let hours = now.getHours();
|
|
221
|
+
let suffix = "";
|
|
222
|
+
if (opts.format === "12h") {
|
|
223
|
+
suffix = hours >= 12 ? "pm" : "am";
|
|
224
|
+
hours = hours % 12 || 12;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const mins = now.getMinutes().toString().padStart(2, "0");
|
|
228
|
+
let timeStr = `${hours}:${mins}`;
|
|
229
|
+
if (opts.showSeconds) {
|
|
230
|
+
timeStr += `:${now.getSeconds().toString().padStart(2, "0")}`;
|
|
231
|
+
}
|
|
232
|
+
timeStr += suffix;
|
|
233
|
+
|
|
234
|
+
return { content: withIcon(icons.time, timeStr), visible: true };
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
export const sessionSegment: StatusLineSegment = {
|
|
239
|
+
id: "session",
|
|
240
|
+
render(ctx) {
|
|
241
|
+
const icons = getIcons();
|
|
242
|
+
const sessionId = ctx.sessionId;
|
|
243
|
+
const display = sessionId?.slice(0, 8) || "new";
|
|
244
|
+
|
|
245
|
+
return { content: withIcon(icons.session, display), visible: true };
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export const hostnameSegment: StatusLineSegment = {
|
|
250
|
+
id: "hostname",
|
|
251
|
+
render() {
|
|
252
|
+
const icons = getIcons();
|
|
253
|
+
const name = osHostname().split(".")[0];
|
|
254
|
+
return { content: withIcon(icons.host, name), visible: true };
|
|
255
|
+
},
|
|
256
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import type {
|
|
3
|
+
CustomSegmentConfig,
|
|
4
|
+
RenderedSegment,
|
|
5
|
+
SegmentContext,
|
|
6
|
+
StatusLineSegment,
|
|
7
|
+
StatusLineSegmentId,
|
|
8
|
+
} from "../config/types.ts";
|
|
9
|
+
import { normalizeExtensionStatusValue } from "../config/powerline-config.ts";
|
|
10
|
+
import { applyColor } from "../theme/theme.ts";
|
|
11
|
+
import { SEP_DOT } from "../theme/icons.ts";
|
|
12
|
+
|
|
13
|
+
// User-defined computed segments (command/env/static), registered at config time.
|
|
14
|
+
export const customComputedSegments = new Map<string, StatusLineSegment>();
|
|
15
|
+
const commandCache = new Map<string, { at: number; value: string }>();
|
|
16
|
+
|
|
17
|
+
function runCommandCached(
|
|
18
|
+
command: string,
|
|
19
|
+
cacheMs: number | undefined,
|
|
20
|
+
): string | null {
|
|
21
|
+
// ponytail: simple in-memory cache keyed by command; avoids re-spawning shells every paint
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
const cached = commandCache.get(command);
|
|
24
|
+
if (cached && cacheMs !== undefined && now - cached.at < cacheMs)
|
|
25
|
+
return cached.value;
|
|
26
|
+
try {
|
|
27
|
+
const out = execSync(command, { encoding: "utf8" }).trim();
|
|
28
|
+
if (cacheMs !== undefined)
|
|
29
|
+
commandCache.set(command, { at: now, value: out });
|
|
30
|
+
return out;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function makeComputedSegment(
|
|
37
|
+
id: string,
|
|
38
|
+
def: CustomSegmentConfig,
|
|
39
|
+
): StatusLineSegment {
|
|
40
|
+
return {
|
|
41
|
+
id: `custom:${id}` as StatusLineSegmentId,
|
|
42
|
+
render(ctx: SegmentContext): RenderedSegment {
|
|
43
|
+
let text = "";
|
|
44
|
+
if (def.type === "command") {
|
|
45
|
+
const out = runCommandCached(def.command, def.cacheMs);
|
|
46
|
+
if (out === null) return { content: "", visible: false };
|
|
47
|
+
text = out;
|
|
48
|
+
} else if (def.type === "env") {
|
|
49
|
+
const val = process.env[def.env];
|
|
50
|
+
if (!val) {
|
|
51
|
+
if (def.fallback === undefined)
|
|
52
|
+
return { content: "", visible: false };
|
|
53
|
+
text = def.fallback;
|
|
54
|
+
} else {
|
|
55
|
+
text = val;
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
text = def.text;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!text) return { content: "", visible: false };
|
|
62
|
+
|
|
63
|
+
let content = text;
|
|
64
|
+
if (def.prefix) content = `${def.prefix}${SEP_DOT}${content}`;
|
|
65
|
+
if (def.color) content = applyColor(ctx.theme, def.color, content);
|
|
66
|
+
return { content, visible: true };
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Register user-defined computed segments from settings. Replaces any previously registered. */
|
|
72
|
+
export function registerCustomSegments(
|
|
73
|
+
defs: Record<string, CustomSegmentConfig>,
|
|
74
|
+
): void {
|
|
75
|
+
customComputedSegments.clear();
|
|
76
|
+
for (const [id, def] of Object.entries(defs)) {
|
|
77
|
+
customComputedSegments.set(id, makeComputedSegment(id, def));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function renderCustomSegment(
|
|
82
|
+
id: `custom:${string}`,
|
|
83
|
+
ctx: SegmentContext,
|
|
84
|
+
): RenderedSegment {
|
|
85
|
+
const customItemId = id.slice("custom:".length);
|
|
86
|
+
const custom = ctx.customItemsById.get(customItemId);
|
|
87
|
+
if (!custom) return { content: "", visible: false };
|
|
88
|
+
|
|
89
|
+
const rawStatus = ctx.extensionStatuses.get(custom.statusKey);
|
|
90
|
+
const normalizedStatus = rawStatus
|
|
91
|
+
? normalizeExtensionStatusValue(rawStatus)
|
|
92
|
+
: null;
|
|
93
|
+
if (!normalizedStatus) {
|
|
94
|
+
return custom.hideWhenMissing
|
|
95
|
+
? { content: "", visible: false }
|
|
96
|
+
: { content: custom.prefix ?? custom.id, visible: true };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let content = normalizedStatus;
|
|
100
|
+
if (custom.prefix) {
|
|
101
|
+
content = `${custom.prefix}${SEP_DOT}${content}`;
|
|
102
|
+
}
|
|
103
|
+
if (custom.color) {
|
|
104
|
+
content = applyColor(ctx.theme, custom.color, content);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { content, visible: true };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function isCustomSegmentId(
|
|
111
|
+
id: StatusLineSegmentId,
|
|
112
|
+
): id is `custom:${string}` {
|
|
113
|
+
return id.startsWith("custom:");
|
|
114
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BuiltinStatusLineSegmentId,
|
|
3
|
+
RenderedSegment,
|
|
4
|
+
SegmentContext,
|
|
5
|
+
StatusLineSegment,
|
|
6
|
+
StatusLineSegmentId,
|
|
7
|
+
} from "../config/types.ts";
|
|
8
|
+
import {
|
|
9
|
+
modelSegment,
|
|
10
|
+
shellModeSegment,
|
|
11
|
+
pathSegment,
|
|
12
|
+
gitSegment,
|
|
13
|
+
timeSpentSegment,
|
|
14
|
+
timeSegment,
|
|
15
|
+
sessionSegment,
|
|
16
|
+
hostnameSegment,
|
|
17
|
+
} from "./core.ts";
|
|
18
|
+
import {
|
|
19
|
+
tokenInSegment,
|
|
20
|
+
tokenOutSegment,
|
|
21
|
+
tokenTotalSegment,
|
|
22
|
+
costSegment,
|
|
23
|
+
contextPctSegment,
|
|
24
|
+
contextTotalSegment,
|
|
25
|
+
cacheReadSegment,
|
|
26
|
+
cacheWriteSegment,
|
|
27
|
+
} from "./usage.ts";
|
|
28
|
+
import {
|
|
29
|
+
thinkingSegment,
|
|
30
|
+
subagentsSegment,
|
|
31
|
+
queueSegment,
|
|
32
|
+
extensionStatusesSegment,
|
|
33
|
+
tpsSegment,
|
|
34
|
+
openPortsSegment,
|
|
35
|
+
} from "./system.ts";
|
|
36
|
+
import {
|
|
37
|
+
customComputedSegments,
|
|
38
|
+
renderCustomSegment,
|
|
39
|
+
isCustomSegmentId,
|
|
40
|
+
} from "./custom.ts";
|
|
41
|
+
|
|
42
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
43
|
+
// Segment Registry
|
|
44
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
45
|
+
|
|
46
|
+
export const SEGMENTS: Record<BuiltinStatusLineSegmentId, StatusLineSegment> = {
|
|
47
|
+
model: modelSegment,
|
|
48
|
+
shell_mode: shellModeSegment,
|
|
49
|
+
path: pathSegment,
|
|
50
|
+
git: gitSegment,
|
|
51
|
+
thinking: thinkingSegment,
|
|
52
|
+
subagents: subagentsSegment,
|
|
53
|
+
queue: queueSegment,
|
|
54
|
+
token_in: tokenInSegment,
|
|
55
|
+
token_out: tokenOutSegment,
|
|
56
|
+
token_total: tokenTotalSegment,
|
|
57
|
+
cost: costSegment,
|
|
58
|
+
context_pct: contextPctSegment,
|
|
59
|
+
context_total: contextTotalSegment,
|
|
60
|
+
time_spent: timeSpentSegment,
|
|
61
|
+
time: timeSegment,
|
|
62
|
+
session: sessionSegment,
|
|
63
|
+
hostname: hostnameSegment,
|
|
64
|
+
cache_read: cacheReadSegment,
|
|
65
|
+
cache_write: cacheWriteSegment,
|
|
66
|
+
tps: tpsSegment,
|
|
67
|
+
open_ports: openPortsSegment,
|
|
68
|
+
extension_statuses: extensionStatusesSegment,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export function renderSegment(
|
|
72
|
+
id: StatusLineSegmentId,
|
|
73
|
+
ctx: SegmentContext,
|
|
74
|
+
): RenderedSegment {
|
|
75
|
+
if (isCustomSegmentId(id)) {
|
|
76
|
+
const customId = id.slice("custom:".length);
|
|
77
|
+
const computed = customComputedSegments.get(customId);
|
|
78
|
+
if (computed) return computed.render(ctx);
|
|
79
|
+
return renderCustomSegment(id, ctx);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const segment = SEGMENTS[id];
|
|
83
|
+
if (!segment) {
|
|
84
|
+
return { content: "", visible: false };
|
|
85
|
+
}
|
|
86
|
+
return segment.render(ctx);
|
|
87
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { SegmentContext, SemanticColor } from "../config/types.ts";
|
|
2
|
+
import { fg } from "../theme/theme.ts";
|
|
3
|
+
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
5
|
+
// Helpers
|
|
6
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
7
|
+
|
|
8
|
+
export function color(
|
|
9
|
+
ctx: SegmentContext,
|
|
10
|
+
semantic: SemanticColor,
|
|
11
|
+
text: string,
|
|
12
|
+
): string {
|
|
13
|
+
return fg(ctx.theme, semantic, text, ctx.colors);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function withIcon(icon: string, text: string): string {
|
|
17
|
+
return icon ? `${icon} ${text}` : text;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function formatTokens(n: number): string {
|
|
21
|
+
if (n < 1000) return n.toString();
|
|
22
|
+
if (n < 10000) return `${(n / 1000).toFixed(1)}k`;
|
|
23
|
+
if (n < 1000000) return `${Math.round(n / 1000)}k`;
|
|
24
|
+
if (n < 10000000) return `${(n / 1000000).toFixed(1)}M`;
|
|
25
|
+
return `${Math.round(n / 1000000)}M`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function formatDuration(ms: number): string {
|
|
29
|
+
const seconds = Math.floor(ms / 1000);
|
|
30
|
+
const minutes = Math.floor(seconds / 60);
|
|
31
|
+
const hours = Math.floor(minutes / 60);
|
|
32
|
+
|
|
33
|
+
if (hours > 0) return `${hours}h${minutes % 60}m`;
|
|
34
|
+
if (minutes > 0) return `${minutes}m${seconds % 60}s`;
|
|
35
|
+
return `${seconds}s`;
|
|
36
|
+
}
|