opencode-usage-coach 0.1.2 → 0.1.4

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.
@@ -32,8 +32,10 @@ Default to the loop only when it genuinely adds value. Do not over-engineer smal
32
32
  ## Harness loop (only for substantive work)
33
33
  The user's message is the task source. If it has multiple distinct parts, decompose into discrete tasks (N); if it is one unit, N = 1.
34
34
 
35
+ **Parallel dispatch (independent tasks):** If the decomposed tasks are INDEPENDENT (no data dependency), dispatch them concurrently — issue **multiple `task` calls in the same turn** so they run as parallel subagents. (This is the only reliable parallel path — the deterministic script cannot parallelize due to opencode's single-server model.) Cap concurrency by quota coaching: "big tasks OK" → up to 3-4 parallel; "moderate/small" → 1-2; STOP → don't dispatch. For DEPENDENT tasks (B needs A's output), run sequentially.
36
+
35
37
  1. Call `harness_start(name, N)` to register the run on the panel.
36
- 2. For each task i (1..N):
38
+ 2. For each task i (1..N) — in parallel batches when independent:
37
39
  a. `task_update(i, title, "generating")`.
38
40
  b. **Generate** — delegate to a subagent via the `task` tool (`subagent_type: "general"`):
39
41
  prompt: `"Task: {title}. Perform it for real in the current directory (write/edit files, run commands as needed)."`
package/dist/tui.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/tui.tsx
2
+ import { insert as _$insert } from "@opentui/solid";
2
3
  import { setProp as _$setProp } from "@opentui/solid";
3
4
  import { effect as _$effect } from "@opentui/solid";
4
- import { insert as _$insert } from "@opentui/solid";
5
5
  import { createTextNode as _$createTextNode } from "@opentui/solid";
6
6
  import { insertNode as _$insertNode } from "@opentui/solid";
7
7
  import { createElement as _$createElement } from "@opentui/solid";
@@ -49,9 +49,11 @@ var TLABEL = {
49
49
  timed_out: "timeout",
50
50
  halted_quota: "quota-halt"
51
51
  };
52
- function bar(p) {
53
- const n = Math.max(0, Math.min(10, Math.round(p / 10)));
54
- return "\u2588".repeat(n) + "\u2591".repeat(10 - n);
52
+ function barFill(p) {
53
+ return "\u2588".repeat(Math.max(0, Math.min(10, Math.round(p / 10))));
54
+ }
55
+ function barEmpty(p) {
56
+ return "\u2591".repeat(10 - Math.max(0, Math.min(10, Math.round(p / 10))));
55
57
  }
56
58
  function short(s, n) {
57
59
  return s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
@@ -79,6 +81,26 @@ function initializeTui(api, disposeRoot) {
79
81
  }, 3e3);
80
82
  onCleanup(() => clearInterval(timer));
81
83
  onCleanup(() => disposeRoot());
84
+ const [collapsed, setCollapsed] = createSignal(false);
85
+ let cmdDispose;
86
+ try {
87
+ cmdDispose = api.command?.register?.(() => [{
88
+ title: "Toggle usage-coach panel",
89
+ value: "usage-coach-toggle",
90
+ category: "usage-coach",
91
+ keybind: "alt+h",
92
+ onSelect: () => {
93
+ setCollapsed((c) => !c);
94
+ }
95
+ }]);
96
+ } catch {
97
+ }
98
+ onCleanup(() => {
99
+ try {
100
+ cmdDispose?.();
101
+ } catch {
102
+ }
103
+ });
82
104
  const statusKey = {
83
105
  generating: "info",
84
106
  grading: "accent",
@@ -93,6 +115,15 @@ function initializeTui(api, disposeRoot) {
93
115
  const st = (k) => ({
94
116
  fg: th[k]
95
117
  });
118
+ if (collapsed()) {
119
+ return (() => {
120
+ var _el$ = _$createElement("box"), _el$2 = _$createElement("text");
121
+ _$insertNode(_el$, _el$2);
122
+ _$insertNode(_el$2, _$createTextNode(`usage-coach (hidden \u2014 Alt+H)`));
123
+ _$effect((_$p) => _$setProp(_el$2, "style", st("textMuted"), _$p));
124
+ return _el$;
125
+ })();
126
+ }
96
127
  let s = null;
97
128
  try {
98
129
  s = getState();
@@ -109,106 +140,177 @@ function initializeTui(api, disposeRoot) {
109
140
  if (s) {
110
141
  const dKey = s.decision === "GO" ? "success" : s.decision === "THROTTLE" ? "warning" : "error";
111
142
  nodes.push((() => {
112
- var _el$ = _$createElement("text"), _el$2 = _$createTextNode(`usage-coach [`), _el$3 = _$createTextNode(`]`);
113
- _$insertNode(_el$, _el$2);
114
- _$insertNode(_el$, _el$3);
115
- _$insert(_el$, () => TAG[s.decision], _el$3);
116
- _$effect((_$p) => _$setProp(_el$, "style", st(dKey), _$p));
117
- return _el$;
143
+ var _el$4 = _$createElement("text"), _el$5 = _$createTextNode(`usage-coach [`), _el$6 = _$createTextNode(`]`);
144
+ _$insertNode(_el$4, _el$5);
145
+ _$insertNode(_el$4, _el$6);
146
+ _$insert(_el$4, () => TAG[s.decision], _el$6);
147
+ _$effect((_$p) => _$setProp(_el$4, "style", st(dKey), _$p));
148
+ return _el$4;
118
149
  })());
119
150
  if (s.providers && s.providers.length > 0) {
120
151
  for (const p of s.providers) {
121
152
  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;
153
+ var _el$7 = _$createElement("text"), _el$8 = _$createTextNode(` `);
154
+ _$insertNode(_el$7, _el$8);
155
+ _$insert(_el$7, () => p.name, null);
156
+ _$effect((_$p) => _$setProp(_el$7, "style", st("textMuted"), _$p));
157
+ return _el$7;
158
+ })());
159
+ nodes.push((() => {
160
+ var _el$9 = _$createElement("text");
161
+ _$insertNode(_el$9, _$createTextNode(` 5h `));
162
+ return _el$9;
163
+ })());
164
+ nodes.push((() => {
165
+ var _el$1 = _$createElement("text");
166
+ _$insert(_el$1, () => barFill(p.fiveHour));
167
+ _$effect((_$p) => _$setProp(_el$1, "style", st("text"), _$p));
168
+ return _el$1;
169
+ })());
170
+ nodes.push((() => {
171
+ var _el$10 = _$createElement("text");
172
+ _$insert(_el$10, () => barEmpty(p.fiveHour));
173
+ _$effect((_$p) => _$setProp(_el$10, "style", st("textMuted"), _$p));
174
+ return _el$10;
127
175
  })());
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
176
  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;
177
+ var _el$11 = _$createElement("text"), _el$12 = _$createTextNode(` `), _el$13 = _$createTextNode(`% `);
178
+ _$insertNode(_el$11, _el$12);
179
+ _$insertNode(_el$11, _el$13);
180
+ _$insert(_el$11, () => p.fiveHour, _el$13);
181
+ _$insert(_el$11, () => p.fiveHourReset, null);
182
+ return _el$11;
140
183
  })());
141
184
  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;
185
+ var _el$14 = _$createElement("text");
186
+ _$insertNode(_el$14, _$createTextNode(` wk `));
187
+ return _el$14;
151
188
  })());
152
189
  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;
190
+ var _el$16 = _$createElement("text");
191
+ _$insert(_el$16, () => barFill(p.weekly));
192
+ _$effect((_$p) => _$setProp(_el$16, "style", st("text"), _$p));
193
+ return _el$16;
194
+ })());
195
+ nodes.push((() => {
196
+ var _el$17 = _$createElement("text");
197
+ _$insert(_el$17, () => barEmpty(p.weekly));
198
+ _$effect((_$p) => _$setProp(_el$17, "style", st("textMuted"), _$p));
199
+ return _el$17;
200
+ })());
201
+ nodes.push((() => {
202
+ var _el$18 = _$createElement("text"), _el$19 = _$createTextNode(` `), _el$20 = _$createTextNode(`% `);
203
+ _$insertNode(_el$18, _el$19);
204
+ _$insertNode(_el$18, _el$20);
205
+ _$insert(_el$18, () => p.weekly, _el$20);
206
+ _$insert(_el$18, () => p.weeklyReset, null);
207
+ return _el$18;
208
+ })());
209
+ nodes.push((() => {
210
+ var _el$21 = _$createElement("text"), _el$22 = _$createTextNode(` -> `);
211
+ _$insertNode(_el$21, _el$22);
212
+ _$insert(_el$21, () => p.advice, null);
213
+ _$effect((_$p) => _$setProp(_el$21, "style", st(dKey), _$p));
214
+ return _el$21;
158
215
  })());
