@openexpert/opencore 0.2.1 → 0.3.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 +1 -1
- package/bin/opencore.js +1 -1
- package/dist/cli.js +70 -33
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.js +5 -1
- package/dist/config.js.map +1 -1
- package/dist/index.js +12 -5
- package/dist/index.js.map +1 -1
- package/dist/model-provider.d.ts +0 -5
- package/dist/model-provider.js +7 -1
- package/dist/model-provider.js.map +1 -1
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +0 -1
- package/dist/storage.js.map +1 -1
- package/dist/tools.js +0 -2
- package/dist/tools.js.map +1 -1
- package/package.json +6 -6
- package/schema/openexpert.schema.json +10 -0
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ edición cloud.
|
|
|
10
10
|
- `src/config.ts` — lee `openexpert.json` + `OPENEXPERT_*`.
|
|
11
11
|
- `src/storage.ts` — interfaz de guardado local.
|
|
12
12
|
- `src/secrets.ts` — interfaz de tokens de Drive (fichero local, `0600`).
|
|
13
|
-
- `src/tools.ts` — catálogo de las
|
|
13
|
+
- `src/tools.ts` — catálogo de las 12 herramientas y sus dominios.
|
|
14
14
|
- `src/cli/` — el CLI `opencore` (asistente, doctor, fix, models, serve, update).
|
|
15
15
|
|
|
16
16
|
## Uso
|
package/bin/opencore.js
CHANGED
|
@@ -13,7 +13,7 @@ const dist = join(here, "..", "dist", "cli.js");
|
|
|
13
13
|
|
|
14
14
|
if (!existsSync(dist)) {
|
|
15
15
|
console.error(
|
|
16
|
-
"
|
|
16
|
+
"OpenExpert CLI is not built yet.\n" +
|
|
17
17
|
"Run: npm run build --workspace @openexpert/opencore\n" +
|
|
18
18
|
"(or use `npm run opencore:doctor` from the repository root).",
|
|
19
19
|
);
|
package/dist/cli.js
CHANGED
|
@@ -9,8 +9,14 @@ import * as p from '@clack/prompts';
|
|
|
9
9
|
// src/cli/commands.ts
|
|
10
10
|
|
|
11
11
|
// src/model-provider.ts
|
|
12
|
+
function stripTrailingSlashes(value) {
|
|
13
|
+
let end = value.length;
|
|
14
|
+
while (end > 0 && value[end - 1] === "/") end -= 1;
|
|
15
|
+
return value.slice(0, end);
|
|
16
|
+
}
|
|
12
17
|
function ollamaApiBaseUrl(baseUrl) {
|
|
13
|
-
|
|
18
|
+
const base = stripTrailingSlashes(baseUrl.trim());
|
|
19
|
+
return base.endsWith("/v1") ? base.slice(0, -3) : base;
|
|
14
20
|
}
|
|
15
21
|
var DEFAULT_OLLAMA_URL = "http://localhost:11434";
|
|
16
22
|
var DEFAULT_DATA_DIR = "~/.openexpert";
|
|
@@ -87,6 +93,9 @@ function secretsAsEnv(cwd = process.cwd()) {
|
|
|
87
93
|
for (const [k, v] of Object.entries(s)) if (v) out[k] = v;
|
|
88
94
|
return out;
|
|
89
95
|
}
|
|
96
|
+
function localServerEnv(env = process.env) {
|
|
97
|
+
return { HOST: env["OPENEXPERT_HOST"] || env["HOST"] || "127.0.0.1" };
|
|
98
|
+
}
|
|
90
99
|
async function httpGetJson(url, opts = {}) {
|
|
91
100
|
try {
|
|
92
101
|
const res = await fetch(url, {
|
|
@@ -164,24 +173,24 @@ function run(cmd, args, opts = {}) {
|
|
|
164
173
|
|
|
165
174
|
// src/cli/commands.ts
|
|
166
175
|
function cmdVersion() {
|
|
167
|
-
console.log(
|
|
176
|
+
console.log(`OpenExpert ${packageVersion()}`);
|
|
168
177
|
return 0;
|
|
169
178
|
}
|
|
170
|
-
function configuredProvider() {
|
|
171
|
-
const raw = process.env["OPENEXPERT_MODEL_PROVIDER"] || readConfig().modelProvider;
|
|
179
|
+
function configuredProvider(cwd = process.cwd()) {
|
|
180
|
+
const raw = process.env["OPENEXPERT_MODEL_PROVIDER"] || readConfig(cwd).modelProvider;
|
|
172
181
|
if (raw === "ollama" || raw === "openai-compatible") return raw;
|
|
173
182
|
return "google";
|
|
174
183
|
}
|
|
175
184
|
function cmdDoctor(cwd = process.cwd()) {
|
|
176
|
-
const provider = configuredProvider();
|
|
185
|
+
const provider = configuredProvider(cwd);
|
|
177
186
|
const dataDir = resolvedDataDir(cwd);
|
|
178
187
|
const secrets = readSecrets(cwd);
|
|
179
|
-
console.log("
|
|
188
|
+
console.log("OpenExpert doctor");
|
|
180
189
|
console.log(` provider: ${provider}`);
|
|
181
190
|
console.log(` data dir: ${dataDir}`);
|
|
182
191
|
const problems = [];
|
|
183
192
|
if (provider === "google" && !process.env["GOOGLE_API_KEY"] && !secrets.GOOGLE_API_KEY) {
|
|
184
|
-
problems.push("Falta GOOGLE_API_KEY. Ejecuta `
|
|
193
|
+
problems.push("Falta GOOGLE_API_KEY. Ejecuta `openexpert init` o usa Ollama.");
|
|
185
194
|
}
|
|
186
195
|
if (provider === "openai-compatible") {
|
|
187
196
|
if (!process.env["OPENEXPERT_MODEL_KEY"] && !secrets.OPENEXPERT_MODEL_KEY) {
|
|
@@ -236,11 +245,11 @@ async function cmdFix(cwd = process.cwd()) {
|
|
|
236
245
|
}
|
|
237
246
|
console.log("\u2713 directorio de datos listo:", dataDir);
|
|
238
247
|
cmdDoctor(cwd);
|
|
239
|
-
console.log("\nSiguiente paso: `
|
|
248
|
+
console.log("\nSiguiente paso: `openexpert serve` (o `openexpert init` para elegir modelo).");
|
|
240
249
|
return 0;
|
|
241
250
|
}
|
|
242
251
|
async function cmdModels(cwd = process.cwd()) {
|
|
243
|
-
const provider = configuredProvider();
|
|
252
|
+
const provider = configuredProvider(cwd);
|
|
244
253
|
const secrets = readSecrets(cwd);
|
|
245
254
|
const cfg = readConfig(cwd);
|
|
246
255
|
if (provider === "ollama") {
|
|
@@ -262,7 +271,7 @@ async function cmdModels(cwd = process.cwd()) {
|
|
|
262
271
|
const base = process.env["OPENEXPERT_BASE_URL"] || secrets.OPENEXPERT_BASE_URL;
|
|
263
272
|
const key = process.env["OPENEXPERT_MODEL_KEY"] || secrets.OPENEXPERT_MODEL_KEY;
|
|
264
273
|
if (!base || !key) {
|
|
265
|
-
console.log("Configura OPENEXPERT_BASE_URL y OPENEXPERT_MODEL_KEY (
|
|
274
|
+
console.log("Configura OPENEXPERT_BASE_URL y OPENEXPERT_MODEL_KEY (openexpert init).");
|
|
266
275
|
return 1;
|
|
267
276
|
}
|
|
268
277
|
const data = await httpGetJson(
|
|
@@ -295,10 +304,18 @@ async function cmdServe(cwd = process.cwd()) {
|
|
|
295
304
|
);
|
|
296
305
|
return 1;
|
|
297
306
|
}
|
|
298
|
-
|
|
307
|
+
const serverEnv = {
|
|
308
|
+
...env,
|
|
309
|
+
...localServerEnv(),
|
|
310
|
+
PORT: process.env["PORT"] || "3000"
|
|
311
|
+
};
|
|
312
|
+
const customEndpoint = serverEnv["HOST"] !== "127.0.0.1" || serverEnv["PORT"] !== "3000";
|
|
313
|
+
console.log(
|
|
314
|
+
"Arrancando OpenExpert en http://127.0.0.1:3000 \u2026" + (customEndpoint ? " (host o puerto personalizados: mira OPENEXPERT_HOST y PORT)" : "")
|
|
315
|
+
);
|
|
299
316
|
return run(process.execPath, [join(app, ".output", "server", "index.mjs")], {
|
|
300
317
|
cwd: app,
|
|
301
|
-
env:
|
|
318
|
+
env: serverEnv
|
|
302
319
|
});
|
|
303
320
|
}
|
|
304
321
|
function cmdUpdate() {
|
|
@@ -317,18 +334,24 @@ function port() {
|
|
|
317
334
|
function appUrl() {
|
|
318
335
|
return `http://localhost:${port()}`;
|
|
319
336
|
}
|
|
320
|
-
|
|
337
|
+
function looksLikeOpenExpert(status, headers) {
|
|
338
|
+
return status < 500 && headers.has("x-request-id") && headers.has("x-frame-options");
|
|
339
|
+
}
|
|
340
|
+
async function probeServer(timeoutMs = 1500) {
|
|
321
341
|
try {
|
|
322
|
-
const res = await fetch(appUrl(), {
|
|
323
|
-
|
|
342
|
+
const res = await fetch(appUrl(), {
|
|
343
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
344
|
+
redirect: "manual"
|
|
345
|
+
});
|
|
346
|
+
return looksLikeOpenExpert(res.status, res.headers) ? "openexpert" : "other";
|
|
324
347
|
} catch {
|
|
325
|
-
return
|
|
348
|
+
return "none";
|
|
326
349
|
}
|
|
327
350
|
}
|
|
328
351
|
async function waitForServer(timeoutMs = 12e4) {
|
|
329
352
|
const start = Date.now();
|
|
330
353
|
while (Date.now() - start < timeoutMs) {
|
|
331
|
-
if (await
|
|
354
|
+
if (await probeServer() === "openexpert") return true;
|
|
332
355
|
await new Promise((r) => setTimeout(r, 400));
|
|
333
356
|
}
|
|
334
357
|
return false;
|
|
@@ -369,20 +392,24 @@ async function spawnServer(cwd, env) {
|
|
|
369
392
|
if (existsSync(built) && buildIsFresh(repo)) {
|
|
370
393
|
return spawn(process.execPath, [built], {
|
|
371
394
|
cwd: repo,
|
|
372
|
-
env: { ...env, NODE_ENV: "production", PORT: port() },
|
|
395
|
+
env: { ...env, ...localServerEnv(), NODE_ENV: "production", PORT: port() },
|
|
373
396
|
stdio: "inherit"
|
|
374
397
|
});
|
|
375
398
|
}
|
|
376
399
|
console.log(
|
|
377
400
|
existsSync(built) ? "La build est\xE1 desactualizada; arrancando en modo desarrollo\u2026" : "Arrancando el servidor en modo desarrollo (no hay build)\u2026"
|
|
378
401
|
);
|
|
379
|
-
return spawn("npm", ["run", "dev"], {
|
|
402
|
+
return spawn("npm", ["run", "dev"], {
|
|
403
|
+
cwd: repo,
|
|
404
|
+
env: { ...env, ...localServerEnv() },
|
|
405
|
+
stdio: "inherit"
|
|
406
|
+
});
|
|
380
407
|
}
|
|
381
408
|
const app = await ensureStandaloneApp(packageVersion(), { log: (m) => console.log(` ${m}`) });
|
|
382
409
|
if (!app) return null;
|
|
383
410
|
return spawn(process.execPath, [join(app, ".output", "server", "index.mjs")], {
|
|
384
411
|
cwd: app,
|
|
385
|
-
env: { ...env, NODE_ENV: "production", PORT: port() },
|
|
412
|
+
env: { ...env, ...localServerEnv(), NODE_ENV: "production", PORT: port() },
|
|
386
413
|
stdio: "inherit"
|
|
387
414
|
});
|
|
388
415
|
}
|
|
@@ -410,8 +437,18 @@ async function cmdDesktop(cwd = process.cwd()) {
|
|
|
410
437
|
}
|
|
411
438
|
let server = null;
|
|
412
439
|
let startedServer = false;
|
|
413
|
-
|
|
440
|
+
const probe = await probeServer();
|
|
441
|
+
if (probe === "openexpert") {
|
|
414
442
|
console.log(`\u2022 Usando el servidor ya activo en ${appUrl()}`);
|
|
443
|
+
} else if (probe === "other") {
|
|
444
|
+
console.error(
|
|
445
|
+
`El puerto ${port()} est\xE1 ocupado por otra aplicaci\xF3n, no por OpenExpert.
|
|
446
|
+
La ventana apunta siempre a ${appUrl()}, as\xED que mostrar\xEDa esa otra aplicaci\xF3n.
|
|
447
|
+
Mira qui\xE9n lo ocupa, lib\xE9ralo (o cambia el puerto de ese programa) y vuelve a intentarlo:
|
|
448
|
+
ss -ltnp | grep :${port()} # o: lsof -i :${port()}
|
|
449
|
+
`
|
|
450
|
+
);
|
|
451
|
+
return 1;
|
|
415
452
|
} else {
|
|
416
453
|
console.log("Arrancando el servidor local\u2026");
|
|
417
454
|
server = await spawnServer(cwd, env);
|
|
@@ -469,7 +506,7 @@ async function runWizard(opts = {}) {
|
|
|
469
506
|
writeConfig(config2, cwd);
|
|
470
507
|
return { config: config2, secrets: {}, provider: "ollama", modelId: config2.modelId, started: false };
|
|
471
508
|
}
|
|
472
|
-
p.intro("
|
|
509
|
+
p.intro("OpenExpert \u2014 configuraci\xF3n");
|
|
473
510
|
const spin = p.spinner();
|
|
474
511
|
spin.start("Buscando Ollama local\u2026");
|
|
475
512
|
const ollama = await detectOllama();
|
|
@@ -494,7 +531,7 @@ async function runWizard(opts = {}) {
|
|
|
494
531
|
if (p.isCancel(choice)) bail();
|
|
495
532
|
if (choice === "install-ollama") {
|
|
496
533
|
p.note(
|
|
497
|
-
"Instala Ollama y descarga un modelo:\n\n curl -fsSL https://ollama.com/install.sh | sh\n ollama pull llama3.1\n\nVuelve a ejecutar `
|
|
534
|
+
"Instala Ollama y descarga un modelo:\n\n curl -fsSL https://ollama.com/install.sh | sh\n ollama pull llama3.1\n\nVuelve a ejecutar `openexpert init` cuando est\xE9 listo.",
|
|
498
535
|
"Instalar Ollama"
|
|
499
536
|
);
|
|
500
537
|
p.outro("Sin cambios.");
|
|
@@ -554,18 +591,18 @@ async function runWizard(opts = {}) {
|
|
|
554
591
|
|
|
555
592
|
// src/cli.ts
|
|
556
593
|
function help() {
|
|
557
|
-
console.log(`
|
|
594
|
+
console.log(`OpenExpert \u2014 motor local
|
|
558
595
|
|
|
559
596
|
Uso:
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
597
|
+
openexpert asistente de configuraci\xF3n y arranque
|
|
598
|
+
openexpert init igual que sin argumentos
|
|
599
|
+
openexpert serve arranca la edici\xF3n local en http://localhost:3000
|
|
600
|
+
openexpert desktop arranca el servidor y abre la ventana de escritorio
|
|
601
|
+
openexpert doctor revisa tu configuraci\xF3n
|
|
602
|
+
openexpert fix auto-configura lo que falte
|
|
603
|
+
openexpert models lista los modelos del proveedor actual
|
|
604
|
+
openexpert update limpia la cach\xE9 del servidor descargado
|
|
605
|
+
openexpert version muestra la versi\xF3n
|
|
569
606
|
|
|
570
607
|
Ejemplos:
|
|
571
608
|
npx @openexpert/opencore
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/model-provider.ts","../src/cli/lib.ts","../src/cli/commands.ts","../src/cli/desktop.ts","../src/cli/wizard.ts","../src/cli.ts"],"names":["p","mkdirSync","existsSync","join","writeFileSync","spawn","homedir","resolve","ollama","config","run"],"mappings":";;;;;;;;;;;AAgDO,SAAS,iBAAiB,OAAA,EAAyB;AACxD,EAAA,OAAO,OAAA,CAAQ,MAAK,CAAE,OAAA,CAAQ,QAAQ,EAAE,CAAA,CAAE,OAAA,CAAQ,OAAA,EAAS,EAAE,CAAA;AAC/D;ACrBO,IAAM,kBAAA,GAAqB,wBAAA;AAC3B,IAAM,gBAAA,GAAmB,eAAA;AAEzB,SAAS,WAAWA,EAAAA,EAAmB;AAC5C,EAAA,IAAIA,EAAAA,KAAM,GAAA,EAAK,OAAO,OAAA,EAAQ;AAC9B,EAAA,IAAIA,EAAAA,CAAE,UAAA,CAAW,IAAI,CAAA,EAAG,OAAO,IAAA,CAAK,OAAA,EAAQ,EAAGA,EAAAA,CAAE,KAAA,CAAM,CAAC,CAAC,CAAA;AACzD,EAAA,OAAOA,EAAAA;AACT;AAEO,SAAS,WAAA,GAAsB;AAEpC,EAAA,OAAO,QAAQ,OAAA,CAAQ,aAAA,CAAc,YAAY,GAAG,CAAC,GAAG,IAAI,CAAA;AAC9D;AAEO,SAAS,cAAA,GAAyB;AACvC,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,YAAA,CAAa,IAAA,CAAK,aAAY,EAAG,cAAc,CAAA,EAAG,MAAM,CAAC,CAAA;AAGhF,IAAA,OAAO,IAAI,OAAA,IAAW,OAAA;AAAA,EACxB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,OAAA;AAAA,EACT;AACF;AAGO,SAAS,YAAA,CAAa,KAAA,GAAQ,OAAA,CAAQ,GAAA,EAAI,EAAkB;AACjE,EAAA,IAAI,GAAA,GAAM,QAAQ,KAAK,CAAA;AACvB,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA,EAAA,EAAK;AAC1B,IAAA,IACE,WAAW,IAAA,CAAK,GAAA,EAAK,cAAc,CAAC,CAAA,IACpC,WAAW,IAAA,CAAK,GAAA,EAAK,UAAA,EAAY,UAAU,CAAC,CAAA,IAC5C,UAAA,CAAW,KAAK,GAAA,EAAK,gBAAgB,CAAC,CAAA,EACtC;AACA,MAAA,OAAO,GAAA;AAAA,IACT;AACA,IAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,GAAA,EAAK,IAAI,CAAA;AAChC,IAAA,IAAI,WAAW,GAAA,EAAK;AACpB,IAAA,GAAA,GAAM,MAAA;AAAA,EACR;AACA,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,UAAA,CAAW,GAAA,GAAM,OAAA,CAAQ,GAAA,EAAI,EAAW;AACtD,EAAA,OAAO,IAAA,CAAK,KAAK,iBAAiB,CAAA;AACpC;AAEO,SAAS,UAAA,CAAW,GAAA,GAAM,OAAA,CAAQ,GAAA,EAAI,EAAe;AAC1D,EAAA,IAAI;AACF,IAAA,MAAMA,EAAAA,GAAI,WAAW,GAAG,CAAA;AACxB,IAAA,IAAI,CAAC,UAAA,CAAWA,EAAC,CAAA,SAAU,EAAC;AAC5B,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,YAAA,CAAaA,EAAAA,EAAG,MAAM,CAAC,CAAA;AAAA,EAC3C,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAC;AAAA,EACV;AACF;AAEO,SAAS,WAAA,CAAY,GAAA,EAAiB,GAAA,GAAM,OAAA,CAAQ,KAAI,EAAS;AACtE,EAAA,aAAA,CAAc,UAAA,CAAW,GAAG,CAAA,EAAG,IAAA,CAAK,UAAU,GAAA,EAAK,IAAA,EAAM,CAAC,CAAA,GAAI,IAAI,CAAA;AACpE;AAEO,SAAS,eAAA,CAAgB,GAAA,GAAM,OAAA,CAAQ,GAAA,EAAI,EAAW;AAC3D,EAAA,MAAM,GAAA,GAAM,WAAW,GAAG,CAAA;AAC1B,EAAA,MAAM,MAAM,OAAA,CAAQ,GAAA,CAAI,qBAAqB,CAAA,IAAK,IAAI,OAAA,IAAW,gBAAA;AACjE,EAAA,OAAO,WAAW,GAAG,CAAA;AACvB;AAEO,SAAS,WAAA,CAAY,GAAA,GAAM,OAAA,CAAQ,GAAA,EAAI,EAAW;AACvD,EAAA,OAAO,IAAA,CAAK,eAAA,CAAgB,GAAG,CAAA,EAAG,cAAc,CAAA;AAClD;AAEO,SAAS,WAAA,CAAY,GAAA,GAAM,OAAA,CAAQ,GAAA,EAAI,EAAY;AACxD,EAAA,IAAI;AACF,IAAA,MAAMA,EAAAA,GAAI,YAAY,GAAG,CAAA;AACzB,IAAA,IAAI,CAAC,UAAA,CAAWA,EAAC,CAAA,SAAU,EAAC;AAC5B,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,YAAA,CAAaA,EAAAA,EAAG,MAAM,CAAC,CAAA;AAAA,EAC3C,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAC;AAAA,EACV;AACF;AAEO,SAAS,YAAA,CAAa,OAAA,EAAkB,GAAA,GAAM,OAAA,CAAQ,KAAI,EAAW;AAC1E,EAAA,MAAM,GAAA,GAAM,gBAAgB,GAAG,CAAA;AAC/B,EAAA,SAAA,CAAU,GAAA,EAAK,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AAClC,EAAA,MAAMA,EAAAA,GAAI,IAAA,CAAK,GAAA,EAAK,cAAc,CAAA;AAClC,EAAA,aAAA,CAAcA,EAAAA,EAAG,IAAA,CAAK,SAAA,CAAU,OAAA,EAAS,IAAA,EAAM,CAAC,CAAA,EAAG,EAAE,IAAA,EAAM,GAAA,EAAO,CAAA;AAClE,EAAA,OAAOA,EAAAA;AACT;AAGO,SAAS,YAAA,CAAa,GAAA,GAAM,OAAA,CAAQ,GAAA,EAAI,EAA2B;AACxE,EAAA,MAAM,CAAA,GAAI,YAAY,GAAG,CAAA;AACzB,EAAA,MAAM,MAA8B,EAAC;AACrC,EAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,CAAA,IAAK,MAAA,CAAO,OAAA,CAAQ,CAAC,CAAA,EAAG,IAAI,CAAA,EAAG,GAAA,CAAI,CAAC,CAAA,GAAI,CAAA;AACxD,EAAA,OAAO,GAAA;AACT;AAEA,eAAsB,WAAA,CACpB,GAAA,EACA,IAAA,GAAiE,EAAC,EAC/C;AACnB,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,MAC3B,GAAI,KAAK,OAAA,GAAU,EAAE,SAAS,IAAA,CAAK,OAAA,KAAY,EAAC;AAAA,MAChD,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,IAAA,CAAK,aAAa,IAAI;AAAA,KACnD,CAAA;AACD,IAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,OAAO,IAAA;AACpB,IAAA,OAAQ,MAAM,IAAI,IAAA,EAAK;AAAA,EACzB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEA,eAAsB,YAAA,CACpB,OAAA,GAAU,kBAAA,EACV,SAAA,GAAY,IAAA,EACgC;AAC5C,EAAA,MAAM,IAAA,GAAO,iBAAiB,OAAO,CAAA;AACrC,EAAA,MAAM,IAAA,GAAO,MAAM,WAAA,CAA6C,CAAA,EAAG,IAAI,CAAA,SAAA,CAAA,EAAa;AAAA,IAClF;AAAA,GACD,CAAA;AACD,EAAA,IAAI,CAAC,MAAM,OAAO,EAAE,IAAI,KAAA,EAAO,MAAA,EAAQ,EAAC,EAAE;AAC1C,EAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,MAAA,EAAA,CAAS,IAAA,CAAK,MAAA,IAAU,EAAC,EAAG,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAA,EAAE;AACpE;AAEO,SAAS,YAAY,OAAA,EAAyB;AACnD,EAAA,OAAO,IAAA,CAAK,UAAA,CAAW,gBAAgB,CAAA,EAAG,OAAO,OAAO,CAAA;AAC1D;AAOA,eAAsB,mBAAA,CACpB,OAAA,EACA,IAAA,GAAqD,EAAC,EAC9B;AACxB,EAAA,MAAM,GAAA,GAAM,YAAY,OAAO,CAAA;AAC/B,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,EAAK,SAAA,EAAW,UAAU,WAAW,CAAA;AACxD,EAAA,IAAI,UAAA,CAAW,KAAK,CAAA,EAAG,OAAO,GAAA;AAE9B,EAAA,MAAM,IAAA,GAAO,KAAK,IAAA,IAAQ,0BAAA;AAC1B,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,KAAQ,MAAM;AAAA,EAAC,CAAA,CAAA;AAChC,EAAA,MAAM,OAAA,GAAU,qBAAqB,OAAO,CAAA,OAAA,CAAA;AAE5C,EAAA,MAAM,IAAA,GAAO,CAAC,CAAA,CAAA,EAAI,OAAO,IAAI,CAAA,qBAAA,EAAwB,OAAO,IAAI,OAAO,CAAA;AAEvE,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,MAAM,IAAA,GAAO,CAAA,mBAAA,EAAsB,IAAI,CAAA,mBAAA,EAAsB,GAAG,CAAA,CAAA;AAChE,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA;AAC9B,IAAA,GAAA,CAAI,CAAA,YAAA,EAAe,GAAG,CAAA,CAAE,CAAA;AACxB,IAAA,IAAI;AACF,MAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK,EAAE,QAAQ,WAAA,CAAY,OAAA,CAAQ,GAAM,CAAA,EAAG,CAAA;AACpE,MAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACb,MAAA,MAAM,MAAM,MAAA,CAAO,IAAA,CAAK,MAAM,GAAA,CAAI,aAAa,CAAA;AAG/C,MAAA,IAAI;AACF,QAAA,MAAM,SAAS,MAAM,KAAA,CAAM,GAAG,IAAI,CAAA,CAAA,EAAI,OAAO,CAAA,OAAA,CAAA,EAAW;AAAA,UACtD,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,IAAM;AAAA,SACnC,CAAA;AACD,QAAA,IAAI,OAAO,EAAA,EAAI;AACb,UAAA,MAAM,QAAA,GAAA,CAAY,MAAM,MAAA,CAAO,IAAA,EAAK,EAAG,MAAK,CAAE,KAAA,CAAM,KAAK,CAAA,CAAE,CAAC,CAAA;AAC5D,UAAA,MAAM,MAAA,GAAS,WAAW,QAAQ,CAAA,CAAE,OAAO,GAAG,CAAA,CAAE,OAAO,KAAK,CAAA;AAC5D,UAAA,IAAI,QAAA,IAAY,aAAa,MAAA,EAAQ;AACnC,YAAA,GAAA,CAAI,6BAA6B,CAAA;AACjC,YAAA,OAAO,IAAA;AAAA,UACT;AAAA,QACF;AAAA,MACF,CAAA,CAAA,MAAQ;AAAA,MAER;AAEA,MAAA,SAAA,CAAU,GAAA,EAAK,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AAClC,MAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,EAAK,OAAO,CAAA;AAC7B,MAAA,aAAA,CAAc,KAAK,GAAG,CAAA;AACtB,MAAA,MAAM,CAAA,GAAI,SAAA,CAAU,KAAA,EAAO,CAAC,MAAA,EAAQ,GAAA,EAAK,IAAA,EAAM,GAAG,CAAA,EAAG,EAAE,KAAA,EAAO,QAAA,EAAU,CAAA;AACxE,MAAA,IAAI,CAAA,CAAE,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAC3B,MAAA,OAAO,UAAA,CAAW,KAAK,CAAA,GAAI,GAAA,GAAM,IAAA;AAAA,IACnC,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AACA,EAAA,OAAO,IAAA;AACT;AAEO,SAAS,GAAA,CACd,GAAA,EACA,IAAA,EACA,IAAA,GAAkD,EAAC,EAClC;AACjB,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,GAAA,KAAQ;AAC1B,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,EAAK,IAAA,EAAM,EAAE,KAAA,EAAO,SAAA,EAAW,GAAA,EAAK,IAAA,CAAK,GAAA,EAAK,GAAA,EAAK,IAAA,CAAK,KAAK,CAAA;AACjF,IAAA,MAAM,UAAU,CAAC,GAAA,KAAwB,MAAM,KAAA,CAAM,KAAK,GAAG,CAAA;AAC7D,IAAA,OAAA,CAAQ,EAAA,CAAG,QAAA,EAAU,OAAA,CAAQ,QAAQ,CAAC,CAAA;AACtC,IAAA,OAAA,CAAQ,EAAA,CAAG,SAAA,EAAW,OAAA,CAAQ,SAAS,CAAC,CAAA;AACxC,IAAA,KAAA,CAAM,GAAG,MAAA,EAAQ,CAAC,SAAS,GAAA,CAAI,IAAA,IAAQ,CAAC,CAAC,CAAA;AAAA,EAC3C,CAAC,CAAA;AACH;;;AC7MO,SAAS,UAAA,GAAqB;AACnC,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,qBAAA,EAAwB,cAAA,EAAgB,CAAA,CAAE,CAAA;AACtD,EAAA,OAAO,CAAA;AACT;AAEA,SAAS,kBAAA,GAA+B;AACtC,EAAA,MAAM,MAAM,OAAA,CAAQ,GAAA,CAAI,2BAA2B,CAAA,IAAK,YAAW,CAAE,aAAA;AACrE,EAAA,IAAI,GAAA,KAAQ,QAAA,IAAY,GAAA,KAAQ,mBAAA,EAAqB,OAAO,GAAA;AAC5D,EAAA,OAAO,QAAA;AACT;AAEO,SAAS,SAAA,CAAU,GAAA,GAAM,OAAA,CAAQ,GAAA,EAAI,EAAW;AACrD,EAAA,MAAM,WAAW,kBAAA,EAAmB;AACpC,EAAA,MAAM,OAAA,GAAU,gBAAgB,GAAG,CAAA;AACnC,EAAA,MAAM,OAAA,GAAU,YAAY,GAAG,CAAA;AAE/B,EAAA,OAAA,CAAQ,IAAI,iBAAiB,CAAA;AAC7B,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,YAAA,EAAe,QAAQ,CAAA,CAAE,CAAA;AACrC,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,YAAA,EAAe,OAAO,CAAA,CAAE,CAAA;AAEpC,EAAA,MAAM,WAAqB,EAAC;AAE5B,EAAA,IAAI,QAAA,KAAa,YAAY,CAAC,OAAA,CAAQ,IAAI,gBAAgB,CAAA,IAAK,CAAC,OAAA,CAAQ,cAAA,EAAgB;AACtF,IAAA,QAAA,CAAS,KAAK,6DAA6D,CAAA;AAAA,EAC7E;AACA,EAAA,IAAI,aAAa,mBAAA,EAAqB;AACpC,IAAA,IAAI,CAAC,OAAA,CAAQ,GAAA,CAAI,sBAAsB,CAAA,IAAK,CAAC,QAAQ,oBAAA,EAAsB;AACzE,MAAA,QAAA,CAAS,KAAK,6BAA6B,CAAA;AAAA,IAC7C;AACA,IAAA,IAAI,CAAC,OAAA,CAAQ,GAAA,CAAI,qBAAqB,CAAA,IAAK,CAAC,QAAQ,mBAAA,EAAqB;AACvE,MAAA,QAAA,CAAS,KAAK,4BAA4B,CAAA;AAAA,IAC5C;AAAA,EACF;AACA,EAAA,IAAI,aAAa,QAAA,EAAU;AACzB,IAAA,OAAA,CAAQ,IAAI,CAAA,YAAA,EAAe,OAAA,CAAQ,IAAI,iBAAiB,CAAA,IAAK,kBAAkB,CAAA,CAAE,CAAA;AAAA,EACnF;AACA,EAAA,IAAI,CAAC,OAAA,CAAQ,GAAA,CAAI,kBAAkB,CAAA,IAAK,CAAC,QAAQ,gBAAA,EAAkB;AACjE,IAAA,OAAA,CAAQ,IAAI,0EAA0E,CAAA;AAAA,EACxF;AACA,EAAA,IAAI;AACF,IAAAC,SAAAA,CAAU,OAAA,EAAS,EAAE,SAAA,EAAW,MAAM,CAAA;AAAA,EACxC,CAAA,CAAA,MAAQ;AACN,IAAA,QAAA,CAAS,IAAA,CAAK,CAAA,wBAAA,EAA2B,OAAO,CAAA,CAAA,CAAG,CAAA;AAAA,EACrD;AAEA,EAAA,IAAI,CAAC,SAAS,MAAA,EAAQ;AACpB,IAAA,OAAA,CAAQ,IAAI,4BAA4B,CAAA;AACxC,IAAA,OAAO,CAAA;AAAA,EACT;AACA,EAAA,OAAA,CAAQ,IAAI,cAAc,CAAA;AAC1B,EAAA,KAAA,MAAW,KAAK,QAAA,EAAU,OAAA,CAAQ,GAAA,CAAI,CAAA,IAAA,EAAO,CAAC,CAAA,CAAE,CAAA;AAChD,EAAA,OAAO,CAAA;AACT;AAEA,eAAsB,MAAA,CAAO,GAAA,GAAM,OAAA,CAAQ,GAAA,EAAI,EAAoB;AACjE,EAAA,MAAM,OAAA,GAAU,gBAAgB,GAAG,CAAA;AACnC,EAAAA,SAAAA,CAAU,OAAA,EAAS,EAAE,SAAA,EAAW,MAAM,CAAA;AAEtC,EAAA,IAAI,CAACC,UAAAA,CAAWC,IAAAA,CAAK,GAAA,EAAK,iBAAiB,CAAC,CAAA,EAAG;AAC7C,IAAA,MAAM,MAAA,GAAS,MAAM,YAAA,EAAa;AAClC,IAAA,WAAA;AAAA,MACE;AAAA,QACE,aAAA,EAAe,QAAA;AAAA,QACf,OAAA,EAAS,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,IAAK,UAAA;AAAA,QAC7B,aAAA,EAAe,kBAAA;AAAA,QACf,OAAA,EAAS;AAAA,OACX;AAAA,MACA;AAAA,KACF;AACA,IAAA,OAAA,CAAQ,IAAI,yCAAoC,CAAA;AAAA,EAClD,CAAA,MAAO;AACL,IAAA,OAAA,CAAQ,IAAI,mCAA8B,CAAA;AAAA,EAC5C;AAEA,EAAA,MAAM,OAAA,GAAU,YAAY,GAAG,CAAA;AAC/B,EAAA,IAAI,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA,CAAE,MAAA,EAAQ;AAC/B,IAAA,MAAMH,EAAAA,GAAIG,IAAAA,CAAK,OAAA,EAAS,cAAc,CAAA;AACtC,IAAAC,aAAAA,CAAcJ,EAAAA,EAAG,IAAA,CAAK,SAAA,CAAU,OAAA,EAAS,IAAA,EAAM,CAAC,CAAA,EAAG,EAAE,IAAA,EAAM,GAAA,EAAO,CAAA;AAClE,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,mBAAA,EAAiBA,EAAC,CAAA,qBAAA,CAAuB,CAAA;AAAA,EACvD;AAEA,EAAA,OAAA,CAAQ,GAAA,CAAI,qCAAgC,OAAO,CAAA;AACnD,EAAA,SAAA,CAAU,GAAG,CAAA;AACb,EAAA,OAAA,CAAQ,IAAI,4EAA4E,CAAA;AACxF,EAAA,OAAO,CAAA;AACT;AAEA,eAAsB,SAAA,CAAU,GAAA,GAAM,OAAA,CAAQ,GAAA,EAAI,EAAoB;AACpE,EAAA,MAAM,WAAW,kBAAA,EAAmB;AACpC,EAAA,MAAM,OAAA,GAAU,YAAY,GAAG,CAAA;AAC/B,EAAA,MAAM,GAAA,GAAM,WAAW,GAAG,CAAA;AAE1B,EAAA,IAAI,aAAa,QAAA,EAAU;AACzB,IAAA,MAAM,IAAA,GAAO,gBAAA;AAAA,MACX,OAAA,CAAQ,GAAA,CAAI,iBAAiB,CAAA,IAAK,IAAI,aAAA,IAAiB;AAAA,KACzD;AACA,IAAA,MAAM,EAAE,EAAA,EAAI,MAAA,EAAO,GAAI,MAAM,aAAa,IAAI,CAAA;AAC9C,IAAA,IAAI,CAAC,EAAA,EAAI;AACP,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,oCAAA,EAAuC,IAAI,CAAA,CAAA,CAAG,CAAA;AAC1D,MAAA,OAAA,CAAQ,IAAI,iEAA8D,CAAA;AAC1E,MAAA,OAAO,CAAA;AAAA,IACT;AACA,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,mBAAA,EAAsB,IAAI,CAAA,EAAA,CAAI,CAAA;AAC1C,IAAA,KAAA,MAAW,CAAA,IAAK,MAAA,EAAQ,OAAA,CAAQ,GAAA,CAAI,CAAA,EAAA,EAAK,CAAA,KAAM,GAAA,CAAI,OAAA,GAAU,GAAA,GAAM,GAAG,CAAA,CAAA,EAAI,CAAC,CAAA,CAAE,CAAA;AAC7E,IAAA,IAAI,CAAC,MAAA,CAAO,MAAA,EAAQ,OAAA,CAAQ,IAAI,4CAA4C,CAAA;AAC5E,IAAA,OAAO,CAAA;AAAA,EACT;AAEA,EAAA,IAAI,aAAa,mBAAA,EAAqB;AACpC,IAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,GAAA,CAAI,qBAAqB,KAAK,OAAA,CAAQ,mBAAA;AAC3D,IAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,GAAA,CAAI,sBAAsB,KAAK,OAAA,CAAQ,oBAAA;AAC3D,IAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,GAAA,EAAK;AACjB,MAAA,OAAA,CAAQ,IAAI,uEAAuE,CAAA;AACnF,MAAA,OAAO,CAAA;AAAA,IACT;AACA,IAAA,MAAM,OAAO,MAAM,WAAA;AAAA,MACjB,CAAA,EAAG,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAC,CAAA,OAAA,CAAA;AAAA,MAC3B,EAAE,OAAA,EAAS,EAAE,eAAe,CAAA,OAAA,EAAU,GAAG,IAAG;AAAE,KAChD;AACA,IAAA,IAAI,CAAC,IAAA,EAAM;AACT,MAAA,OAAA,CAAQ,IAAI,yCAAyC,CAAA;AACrD,MAAA,OAAO,CAAA;AAAA,IACT;AACA,IAAA,OAAA,CAAQ,IAAI,sBAAsB,CAAA;AAClC,IAAA,KAAA,MAAW,KAAK,IAAA,CAAK,IAAA,IAAQ,EAAC,UAAW,GAAA,CAAI,CAAA,EAAA,EAAK,CAAA,CAAE,EAAA,KAAO,IAAI,OAAA,GAAU,GAAA,GAAM,GAAG,CAAA,CAAA,EAAI,CAAA,CAAE,EAAE,CAAA,CAAE,CAAA;AAC5F,IAAA,OAAO,CAAA;AAAA,EACT;AAEA,EAAA,OAAA,CAAQ,IAAI,uEAAuE,CAAA;AACnF,EAAA,OAAO,CAAA;AACT;AAEA,eAAsB,QAAA,CAAS,GAAA,GAAM,OAAA,CAAQ,GAAA,EAAI,EAAoB;AACnE,EAAA,MAAM,GAAA,GAAM,EAAE,GAAG,OAAA,CAAQ,KAAK,GAAG,YAAA,CAAa,GAAG,CAAA,EAAE;AAEnD,EAAA,MAAM,IAAA,GAAO,aAAa,GAAG,CAAA;AAC7B,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,OAAA,CAAQ,IAAI,yDAAiD,CAAA;AAC7D,IAAA,OAAO,GAAA,CAAI,KAAA,EAAO,CAAC,KAAA,EAAO,KAAK,GAAG,EAAE,GAAA,EAAK,IAAA,EAAM,GAAA,EAAK,CAAA;AAAA,EACtD;AAEA,EAAA,MAAM,UAAU,cAAA,EAAe;AAC/B,EAAA,MAAM,GAAA,GAAM,MAAM,mBAAA,CAAoB,OAAA,EAAS,EAAE,GAAA,EAAK,CAAC,CAAA,KAAM,OAAA,CAAQ,GAAA,CAAI,CAAA,EAAA,EAAK,CAAC,CAAA,CAAE,GAAG,CAAA;AACpF,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,OAAA,CAAQ,KAAA;AAAA,MACN;AAAA,KAIF;AACA,IAAA,OAAO,CAAA;AAAA,EACT;AACA,EAAA,OAAA,CAAQ,IAAI,qDAAgD,CAAA;AAC5D,EAAA,OAAO,GAAA,CAAI,OAAA,CAAQ,QAAA,EAAU,CAACG,IAAAA,CAAK,KAAK,SAAA,EAAW,QAAA,EAAU,WAAW,CAAC,CAAA,EAAG;AAAA,IAC1E,GAAA,EAAK,GAAA;AAAA,IACL,GAAA,EAAK,EAAE,GAAG,GAAA,EAAK,MAAM,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAA,IAAK,MAAA;AAAO,GACpD,CAAA;AACH;AAEO,SAAS,SAAA,GAAoB;AAClC,EAAA,MAAM,GAAA,GAAM,WAAA,CAAY,cAAA,EAAgB,CAAA;AACxC,EAAA,IAAID,UAAAA,CAAW,GAAG,CAAA,EAAG;AACnB,IAAA,MAAA,CAAO,KAAK,EAAE,SAAA,EAAW,IAAA,EAAM,KAAA,EAAO,MAAM,CAAA;AAC5C,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,wCAAA,EAAmC,GAAG,CAAA,EAAA,CAAI,CAAA;AAAA,EACxD,CAAA,MAAO;AACL,IAAA,OAAA,CAAQ,IAAI,4CAA4C,CAAA;AAAA,EAC1D;AACA,EAAA,OAAO,CAAA;AACT;AC3KA,SAAS,IAAA,GAAe;AACtB,EAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAA,IAAK,MAAA;AAChC;AAEA,SAAS,MAAA,GAAiB;AACxB,EAAA,OAAO,CAAA,iBAAA,EAAoB,MAAM,CAAA,CAAA;AACnC;AAEA,eAAe,UAAA,CAAW,YAAY,IAAA,EAAwB;AAC5D,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,MAAA,EAAO,EAAG,EAAE,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,SAAS,CAAA,EAAG,CAAA;AAC5E,IAAA,OAAO,IAAI,MAAA,GAAS,GAAA;AAAA,EACtB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAEA,eAAe,aAAA,CAAc,YAAY,IAAA,EAA2B;AAClE,EAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,EAAI;AACvB,EAAA,OAAO,IAAA,CAAK,GAAA,EAAI,GAAI,KAAA,GAAQ,SAAA,EAAW;AACrC,IAAA,IAAI,MAAM,UAAA,EAAW,EAAG,OAAO,IAAA;AAC/B,IAAA,MAAM,IAAI,OAAA,CAAQ,CAAC,MAAM,UAAA,CAAW,CAAA,EAAG,GAAG,CAAC,CAAA;AAAA,EAC7C;AACA,EAAA,OAAO,KAAA;AACT;AAQA,SAAS,YAAY,GAAA,EAA4B;AAC/C,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,GAAA,CAAI,yBAAyB,CAAA;AACrD,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,MAAM,CAAA,GAAI,aAAa,OAAO,CAAA;AAC9B,IAAA,IAAI,GAAG,OAAO,CAAA;AAAA,EAChB;AACA,EAAA,OAAO,YAAA,CAAa,WAAA,EAAa,CAAA,IAAK,aAAa,GAAG,CAAA;AACxD;AAGA,SAAS,YAAY,GAAA,EAAqB;AACxC,EAAA,IAAI,MAAA,GAAS,CAAA;AACb,EAAA,IAAI;AACF,IAAA,KAAA,MAAW,SAAS,WAAA,CAAY,GAAA,EAAK,EAAE,aAAA,EAAe,IAAA,EAAM,CAAA,EAAG;AAC7D,MAAA,MAAM,IAAA,GAAOC,IAAAA,CAAK,GAAA,EAAK,KAAA,CAAM,IAAI,CAAA;AACjC,MAAA,IAAI,KAAA,CAAM,aAAY,EAAG,MAAA,GAAS,KAAK,GAAA,CAAI,MAAA,EAAQ,WAAA,CAAY,IAAI,CAAC,CAAA;AAAA,WAAA,IAC3D,KAAA,CAAM,MAAA,EAAO,EAAG,MAAA,GAAS,IAAA,CAAK,IAAI,MAAA,EAAQ,QAAA,CAAS,IAAI,CAAA,CAAE,OAAO,CAAA;AAAA,IAC3E;AAAA,EACF,CAAA,CAAA,MAAQ;AAAA,EAER;AACA,EAAA,OAAO,MAAA;AACT;AAGA,SAAS,aAAa,IAAA,EAAuB;AAC3C,EAAA,MAAM,KAAA,GAAQA,IAAAA,CAAK,IAAA,EAAM,SAAA,EAAW,UAAU,WAAW,CAAA;AACzD,EAAA,IAAI,CAACD,UAAAA,CAAW,KAAK,CAAA,EAAG,OAAO,KAAA;AAC/B,EAAA,IAAI;AACF,IAAA,OAAO,QAAA,CAAS,KAAK,CAAA,CAAE,OAAA,IAAW,YAAYC,IAAAA,CAAK,IAAA,EAAM,KAAK,CAAC,CAAA;AAAA,EACjE,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAGA,eAAe,WAAA,CAAY,KAAa,GAAA,EAAsD;AAC5F,EAAA,MAAM,IAAA,GAAO,YAAY,GAAG,CAAA;AAC5B,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,MAAM,KAAA,GAAQA,IAAAA,CAAK,IAAA,EAAM,SAAA,EAAW,UAAU,WAAW,CAAA;AACzD,IAAA,IAAID,UAAAA,CAAW,KAAK,CAAA,IAAK,YAAA,CAAa,IAAI,CAAA,EAAG;AAC3C,MAAA,OAAOG,KAAAA,CAAM,OAAA,CAAQ,QAAA,EAAU,CAAC,KAAK,CAAA,EAAG;AAAA,QACtC,GAAA,EAAK,IAAA;AAAA,QACL,GAAA,EAAK,EAAE,GAAG,GAAA,EAAK,UAAU,YAAA,EAAc,IAAA,EAAM,MAAK,EAAE;AAAA,QACpD,KAAA,EAAO;AAAA,OACR,CAAA;AAAA,IACH;AACA,IAAA,OAAA,CAAQ,GAAA;AAAA,MACNH,UAAAA,CAAW,KAAK,CAAA,GACZ,sEAAA,GACA;AAAA,KACN;AACA,IAAA,OAAOG,KAAAA,CAAM,KAAA,EAAO,CAAC,KAAA,EAAO,KAAK,CAAA,EAAG,EAAE,GAAA,EAAK,IAAA,EAAM,GAAA,EAAK,KAAA,EAAO,SAAA,EAAW,CAAA;AAAA,EAC1E;AAEA,EAAA,MAAM,GAAA,GAAM,MAAM,mBAAA,CAAoB,cAAA,IAAkB,EAAE,GAAA,EAAK,CAAC,CAAA,KAAM,QAAQ,GAAA,CAAI,CAAA,EAAA,EAAK,CAAC,CAAA,CAAE,GAAG,CAAA;AAC7F,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,EAAA,OAAOA,KAAAA,CAAM,OAAA,CAAQ,QAAA,EAAU,CAACF,IAAAA,CAAK,KAAK,SAAA,EAAW,QAAA,EAAU,WAAW,CAAC,CAAA,EAAG;AAAA,IAC5E,GAAA,EAAK,GAAA;AAAA,IACL,GAAA,EAAK,EAAE,GAAG,GAAA,EAAK,UAAU,YAAA,EAAc,IAAA,EAAM,MAAK,EAAE;AAAA,IACpD,KAAA,EAAO;AAAA,GACR,CAAA;AACH;AAEA,SAAS,kBAAkB,GAAA,EAA4B;AACrD,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,GAAA,CAAI,wBAAwB,CAAA;AACpD,EAAA,IAAI,OAAA,IAAWD,UAAAA,CAAW,OAAO,CAAA,EAAG,OAAO,OAAA;AAE3C,EAAA,MAAM,UAAA,GAAa,CAACC,IAAAA,CAAKG,OAAAA,IAAW,QAAA,EAAU,KAAA,EAAO,oBAAoB,CAAC,CAAA;AAC1E,EAAA,MAAM,IAAA,GAAO,YAAY,GAAG,CAAA;AAC5B,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,UAAA,CAAW,IAAA;AAAA,MACTH,IAAAA,CAAK,IAAA,EAAM,WAAA,EAAa,QAAA,EAAU,WAAW,oBAAoB,CAAA;AAAA,MACjEA,IAAAA,CAAK,IAAA,EAAM,WAAA,EAAa,QAAA,EAAU,SAAS,oBAAoB;AAAA,KACjE;AAAA,EACF;AACA,EAAA,OAAO,WAAW,IAAA,CAAK,CAAC,MAAMD,UAAAA,CAAW,CAAC,CAAC,CAAA,IAAK,IAAA;AAClD;AAEA,eAAsB,UAAA,CAAW,GAAA,GAAM,OAAA,CAAQ,GAAA,EAAI,EAAoB;AACrE,EAAA,MAAM,GAAA,GAAM,EAAE,GAAG,OAAA,CAAQ,KAAK,GAAG,YAAA,CAAa,GAAG,CAAA,EAAE;AAEnD,EAAA,MAAM,GAAA,GAAM,kBAAkB,GAAG,CAAA;AACjC,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,OAAA,CAAQ,KAAA;AAAA,MACN;AAAA,KAEF;AACA,IAAA,OAAO,CAAA;AAAA,EACT;AAEA,EAAA,IAAI,MAAA,GAA8B,IAAA;AAClC,EAAA,IAAI,aAAA,GAAgB,KAAA;AAEpB,EAAA,IAAI,MAAM,YAAW,EAAG;AACtB,IAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,uCAAA,EAAqC,MAAA,EAAQ,CAAA,CAAE,CAAA;AAAA,EAC7D,CAAA,MAAO;AACL,IAAA,OAAA,CAAQ,IAAI,oCAA+B,CAAA;AAC3C,IAAA,MAAA,GAAS,MAAM,WAAA,CAAY,GAAA,EAAK,GAAG,CAAA;AACnC,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,OAAA,CAAQ,KAAA;AAAA,QACN;AAAA,OAGF;AACA,MAAA,OAAO,CAAA;AAAA,IACT;AACA,IAAA,aAAA,GAAgB,IAAA;AAChB,IAAA,IAAI,CAAE,MAAM,aAAA,EAAc,EAAI;AAC5B,MAAA,OAAA,CAAQ,MAAM,uCAAoC,CAAA;AAClD,MAAA,MAAA,CAAO,KAAK,SAAS,CAAA;AACrB,MAAA,OAAO,CAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,MAAM,aAAa,MAAY;AAC7B,IAAA,IAAI,aAAA,IAAiB,MAAA,IAAU,CAAC,MAAA,CAAO,MAAA,EAAQ;AAC7C,MAAA,IAAI;AACF,QAAA,MAAA,CAAO,KAAK,SAAS,CAAA;AAAA,MACvB,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,WAAW,MAAY;AAC3B,IAAA,UAAA,EAAW;AACX,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB,CAAA;AACA,EAAA,OAAA,CAAQ,IAAA,CAAK,UAAU,QAAQ,CAAA;AAC/B,EAAA,OAAA,CAAQ,IAAA,CAAK,WAAW,QAAQ,CAAA;AAEhC,EAAA,OAAA,CAAQ,IAAI,yCAAoC,CAAA;AAChD,EAAA,MAAM,IAAA,GAAO,MAAM,IAAI,OAAA,CAAgB,CAACK,QAAAA,KAAY;AAClD,IAAA,MAAM,GAAA,GAAMF,MAAM,GAAA,EAAK,IAAI,EAAE,KAAA,EAAO,SAAA,EAAW,GAAA,EAAK,CAAA;AACpD,IAAA,GAAA,CAAI,GAAG,MAAA,EAAQ,CAAC,MAAME,QAAAA,CAAQ,CAAA,IAAK,CAAC,CAAC,CAAA;AAAA,EACvC,CAAC,CAAA;AAED,EAAA,UAAA,EAAW;AACX,EAAA,OAAO,IAAA;AACT;ACzKA,SAAS,IAAA,GAAc;AACrB,EAAE,SAAO,6BAA0B,CAAA;AACnC,EAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAChB;AAEA,eAAsB,SAAA,CACpB,IAAA,GAA8C,EAAC,EACxB;AACvB,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,IAAO,OAAA,CAAQ,GAAA,EAAI;AACpC,EAAA,MAAM,QAAA,GAAW,WAAW,GAAG,CAAA;AAC/B,EAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,SAAA,IAAa,CAAC,QAAQ,KAAA,CAAM,KAAA;AAExD,EAAA,IAAI,cAAA,EAAgB;AAClB,IAAA,MAAMC,OAAAA,GAAS,MAAM,YAAA,EAAa;AAClC,IAAA,MAAMC,OAAAA,GAAqB;AAAA,MACzB,aAAA,EAAe,QAAA;AAAA,MACf,OAAA,EAASD,OAAAA,CAAO,MAAA,CAAO,CAAC,CAAA,IAAK,UAAA;AAAA,MAC7B,aAAA,EAAe,kBAAA;AAAA,MACf,OAAA,EAAS;AAAA,KACX;AACA,IAAA,WAAA,CAAYC,SAAQ,GAAG,CAAA;AACvB,IAAA,OAAO,EAAE,MAAA,EAAAA,OAAAA,EAAQ,OAAA,EAAS,EAAC,EAAG,QAAA,EAAU,QAAA,EAAU,OAAA,EAASA,OAAAA,CAAO,OAAA,EAAU,OAAA,EAAS,KAAA,EAAM;AAAA,EAC7F;AAEA,EAAE,QAAM,kCAA0B,CAAA;AAElC,EAAA,MAAM,OAAS,CAAA,CAAA,OAAA,EAAQ;AACvB,EAAA,IAAA,CAAK,MAAM,6BAAwB,CAAA;AACnC,EAAA,MAAM,MAAA,GAAS,MAAM,YAAA,EAAa;AAClC,EAAA,IAAA,CAAK,IAAA;AAAA,IACH,OAAO,EAAA,GACH,CAAA,kBAAA,EAAqB,MAAA,CAAO,MAAA,CAAO,MAAM,CAAA,WAAA,CAAA,GACzC;AAAA,GACN;AAEA,EAAA,MAAM,MAAA,GAAS,MAAQ,CAAA,CAAA,MAAA,CAAO;AAAA,IAC5B,OAAA,EAAS,iCAAA;AAAA,IACT,OAAA,EAAS;AAAA,MACP,GAAI,OAAO,EAAA,GACP;AAAA,QACE;AAAA,UACE,KAAA,EAAO,QAAA;AAAA,UACP,KAAA,EAAO,uBAAA;AAAA,UACP,IAAA,EAAM;AAAA;AACR,UAEF,EAAC;AAAA,MACL,EAAE,KAAA,EAAO,QAAA,EAAmB,KAAA,EAAO,gCAAA,EAAkC,MAAM,WAAA,EAAY;AAAA,MACvF,EAAE,KAAA,EAAO,mBAAA,EAA8B,KAAA,EAAO,0BAAA,EAA4B,MAAM,MAAA,EAAO;AAAA,MACvF,GAAI,CAAC,MAAA,CAAO,EAAA,GACR,CAAC,EAAE,KAAA,EAAO,gBAAA,EAA2B,KAAA,EAAO,iBAAA,EAAmB,IAAA,EAAM,oBAAA,EAAmB,IACxF;AAAC;AACP,GACD,CAAA;AACD,EAAA,IAAM,CAAA,CAAA,QAAA,CAAS,MAAM,CAAA,EAAG,IAAA,EAAK;AAE7B,EAAA,IAAI,WAAW,gBAAA,EAAkB;AAC/B,IAAE,CAAA,CAAA,IAAA;AAAA,MACA,4KAAA;AAAA,MAIA;AAAA,KACF;AACA,IAAE,QAAM,cAAc,CAAA;AACtB,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB;AAEA,EAAA,MAAM,MAAA,GAAqB,EAAE,OAAA,EAAS,eAAA,EAAgB;AACtD,EAAA,MAAM,UAAmB,EAAC;AAC1B,EAAA,IAAI,OAAA,GAAU,UAAA;AAEd,EAAA,IAAI,WAAW,QAAA,EAAU;AACvB,IAAA,MAAM,SAAS,MAAA,CAAO,MAAA,CAAO,SAAS,MAAA,CAAO,MAAA,GAAS,CAAC,UAAU,CAAA;AACjE,IAAA,MAAM,MAAA,GAAS,MAAQ,CAAA,CAAA,MAAA,CAAO;AAAA,MAC5B,OAAA,EAAS,kBAAA;AAAA,MACT,OAAA,EAAS,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,KAAA,EAAO,CAAA,EAAG,KAAA,EAAO,CAAA,EAAE,CAAE;AAAA,KACpD,CAAA;AACD,IAAA,IAAM,CAAA,CAAA,QAAA,CAAS,MAAM,CAAA,EAAG,IAAA,EAAK;AAC7B,IAAA,OAAA,GAAU,OAAO,MAAM,CAAA;AACvB,IAAA,MAAA,CAAO,aAAA,GAAgB,QAAA;AACvB,IAAA,MAAA,CAAO,OAAA,GAAU,OAAA;AACjB,IAAA,MAAA,CAAO,aAAA,GAAgB,kBAAA;AAAA,EACzB,CAAA,MAAA,IAAW,WAAW,QAAA,EAAU;AAC9B,IAAA,MAAM,SAAS,MAAQ,CAAA,CAAA,QAAA,CAAS,EAAE,OAAA,EAAS,8BAA8B,CAAA;AACzE,IAAA,IAAM,CAAA,CAAA,QAAA,CAAS,MAAM,CAAA,EAAG,IAAA,EAAK;AAC7B,IAAA,MAAM,EAAA,GAAK,MAAQ,CAAA,CAAA,IAAA,CAAK,EAAE,SAAS,QAAA,EAAU,YAAA,EAAc,oBAAoB,CAAA;AAC/E,IAAA,IAAM,CAAA,CAAA,QAAA,CAAS,EAAE,CAAA,EAAG,IAAA,EAAK;AACzB,IAAA,OAAA,GAAU,MAAA,CAAO,EAAE,CAAA,IAAK,kBAAA;AACxB,IAAA,MAAA,CAAO,aAAA,GAAgB,QAAA;AACvB,IAAA,MAAA,CAAO,OAAA,GAAU,OAAA;AACjB,IAAA,OAAA,CAAQ,cAAA,GAAiB,OAAO,MAAM,CAAA;AAAA,EACxC,CAAA,MAAA,IAAW,WAAW,mBAAA,EAAqB;AACzC,IAAA,MAAM,OAAA,GAAU,MAAQ,CAAA,CAAA,IAAA,CAAK,EAAE,SAAS,sBAAA,EAAmB,YAAA,EAAc,IAAI,CAAA;AAC7E,IAAA,IAAM,CAAA,CAAA,QAAA,CAAS,OAAO,CAAA,EAAG,IAAA,EAAK;AAC9B,IAAA,MAAM,SAAS,MAAQ,CAAA,CAAA,QAAA,CAAS,EAAE,OAAA,EAAS,WAAW,CAAA;AACtD,IAAA,IAAM,CAAA,CAAA,QAAA,CAAS,MAAM,CAAA,EAAG,IAAA,EAAK;AAC7B,IAAA,MAAM,EAAA,GAAK,MAAQ,CAAA,CAAA,IAAA,CAAK,EAAE,SAAS,QAAA,EAAU,YAAA,EAAc,eAAe,CAAA;AAC1E,IAAA,IAAM,CAAA,CAAA,QAAA,CAAS,EAAE,CAAA,EAAG,IAAA,EAAK;AACzB,IAAA,OAAA,GAAU,MAAA,CAAO,EAAE,CAAA,IAAK,aAAA;AACxB,IAAA,MAAA,CAAO,aAAA,GAAgB,mBAAA;AACvB,IAAA,MAAA,CAAO,OAAA,GAAU,OAAA;AACjB,IAAA,OAAA,CAAQ,mBAAA,GAAsB,OAAO,OAAO,CAAA;AAC5C,IAAA,OAAA,CAAQ,oBAAA,GAAuB,OAAO,MAAM,CAAA;AAAA,EAC9C;AAEA,EAAA,WAAA,CAAY,EAAE,GAAG,QAAA,EAAU,GAAG,MAAA,IAAU,GAAG,CAAA;AAC3C,EAAA,IAAI,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA,CAAE,MAAA,eAAqB,EAAE,GAAG,OAAA,EAAQ,EAAG,GAAG,CAAA;AAEjE,EAAE,CAAA,CAAA,KAAA;AAAA,IACA,CAAA,sDAAA,EAAsD,MAAA,CAAO,aAAa,CAAA,CAAA,EAAI,OAAO,CAAA,EAAA;AAAA,GACvF;AAEA,EAAA,MAAM,EAAA,GAAK,MAAQ,CAAA,CAAA,OAAA,CAAQ,EAAE,SAAS,qBAAA,EAAoB,YAAA,EAAc,MAAM,CAAA;AAC9E,EAAA,IAAM,CAAA,CAAA,QAAA,CAAS,EAAE,CAAA,EAAG,IAAA,EAAK;AAEzB,EAAA,OAAO;AAAA,IACL,MAAA,EAAQ,EAAE,GAAG,QAAA,EAAU,GAAG,MAAA,EAAO;AAAA,IACjC,OAAA;AAAA,IACA,UAAU,MAAA,CAAO,aAAA;AAAA,IACjB,OAAA;AAAA,IACA,OAAA,EAAS,QAAQ,EAAE;AAAA,GACrB;AACF;;;AC3IA,SAAS,IAAA,GAAa;AACpB,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA,mFAAA,CAkBsE,CAAA;AACpF;AAEA,eAAsBC,KAAI,IAAA,EAA+B;AACvD,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,CAAC,CAAA,IAAK,EAAA;AACvB,EAAA,QAAQ,GAAA;AAAK,IACX,KAAK,EAAA;AAAA,IACL,KAAK,MAAA;AAAA,IACL,KAAK,OAAA,EAAS;AACZ,MAAA,MAAM,GAAA,GAAM,MAAM,SAAA,CAAU,EAAE,CAAA;AAC9B,MAAA,IAAI,IAAI,OAAA,EAAS,OAAA,CAAQ,IAAA,CAAK,MAAM,UAAU,CAAA;AAC9C,MAAA;AAAA,IACF;AAAA,IACA,KAAK,QAAA;AACH,MAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,SAAA,EAAW,CAAA;AAAA,IACjC,KAAK,KAAA;AACH,MAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,MAAA,EAAQ,CAAA;AAAA,IACpC,KAAK,QAAA;AACH,MAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,SAAA,EAAW,CAAA;AAAA,IACvC,KAAK,OAAA;AAAA,IACL,KAAK,KAAA;AACH,MAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,QAAA,EAAU,CAAA;AAAA,IACtC,KAAK,SAAA;AAAA,IACL,KAAK,KAAA,EAAO;AACV,MAAA,MAAM,IAAA,GAAO,MAAM,UAAA,EAAW;AAC9B,MAAA,OAAO,OAAA,CAAQ,KAAK,IAAI,CAAA;AAAA,IAC1B;AAAA,IACA,KAAK,QAAA;AACH,MAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,SAAA,EAAW,CAAA;AAAA,IACjC,KAAK,SAAA;AAAA,IACL,KAAK,WAAA;AAAA,IACL,KAAK,IAAA;AACH,MAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,UAAA,EAAY,CAAA;AAAA,IAClC,KAAK,MAAA;AAAA,IACL,KAAK,QAAA;AAAA,IACL,KAAK,IAAA;AACH,MAAA,IAAA,EAAK;AACL,MAAA;AAAA,IACF;AACE,MAAA,OAAA,CAAQ,KAAA,CAAM,wBAAwB,GAAG;AAAA,CAAI,CAAA;AAC7C,MAAA,IAAA,EAAK;AACL,MAAA,OAAO,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA;AAE3B","file":"cli.js","sourcesContent":["// SPDX-License-Identifier: MIT\n// OpenCore — which model to use. The harness is stable; the model is configuration.\n//\n// Providers:\n// - google: Gemini via GOOGLE_API_KEY (free API key).\n// - ollama: Local model via Ollama's OpenAI-compatible endpoint.\n// - openai-compatible: Any OpenAI-compatible endpoint with your own key.\n\nexport type ModelProviderId = \"google\" | \"ollama\" | \"openai-compatible\";\n\nexport type ModelSelection = {\n provider: ModelProviderId;\n modelId: string;\n /** Where the key comes from, without exposing the value. */\n keySource: \"GOOGLE_API_KEY\" | \"OPENEXPERT_MODEL_KEY\" | \"none (local)\";\n};\n\nexport function selectModel(env: NodeJS.ProcessEnv = process.env): ModelSelection {\n const raw = env[\"OPENEXPERT_MODEL_PROVIDER\"];\n const provider: ModelProviderId =\n raw === \"ollama\" || raw === \"openai-compatible\" ? raw : \"google\";\n\n if (provider === \"ollama\") {\n return {\n provider,\n modelId: env[\"OPENEXPERT_MODEL_ID\"] || \"llama3.1\",\n keySource: \"none (local)\",\n };\n }\n if (provider === \"openai-compatible\") {\n return {\n provider,\n modelId: env[\"OPENEXPERT_MODEL_ID\"] || \"gpt-4o-mini\",\n keySource: \"OPENEXPERT_MODEL_KEY\",\n };\n }\n return {\n provider: \"google\",\n modelId: env[\"OPENEXPERT_MODEL_ID\"] || \"gemini-2.5-flash\",\n keySource: \"GOOGLE_API_KEY\",\n };\n}\n\n/**\n * Ollama exposes two surfaces: the native API at the server root (`/api/*`)\n * and an OpenAI-compatible API under `/v1`. Callers may configure either\n * shape, so normalise before using it.\n */\nexport function ollamaApiBaseUrl(baseUrl: string): string {\n return baseUrl.trim().replace(/\\/+$/, \"\").replace(/\\/v1$/, \"\");\n}\n\nexport function ollamaChatBaseUrl(baseUrl: string): string {\n return `${ollamaApiBaseUrl(baseUrl)}/v1`;\n}\n\nexport function missingKeyHint(\n sel: ModelSelection,\n env: NodeJS.ProcessEnv = process.env,\n): string | null {\n if (sel.provider === \"ollama\") return null;\n if (sel.provider === \"google\" && !env[\"GOOGLE_API_KEY\"]) {\n return \"Falta GOOGLE_API_KEY (o usa OPENEXPERT_MODEL_PROVIDER=ollama para modelo local).\";\n }\n if (sel.provider === \"openai-compatible\" && !env[\"OPENEXPERT_MODEL_KEY\"]) {\n return \"Falta OPENEXPERT_MODEL_KEY para tu endpoint OpenAI-compatible.\";\n }\n return null;\n}\n","// SPDX-License-Identifier: MIT\n// Shared helpers for the OpenCore CLI: paths, config/secrets I/O, HTTP\n// detection, and release download with checksum verification.\n\nimport { spawn, spawnSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { ollamaApiBaseUrl } from \"../model-provider.js\";\n\nexport type Provider = \"google\" | \"ollama\" | \"openai-compatible\";\n\nexport type FileConfig = {\n modelProvider?: Provider;\n modelId?: string;\n ollamaBaseUrl?: string;\n dataDir?: string;\n};\n\nexport type Secrets = {\n GOOGLE_API_KEY?: string;\n GOOGLE_CLIENT_ID?: string;\n GOOGLE_CLIENT_SECRET?: string;\n OPENEXPERT_MODEL_KEY?: string;\n OPENEXPERT_BASE_URL?: string;\n};\n\nexport const DEFAULT_OLLAMA_URL = \"http://localhost:11434\";\nexport const DEFAULT_DATA_DIR = \"~/.openexpert\";\n\nexport function expandHome(p: string): string {\n if (p === \"~\") return homedir();\n if (p.startsWith(\"~/\")) return join(homedir(), p.slice(2));\n return p;\n}\n\nexport function packageRoot(): string {\n // dist/cli.js -> package root is one level up.\n return resolve(dirname(fileURLToPath(import.meta.url)), \"..\");\n}\n\nexport function packageVersion(): string {\n try {\n const pkg = JSON.parse(readFileSync(join(packageRoot(), \"package.json\"), \"utf8\")) as {\n version?: string;\n };\n return pkg.version ?? \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\n/** Walk up from `start` to find a checkout (package.json + packages/opencore). */\nexport function findRepoRoot(start = process.cwd()): string | null {\n let dir = resolve(start);\n for (let i = 0; i < 8; i++) {\n if (\n existsSync(join(dir, \"package.json\")) &&\n existsSync(join(dir, \"packages\", \"opencore\")) &&\n existsSync(join(dir, \"vite.config.ts\"))\n ) {\n return dir;\n }\n const parent = resolve(dir, \"..\");\n if (parent === dir) break;\n dir = parent;\n }\n return null;\n}\n\nexport function configPath(cwd = process.cwd()): string {\n return join(cwd, \"openexpert.json\");\n}\n\nexport function readConfig(cwd = process.cwd()): FileConfig {\n try {\n const p = configPath(cwd);\n if (!existsSync(p)) return {};\n return JSON.parse(readFileSync(p, \"utf8\")) as FileConfig;\n } catch {\n return {};\n }\n}\n\nexport function writeConfig(cfg: FileConfig, cwd = process.cwd()): void {\n writeFileSync(configPath(cwd), JSON.stringify(cfg, null, 2) + \"\\n\");\n}\n\nexport function resolvedDataDir(cwd = process.cwd()): string {\n const cfg = readConfig(cwd);\n const raw = process.env[\"OPENEXPERT_DATA_DIR\"] || cfg.dataDir || DEFAULT_DATA_DIR;\n return expandHome(raw);\n}\n\nexport function secretsPath(cwd = process.cwd()): string {\n return join(resolvedDataDir(cwd), \"secrets.json\");\n}\n\nexport function readSecrets(cwd = process.cwd()): Secrets {\n try {\n const p = secretsPath(cwd);\n if (!existsSync(p)) return {};\n return JSON.parse(readFileSync(p, \"utf8\")) as Secrets;\n } catch {\n return {};\n }\n}\n\nexport function writeSecrets(secrets: Secrets, cwd = process.cwd()): string {\n const dir = resolvedDataDir(cwd);\n mkdirSync(dir, { recursive: true });\n const p = join(dir, \"secrets.json\");\n writeFileSync(p, JSON.stringify(secrets, null, 2), { mode: 0o600 });\n return p;\n}\n\n/** Merge stored secrets into the current environment for the child server. */\nexport function secretsAsEnv(cwd = process.cwd()): Record<string, string> {\n const s = readSecrets(cwd);\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(s)) if (v) out[k] = v;\n return out;\n}\n\nexport async function httpGetJson<T>(\n url: string,\n opts: { headers?: Record<string, string>; timeoutMs?: number } = {},\n): Promise<T | null> {\n try {\n const res = await fetch(url, {\n ...(opts.headers ? { headers: opts.headers } : {}),\n signal: AbortSignal.timeout(opts.timeoutMs ?? 2500),\n });\n if (!res.ok) return null;\n return (await res.json()) as T;\n } catch {\n return null;\n }\n}\n\nexport async function detectOllama(\n baseUrl = DEFAULT_OLLAMA_URL,\n timeoutMs = 1500,\n): Promise<{ up: boolean; models: string[] }> {\n const root = ollamaApiBaseUrl(baseUrl);\n const data = await httpGetJson<{ models?: { name: string }[] }>(`${root}/api/tags`, {\n timeoutMs,\n });\n if (!data) return { up: false, models: [] };\n return { up: true, models: (data.models ?? []).map((m) => m.name) };\n}\n\nexport function appCacheDir(version: string): string {\n return join(expandHome(DEFAULT_DATA_DIR), \"app\", version);\n}\n\n/**\n * Ensure a standalone server build exists for `version`. Downloads the\n * release tarball published by the CI workflow and verifies its checksum.\n * Returns the directory containing `.output/server/index.mjs`, or null.\n */\nexport async function ensureStandaloneApp(\n version: string,\n opts: { repo?: string; log?: (m: string) => void } = {},\n): Promise<string | null> {\n const dir = appCacheDir(version);\n const entry = join(dir, \".output\", \"server\", \"index.mjs\");\n if (existsSync(entry)) return dir;\n\n const repo = opts.repo ?? \"OpenExpert-ai/OpenExpert\";\n const log = opts.log ?? (() => {});\n const tarball = `openexpert-server-${version}.tar.gz`;\n // Different tools tag releases differently; try the common shapes.\n const tags = [`v${version}`, `@openexpert/opencore@${version}`, version];\n\n for (const tag of tags) {\n const base = `https://github.com/${repo}/releases/download/${tag}`;\n const url = `${base}/${tarball}`;\n log(`downloading ${url}`);\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(60_000) });\n if (!res.ok) continue;\n const buf = Buffer.from(await res.arrayBuffer());\n\n // Verify checksum when the .sha256 asset is available.\n try {\n const shaRes = await fetch(`${base}/${tarball}.sha256`, {\n signal: AbortSignal.timeout(15_000),\n });\n if (shaRes.ok) {\n const expected = (await shaRes.text()).trim().split(/\\s+/)[0];\n const actual = createHash(\"sha256\").update(buf).digest(\"hex\");\n if (expected && expected !== actual) {\n log(\"checksum mismatch, aborting\");\n return null;\n }\n }\n } catch {\n // No checksum asset: continue (release is fetched over HTTPS).\n }\n\n mkdirSync(dir, { recursive: true });\n const tmp = join(dir, tarball);\n writeFileSync(tmp, buf);\n const r = spawnSync(\"tar\", [\"-xzf\", tmp, \"-C\", dir], { stdio: \"ignore\" });\n if (r.status !== 0) return null;\n return existsSync(entry) ? dir : null;\n } catch {\n // try the next tag\n }\n }\n return null;\n}\n\nexport function run(\n cmd: string,\n args: string[],\n opts: { cwd?: string; env?: NodeJS.ProcessEnv } = {},\n): Promise<number> {\n return new Promise((res) => {\n const child = spawn(cmd, args, { stdio: \"inherit\", cwd: opts.cwd, env: opts.env });\n const forward = (sig: NodeJS.Signals) => () => child.kill(sig);\n process.on(\"SIGINT\", forward(\"SIGINT\"));\n process.on(\"SIGTERM\", forward(\"SIGTERM\"));\n child.on(\"exit\", (code) => res(code ?? 0));\n });\n}\n","// SPDX-License-Identifier: MIT\n// Non-interactive CLI commands: doctor, fix, models, serve, version, update.\n\nimport { existsSync, mkdirSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { ollamaApiBaseUrl } from \"../model-provider.js\";\nimport {\n DEFAULT_OLLAMA_URL,\n appCacheDir,\n detectOllama,\n ensureStandaloneApp,\n findRepoRoot,\n httpGetJson,\n packageVersion,\n readConfig,\n readSecrets,\n resolvedDataDir,\n run,\n secretsAsEnv,\n writeConfig,\n type Provider,\n} from \"./lib.js\";\n\nexport function cmdVersion(): number {\n console.log(`@openexpert/opencore ${packageVersion()}`);\n return 0;\n}\n\nfunction configuredProvider(): Provider {\n const raw = process.env[\"OPENEXPERT_MODEL_PROVIDER\"] || readConfig().modelProvider;\n if (raw === \"ollama\" || raw === \"openai-compatible\") return raw;\n return \"google\";\n}\n\nexport function cmdDoctor(cwd = process.cwd()): number {\n const provider = configuredProvider();\n const dataDir = resolvedDataDir(cwd);\n const secrets = readSecrets(cwd);\n\n console.log(\"OpenCore doctor\");\n console.log(` provider: ${provider}`);\n console.log(` data dir: ${dataDir}`);\n\n const problems: string[] = [];\n\n if (provider === \"google\" && !process.env[\"GOOGLE_API_KEY\"] && !secrets.GOOGLE_API_KEY) {\n problems.push(\"Falta GOOGLE_API_KEY. Ejecuta `opencore init` o usa Ollama.\");\n }\n if (provider === \"openai-compatible\") {\n if (!process.env[\"OPENEXPERT_MODEL_KEY\"] && !secrets.OPENEXPERT_MODEL_KEY) {\n problems.push(\"Falta OPENEXPERT_MODEL_KEY.\");\n }\n if (!process.env[\"OPENEXPERT_BASE_URL\"] && !secrets.OPENEXPERT_BASE_URL) {\n problems.push(\"Falta OPENEXPERT_BASE_URL.\");\n }\n }\n if (provider === \"ollama\") {\n console.log(` ollama: ${process.env[\"OLLAMA_BASE_URL\"] || DEFAULT_OLLAMA_URL}`);\n }\n if (!process.env[\"GOOGLE_CLIENT_ID\"] && !secrets.GOOGLE_CLIENT_ID) {\n console.log(\" note: sin Google OAuth el chat funciona; Drive queda desconectado.\");\n }\n try {\n mkdirSync(dataDir, { recursive: true });\n } catch {\n problems.push(`No se puede escribir en ${dataDir}.`);\n }\n\n if (!problems.length) {\n console.log(\" OK: listo para arrancar.\");\n return 0;\n }\n console.log(\" pendiente:\");\n for (const x of problems) console.log(` - ${x}`);\n return 1;\n}\n\nexport async function cmdFix(cwd = process.cwd()): Promise<number> {\n const dataDir = resolvedDataDir(cwd);\n mkdirSync(dataDir, { recursive: true });\n\n if (!existsSync(join(cwd, \"openexpert.json\"))) {\n const ollama = await detectOllama();\n writeConfig(\n {\n modelProvider: \"ollama\",\n modelId: ollama.models[0] ?? \"llama3.1\",\n ollamaBaseUrl: DEFAULT_OLLAMA_URL,\n dataDir: \"~/.openexpert\",\n },\n cwd,\n );\n console.log(\"✓ openexpert.json creado (Ollama).\");\n } else {\n console.log(\"• openexpert.json ya existe.\");\n }\n\n const secrets = readSecrets(cwd);\n if (Object.keys(secrets).length) {\n const p = join(dataDir, \"secrets.json\");\n writeFileSync(p, JSON.stringify(secrets, null, 2), { mode: 0o600 });\n console.log(`✓ permisos de ${p} normalizados (0600).`);\n }\n\n console.log(\"✓ directorio de datos listo:\", dataDir);\n cmdDoctor(cwd);\n console.log(\"\\nSiguiente paso: `opencore serve` (o `opencore init` para elegir modelo).\");\n return 0;\n}\n\nexport async function cmdModels(cwd = process.cwd()): Promise<number> {\n const provider = configuredProvider();\n const secrets = readSecrets(cwd);\n const cfg = readConfig(cwd);\n\n if (provider === \"ollama\") {\n const base = ollamaApiBaseUrl(\n process.env[\"OLLAMA_BASE_URL\"] || cfg.ollamaBaseUrl || DEFAULT_OLLAMA_URL,\n );\n const { up, models } = await detectOllama(base);\n if (!up) {\n console.log(`No se puede contactar con Ollama en ${base}.`);\n console.log(\"Instálalo con: curl -fsSL https://ollama.com/install.sh | sh\");\n return 1;\n }\n console.log(`Modelos en Ollama (${base}):`);\n for (const m of models) console.log(` ${m === cfg.modelId ? \"*\" : \" \"} ${m}`);\n if (!models.length) console.log(\" (ninguno; prueba `ollama pull llama3.1`)\");\n return 0;\n }\n\n if (provider === \"openai-compatible\") {\n const base = process.env[\"OPENEXPERT_BASE_URL\"] || secrets.OPENEXPERT_BASE_URL;\n const key = process.env[\"OPENEXPERT_MODEL_KEY\"] || secrets.OPENEXPERT_MODEL_KEY;\n if (!base || !key) {\n console.log(\"Configura OPENEXPERT_BASE_URL y OPENEXPERT_MODEL_KEY (opencore init).\");\n return 1;\n }\n const data = await httpGetJson<{ data?: { id: string }[] }>(\n `${base.replace(/\\/+$/, \"\")}/models`,\n { headers: { Authorization: `Bearer ${key}` } },\n );\n if (!data) {\n console.log(\"No se pudo listar modelos del endpoint.\");\n return 1;\n }\n console.log(\"Modelos disponibles:\");\n for (const m of data.data ?? []) console.log(` ${m.id === cfg.modelId ? \"*\" : \" \"} ${m.id}`);\n return 0;\n }\n\n console.log(\"Proveedor google: usa `gemini-2.5-flash` o consulta Google AI Studio.\");\n return 0;\n}\n\nexport async function cmdServe(cwd = process.cwd()): Promise<number> {\n const env = { ...process.env, ...secretsAsEnv(cwd) } as NodeJS.ProcessEnv;\n\n const repo = findRepoRoot(cwd);\n if (repo) {\n console.log(\"Arrancando OpenExpert (desde el código fuente)…\");\n return run(\"npm\", [\"run\", \"dev\"], { cwd: repo, env });\n }\n\n const version = packageVersion();\n const app = await ensureStandaloneApp(version, { log: (m) => console.log(` ${m}`) });\n if (!app) {\n console.error(\n \"No hay un servidor preconstruido disponible y no se pudo descargar.\\n\" +\n \"Opciones:\\n\" +\n \" • Usa Docker: docker run -p 3000:3000 -v openexpert-data:/data ghcr.io/openexpert-ai/openexpert:local\\n\" +\n \" • O clona el repositorio y ejecuta `npm ci && npm run dev`.\",\n );\n return 1;\n }\n console.log(\"Arrancando OpenCore en http://localhost:3000 …\");\n return run(process.execPath, [join(app, \".output\", \"server\", \"index.mjs\")], {\n cwd: app,\n env: { ...env, PORT: process.env[\"PORT\"] || \"3000\" },\n });\n}\n\nexport function cmdUpdate(): number {\n const dir = appCacheDir(packageVersion());\n if (existsSync(dir)) {\n rmSync(dir, { recursive: true, force: true });\n console.log(`✓ caché del servidor eliminada (${dir}).`);\n } else {\n console.log(\"No hay servidor descargado que actualizar.\");\n }\n return 0;\n}\n","// SPDX-License-Identifier: MIT\n// `opencore desktop` — start the local server (unless it is already running)\n// and open the native OpenExpert window (the Tauri shell). This command owns\n// the server lifecycle so it can stop the server when the window closes.\n//\n// The repository is located from the CLI installation itself (not the current\n// directory), so `OpenExpert` works from anywhere.\n\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport { existsSync, readdirSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n ensureStandaloneApp,\n findRepoRoot,\n packageRoot,\n packageVersion,\n secretsAsEnv,\n} from \"./lib.js\";\n\nfunction port(): string {\n return process.env[\"PORT\"] || \"3000\";\n}\n\nfunction appUrl(): string {\n return `http://localhost:${port()}`;\n}\n\nasync function isServerUp(timeoutMs = 1500): Promise<boolean> {\n try {\n const res = await fetch(appUrl(), { signal: AbortSignal.timeout(timeoutMs) });\n return res.status < 500;\n } catch {\n return false;\n }\n}\n\nasync function waitForServer(timeoutMs = 120_000): Promise<boolean> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n if (await isServerUp()) return true;\n await new Promise((r) => setTimeout(r, 400));\n }\n return false;\n}\n\n/**\n * Find the source checkout, in order of confidence:\n * 1. OPENEXPERT_DESKTOP_REPO (baked by the installer),\n * 2. relative to the CLI installation (packages/opencore → repo root),\n * 3. walking up from the current directory.\n */\nfunction resolveRepo(cwd: string): string | null {\n const fromEnv = process.env[\"OPENEXPERT_DESKTOP_REPO\"];\n if (fromEnv) {\n const r = findRepoRoot(fromEnv);\n if (r) return r;\n }\n return findRepoRoot(packageRoot()) ?? findRepoRoot(cwd);\n}\n\n/** Newest modification time (ms) under a directory, or 0 if unreadable. */\nfunction newestMtime(dir: string): number {\n let newest = 0;\n try {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) newest = Math.max(newest, newestMtime(full));\n else if (entry.isFile()) newest = Math.max(newest, statSync(full).mtimeMs);\n }\n } catch {\n // ignore unreadable paths\n }\n return newest;\n}\n\n/** True when the production build exists and is not older than src/. */\nfunction buildIsFresh(repo: string): boolean {\n const built = join(repo, \".output\", \"server\", \"index.mjs\");\n if (!existsSync(built)) return false;\n try {\n return statSync(built).mtimeMs >= newestMtime(join(repo, \"src\"));\n } catch {\n return true;\n }\n}\n\n/** Start the local server, reusing the source checkout when available. */\nasync function spawnServer(cwd: string, env: NodeJS.ProcessEnv): Promise<ChildProcess | null> {\n const repo = resolveRepo(cwd);\n if (repo) {\n const built = join(repo, \".output\", \"server\", \"index.mjs\");\n if (existsSync(built) && buildIsFresh(repo)) {\n return spawn(process.execPath, [built], {\n cwd: repo,\n env: { ...env, NODE_ENV: \"production\", PORT: port() },\n stdio: \"inherit\",\n });\n }\n console.log(\n existsSync(built)\n ? \"La build está desactualizada; arrancando en modo desarrollo…\"\n : \"Arrancando el servidor en modo desarrollo (no hay build)…\",\n );\n return spawn(\"npm\", [\"run\", \"dev\"], { cwd: repo, env, stdio: \"inherit\" });\n }\n\n const app = await ensureStandaloneApp(packageVersion(), { log: (m) => console.log(` ${m}`) });\n if (!app) return null;\n return spawn(process.execPath, [join(app, \".output\", \"server\", \"index.mjs\")], {\n cwd: app,\n env: { ...env, NODE_ENV: \"production\", PORT: port() },\n stdio: \"inherit\",\n });\n}\n\nfunction findDesktopBinary(cwd: string): string | null {\n const fromEnv = process.env[\"OPENEXPERT_DESKTOP_BIN\"];\n if (fromEnv && existsSync(fromEnv)) return fromEnv;\n\n const candidates = [join(homedir(), \".local\", \"bin\", \"openexpert-desktop\")];\n const repo = resolveRepo(cwd);\n if (repo) {\n candidates.push(\n join(repo, \"src-tauri\", \"target\", \"release\", \"openexpert-desktop\"),\n join(repo, \"src-tauri\", \"target\", \"debug\", \"openexpert-desktop\"),\n );\n }\n return candidates.find((c) => existsSync(c)) ?? null;\n}\n\nexport async function cmdDesktop(cwd = process.cwd()): Promise<number> {\n const env = { ...process.env, ...secretsAsEnv(cwd) } as NodeJS.ProcessEnv;\n\n const bin = findDesktopBinary(cwd);\n if (!bin) {\n console.error(\n \"No se encontró la ventana de escritorio (openexpert-desktop).\\n\" +\n \"Compílala con `npx tauri build` o usa `npm run desktop:install`.\",\n );\n return 1;\n }\n\n let server: ChildProcess | null = null;\n let startedServer = false;\n\n if (await isServerUp()) {\n console.log(`• Usando el servidor ya activo en ${appUrl()}`);\n } else {\n console.log(\"Arrancando el servidor local…\");\n server = await spawnServer(cwd, env);\n if (!server) {\n console.error(\n \"No se pudo arrancar el servidor.\\n\" +\n \"Si usas el código fuente, entra en el repositorio y ejecuta:\\n\" +\n \" npm ci && npm run build && npm run desktop:install\",\n );\n return 1;\n }\n startedServer = true;\n if (!(await waitForServer())) {\n console.error(\"El servidor no respondió a tiempo.\");\n server.kill(\"SIGTERM\");\n return 1;\n }\n }\n\n const stopServer = (): void => {\n if (startedServer && server && !server.killed) {\n try {\n server.kill(\"SIGTERM\");\n } catch {\n // already gone\n }\n }\n };\n\n const onSignal = (): void => {\n stopServer();\n process.exit(0);\n };\n process.once(\"SIGINT\", onSignal);\n process.once(\"SIGTERM\", onSignal);\n\n console.log(\"Abriendo la ventana de OpenExpert…\");\n const code = await new Promise<number>((resolve) => {\n const win = spawn(bin, [], { stdio: \"inherit\", env });\n win.on(\"exit\", (c) => resolve(c ?? 0));\n });\n\n stopServer();\n return code;\n}\n","// SPDX-License-Identifier: MIT\n// Interactive setup wizard: pick a model provider, write config + secrets.\n\nimport * as p from \"@clack/prompts\";\nimport {\n DEFAULT_OLLAMA_URL,\n detectOllama,\n readConfig,\n writeConfig,\n writeSecrets,\n type FileConfig,\n type Provider,\n type Secrets,\n} from \"./lib.js\";\n\nexport type WizardResult = {\n config: FileConfig;\n secrets: Secrets;\n provider: Provider;\n modelId: string;\n started: boolean;\n};\n\nfunction bail(): never {\n p.cancel(\"Configuración cancelada.\");\n process.exit(1);\n}\n\nexport async function runWizard(\n opts: { cwd?: string; assumeYes?: boolean } = {},\n): Promise<WizardResult> {\n const cwd = opts.cwd ?? process.cwd();\n const existing = readConfig(cwd);\n const nonInteractive = opts.assumeYes || !process.stdin.isTTY;\n\n if (nonInteractive) {\n const ollama = await detectOllama();\n const config: FileConfig = {\n modelProvider: \"ollama\",\n modelId: ollama.models[0] ?? \"llama3.1\",\n ollamaBaseUrl: DEFAULT_OLLAMA_URL,\n dataDir: \"~/.openexpert\",\n };\n writeConfig(config, cwd);\n return { config, secrets: {}, provider: \"ollama\", modelId: config.modelId!, started: false };\n }\n\n p.intro(\"OpenCore — configuración\");\n\n const spin = p.spinner();\n spin.start(\"Buscando Ollama local…\");\n const ollama = await detectOllama();\n spin.stop(\n ollama.up\n ? `Ollama detectado (${ollama.models.length} modelo(s))`\n : \"No se ha detectado Ollama\",\n );\n\n const choice = await p.select({\n message: \"¿Qué modelo quieres usar?\",\n options: [\n ...(ollama.up\n ? [\n {\n value: \"ollama\" as const,\n label: \"Modelo local (Ollama)\",\n hint: \"sin claves, datos en tu equipo\",\n },\n ]\n : []),\n { value: \"google\" as const, label: \"Google Gemini (clave gratuita)\", hint: \"AI Studio\" },\n { value: \"openai-compatible\" as const, label: \"Otro endpoint compatible\", hint: \"BYOK\" },\n ...(!ollama.up\n ? [{ value: \"install-ollama\" as const, label: \"Instalar Ollama\", hint: \"te muestro cómo\" }]\n : []),\n ],\n });\n if (p.isCancel(choice)) bail();\n\n if (choice === \"install-ollama\") {\n p.note(\n \"Instala Ollama y descarga un modelo:\\n\\n\" +\n \" curl -fsSL https://ollama.com/install.sh | sh\\n\" +\n \" ollama pull llama3.1\\n\\n\" +\n \"Vuelve a ejecutar `opencore init` cuando esté listo.\",\n \"Instalar Ollama\",\n );\n p.outro(\"Sin cambios.\");\n process.exit(0);\n }\n\n const config: FileConfig = { dataDir: \"~/.openexpert\" };\n const secrets: Secrets = {};\n let modelId = \"llama3.1\";\n\n if (choice === \"ollama\") {\n const models = ollama.models.length ? ollama.models : [\"llama3.1\"];\n const picked = await p.select({\n message: \"Modelo de Ollama\",\n options: models.map((m) => ({ value: m, label: m })),\n });\n if (p.isCancel(picked)) bail();\n modelId = String(picked);\n config.modelProvider = \"ollama\";\n config.modelId = modelId;\n config.ollamaBaseUrl = DEFAULT_OLLAMA_URL;\n } else if (choice === \"google\") {\n const apiKey = await p.password({ message: \"GOOGLE_API_KEY (AI Studio)\" });\n if (p.isCancel(apiKey)) bail();\n const id = await p.text({ message: \"Modelo\", initialValue: \"gemini-2.5-flash\" });\n if (p.isCancel(id)) bail();\n modelId = String(id) || \"gemini-2.5-flash\";\n config.modelProvider = \"google\";\n config.modelId = modelId;\n secrets.GOOGLE_API_KEY = String(apiKey);\n } else if (choice === \"openai-compatible\") {\n const baseURL = await p.text({ message: \"Base URL (…/v1)\", initialValue: \"\" });\n if (p.isCancel(baseURL)) bail();\n const apiKey = await p.password({ message: \"API key\" });\n if (p.isCancel(apiKey)) bail();\n const id = await p.text({ message: \"Modelo\", initialValue: \"gpt-4o-mini\" });\n if (p.isCancel(id)) bail();\n modelId = String(id) || \"gpt-4o-mini\";\n config.modelProvider = \"openai-compatible\";\n config.modelId = modelId;\n secrets.OPENEXPERT_BASE_URL = String(baseURL);\n secrets.OPENEXPERT_MODEL_KEY = String(apiKey);\n }\n\n writeConfig({ ...existing, ...config }, cwd);\n if (Object.keys(secrets).length) writeSecrets({ ...secrets }, cwd);\n\n p.outro(\n `Configuración guardada en openexpert.json (modelo: ${config.modelProvider}:${modelId}).`,\n );\n\n const go = await p.confirm({ message: \"¿Arrancar ahora?\", initialValue: true });\n if (p.isCancel(go)) bail();\n\n return {\n config: { ...existing, ...config },\n secrets,\n provider: config.modelProvider as Provider,\n modelId,\n started: Boolean(go),\n };\n}\n","// SPDX-License-Identifier: MIT\n// OpenCore CLI entry point. Dispatches subcommands. See `run`.\n\nimport { cmdDoctor, cmdFix, cmdModels, cmdServe, cmdUpdate, cmdVersion } from \"./cli/commands.js\";\nimport { cmdDesktop } from \"./cli/desktop.js\";\nimport { runWizard } from \"./cli/wizard.js\";\n\nfunction help(): void {\n console.log(`opencore — motor abierto de OpenExpert\n\nUso:\n opencore asistente de configuración y arranque\n opencore init igual que sin argumentos\n opencore serve arranca la edición local en http://localhost:3000\n opencore desktop arranca el servidor y abre la ventana de escritorio\n opencore doctor revisa tu configuración\n opencore fix auto-configura lo que falte\n opencore models lista los modelos del proveedor actual\n opencore update limpia la caché del servidor descargado\n opencore version muestra la versión\n\nEjemplos:\n npx @openexpert/opencore\n npx @openexpert/opencore serve\n\nDocker (sin Node):\n docker run -p 3000:3000 -v openexpert-data:/data ghcr.io/openexpert-ai/openexpert`);\n}\n\nexport async function run(argv: string[]): Promise<void> {\n const cmd = argv[0] ?? \"\";\n switch (cmd) {\n case \"\":\n case \"init\":\n case \"setup\": {\n const res = await runWizard({});\n if (res.started) process.exit(await cmdServe());\n return;\n }\n case \"doctor\":\n return process.exit(cmdDoctor());\n case \"fix\":\n return process.exit(await cmdFix());\n case \"models\":\n return process.exit(await cmdModels());\n case \"serve\":\n case \"dev\":\n return process.exit(await cmdServe());\n case \"desktop\":\n case \"app\": {\n const code = await cmdDesktop();\n return process.exit(code);\n }\n case \"update\":\n return process.exit(cmdUpdate());\n case \"version\":\n case \"--version\":\n case \"-v\":\n return process.exit(cmdVersion());\n case \"help\":\n case \"--help\":\n case \"-h\":\n help();\n return;\n default:\n console.error(`Comando desconocido: ${cmd}\\n`);\n help();\n return process.exit(1);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/model-provider.ts","../src/cli/lib.ts","../src/cli/commands.ts","../src/cli/desktop.ts","../src/cli/wizard.ts","../src/cli.ts"],"names":["p","mkdirSync","existsSync","join","writeFileSync","spawn","homedir","resolve","ollama","config","run"],"mappings":"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","file":"cli.js","sourcesContent":["// SPDX-License-Identifier: MIT\n// OpenCore — which model to use. The harness is stable; the model is configuration.\n//\n// Providers:\n// - google: Gemini via GOOGLE_API_KEY (free API key).\n// - ollama: Local model via Ollama's OpenAI-compatible endpoint.\n// - openai-compatible: Any OpenAI-compatible endpoint with your own key.\n\nexport type ModelProviderId = \"google\" | \"ollama\" | \"openai-compatible\";\n\nexport type ModelSelection = {\n provider: ModelProviderId;\n modelId: string;\n /** Where the key comes from, without exposing the value. */\n keySource: \"GOOGLE_API_KEY\" | \"OPENEXPERT_MODEL_KEY\" | \"none (local)\";\n};\n\nexport function selectModel(env: NodeJS.ProcessEnv = process.env): ModelSelection {\n const raw = env[\"OPENEXPERT_MODEL_PROVIDER\"];\n const provider: ModelProviderId =\n raw === \"ollama\" || raw === \"openai-compatible\" ? raw : \"google\";\n\n if (provider === \"ollama\") {\n return {\n provider,\n modelId: env[\"OPENEXPERT_MODEL_ID\"] || \"llama3.1\",\n keySource: \"none (local)\",\n };\n }\n if (provider === \"openai-compatible\") {\n return {\n provider,\n modelId: env[\"OPENEXPERT_MODEL_ID\"] || \"gpt-4o-mini\",\n keySource: \"OPENEXPERT_MODEL_KEY\",\n };\n }\n return {\n provider: \"google\",\n modelId: env[\"OPENEXPERT_MODEL_ID\"] || \"gemini-2.5-flash\",\n keySource: \"GOOGLE_API_KEY\",\n };\n}\n\n/**\n * Ollama exposes two surfaces: the native API at the server root (`/api/*`)\n * and an OpenAI-compatible API under `/v1`. Callers may configure either\n * shape, so normalise before using it.\n *\n * Trailing slashes are removed with a loop, not with `/\\/+$/`: a regex has to\n * retry at every position of a long run of slashes, which is what CodeQL\n * reports as a polynomial ReDoS on `baseUrl`.\n */\nfunction stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value[end - 1] === \"/\") end -= 1;\n return value.slice(0, end);\n}\n\nexport function ollamaApiBaseUrl(baseUrl: string): string {\n const base = stripTrailingSlashes(baseUrl.trim());\n return base.endsWith(\"/v1\") ? base.slice(0, -3) : base;\n}\n\nexport function ollamaChatBaseUrl(baseUrl: string): string {\n return `${ollamaApiBaseUrl(baseUrl)}/v1`;\n}\n\nexport function missingKeyHint(\n sel: ModelSelection,\n env: NodeJS.ProcessEnv = process.env,\n): string | null {\n if (sel.provider === \"ollama\") return null;\n if (sel.provider === \"google\" && !env[\"GOOGLE_API_KEY\"]) {\n return \"Falta GOOGLE_API_KEY (o usa OPENEXPERT_MODEL_PROVIDER=ollama para modelo local).\";\n }\n if (sel.provider === \"openai-compatible\" && !env[\"OPENEXPERT_MODEL_KEY\"]) {\n return \"Falta OPENEXPERT_MODEL_KEY para tu endpoint OpenAI-compatible.\";\n }\n return null;\n}\n","// SPDX-License-Identifier: MIT\n// Shared helpers for the OpenCore CLI: paths, config/secrets I/O, HTTP\n// detection, and release download with checksum verification.\n\nimport { spawn, spawnSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { ollamaApiBaseUrl } from \"../model-provider.js\";\n\nexport type Provider = \"google\" | \"ollama\" | \"openai-compatible\";\n\nexport type FileConfig = {\n modelProvider?: Provider;\n modelId?: string;\n ollamaBaseUrl?: string;\n dataDir?: string;\n};\n\nexport type Secrets = {\n GOOGLE_API_KEY?: string;\n GOOGLE_CLIENT_ID?: string;\n GOOGLE_CLIENT_SECRET?: string;\n OPENEXPERT_MODEL_KEY?: string;\n OPENEXPERT_BASE_URL?: string;\n};\n\nexport const DEFAULT_OLLAMA_URL = \"http://localhost:11434\";\nexport const DEFAULT_DATA_DIR = \"~/.openexpert\";\n\nexport function expandHome(p: string): string {\n if (p === \"~\") return homedir();\n if (p.startsWith(\"~/\")) return join(homedir(), p.slice(2));\n return p;\n}\n\nexport function packageRoot(): string {\n // dist/cli.js -> package root is one level up.\n return resolve(dirname(fileURLToPath(import.meta.url)), \"..\");\n}\n\nexport function packageVersion(): string {\n try {\n const pkg = JSON.parse(readFileSync(join(packageRoot(), \"package.json\"), \"utf8\")) as {\n version?: string;\n };\n return pkg.version ?? \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\n/** Walk up from `start` to find a checkout (package.json + packages/opencore). */\nexport function findRepoRoot(start = process.cwd()): string | null {\n let dir = resolve(start);\n for (let i = 0; i < 8; i++) {\n if (\n existsSync(join(dir, \"package.json\")) &&\n existsSync(join(dir, \"packages\", \"opencore\")) &&\n existsSync(join(dir, \"vite.config.ts\"))\n ) {\n return dir;\n }\n const parent = resolve(dir, \"..\");\n if (parent === dir) break;\n dir = parent;\n }\n return null;\n}\n\nexport function configPath(cwd = process.cwd()): string {\n return join(cwd, \"openexpert.json\");\n}\n\nexport function readConfig(cwd = process.cwd()): FileConfig {\n try {\n const p = configPath(cwd);\n if (!existsSync(p)) return {};\n return JSON.parse(readFileSync(p, \"utf8\")) as FileConfig;\n } catch {\n return {};\n }\n}\n\nexport function writeConfig(cfg: FileConfig, cwd = process.cwd()): void {\n writeFileSync(configPath(cwd), JSON.stringify(cfg, null, 2) + \"\\n\");\n}\n\nexport function resolvedDataDir(cwd = process.cwd()): string {\n const cfg = readConfig(cwd);\n const raw = process.env[\"OPENEXPERT_DATA_DIR\"] || cfg.dataDir || DEFAULT_DATA_DIR;\n return expandHome(raw);\n}\n\nexport function secretsPath(cwd = process.cwd()): string {\n return join(resolvedDataDir(cwd), \"secrets.json\");\n}\n\nexport function readSecrets(cwd = process.cwd()): Secrets {\n try {\n const p = secretsPath(cwd);\n if (!existsSync(p)) return {};\n return JSON.parse(readFileSync(p, \"utf8\")) as Secrets;\n } catch {\n return {};\n }\n}\n\nexport function writeSecrets(secrets: Secrets, cwd = process.cwd()): string {\n const dir = resolvedDataDir(cwd);\n mkdirSync(dir, { recursive: true });\n const p = join(dir, \"secrets.json\");\n writeFileSync(p, JSON.stringify(secrets, null, 2), { mode: 0o600 });\n return p;\n}\n\n/** Merge stored secrets into the current environment for the child server. */\nexport function secretsAsEnv(cwd = process.cwd()): Record<string, string> {\n const s = readSecrets(cwd);\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(s)) if (v) out[k] = v;\n return out;\n}\n\n/**\n * Environment for the local server child process.\n *\n * The bundled server (Nitro) reads `NITRO_HOST`/`HOST` and defaults to every\n * interface, which would publish the dashboard — no authentication, read and\n * write access to the folders the owner granted — to whoever shares the\n * network. Bind loopback unless the operator asks for something else through\n * `OPENEXPERT_HOST` (or a `HOST` of their own).\n */\nexport function localServerEnv(env: NodeJS.ProcessEnv = process.env): Record<string, string> {\n return { HOST: env[\"OPENEXPERT_HOST\"] || env[\"HOST\"] || \"127.0.0.1\" };\n}\n\nexport async function httpGetJson<T>(\n url: string,\n opts: { headers?: Record<string, string>; timeoutMs?: number } = {},\n): Promise<T | null> {\n try {\n const res = await fetch(url, {\n ...(opts.headers ? { headers: opts.headers } : {}),\n signal: AbortSignal.timeout(opts.timeoutMs ?? 2500),\n });\n if (!res.ok) return null;\n return (await res.json()) as T;\n } catch {\n return null;\n }\n}\n\nexport async function detectOllama(\n baseUrl = DEFAULT_OLLAMA_URL,\n timeoutMs = 1500,\n): Promise<{ up: boolean; models: string[] }> {\n const root = ollamaApiBaseUrl(baseUrl);\n const data = await httpGetJson<{ models?: { name: string }[] }>(`${root}/api/tags`, {\n timeoutMs,\n });\n if (!data) return { up: false, models: [] };\n return { up: true, models: (data.models ?? []).map((m) => m.name) };\n}\n\nexport function appCacheDir(version: string): string {\n return join(expandHome(DEFAULT_DATA_DIR), \"app\", version);\n}\n\n/**\n * Ensure a standalone server build exists for `version`. Downloads the\n * release tarball published by the CI workflow and verifies its checksum.\n * Returns the directory containing `.output/server/index.mjs`, or null.\n */\nexport async function ensureStandaloneApp(\n version: string,\n opts: { repo?: string; log?: (m: string) => void } = {},\n): Promise<string | null> {\n const dir = appCacheDir(version);\n const entry = join(dir, \".output\", \"server\", \"index.mjs\");\n if (existsSync(entry)) return dir;\n\n const repo = opts.repo ?? \"OpenExpert-ai/OpenExpert\";\n const log = opts.log ?? (() => {});\n const tarball = `openexpert-server-${version}.tar.gz`;\n // Different tools tag releases differently; try the common shapes.\n const tags = [`v${version}`, `@openexpert/opencore@${version}`, version];\n\n for (const tag of tags) {\n const base = `https://github.com/${repo}/releases/download/${tag}`;\n const url = `${base}/${tarball}`;\n log(`downloading ${url}`);\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(60_000) });\n if (!res.ok) continue;\n const buf = Buffer.from(await res.arrayBuffer());\n\n // Verify checksum when the .sha256 asset is available.\n try {\n const shaRes = await fetch(`${base}/${tarball}.sha256`, {\n signal: AbortSignal.timeout(15_000),\n });\n if (shaRes.ok) {\n const expected = (await shaRes.text()).trim().split(/\\s+/)[0];\n const actual = createHash(\"sha256\").update(buf).digest(\"hex\");\n if (expected && expected !== actual) {\n log(\"checksum mismatch, aborting\");\n return null;\n }\n }\n } catch {\n // No checksum asset: continue (release is fetched over HTTPS).\n }\n\n mkdirSync(dir, { recursive: true });\n const tmp = join(dir, tarball);\n writeFileSync(tmp, buf);\n const r = spawnSync(\"tar\", [\"-xzf\", tmp, \"-C\", dir], { stdio: \"ignore\" });\n if (r.status !== 0) return null;\n return existsSync(entry) ? dir : null;\n } catch {\n // try the next tag\n }\n }\n return null;\n}\n\nexport function run(\n cmd: string,\n args: string[],\n opts: { cwd?: string; env?: NodeJS.ProcessEnv } = {},\n): Promise<number> {\n return new Promise((res) => {\n const child = spawn(cmd, args, { stdio: \"inherit\", cwd: opts.cwd, env: opts.env });\n const forward = (sig: NodeJS.Signals) => () => child.kill(sig);\n process.on(\"SIGINT\", forward(\"SIGINT\"));\n process.on(\"SIGTERM\", forward(\"SIGTERM\"));\n child.on(\"exit\", (code) => res(code ?? 0));\n });\n}\n","// SPDX-License-Identifier: MIT\n// Non-interactive CLI commands: doctor, fix, models, serve, version, update.\n\nimport { existsSync, mkdirSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { ollamaApiBaseUrl } from \"../model-provider.js\";\nimport {\n DEFAULT_OLLAMA_URL,\n appCacheDir,\n detectOllama,\n ensureStandaloneApp,\n findRepoRoot,\n httpGetJson,\n localServerEnv,\n packageVersion,\n readConfig,\n readSecrets,\n resolvedDataDir,\n run,\n secretsAsEnv,\n writeConfig,\n type Provider,\n} from \"./lib.js\";\n\nexport function cmdVersion(): number {\n console.log(`OpenExpert ${packageVersion()}`);\n return 0;\n}\n\n// Takes the directory the command was given: reading the config from\n// process.cwd() made `doctor` and `models` ignore an `openexpert.json` in the\n// directory they were pointed at, while everything around them honoured it.\nfunction configuredProvider(cwd = process.cwd()): Provider {\n const raw = process.env[\"OPENEXPERT_MODEL_PROVIDER\"] || readConfig(cwd).modelProvider;\n if (raw === \"ollama\" || raw === \"openai-compatible\") return raw;\n return \"google\";\n}\n\nexport function cmdDoctor(cwd = process.cwd()): number {\n const provider = configuredProvider(cwd);\n const dataDir = resolvedDataDir(cwd);\n const secrets = readSecrets(cwd);\n\n console.log(\"OpenExpert doctor\");\n console.log(` provider: ${provider}`);\n console.log(` data dir: ${dataDir}`);\n\n const problems: string[] = [];\n\n if (provider === \"google\" && !process.env[\"GOOGLE_API_KEY\"] && !secrets.GOOGLE_API_KEY) {\n problems.push(\"Falta GOOGLE_API_KEY. Ejecuta `openexpert init` o usa Ollama.\");\n }\n if (provider === \"openai-compatible\") {\n if (!process.env[\"OPENEXPERT_MODEL_KEY\"] && !secrets.OPENEXPERT_MODEL_KEY) {\n problems.push(\"Falta OPENEXPERT_MODEL_KEY.\");\n }\n if (!process.env[\"OPENEXPERT_BASE_URL\"] && !secrets.OPENEXPERT_BASE_URL) {\n problems.push(\"Falta OPENEXPERT_BASE_URL.\");\n }\n }\n if (provider === \"ollama\") {\n console.log(` ollama: ${process.env[\"OLLAMA_BASE_URL\"] || DEFAULT_OLLAMA_URL}`);\n }\n if (!process.env[\"GOOGLE_CLIENT_ID\"] && !secrets.GOOGLE_CLIENT_ID) {\n console.log(\" note: sin Google OAuth el chat funciona; Drive queda desconectado.\");\n }\n try {\n mkdirSync(dataDir, { recursive: true });\n } catch {\n problems.push(`No se puede escribir en ${dataDir}.`);\n }\n\n if (!problems.length) {\n console.log(\" OK: listo para arrancar.\");\n return 0;\n }\n console.log(\" pendiente:\");\n for (const x of problems) console.log(` - ${x}`);\n return 1;\n}\n\nexport async function cmdFix(cwd = process.cwd()): Promise<number> {\n const dataDir = resolvedDataDir(cwd);\n mkdirSync(dataDir, { recursive: true });\n\n if (!existsSync(join(cwd, \"openexpert.json\"))) {\n const ollama = await detectOllama();\n writeConfig(\n {\n modelProvider: \"ollama\",\n modelId: ollama.models[0] ?? \"llama3.1\",\n ollamaBaseUrl: DEFAULT_OLLAMA_URL,\n dataDir: \"~/.openexpert\",\n },\n cwd,\n );\n console.log(\"✓ openexpert.json creado (Ollama).\");\n } else {\n console.log(\"• openexpert.json ya existe.\");\n }\n\n const secrets = readSecrets(cwd);\n if (Object.keys(secrets).length) {\n const p = join(dataDir, \"secrets.json\");\n writeFileSync(p, JSON.stringify(secrets, null, 2), { mode: 0o600 });\n console.log(`✓ permisos de ${p} normalizados (0600).`);\n }\n\n console.log(\"✓ directorio de datos listo:\", dataDir);\n cmdDoctor(cwd);\n console.log(\"\\nSiguiente paso: `openexpert serve` (o `openexpert init` para elegir modelo).\");\n return 0;\n}\n\nexport async function cmdModels(cwd = process.cwd()): Promise<number> {\n const provider = configuredProvider(cwd);\n const secrets = readSecrets(cwd);\n const cfg = readConfig(cwd);\n\n if (provider === \"ollama\") {\n const base = ollamaApiBaseUrl(\n process.env[\"OLLAMA_BASE_URL\"] || cfg.ollamaBaseUrl || DEFAULT_OLLAMA_URL,\n );\n const { up, models } = await detectOllama(base);\n if (!up) {\n console.log(`No se puede contactar con Ollama en ${base}.`);\n console.log(\"Instálalo con: curl -fsSL https://ollama.com/install.sh | sh\");\n return 1;\n }\n console.log(`Modelos en Ollama (${base}):`);\n for (const m of models) console.log(` ${m === cfg.modelId ? \"*\" : \" \"} ${m}`);\n if (!models.length) console.log(\" (ninguno; prueba `ollama pull llama3.1`)\");\n return 0;\n }\n\n if (provider === \"openai-compatible\") {\n const base = process.env[\"OPENEXPERT_BASE_URL\"] || secrets.OPENEXPERT_BASE_URL;\n const key = process.env[\"OPENEXPERT_MODEL_KEY\"] || secrets.OPENEXPERT_MODEL_KEY;\n if (!base || !key) {\n console.log(\"Configura OPENEXPERT_BASE_URL y OPENEXPERT_MODEL_KEY (openexpert init).\");\n return 1;\n }\n const data = await httpGetJson<{ data?: { id: string }[] }>(\n `${base.replace(/\\/+$/, \"\")}/models`,\n { headers: { Authorization: `Bearer ${key}` } },\n );\n if (!data) {\n console.log(\"No se pudo listar modelos del endpoint.\");\n return 1;\n }\n console.log(\"Modelos disponibles:\");\n for (const m of data.data ?? []) console.log(` ${m.id === cfg.modelId ? \"*\" : \" \"} ${m.id}`);\n return 0;\n }\n\n console.log(\"Proveedor google: usa `gemini-2.5-flash` o consulta Google AI Studio.\");\n return 0;\n}\n\nexport async function cmdServe(cwd = process.cwd()): Promise<number> {\n const env = { ...process.env, ...secretsAsEnv(cwd) } as NodeJS.ProcessEnv;\n\n const repo = findRepoRoot(cwd);\n if (repo) {\n console.log(\"Arrancando OpenExpert (desde el código fuente)…\");\n return run(\"npm\", [\"run\", \"dev\"], { cwd: repo, env });\n }\n\n const version = packageVersion();\n const app = await ensureStandaloneApp(version, { log: (m) => console.log(` ${m}`) });\n if (!app) {\n console.error(\n \"No hay un servidor preconstruido disponible y no se pudo descargar.\\n\" +\n \"Opciones:\\n\" +\n \" • Usa Docker: docker run -p 3000:3000 -v openexpert-data:/data ghcr.io/openexpert-ai/openexpert:local\\n\" +\n \" • O clona el repositorio y ejecuta `npm ci && npm run dev`.\",\n );\n return 1;\n }\n const serverEnv: Record<string, string> = {\n ...(env as Record<string, string>),\n ...localServerEnv(),\n PORT: process.env[\"PORT\"] || \"3000\",\n };\n // Print constants, never values read from the environment: CodeQL flags an\n // environment value written to a log as clear-text logging of sensitive data.\n const customEndpoint = serverEnv[\"HOST\"] !== \"127.0.0.1\" || serverEnv[\"PORT\"] !== \"3000\";\n console.log(\n \"Arrancando OpenExpert en http://127.0.0.1:3000 …\" +\n (customEndpoint ? \" (host o puerto personalizados: mira OPENEXPERT_HOST y PORT)\" : \"\"),\n );\n return run(process.execPath, [join(app, \".output\", \"server\", \"index.mjs\")], {\n cwd: app,\n env: serverEnv,\n });\n}\n\nexport function cmdUpdate(): number {\n const dir = appCacheDir(packageVersion());\n if (existsSync(dir)) {\n rmSync(dir, { recursive: true, force: true });\n console.log(`✓ caché del servidor eliminada (${dir}).`);\n } else {\n console.log(\"No hay servidor descargado que actualizar.\");\n }\n return 0;\n}\n","// SPDX-License-Identifier: MIT\n// `opencore desktop` — start the local server (unless it is already running)\n// and open the native OpenExpert window (the Tauri shell). This command owns\n// the server lifecycle so it can stop the server when the window closes.\n//\n// The repository is located from the CLI installation itself (not the current\n// directory), so `OpenExpert` works from anywhere.\n\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport { existsSync, readdirSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n ensureStandaloneApp,\n findRepoRoot,\n localServerEnv,\n packageRoot,\n packageVersion,\n secretsAsEnv,\n} from \"./lib.js\";\n\nfunction port(): string {\n return process.env[\"PORT\"] || \"3000\";\n}\n\nfunction appUrl(): string {\n return `http://localhost:${port()}`;\n}\n\n/**\n * Does this HTTP response come from the OpenExpert server? Every response\n * carries `x-request-id` (src/server.ts) and the security headers\n * (src/lib/http.server.ts). Any other app squatting the port (an unrelated\n * Express service, a dev server…) must NOT be reused: the window always points\n * at http://localhost:3000 and would show that other app instead (\"Cannot GET /\").\n */\nexport function looksLikeOpenExpert(status: number, headers: Headers): boolean {\n return status < 500 && headers.has(\"x-request-id\") && headers.has(\"x-frame-options\");\n}\n\ntype ServerProbe = \"openexpert\" | \"other\" | \"none\";\n\n/** Classify whatever is listening on the app port (if anything). */\nasync function probeServer(timeoutMs = 1500): Promise<ServerProbe> {\n try {\n const res = await fetch(appUrl(), {\n signal: AbortSignal.timeout(timeoutMs),\n redirect: \"manual\",\n });\n return looksLikeOpenExpert(res.status, res.headers) ? \"openexpert\" : \"other\";\n } catch {\n return \"none\";\n }\n}\n\nasync function waitForServer(timeoutMs = 120_000): Promise<boolean> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n if ((await probeServer()) === \"openexpert\") return true;\n await new Promise((r) => setTimeout(r, 400));\n }\n return false;\n}\n\n/**\n * Find the source checkout, in order of confidence:\n * 1. OPENEXPERT_DESKTOP_REPO (baked by the installer),\n * 2. relative to the CLI installation (packages/opencore → repo root),\n * 3. walking up from the current directory.\n */\nfunction resolveRepo(cwd: string): string | null {\n const fromEnv = process.env[\"OPENEXPERT_DESKTOP_REPO\"];\n if (fromEnv) {\n const r = findRepoRoot(fromEnv);\n if (r) return r;\n }\n return findRepoRoot(packageRoot()) ?? findRepoRoot(cwd);\n}\n\n/** Newest modification time (ms) under a directory, or 0 if unreadable. */\nfunction newestMtime(dir: string): number {\n let newest = 0;\n try {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) newest = Math.max(newest, newestMtime(full));\n else if (entry.isFile()) newest = Math.max(newest, statSync(full).mtimeMs);\n }\n } catch {\n // ignore unreadable paths\n }\n return newest;\n}\n\n/** True when the production build exists and is not older than src/. */\nfunction buildIsFresh(repo: string): boolean {\n const built = join(repo, \".output\", \"server\", \"index.mjs\");\n if (!existsSync(built)) return false;\n try {\n return statSync(built).mtimeMs >= newestMtime(join(repo, \"src\"));\n } catch {\n return true;\n }\n}\n\n/** Start the local server, reusing the source checkout when available. */\nasync function spawnServer(cwd: string, env: NodeJS.ProcessEnv): Promise<ChildProcess | null> {\n const repo = resolveRepo(cwd);\n if (repo) {\n const built = join(repo, \".output\", \"server\", \"index.mjs\");\n if (existsSync(built) && buildIsFresh(repo)) {\n return spawn(process.execPath, [built], {\n cwd: repo,\n env: { ...env, ...localServerEnv(), NODE_ENV: \"production\", PORT: port() },\n stdio: \"inherit\",\n });\n }\n console.log(\n existsSync(built)\n ? \"La build está desactualizada; arrancando en modo desarrollo…\"\n : \"Arrancando el servidor en modo desarrollo (no hay build)…\",\n );\n return spawn(\"npm\", [\"run\", \"dev\"], {\n cwd: repo,\n env: { ...env, ...localServerEnv() },\n stdio: \"inherit\",\n });\n }\n\n const app = await ensureStandaloneApp(packageVersion(), { log: (m) => console.log(` ${m}`) });\n if (!app) return null;\n return spawn(process.execPath, [join(app, \".output\", \"server\", \"index.mjs\")], {\n cwd: app,\n env: { ...env, ...localServerEnv(), NODE_ENV: \"production\", PORT: port() },\n stdio: \"inherit\",\n });\n}\n\nfunction findDesktopBinary(cwd: string): string | null {\n const fromEnv = process.env[\"OPENEXPERT_DESKTOP_BIN\"];\n if (fromEnv && existsSync(fromEnv)) return fromEnv;\n\n const candidates = [join(homedir(), \".local\", \"bin\", \"openexpert-desktop\")];\n const repo = resolveRepo(cwd);\n if (repo) {\n candidates.push(\n join(repo, \"src-tauri\", \"target\", \"release\", \"openexpert-desktop\"),\n join(repo, \"src-tauri\", \"target\", \"debug\", \"openexpert-desktop\"),\n );\n }\n return candidates.find((c) => existsSync(c)) ?? null;\n}\n\nexport async function cmdDesktop(cwd = process.cwd()): Promise<number> {\n const env = { ...process.env, ...secretsAsEnv(cwd) } as NodeJS.ProcessEnv;\n\n const bin = findDesktopBinary(cwd);\n if (!bin) {\n console.error(\n \"No se encontró la ventana de escritorio (openexpert-desktop).\\n\" +\n \"Compílala con `npx tauri build` o usa `npm run desktop:install`.\",\n );\n return 1;\n }\n\n let server: ChildProcess | null = null;\n let startedServer = false;\n\n const probe = await probeServer();\n if (probe === \"openexpert\") {\n console.log(`• Usando el servidor ya activo en ${appUrl()}`);\n } else if (probe === \"other\") {\n console.error(\n `El puerto ${port()} está ocupado por otra aplicación, no por OpenExpert.\\n` +\n `La ventana apunta siempre a ${appUrl()}, así que mostraría esa otra aplicación.\\n` +\n `Mira quién lo ocupa, libéralo (o cambia el puerto de ese programa) y vuelve a intentarlo:\\n` +\n ` ss -ltnp | grep :${port()} # o: lsof -i :${port()}\\n`,\n );\n return 1;\n } else {\n console.log(\"Arrancando el servidor local…\");\n server = await spawnServer(cwd, env);\n if (!server) {\n console.error(\n \"No se pudo arrancar el servidor.\\n\" +\n \"Si usas el código fuente, entra en el repositorio y ejecuta:\\n\" +\n \" npm ci && npm run build && npm run desktop:install\",\n );\n return 1;\n }\n startedServer = true;\n if (!(await waitForServer())) {\n console.error(\"El servidor no respondió a tiempo.\");\n server.kill(\"SIGTERM\");\n return 1;\n }\n }\n\n const stopServer = (): void => {\n if (startedServer && server && !server.killed) {\n try {\n server.kill(\"SIGTERM\");\n } catch {\n // already gone\n }\n }\n };\n\n const onSignal = (): void => {\n stopServer();\n process.exit(0);\n };\n process.once(\"SIGINT\", onSignal);\n process.once(\"SIGTERM\", onSignal);\n\n console.log(\"Abriendo la ventana de OpenExpert…\");\n const code = await new Promise<number>((resolve) => {\n const win = spawn(bin, [], { stdio: \"inherit\", env });\n win.on(\"exit\", (c) => resolve(c ?? 0));\n });\n\n stopServer();\n return code;\n}\n","// SPDX-License-Identifier: MIT\n// Interactive setup wizard: pick a model provider, write config + secrets.\n\nimport * as p from \"@clack/prompts\";\nimport {\n DEFAULT_OLLAMA_URL,\n detectOllama,\n readConfig,\n writeConfig,\n writeSecrets,\n type FileConfig,\n type Provider,\n type Secrets,\n} from \"./lib.js\";\n\nexport type WizardResult = {\n config: FileConfig;\n secrets: Secrets;\n provider: Provider;\n modelId: string;\n started: boolean;\n};\n\nfunction bail(): never {\n p.cancel(\"Configuración cancelada.\");\n process.exit(1);\n}\n\nexport async function runWizard(\n opts: { cwd?: string; assumeYes?: boolean } = {},\n): Promise<WizardResult> {\n const cwd = opts.cwd ?? process.cwd();\n const existing = readConfig(cwd);\n const nonInteractive = opts.assumeYes || !process.stdin.isTTY;\n\n if (nonInteractive) {\n const ollama = await detectOllama();\n const config: FileConfig = {\n modelProvider: \"ollama\",\n modelId: ollama.models[0] ?? \"llama3.1\",\n ollamaBaseUrl: DEFAULT_OLLAMA_URL,\n dataDir: \"~/.openexpert\",\n };\n writeConfig(config, cwd);\n return { config, secrets: {}, provider: \"ollama\", modelId: config.modelId!, started: false };\n }\n\n p.intro(\"OpenExpert — configuración\");\n\n const spin = p.spinner();\n spin.start(\"Buscando Ollama local…\");\n const ollama = await detectOllama();\n spin.stop(\n ollama.up\n ? `Ollama detectado (${ollama.models.length} modelo(s))`\n : \"No se ha detectado Ollama\",\n );\n\n const choice = await p.select({\n message: \"¿Qué modelo quieres usar?\",\n options: [\n ...(ollama.up\n ? [\n {\n value: \"ollama\" as const,\n label: \"Modelo local (Ollama)\",\n hint: \"sin claves, datos en tu equipo\",\n },\n ]\n : []),\n { value: \"google\" as const, label: \"Google Gemini (clave gratuita)\", hint: \"AI Studio\" },\n { value: \"openai-compatible\" as const, label: \"Otro endpoint compatible\", hint: \"BYOK\" },\n ...(!ollama.up\n ? [{ value: \"install-ollama\" as const, label: \"Instalar Ollama\", hint: \"te muestro cómo\" }]\n : []),\n ],\n });\n if (p.isCancel(choice)) bail();\n\n if (choice === \"install-ollama\") {\n p.note(\n \"Instala Ollama y descarga un modelo:\\n\\n\" +\n \" curl -fsSL https://ollama.com/install.sh | sh\\n\" +\n \" ollama pull llama3.1\\n\\n\" +\n \"Vuelve a ejecutar `openexpert init` cuando esté listo.\",\n \"Instalar Ollama\",\n );\n p.outro(\"Sin cambios.\");\n process.exit(0);\n }\n\n const config: FileConfig = { dataDir: \"~/.openexpert\" };\n const secrets: Secrets = {};\n let modelId = \"llama3.1\";\n\n if (choice === \"ollama\") {\n const models = ollama.models.length ? ollama.models : [\"llama3.1\"];\n const picked = await p.select({\n message: \"Modelo de Ollama\",\n options: models.map((m) => ({ value: m, label: m })),\n });\n if (p.isCancel(picked)) bail();\n modelId = String(picked);\n config.modelProvider = \"ollama\";\n config.modelId = modelId;\n config.ollamaBaseUrl = DEFAULT_OLLAMA_URL;\n } else if (choice === \"google\") {\n const apiKey = await p.password({ message: \"GOOGLE_API_KEY (AI Studio)\" });\n if (p.isCancel(apiKey)) bail();\n const id = await p.text({ message: \"Modelo\", initialValue: \"gemini-2.5-flash\" });\n if (p.isCancel(id)) bail();\n modelId = String(id) || \"gemini-2.5-flash\";\n config.modelProvider = \"google\";\n config.modelId = modelId;\n secrets.GOOGLE_API_KEY = String(apiKey);\n } else if (choice === \"openai-compatible\") {\n const baseURL = await p.text({ message: \"Base URL (…/v1)\", initialValue: \"\" });\n if (p.isCancel(baseURL)) bail();\n const apiKey = await p.password({ message: \"API key\" });\n if (p.isCancel(apiKey)) bail();\n const id = await p.text({ message: \"Modelo\", initialValue: \"gpt-4o-mini\" });\n if (p.isCancel(id)) bail();\n modelId = String(id) || \"gpt-4o-mini\";\n config.modelProvider = \"openai-compatible\";\n config.modelId = modelId;\n secrets.OPENEXPERT_BASE_URL = String(baseURL);\n secrets.OPENEXPERT_MODEL_KEY = String(apiKey);\n }\n\n writeConfig({ ...existing, ...config }, cwd);\n if (Object.keys(secrets).length) writeSecrets({ ...secrets }, cwd);\n\n p.outro(\n `Configuración guardada en openexpert.json (modelo: ${config.modelProvider}:${modelId}).`,\n );\n\n const go = await p.confirm({ message: \"¿Arrancar ahora?\", initialValue: true });\n if (p.isCancel(go)) bail();\n\n return {\n config: { ...existing, ...config },\n secrets,\n provider: config.modelProvider as Provider,\n modelId,\n started: Boolean(go),\n };\n}\n","// SPDX-License-Identifier: MIT\n// OpenCore CLI entry point. Dispatches subcommands. See `run`.\n\nimport { cmdDoctor, cmdFix, cmdModels, cmdServe, cmdUpdate, cmdVersion } from \"./cli/commands.js\";\nimport { cmdDesktop } from \"./cli/desktop.js\";\nimport { runWizard } from \"./cli/wizard.js\";\n\nfunction help(): void {\n console.log(`OpenExpert — motor local\n\nUso:\n openexpert asistente de configuración y arranque\n openexpert init igual que sin argumentos\n openexpert serve arranca la edición local en http://localhost:3000\n openexpert desktop arranca el servidor y abre la ventana de escritorio\n openexpert doctor revisa tu configuración\n openexpert fix auto-configura lo que falte\n openexpert models lista los modelos del proveedor actual\n openexpert update limpia la caché del servidor descargado\n openexpert version muestra la versión\n\nEjemplos:\n npx @openexpert/opencore\n npx @openexpert/opencore serve\n\nDocker (sin Node):\n docker run -p 3000:3000 -v openexpert-data:/data ghcr.io/openexpert-ai/openexpert`);\n}\n\nexport async function run(argv: string[]): Promise<void> {\n const cmd = argv[0] ?? \"\";\n switch (cmd) {\n case \"\":\n case \"init\":\n case \"setup\": {\n const res = await runWizard({});\n if (res.started) process.exit(await cmdServe());\n return;\n }\n case \"doctor\":\n return process.exit(cmdDoctor());\n case \"fix\":\n return process.exit(await cmdFix());\n case \"models\":\n return process.exit(await cmdModels());\n case \"serve\":\n case \"dev\":\n return process.exit(await cmdServe());\n case \"desktop\":\n case \"app\": {\n const code = await cmdDesktop();\n return process.exit(code);\n }\n case \"update\":\n return process.exit(cmdUpdate());\n case \"version\":\n case \"--version\":\n case \"-v\":\n return process.exit(cmdVersion());\n case \"help\":\n case \"--help\":\n case \"-h\":\n help();\n return;\n default:\n console.error(`Comando desconocido: ${cmd}\\n`);\n help();\n return process.exit(1);\n }\n}\n"]}
|
package/dist/config.d.ts
CHANGED
|
@@ -16,6 +16,16 @@ type OpenExpertConfig = {
|
|
|
16
16
|
modelId: string;
|
|
17
17
|
ollamaBaseUrl: string;
|
|
18
18
|
dataDir: string;
|
|
19
|
+
/**
|
|
20
|
+
* Google OAuth client ID (non-secret public identifier) for the Drive
|
|
21
|
+
* integration. The secret lives in env or ~/.openexpert/secrets.json.
|
|
22
|
+
*/
|
|
23
|
+
googleClientId: string;
|
|
24
|
+
/**
|
|
25
|
+
* Notion OAuth client ID (non-secret public identifier) for the Notion
|
|
26
|
+
* connection. The secret lives in env or ~/.openexpert/secrets.json.
|
|
27
|
+
*/
|
|
28
|
+
notionClientId: string;
|
|
19
29
|
ai: AiConfig;
|
|
20
30
|
chat: ChatConfig;
|
|
21
31
|
};
|
package/dist/config.js
CHANGED
|
@@ -7,7 +7,9 @@ var DEFAULTS = {
|
|
|
7
7
|
modelId: "gemini-2.5-flash",
|
|
8
8
|
ollamaBaseUrl: "http://localhost:11434",
|
|
9
9
|
dataDir: "~/.openexpert",
|
|
10
|
-
|
|
10
|
+
googleClientId: "",
|
|
11
|
+
notionClientId: "",
|
|
12
|
+
ai: { temperature: 0.5, topP: 1, maxOutputTokens: 4096 },
|
|
11
13
|
chat: {
|
|
12
14
|
maxSteps: 50,
|
|
13
15
|
injectionGuard: true,
|
|
@@ -46,6 +48,8 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
46
48
|
modelId: env["OPENEXPERT_MODEL_ID"] ?? file.modelId ?? DEFAULTS.modelId,
|
|
47
49
|
ollamaBaseUrl: env["OLLAMA_BASE_URL"] ?? file.ollamaBaseUrl ?? DEFAULTS.ollamaBaseUrl,
|
|
48
50
|
dataDir: expandHome(env["OPENEXPERT_DATA_DIR"] ?? file.dataDir ?? DEFAULTS.dataDir),
|
|
51
|
+
googleClientId: env["OPENEXPERT_GOOGLE_CLIENT_ID"] ?? file.googleClientId ?? DEFAULTS.googleClientId,
|
|
52
|
+
notionClientId: env["OPENEXPERT_NOTION_CLIENT_ID"] ?? file.notionClientId ?? DEFAULTS.notionClientId,
|
|
49
53
|
ai: {
|
|
50
54
|
temperature: num(fileAi.temperature, DEFAULTS.ai.temperature),
|
|
51
55
|
topP: num(fileAi.topP, DEFAULTS.ai.topP),
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts"],"names":[],"mappings":";;;;
|
|
1
|
+
{"version":3,"sources":["../src/config.ts"],"names":[],"mappings":"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","file":"config.js","sourcesContent":["// SPDX-License-Identifier: MIT\n// OpenCore — local configuration.\n// Precedence: OPENEXPERT_* env vars > openexpert.json > defaults.\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ModelProviderId } from \"./model-provider.js\";\n\nexport type AiConfig = {\n temperature: number;\n topP: number;\n maxOutputTokens: number;\n};\n\nexport type ChatConfig = {\n maxSteps: number;\n injectionGuard: boolean;\n injectionExtraPatterns: string[];\n retentionDays: number;\n};\n\nexport type OpenExpertConfig = {\n modelProvider: ModelProviderId;\n modelId: string;\n ollamaBaseUrl: string;\n dataDir: string;\n /**\n * Google OAuth client ID (non-secret public identifier) for the Drive\n * integration. The secret lives in env or ~/.openexpert/secrets.json.\n */\n googleClientId: string;\n /**\n * Notion OAuth client ID (non-secret public identifier) for the Notion\n * connection. The secret lives in env or ~/.openexpert/secrets.json.\n */\n notionClientId: string;\n ai: AiConfig;\n chat: ChatConfig;\n};\n\nexport const DEFAULTS: OpenExpertConfig = {\n modelProvider: \"google\",\n modelId: \"gemini-2.5-flash\",\n ollamaBaseUrl: \"http://localhost:11434\",\n dataDir: \"~/.openexpert\",\n googleClientId: \"\",\n notionClientId: \"\",\n ai: { temperature: 0.5, topP: 1, maxOutputTokens: 4096 },\n chat: {\n maxSteps: 50,\n injectionGuard: true,\n injectionExtraPatterns: [],\n retentionDays: 30,\n },\n};\n\nfunction expandHome(p: string): string {\n if (p.startsWith(\"~/\")) {\n const home = process.env[\"HOME\"] || process.env[\"USERPROFILE\"] || \"~\";\n return join(home, p.slice(2));\n }\n return p;\n}\n\nfunction num(value: unknown, fallback: number): number {\n const n = typeof value === \"string\" ? Number(value) : value;\n return typeof n === \"number\" && Number.isFinite(n) ? n : fallback;\n}\n\nexport function loadConfig(cwd: string = process.cwd()): OpenExpertConfig {\n let file: Partial<OpenExpertConfig> = {};\n const path = join(cwd, \"openexpert.json\");\n if (existsSync(path)) {\n try {\n file = JSON.parse(readFileSync(path, \"utf8\")) as Partial<OpenExpertConfig>;\n } catch {\n // Broken file: ignore it, env/defaults win.\n file = {};\n }\n }\n const env = process.env;\n const providerEnv = env[\"OPENEXPERT_MODEL_PROVIDER\"];\n const providerFromEnv: ModelProviderId | undefined =\n providerEnv === \"google\" || providerEnv === \"ollama\" || providerEnv === \"openai-compatible\"\n ? providerEnv\n : undefined;\n\n const fileAi: Partial<AiConfig> = file.ai ?? {};\n const fileChat: Partial<ChatConfig> = file.chat ?? {};\n\n return {\n modelProvider: providerFromEnv ?? file.modelProvider ?? DEFAULTS.modelProvider,\n modelId: env[\"OPENEXPERT_MODEL_ID\"] ?? file.modelId ?? DEFAULTS.modelId,\n ollamaBaseUrl: env[\"OLLAMA_BASE_URL\"] ?? file.ollamaBaseUrl ?? DEFAULTS.ollamaBaseUrl,\n dataDir: expandHome(env[\"OPENEXPERT_DATA_DIR\"] ?? file.dataDir ?? DEFAULTS.dataDir),\n googleClientId:\n env[\"OPENEXPERT_GOOGLE_CLIENT_ID\"] ?? file.googleClientId ?? DEFAULTS.googleClientId,\n notionClientId:\n env[\"OPENEXPERT_NOTION_CLIENT_ID\"] ?? file.notionClientId ?? DEFAULTS.notionClientId,\n ai: {\n temperature: num(fileAi.temperature, DEFAULTS.ai.temperature),\n topP: num(fileAi.topP, DEFAULTS.ai.topP),\n maxOutputTokens: num(fileAi.maxOutputTokens, DEFAULTS.ai.maxOutputTokens),\n },\n chat: {\n maxSteps: num(fileChat.maxSteps, DEFAULTS.chat.maxSteps),\n injectionGuard: fileChat.injectionGuard ?? DEFAULTS.chat.injectionGuard,\n injectionExtraPatterns: Array.isArray(fileChat.injectionExtraPatterns)\n ? fileChat.injectionExtraPatterns.filter((p): p is string => typeof p === \"string\")\n : DEFAULTS.chat.injectionExtraPatterns,\n retentionDays: num(fileChat.retentionDays, DEFAULTS.chat.retentionDays),\n },\n };\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,9 @@ var DEFAULTS = {
|
|
|
7
7
|
modelId: "gemini-2.5-flash",
|
|
8
8
|
ollamaBaseUrl: "http://localhost:11434",
|
|
9
9
|
dataDir: "~/.openexpert",
|
|
10
|
-
|
|
10
|
+
googleClientId: "",
|
|
11
|
+
notionClientId: "",
|
|
12
|
+
ai: { temperature: 0.5, topP: 1, maxOutputTokens: 4096 },
|
|
11
13
|
chat: {
|
|
12
14
|
maxSteps: 50,
|
|
13
15
|
injectionGuard: true,
|
|
@@ -46,6 +48,8 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
46
48
|
modelId: env["OPENEXPERT_MODEL_ID"] ?? file.modelId ?? DEFAULTS.modelId,
|
|
47
49
|
ollamaBaseUrl: env["OLLAMA_BASE_URL"] ?? file.ollamaBaseUrl ?? DEFAULTS.ollamaBaseUrl,
|
|
48
50
|
dataDir: expandHome(env["OPENEXPERT_DATA_DIR"] ?? file.dataDir ?? DEFAULTS.dataDir),
|
|
51
|
+
googleClientId: env["OPENEXPERT_GOOGLE_CLIENT_ID"] ?? file.googleClientId ?? DEFAULTS.googleClientId,
|
|
52
|
+
notionClientId: env["OPENEXPERT_NOTION_CLIENT_ID"] ?? file.notionClientId ?? DEFAULTS.notionClientId,
|
|
49
53
|
ai: {
|
|
50
54
|
temperature: num(fileAi.temperature, DEFAULTS.ai.temperature),
|
|
51
55
|
topP: num(fileAi.topP, DEFAULTS.ai.topP),
|
|
@@ -84,8 +88,14 @@ function selectModel(env = process.env) {
|
|
|
84
88
|
keySource: "GOOGLE_API_KEY"
|
|
85
89
|
};
|
|
86
90
|
}
|
|
91
|
+
function stripTrailingSlashes(value) {
|
|
92
|
+
let end = value.length;
|
|
93
|
+
while (end > 0 && value[end - 1] === "/") end -= 1;
|
|
94
|
+
return value.slice(0, end);
|
|
95
|
+
}
|
|
87
96
|
function ollamaApiBaseUrl(baseUrl) {
|
|
88
|
-
|
|
97
|
+
const base = stripTrailingSlashes(baseUrl.trim());
|
|
98
|
+
return base.endsWith("/v1") ? base.slice(0, -3) : base;
|
|
89
99
|
}
|
|
90
100
|
function ollamaChatBaseUrl(baseUrl) {
|
|
91
101
|
return `${ollamaApiBaseUrl(baseUrl)}/v1`;
|
|
@@ -105,7 +115,6 @@ function missingKeyHint(sel, env = process.env) {
|
|
|
105
115
|
var LOCAL_TABLES = [
|
|
106
116
|
"experts",
|
|
107
117
|
"integrations",
|
|
108
|
-
"processes",
|
|
109
118
|
"activity",
|
|
110
119
|
"chat_messages",
|
|
111
120
|
"deals",
|
|
@@ -128,14 +137,12 @@ var TOOL_CATALOG = [
|
|
|
128
137
|
{ name: "list_overdue_invoices", domain: "finanzas", needs: "read", proposesOnly: false },
|
|
129
138
|
{ name: "get_campaign_performance", domain: "marketing", needs: "read", proposesOnly: false },
|
|
130
139
|
{ name: "get_churn_risk", domain: "general", needs: "read", proposesOnly: false },
|
|
131
|
-
{ name: "list_processes", domain: "transversal", needs: "read", proposesOnly: false },
|
|
132
140
|
{ name: "search_drive", domain: "drive", needs: "read", proposesOnly: false },
|
|
133
141
|
{ name: "read_drive_file", domain: "drive", needs: "read", proposesOnly: false },
|
|
134
142
|
{ name: "create_drive_file", domain: "drive", needs: "exec", proposesOnly: false },
|
|
135
143
|
{ name: "update_drive_file", domain: "drive", needs: "exec", proposesOnly: false },
|
|
136
144
|
{ name: "propose_invoice_reminders", domain: "finanzas", needs: "exec", proposesOnly: true },
|
|
137
145
|
{ name: "propose_pause_campaigns", domain: "marketing", needs: "exec", proposesOnly: true },
|
|
138
|
-
{ name: "request_process_run", domain: "transversal", needs: "exec", proposesOnly: true },
|
|
139
146
|
{ name: "open_expert_form", domain: "transversal", needs: "exec", proposesOnly: false }
|
|
140
147
|
];
|
|
141
148
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/model-provider.ts","../src/storage.ts","../src/secrets.ts","../src/tools.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// SPDX-License-Identifier: MIT\n// OpenCore — local configuration.\n// Precedence: OPENEXPERT_* env vars > openexpert.json > defaults.\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ModelProviderId } from \"./model-provider.js\";\n\nexport type AiConfig = {\n temperature: number;\n topP: number;\n maxOutputTokens: number;\n};\n\nexport type ChatConfig = {\n maxSteps: number;\n injectionGuard: boolean;\n injectionExtraPatterns: string[];\n retentionDays: number;\n};\n\nexport type OpenExpertConfig = {\n modelProvider: ModelProviderId;\n modelId: string;\n ollamaBaseUrl: string;\n dataDir: string;\n ai: AiConfig;\n chat: ChatConfig;\n};\n\nexport const DEFAULTS: OpenExpertConfig = {\n modelProvider: \"google\",\n modelId: \"gemini-2.5-flash\",\n ollamaBaseUrl: \"http://localhost:11434\",\n dataDir: \"~/.openexpert\",\n ai: { temperature: 0.2, topP: 1, maxOutputTokens: 4096 },\n chat: {\n maxSteps: 50,\n injectionGuard: true,\n injectionExtraPatterns: [],\n retentionDays: 30,\n },\n};\n\nfunction expandHome(p: string): string {\n if (p.startsWith(\"~/\")) {\n const home = process.env[\"HOME\"] || process.env[\"USERPROFILE\"] || \"~\";\n return join(home, p.slice(2));\n }\n return p;\n}\n\nfunction num(value: unknown, fallback: number): number {\n const n = typeof value === \"string\" ? Number(value) : value;\n return typeof n === \"number\" && Number.isFinite(n) ? n : fallback;\n}\n\nexport function loadConfig(cwd: string = process.cwd()): OpenExpertConfig {\n let file: Partial<OpenExpertConfig> = {};\n const path = join(cwd, \"openexpert.json\");\n if (existsSync(path)) {\n try {\n file = JSON.parse(readFileSync(path, \"utf8\")) as Partial<OpenExpertConfig>;\n } catch {\n // Broken file: ignore it, env/defaults win.\n file = {};\n }\n }\n const env = process.env;\n const providerEnv = env[\"OPENEXPERT_MODEL_PROVIDER\"];\n const providerFromEnv: ModelProviderId | undefined =\n providerEnv === \"google\" || providerEnv === \"ollama\" || providerEnv === \"openai-compatible\"\n ? providerEnv\n : undefined;\n\n const fileAi: Partial<AiConfig> = file.ai ?? {};\n const fileChat: Partial<ChatConfig> = file.chat ?? {};\n\n return {\n modelProvider: providerFromEnv ?? file.modelProvider ?? DEFAULTS.modelProvider,\n modelId: env[\"OPENEXPERT_MODEL_ID\"] ?? file.modelId ?? DEFAULTS.modelId,\n ollamaBaseUrl: env[\"OLLAMA_BASE_URL\"] ?? file.ollamaBaseUrl ?? DEFAULTS.ollamaBaseUrl,\n dataDir: expandHome(env[\"OPENEXPERT_DATA_DIR\"] ?? file.dataDir ?? DEFAULTS.dataDir),\n ai: {\n temperature: num(fileAi.temperature, DEFAULTS.ai.temperature),\n topP: num(fileAi.topP, DEFAULTS.ai.topP),\n maxOutputTokens: num(fileAi.maxOutputTokens, DEFAULTS.ai.maxOutputTokens),\n },\n chat: {\n maxSteps: num(fileChat.maxSteps, DEFAULTS.chat.maxSteps),\n injectionGuard: fileChat.injectionGuard ?? DEFAULTS.chat.injectionGuard,\n injectionExtraPatterns: Array.isArray(fileChat.injectionExtraPatterns)\n ? fileChat.injectionExtraPatterns.filter((p): p is string => typeof p === \"string\")\n : DEFAULTS.chat.injectionExtraPatterns,\n retentionDays: num(fileChat.retentionDays, DEFAULTS.chat.retentionDays),\n },\n };\n}\n","// SPDX-License-Identifier: MIT\n// OpenCore — which model to use. The harness is stable; the model is configuration.\n//\n// Providers:\n// - google: Gemini via GOOGLE_API_KEY (free API key).\n// - ollama: Local model via Ollama's OpenAI-compatible endpoint.\n// - openai-compatible: Any OpenAI-compatible endpoint with your own key.\n\nexport type ModelProviderId = \"google\" | \"ollama\" | \"openai-compatible\";\n\nexport type ModelSelection = {\n provider: ModelProviderId;\n modelId: string;\n /** Where the key comes from, without exposing the value. */\n keySource: \"GOOGLE_API_KEY\" | \"OPENEXPERT_MODEL_KEY\" | \"none (local)\";\n};\n\nexport function selectModel(env: NodeJS.ProcessEnv = process.env): ModelSelection {\n const raw = env[\"OPENEXPERT_MODEL_PROVIDER\"];\n const provider: ModelProviderId =\n raw === \"ollama\" || raw === \"openai-compatible\" ? raw : \"google\";\n\n if (provider === \"ollama\") {\n return {\n provider,\n modelId: env[\"OPENEXPERT_MODEL_ID\"] || \"llama3.1\",\n keySource: \"none (local)\",\n };\n }\n if (provider === \"openai-compatible\") {\n return {\n provider,\n modelId: env[\"OPENEXPERT_MODEL_ID\"] || \"gpt-4o-mini\",\n keySource: \"OPENEXPERT_MODEL_KEY\",\n };\n }\n return {\n provider: \"google\",\n modelId: env[\"OPENEXPERT_MODEL_ID\"] || \"gemini-2.5-flash\",\n keySource: \"GOOGLE_API_KEY\",\n };\n}\n\n/**\n * Ollama exposes two surfaces: the native API at the server root (`/api/*`)\n * and an OpenAI-compatible API under `/v1`. Callers may configure either\n * shape, so normalise before using it.\n */\nexport function ollamaApiBaseUrl(baseUrl: string): string {\n return baseUrl.trim().replace(/\\/+$/, \"\").replace(/\\/v1$/, \"\");\n}\n\nexport function ollamaChatBaseUrl(baseUrl: string): string {\n return `${ollamaApiBaseUrl(baseUrl)}/v1`;\n}\n\nexport function missingKeyHint(\n sel: ModelSelection,\n env: NodeJS.ProcessEnv = process.env,\n): string | null {\n if (sel.provider === \"ollama\") return null;\n if (sel.provider === \"google\" && !env[\"GOOGLE_API_KEY\"]) {\n return \"Falta GOOGLE_API_KEY (o usa OPENEXPERT_MODEL_PROVIDER=ollama para modelo local).\";\n }\n if (sel.provider === \"openai-compatible\" && !env[\"OPENEXPERT_MODEL_KEY\"]) {\n return \"Falta OPENEXPERT_MODEL_KEY para tu endpoint OpenAI-compatible.\";\n }\n return null;\n}\n","// SPDX-License-Identifier: MIT\n// OpenCore — storage interface. OpenExpert is local-first: implementations\n// are backed by the local data directory (SQLite / files).\n\nexport type TableName =\n | \"experts\"\n | \"integrations\"\n | \"processes\"\n | \"activity\"\n | \"chat_messages\"\n | \"deals\"\n | \"invoices\"\n | \"campaigns\"\n | \"accounts\"\n | \"settings\";\n\nexport type Row = Record<string, unknown>;\n\nexport interface Storage {\n readonly kind: \"file\";\n list(table: TableName): Promise<Row[]>;\n insert(table: TableName, rows: Row | Row[]): Promise<void>;\n}\n\nexport const LOCAL_TABLES: TableName[] = [\n \"experts\",\n \"integrations\",\n \"processes\",\n \"activity\",\n \"chat_messages\",\n \"deals\",\n \"invoices\",\n \"campaigns\",\n \"accounts\",\n \"settings\",\n];\n","// SPDX-License-Identifier: MIT\n// OpenCore — where Google Drive tokens live.\n// Cloud: google_tokens table (only the server touches it).\n// Local: a file on your machine (~/.openexpert/credentials.json by\n// default), never uploaded to Git.\n\nexport type DriveTokenRow = {\n access_token: string;\n refresh_token: string | null;\n expires_at: string;\n scopes: string;\n updated_at: string;\n};\n\nexport interface TokenStore {\n readonly kind: \"file\";\n load(userId: string): Promise<DriveTokenRow | null>;\n save(userId: string, row: DriveTokenRow): Promise<void>;\n remove(userId: string): Promise<void>;\n}\n\nexport function credentialsPath(dataDir: string): string {\n const home = dataDir.startsWith(\"~/\")\n ? `${process.env[\"HOME\"] || \"~\"}/${dataDir.slice(2)}`\n : dataDir;\n return `${home.replace(/\\/$/, \"\")}/credentials.json`;\n}\n","// SPDX-License-Identifier: MIT\n// OpenCore — tool catalog (the current 14).\n// The model never touches the database directly: it only calls these\n// functions, and each one checks your permission before answering.\n\nexport type ToolDomain = \"ventas\" | \"finanzas\" | \"marketing\" | \"general\" | \"transversal\" | \"drive\";\n\nexport type ToolDef = {\n name: string;\n domain: ToolDomain;\n needs: \"read\" | \"exec\";\n proposesOnly: boolean;\n};\n\nexport const TOOL_CATALOG: ToolDef[] = [\n { name: \"get_pipeline_summary\", domain: \"ventas\", needs: \"read\", proposesOnly: false },\n { name: \"list_deals\", domain: \"ventas\", needs: \"read\", proposesOnly: false },\n { name: \"list_overdue_invoices\", domain: \"finanzas\", needs: \"read\", proposesOnly: false },\n { name: \"get_campaign_performance\", domain: \"marketing\", needs: \"read\", proposesOnly: false },\n { name: \"get_churn_risk\", domain: \"general\", needs: \"read\", proposesOnly: false },\n { name: \"list_processes\", domain: \"transversal\", needs: \"read\", proposesOnly: false },\n { name: \"search_drive\", domain: \"drive\", needs: \"read\", proposesOnly: false },\n { name: \"read_drive_file\", domain: \"drive\", needs: \"read\", proposesOnly: false },\n { name: \"create_drive_file\", domain: \"drive\", needs: \"exec\", proposesOnly: false },\n { name: \"update_drive_file\", domain: \"drive\", needs: \"exec\", proposesOnly: false },\n { name: \"propose_invoice_reminders\", domain: \"finanzas\", needs: \"exec\", proposesOnly: true },\n { name: \"propose_pause_campaigns\", domain: \"marketing\", needs: \"exec\", proposesOnly: true },\n { name: \"request_process_run\", domain: \"transversal\", needs: \"exec\", proposesOnly: true },\n { name: \"open_expert_form\", domain: \"transversal\", needs: \"exec\", proposesOnly: false },\n];\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/model-provider.ts","../src/storage.ts","../src/secrets.ts","../src/tools.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// SPDX-License-Identifier: MIT\n// OpenCore — local configuration.\n// Precedence: OPENEXPERT_* env vars > openexpert.json > defaults.\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ModelProviderId } from \"./model-provider.js\";\n\nexport type AiConfig = {\n temperature: number;\n topP: number;\n maxOutputTokens: number;\n};\n\nexport type ChatConfig = {\n maxSteps: number;\n injectionGuard: boolean;\n injectionExtraPatterns: string[];\n retentionDays: number;\n};\n\nexport type OpenExpertConfig = {\n modelProvider: ModelProviderId;\n modelId: string;\n ollamaBaseUrl: string;\n dataDir: string;\n /**\n * Google OAuth client ID (non-secret public identifier) for the Drive\n * integration. The secret lives in env or ~/.openexpert/secrets.json.\n */\n googleClientId: string;\n /**\n * Notion OAuth client ID (non-secret public identifier) for the Notion\n * connection. The secret lives in env or ~/.openexpert/secrets.json.\n */\n notionClientId: string;\n ai: AiConfig;\n chat: ChatConfig;\n};\n\nexport const DEFAULTS: OpenExpertConfig = {\n modelProvider: \"google\",\n modelId: \"gemini-2.5-flash\",\n ollamaBaseUrl: \"http://localhost:11434\",\n dataDir: \"~/.openexpert\",\n googleClientId: \"\",\n notionClientId: \"\",\n ai: { temperature: 0.5, topP: 1, maxOutputTokens: 4096 },\n chat: {\n maxSteps: 50,\n injectionGuard: true,\n injectionExtraPatterns: [],\n retentionDays: 30,\n },\n};\n\nfunction expandHome(p: string): string {\n if (p.startsWith(\"~/\")) {\n const home = process.env[\"HOME\"] || process.env[\"USERPROFILE\"] || \"~\";\n return join(home, p.slice(2));\n }\n return p;\n}\n\nfunction num(value: unknown, fallback: number): number {\n const n = typeof value === \"string\" ? Number(value) : value;\n return typeof n === \"number\" && Number.isFinite(n) ? n : fallback;\n}\n\nexport function loadConfig(cwd: string = process.cwd()): OpenExpertConfig {\n let file: Partial<OpenExpertConfig> = {};\n const path = join(cwd, \"openexpert.json\");\n if (existsSync(path)) {\n try {\n file = JSON.parse(readFileSync(path, \"utf8\")) as Partial<OpenExpertConfig>;\n } catch {\n // Broken file: ignore it, env/defaults win.\n file = {};\n }\n }\n const env = process.env;\n const providerEnv = env[\"OPENEXPERT_MODEL_PROVIDER\"];\n const providerFromEnv: ModelProviderId | undefined =\n providerEnv === \"google\" || providerEnv === \"ollama\" || providerEnv === \"openai-compatible\"\n ? providerEnv\n : undefined;\n\n const fileAi: Partial<AiConfig> = file.ai ?? {};\n const fileChat: Partial<ChatConfig> = file.chat ?? {};\n\n return {\n modelProvider: providerFromEnv ?? file.modelProvider ?? DEFAULTS.modelProvider,\n modelId: env[\"OPENEXPERT_MODEL_ID\"] ?? file.modelId ?? DEFAULTS.modelId,\n ollamaBaseUrl: env[\"OLLAMA_BASE_URL\"] ?? file.ollamaBaseUrl ?? DEFAULTS.ollamaBaseUrl,\n dataDir: expandHome(env[\"OPENEXPERT_DATA_DIR\"] ?? file.dataDir ?? DEFAULTS.dataDir),\n googleClientId:\n env[\"OPENEXPERT_GOOGLE_CLIENT_ID\"] ?? file.googleClientId ?? DEFAULTS.googleClientId,\n notionClientId:\n env[\"OPENEXPERT_NOTION_CLIENT_ID\"] ?? file.notionClientId ?? DEFAULTS.notionClientId,\n ai: {\n temperature: num(fileAi.temperature, DEFAULTS.ai.temperature),\n topP: num(fileAi.topP, DEFAULTS.ai.topP),\n maxOutputTokens: num(fileAi.maxOutputTokens, DEFAULTS.ai.maxOutputTokens),\n },\n chat: {\n maxSteps: num(fileChat.maxSteps, DEFAULTS.chat.maxSteps),\n injectionGuard: fileChat.injectionGuard ?? DEFAULTS.chat.injectionGuard,\n injectionExtraPatterns: Array.isArray(fileChat.injectionExtraPatterns)\n ? fileChat.injectionExtraPatterns.filter((p): p is string => typeof p === \"string\")\n : DEFAULTS.chat.injectionExtraPatterns,\n retentionDays: num(fileChat.retentionDays, DEFAULTS.chat.retentionDays),\n },\n };\n}\n","// SPDX-License-Identifier: MIT\n// OpenCore — which model to use. The harness is stable; the model is configuration.\n//\n// Providers:\n// - google: Gemini via GOOGLE_API_KEY (free API key).\n// - ollama: Local model via Ollama's OpenAI-compatible endpoint.\n// - openai-compatible: Any OpenAI-compatible endpoint with your own key.\n\nexport type ModelProviderId = \"google\" | \"ollama\" | \"openai-compatible\";\n\nexport type ModelSelection = {\n provider: ModelProviderId;\n modelId: string;\n /** Where the key comes from, without exposing the value. */\n keySource: \"GOOGLE_API_KEY\" | \"OPENEXPERT_MODEL_KEY\" | \"none (local)\";\n};\n\nexport function selectModel(env: NodeJS.ProcessEnv = process.env): ModelSelection {\n const raw = env[\"OPENEXPERT_MODEL_PROVIDER\"];\n const provider: ModelProviderId =\n raw === \"ollama\" || raw === \"openai-compatible\" ? raw : \"google\";\n\n if (provider === \"ollama\") {\n return {\n provider,\n modelId: env[\"OPENEXPERT_MODEL_ID\"] || \"llama3.1\",\n keySource: \"none (local)\",\n };\n }\n if (provider === \"openai-compatible\") {\n return {\n provider,\n modelId: env[\"OPENEXPERT_MODEL_ID\"] || \"gpt-4o-mini\",\n keySource: \"OPENEXPERT_MODEL_KEY\",\n };\n }\n return {\n provider: \"google\",\n modelId: env[\"OPENEXPERT_MODEL_ID\"] || \"gemini-2.5-flash\",\n keySource: \"GOOGLE_API_KEY\",\n };\n}\n\n/**\n * Ollama exposes two surfaces: the native API at the server root (`/api/*`)\n * and an OpenAI-compatible API under `/v1`. Callers may configure either\n * shape, so normalise before using it.\n *\n * Trailing slashes are removed with a loop, not with `/\\/+$/`: a regex has to\n * retry at every position of a long run of slashes, which is what CodeQL\n * reports as a polynomial ReDoS on `baseUrl`.\n */\nfunction stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value[end - 1] === \"/\") end -= 1;\n return value.slice(0, end);\n}\n\nexport function ollamaApiBaseUrl(baseUrl: string): string {\n const base = stripTrailingSlashes(baseUrl.trim());\n return base.endsWith(\"/v1\") ? base.slice(0, -3) : base;\n}\n\nexport function ollamaChatBaseUrl(baseUrl: string): string {\n return `${ollamaApiBaseUrl(baseUrl)}/v1`;\n}\n\nexport function missingKeyHint(\n sel: ModelSelection,\n env: NodeJS.ProcessEnv = process.env,\n): string | null {\n if (sel.provider === \"ollama\") return null;\n if (sel.provider === \"google\" && !env[\"GOOGLE_API_KEY\"]) {\n return \"Falta GOOGLE_API_KEY (o usa OPENEXPERT_MODEL_PROVIDER=ollama para modelo local).\";\n }\n if (sel.provider === \"openai-compatible\" && !env[\"OPENEXPERT_MODEL_KEY\"]) {\n return \"Falta OPENEXPERT_MODEL_KEY para tu endpoint OpenAI-compatible.\";\n }\n return null;\n}\n","// SPDX-License-Identifier: MIT\n// OpenCore — storage interface. OpenExpert is local-first: implementations\n// are backed by the local data directory (SQLite / files).\n\nexport type TableName =\n | \"experts\"\n | \"integrations\"\n | \"activity\"\n | \"chat_messages\"\n | \"deals\"\n | \"invoices\"\n | \"campaigns\"\n | \"accounts\"\n | \"settings\";\n\nexport type Row = Record<string, unknown>;\n\nexport interface Storage {\n readonly kind: \"file\";\n list(table: TableName): Promise<Row[]>;\n insert(table: TableName, rows: Row | Row[]): Promise<void>;\n}\n\nexport const LOCAL_TABLES: TableName[] = [\n \"experts\",\n \"integrations\",\n \"activity\",\n \"chat_messages\",\n \"deals\",\n \"invoices\",\n \"campaigns\",\n \"accounts\",\n \"settings\",\n];\n","// SPDX-License-Identifier: MIT\n// OpenCore — where Google Drive tokens live.\n// Cloud: google_tokens table (only the server touches it).\n// Local: a file on your machine (~/.openexpert/credentials.json by\n// default), never uploaded to Git.\n\nexport type DriveTokenRow = {\n access_token: string;\n refresh_token: string | null;\n expires_at: string;\n scopes: string;\n updated_at: string;\n};\n\nexport interface TokenStore {\n readonly kind: \"file\";\n load(userId: string): Promise<DriveTokenRow | null>;\n save(userId: string, row: DriveTokenRow): Promise<void>;\n remove(userId: string): Promise<void>;\n}\n\nexport function credentialsPath(dataDir: string): string {\n const home = dataDir.startsWith(\"~/\")\n ? `${process.env[\"HOME\"] || \"~\"}/${dataDir.slice(2)}`\n : dataDir;\n return `${home.replace(/\\/$/, \"\")}/credentials.json`;\n}\n","// SPDX-License-Identifier: MIT\n// OpenCore — tool catalog (the current 12).\n// The model never touches the database directly: it only calls these\n// functions, and each one checks your permission before answering.\n\nexport type ToolDomain = \"ventas\" | \"finanzas\" | \"marketing\" | \"general\" | \"transversal\" | \"drive\";\n\nexport type ToolDef = {\n name: string;\n domain: ToolDomain;\n needs: \"read\" | \"exec\";\n proposesOnly: boolean;\n};\n\nexport const TOOL_CATALOG: ToolDef[] = [\n { name: \"get_pipeline_summary\", domain: \"ventas\", needs: \"read\", proposesOnly: false },\n { name: \"list_deals\", domain: \"ventas\", needs: \"read\", proposesOnly: false },\n { name: \"list_overdue_invoices\", domain: \"finanzas\", needs: \"read\", proposesOnly: false },\n { name: \"get_campaign_performance\", domain: \"marketing\", needs: \"read\", proposesOnly: false },\n { name: \"get_churn_risk\", domain: \"general\", needs: \"read\", proposesOnly: false },\n { name: \"search_drive\", domain: \"drive\", needs: \"read\", proposesOnly: false },\n { name: \"read_drive_file\", domain: \"drive\", needs: \"read\", proposesOnly: false },\n { name: \"create_drive_file\", domain: \"drive\", needs: \"exec\", proposesOnly: false },\n { name: \"update_drive_file\", domain: \"drive\", needs: \"exec\", proposesOnly: false },\n { name: \"propose_invoice_reminders\", domain: \"finanzas\", needs: \"exec\", proposesOnly: true },\n { name: \"propose_pause_campaigns\", domain: \"marketing\", needs: \"exec\", proposesOnly: true },\n { name: \"open_expert_form\", domain: \"transversal\", needs: \"exec\", proposesOnly: false },\n];\n"]}
|
package/dist/model-provider.d.ts
CHANGED
|
@@ -6,11 +6,6 @@ type ModelSelection = {
|
|
|
6
6
|
keySource: "GOOGLE_API_KEY" | "OPENEXPERT_MODEL_KEY" | "none (local)";
|
|
7
7
|
};
|
|
8
8
|
declare function selectModel(env?: NodeJS.ProcessEnv): ModelSelection;
|
|
9
|
-
/**
|
|
10
|
-
* Ollama exposes two surfaces: the native API at the server root (`/api/*`)
|
|
11
|
-
* and an OpenAI-compatible API under `/v1`. Callers may configure either
|
|
12
|
-
* shape, so normalise before using it.
|
|
13
|
-
*/
|
|
14
9
|
declare function ollamaApiBaseUrl(baseUrl: string): string;
|
|
15
10
|
declare function ollamaChatBaseUrl(baseUrl: string): string;
|
|
16
11
|
declare function missingKeyHint(sel: ModelSelection, env?: NodeJS.ProcessEnv): string | null;
|
package/dist/model-provider.js
CHANGED
|
@@ -22,8 +22,14 @@ function selectModel(env = process.env) {
|
|
|
22
22
|
keySource: "GOOGLE_API_KEY"
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
+
function stripTrailingSlashes(value) {
|
|
26
|
+
let end = value.length;
|
|
27
|
+
while (end > 0 && value[end - 1] === "/") end -= 1;
|
|
28
|
+
return value.slice(0, end);
|
|
29
|
+
}
|
|
25
30
|
function ollamaApiBaseUrl(baseUrl) {
|
|
26
|
-
|
|
31
|
+
const base = stripTrailingSlashes(baseUrl.trim());
|
|
32
|
+
return base.endsWith("/v1") ? base.slice(0, -3) : base;
|
|
27
33
|
}
|
|
28
34
|
function ollamaChatBaseUrl(baseUrl) {
|
|
29
35
|
return `${ollamaApiBaseUrl(baseUrl)}/v1`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/model-provider.ts"],"names":[],"mappings":";AAiBO,SAAS,WAAA,CAAY,GAAA,GAAyB,OAAA,CAAQ,GAAA,EAAqB;AAChF,EAAA,MAAM,GAAA,GAAM,IAAI,2BAA2B,CAAA;AAC3C,EAAA,MAAM,QAAA,GACJ,GAAA,KAAQ,QAAA,IAAY,GAAA,KAAQ,sBAAsB,GAAA,GAAM,QAAA;AAE1D,EAAA,IAAI,aAAa,QAAA,EAAU;AACzB,IAAA,OAAO;AAAA,MACL,QAAA;AAAA,MACA,OAAA,EAAS,GAAA,CAAI,qBAAqB,CAAA,IAAK,UAAA;AAAA,MACvC,SAAA,EAAW;AAAA,KACb;AAAA,EACF;AACA,EAAA,IAAI,aAAa,mBAAA,EAAqB;AACpC,IAAA,OAAO;AAAA,MACL,QAAA;AAAA,MACA,OAAA,EAAS,GAAA,CAAI,qBAAqB,CAAA,IAAK,aAAA;AAAA,MACvC,SAAA,EAAW;AAAA,KACb;AAAA,EACF;AACA,EAAA,OAAO;AAAA,IACL,QAAA,EAAU,QAAA;AAAA,IACV,OAAA,EAAS,GAAA,CAAI,qBAAqB,CAAA,IAAK,kBAAA;AAAA,IACvC,SAAA,EAAW;AAAA,GACb;AACF;
|
|
1
|
+
{"version":3,"sources":["../src/model-provider.ts"],"names":[],"mappings":";AAiBO,SAAS,WAAA,CAAY,GAAA,GAAyB,OAAA,CAAQ,GAAA,EAAqB;AAChF,EAAA,MAAM,GAAA,GAAM,IAAI,2BAA2B,CAAA;AAC3C,EAAA,MAAM,QAAA,GACJ,GAAA,KAAQ,QAAA,IAAY,GAAA,KAAQ,sBAAsB,GAAA,GAAM,QAAA;AAE1D,EAAA,IAAI,aAAa,QAAA,EAAU;AACzB,IAAA,OAAO;AAAA,MACL,QAAA;AAAA,MACA,OAAA,EAAS,GAAA,CAAI,qBAAqB,CAAA,IAAK,UAAA;AAAA,MACvC,SAAA,EAAW;AAAA,KACb;AAAA,EACF;AACA,EAAA,IAAI,aAAa,mBAAA,EAAqB;AACpC,IAAA,OAAO;AAAA,MACL,QAAA;AAAA,MACA,OAAA,EAAS,GAAA,CAAI,qBAAqB,CAAA,IAAK,aAAA;AAAA,MACvC,SAAA,EAAW;AAAA,KACb;AAAA,EACF;AACA,EAAA,OAAO;AAAA,IACL,QAAA,EAAU,QAAA;AAAA,IACV,OAAA,EAAS,GAAA,CAAI,qBAAqB,CAAA,IAAK,kBAAA;AAAA,IACvC,SAAA,EAAW;AAAA,GACb;AACF;AAWA,SAAS,qBAAqB,KAAA,EAAuB;AACnD,EAAA,IAAI,MAAM,KAAA,CAAM,MAAA;AAChB,EAAA,OAAO,MAAM,CAAA,IAAK,KAAA,CAAM,MAAM,CAAC,CAAA,KAAM,KAAK,GAAA,IAAO,CAAA;AACjD,EAAA,OAAO,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA;AAC3B;AAEO,SAAS,iBAAiB,OAAA,EAAyB;AACxD,EAAA,MAAM,IAAA,GAAO,oBAAA,CAAqB,OAAA,CAAQ,IAAA,EAAM,CAAA;AAChD,EAAA,OAAO,IAAA,CAAK,SAAS,KAAK,CAAA,GAAI,KAAK,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,GAAI,IAAA;AACpD;AAEO,SAAS,kBAAkB,OAAA,EAAyB;AACzD,EAAA,OAAO,CAAA,EAAG,gBAAA,CAAiB,OAAO,CAAC,CAAA,GAAA,CAAA;AACrC;AAEO,SAAS,cAAA,CACd,GAAA,EACA,GAAA,GAAyB,OAAA,CAAQ,GAAA,EAClB;AACf,EAAA,IAAI,GAAA,CAAI,QAAA,KAAa,QAAA,EAAU,OAAO,IAAA;AACtC,EAAA,IAAI,IAAI,QAAA,KAAa,QAAA,IAAY,CAAC,GAAA,CAAI,gBAAgB,CAAA,EAAG;AACvD,IAAA,OAAO,kFAAA;AAAA,EACT;AACA,EAAA,IAAI,IAAI,QAAA,KAAa,mBAAA,IAAuB,CAAC,GAAA,CAAI,sBAAsB,CAAA,EAAG;AACxE,IAAA,OAAO,gEAAA;AAAA,EACT;AACA,EAAA,OAAO,IAAA;AACT","file":"model-provider.js","sourcesContent":["// SPDX-License-Identifier: MIT\n// OpenCore — which model to use. The harness is stable; the model is configuration.\n//\n// Providers:\n// - google: Gemini via GOOGLE_API_KEY (free API key).\n// - ollama: Local model via Ollama's OpenAI-compatible endpoint.\n// - openai-compatible: Any OpenAI-compatible endpoint with your own key.\n\nexport type ModelProviderId = \"google\" | \"ollama\" | \"openai-compatible\";\n\nexport type ModelSelection = {\n provider: ModelProviderId;\n modelId: string;\n /** Where the key comes from, without exposing the value. */\n keySource: \"GOOGLE_API_KEY\" | \"OPENEXPERT_MODEL_KEY\" | \"none (local)\";\n};\n\nexport function selectModel(env: NodeJS.ProcessEnv = process.env): ModelSelection {\n const raw = env[\"OPENEXPERT_MODEL_PROVIDER\"];\n const provider: ModelProviderId =\n raw === \"ollama\" || raw === \"openai-compatible\" ? raw : \"google\";\n\n if (provider === \"ollama\") {\n return {\n provider,\n modelId: env[\"OPENEXPERT_MODEL_ID\"] || \"llama3.1\",\n keySource: \"none (local)\",\n };\n }\n if (provider === \"openai-compatible\") {\n return {\n provider,\n modelId: env[\"OPENEXPERT_MODEL_ID\"] || \"gpt-4o-mini\",\n keySource: \"OPENEXPERT_MODEL_KEY\",\n };\n }\n return {\n provider: \"google\",\n modelId: env[\"OPENEXPERT_MODEL_ID\"] || \"gemini-2.5-flash\",\n keySource: \"GOOGLE_API_KEY\",\n };\n}\n\n/**\n * Ollama exposes two surfaces: the native API at the server root (`/api/*`)\n * and an OpenAI-compatible API under `/v1`. Callers may configure either\n * shape, so normalise before using it.\n *\n * Trailing slashes are removed with a loop, not with `/\\/+$/`: a regex has to\n * retry at every position of a long run of slashes, which is what CodeQL\n * reports as a polynomial ReDoS on `baseUrl`.\n */\nfunction stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value[end - 1] === \"/\") end -= 1;\n return value.slice(0, end);\n}\n\nexport function ollamaApiBaseUrl(baseUrl: string): string {\n const base = stripTrailingSlashes(baseUrl.trim());\n return base.endsWith(\"/v1\") ? base.slice(0, -3) : base;\n}\n\nexport function ollamaChatBaseUrl(baseUrl: string): string {\n return `${ollamaApiBaseUrl(baseUrl)}/v1`;\n}\n\nexport function missingKeyHint(\n sel: ModelSelection,\n env: NodeJS.ProcessEnv = process.env,\n): string | null {\n if (sel.provider === \"ollama\") return null;\n if (sel.provider === \"google\" && !env[\"GOOGLE_API_KEY\"]) {\n return \"Falta GOOGLE_API_KEY (o usa OPENEXPERT_MODEL_PROVIDER=ollama para modelo local).\";\n }\n if (sel.provider === \"openai-compatible\" && !env[\"OPENEXPERT_MODEL_KEY\"]) {\n return \"Falta OPENEXPERT_MODEL_KEY para tu endpoint OpenAI-compatible.\";\n }\n return null;\n}\n"]}
|
package/dist/storage.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type TableName = "experts" | "integrations" | "
|
|
1
|
+
type TableName = "experts" | "integrations" | "activity" | "chat_messages" | "deals" | "invoices" | "campaigns" | "accounts" | "settings";
|
|
2
2
|
type Row = Record<string, unknown>;
|
|
3
3
|
interface Storage {
|
|
4
4
|
readonly kind: "file";
|
package/dist/storage.js
CHANGED
package/dist/storage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/storage.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../src/storage.ts"],"names":[],"mappings":";AAuBO,IAAM,YAAA,GAA4B;AAAA,EACvC,SAAA;AAAA,EACA,cAAA;AAAA,EACA,UAAA;AAAA,EACA,eAAA;AAAA,EACA,OAAA;AAAA,EACA,UAAA;AAAA,EACA,WAAA;AAAA,EACA,UAAA;AAAA,EACA;AACF","file":"storage.js","sourcesContent":["// SPDX-License-Identifier: MIT\n// OpenCore — storage interface. OpenExpert is local-first: implementations\n// are backed by the local data directory (SQLite / files).\n\nexport type TableName =\n | \"experts\"\n | \"integrations\"\n | \"activity\"\n | \"chat_messages\"\n | \"deals\"\n | \"invoices\"\n | \"campaigns\"\n | \"accounts\"\n | \"settings\";\n\nexport type Row = Record<string, unknown>;\n\nexport interface Storage {\n readonly kind: \"file\";\n list(table: TableName): Promise<Row[]>;\n insert(table: TableName, rows: Row | Row[]): Promise<void>;\n}\n\nexport const LOCAL_TABLES: TableName[] = [\n \"experts\",\n \"integrations\",\n \"activity\",\n \"chat_messages\",\n \"deals\",\n \"invoices\",\n \"campaigns\",\n \"accounts\",\n \"settings\",\n];\n"]}
|
package/dist/tools.js
CHANGED
|
@@ -5,14 +5,12 @@ var TOOL_CATALOG = [
|
|
|
5
5
|
{ name: "list_overdue_invoices", domain: "finanzas", needs: "read", proposesOnly: false },
|
|
6
6
|
{ name: "get_campaign_performance", domain: "marketing", needs: "read", proposesOnly: false },
|
|
7
7
|
{ name: "get_churn_risk", domain: "general", needs: "read", proposesOnly: false },
|
|
8
|
-
{ name: "list_processes", domain: "transversal", needs: "read", proposesOnly: false },
|
|
9
8
|
{ name: "search_drive", domain: "drive", needs: "read", proposesOnly: false },
|
|
10
9
|
{ name: "read_drive_file", domain: "drive", needs: "read", proposesOnly: false },
|
|
11
10
|
{ name: "create_drive_file", domain: "drive", needs: "exec", proposesOnly: false },
|
|
12
11
|
{ name: "update_drive_file", domain: "drive", needs: "exec", proposesOnly: false },
|
|
13
12
|
{ name: "propose_invoice_reminders", domain: "finanzas", needs: "exec", proposesOnly: true },
|
|
14
13
|
{ name: "propose_pause_campaigns", domain: "marketing", needs: "exec", proposesOnly: true },
|
|
15
|
-
{ name: "request_process_run", domain: "transversal", needs: "exec", proposesOnly: true },
|
|
16
14
|
{ name: "open_expert_form", domain: "transversal", needs: "exec", proposesOnly: false }
|
|
17
15
|
];
|
|
18
16
|
|
package/dist/tools.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tools.ts"],"names":[],"mappings":";AAcO,IAAM,YAAA,GAA0B;AAAA,EACrC,EAAE,MAAM,sBAAA,EAAwB,MAAA,EAAQ,UAAU,KAAA,EAAO,MAAA,EAAQ,cAAc,KAAA,EAAM;AAAA,EACrF,EAAE,MAAM,YAAA,EAAc,MAAA,EAAQ,UAAU,KAAA,EAAO,MAAA,EAAQ,cAAc,KAAA,EAAM;AAAA,EAC3E,EAAE,MAAM,uBAAA,EAAyB,MAAA,EAAQ,YAAY,KAAA,EAAO,MAAA,EAAQ,cAAc,KAAA,EAAM;AAAA,EACxF,EAAE,MAAM,0BAAA,EAA4B,MAAA,EAAQ,aAAa,KAAA,EAAO,MAAA,EAAQ,cAAc,KAAA,EAAM;AAAA,EAC5F,EAAE,MAAM,gBAAA,EAAkB,MAAA,EAAQ,WAAW,KAAA,EAAO,MAAA,EAAQ,cAAc,KAAA,EAAM;AAAA,EAChF,EAAE,MAAM,
|
|
1
|
+
{"version":3,"sources":["../src/tools.ts"],"names":[],"mappings":";AAcO,IAAM,YAAA,GAA0B;AAAA,EACrC,EAAE,MAAM,sBAAA,EAAwB,MAAA,EAAQ,UAAU,KAAA,EAAO,MAAA,EAAQ,cAAc,KAAA,EAAM;AAAA,EACrF,EAAE,MAAM,YAAA,EAAc,MAAA,EAAQ,UAAU,KAAA,EAAO,MAAA,EAAQ,cAAc,KAAA,EAAM;AAAA,EAC3E,EAAE,MAAM,uBAAA,EAAyB,MAAA,EAAQ,YAAY,KAAA,EAAO,MAAA,EAAQ,cAAc,KAAA,EAAM;AAAA,EACxF,EAAE,MAAM,0BAAA,EAA4B,MAAA,EAAQ,aAAa,KAAA,EAAO,MAAA,EAAQ,cAAc,KAAA,EAAM;AAAA,EAC5F,EAAE,MAAM,gBAAA,EAAkB,MAAA,EAAQ,WAAW,KAAA,EAAO,MAAA,EAAQ,cAAc,KAAA,EAAM;AAAA,EAChF,EAAE,MAAM,cAAA,EAAgB,MAAA,EAAQ,SAAS,KAAA,EAAO,MAAA,EAAQ,cAAc,KAAA,EAAM;AAAA,EAC5E,EAAE,MAAM,iBAAA,EAAmB,MAAA,EAAQ,SAAS,KAAA,EAAO,MAAA,EAAQ,cAAc,KAAA,EAAM;AAAA,EAC/E,EAAE,MAAM,mBAAA,EAAqB,MAAA,EAAQ,SAAS,KAAA,EAAO,MAAA,EAAQ,cAAc,KAAA,EAAM;AAAA,EACjF,EAAE,MAAM,mBAAA,EAAqB,MAAA,EAAQ,SAAS,KAAA,EAAO,MAAA,EAAQ,cAAc,KAAA,EAAM;AAAA,EACjF,EAAE,MAAM,2BAAA,EAA6B,MAAA,EAAQ,YAAY,KAAA,EAAO,MAAA,EAAQ,cAAc,IAAA,EAAK;AAAA,EAC3F,EAAE,MAAM,yBAAA,EAA2B,MAAA,EAAQ,aAAa,KAAA,EAAO,MAAA,EAAQ,cAAc,IAAA,EAAK;AAAA,EAC1F,EAAE,MAAM,kBAAA,EAAoB,MAAA,EAAQ,eAAe,KAAA,EAAO,MAAA,EAAQ,cAAc,KAAA;AAClF","file":"tools.js","sourcesContent":["// SPDX-License-Identifier: MIT\n// OpenCore — tool catalog (the current 12).\n// The model never touches the database directly: it only calls these\n// functions, and each one checks your permission before answering.\n\nexport type ToolDomain = \"ventas\" | \"finanzas\" | \"marketing\" | \"general\" | \"transversal\" | \"drive\";\n\nexport type ToolDef = {\n name: string;\n domain: ToolDomain;\n needs: \"read\" | \"exec\";\n proposesOnly: boolean;\n};\n\nexport const TOOL_CATALOG: ToolDef[] = [\n { name: \"get_pipeline_summary\", domain: \"ventas\", needs: \"read\", proposesOnly: false },\n { name: \"list_deals\", domain: \"ventas\", needs: \"read\", proposesOnly: false },\n { name: \"list_overdue_invoices\", domain: \"finanzas\", needs: \"read\", proposesOnly: false },\n { name: \"get_campaign_performance\", domain: \"marketing\", needs: \"read\", proposesOnly: false },\n { name: \"get_churn_risk\", domain: \"general\", needs: \"read\", proposesOnly: false },\n { name: \"search_drive\", domain: \"drive\", needs: \"read\", proposesOnly: false },\n { name: \"read_drive_file\", domain: \"drive\", needs: \"read\", proposesOnly: false },\n { name: \"create_drive_file\", domain: \"drive\", needs: \"exec\", proposesOnly: false },\n { name: \"update_drive_file\", domain: \"drive\", needs: \"exec\", proposesOnly: false },\n { name: \"propose_invoice_reminders\", domain: \"finanzas\", needs: \"exec\", proposesOnly: true },\n { name: \"propose_pause_campaigns\", domain: \"marketing\", needs: \"exec\", proposesOnly: true },\n { name: \"open_expert_form\", domain: \"transversal\", needs: \"exec\", proposesOnly: false },\n];\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openexpert/opencore",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "OpenCore — open-source engine of OpenExpert (MIT).
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "OpenCore — open-source engine of OpenExpert (MIT). Config, model provider selector (Google / Ollama / OpenAI-compatible), storage and secrets interfaces, tool catalog, and CLI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"agents",
|
|
57
57
|
"mit",
|
|
58
58
|
"oss",
|
|
59
|
-
"
|
|
59
|
+
"local-first"
|
|
60
60
|
],
|
|
61
61
|
"repository": {
|
|
62
62
|
"type": "git",
|
|
@@ -84,12 +84,12 @@
|
|
|
84
84
|
"typecheck": "tsc --noEmit"
|
|
85
85
|
},
|
|
86
86
|
"dependencies": {
|
|
87
|
-
"@ai-sdk/openai": "^4.0.
|
|
88
|
-
"@clack/prompts": "^
|
|
87
|
+
"@ai-sdk/openai": "^4.0.91",
|
|
88
|
+
"@clack/prompts": "^1.8.1",
|
|
89
89
|
"ai": "^7.0.0"
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
|
92
|
-
"@types/node": "^
|
|
92
|
+
"@types/node": "^26.6.5",
|
|
93
93
|
"tsup": "^8.3.5",
|
|
94
94
|
"typescript": "^5.8.3",
|
|
95
95
|
"vitest": "^5.0.3"
|
|
@@ -26,6 +26,16 @@
|
|
|
26
26
|
"minLength": 1,
|
|
27
27
|
"description": "Directory where the SQLite database and credentials live. `~/` expands to the user's home directory."
|
|
28
28
|
},
|
|
29
|
+
"googleClientId": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"minLength": 1,
|
|
32
|
+
"description": "Google OAuth client ID (public identifier) used for the Drive integration. The matching client secret lives in secrets.json."
|
|
33
|
+
},
|
|
34
|
+
"notionClientId": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"minLength": 1,
|
|
37
|
+
"description": "Notion OAuth client ID (public identifier) used to connect Notion. The matching client secret lives in secrets.json."
|
|
38
|
+
},
|
|
29
39
|
"ai": {
|
|
30
40
|
"type": "object",
|
|
31
41
|
"additionalProperties": false,
|