@jmanuelcorral/openteam 0.1.1 → 0.1.3

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.
@@ -0,0 +1,12 @@
1
+ import type { OpenTeamConfig } from "../config/schema";
2
+ import { type ModelRef } from "../config/schema";
3
+ import type { CommandOutcome } from "./types";
4
+ /**
5
+ * Parse a `provider/model` string into a validated {@link ModelRef}.
6
+ * Splits on the first `/` so model IDs may themselves contain `/` or `:`.
7
+ */
8
+ export declare function parseModelRef(input: string): ModelRef;
9
+ export declare function showBaseline(config: OpenTeamConfig): CommandOutcome;
10
+ export declare function setBaseline(config: OpenTeamConfig, ref: ModelRef): CommandOutcome;
11
+ export declare function autoBaseline(config: OpenTeamConfig): CommandOutcome;
12
+ //# sourceMappingURL=baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../src/commands/baseline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,KAAK,QAAQ,EAAkB,MAAM,kBAAkB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAiB9C;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAcrD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAUnE;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,QAAQ,GACZ,cAAc,CAchB;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAcnE"}
@@ -0,0 +1,17 @@
1
+ import type { OpenTeamConfig } from "../config/schema";
2
+ import type { LocalRuntimeSnapshot } from "../local/types";
3
+ import type { CostRecord } from "../telemetry/types";
4
+ export type CliDeps = {
5
+ loadConfig: (path: string) => Promise<OpenTeamConfig>;
6
+ saveConfig: (config: OpenTeamConfig, path: string) => Promise<void>;
7
+ probe: (config: OpenTeamConfig) => Promise<LocalRuntimeSnapshot[]>;
8
+ readTelemetry: (path: string) => Promise<CostRecord[]>;
9
+ configPath: string;
10
+ telemetryPath: string;
11
+ };
12
+ export type CliResult = {
13
+ exitCode: number;
14
+ stdout: string;
15
+ };
16
+ export declare function runCli(argv: readonly string[], deps: CliDeps): Promise<CliResult>;
17
+ //# sourceMappingURL=dispatch.d.ts.map
@@ -0,0 +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;AAUrD,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,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AA4FF,wBAAsB,MAAM,CAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,SAAS,CAAC,CAgDpB"}
@@ -0,0 +1,10 @@
1
+ import type { OpenTeamConfig } from "../config/schema";
2
+ import type { LocalRuntimeSnapshot } from "../local/types";
3
+ export type DoctorInput = {
4
+ config: OpenTeamConfig;
5
+ snapshots: readonly LocalRuntimeSnapshot[];
6
+ telemetryPath: string;
7
+ telemetryRecords: number;
8
+ };
9
+ export declare function renderDoctor(input: DoctorInput): string;
10
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE3D,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAC3C,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAWF,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAkCvD"}
@@ -0,0 +1,22 @@
1
+ import type { ComplexityTier } from "../capabilities/types";
2
+ import type { CostRecord } from "../telemetry/types";
3
+ export type TierSummary = {
4
+ count: number;
5
+ estimatedUSD: number;
6
+ savingsUSD: number;
7
+ };
8
+ export type CostReport = {
9
+ count: number;
10
+ localCount: number;
11
+ frontierCount: number;
12
+ totalEstimatedUSD: number;
13
+ totalBaselineUSD: number;
14
+ totalSavingsUSD: number;
15
+ savingsPct: number;
16
+ tokensIn: number;
17
+ tokensOut: number;
18
+ byTier: Record<ComplexityTier, TierSummary>;
19
+ };
20
+ export declare function summarizeCostRecords(records: readonly CostRecord[]): CostReport;
21
+ export declare function renderReport(report: CostReport): string;
22
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/commands/report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AASrD,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;CAC7C,CAAC;AAWF,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,SAAS,UAAU,EAAE,GAC7B,UAAU,CAuCZ;AAMD,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAuBvD"}
@@ -0,0 +1,6 @@
1
+ import type { OpenTeamConfig } from "../config/schema";
2
+ export type CommandOutcome = {
3
+ message: string;
4
+ config?: OpenTeamConfig;
5
+ };
6
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/commands/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAEhB,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { type OpenTeamConfig } from "./schema";
3
+ export declare const DEFAULT_CONFIG_PATH = ".opencode/openteam.json";
4
+ /**
5
+ * Validate and serialize a config to canonical JSON (2-space indent, trailing newline).
6
+ * Pure: no filesystem access.
7
+ */
8
+ export declare function serializeOpenTeamConfig(config: OpenTeamConfig): string;
9
+ export type WriteConfigDeps = {
10
+ mkdir?: typeof mkdir;
11
+ writeFile?: typeof writeFile;
12
+ };
13
+ export declare function writeOpenTeamConfigFile(config: OpenTeamConfig, path?: string, deps?: WriteConfigDeps): Promise<void>;
14
+ //# sourceMappingURL=persist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../src/config/persist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,EAAE,KAAK,cAAc,EAAwB,MAAM,UAAU,CAAC;AAErE,eAAO,MAAM,mBAAmB,4BAA4B,CAAC;AAE7D;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAGtE;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC;CAC9B,CAAC;AAEF,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,cAAc,EACtB,IAAI,SAAsB,EAC1B,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,IAAI,CAAC,CAMf"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Contrato con la superficie de extensión de opencode.
3
+ *
4
+ * openteam depende de un conjunto acotado de tipos y comportamientos de
5
+ * `@opencode-ai/plugin` y `@opencode-ai/sdk`. Este módulo es la única fuente de
6
+ * verdad, en código, de las versiones soportadas. Los tests de contrato en
7
+ * `tests/contract/**` fallan cuando la versión instalada se aleja de esta
8
+ * allow-list, forzando una re-verificación manual de la superficie antes de
9
+ * mergear un bump.
10
+ *
11
+ * Módulo puro: sin I/O, red, reloj, random ni env vars.
12
+ */
13
+ /**
14
+ * Paquetes npm de opencode de los que depende openteam en runtime.
15
+ */
16
+ export declare const OPENCODE_PACKAGES: readonly ["@opencode-ai/plugin", "@opencode-ai/sdk"];
17
+ export type OpencodePackage = (typeof OPENCODE_PACKAGES)[number];
18
+ /**
19
+ * Versión mínima de los paquetes `@opencode-ai/*` verificada contra la
20
+ * superficie que usa openteam.
21
+ */
22
+ export declare const MIN_SUPPORTED_OPENCODE_VERSION = "1.17.13";
23
+ /**
24
+ * Versiones exactas cuya superficie de contrato ha sido verificada. Al subir a
25
+ * una versión nueva hay que añadirla aquí **después** de revisar los puntos de
26
+ * integración documentados en {@link OPENCODE_CONTRACT_SURFACES}.
27
+ */
28
+ export declare const SUPPORTED_OPENCODE_VERSIONS: readonly ["1.17.13"];
29
+ export type SupportedOpencodeVersion = (typeof SUPPORTED_OPENCODE_VERSIONS)[number];
30
+ /**
31
+ * Puntos de integración concretos que openteam asume estables. Sirve como
32
+ * checklist ejecutable/documental cuando se verifica una versión nueva.
33
+ */
34
+ export declare const OPENCODE_CONTRACT_SURFACES: readonly ["Hooks[\"chat.message\"] permite mutar output.message.model", "tool() + tool.schema (zod) para custom tools", "PluginModule = { id?, server } con server: (input, options?) => Promise<Hooks>", "SDK session.create / session.prompt aceptan body.model = { providerID, modelID }"];
35
+ export type Semver = {
36
+ readonly major: number;
37
+ readonly minor: number;
38
+ readonly patch: number;
39
+ };
40
+ /**
41
+ * Parsea una versión semver, tolerando prefijos de rango habituales en
42
+ * `package.json` (`^`, `~`, `=`, `v`). Ignora sufijos de pre-release/build.
43
+ */
44
+ export declare function parseSemver(version: string): Semver;
45
+ /**
46
+ * Compara dos versiones semver. Devuelve -1 si a<b, 0 si iguales, 1 si a>b.
47
+ */
48
+ export declare function compareSemver(a: Semver, b: Semver): -1 | 0 | 1;
49
+ /**
50
+ * true si la versión exacta está en la allow-list verificada.
51
+ */
52
+ export declare function isSupportedOpencodeVersion(version: string): version is SupportedOpencodeVersion;
53
+ /**
54
+ * true si la versión cumple el piso mínimo soportado.
55
+ */
56
+ export declare function meetsMinimumOpencodeVersion(version: string): boolean;
57
+ //# sourceMappingURL=opencode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../src/contract/opencode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB,sDAGpB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjE;;;GAGG;AACH,eAAO,MAAM,8BAA8B,YAAY,CAAC;AAExD;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,sBAAuB,CAAC;AAEhE,MAAM,MAAM,wBAAwB,GAClC,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,0BAA0B,+RAK7B,CAAC;AAEX,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,CAAC;AAIF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAanD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAc9D;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,GACd,OAAO,IAAI,wBAAwB,CAErC;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAOpE"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import { appendFile, mkdir } from "node:fs/promises";
2
2
  import type { Plugin, PluginModule } from "@opencode-ai/plugin";
