@neilurk12/pi-clean-footer 0.1.1 → 0.2.1
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 +2 -2
- package/package.json +9 -2
- package/src/config.ts +150 -0
- package/src/index.ts +14 -146
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# pi-clean-footer
|
|
2
2
|
|
|
3
|
-
Clean
|
|
3
|
+
Clean, minimal, and lightweight powerline-style footer extension for [pi](https://pi.dev).
|
|
4
4
|
|
|
5
5
|
Shows a compact split footer:
|
|
6
6
|
|
|
7
|
-

|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neilurk12/pi-clean-footer",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Clean
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Clean, minimal, and lightweight powerline-style footer extension for pi coding agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
"footer",
|
|
10
10
|
"terminal"
|
|
11
11
|
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/Neil-urk12/pi-dots.git"
|
|
15
|
+
},
|
|
12
16
|
"license": "MIT",
|
|
13
17
|
"files": [
|
|
14
18
|
"src",
|
|
@@ -24,5 +28,8 @@
|
|
|
24
28
|
"@earendil-works/pi-ai": "*",
|
|
25
29
|
"@earendil-works/pi-coding-agent": "*",
|
|
26
30
|
"@earendil-works/pi-tui": "*"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"typescript": "^6.0.3"
|
|
27
34
|
}
|
|
28
35
|
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_GIT_REFRESH_DEBOUNCE_MS = 500;
|
|
4
|
+
|
|
5
|
+
export type CleanFooterConfig = {
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
showGit?: boolean;
|
|
8
|
+
showTokens?: boolean;
|
|
9
|
+
showCache?: boolean;
|
|
10
|
+
showContext?: boolean;
|
|
11
|
+
showDirectory?: boolean;
|
|
12
|
+
showEffort?: boolean;
|
|
13
|
+
gitRefreshDebounceMs?: number;
|
|
14
|
+
contextWarningPercent?: number;
|
|
15
|
+
contextDangerPercent?: number;
|
|
16
|
+
modelAliases?: Record<string, string>;
|
|
17
|
+
colors?: Partial<ColorConfig>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type ResolvedConfig = Required<
|
|
21
|
+
Omit<CleanFooterConfig, "modelAliases" | "colors">
|
|
22
|
+
> & {
|
|
23
|
+
modelAliases: Record<string, string>;
|
|
24
|
+
colors: ColorConfig;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type ColorConfig = {
|
|
28
|
+
model: string;
|
|
29
|
+
directory: string;
|
|
30
|
+
git: string;
|
|
31
|
+
gitDirty: string;
|
|
32
|
+
contextNormal: string;
|
|
33
|
+
contextWarning: string;
|
|
34
|
+
contextDanger: string;
|
|
35
|
+
tokens: string;
|
|
36
|
+
separator: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type ConfigLoadResult = {
|
|
40
|
+
config: ResolvedConfig;
|
|
41
|
+
loadedPaths: string[];
|
|
42
|
+
error?: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const defaultConfig: ResolvedConfig = {
|
|
46
|
+
enabled: true,
|
|
47
|
+
showGit: true,
|
|
48
|
+
showTokens: true,
|
|
49
|
+
showCache: true,
|
|
50
|
+
showContext: true,
|
|
51
|
+
showDirectory: true,
|
|
52
|
+
showEffort: true,
|
|
53
|
+
gitRefreshDebounceMs: DEFAULT_GIT_REFRESH_DEBOUNCE_MS,
|
|
54
|
+
contextWarningPercent: 70,
|
|
55
|
+
contextDangerPercent: 85,
|
|
56
|
+
modelAliases: {},
|
|
57
|
+
colors: {
|
|
58
|
+
model: "accent",
|
|
59
|
+
directory: "dim",
|
|
60
|
+
git: "success",
|
|
61
|
+
gitDirty: "warning",
|
|
62
|
+
contextNormal: "success",
|
|
63
|
+
contextWarning: "warning",
|
|
64
|
+
contextDanger: "error",
|
|
65
|
+
tokens: "muted",
|
|
66
|
+
separator: "dim",
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export function loadConfig(paths: string[]): ConfigLoadResult {
|
|
71
|
+
const loaded: string[] = [];
|
|
72
|
+
let merged: CleanFooterConfig = {};
|
|
73
|
+
let error: string | undefined;
|
|
74
|
+
|
|
75
|
+
for (const configPath of paths) {
|
|
76
|
+
if (!existsSync(configPath)) continue;
|
|
77
|
+
try {
|
|
78
|
+
const parsed = JSON.parse(
|
|
79
|
+
readFileSync(configPath, "utf8"),
|
|
80
|
+
) as CleanFooterConfig;
|
|
81
|
+
merged = mergeConfig(merged, parsed);
|
|
82
|
+
loaded.push(configPath);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
error = `${configPath}: ${err instanceof Error ? err.message : String(err)}`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
config: resolveConfig(merged),
|
|
90
|
+
loadedPaths: loaded,
|
|
91
|
+
error,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function mergeConfig(
|
|
96
|
+
base: CleanFooterConfig,
|
|
97
|
+
override: CleanFooterConfig,
|
|
98
|
+
): CleanFooterConfig {
|
|
99
|
+
return {
|
|
100
|
+
...base,
|
|
101
|
+
...override,
|
|
102
|
+
modelAliases: {
|
|
103
|
+
...(base.modelAliases ?? {}),
|
|
104
|
+
...(override.modelAliases ?? {}),
|
|
105
|
+
},
|
|
106
|
+
colors: {
|
|
107
|
+
...(base.colors ?? {}),
|
|
108
|
+
...(override.colors ?? {}),
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function resolveConfig(config: CleanFooterConfig): ResolvedConfig {
|
|
114
|
+
return {
|
|
115
|
+
...defaultConfig,
|
|
116
|
+
...config,
|
|
117
|
+
gitRefreshDebounceMs: positiveNumber(
|
|
118
|
+
config.gitRefreshDebounceMs,
|
|
119
|
+
defaultConfig.gitRefreshDebounceMs,
|
|
120
|
+
),
|
|
121
|
+
contextWarningPercent: percentNumber(
|
|
122
|
+
config.contextWarningPercent,
|
|
123
|
+
defaultConfig.contextWarningPercent,
|
|
124
|
+
),
|
|
125
|
+
contextDangerPercent: percentNumber(
|
|
126
|
+
config.contextDangerPercent,
|
|
127
|
+
defaultConfig.contextDangerPercent,
|
|
128
|
+
),
|
|
129
|
+
modelAliases: {
|
|
130
|
+
...defaultConfig.modelAliases,
|
|
131
|
+
...(config.modelAliases ?? {}),
|
|
132
|
+
},
|
|
133
|
+
colors: { ...defaultConfig.colors, ...(config.colors ?? {}) },
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function positiveNumber(value: unknown, fallback: number): number {
|
|
138
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
139
|
+
? value
|
|
140
|
+
: fallback;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function percentNumber(value: unknown, fallback: number): number {
|
|
144
|
+
return typeof value === "number" &&
|
|
145
|
+
Number.isFinite(value) &&
|
|
146
|
+
value >= 0 &&
|
|
147
|
+
value <= 100
|
|
148
|
+
? value
|
|
149
|
+
: fallback;
|
|
150
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -5,49 +5,19 @@ import type {
|
|
|
5
5
|
} from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
7
7
|
import { execFile } from "node:child_process";
|
|
8
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
9
8
|
import os from "node:os";
|
|
10
9
|
import path from "node:path";
|
|
11
10
|
import { promisify } from "node:util";
|
|
12
11
|
|
|
13
12
|
const execFileAsync = promisify(execFile);
|
|
14
|
-
const DEFAULT_GIT_REFRESH_DEBOUNCE_MS = 500;
|
|
15
13
|
|
|
16
14
|
type Theme = ExtensionContext["ui"]["theme"];
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
showContext?: boolean;
|
|
24
|
-
showDirectory?: boolean;
|
|
25
|
-
showEffort?: boolean;
|
|
26
|
-
gitRefreshDebounceMs?: number;
|
|
27
|
-
contextWarningPercent?: number;
|
|
28
|
-
contextDangerPercent?: number;
|
|
29
|
-
modelAliases?: Record<string, string>;
|
|
30
|
-
colors?: Partial<ColorConfig>;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
type ResolvedConfig = Required<
|
|
34
|
-
Omit<CleanFooterConfig, "modelAliases" | "colors">
|
|
35
|
-
> & {
|
|
36
|
-
modelAliases: Record<string, string>;
|
|
37
|
-
colors: ColorConfig;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
type ColorConfig = {
|
|
41
|
-
model: string;
|
|
42
|
-
directory: string;
|
|
43
|
-
git: string;
|
|
44
|
-
gitDirty: string;
|
|
45
|
-
contextNormal: string;
|
|
46
|
-
contextWarning: string;
|
|
47
|
-
contextDanger: string;
|
|
48
|
-
tokens: string;
|
|
49
|
-
separator: string;
|
|
50
|
-
};
|
|
16
|
+
import {
|
|
17
|
+
defaultConfig,
|
|
18
|
+
loadConfig,
|
|
19
|
+
type ResolvedConfig,
|
|
20
|
+
} from "./config.js";
|
|
51
21
|
|
|
52
22
|
type GitState = {
|
|
53
23
|
inRepo: boolean;
|
|
@@ -74,31 +44,6 @@ type FooterRuntime = {
|
|
|
74
44
|
configError?: string;
|
|
75
45
|
};
|
|
76
46
|
|
|
77
|
-
const defaultConfig: ResolvedConfig = {
|
|
78
|
-
enabled: true,
|
|
79
|
-
showGit: true,
|
|
80
|
-
showTokens: true,
|
|
81
|
-
showCache: true,
|
|
82
|
-
showContext: true,
|
|
83
|
-
showDirectory: true,
|
|
84
|
-
showEffort: true,
|
|
85
|
-
gitRefreshDebounceMs: DEFAULT_GIT_REFRESH_DEBOUNCE_MS,
|
|
86
|
-
contextWarningPercent: 70,
|
|
87
|
-
contextDangerPercent: 85,
|
|
88
|
-
modelAliases: {},
|
|
89
|
-
colors: {
|
|
90
|
-
model: "accent",
|
|
91
|
-
directory: "dim",
|
|
92
|
-
git: "success",
|
|
93
|
-
gitDirty: "warning",
|
|
94
|
-
contextNormal: "success",
|
|
95
|
-
contextWarning: "warning",
|
|
96
|
-
contextDanger: "error",
|
|
97
|
-
tokens: "muted",
|
|
98
|
-
separator: "dim",
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
|
|
102
47
|
const runtime: FooterRuntime = {
|
|
103
48
|
enabled: true,
|
|
104
49
|
git: { inRepo: false, dirtyCount: 0 },
|
|
@@ -124,7 +69,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
124
69
|
}
|
|
125
70
|
|
|
126
71
|
if (command === "reload") {
|
|
127
|
-
|
|
72
|
+
loadRuntimeConfig(ctx.cwd);
|
|
128
73
|
runtime.enabled = runtime.config.enabled;
|
|
129
74
|
if (ctx.hasUI && runtime.enabled) installFooter(ctx);
|
|
130
75
|
if (ctx.hasUI && !runtime.enabled) ctx.ui.setFooter(undefined);
|
|
@@ -155,7 +100,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
155
100
|
});
|
|
156
101
|
|
|
157
102
|
pi.on("session_start", async (_event, ctx) => {
|
|
158
|
-
|
|
103
|
+
loadRuntimeConfig(ctx.cwd);
|
|
159
104
|
runtime.enabled = runtime.config.enabled;
|
|
160
105
|
runtime.thinkingLevel = normalizeThinkingLevel(pi.getThinkingLevel?.());
|
|
161
106
|
if (!ctx.hasUI || !runtime.enabled) return;
|
|
@@ -270,90 +215,13 @@ function installFooter(ctx: ExtensionContext) {
|
|
|
270
215
|
});
|
|
271
216
|
}
|
|
272
217
|
|
|
273
|
-
function
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
let merged: CleanFooterConfig = {};
|
|
281
|
-
let error: string | undefined;
|
|
282
|
-
|
|
283
|
-
for (const configPath of [configPaths.global, configPaths.project]) {
|
|
284
|
-
if (!existsSync(configPath)) continue;
|
|
285
|
-
try {
|
|
286
|
-
const parsed = JSON.parse(
|
|
287
|
-
readFileSync(configPath, "utf8"),
|
|
288
|
-
) as CleanFooterConfig;
|
|
289
|
-
merged = mergeConfig(merged, parsed);
|
|
290
|
-
loaded.push(configPath);
|
|
291
|
-
} catch (err) {
|
|
292
|
-
error = `${configPath}: ${err instanceof Error ? err.message : String(err)}`;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
runtime.configPaths = configPaths;
|
|
297
|
-
runtime.loadedConfigPaths = loaded;
|
|
298
|
-
runtime.configError = error;
|
|
299
|
-
runtime.config = resolveConfig(merged);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function mergeConfig(
|
|
303
|
-
base: CleanFooterConfig,
|
|
304
|
-
override: CleanFooterConfig,
|
|
305
|
-
): CleanFooterConfig {
|
|
306
|
-
return {
|
|
307
|
-
...base,
|
|
308
|
-
...override,
|
|
309
|
-
modelAliases: {
|
|
310
|
-
...(base.modelAliases ?? {}),
|
|
311
|
-
...(override.modelAliases ?? {}),
|
|
312
|
-
},
|
|
313
|
-
colors: {
|
|
314
|
-
...(base.colors ?? {}),
|
|
315
|
-
...(override.colors ?? {}),
|
|
316
|
-
},
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function resolveConfig(config: CleanFooterConfig): ResolvedConfig {
|
|
321
|
-
return {
|
|
322
|
-
...defaultConfig,
|
|
323
|
-
...config,
|
|
324
|
-
gitRefreshDebounceMs: positiveNumber(
|
|
325
|
-
config.gitRefreshDebounceMs,
|
|
326
|
-
defaultConfig.gitRefreshDebounceMs,
|
|
327
|
-
),
|
|
328
|
-
contextWarningPercent: percentNumber(
|
|
329
|
-
config.contextWarningPercent,
|
|
330
|
-
defaultConfig.contextWarningPercent,
|
|
331
|
-
),
|
|
332
|
-
contextDangerPercent: percentNumber(
|
|
333
|
-
config.contextDangerPercent,
|
|
334
|
-
defaultConfig.contextDangerPercent,
|
|
335
|
-
),
|
|
336
|
-
modelAliases: {
|
|
337
|
-
...defaultConfig.modelAliases,
|
|
338
|
-
...(config.modelAliases ?? {}),
|
|
339
|
-
},
|
|
340
|
-
colors: { ...defaultConfig.colors, ...(config.colors ?? {}) },
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function positiveNumber(value: unknown, fallback: number): number {
|
|
345
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
346
|
-
? value
|
|
347
|
-
: fallback;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function percentNumber(value: unknown, fallback: number): number {
|
|
351
|
-
return typeof value === "number" &&
|
|
352
|
-
Number.isFinite(value) &&
|
|
353
|
-
value >= 0 &&
|
|
354
|
-
value <= 100
|
|
355
|
-
? value
|
|
356
|
-
: fallback;
|
|
218
|
+
function loadRuntimeConfig(cwd: string) {
|
|
219
|
+
const projectPath = path.join(cwd, ".pi", "clean-footer.json");
|
|
220
|
+
const result = loadConfig([runtime.configPaths.global, projectPath]);
|
|
221
|
+
runtime.configPaths = { global: runtime.configPaths.global, project: projectPath };
|
|
222
|
+
runtime.loadedConfigPaths = result.loadedPaths;
|
|
223
|
+
runtime.configError = result.error;
|
|
224
|
+
runtime.config = result.config;
|
|
357
225
|
}
|
|
358
226
|
|
|
359
227
|
function notifyConfigStatus(ctx: ExtensionContext) {
|