opencode-usage-coach 0.9.1 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +12 -0
  2. package/dist/index.js +81 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -49,6 +49,18 @@ For local dev without npm: `bun install && bun run build`, then point both confi
49
49
 
50
50
  Four config surfaces; only the first two (install config + codexbar) are required to run. The **harness config** (`harness.config.json`) maps roles to models so per-model quota is tracked — set `generator` (required) and optionally `grader`, `lighterModel`, and `provider`. Thresholds and tuning live in env vars (`UC_STOP_5H`, `UC_THROTTLE_5H`, …).
51
51
 
52
+ ### Changing models at runtime
53
+
54
+ Type `/coach-config` in the TUI to view or update harness models interactively. The AI calls the `coach_config` tool, which reads/writes `harness.config.json` with merge semantics — no manual JSON editing required:
55
+
56
+ ```
57
+ /coach-config # view current config
58
+ "change generator to anthropic/claude-sonnet-4-20250514"
59
+ "set grader and lighterModel to opencode/mimo-v2.5-free"
60
+ ```
61
+
62
+ Changes take effect immediately for new `generate` / `grade` calls.
63
+
52
64
  See **[docs/configuration.md](docs/configuration.md)** for the full reference (env var table, harness config fields, agent-mode scoping, local dev setup).
53
65
 
54
66
  ## Harness Loop
package/dist/index.js CHANGED
@@ -909,6 +909,25 @@ function readHarnessCfg(dir) {
909
909
  ...tryRead(join2(dir, "harness.config.json"))
910
910
  };
911
911
  }
