opencode-usage-coach 0.5.0 → 0.6.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.
- package/README.md +1 -0
- package/dist/index.js +176 -94
- package/dist/tui.js +163 -136
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -155,6 +155,7 @@ 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 |
|
|
158
159
|
|
|
159
160
|
## Agent-mode scoping
|
|
160
161
|
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { mkdirSync
|
|
2
|
+
import { mkdirSync, writeFileSync, appendFileSync, 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,108 +7,188 @@ 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 {
|
|
10
|
+
import { Database, Connection } from "@ladybugdb/core";
|
|
11
11
|
import { join } from "path";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
BASE_DIR = stateDir;
|
|
15
|
-
}
|
|
16
|
-
var nodesFile = () => join(BASE_DIR, "nodes.ndjson");
|
|
17
|
-
var edgesFile = () => join(BASE_DIR, "edges.ndjson");
|
|
18
|
-
function readNdjson(path) {
|
|
12
|
+
import { existsSync, readFileSync } from "fs";
|
|
13
|
+
var QUERY_TIMEOUT_MS = (() => {
|
|
19
14
|
try {
|
|
20
|
-
|
|
21
|
-
return
|
|
15
|
+
const v = Number(process.env.UC_DOMAIN_TIMEOUT_MS);
|
|
16
|
+
return Number.isFinite(v) && v > 0 ? v : 5e3;
|
|
22
17
|
} catch {
|
|
23
|
-
return
|
|
18
|
+
return 5e3;
|
|
24
19
|
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
})();
|
|
21
|
+
var BASE_DIR = "";
|
|
22
|
+
var DB_PATH = "";
|
|
23
|
+
var db = null;
|
|
24
|
+
var conn = null;
|
|
25
|
+
var schemaReady = false;
|
|
26
|
+
var migrated = false;
|
|
27
|
+
function initDomain(stateDir) {
|
|
28
|
+
BASE_DIR = stateDir;
|
|
29
|
+
DB_PATH = join(BASE_DIR, "domain.ladybug");
|
|
30
|
+
db = null;
|
|
31
|
+
conn = null;
|
|
32
|
+
schemaReady = false;
|
|
33
|
+
migrated = false;
|
|
31
34
|
}
|
|
32
35
|
function uid(prefix) {
|
|
33
36
|
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
34
37
|
}
|
|
35
|
-
function
|
|
36
|
-
|
|
38
|
+
function esc(s) {
|
|
39
|
+
return String(s ?? "").replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
40
|
+
}
|
|
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
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function parseNode(r) {
|
|
51
|
+
let props = {};
|
|
37
52
|
try {
|
|
38
|
-
|
|
39
|
-
appendFileSync(nodesFile(), JSON.stringify(full) + "\n");
|
|
53
|
+
if (r.props) props = JSON.parse(r.props);
|
|
40
54
|
} catch {
|
|
41
55
|
}
|
|
42
|
-
return
|
|
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;
|
|
43
90
|
}
|
|
44
|
-
function
|
|
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;
|
|
45
96
|
try {
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
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
|
+
}
|
|
49
115
|
} catch {
|
|
50
116
|
}
|
|
51
117
|
}
|
|
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
|
+
}
|
|
52
130
|
function queryDomain(keywords) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
const
|
|
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
|
+
}
|
|
62
147
|
return { nodes: matched, edges };
|
|
63
148
|
}
|
|
64
|
-
function
|
|
149
|
+
async function _touchNodes(ids) {
|
|
65
150
|
if (ids.size === 0) return;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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);
|
|
79
173
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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 };
|
|
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`);
|
|
98
178
|
}
|
|
179
|
+
return { removed: toRemove.length, kept: kept.length };
|
|
99
180
|
}
|
|
100
181
|
function saveInvestigationResult(keywords, result, source) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
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
|
+
});
|
|
112
192
|
}
|
|
113
193
|
|
|
114
194
|
// src/index.ts
|
|
@@ -130,15 +210,15 @@ function setStateDir(dir) {
|
|
|
130
210
|
var NOOP_HOOKS = {};
|
|
131
211
|
function log(msg) {
|
|
132
212
|
try {
|
|
133
|
-
|
|
213
|
+
appendFileSync(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
134
214
|
`);
|
|
135
215
|
} catch {
|
|
136
216
|
}
|
|
137
217
|
}
|
|
138
218
|
function writeState(c) {
|
|
139
219
|
try {
|
|
140
|
-
|
|
141
|
-
|
|
220
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
221
|
+
writeFileSync(STATE_FILE, JSON.stringify({ ...c, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
142
222
|
} catch {
|
|
143
223
|
}
|
|
144
224
|
}
|
|
@@ -189,9 +269,9 @@ function readHarness(sessionID) {
|
|
|
189
269
|
function writeHarness(sessionID, h) {
|
|
190
270
|
try {
|
|
191
271
|
const f = harnessFile(sessionID);
|
|
192
|
-
|
|
272
|
+
mkdirSync(dirname(f), { recursive: true });
|
|
193
273
|
h.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
194
|
-
|
|
274
|
+
writeFileSync(f, JSON.stringify(h, null, 2));
|
|
195
275
|
} catch {
|
|
196
276
|
}
|
|
197
277
|
}
|
|
@@ -371,6 +451,7 @@ function coach(q, lighter) {
|
|
|
371
451
|
return { decision: "GO", advice: `Comfortable \u2014 weekly ${wk}% \xB7 5h ${h5}% \xB7 monthly ${mo}%. proceed. 5h window ${h5R}.`, weekly: wk, monthly: mo, fiveHour: h5 };
|
|
372
452
|
}
|
|
373
453
|
var agentCache = /* @__PURE__ */ new Map();
|
|
454
|
+
var lastResolvedAgent = "";
|
|
374
455
|
async function resolveAgent(client, sessionID) {
|
|
375
456
|
if (!sessionID) return "";
|
|
376
457
|
const hit = agentCache.get(sessionID);
|
|
@@ -379,6 +460,7 @@ async function resolveAgent(client, sessionID) {
|
|
|
379
460
|
const s = await client.session.get({ path: { id: sessionID } });
|
|
380
461
|
const agent = String(s?.data?.info?.agent ?? s?.data?.agent ?? s?.info?.agent ?? "");
|
|
381
462
|
agentCache.set(sessionID, { agent, ts: Date.now() });
|
|
463
|
+
lastResolvedAgent = agent;
|
|
382
464
|
return agent;
|
|
383
465
|
} catch (e) {
|
|
384
466
|
log(`resolveAgent err: ${String(e)}`);
|
|
@@ -418,7 +500,7 @@ async function UsageCoachPlugin(input) {
|
|
|
418
500
|
const p0 = providers[0];
|
|
419
501
|
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" };
|
|
420
502
|
}
|
|
421
|
-
writeState({ ...last, providers, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
503
|
+
writeState({ ...last, agent: lastResolvedAgent, providers, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
422
504
|
log(`${last.decision} | weekly=${last.weekly}% 5h=${last.fiveHour}% | providers=${providers.length}`);
|
|
423
505
|
} catch (e) {
|
|
424
506
|
log(`refresh-in-then err: ${String(e)}`);
|
|
@@ -446,7 +528,7 @@ async function UsageCoachPlugin(input) {
|
|
|
446
528
|
if (event.type === "session.created" || event.type === "session.idle") refreshBackground();
|
|
447
529
|
if (event.type === "session.idle") {
|
|
448
530
|
try {
|
|
449
|
-
const r = evictStale(WORM_MAX_AGE_DAYS, WORM_MAX_NODES);
|
|
531
|
+
const r = await evictStale(WORM_MAX_AGE_DAYS, WORM_MAX_NODES);
|
|
450
532
|
if (r.removed) log(`evictStale: removed ${r.removed}, kept ${r.kept} (maxAge=${WORM_MAX_AGE_DAYS}d, maxNodes=${WORM_MAX_NODES})`);
|
|
451
533
|
} catch (e) {
|
|
452
534
|
log(`evictStale err: ${String(e)}`);
|
|
@@ -571,8 +653,8 @@ Then: harness_done(). Follow the [usage-coach NEXT] directive each tool returns.
|
|
|
571
653
|
async execute(args, _ctx) {
|
|
572
654
|
const rec = { ts: (/* @__PURE__ */ new Date()).toISOString(), task: args.task, prompt: args.prompt, gradeResult: args.gradeResult, model: args.model, revisions: args.revisions };
|
|
573
655
|
try {
|
|
574
|
-
|
|
575
|
-
|
|
656
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
657
|
+
appendFileSync(failuresFile(), JSON.stringify(rec) + "\n");
|
|
576
658
|
} catch (e) {
|
|
577
659
|
log(`record_failure err: ${String(e)}`);
|
|
578
660
|
}
|
|
@@ -596,7 +678,7 @@ Then: harness_done(). Follow the [usage-coach NEXT] directive each tool returns.
|
|
|
596
678
|
try {
|
|
597
679
|
keywords = extractKeywords(`${args.task} ${args.gradeResult}`);
|
|
598
680
|
if (keywords.length) {
|
|
599
|
-
const { nodes, edges } = queryDomain(keywords);
|
|
681
|
+
const { nodes, edges } = await queryDomain(keywords);
|
|
600
682
|
if (nodes && nodes.length || edges && edges.length) {
|
|
601
683
|
domainEmpty = false;
|
|
602
684
|
domainPrefix = `Known facts from domain DB: ${JSON.stringify({ nodes, edges })}. Use these if relevant.
|
|
@@ -620,7 +702,7 @@ evidence: <file/line or specific quote>`;
|
|
|
620
702
|
const out = await runModel(input.client, cfg.generator, domainPrefix + rcaPrompt, ctx.directory);
|
|
621
703
|
if (domainEmpty && keywords.length) {
|
|
622
704
|
try {
|
|
623
|
-
saveInvestigationResult(keywords, out, "investigate");
|
|
705
|
+
await saveInvestigationResult(keywords, out, "investigate");
|
|
624
706
|
} catch (e) {
|
|
625
707
|
log(`investigate save err: ${String(e)}`);
|
|
626
708
|
}
|
|
@@ -676,8 +758,8 @@ Keep it concrete and actionable.`;
|
|
|
676
758
|
const rule = out;
|
|
677
759
|
try {
|
|
678
760
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
679
|
-
|
|
680
|
-
|
|
761
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
762
|
+
appendFileSync(rulesFile(), `## Rule (${date})
|
|
681
763
|
${rule}
|
|
682
764
|
Origin: ${args.task}
|
|
683
765
|
|
|
@@ -716,7 +798,7 @@ ${rules}
|
|
|
716
798
|
try {
|
|
717
799
|
keywords = extractKeywords(args.prompt);
|
|
718
800
|
if (keywords.length) {
|
|
719
|
-
const { nodes, edges } = queryDomain(keywords);
|
|
801
|
+
const { nodes, edges } = await queryDomain(keywords);
|
|
720
802
|
if (nodes && nodes.length || edges && edges.length) {
|
|
721
803
|
domainEmpty = false;
|
|
722
804
|
prefix = `Known facts from domain DB: ${JSON.stringify({ nodes, edges })}. Use these if relevant.
|
|
@@ -732,7 +814,7 @@ ${rules}
|
|
|
732
814
|
const out = await runModel(input.client, model, prefix + args.prompt, ctx.directory);
|
|
733
815
|
if (domainEmpty && keywords.length) {
|
|
734
816
|
try {
|
|
735
|
-
saveInvestigationResult(keywords, out, "generate");
|
|
817
|
+
await saveInvestigationResult(keywords, out, "generate");
|
|
736
818
|
} catch (e) {
|
|
737
819
|
log(`generate save err: ${String(e)}`);
|
|
738
820
|
}
|
package/dist/tui.js
CHANGED
|
@@ -19,6 +19,7 @@ var STATE_DIR = join(homedir(), ".cache", "opencode-usage-coach");
|
|
|
19
19
|
var STATE_FILE = join(STATE_DIR, "state.json");
|
|
20
20
|
var HARNESS_FILE = join(STATE_DIR, "harness.json");
|
|
21
21
|
var MARKER = join(STATE_DIR, "tui-loaded.txt");
|
|
22
|
+
var HARNESS_AGENT_IDS = (process.env.UC_HARNESS_AGENT ?? "Usage-Coach-Harness").split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
22
23
|
function readState() {
|
|
23
24
|
try {
|
|
24
25
|
if (!existsSync(STATE_FILE)) return null;
|
|
@@ -188,6 +189,16 @@ function initializeTui(api, disposeRoot) {
|
|
|
188
189
|
} catch {
|
|
189
190
|
s = null;
|
|
190
191
|
}
|
|
192
|
+
const isHarness = s?.agent ? HARNESS_AGENT_IDS.includes(s.agent.toLowerCase()) : false;
|
|
193
|
+
if (s?.agent && !isHarness) {
|
|
194
|
+
return (() => {
|
|
195
|
+
var _el$4 = _$createElement("box"), _el$5 = _$createElement("text");
|
|
196
|
+
_$insertNode(_el$4, _el$5);
|
|
197
|
+
_$insertNode(_el$5, _$createTextNode(`usage-coach`));
|
|
198
|
+
_$effect((_$p) => _$setProp(_el$5, "style", st("textMuted"), _$p));
|
|
199
|
+
return _el$4;
|
|
200
|
+
})();
|
|
201
|
+
}
|
|
191
202
|
let h = null;
|
|
192
203
|
try {
|
|
193
204
|
const routeSid = api.route?.current?.params?.sessionID ?? "";
|
|
@@ -205,149 +216,149 @@ function initializeTui(api, disposeRoot) {
|
|
|
205
216
|
if (s) {
|
|
206
217
|
const dKey = s.decision === "GO" ? "success" : s.decision === "THROTTLE" ? "warning" : "error";
|
|
207
218
|
nodes.push((() => {
|
|
208
|
-
var _el$
|
|
209
|
-
_$insertNode(_el$
|
|
210
|
-
_$insertNode(_el$
|
|
211
|
-
_$insert(_el$
|
|
212
|
-
_$effect((_$p) => _$setProp(_el$
|
|
213
|
-
return _el$
|
|
219
|
+
var _el$7 = _$createElement("text"), _el$8 = _$createTextNode(`usage-coach [`), _el$9 = _$createTextNode(`]`);
|
|
220
|
+
_$insertNode(_el$7, _el$8);
|
|
221
|
+
_$insertNode(_el$7, _el$9);
|
|
222
|
+
_$insert(_el$7, () => TAG[s.decision], _el$9);
|
|
223
|
+
_$effect((_$p) => _$setProp(_el$7, "style", st(dKey), _$p));
|
|
224
|
+
return _el$7;
|
|
214
225
|
})());
|
|
215
226
|
if (s.providers && s.providers.length > 0) {
|
|
216
227
|
for (const p of s.providers) {
|
|
217
228
|
nodes.push((() => {
|
|
218
|
-
var _el$
|
|
219
|
-
_$insertNode(_el$
|
|
220
|
-
_$insert(_el$
|
|
221
|
-
_$effect((_$p) => _$setProp(_el$
|
|
222
|
-
return _el$
|
|
229
|
+
var _el$0 = _$createElement("text"), _el$1 = _$createTextNode(` `);
|
|
230
|
+
_$insertNode(_el$0, _el$1);
|
|
231
|
+
_$insert(_el$0, () => p.name, null);
|
|
232
|
+
_$effect((_$p) => _$setProp(_el$0, "style", st("textMuted"), _$p));
|
|
233
|
+
return _el$0;
|
|
223
234
|
})());
|
|
224
235
|
nodes.push((() => {
|
|
225
|
-
var _el$
|
|
226
|
-
_$insertNode(_el$
|
|
227
|
-
_$insertNode(_el$
|
|
228
|
-
_$insertNode(_el$
|
|
229
|
-
_$insertNode(_el$
|
|
230
|
-
_$setProp(_el$
|
|
231
|
-
_$insertNode(_el$
|
|
232
|
-
_$insert(_el$
|
|
233
|
-
_$insert(_el$
|
|
234
|
-
_$insertNode(_el$
|
|
235
|
-
_$insertNode(_el$
|
|
236
|
-
_$insert(_el$
|
|
237
|
-
_$insert(_el$
|
|
236
|
+
var _el$10 = _$createElement("box"), _el$11 = _$createElement("text"), _el$13 = _$createElement("text"), _el$14 = _$createElement("text"), _el$15 = _$createElement("text"), _el$16 = _$createTextNode(` `), _el$17 = _$createTextNode(`% `);
|
|
237
|
+
_$insertNode(_el$10, _el$11);
|
|
238
|
+
_$insertNode(_el$10, _el$13);
|
|
239
|
+
_$insertNode(_el$10, _el$14);
|
|
240
|
+
_$insertNode(_el$10, _el$15);
|
|
241
|
+
_$setProp(_el$10, "flexDirection", "row");
|
|
242
|
+
_$insertNode(_el$11, _$createTextNode(` 5h `));
|
|
243
|
+
_$insert(_el$13, () => barFill(p.fiveHour));
|
|
244
|
+
_$insert(_el$14, () => barEmpty(p.fiveHour));
|
|
245
|
+
_$insertNode(_el$15, _el$16);
|
|
246
|
+
_$insertNode(_el$15, _el$17);
|
|
247
|
+
_$insert(_el$15, () => p.fiveHour, _el$17);
|
|
248
|
+
_$insert(_el$15, () => p.fiveHourReset, null);
|
|
238
249
|
_$effect((_p$) => {
|
|
239
250
|
var _v$ = st("text"), _v$2 = st("text");
|
|
240
|
-
_v$ !== _p$.e && (_p$.e = _$setProp(_el$
|
|
241
|
-
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$
|
|
251
|
+
_v$ !== _p$.e && (_p$.e = _$setProp(_el$13, "style", _v$, _p$.e));
|
|
252
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$14, "style", _v$2, _p$.t));
|
|
242
253
|
return _p$;
|
|
243
254
|
}, {
|
|
244
255
|
e: void 0,
|
|
245
256
|
t: void 0
|
|
246
257
|
});
|
|
247
|
-
return _el$
|
|
258
|
+
return _el$10;
|
|
248
259
|
})());
|
|
249
260
|
nodes.push((() => {
|
|
250
|
-
var _el$
|
|
251
|
-
_$insertNode(_el$
|
|
252
|
-
_$insertNode(_el$
|
|
253
|
-
_$insertNode(_el$
|
|
254
|
-
_$insertNode(_el$
|
|
255
|
-
_$setProp(_el$
|
|
256
|
-
_$insertNode(_el$
|
|
257
|
-
_$insert(_el$
|
|
258
|
-
_$insert(_el$
|
|
259
|
-
_$insertNode(_el$
|
|
260
|
-
_$insertNode(_el$
|
|
261
|
-
_$insert(_el$
|
|
262
|
-
_$insert(_el$
|
|
261
|
+
var _el$18 = _$createElement("box"), _el$19 = _$createElement("text"), _el$21 = _$createElement("text"), _el$22 = _$createElement("text"), _el$23 = _$createElement("text"), _el$24 = _$createTextNode(` `), _el$25 = _$createTextNode(`% `);
|
|
262
|
+
_$insertNode(_el$18, _el$19);
|
|
263
|
+
_$insertNode(_el$18, _el$21);
|
|
264
|
+
_$insertNode(_el$18, _el$22);
|
|
265
|
+
_$insertNode(_el$18, _el$23);
|
|
266
|
+
_$setProp(_el$18, "flexDirection", "row");
|
|
267
|
+
_$insertNode(_el$19, _$createTextNode(` 1w `));
|
|
268
|
+
_$insert(_el$21, () => barFill(p.weekly));
|
|
269
|
+
_$insert(_el$22, () => barEmpty(p.weekly));
|
|
270
|
+
_$insertNode(_el$23, _el$24);
|
|
271
|
+
_$insertNode(_el$23, _el$25);
|
|
272
|
+
_$insert(_el$23, () => p.weekly, _el$25);
|
|
273
|
+
_$insert(_el$23, () => p.weeklyReset, null);
|
|
263
274
|
_$effect((_p$) => {
|
|
264
275
|
var _v$3 = st("text"), _v$4 = st("text");
|
|
265
|
-
_v$3 !== _p$.e && (_p$.e = _$setProp(_el$
|
|
266
|
-
_v$4 !== _p$.t && (_p$.t = _$setProp(_el$
|
|
276
|
+
_v$3 !== _p$.e && (_p$.e = _$setProp(_el$21, "style", _v$3, _p$.e));
|
|
277
|
+
_v$4 !== _p$.t && (_p$.t = _$setProp(_el$22, "style", _v$4, _p$.t));
|
|
267
278
|
return _p$;
|
|
268
279
|
}, {
|
|
269
280
|
e: void 0,
|
|
270
281
|
t: void 0
|
|
271
282
|
});
|
|
272
|
-
return _el$
|
|
283
|
+
return _el$18;
|
|
273
284
|
})());
|
|
274
285
|
nodes.push((() => {
|
|
275
|
-
var _el$
|
|
276
|
-
_$insertNode(_el$
|
|
277
|
-
_$insert(_el$
|
|
278
|
-
_$effect((_$p) => _$setProp(_el$
|
|
279
|
-
return _el$
|
|
286
|
+
var _el$26 = _$createElement("text"), _el$27 = _$createTextNode(` -> `);
|
|
287
|
+
_$insertNode(_el$26, _el$27);
|
|
288
|
+
_$insert(_el$26, () => p.advice, null);
|
|
289
|
+
_$effect((_$p) => _$setProp(_el$26, "style", st(dKey), _$p));
|
|
290
|
+
return _el$26;
|
|
280
291
|
})());
|
|
281
292
|
}
|
|
282
293
|
} else {
|
|
283
294
|
nodes.push((() => {
|
|
284
|
-
var _el$
|
|
285
|
-
_$insertNode(_el$
|
|
286
|
-
_$insertNode(_el$
|
|
287
|
-
_$insertNode(_el$
|
|
288
|
-
_$insertNode(_el$
|
|
289
|
-
_$setProp(_el$
|
|
290
|
-
_$insertNode(_el$
|
|
291
|
-
_$insert(_el$
|
|
292
|
-
_$insert(_el$
|
|
293
|
-
_$insertNode(_el$
|
|
295
|
+
var _el$30 = _$createElement("box"), _el$31 = _$createElement("text"), _el$33 = _$createElement("text"), _el$34 = _$createElement("text"), _el$35 = _$createElement("text");
|
|
296
|
+
_$insertNode(_el$30, _el$31);
|
|
297
|
+
_$insertNode(_el$30, _el$33);
|
|
298
|
+
_$insertNode(_el$30, _el$34);
|
|
299
|
+
_$insertNode(_el$30, _el$35);
|
|
300
|
+
_$setProp(_el$30, "flexDirection", "row");
|
|
301
|
+
_$insertNode(_el$31, _$createTextNode(` 5h `));
|
|
302
|
+
_$insert(_el$33, () => barFill(s.fiveHour));
|
|
303
|
+
_$insert(_el$34, () => barEmpty(s.fiveHour));
|
|
304
|
+
_$insertNode(_el$35, _$createTextNode(` 0%`));
|
|
294
305
|
_$effect((_p$) => {
|
|
295
306
|
var _v$5 = st("text"), _v$6 = st("text");
|
|
296
|
-
_v$5 !== _p$.e && (_p$.e = _$setProp(_el$
|
|
297
|
-
_v$6 !== _p$.t && (_p$.t = _$setProp(_el$
|
|
307
|
+
_v$5 !== _p$.e && (_p$.e = _$setProp(_el$33, "style", _v$5, _p$.e));
|
|
308
|
+
_v$6 !== _p$.t && (_p$.t = _$setProp(_el$34, "style", _v$6, _p$.t));
|
|
298
309
|
return _p$;
|
|
299
310
|
}, {
|
|
300
311
|
e: void 0,
|
|
301
312
|
t: void 0
|
|
302
313
|
});
|
|
303
|
-
return _el$
|
|
314
|
+
return _el$30;
|
|
304
315
|
})());
|
|
305
316
|
nodes.push((() => {
|
|
306
|
-
var _el$
|
|
307
|
-
_$insertNode(_el$
|
|
308
|
-
_$insertNode(_el$
|
|
309
|
-
_$insertNode(_el$
|
|
310
|
-
_$insertNode(_el$
|
|
311
|
-
_$setProp(_el$
|
|
312
|
-
_$insertNode(_el$
|
|
313
|
-
_$insert(_el$
|
|
314
|
-
_$insert(_el$
|
|
315
|
-
_$insertNode(_el$
|
|
317
|
+
var _el$37 = _$createElement("box"), _el$38 = _$createElement("text"), _el$40 = _$createElement("text"), _el$41 = _$createElement("text"), _el$42 = _$createElement("text");
|
|
318
|
+
_$insertNode(_el$37, _el$38);
|
|
319
|
+
_$insertNode(_el$37, _el$40);
|
|
320
|
+
_$insertNode(_el$37, _el$41);
|
|
321
|
+
_$insertNode(_el$37, _el$42);
|
|
322
|
+
_$setProp(_el$37, "flexDirection", "row");
|
|
323
|
+
_$insertNode(_el$38, _$createTextNode(` 1w `));
|
|
324
|
+
_$insert(_el$40, () => barFill(s.weekly));
|
|
325
|
+
_$insert(_el$41, () => barEmpty(s.weekly));
|
|
326
|
+
_$insertNode(_el$42, _$createTextNode(` 0%`));
|
|
316
327
|
_$effect((_p$) => {
|
|
317
328
|
var _v$7 = st("text"), _v$8 = st("text");
|
|
318
|
-
_v$7 !== _p$.e && (_p$.e = _$setProp(_el$
|
|
319
|
-
_v$8 !== _p$.t && (_p$.t = _$setProp(_el$
|
|
329
|
+
_v$7 !== _p$.e && (_p$.e = _$setProp(_el$40, "style", _v$7, _p$.e));
|
|
330
|
+
_v$8 !== _p$.t && (_p$.t = _$setProp(_el$41, "style", _v$8, _p$.t));
|
|
320
331
|
return _p$;
|
|
321
332
|
}, {
|
|
322
333
|
e: void 0,
|
|
323
334
|
t: void 0
|
|
324
335
|
});
|
|
325
|
-
return _el$
|
|
336
|
+
return _el$37;
|
|
326
337
|
})());
|
|
327
338
|
}
|
|
328
339
|
} else {
|
|
329
340
|
nodes.push((() => {
|
|
330
|
-
var _el$
|
|
331
|
-
_$insertNode(_el$
|
|
332
|
-
return _el$
|
|
341
|
+
var _el$44 = _$createElement("text");
|
|
342
|
+
_$insertNode(_el$44, _$createTextNode(`usage-coach: ...`));
|
|
343
|
+
return _el$44;
|
|
333
344
|
})());
|
|
334
345
|
}
|
|
335
346
|
if (h && h.active !== false && h.tasks.length > 0) {
|
|
336
347
|
nodes.push((() => {
|
|
337
|
-
var _el$
|
|
338
|
-
_$insertNode(_el$
|
|
339
|
-
return _el$
|
|
348
|
+
var _el$46 = _$createElement("text");
|
|
349
|
+
_$insertNode(_el$46, _$createTextNode(` `));
|
|
350
|
+
return _el$46;
|
|
340
351
|
})());
|
|
341
352
|
nodes.push((() => {
|
|
342
|
-
var _el$
|
|
343
|
-
_$insertNode(_el$
|
|
344
|
-
_$insertNode(_el$
|
|
345
|
-
_$insertNode(_el$
|
|
346
|
-
_$insert(_el$
|
|
347
|
-
_$insert(_el$
|
|
348
|
-
_$insert(_el$
|
|
349
|
-
_$effect((_$p) => _$setProp(_el$
|
|
350
|
-
return _el$
|
|
353
|
+
var _el$48 = _$createElement("text"), _el$49 = _$createTextNode(`harness: `), _el$50 = _$createTextNode(` `), _el$51 = _$createTextNode(`/`);
|
|
354
|
+
_$insertNode(_el$48, _el$49);
|
|
355
|
+
_$insertNode(_el$48, _el$50);
|
|
356
|
+
_$insertNode(_el$48, _el$51);
|
|
357
|
+
_$insert(_el$48, () => h.name, _el$50);
|
|
358
|
+
_$insert(_el$48, () => h.current, _el$51);
|
|
359
|
+
_$insert(_el$48, () => h.total, null);
|
|
360
|
+
_$effect((_$p) => _$setProp(_el$48, "style", st("textMuted"), _$p));
|
|
361
|
+
return _el$48;
|
|
351
362
|
})());
|
|
352
363
|
for (const t of h.tasks) {
|
|
353
364
|
const sKey = statusKey[t.status] ?? "text";
|
|
@@ -357,54 +368,70 @@ function initializeTui(api, disposeRoot) {
|
|
|
357
368
|
const elapsed = t.startedAt ? Math.max(0, Math.round((Date.now() - new Date(t.startedAt).getTime()) / 1e3)) : 0;
|
|
358
369
|
const elapsedStr = t.status === "completed" || t.status === "failed" ? "" : elapsed > 0 ? ` ${elapsed}s` : "";
|
|
359
370
|
nodes.push((() => {
|
|
360
|
-
var _el$
|
|
361
|
-
_$insertNode(_el$
|
|
362
|
-
_$insertNode(_el$
|
|
363
|
-
_$insertNode(_el$
|
|
364
|
-
_$insert(_el$
|
|
365
|
-
_$insert(_el$
|
|
366
|
-
_$insert(_el$
|
|
367
|
-
_$insert(_el$
|
|
368
|
-
_$insert(_el$
|
|
369
|
-
_$insert(_el$
|
|
370
|
-
_$effect((_$p) => _$setProp(_el$
|
|
371
|
-
return _el$
|
|
372
|
-
})());
|
|
373
|
-
const pv = t.model ? (t.model.split("/")[0] ?? "").split("-")[0] : "";
|
|
374
|
-
const provCoach = pv ? s?.providers?.find((p) => p.id === pv || pv && p.id.startsWith(pv) || pv && pv.startsWith(p.id)) : s?.providers?.[0];
|
|
375
|
-
const rawPct = provCoach?.fiveHour ?? s?.fiveHour ?? -1;
|
|
376
|
-
const pct = rawPct < 0 ? 0 : rawPct;
|
|
377
|
-
const pctLabel = rawPct < 0 ? "n/a" : `${rawPct}%`;
|
|
378
|
-
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);
|
|
390
|
-
_$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));
|
|
394
|
-
return _p$;
|
|
395
|
-
}, {
|
|
396
|
-
e: void 0,
|
|
397
|
-
t: void 0
|
|
398
|
-
});
|
|
399
|
-
return _el$53;
|
|
371
|
+
var _el$52 = _$createElement("text"), _el$53 = _$createTextNode(` \u25CF `), _el$54 = _$createTextNode(` `), _el$55 = _$createTextNode(` `);
|
|
372
|
+
_$insertNode(_el$52, _el$53);
|
|
373
|
+
_$insertNode(_el$52, _el$54);
|
|
374
|
+
_$insertNode(_el$52, _el$55);
|
|
375
|
+
_$insert(_el$52, () => t.id, _el$54);
|
|
376
|
+
_$insert(_el$52, mdl, _el$54);
|
|
377
|
+
_$insert(_el$52, lbl, _el$55);
|
|
378
|
+
_$insert(_el$52, rev, _el$55);
|
|
379
|
+
_$insert(_el$52, elapsedStr, _el$55);
|
|
380
|
+
_$insert(_el$52, () => t.title, null);
|
|
381
|
+
_$effect((_$p) => _$setProp(_el$52, "style", st(sKey), _$p));
|
|
382
|
+
return _el$52;
|
|
400
383
|
})());
|
|
384
|
+
if (t.model) {
|
|
385
|
+
const prefix = t.model.split("/")[0] ?? "";
|
|
386
|
+
if (prefix === "opencode") {
|
|
387
|
+
nodes.push((() => {
|
|
388
|
+
var _el$56 = _$createElement("box"), _el$57 = _$createElement("text"), _el$59 = _$createElement("text");
|
|
389
|
+
_$insertNode(_el$56, _el$57);
|
|
390
|
+
_$insertNode(_el$56, _el$59);
|
|
391
|
+
_$setProp(_el$56, "flexDirection", "row");
|
|
392
|
+
_$insertNode(_el$57, _$createTextNode(` `));
|
|
393
|
+
_$insertNode(_el$59, _$createTextNode(`free`));
|
|
394
|
+
_$effect((_$p) => _$setProp(_el$59, "style", st("success"), _$p));
|
|
395
|
+
return _el$56;
|
|
396
|
+
})());
|
|
397
|
+
} else {
|
|
398
|
+
const provCoach = s?.providers?.find((p) => p.id === prefix || p.id.startsWith(prefix) || prefix.startsWith(p.id));
|
|
399
|
+
const rawPct = provCoach?.fiveHour ?? -1;
|
|
400
|
+
if (rawPct >= 0) {
|
|
401
|
+
nodes.push((() => {
|
|
402
|
+
var _el$61 = _$createElement("box"), _el$62 = _$createElement("text"), _el$64 = _$createElement("text"), _el$65 = _$createElement("text"), _el$66 = _$createElement("text"), _el$67 = _$createTextNode(` `), _el$68 = _$createTextNode(`%`);
|
|
403
|
+
_$insertNode(_el$61, _el$62);
|
|
404
|
+
_$insertNode(_el$61, _el$64);
|
|
405
|
+
_$insertNode(_el$61, _el$65);
|
|
406
|
+
_$insertNode(_el$61, _el$66);
|
|
407
|
+
_$setProp(_el$61, "flexDirection", "row");
|
|
408
|
+
_$insertNode(_el$62, _$createTextNode(` 5h `));
|
|
409
|
+
_$insert(_el$64, () => barFill(rawPct));
|
|
410
|
+
_$insert(_el$65, () => barEmpty(rawPct));
|
|
411
|
+
_$insertNode(_el$66, _el$67);
|
|
412
|
+
_$insertNode(_el$66, _el$68);
|
|
413
|
+
_$insert(_el$66, rawPct, _el$68);
|
|
414
|
+
_$effect((_p$) => {
|
|
415
|
+
var _v$9 = st("text"), _v$0 = st("text");
|
|
416
|
+
_v$9 !== _p$.e && (_p$.e = _$setProp(_el$64, "style", _v$9, _p$.e));
|
|
417
|
+
_v$0 !== _p$.t && (_p$.t = _$setProp(_el$65, "style", _v$0, _p$.t));
|
|
418
|
+
return _p$;
|
|
419
|
+
}, {
|
|
420
|
+
e: void 0,
|
|
421
|
+
t: void 0
|
|
422
|
+
});
|
|
423
|
+
return _el$61;
|
|
424
|
+
})());
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
401
428
|
}
|
|
402
429
|
}
|
|
403
430
|
return (() => {
|
|
404
|
-
var _el$
|
|
405
|
-
_$setProp(_el$
|
|
406
|
-
_$insert(_el$
|
|
407
|
-
return _el$
|
|
431
|
+
var _el$69 = _$createElement("box");
|
|
432
|
+
_$setProp(_el$69, "flexDirection", "column");
|
|
433
|
+
_$insert(_el$69, nodes);
|
|
434
|
+
return _el$69;
|
|
408
435
|
})();
|
|
409
436
|
};
|
|
410
437
|
tlog("registering slots");
|
|
@@ -419,9 +446,9 @@ function initializeTui(api, disposeRoot) {
|
|
|
419
446
|
} catch (e) {
|
|
420
447
|
tlog(`sidebar_footer err: ${String(e)}`);
|
|
421
448
|
result = (() => {
|
|
422
|
-
var _el$
|
|
423
|
-
_$insertNode(_el$
|
|
424
|
-
return _el$
|
|
449
|
+
var _el$70 = _$createElement("text");
|
|
450
|
+
_$insertNode(_el$70, _$createTextNode(`usage-coach`));
|
|
451
|
+
return _el$70;
|
|
425
452
|
})();
|
|
426
453
|
}
|
|
427
454
|
return result;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-usage-coach",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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",
|
|
@@ -58,5 +58,11 @@
|
|
|
58
58
|
"tsup": "^8.5",
|
|
59
59
|
"typescript": "^5",
|
|
60
60
|
"typescript-eslint": "^8.63.0"
|
|
61
|
-
}
|
|
62
|
-
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@ladybugdb/core": "^0.18.0"
|
|
64
|
+
},
|
|
65
|
+
"trustedDependencies": [
|
|
66
|
+
"@ladybugdb/core"
|
|
67
|
+
]
|
|
68
|
+
}
|