pi-cliproxyapi 0.1.1 → 0.2.0
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 +34 -6
- package/index.ts +1 -1
- package/package.json +1 -1
- package/src/apply.ts +85 -10
- package/src/commands.ts +1 -28
- package/src/ui-frame.ts +118 -0
- package/src/ui-overlay.ts +18 -75
- package/src/ui-picker/catalog.ts +52 -0
- package/src/ui-picker/index.ts +35 -0
- package/src/ui-picker/mutate.ts +133 -0
- package/src/ui-picker/picker-component.ts +432 -0
- package/src/ui-picker/picker.ts +247 -0
- package/src/ui-picker/prompt-confirm.ts +71 -0
- package/src/ui-picker/prompt-name.ts +90 -0
- package/src/ui-picker/providers.ts +68 -0
- package/src/ui-picker/render-text.ts +39 -0
- package/src/ui-picker/rows.ts +151 -0
- package/src/ui-picker/types.ts +56 -0
- package/src/ui-setup.ts +20 -47
- package/src/ui-picker.ts +0 -842
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Shared types for the /cliproxy three-panel picker.
|
|
2
|
+
|
|
3
|
+
import type { Api } from "@earendil-works/pi-ai";
|
|
4
|
+
|
|
5
|
+
export interface Theme {
|
|
6
|
+
fg(name: string, s: string): string;
|
|
7
|
+
bold(s: string): string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface OverlayTui {
|
|
11
|
+
requestRender(): void;
|
|
12
|
+
rows?: number;
|
|
13
|
+
cols?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type PanelId = "providers" | "assigned" | "pool";
|
|
17
|
+
|
|
18
|
+
export interface ProviderEntry {
|
|
19
|
+
kind: "builtin" | "custom";
|
|
20
|
+
name: string;
|
|
21
|
+
/** API the provider expects. Used for compat warnings on pool rows. */
|
|
22
|
+
api: Api;
|
|
23
|
+
/** Subtitle shown in the providers panel. */
|
|
24
|
+
subtitle: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ModelEntry {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
/** Suggested API for this model (from server hint / catalog). */
|
|
31
|
+
suggestedApi: Api;
|
|
32
|
+
/** Subtitle to render. */
|
|
33
|
+
subtitle?: string;
|
|
34
|
+
/** Origin label for grouping (e.g. "lproxy-glm"); empty for built-in. */
|
|
35
|
+
origin?: string;
|
|
36
|
+
/** Raw upstream owned_by tag, for grouping/UX. */
|
|
37
|
+
ownedBy?: string;
|
|
38
|
+
reasoning: boolean;
|
|
39
|
+
contextWindow: number;
|
|
40
|
+
maxTokens: number;
|
|
41
|
+
cost?: {
|
|
42
|
+
input: number;
|
|
43
|
+
output: number;
|
|
44
|
+
cacheRead: number;
|
|
45
|
+
cacheWrite: number;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface CatalogIndex {
|
|
50
|
+
/** All models indexed by id, regardless of provider scope. */
|
|
51
|
+
byId: Map<string, ModelEntry>;
|
|
52
|
+
/** For each built-in provider name, ordered model ids that it offers on the proxy. */
|
|
53
|
+
builtinModelIds: Map<string, string[]>;
|
|
54
|
+
/** Ordered list of all custom-pool model ids. */
|
|
55
|
+
customPoolIds: string[];
|
|
56
|
+
}
|
package/src/ui-setup.ts
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
getKeybindings,
|
|
15
15
|
Input,
|
|
16
16
|
matchesKey,
|
|
17
|
-
visibleWidth,
|
|
18
17
|
} from "@earendil-works/pi-tui";
|
|
19
18
|
|
|
20
19
|
import {
|
|
@@ -23,6 +22,7 @@ import {
|
|
|
23
22
|
resolveConfigValue,
|
|
24
23
|
saveConfig,
|
|
25
24
|
} from "./config.ts";
|
|
25
|
+
import { frame, frameInner } from "./ui-frame.ts";
|
|
26
26
|
|
|
27
27
|
interface Theme {
|
|
28
28
|
fg(name: string, s: string): string;
|
|
@@ -104,13 +104,9 @@ export async function runSetup(
|
|
|
104
104
|
const prefill = forceAll
|
|
105
105
|
? (values[step.label] ?? step.initialValue ?? "")
|
|
106
106
|
: (values[step.label] ?? "");
|
|
107
|
-
// Skip already-filled non-empty fields when called for first-run setup
|
|
108
|
-
// (we still want to ask for missing pieces, but not re-prompt for
|
|
109
|
-
// what's already saved).
|
|
110
107
|
if (!forceAll && prefill) {
|
|
111
108
|
continue;
|
|
112
109
|
}
|
|
113
|
-
|
|
114
110
|
const result = await promptStep(
|
|
115
111
|
ctx,
|
|
116
112
|
step,
|
|
@@ -123,7 +119,7 @@ export async function runSetup(
|
|
|
123
119
|
const trimmed = result.trim();
|
|
124
120
|
if (!trimmed) {
|
|
125
121
|
if (step.required) {
|
|
126
|
-
ctx.ui.notify(`${step.label} is required
|
|
122
|
+
ctx.ui.notify(`${step.label} is required \u2014 aborted`, "warning");
|
|
127
123
|
cancelled = true;
|
|
128
124
|
break;
|
|
129
125
|
}
|
|
@@ -153,10 +149,6 @@ export async function runSetup(
|
|
|
153
149
|
return true;
|
|
154
150
|
}
|
|
155
151
|
|
|
156
|
-
/**
|
|
157
|
-
* If the user typed a bare ~/path or /abs/path, wrap it in `!cat <path>`
|
|
158
|
-
* so resolveConfigValue executes it. Otherwise return as-is.
|
|
159
|
-
*/
|
|
160
152
|
function normalizeValue(raw: string): string {
|
|
161
153
|
if (raw.startsWith("!") || raw.startsWith("$")) return raw;
|
|
162
154
|
if (raw.startsWith("~/")) {
|
|
@@ -189,10 +181,7 @@ async function promptStep(
|
|
|
189
181
|
prefill,
|
|
190
182
|
done,
|
|
191
183
|
),
|
|
192
|
-
{
|
|
193
|
-
overlay: true,
|
|
194
|
-
overlayOptions: { width: 100, maxHeight: "60%" },
|
|
195
|
-
},
|
|
184
|
+
{ overlay: true, overlayOptions: { width: 100, maxHeight: "60%" } },
|
|
196
185
|
);
|
|
197
186
|
}
|
|
198
187
|
|
|
@@ -220,8 +209,6 @@ function buildStepOverlay(
|
|
|
220
209
|
}
|
|
221
210
|
if (step.validate) {
|
|
222
211
|
const trimmed = raw.trim();
|
|
223
|
-
// Only validate the literal form. Indirect ("!", "$") values are
|
|
224
|
-
// validated at apply time, not here.
|
|
225
212
|
if (trimmed && !trimmed.startsWith("!") && !trimmed.startsWith("$")) {
|
|
226
213
|
const err = step.validate(trimmed);
|
|
227
214
|
if (err) {
|
|
@@ -239,38 +226,30 @@ function buildStepOverlay(
|
|
|
239
226
|
|
|
240
227
|
return {
|
|
241
228
|
render(width: number): string[] {
|
|
242
|
-
const inner =
|
|
243
|
-
const top = theme.fg(
|
|
244
|
-
"borderAccent",
|
|
245
|
-
`\u256d\u2500 ${theme.bold(theme.fg("accent", `setup: ${step.label}`))} ${"\u2500".repeat(Math.max(0, inner - visibleWidth(`setup: ${step.label}`) - 4))}\u256e`,
|
|
246
|
-
);
|
|
247
|
-
const hintLine = pad(theme.fg("dim", step.hint), inner - 2);
|
|
229
|
+
const inner = frameInner(width);
|
|
248
230
|
const inputLines = input.render(inner - 4);
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
);
|
|
266
|
-
return out;
|
|
231
|
+
const lines: string[] = [
|
|
232
|
+
"",
|
|
233
|
+
` ${theme.fg("dim", step.hint)}`,
|
|
234
|
+
"",
|
|
235
|
+
...inputLines.map((ln) => ` ${theme.fg("accent", `\u276f ${ln}`)}`),
|
|
236
|
+
"",
|
|
237
|
+
error
|
|
238
|
+
? ` ${theme.fg("error", `! ${error}`)}`
|
|
239
|
+
: ` ${theme.fg("dim", "enter = save \u00b7 esc = cancel")}`,
|
|
240
|
+
];
|
|
241
|
+
return frame(theme, {
|
|
242
|
+
width,
|
|
243
|
+
title: ` setup: ${step.label} `,
|
|
244
|
+
lines,
|
|
245
|
+
footer: { hint: " enter = save \u00b7 esc = cancel " },
|
|
246
|
+
});
|
|
267
247
|
},
|
|
268
248
|
invalidate(): void {
|
|
269
249
|
input.invalidate();
|
|
270
250
|
},
|
|
271
251
|
handleInput(data: string): void {
|
|
272
252
|
const kb = getKeybindings();
|
|
273
|
-
// Plain Esc cancels even if Input doesn't dispatch it.
|
|
274
253
|
if (kb.matches(data, "tui.select.cancel") || matchesKey(data, "escape")) {
|
|
275
254
|
done(undefined);
|
|
276
255
|
return;
|
|
@@ -281,9 +260,3 @@ function buildStepOverlay(
|
|
|
281
260
|
},
|
|
282
261
|
};
|
|
283
262
|
}
|
|
284
|
-
|
|
285
|
-
function pad(s: string, width: number): string {
|
|
286
|
-
const w = visibleWidth(s);
|
|
287
|
-
if (w >= width) return s;
|
|
288
|
-
return s + " ".repeat(width - w);
|
|
289
|
-
}
|