3
+ import type { CliDeps } from "./commands/dispatch";
4
+ import type { OpenTeamConfig } from "./config/schema";
5
+ import { RuntimeRegistry } from "./local/registry";
3
6
  type FileAppenderDeps = {
4
7
  mkdir?: typeof mkdir;
5
8
  appendFile?: typeof appendFile;
@@ -7,6 +10,8 @@ type FileAppenderDeps = {
7
10
  export declare function logAvailabilityRefreshError(error: unknown): void;
8
11
  export declare function logTelemetryError(error: unknown): void;
9
12
  export declare function createFileAppender(filePath: string, deps?: FileAppenderDeps): (line: string) => Promise<void>;
13
+ export declare function isMissingFile(error: unknown): boolean;
14
+ export declare function createCliDeps(config: OpenTeamConfig, registry: RuntimeRegistry, telemetryPath: string): CliDeps;
10
15
  export declare const server: Plugin;
11
16
  declare const plugin: PluginModule;
12
17
  export default plugin;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,KAAK,EAAE,MAAM,EAAe,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAoB7E,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,eAAO,MAAM,MAAM,EAAE,MAyBpB,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,EAAE,UAAU,EAAE,KAAK,EAAY,MAAM,kBAAkB,CAAC;AAG/D,OAAO,KAAK,EAAE,MAAM,EAAe,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAE7E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAGnD,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,CAkBT;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,6 @@
1
1
  // src/index.ts
2
- import { appendFile, mkdir } from "node:fs/promises";
3
- import { dirname } from "node:path";
2
+ import { appendFile, mkdir as mkdir2, readFile as readFile2 } from "node:fs/promises";
3
+ import { dirname as dirname2 } from "node:path";
4
4
 
5
5
  // src/config/schema.ts
6
6
  import { z } from "zod";
@@ -84,6 +84,22 @@ function loadOpenTeamConfig(raw) {
84
84
  return OpenTeamConfigSchema.parse(raw ?? {});
85
85
  }
86
86
 
87
+ // src/config/persist.ts
88
+ import { mkdir, writeFile } from "node:fs/promises";
89
+ import { dirname } from "node:path";
90
+ var DEFAULT_CONFIG_PATH = ".opencode/openteam.json";
91
+ function serializeOpenTeamConfig(config) {
92
+ const validated = OpenTeamConfigSchema.parse(config);
93
+ return `${JSON.stringify(validated, null, 2)}
94
+ `;
95
+ }
96
+ async function writeOpenTeamConfigFile(config, path = DEFAULT_CONFIG_PATH, deps = {}) {
97
+ const mkdirFn = deps.mkdir ?? mkdir;
98
+ const writeFileFn = deps.writeFile ?? writeFile;
99
+ await mkdirFn(dirname(path), { recursive: true });
100
+ await writeFileFn(path, serializeOpenTeamConfig(config), "utf8");
101
+ }
102
+
87
103
  // src/local/openai-compatible.ts
88
104
  function normalizeBaseURL(baseURL) {
89
105
  return baseURL.replace(/\/+$/, "");
@@ -520,6 +536,323 @@ function createAvailabilityCache(options) {
520
536
  };
521
537
  }
522
538
 
539
+ // src/plugin/commandTool.ts
540
+ import { tool } from "@opencode-ai/plugin";
541
+
542
+ // src/commands/baseline.ts
543
+ function formatRef(ref) {
544
+ return ref === null ? "—" : `${ref.providerID}/${ref.modelID}`;
545
+ }
546
+ function effectiveBaseline(config) {
547
+ if (config.baseline.mode === "pinned" && config.baseline.pinnedModel !== null) {
548
+ return `${formatRef(config.baseline.pinnedModel)} (pinned)`;
549
+ }
550
+ return "cheapest-capable (auto)";
551
+ }
552
+ function parseModelRef(input) {
553
+ const trimmed = input.trim();
554
+ const slash = trimmed.indexOf("/");
555
+ if (slash <= 0 || slash === trimmed.length - 1) {
556
+ throw new Error(`Modelo inválido "${input}". Usa el formato provider/model, p.ej. anthropic/claude-sonnet-4-5.`);
557
+ }
558
+ return ModelRefSchema.parse({
559
+ providerID: trimmed.slice(0, slash),
560
+ modelID: trimmed.slice(slash + 1)
561
+ });
562
+ }
563
+ function showBaseline(config) {
564
+ const lines = [
565
+ "openteam baseline:",
566
+ ` modo: ${config.baseline.mode}`,
567
+ ` pinned: ${formatRef(config.baseline.pinnedModel)}`,
568
+ ` hardDefault: ${formatRef(config.baseline.hardDefault)}`,
569
+ ` efectivo: ${effectiveBaseline(config)}`
570
+ ];
571
+ return { message: lines.join(`
572
+ `) };
573
+ }
574
+ function setBaseline(config, ref) {
575
+ const next = {
576
+ ...config,
577
+ baseline: {
578
+ ...config.baseline,
579
+ mode: "pinned",
580
+ pinnedModel: ref
581
+ }
582
+ };
583
+ return {
584
+ config: next,
585
+ message: `Baseline fijado a ${formatRef(ref)} (modo pinned).`
586
+ };
587
+ }
588
+ function autoBaseline(config) {
589
+ const next = {
590
+ ...config,
591
+ baseline: {
592
+ ...config.baseline,
593
+ mode: "auto",
594
+ pinnedModel: null
595
+ }
596
+ };
597
+ return {
598
+ config: next,
599
+ message: "Baseline en modo auto (cheapest-capable)."
600
+ };
601
+ }
602
+
603
+ // src/commands/doctor.ts
604
+ function runtimeLine(snapshot) {
605
+ const mark = snapshot.reachable ? "✓" : "✗";
606
+ const detail = snapshot.reachable ? `${snapshot.models.length} modelo(s)` : snapshot.error ?? "inalcanzable";
607
+ return ` ${mark} ${snapshot.id.padEnd(14)} ${snapshot.baseURL || "(sin baseURL)"} — ${detail}`;
608
+ }
609
+ function renderDoctor(input) {
610
+ const enabledRuntimes = input.config.local.runtimes.filter((r) => r.enabled);
611
+ const reachable = input.snapshots.filter((s) => s.reachable).length;
612
+ const lines = [
613
+ "openteam doctor:",
614
+ ` baseline: ${input.config.baseline.mode}` + (input.config.baseline.mode === "pinned" && input.config.baseline.pinnedModel !== null ? ` (${input.config.baseline.pinnedModel.providerID}/${input.config.baseline.pinnedModel.modelID})` : ""),
615
+ ` privacidad: ${input.config.privacyMode}`,
616
+ ` runtimes locales (${reachable}/${enabledRuntimes.length} alcanzables):`
617
+ ];
618
+ if (input.snapshots.length === 0) {
619
+ lines.push(" (ningún runtime habilitado)");
620
+ } else {
621
+ for (const snapshot of input.snapshots) {
622
+ lines.push(runtimeLine(snapshot));
623
+ }
624
+ }
625
+ lines.push(` telemetría: ${input.telemetryPath} — ${input.telemetryRecords} registro(s)`);
626
+ if (reachable === 0) {
627
+ lines.push(" ⚠ Ningún runtime local alcanzable: todo el tráfico irá a frontier.");
628
+ }
629
+ return lines.join(`
630
+ `);
631
+ }
632
+
633
+ // src/commands/report.ts
634
+ var TIERS = [
635
+ "trivial",
636
+ "simple",
637
+ "moderate",
638
+ "hard"
639
+ ];
640
+ function emptyTierSummary() {
641
+ return {
642
+ trivial: { count: 0, estimatedUSD: 0, savingsUSD: 0 },
643
+ simple: { count: 0, estimatedUSD: 0, savingsUSD: 0 },
644
+ moderate: { count: 0, estimatedUSD: 0, savingsUSD: 0 },
645
+ hard: { count: 0, estimatedUSD: 0, savingsUSD: 0 }
646
+ };
647
+ }
648
+ function summarizeCostRecords(records) {
649
+ const report = {
650
+ count: 0,
651
+ localCount: 0,
652
+ frontierCount: 0,
653
+ totalEstimatedUSD: 0,
654
+ totalBaselineUSD: 0,
655
+ totalSavingsUSD: 0,
656
+ savingsPct: 0,
657
+ tokensIn: 0,
658
+ tokensOut: 0,
659
+ byTier: emptyTierSummary()
660
+ };
661
+ for (const record of records) {
662
+ report.count += 1;
663
+ if (record.routeKind === "local") {
664
+ report.localCount += 1;
665
+ } else {
666
+ report.frontierCount += 1;
667
+ }
668
+ report.totalEstimatedUSD += record.estimatedCostUSD;
669
+ report.totalBaselineUSD += record.baselineCostUSD;
670
+ report.totalSavingsUSD += record.estimatedSavingsUSD;
671
+ report.tokensIn += record.tokensIn ?? 0;
672
+ report.tokensOut += record.tokensOut ?? 0;
673
+ const tier = report.byTier[record.tier];
674
+ tier.count += 1;
675
+ tier.estimatedUSD += record.estimatedCostUSD;
676
+ tier.savingsUSD += record.estimatedSavingsUSD;
677
+ }
678
+ report.savingsPct = report.totalBaselineUSD > 0 ? report.totalSavingsUSD / report.totalBaselineUSD * 100 : 0;
679
+ return report;
680
+ }
681
+ function usd(value) {
682
+ return `$${value.toFixed(5)}`;
683
+ }
684
+ function renderReport(report) {
685
+ if (report.count === 0) {
686
+ return "openteam report: sin registros de telemetría todavía.";
687
+ }
688
+ const lines = [
689
+ "openteam report:",
690
+ ` decisiones: ${report.count} (local ${report.localCount} · frontier ${report.frontierCount})`,
691
+ ` coste real: ${usd(report.totalEstimatedUSD)}`,
692
+ ` baseline: ${usd(report.totalBaselineUSD)}`,
693
+ ` ahorro: ${usd(report.totalSavingsUSD)} (${report.savingsPct.toFixed(2)}%)`,
694
+ ` tokens: in ${report.tokensIn} · out ${report.tokensOut}`,
695
+ " por tier:"
696
+ ];
697
+ for (const tier of TIERS) {
698
+ const summary = report.byTier[tier];
699
+ lines.push(` ${tier.padEnd(9)} ${summary.count} · coste ${usd(summary.estimatedUSD)} · ahorro ${usd(summary.savingsUSD)}`);
700
+ }
701
+ return lines.join(`
702
+ `);
703
+ }
704
+
705
+ // src/commands/dispatch.ts
706
+ var HELP = [
707
+ "openteam — comandos runtime",
708
+ "",
709
+ "Uso:",
710
+ " openteam baseline show Muestra el baseline efectivo",
711
+ " openteam baseline set <p/model> Fija el baseline (modo pinned)",
712
+ " openteam baseline auto Baseline cheapest-capable (modo auto)",
713
+ " openteam doctor Diagnóstico de runtimes y config",
714
+ " openteam report Resumen de coste/ahorro (telemetría)",
715
+ "",
716
+ "Opciones:",
717
+ " --config <path> Ruta de .opencode/openteam.json",
718
+ " --telemetry <path> Ruta del JSONL de telemetría"
719
+ ].join(`
720
+ `);
721
+ function parseArgs(argv) {
722
+ const positionals = [];
723
+ const result = { positionals };
724
+ for (let i = 0;i < argv.length; i += 1) {
725
+ const arg = argv[i];
726
+ if (arg === "--config") {
727
+ const value = argv[i + 1];
728
+ if (value !== undefined) {
729
+ result.configPath = value;
730
+ }
731
+ i += 1;
732
+ } else if (arg === "--telemetry") {
733
+ const value = argv[i + 1];
734
+ if (value !== undefined) {
735
+ result.telemetryPath = value;
736
+ }
737
+ i += 1;
738
+ } else if (arg !== undefined) {
739
+ positionals.push(arg);
740
+ }
741
+ }
742
+ return result;
743
+ }
744
+ async function runBaseline(positionals, deps, configPath) {
745
+ const sub = positionals[1];
746
+ if (sub === "show" || sub === undefined) {
747
+ const config = await deps.loadConfig(configPath);
748
+ return { exitCode: 0, stdout: showBaseline(config).message };
749
+ }
750
+ if (sub === "auto") {
751
+ const config = await deps.loadConfig(configPath);
752
+ const outcome = autoBaseline(config);
753
+ if (outcome.config !== undefined) {
754
+ await deps.saveConfig(outcome.config, configPath);
755
+ }
756
+ return { exitCode: 0, stdout: outcome.message };
757
+ }
758
+ if (sub === "set") {
759
+ const target = positionals[2];
760
+ if (target === undefined) {
761
+ return {
762
+ exitCode: 1,
763
+ stdout: "Falta el modelo. Uso: openteam baseline set <provider/model>"
764
+ };
765
+ }
766
+ const config = await deps.loadConfig(configPath);
767
+ const outcome = setBaseline(config, parseModelRef(target));
768
+ if (outcome.config !== undefined) {
769
+ await deps.saveConfig(outcome.config, configPath);
770
+ }
771
+ return { exitCode: 0, stdout: outcome.message };
772
+ }
773
+ return {
774
+ exitCode: 1,
775
+ stdout: `Subcomando de baseline desconocido: ${sub}
776
+
777
+ ${HELP}`
778
+ };
779
+ }
780
+ async function runCli(argv, deps) {
781
+ const parsed = parseArgs(argv);
782
+ const command = parsed.positionals[0];
783
+ const configPath = parsed.configPath ?? deps.configPath;
784
+ const telemetryPath = parsed.telemetryPath ?? deps.telemetryPath;
785
+ try {
786
+ if (command === "baseline") {
787
+ return await runBaseline(parsed.positionals, deps, configPath);
788
+ }
789
+ if (command === "doctor") {
790
+ const config = await deps.loadConfig(configPath);
791
+ const [snapshots, records] = await Promise.all([
792
+ deps.probe(config),
793
+ deps.readTelemetry(telemetryPath)
794
+ ]);
795
+ return {
796
+ exitCode: 0,
797
+ stdout: renderDoctor({
798
+ config,
799
+ snapshots,
800
+ telemetryPath,
801
+ telemetryRecords: records.length
802
+ })
803
+ };
804
+ }
805
+ if (command === "report") {
806
+ const records = await deps.readTelemetry(telemetryPath);
807
+ return {
808
+ exitCode: 0,
809
+ stdout: renderReport(summarizeCostRecords(records))
810
+ };
811
+ }
812
+ if (command === undefined || command === "help" || command === "--help") {
813
+ return { exitCode: 0, stdout: HELP };
814
+ }
815
+ return {
816
+ exitCode: 1,
817
+ stdout: `Comando desconocido: ${command}
818
+
819
+ ${HELP}`
820
+ };
821
+ } catch (error) {
822
+ const message = error instanceof Error ? error.message : String(error);
823
+ return { exitCode: 1, stdout: `Error: ${message}` };
824
+ }
825
+ }
826
+
827
+ // src/plugin/commandTool.ts
828
+ function commandArgv(action, model) {
829
+ switch (action) {
830
+ case "show":
831
+ return ["baseline", "show"];
832
+ case "auto":
833
+ return ["baseline", "auto"];
834
+ case "set":
835
+ return model === undefined ? ["baseline", "set"] : ["baseline", "set", model];
836
+ case "doctor":
837
+ return ["doctor"];
838
+ case "report":
839
+ return ["report"];
840
+ }
841
+ }
842
+ function createCommandTool(deps) {
843
+ return tool({
844
+ description: "Comandos runtime de openteam: baseline (show/set/auto), doctor y report.",
845
+ args: {
846
+ action: tool.schema.enum(["show", "set", "auto", "doctor", "report"]).describe("Acción a ejecutar"),
847
+ model: tool.schema.string().optional().describe("Modelo provider/model (solo para action=set)")
848
+ },
849
+ async execute(args) {
850
+ const result = await runCli(commandArgv(args.action, args.model), deps);
851
+ return result.stdout;
852
+ }
853
+ });
854
+ }
855
+
523
856
  // src/capabilities/budget.ts
524
857
  var WARNING_UTILIZATION = 0.8;
525
858
  function projectedSpend(current, estimatedCostUSD) {
@@ -1113,8 +1446,47 @@ function createHooks(config, getAvailable, options = {}) {
1113
1446
  };
1114
1447
  }
1115
1448
 
1116
- // src/index.ts
1449
+ // src/telemetry/read.ts
1450
+ import { readFile } from "node:fs/promises";
1117
1451
  var DEFAULT_TELEMETRY_PATH = ".opencode/openteam-telemetry.jsonl";
1452
+ function parseCostRecordsJsonl(text) {
1453
+ const records = [];
1454
+ for (const line of text.split(`
1455
+ `)) {
1456
+ const trimmed = line.trim();
1457
+ if (trimmed.length === 0) {
1458
+ continue;
1459
+ }
1460
+ let candidate;
1461
+ try {
1462
+ candidate = JSON.parse(trimmed);
1463
+ } catch {
1464
+ continue;
1465
+ }
1466
+ const parsed = CostRecordSchema.safeParse(candidate);
1467
+ if (parsed.success) {
1468
+ records.push(parsed.data);
1469
+ }
1470
+ }
1471
+ return records;
1472
+ }
1473
+ function isMissingFile(error) {
1474
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
1475
+ }
1476
+ async function readCostRecords(path, deps = {}) {
1477
+ const readFileFn = deps.readFile ?? readFile;
1478
+ try {
1479
+ const content = await readFileFn(path, "utf8");
1480
+ return parseCostRecordsJsonl(content);
1481
+ } catch (error) {
1482
+ if (isMissingFile(error)) {
1483
+ return [];
1484
+ }
1485
+ throw error;
1486
+ }
1487
+ }
1488
+
1489
+ // src/index.ts
1118
1490
  function createShellExec($) {
1119
1491
  return async (command, args) => {
1120
1492
  const output = await $`${command} ${[...args]}`.nothrow().quiet();
@@ -1143,10 +1515,10 @@ function telemetryOptions(rawOptions) {
1143
1515
  };
1144
1516
  }
1145
1517
  function createFileAppender(filePath, deps = {}) {
1146
- const mkdirFn = deps.mkdir ?? mkdir;
1518
+ const mkdirFn = deps.mkdir ?? mkdir2;
1147
1519
  const appendFileFn = deps.appendFile ?? appendFile;
1148
1520
  return async (line) => {
1149
- await mkdirFn(dirname(filePath), { recursive: true });
1521
+ await mkdirFn(dirname2(filePath), { recursive: true });
1150
1522
  await appendFileFn(filePath, line, "utf8");
1151
1523
  };
1152
1524
  }
@@ -1160,6 +1532,28 @@ function createTelemetrySink(rawOptions) {
1160
1532
  onError: logTelemetryError
1161
1533
  });
1162
1534
  }
1535
+ function isMissingFile2(error) {
1536
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
1537
+ }
1538
+ function createCliDeps(config, registry, telemetryPath) {
1539
+ return {
1540
+ loadConfig: async (path) => {
1541
+ try {
1542
+ return loadOpenTeamConfig(JSON.parse(await readFile2(path, "utf8")));
1543
+ } catch (error) {
1544
+ if (isMissingFile2(error)) {
1545
+ return config;
1546
+ }
1547
+ throw error;
1548
+ }
1549
+ },
1550
+ saveConfig: (nextConfig, path) => writeOpenTeamConfigFile(nextConfig, path),
1551
+ probe: (nextConfig) => registry.probe(nextConfig.local.runtimes),
1552
+ readTelemetry: (path) => readCostRecords(path),
1553
+ configPath: DEFAULT_CONFIG_PATH,
1554
+ telemetryPath
1555
+ };
1556
+ }
1163
1557
  var server = async (ctx, rawOptions) => {
1164
1558
  const config = loadOpenTeamConfig(rawOptions ?? {});
1165
1559
  const registry = new RuntimeRegistry({
@@ -1173,11 +1567,16 @@ var server = async (ctx, rawOptions) => {
1173
1567
  onRefreshError: logAvailabilityRefreshError
1174
1568
  });
1175
1569
  cache.refresh();
1570
+ const telemetryPath = telemetryOptions(rawOptions).path;
1176
1571
  const hooks = createHooks(config, cache.get, {
1177
1572
  sink: createTelemetrySink(rawOptions)
1178
1573
  });
1574
+ const cliDeps = createCliDeps(config, registry, telemetryPath);
1179
1575
  return {
1180
1576
  ...hooks,
1577
+ tool: {
1578
+ openteam: createCommandTool(cliDeps)
1579
+ },
1181
1580
  event: async ({ event }) => {
1182
1581
  if (event.type === "session.created" || event.type === "session.idle") {
1183
1582
  cache.refresh();
@@ -1194,6 +1593,8 @@ export {
1194
1593
  server,
1195
1594
  logTelemetryError,
1196
1595
  logAvailabilityRefreshError,
1596
+ isMissingFile2 as isMissingFile,
1197
1597
  src_default as default,
1198
- createFileAppender
1598
+ createFileAppender,
1599
+ createCliDeps
1199
1600
  };