kaizen-agent 1.33.1 → 1.34.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.
- package/cli/browser.test.ts +1 -0
- package/cli/browser.ts +12 -8
- package/cli/sources.test.ts +57 -3
- package/cli/sources.ts +111 -17
- package/cli/state.test.ts +31 -0
- package/cli/state.ts +10 -0
- package/cli/web.ts +27 -8
- package/package.json +1 -1
- package/skills/kaizen/gather.md +4 -1
- package/skills/kaizen/spec.md +5 -1
- package/web/board/icebox.ts +26 -26
- package/web/board/projects.ts +3 -1
- package/web/board.html +10 -2
- package/web/board.js +18 -18
- package/web/notebook.js +1 -1
- package/web/term.js +1 -1
package/web/board/icebox.ts
CHANGED
|
@@ -134,7 +134,9 @@ export async function openSources() {
|
|
|
134
134
|
return "link";
|
|
135
135
|
};
|
|
136
136
|
const list = (d && d.sources) || [],
|
|
137
|
-
|
|
137
|
+
// Dismissed items ride along, shown only under their own category.
|
|
138
|
+
ice = [...((d && d.icebox) || []), ...((d && d.dismissed) || [])],
|
|
139
|
+
waiting = ((d && d.icebox) || []).length,
|
|
138
140
|
G = (d && d.gatherer) || {},
|
|
139
141
|
chatOn = G.use === "chatgpt" && G.connected;
|
|
140
142
|
// A source belongs to one project, named on its row. Picking another only arms Move: a select
|
|
@@ -186,7 +188,7 @@ export async function openSources() {
|
|
|
186
188
|
srcKey = (it) => it.dir + " " + it.source;
|
|
187
189
|
// Filters narrow in turn: project (the board's own picker, all projects only), then
|
|
188
190
|
// category, then source; each chip counts what the filters before it leave.
|
|
189
|
-
const catOf = (it) => it.category || "Uncategorised",
|
|
191
|
+
const catOf = (it) => (it.status === "dismissed" ? "Dismissed" : it.category || "Uncategorised"),
|
|
190
192
|
count = (xs, f) => {
|
|
191
193
|
const m = new Map();
|
|
192
194
|
xs.forEach((it) => {
|
|
@@ -213,7 +215,7 @@ export async function openSources() {
|
|
|
213
215
|
if (iceForm && !keys.has(iceForm)) iceForm = null;
|
|
214
216
|
const groups = new Map();
|
|
215
217
|
ice.forEach((it, i) => {
|
|
216
|
-
if (!inProj(it) || (iceCat
|
|
218
|
+
if (!inProj(it) || (iceCat ? catOf(it) !== iceCat : it.status === "dismissed") || (iceSrc && srcKey(it) !== iceSrc)) return;
|
|
217
219
|
const k = catOf(it);
|
|
218
220
|
groups.has(k) || groups.set(k, []);
|
|
219
221
|
groups.get(k).push(i);
|
|
@@ -227,20 +229,13 @@ export async function openSources() {
|
|
|
227
229
|
}
|
|
228
230
|
if (!order.some((i) => iceKey(ice[i]) === iceCur)) iceCur = order.length ? iceKey(ice[order[0]]) : null;
|
|
229
231
|
const nSel = order.filter((i) => iceSel.has(iceKey(ice[i]))).length;
|
|
230
|
-
// Filters are plain dropdowns, each shown only when it has a choice to make.
|
|
231
|
-
//
|
|
232
|
+
// Filters are plain dropdowns, each shown only when it has a choice to make. Projects are
|
|
233
|
+
// narrowed by the board's own Showing picker above, not here.
|
|
232
234
|
const many = byProj.size > 1,
|
|
233
235
|
opt = (v, on, t) => `<option value="${esc(v)}"${on ? " selected" : ""}>${esc(t)}</option>`;
|
|
234
236
|
const pick = (id, label, xs) =>
|
|
235
237
|
`<select class="sel ice-sel" id="${id}" aria-label="${label}">${xs.join("")}</select>`;
|
|
236
238
|
const chips =
|
|
237
|
-
(many
|
|
238
|
-
? pick("ice-fp", "Project", [
|
|
239
|
-
opt("", !store.shown.size, "All projects"),
|
|
240
|
-
...(store.shown.size > 1 ? [opt("*", true, store.shown.size + " projects")] : []),
|
|
241
|
-
...[...byProj].map(([k, { it }]) => opt(k, store.shown.size === 1 && store.shown.has(k), it.project || k)),
|
|
242
|
-
])
|
|
243
|
-
: "") +
|
|
244
239
|
(byCat.size > 1
|
|
245
240
|
? pick("ice-fc", "Category", [
|
|
246
241
|
opt("", !iceCat, "All categories"),
|
|
@@ -253,7 +248,7 @@ export async function openSources() {
|
|
|
253
248
|
...[...bySrc].map(([k, { it }]) => opt(k, iceSrc === k, srcName(it))),
|
|
254
249
|
])
|
|
255
250
|
: "");
|
|
256
|
-
const bar = `<div class="ice-bar">${order.length ? `<input type="checkbox" id="ice-all" aria-label="Select every request shown"${nSel && nSel === order.length ? " checked" : ""}>` : ""}<span class="count">${order.length
|
|
251
|
+
const bar = `<div class="ice-bar">${order.length ? `<input type="checkbox" id="ice-all" aria-label="Select every request shown"${nSel && nSel === order.length ? " checked" : ""}>` : ""}<span class="count">${iceCat === "Dismissed" ? `${order.length} dismissed` : `${order.length < waiting ? `${order.length} of ${waiting}` : waiting} waiting`}</span>${chips}<span class="end"></span><button class="btn sm" id="src-go"${list.length ? "" : " disabled"}>${ico("refresh")} Gather all</button><button class="btn ghost sm" id="src-tog" aria-expanded="${srcShow ?? !list.length}" aria-controls="src-sec">${ico("link")} Sources · ${list.length}</button></div>`;
|
|
257
252
|
const bulk = iceSel.size
|
|
258
253
|
? `<div class="ice-bulk" role="toolbar" aria-label="Selected requests"><span>${iceSel.size} selected</span><span class="end"></span><button class="btn sm" id="ice-bp" title="Each becomes its own idea, titled as gathered (p)">${ico("plus")} Promote</button><button class="btn ghost sm" id="ice-bd" title="Dismiss (d)">${ico("x")} Dismiss</button><button class="btn ghost sm" id="ice-bc" title="Clear selection (Esc)">Clear</button></div>`
|
|
259
254
|
: "";
|
|
@@ -282,16 +277,16 @@ export async function openSources() {
|
|
|
282
277
|
k = iceKey(it),
|
|
283
278
|
open = iceForm === k;
|
|
284
279
|
return (
|
|
285
|
-
`<div class="ice-row${iceCur === k ? " cur" : ""}${iceSel.has(k) ? " sel" : ""}" data-i="${i}" data-k="${esc(k)}"><input type="checkbox" id="ice-ck-${i}" data-ck="${i}"${iceSel.has(k) ? " checked" : ""} aria-label="Select: ${esc(it.text)}"><span class="ice-src" title="${esc(srcName(it))}">${fav(it.source, "link", list.find((x) => x.dir === it.dir && x.url === it.source)?.social)}</span><button class="ice-t" id="ice-o-${i}" data-o="${i}" aria-expanded="${open}" title="${esc(it.text)}">${esc(it.text)}</button><span class="ice-meta">${many ? `<span class="nm">${esc(it.project || "")}</span>` : ""}${it.replied ? `<span title="Replied ${esc(ago(it.replied))}">replied</span>` : ""}<span class="ice-age">${ago(it.at)}</span><span class="ice-ra"><button class="btn ghost sm" id="ice-pr-${i}" data-pr="${i}" title="Promote to Ideas as gathered (p)" aria-label="Promote to Ideas: ${esc(it.text)}">${ico("plus")}</button
|
|
280
|
+
`<div class="ice-row${iceCur === k ? " cur" : ""}${iceSel.has(k) ? " sel" : ""}" data-i="${i}" data-k="${esc(k)}"><input type="checkbox" id="ice-ck-${i}" data-ck="${i}"${iceSel.has(k) ? " checked" : ""} aria-label="Select: ${esc(it.text)}"><span class="ice-src" title="${esc(srcName(it))}">${fav(it.source, "link", list.find((x) => x.dir === it.dir && x.url === it.source)?.social)}</span><button class="ice-t" id="ice-o-${i}" data-o="${i}" aria-expanded="${open}" title="${esc(it.text)}">${esc(it.text)}</button><span class="ice-meta">${many ? `<span class="nm">${esc(it.project || "")}</span>` : ""}${it.replied ? `<span title="Replied ${esc(ago(it.replied))}">replied</span>` : ""}<span class="ice-age">${ago(it.at)}</span><span class="ice-ra"><button class="btn ghost sm" id="ice-pr-${i}" data-pr="${i}" title="Promote to Ideas as gathered (p)" aria-label="Promote to Ideas: ${esc(it.text)}">${ico("plus")}</button>${it.status === "dismissed" ? `<button class="btn ghost sm" id="ice-rs-${i}" data-rs="${i}" title="Restore to waiting" aria-label="Restore: ${esc(it.text)}">Restore</button>` : `<button class="btn ghost sm" id="ice-ds-${i}" data-ds="${i}" title="Dismiss (d)" aria-label="Dismiss: ${esc(it.text)}">${ico("x")}</button>`}</span></span></div>` +
|
|
286
281
|
(open
|
|
287
|
-
? `<div class="ice-open">${it.notes ? `<div class="ice-notes">${esc(it.notes)}</div>` : ""}<span class="hint" style="margin:0">From ${esc(srcName(it))} · ${ago(it.at)}${it.post ? ` · <a href="${esc(it.post)}" target="_blank" rel="noopener">open post</a>` : ""}</span>${replyBox(it)}${form(it)}</div>`
|
|
282
|
+
? `<div class="ice-open">${it.notes ? `<div class="ice-notes">${esc(it.notes)}</div>` : ""}${(it.images || []).filter((u) => /^https:\/\//.test(u)).length ? `<details class="ice-imgs"><summary>Show images (${it.images.filter((u) => /^https:\/\//.test(u)).length}) — loads them from the source's site</summary>${it.images.filter((u) => /^https:\/\//.test(u)).map((u) => `<a href="${esc(u)}" target="_blank" rel="noopener noreferrer"><img data-src="${esc(u)}" alt="" referrerpolicy="no-referrer"></a>`).join("")}</details>` : ""}<span class="hint" style="margin:0">From ${esc(srcName(it))} · ${ago(it.at)}${it.post ? ` · <a href="${esc(it.post)}" target="_blank" rel="noopener">open post</a>` : ""}</span>${replyBox(it)}${form(it)}</div>`
|
|
288
283
|
: "")
|
|
289
284
|
);
|
|
290
285
|
};
|
|
291
286
|
const inbox =
|
|
292
|
-
|
|
293
|
-
? `<div class="ice-zero"><h4>Nothing matches</h4><p>${
|
|
294
|
-
:
|
|
287
|
+
waiting && !order.length
|
|
288
|
+
? `<div class="ice-zero"><h4>Nothing matches</h4><p>${waiting} waiting outside these filters.</p><p style="margin-top:10px"><button class="btn sm" id="ice-unf">Clear filters</button></p></div>`
|
|
289
|
+
: order.length
|
|
295
290
|
? `<div class="ice-list">${[...groups].map(([g, ix]) => `<div class="ice-cat">${esc(g)}<i>${ix.length}</i></div>${ix.map(row).join("")}`).join("")}</div>`
|
|
296
291
|
: `<div class="ice-zero"><h4>Inbox zero</h4><p>Nothing waiting. Gather all checks your sources for new requests; /kaizen discover in your agent sorts what comes in.</p></div>`;
|
|
297
292
|
// The sources, in the right-hand drawer: shut until asked for, open by itself while there
|
|
@@ -398,14 +393,6 @@ export async function openSources() {
|
|
|
398
393
|
iceCat = e.target.value;
|
|
399
394
|
openSources();
|
|
400
395
|
};
|
|
401
|
-
if ($("ice-fp"))
|
|
402
|
-
$("ice-fp").onchange = (e) => {
|
|
403
|
-
const v = e.target.value;
|
|
404
|
-
if (v === "*") return;
|
|
405
|
-
store.shown = new Set(v ? [v] : []);
|
|
406
|
-
setShown();
|
|
407
|
-
openSources();
|
|
408
|
-
};
|
|
409
396
|
if ($("ice-unf"))
|
|
410
397
|
$("ice-unf").onclick = () => {
|
|
411
398
|
iceCat = iceSrc = "";
|
|
@@ -573,6 +560,10 @@ export async function openSources() {
|
|
|
573
560
|
$("pbody")
|
|
574
561
|
.querySelectorAll("[data-o]")
|
|
575
562
|
.forEach((b) => (b.onclick = () => toggle(+b.dataset.o)));
|
|
563
|
+
// Images from a source load only when asked: fetching one tells its host the item was opened.
|
|
564
|
+
$("pbody")
|
|
565
|
+
.querySelectorAll("details.ice-imgs")
|
|
566
|
+
.forEach((d) => (d.ontoggle = () => d.open && d.querySelectorAll("img[data-src]").forEach((i) => (i.src = i.dataset.src))));
|
|
576
567
|
$("pbody")
|
|
577
568
|
.querySelectorAll("[data-pr]")
|
|
578
569
|
.forEach(
|
|
@@ -591,6 +582,15 @@ export async function openSources() {
|
|
|
591
582
|
iceAct("dismiss", ice[+b.dataset.ds], "Dismissed");
|
|
592
583
|
}),
|
|
593
584
|
);
|
|
585
|
+
$("pbody")
|
|
586
|
+
.querySelectorAll("[data-rs]")
|
|
587
|
+
.forEach(
|
|
588
|
+
(b) =>
|
|
589
|
+
(b.onclick = () => {
|
|
590
|
+
b.disabled = true;
|
|
591
|
+
iceAct("restore", ice[+b.dataset.rs], "Restored");
|
|
592
|
+
}),
|
|
593
|
+
);
|
|
594
594
|
const open1 = ice.find((it) => iceKey(it) === iceForm);
|
|
595
595
|
if ($("ice-rp"))
|
|
596
596
|
$("ice-rp").onclick = async () => {
|
package/web/board/projects.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { store } from "./store.ts";
|
|
|
2
2
|
import { load, post } from "./api.ts";
|
|
3
3
|
import { render } from "./cards.ts";
|
|
4
4
|
import { $, esc } from "./dom.ts";
|
|
5
|
-
import { openSources } from "./icebox.ts";
|
|
5
|
+
import { openSources, srcOpen } from "./icebox.ts";
|
|
6
6
|
import { toast } from "./toast.ts";
|
|
7
7
|
import { view } from "./views.ts";
|
|
8
8
|
|
|
@@ -144,6 +144,8 @@ export function setShown() {
|
|
|
144
144
|
localStorage.setItem("kz-show", JSON.stringify([...store.shown]));
|
|
145
145
|
} catch (e) {}
|
|
146
146
|
render();
|
|
147
|
+
// The Icebox narrows by the same picker, so it redraws too.
|
|
148
|
+
if (srcOpen()) openSources();
|
|
147
149
|
}
|
|
148
150
|
|
|
149
151
|
export function initProjects() {
|
package/web/board.html
CHANGED
|
@@ -2,8 +2,15 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
|
6
6
|
<title>kaizen</title>
|
|
7
|
+
<link rel="manifest" href="/manifest.webmanifest">
|
|
8
|
+
<meta name="theme-color" content="#f6f8fc" media="(prefers-color-scheme: light)">
|
|
9
|
+
<meta name="theme-color" content="#0c162f" media="(prefers-color-scheme: dark)">
|
|
10
|
+
<meta name="mobile-web-app-capable" content="yes">
|
|
11
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
12
|
+
<meta name="apple-mobile-web-app-title" content="kaizen">
|
|
13
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
7
14
|
<link rel="icon" href="/favicon.ico" sizes="any">
|
|
8
15
|
<link rel="icon" type="image/png" href="/logo.png">
|
|
9
16
|
<link rel="apple-touch-icon" href="/logo.png">
|
|
@@ -49,7 +56,7 @@ svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.6;stroke
|
|
|
49
56
|
/* end side menu */
|
|
50
57
|
/* the work area: an inset rounded panel on the canvas the side menu sits on */
|
|
51
58
|
.app{flex:1;min-width:0;display:flex;flex-direction:column;margin:8px 8px 8px 0;background:var(--bg);border:1px solid var(--line);border-radius:var(--r);overflow:hidden}
|
|
52
|
-
@media(max-width:640px){.app{margin:0;border:0;border-radius:0}}
|
|
59
|
+
@media(max-width:640px){.app{margin:0;border:0;border-radius:0}body{padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)}aside:not(.docked){padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) 0}}
|
|
53
60
|
/* header */
|
|
54
61
|
.hdr{display:flex;align-items:center;gap:12px;padding:10px 20px 10px 28px;border-bottom:1px solid color-mix(in srgb,var(--line) 60%,transparent);flex-wrap:wrap}
|
|
55
62
|
pre.mark{margin:0;font:700 8px/1 var(--mono)}
|
|
@@ -378,6 +385,7 @@ body:not(.paged) .src-sec,body:not(.paged) .src-scrim{display:none}
|
|
|
378
385
|
.ice-form-acts .sel{width:auto;flex:none}
|
|
379
386
|
.ice-form-acts .end{flex:1}
|
|
380
387
|
.ice-form-acts .btn{white-space:nowrap}
|
|
388
|
+
.ice-imgs{font-size:12px;color:var(--dim)}.ice-imgs[open]{display:flex;gap:6px;flex-wrap:wrap}.ice-imgs summary{cursor:pointer;flex-basis:100%}.ice-imgs img{height:72px;max-width:120px;object-fit:cover;border-radius:var(--r-s);border:1px solid var(--line)}
|
|
381
389
|
.ice-notes{font-size:12px;color:var(--dim);line-height:1.45;overflow-wrap:anywhere;white-space:pre-wrap}
|
|
382
390
|
.src-empty{padding:26px 16px;text-align:center;background:var(--bg);border:1px dashed var(--line);border-radius:var(--r);margin:12px 0 16px}
|
|
383
391
|
.src-empty-ico{width:36px;height:36px;margin:0 auto 10px;color:var(--dim);display:flex;align-items:center;justify-content:center}
|