159
216
  }
160
217
  } else {
161
218
  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;
219
+ var _el$25 = _$createElement("text");
220
+ _$insertNode(_el$25, _$createTextNode(` 5h `));
221
+ return _el$25;
222
+ })());
223
+ nodes.push((() => {
224
+ var _el$27 = _$createElement("text");
225
+ _$insert(_el$27, () => barFill(s.fiveHour));
226
+ _$effect((_$p) => _$setProp(_el$27, "style", st("text"), _$p));
227
+ return _el$27;
228
+ })());
229
+ nodes.push((() => {
230
+ var _el$28 = _$createElement("text");
231
+ _$insert(_el$28, () => barEmpty(s.fiveHour));
232
+ _$effect((_$p) => _$setProp(_el$28, "style", st("textMuted"), _$p));
233
+ return _el$28;
234
+ })());
235
+ nodes.push((() => {
236
+ var _el$29 = _$createElement("text"), _el$30 = _$createTextNode(` `), _el$31 = _$createTextNode(`%`);
237
+ _$insertNode(_el$29, _el$30);
238
+ _$insertNode(_el$29, _el$31);
239
+ _$insert(_el$29, () => s.fiveHour, _el$31);
240
+ return _el$29;
241
+ })());
242
+ nodes.push((() => {
243
+ var _el$32 = _$createElement("text");
244
+ _$insertNode(_el$32, _$createTextNode(` wk `));
245
+ return _el$32;
169
246
  })());
