@zigai/pi-mode 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 +21 -0
- package/package.json +42 -0
- package/src/constants.ts +20 -0
- package/src/editor.ts +100 -0
- package/src/index.ts +41 -0
- package/src/mode-state.ts +981 -0
- package/src/settings.ts +59 -0
- package/src/storage.ts +136 -0
- package/src/types.ts +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Pi Mode
|
|
2
|
+
|
|
3
|
+
This Pi extension adds prompt modes for model and thinking-level switching.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pi install git:github.com/zigai/pi-ux-tweaks
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Adds `/mode` for selecting and configuring prompt modes.
|
|
14
|
+
- Adds `Ctrl+Shift+M` to select a mode.
|
|
15
|
+
- Adds `Ctrl+Space` to cycle modes.
|
|
16
|
+
- Can show the current mode in the prompt editor border when enabled.
|
|
17
|
+
- Colors the prompt editor border from the active mode or thinking level.
|
|
18
|
+
|
|
19
|
+
Modes can store a provider, model, thinking level, and optional color. Project-local modes live in `.pi/modes.json` when present; otherwise global modes live in `~/.pi/agent/modes.json`.
|
|
20
|
+
|
|
21
|
+
By default, Pi Mode does not print the mode name in the editor border. To opt in, toggle `Show mode name` from `/mode` → `Configure modes…`, or set `"modeShowName": true` in `~/.pi/agent/settings.json`.
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zigai/pi-mode",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Pi package that adds prompt modes for model and thinking-level switching.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mode",
|
|
7
|
+
"pi",
|
|
8
|
+
"pi-coding-agent",
|
|
9
|
+
"pi-extension",
|
|
10
|
+
"pi-package",
|
|
11
|
+
"pi-ux"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/zigai/pi-ux-tweaks/tree/main/packages/pi-mode#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/zigai/pi-ux-tweaks/issues"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "zigai",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/zigai/pi-ux-tweaks.git",
|
|
22
|
+
"directory": "packages/pi-mode"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"src",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@earendil-works/pi-agent-core": "*",
|
|
34
|
+
"@earendil-works/pi-ai": "*",
|
|
35
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
36
|
+
},
|
|
37
|
+
"pi": {
|
|
38
|
+
"extensions": [
|
|
39
|
+
"./src/index.ts"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_MODE_ORDER = ["default"] as const;
|
|
4
|
+
export const CUSTOM_MODE_NAME = "custom" as const;
|
|
5
|
+
|
|
6
|
+
export const MODE_UI_CONFIGURE = "Configure modes…";
|
|
7
|
+
export const MODE_UI_ADD = "Add mode…";
|
|
8
|
+
export const MODE_UI_SHOW_NAME_ON = "Show mode name: on";
|
|
9
|
+
export const MODE_UI_SHOW_NAME_OFF = "Show mode name: off";
|
|
10
|
+
export const MODE_UI_BACK = "Back";
|
|
11
|
+
|
|
12
|
+
export const ALL_THINKING_LEVELS: ThinkingLevel[] = [
|
|
13
|
+
"off",
|
|
14
|
+
"minimal",
|
|
15
|
+
"low",
|
|
16
|
+
"medium",
|
|
17
|
+
"high",
|
|
18
|
+
"xhigh",
|
|
19
|
+
];
|
|
20
|
+
export const THINKING_UNSET_LABEL = "(don't change)";
|
package/src/editor.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CustomEditor,
|
|
3
|
+
type ExtensionAPI,
|
|
4
|
+
type ExtensionContext,
|
|
5
|
+
} from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import { getCurrentMode, getModeBorderColor, setRequestEditorRender } from "./mode-state.ts";
|
|
7
|
+
import { shouldShowModeName } from "./settings.ts";
|
|
8
|
+
|
|
9
|
+
const ANSI_SGR = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
10
|
+
const stripAnsi = (value: string) => value.replace(ANSI_SGR, "");
|
|
11
|
+
const MODE_FACTORY_BASE = Symbol.for("zigai.pi-mode.editor-factory-base");
|
|
12
|
+
|
|
13
|
+
type EditorFactory = NonNullable<ReturnType<ExtensionContext["ui"]["getEditorComponent"]>>;
|
|
14
|
+
|
|
15
|
+
type EditorLike = CustomEditor & {
|
|
16
|
+
borderColor: (text: string) => string;
|
|
17
|
+
getText(): string;
|
|
18
|
+
render(width: number): string[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type WrappedEditorFactory = EditorFactory & {
|
|
22
|
+
[MODE_FACTORY_BASE]?: EditorFactory | undefined;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function modeLabelLine(lines: string[], width: number, editor: EditorLike): string[] {
|
|
26
|
+
if (!shouldShowModeName()) return lines;
|
|
27
|
+
|
|
28
|
+
const mode = getCurrentMode();
|
|
29
|
+
if (mode.length === 0) return lines;
|
|
30
|
+
|
|
31
|
+
const topPlain = stripAnsi(lines[0] ?? "");
|
|
32
|
+
const scrollPrefixMatch = /^(─── ↑ \d+ more )/.exec(topPlain);
|
|
33
|
+
const prefix = scrollPrefixMatch?.[1] ?? "──";
|
|
34
|
+
|
|
35
|
+
let label = mode;
|
|
36
|
+
let labelLeftSpace = " ";
|
|
37
|
+
if (prefix.endsWith(" ")) {
|
|
38
|
+
labelLeftSpace = "";
|
|
39
|
+
}
|
|
40
|
+
const labelRightSpace = " ";
|
|
41
|
+
const minRightBorder = 1;
|
|
42
|
+
const maxLabelLen = Math.max(
|
|
43
|
+
0,
|
|
44
|
+
width - prefix.length - labelLeftSpace.length - labelRightSpace.length - minRightBorder,
|
|
45
|
+
);
|
|
46
|
+
if (maxLabelLen <= 0) return lines;
|
|
47
|
+
if (label.length > maxLabelLen) label = label.slice(0, maxLabelLen);
|
|
48
|
+
|
|
49
|
+
const labelChunk = `${labelLeftSpace}${label}${labelRightSpace}`;
|
|
50
|
+
const remaining = width - prefix.length - labelChunk.length;
|
|
51
|
+
if (remaining < 0) return lines;
|
|
52
|
+
|
|
53
|
+
const right = "─".repeat(Math.max(0, remaining));
|
|
54
|
+
const borderColor = editor.borderColor;
|
|
55
|
+
lines[0] = borderColor(prefix) + borderColor(labelChunk) + borderColor(right);
|
|
56
|
+
return lines;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function enhanceEditor(
|
|
60
|
+
editor: EditorLike,
|
|
61
|
+
pi: ExtensionAPI,
|
|
62
|
+
ctx: ExtensionContext,
|
|
63
|
+
requestRender: () => void,
|
|
64
|
+
): EditorLike {
|
|
65
|
+
const originalRender = editor.render.bind(editor);
|
|
66
|
+
editor.render = (width: number) => modeLabelLine(originalRender(width), width, editor);
|
|
67
|
+
|
|
68
|
+
const borderColor = (text: string) => {
|
|
69
|
+
const isBashMode = editor.getText().trimStart().startsWith("!");
|
|
70
|
+
if (isBashMode) {
|
|
71
|
+
return ctx.ui.theme.getBashModeBorderColor()(text);
|
|
72
|
+
}
|
|
73
|
+
return getModeBorderColor(ctx, pi, getCurrentMode())(text);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
Object.defineProperty(editor, "borderColor", {
|
|
77
|
+
get: () => borderColor,
|
|
78
|
+
set: () => {},
|
|
79
|
+
configurable: true,
|
|
80
|
+
enumerable: true,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
setRequestEditorRender(requestRender);
|
|
84
|
+
return editor;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function applyModeEditor(pi: ExtensionAPI, ctx: ExtensionContext): void {
|
|
88
|
+
if (!ctx.hasUI) return;
|
|
89
|
+
|
|
90
|
+
const existing = ctx.ui.getEditorComponent() as WrappedEditorFactory | undefined;
|
|
91
|
+
const baseFactory = existing?.[MODE_FACTORY_BASE] ?? existing;
|
|
92
|
+
const factory = ((tui, theme, keybindings) => {
|
|
93
|
+
const editor = (baseFactory?.(tui, theme, keybindings) ??
|
|
94
|
+
new CustomEditor(tui, theme, keybindings)) as EditorLike;
|
|
95
|
+
return enhanceEditor(editor, pi, ctx, () => tui.requestRender());
|
|
96
|
+
}) as WrappedEditorFactory;
|
|
97
|
+
factory[MODE_FACTORY_BASE] = baseFactory;
|
|
98
|
+
|
|
99
|
+
ctx.ui.setEditorComponent(factory);
|
|
100
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { applyModeEditor } from "./editor.ts";
|
|
3
|
+
import {
|
|
4
|
+
cycleMode,
|
|
5
|
+
handleModeCommand,
|
|
6
|
+
handleModelSelect,
|
|
7
|
+
handleSessionActivated,
|
|
8
|
+
selectModeUI,
|
|
9
|
+
} from "./mode-state.ts";
|
|
10
|
+
|
|
11
|
+
export default function (pi: ExtensionAPI) {
|
|
12
|
+
pi.registerCommand("mode", {
|
|
13
|
+
description: "Select prompt mode",
|
|
14
|
+
handler: async (args, ctx) => {
|
|
15
|
+
await handleModeCommand(pi, ctx, args);
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
pi.registerShortcut("ctrl+shift+m", {
|
|
20
|
+
description: "Select prompt mode",
|
|
21
|
+
handler: async (ctx) => {
|
|
22
|
+
await selectModeUI(pi, ctx);
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
pi.registerShortcut("ctrl+space", {
|
|
27
|
+
description: "Cycle prompt mode",
|
|
28
|
+
handler: async (ctx) => {
|
|
29
|
+
await cycleMode(pi, ctx, 1);
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
34
|
+
await handleSessionActivated(pi, ctx);
|
|
35
|
+
applyModeEditor(pi, ctx);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
pi.on("model_select", async (event, ctx) => {
|
|
39
|
+
await handleModelSelect(pi, ctx, event);
|
|
40
|
+
});
|
|
41
|
+
}
|