pi-connect 0.1.2 → 0.1.3
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/index.ts +82 -22
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DynamicBorder, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { getEnvApiKey } from "@mariozechner/pi-ai";
|
|
3
|
-
import { Container, SelectList, Text, type SelectItem } from "@mariozechner/pi-tui";
|
|
3
|
+
import { Container, fuzzyFilter, Input, Key, matchesKey, SelectList, Text, type SelectItem } from "@mariozechner/pi-tui";
|
|
4
4
|
import { exec as execCb } from "node:child_process";
|
|
5
5
|
|
|
6
6
|
const DISPLAY_NAME_OVERRIDES: Record<string, string> = {
|
|
@@ -113,30 +113,90 @@ async function pickItem(ctx: any, title: string, subtitle: string | undefined, i
|
|
|
113
113
|
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
114
114
|
container.addChild(new Text(theme.fg("text", theme.bold(title)), 1, 0));
|
|
115
115
|
if (subtitle) container.addChild(new Text(theme.fg("dim", subtitle), 1, 0));
|
|
116
|
+
|
|
117
|
+
container.addChild(new Text(theme.fg("muted", "Search"), 1, 0));
|
|
118
|
+
const searchInput = new Input();
|
|
119
|
+
searchInput.focused = true;
|
|
120
|
+
container.addChild(searchInput);
|
|
116
121
|
container.addChild(new Text("", 0, 0));
|
|
117
122
|
|
|
118
|
-
const maxVisible = Math.max(6, Math.min(items.length, Math.floor((tui.terminal.rows -
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
const maxVisible = Math.max(6, Math.min(items.length, Math.floor((tui.terminal.rows - 14) / 2)));
|
|
124
|
+
const listContainer = new Container();
|
|
125
|
+
container.addChild(listContainer);
|
|
126
|
+
|
|
127
|
+
let list: SelectList;
|
|
128
|
+
|
|
129
|
+
const sectionOauth = items.find((item) => item.value === "__section_oauth");
|
|
130
|
+
const sectionApi = items.find((item) => item.value === "__section_api");
|
|
131
|
+
const itemTheme = {
|
|
132
|
+
selectedPrefix: (t: string) => theme.fg("accent", t),
|
|
133
|
+
selectedText: (t: string) => theme.fg("accent", theme.bold(t)),
|
|
134
|
+
description: (t: string) => theme.fg("muted", t),
|
|
135
|
+
scrollInfo: (t: string) => theme.fg("dim", t),
|
|
136
|
+
noMatch: (t: string) => theme.fg("warning", t),
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const rebuildList = () => {
|
|
140
|
+
const query = searchInput.getValue().trim();
|
|
141
|
+
const normalItems = items.filter((item) => !item.value.startsWith("__section_"));
|
|
142
|
+
const filtered = query
|
|
143
|
+
? fuzzyFilter(normalItems, query, (item) => `${item.label} ${item.description ?? ""}`)
|
|
144
|
+
: normalItems;
|
|
145
|
+
|
|
146
|
+
const oauthItems = filtered.filter((item) => item.value.startsWith("oauth:"));
|
|
147
|
+
const apiItems = filtered.filter((item) => item.value.startsWith("api:"));
|
|
148
|
+
|
|
149
|
+
const displayItems: SelectItem[] = [];
|
|
150
|
+
if (oauthItems.length > 0 && sectionOauth) displayItems.push(sectionOauth);
|
|
151
|
+
displayItems.push(...oauthItems);
|
|
152
|
+
if (apiItems.length > 0 && sectionApi) displayItems.push(sectionApi);
|
|
153
|
+
displayItems.push(...apiItems);
|
|
154
|
+
|
|
155
|
+
const finalItems = displayItems.length > 0
|
|
156
|
+
? displayItems
|
|
157
|
+
: [{ value: "__empty", label: "No matching providers", description: "Try a different search" }];
|
|
158
|
+
|
|
159
|
+
list = new SelectList(finalItems, maxVisible, itemTheme);
|
|
160
|
+
list.onSelect = (item) => {
|
|
161
|
+
if (item.value.startsWith("__section_") || item.value === "__empty") return;
|
|
162
|
+
done(item);
|
|
163
|
+
};
|
|
164
|
+
list.onCancel = () => done(null);
|
|
165
|
+
|
|
166
|
+
listContainer.clear();
|
|
167
|
+
listContainer.addChild(list);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
rebuildList();
|
|
129
171
|
|
|
130
172
|
container.addChild(new Text("", 0, 0));
|
|
131
173
|
container.addChild(new Text(`${theme.fg("success", "●")} connected ${theme.fg("warning", "◌")} env ${theme.fg("muted", "○")} new`, 1, 0));
|
|
132
|
-
container.addChild(new Text(theme.fg("dim", "↑↓ navigate • Enter select • Esc cancel"), 1, 0));
|
|
174
|
+
container.addChild(new Text(theme.fg("dim", "type to search • ↑↓ navigate • Enter select • Esc cancel/clear"), 1, 0));
|
|
133
175
|
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
134
176
|
|
|
135
177
|
return {
|
|
136
178
|
render: (w) => container.render(w),
|
|
137
179
|
invalidate: () => container.invalidate(),
|
|
138
180
|
handleInput: (data) => {
|
|
139
|
-
|
|
181
|
+
if (matchesKey(data, Key.escape)) {
|
|
182
|
+
if (searchInput.getValue()) {
|
|
183
|
+
searchInput.setValue("");
|
|
184
|
+
rebuildList();
|
|
185
|
+
tui.requestRender();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
done(null);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (matchesKey(data, Key.up) || matchesKey(data, Key.down) || matchesKey(data, Key.enter) || matchesKey(data, Key.return) || matchesKey(data, Key.pageUp) || matchesKey(data, Key.pageDown)) {
|
|
193
|
+
list.handleInput(data);
|
|
194
|
+
tui.requestRender();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
searchInput.handleInput(data);
|
|
199
|
+
rebuildList();
|
|
140
200
|
tui.requestRender();
|
|
141
201
|
},
|
|
142
202
|
};
|
|
@@ -150,9 +210,9 @@ export default function piConnectExtension(pi: ExtensionAPI) {
|
|
|
150
210
|
const apiProviderIds = getApiCapableProviderIds(ctx);
|
|
151
211
|
|
|
152
212
|
const statusIcon = (providerId: string) => {
|
|
153
|
-
if (authStorage.has(providerId)) return
|
|
154
|
-
if (getEnvApiKey(providerId)) return
|
|
155
|
-
return
|
|
213
|
+
if (authStorage.has(providerId)) return "●";
|
|
214
|
+
if (getEnvApiKey(providerId)) return "◌";
|
|
215
|
+
return "○";
|
|
156
216
|
};
|
|
157
217
|
|
|
158
218
|
const items: SelectItem[] = [];
|
|
@@ -160,7 +220,7 @@ export default function piConnectExtension(pi: ExtensionAPI) {
|
|
|
160
220
|
if (oauthProviders.length > 0) {
|
|
161
221
|
items.push({
|
|
162
222
|
value: "__section_oauth",
|
|
163
|
-
label:
|
|
223
|
+
label: "OAuth providers",
|
|
164
224
|
description: "login via browser"
|
|
165
225
|
});
|
|
166
226
|
for (const provider of oauthProviders) {
|
|
@@ -175,7 +235,7 @@ export default function piConnectExtension(pi: ExtensionAPI) {
|
|
|
175
235
|
if (apiProviderIds.length > 0) {
|
|
176
236
|
items.push({
|
|
177
237
|
value: "__section_api",
|
|
178
|
-
label:
|
|
238
|
+
label: "API key providers",
|
|
179
239
|
description: "paste and save key"
|
|
180
240
|
});
|
|
181
241
|
for (const providerId of apiProviderIds) {
|
|
@@ -243,8 +303,8 @@ export default function piConnectExtension(pi: ExtensionAPI) {
|
|
|
243
303
|
|
|
244
304
|
if (oauthIds.has(providerId) && apiIds.has(providerId)) {
|
|
245
305
|
const method = await pickItem(ctx, prettyProviderName(providerId), "Choose how to connect", [
|
|
246
|
-
{ value: "oauth", label:
|
|
247
|
-
{ value: "api", label:
|
|
306
|
+
{ value: "oauth", label: "OAuth", description: "browser login" },
|
|
307
|
+
{ value: "api", label: "API key", description: "paste and save key" },
|
|
248
308
|
]);
|
|
249
309
|
if (!method) return;
|
|
250
310
|
if (method.value === "oauth") {
|
|
@@ -275,7 +335,7 @@ export default function piConnectExtension(pi: ExtensionAPI) {
|
|
|
275
335
|
}
|
|
276
336
|
const items: SelectItem[] = providers.map((providerId) => ({
|
|
277
337
|
value: providerId,
|
|
278
|
-
label:
|
|
338
|
+
label: `● ${prettyProviderName(providerId)}`,
|
|
279
339
|
description: "Connected"
|
|
280
340
|
}));
|
|
281
341
|
const selected = await pickItem(ctx, "Disconnect provider", "Remove a saved credential", items);
|
package/package.json
CHANGED