openfox 0.1.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 +68 -0
- package/dist/branch.api-WDHKZJS6.js +48 -0
- package/dist/branch.api-WDHKZJS6.js.map +1 -0
- package/dist/chunk-26NRL3HX.js +116 -0
- package/dist/chunk-26NRL3HX.js.map +1 -0
- package/dist/chunk-43SWNGYW.js +273 -0
- package/dist/chunk-43SWNGYW.js.map +1 -0
- package/dist/chunk-4Z2OZ6NX.js +132 -0
- package/dist/chunk-4Z2OZ6NX.js.map +1 -0
- package/dist/chunk-6MOGZMGX.js +218 -0
- package/dist/chunk-6MOGZMGX.js.map +1 -0
- package/dist/chunk-AF7XS4OI.js +55 -0
- package/dist/chunk-AF7XS4OI.js.map +1 -0
- package/dist/chunk-EI46G5WO.js +138 -0
- package/dist/chunk-EI46G5WO.js.map +1 -0
- package/dist/chunk-FF6Z2MC3.js +844 -0
- package/dist/chunk-FF6Z2MC3.js.map +1 -0
- package/dist/chunk-IKAWIOXM.js +5817 -0
- package/dist/chunk-IKAWIOXM.js.map +1 -0
- package/dist/chunk-IVHCPQO2.js +29 -0
- package/dist/chunk-IVHCPQO2.js.map +1 -0
- package/dist/chunk-MTX5Y3V3.js +46 -0
- package/dist/chunk-MTX5Y3V3.js.map +1 -0
- package/dist/chunk-N2XYX4ZA.js +83 -0
- package/dist/chunk-N2XYX4ZA.js.map +1 -0
- package/dist/chunk-QY7BMXWT.js +124 -0
- package/dist/chunk-QY7BMXWT.js.map +1 -0
- package/dist/chunk-U62ZECSQ.js +54 -0
- package/dist/chunk-U62ZECSQ.js.map +1 -0
- package/dist/chunk-YQ36AK5A.js +5570 -0
- package/dist/chunk-YQ36AK5A.js.map +1 -0
- package/dist/cli/dev.d.ts +1 -0
- package/dist/cli/dev.js +14 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +14 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/command-defaults/defaults/commit-push.command.md +13 -0
- package/dist/command-defaults/defaults/init.command.md +18 -0
- package/dist/command-defaults/defaults/test-ui.command.md +16 -0
- package/dist/config-6GJ4SVFH.js +33 -0
- package/dist/config-6GJ4SVFH.js.map +1 -0
- package/dist/init-UVZDOHMX.js +185 -0
- package/dist/init-UVZDOHMX.js.map +1 -0
- package/dist/paths-4BPXWB2W.js +17 -0
- package/dist/paths-4BPXWB2W.js.map +1 -0
- package/dist/project-creator-CWPKRIKI.js +69 -0
- package/dist/project-creator-CWPKRIKI.js.map +1 -0
- package/dist/protocol-BJsm88OW.d.ts +760 -0
- package/dist/provider-LCE6WZA6.js +343 -0
- package/dist/provider-LCE6WZA6.js.map +1 -0
- package/dist/serve-27DZK34K.js +177 -0
- package/dist/serve-27DZK34K.js.map +1 -0
- package/dist/server/index.d.ts +479 -0
- package/dist/server/index.js +20 -0
- package/dist/server/index.js.map +1 -0
- package/dist/settings-D5O3TMLD.js +17 -0
- package/dist/settings-D5O3TMLD.js.map +1 -0
- package/dist/shared/index.d.ts +19 -0
- package/dist/shared/index.js +17 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/skill-defaults/defaults/playwright-cli.skill.md +38 -0
- package/dist/tools-R4YQAYUF.js +46 -0
- package/dist/tools-R4YQAYUF.js.map +1 -0
- package/dist/web/assets/html2canvas.esm-QH1iLAAe.js +22 -0
- package/dist/web/assets/index-BMNDCsbJ.js +120 -0
- package/dist/web/assets/index-BSlSWZrM.css +1 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-600-normal-8K4wrrwR.woff +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-600-normal-EVf6-Yzo.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-600-normal-H7WoG9Et.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-600-normal-mc2nkWzM.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-600-normal-BfsvjouI.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-600-normal-C8RAYTDA.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-600-normal-BfB_LPfz.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-600-normal-DObL3zCW.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-600-normal-OWROknRo.woff +0 -0
- package/dist/web/assets/openfox.png +0 -0
- package/dist/web/fox.svg +25 -0
- package/dist/web/index.html +14 -0
- package/dist/web/sounds/achievement.mp3 +0 -0
- package/dist/web/sounds/intervention.mp3 +0 -0
- package/dist/web/sounds/notification.mp3 +0 -0
- package/dist/web/sounds/waiting-for-user.mp3 +0 -0
- package/package.json +101 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fetchAvailableModelsFromBackend
|
|
3
|
+
} from "./chunk-6MOGZMGX.js";
|
|
4
|
+
import {
|
|
5
|
+
activateProvider,
|
|
6
|
+
addProvider,
|
|
7
|
+
getActiveProvider,
|
|
8
|
+
loadGlobalConfig,
|
|
9
|
+
removeProvider,
|
|
10
|
+
saveGlobalConfig
|
|
11
|
+
} from "./chunk-43SWNGYW.js";
|
|
12
|
+
import {
|
|
13
|
+
detectBackend,
|
|
14
|
+
detectModel
|
|
15
|
+
} from "./chunk-FF6Z2MC3.js";
|
|
16
|
+
import "./chunk-26NRL3HX.js";
|
|
17
|
+
import "./chunk-U62ZECSQ.js";
|
|
18
|
+
import "./chunk-AF7XS4OI.js";
|
|
19
|
+
|
|
20
|
+
// src/cli/provider.ts
|
|
21
|
+
import { select, text, password, spinner, log, outro, isCancel, cancel } from "@clack/prompts";
|
|
22
|
+
var BACKEND_OPTIONS = [
|
|
23
|
+
{ value: "auto", label: "Auto-detect" },
|
|
24
|
+
{ value: "vllm", label: "vLLM" },
|
|
25
|
+
{ value: "sglang", label: "SGLang" },
|
|
26
|
+
{ value: "ollama", label: "Ollama" },
|
|
27
|
+
{ value: "llamacpp", label: "llama.cpp" },
|
|
28
|
+
{ value: "openai", label: "OpenAI" },
|
|
29
|
+
{ value: "anthropic", label: "Anthropic" }
|
|
30
|
+
];
|
|
31
|
+
function printProviderHelp() {
|
|
32
|
+
console.log(`
|
|
33
|
+
Provider Management Commands:
|
|
34
|
+
|
|
35
|
+
openfox provider add Add a new provider
|
|
36
|
+
openfox provider list List all configured providers
|
|
37
|
+
openfox provider use Switch to a different provider
|
|
38
|
+
openfox provider remove Remove a provider
|
|
39
|
+
`);
|
|
40
|
+
}
|
|
41
|
+
async function runProviderAdd(mode) {
|
|
42
|
+
const config = await loadGlobalConfig(mode);
|
|
43
|
+
const name = await text({
|
|
44
|
+
message: "Provider name:",
|
|
45
|
+
placeholder: "My Local vLLM",
|
|
46
|
+
validate: (value) => {
|
|
47
|
+
if (!value || value.length === 0) return "Name is required";
|
|
48
|
+
if (config.providers.some((p) => p.name === value)) return "Provider with this name already exists";
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
if (isCancel(name)) {
|
|
52
|
+
cancel("Cancelled");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
log.info(`URL examples:
|
|
56
|
+
vLLM/SGLang: http://localhost:8000
|
|
57
|
+
Ollama: http://localhost:11434
|
|
58
|
+
llama.cpp: http://localhost:8080
|
|
59
|
+
OpenAI: https://api.openai.com
|
|
60
|
+
Anthropic: https://api.anthropic.com
|
|
61
|
+
|
|
62
|
+
(Don't include /v1 - it's added automatically)`);
|
|
63
|
+
const url = await text({
|
|
64
|
+
message: "API URL:",
|
|
65
|
+
placeholder: "http://localhost:8000",
|
|
66
|
+
initialValue: "http://localhost:8000",
|
|
67
|
+
validate: (value) => {
|
|
68
|
+
if (!value || value.length === 0) return "URL is required";
|
|
69
|
+
if (!value.startsWith("http")) return "Must start with http:// or https://";
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
if (isCancel(url)) {
|
|
73
|
+
cancel("Cancelled");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const backend = await select({
|
|
77
|
+
message: "Backend type:",
|
|
78
|
+
options: BACKEND_OPTIONS
|
|
79
|
+
});
|
|
80
|
+
if (isCancel(backend)) {
|
|
81
|
+
cancel("Cancelled");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
let selectedModel;
|
|
85
|
+
const s = spinner();
|
|
86
|
+
s.start(`Fetching available models from ${url}...`);
|
|
87
|
+
let availableModels = [];
|
|
88
|
+
let detectedModel = null;
|
|
89
|
+
try {
|
|
90
|
+
let detectedBackend = backend;
|
|
91
|
+
if (backend === "auto") {
|
|
92
|
+
detectedBackend = await detectBackend(url);
|
|
93
|
+
}
|
|
94
|
+
availableModels = await fetchAvailableModelsFromBackend(url);
|
|
95
|
+
s.stop(`Found ${availableModels.length} model(s)`);
|
|
96
|
+
} catch {
|
|
97
|
+
s.stop("\u26A0 Could not fetch models, will use auto-detect");
|
|
98
|
+
}
|
|
99
|
+
if (availableModels.length > 0) {
|
|
100
|
+
const modelChoice = await select({
|
|
101
|
+
message: "Select model:",
|
|
102
|
+
options: availableModels.map((m) => ({
|
|
103
|
+
value: m,
|
|
104
|
+
label: m.split("/").pop() ?? m,
|
|
105
|
+
hint: m
|
|
106
|
+
}))
|
|
107
|
+
});
|
|
108
|
+
if (isCancel(modelChoice)) {
|
|
109
|
+
cancel("Cancelled");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
selectedModel = modelChoice;
|
|
113
|
+
} else {
|
|
114
|
+
const model = await text({
|
|
115
|
+
message: 'Model name (or "auto" to detect):',
|
|
116
|
+
placeholder: "auto",
|
|
117
|
+
initialValue: "auto"
|
|
118
|
+
});
|
|
119
|
+
if (isCancel(model)) {
|
|
120
|
+
cancel("Cancelled");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
selectedModel = model;
|
|
124
|
+
if (selectedModel === "auto") {
|
|
125
|
+
const detectSpinner = spinner();
|
|
126
|
+
detectSpinner.start("Detecting model...");
|
|
127
|
+
try {
|
|
128
|
+
detectedModel = await detectModel(url);
|
|
129
|
+
if (detectedModel) {
|
|
130
|
+
selectedModel = detectedModel;
|
|
131
|
+
detectSpinner.stop(`Detected: ${detectedModel}`);
|
|
132
|
+
} else {
|
|
133
|
+
detectSpinner.stop("Could not detect model, will use auto");
|
|
134
|
+
}
|
|
135
|
+
} catch {
|
|
136
|
+
detectSpinner.stop("Detection failed, will use auto");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
let apiKey;
|
|
141
|
+
if (backend === "openai" || backend === "anthropic") {
|
|
142
|
+
const key = await password({
|
|
143
|
+
message: "API Key:"
|
|
144
|
+
});
|
|
145
|
+
if (isCancel(key)) {
|
|
146
|
+
cancel("Cancelled");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
apiKey = key || void 0;
|
|
150
|
+
} else {
|
|
151
|
+
const needsKey = await select({
|
|
152
|
+
message: "Does this provider require an API key?",
|
|
153
|
+
options: [
|
|
154
|
+
{ value: "no", label: "No" },
|
|
155
|
+
{ value: "yes", label: "Yes" }
|
|
156
|
+
]
|
|
157
|
+
});
|
|
158
|
+
if (isCancel(needsKey)) {
|
|
159
|
+
cancel("Cancelled");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (needsKey === "yes") {
|
|
163
|
+
const key = await password({
|
|
164
|
+
message: "API Key:"
|
|
165
|
+
});
|
|
166
|
+
if (isCancel(key)) {
|
|
167
|
+
cancel("Cancelled");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
apiKey = key || void 0;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const testSpinner = spinner();
|
|
174
|
+
testSpinner.start(`Testing connection to ${url}...`);
|
|
175
|
+
let finalBackend = backend;
|
|
176
|
+
let finalDetectedModel = null;
|
|
177
|
+
try {
|
|
178
|
+
if (backend === "auto") {
|
|
179
|
+
finalBackend = await detectBackend(url);
|
|
180
|
+
}
|
|
181
|
+
if (selectedModel === "auto") {
|
|
182
|
+
finalDetectedModel = await detectModel(url) ?? "auto";
|
|
183
|
+
}
|
|
184
|
+
testSpinner.stop(`\u2713 Connected to ${finalBackend}${finalDetectedModel !== "auto" ? ` (${finalDetectedModel})` : ""}`);
|
|
185
|
+
} catch {
|
|
186
|
+
testSpinner.stop("\u26A0 Could not connect to provider");
|
|
187
|
+
const continueAnyway = await select({
|
|
188
|
+
message: "Provider is not reachable. Save anyway?",
|
|
189
|
+
options: [
|
|
190
|
+
{ value: "yes", label: "Yes, save anyway" },
|
|
191
|
+
{ value: "no", label: "No, cancel" }
|
|
192
|
+
]
|
|
193
|
+
});
|
|
194
|
+
if (isCancel(continueAnyway) || continueAnyway === "no") {
|
|
195
|
+
cancel("Cancelled");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const makeActive = config.providers.length === 0 || await (async () => {
|
|
200
|
+
const choice = await select({
|
|
201
|
+
message: "Make this the active provider?",
|
|
202
|
+
options: [
|
|
203
|
+
{ value: "yes", label: "Yes" },
|
|
204
|
+
{ value: "no", label: "No" }
|
|
205
|
+
]
|
|
206
|
+
});
|
|
207
|
+
if (isCancel(choice)) return false;
|
|
208
|
+
return choice === "yes";
|
|
209
|
+
})();
|
|
210
|
+
let newConfig = addProvider(config, {
|
|
211
|
+
name,
|
|
212
|
+
url,
|
|
213
|
+
backend: finalBackend,
|
|
214
|
+
apiKey,
|
|
215
|
+
maxContext: 2e5,
|
|
216
|
+
isActive: makeActive
|
|
217
|
+
});
|
|
218
|
+
if (makeActive) {
|
|
219
|
+
const { setDefaultModelSelection } = await import("./config-6GJ4SVFH.js");
|
|
220
|
+
newConfig = setDefaultModelSelection(newConfig, newConfig.providers[newConfig.providers.length - 1].id, selectedModel);
|
|
221
|
+
}
|
|
222
|
+
await saveGlobalConfig(mode, newConfig);
|
|
223
|
+
outro(`\u2713 Provider "${name}" added${makeActive ? " and activated" : ""}`);
|
|
224
|
+
}
|
|
225
|
+
async function runProviderList(mode) {
|
|
226
|
+
const config = await loadGlobalConfig(mode);
|
|
227
|
+
if (config.providers.length === 0) {
|
|
228
|
+
log.warn("No providers configured. Run `openfox provider add` to add one.");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
console.log("\nConfigured providers:\n");
|
|
232
|
+
console.log(" NAME URL MODEL BACKEND");
|
|
233
|
+
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
234
|
+
for (const provider of config.providers) {
|
|
235
|
+
const marker = provider.isActive ? "\u25B8" : " ";
|
|
236
|
+
const name = provider.name.padEnd(18);
|
|
237
|
+
const url = provider.url.padEnd(32);
|
|
238
|
+
const model = (provider.model || "auto").padEnd(18);
|
|
239
|
+
const backend = provider.backend;
|
|
240
|
+
console.log(`${marker} ${name} ${url} ${model} ${backend}`);
|
|
241
|
+
}
|
|
242
|
+
console.log("");
|
|
243
|
+
}
|
|
244
|
+
async function runProviderUse(mode) {
|
|
245
|
+
const config = await loadGlobalConfig(mode);
|
|
246
|
+
if (config.providers.length === 0) {
|
|
247
|
+
log.warn("No providers configured. Run `openfox provider add` to add one.");
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (config.providers.length === 1) {
|
|
251
|
+
log.info("Only one provider configured, already active.");
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const activeProvider = getActiveProvider(config);
|
|
255
|
+
const choice = await select({
|
|
256
|
+
message: "Select provider to activate:",
|
|
257
|
+
options: config.providers.map((p) => ({
|
|
258
|
+
value: p.id,
|
|
259
|
+
label: `${p.name}${p.isActive ? " (current)" : ""}`,
|
|
260
|
+
hint: `${p.url} - ${p.model}`
|
|
261
|
+
})),
|
|
262
|
+
initialValue: activeProvider?.id
|
|
263
|
+
});
|
|
264
|
+
if (isCancel(choice)) {
|
|
265
|
+
cancel("Cancelled");
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (choice === activeProvider?.id) {
|
|
269
|
+
log.info("Already the active provider.");
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const newConfig = activateProvider(config, choice);
|
|
273
|
+
await saveGlobalConfig(mode, newConfig);
|
|
274
|
+
const activated = newConfig.providers.find((p) => p.id === choice);
|
|
275
|
+
outro(`\u2713 Now using "${activated?.name}"`);
|
|
276
|
+
}
|
|
277
|
+
async function runProviderRemove(mode) {
|
|
278
|
+
const config = await loadGlobalConfig(mode);
|
|
279
|
+
if (config.providers.length === 0) {
|
|
280
|
+
log.warn("No providers configured.");
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const choice = await select({
|
|
284
|
+
message: "Select provider to remove:",
|
|
285
|
+
options: config.providers.map((p) => ({
|
|
286
|
+
value: p.id,
|
|
287
|
+
label: `${p.name}${p.isActive ? " (active)" : ""}`,
|
|
288
|
+
hint: p.url
|
|
289
|
+
}))
|
|
290
|
+
});
|
|
291
|
+
if (isCancel(choice)) {
|
|
292
|
+
cancel("Cancelled");
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const providerToRemove = config.providers.find((p) => p.id === choice);
|
|
296
|
+
if (providerToRemove?.isActive && config.providers.length > 1) {
|
|
297
|
+
const confirm = await select({
|
|
298
|
+
message: `"${providerToRemove.name}" is the active provider. Remove it anyway?`,
|
|
299
|
+
options: [
|
|
300
|
+
{ value: "yes", label: "Yes, remove and activate next provider" },
|
|
301
|
+
{ value: "no", label: "No, cancel" }
|
|
302
|
+
]
|
|
303
|
+
});
|
|
304
|
+
if (isCancel(confirm) || confirm === "no") {
|
|
305
|
+
cancel("Cancelled");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const newConfig = removeProvider(config, choice);
|
|
310
|
+
await saveGlobalConfig(mode, newConfig);
|
|
311
|
+
outro(`\u2713 Removed "${providerToRemove?.name}"`);
|
|
312
|
+
}
|
|
313
|
+
async function runProviderCommand(mode, subcommand) {
|
|
314
|
+
switch (subcommand) {
|
|
315
|
+
case "add":
|
|
316
|
+
await runProviderAdd(mode);
|
|
317
|
+
break;
|
|
318
|
+
case "list":
|
|
319
|
+
case "ls":
|
|
320
|
+
await runProviderList(mode);
|
|
321
|
+
break;
|
|
322
|
+
case "use":
|
|
323
|
+
case "switch":
|
|
324
|
+
await runProviderUse(mode);
|
|
325
|
+
break;
|
|
326
|
+
case "remove":
|
|
327
|
+
case "rm":
|
|
328
|
+
case "delete":
|
|
329
|
+
await runProviderRemove(mode);
|
|
330
|
+
break;
|
|
331
|
+
default:
|
|
332
|
+
printProviderHelp();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
export {
|
|
336
|
+
printProviderHelp,
|
|
337
|
+
runProviderAdd,
|
|
338
|
+
runProviderCommand,
|
|
339
|
+
runProviderList,
|
|
340
|
+
runProviderRemove,
|
|
341
|
+
runProviderUse
|
|
342
|
+
};
|
|
343
|
+
//# sourceMappingURL=provider-LCE6WZA6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/provider.ts"],"sourcesContent":["import { select, text, password, spinner, log, outro, isCancel, cancel } from '@clack/prompts'\nimport { detectBackend, detectModel } from '../server/llm/index.js'\nimport { fetchAvailableModelsFromBackend } from '../server/provider-manager.js'\nimport {\n loadGlobalConfig,\n saveGlobalConfig,\n addProvider,\n removeProvider,\n activateProvider,\n getActiveProvider,\n} from './config.js'\nimport type { Mode } from './main.js'\nimport type { ProviderBackend } from '../shared/types.js'\n\nconst BACKEND_OPTIONS = [\n { value: 'auto', label: 'Auto-detect' },\n { value: 'vllm', label: 'vLLM' },\n { value: 'sglang', label: 'SGLang' },\n { value: 'ollama', label: 'Ollama' },\n { value: 'llamacpp', label: 'llama.cpp' },\n { value: 'openai', label: 'OpenAI' },\n { value: 'anthropic', label: 'Anthropic' },\n]\n\nexport function printProviderHelp(): void {\n console.log(`\nProvider Management Commands:\n\n openfox provider add Add a new provider\n openfox provider list List all configured providers\n openfox provider use Switch to a different provider\n openfox provider remove Remove a provider\n`)\n}\n\nexport async function runProviderAdd(mode: Mode): Promise<void> {\n const config = await loadGlobalConfig(mode)\n\n // Provider name\n const name = await text({\n message: 'Provider name:',\n placeholder: 'My Local vLLM',\n validate: (value) => {\n if (!value || value.length === 0) return 'Name is required'\n if (config.providers.some(p => p.name === value)) return 'Provider with this name already exists'\n },\n })\n if (isCancel(name)) {\n cancel('Cancelled')\n return\n }\n\n // URL with examples\n log.info(`URL examples:\n vLLM/SGLang: http://localhost:8000\n Ollama: http://localhost:11434\n llama.cpp: http://localhost:8080\n OpenAI: https://api.openai.com\n Anthropic: https://api.anthropic.com\n \n (Don't include /v1 - it's added automatically)`)\n \n const url = await text({\n message: 'API URL:',\n placeholder: 'http://localhost:8000',\n initialValue: 'http://localhost:8000',\n validate: (value) => {\n if (!value || value.length === 0) return 'URL is required'\n if (!value.startsWith('http')) return 'Must start with http:// or https://'\n },\n })\n if (isCancel(url)) {\n cancel('Cancelled')\n return\n }\n\n // Backend\n const backend = await select({\n message: 'Backend type:',\n options: BACKEND_OPTIONS,\n })\n if (isCancel(backend)) {\n cancel('Cancelled')\n return\n }\n\n // Model selection - fetch available models from backend\n let selectedModel: string\n const s = spinner()\n s.start(`Fetching available models from ${url}...`)\n\n let availableModels: string[] = []\n let detectedModel: string | null = null\n\n try {\n // Detect backend first if needed\n let detectedBackend = backend as string\n if (backend === 'auto') {\n detectedBackend = await detectBackend(url as string)\n }\n\n // Fetch available models\n availableModels = await fetchAvailableModelsFromBackend(url as string)\n s.stop(`Found ${availableModels.length} model(s)`)\n } catch {\n s.stop('ā Could not fetch models, will use auto-detect')\n }\n\n if (availableModels.length > 0) {\n // Show dropdown of available models\n const modelChoice = await select({\n message: 'Select model:',\n options: availableModels.map(m => ({\n value: m,\n label: m.split('/').pop() ?? m,\n hint: m,\n })),\n })\n if (isCancel(modelChoice)) {\n cancel('Cancelled')\n return\n }\n selectedModel = modelChoice as string\n } else {\n // Fall back to text input\n const model = await text({\n message: 'Model name (or \"auto\" to detect):',\n placeholder: 'auto',\n initialValue: 'auto',\n })\n if (isCancel(model)) {\n cancel('Cancelled')\n return\n }\n selectedModel = model as string\n\n // Try to detect model if auto\n if (selectedModel === 'auto') {\n const detectSpinner = spinner()\n detectSpinner.start('Detecting model...')\n try {\n detectedModel = await detectModel(url as string)\n if (detectedModel) {\n selectedModel = detectedModel\n detectSpinner.stop(`Detected: ${detectedModel}`)\n } else {\n detectSpinner.stop('Could not detect model, will use auto')\n }\n } catch {\n detectSpinner.stop('Detection failed, will use auto')\n }\n }\n }\n\n // API Key (optional)\n let apiKey: string | undefined\n if (backend === 'openai' || backend === 'anthropic') {\n const key = await password({\n message: 'API Key:',\n })\n if (isCancel(key)) {\n cancel('Cancelled')\n return\n }\n apiKey = key || undefined\n } else {\n const needsKey = await select({\n message: 'Does this provider require an API key?',\n options: [\n { value: 'no', label: 'No' },\n { value: 'yes', label: 'Yes' },\n ],\n })\n if (isCancel(needsKey)) {\n cancel('Cancelled')\n return\n }\n if (needsKey === 'yes') {\n const key = await password({\n message: 'API Key:',\n })\n if (isCancel(key)) {\n cancel('Cancelled')\n return\n }\n apiKey = key || undefined\n }\n }\n\n // Test connection\n const testSpinner = spinner()\n testSpinner.start(`Testing connection to ${url}...`)\n\n let finalBackend = backend as string\n let finalDetectedModel: string | null = null\n\n try {\n if (backend === 'auto') {\n finalBackend = await detectBackend(url as string)\n }\n if (selectedModel === 'auto') {\n finalDetectedModel = await detectModel(url as string) ?? 'auto'\n }\n testSpinner.stop(`ā Connected to ${finalBackend}${finalDetectedModel !== 'auto' ? ` (${finalDetectedModel})` : ''}`)\n } catch {\n testSpinner.stop('ā Could not connect to provider')\n const continueAnyway = await select({\n message: 'Provider is not reachable. Save anyway?',\n options: [\n { value: 'yes', label: 'Yes, save anyway' },\n { value: 'no', label: 'No, cancel' },\n ],\n })\n if (isCancel(continueAnyway) || continueAnyway === 'no') {\n cancel('Cancelled')\n return\n }\n }\n\n // Make active?\n const makeActive = config.providers.length === 0 || await (async () => {\n const choice = await select({\n message: 'Make this the active provider?',\n options: [\n { value: 'yes', label: 'Yes' },\n { value: 'no', label: 'No' },\n ],\n })\n if (isCancel(choice)) return false\n return choice === 'yes'\n })()\n\n let newConfig = addProvider(config, {\n name: name as string,\n url: url as string,\n backend: finalBackend as ProviderBackend,\n apiKey,\n maxContext: 200000,\n isActive: makeActive as boolean,\n })\n\n // If making active, set the default model selection\n if (makeActive) {\n const { setDefaultModelSelection } = await import('./config.js')\n newConfig = setDefaultModelSelection(newConfig, newConfig.providers[newConfig.providers.length - 1]!.id, selectedModel)\n }\n\n await saveGlobalConfig(mode, newConfig)\n outro(`ā Provider \"${name}\" added${makeActive ? ' and activated' : ''}`)\n}\n\nexport async function runProviderList(mode: Mode): Promise<void> {\n const config = await loadGlobalConfig(mode)\n\n if (config.providers.length === 0) {\n log.warn('No providers configured. Run `openfox provider add` to add one.')\n return\n }\n\n console.log('\\nConfigured providers:\\n')\n console.log(' NAME URL MODEL BACKEND')\n console.log(' āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā')\n\n for (const provider of config.providers) {\n const marker = provider.isActive ? 'āø' : ' '\n const name = provider.name.padEnd(18)\n const url = provider.url.padEnd(32)\n const model = (provider.model || 'auto').padEnd(18)\n const backend = provider.backend\n\n console.log(`${marker} ${name} ${url} ${model} ${backend}`)\n }\n\n console.log('')\n}\n\nexport async function runProviderUse(mode: Mode): Promise<void> {\n const config = await loadGlobalConfig(mode)\n\n if (config.providers.length === 0) {\n log.warn('No providers configured. Run `openfox provider add` to add one.')\n return\n }\n\n if (config.providers.length === 1) {\n log.info('Only one provider configured, already active.')\n return\n }\n\n const activeProvider = getActiveProvider(config)\n\n const choice = await select({\n message: 'Select provider to activate:',\n options: config.providers.map(p => ({\n value: p.id,\n label: `${p.name}${p.isActive ? ' (current)' : ''}`,\n hint: `${p.url} - ${p.model}`,\n })),\n initialValue: activeProvider?.id,\n })\n\n if (isCancel(choice)) {\n cancel('Cancelled')\n return\n }\n\n if (choice === activeProvider?.id) {\n log.info('Already the active provider.')\n return\n }\n\n const newConfig = activateProvider(config, choice as string)\n await saveGlobalConfig(mode, newConfig)\n\n const activated = newConfig.providers.find(p => p.id === choice)\n outro(`ā Now using \"${activated?.name}\"`)\n}\n\nexport async function runProviderRemove(mode: Mode): Promise<void> {\n const config = await loadGlobalConfig(mode)\n\n if (config.providers.length === 0) {\n log.warn('No providers configured.')\n return\n }\n\n const choice = await select({\n message: 'Select provider to remove:',\n options: config.providers.map(p => ({\n value: p.id,\n label: `${p.name}${p.isActive ? ' (active)' : ''}`,\n hint: p.url,\n })),\n })\n\n if (isCancel(choice)) {\n cancel('Cancelled')\n return\n }\n\n const providerToRemove = config.providers.find(p => p.id === choice)\n\n if (providerToRemove?.isActive && config.providers.length > 1) {\n const confirm = await select({\n message: `\"${providerToRemove.name}\" is the active provider. Remove it anyway?`,\n options: [\n { value: 'yes', label: 'Yes, remove and activate next provider' },\n { value: 'no', label: 'No, cancel' },\n ],\n })\n if (isCancel(confirm) || confirm === 'no') {\n cancel('Cancelled')\n return\n }\n }\n\n const newConfig = removeProvider(config, choice as string)\n await saveGlobalConfig(mode, newConfig)\n\n outro(`ā Removed \"${providerToRemove?.name}\"`)\n}\n\nexport async function runProviderCommand(mode: Mode, subcommand?: string): Promise<void> {\n switch (subcommand) {\n case 'add':\n await runProviderAdd(mode)\n break\n case 'list':\n case 'ls':\n await runProviderList(mode)\n break\n case 'use':\n case 'switch':\n await runProviderUse(mode)\n break\n case 'remove':\n case 'rm':\n case 'delete':\n await runProviderRemove(mode)\n break\n default:\n printProviderHelp()\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,SAAS,QAAQ,MAAM,UAAU,SAAS,KAAK,OAAO,UAAU,cAAc;AAc9E,IAAM,kBAAkB;AAAA,EACtB,EAAE,OAAO,QAAQ,OAAO,cAAc;AAAA,EACtC,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,EAC/B,EAAE,OAAO,UAAU,OAAO,SAAS;AAAA,EACnC,EAAE,OAAO,UAAU,OAAO,SAAS;AAAA,EACnC,EAAE,OAAO,YAAY,OAAO,YAAY;AAAA,EACxC,EAAE,OAAO,UAAU,OAAO,SAAS;AAAA,EACnC,EAAE,OAAO,aAAa,OAAO,YAAY;AAC3C;AAEO,SAAS,oBAA0B;AACxC,UAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAOb;AACD;AAEA,eAAsB,eAAe,MAA2B;AAC9D,QAAM,SAAS,MAAM,iBAAiB,IAAI;AAG1C,QAAM,OAAO,MAAM,KAAK;AAAA,IACtB,SAAS;AAAA,IACT,aAAa;AAAA,IACb,UAAU,CAAC,UAAU;AACnB,UAAI,CAAC,SAAS,MAAM,WAAW,EAAG,QAAO;AACzC,UAAI,OAAO,UAAU,KAAK,OAAK,EAAE,SAAS,KAAK,EAAG,QAAO;AAAA,IAC3D;AAAA,EACF,CAAC;AACD,MAAI,SAAS,IAAI,GAAG;AAClB,WAAO,WAAW;AAClB;AAAA,EACF;AAGA,MAAI,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iDAOsC;AAE/C,QAAM,MAAM,MAAM,KAAK;AAAA,IACrB,SAAS;AAAA,IACT,aAAa;AAAA,IACb,cAAc;AAAA,IACd,UAAU,CAAC,UAAU;AACnB,UAAI,CAAC,SAAS,MAAM,WAAW,EAAG,QAAO;AACzC,UAAI,CAAC,MAAM,WAAW,MAAM,EAAG,QAAO;AAAA,IACxC;AAAA,EACF,CAAC;AACD,MAAI,SAAS,GAAG,GAAG;AACjB,WAAO,WAAW;AAClB;AAAA,EACF;AAGA,QAAM,UAAU,MAAM,OAAO;AAAA,IAC3B,SAAS;AAAA,IACT,SAAS;AAAA,EACX,CAAC;AACD,MAAI,SAAS,OAAO,GAAG;AACrB,WAAO,WAAW;AAClB;AAAA,EACF;AAGA,MAAI;AACJ,QAAM,IAAI,QAAQ;AAClB,IAAE,MAAM,kCAAkC,GAAG,KAAK;AAElD,MAAI,kBAA4B,CAAC;AACjC,MAAI,gBAA+B;AAEnC,MAAI;AAEF,QAAI,kBAAkB;AACtB,QAAI,YAAY,QAAQ;AACtB,wBAAkB,MAAM,cAAc,GAAa;AAAA,IACrD;AAGA,sBAAkB,MAAM,gCAAgC,GAAa;AACrE,MAAE,KAAK,SAAS,gBAAgB,MAAM,WAAW;AAAA,EACnD,QAAQ;AACN,MAAE,KAAK,qDAAgD;AAAA,EACzD;AAEA,MAAI,gBAAgB,SAAS,GAAG;AAE9B,UAAM,cAAc,MAAM,OAAO;AAAA,MAC/B,SAAS;AAAA,MACT,SAAS,gBAAgB,IAAI,QAAM;AAAA,QACjC,OAAO;AAAA,QACP,OAAO,EAAE,MAAM,GAAG,EAAE,IAAI,KAAK;AAAA,QAC7B,MAAM;AAAA,MACR,EAAE;AAAA,IACJ,CAAC;AACD,QAAI,SAAS,WAAW,GAAG;AACzB,aAAO,WAAW;AAClB;AAAA,IACF;AACA,oBAAgB;AAAA,EAClB,OAAO;AAEL,UAAM,QAAQ,MAAM,KAAK;AAAA,MACvB,SAAS;AAAA,MACT,aAAa;AAAA,MACb,cAAc;AAAA,IAChB,CAAC;AACD,QAAI,SAAS,KAAK,GAAG;AACnB,aAAO,WAAW;AAClB;AAAA,IACF;AACA,oBAAgB;AAGhB,QAAI,kBAAkB,QAAQ;AAC5B,YAAM,gBAAgB,QAAQ;AAC9B,oBAAc,MAAM,oBAAoB;AACxC,UAAI;AACF,wBAAgB,MAAM,YAAY,GAAa;AAC/C,YAAI,eAAe;AACjB,0BAAgB;AAChB,wBAAc,KAAK,aAAa,aAAa,EAAE;AAAA,QACjD,OAAO;AACL,wBAAc,KAAK,uCAAuC;AAAA,QAC5D;AAAA,MACF,QAAQ;AACN,sBAAc,KAAK,iCAAiC;AAAA,MACtD;AAAA,IACF;AAAA,EACF;AAGA,MAAI;AACJ,MAAI,YAAY,YAAY,YAAY,aAAa;AACnD,UAAM,MAAM,MAAM,SAAS;AAAA,MACzB,SAAS;AAAA,IACX,CAAC;AACD,QAAI,SAAS,GAAG,GAAG;AACjB,aAAO,WAAW;AAClB;AAAA,IACF;AACA,aAAS,OAAO;AAAA,EAClB,OAAO;AACL,UAAM,WAAW,MAAM,OAAO;AAAA,MAC5B,SAAS;AAAA,MACT,SAAS;AAAA,QACP,EAAE,OAAO,MAAM,OAAO,KAAK;AAAA,QAC3B,EAAE,OAAO,OAAO,OAAO,MAAM;AAAA,MAC/B;AAAA,IACF,CAAC;AACD,QAAI,SAAS,QAAQ,GAAG;AACtB,aAAO,WAAW;AAClB;AAAA,IACF;AACA,QAAI,aAAa,OAAO;AACtB,YAAM,MAAM,MAAM,SAAS;AAAA,QACzB,SAAS;AAAA,MACX,CAAC;AACD,UAAI,SAAS,GAAG,GAAG;AACjB,eAAO,WAAW;AAClB;AAAA,MACF;AACA,eAAS,OAAO;AAAA,IAClB;AAAA,EACF;AAGA,QAAM,cAAc,QAAQ;AAC5B,cAAY,MAAM,yBAAyB,GAAG,KAAK;AAEnD,MAAI,eAAe;AACnB,MAAI,qBAAoC;AAExC,MAAI;AACF,QAAI,YAAY,QAAQ;AACtB,qBAAe,MAAM,cAAc,GAAa;AAAA,IAClD;AACA,QAAI,kBAAkB,QAAQ;AAC5B,2BAAqB,MAAM,YAAY,GAAa,KAAK;AAAA,IAC3D;AACA,gBAAY,KAAK,uBAAkB,YAAY,GAAG,uBAAuB,SAAS,KAAK,kBAAkB,MAAM,EAAE,EAAE;AAAA,EACrH,QAAQ;AACN,gBAAY,KAAK,sCAAiC;AAClD,UAAM,iBAAiB,MAAM,OAAO;AAAA,MAClC,SAAS;AAAA,MACT,SAAS;AAAA,QACP,EAAE,OAAO,OAAO,OAAO,mBAAmB;AAAA,QAC1C,EAAE,OAAO,MAAM,OAAO,aAAa;AAAA,MACrC;AAAA,IACF,CAAC;AACD,QAAI,SAAS,cAAc,KAAK,mBAAmB,MAAM;AACvD,aAAO,WAAW;AAClB;AAAA,IACF;AAAA,EACF;AAGA,QAAM,aAAa,OAAO,UAAU,WAAW,KAAK,OAAO,YAAY;AACrE,UAAM,SAAS,MAAM,OAAO;AAAA,MAC1B,SAAS;AAAA,MACT,SAAS;AAAA,QACP,EAAE,OAAO,OAAO,OAAO,MAAM;AAAA,QAC7B,EAAE,OAAO,MAAM,OAAO,KAAK;AAAA,MAC7B;AAAA,IACF,CAAC;AACD,QAAI,SAAS,MAAM,EAAG,QAAO;AAC7B,WAAO,WAAW;AAAA,EACpB,GAAG;AAEH,MAAI,YAAY,YAAY,QAAQ;AAAA,IAClC;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA,YAAY;AAAA,IACZ,UAAU;AAAA,EACZ,CAAC;AAGD,MAAI,YAAY;AACd,UAAM,EAAE,yBAAyB,IAAI,MAAM,OAAO,sBAAa;AAC/D,gBAAY,yBAAyB,WAAW,UAAU,UAAU,UAAU,UAAU,SAAS,CAAC,EAAG,IAAI,aAAa;AAAA,EACxH;AAEA,QAAM,iBAAiB,MAAM,SAAS;AACtC,QAAM,oBAAe,IAAI,UAAU,aAAa,mBAAmB,EAAE,EAAE;AACzE;AAEA,eAAsB,gBAAgB,MAA2B;AAC/D,QAAM,SAAS,MAAM,iBAAiB,IAAI;AAE1C,MAAI,OAAO,UAAU,WAAW,GAAG;AACjC,QAAI,KAAK,iEAAiE;AAC1E;AAAA,EACF;AAEA,UAAQ,IAAI,2BAA2B;AACvC,UAAQ,IAAI,mFAAmF;AAC/F,UAAQ,IAAI,oeAAoF;AAEhG,aAAW,YAAY,OAAO,WAAW;AACvC,UAAM,SAAS,SAAS,WAAW,WAAM;AACzC,UAAM,OAAO,SAAS,KAAK,OAAO,EAAE;AACpC,UAAM,MAAM,SAAS,IAAI,OAAO,EAAE;AAClC,UAAM,SAAS,SAAS,SAAS,QAAQ,OAAO,EAAE;AAClD,UAAM,UAAU,SAAS;AAEzB,YAAQ,IAAI,GAAG,MAAM,IAAI,IAAI,IAAI,GAAG,IAAI,KAAK,IAAI,OAAO,EAAE;AAAA,EAC5D;AAEA,UAAQ,IAAI,EAAE;AAChB;AAEA,eAAsB,eAAe,MAA2B;AAC9D,QAAM,SAAS,MAAM,iBAAiB,IAAI;AAE1C,MAAI,OAAO,UAAU,WAAW,GAAG;AACjC,QAAI,KAAK,iEAAiE;AAC1E;AAAA,EACF;AAEA,MAAI,OAAO,UAAU,WAAW,GAAG;AACjC,QAAI,KAAK,+CAA+C;AACxD;AAAA,EACF;AAEA,QAAM,iBAAiB,kBAAkB,MAAM;AAE/C,QAAM,SAAS,MAAM,OAAO;AAAA,IAC1B,SAAS;AAAA,IACT,SAAS,OAAO,UAAU,IAAI,QAAM;AAAA,MAClC,OAAO,EAAE;AAAA,MACT,OAAO,GAAG,EAAE,IAAI,GAAG,EAAE,WAAW,eAAe,EAAE;AAAA,MACjD,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,KAAK;AAAA,IAC7B,EAAE;AAAA,IACF,cAAc,gBAAgB;AAAA,EAChC,CAAC;AAED,MAAI,SAAS,MAAM,GAAG;AACpB,WAAO,WAAW;AAClB;AAAA,EACF;AAEA,MAAI,WAAW,gBAAgB,IAAI;AACjC,QAAI,KAAK,8BAA8B;AACvC;AAAA,EACF;AAEA,QAAM,YAAY,iBAAiB,QAAQ,MAAgB;AAC3D,QAAM,iBAAiB,MAAM,SAAS;AAEtC,QAAM,YAAY,UAAU,UAAU,KAAK,OAAK,EAAE,OAAO,MAAM;AAC/D,QAAM,qBAAgB,WAAW,IAAI,GAAG;AAC1C;AAEA,eAAsB,kBAAkB,MAA2B;AACjE,QAAM,SAAS,MAAM,iBAAiB,IAAI;AAE1C,MAAI,OAAO,UAAU,WAAW,GAAG;AACjC,QAAI,KAAK,0BAA0B;AACnC;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,OAAO;AAAA,IAC1B,SAAS;AAAA,IACT,SAAS,OAAO,UAAU,IAAI,QAAM;AAAA,MAClC,OAAO,EAAE;AAAA,MACT,OAAO,GAAG,EAAE,IAAI,GAAG,EAAE,WAAW,cAAc,EAAE;AAAA,MAChD,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,EACJ,CAAC;AAED,MAAI,SAAS,MAAM,GAAG;AACpB,WAAO,WAAW;AAClB;AAAA,EACF;AAEA,QAAM,mBAAmB,OAAO,UAAU,KAAK,OAAK,EAAE,OAAO,MAAM;AAEnE,MAAI,kBAAkB,YAAY,OAAO,UAAU,SAAS,GAAG;AAC7D,UAAM,UAAU,MAAM,OAAO;AAAA,MAC3B,SAAS,IAAI,iBAAiB,IAAI;AAAA,MAClC,SAAS;AAAA,QACP,EAAE,OAAO,OAAO,OAAO,yCAAyC;AAAA,QAChE,EAAE,OAAO,MAAM,OAAO,aAAa;AAAA,MACrC;AAAA,IACF,CAAC;AACD,QAAI,SAAS,OAAO,KAAK,YAAY,MAAM;AACzC,aAAO,WAAW;AAClB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,YAAY,eAAe,QAAQ,MAAgB;AACzD,QAAM,iBAAiB,MAAM,SAAS;AAEtC,QAAM,mBAAc,kBAAkB,IAAI,GAAG;AAC/C;AAEA,eAAsB,mBAAmB,MAAY,YAAoC;AACvF,UAAQ,YAAY;AAAA,IAClB,KAAK;AACH,YAAM,eAAe,IAAI;AACzB;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,YAAM,gBAAgB,IAAI;AAC1B;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,YAAM,eAAe,IAAI;AACzB;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,YAAM,kBAAkB,IAAI;AAC5B;AAAA,IACF;AACE,wBAAkB;AAAA,EACtB;AACF;","names":[]}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createServer
|
|
3
|
+
} from "./chunk-YQ36AK5A.js";
|
|
4
|
+
import {
|
|
5
|
+
loadConfig
|
|
6
|
+
} from "./chunk-IKAWIOXM.js";
|
|
7
|
+
import "./chunk-MTX5Y3V3.js";
|
|
8
|
+
import "./chunk-N2XYX4ZA.js";
|
|
9
|
+
import "./chunk-4Z2OZ6NX.js";
|
|
10
|
+
import "./chunk-QY7BMXWT.js";
|
|
11
|
+
import "./chunk-IVHCPQO2.js";
|
|
12
|
+
import "./chunk-6MOGZMGX.js";
|
|
13
|
+
import {
|
|
14
|
+
getActiveProvider,
|
|
15
|
+
getDefaultModel,
|
|
16
|
+
loadGlobalConfig
|
|
17
|
+
} from "./chunk-43SWNGYW.js";
|
|
18
|
+
import "./chunk-FF6Z2MC3.js";
|
|
19
|
+
import "./chunk-26NRL3HX.js";
|
|
20
|
+
import {
|
|
21
|
+
logger
|
|
22
|
+
} from "./chunk-U62ZECSQ.js";
|
|
23
|
+
import {
|
|
24
|
+
ensureDataDirExists,
|
|
25
|
+
getDatabasePath,
|
|
26
|
+
getGlobalConfigPath
|
|
27
|
+
} from "./chunk-AF7XS4OI.js";
|
|
28
|
+
|
|
29
|
+
// src/server/utils/network.ts
|
|
30
|
+
import os from "os";
|
|
31
|
+
import { statSync } from "fs";
|
|
32
|
+
function getNetworkInterfaces() {
|
|
33
|
+
const interfaces = os.networkInterfaces();
|
|
34
|
+
const result = [];
|
|
35
|
+
for (const [name, addresses] of Object.entries(interfaces)) {
|
|
36
|
+
if (!addresses) continue;
|
|
37
|
+
for (const addr of addresses) {
|
|
38
|
+
if (addr.family === "IPv4") {
|
|
39
|
+
result.push({
|
|
40
|
+
ip: addr.address,
|
|
41
|
+
family: "IPv4",
|
|
42
|
+
name
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
function getValidIPv4Addresses() {
|
|
50
|
+
const interfaces = getNetworkInterfaces();
|
|
51
|
+
const valid = interfaces.filter((addr) => {
|
|
52
|
+
if (addr.ip.startsWith("127.")) return false;
|
|
53
|
+
if (addr.ip.startsWith("169.254.")) return false;
|
|
54
|
+
return true;
|
|
55
|
+
}).map((addr) => addr.ip);
|
|
56
|
+
const sorted = valid.sort((a, b) => {
|
|
57
|
+
const aInterface = interfaces.find((i) => i.ip === a)?.name || "";
|
|
58
|
+
const bInterface = interfaces.find((i) => i.ip === b)?.name || "";
|
|
59
|
+
if (aInterface.startsWith("eth") && !bInterface.startsWith("eth")) return -1;
|
|
60
|
+
if (!aInterface.startsWith("eth") && bInterface.startsWith("eth")) return 1;
|
|
61
|
+
if (aInterface.startsWith("wlan") && !bInterface.startsWith("wlan")) return -1;
|
|
62
|
+
if (!aInterface.startsWith("wlan") && bInterface.startsWith("wlan")) return 1;
|
|
63
|
+
return 0;
|
|
64
|
+
});
|
|
65
|
+
return sorted;
|
|
66
|
+
}
|
|
67
|
+
function formatFileSize(bytes) {
|
|
68
|
+
if (bytes === 0) return "0KB";
|
|
69
|
+
const KB = 1024;
|
|
70
|
+
const MB = KB * 1024;
|
|
71
|
+
const GB = MB * 1024;
|
|
72
|
+
if (bytes >= GB) {
|
|
73
|
+
return `${(bytes / GB).toFixed(1)}GB`;
|
|
74
|
+
} else if (bytes >= MB) {
|
|
75
|
+
return `${(bytes / MB).toFixed(1)}MB`;
|
|
76
|
+
} else {
|
|
77
|
+
return `${Math.round(bytes / KB)}KB`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function getDatabaseSize(databasePath) {
|
|
81
|
+
try {
|
|
82
|
+
const stats = statSync(databasePath);
|
|
83
|
+
return formatFileSize(stats.size);
|
|
84
|
+
} catch {
|
|
85
|
+
return "0KB";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function displayStartupBanner(config) {
|
|
89
|
+
const { host, port, databasePath, configPath } = config;
|
|
90
|
+
const isLocalhost = host === "127.0.0.1";
|
|
91
|
+
console.log("\n\u{1F98A} OpenFox v0.1.0\n");
|
|
92
|
+
if (isLocalhost) {
|
|
93
|
+
console.log(` \u{1F310} Server: http://localhost:${port}`);
|
|
94
|
+
console.log(" \u{1F512} Access: Localhost only");
|
|
95
|
+
} else {
|
|
96
|
+
const ips = getValidIPv4Addresses();
|
|
97
|
+
if (ips.length === 0) {
|
|
98
|
+
console.log(` \u{1F310} Server: http://0.0.0.0:${port}`);
|
|
99
|
+
console.warn(" \u26A0\uFE0F Warning: No valid network interfaces detected");
|
|
100
|
+
} else {
|
|
101
|
+
console.log(" \u{1F310} Server:");
|
|
102
|
+
for (const ip of ips) {
|
|
103
|
+
console.log(` \u2022 http://${ip}:${port}`);
|
|
104
|
+
}
|
|
105
|
+
console.log(" \u{1F30D} Access: Local network");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const size = getDatabaseSize(databasePath);
|
|
109
|
+
console.log(` \u{1F4BE} Database: ${databasePath} (${size})`);
|
|
110
|
+
console.log(` \u2699\uFE0F Config: ${configPath}`);
|
|
111
|
+
console.log("\n\u{1F4A1} Tip: Press Ctrl+C to stop the server\n");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/cli/serve.ts
|
|
115
|
+
import open from "open";
|
|
116
|
+
async function runServe(options) {
|
|
117
|
+
const { mode, port, openBrowser } = options;
|
|
118
|
+
await ensureDataDirExists(mode);
|
|
119
|
+
const globalConfig = await loadGlobalConfig(mode);
|
|
120
|
+
const activeProvider = getActiveProvider(globalConfig);
|
|
121
|
+
const env = loadConfig();
|
|
122
|
+
const envBackend = env.llm.backend;
|
|
123
|
+
const envModel = env.llm.model;
|
|
124
|
+
const envUrl = env.llm.baseUrl;
|
|
125
|
+
const isEnvBackendExplicit = envBackend !== "auto";
|
|
126
|
+
const isEnvModelExplicit = envModel !== "qwen3.5-122b-int4-autoround";
|
|
127
|
+
const isEnvUrlExplicit = envUrl !== "http://localhost:8000/v1";
|
|
128
|
+
const providerUrl = activeProvider?.url ?? envUrl;
|
|
129
|
+
const defaultModel = getDefaultModel(globalConfig) ?? envModel;
|
|
130
|
+
const providerBackend = activeProvider?.backend ?? envBackend;
|
|
131
|
+
const merged = {
|
|
132
|
+
...env,
|
|
133
|
+
llm: {
|
|
134
|
+
...env.llm,
|
|
135
|
+
baseUrl: isEnvUrlExplicit ? envUrl : providerUrl,
|
|
136
|
+
model: isEnvModelExplicit ? envModel : defaultModel,
|
|
137
|
+
backend: isEnvBackendExplicit ? envBackend : providerBackend
|
|
138
|
+
},
|
|
139
|
+
server: {
|
|
140
|
+
...env.server,
|
|
141
|
+
port: port ?? (mode === "development" ? 10469 : env.server.port),
|
|
142
|
+
host: env.server.host ?? globalConfig.server.host ?? "127.0.0.1",
|
|
143
|
+
openBrowser: openBrowser ?? globalConfig.server.openBrowser
|
|
144
|
+
},
|
|
145
|
+
database: {
|
|
146
|
+
// Use env OPENFOX_DB_PATH if explicitly set (e.g., ":memory:" for tests), otherwise use standard path
|
|
147
|
+
path: env.database.path !== "./openfox.db" ? env.database.path : getDatabasePath(mode)
|
|
148
|
+
},
|
|
149
|
+
logging: {
|
|
150
|
+
level: globalConfig.logging?.level ?? "info"
|
|
151
|
+
},
|
|
152
|
+
mode,
|
|
153
|
+
// Pass providers for the server to use
|
|
154
|
+
providers: globalConfig.providers,
|
|
155
|
+
activeProviderId: globalConfig.activeProviderId,
|
|
156
|
+
activeWorkflowId: globalConfig.activeWorkflowId,
|
|
157
|
+
// Workdir precedence: .env override ā global config ā process.cwd()
|
|
158
|
+
// Normalize: remove trailing slash to prevent double slashes in paths
|
|
159
|
+
workdir: (process.env["OPENFOX_WORKDIR"] ?? globalConfig.workspace?.workdir ?? process.cwd()).replace(/\/$/, "")
|
|
160
|
+
};
|
|
161
|
+
await createServer(merged);
|
|
162
|
+
displayStartupBanner({
|
|
163
|
+
host: merged.server.host,
|
|
164
|
+
port: merged.server.port,
|
|
165
|
+
databasePath: merged.database.path,
|
|
166
|
+
configPath: getGlobalConfigPath(mode)
|
|
167
|
+
});
|
|
168
|
+
if (merged.server.openBrowser) {
|
|
169
|
+
open(`http://${merged.server.host === "127.0.0.1" ? "localhost" : merged.server.host}:${merged.server.port}`).catch(() => {
|
|
170
|
+
logger.warn("Could not open browser automatically");
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export {
|
|
175
|
+
runServe
|
|
176
|
+
};
|
|
177
|
+
//# sourceMappingURL=serve-27DZK34K.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/utils/network.ts","../src/cli/serve.ts"],"sourcesContent":["import os from 'node:os'\nimport { statSync } from 'node:fs'\n\nexport interface NetworkInterface {\n ip: string\n family: 'IPv4' | 'IPv6'\n name: string\n}\n\nexport function getNetworkInterfaces(): NetworkInterface[] {\n const interfaces = os.networkInterfaces()\n const result: NetworkInterface[] = []\n\n for (const [name, addresses] of Object.entries(interfaces)) {\n if (!addresses) continue\n\n for (const addr of addresses) {\n if (addr.family === 'IPv4') {\n result.push({\n ip: addr.address,\n family: 'IPv4',\n name,\n })\n }\n }\n }\n\n return result\n}\n\nexport function getValidIPv4Addresses(): string[] {\n const interfaces = getNetworkInterfaces()\n \n // Filter out loopback and internal addresses\n const valid = interfaces\n .filter(addr => {\n // Exclude loopback\n if (addr.ip.startsWith('127.')) return false\n // Exclude link-local\n if (addr.ip.startsWith('169.254.')) return false\n // Exclude unique local addresses (fc00::/7 for IPv6, but we're only looking at IPv4)\n return true\n })\n .map(addr => addr.ip)\n\n // Sort: prefer eth*, wlan*, then others\n const sorted = valid.sort((a, b) => {\n const aInterface = interfaces.find(i => i.ip === a)?.name || ''\n const bInterface = interfaces.find(i => i.ip === b)?.name || ''\n \n // Prefer eth* interfaces\n if (aInterface.startsWith('eth') && !bInterface.startsWith('eth')) return -1\n if (!aInterface.startsWith('eth') && bInterface.startsWith('eth')) return 1\n \n // Then prefer wlan* interfaces\n if (aInterface.startsWith('wlan') && !bInterface.startsWith('wlan')) return -1\n if (!aInterface.startsWith('wlan') && bInterface.startsWith('wlan')) return 1\n \n return 0\n })\n\n return sorted\n}\n\nexport function formatFileSize(bytes: number): string {\n if (bytes === 0) return '0KB'\n \n const KB = 1024\n const MB = KB * 1024\n const GB = MB * 1024\n \n if (bytes >= GB) {\n return `${(bytes / GB).toFixed(1)}GB`\n } else if (bytes >= MB) {\n return `${(bytes / MB).toFixed(1)}MB`\n } else {\n return `${Math.round(bytes / KB)}KB`\n }\n}\n\nexport function getDatabaseSize(databasePath: string): string {\n try {\n const stats = statSync(databasePath)\n return formatFileSize(stats.size)\n } catch {\n return '0KB'\n }\n}\n\nexport function displayStartupBanner(config: {\n host: string\n port: number\n databasePath: string\n configPath: string\n}): void {\n const { host, port, databasePath, configPath } = config\n const isLocalhost = host === '127.0.0.1'\n \n console.log('\\nš¦ OpenFox v0.1.0\\n')\n \n if (isLocalhost) {\n console.log(` š Server: http://localhost:${port}`)\n console.log(' š Access: Localhost only')\n } else {\n const ips = getValidIPv4Addresses()\n \n if (ips.length === 0) {\n console.log(` š Server: http://0.0.0.0:${port}`)\n console.warn(' ā ļø Warning: No valid network interfaces detected')\n } else {\n console.log(' š Server:')\n for (const ip of ips) {\n console.log(` ⢠http://${ip}:${port}`)\n }\n console.log(' š Access: Local network')\n }\n }\n \n const size = getDatabaseSize(databasePath)\n console.log(` š¾ Database: ${databasePath} (${size})`)\n console.log(` āļø Config: ${configPath}`)\n console.log('\\nš” Tip: Press Ctrl+C to stop the server\\n')\n}\n","import { createServer } from '../server/index.js'\nimport { loadConfig } from '../server/config.js'\nimport { logger } from '../server/utils/logger.js'\nimport { displayStartupBanner } from '../server/utils/network.js'\nimport { loadGlobalConfig, getActiveProvider, getDefaultModel } from './config.js'\nimport { getDatabasePath, getGlobalConfigPath, ensureDataDirExists } from './paths.js'\nimport open from 'open'\nimport type { Mode } from './main.js'\nimport type { LlmBackend } from '../shared/types.js'\n\nexport interface ServeOptions {\n mode: Mode\n port?: number\n openBrowser?: boolean\n}\n\nexport async function runServe(options: ServeOptions): Promise<void> {\n const { mode, port, openBrowser } = options\n \n // Ensure data directory exists before starting server\n await ensureDataDirExists(mode)\n \n const globalConfig = await loadGlobalConfig(mode)\n const activeProvider = getActiveProvider(globalConfig)\n const env = loadConfig()\n \n // Environment variables take precedence over global config file\n // This allows CLI overrides and e2e test configuration to work properly\n const envBackend = env.llm.backend\n const envModel = env.llm.model\n const envUrl = env.llm.baseUrl\n \n // Only use env values if they're not the defaults (meaning they were explicitly set)\n const isEnvBackendExplicit = envBackend !== 'auto'\n const isEnvModelExplicit = envModel !== 'qwen3.5-122b-int4-autoround' // default in config.ts\n const isEnvUrlExplicit = envUrl !== 'http://localhost:8000/v1' // default in config.ts\n \n // Get provider values with fallbacks\n const providerUrl = activeProvider?.url ?? envUrl\n const defaultModel = getDefaultModel(globalConfig) ?? envModel\n const providerBackend = (activeProvider?.backend ?? envBackend) as LlmBackend | 'auto'\n \n const merged = {\n ...env,\n llm: { \n ...env.llm, \n baseUrl: isEnvUrlExplicit ? envUrl : providerUrl,\n model: isEnvModelExplicit ? envModel : defaultModel,\n backend: isEnvBackendExplicit ? envBackend : providerBackend,\n },\n server: { \n ...env.server, \n port: port ?? (mode === 'development' ? 10469 : env.server.port),\n host: env.server.host ?? globalConfig.server.host ?? '127.0.0.1',\n openBrowser: openBrowser ?? globalConfig.server.openBrowser,\n },\n database: {\n // Use env OPENFOX_DB_PATH if explicitly set (e.g., \":memory:\" for tests), otherwise use standard path\n path: env.database.path !== './openfox.db' ? env.database.path : getDatabasePath(mode),\n },\n logging: {\n level: globalConfig.logging?.level ?? 'info' as const,\n },\n mode,\n // Pass providers for the server to use\n providers: globalConfig.providers,\n activeProviderId: globalConfig.activeProviderId,\n activeWorkflowId: globalConfig.activeWorkflowId,\n // Workdir precedence: .env override ā global config ā process.cwd()\n // Normalize: remove trailing slash to prevent double slashes in paths\n workdir: (process.env['OPENFOX_WORKDIR'] ?? globalConfig.workspace?.workdir ?? process.cwd()).replace(/\\/$/, ''),\n }\n \n await createServer(merged)\n \n // Display startup banner\n displayStartupBanner({\n host: merged.server.host,\n port: merged.server.port,\n databasePath: merged.database.path,\n configPath: getGlobalConfigPath(mode),\n })\n \n if (merged.server.openBrowser) {\n open(`http://${merged.server.host === '127.0.0.1' ? 'localhost' : merged.server.host}:${merged.server.port}`).catch(() => {\n logger.warn('Could not open browser automatically')\n })\n }\n}\n"],"mappings":"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","names":[]}
|