stratum-cli 0.7.0-beta.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.stratumrc.json.example +8 -0
- package/CHANGELOG.md +18 -0
- package/dist/index.js +478 -101
- package/package.json +1 -1
package/.stratumrc.json.example
CHANGED
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
"model": "claude-sonnet-4-5",
|
|
17
17
|
"apiKey": "${LITELLM_API_KEY}",
|
|
18
18
|
"contextWindow": 200000
|
|
19
|
+
},
|
|
20
|
+
"hosted": {
|
|
21
|
+
"type": "openai-compatible",
|
|
22
|
+
"baseUrl": "https://api.example.com/v1",
|
|
23
|
+
"apiKey": "${EXAMPLE_API_KEY}",
|
|
24
|
+
"models": {
|
|
25
|
+
"big-model": { "contextWindow": 200000 }
|
|
26
|
+
}
|
|
19
27
|
}
|
|
20
28
|
}
|
|
21
29
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,24 @@ El formato sigue [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/) y l
|
|
|
6
6
|
|
|
7
7
|
> Las versiones anteriores a la 0.2.1-beta.0 solo existen como tags de git: la primera publicación en npm fue la 0.2.1-beta.0.
|
|
8
8
|
|
|
9
|
+
## [0.7.0] — 2026-10-03
|
|
10
|
+
|
|
11
|
+
Versión estable de la 0.7.0: incluye todo lo de la [0.7.0-beta.0](#070-beta0--2026-09-27) (endurecimiento de la persistencia, de la escritura de ficheros, de MCP, del cliente del LLM y de `web_fetch`) y, además, los **modelos descubiertos**: a un provider le basta la URL y la key.
|
|
12
|
+
|
|
13
|
+
### Añadido
|
|
14
|
+
- **Providers sin `model`**: un provider ya no necesita `model` ni `contextWindow` en la config; con `baseUrl` y `apiKey` los modelos se piden a `GET /models`. `chat` abre el selector de modelos al arrancar si el provider no tiene ninguno y guarda la primera elección como modelo por defecto.
|
|
15
|
+
- `stratum run` y `stratum init` aceptan `--model`. Sin modelo fijado, usan el único que exponga el provider; con varios, fallan listándolos en vez de elegir uno.
|
|
16
|
+
- `stratum provider add <alias> --base-url … --api-key-env VAR` da de alta un provider sin pasar por el wizard, y `stratum provider models [alias] [--set <id>]` lista los modelos y fija el de por defecto.
|
|
17
|
+
- `/provider remove <alias>` en el chat.
|
|
18
|
+
- Core de Stratum Desktop: guardado atómico de conversaciones y sesiones, guardado al cerrar y avisos cuando un guardado falla. Sin cambios de comportamiento en la CLI.
|
|
19
|
+
|
|
20
|
+
### Cambiado
|
|
21
|
+
- **Ventana de contexto por modelo**: `models.<id>.contextWindow` > la del provider si está escrita > la que declare `/models` > 32768. El wizard ya no escribe `contextWindow`.
|
|
22
|
+
- `stratum provider remove` quita el provider de todos los ficheros que lo definen (global y proyecto), no solo del primero.
|
|
23
|
+
|
|
24
|
+
### Corregido
|
|
25
|
+
- **Wizard de providers**: con una `apiKey` escrita como `${VAR}`, la consulta de modelos enviaba el placeholder literal y caía siempre a la entrada manual. Ahora consulta con la variable expandida y guarda el placeholder.
|
|
26
|
+
|
|
9
27
|
## [0.7.0-beta.0] — 2026-09-27
|
|
10
28
|
|
|
11
29
|
Pre-release para pruebas (`npm install -g stratum-cli@beta`); `latest` sigue en la 0.6.0.
|
package/dist/index.js
CHANGED
|
@@ -177,19 +177,32 @@ var init_accelerator = __esm({
|
|
|
177
177
|
|
|
178
178
|
// src/config/schema.ts
|
|
179
179
|
import { z } from "zod";
|
|
180
|
-
var ProviderConfigSchema, McpServerSchema, SSHHostSchema, SSHConfigSchema, EnvironmentSchema, SessionProfileSchema, StratumConfigSchema;
|
|
180
|
+
var DEFAULT_CONTEXT_WINDOW, ProviderConfigSchema, McpServerSchema, SSHHostSchema, SSHConfigSchema, EnvironmentSchema, SessionProfileSchema, StratumConfigSchema;
|
|
181
181
|
var init_schema = __esm({
|
|
182
182
|
"src/config/schema.ts"() {
|
|
183
183
|
"use strict";
|
|
184
184
|
init_esm_shims();
|
|
185
185
|
init_secrets();
|
|
186
186
|
init_accelerator();
|
|
187
|
+
DEFAULT_CONTEXT_WINDOW = 32768;
|
|
187
188
|
ProviderConfigSchema = z.object({
|
|
188
189
|
type: z.literal("openai-compatible"),
|
|
189
190
|
baseUrl: z.string().url(),
|
|
190
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Modelo de arranque. Vacío (o ausente) = se resuelve contra `GET /models`:
|
|
193
|
+
* `chat` abre el selector, `run`/`init` exigen `--model` salvo que el
|
|
194
|
+
* provider exponga uno solo. Basta con `baseUrl` + `apiKey`.
|
|
195
|
+
*/
|
|
196
|
+
model: z.string().default(""),
|
|
191
197
|
apiKey: z.string().default(""),
|
|
192
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Ventana de contexto del provider. Opcional: si falta se usa la que
|
|
200
|
+
* `/models` declare para el modelo activo y, si no declara ninguna,
|
|
201
|
+
* `DEFAULT_CONTEXT_WINDOW`. Explícita, gana a lo descubierto.
|
|
202
|
+
*/
|
|
203
|
+
contextWindow: z.number().int().positive().optional(),
|
|
204
|
+
/** Ajustes por modelo (id exacto); ganan a los del provider. */
|
|
205
|
+
models: z.record(z.object({ contextWindow: z.number().int().positive().optional() }).strict()).optional(),
|
|
193
206
|
/**
|
|
194
207
|
* Timeouts de inactividad del cliente en ms (§12.3); `0` desactiva. Por
|
|
195
208
|
* defecto 120 s hasta las cabeceras y 300 s sin recibir nada del cuerpo, que
|
|
@@ -2044,6 +2057,99 @@ var init_openai_compatible = __esm({
|
|
|
2044
2057
|
}
|
|
2045
2058
|
});
|
|
2046
2059
|
|
|
2060
|
+
// src/providers/utils.ts
|
|
2061
|
+
function contextWindowOf(entry) {
|
|
2062
|
+
for (const field of CONTEXT_WINDOW_FIELDS) {
|
|
2063
|
+
const value = entry[field];
|
|
2064
|
+
if (typeof value === "number" && Number.isInteger(value) && value > 0) return value;
|
|
2065
|
+
}
|
|
2066
|
+
return void 0;
|
|
2067
|
+
}
|
|
2068
|
+
function discoveredContextWindow(baseUrl, model) {
|
|
2069
|
+
return discoveredWindows.get(normalizeBaseUrl(baseUrl))?.get(model);
|
|
2070
|
+
}
|
|
2071
|
+
async function fetchModels(baseUrl, apiKey, opts = {}) {
|
|
2072
|
+
return (await fetchModelInfos(baseUrl, apiKey, opts)).map((m) => m.id);
|
|
2073
|
+
}
|
|
2074
|
+
async function fetchModelInfos(baseUrl, apiKey, opts = {}) {
|
|
2075
|
+
const { timeoutMs = 5e3, fetchFn = fetch } = opts;
|
|
2076
|
+
const url = `${normalizeBaseUrl(baseUrl)}/models`;
|
|
2077
|
+
const headers = {};
|
|
2078
|
+
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
|
|
2079
|
+
const response = await fetchFn(url, {
|
|
2080
|
+
headers,
|
|
2081
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
2082
|
+
});
|
|
2083
|
+
if (!response.ok) {
|
|
2084
|
+
throw new Error(`GET ${url} \u2192 HTTP ${response.status}`);
|
|
2085
|
+
}
|
|
2086
|
+
const body = await response.json();
|
|
2087
|
+
if (!Array.isArray(body.data)) {
|
|
2088
|
+
throw new Error(`GET ${url} \u2192 respuesta sin campo "data" (no es OpenAI-compatible)`);
|
|
2089
|
+
}
|
|
2090
|
+
const byId = /* @__PURE__ */ new Map();
|
|
2091
|
+
for (const entry of body.data) {
|
|
2092
|
+
const id = entry?.id;
|
|
2093
|
+
if (typeof id !== "string" || id.length === 0 || byId.has(id)) continue;
|
|
2094
|
+
const contextWindow = contextWindowOf(entry);
|
|
2095
|
+
byId.set(id, contextWindow ? { id, contextWindow } : { id });
|
|
2096
|
+
}
|
|
2097
|
+
const infos = [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
2098
|
+
const windows = /* @__PURE__ */ new Map();
|
|
2099
|
+
for (const info of infos) {
|
|
2100
|
+
if (info.contextWindow) windows.set(info.id, info.contextWindow);
|
|
2101
|
+
}
|
|
2102
|
+
discoveredWindows.set(normalizeBaseUrl(baseUrl), windows);
|
|
2103
|
+
return infos;
|
|
2104
|
+
}
|
|
2105
|
+
function classifyBackendByUrl(baseUrl) {
|
|
2106
|
+
const u = baseUrl.toLowerCase();
|
|
2107
|
+
if (u.includes("11434")) return "ollama";
|
|
2108
|
+
if (u.includes("/ollama") || u.includes("ollama")) return "ollama";
|
|
2109
|
+
if (u.includes(":4000") || u.includes("litellm")) return "litellm";
|
|
2110
|
+
if (u.includes("api.openai.com")) return "openai";
|
|
2111
|
+
if (u.includes(":8000") || u.includes("vllm")) return "vllm";
|
|
2112
|
+
if (u.includes(":8080") || u.includes("llama")) return "llamacpp";
|
|
2113
|
+
return "unknown";
|
|
2114
|
+
}
|
|
2115
|
+
async function detectCapabilities(baseUrl, apiKey, opts = {}) {
|
|
2116
|
+
const backend = classifyBackendByUrl(baseUrl);
|
|
2117
|
+
try {
|
|
2118
|
+
const models = await fetchModels(baseUrl, apiKey, opts);
|
|
2119
|
+
if (models.length === 0) {
|
|
2120
|
+
return {
|
|
2121
|
+
backend,
|
|
2122
|
+
listsModels: false,
|
|
2123
|
+
models: [],
|
|
2124
|
+
note: `El endpoint ${baseUrl}/models respondi\xF3 pero no devolvi\xF3 modelos` + (backend === "llamacpp" ? " (llama.cpp server suele exponer solo el modelo cargado)." : ".")
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
return { backend, listsModels: true, models };
|
|
2128
|
+
} catch (err) {
|
|
2129
|
+
return {
|
|
2130
|
+
backend,
|
|
2131
|
+
listsModels: false,
|
|
2132
|
+
models: [],
|
|
2133
|
+
note: `No se pudo listar modelos en ${baseUrl}/models: ${String(err)}` + (backend === "llamacpp" ? " \u2014 llama.cpp server puede no implementar /models; escribe el modelo a mano." : " \u2014 escribe el modelo a mano.")
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
var CONTEXT_WINDOW_FIELDS, discoveredWindows, normalizeBaseUrl;
|
|
2138
|
+
var init_utils = __esm({
|
|
2139
|
+
"src/providers/utils.ts"() {
|
|
2140
|
+
"use strict";
|
|
2141
|
+
init_esm_shims();
|
|
2142
|
+
CONTEXT_WINDOW_FIELDS = [
|
|
2143
|
+
"context_length",
|
|
2144
|
+
"context_window",
|
|
2145
|
+
"max_model_len",
|
|
2146
|
+
"max_context_length"
|
|
2147
|
+
];
|
|
2148
|
+
discoveredWindows = /* @__PURE__ */ new Map();
|
|
2149
|
+
normalizeBaseUrl = (baseUrl) => baseUrl.replace(/\/$/, "");
|
|
2150
|
+
}
|
|
2151
|
+
});
|
|
2152
|
+
|
|
2047
2153
|
// src/providers/router.ts
|
|
2048
2154
|
function makeClient(cfg) {
|
|
2049
2155
|
return new OpenAICompatible(cfg.baseUrl, cfg.apiKey, cfg.model, { timeouts: cfg.timeouts });
|
|
@@ -2053,7 +2159,9 @@ var init_router = __esm({
|
|
|
2053
2159
|
"src/providers/router.ts"() {
|
|
2054
2160
|
"use strict";
|
|
2055
2161
|
init_esm_shims();
|
|
2162
|
+
init_schema();
|
|
2056
2163
|
init_openai_compatible();
|
|
2164
|
+
init_utils();
|
|
2057
2165
|
init_logging();
|
|
2058
2166
|
log2 = getLogger("provider");
|
|
2059
2167
|
ProviderRouter = class {
|
|
@@ -2096,6 +2204,8 @@ var init_router = __esm({
|
|
|
2096
2204
|
*/
|
|
2097
2205
|
switchModel(model) {
|
|
2098
2206
|
this.activeConfig = { ...this.activeConfig, model };
|
|
2207
|
+
const entry = this.providers[this.activeKey];
|
|
2208
|
+
if (entry && !entry.model) this.providers[this.activeKey] = { ...entry, model };
|
|
2099
2209
|
}
|
|
2100
2210
|
/**
|
|
2101
2211
|
* Cambia el provider activo en caliente (comando `/provider <name>`, Hito 6).
|
|
@@ -2114,6 +2224,19 @@ var init_router = __esm({
|
|
|
2114
2224
|
this.provider = makeClient(cfg);
|
|
2115
2225
|
this.failedKeys.clear();
|
|
2116
2226
|
}
|
|
2227
|
+
/**
|
|
2228
|
+
* Retira de la sesión un provider eliminado de la config (`/provider remove`):
|
|
2229
|
+
* deja de listarse y de servir de fallback. El activo no se puede retirar.
|
|
2230
|
+
*/
|
|
2231
|
+
forgetProvider(name) {
|
|
2232
|
+
if (name === this.activeKey) {
|
|
2233
|
+
throw new Error(`"${name}" es el provider activo: cambia antes a otro.`);
|
|
2234
|
+
}
|
|
2235
|
+
delete this.providers[name];
|
|
2236
|
+
const index = this.fallbackOrder.indexOf(name);
|
|
2237
|
+
if (index !== -1) this.fallbackOrder.splice(index, 1);
|
|
2238
|
+
this.failedKeys.delete(name);
|
|
2239
|
+
}
|
|
2117
2240
|
/**
|
|
2118
2241
|
* Reaplica la configuración del provider activo en caliente
|
|
2119
2242
|
* (comando `/config_provider`, Hito 3.5). Recrea el cliente HTTP.
|
|
@@ -2143,7 +2266,7 @@ var init_router = __esm({
|
|
|
2143
2266
|
*/
|
|
2144
2267
|
advanceProvider() {
|
|
2145
2268
|
this.failedKeys.add(this.activeKey);
|
|
2146
|
-
const next = this.fallbackOrder.find((k) => !this.failedKeys.has(k));
|
|
2269
|
+
const next = this.fallbackOrder.find((k) => !this.failedKeys.has(k) && this.providers[k].model);
|
|
2147
2270
|
if (!next) {
|
|
2148
2271
|
log2.error("fallback exhausted", { tried: [...this.failedKeys] });
|
|
2149
2272
|
return null;
|
|
@@ -2171,8 +2294,18 @@ var init_router = __esm({
|
|
|
2171
2294
|
get model() {
|
|
2172
2295
|
return this.activeConfig.model;
|
|
2173
2296
|
}
|
|
2297
|
+
/**
|
|
2298
|
+
* Ventana del modelo activo: ajuste por modelo (`models.<id>`) > la del
|
|
2299
|
+
* provider si es explícita > la que declaró `/models` > default.
|
|
2300
|
+
*/
|
|
2174
2301
|
get contextWindow() {
|
|
2175
|
-
|
|
2302
|
+
const cfg = this.activeConfig;
|
|
2303
|
+
return cfg.models?.[cfg.model]?.contextWindow ?? cfg.contextWindow ?? discoveredContextWindow(cfg.baseUrl, cfg.model) ?? DEFAULT_CONTEXT_WINDOW;
|
|
2304
|
+
}
|
|
2305
|
+
/** ¿Depende la ventana de lo que declare `/models`? (ni modelo ni provider la fijan). */
|
|
2306
|
+
get contextWindowIsDiscoverable() {
|
|
2307
|
+
const cfg = this.activeConfig;
|
|
2308
|
+
return cfg.models?.[cfg.model]?.contextWindow === void 0 && cfg.contextWindow === void 0;
|
|
2176
2309
|
}
|
|
2177
2310
|
async healthCheck() {
|
|
2178
2311
|
return this.provider.healthCheck();
|
|
@@ -2181,6 +2314,56 @@ var init_router = __esm({
|
|
|
2181
2314
|
}
|
|
2182
2315
|
});
|
|
2183
2316
|
|
|
2317
|
+
// src/cli/startup-model.ts
|
|
2318
|
+
async function resolveStartupModel(config, opts = {}) {
|
|
2319
|
+
const alias = opts.provider ?? config.provider?.default;
|
|
2320
|
+
const entry = alias ? config.provider?.providers[alias] : void 0;
|
|
2321
|
+
if (!config.provider || !alias || !entry) return { ok: true, fetched: false };
|
|
2322
|
+
const requested = opts.model?.trim();
|
|
2323
|
+
if (requested) {
|
|
2324
|
+
config.provider.providers[alias] = { ...entry, model: requested };
|
|
2325
|
+
return { ok: true, fetched: false };
|
|
2326
|
+
}
|
|
2327
|
+
if (entry.model) return { ok: true, fetched: false };
|
|
2328
|
+
let ids;
|
|
2329
|
+
try {
|
|
2330
|
+
ids = (await fetchModelInfos(entry.baseUrl, entry.apiKey, opts)).map((m) => m.id);
|
|
2331
|
+
} catch (err) {
|
|
2332
|
+
return {
|
|
2333
|
+
ok: false,
|
|
2334
|
+
error: `El provider "${alias}" no tiene modelo configurado y no se pudo consultar ${entry.baseUrl}/models (${err instanceof Error ? err.message : String(err)}). Indica uno con --model <id>.`
|
|
2335
|
+
};
|
|
2336
|
+
}
|
|
2337
|
+
if (ids.length === 1) {
|
|
2338
|
+
config.provider.providers[alias] = { ...entry, model: ids[0] };
|
|
2339
|
+
return { ok: true, fetched: true };
|
|
2340
|
+
}
|
|
2341
|
+
if (ids.length === 0) {
|
|
2342
|
+
return {
|
|
2343
|
+
ok: false,
|
|
2344
|
+
error: `El provider "${alias}" no tiene modelo configurado y ${entry.baseUrl}/models no devolvi\xF3 ninguno. Indica uno con --model <id>.`
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
return {
|
|
2348
|
+
ok: false,
|
|
2349
|
+
error: `El provider "${alias}" no tiene modelo configurado. Elige uno con --model <id>, o f\xEDjalo con \`stratum provider models ${alias} --set <id>\`.
|
|
2350
|
+
Modelos disponibles: ${ids.join(", ")}`
|
|
2351
|
+
};
|
|
2352
|
+
}
|
|
2353
|
+
async function discoverContextWindow(provider, opts = {}) {
|
|
2354
|
+
try {
|
|
2355
|
+
await fetchModelInfos(provider.baseUrl, provider.apiKey, opts);
|
|
2356
|
+
} catch {
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
var init_startup_model = __esm({
|
|
2360
|
+
"src/cli/startup-model.ts"() {
|
|
2361
|
+
"use strict";
|
|
2362
|
+
init_esm_shims();
|
|
2363
|
+
init_utils();
|
|
2364
|
+
}
|
|
2365
|
+
});
|
|
2366
|
+
|
|
2184
2367
|
// src/tools/truncate.ts
|
|
2185
2368
|
function truncateToolOutput(text, maxChars = MAX_TOOL_OUTPUT_CHARS) {
|
|
2186
2369
|
if (text.length <= maxChars) return text;
|
|
@@ -6031,6 +6214,16 @@ function removeProvider(name, configPath) {
|
|
|
6031
6214
|
const backupPath = writeConfigWithBackup(path2, raw);
|
|
6032
6215
|
return { configPath: path2, backupPath, newDefault };
|
|
6033
6216
|
}
|
|
6217
|
+
function removeProviderEverywhere(name, startDir) {
|
|
6218
|
+
const projectPath = findConfigFile(startDir ?? process.cwd());
|
|
6219
|
+
const paths = [.../* @__PURE__ */ new Set([projectPath, GLOBAL_CONFIG_PATH])].filter(
|
|
6220
|
+
(p) => !!p && hasProvider(p, name)
|
|
6221
|
+
);
|
|
6222
|
+
if (paths.length === 0) {
|
|
6223
|
+
throw new Error(`Provider "${name}" no existe en ning\xFAn .stratumrc.json`);
|
|
6224
|
+
}
|
|
6225
|
+
return paths.map((path2) => removeProvider(name, path2));
|
|
6226
|
+
}
|
|
6034
6227
|
function setDefaultProvider(name, configPath) {
|
|
6035
6228
|
const path2 = configPath ?? resolveWritableConfigPath();
|
|
6036
6229
|
const raw = readRaw(path2);
|
|
@@ -6050,6 +6243,26 @@ function readRawProvider(name, configPath) {
|
|
|
6050
6243
|
const entry = providers?.[name];
|
|
6051
6244
|
return entry && typeof entry === "object" ? entry : null;
|
|
6052
6245
|
}
|
|
6246
|
+
function hasProvider(path2, name) {
|
|
6247
|
+
if (!existsSync3(path2)) return false;
|
|
6248
|
+
try {
|
|
6249
|
+
return readRawProvider(name, path2) !== null;
|
|
6250
|
+
} catch {
|
|
6251
|
+
return false;
|
|
6252
|
+
}
|
|
6253
|
+
}
|
|
6254
|
+
function setProviderModel(name, model, startDir) {
|
|
6255
|
+
const projectPath = findConfigFile(startDir ?? process.cwd());
|
|
6256
|
+
const path2 = [projectPath, GLOBAL_CONFIG_PATH].find(
|
|
6257
|
+
(p) => !!p && hasProvider(p, name)
|
|
6258
|
+
);
|
|
6259
|
+
if (!path2) return null;
|
|
6260
|
+
const raw = readRaw(path2);
|
|
6261
|
+
const providers = raw["provider"]["providers"];
|
|
6262
|
+
providers[name]["model"] = model;
|
|
6263
|
+
writeConfigWithBackup(path2, raw);
|
|
6264
|
+
return path2;
|
|
6265
|
+
}
|
|
6053
6266
|
function setConfigValue(key, value, cwd = process.cwd()) {
|
|
6054
6267
|
const path2 = findConfigFile(cwd) ?? join9(cwd, ".stratumrc.json");
|
|
6055
6268
|
const raw = readRaw(path2);
|
|
@@ -6442,7 +6655,7 @@ var init_local = __esm({
|
|
|
6442
6655
|
killedOnMaxBytes: false
|
|
6443
6656
|
});
|
|
6444
6657
|
};
|
|
6445
|
-
const
|
|
6658
|
+
const fail2 = (err) => {
|
|
6446
6659
|
if (settled) return;
|
|
6447
6660
|
settled = true;
|
|
6448
6661
|
cleanup();
|
|
@@ -6476,7 +6689,7 @@ var init_local = __esm({
|
|
|
6476
6689
|
subprocess.then(
|
|
6477
6690
|
(result) => {
|
|
6478
6691
|
if (!killing && result.failed && result.exitCode === void 0 && !result.signal) {
|
|
6479
|
-
|
|
6692
|
+
fail2(new ExecSpawnError(result.shortMessage ?? result.message ?? "spawn failed"));
|
|
6480
6693
|
return;
|
|
6481
6694
|
}
|
|
6482
6695
|
settle2(killing ? null : result.exitCode ?? null);
|
|
@@ -6486,7 +6699,7 @@ var init_local = __esm({
|
|
|
6486
6699
|
settle2(null);
|
|
6487
6700
|
return;
|
|
6488
6701
|
}
|
|
6489
|
-
|
|
6702
|
+
fail2(new ExecSpawnError(err.message));
|
|
6490
6703
|
}
|
|
6491
6704
|
);
|
|
6492
6705
|
});
|
|
@@ -6808,7 +7021,7 @@ function runRemote(client, alias, command, req) {
|
|
|
6808
7021
|
killedOnMaxBytes: true
|
|
6809
7022
|
});
|
|
6810
7023
|
};
|
|
6811
|
-
const
|
|
7024
|
+
const fail2 = (err) => {
|
|
6812
7025
|
if (settled) return;
|
|
6813
7026
|
settled = true;
|
|
6814
7027
|
cleanup();
|
|
@@ -6855,7 +7068,7 @@ function runRemote(client, alias, command, req) {
|
|
|
6855
7068
|
return;
|
|
6856
7069
|
}
|
|
6857
7070
|
if (err) {
|
|
6858
|
-
|
|
7071
|
+
fail2(new ExecSpawnError(`Could not run the command on "${alias}": ${err.message}`));
|
|
6859
7072
|
return;
|
|
6860
7073
|
}
|
|
6861
7074
|
stream = channel;
|
|
@@ -6887,7 +7100,7 @@ function runRemote(client, alias, command, req) {
|
|
|
6887
7100
|
finish();
|
|
6888
7101
|
return;
|
|
6889
7102
|
}
|
|
6890
|
-
|
|
7103
|
+
fail2(new ExecSpawnError(streamErr.message));
|
|
6891
7104
|
});
|
|
6892
7105
|
if (req.stdin !== void 0) channel.end(req.stdin);
|
|
6893
7106
|
else channel.end();
|
|
@@ -14738,6 +14951,10 @@ var init_core = __esm({
|
|
|
14738
14951
|
this.router.switchProvider(name);
|
|
14739
14952
|
this.reloadMemory();
|
|
14740
14953
|
}
|
|
14954
|
+
/** Retira de la sesión un provider eliminado de la config (`/provider remove`). */
|
|
14955
|
+
forgetProvider(name) {
|
|
14956
|
+
this.router.forgetProvider(name);
|
|
14957
|
+
}
|
|
14741
14958
|
/** Alias de los providers configurados (para `/provider` y su autocompletado). */
|
|
14742
14959
|
get providerNames() {
|
|
14743
14960
|
return this.router.providerNames;
|
|
@@ -16243,65 +16460,6 @@ var init_cleanup = __esm({
|
|
|
16243
16460
|
}
|
|
16244
16461
|
});
|
|
16245
16462
|
|
|
16246
|
-
// src/providers/utils.ts
|
|
16247
|
-
async function fetchModels(baseUrl, apiKey, opts = {}) {
|
|
16248
|
-
const { timeoutMs = 5e3, fetchFn = fetch } = opts;
|
|
16249
|
-
const url = `${baseUrl.replace(/\/$/, "")}/models`;
|
|
16250
|
-
const headers = {};
|
|
16251
|
-
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
|
|
16252
|
-
const response = await fetchFn(url, {
|
|
16253
|
-
headers,
|
|
16254
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
16255
|
-
});
|
|
16256
|
-
if (!response.ok) {
|
|
16257
|
-
throw new Error(`GET ${url} \u2192 HTTP ${response.status}`);
|
|
16258
|
-
}
|
|
16259
|
-
const body = await response.json();
|
|
16260
|
-
if (!Array.isArray(body.data)) {
|
|
16261
|
-
throw new Error(`GET ${url} \u2192 respuesta sin campo "data" (no es OpenAI-compatible)`);
|
|
16262
|
-
}
|
|
16263
|
-
const ids = body.data.map((m) => m.id).filter((id) => typeof id === "string" && id.length > 0);
|
|
16264
|
-
return [...new Set(ids)].sort((a, b) => a.localeCompare(b));
|
|
16265
|
-
}
|
|
16266
|
-
function classifyBackendByUrl(baseUrl) {
|
|
16267
|
-
const u = baseUrl.toLowerCase();
|
|
16268
|
-
if (u.includes("11434")) return "ollama";
|
|
16269
|
-
if (u.includes("/ollama") || u.includes("ollama")) return "ollama";
|
|
16270
|
-
if (u.includes(":4000") || u.includes("litellm")) return "litellm";
|
|
16271
|
-
if (u.includes("api.openai.com")) return "openai";
|
|
16272
|
-
if (u.includes(":8000") || u.includes("vllm")) return "vllm";
|
|
16273
|
-
if (u.includes(":8080") || u.includes("llama")) return "llamacpp";
|
|
16274
|
-
return "unknown";
|
|
16275
|
-
}
|
|
16276
|
-
async function detectCapabilities(baseUrl, apiKey, opts = {}) {
|
|
16277
|
-
const backend = classifyBackendByUrl(baseUrl);
|
|
16278
|
-
try {
|
|
16279
|
-
const models = await fetchModels(baseUrl, apiKey, opts);
|
|
16280
|
-
if (models.length === 0) {
|
|
16281
|
-
return {
|
|
16282
|
-
backend,
|
|
16283
|
-
listsModels: false,
|
|
16284
|
-
models: [],
|
|
16285
|
-
note: `El endpoint ${baseUrl}/models respondi\xF3 pero no devolvi\xF3 modelos` + (backend === "llamacpp" ? " (llama.cpp server suele exponer solo el modelo cargado)." : ".")
|
|
16286
|
-
};
|
|
16287
|
-
}
|
|
16288
|
-
return { backend, listsModels: true, models };
|
|
16289
|
-
} catch (err) {
|
|
16290
|
-
return {
|
|
16291
|
-
backend,
|
|
16292
|
-
listsModels: false,
|
|
16293
|
-
models: [],
|
|
16294
|
-
note: `No se pudo listar modelos en ${baseUrl}/models: ${String(err)}` + (backend === "llamacpp" ? " \u2014 llama.cpp server puede no implementar /models; escribe el modelo a mano." : " \u2014 escribe el modelo a mano.")
|
|
16295
|
-
};
|
|
16296
|
-
}
|
|
16297
|
-
}
|
|
16298
|
-
var init_utils = __esm({
|
|
16299
|
-
"src/providers/utils.ts"() {
|
|
16300
|
-
"use strict";
|
|
16301
|
-
init_esm_shims();
|
|
16302
|
-
}
|
|
16303
|
-
});
|
|
16304
|
-
|
|
16305
16463
|
// src/cli/ui/theme.ts
|
|
16306
16464
|
var theme;
|
|
16307
16465
|
var init_theme = __esm({
|
|
@@ -19003,9 +19161,22 @@ function validateBaseUrl(url) {
|
|
|
19003
19161
|
return "URL inv\xE1lida (ej. http://localhost:11434/v1)";
|
|
19004
19162
|
}
|
|
19005
19163
|
}
|
|
19164
|
+
function resolveApiKey(raw) {
|
|
19165
|
+
const missing = [];
|
|
19166
|
+
const key = String(expandEnvVars(raw, (m) => missing.push(m.name)));
|
|
19167
|
+
return { key, missing };
|
|
19168
|
+
}
|
|
19006
19169
|
async function discoverModels(baseUrl, apiKey, fetchFn) {
|
|
19170
|
+
const { key, missing } = resolveApiKey(apiKey);
|
|
19171
|
+
if (missing.length > 0) {
|
|
19172
|
+
return {
|
|
19173
|
+
models: [],
|
|
19174
|
+
manualFallback: true,
|
|
19175
|
+
error: `variable de entorno no definida: ${missing.join(", ")}`
|
|
19176
|
+
};
|
|
19177
|
+
}
|
|
19007
19178
|
try {
|
|
19008
|
-
const models = await fetchModels(baseUrl,
|
|
19179
|
+
const models = await fetchModels(baseUrl, key, fetchFn ? { fetchFn } : {});
|
|
19009
19180
|
if (models.length === 0) {
|
|
19010
19181
|
return { models: [], manualFallback: true, error: "El endpoint /models no devolvi\xF3 modelos" };
|
|
19011
19182
|
}
|
|
@@ -19015,12 +19186,13 @@ async function discoverModels(baseUrl, apiKey, fetchFn) {
|
|
|
19015
19186
|
}
|
|
19016
19187
|
}
|
|
19017
19188
|
function buildProviderEntry(params) {
|
|
19189
|
+
const model = params.model?.trim();
|
|
19018
19190
|
return {
|
|
19019
19191
|
type: "openai-compatible",
|
|
19020
19192
|
baseUrl: params.baseUrl.trim().replace(/\/$/, ""),
|
|
19021
|
-
model:
|
|
19193
|
+
...model ? { model } : {},
|
|
19022
19194
|
apiKey: params.apiKey,
|
|
19023
|
-
contextWindow: params.contextWindow
|
|
19195
|
+
...params.contextWindow !== void 0 ? { contextWindow: params.contextWindow } : {}
|
|
19024
19196
|
};
|
|
19025
19197
|
}
|
|
19026
19198
|
var PROVIDER_TYPE_PRESETS;
|
|
@@ -19028,6 +19200,7 @@ var init_wizard_logic = __esm({
|
|
|
19028
19200
|
"src/cli/ui/wizard-logic.ts"() {
|
|
19029
19201
|
"use strict";
|
|
19030
19202
|
init_esm_shims();
|
|
19203
|
+
init_loader();
|
|
19031
19204
|
init_utils();
|
|
19032
19205
|
PROVIDER_TYPE_PRESETS = [
|
|
19033
19206
|
{
|
|
@@ -19189,9 +19362,11 @@ function ProviderWizard({
|
|
|
19189
19362
|
] }),
|
|
19190
19363
|
/* @__PURE__ */ jsxs26(Text26, { color: theme.textFaint, children: [
|
|
19191
19364
|
" ",
|
|
19192
|
-
"
|
|
19365
|
+
"Escribe ",
|
|
19193
19366
|
"${VAR}",
|
|
19194
|
-
"
|
|
19367
|
+
" (p. ej. ",
|
|
19368
|
+
"${NAN_API_KEY}",
|
|
19369
|
+
") para no guardar la key en el archivo"
|
|
19195
19370
|
] })
|
|
19196
19371
|
] });
|
|
19197
19372
|
case "name":
|
|
@@ -19421,7 +19596,7 @@ var init_session_commands = __esm({
|
|
|
19421
19596
|
},
|
|
19422
19597
|
{
|
|
19423
19598
|
name: "/provider",
|
|
19424
|
-
description: "Cambia el provider activo en esta sesi\xF3n (sin args: lista
|
|
19599
|
+
description: "Cambia el provider activo en esta sesi\xF3n (sin args: lista; remove <alias>: lo elimina)",
|
|
19425
19600
|
hasArgs: true
|
|
19426
19601
|
},
|
|
19427
19602
|
{
|
|
@@ -20911,7 +21086,12 @@ function App({
|
|
|
20911
21086
|
setOverlay({ kind: "model-loading" });
|
|
20912
21087
|
const cfg = agent.getActiveProviderConfig();
|
|
20913
21088
|
detectCapabilities(cfg.baseUrl, cfg.apiKey).then((caps) => {
|
|
20914
|
-
if (
|
|
21089
|
+
if (!agent.model && caps.models.length === 1) {
|
|
21090
|
+
setOverlay(null);
|
|
21091
|
+
agent.switchModel(caps.models[0]);
|
|
21092
|
+
refreshProviderHealth();
|
|
21093
|
+
dispatch({ type: "SYSTEM_MESSAGE", text: `Modelo: ${caps.models[0]}` });
|
|
21094
|
+
} else if (caps.listsModels && caps.models.length > 0) {
|
|
20915
21095
|
setOverlay({ kind: "model-select", models: caps.models });
|
|
20916
21096
|
} else {
|
|
20917
21097
|
setModelManualValue(agent.model);
|
|
@@ -20921,21 +21101,46 @@ function App({
|
|
|
20921
21101
|
setModelManualValue(agent.model);
|
|
20922
21102
|
setOverlay({ kind: "model-manual", note: String(err) });
|
|
20923
21103
|
});
|
|
20924
|
-
}, [agent]);
|
|
21104
|
+
}, [agent, refreshProviderHealth]);
|
|
20925
21105
|
const applyModel = useCallback4(
|
|
20926
21106
|
(model) => {
|
|
20927
21107
|
setOverlay(null);
|
|
20928
21108
|
const trimmed = model.trim();
|
|
20929
21109
|
if (!trimmed || trimmed === agent.model) return;
|
|
21110
|
+
const wasUnset = !agent.model;
|
|
20930
21111
|
agent.switchModel(trimmed);
|
|
20931
21112
|
refreshProviderHealth();
|
|
21113
|
+
if (!wasUnset) {
|
|
21114
|
+
dispatch({
|
|
21115
|
+
type: "SYSTEM_MESSAGE",
|
|
21116
|
+
text: `Modelo cambiado a ${trimmed} (solo esta sesi\xF3n; no se ha modificado .stratumrc.json).`
|
|
21117
|
+
});
|
|
21118
|
+
return;
|
|
21119
|
+
}
|
|
21120
|
+
let saved = null;
|
|
21121
|
+
let saveError;
|
|
21122
|
+
try {
|
|
21123
|
+
saved = setProviderModel(agent.providerName, trimmed);
|
|
21124
|
+
} catch (err) {
|
|
21125
|
+
saveError = err instanceof Error ? err.message : String(err);
|
|
21126
|
+
}
|
|
20932
21127
|
dispatch({
|
|
20933
21128
|
type: "SYSTEM_MESSAGE",
|
|
20934
|
-
text: `Modelo
|
|
21129
|
+
text: saved ? `Modelo: ${trimmed} (guardado como modelo por defecto de "${agent.providerName}" en ${saved}; /model lo cambia en la sesi\xF3n).` : `Modelo: ${trimmed} (solo esta sesi\xF3n${saveError ? `; no se pudo guardar: ${saveError}` : ""}).`
|
|
20935
21130
|
});
|
|
20936
21131
|
},
|
|
20937
21132
|
[agent, refreshProviderHealth]
|
|
20938
21133
|
);
|
|
21134
|
+
const promptForModel = useCallback4(() => {
|
|
21135
|
+
dispatch({
|
|
21136
|
+
type: "SYSTEM_MESSAGE",
|
|
21137
|
+
text: `El provider "${agent.providerName}" no tiene modelo fijado: elige uno de los que expone.`
|
|
21138
|
+
});
|
|
21139
|
+
openModelSelector();
|
|
21140
|
+
}, [agent, openModelSelector]);
|
|
21141
|
+
useEffect4(() => {
|
|
21142
|
+
if (!agent.model) promptForModel();
|
|
21143
|
+
}, []);
|
|
20939
21144
|
const openProviderEditor = useCallback4(() => {
|
|
20940
21145
|
const name = agent.providerName;
|
|
20941
21146
|
const active = agent.getActiveProviderConfig();
|
|
@@ -20947,7 +21152,8 @@ function App({
|
|
|
20947
21152
|
baseUrl: typeof raw?.["baseUrl"] === "string" ? raw["baseUrl"] : active.baseUrl,
|
|
20948
21153
|
apiKey: typeof raw?.["apiKey"] === "string" ? raw["apiKey"] : active.apiKey,
|
|
20949
21154
|
model: typeof raw?.["model"] === "string" ? raw["model"] : active.model,
|
|
20950
|
-
|
|
21155
|
+
// Solo si está escrita: sin ella manda lo que declare `/models`.
|
|
21156
|
+
contextWindow: typeof raw?.["contextWindow"] === "number" ? raw["contextWindow"] : void 0
|
|
20951
21157
|
}
|
|
20952
21158
|
});
|
|
20953
21159
|
}, [agent]);
|
|
@@ -20958,7 +21164,7 @@ function App({
|
|
|
20958
21164
|
const { configPath, backupPath } = upsertProvider(result.name, result.config, false);
|
|
20959
21165
|
if (result.name === agent.providerName) {
|
|
20960
21166
|
const expanded = expandEnvVars(result.config);
|
|
20961
|
-
agent.reconfigureProvider(expanded);
|
|
21167
|
+
agent.reconfigureProvider({ model: "", ...expanded });
|
|
20962
21168
|
}
|
|
20963
21169
|
dispatch({
|
|
20964
21170
|
type: "SYSTEM_MESSAGE",
|
|
@@ -21471,6 +21677,33 @@ ${lines.join("\n")}`
|
|
|
21471
21677
|
dispatch({ type: "INPUT_CHANGE", value: "" });
|
|
21472
21678
|
const target = cmd.slice("/provider".length).trim();
|
|
21473
21679
|
const names = agent.providerNames;
|
|
21680
|
+
const removal = target.match(/^(?:remove|rm)\s+(\S+)$/);
|
|
21681
|
+
if (removal) {
|
|
21682
|
+
const alias = removal[1];
|
|
21683
|
+
if (alias === agent.providerName) {
|
|
21684
|
+
dispatch({
|
|
21685
|
+
type: "SYSTEM_MESSAGE",
|
|
21686
|
+
text: `"${alias}" es el provider activo: cambia antes a otro con /provider <alias>.`
|
|
21687
|
+
});
|
|
21688
|
+
return;
|
|
21689
|
+
}
|
|
21690
|
+
try {
|
|
21691
|
+
const results = removeProviderEverywhere(alias);
|
|
21692
|
+
if (names.includes(alias)) agent.forgetProvider(alias);
|
|
21693
|
+
const files = results.map((r) => r.configPath).join(", ");
|
|
21694
|
+
const promoted = results.find((r) => r.newDefault)?.newDefault;
|
|
21695
|
+
dispatch({
|
|
21696
|
+
type: "SYSTEM_MESSAGE",
|
|
21697
|
+
text: `Provider "${alias}" eliminado de ${files} (copia en .bak).` + (promoted ? ` Era el provider por defecto: ahora lo es "${promoted}".` : "")
|
|
21698
|
+
});
|
|
21699
|
+
} catch (err) {
|
|
21700
|
+
dispatch({
|
|
21701
|
+
type: "SYSTEM_MESSAGE",
|
|
21702
|
+
text: `No se pudo eliminar "${alias}": ${err instanceof Error ? err.message : String(err)}`
|
|
21703
|
+
});
|
|
21704
|
+
}
|
|
21705
|
+
return;
|
|
21706
|
+
}
|
|
21474
21707
|
if (!target) {
|
|
21475
21708
|
const lines = names.map(
|
|
21476
21709
|
(n) => n === agent.providerName ? ` \u25B6 ${n} (activo)` : ` ${n}`
|
|
@@ -21481,7 +21714,7 @@ ${lines.join("\n")}`
|
|
|
21481
21714
|
|
|
21482
21715
|
${lines.join("\n")}
|
|
21483
21716
|
|
|
21484
|
-
Uso: /provider <alias> para cambiar en esta sesi\xF3n.`
|
|
21717
|
+
Uso: /provider <alias> para cambiar en esta sesi\xF3n \xB7 /provider remove <alias> para eliminarlo de la config.`
|
|
21485
21718
|
});
|
|
21486
21719
|
return;
|
|
21487
21720
|
}
|
|
@@ -21499,6 +21732,10 @@ Uso: /provider <alias> para cambiar en esta sesi\xF3n.`
|
|
|
21499
21732
|
try {
|
|
21500
21733
|
agent.switchProvider(target);
|
|
21501
21734
|
refreshProviderHealth();
|
|
21735
|
+
if (!agent.model) {
|
|
21736
|
+
promptForModel();
|
|
21737
|
+
return;
|
|
21738
|
+
}
|
|
21502
21739
|
dispatch({
|
|
21503
21740
|
type: "SYSTEM_MESSAGE",
|
|
21504
21741
|
text: `Provider activo: ${target} (modelo ${agent.model}, solo esta sesi\xF3n; no se ha modificado .stratumrc.json).`
|
|
@@ -21603,6 +21840,10 @@ Uso: /provider <alias> para cambiar en esta sesi\xF3n.`
|
|
|
21603
21840
|
return;
|
|
21604
21841
|
}
|
|
21605
21842
|
if (state.thinking) return;
|
|
21843
|
+
if (!agent.model && (!cmd.startsWith("/") || /^\/(init|plan|compact)(\s|$)/.test(cmd))) {
|
|
21844
|
+
promptForModel();
|
|
21845
|
+
return;
|
|
21846
|
+
}
|
|
21606
21847
|
if (paletteItems.length > 0) {
|
|
21607
21848
|
const sel = paletteItems[effPaletteIndex];
|
|
21608
21849
|
if (sel && sel.name !== cmd) {
|
|
@@ -21622,7 +21863,9 @@ Uso: /provider <alias> para cambiar en esta sesi\xF3n.`
|
|
|
21622
21863
|
paletteItems,
|
|
21623
21864
|
effPaletteIndex,
|
|
21624
21865
|
completePaletteSelection,
|
|
21625
|
-
executeCommand
|
|
21866
|
+
executeCommand,
|
|
21867
|
+
agent,
|
|
21868
|
+
promptForModel
|
|
21626
21869
|
]
|
|
21627
21870
|
);
|
|
21628
21871
|
if (state.phase === "banner") {
|
|
@@ -21676,7 +21919,8 @@ Uso: /provider <alias> para cambiar en esta sesi\xF3n.`
|
|
|
21676
21919
|
/* @__PURE__ */ jsxs27(Text27, { color: theme.textMuted, bold: false, children: [
|
|
21677
21920
|
" \xB7 ",
|
|
21678
21921
|
agent.providerName,
|
|
21679
|
-
" \xB7
|
|
21922
|
+
" \xB7 ",
|
|
21923
|
+
agent.model ? "solo esta sesi\xF3n" : "modelo por defecto"
|
|
21680
21924
|
] })
|
|
21681
21925
|
] }),
|
|
21682
21926
|
/* @__PURE__ */ jsx30(Text27, { children: " " }),
|
|
@@ -21721,7 +21965,8 @@ Uso: /provider <alias> para cambiar en esta sesi\xF3n.`
|
|
|
21721
21965
|
/* @__PURE__ */ jsxs27(Text27, { color: theme.textMuted, bold: false, children: [
|
|
21722
21966
|
" \xB7 ",
|
|
21723
21967
|
agent.providerName,
|
|
21724
|
-
" \xB7
|
|
21968
|
+
" \xB7 ",
|
|
21969
|
+
agent.model ? "solo esta sesi\xF3n" : "modelo por defecto"
|
|
21725
21970
|
] })
|
|
21726
21971
|
] }),
|
|
21727
21972
|
overlay.note && /* @__PURE__ */ jsxs27(Text27, { color: theme.warning, children: [
|
|
@@ -22047,7 +22292,7 @@ async function settle(step, fn) {
|
|
|
22047
22292
|
}
|
|
22048
22293
|
}
|
|
22049
22294
|
function resolveVersion() {
|
|
22050
|
-
if (true) return "0.7.0
|
|
22295
|
+
if (true) return "0.7.0";
|
|
22051
22296
|
try {
|
|
22052
22297
|
const thisDir = dirname13(fileURLToPath2(import.meta.url));
|
|
22053
22298
|
const pkg = JSON.parse(
|
|
@@ -22065,6 +22310,7 @@ var init_chat = __esm({
|
|
|
22065
22310
|
init_esm_shims();
|
|
22066
22311
|
init_loader();
|
|
22067
22312
|
init_router();
|
|
22313
|
+
init_startup_model();
|
|
22068
22314
|
init_registry();
|
|
22069
22315
|
init_tools();
|
|
22070
22316
|
init_manager();
|
|
@@ -22084,7 +22330,7 @@ var init_chat = __esm({
|
|
|
22084
22330
|
init_session_flags();
|
|
22085
22331
|
init_logging();
|
|
22086
22332
|
CHECKPOINT_EVERY_MS = 3e4;
|
|
22087
|
-
chatCommand = new Command("chat").description("Start an interactive REPL session with the agent").option("--provider <name>", "use a specific provider from config").option("--resume <session-id>", "resume a previous session").option("--read-only", "observation only: no file writes and only read-only commands").option("--profile <name>", "session profile: auto | code | infra | full | <custom>").option("--infra", "shortcut for --profile infra").option("--code", "shortcut for --profile code").option("--log-level <level>", "log level: trace|debug|info|warn|error|silent").option("--debug", "enable verbose debug logging (level debug + file sink)").action(
|
|
22333
|
+
chatCommand = new Command("chat").description("Start an interactive REPL session with the agent").option("--provider <name>", "use a specific provider from config").option("--model <id>", "start with a specific model of the provider (this session only)").option("--resume <session-id>", "resume a previous session").option("--read-only", "observation only: no file writes and only read-only commands").option("--profile <name>", "session profile: auto | code | infra | full | <custom>").option("--infra", "shortcut for --profile infra").option("--code", "shortcut for --profile code").option("--log-level <level>", "log level: trace|debug|info|warn|error|silent").option("--debug", "enable verbose debug logging (level debug + file sink)").action(
|
|
22088
22334
|
async (opts) => {
|
|
22089
22335
|
const profileFlag = sessionProfileFlag(opts);
|
|
22090
22336
|
if (!profileFlag.ok) {
|
|
@@ -22121,6 +22367,10 @@ var init_chat = __esm({
|
|
|
22121
22367
|
`);
|
|
22122
22368
|
process.exit(1);
|
|
22123
22369
|
}
|
|
22370
|
+
if (opts.model?.trim()) router.switchModel(opts.model.trim());
|
|
22371
|
+
if (router.model && router.contextWindowIsDiscoverable) {
|
|
22372
|
+
void discoverContextWindow(router.getActiveConfig());
|
|
22373
|
+
}
|
|
22124
22374
|
const registry = new ToolRegistry();
|
|
22125
22375
|
registerBuiltinTools(registry, config);
|
|
22126
22376
|
const mcpManager = new McpManager(config);
|
|
@@ -22413,6 +22663,7 @@ init_subagent_store();
|
|
|
22413
22663
|
init_store();
|
|
22414
22664
|
init_loader();
|
|
22415
22665
|
init_router();
|
|
22666
|
+
init_startup_model();
|
|
22416
22667
|
init_registry();
|
|
22417
22668
|
init_tools();
|
|
22418
22669
|
init_manager();
|
|
@@ -22429,7 +22680,7 @@ function summarizeInput(input) {
|
|
|
22429
22680
|
const val = String(input[keys[0]] ?? "");
|
|
22430
22681
|
return val.length > 60 ? val.slice(0, 57) + "..." : val;
|
|
22431
22682
|
}
|
|
22432
|
-
var runCommand = new Command2("run").description("Run a one-shot task with the agent").argument("<task>", "task to execute").option("--provider <name>", "use a specific provider from config").option("--allow-destructive", "approve all destructive operations without prompting").option("--deny-destructive", "block all destructive operations automatically").option("--plan", "plan-and-execute mode: produce a plan, approve, then execute step by step").option("--yes, --approve-plan", "auto-approve the plan in --plan mode (no prompt)").option("--agent <profile>", "use an agent profile (mode primary/all) as the main agent").option(
|
|
22683
|
+
var runCommand = new Command2("run").description("Run a one-shot task with the agent").argument("<task>", "task to execute").option("--provider <name>", "use a specific provider from config").option("--model <id>", "use a specific model of the provider for this run").option("--allow-destructive", "approve all destructive operations without prompting").option("--deny-destructive", "block all destructive operations automatically").option("--plan", "plan-and-execute mode: produce a plan, approve, then execute step by step").option("--yes, --approve-plan", "auto-approve the plan in --plan mode (no prompt)").option("--agent <profile>", "use an agent profile (mode primary/all) as the main agent").option(
|
|
22433
22684
|
"--delegate <profile>",
|
|
22434
22685
|
"hand the task straight to a subagent profile (mode subagent/all), without the main agent"
|
|
22435
22686
|
).option("--read-only", "observation only: no file writes and only read-only commands").option("--profile <name>", "session profile: auto | code | infra | full | <custom>").option("--infra", "shortcut for --profile infra").option("--code", "shortcut for --profile code").option("--log-level <level>", "log level: trace|debug|info|warn|error|silent").option("--debug", "enable verbose debug logging (level debug + file sink)").action(
|
|
@@ -22469,6 +22720,12 @@ var runCommand = new Command2("run").description("Run a one-shot task with the a
|
|
|
22469
22720
|
getLogger("cli").debug("run start", { task: task.slice(0, 120) });
|
|
22470
22721
|
warnInheritedGitRouting();
|
|
22471
22722
|
warnConfigDeprecations();
|
|
22723
|
+
const startupModel = await resolveStartupModel(config, opts);
|
|
22724
|
+
if (!startupModel.ok) {
|
|
22725
|
+
process.stderr.write(`[fatal] ${startupModel.error}
|
|
22726
|
+
`);
|
|
22727
|
+
process.exit(1);
|
|
22728
|
+
}
|
|
22472
22729
|
let router;
|
|
22473
22730
|
try {
|
|
22474
22731
|
router = new ProviderRouter(config, opts.provider);
|
|
@@ -22477,6 +22734,9 @@ var runCommand = new Command2("run").description("Run a one-shot task with the a
|
|
|
22477
22734
|
`);
|
|
22478
22735
|
process.exit(1);
|
|
22479
22736
|
}
|
|
22737
|
+
if (router.contextWindowIsDiscoverable && !startupModel.fetched) {
|
|
22738
|
+
await discoverContextWindow(router.getActiveConfig(), { timeoutMs: 3e3 });
|
|
22739
|
+
}
|
|
22480
22740
|
const registry = new ToolRegistry();
|
|
22481
22741
|
registerBuiltinTools(registry, config);
|
|
22482
22742
|
const mcpManager = new McpManager(config);
|
|
@@ -22997,6 +23257,7 @@ var configCommand = new Command5("config").description("Get or set configuration
|
|
|
22997
23257
|
init_esm_shims();
|
|
22998
23258
|
init_loader();
|
|
22999
23259
|
init_router();
|
|
23260
|
+
init_startup_model();
|
|
23000
23261
|
init_registry();
|
|
23001
23262
|
init_tools();
|
|
23002
23263
|
init_core();
|
|
@@ -23023,7 +23284,7 @@ var DEFAULT_CONFIG = {
|
|
|
23023
23284
|
var initCommand = new Command6("init").description("Explore the project and create or update STRATUM.md").argument(
|
|
23024
23285
|
"[focus]",
|
|
23025
23286
|
'Optional focus or constraints for the agent (e.g. "focus on the test setup")'
|
|
23026
|
-
).option("--provider <name>", "use a specific provider from config").option("--allow-destructive", "approve all destructive operations without prompting").action(
|
|
23287
|
+
).option("--provider <name>", "use a specific provider from config").option("--model <id>", "use a specific model of the provider for this run").option("--allow-destructive", "approve all destructive operations without prompting").action(
|
|
23027
23288
|
async (focus, opts) => {
|
|
23028
23289
|
const cwd = process.cwd();
|
|
23029
23290
|
process.stdout.write("\n Stratum \u2014 Inicializando proyecto\n\n");
|
|
@@ -23043,6 +23304,13 @@ var initCommand = new Command6("init").description("Explore the project and crea
|
|
|
23043
23304
|
);
|
|
23044
23305
|
process.exit(1);
|
|
23045
23306
|
}
|
|
23307
|
+
const startupModel = await resolveStartupModel(config, opts);
|
|
23308
|
+
if (!startupModel.ok) {
|
|
23309
|
+
process.stderr.write(`
|
|
23310
|
+
[error] Provider: ${startupModel.error}
|
|
23311
|
+
`);
|
|
23312
|
+
process.exit(1);
|
|
23313
|
+
}
|
|
23046
23314
|
let router;
|
|
23047
23315
|
try {
|
|
23048
23316
|
router = new ProviderRouter(config, opts.provider);
|
|
@@ -23174,8 +23442,10 @@ init_esm_shims();
|
|
|
23174
23442
|
init_loader();
|
|
23175
23443
|
init_openai_compatible();
|
|
23176
23444
|
init_writer();
|
|
23445
|
+
init_utils();
|
|
23177
23446
|
init_ProviderWizard();
|
|
23178
23447
|
init_theme();
|
|
23448
|
+
init_wizard_logic();
|
|
23179
23449
|
import { Command as Command7 } from "commander";
|
|
23180
23450
|
import React12 from "react";
|
|
23181
23451
|
import { render as render2, Box as Box28, Text as Text28 } from "ink";
|
|
@@ -23199,13 +23469,75 @@ function AddApp({ existingNames, onDone }) {
|
|
|
23199
23469
|
}
|
|
23200
23470
|
);
|
|
23201
23471
|
}
|
|
23202
|
-
|
|
23472
|
+
function fail(message) {
|
|
23473
|
+
process.stderr.write(`${message}
|
|
23474
|
+
`);
|
|
23475
|
+
process.exit(1);
|
|
23476
|
+
}
|
|
23477
|
+
var errorText = (err) => err instanceof Error ? err.message : String(err);
|
|
23478
|
+
async function addFromFlags(name, flags, existingNames) {
|
|
23479
|
+
if (!name) fail("Falta el alias: stratum provider add <alias> --base-url <url> \u2026");
|
|
23480
|
+
if (!flags.baseUrl) fail("Falta --base-url <url>.");
|
|
23481
|
+
if (flags.apiKey && flags.apiKeyEnv) fail("Usa --api-key o --api-key-env, no los dos.");
|
|
23482
|
+
const aliasError = validateAlias(name, existingNames);
|
|
23483
|
+
if (aliasError) fail(aliasError);
|
|
23484
|
+
const urlError = validateBaseUrl(flags.baseUrl);
|
|
23485
|
+
if (urlError) fail(urlError);
|
|
23486
|
+
let contextWindow;
|
|
23487
|
+
if (flags.contextWindow !== void 0) {
|
|
23488
|
+
contextWindow = Number(flags.contextWindow);
|
|
23489
|
+
if (!Number.isInteger(contextWindow) || contextWindow <= 0) {
|
|
23490
|
+
fail(`--context-window inv\xE1lido: ${flags.contextWindow}`);
|
|
23491
|
+
}
|
|
23492
|
+
}
|
|
23493
|
+
const storedKey = flags.apiKeyEnv ? "${" + flags.apiKeyEnv + "}" : flags.apiKey ?? "";
|
|
23494
|
+
const { key, missing } = resolveApiKey(storedKey);
|
|
23495
|
+
if (missing.length > 0) fail(`Variable de entorno no definida: ${missing.join(", ")}`);
|
|
23496
|
+
let models = [];
|
|
23497
|
+
try {
|
|
23498
|
+
models = await fetchModelInfos(flags.baseUrl, key);
|
|
23499
|
+
process.stdout.write(
|
|
23500
|
+
`${models.length} modelos en ${flags.baseUrl}: ${models.map((m) => m.id).join(", ")}
|
|
23501
|
+
`
|
|
23502
|
+
);
|
|
23503
|
+
} catch (err) {
|
|
23504
|
+
process.stderr.write(`Aviso: no se pudieron listar los modelos (${errorText(err)}).
|
|
23505
|
+
`);
|
|
23506
|
+
}
|
|
23507
|
+
const model = flags.model?.trim();
|
|
23508
|
+
if (model && models.length > 0 && !models.some((m) => m.id === model)) {
|
|
23509
|
+
fail(`El modelo "${model}" no est\xE1 entre los que expone el provider.`);
|
|
23510
|
+
}
|
|
23511
|
+
try {
|
|
23512
|
+
const entry = buildProviderEntry({
|
|
23513
|
+
baseUrl: flags.baseUrl,
|
|
23514
|
+
apiKey: storedKey,
|
|
23515
|
+
model,
|
|
23516
|
+
contextWindow
|
|
23517
|
+
});
|
|
23518
|
+
const { configPath, created } = upsertProvider(name, entry, flags.default === true);
|
|
23519
|
+
process.stdout.write(`${created ? "Creado" : "Actualizado"} ${configPath}
|
|
23520
|
+
`);
|
|
23521
|
+
process.stdout.write(
|
|
23522
|
+
model ? `Provider "${name}" configurado con el modelo ${model}.
|
|
23523
|
+
` : `Provider "${name}" configurado sin modelo fijo: se elige al arrancar \`stratum chat\`, o con --model en \`stratum run\`.
|
|
23524
|
+
`
|
|
23525
|
+
);
|
|
23526
|
+
} catch (err) {
|
|
23527
|
+
fail(`Error al escribir la config: ${errorText(err)}`);
|
|
23528
|
+
}
|
|
23529
|
+
}
|
|
23530
|
+
var providerAdd = new Command7("add").description("A\xF1ade un provider a .stratumrc.json: wizard interactivo, o directo con --base-url").argument("[name]", "alias del provider (solo con --base-url)").option("--base-url <url>", "URL base de la API (con /v1): alta sin wizard").option("--api-key-env <VAR>", "variable de entorno con la API key (se guarda como ${VAR})").option("--api-key <key>", "API key literal (queda en claro en el archivo)").option("--model <id>", "modelo por defecto; sin \xE9l se elige al arrancar").option("--context-window <n>", "ventana de contexto en tokens").option("--default", "dejarlo como provider activo").action(async (name, flags) => {
|
|
23203
23531
|
let existingNames = [];
|
|
23204
23532
|
try {
|
|
23205
23533
|
const config = loadConfig();
|
|
23206
23534
|
existingNames = Object.keys(config.provider?.providers ?? {});
|
|
23207
23535
|
} catch {
|
|
23208
23536
|
}
|
|
23537
|
+
if (flags.baseUrl || name) {
|
|
23538
|
+
await addFromFlags(name, flags, existingNames);
|
|
23539
|
+
return;
|
|
23540
|
+
}
|
|
23209
23541
|
let result = null;
|
|
23210
23542
|
const { unmount, waitUntilExit } = render2(
|
|
23211
23543
|
React12.createElement(AddApp, {
|
|
@@ -23229,7 +23561,7 @@ var providerAdd = new Command7("add").description("Wizard interactivo para a\xF1
|
|
|
23229
23561
|
`${created ? "Creado" : "Actualizado"} ${configPath}` + (backupPath ? ` (backup: ${backupPath})` : "") + "\n"
|
|
23230
23562
|
);
|
|
23231
23563
|
if (r.makeDefault) {
|
|
23232
|
-
process.stdout.write(`Provider activo: ${r.name} (${r.config.model})
|
|
23564
|
+
process.stdout.write(`Provider activo: ${r.name} (${r.config.model ?? ""})
|
|
23233
23565
|
`);
|
|
23234
23566
|
}
|
|
23235
23567
|
} catch (err) {
|
|
@@ -23264,7 +23596,8 @@ var providerList = new Command7("list").description("Lista los providers configu
|
|
|
23264
23596
|
const col = (s, w) => s.length > w ? s.slice(0, w - 1) + "\u2026" : s.padEnd(w);
|
|
23265
23597
|
const wName = Math.max(...names.map((n) => n.length + 2), 8);
|
|
23266
23598
|
const wUrl = Math.max(...names.map((n) => providers[n].baseUrl.length), 10);
|
|
23267
|
-
const
|
|
23599
|
+
const modelOf = (n) => providers[n].model || "(al arrancar)";
|
|
23600
|
+
const wModel = Math.max(...names.map((n) => modelOf(n).length), 8);
|
|
23268
23601
|
process.stdout.write(
|
|
23269
23602
|
chalk3.bold(
|
|
23270
23603
|
` ${col("ALIAS", wName)} ${col("TIPO", 18)} ${col("BASE URL", wUrl)} ${col("MODELO", wModel)} ESTADO
|
|
@@ -23277,12 +23610,55 @@ var providerList = new Command7("list").description("Lista los providers configu
|
|
|
23277
23610
|
const mark = name === defaultName ? chalk3.hex("#F59E0B")("\u25B6 ") : " ";
|
|
23278
23611
|
const alias = name === defaultName ? chalk3.bold(col(name, wName)) : col(name, wName);
|
|
23279
23612
|
process.stdout.write(
|
|
23280
|
-
`${mark}${alias} ${col(p.type, 18)} ${col(p.baseUrl, wUrl)} ${col(
|
|
23613
|
+
`${mark}${alias} ${col(p.type, 18)} ${col(p.baseUrl, wUrl)} ${col(modelOf(name), wModel)} ${dot}
|
|
23281
23614
|
`
|
|
23282
23615
|
);
|
|
23283
23616
|
});
|
|
23284
23617
|
process.stdout.write(chalk3.dim("\n \u25B6 = provider activo \xB7 \u25CF verde = /models responde\n"));
|
|
23285
23618
|
});
|
|
23619
|
+
var providerModels = new Command7("models").description("Lista los modelos que expone un provider (GET /models)").argument("[name]", "alias del provider (por defecto, el activo)").option("--set <id>", "fija ese modelo como el por defecto del provider").action(async (name, opts) => {
|
|
23620
|
+
let config;
|
|
23621
|
+
try {
|
|
23622
|
+
config = loadConfig();
|
|
23623
|
+
} catch (err) {
|
|
23624
|
+
fail(`Config error: ${errorText(err)}`);
|
|
23625
|
+
}
|
|
23626
|
+
const alias = name ?? config.provider?.default;
|
|
23627
|
+
const entry = alias ? config.provider?.providers[alias] : void 0;
|
|
23628
|
+
if (!alias || !entry) fail(`Provider "${alias ?? ""}" no existe en la config.`);
|
|
23629
|
+
let models;
|
|
23630
|
+
try {
|
|
23631
|
+
models = await fetchModelInfos(entry.baseUrl, entry.apiKey);
|
|
23632
|
+
} catch (err) {
|
|
23633
|
+
fail(`No se pudieron listar los modelos: ${errorText(err)}`);
|
|
23634
|
+
}
|
|
23635
|
+
if (opts.set) {
|
|
23636
|
+
if (models.length > 0 && !models.some((m) => m.id === opts.set)) {
|
|
23637
|
+
fail(`El modelo "${opts.set}" no est\xE1 entre los que expone "${alias}".`);
|
|
23638
|
+
}
|
|
23639
|
+
let path2;
|
|
23640
|
+
try {
|
|
23641
|
+
path2 = setProviderModel(alias, opts.set);
|
|
23642
|
+
} catch (err) {
|
|
23643
|
+
fail(`Error al escribir la config: ${errorText(err)}`);
|
|
23644
|
+
}
|
|
23645
|
+
if (!path2) fail(`"${alias}" no est\xE1 definido en ning\xFAn .stratumrc.json escribible.`);
|
|
23646
|
+
process.stdout.write(`Modelo por defecto de "${alias}": ${opts.set} (${path2})
|
|
23647
|
+
`);
|
|
23648
|
+
return;
|
|
23649
|
+
}
|
|
23650
|
+
for (const m of models) {
|
|
23651
|
+
const mark = m.id === entry.model ? chalk3.hex("#F59E0B")("\u25B6 ") : " ";
|
|
23652
|
+
const ctx = entry.models?.[m.id]?.contextWindow ?? m.contextWindow;
|
|
23653
|
+
process.stdout.write(`${mark}${m.id}${ctx ? chalk3.dim(` ${ctx} tokens`) : ""}
|
|
23654
|
+
`);
|
|
23655
|
+
}
|
|
23656
|
+
process.stdout.write(
|
|
23657
|
+
chalk3.dim(`
|
|
23658
|
+
${models.length} modelos en ${entry.baseUrl} \xB7 \u25B6 = modelo por defecto
|
|
23659
|
+
`)
|
|
23660
|
+
);
|
|
23661
|
+
});
|
|
23286
23662
|
var providerUse = new Command7("use").description("Cambia el provider activo (provider.default)").argument("<name>", "alias del provider").action((name) => {
|
|
23287
23663
|
try {
|
|
23288
23664
|
const { configPath, backupPath } = setDefaultProvider(name);
|
|
@@ -23295,16 +23671,17 @@ var providerUse = new Command7("use").description("Cambia el provider activo (pr
|
|
|
23295
23671
|
process.exit(1);
|
|
23296
23672
|
}
|
|
23297
23673
|
});
|
|
23298
|
-
var providerRemove = new Command7("remove").description("Elimina un provider de la config").argument("<name>", "alias del provider").action((name) => {
|
|
23674
|
+
var providerRemove = new Command7("remove").alias("rm").description("Elimina un provider (con su modelo y sus ajustes por modelo) de la config").argument("<name>", "alias del provider").action((name) => {
|
|
23299
23675
|
try {
|
|
23300
|
-
const { configPath, backupPath, newDefault }
|
|
23301
|
-
|
|
23676
|
+
for (const { configPath, backupPath, newDefault } of removeProviderEverywhere(name)) {
|
|
23677
|
+
process.stdout.write(`Eliminado "${name}" de ${configPath}
|
|
23302
23678
|
`);
|
|
23303
|
-
|
|
23679
|
+
if (backupPath) process.stdout.write(`Backup: ${backupPath}
|
|
23304
23680
|
`);
|
|
23305
|
-
|
|
23306
|
-
|
|
23681
|
+
if (newDefault) {
|
|
23682
|
+
process.stdout.write(`El provider activo era "${name}" \u2192 nuevo activo: ${newDefault}
|
|
23307
23683
|
`);
|
|
23684
|
+
}
|
|
23308
23685
|
}
|
|
23309
23686
|
} catch (err) {
|
|
23310
23687
|
process.stderr.write(`${err.message}
|
|
@@ -23312,7 +23689,7 @@ var providerRemove = new Command7("remove").description("Elimina un provider de
|
|
|
23312
23689
|
process.exit(1);
|
|
23313
23690
|
}
|
|
23314
23691
|
});
|
|
23315
|
-
var providerCommand = new Command7("provider").alias("providers").description("Gesti\xF3n de providers LLM (add/list/use/remove) sin editar .stratumrc.json a mano").addCommand(providerAdd).addCommand(providerList).addCommand(providerUse).addCommand(providerRemove);
|
|
23692
|
+
var providerCommand = new Command7("provider").alias("providers").description("Gesti\xF3n de providers LLM (add/list/use/remove) sin editar .stratumrc.json a mano").addCommand(providerAdd).addCommand(providerList).addCommand(providerModels).addCommand(providerUse).addCommand(providerRemove);
|
|
23316
23693
|
|
|
23317
23694
|
// src/cli/commands/mcp.ts
|
|
23318
23695
|
init_esm_shims();
|
|
@@ -23693,7 +24070,7 @@ var agentsCommand = new Command11("agents").description("Inspect agent profiles
|
|
|
23693
24070
|
init_loader();
|
|
23694
24071
|
init_schema_version();
|
|
23695
24072
|
function resolveVersion2() {
|
|
23696
|
-
if (true) return "0.7.0
|
|
24073
|
+
if (true) return "0.7.0";
|
|
23697
24074
|
const thisDir = dirname14(fileURLToPath3(import.meta.url));
|
|
23698
24075
|
const pkg = JSON.parse(readFileSync20(join26(thisDir, "..", "..", "package.json"), "utf-8"));
|
|
23699
24076
|
return pkg.version;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stratum-cli",
|
|
3
|
-
"version": "0.7.0
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Agente CLI extensible sobre un loop ReAct — provider-agnostic (Ollama, llama.cpp, vLLM, LiteLLM, OpenAI), con plan-and-execute, subagentes, MCP y SSH nativo",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|