@jmanuelcorral/openteam 0.1.14 → 0.1.15

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 CHANGED
@@ -207,9 +207,9 @@ Tras `init`, autentica el provider frontier (`opencode auth login`), arranca tu
207
207
 
208
208
  `openteam init` genera un único **agente primario** llamado `openteam` (`.opencode/agent/openteam.md`). Pulsa **Tab** en opencode para seleccionarlo: así sabes explícitamente que estás en modo openteam, mientras el plugin sigue optimizando el modelo por debajo.
209
209
 
210
- Ese orquestador funciona al estilo [Squad](https://bradygaster.github.io/squad/): **no trae subagentes precreados**. En **cada petición** su primera acción es comprobar si ya existe equipo (`roster.md` + subagentes en `.opencode/agent/`). Si **existe**, reparte el trabajo entre los especialistas adecuados; si **no existe**, entiende las tareas que implica tu petición, diseña el equipo mínimo y **lo crea a demanda** —sin que tengas que pedírselo explícitamente— antes de pasarle esas tareas (escribiendo `.opencode/agent/<nombre>.md` con `mode: subagent`), reutilizando los existentes antes de crear nuevos. Los roles mecánicos/bulk se dejan enrutar a modelos locales y solo se escala a frontier para arquitectura, seguridad o debugging ambiguo.
210
+ Ese orquestador funciona al estilo [Squad](https://bradygaster.github.io/squad/): **no trae subagentes precreados**. En **cada petición** su primera acción es comprobar si ya existe equipo (`.opencode/openteam-roster.md` + subagentes en `.opencode/agent/`). Si **existe**, reparte el trabajo entre los especialistas adecuados; si **no existe**, entiende las tareas que implica tu petición, diseña el equipo mínimo y **lo crea a demanda** —sin que tengas que pedírselo explícitamente— antes de pasarle esas tareas (escribiendo `.opencode/agent/<nombre>.md` con `mode: subagent`), reutilizando los existentes antes de crear nuevos. Los roles mecánicos/bulk se dejan enrutar a modelos locales y solo se escala a frontier para arquitectura, seguridad o debugging ambiguo.
211
211
 
212
- Como Squad, el equipo tiene **identidad temática**: el orquestador elige (o te pregunta) un **universo** y nombra a cada agente con un personaje de ese universo, registrando el reparto en `.opencode/agent/roster.md` para que los nombres **persistan** entre sesiones. Además de los roles específicos del proyecto, contempla tres **roles estándar** creados a demanda:
212
+ Como Squad, el equipo tiene **identidad temática**: el orquestador elige (o te pregunta) un **universo** y nombra a cada agente con un personaje de ese universo, registrando el reparto en `.opencode/openteam-roster.md` (fuera de `.opencode/agent/`, para que opencode no lo cargue como un agente fantasma) para que los nombres **persistan** entre sesiones. Además de los roles específicos del proyecto, contempla tres **roles estándar** creados a demanda:
213
213
 
214
214
  - **scribe** — memoria silenciosa: registra decisiones y aprendizajes en `.opencode/openteam-decisions.md` sin tocar el código (modelo local).
215
215
  - **ralph** — automatización y triage: monitoriza el trabajo pendiente, lo prioriza, coordina la ejecución y **escala al humano** ante bloqueos o aprobaciones.
@@ -364,6 +364,7 @@ npm i -g @jmanuelcorral/openteam
364
364
  openteam baseline show
365
365
  openteam baseline set openai/gpt-5-mini
366
366
  openteam doctor --config .opencode/openteam.json
367
+ openteam agents
367
368
  openteam report --telemetry .opencode/openteam-telemetry.jsonl
368
369
  openteam yolo status
369
370
  openteam yolo on
@@ -371,6 +372,28 @@ openteam yolo on
371
372
 
372
373
  Flags: `--config <path>` y `--telemetry <path>` sobrescriben las rutas por defecto; `--opencode <path>` apunta el comando `yolo` a un `opencode.json` concreto.
373
374
 
375
+ ### Ver el LLM de cada agente: `openteam agents`
376
+
377
+ Lista los agentes de `.opencode/agent/` indicando **qué LLM usa cada uno**: si es **local** (Ollama/LM Studio/Foundry, sin coste de tokens) o **frontier** (consume tu suscripción), y el **proveedor** (Anthropic, OpenAI, GitHub Copilot, Google…). Los agentes sin `model` propio **heredan** el default de `opencode.json` (que openteam enruta local-first por mensaje).
378
+
379
+ ```powershell
380
+ openteam agents
381
+ ```
382
+
383
+ ```text
384
+ openteam agents — LLM por agente:
385
+
386
+ ● openteam [primary ] frontier · Anthropic (consume tu suscripción) (anthropic/claude-sonnet-4-5)
387
+ ○ basher [subagent] local · Ollama (sin coste de tokens) (ollama/qwen3:8b)
388
+ ○ scribe [subagent] hereda default → frontier · Anthropic (consume tu suscripción) · openteam enruta local-first por mensaje
389
+
390
+ Default de opencode.json: anthropic/claude-sonnet-4-5 (frontier · Anthropic)
391
+ Leyenda: ● primary · ○ subagent · local = sin coste de tokens · frontier = consume tu suscripción.
392
+ ```
393
+
394
+ También está disponible **dentro de opencode**: pídele a openteam la lista de agentes y ejecutará la acción `agents` de su herramienta de comandos.
395
+
396
+
374
397
  ### Modo YOLO (auto-aprobar permisos)
375
398
 
376
399
  El **modo YOLO** hace que opencode auto-apruebe **todos** los permisos (equivale al flag nativo `opencode --auto`, pero persistente). Escribe la regla global `permission: { "*": "allow" }` en `opencode.json` y **regenera el agente `openteam`** (`.opencode/agent/openteam.md`) con el mismo comodín, conservando el resto de tu configuración:
package/dist/cli.js CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { execFile } from "node:child_process";
5
- import { access, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
6
- import { dirname as dirname2 } from "node:path";
5
+ import { access, mkdir as mkdir2, readdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
6
+ import { dirname as dirname2, join as join2 } from "node:path";
7
7
  import { promisify } from "node:util";
8
8
 
9
9
  // src/cli/initAdapters.ts
@@ -791,6 +791,158 @@ function createDetect(exec) {
791
791
  };
792
792
  }
793
793
 
794
+ // src/commands/agents.ts
795
+ var PROVIDER_LABELS = {
796
+ "github-copilot": "GitHub Copilot",
797
+ anthropic: "Anthropic",
798
+ openai: "OpenAI",
799
+ google: "Google",
800
+ openrouter: "OpenRouter",
801
+ ollama: "Ollama",
802
+ lmstudio: "LM Studio",
803
+ "foundry-local": "Foundry Local"
804
+ };
805
+ function providerLabel(providerID) {
806
+ return PROVIDER_LABELS[providerID] ?? providerID;
807
+ }
808
+ function unquote(value) {
809
+ const trimmed = value.trim();
810
+ if (trimmed.length >= 2 && (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'"))) {
811
+ return trimmed.slice(1, -1);
812
+ }
813
+ return trimmed;
814
+ }
815
+ function parseModelValue(value) {
816
+ const raw = unquote(value);
817
+ const slash = raw.indexOf("/");
818
+ if (slash <= 0 || slash === raw.length - 1) {
819
+ return;
820
+ }
821
+ return { providerID: raw.slice(0, slash), modelID: raw.slice(slash + 1) };
822
+ }
823
+ function parseAgentFile(file) {
824
+ const info = { name: file.name, mode: "subagent" };
825
+ const lines = file.contents.split(/\r?\n/);
826
+ if (lines[0]?.trim() !== "---") {
827
+ return info;
828
+ }
829
+ for (let i = 1;i < lines.length; i += 1) {
830
+ const line = lines[i] ?? "";
831
+ if (line.trim() === "---") {
832
+ break;
833
+ }
834
+ const colon = line.indexOf(":");
835
+ if (colon <= 0) {
836
+ continue;
837
+ }
838
+ const key = line.slice(0, colon).trim();
839
+ const value = line.slice(colon + 1).trim();
840
+ if (key === "mode") {
841
+ info.mode = unquote(value) || "subagent";
842
+ } else if (key === "description") {
843
+ info.description = unquote(value);
844
+ } else if (key === "model") {
845
+ const model = parseModelValue(value);
846
+ if (model !== undefined) {
847
+ info.model = model;
848
+ }
849
+ }
850
+ }
851
+ return info;
852
+ }
853
+ function localProviderIDs(config) {
854
+ const ids = new Set;
855
+ for (const runtime of config.local.runtimes) {
856
+ ids.add(runtime.id);
857
+ ids.add(runtime.defaultModel.providerID);
858
+ }
859
+ ids.add(config.router.localDefault.providerID);
860
+ return ids;
861
+ }
862
+ function subscriptionLabel(model, localProviders) {
863
+ const isLocal = localProviders.has(model.providerID);
864
+ if (isLocal) {
865
+ return {
866
+ kind: "local",
867
+ subscription: `local · ${providerLabel(model.providerID)} (sin coste de tokens)`
868
+ };
869
+ }
870
+ return {
871
+ kind: "frontier",
872
+ subscription: `frontier · ${providerLabel(model.providerID)} (consume tu suscripción)`
873
+ };
874
+ }
875
+ function classifyAgent(info, context) {
876
+ if (info.model !== undefined) {
877
+ const { kind, subscription: subscription2 } = subscriptionLabel(info.model, context.localProviders);
878
+ return {
879
+ name: info.name,
880
+ mode: info.mode,
881
+ kind,
882
+ model: info.model,
883
+ subscription: subscription2
884
+ };
885
+ }
886
+ if (context.defaultModel === undefined) {
887
+ return {
888
+ name: info.name,
889
+ mode: info.mode,
890
+ kind: "inherited",
891
+ subscription: "hereda el default de opencode.json (sin definir) · openteam enruta local-first por mensaje"
892
+ };
893
+ }
894
+ const { subscription } = subscriptionLabel(context.defaultModel, context.localProviders);
895
+ return {
896
+ name: info.name,
897
+ mode: info.mode,
898
+ kind: "inherited",
899
+ model: context.defaultModel,
900
+ subscription: `hereda default → ${subscription} · openteam enruta local-first por mensaje`
901
+ };
902
+ }
903
+ function modeRank(mode) {
904
+ if (mode === "primary") {
905
+ return 0;
906
+ }
907
+ if (mode === "all") {
908
+ return 1;
909
+ }
910
+ return 2;
911
+ }
912
+ function classifyAgents(files, context) {
913
+ return files.map((file) => classifyAgent(parseAgentFile(file), context)).sort((a, b) => {
914
+ const rank = modeRank(a.mode) - modeRank(b.mode);
915
+ return rank !== 0 ? rank : a.name.localeCompare(b.name);
916
+ });
917
+ }
918
+ function modelText(classification) {
919
+ return classification.model === undefined ? "" : ` (${classification.model.providerID}/${classification.model.modelID})`;
920
+ }
921
+ function renderAgentList(classifications, context) {
922
+ const lines = ["openteam agents — LLM por agente:", ""];
923
+ if (classifications.length === 0) {
924
+ lines.push(" (sin agentes en .opencode/agent/) — ejecuta 'openteam init' o pide al orquestador que cree el equipo.");
925
+ return lines.join(`
926
+ `);
927
+ }
928
+ const nameWidth = Math.max(...classifications.map((c) => c.name.length), "agente".length);
929
+ const modeWidth = Math.max(...classifications.map((c) => c.mode.length), "subagent".length);
930
+ for (const c of classifications) {
931
+ const bullet = c.mode === "primary" ? "●" : "○";
932
+ lines.push(` ${bullet} ${c.name.padEnd(nameWidth)} [${c.mode.padEnd(modeWidth)}] ${c.subscription}${modelText(c)}`);
933
+ }
934
+ lines.push("");
935
+ if (context.defaultModel !== undefined) {
936
+ const { kind } = subscriptionLabel(context.defaultModel, context.localProviders);
937
+ lines.push(`Default de opencode.json: ${context.defaultModel.providerID}/${context.defaultModel.modelID} (${kind} · ${providerLabel(context.defaultModel.providerID)})`);
938
+ } else {
939
+ lines.push("Default de opencode.json: (sin definir)");
940
+ }
941
+ lines.push("Leyenda: ● primary · ○ subagent · local = sin coste de tokens · frontier = consume tu suscripción.");
942
+ return lines.join(`
943
+ `);
944
+ }
945
+
794
946
  // src/config/schema.ts
795
947
  import { z as z2 } from "zod";
796
948
  var ModelRefSchema = z2.object({
@@ -997,14 +1149,14 @@ function buildOrchestratorAgent(frontier, options = {}) {
997
1149
  "Antes de responder o delegar **cualquier** petición, tu **primera acción",
998
1150
  "siempre** es comprobar si ya existe un equipo, aunque el usuario no te pida",
999
1151
  "explícitamente crearlo. Nunca asumas que hay equipo ni que no lo hay:",
1000
- "verifícalo mirando `.opencode/agent/roster.md` y los `.opencode/agent/*.md`",
1152
+ "verifícalo mirando `.opencode/openteam-roster.md` y los `.opencode/agent/*.md`",
1001
1153
  "con `mode: subagent` (usa `read`/`glob`/`list`).",
1002
1154
  "",
1003
1155
  "- **Si el equipo YA existe** → actúa como coordinador: entiende la tarea,",
1004
1156
  " repártela entre los subagentes adecuados con la herramienta `task` y",
1005
1157
  " reutiliza el reparto existente. No vuelvas a castear ni recrear agentes.",
1006
- "- **Si NO existe equipo** (no hay `roster.md` ni subagentes) **no pidas",
1007
- " permiso para crearlo**: entiende primero las tareas que implica la",
1158
+ "- **Si NO existe equipo** (sin `openteam-roster.md` ni subagentes): **no pidas",
1159
+ " permiso para crearlo**. Entiende primero las tareas que implica la",
1008
1160
  " petición, diseña el equipo mínimo necesario, **créalo a demanda** y luego",
1009
1161
  " pásale esas tareas. Crear el equipo es parte implícita de atender la",
1010
1162
  " petición, no un paso opcional que el usuario deba solicitar.",
@@ -1022,9 +1174,11 @@ function buildOrchestratorAgent(frontier, options = {}) {
1022
1174
  "2. Asigna a cada subagente el **nombre de un personaje** de ese universo; el",
1023
1175
  " nombre del fichero (`.opencode/agent/<nombre>.md`) es su alias para",
1024
1176
  " `@mención`, y su rol queda claro en el `description`.",
1025
- "3. **Registra el casting** en `.opencode/agent/roster.md` (universo + tabla",
1177
+ "3. **Registra el casting** en `.opencode/openteam-roster.md` (universo + tabla",
1026
1178
  " nombre → rol → modelo) para que los nombres **persistan** entre sesiones.",
1027
- " Reutiliza siempre el mismo reparto; no vuelvas a castear sin motivo.",
1179
+ " Este fichero va **fuera** de `.opencode/agent/` (si no, opencode lo cargaría",
1180
+ " como un agente fantasma). Reutiliza siempre el mismo reparto; no vuelvas a",
1181
+ " castear sin motivo.",
1028
1182
  "",
1029
1183
  "## Cómo crear el equipo cuando no existe",
1030
1184
  "",
@@ -1048,8 +1202,8 @@ function buildOrchestratorAgent(frontier, options = {}) {
1048
1202
  " frontier solo para arquitectura, seguridad o debugging ambiguo:",
1049
1203
  " openteam ya enruta cheapest-capable automáticamente.",
1050
1204
  " - Un prompt de rol enfocado y accionable.",
1051
- "4. Registra el reparto en `roster.md` y **reparte las tareas** al equipo",
1052
- " recién creado con la herramienta `task`.",
1205
+ "4. Registra el reparto en `.opencode/openteam-roster.md` y **reparte las",
1206
+ " tareas** al equipo recién creado con la herramienta `task`.",
1053
1207
  "",
1054
1208
  "## Roles estándar del equipo",
1055
1209
  "",
@@ -1122,8 +1276,8 @@ function buildOrchestratorAgent(frontier, options = {}) {
1122
1276
  "- **Lanza en un solo turno las tareas independientes** para que corran en",
1123
1277
  " paralelo; secuencia solo las que dependen de un resultado previo.",
1124
1278
  "- Crea subagentes **justo cuando el trabajo lo pide**, nunca “por si acaso”.",
1125
- " Revisa `.opencode/agent/` y el `roster.md`, y reutiliza los existentes antes",
1126
- " de crear uno nuevo.",
1279
+ " Revisa `.opencode/agent/` y el `openteam-roster.md`, y reutiliza los",
1280
+ " existentes antes de crear uno nuevo.",
1127
1281
  "- Respeta el routing y la privacidad de openteam; el `guardian` es la última",
1128
1282
  " línea antes de enviar nada sensible a frontier.",
1129
1283
  "- Resume al usuario qué universo elegiste, qué equipo has creado y por qué.",
@@ -1131,7 +1285,7 @@ function buildOrchestratorAgent(frontier, options = {}) {
1131
1285
  "## Límites",
1132
1286
  "",
1133
1287
  "- Un único primario (tú); todo lo demás son subagentes creados a demanda.",
1134
- "- No borres ni sobrescribas subagentes ni el `roster.md` sin confirmarlo.",
1288
+ "- No borres ni sobrescribas subagentes ni el `openteam-roster.md` sin confirmarlo.",
1135
1289
  "- Los subagentes nuevos quedan disponibles para `@mención` y para la",
1136
1290
  " herramienta `task` una vez opencode recarga los agentes del proyecto.",
1137
1291
  ""
@@ -1260,6 +1414,7 @@ var HELP = [
1260
1414
  " openteam baseline set <p/model> Fija el baseline (modo pinned)",
1261
1415
  " openteam baseline auto Baseline cheapest-capable (modo auto)",
1262
1416
  " openteam doctor Diagnóstico de runtimes y config",
1417
+ " openteam agents Lista los agentes y el LLM (local/frontier) de cada uno",
1263
1418
  " openteam report Resumen de coste/ahorro (telemetría)",
1264
1419
  " openteam yolo status Muestra si el modo YOLO está activo",
1265
1420
  " openteam yolo on Activa YOLO (opencode auto-aprueba todo)",
@@ -1390,6 +1545,26 @@ ${deps.orchestratorAgentPath} regenerado (permission acorde a YOLO).`;
1390
1545
  Aviso: no pude regenerar ${deps.orchestratorAgentPath} (model inválido en opencode.json); ajusta su permission a mano.`;
1391
1546
  }
1392
1547
  }
1548
+ async function runAgents(deps, configPath, opencodeConfigPath) {
1549
+ const [config, opencodeConfig, files] = await Promise.all([
1550
+ deps.loadConfig(configPath),
1551
+ deps.readOpencodeConfig(opencodeConfigPath),
1552
+ deps.listAgentFiles(deps.agentDir)
1553
+ ]);
1554
+ const localProviders = localProviderIDs(config);
1555
+ const rawDefault = opencodeConfig?.model;
1556
+ let defaultModel;
1557
+ if (typeof rawDefault === "string" && rawDefault.trim() !== "") {
1558
+ try {
1559
+ defaultModel = parseModelRef(rawDefault);
1560
+ } catch {
1561
+ defaultModel = undefined;
1562
+ }
1563
+ }
1564
+ const context = defaultModel === undefined ? { localProviders } : { localProviders, defaultModel };
1565
+ const classifications = classifyAgents(files, context);
1566
+ return { exitCode: 0, stdout: renderAgentList(classifications, context) };
1567
+ }
1393
1568
  async function runCli(argv, deps) {
1394
1569
  const parsed = parseArgs(argv);
1395
1570
  const command = parsed.positionals[0];
@@ -1426,6 +1601,9 @@ async function runCli(argv, deps) {
1426
1601
  stdout: renderReport(summarizeCostRecords(records))
1427
1602
  };
1428
1603
  }
1604
+ if (command === "agents") {
1605
+ return await runAgents(deps, configPath, opencodeConfigPath);
1606
+ }
1429
1607
  if (command === undefined || command === "help" || command === "--help") {
1430
1608
  return { exitCode: 0, stdout: HELP };
1431
1609
  }
@@ -1919,6 +2097,7 @@ async function readCostRecords(path, deps = {}) {
1919
2097
 
1920
2098
  // src/cli.ts
1921
2099
  var execFileAsync = promisify(execFile);
2100
+ var AGENT_DIR = dirname2(ORCHESTRATOR_AGENT_PATH);
1922
2101
  function isMissingFile2(error) {
1923
2102
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
1924
2103
  }
@@ -1969,6 +2148,22 @@ var deps = {
1969
2148
  await mkdir2(dirname2(path), { recursive: true });
1970
2149
  await writeFile2(path, contents, "utf8");
1971
2150
  },
2151
+ listAgentFiles: async (dir) => {
2152
+ let entries;
2153
+ try {
2154
+ entries = await readdir(dir);
2155
+ } catch (error) {
2156
+ if (isMissingFile2(error)) {
2157
+ return [];
2158
+ }
2159
+ throw error;
2160
+ }
2161
+ const mdFiles = entries.filter((entry) => entry.endsWith(".md"));
2162
+ return Promise.all(mdFiles.map(async (entry) => ({
2163
+ name: entry.replace(/\.md$/, ""),
2164
+ contents: await readFile2(join2(dir, entry), "utf8")
2165
+ })));
2166
+ },
1972
2167
  probe: (config) => {
1973
2168
  const registry = new RuntimeRegistry({
1974
2169
  fetch: globalThis.fetch,
@@ -1981,7 +2176,8 @@ var deps = {
1981
2176
  configPath: DEFAULT_CONFIG_PATH,
1982
2177
  telemetryPath: DEFAULT_TELEMETRY_PATH,
1983
2178
  opencodeConfigPath: OPENCODE_CONFIG_PATH,
1984
- orchestratorAgentPath: ORCHESTRATOR_AGENT_PATH
2179
+ orchestratorAgentPath: ORCHESTRATOR_AGENT_PATH,
2180
+ agentDir: AGENT_DIR
1985
2181
  };
1986
2182
  function createInitDeps() {
1987
2183
  return {
@@ -0,0 +1,65 @@
1
+ import type { ModelRef, OpenTeamConfig } from "../config/schema";
2
+ /**
3
+ * Listado de agentes con el LLM que usa cada uno. Puro: sin I/O. El driver (leer
4
+ * `.opencode/agent/*.md` y `opencode.json`) se inyecta desde la CLI.
5
+ *
6
+ * Un agente puede: fijar su `model` en el frontmatter (explícito), o no fijarlo y
7
+ * **heredar** el `model` por defecto de `opencode.json` (que openteam enruta
8
+ * local-first por mensaje). Clasificamos el proveedor como **local** (Ollama/LM
9
+ * Studio/Foundry, sin coste de tokens) o **frontier** (consume tu suscripción).
10
+ */
11
+ /** Un agente `.md` de `.opencode/agent/` ya leído (nombre = fichero sin `.md`). */
12
+ export type AgentFile = {
13
+ name: string;
14
+ contents: string;
15
+ };
16
+ /** Frontmatter relevante de un agente. */
17
+ export type AgentInfo = {
18
+ name: string;
19
+ mode: string;
20
+ description?: string;
21
+ model?: ModelRef;
22
+ };
23
+ export type AgentModelKind = "local" | "frontier" | "inherited";
24
+ /** Agente clasificado por el LLM efectivo que usa. */
25
+ export type AgentClassification = {
26
+ name: string;
27
+ mode: string;
28
+ kind: AgentModelKind;
29
+ model?: ModelRef;
30
+ /** Etiqueta legible de la suscripción/proveedor efectivo. */
31
+ subscription: string;
32
+ };
33
+ /**
34
+ * Extrae el frontmatter YAML simple (`key: value` entre `---`) de un agente. Solo
35
+ * nos interesan `mode`, `description` y `model`. Pure.
36
+ */
37
+ export declare function parseAgentFile(file: AgentFile): AgentInfo;
38
+ /**
39
+ * Conjunto de providerIDs considerados **locales**: los de los runtimes locales
40
+ * configurados (Ollama/LM Studio/Foundry), su `id`, y el `router.localDefault`.
41
+ * Pure.
42
+ */
43
+ export declare function localProviderIDs(config: OpenTeamConfig): Set<string>;
44
+ /**
45
+ * Clasifica un agente por el LLM efectivo. Si no fija `model`, hereda el
46
+ * `defaultModel` de `opencode.json` (marcado como `inherited`). Pure.
47
+ */
48
+ export declare function classifyAgent(info: AgentInfo, context: {
49
+ localProviders: Set<string>;
50
+ defaultModel?: ModelRef;
51
+ }): AgentClassification;
52
+ /**
53
+ * Clasifica y ordena todos los agentes (primary primero, luego alfabético).
54
+ * Pure.
55
+ */
56
+ export declare function classifyAgents(files: readonly AgentFile[], context: {
57
+ localProviders: Set<string>;
58
+ defaultModel?: ModelRef;
59
+ }): AgentClassification[];
60
+ /** Render legible del listado de agentes con su LLM. Pure. */
61
+ export declare function renderAgentList(classifications: readonly AgentClassification[], context: {
62
+ defaultModel?: ModelRef;
63
+ localProviders: Set<string>;
64
+ }): string;
65
+ //# sourceMappingURL=agents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEjE;;;;;;;;GAQG;AAEH,mFAAmF;AACnF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,0CAA0C;AAC1C,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,UAAU,GAAG,WAAW,CAAC;AAEhE,sDAAsD;AACtD,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,6DAA6D;IAC7D,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAsCF;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAgCzD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,GAAG,CAAC,MAAM,CAAC,CAQpE;AAmBD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,SAAS,EACf,OAAO,EAAE;IAAE,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAAC,YAAY,CAAC,EAAE,QAAQ,CAAA;CAAE,GAChE,mBAAmB,CAoCrB;AAYD;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,OAAO,EAAE;IAAE,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAAC,YAAY,CAAC,EAAE,QAAQ,CAAA;CAAE,GAChE,mBAAmB,EAAE,CAOvB;AAQD,8DAA8D;AAC9D,wBAAgB,eAAe,CAC7B,eAAe,EAAE,SAAS,mBAAmB,EAAE,EAC/C,OAAO,EAAE;IAAE,YAAY,CAAC,EAAE,QAAQ,CAAC;IAAC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,GAChE,MAAM,CA2CR"}
@@ -1,6 +1,7 @@
1
1
  import type { OpenTeamConfig } from "../config/schema";
2
2
  import type { LocalRuntimeSnapshot } from "../local/types";
3
3
  import type { CostRecord } from "../telemetry/types";
4
+ import { type AgentFile } from "./agents";
4
5
  export type CliDeps = {
5
6
  loadConfig: (path: string) => Promise<OpenTeamConfig>;
6
7
  saveConfig: (config: OpenTeamConfig, path: string) => Promise<void>;
@@ -9,10 +10,12 @@ export type CliDeps = {
9
10
  readOpencodeConfig: (path: string) => Promise<Record<string, unknown> | undefined>;
10
11
  writeOpencodeConfig: (config: Record<string, unknown>, path: string) => Promise<void>;
11
12
  writeOrchestratorAgent: (contents: string, path: string) => Promise<void>;
13
+ listAgentFiles: (dir: string) => Promise<AgentFile[]>;
12
14
  configPath: string;
13
15
  telemetryPath: string;
14
16
  opencodeConfigPath: string;
15
17
  orchestratorAgentPath: string;
18
+ agentDir: string;
16
19
  };
17
20
  export type CliResult = {
18
21
  exitCode: number;
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/commands/dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAYrD,MAAM,MAAM,OAAO,GAAG;IACpB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACtD,UAAU,EAAE,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,KAAK,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;IACnE,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACvD,kBAAkB,EAAE,CAClB,IAAI,EAAE,MAAM,KACT,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAClD,mBAAmB,EAAE,CACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,EAAE,MAAM,KACT,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,sBAAsB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qBAAqB,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAkLF,wBAAsB,MAAM,CAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,SAAS,CAAC,CAsDpB"}
1
+ {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/commands/dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EACL,KAAK,SAAS,EAIf,MAAM,UAAU,CAAC;AAYlB,MAAM,MAAM,OAAO,GAAG;IACpB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACtD,UAAU,EAAE,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,KAAK,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;IACnE,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACvD,kBAAkB,EAAE,CAClB,IAAI,EAAE,MAAM,KACT,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAClD,mBAAmB,EAAE,CACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,EAAE,MAAM,KACT,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,sBAAsB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACtD,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAuNF,wBAAsB,MAAM,CAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,SAAS,CAAC,CA0DpB"}
@@ -4,6 +4,14 @@ import type { FrontierChoice } from "./init";
4
4
  * nombre del fichero (`openteam`) es el nombre del agente en opencode.
5
5
  */
6
6
  export declare const ORCHESTRATOR_AGENT_PATH = ".opencode/agent/openteam.md";
7
+ /**
8
+ * Ruta del registro de casting (universo + tabla nombre→rol→modelo). Vive
9
+ * **fuera** de `.opencode/agent/` a propósito: opencode carga como agente cada
10
+ * `.md` de esa carpeta, así que un `roster.md` dentro aparecería como un agente
11
+ * fantasma en el Tab-switcher. Lo guardamos junto al log de decisiones del
12
+ * `scribe`, como fichero de datos del proyecto.
13
+ */
14
+ export declare const OPENTEAM_ROSTER_PATH = ".opencode/openteam-roster.md";
7
15
  /**
8
16
  * Construye el Markdown del agente primario `openteam` (estilo Squad): un
9
17
  * orquestador que aparece en el Tab-switcher, elige un universo temático y
@@ -1 +1 @@
1
- {"version":3,"file":"orchestratorAgent.d.ts","sourceRoot":"","sources":["../../src/commands/orchestratorAgent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAE7C;;;GAGG;AACH,eAAO,MAAM,uBAAuB,gCAAgC,CAAC;AAErE;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,cAAc,EACxB,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAC/B,MAAM,CAqLR"}
1
+ {"version":3,"file":"orchestratorAgent.d.ts","sourceRoot":"","sources":["../../src/commands/orchestratorAgent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAE7C;;;GAGG;AACH,eAAO,MAAM,uBAAuB,gCAAgC,CAAC;AAErE;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,iCAAiC,CAAC;AAEnE;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,cAAc,EACxB,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAC/B,MAAM,CAuLR"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAuB,MAAM,kBAAkB,CAAC;AAG1E,OAAO,KAAK,EAAE,MAAM,EAAe,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAE7E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAKnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAgBnD,KAAK,gBAAgB,GAAG;IACtB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,UAAU,CAAC;CAChC,CAAC;AAcF,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAGhE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAGtD;AA0BD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,gBAAqB,GAC1B,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAQjC;AAeD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAOrD;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,eAAe,EACzB,aAAa,EAAE,MAAM,GACpB,OAAO,CAyCT;AAED,eAAO,MAAM,MAAM,EAAE,MA8BpB,CAAC;AAEF,QAAA,MAAM,MAAM,EAAE,YAGb,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,KAAK,EAIN,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAE,MAAM,EAAe,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAE7E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAKnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAgBnD,KAAK,gBAAgB,GAAG;IACtB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,UAAU,CAAC;CAChC,CAAC;AAcF,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAGhE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAGtD;AA0BD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,gBAAqB,GAC1B,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAQjC;AAeD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAOrD;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,eAAe,EACzB,aAAa,EAAE,MAAM,GACpB,OAAO,CA4DT;AAED,eAAO,MAAM,MAAM,EAAE,MA8BpB,CAAC;AAEF,QAAA,MAAM,MAAM,EAAE,YAGb,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,12 @@
1
1
  // src/index.ts
2
- import { appendFile, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
3
- import { dirname as dirname2 } from "node:path";
2
+ import {
3
+ appendFile,
4
+ mkdir as mkdir2,
5
+ readdir,
6
+ readFile as readFile2,
7
+ writeFile as writeFile2
8
+ } from "node:fs/promises";
9
+ import { dirname as dirname2, join } from "node:path";
4
10
 
5
11
  // src/config/persist.ts
6
12
  import { mkdir, writeFile } from "node:fs/promises";
@@ -135,14 +141,14 @@ function buildOrchestratorAgent(frontier, options = {}) {
135
141
  "Antes de responder o delegar **cualquier** petición, tu **primera acción",
136
142
  "siempre** es comprobar si ya existe un equipo, aunque el usuario no te pida",
137
143
  "explícitamente crearlo. Nunca asumas que hay equipo ni que no lo hay:",
138
- "verifícalo mirando `.opencode/agent/roster.md` y los `.opencode/agent/*.md`",
144
+ "verifícalo mirando `.opencode/openteam-roster.md` y los `.opencode/agent/*.md`",
139
145
  "con `mode: subagent` (usa `read`/`glob`/`list`).",
140
146
  "",
141
147
  "- **Si el equipo YA existe** → actúa como coordinador: entiende la tarea,",
142
148
  " repártela entre los subagentes adecuados con la herramienta `task` y",
143
149
  " reutiliza el reparto existente. No vuelvas a castear ni recrear agentes.",
144
- "- **Si NO existe equipo** (no hay `roster.md` ni subagentes) **no pidas",
145
- " permiso para crearlo**: entiende primero las tareas que implica la",
150
+ "- **Si NO existe equipo** (sin `openteam-roster.md` ni subagentes): **no pidas",
151
+ " permiso para crearlo**. Entiende primero las tareas que implica la",
146
152
  " petición, diseña el equipo mínimo necesario, **créalo a demanda** y luego",
147
153
  " pásale esas tareas. Crear el equipo es parte implícita de atender la",
148
154
  " petición, no un paso opcional que el usuario deba solicitar.",
@@ -160,9 +166,11 @@ function buildOrchestratorAgent(frontier, options = {}) {
160
166
  "2. Asigna a cada subagente el **nombre de un personaje** de ese universo; el",
161
167
  " nombre del fichero (`.opencode/agent/<nombre>.md`) es su alias para",
162
168
  " `@mención`, y su rol queda claro en el `description`.",
163
- "3. **Registra el casting** en `.opencode/agent/roster.md` (universo + tabla",
169
+ "3. **Registra el casting** en `.opencode/openteam-roster.md` (universo + tabla",
164
170
  " nombre → rol → modelo) para que los nombres **persistan** entre sesiones.",
165
- " Reutiliza siempre el mismo reparto; no vuelvas a castear sin motivo.",
171
+ " Este fichero va **fuera** de `.opencode/agent/` (si no, opencode lo cargaría",
172
+ " como un agente fantasma). Reutiliza siempre el mismo reparto; no vuelvas a",
173
+ " castear sin motivo.",
166
174
  "",
167
175
  "## Cómo crear el equipo cuando no existe",
168
176
  "",
@@ -186,8 +194,8 @@ function buildOrchestratorAgent(frontier, options = {}) {
186
194
  " frontier solo para arquitectura, seguridad o debugging ambiguo:",
187
195
  " openteam ya enruta cheapest-capable automáticamente.",
188
196
  " - Un prompt de rol enfocado y accionable.",
189
- "4. Registra el reparto en `roster.md` y **reparte las tareas** al equipo",
190
- " recién creado con la herramienta `task`.",
197
+ "4. Registra el reparto en `.opencode/openteam-roster.md` y **reparte las",
198
+ " tareas** al equipo recién creado con la herramienta `task`.",
191
199
  "",
192
200
  "## Roles estándar del equipo",
193
201
  "",
@@ -260,8 +268,8 @@ function buildOrchestratorAgent(frontier, options = {}) {
260
268
  "- **Lanza en un solo turno las tareas independientes** para que corran en",
261
269
  " paralelo; secuencia solo las que dependen de un resultado previo.",
262
270
  "- Crea subagentes **justo cuando el trabajo lo pide**, nunca “por si acaso”.",
263
- " Revisa `.opencode/agent/` y el `roster.md`, y reutiliza los existentes antes",
264
- " de crear uno nuevo.",
271
+ " Revisa `.opencode/agent/` y el `openteam-roster.md`, y reutiliza los",
272
+ " existentes antes de crear uno nuevo.",
265
273
  "- Respeta el routing y la privacidad de openteam; el `guardian` es la última",
266
274
  " línea antes de enviar nada sensible a frontier.",
267
275
  "- Resume al usuario qué universo elegiste, qué equipo has creado y por qué.",
@@ -269,7 +277,7 @@ function buildOrchestratorAgent(frontier, options = {}) {
269
277
  "## Límites",
270
278
  "",
271
279
  "- Un único primario (tú); todo lo demás son subagentes creados a demanda.",
272
- "- No borres ni sobrescribas subagentes ni el `roster.md` sin confirmarlo.",
280
+ "- No borres ni sobrescribas subagentes ni el `openteam-roster.md` sin confirmarlo.",
273
281
  "- Los subagentes nuevos quedan disponibles para `@mención` y para la",
274
282
  " herramienta `task` una vez opencode recarga los agentes del proyecto.",
275
283
  ""
@@ -727,6 +735,158 @@ function createAvailabilityCache(options) {
727
735
  // src/plugin/commandTool.ts
728
736
  import { tool } from "@opencode-ai/plugin";
729
737
 
738
+ // src/commands/agents.ts
739
+ var PROVIDER_LABELS = {
740
+ "github-copilot": "GitHub Copilot",
741
+ anthropic: "Anthropic",
742
+ openai: "OpenAI",
743
+ google: "Google",
744
+ openrouter: "OpenRouter",
745
+ ollama: "Ollama",
746
+ lmstudio: "LM Studio",
747
+ "foundry-local": "Foundry Local"
748
+ };
749
+ function providerLabel(providerID) {
750
+ return PROVIDER_LABELS[providerID] ?? providerID;
751
+ }
752
+ function unquote(value) {
753
+ const trimmed = value.trim();
754
+ if (trimmed.length >= 2 && (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'"))) {
755
+ return trimmed.slice(1, -1);
756
+ }
757
+ return trimmed;
758
+ }
759
+ function parseModelValue(value) {
760
+ const raw = unquote(value);
761
+ const slash = raw.indexOf("/");
762
+ if (slash <= 0 || slash === raw.length - 1) {
763
+ return;
764
+ }
765
+ return { providerID: raw.slice(0, slash), modelID: raw.slice(slash + 1) };
766
+ }
767
+ function parseAgentFile(file) {
768
+ const info = { name: file.name, mode: "subagent" };
769
+ const lines = file.contents.split(/\r?\n/);
770
+ if (lines[0]?.trim() !== "---") {
771
+ return info;
772
+ }
773
+ for (let i = 1;i < lines.length; i += 1) {
774
+ const line = lines[i] ?? "";
775
+ if (line.trim() === "---") {
776
+ break;
777
+ }
778
+ const colon = line.indexOf(":");
779
+ if (colon <= 0) {
780
+ continue;
781
+ }
782
+ const key = line.slice(0, colon).trim();
783
+ const value = line.slice(colon + 1).trim();
784
+ if (key === "mode") {
785
+ info.mode = unquote(value) || "subagent";
786
+ } else if (key === "description") {
787
+ info.description = unquote(value);
788
+ } else if (key === "model") {
789
+ const model = parseModelValue(value);
790
+ if (model !== undefined) {
791
+ info.model = model;
792
+ }
793
+ }
794
+ }
795
+ return info;
796
+ }
797
+ function localProviderIDs(config) {
798
+ const ids = new Set;
799
+ for (const runtime of config.local.runtimes) {
800
+ ids.add(runtime.id);
801
+ ids.add(runtime.defaultModel.providerID);
802
+ }
803
+ ids.add(config.router.localDefault.providerID);
804
+ return ids;
805
+ }
806
+ function subscriptionLabel(model, localProviders) {
807
+ const isLocal = localProviders.has(model.providerID);
808
+ if (isLocal) {
809
+ return {
810
+ kind: "local",
811
+ subscription: `local · ${providerLabel(model.providerID)} (sin coste de tokens)`
812
+ };
813
+ }
814
+ return {
815
+ kind: "frontier",
816
+ subscription: `frontier · ${providerLabel(model.providerID)} (consume tu suscripción)`
817
+ };
818
+ }
819
+ function classifyAgent(info, context) {
820
+ if (info.model !== undefined) {
821
+ const { kind, subscription: subscription2 } = subscriptionLabel(info.model, context.localProviders);
822
+ return {
823
+ name: info.name,
824
+ mode: info.mode,
825
+ kind,
826
+ model: info.model,
827
+ subscription: subscription2
828
+ };
829
+ }
830
+ if (context.defaultModel === undefined) {
831
+ return {
832
+ name: info.name,
833
+ mode: info.mode,
834
+ kind: "inherited",
835
+ subscription: "hereda el default de opencode.json (sin definir) · openteam enruta local-first por mensaje"
836
+ };
837
+ }
838
+ const { subscription } = subscriptionLabel(context.defaultModel, context.localProviders);
839
+ return {
840
+ name: info.name,
841
+ mode: info.mode,
842
+ kind: "inherited",
843
+ model: context.defaultModel,
844
+ subscription: `hereda default → ${subscription} · openteam enruta local-first por mensaje`
845
+ };
846
+ }
847
+ function modeRank(mode) {
848
+ if (mode === "primary") {
849
+ return 0;
850
+ }
851
+ if (mode === "all") {
852
+ return 1;
853
+ }
854
+ return 2;
855
+ }
856
+ function classifyAgents(files, context) {
857
+ return files.map((file) => classifyAgent(parseAgentFile(file), context)).sort((a, b) => {
858
+ const rank = modeRank(a.mode) - modeRank(b.mode);
859
+ return rank !== 0 ? rank : a.name.localeCompare(b.name);
860
+ });
861
+ }
862
+ function modelText(classification) {
863
+ return classification.model === undefined ? "" : ` (${classification.model.providerID}/${classification.model.modelID})`;
864
+ }
865
+ function renderAgentList(classifications, context) {
866
+ const lines = ["openteam agents — LLM por agente:", ""];
867
+ if (classifications.length === 0) {
868
+ lines.push(" (sin agentes en .opencode/agent/) — ejecuta 'openteam init' o pide al orquestador que cree el equipo.");
869
+ return lines.join(`
870
+ `);
871
+ }
872
+ const nameWidth = Math.max(...classifications.map((c) => c.name.length), "agente".length);
873
+ const modeWidth = Math.max(...classifications.map((c) => c.mode.length), "subagent".length);
874
+ for (const c of classifications) {
875
+ const bullet = c.mode === "primary" ? "●" : "○";
876
+ lines.push(` ${bullet} ${c.name.padEnd(nameWidth)} [${c.mode.padEnd(modeWidth)}] ${c.subscription}${modelText(c)}`);
877
+ }
878
+ lines.push("");
879
+ if (context.defaultModel !== undefined) {
880
+ const { kind } = subscriptionLabel(context.defaultModel, context.localProviders);
881
+ lines.push(`Default de opencode.json: ${context.defaultModel.providerID}/${context.defaultModel.modelID} (${kind} · ${providerLabel(context.defaultModel.providerID)})`);
882
+ } else {
883
+ lines.push("Default de opencode.json: (sin definir)");
884
+ }
885
+ lines.push("Leyenda: ● primary · ○ subagent · local = sin coste de tokens · frontier = consume tu suscripción.");
886
+ return lines.join(`
887
+ `);
888
+ }
889
+
730
890
  // src/commands/baseline.ts
731
891
  function formatRef(ref) {
732
892
  return ref === null ? "—" : `${ref.providerID}/${ref.modelID}`;
@@ -936,6 +1096,7 @@ var HELP = [
936
1096
  " openteam baseline set <p/model> Fija el baseline (modo pinned)",
937
1097
  " openteam baseline auto Baseline cheapest-capable (modo auto)",
938
1098
  " openteam doctor Diagnóstico de runtimes y config",
1099
+ " openteam agents Lista los agentes y el LLM (local/frontier) de cada uno",
939
1100
  " openteam report Resumen de coste/ahorro (telemetría)",
940
1101
  " openteam yolo status Muestra si el modo YOLO está activo",
941
1102
  " openteam yolo on Activa YOLO (opencode auto-aprueba todo)",
@@ -1066,6 +1227,26 @@ ${deps.orchestratorAgentPath} regenerado (permission acorde a YOLO).`;
1066
1227
  Aviso: no pude regenerar ${deps.orchestratorAgentPath} (model inválido en opencode.json); ajusta su permission a mano.`;
1067
1228
  }
1068
1229
  }
1230
+ async function runAgents(deps, configPath, opencodeConfigPath) {
1231
+ const [config, opencodeConfig, files] = await Promise.all([
1232
+ deps.loadConfig(configPath),
1233
+ deps.readOpencodeConfig(opencodeConfigPath),
1234
+ deps.listAgentFiles(deps.agentDir)
1235
+ ]);
1236
+ const localProviders = localProviderIDs(config);
1237
+ const rawDefault = opencodeConfig?.model;
1238
+ let defaultModel;
1239
+ if (typeof rawDefault === "string" && rawDefault.trim() !== "") {
1240
+ try {
1241
+ defaultModel = parseModelRef(rawDefault);
1242
+ } catch {
1243
+ defaultModel = undefined;
1244
+ }
1245
+ }
1246
+ const context = defaultModel === undefined ? { localProviders } : { localProviders, defaultModel };
1247
+ const classifications = classifyAgents(files, context);
1248
+ return { exitCode: 0, stdout: renderAgentList(classifications, context) };
1249
+ }
1069
1250
  async function runCli(argv, deps) {
1070
1251
  const parsed = parseArgs(argv);
1071
1252
  const command = parsed.positionals[0];
@@ -1102,6 +1283,9 @@ async function runCli(argv, deps) {
1102
1283
  stdout: renderReport(summarizeCostRecords(records))
1103
1284
  };
1104
1285
  }
1286
+ if (command === "agents") {
1287
+ return await runAgents(deps, configPath, opencodeConfigPath);
1288
+ }
1105
1289
  if (command === undefined || command === "help" || command === "--help") {
1106
1290
  return { exitCode: 0, stdout: HELP };
1107
1291
  }
@@ -1130,13 +1314,15 @@ function commandArgv(action, model) {
1130
1314
  return ["doctor"];
1131
1315
  case "report":
1132
1316
  return ["report"];
1317
+ case "agents":
1318
+ return ["agents"];
1133
1319
  }
1134
1320
  }
1135
1321
  function createCommandTool(deps) {
1136
1322
  return tool({
1137
- description: "Comandos runtime de openteam: baseline (show/set/auto), doctor y report.",
1323
+ description: "Comandos runtime de openteam: baseline (show/set/auto), doctor, report y agents (LLM por agente: local/frontier + suscripción).",
1138
1324
  args: {
1139
- action: tool.schema.enum(["show", "set", "auto", "doctor", "report"]).describe("Acción a ejecutar"),
1325
+ action: tool.schema.enum(["show", "set", "auto", "doctor", "report", "agents"]).describe("Acción a ejecutar"),
1140
1326
  model: tool.schema.string().optional().describe("Modelo provider/model (solo para action=set)")
1141
1327
  },
1142
1328
  async execute(args) {
@@ -1860,12 +2046,29 @@ function createCliDeps(config, registry, telemetryPath) {
1860
2046
  await mkdir2(dirname2(path), { recursive: true });
1861
2047
  await writeFile2(path, contents, "utf8");
1862
2048
  },
2049
+ listAgentFiles: async (dir) => {
2050
+ let entries;
2051
+ try {
2052
+ entries = await readdir(dir);
2053
+ } catch (error) {
2054
+ if (isMissingFile2(error)) {
2055
+ return [];
2056
+ }
2057
+ throw error;
2058
+ }
2059
+ const mdFiles = entries.filter((entry) => entry.endsWith(".md"));
2060
+ return Promise.all(mdFiles.map(async (entry) => ({
2061
+ name: entry.replace(/\.md$/, ""),
2062
+ contents: await readFile2(join(dir, entry), "utf8")
2063
+ })));
2064
+ },
1863
2065
  probe: (nextConfig) => registry.probe(nextConfig.local.runtimes),
1864
2066
  readTelemetry: (path) => readCostRecords(path),
1865
2067
  configPath: DEFAULT_CONFIG_PATH,
1866
2068
  telemetryPath,
1867
2069
  opencodeConfigPath: OPENCODE_CONFIG_PATH,
1868
- orchestratorAgentPath: ORCHESTRATOR_AGENT_PATH
2070
+ orchestratorAgentPath: ORCHESTRATOR_AGENT_PATH,
2071
+ agentDir: dirname2(ORCHESTRATOR_AGENT_PATH)
1869
2072
  };
1870
2073
  }
1871
2074
  var server = async (ctx, rawOptions) => {
@@ -1,6 +1,6 @@
1
1
  import { type ToolDefinition } from "@opencode-ai/plugin";
2
2
  import { type CliDeps } from "../commands/dispatch";
3
- export type CommandAction = "show" | "set" | "auto" | "doctor" | "report";
3
+ export type CommandAction = "show" | "set" | "auto" | "doctor" | "report" | "agents";
4
4
  /**
5
5
  * Map a tool action (+ optional model) to the argv understood by {@link runCli}.
6
6
  * Pure so it can be unit-tested without the opencode runtime.
@@ -1 +1 @@
1
- {"version":3,"file":"commandTool.d.ts","sourceRoot":"","sources":["../../src/plugin/commandTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAEhE,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,sBAAsB,CAAC;AAE5D,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE1E;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAe3E;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,CAkB/D"}
1
+ {"version":3,"file":"commandTool.d.ts","sourceRoot":"","sources":["../../src/plugin/commandTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAEhE,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,sBAAsB,CAAC;AAE5D,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,KAAK,GACL,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,QAAQ,CAAC;AAEb;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAiB3E;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,CAkB/D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jmanuelcorral/openteam",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Plugin opencode de routing coste-consciente local-first con orquestación Squad.",
5
5
  "license": "MIT",
6
6
  "author": "Jose Manuel Corral",