opencode-usage-coach 0.1.1 → 0.1.2
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/dist/index.js +92 -16
- package/dist/tui.js +147 -82
- package/package.json +13 -4
package/dist/index.js
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { mkdirSync, writeFileSync, appendFileSync, readFileSync, existsSync } from "fs";
|
|
3
3
|
import { spawn } from "child_process";
|
|
4
|
+
import { createHash } from "crypto";
|
|
4
5
|
import { homedir } from "os";
|
|
5
|
-
import { join } from "path";
|
|
6
|
+
import { join, resolve } from "path";
|
|
6
7
|
import { tool } from "@opencode-ai/plugin";
|
|
7
8
|
var PLUGIN_NAME = "opencode-usage-coach";
|
|
8
|
-
var
|
|
9
|
+
var DEBUG = process.env.UC_DEBUG === "1";
|
|
10
|
+
var TTL_MS = Number(process.env.UC_TTL_MS ?? 6e4);
|
|
11
|
+
var STATE_DIR = join(homedir(), ".cache", "opencode-usage-coach");
|
|
9
12
|
var STATE_FILE = join(STATE_DIR, "state.json");
|
|
10
13
|
var HARNESS_FILE = join(STATE_DIR, "harness.json");
|
|
11
14
|
var LOG_FILE = join(STATE_DIR, "coach.log");
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
function projectStateDir(dir) {
|
|
16
|
+
const abs = resolve(dir || ".");
|
|
17
|
+
const h = createHash("sha1").update(abs).digest("hex").slice(0, 12);
|
|
18
|
+
return join(homedir(), ".cache", "opencode-usage-coach", "projects", h);
|
|
19
|
+
}
|
|
20
|
+
function setStateDir(dir) {
|
|
21
|
+
STATE_DIR = process.env.UC_STATE_DIR ?? projectStateDir(dir);
|
|
22
|
+
STATE_FILE = join(STATE_DIR, "state.json");
|
|
23
|
+
HARNESS_FILE = join(STATE_DIR, "harness.json");
|
|
24
|
+
LOG_FILE = join(STATE_DIR, "coach.log");
|
|
25
|
+
}
|
|
14
26
|
var NOOP_HOOKS = {};
|
|
15
27
|
function log(msg) {
|
|
16
28
|
if (DEBUG) try {
|
|
@@ -63,14 +75,72 @@ function humanRemaining(iso) {
|
|
|
63
75
|
const mins = Math.floor((new Date(iso).getTime() - Date.now()) / 6e4);
|
|
64
76
|
if (mins < 0) return "resets soon";
|
|
65
77
|
if (mins < 60) return `resets in ${mins}m`;
|
|
66
|
-
if (mins < 1440) return `resets in ${Math.floor(mins / 60)}h`;
|
|
67
|
-
return
|
|
78
|
+
if (mins < 1440) return `resets in ${Math.floor(mins / 60)}h ${mins % 60}m`;
|
|
79
|
+
return `${Math.floor(mins / 1440)}d left`;
|
|
68
80
|
} catch {
|
|
69
81
|
return "";
|
|
70
82
|
}
|
|
71
83
|
}
|
|
84
|
+
function captureStdout(args) {
|
|
85
|
+
return new Promise((resolve2) => {
|
|
86
|
+
let out = "";
|
|
87
|
+
let p;
|
|
88
|
+
try {
|
|
89
|
+
p = spawn("codexbar", args, { stdio: ["ignore", "pipe", "ignore"] });
|
|
90
|
+
} catch {
|
|
91
|
+
return resolve2("");
|
|
92
|
+
}
|
|
93
|
+
p.stdout?.on("data", (d) => {
|
|
94
|
+
out += d.toString();
|
|
95
|
+
});
|
|
96
|
+
p.on("error", () => resolve2(""));
|
|
97
|
+
p.on("close", () => resolve2(out));
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async function fetchEnabledProviders() {
|
|
101
|
+
const out = await captureStdout(["config", "providers"]);
|
|
102
|
+
const ids = [];
|
|
103
|
+
for (const line of out.split("\n")) {
|
|
104
|
+
const m = line.match(/^\s*([a-zA-Z0-9_-]+):\s*enabled/);
|
|
105
|
+
if (m) ids.push(m[1]);
|
|
106
|
+
}
|
|
107
|
+
return ids;
|
|
108
|
+
}
|
|
109
|
+
function providerAdvice(h5, wk) {
|
|
110
|
+
const S5H = STOP_5H, SWK = STOP_WK, T5H = THR_5H, TWK = THR_WK;
|
|
111
|
+
if (h5 >= S5H || wk >= SWK) return "STOP \u2014 finish current only";
|
|
112
|
+
if (h5 >= T5H && wk >= TWK) return "small tasks only \u2014 big ones will hit both limits";
|
|
113
|
+
if (h5 >= T5H) return "small tasks only \u2014 5h window nearly full, big tasks after reset";
|
|
114
|
+
if (wk >= TWK) return "small tasks only \u2014 big ones will strain late-week";
|
|
115
|
+
if (h5 >= 50 || wk >= 50) return "moderate tasks OK \u2014 save big ones for headroom";
|
|
116
|
+
return "big tasks OK \u2014 short & long limits comfortable";
|
|
117
|
+
}
|
|
118
|
+
async function fetchProvidersCoach() {
|
|
119
|
+
const ids = await fetchEnabledProviders();
|
|
120
|
+
const results = await Promise.all(ids.map(async (id) => {
|
|
121
|
+
try {
|
|
122
|
+
const out = await captureStdout(["usage", "--provider", id, "--json"]);
|
|
123
|
+
const u = JSON.parse(out)[0]?.usage;
|
|
124
|
+
if (!u) return null;
|
|
125
|
+
const h5 = Math.round(u.tertiary?.usedPercent ?? 0);
|
|
126
|
+
const wk = Math.round(u.primary?.usedPercent ?? 0);
|
|
127
|
+
return {
|
|
128
|
+
id,
|
|
129
|
+
name: id,
|
|
130
|
+
fiveHour: h5,
|
|
131
|
+
weekly: wk,
|
|
132
|
+
fiveHourReset: humanRemaining(u.tertiary?.resetsAt),
|
|
133
|
+
weeklyReset: humanRemaining(u.primary?.resetsAt),
|
|
134
|
+
advice: providerAdvice(h5, wk)
|
|
135
|
+
};
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}));
|
|
140
|
+
return results.filter(Boolean);
|
|
141
|
+
}
|
|
72
142
|
function fetchQuota() {
|
|
73
|
-
return new Promise((
|
|
143
|
+
return new Promise((resolve2) => {
|
|
74
144
|
let out = "";
|
|
75
145
|
let p;
|
|
76
146
|
try {
|
|
@@ -78,21 +148,21 @@ function fetchQuota() {
|
|
|
78
148
|
stdio: ["ignore", "pipe", "ignore"]
|
|
79
149
|
});
|
|
80
150
|
} catch {
|
|
81
|
-
return
|
|
151
|
+
return resolve2(null);
|
|
82
152
|
}
|
|
83
153
|
p.stdout?.on("data", (d) => {
|
|
84
154
|
out += d.toString();
|
|
85
155
|
});
|
|
86
|
-
p.on("error", () =>
|
|
156
|
+
p.on("error", () => resolve2(null));
|
|
87
157
|
p.on("close", () => {
|
|
88
158
|
try {
|
|
89
159
|
const text = out.trim();
|
|
90
|
-
if (!text || text === "[]") return
|
|
160
|
+
if (!text || text === "[]") return resolve2(null);
|
|
91
161
|
const u = JSON.parse(text)[0]?.usage;
|
|
92
|
-
if (!u) return
|
|
93
|
-
|
|
162
|
+
if (!u) return resolve2(null);
|
|
163
|
+
resolve2({ weekly: u.primary ?? { usedPercent: 0 }, monthly: u.secondary ?? { usedPercent: 0 }, fiveHour: u.tertiary ?? { usedPercent: 0 } });
|
|
94
164
|
} catch {
|
|
95
|
-
|
|
165
|
+
resolve2(null);
|
|
96
166
|
}
|
|
97
167
|
});
|
|
98
168
|
});
|
|
@@ -113,6 +183,7 @@ function coach(q) {
|
|
|
113
183
|
var LOADING = { decision: "GO", advice: "quota loading\u2026", weekly: -1, monthly: -1, fiveHour: -1 };
|
|
114
184
|
async function UsageCoachPlugin(input) {
|
|
115
185
|
try {
|
|
186
|
+
setStateDir(input.directory);
|
|
116
187
|
let last = null;
|
|
117
188
|
let lastFetchedAt = 0;
|
|
118
189
|
let refreshing = false;
|
|
@@ -121,12 +192,17 @@ async function UsageCoachPlugin(input) {
|
|
|
121
192
|
if (refreshing) return;
|
|
122
193
|
if (last && Date.now() - lastFetchedAt < TTL_MS) return;
|
|
123
194
|
refreshing = true;
|
|
124
|
-
fetchQuota().then((q) => {
|
|
195
|
+
fetchQuota().then(async (q) => {
|
|
125
196
|
try {
|
|
126
197
|
last = coach(q);
|
|
127
198
|
lastFetchedAt = Date.now();
|
|
128
|
-
|
|
129
|
-
|
|
199
|
+
let providers = [];
|
|
200
|
+
try {
|
|
201
|
+
providers = await fetchProvidersCoach();
|
|
202
|
+
} catch {
|
|
203
|
+
}
|
|
204
|
+
writeState({ ...last, providers, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
205
|
+
log(`${last.decision} | weekly=${last.weekly}% 5h=${last.fiveHour}% | providers=${providers.length}`);
|
|
130
206
|
} catch (e) {
|
|
131
207
|
log(`refresh-in-then err: ${String(e)}`);
|
|
132
208
|
}
|
package/dist/tui.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
// src/tui.tsx
|
|
2
2
|
import { setProp as _$setProp } from "@opentui/solid";
|
|
3
|
+
import { effect as _$effect } from "@opentui/solid";
|
|
3
4
|
import { insert as _$insert } from "@opentui/solid";
|
|
4
5
|
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
5
6
|
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
6
7
|
import { createElement as _$createElement } from "@opentui/solid";
|
|
7
8
|
import { readFileSync, existsSync, writeFileSync, mkdirSync } from "fs";
|
|
9
|
+
import { createHash } from "crypto";
|
|
8
10
|
import { homedir } from "os";
|
|
9
|
-
import { join } from "path";
|
|
11
|
+
import { join, resolve } from "path";
|
|
10
12
|
import { createRoot, createSignal, onCleanup } from "solid-js";
|
|
11
|
-
|
|
13
|
+
function projectStateDir(dir) {
|
|
14
|
+
const abs = resolve(dir || ".");
|
|
15
|
+
const h = createHash("sha1").update(abs).digest("hex").slice(0, 12);
|
|
16
|
+
return join(homedir(), ".cache", "opencode-usage-coach", "projects", h);
|
|
17
|
+
}
|
|
18
|
+
var STATE_DIR = join(homedir(), ".cache", "opencode-usage-coach");
|
|
12
19
|
var STATE_FILE = join(STATE_DIR, "state.json");
|
|
13
20
|
var HARNESS_FILE = join(STATE_DIR, "harness.json");
|
|
14
21
|
var MARKER = join(STATE_DIR, "tui-loaded.txt");
|
|
@@ -50,11 +57,15 @@ function short(s, n) {
|
|
|
50
57
|
return s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
|
|
51
58
|
}
|
|
52
59
|
function initializeTui(api, disposeRoot) {
|
|
60
|
+
STATE_DIR = process.env.UC_STATE_DIR ?? projectStateDir(api.state.path.directory);
|
|
61
|
+
STATE_FILE = join(STATE_DIR, "state.json");
|
|
62
|
+
HARNESS_FILE = join(STATE_DIR, "harness.json");
|
|
63
|
+
MARKER = join(STATE_DIR, "tui-loaded.txt");
|
|
53
64
|
try {
|
|
54
65
|
mkdirSync(STATE_DIR, {
|
|
55
66
|
recursive: true
|
|
56
67
|
});
|
|
57
|
-
writeFileSync(MARKER, `loaded ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
68
|
+
writeFileSync(MARKER, `loaded ${(/* @__PURE__ */ new Date()).toISOString()} @ ${api.state.path.directory}`);
|
|
58
69
|
} catch {
|
|
59
70
|
}
|
|
60
71
|
const [getState, setState] = createSignal(readState());
|
|
@@ -68,16 +79,20 @@ function initializeTui(api, disposeRoot) {
|
|
|
68
79
|
}, 3e3);
|
|
69
80
|
onCleanup(() => clearInterval(timer));
|
|
70
81
|
onCleanup(() => disposeRoot());
|
|
71
|
-
const
|
|
72
|
-
generating: "
|
|
73
|
-
grading: "
|
|
74
|
-
revising: "
|
|
75
|
-
completed: "
|
|
76
|
-
failed: "
|
|
77
|
-
timed_out: "
|
|
78
|
-
halted_quota: "
|
|
82
|
+
const statusKey = {
|
|
83
|
+
generating: "info",
|
|
84
|
+
grading: "accent",
|
|
85
|
+
revising: "warning",
|
|
86
|
+
completed: "success",
|
|
87
|
+
failed: "error",
|
|
88
|
+
timed_out: "error",
|
|
89
|
+
halted_quota: "error"
|
|
79
90
|
};
|
|
80
91
|
const panel = (ctx) => {
|
|
92
|
+
const th = ctx.theme?.current ?? {};
|
|
93
|
+
const st = (k) => ({
|
|
94
|
+
fg: th[k]
|
|
95
|
+
});
|
|
81
96
|
let s = null;
|
|
82
97
|
try {
|
|
83
98
|
s = getState();
|
|
@@ -92,98 +107,148 @@ function initializeTui(api, disposeRoot) {
|
|
|
92
107
|
}
|
|
93
108
|
const nodes = [];
|
|
94
109
|
if (s) {
|
|
110
|
+
const dKey = s.decision === "GO" ? "success" : s.decision === "THROTTLE" ? "warning" : "error";
|
|
95
111
|
nodes.push((() => {
|
|
96
112
|
var _el$ = _$createElement("text"), _el$2 = _$createTextNode(`usage-coach [`), _el$3 = _$createTextNode(`]`);
|
|
97
113
|
_$insertNode(_el$, _el$2);
|
|
98
114
|
_$insertNode(_el$, _el$3);
|
|
99
115
|
_$insert(_el$, () => TAG[s.decision], _el$3);
|
|
116
|
+
_$effect((_$p) => _$setProp(_el$, "style", st(dKey), _$p));
|
|
100
117
|
return _el$;
|
|
101
118
|
})());
|
|
119
|
+
if (s.providers && s.providers.length > 0) {
|
|
120
|
+
for (const p of s.providers) {
|
|
121
|
+
nodes.push((() => {
|
|
122
|
+
var _el$4 = _$createElement("text"), _el$5 = _$createTextNode(` `);
|
|
123
|
+
_$insertNode(_el$4, _el$5);
|
|
124
|
+
_$insert(_el$4, () => p.name, null);
|
|
125
|
+
_$effect((_$p) => _$setProp(_el$4, "style", st("textMuted"), _$p));
|
|
126
|
+
return _el$4;
|
|
127
|
+
})());
|
|
128
|
+
const k5 = p.fiveHour >= 70 ? "error" : p.fiveHour >= 40 ? "warning" : "success";
|
|
129
|
+
const kw = p.weekly >= 85 ? "error" : p.weekly >= 60 ? "warning" : "success";
|
|
130
|
+
nodes.push((() => {
|
|
131
|
+
var _el$6 = _$createElement("text"), _el$7 = _$createTextNode(` 5h `), _el$8 = _$createTextNode(` `), _el$9 = _$createTextNode(`% `);
|
|
132
|
+
_$insertNode(_el$6, _el$7);
|
|
133
|
+
_$insertNode(_el$6, _el$8);
|
|
134
|
+
_$insertNode(_el$6, _el$9);
|
|
135
|
+
_$insert(_el$6, () => bar(p.fiveHour), _el$8);
|
|
136
|
+
_$insert(_el$6, () => p.fiveHour, _el$9);
|
|
137
|
+
_$insert(_el$6, () => p.fiveHourReset, null);
|
|
138
|
+
_$effect((_$p) => _$setProp(_el$6, "style", st(k5), _$p));
|
|
139
|
+
return _el$6;
|
|
140
|
+
})());
|
|
141
|
+
nodes.push((() => {
|
|
142
|
+
var _el$0 = _$createElement("text"), _el$1 = _$createTextNode(` wk `), _el$10 = _$createTextNode(` `), _el$11 = _$createTextNode(`% `);
|
|
143
|
+
_$insertNode(_el$0, _el$1);
|
|
144
|
+
_$insertNode(_el$0, _el$10);
|
|
145
|
+
_$insertNode(_el$0, _el$11);
|
|
146
|
+
_$insert(_el$0, () => bar(p.weekly), _el$10);
|
|
147
|
+
_$insert(_el$0, () => p.weekly, _el$11);
|
|
148
|
+
_$insert(_el$0, () => p.weeklyReset, null);
|
|
149
|
+
_$effect((_$p) => _$setProp(_el$0, "style", st(kw), _$p));
|
|
150
|
+
return _el$0;
|
|
151
|
+
})());
|
|
152
|
+
nodes.push((() => {
|
|
153
|
+
var _el$12 = _$createElement("text"), _el$13 = _$createTextNode(` -> `);
|
|
154
|
+
_$insertNode(_el$12, _el$13);
|
|
155
|
+
_$insert(_el$12, () => p.advice, null);
|
|
156
|
+
_$effect((_$p) => _$setProp(_el$12, "style", st(dKey), _$p));
|
|
157
|
+
return _el$12;
|
|
158
|
+
})());
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
nodes.push((() => {
|
|
162
|
+
var _el$16 = _$createElement("text"), _el$17 = _$createTextNode(` 5h `), _el$18 = _$createTextNode(` `), _el$19 = _$createTextNode(`%`);
|
|
163
|
+
_$insertNode(_el$16, _el$17);
|
|
164
|
+
_$insertNode(_el$16, _el$18);
|
|
165
|
+
_$insertNode(_el$16, _el$19);
|
|
166
|
+
_$insert(_el$16, () => bar(s.fiveHour), _el$18);
|
|
167
|
+
_$insert(_el$16, () => s.fiveHour, _el$19);
|
|
168
|
+
return _el$16;
|
|
169
|
+
})());
|
|
170
|
+
nodes.push((() => {
|
|
171
|
+
var _el$20 = _$createElement("text"), _el$21 = _$createTextNode(` wk `), _el$22 = _$createTextNode(` `), _el$23 = _$createTextNode(`%`);
|
|
172
|
+
_$insertNode(_el$20, _el$21);
|
|
173
|
+
_$insertNode(_el$20, _el$22);
|
|
174
|
+
_$insertNode(_el$20, _el$23);
|
|
175
|
+
_$insert(_el$20, () => bar(s.weekly), _el$22);
|
|
176
|
+
_$insert(_el$20, () => s.weekly, _el$23);
|
|
177
|
+
return _el$20;
|
|
178
|
+
})());
|
|
179
|
+
nodes.push((() => {
|
|
180
|
+
var _el$24 = _$createElement("text"), _el$25 = _$createTextNode(` mo `), _el$26 = _$createTextNode(` `), _el$27 = _$createTextNode(`%`);
|
|
181
|
+
_$insertNode(_el$24, _el$25);
|
|
182
|
+
_$insertNode(_el$24, _el$26);
|
|
183
|
+
_$insertNode(_el$24, _el$27);
|
|
184
|
+
_$insert(_el$24, () => bar(s.monthly), _el$26);
|
|
185
|
+
_$insert(_el$24, () => s.monthly, _el$27);
|
|
186
|
+
return _el$24;
|
|
187
|
+
})());
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
102
190
|
nodes.push((() => {
|
|
103
|
-
var _el$
|
|
104
|
-
_$insertNode(_el$
|
|
105
|
-
|
|
106
|
-
_$insertNode(_el$4, _el$7);
|
|
107
|
-
_$insert(_el$4, () => bar(s.fiveHour), _el$6);
|
|
108
|
-
_$insert(_el$4, () => s.fiveHour, _el$7);
|
|
109
|
-
return _el$4;
|
|
110
|
-
})());
|
|
111
|
-
nodes.push((() => {
|
|
112
|
-
var _el$8 = _$createElement("text"), _el$9 = _$createTextNode(` wk `), _el$0 = _$createTextNode(` `), _el$1 = _$createTextNode(`%`);
|
|
113
|
-
_$insertNode(_el$8, _el$9);
|
|
114
|
-
_$insertNode(_el$8, _el$0);
|
|
115
|
-
_$insertNode(_el$8, _el$1);
|
|
116
|
-
_$insert(_el$8, () => bar(s.weekly), _el$0);
|
|
117
|
-
_$insert(_el$8, () => s.weekly, _el$1);
|
|
118
|
-
return _el$8;
|
|
191
|
+
var _el$28 = _$createElement("text");
|
|
192
|
+
_$insertNode(_el$28, _$createTextNode(`usage-coach: ...`));
|
|
193
|
+
return _el$28;
|
|
119
194
|
})());
|
|
195
|
+
}
|
|
196
|
+
if (h && h.active !== false && h.tasks.length > 0) {
|
|
120
197
|
nodes.push((() => {
|
|
121
|
-
var _el$
|
|
122
|
-
_$insertNode(_el$
|
|
123
|
-
|
|
124
|
-
_$insertNode(_el$10, _el$13);
|
|
125
|
-
_$insert(_el$10, () => bar(s.monthly), _el$12);
|
|
126
|
-
_$insert(_el$10, () => s.monthly, _el$13);
|
|
127
|
-
return _el$10;
|
|
198
|
+
var _el$30 = _$createElement("text");
|
|
199
|
+
_$insertNode(_el$30, _$createTextNode(` `));
|
|
200
|
+
return _el$30;
|
|
128
201
|
})());
|
|
129
|
-
} else {
|
|
130
202
|
nodes.push((() => {
|
|
131
|
-
var _el$
|
|
132
|
-
_$insertNode(_el$
|
|
133
|
-
|
|
203
|
+
var _el$32 = _$createElement("text"), _el$33 = _$createTextNode(`harness: `), _el$34 = _$createTextNode(` `), _el$35 = _$createTextNode(`/`);
|
|
204
|
+
_$insertNode(_el$32, _el$33);
|
|
205
|
+
_$insertNode(_el$32, _el$34);
|
|
206
|
+
_$insertNode(_el$32, _el$35);
|
|
207
|
+
_$insert(_el$32, () => h.name, _el$34);
|
|
208
|
+
_$insert(_el$32, () => h.current, _el$35);
|
|
209
|
+
_$insert(_el$32, () => h.total, null);
|
|
210
|
+
_$effect((_$p) => _$setProp(_el$32, "style", st("textMuted"), _$p));
|
|
211
|
+
return _el$32;
|
|
134
212
|
})());
|
|
135
|
-
}
|
|
136
|
-
const hStatus = h && h.tasks.length > 0 ? `${h.name} ${h.current}/${h.total}` : "idle";
|
|
137
|
-
nodes.push((() => {
|
|
138
|
-
var _el$16 = _$createElement("text");
|
|
139
|
-
_$insertNode(_el$16, _$createTextNode(` `));
|
|
140
|
-
return _el$16;
|
|
141
|
-
})());
|
|
142
|
-
nodes.push((() => {
|
|
143
|
-
var _el$18 = _$createElement("text"), _el$19 = _$createTextNode(`harness: `);
|
|
144
|
-
_$insertNode(_el$18, _el$19);
|
|
145
|
-
_$insert(_el$18, hStatus, null);
|
|
146
|
-
return _el$18;
|
|
147
|
-
})());
|
|
148
|
-
if (h && h.tasks.length > 0) {
|
|
149
213
|
for (const t of h.tasks) {
|
|
150
|
-
const
|
|
214
|
+
const sKey = statusKey[t.status] ?? "text";
|
|
151
215
|
const lbl = TLABEL[t.status] ?? t.status;
|
|
152
216
|
const rev = t.revisions > 0 && t.status === "revising" ? `(${t.revisions})` : "";
|
|
153
217
|
const mdl = t.model ? ` ${t.model.split("/").pop() ?? t.model}` : "";
|
|
154
218
|
nodes.push((() => {
|
|
155
|
-
var _el$
|
|
156
|
-
_$insertNode(_el$
|
|
157
|
-
_$insertNode(_el$
|
|
158
|
-
_$insertNode(_el$
|
|
159
|
-
_$
|
|
160
|
-
_$insert(_el$
|
|
161
|
-
_$insert(_el$
|
|
162
|
-
_$insert(_el$
|
|
163
|
-
_$insert(_el$
|
|
164
|
-
_$
|
|
165
|
-
|
|
166
|
-
return _el$20;
|
|
219
|
+
var _el$36 = _$createElement("text"), _el$37 = _$createTextNode(` \u25CF `), _el$38 = _$createTextNode(` `), _el$39 = _$createTextNode(` `);
|
|
220
|
+
_$insertNode(_el$36, _el$37);
|
|
221
|
+
_$insertNode(_el$36, _el$38);
|
|
222
|
+
_$insertNode(_el$36, _el$39);
|
|
223
|
+
_$insert(_el$36, () => t.id, _el$38);
|
|
224
|
+
_$insert(_el$36, mdl, _el$38);
|
|
225
|
+
_$insert(_el$36, lbl, _el$39);
|
|
226
|
+
_$insert(_el$36, rev, _el$39);
|
|
227
|
+
_$insert(_el$36, () => short(t.title, 12), null);
|
|
228
|
+
_$effect((_$p) => _$setProp(_el$36, "style", st(sKey), _$p));
|
|
229
|
+
return _el$36;
|
|
167
230
|
})());
|
|
168
231
|
const pv = t.model ? t.model.startsWith("zai") ? "zai" : (t.model.split("/")[0] ?? "").split("-")[0] : "";
|
|
169
232
|
const q = pv && h.quotas?.[pv] ? h.quotas[pv] : null;
|
|
170
233
|
const pct = q ? q.fiveHour : 0;
|
|
234
|
+
const qKey = pct >= 70 ? "error" : pct >= 40 ? "warning" : "success";
|
|
171
235
|
nodes.push((() => {
|
|
172
|
-
var _el$
|
|
173
|
-
_$insertNode(_el$
|
|
174
|
-
_$insertNode(_el$
|
|
175
|
-
_$insertNode(_el$
|
|
176
|
-
_$insert(_el$
|
|
177
|
-
_$insert(_el$
|
|
178
|
-
|
|
236
|
+
var _el$40 = _$createElement("text"), _el$41 = _$createTextNode(` 5h `), _el$42 = _$createTextNode(` `), _el$43 = _$createTextNode(`%`);
|
|
237
|
+
_$insertNode(_el$40, _el$41);
|
|
238
|
+
_$insertNode(_el$40, _el$42);
|
|
239
|
+
_$insertNode(_el$40, _el$43);
|
|
240
|
+
_$insert(_el$40, () => bar(pct), _el$42);
|
|
241
|
+
_$insert(_el$40, pct, _el$43);
|
|
242
|
+
_$effect((_$p) => _$setProp(_el$40, "style", st(qKey), _$p));
|
|
243
|
+
return _el$40;
|
|
179
244
|
})());
|
|
180
245
|
}
|
|
181
246
|
}
|
|
182
247
|
return (() => {
|
|
183
|
-
var _el$
|
|
184
|
-
_$setProp(_el$
|
|
185
|
-
_$insert(_el$
|
|
186
|
-
return _el$
|
|
248
|
+
var _el$44 = _$createElement("box");
|
|
249
|
+
_$setProp(_el$44, "flexDirection", "column");
|
|
250
|
+
_$insert(_el$44, nodes);
|
|
251
|
+
return _el$44;
|
|
187
252
|
})();
|
|
188
253
|
};
|
|
189
254
|
api.slots.register({
|
|
@@ -194,9 +259,9 @@ function initializeTui(api, disposeRoot) {
|
|
|
194
259
|
return panel(ctx);
|
|
195
260
|
} catch {
|
|
196
261
|
return (() => {
|
|
197
|
-
var _el$
|
|
198
|
-
_$insertNode(_el$
|
|
199
|
-
return _el$
|
|
262
|
+
var _el$45 = _$createElement("text");
|
|
263
|
+
_$insertNode(_el$45, _$createTextNode(`usage-coach`));
|
|
264
|
+
return _el$45;
|
|
200
265
|
})();
|
|
201
266
|
}
|
|
202
267
|
},
|
|
@@ -205,9 +270,9 @@ function initializeTui(api, disposeRoot) {
|
|
|
205
270
|
return panel(ctx);
|
|
206
271
|
} catch {
|
|
207
272
|
return (() => {
|
|
208
|
-
var _el$
|
|
209
|
-
_$insertNode(_el$
|
|
210
|
-
return _el$
|
|
273
|
+
var _el$47 = _$createElement("text");
|
|
274
|
+
_$insertNode(_el$47, _$createTextNode(`usage-coach`));
|
|
275
|
+
return _el$47;
|
|
211
276
|
})();
|
|
212
277
|
}
|
|
213
278
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-usage-coach",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "opencode closed-loop usage coach
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "opencode closed-loop usage coach \u2014 quota SENSE -> coaching DECIDE -> loop ACT + TUI integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -26,7 +26,16 @@
|
|
|
26
26
|
"README.md",
|
|
27
27
|
"LICENSE"
|
|
28
28
|
],
|
|
29
|
-
"keywords": [
|
|
29
|
+
"keywords": [
|
|
30
|
+
"opencode",
|
|
31
|
+
"opencode-plugin",
|
|
32
|
+
"quota",
|
|
33
|
+
"usage",
|
|
34
|
+
"coach",
|
|
35
|
+
"loop",
|
|
36
|
+
"zai",
|
|
37
|
+
"glm"
|
|
38
|
+
],
|
|
30
39
|
"license": "MIT",
|
|
31
40
|
"peerDependencies": {
|
|
32
41
|
"@opencode-ai/plugin": ">=1.14",
|
|
@@ -43,4 +52,4 @@
|
|
|
43
52
|
"tsup": "^8.5",
|
|
44
53
|
"typescript": "^5"
|
|
45
54
|
}
|
|
46
|
-
}
|
|
55
|
+
}
|