@openlines/opl-pi-sht 0.2.11 → 0.3.0
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 +92 -82
- package/configs/opl-browser.json.sample +3 -2
- package/configs/opl-footer.json.sample +3 -2
- package/configs/opl-guardian.json.sample +8 -2
- package/configs/opl-modes.json.sample +5 -0
- package/configs/opl-webaccess.json.sample +7 -1
- package/extensions/opl-browser/README.md +81 -55
- package/extensions/opl-browser/browser.ts +244 -50
- package/extensions/opl-browser/config.ts +15 -7
- package/extensions/opl-browser/index.ts +5 -4
- package/extensions/opl-browser/package.json +4 -4
- package/extensions/opl-browser/validate.ts +313 -60
- package/extensions/opl-configurator/README.md +40 -0
- package/extensions/opl-configurator/configurator.ts +148 -0
- package/extensions/opl-configurator/index.ts +12 -0
- package/extensions/opl-ctxtrim/README.md +45 -14
- package/extensions/opl-ctxtrim/index.ts +33 -13
- package/extensions/opl-footer/README.md +32 -7
- package/extensions/opl-footer/config.ts +11 -10
- package/extensions/opl-footer/configure.ts +157 -115
- package/extensions/opl-footer/git-status.ts +58 -1
- package/extensions/opl-footer/icons.ts +3 -0
- package/extensions/opl-footer/index.ts +287 -109
- package/extensions/opl-footer/segments/caveman.ts +9 -1
- package/extensions/opl-footer/segments/chat-mode.ts +8 -1
- package/extensions/opl-footer/segments/compactions.ts +13 -0
- package/extensions/opl-footer/segments/context.ts +6 -5
- package/extensions/opl-footer/segments/index.ts +2 -0
- package/extensions/opl-footer/segments/mode-switcher.ts +14 -1
- package/extensions/opl-footer/segments/path.ts +1 -1
- package/extensions/opl-footer/segments/plan-mode.ts +8 -1
- package/extensions/opl-footer/theme.ts +5 -2
- package/extensions/opl-footer/types.ts +4 -0
- package/extensions/opl-guardian/README.md +17 -5
- package/extensions/opl-guardian/config.ts +72 -6
- package/extensions/opl-guardian/guardian.ts +42 -10
- package/extensions/opl-guardian/index.ts +52 -17
- package/extensions/opl-guardian/policies.ts +52 -16
- package/extensions/opl-init/README.md +12 -4
- package/extensions/opl-init/index.ts +228 -27
- package/extensions/opl-input/README.md +17 -3
- package/extensions/opl-input/config.ts +59 -5
- package/extensions/opl-input/configure.ts +144 -0
- package/extensions/opl-input/index.ts +38 -30
- package/extensions/opl-input/mode-style.ts +6 -2
- package/extensions/opl-modes/README.md +16 -4
- package/extensions/opl-modes/config.ts +62 -8
- package/extensions/opl-modes/index.ts +48 -17
- package/extensions/opl-modes/state.ts +50 -2
- package/extensions/opl-modes/types.ts +6 -0
- package/extensions/opl-modes/utils.ts +18 -8
- package/extensions/opl-questionnaire/index.ts +10 -4
- package/extensions/opl-questionnaire/types.ts +4 -0
- package/extensions/opl-simplebench/README.md +21 -15
- package/extensions/opl-simplebench/artifact.ts +7 -4
- package/extensions/opl-simplebench/benchmark.ts +96 -35
- package/extensions/opl-simplebench/coding.ts +9 -4
- package/extensions/opl-simplebench/index.ts +19 -19
- package/extensions/opl-simplebench/llama-server.ts +3 -2
- package/extensions/opl-simplebench/report.ts +0 -6
- package/extensions/opl-simplebench/research.ts +5 -4
- package/extensions/opl-simplebench/scoring.ts +4 -2
- package/extensions/opl-simplebench/util/config.ts +15 -8
- package/extensions/opl-simplebench/util/exec-env.ts +36 -0
- package/extensions/opl-simplebench/util/format.ts +2 -2
- package/extensions/opl-simplebench/util/providers.ts +23 -194
- package/extensions/opl-todo/README.md +5 -5
- package/extensions/opl-todo/config.ts +7 -2
- package/extensions/opl-todo/index.ts +50 -9
- package/extensions/opl-webaccess/README.md +31 -5
- package/extensions/opl-webaccess/config.ts +36 -7
- package/extensions/opl-webaccess/extract.ts +37 -47
- package/extensions/opl-webaccess/http.ts +259 -0
- package/extensions/opl-webaccess/index.ts +12 -6
- package/extensions/opl-webaccess/package.json +4 -9
- package/extensions/opl-webaccess/pdf.ts +4 -4
- package/extensions/opl-webaccess/search.ts +19 -8
- package/extensions/opl-webaccess/storage.ts +134 -2
- package/extensions/opl-webaccess/types.ts +4 -0
- package/extensions/opl-webaccess/utils.ts +221 -33
- package/images/ss-init.png +0 -0
- package/images/ss-input-footer.png +0 -0
- package/images/ss-input-pet.png +0 -0
- package/images/ss-mode-custom.png +0 -0
- package/images/ss-questionnaire0.png +0 -0
- package/images/ss-questionnaire1.png +0 -0
- package/images/ss-simplebench.png +0 -0
- package/images/ss-todo.png +0 -0
- package/package.json +20 -12
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { ExtensionContext, KeybindingsManager, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { Key, matchesKey, SettingsList, truncateToWidth, type SettingItem } from "@earendil-works/pi-tui";
|
|
4
4
|
|
|
@@ -29,129 +29,171 @@ function segmentLabel(segment: string): string {
|
|
|
29
29
|
return segment.replaceAll("_", " ");
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
// The configurator seam. opl-configurator owns the /configurator command and reads this
|
|
33
|
+
// globalThis array lazily; the entry shape is duplicated by hand on both sides because
|
|
34
|
+
// install.sh copies one extension directory at a time.
|
|
35
|
+
export const CONFIGURATOR_REGISTRY_KEY = "__oplConfiguratorTabs";
|
|
36
|
+
|
|
37
|
+
/** What the configurator shell hands to each tab's factory. */
|
|
38
|
+
export interface ConfiguratorTabApi {
|
|
39
|
+
ctx: ExtensionContext;
|
|
40
|
+
tui: { requestRender(): void };
|
|
41
|
+
theme: Theme;
|
|
42
|
+
keybindings: KeybindingsManager;
|
|
43
|
+
done: (result: void) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ConfiguratorTabComponent {
|
|
47
|
+
render(width: number): string[];
|
|
48
|
+
invalidate?(): void;
|
|
49
|
+
handleInput?(data: string): void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface ConfiguratorTab {
|
|
53
|
+
id: string;
|
|
54
|
+
hotkey: string;
|
|
55
|
+
label: string;
|
|
56
|
+
create: (api: ConfiguratorTabApi) => ConfiguratorTabComponent;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Publish the footer's settings/reorder screen as a tab, once per extension load. */
|
|
60
|
+
export function registerFooterConfiguratorTab(onSaved: (ctx: ExtensionContext) => void): void {
|
|
61
|
+
const tab: ConfiguratorTab = {
|
|
62
|
+
id: "opl-footer",
|
|
63
|
+
hotkey: "f",
|
|
64
|
+
label: "Footer",
|
|
65
|
+
create: (api) => createFooterComponent(api, onSaved),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const holder = globalThis as Record<string, unknown>;
|
|
69
|
+
const registry = holder[CONFIGURATOR_REGISTRY_KEY];
|
|
70
|
+
if (Array.isArray(registry)) {
|
|
71
|
+
if (!registry.some((entry) => entry?.id === "opl-footer")) registry.push(tab);
|
|
72
|
+
} else {
|
|
73
|
+
holder[CONFIGURATOR_REGISTRY_KEY] = [tab];
|
|
36
74
|
}
|
|
75
|
+
}
|
|
37
76
|
|
|
77
|
+
function createFooterComponent(
|
|
78
|
+
api: ConfiguratorTabApi,
|
|
79
|
+
onSaved: (ctx: ExtensionContext) => void,
|
|
80
|
+
): ConfiguratorTabComponent {
|
|
81
|
+
const { ctx, tui, theme, done } = api;
|
|
38
82
|
let config = loadUserConfig() ?? {};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
let settingsLists: SettingsList[] = [];
|
|
44
|
-
|
|
45
|
-
const persist = (next: typeof config, selectedId?: string): boolean => {
|
|
46
|
-
try {
|
|
47
|
-
saveUserConfig(next);
|
|
48
|
-
config = next;
|
|
49
|
-
settingsLists = createSettingsLists();
|
|
50
|
-
if (selectedId) restoreSelectedItem(settingsLists, activeTab, selectedId);
|
|
51
|
-
onSaved();
|
|
52
|
-
return true;
|
|
53
|
-
} catch (error) {
|
|
54
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
55
|
-
ctx.ui.notify(`Could not save footer config: ${message}`, "error");
|
|
56
|
-
done(undefined);
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const save = (id: string, value: string) => {
|
|
62
|
-
const [key, segment, kind] = id.split(":") as [FooterLayoutKey, typeof CONFIGURABLE_SEGMENTS[number], string];
|
|
63
|
-
const next = kind === "separator"
|
|
64
|
-
? setSegmentSeparator(config, key, segment, value === "shown")
|
|
65
|
-
: setLayoutSegment(config, key, segment, value === "shown");
|
|
66
|
-
persist(next, id);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const createSettingsLists = (): SettingsList[] => FOOTER_LAYOUT_KEYS.map((key) => {
|
|
70
|
-
const layout = getLayoutSegments(config, key);
|
|
71
|
-
const items: SettingItem[] = CONFIGURABLE_SEGMENTS.flatMap((segment) => {
|
|
72
|
-
const index = layout.indexOf(segment);
|
|
73
|
-
return [
|
|
74
|
-
{
|
|
75
|
-
id: `${key}:${segment}:segment`,
|
|
76
|
-
label: segmentLabel(segment),
|
|
77
|
-
currentValue: index === -1 ? "hidden" : "shown",
|
|
78
|
-
values: ["shown", "hidden"],
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
id: `${key}:${segment}:separator`,
|
|
82
|
-
label: `${segmentLabel(segment)} separator`,
|
|
83
|
-
currentValue: hasSegmentSeparator(config, key, segment) ? "shown" : "hidden",
|
|
84
|
-
values: ["shown", "hidden"],
|
|
85
|
-
},
|
|
86
|
-
];
|
|
87
|
-
});
|
|
88
|
-
return new SettingsList(items, 15, getSettingsListTheme(), save, () => done(undefined));
|
|
89
|
-
});
|
|
83
|
+
let activeTab = 0;
|
|
84
|
+
let view: "settings" | "reorder" = "settings";
|
|
85
|
+
let reorderIndex = 0;
|
|
86
|
+
let settingsLists: SettingsList[] = [];
|
|
90
87
|
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
const persist = (next: typeof config, selectedId?: string): boolean => {
|
|
89
|
+
try {
|
|
90
|
+
saveUserConfig(next);
|
|
91
|
+
config = next;
|
|
92
|
+
settingsLists = createSettingsLists();
|
|
93
|
+
if (selectedId) restoreSelectedItem(settingsLists, activeTab, selectedId);
|
|
94
|
+
onSaved(ctx);
|
|
95
|
+
return true;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
98
|
+
ctx.ui.notify(`Could not save footer config: ${message}`, "error");
|
|
99
|
+
done(undefined);
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
93
103
|
|
|
94
|
-
|
|
104
|
+
const save = (id: string, value: string) => {
|
|
105
|
+
const [key, segment, kind] = id.split(":") as [FooterLayoutKey, typeof CONFIGURABLE_SEGMENTS[number], string];
|
|
106
|
+
const next = kind === "separator"
|
|
107
|
+
? setSegmentSeparator(config, key, segment, value === "shown")
|
|
108
|
+
: setLayoutSegment(config, key, segment, value === "shown");
|
|
109
|
+
persist(next, id);
|
|
110
|
+
};
|
|
95
111
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
const createSettingsLists = (): SettingsList[] => FOOTER_LAYOUT_KEYS.map((key) => {
|
|
113
|
+
const layout = getLayoutSegments(config, key);
|
|
114
|
+
const items: SettingItem[] = CONFIGURABLE_SEGMENTS.flatMap((segment) => {
|
|
115
|
+
const index = layout.indexOf(segment);
|
|
116
|
+
return [
|
|
117
|
+
{
|
|
118
|
+
id: `${key}:${segment}:segment`,
|
|
119
|
+
label: segmentLabel(segment),
|
|
120
|
+
currentValue: index === -1 ? "hidden" : "shown",
|
|
121
|
+
values: ["shown", "hidden"],
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: `${key}:${segment}:separator`,
|
|
125
|
+
label: `${segmentLabel(segment)} separator`,
|
|
126
|
+
currentValue: hasSegmentSeparator(config, key, segment) ? "shown" : "hidden",
|
|
127
|
+
values: ["shown", "hidden"],
|
|
128
|
+
},
|
|
129
|
+
];
|
|
130
|
+
});
|
|
131
|
+
return new SettingsList(items, 15, getSettingsListTheme(), save, () => done(undefined));
|
|
132
|
+
});
|
|
112
133
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
134
|
+
const visibleSegments = (): typeof CONFIGURABLE_SEGMENTS[number][] =>
|
|
135
|
+
getLayoutSegments(config, FOOTER_LAYOUT_KEYS[activeTab]!).filter((segment) => CONFIGURABLE_SEGMENTS.includes(segment));
|
|
136
|
+
|
|
137
|
+
settingsLists = createSettingsLists();
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
render(width: number) {
|
|
141
|
+
const tabs = FOOTER_LAYOUT_KEYS.map((key, index) =>
|
|
142
|
+
theme.fg(index === activeTab ? "accent" : "dim", `[${LAYOUT_LABELS[key]}]`),
|
|
143
|
+
).join(" ");
|
|
144
|
+
const header = [
|
|
145
|
+
truncateToWidth(theme.bold("Configure OPL Footer"), width),
|
|
146
|
+
truncateToWidth(tabs, width),
|
|
147
|
+
];
|
|
148
|
+
if (view === "settings") {
|
|
121
149
|
return [
|
|
122
150
|
...header,
|
|
123
|
-
truncateToWidth(theme.fg("dim", "
|
|
124
|
-
...
|
|
151
|
+
truncateToWidth(theme.fg("dim", "←/→ switch layout · r reorder · Changes apply immediately"), width),
|
|
152
|
+
...settingsLists[activeTab]!.render(width),
|
|
125
153
|
];
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
reorderIndex
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const segments = visibleSegments();
|
|
157
|
+
reorderIndex = Math.min(reorderIndex, Math.max(0, segments.length - 1));
|
|
158
|
+
const rows = segments.length === 0
|
|
159
|
+
? [theme.fg("dim", "No visible standard segments.")]
|
|
160
|
+
: segments.map((segment, index) => {
|
|
161
|
+
const text = `${index === reorderIndex ? "›" : " "} ${segmentLabel(segment)}${hasSegmentSeparator(config, FOOTER_LAYOUT_KEYS[activeTab]!, segment) ? " + separator" : ""}`;
|
|
162
|
+
return index === reorderIndex ? theme.fg("accent", text) : text;
|
|
163
|
+
});
|
|
164
|
+
return [
|
|
165
|
+
...header,
|
|
166
|
+
truncateToWidth(theme.fg("dim", "↑/↓ select · ,/. move · r settings · esc close"), width),
|
|
167
|
+
...rows.map((row) => truncateToWidth(row, width)),
|
|
168
|
+
];
|
|
169
|
+
},
|
|
170
|
+
invalidate() { settingsLists.forEach((list) => list.invalidate()); },
|
|
171
|
+
handleInput(data: string) {
|
|
172
|
+
if (matchesKey(data, Key.left)) {
|
|
173
|
+
activeTab = nextTabIndex(activeTab, "left", FOOTER_LAYOUT_KEYS.length);
|
|
174
|
+
reorderIndex = 0;
|
|
175
|
+
} else if (matchesKey(data, Key.right)) {
|
|
176
|
+
activeTab = nextTabIndex(activeTab, "right", FOOTER_LAYOUT_KEYS.length);
|
|
177
|
+
reorderIndex = 0;
|
|
178
|
+
} else if (view === "reorder") {
|
|
179
|
+
const segments = visibleSegments();
|
|
180
|
+
if (data === "r") view = "settings";
|
|
181
|
+
else if (matchesKey(data, Key.escape)) done(undefined);
|
|
182
|
+
else if (matchesKey(data, Key.up) && segments.length > 0) reorderIndex = (reorderIndex + segments.length - 1) % segments.length;
|
|
183
|
+
else if (matchesKey(data, Key.down) && segments.length > 0) reorderIndex = (reorderIndex + 1) % segments.length;
|
|
184
|
+
else if ((data === "," || data === ".") && segments[reorderIndex]) {
|
|
185
|
+
const next = moveLayoutSegment(config, FOOTER_LAYOUT_KEYS[activeTab]!, segments[reorderIndex]!, data === "," ? "up" : "down");
|
|
186
|
+
if (next !== config && persist(next)) {
|
|
187
|
+
reorderIndex = data === "," ? Math.max(0, reorderIndex - 1) : Math.min(segments.length - 1, reorderIndex + 1);
|
|
146
188
|
}
|
|
147
|
-
} else if (data === "r") {
|
|
148
|
-
view = "reorder";
|
|
149
|
-
reorderIndex = 0;
|
|
150
|
-
} else {
|
|
151
|
-
settingsLists[activeTab]!.handleInput?.(data);
|
|
152
189
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
190
|
+
} else if (data === "r") {
|
|
191
|
+
view = "reorder";
|
|
192
|
+
reorderIndex = 0;
|
|
193
|
+
} else {
|
|
194
|
+
settingsLists[activeTab]!.handleInput?.(data);
|
|
195
|
+
}
|
|
196
|
+
tui.requestRender();
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
@@ -78,9 +78,66 @@ function parseGitStatusOutput(output: string): { staged: number; unstaged: numbe
|
|
|
78
78
|
return { staged, unstaged, untracked };
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
// BEGIN SHARED GIT GUARD
|
|
82
|
+
// A repository is attacker-chosen data: its local config, `.gitattributes` and system
|
|
83
|
+
// config can name programs that git runs while answering what looks like a read-only query.
|
|
84
|
+
// Measured on git 2.54: `git diff HEAD` executes a declared `[diff "x"] textconv` driver,
|
|
85
|
+
// while `--name-only`, `status --porcelain`, `ls-files` and `rev-parse` do not. Every call
|
|
86
|
+
// still goes through this guard so a future content-diff call cannot reintroduce the
|
|
87
|
+
// execution path. Duplicated per extension because install.sh installs one directory at a
|
|
88
|
+
// time; tests/net-guard-parity.test.mjs fails if the copies drift.
|
|
89
|
+
const SAFE_GIT_GLOBAL_ARGS = [
|
|
90
|
+
"--no-pager",
|
|
91
|
+
"-c",
|
|
92
|
+
"core.fsmonitor=false",
|
|
93
|
+
"-c",
|
|
94
|
+
"core.hooksPath=",
|
|
95
|
+
"-c",
|
|
96
|
+
"protocol.ext.allow=never",
|
|
97
|
+
"-c",
|
|
98
|
+
"credential.helper=",
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
/** Refuse external content handlers. Diff-family subcommands only: plumbing rejects them. */
|
|
102
|
+
const SAFE_GIT_DIFF_FLAGS = ["--no-ext-diff", "--no-textconv"];
|
|
103
|
+
const GIT_DIFF_COMMANDS = new Set(["diff", "log", "show", "stash", "range-diff", "format-patch"]);
|
|
104
|
+
|
|
105
|
+
const SAFE_GIT_ENV: Record<string, string> = {
|
|
106
|
+
// /etc/gitconfig and the user's own config can name helpers; a repo probe must not read them.
|
|
107
|
+
GIT_CONFIG_NOSYSTEM: "1",
|
|
108
|
+
GIT_ATTR_NOSYSTEM: "1",
|
|
109
|
+
GIT_PAGER: "cat",
|
|
110
|
+
// A credential prompt would hang the caller instead of failing fast.
|
|
111
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
112
|
+
GIT_ASKPASS: "",
|
|
113
|
+
SSH_ASKPASS: "",
|
|
114
|
+
// Read-only plumbing must not create or wait on index.lock.
|
|
115
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export function safeGitInvocation(args: string[]): string[] {
|
|
119
|
+
const [subcommand, ...rest] = args;
|
|
120
|
+
const diffFlags = subcommand && GIT_DIFF_COMMANDS.has(subcommand) ? SAFE_GIT_DIFF_FLAGS : [];
|
|
121
|
+
// A missing subcommand must not reach execFileSync/spawn as an undefined argv slot.
|
|
122
|
+
return subcommand
|
|
123
|
+
? [...SAFE_GIT_GLOBAL_ARGS, subcommand, ...diffFlags, ...rest]
|
|
124
|
+
: [...SAFE_GIT_GLOBAL_ARGS, ...rest];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function gitGuardEnv(base: Record<string, string | undefined> = process.env): Record<string, string> {
|
|
128
|
+
const env: Record<string, string> = {};
|
|
129
|
+
for (const [key, value] of Object.entries(base)) {
|
|
130
|
+
if (typeof value === "string") env[key] = value;
|
|
131
|
+
}
|
|
132
|
+
return { ...env, ...SAFE_GIT_ENV };
|
|
133
|
+
}
|
|
134
|
+
// END SHARED GIT GUARD
|
|
135
|
+
|
|
81
136
|
function runGit(args: string[], timeoutMs = 200): Promise<string | null> {
|
|
137
|
+
// Porcelain/name-only only; the guard above refuses patch content.
|
|
82
138
|
return new Promise((resolve) => {
|
|
83
|
-
const proc = spawn("git", args, {
|
|
139
|
+
const proc = spawn("git", safeGitInvocation(args), {
|
|
140
|
+
env: gitGuardEnv(),
|
|
84
141
|
stdio: ["ignore", "pipe", "pipe"],
|
|
85
142
|
});
|
|
86
143
|
|
|
@@ -3,6 +3,7 @@ export interface IconSet {
|
|
|
3
3
|
folder: string;
|
|
4
4
|
branch: string;
|
|
5
5
|
git: string;
|
|
6
|
+
compress: string;
|
|
6
7
|
separator: string;
|
|
7
8
|
}
|
|
8
9
|
|
|
@@ -12,6 +13,7 @@ export const NERD_ICONS: IconSet = {
|
|
|
12
13
|
folder: "\uF115", // nf-fa-folder_open
|
|
13
14
|
branch: "\uF126", // nf-fa-code_fork
|
|
14
15
|
git: "\uF1D3", // nf-fa-git
|
|
16
|
+
compress: "\u{F05C4}", // nf-md-zip_box
|
|
15
17
|
separator: "|",
|
|
16
18
|
};
|
|
17
19
|
|
|
@@ -21,6 +23,7 @@ export const ASCII_ICONS: IconSet = {
|
|
|
21
23
|
folder: "📂",
|
|
22
24
|
branch: "⎇",
|
|
23
25
|
git: "⎇",
|
|
26
|
+
compress: "📦",
|
|
24
27
|
separator: "|",
|
|
25
28
|
};
|
|
26
29
|
|