912
+ function writeHarnessCfg(updates) {
913
+ const configDir = join2(homedir(), ".config", "opencode-usage-coach");
914
+ const configPath = join2(configDir, "harness.config.json");
915
+ try {
916
+ mkdirSync2(configDir, { recursive: true });
917
+ const existing = (() => {
918
+ try {
919
+ return JSON.parse(readFileSync2(configPath, "utf8"));
920
+ } catch {
921
+ return {};
922
+ }
923
+ })();
924
+ const merged = { ...existing, ...updates };
925
+ writeFileSync2(configPath, JSON.stringify(merged, null, 2) + "\n");
926
+ return configPath;
927
+ } catch (e) {
928
+ throw new Error(`Failed to write harness config: ${String(e)}`, { cause: e });
929
+ }
930
+ }
912
931
  async function runModel(client, model, prompt, directory, track, maxSteps = DEFAULT_MAX_STEPS) {
913
932
  const t0 = Date.now();
914
933
  const subStart = Date.now();
@@ -1245,6 +1264,7 @@ function isHarnessAgent(agent) {
1245
1264
  }
1246
1265
  var LOADING = { decision: "GO", advice: "quota loading\u2026", weekly: -1, monthly: -1, fiveHour: -1 };
1247
1266
  async function UsageCoachPlugin(input) {
1267
+ pipeLog(`UsageCoachPlugin CALLED | dir=${input.directory} | worktree=${input.worktree}`);
1248
1268
  try {
1249
1269
  setStateDir(input.directory);
1250
1270
  initDomain(STATE_DIR);
@@ -2139,10 +2159,67 @@ If the task is already well-specified with no significant ambiguities, return {"
2139
2159
  const qNum = state.currentIndex + 1;
2140
2160
  return formatQuestionOutput(qNum, total, q);
2141
2161
  }
2162
+ }),
2163
+ coach_config: tool({
2164
+ description: 'View or update harness model configuration (generator, grader, lighterModel, provider). Call with no args to view current config. Pass any combination of generator/grader/lighterModel/provider to update. Example: coach_config({ generator: "anthropic/claude-sonnet-4-20250514", grader: "opencode/mimo-v2.5-free" })',
2165
+ args: {
2166
+ generator: tool.schema.string().optional(),
2167
+ grader: tool.schema.string().optional(),
2168
+ lighterModel: tool.schema.string().optional(),
2169
+ provider: tool.schema.string().optional()
2170
+ },
2171
+ async execute(args, ctx) {
2172
+ const dir = ctx?.directory ?? input.directory;
2173
+ const current2 = readHarnessCfg(dir);
2174
+ const hasUpdates = args.generator !== void 0 || args.grader !== void 0 || args.lighterModel !== void 0 || args.provider !== void 0;
2175
+ if (!hasUpdates) {
2176
+ const envOverrides = [];
2177
+ if (process.env.UC_PROVIDER) envOverrides.push(`UC_PROVIDER=${process.env.UC_PROVIDER}`);
2178
+ if (process.env.UC_LIGHTER_MODEL) envOverrides.push(`UC_LIGHTER_MODEL=${process.env.UC_LIGHTER_MODEL}`);
2179
+ return [
2180
+ `Harness Configuration`,
2181
+ `\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
2182
+ ` generator: ${current2.generator ?? "(not set \u2014 harness won't work!)"}`,
2183
+ ` grader: ${current2.grader ?? "(defaults to generator)"}`,
2184
+ ` lighterModel: ${current2.lighterModel ?? "(not set \u2014 no THROTTLE fallback)"}`,
2185
+ ` provider: ${current2.provider ?? "(auto-detected from model)"}`,
2186
+ `\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
2187
+ envOverrides.length > 0 ? `
2188
+ Environment overrides (take precedence):
2189
+ ${envOverrides.join("\n ")}` : "",
2190
+ `
2191
+ Config file: ~/.config/opencode-usage-coach/harness.config.json`,
2192
+ `
2193
+ To update, call: coach_config({ generator: "provider/model-id", ... })`
2194
+ ].filter(Boolean).join("\n");
2195
+ }
2196
+ const updates = {};
2197
+ if (args.generator !== void 0) updates.generator = args.generator.trim();
2198
+ if (args.grader !== void 0) updates.grader = args.grader.trim();
2199
+ if (args.lighterModel !== void 0) updates.lighterModel = args.lighterModel.trim();
2200
+ if (args.provider !== void 0) updates.provider = args.provider.trim();
2201
+ const writtenPath = writeHarnessCfg(updates);
2202
+ const updated = readHarnessCfg(dir);
2203
+ return [
2204
+ `\u2705 Harness config updated successfully!`,
2205
+ `\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
2206
+ ` generator: ${updated.generator ?? "(not set)"}`,
2207
+ ` grader: ${updated.grader ?? "(defaults to generator)"}`,
2208
+ ` lighterModel: ${updated.lighterModel ?? "(not set)"}`,
2209
+ ` provider: ${updated.provider ?? "(auto-detected)"}`,
2210
+ `\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`,
2211
+ `
2212
+ Saved to: ${writtenPath}`,
2213
+ `
2214
+ Note: Changes take effect immediately for new generate/grade calls.`,
2215
+ ` A running harness will pick up the new config on the next tool call.`
2216
+ ].join("\n");
2217
+ }
2142
2218
  })
2143
2219
  }
2144
2220
  };
2145
2221
  } catch (e) {
2222
+ pipeLog(`PLUGIN INIT FAILED (no-op): ${String(e)}`);
2146
2223
  log(`PLUGIN INIT FAILED (no-op): ${String(e)}`);
2147
2224
  return NOOP_HOOKS;
2148
2225
  }
@@ -2168,8 +2245,11 @@ export {
2168
2245
  providerAdvice,
2169
2246
  providerToCodexbar,
2170
2247
  readHarness,
2248
+ readHarnessCfg,
2171
2249
  readRules,
2250
+ UsageCoachPlugin as server,
2172
2251
  setStateDir,
2173
2252
  updateSubSession,
2174
- writeHarness
2253
+ writeHarness,
2254
+ writeHarnessCfg
2175
2255
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-usage-coach",
3
- "version": "0.9.1",
3
+ "version": "0.10.1",
4
4
  "description": "opencode closed-loop usage coach — quota SENSE -> coaching DECIDE -> loop ACT + TUI integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",