@firstpick/pi-extension-bang-command-autocomplete 0.1.1 → 0.1.2
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 +16 -27
- package/index.ts +298 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Autocomplete for `!<command>` in Pi.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## What it does
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
7
|
+
- Suggests command names while typing `!<command>`.
|
|
8
|
+
- Uses a built-in common-command index out of the box.
|
|
9
|
+
- Learns commands you run via `!`/`!!` and persists them across Pi sessions.
|
|
10
|
+
- Learns full bang command lines (e.g. `!git add .`) and suggests them directly.
|
|
11
|
+
- Learns flags used with those commands (e.g. `!rg -n`) and suggests them when you type `!<command> ` or `!<command> -...`.
|
|
12
|
+
- Also suggests learned command+flag combos directly while typing `!<command>`.
|
|
13
|
+
- Optionally adds commands from shell history for personalized suggestions.
|
|
14
|
+
- Keeps scope intentionally narrow (command + flag completion only; no positional-argument prediction).
|
|
14
15
|
|
|
15
16
|
## Install
|
|
16
17
|
|
|
@@ -18,33 +19,21 @@ Make shell-style `!` execution faster and less error-prone by suggesting command
|
|
|
18
19
|
pi install npm:@firstpick/pi-extension-bang-command-autocomplete
|
|
19
20
|
```
|
|
20
21
|
|
|
21
|
-
Local testing:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
pi install /absolute/path/to/pi-extension-bang-command-autocomplete
|
|
25
|
-
```
|
|
26
|
-
|
|
27
22
|
## Configuration
|
|
28
23
|
|
|
29
24
|
- `PI_BANG_AUTOCOMPLETE_INCLUDE_HISTORY`
|
|
30
25
|
- `1|true|yes|on`: include commands from `~/.bash_history` and fish history.
|
|
31
|
-
- unset/other: use built-in
|
|
26
|
+
- unset/other: use built-in command list only (default).
|
|
27
|
+
- `PI_BANG_AUTOCOMPLETE_RUNTIME_STORE_PATH`
|
|
28
|
+
- optional absolute/relative file path for persisted learned commands.
|
|
29
|
+
- default: `~/.pi/agent/state/bang-command-autocomplete-runtime.json`.
|
|
30
|
+
- stores learned command names, learned full command lines, and per-command learned flags.
|
|
32
31
|
|
|
33
32
|
## Commands
|
|
34
33
|
|
|
35
|
-
- `/bang-refresh` —
|
|
36
|
-
- `/bang-status` —
|
|
34
|
+
- `/bang-refresh` — rebuild autocomplete index.
|
|
35
|
+
- `/bang-status` — show indexed command count, history-index status, runtime-learned command/line counts, and learned flag count.
|
|
37
36
|
|
|
38
37
|
## Tools
|
|
39
38
|
|
|
40
39
|
None.
|
|
41
|
-
|
|
42
|
-
## Publish
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
bun publish --access public
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
npm publish --access public
|
|
50
|
-
```
|
package/index.ts
CHANGED
|
@@ -3,7 +3,15 @@ import * as os from "node:os";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
5
5
|
|
|
6
|
-
type CommandSource = "common" | "history";
|
|
6
|
+
type CommandSource = "common" | "history" | "runtime";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_RUNTIME_STORE_PATH = path.join(
|
|
9
|
+
os.homedir(),
|
|
10
|
+
".pi",
|
|
11
|
+
"agent",
|
|
12
|
+
"state",
|
|
13
|
+
"bang-command-autocomplete-runtime.json",
|
|
14
|
+
);
|
|
7
15
|
|
|
8
16
|
function envFlag(name: string, fallback: boolean): boolean {
|
|
9
17
|
const raw = process.env[name]?.trim().toLowerCase();
|
|
@@ -57,19 +65,33 @@ const COMMON_COMMANDS = [
|
|
|
57
65
|
"btop",
|
|
58
66
|
] as const;
|
|
59
67
|
|
|
60
|
-
function
|
|
68
|
+
function parseCommandLine(commandLine: string): { executable?: string; flags: string[] } {
|
|
61
69
|
const trimmed = commandLine.trim();
|
|
62
|
-
if (!trimmed || trimmed.startsWith("#")) return
|
|
70
|
+
if (!trimmed || trimmed.startsWith("#")) return { flags: [] };
|
|
63
71
|
|
|
64
72
|
const tokens = trimmed.split(/\s+/).filter(Boolean);
|
|
65
|
-
if (tokens.length === 0) return
|
|
73
|
+
if (tokens.length === 0) return { flags: [] };
|
|
74
|
+
|
|
75
|
+
let startIndex = 0;
|
|
76
|
+
let executable = tokens[startIndex] ?? "";
|
|
77
|
+
|
|
78
|
+
if (executable === "sudo") {
|
|
79
|
+
startIndex += 1;
|
|
80
|
+
executable = tokens[startIndex] ?? "";
|
|
81
|
+
}
|
|
66
82
|
|
|
67
|
-
let executable = tokens[0] ?? "";
|
|
68
|
-
if (executable === "sudo") executable = tokens[1] ?? "";
|
|
69
83
|
if (executable.startsWith("!")) executable = executable.slice(1);
|
|
84
|
+
if (!executable) return { flags: [] };
|
|
70
85
|
|
|
71
|
-
|
|
72
|
-
|
|
86
|
+
const flags = tokens
|
|
87
|
+
.slice(startIndex + 1)
|
|
88
|
+
.filter((token) => token.startsWith("-") && token !== "-");
|
|
89
|
+
|
|
90
|
+
return { executable, flags };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function extractExecutable(commandLine: string): string | undefined {
|
|
94
|
+
return parseCommandLine(commandLine).executable;
|
|
73
95
|
}
|
|
74
96
|
|
|
75
97
|
function readFishHistoryExecutables(): string[] {
|
|
@@ -102,6 +124,97 @@ function readBashHistoryExecutables(): string[] {
|
|
|
102
124
|
.filter((v): v is string => Boolean(v));
|
|
103
125
|
}
|
|
104
126
|
|
|
127
|
+
function getRuntimeStorePath(): string {
|
|
128
|
+
const configured = process.env.PI_BANG_AUTOCOMPLETE_RUNTIME_STORE_PATH?.trim();
|
|
129
|
+
return configured ? path.resolve(configured) : DEFAULT_RUNTIME_STORE_PATH;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
type RuntimeStoreData = {
|
|
133
|
+
commands: Set<string>;
|
|
134
|
+
flagsByCommand: Map<string, Set<string>>;
|
|
135
|
+
lines: Set<string>;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
function readRuntimeData(storePath: string): RuntimeStoreData {
|
|
139
|
+
const empty: RuntimeStoreData = { commands: new Set<string>(), flagsByCommand: new Map<string, Set<string>>(), lines: new Set<string>() };
|
|
140
|
+
if (!fs.existsSync(storePath)) return empty;
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const parsed = JSON.parse(fs.readFileSync(storePath, "utf8")) as unknown;
|
|
144
|
+
|
|
145
|
+
// Backward compatibility with older format: string[]
|
|
146
|
+
if (Array.isArray(parsed)) {
|
|
147
|
+
for (const item of parsed) {
|
|
148
|
+
if (typeof item === "string" && item.trim()) {
|
|
149
|
+
const normalized = item.trim();
|
|
150
|
+
empty.commands.add(normalized);
|
|
151
|
+
empty.lines.add(normalized);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return empty;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!parsed || typeof parsed !== "object") return empty;
|
|
158
|
+
|
|
159
|
+
const commandsRaw = (parsed as { commands?: unknown }).commands;
|
|
160
|
+
if (Array.isArray(commandsRaw)) {
|
|
161
|
+
for (const item of commandsRaw) {
|
|
162
|
+
if (typeof item === "string" && item.trim()) {
|
|
163
|
+
empty.commands.add(item.trim());
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const flagsRaw = (parsed as { flags?: unknown }).flags;
|
|
169
|
+
if (flagsRaw && typeof flagsRaw === "object") {
|
|
170
|
+
for (const [command, flags] of Object.entries(flagsRaw)) {
|
|
171
|
+
if (!command.trim() || !Array.isArray(flags)) continue;
|
|
172
|
+
const normalizedFlags = flags
|
|
173
|
+
.map((flag) => (typeof flag === "string" ? flag.trim() : ""))
|
|
174
|
+
.filter(Boolean);
|
|
175
|
+
if (normalizedFlags.length === 0) continue;
|
|
176
|
+
empty.flagsByCommand.set(command.trim(), new Set(normalizedFlags));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const linesRaw = (parsed as { lines?: unknown }).lines;
|
|
181
|
+
if (Array.isArray(linesRaw)) {
|
|
182
|
+
for (const item of linesRaw) {
|
|
183
|
+
if (typeof item === "string" && item.trim()) {
|
|
184
|
+
empty.lines.add(item.trim());
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return empty;
|
|
190
|
+
} catch {
|
|
191
|
+
return empty;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function writeRuntimeData(storePath: string, data: RuntimeStoreData): void {
|
|
196
|
+
try {
|
|
197
|
+
fs.mkdirSync(path.dirname(storePath), { recursive: true });
|
|
198
|
+
|
|
199
|
+
const commands = Array.from(data.commands).sort((a, b) => a.localeCompare(b));
|
|
200
|
+
const flags: Record<string, string[]> = {};
|
|
201
|
+
const sortedCommands = Array.from(data.flagsByCommand.keys()).sort((a, b) => a.localeCompare(b));
|
|
202
|
+
|
|
203
|
+
for (const command of sortedCommands) {
|
|
204
|
+
const commandFlags = Array.from(data.flagsByCommand.get(command) ?? []).sort((a, b) => a.localeCompare(b));
|
|
205
|
+
if (commandFlags.length > 0) {
|
|
206
|
+
flags[command] = commandFlags;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const lines = Array.from(data.lines).sort((a, b) => a.localeCompare(b));
|
|
211
|
+
|
|
212
|
+
fs.writeFileSync(storePath, `${JSON.stringify({ commands, flags, lines }, null, 2)}\n`, "utf8");
|
|
213
|
+
} catch {
|
|
214
|
+
// Ignore persistence errors; autocomplete should still work in-memory.
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
105
218
|
function buildCommandIndex(includeHistory: boolean): Array<{ command: string; source: CommandSource }> {
|
|
106
219
|
const merged = new Map<string, CommandSource>();
|
|
107
220
|
|
|
@@ -125,6 +238,28 @@ function buildCommandIndex(includeHistory: boolean): Array<{ command: string; so
|
|
|
125
238
|
return Array.from(merged.entries()).map(([command, source]) => ({ command, source }));
|
|
126
239
|
}
|
|
127
240
|
|
|
241
|
+
function addRuntimeCommand(
|
|
242
|
+
index: Array<{ command: string; source: CommandSource }>,
|
|
243
|
+
command: string,
|
|
244
|
+
): Array<{ command: string; source: CommandSource }> {
|
|
245
|
+
const normalized = command.trim();
|
|
246
|
+
if (!normalized) return index;
|
|
247
|
+
|
|
248
|
+
const existingIndex = index.findIndex((entry) => entry.command === normalized);
|
|
249
|
+
if (existingIndex === -1) {
|
|
250
|
+
return [...index, { command: normalized, source: "runtime" }];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const existing = index[existingIndex];
|
|
254
|
+
if (existing?.source === "runtime") {
|
|
255
|
+
return index;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const next = [...index];
|
|
259
|
+
next[existingIndex] = { command: normalized, source: "runtime" };
|
|
260
|
+
return next;
|
|
261
|
+
}
|
|
262
|
+
|
|
128
263
|
function rankCommands(commands: Array<{ command: string; source: CommandSource }>, query: string) {
|
|
129
264
|
const q = query.toLowerCase();
|
|
130
265
|
|
|
@@ -136,12 +271,79 @@ function rankCommands(commands: Array<{ command: string; source: CommandSource }
|
|
|
136
271
|
return [...startsWith, ...includes].slice(0, 24);
|
|
137
272
|
}
|
|
138
273
|
|
|
274
|
+
function rankFlags(flags: string[], query: string): string[] {
|
|
275
|
+
const q = query.toLowerCase();
|
|
276
|
+
const startsWith = flags.filter((flag) => flag.toLowerCase().startsWith(q));
|
|
277
|
+
const includes = flags.filter((flag) => !flag.toLowerCase().startsWith(q) && flag.toLowerCase().includes(q));
|
|
278
|
+
return [...startsWith, ...includes].slice(0, 24);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function rankLineCandidates(lines: string[], query: string): string[] {
|
|
282
|
+
const q = query.toLowerCase();
|
|
283
|
+
const startsWith = lines.filter((line) => line.toLowerCase().startsWith(q));
|
|
284
|
+
const includes = lines.filter((line) => !line.toLowerCase().startsWith(q) && line.toLowerCase().includes(q));
|
|
285
|
+
return [...startsWith, ...includes].slice(0, 24);
|
|
286
|
+
}
|
|
287
|
+
|
|
139
288
|
export default function bangCommandAutocomplete(pi: ExtensionAPI) {
|
|
140
289
|
const includeHistory = envFlag("PI_BANG_AUTOCOMPLETE_INCLUDE_HISTORY", false);
|
|
290
|
+
const runtimeStorePath = getRuntimeStorePath();
|
|
291
|
+
const runtimeData = readRuntimeData(runtimeStorePath);
|
|
292
|
+
const runtimeLearned = runtimeData.commands;
|
|
293
|
+
const runtimeFlagsByCommand = runtimeData.flagsByCommand;
|
|
294
|
+
const runtimeLearnedLines = runtimeData.lines;
|
|
141
295
|
let commandIndex = buildCommandIndex(includeHistory);
|
|
142
296
|
|
|
297
|
+
const learnFromCommandLine = (commandLine: string | undefined) => {
|
|
298
|
+
if (!commandLine) return;
|
|
299
|
+
const normalizedLine = commandLine.trim().replace(/^!+/, "");
|
|
300
|
+
if (!normalizedLine) return;
|
|
301
|
+
|
|
302
|
+
const parsed = parseCommandLine(commandLine);
|
|
303
|
+
const executable = parsed.executable;
|
|
304
|
+
if (!executable) return;
|
|
305
|
+
|
|
306
|
+
let changed = false;
|
|
307
|
+
|
|
308
|
+
const beforeSize = runtimeLearned.size;
|
|
309
|
+
runtimeLearned.add(executable);
|
|
310
|
+
commandIndex = addRuntimeCommand(commandIndex, executable);
|
|
311
|
+
if (runtimeLearned.size !== beforeSize) {
|
|
312
|
+
changed = true;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const beforeLines = runtimeLearnedLines.size;
|
|
316
|
+
runtimeLearnedLines.add(normalizedLine);
|
|
317
|
+
if (runtimeLearnedLines.size !== beforeLines) {
|
|
318
|
+
changed = true;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (parsed.flags.length > 0) {
|
|
322
|
+
const flagSet = runtimeFlagsByCommand.get(executable) ?? new Set<string>();
|
|
323
|
+
const beforeFlags = flagSet.size;
|
|
324
|
+
for (const flag of parsed.flags) {
|
|
325
|
+
flagSet.add(flag);
|
|
326
|
+
}
|
|
327
|
+
if (flagSet.size !== beforeFlags) {
|
|
328
|
+
runtimeFlagsByCommand.set(executable, flagSet);
|
|
329
|
+
changed = true;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (changed) {
|
|
334
|
+
writeRuntimeData(runtimeStorePath, {
|
|
335
|
+
commands: runtimeLearned,
|
|
336
|
+
flagsByCommand: runtimeFlagsByCommand,
|
|
337
|
+
lines: runtimeLearnedLines,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
143
342
|
const refreshIndex = () => {
|
|
144
343
|
commandIndex = buildCommandIndex(includeHistory);
|
|
344
|
+
for (const command of runtimeLearned) {
|
|
345
|
+
commandIndex = addRuntimeCommand(commandIndex, command);
|
|
346
|
+
}
|
|
145
347
|
};
|
|
146
348
|
|
|
147
349
|
pi.on("session_start", (_event, ctx) => {
|
|
@@ -152,6 +354,46 @@ export default function bangCommandAutocomplete(pi: ExtensionAPI) {
|
|
|
152
354
|
const line = lines[cursorLine] ?? "";
|
|
153
355
|
const beforeCursor = line.slice(0, cursorCol);
|
|
154
356
|
|
|
357
|
+
const flagMatch = beforeCursor.match(/(?:^|[ \t])!([^\s!]+)\s+([^\s]*)$/);
|
|
358
|
+
if (flagMatch) {
|
|
359
|
+
const command = flagMatch[1] ?? "";
|
|
360
|
+
const partialFlag = flagMatch[2] ?? "";
|
|
361
|
+
|
|
362
|
+
if (partialFlag === "" || partialFlag.startsWith("-")) {
|
|
363
|
+
const knownFlags = Array.from(runtimeFlagsByCommand.get(command) ?? []);
|
|
364
|
+
const rankedFlags = rankFlags(knownFlags, partialFlag);
|
|
365
|
+
|
|
366
|
+
if (rankedFlags.length > 0) {
|
|
367
|
+
return {
|
|
368
|
+
prefix: partialFlag,
|
|
369
|
+
items: rankedFlags.map((flag) => ({
|
|
370
|
+
value: flag,
|
|
371
|
+
label: flag,
|
|
372
|
+
description: `learned for ${command}`,
|
|
373
|
+
})),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const fullLineMatch = beforeCursor.match(/(?:^|[ \t])!(.*)$/);
|
|
380
|
+
if (fullLineMatch) {
|
|
381
|
+
const partialLine = fullLineMatch[1] ?? "";
|
|
382
|
+
if (partialLine.includes(" ")) {
|
|
383
|
+
const rankedLines = rankLineCandidates(Array.from(runtimeLearnedLines), partialLine);
|
|
384
|
+
if (rankedLines.length > 0) {
|
|
385
|
+
return {
|
|
386
|
+
prefix: `!${partialLine}`,
|
|
387
|
+
items: rankedLines.map((lineCandidate) => ({
|
|
388
|
+
value: `!${lineCandidate}`,
|
|
389
|
+
label: `!${lineCandidate}`,
|
|
390
|
+
description: "learned full line",
|
|
391
|
+
})),
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
155
397
|
// Trigger on `!<command>` in the current token.
|
|
156
398
|
const match = beforeCursor.match(/(?:^|[ \t])!([^\s!]*)$/);
|
|
157
399
|
if (!match) {
|
|
@@ -161,13 +403,37 @@ export default function bangCommandAutocomplete(pi: ExtensionAPI) {
|
|
|
161
403
|
const partial = match[1] ?? "";
|
|
162
404
|
const ranked = rankCommands(commandIndex, partial);
|
|
163
405
|
|
|
406
|
+
const commandItems = ranked.map((entry) => ({
|
|
407
|
+
value: `!${entry.command}`,
|
|
408
|
+
label: `!${entry.command}`,
|
|
409
|
+
description:
|
|
410
|
+
entry.source === "history"
|
|
411
|
+
? "shell history"
|
|
412
|
+
: entry.source === "runtime"
|
|
413
|
+
? "current session"
|
|
414
|
+
: "common command",
|
|
415
|
+
}));
|
|
416
|
+
|
|
417
|
+
const commandWithFlagItems = ranked.flatMap((entry) => {
|
|
418
|
+
const knownFlags = Array.from(runtimeFlagsByCommand.get(entry.command) ?? []);
|
|
419
|
+
return knownFlags.slice(0, 3).map((flag) => ({
|
|
420
|
+
value: `!${entry.command} ${flag}`,
|
|
421
|
+
label: `!${entry.command} ${flag}`,
|
|
422
|
+
description: "learned command + flag",
|
|
423
|
+
}));
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const fullLineItems = rankLineCandidates(Array.from(runtimeLearnedLines), partial)
|
|
427
|
+
.map((lineCandidate) => ({
|
|
428
|
+
value: `!${lineCandidate}`,
|
|
429
|
+
label: `!${lineCandidate}`,
|
|
430
|
+
description: "learned full line",
|
|
431
|
+
}))
|
|
432
|
+
.filter((item) => item.value.startsWith(`!${partial}`));
|
|
433
|
+
|
|
164
434
|
return {
|
|
165
435
|
prefix: `!${partial}`,
|
|
166
|
-
items:
|
|
167
|
-
value: `!${entry.command}`,
|
|
168
|
-
label: `!${entry.command}`,
|
|
169
|
-
description: entry.source === "history" ? "shell history" : "common command",
|
|
170
|
-
})),
|
|
436
|
+
items: [...fullLineItems, ...commandItems, ...commandWithFlagItems].slice(0, 24),
|
|
171
437
|
};
|
|
172
438
|
},
|
|
173
439
|
|
|
@@ -181,7 +447,10 @@ export default function bangCommandAutocomplete(pi: ExtensionAPI) {
|
|
|
181
447
|
|
|
182
448
|
// Allow Tab-forced autocomplete for bang commands (editor reserves auto-popups
|
|
183
449
|
// for /, @, # contexts by default).
|
|
184
|
-
if (
|
|
450
|
+
if (
|
|
451
|
+
beforeCursor.match(/(?:^|[ \t])![^\s!]*$/) ||
|
|
452
|
+
beforeCursor.match(/(?:^|[ \t])![^\s!]+\s+[^\s]*$/)
|
|
453
|
+
) {
|
|
185
454
|
return true;
|
|
186
455
|
}
|
|
187
456
|
|
|
@@ -190,12 +459,25 @@ export default function bangCommandAutocomplete(pi: ExtensionAPI) {
|
|
|
190
459
|
}));
|
|
191
460
|
});
|
|
192
461
|
|
|
462
|
+
pi.on("user_bash", (event) => {
|
|
463
|
+
learnFromCommandLine(event.command);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// Compatibility with extensions that intercept user_bash and short-circuit
|
|
467
|
+
// subsequent handlers (e.g. fish-user-bash).
|
|
468
|
+
pi.events.on("fish-user-bash:executed", (payload: unknown) => {
|
|
469
|
+
if (!payload || typeof payload !== "object") return;
|
|
470
|
+
const command = (payload as { command?: unknown }).command;
|
|
471
|
+
if (typeof command !== "string") return;
|
|
472
|
+
learnFromCommandLine(command);
|
|
473
|
+
});
|
|
474
|
+
|
|
193
475
|
pi.registerCommand("bang-refresh", {
|
|
194
476
|
description: "Refresh !command autocomplete index",
|
|
195
477
|
handler: async (_args, ctx) => {
|
|
196
478
|
refreshIndex();
|
|
197
479
|
ctx.ui.notify(
|
|
198
|
-
`Bang autocomplete refreshed (${commandIndex.length} commands, history ${includeHistory ? "enabled" : "disabled"})`,
|
|
480
|
+
`Bang autocomplete refreshed (${commandIndex.length} commands, history ${includeHistory ? "enabled" : "disabled"}, runtime learned commands ${runtimeLearned.size}, learned lines ${runtimeLearnedLines.size}, learned flags ${Array.from(runtimeFlagsByCommand.values()).reduce((acc, set) => acc + set.size, 0)})`,
|
|
199
481
|
"info",
|
|
200
482
|
);
|
|
201
483
|
},
|
|
@@ -205,7 +487,7 @@ export default function bangCommandAutocomplete(pi: ExtensionAPI) {
|
|
|
205
487
|
description: "Show !command autocomplete configuration",
|
|
206
488
|
handler: async (_args, ctx) => {
|
|
207
489
|
ctx.ui.notify(
|
|
208
|
-
`Bang autocomplete: ${commandIndex.length} commands · history ${includeHistory ? "enabled" : "disabled"} (${includeHistory ? "PI_BANG_AUTOCOMPLETE_INCLUDE_HISTORY=1" : "set PI_BANG_AUTOCOMPLETE_INCLUDE_HISTORY=1 to enable"})`,
|
|
490
|
+
`Bang autocomplete: ${commandIndex.length} commands · history ${includeHistory ? "enabled" : "disabled"} · runtime learned commands ${runtimeLearned.size} · learned lines ${runtimeLearnedLines.size} · learned flags ${Array.from(runtimeFlagsByCommand.values()).reduce((acc, set) => acc + set.size, 0)} (${includeHistory ? "PI_BANG_AUTOCOMPLETE_INCLUDE_HISTORY=1" : "set PI_BANG_AUTOCOMPLETE_INCLUDE_HISTORY=1 to enable"}) · store ${runtimeStorePath}`,
|
|
209
491
|
"info",
|
|
210
492
|
);
|
|
211
493
|
},
|