@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,114 @@
|
|
|
1
|
+
import { isRecord, normalizeCustomItemId } from "./primitives.ts";
|
|
2
|
+
import { BUILTIN_STATUS_LINE_SEGMENT_IDS } from "./types.ts";
|
|
3
|
+
import type {
|
|
4
|
+
CustomStatusItem,
|
|
5
|
+
StatusLineLayout,
|
|
6
|
+
StatusLineSegmentId,
|
|
7
|
+
} from "./types.ts";
|
|
8
|
+
|
|
9
|
+
const BUILTIN_STATUS_LINE_SEGMENT_ID_SET = new Set<string>(
|
|
10
|
+
BUILTIN_STATUS_LINE_SEGMENT_IDS,
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
export function normalizeStatusLineSegmentId(
|
|
14
|
+
value: unknown,
|
|
15
|
+
customItemIds: ReadonlySet<string>,
|
|
16
|
+
customSegmentIds: ReadonlySet<string> = new Set(),
|
|
17
|
+
): StatusLineSegmentId | null {
|
|
18
|
+
if (typeof value !== "string") return null;
|
|
19
|
+
|
|
20
|
+
const normalized = value.trim();
|
|
21
|
+
if (BUILTIN_STATUS_LINE_SEGMENT_ID_SET.has(normalized)) {
|
|
22
|
+
return normalized as StatusLineSegmentId;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const customId = normalized.startsWith("custom:")
|
|
26
|
+
? normalizeCustomItemId(normalized.slice("custom:".length))
|
|
27
|
+
: null;
|
|
28
|
+
// ponytail: custom: ids resolve to either a user-defined item OR a computed segment
|
|
29
|
+
return customId &&
|
|
30
|
+
(customItemIds.has(customId) || customSegmentIds.has(customId))
|
|
31
|
+
? `custom:${customId}`
|
|
32
|
+
: null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function normalizeDisabledSegments(
|
|
36
|
+
raw: unknown,
|
|
37
|
+
customItems: readonly CustomStatusItem[],
|
|
38
|
+
customSegmentIds: ReadonlySet<string>,
|
|
39
|
+
): {
|
|
40
|
+
disabledSegments: StatusLineSegmentId[];
|
|
41
|
+
invalidDisabledSegments: string[];
|
|
42
|
+
} {
|
|
43
|
+
if (!Array.isArray(raw))
|
|
44
|
+
return { disabledSegments: [], invalidDisabledSegments: [] };
|
|
45
|
+
|
|
46
|
+
const disabledSegments: StatusLineSegmentId[] = [];
|
|
47
|
+
const invalidDisabledSegments: string[] = [];
|
|
48
|
+
const customItemIds = new Set(customItems.map((item) => item.id));
|
|
49
|
+
const seen = new Set<StatusLineSegmentId>();
|
|
50
|
+
|
|
51
|
+
for (const entry of raw) {
|
|
52
|
+
const segmentId = normalizeStatusLineSegmentId(
|
|
53
|
+
entry,
|
|
54
|
+
customItemIds,
|
|
55
|
+
customSegmentIds,
|
|
56
|
+
);
|
|
57
|
+
if (!segmentId) {
|
|
58
|
+
invalidDisabledSegments.push(
|
|
59
|
+
typeof entry === "string" ? entry.trim() : String(entry),
|
|
60
|
+
);
|
|
61
|
+
} else if (!seen.has(segmentId)) {
|
|
62
|
+
seen.add(segmentId);
|
|
63
|
+
disabledSegments.push(segmentId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { disabledSegments, invalidDisabledSegments };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function normalizeLayout(
|
|
71
|
+
raw: unknown,
|
|
72
|
+
customItems: readonly CustomStatusItem[],
|
|
73
|
+
customSegmentIds: ReadonlySet<string>,
|
|
74
|
+
): { layout: StatusLineLayout | null; invalidLayoutSegments: string[] } {
|
|
75
|
+
if (!isRecord(raw)) return { layout: null, invalidLayoutSegments: [] };
|
|
76
|
+
|
|
77
|
+
const layout: StatusLineLayout = {};
|
|
78
|
+
const invalidLayoutSegments: string[] = [];
|
|
79
|
+
const customItemIds = new Set(customItems.map((item) => item.id));
|
|
80
|
+
const globallyPlaced = new Set<StatusLineSegmentId>();
|
|
81
|
+
|
|
82
|
+
for (const row of ["left", "right", "secondary"] as const) {
|
|
83
|
+
const entries = raw[row];
|
|
84
|
+
if (!Array.isArray(entries)) continue;
|
|
85
|
+
|
|
86
|
+
const segments: StatusLineSegmentId[] = [];
|
|
87
|
+
const seen = new Set<StatusLineSegmentId>();
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
const segmentId = normalizeStatusLineSegmentId(
|
|
90
|
+
entry,
|
|
91
|
+
customItemIds,
|
|
92
|
+
customSegmentIds,
|
|
93
|
+
);
|
|
94
|
+
if (!segmentId) {
|
|
95
|
+
invalidLayoutSegments.push(
|
|
96
|
+
`${row}:${typeof entry === "string" ? entry.trim() : String(entry)}`,
|
|
97
|
+
);
|
|
98
|
+
} else if (!seen.has(segmentId)) {
|
|
99
|
+
seen.add(segmentId);
|
|
100
|
+
if (globallyPlaced.has(segmentId)) {
|
|
101
|
+
invalidLayoutSegments.push(`${row}:${segmentId}`);
|
|
102
|
+
} else {
|
|
103
|
+
globallyPlaced.add(segmentId);
|
|
104
|
+
segments.push(segmentId);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
layout[row] = segments;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return Object.keys(layout).length > 0
|
|
112
|
+
? { layout, invalidLayoutSegments }
|
|
113
|
+
: { layout: null, invalidLayoutSegments };
|
|
114
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { normalizeCostCurrency } from "../usage/rates.ts";
|
|
2
|
+
import { isRecord } from "./primitives.ts";
|
|
3
|
+
import type { StatusLineSegmentOptions } from "./types.ts";
|
|
4
|
+
|
|
5
|
+
export function normalizeSegmentOptions(
|
|
6
|
+
raw: Record<string, unknown>,
|
|
7
|
+
): StatusLineSegmentOptions {
|
|
8
|
+
const options: StatusLineSegmentOptions = {};
|
|
9
|
+
|
|
10
|
+
if (isRecord(raw.model)) {
|
|
11
|
+
options.model = {
|
|
12
|
+
...(typeof raw.model.showThinkingLevel === "boolean"
|
|
13
|
+
? { showThinkingLevel: raw.model.showThinkingLevel }
|
|
14
|
+
: {}),
|
|
15
|
+
...(raw.model.display === "name" || raw.model.display === "qualified"
|
|
16
|
+
? { display: raw.model.display }
|
|
17
|
+
: {}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (isRecord(raw.path)) {
|
|
22
|
+
options.path = {
|
|
23
|
+
...(raw.path.mode === "basename" ||
|
|
24
|
+
raw.path.mode === "abbreviated" ||
|
|
25
|
+
raw.path.mode === "full"
|
|
26
|
+
? { mode: raw.path.mode }
|
|
27
|
+
: {}),
|
|
28
|
+
...(typeof raw.path.maxLength === "number" &&
|
|
29
|
+
Number.isFinite(raw.path.maxLength) &&
|
|
30
|
+
raw.path.maxLength > 0
|
|
31
|
+
? { maxLength: Math.floor(raw.path.maxLength) }
|
|
32
|
+
: {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (isRecord(raw.git)) {
|
|
37
|
+
options.git = {
|
|
38
|
+
...(typeof raw.git.showBranch === "boolean"
|
|
39
|
+
? { showBranch: raw.git.showBranch }
|
|
40
|
+
: {}),
|
|
41
|
+
...(typeof raw.git.showStaged === "boolean"
|
|
42
|
+
? { showStaged: raw.git.showStaged }
|
|
43
|
+
: {}),
|
|
44
|
+
...(typeof raw.git.showUnstaged === "boolean"
|
|
45
|
+
? { showUnstaged: raw.git.showUnstaged }
|
|
46
|
+
: {}),
|
|
47
|
+
...(typeof raw.git.showUntracked === "boolean"
|
|
48
|
+
? { showUntracked: raw.git.showUntracked }
|
|
49
|
+
: {}),
|
|
50
|
+
...(raw.git.polling === "full" ||
|
|
51
|
+
raw.git.polling === "branch" ||
|
|
52
|
+
raw.git.polling === "off"
|
|
53
|
+
? { polling: raw.git.polling }
|
|
54
|
+
: {}),
|
|
55
|
+
...(typeof raw.git.hostIcon === "boolean"
|
|
56
|
+
? { hostIcon: raw.git.hostIcon }
|
|
57
|
+
: {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (isRecord(raw.time)) {
|
|
62
|
+
options.time = {
|
|
63
|
+
...(raw.time.format === "12h" || raw.time.format === "24h"
|
|
64
|
+
? { format: raw.time.format }
|
|
65
|
+
: {}),
|
|
66
|
+
...(typeof raw.time.showSeconds === "boolean"
|
|
67
|
+
? { showSeconds: raw.time.showSeconds }
|
|
68
|
+
: {}),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (isRecord(raw.cost)) {
|
|
73
|
+
const currency = normalizeCostCurrency(raw.cost.currency);
|
|
74
|
+
options.cost = {
|
|
75
|
+
...(raw.cost.subscriptionDisplay === "subscription" ||
|
|
76
|
+
raw.cost.subscriptionDisplay === "reported-cost" ||
|
|
77
|
+
raw.cost.subscriptionDisplay === "both"
|
|
78
|
+
? { subscriptionDisplay: raw.cost.subscriptionDisplay }
|
|
79
|
+
: {}),
|
|
80
|
+
...(currency ? { currency } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (isRecord(raw.context)) {
|
|
85
|
+
options.context = {
|
|
86
|
+
...(raw.context.format === "full" || raw.context.format === "percent"
|
|
87
|
+
? { format: raw.context.format }
|
|
88
|
+
: {}),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (isRecord(raw.cache_read)) {
|
|
93
|
+
options.cache_read = {
|
|
94
|
+
...(raw.cache_read.format === "tokens" ||
|
|
95
|
+
raw.cache_read.format === "percent" ||
|
|
96
|
+
raw.cache_read.format === "both"
|
|
97
|
+
? { format: raw.cache_read.format }
|
|
98
|
+
: {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (isRecord(raw.openPorts)) {
|
|
103
|
+
options.openPorts = {
|
|
104
|
+
...(typeof raw.openPorts.includeUdp === "boolean"
|
|
105
|
+
? { includeUdp: raw.openPorts.includeUdp }
|
|
106
|
+
: {}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return options;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function mergeSegmentOptions(
|
|
114
|
+
defaults: StatusLineSegmentOptions = {},
|
|
115
|
+
overrides: StatusLineSegmentOptions = {},
|
|
116
|
+
): StatusLineSegmentOptions {
|
|
117
|
+
return {
|
|
118
|
+
...defaults,
|
|
119
|
+
...overrides,
|
|
120
|
+
model: { ...defaults.model, ...overrides.model },
|
|
121
|
+
path: { ...defaults.path, ...overrides.path },
|
|
122
|
+
git: { ...defaults.git, ...overrides.git },
|
|
123
|
+
time: { ...defaults.time, ...overrides.time },
|
|
124
|
+
cost: { ...defaults.cost, ...overrides.cost },
|
|
125
|
+
context: { ...defaults.context, ...overrides.context },
|
|
126
|
+
cache_read: { ...defaults.cache_read, ...overrides.cache_read },
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { isRecord } from "./primitives.ts";
|
|
2
|
+
import type { PowerlineConfig } from "./parse.ts";
|
|
3
|
+
import type { StatusLinePreset } from "./types.ts";
|
|
4
|
+
|
|
5
|
+
export function nextPowerlineSettingWithPreset(
|
|
6
|
+
existingPowerlineSetting: unknown,
|
|
7
|
+
preset: StatusLinePreset,
|
|
8
|
+
): unknown {
|
|
9
|
+
if (!isRecord(existingPowerlineSetting)) {
|
|
10
|
+
return preset;
|
|
11
|
+
}
|
|
12
|
+
return { ...existingPowerlineSetting, preset };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function nextPowerlineSettingWithOptions(
|
|
16
|
+
existingPowerlineSetting: unknown,
|
|
17
|
+
updates: Partial<
|
|
18
|
+
Pick<PowerlineConfig, "welcome" | "stashSharpSShortcut" | "placement">
|
|
19
|
+
>,
|
|
20
|
+
currentPreset: StatusLinePreset,
|
|
21
|
+
): unknown {
|
|
22
|
+
if (!isRecord(existingPowerlineSetting)) {
|
|
23
|
+
return { preset: currentPreset, ...updates };
|
|
24
|
+
}
|
|
25
|
+
return { ...existingPowerlineSetting, ...updates };
|
|
26
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { CostCurrencyCode } from "../usage/rates.ts";
|
|
3
|
+
|
|
4
|
+
// Theme color - either a pi theme color name or a custom hex color
|
|
5
|
+
export type ColorValue = ThemeColor | `#${string}`;
|
|
6
|
+
export type ThemeLike = Pick<Theme, "fg">;
|
|
7
|
+
|
|
8
|
+
// Semantic color names for segments
|
|
9
|
+
export type SemanticColor =
|
|
10
|
+
| "model"
|
|
11
|
+
| "shellMode"
|
|
12
|
+
| "path"
|
|
13
|
+
| "gitDirty"
|
|
14
|
+
| "gitClean"
|
|
15
|
+
| "thinking"
|
|
16
|
+
| "thinkingMinimal"
|
|
17
|
+
| "thinkingLow"
|
|
18
|
+
| "thinkingMedium"
|
|
19
|
+
| "context"
|
|
20
|
+
| "contextWarn"
|
|
21
|
+
| "contextError"
|
|
22
|
+
| "cost"
|
|
23
|
+
| "tokens"
|
|
24
|
+
| "queue"
|
|
25
|
+
| "separator"
|
|
26
|
+
| "border";
|
|
27
|
+
|
|
28
|
+
// Color scheme mapping semantic names to actual colors
|
|
29
|
+
export type ColorScheme = Partial<Record<SemanticColor, ColorValue>>;
|
|
30
|
+
|
|
31
|
+
// Built-in segment identifiers
|
|
32
|
+
export const BUILTIN_STATUS_LINE_SEGMENT_IDS = [
|
|
33
|
+
"model",
|
|
34
|
+
"shell_mode",
|
|
35
|
+
"path",
|
|
36
|
+
"git",
|
|
37
|
+
"subagents",
|
|
38
|
+
"queue",
|
|
39
|
+
"token_in",
|
|
40
|
+
"token_out",
|
|
41
|
+
"token_total",
|
|
42
|
+
"cost",
|
|
43
|
+
"context_pct",
|
|
44
|
+
"context_total",
|
|
45
|
+
"time_spent",
|
|
46
|
+
"time",
|
|
47
|
+
"session",
|
|
48
|
+
"hostname",
|
|
49
|
+
"cache_read",
|
|
50
|
+
"cache_write",
|
|
51
|
+
"thinking",
|
|
52
|
+
"tps",
|
|
53
|
+
"open_ports",
|
|
54
|
+
"extension_statuses",
|
|
55
|
+
] as const;
|
|
56
|
+
|
|
57
|
+
export type BuiltinStatusLineSegmentId =
|
|
58
|
+
(typeof BUILTIN_STATUS_LINE_SEGMENT_IDS)[number];
|
|
59
|
+
|
|
60
|
+
// Segment identifiers (built-in + dynamically registered custom items)
|
|
61
|
+
export type StatusLineSegmentId =
|
|
62
|
+
BuiltinStatusLineSegmentId | `custom:${string}`;
|
|
63
|
+
|
|
64
|
+
// Separator styles
|
|
65
|
+
export type StatusLineSeparatorStyle =
|
|
66
|
+
| "powerline"
|
|
67
|
+
| "powerline-thin"
|
|
68
|
+
| "slash"
|
|
69
|
+
| "pipe"
|
|
70
|
+
| "block"
|
|
71
|
+
| "none"
|
|
72
|
+
| "ascii"
|
|
73
|
+
| "dot"
|
|
74
|
+
| "chevron"
|
|
75
|
+
| "star";
|
|
76
|
+
|
|
77
|
+
// Preset names
|
|
78
|
+
export type PowerlinePlacement = "above" | "below";
|
|
79
|
+
|
|
80
|
+
export type StatusLinePreset =
|
|
81
|
+
"default" | "minimal" | "compact" | "full" | "nerd" | "ascii" | "chef";
|
|
82
|
+
|
|
83
|
+
// Per-segment options
|
|
84
|
+
export interface StatusLineSegmentOptions {
|
|
85
|
+
model?: { showThinkingLevel?: boolean; display?: "name" | "qualified" };
|
|
86
|
+
path?: {
|
|
87
|
+
mode?: "basename" | "abbreviated" | "full";
|
|
88
|
+
maxLength?: number;
|
|
89
|
+
};
|
|
90
|
+
git?: {
|
|
91
|
+
showBranch?: boolean;
|
|
92
|
+
showStaged?: boolean;
|
|
93
|
+
showUnstaged?: boolean;
|
|
94
|
+
showUntracked?: boolean;
|
|
95
|
+
polling?: "full" | "branch" | "off";
|
|
96
|
+
/** Replace the branch icon with the origin remote's host logo
|
|
97
|
+
* (GitHub/GitLab/Bitbucket, or a generic git logo). Default false. */
|
|
98
|
+
hostIcon?: boolean;
|
|
99
|
+
};
|
|
100
|
+
time?: { format?: "12h" | "24h"; showSeconds?: boolean };
|
|
101
|
+
cost?: {
|
|
102
|
+
subscriptionDisplay?: "subscription" | "reported-cost" | "both";
|
|
103
|
+
currency?: CostCurrencyCode;
|
|
104
|
+
};
|
|
105
|
+
context?: { format?: "full" | "percent" };
|
|
106
|
+
cache_read?: { format?: "tokens" | "percent" | "both" };
|
|
107
|
+
openPorts?: {
|
|
108
|
+
/** Include UDP listeners (mDNS/DHCP/ephemeral) in the count. Default false. */ includeUdp?: boolean;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export type CustomItemPosition = "left" | "right" | "secondary";
|
|
113
|
+
|
|
114
|
+
export interface StatusLineLayout {
|
|
115
|
+
left?: StatusLineSegmentId[];
|
|
116
|
+
right?: StatusLineSegmentId[];
|
|
117
|
+
secondary?: StatusLineSegmentId[];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface CustomStatusItem {
|
|
121
|
+
id: string;
|
|
122
|
+
statusKey: string;
|
|
123
|
+
position: CustomItemPosition;
|
|
124
|
+
color?: ColorValue;
|
|
125
|
+
prefix?: string;
|
|
126
|
+
hideWhenMissing: boolean;
|
|
127
|
+
excludeFromExtensionStatuses: boolean;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Preset definition
|
|
131
|
+
export interface PresetDef {
|
|
132
|
+
leftSegments: StatusLineSegmentId[];
|
|
133
|
+
rightSegments: StatusLineSegmentId[];
|
|
134
|
+
/** Secondary row segments (shown in footer, above sub bar) */
|
|
135
|
+
secondarySegments?: StatusLineSegmentId[];
|
|
136
|
+
separator: StatusLineSeparatorStyle;
|
|
137
|
+
segmentOptions?: StatusLineSegmentOptions;
|
|
138
|
+
/** Color scheme for this preset */
|
|
139
|
+
colors?: ColorScheme;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Separator definition
|
|
143
|
+
export interface SeparatorDef {
|
|
144
|
+
left: string;
|
|
145
|
+
right: string;
|
|
146
|
+
endCaps?: {
|
|
147
|
+
left: string;
|
|
148
|
+
right: string;
|
|
149
|
+
useBgAsFg: boolean;
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Git status data
|
|
154
|
+
export interface GitStatus {
|
|
155
|
+
branch: string | null;
|
|
156
|
+
staged: number;
|
|
157
|
+
unstaged: number;
|
|
158
|
+
untracked: number;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Usage statistics
|
|
162
|
+
export interface QueueSummary {
|
|
163
|
+
queueCount: number;
|
|
164
|
+
ideaCount: number;
|
|
165
|
+
blockedCount: number;
|
|
166
|
+
compacting: boolean;
|
|
167
|
+
leadingText: string | null;
|
|
168
|
+
leadingIntent: "steer" | "follow-up" | "post-compact" | "idea" | null;
|
|
169
|
+
leadingStatus: "queued" | "blocked" | "delivering" | "sent" | "failed" | null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface UsageStats {
|
|
173
|
+
input: number;
|
|
174
|
+
output: number;
|
|
175
|
+
cacheRead: number;
|
|
176
|
+
cacheWrite: number;
|
|
177
|
+
cost: number;
|
|
178
|
+
// Cumulative cost of subagent child runs (e.g. /parallel, /worker) launched from this session.
|
|
179
|
+
subagentCost: number;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Context passed to segment render functions
|
|
183
|
+
export interface SegmentContext {
|
|
184
|
+
// From pi-mono
|
|
185
|
+
model:
|
|
186
|
+
| {
|
|
187
|
+
id: string;
|
|
188
|
+
name?: string;
|
|
189
|
+
provider?: string;
|
|
190
|
+
providerId?: string;
|
|
191
|
+
providerName?: string;
|
|
192
|
+
reasoning?: boolean;
|
|
193
|
+
contextWindow?: number;
|
|
194
|
+
}
|
|
195
|
+
| undefined;
|
|
196
|
+
thinkingLevel: string;
|
|
197
|
+
sessionId: string | undefined;
|
|
198
|
+
cwd?: string;
|
|
199
|
+
|
|
200
|
+
// Computed
|
|
201
|
+
usageStats: UsageStats;
|
|
202
|
+
contextTokens: number;
|
|
203
|
+
contextPercent: number;
|
|
204
|
+
contextWindow: number;
|
|
205
|
+
autoCompactEnabled: boolean;
|
|
206
|
+
customCompactionEnabled: boolean;
|
|
207
|
+
usingSubscription: boolean;
|
|
208
|
+
queueSummary: QueueSummary;
|
|
209
|
+
sessionStartTime: number;
|
|
210
|
+
shellModeActive: boolean;
|
|
211
|
+
shellRunning: boolean;
|
|
212
|
+
shellName: string | null;
|
|
213
|
+
shellCwd: string | null;
|
|
214
|
+
|
|
215
|
+
// Git
|
|
216
|
+
git: GitStatus;
|
|
217
|
+
|
|
218
|
+
// Extension statuses
|
|
219
|
+
extensionStatuses: ReadonlyMap<string, string>;
|
|
220
|
+
hiddenExtensionStatusKeys: ReadonlySet<string>;
|
|
221
|
+
customItemsById: ReadonlyMap<string, CustomStatusItem>;
|
|
222
|
+
|
|
223
|
+
// Options
|
|
224
|
+
options: StatusLineSegmentOptions;
|
|
225
|
+
/** Per-segment custom text labels (from powerline.segmentLabels). */
|
|
226
|
+
segmentLabels: ReadonlyMap<string, string>;
|
|
227
|
+
|
|
228
|
+
// Theming
|
|
229
|
+
theme: ThemeLike;
|
|
230
|
+
colors: ColorScheme;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Rendered segment output
|
|
234
|
+
export interface RenderedSegment {
|
|
235
|
+
content: string;
|
|
236
|
+
visible: boolean;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Segment definition
|
|
240
|
+
export interface StatusLineSegment {
|
|
241
|
+
id: StatusLineSegmentId;
|
|
242
|
+
render(ctx: SegmentContext): RenderedSegment;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// User-defined computed segment (configured via settings, no TS needed)
|
|
246
|
+
export type CustomSegmentConfig =
|
|
247
|
+
| {
|
|
248
|
+
type: "command";
|
|
249
|
+
command: string;
|
|
250
|
+
prefix?: string;
|
|
251
|
+
color?: ColorValue;
|
|
252
|
+
/** Cache command output for this many ms to avoid re-running every paint */
|
|
253
|
+
cacheMs?: number;
|
|
254
|
+
}
|
|
255
|
+
| {
|
|
256
|
+
type: "env";
|
|
257
|
+
env: string;
|
|
258
|
+
prefix?: string;
|
|
259
|
+
color?: ColorValue;
|
|
260
|
+
fallback?: string;
|
|
261
|
+
}
|
|
262
|
+
| {
|
|
263
|
+
type: "static";
|
|
264
|
+
text: string;
|
|
265
|
+
prefix?: string;
|
|
266
|
+
color?: ColorValue;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// User-defined preset (configured via settings, merges with built-ins)
|
|
270
|
+
export interface CustomPresetConfig {
|
|
271
|
+
left?: StatusLineSegmentId[];
|
|
272
|
+
right?: StatusLineSegmentId[];
|
|
273
|
+
secondary?: StatusLineSegmentId[];
|
|
274
|
+
separator?: StatusLineSeparatorStyle;
|
|
275
|
+
colors?: ColorScheme;
|
|
276
|
+
segmentOptions?: StatusLineSegmentOptions;
|
|
277
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* frontmatter.ts
|
|
3
|
+
* ------------------------------------------------------------------------
|
|
4
|
+
* Utility for stripping YAML frontmatter from markdown/text files.
|
|
5
|
+
* Used by skills system to extract the actual content from skill files.
|
|
6
|
+
* ------------------------------------------------------------------------
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Strips YAML frontmatter from a text file.
|
|
11
|
+
* Frontmatter is delimited by --- at the start and end.
|
|
12
|
+
*
|
|
13
|
+
* @param content - The full file content including potential frontmatter
|
|
14
|
+
* @returns The content with frontmatter removed
|
|
15
|
+
*/
|
|
16
|
+
export function stripFrontmatter(content: string): string {
|
|
17
|
+
const lines = content.split("\n");
|
|
18
|
+
|
|
19
|
+
// Check if file starts with frontmatter delimiter
|
|
20
|
+
if (lines[0]?.trim() !== "---") {
|
|
21
|
+
return content;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Find the closing delimiter
|
|
25
|
+
let endIndex = -1;
|
|
26
|
+
for (let i = 1; i < lines.length; i++) {
|
|
27
|
+
if (lines[i]?.trim() === "---") {
|
|
28
|
+
endIndex = i;
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// If no closing delimiter found, return original content
|
|
34
|
+
if (endIndex === -1) {
|
|
35
|
+
return content;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Return everything after the frontmatter without changing user-authored whitespace
|
|
39
|
+
return lines.slice(endIndex + 1).join("\n");
|
|
40
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AutocompleteProvider } from "@earendil-works/pi-tui";
|
|
2
|
+
|
|
3
|
+
function isObject(val: unknown): val is Record<string, unknown> {
|
|
4
|
+
return val !== null && typeof val === "object";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function isProvider(val: unknown): val is AutocompleteProvider {
|
|
8
|
+
if (!isObject(val)) return false;
|
|
9
|
+
return typeof val.getSuggestions === "function" && typeof val.applyCompletion === "function";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getEditorAutocompleteProvider(sourceEditor: unknown): AutocompleteProvider | undefined {
|
|
13
|
+
if (!isObject(sourceEditor) || !("autocompleteProvider" in sourceEditor)) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const provider = sourceEditor.autocompleteProvider;
|
|
17
|
+
if (isProvider(provider)) {
|
|
18
|
+
return provider;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function passAutocompleteProviderThroughPreviousEditor(
|
|
24
|
+
provider: AutocompleteProvider,
|
|
25
|
+
previousEditor: unknown,
|
|
26
|
+
): AutocompleteProvider {
|
|
27
|
+
if (!isObject(previousEditor)) {
|
|
28
|
+
return provider;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if ("setAutocompleteProvider" in previousEditor && typeof previousEditor.setAutocompleteProvider === "function") {
|
|
32
|
+
previousEditor.setAutocompleteProvider(provider);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const existingProvider = getEditorAutocompleteProvider(previousEditor);
|
|
36
|
+
if (existingProvider !== undefined) {
|
|
37
|
+
return existingProvider;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return provider;
|
|
41
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
import { parsePowerlineConfig } from "../config/powerline-config.ts";
|
|
4
|
+
import { registerCustomPresets } from "../config/presets.ts";
|
|
5
|
+
import { registerCustomSegments } from "../segments/index.ts";
|
|
6
|
+
import { readSettings } from "./settings-io.ts";
|
|
7
|
+
import { registerSessionLifecycle } from "./session-lifecycle.ts";
|
|
8
|
+
import { registerCommands } from "./commands.ts";
|
|
9
|
+
import { setupInlineInvocation } from "./skills/inline-invocation.ts";
|
|
10
|
+
import {
|
|
11
|
+
config,
|
|
12
|
+
createRuntimeState,
|
|
13
|
+
PRESET_NAMES,
|
|
14
|
+
setConfig,
|
|
15
|
+
} from "./state.ts";
|
|
16
|
+
|
|
17
|
+
export default function powerlineFooter(pi: ExtensionAPI) {
|
|
18
|
+
const startupSettings = readSettings();
|
|
19
|
+
setConfig(parsePowerlineConfig(startupSettings.powerline, PRESET_NAMES));
|
|
20
|
+
registerCustomSegments(config.segments);
|
|
21
|
+
registerCustomPresets(config.presets);
|
|
22
|
+
|
|
23
|
+
const rt = createRuntimeState(startupSettings);
|
|
24
|
+
|
|
25
|
+
registerSessionLifecycle(pi, rt);
|
|
26
|
+
registerCommands(pi, rt);
|
|
27
|
+
setupInlineInvocation(pi, rt);
|
|
28
|
+
}
|