170
247
  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;
248
+ var _el$34 = _$createElement("text");
249
+ _$insert(_el$34, () => barFill(s.weekly));
250
+ _$effect((_$p) => _$setProp(_el$34, "style", st("text"), _$p));
251
+ return _el$34;
178
252
  })());
179
253
  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;
254
+ var _el$35 = _$createElement("text");
255
+ _$insert(_el$35, () => barEmpty(s.weekly));
256
+ _$effect((_$p) => _$setProp(_el$35, "style", st("textMuted"), _$p));
257
+ return _el$35;
258
+ })());
259
+ nodes.push((() => {
260
+ var _el$36 = _$createElement("text"), _el$37 = _$createTextNode(` `), _el$38 = _$createTextNode(`%`);
261
+ _$insertNode(_el$36, _el$37);
262
+ _$insertNode(_el$36, _el$38);
263
+ _$insert(_el$36, () => s.weekly, _el$38);
264
+ return _el$36;
265
+ })());
266
+ nodes.push((() => {
267
+ var _el$39 = _$createElement("text");
268
+ _$insertNode(_el$39, _$createTextNode(` mo `));
269
+ return _el$39;
270
+ })());
271
+ nodes.push((() => {
272
+ var _el$41 = _$createElement("text");
273
+ _$insert(_el$41, () => barFill(s.monthly));
274
+ _$effect((_$p) => _$setProp(_el$41, "style", st("text"), _$p));
275
+ return _el$41;
276
+ })());
277
+ nodes.push((() => {
278
+ var _el$42 = _$createElement("text");
279
+ _$insert(_el$42, () => barEmpty(s.monthly));
280
+ _$effect((_$p) => _$setProp(_el$42, "style", st("textMuted"), _$p));
281
+ return _el$42;
282
+ })());
283
+ nodes.push((() => {
284
+ var _el$43 = _$createElement("text"), _el$44 = _$createTextNode(` `), _el$45 = _$createTextNode(`%`);
285
+ _$insertNode(_el$43, _el$44);
286
+ _$insertNode(_el$43, _el$45);
287
+ _$insert(_el$43, () => s.monthly, _el$45);
288
+ return _el$43;
187
289
  })());
188
290
  }
189
291
  } else {
190
292
  nodes.push((() => {
191
- var _el$28 = _$createElement("text");
192
- _$insertNode(_el$28, _$createTextNode(`usage-coach: ...`));
193
- return _el$28;
293
+ var _el$46 = _$createElement("text");
294
+ _$insertNode(_el$46, _$createTextNode(`usage-coach: ...`));
295
+ return _el$46;
194
296
  })());
195
297
  }
