opencode-usage-coach 0.6.2 → 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.
Files changed (4) hide show
  1. package/README.md +0 -1
  2. package/dist/index.js +155 -183
  3. package/dist/tui.js +186 -149
  4. package/package.json +2 -8
package/README.md CHANGED
@@ -155,7 +155,6 @@ Place in the **work directory**. Each role runs on its model, so per-model quota
155
155
  | `UC_HARNESS_AGENT` | `Usage-Coach-Harness` | comma-separated agent modes allowed to use harness tools + receive quota coaching (case-insensitive; must match the agent id, e.g. `usage-coach-harness` from `agents/usage-coach-harness.md`) |
156
156
  | `UC_WORM_MAX_AGE_DAYS` | 180 | domain DB worm (GC): drop nodes not accessed in N days (~6 months) |
157
157
  | `UC_WORM_MAX_NODES` | 100000 | domain DB worm (GC): cap node count, evict oldest-accessed beyond this |
158
- | `UC_DOMAIN_TIMEOUT_MS` | 5000 | domain DB query timeout (ms) — a slow/hung query resolves to a safe fallback instead of blocking the plugin |
159
158
 
160
159
  ## Agent-mode scoping
161
160
 
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { mkdirSync, writeFileSync, appendFileSync, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
2
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, appendFileSync as appendFileSync2, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
3
3
  import { spawn } from "child_process";
4
4
  import { createHash } from "crypto";
5
5
  import { homedir } from "os";
@@ -7,193 +7,123 @@ import { join as join2, resolve, dirname } from "path";
7
7
  import { tool } from "@opencode-ai/plugin";
8
8
 
9
9
  // src/domain.ts
10
- import { Database, Connection } from "@ladybugdb/core";
10
+ import { mkdirSync, appendFileSync, readFileSync, existsSync, writeFileSync } from "fs";
11
11
  import { join } from "path";
12
- import { existsSync, readFileSync } from "fs";
13
- var QUERY_TIMEOUT_MS = (() => {
14
- try {
15
- const v = Number(process.env.UC_DOMAIN_TIMEOUT_MS);
16
- return Number.isFinite(v) && v > 0 ? v : 5e3;
17
- } catch {
18
- return 5e3;
19
- }
20
- })();
21
12
  var BASE_DIR = "";
22
- var DB_PATH = "";
23
- var db = null;
24
- var conn = null;
25
- var schemaReady = false;
26
- var migrated = false;
27
13
  function initDomain(stateDir) {
28
14
  BASE_DIR = stateDir;
29
- DB_PATH = join(BASE_DIR, "domain.ladybug");
30
- db = null;
31
- conn = null;
32
- schemaReady = false;
33
- migrated = false;
34
15
  }
35
- function uid(prefix) {
36
- return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
16
+ var nodesFile = () => join(BASE_DIR, "nodes.ndjson");
17
+ var edgesFile = () => join(BASE_DIR, "edges.ndjson");
18
+ function readNdjson(path) {
19
+ try {
20
+ if (!existsSync(path)) return [];
21
+ return readFileSync(path, "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
22
+ } catch {
23
+ return [];
24
+ }
37
25
  }
38
- function esc(s) {
39
- return String(s ?? "").replace(/\\/g, "\\\\").replace(/'/g, "\\'");
26
+ function readNodes() {
27
+ return readNdjson(nodesFile());
40
28
  }
41
- function timed(op, fallback) {
42
- let timer;
43
- const timeout = new Promise((resolve2) => {
44
- timer = setTimeout(() => resolve2(fallback), QUERY_TIMEOUT_MS);
45
- });
46
- return Promise.race([op().catch(() => fallback), timeout]).finally(() => {
47
- if (timer) clearTimeout(timer);
48
- });
29
+ function readEdges() {
30
+ return readNdjson(edgesFile());
31
+ }
32
+ function uid(prefix) {
33
+ return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
49
34
  }
50
- function parseNode(r) {
51
- let props = {};
35
+ function addDomainNode(node) {
36
+ const full = { ...node, id: uid("node"), ts: (/* @__PURE__ */ new Date()).toISOString() };
52
37
  try {
53
- if (r.props) props = JSON.parse(r.props);
38
+ mkdirSync(BASE_DIR, { recursive: true });
39
+ appendFileSync(nodesFile(), JSON.stringify(full) + "\n");
54
40
  } catch {
55
41
  }
56
- return {
57
- id: r.id,
58
- type: r.type,
59
- name: r.name,
60
- props,
61
- source: r.source ?? "",
62
- confidence: r.confidence ?? 0,
63
- ts: r.ts,
64
- lastAccessed: r.lastAccessed || void 0,
65
- accessCount: r.accessCount ?? void 0
66
- };
67
- }
68
- var NODE_COLS = "n.id AS id, n.type AS type, n.name AS name, n.props AS props, n.source AS source, n.confidence AS confidence, n.ts AS ts, n.lastAccessed AS lastAccessed, n.accessCount AS accessCount";
69
- async function ready() {
70
- if (!conn) {
71
- db = new Database(DB_PATH);
72
- conn = new Connection(db);
73
- }
74
- if (!schemaReady) {
75
- try {
76
- await conn.query("CREATE NODE TABLE DomainNode(id STRING, type STRING, name STRING, props STRING, source STRING, confidence DOUBLE, lastAccessed STRING, accessCount INT64 DEFAULT 0, ts STRING, PRIMARY KEY(id))");
77
- } catch {
78
- }
79
- try {
80
- await conn.query("CREATE REL TABLE Related(FROM DomainNode TO DomainNode, rel STRING, note STRING, ts STRING)");
81
- } catch {
82
- }
83
- schemaReady = true;
84
- }
85
- if (!migrated) {
86
- migrated = true;
87
- await migrateFromNdjson();
88
- }
89
- return conn;
42
+ return full.id;
90
43
  }
91
- async function migrateFromNdjson() {
92
- if (!conn) return;
93
- const nf = join(BASE_DIR, "nodes.ndjson");
94
- const ef = join(BASE_DIR, "edges.ndjson");
95
- if (!existsSync(nf)) return;
44
+ function writeNodes(nodes) {
96
45
  try {
97
- const existing = await conn.query("MATCH (n:DomainNode) RETURN count(n) AS c");
98
- const cnt = (await existing.getAll())[0]?.c ?? 0;
99
- if (cnt > 0) return;
100
- const readNdjson = (p) => {
101
- try {
102
- return readFileSync(p, "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
103
- } catch {
104
- return [];
105
- }
106
- };
107
- for (const n of readNdjson(nf)) {
108
- await conn.query(`CREATE (n:DomainNode {id:'${esc(n.id)}',type:'${esc(n.type)}',name:'${esc(n.name)}',props:'${esc(JSON.stringify(n.props ?? {}))}',source:'${esc(n.source ?? "")}',confidence:${Number(n.confidence ?? 0)},ts:'${esc(n.ts ?? "")}',lastAccessed:'${esc(n.lastAccessed ?? "")}',accessCount:${Number(n.accessCount ?? 0)}})`);
109
- }
110
- if (existsSync(ef)) {
111
- for (const e of readNdjson(ef)) {
112
- await conn.query(`MATCH (a:DomainNode {id:'${esc(e.from)}'}), (b:DomainNode {id:'${esc(e.to)}'}) CREATE (a)-[:Related {rel:'${esc(e.rel)}',note:'${esc(e.note ?? "")}',ts:'${esc(e.ts ?? "")}'}]->(b)`);
113
- }
114
- }
46
+ mkdirSync(BASE_DIR, { recursive: true });
47
+ const lines = nodes.map((n) => JSON.stringify(n));
48
+ writeFileSync(nodesFile(), lines.length ? lines.join("\n") + "\n" : "");
115
49
  } catch {
116
50
  }
117
51
  }
118
- async function _readNodes() {
119
- const c = await ready();
120
- const r = await c.query(`MATCH (n:DomainNode) RETURN ${NODE_COLS}`);
121
- return (await r.getAll()).map(parseNode);
122
- }
123
- async function _addDomainNode(node) {
124
- const c = await ready();
125
- const id = uid("node");
126
- const now = (/* @__PURE__ */ new Date()).toISOString();
127
- await c.query(`CREATE (n:DomainNode {id:'${esc(id)}',type:'${esc(node.type)}',name:'${esc(node.name)}',props:'${esc(JSON.stringify(node.props ?? {}))}',source:'${esc(node.source ?? "")}',confidence:${Number(node.confidence ?? 0)},ts:'${esc(now)}',lastAccessed:'',accessCount:0})`);
128
- return id;
129
- }
130
52
  function queryDomain(keywords) {
131
- return timed(() => _queryDomain(keywords), { nodes: [], edges: [] });
132
- }
133
- async function _queryDomain(keywords) {
134
- const lc = keywords.map((k) => k.toLowerCase()).filter(Boolean);
135
- if (lc.length === 0) return { nodes: [], edges: [] };
136
- const c = await ready();
137
- const conds = lc.map((kw) => `(lower(n.name) CONTAINS '${esc(kw)}' OR lower(n.props) CONTAINS '${esc(kw)}')`).join(" OR ");
138
- const r = await c.query(`MATCH (n:DomainNode) WHERE ${conds} RETURN ${NODE_COLS}`);
139
- const matched = (await r.getAll()).map(parseNode);
140
- if (matched.length) await _touchNodes(new Set(matched.map((n) => n.id)));
141
- let edges = [];
142
- if (matched.length) {
143
- const ids = matched.map((n) => `'${esc(n.id)}'`).join(",");
144
- const er = await c.query(`MATCH (a:DomainNode)-[r:Related]->(b:DomainNode) WHERE a.id IN [${ids}] OR b.id IN [${ids}] RETURN a.id AS from, b.id AS to, r.rel AS rel, r.note AS note, r.ts AS ts`);
145
- edges = await er.getAll();
146
- }
53
+ const lc = keywords.map((k) => k.toLowerCase());
54
+ const nodes = readNodes();
55
+ const matched = nodes.filter((n) => {
56
+ const hay = (n.name + " " + JSON.stringify(n.props)).toLowerCase();
57
+ return lc.some((k) => k && hay.includes(k));
58
+ });
59
+ if (matched.length) touchNodes(new Set(matched.map((n) => n.id)));
60
+ const ids = new Set(matched.map((n) => n.id));
61
+ const edges = readEdges().filter((e) => ids.has(e.from) || ids.has(e.to));
147
62
  return { nodes: matched, edges };
148
63
  }
149
- async function _touchNodes(ids) {
64
+ function touchNodes(ids) {
150
65
  if (ids.size === 0) return;
151
- const c = await ready();
152
- const now = (/* @__PURE__ */ new Date()).toISOString();
153
- const idList = [...ids].map((id) => `'${esc(id)}'`).join(",");
154
- await c.query(`MATCH (n:DomainNode) WHERE n.id IN [${idList}] SET n.lastAccessed = '${esc(now)}', n.accessCount = COALESCE(n.accessCount, 0) + 1`);
155
- }
156
- function evictStale(maxAgeDays = 30, maxNodes = 1e5) {
157
- return timed(() => _evictStale(maxAgeDays, maxNodes), { removed: 0, kept: 0 });
158
- }
159
- async function _evictStale(maxAgeDays, maxNodes) {
160
- const c = await ready();
161
- const all = await _readNodes();
162
- if (all.length === 0) return { removed: 0, kept: 0 };
163
- const now = Date.now();
164
- const ageMs = maxAgeDays * 864e5;
165
- const lastTs = (n) => {
166
- const la = n.lastAccessed ? new Date(n.lastAccessed).getTime() : 0;
167
- return la || new Date(n.ts).getTime();
168
- };
169
- let kept = all.filter((n) => now - lastTs(n) < ageMs);
170
- if (kept.length > maxNodes) {
171
- kept.sort((a, b) => lastTs(b) - lastTs(a));
172
- kept = kept.slice(0, maxNodes);
66
+ try {
67
+ const nodes = readNodes();
68
+ let changed = false;
69
+ const now = (/* @__PURE__ */ new Date()).toISOString();
70
+ for (const n of nodes) {
71
+ if (ids.has(n.id)) {
72
+ n.lastAccessed = now;
73
+ n.accessCount = (n.accessCount ?? 0) + 1;
74
+ changed = true;
75
+ }
76
+ }
77
+ if (changed) writeNodes(nodes);
78
+ } catch {
173
79
  }
174
- const keepIds = new Set(kept.map((n) => n.id));
175
- const toRemove = all.filter((n) => !keepIds.has(n.id));
176
- for (const n of toRemove) {
177
- await c.query(`MATCH (n:DomainNode {id:'${esc(n.id)}'}) DETACH DELETE n`);
80
+ }
81
+ function evictStale(maxAgeDays = 30, maxNodes = 1e3) {
82
+ try {
83
+ const nodes = readNodes();
84
+ if (nodes.length === 0) return { removed: 0, kept: 0 };
85
+ const now = Date.now();
86
+ const ageMs = maxAgeDays * 864e5;
87
+ const lastTs = (n) => new Date(n.lastAccessed ?? n.ts).getTime();
88
+ let kept = nodes.filter((n) => now - lastTs(n) < ageMs);
89
+ if (kept.length > maxNodes) {
90
+ kept.sort((a, b) => lastTs(b) - lastTs(a));
91
+ kept = kept.slice(0, maxNodes);
92
+ }
93
+ const removed = nodes.length - kept.length;
94
+ if (removed > 0) writeNodes(kept);
95
+ return { removed, kept: kept.length };
96
+ } catch {
97
+ return { removed: 0, kept: 0 };
178
98
  }
179
- return { removed: toRemove.length, kept: kept.length };
180
99
  }
181
100
  function saveInvestigationResult(keywords, result, source) {
182
- return timed(() => _saveInvestigationResult(keywords, result, source), "");
183
- }
184
- async function _saveInvestigationResult(keywords, result, source) {
185
- return _addDomainNode({
186
- type: "fact",
187
- name: keywords.join(" "),
188
- props: { result },
189
- source: source || "investigation",
190
- confidence: 0.7
191
- });
101
+ try {
102
+ return addDomainNode({
103
+ type: "fact",
104
+ name: keywords.join(" "),
105
+ props: { result },
106
+ source: source || "investigation",
107
+ confidence: 0.7
108
+ });
109
+ } catch {
110
+ return "";
111
+ }
192
112
  }
193
113
 
194
114
  // src/index.ts
195
115
  var PLUGIN_NAME = "opencode-usage-coach";
196
116
  var TTL_MS = Number(process.env.UC_TTL_MS ?? 6e4);
117
+ var PIPE_LOG = join2(homedir(), ".cache", "opencode-usage-coach", "pipeline.log");
118
+ function pipeLog(msg) {
119
+ try {
120
+ mkdirSync2(dirname(PIPE_LOG), { recursive: true });
121
+ appendFileSync2(PIPE_LOG, `[SERVER] ${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
122
+ `);
123
+ } catch {
124
+ }
125
+ }
126
+ pipeLog(`MODULE LOADED | node=${process.version} | pid=${process.pid}`);
197
127
  var STATE_DIR = join2(homedir(), ".cache", "opencode-usage-coach");
198
128
  var STATE_FILE = join2(STATE_DIR, "state.json");
199
129
  var LOG_FILE = join2(STATE_DIR, "coach.log");
@@ -210,15 +140,15 @@ function setStateDir(dir) {
210
140
  var NOOP_HOOKS = {};
211
141
  function log(msg) {
212
142
  try {
213
- appendFileSync(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
143
+ appendFileSync2(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
214
144
  `);
215
145
  } catch {
216
146
  }
217
147
  }
218
148
  function writeState(c) {
219
149
  try {
220
- mkdirSync(STATE_DIR, { recursive: true });
221
- writeFileSync(STATE_FILE, JSON.stringify({ ...c, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }));
150
+ mkdirSync2(STATE_DIR, { recursive: true });
151
+ writeFileSync2(STATE_FILE, JSON.stringify({ ...c, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }));
222
152
  } catch {
223
153
  }
224
154
  }
@@ -269,9 +199,9 @@ function readHarness(sessionID) {
269
199
  function writeHarness(sessionID, h) {
270
200
  try {
271
201
  const f = harnessFile(sessionID);
272
- mkdirSync(dirname(f), { recursive: true });
202
+ mkdirSync2(dirname(f), { recursive: true });
273
203
  h.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
274
- writeFileSync(f, JSON.stringify(h, null, 2));
204
+ writeFileSync2(f, JSON.stringify(h, null, 2));
275
205
  } catch {
276
206
  }
277
207
  }
@@ -451,14 +381,44 @@ function coach(q, lighter) {
451
381
  return { decision: "GO", advice: `Comfortable \u2014 weekly ${wk}% \xB7 5h ${h5}% \xB7 monthly ${mo}%. proceed. 5h window ${h5R}.`, weekly: wk, monthly: mo, fiveHour: h5 };
452
382
  }
453
383
  var agentCache = /* @__PURE__ */ new Map();
384
+ var currentModel = "";
385
+ var currentProvider = "";
386
+ var currentAgent = "";
387
+ var modelChanged = false;
388
+ function isFreeModel(model, provider) {
389
+ if (!model && !provider) return false;
390
+ if (provider === "opencode") return true;
391
+ if (model.toLowerCase().includes("free")) return true;
392
+ return false;
393
+ }
394
+ function providerToCodexbar(provider) {
395
+ if (!provider) return "";
396
+ return provider.split("-")[0];
397
+ }
454
398
  async function resolveAgent(client, sessionID) {
455
399
  if (!sessionID) return "";
456
400
  const hit = agentCache.get(sessionID);
457
- if (hit && Date.now() - hit.ts < 6e4) return hit.agent;
401
+ if (hit && Date.now() - hit.ts < 6e4) {
402
+ currentModel = hit.model;
403
+ currentProvider = hit.provider;
404
+ return hit.agent;
405
+ }
458
406
  try {
459
407
  const s = await client.session.get({ path: { id: sessionID } });
460
- const agent = String(s?.data?.info?.agent ?? s?.data?.agent ?? s?.info?.agent ?? "");
461
- agentCache.set(sessionID, { agent, ts: Date.now() });
408
+ log(`resolveAgent raw session: ${JSON.stringify(s?.data?.info ?? s?.data ?? s?.info ?? s).slice(0, 500)}`);
409
+ const info = s?.data?.info ?? s?.data ?? s?.info ?? s;
410
+ const agent = String(info?.agent ?? "");
411
+ const rawModel = info?.model;
412
+ const model = typeof rawModel === "string" ? rawModel : rawModel?.id ?? rawModel?.modelID ?? rawModel?.name ?? "";
413
+ const rawProvider = info?.providerID ?? info?.provider ?? (typeof rawModel === "object" ? rawModel?.providerID ?? rawModel?.provider : "");
414
+ const provider = typeof rawProvider === "string" ? rawProvider : rawProvider?.id ?? "";
415
+ if (currentModel && model && currentModel !== model) {
416
+ log(`MODEL CHANGED: ${currentModel} \u2192 ${model} (provider: ${currentProvider} \u2192 ${provider})`);
417
+ modelChanged = true;
418
+ }
419
+ agentCache.set(sessionID, { agent, model, provider, ts: Date.now() });
420
+ currentModel = model;
421
+ currentProvider = provider;
462
422
  return agent;
463
423
  } catch (e) {
464
424
  log(`resolveAgent err: ${String(e)}`);
@@ -483,9 +443,19 @@ async function UsageCoachPlugin(input) {
483
443
  const refreshBackground = () => {
484
444
  try {
485
445
  if (refreshing) return;
486
- if (last && Date.now() - lastFetchedAt < TTL_MS) return;
446
+ if (last && !modelChanged && Date.now() - lastFetchedAt < TTL_MS) return;
487
447
  refreshing = true;
488
- fetchQuota(PROVIDER).then(async (q) => {
448
+ modelChanged = false;
449
+ if (isFreeModel(currentModel, currentProvider)) {
450
+ last = { decision: "GO", advice: `${currentModel || currentProvider || "free model"} \u2014 no quota limit.`, weekly: -1, monthly: -1, fiveHour: -1, model: currentModel, provider: currentProvider, isFree: true };
451
+ lastFetchedAt = Date.now();
452
+ writeState({ ...last, providers: [], model: currentModel, provider: currentProvider, isFree: true, agent: currentAgent, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
453
+ log(`FREE | model=${currentModel} provider=${currentProvider}`);
454
+ refreshing = false;
455
+ return;
456
+ }
457
+ const activeProvider = providerToCodexbar(currentProvider) || PROVIDER;
458
+ fetchQuota(activeProvider).then(async (q) => {
489
459
  try {
490
460
  last = coach(q, LIGHTER);
491
461
  lastFetchedAt = Date.now();
@@ -498,7 +468,7 @@ async function UsageCoachPlugin(input) {
498
468
  const p0 = providers[0];
499
469
  last = { ...last, weekly: p0.weekly, fiveHour: p0.fiveHour, monthly: p0.weekly >= 0 ? 0 : -1, advice: p0.advice, decision: p0.weekly >= STOP_WK ? "STOP" : p0.weekly >= THR_WK ? "THROTTLE" : "GO" };
500
470
  }
501
- writeState({ ...last, providers, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
471
+ writeState({ ...last, providers, model: currentModel, provider: currentProvider, isFree: false, agent: currentAgent, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
502
472
  log(`${last.decision} | weekly=${last.weekly}% 5h=${last.fiveHour}% | providers=${providers.length}`);
503
473
  } catch (e) {
504
474
  log(`refresh-in-then err: ${String(e)}`);
@@ -526,7 +496,7 @@ async function UsageCoachPlugin(input) {
526
496
  if (event.type === "session.created" || event.type === "session.idle") refreshBackground();
527
497
  if (event.type === "session.idle") {
528
498
  try {
529
- const r = await evictStale(WORM_MAX_AGE_DAYS, WORM_MAX_NODES);
499
+ const r = evictStale(WORM_MAX_AGE_DAYS, WORM_MAX_NODES);
530
500
  if (r.removed) log(`evictStale: removed ${r.removed}, kept ${r.kept} (maxAge=${WORM_MAX_AGE_DAYS}d, maxNodes=${WORM_MAX_NODES})`);
531
501
  } catch (e) {
532
502
  log(`evictStale err: ${String(e)}`);
@@ -536,13 +506,14 @@ async function UsageCoachPlugin(input) {
536
506
  log(`event err: ${String(e)}`);
537
507
  }
538
508
  },
539
- // ACT(1) hard gate harness tools are restricted to the configured harness
540
- // agent mode AND gated by quota STOP. General tools (read/edit/bash/grep/task)
541
- // are NEVER gated, in ANY mode — they don't consume model quota.
509
+ // ACT(1) model/agent detection on EVERY tool call (cached 60s, negligible overhead).
510
+ // Then hard-gate harness tools by agent mode + quota STOP.
542
511
  "tool.execute.before": async (_input) => {
512
+ const agent = await resolveAgent(input.client, _input.sessionID);
513
+ currentAgent = agent;
514
+ refreshBackground();
543
515
  const harnessTools = ["generate", "generate_batch", "grade", "investigate", "verify_diagnosis", "generalize", "harness_start", "task_update", "harness_done", "record_failure"];
544
516
  if (!harnessTools.includes(_input.tool)) return;
545
- const agent = await resolveAgent(input.client, _input.sessionID);
546
517
  if (!isHarnessAgent(agent)) {
547
518
  throw new Error(`[${PLUGIN_NAME}] '${_input.tool}' is restricted to agent mode ${JSON.stringify(HARNESS_AGENTS)} (current: ${JSON.stringify(agent || "unknown")}). Switch to that agent mode to use it.`);
548
519
  }
@@ -562,6 +533,7 @@ async function UsageCoachPlugin(input) {
562
533
  try {
563
534
  if (_input.sessionID) {
564
535
  const agent = await resolveAgent(input.client, _input.sessionID);
536
+ refreshBackground();
565
537
  if (!isHarnessAgent(agent)) return;
566
538
  }
567
539
  const c = current();
@@ -651,8 +623,8 @@ Then: harness_done(). Follow the [usage-coach NEXT] directive each tool returns.
651
623
  async execute(args, _ctx) {
652
624
  const rec = { ts: (/* @__PURE__ */ new Date()).toISOString(), task: args.task, prompt: args.prompt, gradeResult: args.gradeResult, model: args.model, revisions: args.revisions };
653
625
  try {
654
- mkdirSync(STATE_DIR, { recursive: true });
655
- appendFileSync(failuresFile(), JSON.stringify(rec) + "\n");
626
+ mkdirSync2(STATE_DIR, { recursive: true });
627
+ appendFileSync2(failuresFile(), JSON.stringify(rec) + "\n");
656
628
  } catch (e) {
657
629
  log(`record_failure err: ${String(e)}`);
658
630
  }
@@ -676,7 +648,7 @@ Then: harness_done(). Follow the [usage-coach NEXT] directive each tool returns.
676
648
  try {
677
649
  keywords = extractKeywords(`${args.task} ${args.gradeResult}`);
678
650
  if (keywords.length) {
679
- const { nodes, edges } = await queryDomain(keywords);
651
+ const { nodes, edges } = queryDomain(keywords);
680
652
  if (nodes && nodes.length || edges && edges.length) {
681
653
  domainEmpty = false;
682
654
  domainPrefix = `Known facts from domain DB: ${JSON.stringify({ nodes, edges })}. Use these if relevant.
@@ -700,7 +672,7 @@ evidence: <file/line or specific quote>`;
700
672
  const out = await runModel(input.client, cfg.generator, domainPrefix + rcaPrompt, ctx.directory);
701
673
  if (domainEmpty && keywords.length) {
702
674
  try {
703
- await saveInvestigationResult(keywords, out, "investigate");
675
+ saveInvestigationResult(keywords, out, "investigate");
704
676
  } catch (e) {
705
677
  log(`investigate save err: ${String(e)}`);
706
678
  }
@@ -756,8 +728,8 @@ Keep it concrete and actionable.`;
756
728
  const rule = out;
757
729
  try {
758
730
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
759
- mkdirSync(STATE_DIR, { recursive: true });
760
- appendFileSync(rulesFile(), `## Rule (${date})
731
+ mkdirSync2(STATE_DIR, { recursive: true });
732
+ appendFileSync2(rulesFile(), `## Rule (${date})
761
733
  ${rule}
762
734
  Origin: ${args.task}
763
735
 
@@ -796,7 +768,7 @@ ${rules}
796
768
  try {
797
769
  keywords = extractKeywords(args.prompt);
798
770
  if (keywords.length) {
799
- const { nodes, edges } = await queryDomain(keywords);
771
+ const { nodes, edges } = queryDomain(keywords);
800
772
  if (nodes && nodes.length || edges && edges.length) {
801
773
  domainEmpty = false;
802
774
  prefix = `Known facts from domain DB: ${JSON.stringify({ nodes, edges })}. Use these if relevant.
@@ -812,7 +784,7 @@ ${rules}
812
784
  const out = await runModel(input.client, model, prefix + args.prompt, ctx.directory);
813
785
  if (domainEmpty && keywords.length) {
814
786
  try {
815
- await saveInvestigationResult(keywords, out, "generate");
787
+ saveInvestigationResult(keywords, out, "generate");
816
788
  } catch (e) {
817
789
  log(`generate save err: ${String(e)}`);
818
790
  }
package/dist/tui.js CHANGED
@@ -202,152 +202,189 @@ function initializeTui(api, disposeRoot) {
202
202
  h = null;
203
203
  }
204
204
  const nodes = [];
205
+ const HARNESS_AGENTS = ["usage-coach-harness"];
206
+ if (s && s.agent && !HARNESS_AGENTS.includes(s.agent)) {
207
+ return _$createElement("box");
208
+ }
205
209
  if (s) {
206
210
  const dKey = s.decision === "GO" ? "success" : s.decision === "THROTTLE" ? "warning" : "error";
207
- nodes.push((() => {
208
- var _el$4 = _$createElement("text"), _el$5 = _$createTextNode(`usage-coach [`), _el$6 = _$createTextNode(`]`);
209
- _$insertNode(_el$4, _el$5);
210
- _$insertNode(_el$4, _el$6);
211
- _$insert(_el$4, () => TAG[s.decision], _el$6);
212
- _$effect((_$p) => _$setProp(_el$4, "style", st(dKey), _$p));
213
- return _el$4;
214
- })());
215
- if (s.providers && s.providers.length > 0) {
216
- for (const p of s.providers) {
217
- nodes.push((() => {
218
- var _el$7 = _$createElement("text"), _el$8 = _$createTextNode(` `);
219
- _$insertNode(_el$7, _el$8);
220
- _$insert(_el$7, () => p.name, null);
221
- _$effect((_$p) => _$setProp(_el$7, "style", st("textMuted"), _$p));
222
- return _el$7;
223
- })());
211
+ const modelShort = s.model ? s.model.split("/").pop() ?? s.model : "";
212
+ if (s.isFree) {
213
+ nodes.push((() => {
214
+ var _el$5 = _$createElement("box"), _el$6 = _$createElement("text"), _el$8 = _$createElement("text"), _el$9 = _$createTextNode(` `);
215
+ _$insertNode(_el$5, _el$6);
216
+ _$insertNode(_el$5, _el$8);
217
+ _$setProp(_el$5, "flexDirection", "row");
218
+ _$insertNode(_el$6, _$createTextNode(`usage-coach [free]`));
219
+ _$insertNode(_el$8, _el$9);
220
+ _$insert(_el$8, modelShort, null);
221
+ _$effect((_p$) => {
222
+ var _v$ = st(dKey), _v$2 = st("textMuted");
223
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$6, "style", _v$, _p$.e));
224
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$8, "style", _v$2, _p$.t));
225
+ return _p$;
226
+ }, {
227
+ e: void 0,
228
+ t: void 0
229
+ });
230
+ return _el$5;
231
+ })());
232
+ } else {
233
+ if (modelShort) {
224
234
  nodes.push((() => {
225
- var _el$9 = _$createElement("box"), _el$0 = _$createElement("text"), _el$10 = _$createElement("text"), _el$11 = _$createElement("text"), _el$12 = _$createElement("text"), _el$13 = _$createTextNode(` `), _el$14 = _$createTextNode(`% `);
226
- _$insertNode(_el$9, _el$0);
227
- _$insertNode(_el$9, _el$10);
228
- _$insertNode(_el$9, _el$11);
229
- _$insertNode(_el$9, _el$12);
230
- _$setProp(_el$9, "flexDirection", "row");
231
- _$insertNode(_el$0, _$createTextNode(` 5h `));
232
- _$insert(_el$10, () => barFill(p.fiveHour));
233
- _$insert(_el$11, () => barEmpty(p.fiveHour));
235
+ var _el$0 = _$createElement("box"), _el$1 = _$createElement("text"), _el$10 = _$createTextNode(`usage-coach [`), _el$11 = _$createTextNode(`]`), _el$12 = _$createElement("text"), _el$13 = _$createTextNode(` `);
236
+ _$insertNode(_el$0, _el$1);
237
+ _$insertNode(_el$0, _el$12);
238
+ _$setProp(_el$0, "flexDirection", "row");
239
+ _$insertNode(_el$1, _el$10);
240
+ _$insertNode(_el$1, _el$11);
241
+ _$insert(_el$1, () => TAG[s.decision], _el$11);
234
242
  _$insertNode(_el$12, _el$13);
235
- _$insertNode(_el$12, _el$14);
236
- _$insert(_el$12, () => p.fiveHour, _el$14);
237
- _$insert(_el$12, () => p.fiveHourReset, null);
243
+ _$insert(_el$12, modelShort, null);
238
244
  _$effect((_p$) => {
239
- var _v$ = st("text"), _v$2 = st("text");
240
- _v$ !== _p$.e && (_p$.e = _$setProp(_el$10, "style", _v$, _p$.e));
241
- _v$2 !== _p$.t && (_p$.t = _$setProp(_el$11, "style", _v$2, _p$.t));
245
+ var _v$3 = st(dKey), _v$4 = st("textMuted");
246
+ _v$3 !== _p$.e && (_p$.e = _$setProp(_el$1, "style", _v$3, _p$.e));
247
+ _v$4 !== _p$.t && (_p$.t = _$setProp(_el$12, "style", _v$4, _p$.t));
242
248
  return _p$;
243
249
  }, {
244
250
  e: void 0,
245
251
  t: void 0
246
252
  });
247
- return _el$9;
253
+ return _el$0;
254
+ })());
255
+ } else {
256
+ nodes.push((() => {
257
+ var _el$14 = _$createElement("text"), _el$15 = _$createTextNode(`usage-coach [`), _el$16 = _$createTextNode(`]`);
258
+ _$insertNode(_el$14, _el$15);
259
+ _$insertNode(_el$14, _el$16);
260
+ _$insert(_el$14, () => TAG[s.decision], _el$16);
261
+ _$effect((_$p) => _$setProp(_el$14, "style", st(dKey), _$p));
262
+ return _el$14;
248
263
  })());
264
+ }
265
+ if (s.providers && s.providers.length > 0) {
266
+ for (const p of s.providers) {
267
+ nodes.push((() => {
268
+ var _el$17 = _$createElement("box"), _el$18 = _$createElement("text"), _el$20 = _$createElement("text"), _el$21 = _$createElement("text"), _el$22 = _$createElement("text"), _el$23 = _$createTextNode(` `), _el$24 = _$createTextNode(`% `);
269
+ _$insertNode(_el$17, _el$18);
270
+ _$insertNode(_el$17, _el$20);
271
+ _$insertNode(_el$17, _el$21);
272
+ _$insertNode(_el$17, _el$22);
273
+ _$setProp(_el$17, "flexDirection", "row");
274
+ _$insertNode(_el$18, _$createTextNode(` 5h `));
275
+ _$insert(_el$20, () => barFill(p.fiveHour));
276
+ _$insert(_el$21, () => barEmpty(p.fiveHour));
277
+ _$insertNode(_el$22, _el$23);
278
+ _$insertNode(_el$22, _el$24);
279
+ _$insert(_el$22, () => p.fiveHour, _el$24);
280
+ _$insert(_el$22, () => p.fiveHourReset, null);
281
+ _$effect((_p$) => {
282
+ var _v$5 = st("text"), _v$6 = st("text");
283
+ _v$5 !== _p$.e && (_p$.e = _$setProp(_el$20, "style", _v$5, _p$.e));
284
+ _v$6 !== _p$.t && (_p$.t = _$setProp(_el$21, "style", _v$6, _p$.t));
285
+ return _p$;
286
+ }, {
287
+ e: void 0,
288
+ t: void 0
289
+ });
290
+ return _el$17;
291
+ })());
292
+ nodes.push((() => {
293
+ var _el$25 = _$createElement("box"), _el$26 = _$createElement("text"), _el$28 = _$createElement("text"), _el$29 = _$createElement("text"), _el$30 = _$createElement("text"), _el$31 = _$createTextNode(` `), _el$32 = _$createTextNode(`% `);
294
+ _$insertNode(_el$25, _el$26);
295
+ _$insertNode(_el$25, _el$28);
296
+ _$insertNode(_el$25, _el$29);
297
+ _$insertNode(_el$25, _el$30);
298
+ _$setProp(_el$25, "flexDirection", "row");
299
+ _$insertNode(_el$26, _$createTextNode(` 1w `));
300
+ _$insert(_el$28, () => barFill(p.weekly));
301
+ _$insert(_el$29, () => barEmpty(p.weekly));
302
+ _$insertNode(_el$30, _el$31);
303
+ _$insertNode(_el$30, _el$32);
304
+ _$insert(_el$30, () => p.weekly, _el$32);
305
+ _$insert(_el$30, () => p.weeklyReset, null);
306
+ _$effect((_p$) => {
307
+ var _v$7 = st("text"), _v$8 = st("text");
308
+ _v$7 !== _p$.e && (_p$.e = _$setProp(_el$28, "style", _v$7, _p$.e));
309
+ _v$8 !== _p$.t && (_p$.t = _$setProp(_el$29, "style", _v$8, _p$.t));
310
+ return _p$;
311
+ }, {
312
+ e: void 0,
313
+ t: void 0
314
+ });
315
+ return _el$25;
316
+ })());
317
+ }
318
+ } else {
249
319
  nodes.push((() => {
250
- var _el$15 = _$createElement("box"), _el$16 = _$createElement("text"), _el$18 = _$createElement("text"), _el$19 = _$createElement("text"), _el$20 = _$createElement("text"), _el$21 = _$createTextNode(` `), _el$22 = _$createTextNode(`% `);
251
- _$insertNode(_el$15, _el$16);
252
- _$insertNode(_el$15, _el$18);
253
- _$insertNode(_el$15, _el$19);
254
- _$insertNode(_el$15, _el$20);
255
- _$setProp(_el$15, "flexDirection", "row");
256
- _$insertNode(_el$16, _$createTextNode(` 1w `));
257
- _$insert(_el$18, () => barFill(p.weekly));
258
- _$insert(_el$19, () => barEmpty(p.weekly));
259
- _$insertNode(_el$20, _el$21);
260
- _$insertNode(_el$20, _el$22);
261
- _$insert(_el$20, () => p.weekly, _el$22);
262
- _$insert(_el$20, () => p.weeklyReset, null);
320
+ var _el$33 = _$createElement("box"), _el$34 = _$createElement("text"), _el$36 = _$createElement("text"), _el$37 = _$createElement("text"), _el$38 = _$createElement("text");
321
+ _$insertNode(_el$33, _el$34);
322
+ _$insertNode(_el$33, _el$36);
323
+ _$insertNode(_el$33, _el$37);
324
+ _$insertNode(_el$33, _el$38);
325
+ _$setProp(_el$33, "flexDirection", "row");
326
+ _$insertNode(_el$34, _$createTextNode(` 5h `));
327
+ _$insert(_el$36, () => barFill(s.fiveHour));
328
+ _$insert(_el$37, () => barEmpty(s.fiveHour));
329
+ _$insertNode(_el$38, _$createTextNode(` 0%`));
263
330
  _$effect((_p$) => {
264
- var _v$3 = st("text"), _v$4 = st("text");
265
- _v$3 !== _p$.e && (_p$.e = _$setProp(_el$18, "style", _v$3, _p$.e));
266
- _v$4 !== _p$.t && (_p$.t = _$setProp(_el$19, "style", _v$4, _p$.t));
331
+ var _v$9 = st("text"), _v$0 = st("text");
332
+ _v$9 !== _p$.e && (_p$.e = _$setProp(_el$36, "style", _v$9, _p$.e));
333
+ _v$0 !== _p$.t && (_p$.t = _$setProp(_el$37, "style", _v$0, _p$.t));
267
334
  return _p$;
268
335
  }, {
269
336
  e: void 0,
270
337
  t: void 0
271
338
  });
272
- return _el$15;
339
+ return _el$33;
273
340
  })());
274
341
  nodes.push((() => {
275
- var _el$23 = _$createElement("text"), _el$24 = _$createTextNode(` -> `);
276
- _$insertNode(_el$23, _el$24);
277
- _$insert(_el$23, () => p.advice, null);
278
- _$effect((_$p) => _$setProp(_el$23, "style", st(dKey), _$p));
279
- return _el$23;
342
+ var _el$40 = _$createElement("box"), _el$41 = _$createElement("text"), _el$43 = _$createElement("text"), _el$44 = _$createElement("text"), _el$45 = _$createElement("text");
343
+ _$insertNode(_el$40, _el$41);
344
+ _$insertNode(_el$40, _el$43);
345
+ _$insertNode(_el$40, _el$44);
346
+ _$insertNode(_el$40, _el$45);
347
+ _$setProp(_el$40, "flexDirection", "row");
348
+ _$insertNode(_el$41, _$createTextNode(` 1w `));
349
+ _$insert(_el$43, () => barFill(s.weekly));
350
+ _$insert(_el$44, () => barEmpty(s.weekly));
351
+ _$insertNode(_el$45, _$createTextNode(` 0%`));
352
+ _$effect((_p$) => {
353
+ var _v$1 = st("text"), _v$10 = st("text");
354
+ _v$1 !== _p$.e && (_p$.e = _$setProp(_el$43, "style", _v$1, _p$.e));
355
+ _v$10 !== _p$.t && (_p$.t = _$setProp(_el$44, "style", _v$10, _p$.t));
356
+ return _p$;
357
+ }, {
358
+ e: void 0,
359
+ t: void 0
360
+ });
361
+ return _el$40;
280
362
  })());
281
363
  }
282
- } else {
283
- nodes.push((() => {
284
- var _el$27 = _$createElement("box"), _el$28 = _$createElement("text"), _el$30 = _$createElement("text"), _el$31 = _$createElement("text"), _el$32 = _$createElement("text");
285
- _$insertNode(_el$27, _el$28);
286
- _$insertNode(_el$27, _el$30);
287
- _$insertNode(_el$27, _el$31);
288
- _$insertNode(_el$27, _el$32);
289
- _$setProp(_el$27, "flexDirection", "row");
290
- _$insertNode(_el$28, _$createTextNode(` 5h `));
291
- _$insert(_el$30, () => barFill(s.fiveHour));
292
- _$insert(_el$31, () => barEmpty(s.fiveHour));
293
- _$insertNode(_el$32, _$createTextNode(` 0%`));
294
- _$effect((_p$) => {
295
- var _v$5 = st("text"), _v$6 = st("text");
296
- _v$5 !== _p$.e && (_p$.e = _$setProp(_el$30, "style", _v$5, _p$.e));
297
- _v$6 !== _p$.t && (_p$.t = _$setProp(_el$31, "style", _v$6, _p$.t));
298
- return _p$;
299
- }, {
300
- e: void 0,
301
- t: void 0
302
- });
303
- return _el$27;
304
- })());
305
- nodes.push((() => {
306
- var _el$34 = _$createElement("box"), _el$35 = _$createElement("text"), _el$37 = _$createElement("text"), _el$38 = _$createElement("text"), _el$39 = _$createElement("text");
307
- _$insertNode(_el$34, _el$35);
308
- _$insertNode(_el$34, _el$37);
309
- _$insertNode(_el$34, _el$38);
310
- _$insertNode(_el$34, _el$39);
311
- _$setProp(_el$34, "flexDirection", "row");
312
- _$insertNode(_el$35, _$createTextNode(` 1w `));
313
- _$insert(_el$37, () => barFill(s.weekly));
314
- _$insert(_el$38, () => barEmpty(s.weekly));
315
- _$insertNode(_el$39, _$createTextNode(` 0%`));
316
- _$effect((_p$) => {
317
- var _v$7 = st("text"), _v$8 = st("text");
318
- _v$7 !== _p$.e && (_p$.e = _$setProp(_el$37, "style", _v$7, _p$.e));
319
- _v$8 !== _p$.t && (_p$.t = _$setProp(_el$38, "style", _v$8, _p$.t));
320
- return _p$;
321
- }, {
322
- e: void 0,
323
- t: void 0
324
- });
325
- return _el$34;
326
- })());
327
364
  }
328
365
  } else {
329
366
  nodes.push((() => {
330
- var _el$41 = _$createElement("text");
331
- _$insertNode(_el$41, _$createTextNode(`usage-coach: ...`));
332
- return _el$41;
367
+ var _el$47 = _$createElement("text");
368
+ _$insertNode(_el$47, _$createTextNode(`usage-coach: ...`));
369
+ return _el$47;
333
370
  })());
334
371
  }
335
372
  if (h && h.active !== false && h.tasks.length > 0) {
336
373
  nodes.push((() => {
337
- var _el$43 = _$createElement("text");
338
- _$insertNode(_el$43, _$createTextNode(` `));
339
- return _el$43;
374
+ var _el$49 = _$createElement("text");
375
+ _$insertNode(_el$49, _$createTextNode(` `));
376
+ return _el$49;
340
377
  })());
341
378
  nodes.push((() => {
342
- var _el$45 = _$createElement("text"), _el$46 = _$createTextNode(`harness: `), _el$47 = _$createTextNode(` `), _el$48 = _$createTextNode(`/`);
343
- _$insertNode(_el$45, _el$46);
344
- _$insertNode(_el$45, _el$47);
345
- _$insertNode(_el$45, _el$48);
346
- _$insert(_el$45, () => h.name, _el$47);
347
- _$insert(_el$45, () => h.current, _el$48);
348
- _$insert(_el$45, () => h.total, null);
349
- _$effect((_$p) => _$setProp(_el$45, "style", st("textMuted"), _$p));
350
- return _el$45;
379
+ var _el$51 = _$createElement("text"), _el$52 = _$createTextNode(`harness: `), _el$53 = _$createTextNode(` `), _el$54 = _$createTextNode(`/`);
380
+ _$insertNode(_el$51, _el$52);
381
+ _$insertNode(_el$51, _el$53);
382
+ _$insertNode(_el$51, _el$54);
383
+ _$insert(_el$51, () => h.name, _el$53);
384
+ _$insert(_el$51, () => h.current, _el$54);
385
+ _$insert(_el$51, () => h.total, null);
386
+ _$effect((_$p) => _$setProp(_el$51, "style", st("textMuted"), _$p));
387
+ return _el$51;
351
388
  })());
352
389
  for (const t of h.tasks) {
353
390
  const sKey = statusKey[t.status] ?? "text";
@@ -357,18 +394,18 @@ function initializeTui(api, disposeRoot) {
357
394
  const elapsed = t.startedAt ? Math.max(0, Math.round((Date.now() - new Date(t.startedAt).getTime()) / 1e3)) : 0;
358
395
  const elapsedStr = t.status === "completed" || t.status === "failed" ? "" : elapsed > 0 ? ` ${elapsed}s` : "";
359
396
  nodes.push((() => {
360
- var _el$49 = _$createElement("text"), _el$50 = _$createTextNode(` \u25CF `), _el$51 = _$createTextNode(` `), _el$52 = _$createTextNode(` `);
361
- _$insertNode(_el$49, _el$50);
362
- _$insertNode(_el$49, _el$51);
363
- _$insertNode(_el$49, _el$52);
364
- _$insert(_el$49, () => t.id, _el$51);
365
- _$insert(_el$49, mdl, _el$51);
366
- _$insert(_el$49, lbl, _el$52);
367
- _$insert(_el$49, rev, _el$52);
368
- _$insert(_el$49, elapsedStr, _el$52);
369
- _$insert(_el$49, () => t.title, null);
370
- _$effect((_$p) => _$setProp(_el$49, "style", st(sKey), _$p));
371
- return _el$49;
397
+ var _el$55 = _$createElement("text"), _el$56 = _$createTextNode(` \u25CF `), _el$57 = _$createTextNode(` `), _el$58 = _$createTextNode(` `);
398
+ _$insertNode(_el$55, _el$56);
399
+ _$insertNode(_el$55, _el$57);
400
+ _$insertNode(_el$55, _el$58);
401
+ _$insert(_el$55, () => t.id, _el$57);
402
+ _$insert(_el$55, mdl, _el$57);
403
+ _$insert(_el$55, lbl, _el$58);
404
+ _$insert(_el$55, rev, _el$58);
405
+ _$insert(_el$55, elapsedStr, _el$58);
406
+ _$insert(_el$55, () => t.title, null);
407
+ _$effect((_$p) => _$setProp(_el$55, "style", st(sKey), _$p));
408
+ return _el$55;
372
409
  })());
373
410
  const pv = t.model ? (t.model.split("/")[0] ?? "").split("-")[0] : "";
374
411
  const provCoach = pv ? s?.providers?.find((p) => p.id === pv || pv && p.id.startsWith(pv) || pv && pv.startsWith(p.id)) : s?.providers?.[0];
@@ -376,35 +413,35 @@ function initializeTui(api, disposeRoot) {
376
413
  const pct = rawPct < 0 ? 0 : rawPct;
377
414
  const pctLabel = rawPct < 0 ? "n/a" : `${rawPct}%`;
378
415
  nodes.push((() => {
379
- var _el$53 = _$createElement("box"), _el$54 = _$createElement("text"), _el$56 = _$createElement("text"), _el$57 = _$createElement("text"), _el$58 = _$createElement("text"), _el$59 = _$createTextNode(` `);
380
- _$insertNode(_el$53, _el$54);
381
- _$insertNode(_el$53, _el$56);
382
- _$insertNode(_el$53, _el$57);
383
- _$insertNode(_el$53, _el$58);
384
- _$setProp(_el$53, "flexDirection", "row");
385
- _$insertNode(_el$54, _$createTextNode(` 5h `));
386
- _$insert(_el$56, () => barFill(pct));
387
- _$insert(_el$57, () => barEmpty(pct));
388
- _$insertNode(_el$58, _el$59);
389
- _$insert(_el$58, pctLabel, null);
416
+ var _el$59 = _$createElement("box"), _el$60 = _$createElement("text"), _el$62 = _$createElement("text"), _el$63 = _$createElement("text"), _el$64 = _$createElement("text"), _el$65 = _$createTextNode(` `);
417
+ _$insertNode(_el$59, _el$60);
418
+ _$insertNode(_el$59, _el$62);
419
+ _$insertNode(_el$59, _el$63);
420
+ _$insertNode(_el$59, _el$64);
421
+ _$setProp(_el$59, "flexDirection", "row");
422
+ _$insertNode(_el$60, _$createTextNode(` 5h `));
423
+ _$insert(_el$62, () => barFill(pct));
424
+ _$insert(_el$63, () => barEmpty(pct));
425
+ _$insertNode(_el$64, _el$65);
426
+ _$insert(_el$64, pctLabel, null);
390
427
  _$effect((_p$) => {
391
- var _v$9 = st("text"), _v$0 = st("text");
392
- _v$9 !== _p$.e && (_p$.e = _$setProp(_el$56, "style", _v$9, _p$.e));
393
- _v$0 !== _p$.t && (_p$.t = _$setProp(_el$57, "style", _v$0, _p$.t));
428
+ var _v$11 = st("text"), _v$12 = st("text");
429
+ _v$11 !== _p$.e && (_p$.e = _$setProp(_el$62, "style", _v$11, _p$.e));
430
+ _v$12 !== _p$.t && (_p$.t = _$setProp(_el$63, "style", _v$12, _p$.t));
394
431
  return _p$;
395
432
  }, {
396
433
  e: void 0,
397
434
  t: void 0
398
435
  });
399
- return _el$53;
436
+ return _el$59;
400
437
  })());
401
438
  }
402
439
  }
403
440
  return (() => {
404
- var _el$60 = _$createElement("box");
405
- _$setProp(_el$60, "flexDirection", "column");
406
- _$insert(_el$60, nodes);
407
- return _el$60;
441
+ var _el$66 = _$createElement("box");
442
+ _$setProp(_el$66, "flexDirection", "column");
443
+ _$insert(_el$66, nodes);
444
+ return _el$66;
408
445
  })();
409
446
  };
410
447
  tlog("registering slots");
@@ -419,9 +456,9 @@ function initializeTui(api, disposeRoot) {
419
456
  } catch (e) {
420
457
  tlog(`sidebar_footer err: ${String(e)}`);
421
458
  result = (() => {
422
- var _el$61 = _$createElement("text");
423
- _$insertNode(_el$61, _$createTextNode(`usage-coach`));
424
- return _el$61;
459
+ var _el$67 = _$createElement("text");
460
+ _$insertNode(_el$67, _$createTextNode(`usage-coach`));
461
+ return _el$67;
425
462
  })();
426
463
  }
427
464
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-usage-coach",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
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",
@@ -58,11 +58,5 @@
58
58
  "tsup": "^8.5",
59
59
  "typescript": "^5",
60
60
  "typescript-eslint": "^8.63.0"
61
- },
62
- "dependencies": {
63
- "@ladybugdb/core": "^0.18.0"
64
- },
65
- "trustedDependencies": [
66
- "@ladybugdb/core"
67
- ]
61
+ }
68
62
  }