pi-profiles-manager 1.0.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 +96 -0
- package/index.ts +352 -0
- package/package.json +20 -0
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# pi-profiles-manager π
|
|
2
|
+
|
|
3
|
+
Interactive SDD model profile management built natively for the [Pi Coding Agent](https://github.com/earendil-works/pi-mono).
|
|
4
|
+
|
|
5
|
+
Manage your Gentle AI `models.json` configurations dynamically from a beautiful Terminal User Interface (TUI) without ever leaving your session.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## π Features
|
|
10
|
+
|
|
11
|
+
### 1. Unified Profile Management
|
|
12
|
+
Manage all your SDD (Spec-Driven Development) profiles via a rich interface:
|
|
13
|
+
- **Create:** Read your current `models.json` setup and save it as a new profile instantly.
|
|
14
|
+
- **Activate:** Apply a saved profile into the global runtime config. Changes affect the executing session immediately with no restarts required.
|
|
15
|
+
- **Edit Agent Nodes:** Individually adjust `model` strings and `thinking` tiers (`low`, `medium`, `high`, `xhigh`, `max`) for any agent (`sdd-*`, `jd-*`, `review-*`, etc.).
|
|
16
|
+
- **Scaffold Empty Agents:** Add specific overrides for single agents right from the UI.
|
|
17
|
+
- **Delete:** Remove outdated tiers and profiles to keep your workspace tidy.
|
|
18
|
+
|
|
19
|
+
### 2. Native Pi Integration
|
|
20
|
+
- Fully built on top of `@earendil-works/pi-tui`.
|
|
21
|
+
- Works flawlessly with overlay navigation, meaning your terminal output isn't erased when interacting with profiles.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## πΈ Interface Preview (Compact Format)
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
Profiles Manager
|
|
29
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
30
|
+
> β¨ Create New Profile from Current Config
|
|
31
|
+
FREE
|
|
32
|
+
antigravity
|
|
33
|
+
deus
|
|
34
|
+
|
|
35
|
+
ββ navigate β’ enter select β’ esc close
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
Action for 'antigravity'
|
|
40
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
41
|
+
> βΆ Activate Apply this profile to models.json
|
|
42
|
+
β Edit Modify agents in this profile
|
|
43
|
+
β Delete Remove this profile
|
|
44
|
+
β Back Return to profile list
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
Edit Profile 'antigravity'
|
|
49
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
50
|
+
> orchestrator omni/antigravity/gemini-pro-agent (medium)
|
|
51
|
+
jd-fix-agent omniroute/gemini-flash-low (high)
|
|
52
|
+
...
|
|
53
|
+
β Add Subagent Add a specific configuration for a subagent
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## π Installation
|
|
59
|
+
|
|
60
|
+
Clone this repository directly into your Pi extensions directory:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
git clone https://github.com/javinnav/pi-profiles-manager.git ~/.pi/agent/extensions/profiles-manager
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Apply the changes immediately by reloading the active session:
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
/reload
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## π Usage
|
|
75
|
+
|
|
76
|
+
*Ensure the active Pi Session recognizes the newly linked extension context.*
|
|
77
|
+
|
|
78
|
+
Open the Profiles Manager by triggering its slash command:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
/profiles
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Alternatively, to quickly snapshot your current session's `models.json` without routing through the UI:
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
/profiles save <profile-name>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## π Credits and Acknowledgments
|
|
93
|
+
|
|
94
|
+
- **Inspiration:** Developed mirroring the clean extension and TUI architecture provided by [opencode-sdd-engram-manage](https://github.com/j0k3r-dev-rgl/sdd-engram-plugin) from @j0k3r-dev-rgl.
|
|
95
|
+
- **Dependencies:** Built leveraging the core APIs of the [Pi Coding Agent framework](https://github.com/earendil-works/pi-mono).
|
|
96
|
+
- **Ecosystem:** Powered by [Gentle AI (OpenCode)](https://github.com/Gentleman-Programming/gentle-ai).
|
package/index.ts
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as os from "os";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import type { ExtensionAPI, TUIContext } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { DynamicBorder } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import { Container, SelectList, Text, Input } from "@earendil-works/pi-tui";
|
|
7
|
+
|
|
8
|
+
const GENTLE_DIR = path.join(os.homedir(), ".pi", "gentle-ai");
|
|
9
|
+
const MODELS_PATH = path.join(GENTLE_DIR, "models.json");
|
|
10
|
+
const PROFILES_PATH = path.join(GENTLE_DIR, "sdd-profiles-manager.json");
|
|
11
|
+
|
|
12
|
+
interface Profile {
|
|
13
|
+
name: string;
|
|
14
|
+
orchestrator: { model: string; thinking: string };
|
|
15
|
+
agents: Record<string, { model: string; thinking: string }>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function readJson(fp: string): Promise<any> {
|
|
19
|
+
try {
|
|
20
|
+
const data = await fs.readFile(fp, "utf-8");
|
|
21
|
+
return JSON.parse(data);
|
|
22
|
+
} catch (e: any) {
|
|
23
|
+
if (e.code === "ENOENT") return {};
|
|
24
|
+
throw e;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function writeJson(fp: string, data: any): Promise<void> {
|
|
29
|
+
await fs.mkdir(path.dirname(fp), { recursive: true });
|
|
30
|
+
await fs.writeFile(fp, JSON.stringify(data, null, 2));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Helper to ask user for a string
|
|
34
|
+
async function promptInput(ctx: any, title: string, initialValue: string = ""): Promise<string | null> {
|
|
35
|
+
return await ctx.ui.custom<string | null>((tui: any, theme: any, _kb: any, done: any) => {
|
|
36
|
+
const container = new Container() as any;
|
|
37
|
+
container.focused = true; // Make container focusable
|
|
38
|
+
|
|
39
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
40
|
+
container.addChild(new Text(theme.fg("accent", theme.bold(title)), 1, 0));
|
|
41
|
+
|
|
42
|
+
const input = new Input();
|
|
43
|
+
input.setValue(initialValue);
|
|
44
|
+
input.onSubmit = (val) => done(val.trim());
|
|
45
|
+
input.onEscape = () => done(null);
|
|
46
|
+
input.focused = true;
|
|
47
|
+
|
|
48
|
+
// Wire up focus propagation
|
|
49
|
+
container.handleInput = (data: string) => {
|
|
50
|
+
input.handleInput(data);
|
|
51
|
+
tui.requestRender();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
container.addChild(input);
|
|
55
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
render: (w: number) => container.render(w),
|
|
59
|
+
invalidate: () => container.invalidate(),
|
|
60
|
+
handleInput: (data: string) => container.handleInput(data),
|
|
61
|
+
};
|
|
62
|
+
}, { overlay: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default function (pi: ExtensionAPI) {
|
|
66
|
+
pi.registerCommand("profiles", {
|
|
67
|
+
description: "Manage SDD model profiles",
|
|
68
|
+
handler: async (args, ctx) => {
|
|
69
|
+
// Subcommand for save
|
|
70
|
+
if (args[0] === "save") {
|
|
71
|
+
const profileName = args[1];
|
|
72
|
+
if (!profileName) {
|
|
73
|
+
ctx.ui.notify("Error: Provide a profile name (e.g. /profiles save my-profile)", "error");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const models = await readJson(MODELS_PATH);
|
|
78
|
+
const orchestrator = models["gentle-orchestrator"] || { model: "", thinking: "" };
|
|
79
|
+
const agents: any = {};
|
|
80
|
+
for (const key of Object.keys(models)) {
|
|
81
|
+
if (key !== "gentle-orchestrator") {
|
|
82
|
+
agents[key] = models[key];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const profiles = await readJson(PROFILES_PATH);
|
|
86
|
+
profiles[profileName] = { name: profileName, orchestrator, agents };
|
|
87
|
+
await writeJson(PROFILES_PATH, profiles);
|
|
88
|
+
ctx.ui.notify(`Profile '${profileName}' saved`, "success");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
while (true) {
|
|
93
|
+
let profiles: Record<string, Profile> = await readJson(PROFILES_PATH);
|
|
94
|
+
const profileNames = Object.keys(profiles);
|
|
95
|
+
|
|
96
|
+
const items = [
|
|
97
|
+
{ value: "__CREATE__", label: "β¨ Create New Profile from Current Config", description: "Saves models.json into a new profile" },
|
|
98
|
+
...profileNames.map((name) => ({
|
|
99
|
+
value: name,
|
|
100
|
+
label: name,
|
|
101
|
+
description: `Orchestrator: ${profiles[name].orchestrator?.model || "none"}`,
|
|
102
|
+
}))
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
// Pick Profile
|
|
106
|
+
const selectedProfile = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
107
|
+
const container = new Container();
|
|
108
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
109
|
+
container.addChild(new Text(theme.fg("accent", theme.bold("Profiles Manager")), 1, 0));
|
|
110
|
+
|
|
111
|
+
const list = new SelectList(items, Math.min(items.length, 10), {
|
|
112
|
+
selectedPrefix: (t) => theme.fg("accent", t),
|
|
113
|
+
selectedText: (t) => theme.fg("accent", t),
|
|
114
|
+
description: (t) => theme.fg("muted", t),
|
|
115
|
+
scrollInfo: (t) => theme.fg("dim", t),
|
|
116
|
+
noMatch: (t) => theme.fg("warning", t),
|
|
117
|
+
});
|
|
118
|
+
list.onSelect = (item) => done(item.value);
|
|
119
|
+
list.onCancel = () => done(null);
|
|
120
|
+
container.addChild(list);
|
|
121
|
+
|
|
122
|
+
container.addChild(new Text(theme.fg("dim", "ββ navigate β’ enter select β’ esc close"), 1, 0));
|
|
123
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
render: (w) => container.render(w),
|
|
127
|
+
invalidate: () => container.invalidate(),
|
|
128
|
+
handleInput: (data) => { list.handleInput(data); tui.requestRender(); },
|
|
129
|
+
};
|
|
130
|
+
}, { overlay: true });
|
|
131
|
+
|
|
132
|
+
if (!selectedProfile) break; // Finished and exit loop
|
|
133
|
+
|
|
134
|
+
if (selectedProfile === "__CREATE__") {
|
|
135
|
+
const newName = await promptInput(ctx, "Enter new profile name:");
|
|
136
|
+
if (newName) {
|
|
137
|
+
const models = await readJson(MODELS_PATH);
|
|
138
|
+
const orchestrator = models["gentle-orchestrator"] || { model: "", thinking: "" };
|
|
139
|
+
const agents: any = {};
|
|
140
|
+
for (const key of Object.keys(models)) {
|
|
141
|
+
if (key !== "gentle-orchestrator") {
|
|
142
|
+
agents[key] = models[key];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
profiles[newName] = { name: newName, orchestrator, agents };
|
|
146
|
+
await writeJson(PROFILES_PATH, profiles);
|
|
147
|
+
ctx.ui.notify(`Created profile '${newName}'`, "success");
|
|
148
|
+
}
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Action menu for existing profile
|
|
153
|
+
while (true) {
|
|
154
|
+
const actionItems = [
|
|
155
|
+
{ value: "activate", label: "βΆ Activate", description: "Apply this profile to models.json" },
|
|
156
|
+
{ value: "edit", label: "β Edit", description: "Modify agents in this profile" },
|
|
157
|
+
{ value: "delete", label: "β Delete", description: "Remove this profile" },
|
|
158
|
+
{ value: "back", label: "β Back", description: "Return to profile list" },
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
const selectedAction = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
162
|
+
const container = new Container();
|
|
163
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
164
|
+
container.addChild(new Text(theme.fg("accent", theme.bold(`Profile: '${selectedProfile}'`)), 1, 0));
|
|
165
|
+
|
|
166
|
+
const list = new SelectList(actionItems, 4, {
|
|
167
|
+
selectedPrefix: (t) => theme.fg("accent", t),
|
|
168
|
+
selectedText: (t) => theme.fg("accent", t),
|
|
169
|
+
description: (t) => theme.fg("muted", t),
|
|
170
|
+
scrollInfo: (t) => theme.fg("dim", t),
|
|
171
|
+
noMatch: (t) => theme.fg("warning", t),
|
|
172
|
+
});
|
|
173
|
+
list.onSelect = (item) => done(item.value);
|
|
174
|
+
list.onCancel = () => done(null);
|
|
175
|
+
container.addChild(list);
|
|
176
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
render: (w) => container.render(w),
|
|
180
|
+
invalidate: () => container.invalidate(),
|
|
181
|
+
handleInput: (data) => { list.handleInput(data); tui.requestRender(); },
|
|
182
|
+
};
|
|
183
|
+
}, { overlay: true });
|
|
184
|
+
|
|
185
|
+
if (!selectedAction || selectedAction === "back") {
|
|
186
|
+
break; // Back to profile list
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (selectedAction === "activate") {
|
|
190
|
+
const profile = profiles[selectedProfile];
|
|
191
|
+
const newModels: any = {};
|
|
192
|
+
if (profile.orchestrator) {
|
|
193
|
+
newModels["gentle-orchestrator"] = profile.orchestrator;
|
|
194
|
+
}
|
|
195
|
+
if (profile.agents) {
|
|
196
|
+
for (const key of Object.keys(profile.agents)) {
|
|
197
|
+
newModels[key] = profile.agents[key];
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
await writeJson(MODELS_PATH, newModels);
|
|
201
|
+
ctx.ui.notify(`Activated profile '${selectedProfile}'. Run /reload if needed.`, "success");
|
|
202
|
+
break; // Back to profile list after activating
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (selectedAction === "delete") {
|
|
206
|
+
delete profiles[selectedProfile];
|
|
207
|
+
await writeJson(PROFILES_PATH, profiles);
|
|
208
|
+
ctx.ui.notify(`Deleted profile '${selectedProfile}'.`, "success");
|
|
209
|
+
break; // Back to profile list after deleting
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (selectedAction === "edit") {
|
|
213
|
+
// Edit Flow
|
|
214
|
+
while (true) {
|
|
215
|
+
const currentProfile = profiles[selectedProfile];
|
|
216
|
+
const agentKeys = ["orchestrator", ...Object.keys(currentProfile.agents || {})];
|
|
217
|
+
|
|
218
|
+
const editItems = agentKeys.map(k => {
|
|
219
|
+
const conf = k === "orchestrator" ? currentProfile.orchestrator : currentProfile.agents[k];
|
|
220
|
+
return {
|
|
221
|
+
value: k,
|
|
222
|
+
label: k,
|
|
223
|
+
description: `${conf?.model || "none"} (${conf?.thinking || "low"})`
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
editItems.push({ value: "__ADD__", label: "β Add Subagent", description: "Add a specific configuration for a subagent" });
|
|
227
|
+
editItems.push({ value: "back", label: "β Back", description: "Return to profile menu" });
|
|
228
|
+
|
|
229
|
+
const pickedAgent = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
230
|
+
const container = new Container();
|
|
231
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
232
|
+
container.addChild(new Text(theme.fg("accent", theme.bold(`Edit Profile '${selectedProfile}'`)), 1, 0));
|
|
233
|
+
|
|
234
|
+
const list = new SelectList(editItems, Math.min(editItems.length, 10), {
|
|
235
|
+
selectedPrefix: (t) => theme.fg("accent", t),
|
|
236
|
+
selectedText: (t) => theme.fg("accent", t),
|
|
237
|
+
description: (t) => theme.fg("muted", t),
|
|
238
|
+
scrollInfo: (t) => theme.fg("dim", t),
|
|
239
|
+
noMatch: (t) => theme.fg("warning", t),
|
|
240
|
+
});
|
|
241
|
+
list.onSelect = (item) => done(item.value);
|
|
242
|
+
list.onCancel = () => done(null);
|
|
243
|
+
container.addChild(list);
|
|
244
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
render: (w) => container.render(w),
|
|
248
|
+
invalidate: () => container.invalidate(),
|
|
249
|
+
handleInput: (data) => { list.handleInput(data); tui.requestRender(); },
|
|
250
|
+
};
|
|
251
|
+
}, { overlay: true });
|
|
252
|
+
|
|
253
|
+
if (!pickedAgent || pickedAgent === "back") break;
|
|
254
|
+
|
|
255
|
+
let agentName = pickedAgent;
|
|
256
|
+
// If adding, ask for agent name
|
|
257
|
+
if (agentName === "__ADD__") {
|
|
258
|
+
const newName = await promptInput(ctx, "Subagent Name (e.g. sdd-apply):");
|
|
259
|
+
if (!newName) continue;
|
|
260
|
+
agentName = newName;
|
|
261
|
+
if (!currentProfile.agents) currentProfile.agents = {};
|
|
262
|
+
currentProfile.agents[agentName] = { model: "omni/antigravity/gemini-3.6-flash-low", thinking: "low" };
|
|
263
|
+
await writeJson(PROFILES_PATH, profiles);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Edit Agent
|
|
267
|
+
while (true) {
|
|
268
|
+
const conf = agentName === "orchestrator" ? currentProfile.orchestrator : currentProfile.agents[agentName];
|
|
269
|
+
|
|
270
|
+
const modifierItems = [
|
|
271
|
+
{ value: "model", label: "Modify Model", description: conf?.model || "none" },
|
|
272
|
+
{ value: "thinking", label: "Modify Thinking", description: conf?.thinking || "low" },
|
|
273
|
+
{ value: "delete", label: "β Remove Agent from profile", description: "Delete this configuration" },
|
|
274
|
+
{ value: "back", label: "β Back", description: "" },
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
const pickedMod = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
278
|
+
const container = new Container();
|
|
279
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
280
|
+
container.addChild(new Text(theme.fg("accent", theme.bold(`Edit Agent '${agentName}'`)), 1, 0));
|
|
281
|
+
const list = new SelectList(modifierItems, 4, {
|
|
282
|
+
selectedPrefix: (t) => theme.fg("accent", t),
|
|
283
|
+
selectedText: (t) => theme.fg("accent", t),
|
|
284
|
+
description: (t) => theme.fg("muted", t),
|
|
285
|
+
scrollInfo: (t) => theme.fg("dim", t),
|
|
286
|
+
noMatch: (t) => theme.fg("warning", t),
|
|
287
|
+
});
|
|
288
|
+
list.onSelect = (item) => done(item.value);
|
|
289
|
+
list.onCancel = () => done(null);
|
|
290
|
+
container.addChild(list);
|
|
291
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
292
|
+
return {
|
|
293
|
+
render: (w) => container.render(w),
|
|
294
|
+
invalidate: () => container.invalidate(),
|
|
295
|
+
handleInput: (data) => { list.handleInput(data); tui.requestRender(); },
|
|
296
|
+
};
|
|
297
|
+
}, { overlay: true });
|
|
298
|
+
|
|
299
|
+
if (!pickedMod || pickedMod === "back") break;
|
|
300
|
+
|
|
301
|
+
if (pickedMod === "delete") {
|
|
302
|
+
if (agentName === "orchestrator") {
|
|
303
|
+
ctx.ui.notify("Cannot delete orchestrator", "error");
|
|
304
|
+
} else {
|
|
305
|
+
delete currentProfile.agents[agentName];
|
|
306
|
+
await writeJson(PROFILES_PATH, profiles);
|
|
307
|
+
ctx.ui.notify(`Removed ${agentName}`, "success");
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
} else if (pickedMod === "model") {
|
|
311
|
+
const newModel = await promptInput(ctx, `Model for ${agentName}:`, conf.model);
|
|
312
|
+
if (newModel !== null) {
|
|
313
|
+
conf.model = newModel;
|
|
314
|
+
await writeJson(PROFILES_PATH, profiles);
|
|
315
|
+
}
|
|
316
|
+
} else if (pickedMod === "thinking") {
|
|
317
|
+
const thinkingLevels = ["low", "medium", "high", "xhigh", "max"].map(t => ({ value: t, label: t }));
|
|
318
|
+
const newThinking = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
319
|
+
const container = new Container();
|
|
320
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
321
|
+
container.addChild(new Text(theme.fg("accent", theme.bold(`Thinking for ${agentName}`)), 1, 0));
|
|
322
|
+
const list = new SelectList(thinkingLevels, 5, {
|
|
323
|
+
selectedPrefix: (t) => theme.fg("accent", t),
|
|
324
|
+
selectedText: (t) => theme.fg("accent", t),
|
|
325
|
+
description: (t) => theme.fg("muted", t),
|
|
326
|
+
scrollInfo: (t) => theme.fg("dim", t),
|
|
327
|
+
noMatch: (t) => theme.fg("warning", t),
|
|
328
|
+
});
|
|
329
|
+
list.onSelect = (item) => done(item.value);
|
|
330
|
+
list.onCancel = () => done(null);
|
|
331
|
+
container.addChild(list);
|
|
332
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
333
|
+
return {
|
|
334
|
+
render: (w) => container.render(w),
|
|
335
|
+
invalidate: () => container.invalidate(),
|
|
336
|
+
handleInput: (data) => { list.handleInput(data); tui.requestRender(); },
|
|
337
|
+
};
|
|
338
|
+
}, { overlay: true });
|
|
339
|
+
|
|
340
|
+
if (newThinking) {
|
|
341
|
+
conf.thinking = newThinking;
|
|
342
|
+
await writeJson(PROFILES_PATH, profiles);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-profiles-manager",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Interactive SDD model profile management built natively for the Pi Coding Agent.",
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"pi-agent-extension",
|
|
9
|
+
"pi-package"
|
|
10
|
+
],
|
|
11
|
+
"pi": {
|
|
12
|
+
"extensions": ["index.ts"]
|
|
13
|
+
},
|
|
14
|
+
"author": "javinnav",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/javinnav/pi-profiles-manager.git"
|
|
19
|
+
}
|
|
20
|
+
}
|