196
298
  if (h && h.active !== false && h.tasks.length > 0) {
197
299
  nodes.push((() => {
198
- var _el$30 = _$createElement("text");
199
- _$insertNode(_el$30, _$createTextNode(` `));
200
- return _el$30;
300
+ var _el$48 = _$createElement("text");
301
+ _$insertNode(_el$48, _$createTextNode(` `));
302
+ return _el$48;
201
303
  })());
202
304
  nodes.push((() => {
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;
305
+ var _el$50 = _$createElement("text"), _el$51 = _$createTextNode(`harness: `), _el$52 = _$createTextNode(` `), _el$53 = _$createTextNode(`/`);
306
+ _$insertNode(_el$50, _el$51);
307
+ _$insertNode(_el$50, _el$52);
308
+ _$insertNode(_el$50, _el$53);
309
+ _$insert(_el$50, () => h.name, _el$52);
310
+ _$insert(_el$50, () => h.current, _el$53);
311
+ _$insert(_el$50, () => h.total, null);
312
+ _$effect((_$p) => _$setProp(_el$50, "style", st("textMuted"), _$p));
313
+ return _el$50;
212
314
  })());
213
315
  for (const t of h.tasks) {
214
316
  const sKey = statusKey[t.status] ?? "text";
@@ -216,39 +318,52 @@ function initializeTui(api, disposeRoot) {
216
318
  const rev = t.revisions > 0 && t.status === "revising" ? `(${t.revisions})` : "";
217
319
  const mdl = t.model ? ` ${t.model.split("/").pop() ?? t.model}` : "";
218
320
  nodes.push((() => {
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;
321
+ var _el$54 = _$createElement("text"), _el$55 = _$createTextNode(` \u25CF `), _el$56 = _$createTextNode(` `), _el$57 = _$createTextNode(` `);
322
+ _$insertNode(_el$54, _el$55);
323
+ _$insertNode(_el$54, _el$56);
324
+ _$insertNode(_el$54, _el$57);
325
+ _$insert(_el$54, () => t.id, _el$56);
326
+ _$insert(_el$54, mdl, _el$56);
327
+ _$insert(_el$54, lbl, _el$57);
328
+ _$insert(_el$54, rev, _el$57);
329
+ _$insert(_el$54, () => short(t.title, 12), null);
330
+ _$effect((_$p) => _$setProp(_el$54, "style", st(sKey), _$p));
331
+ return _el$54;
230
332
  })());
231
333
  const pv = t.model ? t.model.startsWith("zai") ? "zai" : (t.model.split("/")[0] ?? "").split("-")[0] : "";
232
334
  const q = pv && h.quotas?.[pv] ? h.quotas[pv] : null;
233
335
  const pct = q ? q.fiveHour : 0;
234
- const qKey = pct >= 70 ? "error" : pct >= 40 ? "warning" : "success";
235
336
  nodes.push((() => {
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;
337
+ var _el$58 = _$createElement("text");
338
+ _$insertNode(_el$58, _$createTextNode(` 5h `));
339
+ return _el$58;
340
+ })());
341
+ nodes.push((() => {
342
+ var _el$60 = _$createElement("text");
343
+ _$insert(_el$60, () => barFill(pct));
344
+ _$effect((_$p) => _$setProp(_el$60, "style", st("text"), _$p));
345
+ return _el$60;
346
+ })());
347
+ nodes.push((() => {
348
+ var _el$61 = _$createElement("text");
349
+ _$insert(_el$61, () => barEmpty(pct));
350
+ _$effect((_$p) => _$setProp(_el$61, "style", st("textMuted"), _$p));
351
+ return _el$61;
352
+ })());
353
+ nodes.push((() => {
354
+ var _el$62 = _$createElement("text"), _el$63 = _$createTextNode(` `), _el$64 = _$createTextNode(`%`);
355
+ _$insertNode(_el$62, _el$63);
356
+ _$insertNode(_el$62, _el$64);
357
+ _$insert(_el$62, pct, _el$64);
358
+ return _el$62;
244
359
  })());
245
360
  }
246
361
  }
247
362
  return (() => {
248
- var _el$44 = _$createElement("box");
249
- _$setProp(_el$44, "flexDirection", "column");
250
- _$insert(_el$44, nodes);
251
- return _el$44;
363
+ var _el$65 = _$createElement("box");
364
+ _$setProp(_el$65, "flexDirection", "column");
365
+ _$insert(_el$65, nodes);
366
+ return _el$65;
252
367
  })();
253
368
  };
254
369
  api.slots.register({
@@ -259,9 +374,9 @@ function initializeTui(api, disposeRoot) {
259
374
  return panel(ctx);
260
375
  } catch {
261
376
  return (() => {
262
- var _el$45 = _$createElement("text");
263
- _$insertNode(_el$45, _$createTextNode(`usage-coach`));
264
- return _el$45;
377
+ var _el$66 = _$createElement("text");
378
+ _$insertNode(_el$66, _$createTextNode(`usage-coach`));
379
+ return _el$66;
265
380
  })();
266
381
  }
267
382
  },
@@ -270,9 +385,9 @@ function initializeTui(api, disposeRoot) {
270
385
  return panel(ctx);
271
386
  } catch {
272
387
  return (() => {
273
- var _el$47 = _$createElement("text");
274
- _$insertNode(_el$47, _$createTextNode(`usage-coach`));
275
- return _el$47;
388
+ var _el$68 = _$createElement("text");
389
+ _$insertNode(_el$68, _$createTextNode(`usage-coach`));
390
+ return _el$68;
276
391
  })();
277
392
  }
278
393
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-usage-coach",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
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",