@pi-unipi/unipi 2.0.2 → 2.0.4
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 +10 -3
- package/package.json +2 -2
- package/packages/ask-user/README.md +4 -0
- package/packages/ask-user/ask-ui.ts +97 -90
- package/packages/ask-user/tools.ts +12 -10
- package/packages/autocomplete/README.md +36 -0
- package/packages/compactor/README.md +290 -73
- package/packages/compactor/skills/compactor/SKILL.md +2 -3
- package/packages/compactor/skills/compactor-detail/SKILL.md +49 -64
- package/packages/compactor/skills/compactor-doctor/SKILL.md +28 -31
- package/packages/compactor/skills/compactor-stats/SKILL.md +22 -20
- package/packages/compactor/src/commands/index.ts +4 -1
- package/packages/compactor/src/compaction/auto-trigger.ts +306 -0
- package/packages/compactor/src/config/manager.ts +1 -0
- package/packages/compactor/src/config/presets.ts +26 -0
- package/packages/compactor/src/config/schema.ts +7 -0
- package/packages/compactor/src/index.ts +74 -1
- package/packages/compactor/src/tools/context-budget.ts +18 -2
- package/packages/compactor/src/tools/register.ts +19 -11
- package/packages/compactor/src/tui/settings-overlay.ts +142 -3
- package/packages/compactor/src/types.ts +17 -0
- package/packages/core/events.ts +2 -0
- package/packages/notify/README.md +2 -2
- package/packages/notify/commands.ts +9 -4
- package/packages/notify/events.ts +12 -2
- package/packages/notify/platforms/focus-win.ts +123 -0
- package/packages/notify/platforms/focus.ts +33 -0
- package/packages/notify/platforms/native.ts +33 -1
- package/packages/notify/settings.ts +1 -0
- package/packages/notify/tui/settings-overlay.ts +33 -7
- package/packages/notify/types.ts +8 -0
- package/packages/workflow/README.md +2 -0
- package/packages/workflow/commands.ts +28 -11
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Unipi
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
20 workspace packages that turn Pi into a full development workstation. Structured workflows, persistent memory, parallel agents, web research, notifications, context management, command autocomplete, and a live status bar — all wired together through a shared event system.
|
|
4
4
|
|
|
5
5
|
One command installs everything:
|
|
6
6
|
```bash
|
|
@@ -15,7 +15,9 @@ pi install npm:@pi-unipi/unipi
|
|
|
15
15
|
|
|
16
16
|
**[Memory](./packages/memory/README.md)** — SQLite + vector search stores facts, preferences, and decisions. Project-scoped and global. The agent remembers what you told it last week.
|
|
17
17
|
|
|
18
|
-
**[Compactor](./packages/compactor/README.md)** — Zero-LLM context engine. 6-stage pipeline hits 95%+ token reduction at zero API cost. Session continuity,
|
|
18
|
+
**[Compactor](./packages/compactor/README.md)** — Zero-LLM context engine. 6-stage pipeline hits 95%+ token reduction at zero API cost. Session continuity, percentage auto-compaction, session recall, and sandbox execution.
|
|
19
|
+
|
|
20
|
+
**[CocoIndex](./packages/cocoindex/README.md)** — Project indexing and semantic code search backed by CocoIndex and LanceDB. Agent tools and slash commands for status, init, update, and search.
|
|
19
21
|
|
|
20
22
|
**[Subagents](./packages/subagents/README.md)** — Parallel execution with file locking. Spawn background agents to research, fix, or build while the main agent keeps going.
|
|
21
23
|
|
|
@@ -23,7 +25,7 @@ pi install npm:@pi-unipi/unipi
|
|
|
23
25
|
|
|
24
26
|
**[MCP](./packages/mcp/README.md)** — Browse 7,800+ MCP servers, add them interactively. Tools from servers register automatically as Pi tools.
|
|
25
27
|
|
|
26
|
-
**[Notify](./packages/notify/README.md)** — Push notifications to native OS, Gotify, Telegram, or ntfy. Per-event platform routing
|
|
28
|
+
**[Notify](./packages/notify/README.md)** — Push notifications to native OS, Gotify, Telegram, or ntfy. Per-event platform routing plus native focus suppression so alerts can stay quiet while Pi is already focused.
|
|
27
29
|
|
|
28
30
|
**[Footer](./packages/footer/README.md)** — Persistent status bar showing live stats from every package. Responsive layout, presets, per-segment toggling.
|
|
29
31
|
|
|
@@ -43,6 +45,8 @@ pi install npm:@pi-unipi/unipi
|
|
|
43
45
|
|
|
44
46
|
**[Input Shortcuts](./packages/input-shortcuts/README.md)** — Keyboard shortcuts via vim-style chord overlay. Stash/restore, undo/redo, clipboard, thinking toggle.
|
|
45
47
|
|
|
48
|
+
**[Command Enchantment](./packages/autocomplete/README.md)** — Enhanced `/unipi:*` autocomplete with package grouping, descriptions, colors, and registry audits that catch stale command docs before release.
|
|
49
|
+
|
|
46
50
|
## Architecture
|
|
47
51
|
|
|
48
52
|
Packages discover each other through events, not direct imports. Core defines the event types and constants. Every package emits `MODULE_READY` on load and subscribes to events it cares about.
|
|
@@ -78,6 +82,7 @@ Coexists triggers enhance behavior when packages are installed together. Workflo
|
|
|
78
82
|
| Ralph | `/unipi:ralph` | start, stop, resume, status |
|
|
79
83
|
| Memory | `/unipi:memory-` | process, search, consolidate, forget |
|
|
80
84
|
| Compactor | `/unipi:` | lossless-compact, session-recall, compact-stats, compact-settings, compact-preset, compact-help |
|
|
85
|
+
| CocoIndex | `/unipi:cocoindex-` | init, update, status, search, settings |
|
|
81
86
|
| Notify | `/unipi:notify-` | settings, test, set-tg, set-ntfy |
|
|
82
87
|
| MCP | `/unipi:mcp-` | add, settings, sync, status |
|
|
83
88
|
| Web | `/unipi:web-` | settings, cache-clear |
|
|
@@ -121,6 +126,7 @@ unipi/
|
|
|
121
126
|
│ ├── ralph/ # Iterative loops
|
|
122
127
|
│ ├── memory/ # SQLite + vector search
|
|
123
128
|
│ ├── compactor/ # Context engine
|
|
129
|
+
│ ├── cocoindex/ # Project indexing and semantic search
|
|
124
130
|
│ ├── subagents/ # Parallel execution
|
|
125
131
|
│ ├── web-api/ # Web research
|
|
126
132
|
│ ├── mcp/ # MCP server integration
|
|
@@ -134,6 +140,7 @@ unipi/
|
|
|
134
140
|
│ ├── utility/ # Diagnostics, diff rendering
|
|
135
141
|
│ ├── updater/ # Auto-update, browsers
|
|
136
142
|
│ ├── input-shortcuts/ # Keyboard shortcuts
|
|
143
|
+
│ ├── autocomplete/ # Enhanced command autocomplete
|
|
137
144
|
│ └── unipi/ # Umbrella package
|
|
138
145
|
├── .unipi/ # Runtime data (specs, plans, worktrees)
|
|
139
146
|
└── CHANGELOG.md
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/unipi",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "All-in-one extension suite for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
],
|
|
25
25
|
"scripts": {
|
|
26
26
|
"typecheck": "npx tsc --noEmit --skipLibCheck",
|
|
27
|
-
"test": "npm test --workspaces",
|
|
27
|
+
"test": "npm test --workspaces --if-present",
|
|
28
28
|
"publish:all": "npm publish --workspaces --access public"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
@@ -73,6 +73,10 @@ When the user chooses a `new_session` option:
|
|
|
73
73
|
|
|
74
74
|
The tool result is rendered as `queued compact → ...` or `queued direct → ...`. If automatic delivery fails, ask-user falls back to editor prefill and warns you to press Enter.
|
|
75
75
|
|
|
76
|
+
### History Expansion
|
|
77
|
+
|
|
78
|
+
Completed `ask_user` calls stay readable in chat history. The collapsed result shows the selected answer; press Ctrl+O on the tool result to expand the original question, context, and options that were presented.
|
|
79
|
+
|
|
76
80
|
### Keyboard Controls
|
|
77
81
|
|
|
78
82
|
| Mode | Keys |
|
|
@@ -621,7 +621,7 @@ export function createRenderCall() {
|
|
|
621
621
|
* Create a renderResult function for the ask_user tool.
|
|
622
622
|
*/
|
|
623
623
|
export function createRenderResult() {
|
|
624
|
-
return (result: AgentToolResult<unknown>,
|
|
624
|
+
return (result: AgentToolResult<unknown>, options: unknown, theme: Theme, _context: unknown) => {
|
|
625
625
|
const details = result.details as Record<string, unknown> | undefined;
|
|
626
626
|
if (!details) {
|
|
627
627
|
const content = result.content as unknown as Array<Record<string, unknown>> | undefined;
|
|
@@ -634,103 +634,110 @@ export function createRenderResult() {
|
|
|
634
634
|
return new Text(theme.fg("warning", "No response"), 0, 0);
|
|
635
635
|
}
|
|
636
636
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
637
|
+
const renderOptionSummary = (): string[] => {
|
|
638
|
+
const rawOptions = (details as { options?: unknown }).options;
|
|
639
|
+
if (!Array.isArray(rawOptions) || rawOptions.length === 0) return [];
|
|
640
|
+
return rawOptions.map((opt, index) => {
|
|
641
|
+
if (typeof opt === "string") {
|
|
642
|
+
return theme.fg("dim", ` ${index + 1}. `) + theme.fg("text", opt);
|
|
643
|
+
}
|
|
644
|
+
const record = opt as Record<string, unknown>;
|
|
645
|
+
const label = String(record.label ?? record.value ?? `Option ${index + 1}`);
|
|
646
|
+
const value = typeof record.value === "string" && record.value !== label
|
|
647
|
+
? theme.fg("dim", ` (${record.value})`)
|
|
648
|
+
: "";
|
|
649
|
+
const action = typeof record.action === "string" && record.action !== "select"
|
|
650
|
+
? theme.fg("dim", ` [${record.action}]`)
|
|
651
|
+
: "";
|
|
652
|
+
const description = typeof record.description === "string" && record.description.trim()
|
|
653
|
+
? `\n${theme.fg("muted", ` ${record.description}`)}`
|
|
654
|
+
: "";
|
|
655
|
+
return theme.fg("dim", ` ${index + 1}. `) + theme.fg("text", label) + value + action + description;
|
|
656
|
+
});
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
const answerText = (() => {
|
|
660
|
+
switch (response.kind) {
|
|
661
|
+
case "cancelled":
|
|
662
|
+
return theme.fg("warning", "Cancelled");
|
|
663
|
+
case "timed_out":
|
|
664
|
+
return theme.fg("warning", "Timed out");
|
|
665
|
+
case "freeform":
|
|
666
|
+
return theme.fg("success", "✓ ") +
|
|
645
667
|
theme.fg("muted", "(wrote) ") +
|
|
646
|
-
theme.fg("accent", response.text || "")
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
selections.
|
|
668
|
+
theme.fg("accent", response.text || "");
|
|
669
|
+
case "selection": {
|
|
670
|
+
const selections = response.selections || [];
|
|
671
|
+
const display = selections.length === 1 ? selections[0] : selections.join(", ");
|
|
672
|
+
return theme.fg("success", "✓ ") + theme.fg("accent", display);
|
|
673
|
+
}
|
|
674
|
+
case "combined": {
|
|
675
|
+
const selections = response.selections || [];
|
|
676
|
+
const selDisplay = selections.length === 1
|
|
654
677
|
? selections[0]
|
|
655
678
|
: selections.join(", ");
|
|
656
|
-
|
|
657
|
-
theme.fg("success", "✓ ") + theme.fg("accent", display),
|
|
658
|
-
0,
|
|
659
|
-
0,
|
|
660
|
-
);
|
|
661
|
-
}
|
|
662
|
-
case "combined": {
|
|
663
|
-
const selections = response.selections || [];
|
|
664
|
-
const selDisplay = selections.length === 1
|
|
665
|
-
? selections[0]
|
|
666
|
-
: selections.join(", ");
|
|
667
|
-
return new Text(
|
|
668
|
-
theme.fg("success", "✓ ") +
|
|
679
|
+
return theme.fg("success", "✓ ") +
|
|
669
680
|
theme.fg("accent", selDisplay) +
|
|
670
681
|
theme.fg("muted", " and wrote ") +
|
|
671
|
-
theme.fg("accent", response.text || "")
|
|
672
|
-
0,
|
|
673
|
-
0,
|
|
674
|
-
);
|
|
675
|
-
}
|
|
676
|
-
case "end_turn":
|
|
677
|
-
return new Text(
|
|
678
|
-
theme.fg("success", "✓ ") + theme.fg("muted", "end turn"),
|
|
679
|
-
0,
|
|
680
|
-
0,
|
|
681
|
-
);
|
|
682
|
-
case "new_session": {
|
|
683
|
-
const prefill = response.prefill || "";
|
|
684
|
-
if (response.launchStatus === "editor_prefill") {
|
|
685
|
-
const label = response.launchedWith === "compact"
|
|
686
|
-
? "⚠ compact editor prefill → "
|
|
687
|
-
: "⚠ direct editor prefill → ";
|
|
688
|
-
return new Text(
|
|
689
|
-
theme.fg("warning", label) + theme.fg("accent", prefill),
|
|
690
|
-
0,
|
|
691
|
-
0,
|
|
692
|
-
);
|
|
693
|
-
}
|
|
694
|
-
if (response.launchStatus === "failed") {
|
|
695
|
-
const label = response.launchedWith === "compact"
|
|
696
|
-
? "handoff failed (compact) → "
|
|
697
|
-
: "handoff failed (direct) → ";
|
|
698
|
-
return new Text(
|
|
699
|
-
theme.fg("error", label) + theme.fg("accent", prefill),
|
|
700
|
-
0,
|
|
701
|
-
0,
|
|
702
|
-
);
|
|
682
|
+
theme.fg("accent", response.text || "");
|
|
703
683
|
}
|
|
704
|
-
|
|
705
|
-
return
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
684
|
+
case "end_turn":
|
|
685
|
+
return theme.fg("success", "✓ ") + theme.fg("muted", "end turn");
|
|
686
|
+
case "new_session": {
|
|
687
|
+
const prefill = response.prefill || "";
|
|
688
|
+
if (response.launchStatus === "editor_prefill") {
|
|
689
|
+
const label = response.launchedWith === "compact"
|
|
690
|
+
? "⚠ compact editor prefill → "
|
|
691
|
+
: "⚠ direct editor prefill → ";
|
|
692
|
+
return theme.fg("warning", label) + theme.fg("accent", prefill);
|
|
693
|
+
}
|
|
694
|
+
if (response.launchStatus === "failed") {
|
|
695
|
+
const label = response.launchedWith === "compact"
|
|
696
|
+
? "handoff failed (compact) → "
|
|
697
|
+
: "handoff failed (direct) → ";
|
|
698
|
+
return theme.fg("error", label) + theme.fg("accent", prefill);
|
|
699
|
+
}
|
|
700
|
+
if (response.launchedWith === "compact") {
|
|
701
|
+
return theme.fg("success", "✓ queued compact → ") + theme.fg("accent", prefill);
|
|
702
|
+
}
|
|
703
|
+
if (response.launchedWith === "direct") {
|
|
704
|
+
return theme.fg("success", "✓ queued direct → ") + theme.fg("accent", prefill);
|
|
705
|
+
}
|
|
706
|
+
return theme.fg("success", "✓ ") +
|
|
722
707
|
theme.fg("muted", "new session") +
|
|
723
|
-
(prefill ? theme.fg("accent", `: ${prefill}`) : "")
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
708
|
+
(prefill ? theme.fg("accent", `: ${prefill}`) : "");
|
|
709
|
+
}
|
|
710
|
+
default:
|
|
711
|
+
return theme.fg("text", JSON.stringify(response));
|
|
727
712
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
713
|
+
})();
|
|
714
|
+
|
|
715
|
+
const expanded = typeof options === "object" && options !== null && "expanded" in options
|
|
716
|
+
? Boolean((options as { expanded?: boolean }).expanded)
|
|
717
|
+
: false;
|
|
718
|
+
if (!expanded) {
|
|
719
|
+
const question = typeof (details as { question?: unknown }).question === "string"
|
|
720
|
+
? (details as { question: string }).question
|
|
721
|
+
: "";
|
|
722
|
+
const expandHint = question
|
|
723
|
+
? theme.fg("dim", " · Ctrl+O question/options")
|
|
724
|
+
: "";
|
|
725
|
+
return new Text(answerText + expandHint, 0, 0);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const lines = [answerText];
|
|
729
|
+
const question = (details as { question?: unknown }).question;
|
|
730
|
+
const context = (details as { context?: unknown }).context;
|
|
731
|
+
if (typeof question === "string" && question.trim()) {
|
|
732
|
+
lines.push(theme.fg("muted", "Question: ") + theme.fg("text", question));
|
|
734
733
|
}
|
|
734
|
+
if (typeof context === "string" && context.trim()) {
|
|
735
|
+
lines.push(theme.fg("muted", "Context: ") + theme.fg("text", context));
|
|
736
|
+
}
|
|
737
|
+
const optionLines = renderOptionSummary();
|
|
738
|
+
if (optionLines.length > 0) {
|
|
739
|
+
lines.push(theme.fg("muted", "Options:"), ...optionLines);
|
|
740
|
+
}
|
|
741
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
735
742
|
};
|
|
736
743
|
}
|
|
@@ -252,6 +252,13 @@ export function registerAskUserTools(pi: ExtensionAPI): void {
|
|
|
252
252
|
action: (opt.action as NormalizedOption["action"]) ?? "select",
|
|
253
253
|
prefill: opt.prefill,
|
|
254
254
|
}));
|
|
255
|
+
const detailsBase = {
|
|
256
|
+
question,
|
|
257
|
+
context,
|
|
258
|
+
options: normalizedOptions,
|
|
259
|
+
allowMultiple,
|
|
260
|
+
allowFreeform,
|
|
261
|
+
};
|
|
255
262
|
|
|
256
263
|
// Emit ASK_USER_PROMPT event if notifyOnAsk is enabled
|
|
257
264
|
if (settings.notifyOnAsk) {
|
|
@@ -286,8 +293,7 @@ export function registerAskUserTools(pi: ExtensionAPI): void {
|
|
|
286
293
|
},
|
|
287
294
|
],
|
|
288
295
|
details: {
|
|
289
|
-
|
|
290
|
-
options: normalizedOptions.map((o) => o.label),
|
|
296
|
+
...detailsBase,
|
|
291
297
|
response: {
|
|
292
298
|
kind: "cancelled",
|
|
293
299
|
} as AskUserResponse,
|
|
@@ -348,8 +354,7 @@ export function registerAskUserTools(pi: ExtensionAPI): void {
|
|
|
348
354
|
return {
|
|
349
355
|
content: [{ type: "text", text: "User cancelled the session launch" }],
|
|
350
356
|
details: {
|
|
351
|
-
|
|
352
|
-
options: normalizedOptions.map((o) => o.label),
|
|
357
|
+
...detailsBase,
|
|
353
358
|
response: {
|
|
354
359
|
kind: "cancelled",
|
|
355
360
|
comment: "Session launcher cancelled",
|
|
@@ -374,8 +379,7 @@ export function registerAskUserTools(pi: ExtensionAPI): void {
|
|
|
374
379
|
return {
|
|
375
380
|
content: [{ type: "text", text: "Session launch cancelled: no prefill message was provided." }],
|
|
376
381
|
details: {
|
|
377
|
-
|
|
378
|
-
options: normalizedOptions.map((o) => o.label),
|
|
382
|
+
...detailsBase,
|
|
379
383
|
response: {
|
|
380
384
|
kind: "cancelled",
|
|
381
385
|
comment: "Session launcher had no prefill to queue",
|
|
@@ -404,8 +408,7 @@ export function registerAskUserTools(pi: ExtensionAPI): void {
|
|
|
404
408
|
return {
|
|
405
409
|
content: [{ type: "text", text: contentText }],
|
|
406
410
|
details: {
|
|
407
|
-
|
|
408
|
-
options: normalizedOptions.map((o) => o.label),
|
|
411
|
+
...detailsBase,
|
|
409
412
|
response: {
|
|
410
413
|
...response,
|
|
411
414
|
prefill: handoff.prefill ?? prefill,
|
|
@@ -421,8 +424,7 @@ export function registerAskUserTools(pi: ExtensionAPI): void {
|
|
|
421
424
|
return {
|
|
422
425
|
content: [{ type: "text", text: contentText }],
|
|
423
426
|
details: {
|
|
424
|
-
|
|
425
|
-
options: normalizedOptions.map((o) => o.label),
|
|
427
|
+
...detailsBase,
|
|
426
428
|
response,
|
|
427
429
|
},
|
|
428
430
|
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @pi-unipi/command-enchantment
|
|
2
|
+
|
|
3
|
+
Enhanced autocomplete for Unipi commands. It wraps Pi's base autocomplete provider and makes `/unipi:*` suggestions easier to scan with package grouping, stable sorting, short descriptions, and package colors.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
Command Enchantment has no user commands. It improves the editor autocomplete experience automatically when the package is installed.
|
|
8
|
+
|
|
9
|
+
## What It Does
|
|
10
|
+
|
|
11
|
+
- Groups `/unipi:*` commands by package so workflow, memory, web, footer, and other commands are visually distinct.
|
|
12
|
+
- Sorts matches in predictable tiers: exact Unipi matches first, then other Unipi matches, then system commands.
|
|
13
|
+
- Preserves dynamic argument completions from command providers, including workflow document and worktree suggestions.
|
|
14
|
+
- Ships an audit test that checks registered Unipi commands are represented in the autocomplete registry and have descriptions.
|
|
15
|
+
|
|
16
|
+
## Development Checks
|
|
17
|
+
|
|
18
|
+
Run the registry audit before releases:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm --workspace packages/autocomplete test -- src/__tests__/command-registry.audit.test.ts
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Run all autocomplete tests:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm --workspace packages/autocomplete test
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
Autocomplete enhancement is enabled by default. The package stores its toggle in the Unipi config and can be disabled by setting `autocompleteEnhanced` to `false`.
|
|
33
|
+
|
|
34
|
+
## License
|
|
35
|
+
|
|
36
|
+
MIT
|