kaizen-agent 1.4.0 → 1.5.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 +42 -23
- package/cli/dashboard.ts +54 -504
- package/cli/install.ts +15 -1
- package/cli/publish-gh.sh +27 -0
- package/cli/release.sh +2 -2
- package/cli/state.test.ts +106 -0
- package/cli/state.ts +532 -0
- package/cli/web.ts +235 -0
- package/package.json +5 -2
- package/web/board.html +361 -0
package/web/board.html
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>kaizen</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root{--bg:#f6f8fc;--bg-2:#fff;--line:#d9e0ee;--ink:#0c162f;--ink-hi:#0c162f;--dim:#5b6b8c;--accent:#2d50a5;--run:#0f8fa3;--ok:#1f9d55;--warn:#a8720a;--danger:#c8322f;color-scheme:light}
|
|
9
|
+
@media(prefers-color-scheme:dark){:root:not([data-theme=light]){--bg:#0c162f;--bg-2:#121e3c;--line:#22305a;--ink:#eef2fa;--ink-hi:#deeeff;--dim:#96aacd;--accent:#486ed9;--run:#4fd6e6;--ok:#5fd28a;--warn:#e6b450;--danger:#e85050;color-scheme:dark}}
|
|
10
|
+
:root[data-theme=dark]{--bg:#0c162f;--bg-2:#121e3c;--line:#22305a;--ink:#eef2fa;--ink-hi:#deeeff;--dim:#96aacd;--accent:#486ed9;--run:#4fd6e6;--ok:#5fd28a;--warn:#e6b450;--danger:#e85050;color-scheme:dark}
|
|
11
|
+
*{box-sizing:border-box}
|
|
12
|
+
:root{--r:6px;--r-s:4px}
|
|
13
|
+
body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.5 ui-monospace,"JetBrains Mono","Cascadia Code","SF Mono",Menlo,Consolas,monospace;min-height:100vh;display:flex;flex-direction:column}
|
|
14
|
+
button,input,select{font:inherit;color:inherit;background:none;border:0;padding:0}
|
|
15
|
+
button{cursor:pointer}
|
|
16
|
+
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
|
17
|
+
svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;display:block}
|
|
18
|
+
|
|
19
|
+
/* header */
|
|
20
|
+
.hdr{display:flex;align-items:center;gap:16px;padding:10px 24px;border-bottom:1px solid var(--line);flex-wrap:wrap}
|
|
21
|
+
pre.mark{margin:0;font-size:8px;line-height:1;font-weight:700}
|
|
22
|
+
pre.mark span:nth-child(1){color:var(--ink-hi)}pre.mark span:nth-child(2){color:color-mix(in srgb,var(--ink-hi),var(--accent))}pre.mark span:nth-child(3){color:var(--accent)}
|
|
23
|
+
.sel{border:1px solid var(--line);border-radius:var(--r);padding:5px 8px;background:var(--bg-2);cursor:pointer;color:var(--ink);appearance:auto;font-size:13px}
|
|
24
|
+
.sp{flex:1}
|
|
25
|
+
.ib{width:34px;height:34px;display:grid;place-items:center;color:var(--dim);border:1px solid transparent;border-radius:var(--r)}
|
|
26
|
+
.ib:hover{color:var(--ink);border-color:var(--line)}
|
|
27
|
+
.ib.pri{color:var(--accent)}
|
|
28
|
+
.ib.sm{width:26px;height:26px}.ib.sm svg{width:15px;height:15px}
|
|
29
|
+
.btn{border:1px solid var(--line);border-radius:var(--r);padding:5px 12px;color:var(--ink);background:var(--bg-2);min-height:30px;font-size:13px}
|
|
30
|
+
.btn:hover{border-color:var(--dim)}
|
|
31
|
+
.btn.pri{background:var(--accent);border-color:var(--accent);color:#fff}
|
|
32
|
+
.btn.warn{border-color:var(--warn);color:var(--warn)}.btn.warn:hover{background:var(--warn);color:var(--bg)}
|
|
33
|
+
.btn.danger{color:var(--danger)}.btn.danger:hover{border-color:var(--danger)}
|
|
34
|
+
.btn.ghost{background:none;border-color:transparent;color:var(--dim)}.btn.ghost:hover{color:var(--ink);border-color:var(--line)}
|
|
35
|
+
|
|
36
|
+
/* summary strip */
|
|
37
|
+
.strip{display:flex;gap:18px;padding:8px 24px;border-bottom:1px solid var(--line);flex-wrap:wrap;align-items:center;font-size:13px;color:var(--dim)}
|
|
38
|
+
.strip button{color:var(--dim);padding:2px 8px;border-radius:var(--r-s)}
|
|
39
|
+
.strip button:hover,.strip button.on{color:var(--ink);background:color-mix(in srgb,var(--dim) 12%,transparent)}
|
|
40
|
+
.strip button.attn{color:var(--warn)}
|
|
41
|
+
|
|
42
|
+
/* board */
|
|
43
|
+
main{flex:1;display:grid;grid-template-columns:repeat(5,minmax(200px,1fr));gap:20px;padding:16px 24px;overflow:auto;align-content:start}
|
|
44
|
+
section h2{margin:0;font-size:12px;color:var(--ink);font-weight:600;display:flex;align-items:center;gap:8px;height:30px}
|
|
45
|
+
section h2 i{font-style:normal;font-weight:400}
|
|
46
|
+
section h2 .ib{margin-left:auto}
|
|
47
|
+
ul{list-style:none;margin:0;padding:8px 0 0;display:flex;flex-direction:column;gap:8px}
|
|
48
|
+
|
|
49
|
+
/* card, Linear grammar in our palette
|
|
50
|
+
line 1 status icon · id (dim mono) · ⋯ menu on hover
|
|
51
|
+
line 2 title, regular weight, wraps to 2 lines
|
|
52
|
+
line 3 chips, only when set
|
|
53
|
+
Card ground is --bg-2 on a --bg column. Actions never sit on the card. */
|
|
54
|
+
.card{background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r);padding:10px 12px;cursor:pointer;display:flex;flex-direction:column;gap:6px;position:relative}
|
|
55
|
+
.card:hover{border-color:var(--dim)}
|
|
56
|
+
.card.open{border-color:var(--accent)}
|
|
57
|
+
.c-id{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--dim);min-height:20px}
|
|
58
|
+
.c-id .st{width:14px;height:14px;display:grid;place-items:center;flex-shrink:0}
|
|
59
|
+
.c-id .st svg{width:14px;height:14px;stroke-width:1.8}
|
|
60
|
+
.c-id .idt{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-variant-numeric:tabular-nums}
|
|
61
|
+
.c-id .age{font-variant-numeric:tabular-nums}
|
|
62
|
+
.c-id .more{display:none;margin:-4px -6px -4px 0}
|
|
63
|
+
.card:hover .more,.card:focus-within .more,.card.menu .more{display:flex}
|
|
64
|
+
.card:hover .age,.card.menu .age{display:none}
|
|
65
|
+
.c-title{font-size:13px;line-height:1.45;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;color:var(--ink)}
|
|
66
|
+
.c-chips{display:flex;gap:6px;flex-wrap:wrap}
|
|
67
|
+
.c-chips:empty{display:none}
|
|
68
|
+
.chip{display:inline-flex;align-items:center;gap:5px;height:20px;padding:0 7px;border-radius:var(--r-s);font-size:11px;color:var(--dim);background:color-mix(in srgb,var(--dim) 12%,transparent)}
|
|
69
|
+
.chip i{width:6px;height:6px;border-radius:50%;background:var(--dim)}
|
|
70
|
+
.chip svg{width:11px;height:11px;stroke-width:1.7}
|
|
71
|
+
.chip.warn{color:var(--warn);background:color-mix(in srgb,var(--warn) 14%,transparent)}.chip.warn i{background:var(--warn)}
|
|
72
|
+
.chip.accent{color:var(--accent);background:color-mix(in srgb,var(--accent) 14%,transparent)}.chip.accent i{background:var(--accent)}
|
|
73
|
+
.chip.run{color:var(--run);background:color-mix(in srgb,var(--run) 14%,transparent)}.chip.run i{background:var(--run)}
|
|
74
|
+
.st.d-idea{color:var(--dim)}.st.d-run{color:var(--run)}.st.d-stall{color:var(--dim)}.st.d-wait{color:var(--warn)}.st.d-done{color:var(--ok)}.st.d-aband{color:var(--dim)}
|
|
75
|
+
.d-run{color:var(--run)}.d-wait{color:var(--warn)}.d-done{color:var(--ok)}
|
|
76
|
+
.pulse{display:inline-block;width:7px;height:7px;border-radius:50%;background:currentColor;vertical-align:middle}
|
|
77
|
+
.d-run .pulse,.st.d-run svg{animation:pl 1.6s infinite}
|
|
78
|
+
@keyframes pl{50%{opacity:.35}}
|
|
79
|
+
/* hover menu */
|
|
80
|
+
.menu-pop{position:absolute;right:8px;top:30px;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r);padding:4px;z-index:2;min-width:140px;display:flex;flex-direction:column}
|
|
81
|
+
.menu-pop button{text-align:left;padding:6px 10px;font-size:12px;color:var(--ink);border-radius:var(--r-s);display:flex;gap:8px;align-items:center}
|
|
82
|
+
.menu-pop button:hover{background:color-mix(in srgb,var(--dim) 12%,transparent)}
|
|
83
|
+
.menu-pop button.danger{color:var(--danger)}
|
|
84
|
+
.menu-pop hr{border:0;border-top:1px solid var(--line);margin:4px 0}
|
|
85
|
+
|
|
86
|
+
/* inline forms replace a card while editing */
|
|
87
|
+
.inl{display:flex;flex-direction:column;gap:8px;padding:10px 12px;background:var(--bg-2);border:1px solid var(--accent);border-radius:var(--r)}
|
|
88
|
+
.inl input{width:100%;border:1px solid var(--line);border-radius:var(--r);padding:7px 10px;background:var(--bg-2)}
|
|
89
|
+
.inl input::placeholder{color:var(--dim)}
|
|
90
|
+
.inl .row{display:flex;gap:6px;justify-content:flex-end}
|
|
91
|
+
.inl .q{font-size:13px}.inl .q.dim{color:var(--dim)}
|
|
92
|
+
.empty{grid-column:1/-1;display:flex;flex-direction:column;align-items:center;gap:14px;padding-block:60px;color:var(--dim)}
|
|
93
|
+
.mascot{width:120px;height:112px;background:var(--ink);-webkit-mask:url(data:image/png;base64,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) center/contain no-repeat;mask:url(data:image/png;base64,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) center/contain no-repeat;animation:fi .2s}
|
|
94
|
+
@keyframes fi{from{opacity:0}}
|
|
95
|
+
|
|
96
|
+
/* footer */
|
|
97
|
+
.ftr{display:flex;justify-content:space-between;gap:16px;padding:8px 24px;border-top:1px solid var(--line);color:var(--dim);font-size:12px;flex-wrap:wrap;align-items:center}
|
|
98
|
+
.ftr span+span{margin-left:14px}.ftr .st svg{width:12px;height:12px}
|
|
99
|
+
.ftr a{color:var(--dim);cursor:pointer;text-decoration:underline}
|
|
100
|
+
|
|
101
|
+
/* side panel */
|
|
102
|
+
.scrim{position:fixed;inset:0;background:color-mix(in srgb,var(--bg) 55%,transparent);display:none}
|
|
103
|
+
.scrim.on{display:block}
|
|
104
|
+
aside{position:fixed;top:0;right:0;bottom:0;width:480px;max-width:100%;background:var(--bg-2);border-left:1px solid var(--line);display:flex;flex-direction:column;transform:translateX(100%);transition:transform .18s}
|
|
105
|
+
aside.open{transform:none}
|
|
106
|
+
aside header{display:flex;align-items:flex-start;gap:12px;padding:16px 20px;border-bottom:1px solid var(--line)}
|
|
107
|
+
aside header .t{flex:1;min-width:0}
|
|
108
|
+
aside header b{display:block;font-size:15px}
|
|
109
|
+
aside header .sub{color:var(--dim);font-size:12px;margin-top:2px}
|
|
110
|
+
aside nav{display:flex;border-bottom:1px solid var(--line);padding-inline:8px}
|
|
111
|
+
aside nav:empty{display:none}
|
|
112
|
+
aside nav button{padding:10px 12px;color:var(--dim);border-bottom:2px solid transparent;margin-bottom:-1px}
|
|
113
|
+
aside nav button:hover{color:var(--ink)}
|
|
114
|
+
aside nav button.on{color:var(--ink);border-bottom-color:var(--accent)}
|
|
115
|
+
aside .body{padding:20px;overflow:auto;flex:1}
|
|
116
|
+
aside .foot{padding:14px 20px;border-top:1px solid var(--line);display:flex;gap:8px;flex-wrap:wrap}
|
|
117
|
+
aside .foot:empty{display:none}
|
|
118
|
+
.md h3{font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--dim);margin:18px 0 6px}.md h3:first-child{margin-top:0}
|
|
119
|
+
.md p{margin:0 0 10px;max-width:62ch}.md ol,.md ul{padding-left:1.4em;margin:0 0 10px}.md li{margin:3px 0}
|
|
120
|
+
.md pre{background:var(--bg);border:1px solid var(--line);border-radius:var(--r-s);padding:8px 10px;overflow-x:auto;margin:0 0 8px;font-size:12px}
|
|
121
|
+
.md h1,.md h2{font-size:13px;margin:16px 0 6px}.md h1:first-child,.md h2:first-child{margin-top:0}.md code{background:var(--bg);padding:0 4px;border-radius:var(--r-s)}.md table{border-collapse:collapse;font-size:12px;margin:0 0 10px}.md td,.md th{border:1px solid var(--line);padding:3px 8px;text-align:left}.md hr{border:0;border-top:1px solid var(--line);margin:12px 0}
|
|
122
|
+
.find{border-bottom:1px solid var(--line);padding:10px 0;display:flex;gap:10px;align-items:flex-start}
|
|
123
|
+
.find input{margin-top:4px}
|
|
124
|
+
.find .sev{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--warn)}.find .sev.hi{color:var(--danger)}
|
|
125
|
+
.find .loc{color:var(--dim);font-size:12px;margin-left:8px}
|
|
126
|
+
.set{display:grid;grid-template-columns:auto 1fr;gap:10px 20px;align-items:center;font-size:13px}
|
|
127
|
+
.set label{color:var(--dim)}
|
|
128
|
+
.set .opts{display:flex;gap:4px}
|
|
129
|
+
.set .opts button{border:1px solid var(--line);border-radius:var(--r);padding:3px 10px;color:var(--dim)}
|
|
130
|
+
.set .opts button.on{color:var(--ink);border-color:var(--ink)}
|
|
131
|
+
.toast{position:fixed;left:24px;bottom:52px;background:var(--ink);color:var(--bg);border-radius:var(--r);padding:8px 14px;font-size:13px;opacity:0;transition:opacity .2s;pointer-events:none}
|
|
132
|
+
.toast.on{opacity:1}
|
|
133
|
+
.help{position:fixed;inset:0;display:none;align-items:center;justify-content:center;background:color-mix(in srgb,var(--bg) 70%,transparent)}
|
|
134
|
+
.help.on{display:flex}.help div{background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r);padding:20px 24px;line-height:2}
|
|
135
|
+
.help kbd{display:inline-block;width:7ch;color:var(--accent)}
|
|
136
|
+
@media(max-width:900px){main{grid-template-columns:1fr;padding-inline:16px;gap:8px}.hdr,.strip,.ftr{padding-inline:16px}section h2{position:sticky;top:0;background:var(--bg);z-index:1}.more{display:flex}.card:hover .age{display:inline}}
|
|
137
|
+
@media(prefers-reduced-motion:reduce){aside,.toast,.mascot,.d-run .pulse{transition:none;animation:none}}
|
|
138
|
+
</style>
|
|
139
|
+
</head>
|
|
140
|
+
<body>
|
|
141
|
+
|
|
142
|
+
<svg style="display:none"><defs>
|
|
143
|
+
<symbol id="i-plus" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></symbol>
|
|
144
|
+
<symbol id="i-gear" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></symbol>
|
|
145
|
+
<symbol id="i-play" viewBox="0 0 24 24"><path d="M6 4l14 8-14 8z"/></symbol>
|
|
146
|
+
<symbol id="i-pen" viewBox="0 0 24 24"><path d="M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z"/></symbol>
|
|
147
|
+
<symbol id="i-x" viewBox="0 0 24 24"><path d="M18 6L6 18M6 6l12 12"/></symbol>
|
|
148
|
+
<symbol id="i-trash" viewBox="0 0 24 24"><path d="M3 6h18M8 6V4h8v2M6 6l1 14h10l1-14M10 11v6M14 11v6"/></symbol>
|
|
149
|
+
<symbol id="i-close" viewBox="0 0 24 24"><path d="M18 6L6 18M6 6l12 12"/></symbol>
|
|
150
|
+
<symbol id="i-more" viewBox="0 0 24 24"><circle cx="5" cy="12" r="1.2" fill="currentColor"/><circle cx="12" cy="12" r="1.2" fill="currentColor"/><circle cx="19" cy="12" r="1.2" fill="currentColor"/></symbol>
|
|
151
|
+
<symbol id="s-idea" viewBox="0 0 16 16"><circle cx="8" cy="8" r="6" stroke-dasharray="2 2.5"/></symbol>
|
|
152
|
+
<symbol id="s-run" viewBox="0 0 16 16"><circle cx="8" cy="8" r="6"/><path d="M8 2a6 6 0 0 1 0 12z" fill="currentColor" stroke="none"/></symbol>
|
|
153
|
+
<symbol id="s-stall" viewBox="0 0 16 16"><circle cx="8" cy="8" r="6"/><path d="M8 2a6 6 0 0 1 0 12z" fill="currentColor" stroke="none" opacity=".35"/></symbol>
|
|
154
|
+
<symbol id="s-wait" viewBox="0 0 16 16"><circle cx="8" cy="8" r="6"/><path d="M8 5v3.5M8 11v.1"/></symbol>
|
|
155
|
+
<symbol id="s-done" viewBox="0 0 16 16"><circle cx="8" cy="8" r="6" fill="currentColor" stroke="none"/><path d="M5.5 8l1.8 1.8L10.8 6" stroke="var(--bg-2)"/></symbol>
|
|
156
|
+
<symbol id="s-aband" viewBox="0 0 16 16"><circle cx="8" cy="8" r="6"/><path d="M5.5 5.5l5 5M10.5 5.5l-5 5"/></symbol>
|
|
157
|
+
<!-- chip marks: agents and run kinds. Simple strokes, no trademark art. -->
|
|
158
|
+
<symbol id="c-claude" viewBox="0 0 16 16"><path d="M8 2v12M2 8h12M3.8 3.8l8.4 8.4M12.2 3.8l-8.4 8.4"/></symbol>
|
|
159
|
+
<symbol id="c-codex" viewBox="0 0 16 16"><path d="M8 2l5.2 3v6L8 14l-5.2-3V5z"/><circle cx="8" cy="8" r="2"/></symbol>
|
|
160
|
+
<symbol id="c-agy" viewBox="0 0 16 16"><path d="M8 13V4M4.5 7.5L8 4l3.5 3.5M4 13h8"/></symbol>
|
|
161
|
+
<symbol id="c-opencode" viewBox="0 0 16 16"><path d="M3 4l4 4-4 4M8.5 12H13"/></symbol>
|
|
162
|
+
<symbol id="c-gemini" viewBox="0 0 16 16"><path d="M8 2c0 4 2 6 6 6-4 0-6 2-6 6 0-4-2-6-6-6 4 0 6-2 6-6z"/></symbol>
|
|
163
|
+
<symbol id="c-cursor" viewBox="0 0 16 16"><path d="M4 3l9 5-4 1-1 4z"/></symbol>
|
|
164
|
+
<symbol id="c-full" viewBox="0 0 16 16"><circle cx="5.5" cy="8" r="3"/><circle cx="11" cy="8" r="3"/></symbol>
|
|
165
|
+
<symbol id="c-lite" viewBox="0 0 16 16"><circle cx="8" cy="8" r="3"/></symbol>
|
|
166
|
+
</defs></svg>
|
|
167
|
+
|
|
168
|
+
<div class="hdr">
|
|
169
|
+
<pre class="mark" aria-label="kaizen"><span>█ █ ▄▀█ █ ▀▀█ █▀▀ █▄ █</span>
|
|
170
|
+
<span>█▀▄ █▀█ █ ▄▀ ██▄ █ ▀█</span>
|
|
171
|
+
<span>█ █ █ █ █ █▄▄ █▄▄ █ █</span></pre>
|
|
172
|
+
<select class="sel" id="proj" aria-label="project"></select>
|
|
173
|
+
<span class="sp"></span>
|
|
174
|
+
<button class="btn pri" id="newtop">New idea</button>
|
|
175
|
+
<button class="ib" id="settings" title="Settings" aria-label="Settings"><svg><use href="#i-gear"/></svg></button>
|
|
176
|
+
</div>
|
|
177
|
+
<div class="strip" id="strip"></div>
|
|
178
|
+
|
|
179
|
+
<main id="board" aria-live="polite"></main>
|
|
180
|
+
|
|
181
|
+
<div class="ftr"><span id="legend"></span><span><a id="kbd">Keyboard shortcuts</a></span></div>
|
|
182
|
+
|
|
183
|
+
<div class="scrim" id="scrim"></div>
|
|
184
|
+
<aside id="panel" aria-label="detail">
|
|
185
|
+
<header><div class="t"><b id="ptitle"></b><div class="sub" id="psub"></div></div><button class="ib" id="pclose" aria-label="Close" title="Close"><svg><use href="#i-close"/></svg></button></header>
|
|
186
|
+
<nav id="ptabs"></nav>
|
|
187
|
+
<div class="body md" id="pbody"></div>
|
|
188
|
+
<div class="foot" id="pfoot"></div>
|
|
189
|
+
</aside>
|
|
190
|
+
<div class="toast" id="toast" role="status"></div>
|
|
191
|
+
<div class="help" id="help"><div>
|
|
192
|
+
Optional. Everything here is also a button.<br>
|
|
193
|
+
<kbd>n</kbd>new idea<br><kbd>e</kbd>edit<br><kbd>x</kbd>reject / abandon<br><kbd>r</kbd>run<br><kbd>a</kbd>all projects<br><kbd>Esc</kbd>close<br><kbd>?</kbd>this list</div></div>
|
|
194
|
+
|
|
195
|
+
<script>
|
|
196
|
+
// The page never touches the filesystem. Everything comes from /state and the run
|
|
197
|
+
// files behind /run/:id; every change goes through a POST and comes back as an SSE
|
|
198
|
+
// tick that reloads the board. See design.md for the visual rules.
|
|
199
|
+
const $=id=>document.getElementById(id);
|
|
200
|
+
const esc=s=>String(s).replace(/[&<>"]/g,c=>({"&":"&","<":"<",">":">",'"':"""})[c]);
|
|
201
|
+
const ico=n=>`<svg><use href="#i-${n}"/></svg>`;
|
|
202
|
+
const m=60e3,h=3600e3,d=86400e3;
|
|
203
|
+
const age=(t,now)=>{const x=now-t;return x<h?Math.round(x/m)+"m":x<d?Math.round(x/h)+"h":Math.round(x/d)+"d"};
|
|
204
|
+
const NAMES={claude:"Claude Code",codex:"Codex",agy:"Antigravity",opencode:"OpenCode",gemini:"Gemini",cursor:"Cursor"};
|
|
205
|
+
const MARKS={claude:1,codex:1,agy:1,opencode:1,gemini:1,cursor:1,full:1,lite:1};
|
|
206
|
+
const COLS=["Ideas","Planning","Building","Review","Done"];
|
|
207
|
+
// state.ts statuses onto the icon set in the <defs> above.
|
|
208
|
+
const ICON={idea:"idea",starting:"start",running:"run",stalled:"stall",waiting:"wait",done:"done",abandoned:"aband"};
|
|
209
|
+
const WORD={idea:"idea",starting:"starting",running:"running",stalled:"stalled",waiting:"waiting on you",done:"done",abandoned:"abandoned"};
|
|
210
|
+
|
|
211
|
+
let S={project:null,projects:[],cards:[],now:Date.now(),all:false};
|
|
212
|
+
let all=new URLSearchParams(location.search).get("all")==="1",openKey=null,mode=null,modeKey=null,filter=null,menuKey=null,tab=0,runDoc=null;
|
|
213
|
+
const wasAwaiting=new Map();let first=true;
|
|
214
|
+
|
|
215
|
+
const key=c=>c.kind==="idea"?"idea:"+c.state+":"+c.text:"run:"+c.state+":"+c.id;
|
|
216
|
+
|
|
217
|
+
async function load(){
|
|
218
|
+
const r=await fetch("/state"+(all?"?all=1":""));S=await r.json();
|
|
219
|
+
for(const c of S.cards){if(c.kind!=="run")continue;const k=key(c),before=wasAwaiting.get(k);
|
|
220
|
+
if(!first&&before===null&&c.awaiting)notifyUser("kaizen — waiting on you",c.text+" · "+c.awaiting);wasAwaiting.set(k,c.awaiting)}
|
|
221
|
+
first=false;render();
|
|
222
|
+
if(openKey&&runDoc){const c=S.cards.find(x=>key(x)===openKey);if(c)openRun(c,true)}}
|
|
223
|
+
async function post(path,body){const r=await fetch(path,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(body)});const j=await r.json().catch(()=>({}));if(!r.ok){toast(j.error||j.why||"failed");if(j.manual)console.log("run manually:",j.manual)}return j}
|
|
224
|
+
|
|
225
|
+
function card(c){
|
|
226
|
+
const k=key(c),el=document.createElement("div");el.className="card"+(k===openKey?" open":"")+(k===menuKey?" menu":"");el.tabIndex=0;el.setAttribute("role","button");
|
|
227
|
+
const st=ICON[c.status]||"idea",idt=c.kind==="idea"?"idea":c.text;
|
|
228
|
+
const chips=[];
|
|
229
|
+
if(c.status==="waiting")chips.push(`<span class="chip warn"><i></i>${esc(c.awaiting||"needs you")}</span>`);
|
|
230
|
+
else if(c.status==="running")chips.push(`<span class="chip run"><i></i>running</span>`);
|
|
231
|
+
if(all)chips.push(`<span class="chip">${esc(c.where)}</span>`);
|
|
232
|
+
el.innerHTML=`<div class="c-id"><span class="st d-${st}"><svg><use href="#s-${st}"/></svg></span><span class="idt">${esc(idt)}</span><span class="age">${c.kind==="run"?age(c.moved||S.now,S.now):""}</span><button class="ib sm more" data-a="menu" title="Actions" aria-label="Actions">${ico("more")}</button></div>
|
|
233
|
+
<div class="c-title">${esc(c.kind==="idea"?c.text:(c.title||c.id))}</div>
|
|
234
|
+
<div class="c-chips">${chips.join("")}</div>`;
|
|
235
|
+
if(k===menuKey){
|
|
236
|
+
const items=[];
|
|
237
|
+
if(c.kind==="idea"&&c.status==="idea")items.push(["run","Run",ico("play")],["edit","Edit",ico("pen")],["reject","Reject",ico("x")],null,["delete","Delete",ico("trash"),"danger"]);
|
|
238
|
+
else if(c.kind==="idea")items.push(["delete","Remove",ico("trash"),"danger"]);
|
|
239
|
+
else if(c.status==="waiting"||c.status==="running"||c.status==="stalled")items.push(["open","Open",ico("play")],null,["reject","Abandon",ico("x"),"danger"]);
|
|
240
|
+
else items.push(["open","Open",ico("play")]);
|
|
241
|
+
el.innerHTML+=`<div class="menu-pop">${items.map(i=>i?`<button data-a="${i[0]}" class="${i[3]||""}">${i[2]}${i[1]}</button>`:"<hr>").join("")}</div>`}
|
|
242
|
+
el.querySelectorAll("[data-a]").forEach(bt=>bt.onclick=e=>{e.stopPropagation();const a=bt.dataset.a;menuKey=null;if(a==="menu"){menuKey=k;render();return}if(a==="open"){open(c);return}mode=a;modeKey=k;render()});
|
|
243
|
+
el.onclick=()=>{if(menuKey){menuKey=null;render();return}open(c)};el.onkeydown=e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();open(c)}};
|
|
244
|
+
return el;}
|
|
245
|
+
|
|
246
|
+
function inlineForm(c){
|
|
247
|
+
const li=document.createElement("li");li.className="inl";const dir=c.state;
|
|
248
|
+
if(mode==="edit"){li.innerHTML=`<input value="${esc(c.text)}"><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Save</button></div>`;li.querySelector('[data-a=s]').onclick=async()=>{const next=li.querySelector("input").value.trim();mode=null;if(next&&next!==c.text)await post("/inbox",{op:"edit",dir,text:c.text,next});load()}}
|
|
249
|
+
if(mode==="reject"){const idea=c.kind==="idea";li.innerHTML=`<div class="q">${idea?"Reject this idea?":"Abandon this run?"} The reason is recorded.</div><input placeholder="Reason"><div class="row"><button class="btn ghost" data-a="c">Keep it</button><button class="btn warn" data-a="s">${idea?"Reject":"Abandon"}</button></div>`;li.querySelector('[data-a=s]').onclick=async()=>{const why=li.querySelector("input").value.trim();if(!why){toast("A reason is required");return}mode=null;const r=idea?await post("/inbox",{op:"reject",dir,text:c.text,reason:why}):await post("/abort",{dir,id:c.id,why});if(r.ok)toast(idea?"Rejected":"Abandoned, reason recorded");load()}}
|
|
250
|
+
if(mode==="delete"){li.innerHTML=`<div class="q">Delete "${esc(c.text)}"?</div><div class="q dim">Not recorded anywhere.</div><div class="row"><button class="btn ghost" data-a="c">Keep it</button><button class="btn danger" data-a="s">Delete</button></div>`;li.querySelector('[data-a=s]').onclick=async()=>{mode=null;const r=await post("/inbox",{op:"delete",dir,text:c.text});if(r.ok)toast("Deleted");load()}}
|
|
251
|
+
if(mode==="run"){li.innerHTML=`<div class="q">Start a run for "${esc(c.text)}"</div><div class="q dim">Opens your agent in a new terminal with the request typed in.</div><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn" data-a="l">Lite</button><button class="btn pri" data-a="s">Full run</button></div>`;li.querySelector('[data-a=s]').onclick=()=>startRun(c,"full");li.querySelector('[data-a=l]').onclick=()=>startRun(c,"lite")}
|
|
252
|
+
li.querySelector('[data-a=c]').onclick=()=>{mode=null;render()};
|
|
253
|
+
const inp=li.querySelector("input");if(inp){inp.onkeydown=e=>{if(e.key==="Enter")li.querySelector('[data-a=s]').click();if(e.key==="Escape"){mode=null;render()}};setTimeout(()=>inp.focus())}
|
|
254
|
+
return li;}
|
|
255
|
+
|
|
256
|
+
async function startRun(c,kind){
|
|
257
|
+
mode=null;askNotify();
|
|
258
|
+
const r=await post("/run",{dir:c.state,text:c.text,kind});
|
|
259
|
+
if(r.ok)toast(kind+" run opened in your terminal: "+r.agent.name);
|
|
260
|
+
load()}
|
|
261
|
+
|
|
262
|
+
function render(){
|
|
263
|
+
const cs=S.cards,b=$("board");b.innerHTML="";
|
|
264
|
+
const n=s=>cs.filter(c=>c.status===s).length;
|
|
265
|
+
const strip=[];
|
|
266
|
+
if(n("waiting"))strip.push(`<button class="attn ${filter==="waiting"?"on":""}" data-f="waiting">● ${n("waiting")} waiting on you</button>`);
|
|
267
|
+
strip.push(`<button class="${filter==="running"?"on":""}" data-f="running"><span class="d-run"><span class="pulse"></span></span> ${n("running")} running</button>`);
|
|
268
|
+
if(n("stalled"))strip.push(`<button class="${filter==="stalled"?"on":""}" data-f="stalled">◐ ${n("stalled")} stalled</button>`);
|
|
269
|
+
strip.push(`<span class="sp"></span><span>${cs.filter(c=>c.kind==="idea").length} ideas · ${cs.filter(c=>c.kind==="run").length} runs</span>`);
|
|
270
|
+
$("strip").innerHTML=strip.join("");
|
|
271
|
+
$("strip").querySelectorAll("button").forEach(c=>c.onclick=()=>{filter=filter===c.dataset.f?null:c.dataset.f;render()});
|
|
272
|
+
const sel=$("proj");sel.innerHTML=(S.project?`<option value="cur">${esc(S.project)}</option>`:"")+`<option value="all">All projects</option>`;sel.value=all||!S.project?"all":"cur";
|
|
273
|
+
if(!cs.length&&mode!=="new"){b.innerHTML='<div class="empty"><div class="mascot"></div><div>Nothing here yet.</div><button class="btn pri" id="newempty">New idea</button></div>';$("newempty").onclick=()=>{mode="new";render()};}
|
|
274
|
+
COLS.forEach((name,ci)=>{
|
|
275
|
+
const s=document.createElement("section");const list=cs.filter(c=>c.column===ci&&(!filter||c.status===filter));
|
|
276
|
+
s.innerHTML=`<h2>${name}<i>${list.length||""}</i>${ci===0?`<button class="ib sm" title="New idea" aria-label="New idea">${ico("plus")}</button>`:""}</h2>`;
|
|
277
|
+
if(ci===0)s.querySelector(".ib").onclick=()=>{mode="new";render()};
|
|
278
|
+
const ul=document.createElement("ul");
|
|
279
|
+
if(ci===0&&mode==="new"){const f=document.createElement("li");f.className="inl";f.innerHTML=`<input id="newidea" placeholder="One line, like a commit subject"><div class="row"><button class="btn ghost" id="nc">Cancel</button><button class="btn pri" id="ns">Add to inbox</button></div>`;ul.appendChild(f);
|
|
280
|
+
const inp=f.querySelector("input");const save=async()=>{const text=inp.value.trim();if(!text)return;mode=null;const r=await post("/inbox",{op:"add",text});if(r.ok)toast("Added to inbox.md");load()};
|
|
281
|
+
f.querySelector("#ns").onclick=save;f.querySelector("#nc").onclick=()=>{mode=null;render()};inp.onkeydown=e=>{if(e.key==="Enter")save();if(e.key==="Escape"){mode=null;render()}};setTimeout(()=>inp.focus())}
|
|
282
|
+
list.forEach(c=>{if(modeKey===key(c)&&mode){ul.appendChild(inlineForm(c));return}const li=document.createElement("li");li.appendChild(card(c));ul.appendChild(li)});
|
|
283
|
+
s.appendChild(ul);b.appendChild(s)});
|
|
284
|
+
const seen=new Map();cs.forEach(c=>seen.set(c.status,1));
|
|
285
|
+
$("legend").innerHTML=[...seen.keys()].map(s=>`<span class="st d-${ICON[s]}" style="display:inline-grid;vertical-align:-2px"><svg><use href="#s-${ICON[s]}"/></svg></span> ${WORD[s]||s}`).join(" ");}
|
|
286
|
+
|
|
287
|
+
// ---- detail panel
|
|
288
|
+
function open(c){
|
|
289
|
+
openKey=key(c);$("panel").classList.add("open");$("scrim").classList.add("on");$("ptitle").textContent=c.text;render();
|
|
290
|
+
if(c.kind==="idea"){runDoc=null;$("psub").textContent="idea · inbox.md · "+c.where;$("ptabs").innerHTML="";$("pbody").innerHTML=`<p>${esc(c.text)}</p><p style="color:var(--dim)">Run turns this into a plan you approve before any work starts.</p>`;
|
|
291
|
+
const foot=$("pfoot");foot.innerHTML=c.status==="idea"?`<button class="btn pri" data-a="run">Run</button><button class="btn" data-a="edit">Edit</button><button class="btn" data-a="reject">Reject</button><button class="btn ghost danger" data-a="delete">Delete</button>`:`<span style="color:var(--dim);font-size:12px">Starting: a run was launched for this idea.</span>`;
|
|
292
|
+
foot.querySelectorAll("[data-a]").forEach(b=>b.onclick=()=>{close();mode=b.dataset.a;modeKey=openKey;render()});return}
|
|
293
|
+
openRun(c,false)}
|
|
294
|
+
async function openRun(c,refresh){
|
|
295
|
+
if(!refresh||!runDoc||runDoc.id!==c.id){runDoc=await fetch(`/run/${encodeURIComponent(c.id)}?dir=${encodeURIComponent(c.state)}`).then(r=>r.json())}
|
|
296
|
+
if(openKey!==key(c))return;
|
|
297
|
+
$("psub").textContent=c.id+" · "+(WORD[c.status]||c.status)+(c.awaiting?" · "+c.awaiting:"");
|
|
298
|
+
const tabs=[["Request","request"],["Plan","plan"],["Impl","impl"],["Review","review"],["Backlog","backlog"]];
|
|
299
|
+
$("ptabs").innerHTML=tabs.map((t,i)=>`<button class="${i===tab?"on":""}" data-i="${i}">${t[0]}</button>`).join("");
|
|
300
|
+
[...$("ptabs").children].forEach(b=>b.onclick=()=>{tab=+b.dataset.i;openRun(c,true)});
|
|
301
|
+
const k=tabs[tab][1],doc=runDoc[k];
|
|
302
|
+
$("pbody").innerHTML=doc?md(doc):`<p style="color:var(--dim)">No ${({request:"00-request.md",plan:"01-plan.md",impl:"03-impl.md",review:"04-review.md",backlog:"06-backlog.md"})[k]} yet.</p>`;
|
|
303
|
+
const foot=$("pfoot");
|
|
304
|
+
if(c.status==="waiting")foot.innerHTML=`<span style="color:var(--dim);font-size:12px;flex:1">Waiting on <b>${esc(c.awaiting)}</b>. Answer in the terminal running this run.</span><button class="btn ghost" data-a="ab">Abandon</button>`;
|
|
305
|
+
else if(c.status==="done"||c.status==="abandoned")foot.innerHTML="";
|
|
306
|
+
else foot.innerHTML=`<button class="btn ghost" data-a="ab">Abandon run</button>`;
|
|
307
|
+
foot.querySelectorAll("[data-a]").forEach(b=>b.onclick=()=>{close();mode="reject";modeKey=key(c);render()})}
|
|
308
|
+
function close(){openKey=null;runDoc=null;$("panel").classList.remove("open");$("scrim").classList.remove("on");render()}
|
|
309
|
+
$("pclose").onclick=close;$("scrim").onclick=close;
|
|
310
|
+
|
|
311
|
+
// Enough markdown for kaizen's own files: headings, lists, fences, tables,
|
|
312
|
+
// inline code, bold. Everything is escaped first.
|
|
313
|
+
function md(src){
|
|
314
|
+
const lines=src.split("\n"),out=[];let i=0,list=null;
|
|
315
|
+
const inl=s=>esc(s).replace(/`([^`]+)`/g,"<code>$1</code>").replace(/\*\*([^*]+)\*\*/g,"<b>$1</b>");
|
|
316
|
+
const endList=()=>{if(list){out.push(`</${list}>`);list=null}};
|
|
317
|
+
while(i<lines.length){const l=lines[i];
|
|
318
|
+
if(/^```/.test(l)){endList();const buf=[];i++;while(i<lines.length&&!/^```/.test(lines[i]))buf.push(lines[i++]);out.push(`<pre>${esc(buf.join("\n"))}</pre>`);i++;continue}
|
|
319
|
+
const hm=/^(#{1,6})\s+(.*)$/.exec(l);if(hm){endList();out.push(`<h${Math.min(hm[1].length,3)}>${inl(hm[2])}</h${Math.min(hm[1].length,3)}>`);i++;continue}
|
|
320
|
+
if(/^---+$/.test(l.trim())){endList();out.push("<hr>");i++;continue}
|
|
321
|
+
if(/^\|/.test(l)){endList();const rows=[];while(i<lines.length&&/^\|/.test(lines[i]))rows.push(lines[i++]);const cells=r=>r.replace(/^\||\|$/g,"").split("|").map(c=>inl(c.trim()));out.push("<table>"+rows.filter(r=>!/^\|\s*-/.test(r)).map((r,n)=>`<tr>${cells(r).map(c=>n?`<td>${c}</td>`:`<th>${c}</th>`).join("")}</tr>`).join("")+"</table>");continue}
|
|
322
|
+
const li=/^\s*(?:[-*]|\d+\.)\s+(.*)$/.exec(l);if(li){const t=/^\s*\d+\./.test(l)?"ol":"ul";if(list!==t){endList();out.push(`<${t}>`);list=t}out.push(`<li>${inl(li[1])}</li>`);i++;continue}
|
|
323
|
+
if(!l.trim()){endList();i++;continue}
|
|
324
|
+
endList();const buf=[l];i++;while(i<lines.length&&lines[i].trim()&&!/^(#|```|\||\s*[-*]\s|\s*\d+\.\s|---)/.test(lines[i]))buf.push(lines[i++]);out.push(`<p>${inl(buf.join(" "))}</p>`)}
|
|
325
|
+
endList();return out.join("")}
|
|
326
|
+
|
|
327
|
+
// ---- settings, toast, notifications, keys
|
|
328
|
+
function openSettings(){
|
|
329
|
+
openKey=null;runDoc=null;$("panel").classList.add("open");$("scrim").classList.add("on");$("ptitle").textContent="Settings";$("psub").textContent="theme is this browser's; everything else is kaizen settings in a terminal";$("ptabs").innerHTML="";$("pfoot").innerHTML="";
|
|
330
|
+
const cur=document.documentElement.dataset.theme||"system";
|
|
331
|
+
$("pbody").innerHTML=`<div class="set"><label>Theme</label><div class="opts" id="th">${["system","light","dark"].map(t=>`<button class="${t===cur?"on":""}" data-t="${t}">${t}</button>`).join("")}</div>
|
|
332
|
+
<label>Notifications</label><div class="opts" id="nt"><button>${typeof Notification==="undefined"?"unsupported":Notification.permission==="granted"?"on":"ask"}</button></div></div>
|
|
333
|
+
<p style="color:var(--dim);margin-top:20px;max-width:52ch">Agent, runner, approvals and the rest live in <code>config.yml</code>. Run <code>kaizen settings</code> to change them.</p>`;
|
|
334
|
+
$("th").querySelectorAll("button").forEach(b=>b.onclick=()=>{const t=b.dataset.t;if(t==="system")delete document.documentElement.dataset.theme;else document.documentElement.dataset.theme=t;try{t==="system"?localStorage.removeItem("kz-theme"):localStorage.setItem("kz-theme",t)}catch(e){}openSettings()});
|
|
335
|
+
$("nt").querySelector("button").onclick=()=>{askNotify();setTimeout(openSettings,300)}}
|
|
336
|
+
let tt;function toast(s){const t=$("toast");t.textContent=s;t.classList.add("on");clearTimeout(tt);tt=setTimeout(()=>t.classList.remove("on"),4000)}
|
|
337
|
+
function askNotify(){try{if(typeof Notification!=="undefined"&&Notification.permission==="default")Notification.requestPermission()}catch(e){}}
|
|
338
|
+
function notifyUser(title,body){try{if(typeof Notification!=="undefined"&&Notification.permission==="granted")new Notification(title,{body})}catch(e){}}
|
|
339
|
+
$("proj").onchange=e=>{all=e.target.value==="all";load()};
|
|
340
|
+
$("newtop").onclick=()=>{mode="new";render();$("board").scrollTo(0,0)};
|
|
341
|
+
$("settings").onclick=openSettings;
|
|
342
|
+
try{const t=localStorage.getItem("kz-theme");if(t)document.documentElement.dataset.theme=t}catch(e){}
|
|
343
|
+
$("kbd").onclick=()=>$("help").classList.add("on");$("help").onclick=()=>$("help").classList.remove("on");
|
|
344
|
+
document.addEventListener("click",()=>{if(menuKey){menuKey=null;render()}});
|
|
345
|
+
document.onkeydown=e=>{
|
|
346
|
+
if(e.target.matches("input,select"))return;
|
|
347
|
+
if(e.key==="?"){$("help").classList.toggle("on");return}
|
|
348
|
+
if(e.key==="Escape"){$("help").classList.remove("on");if(mode){mode=null;render()}else close();return}
|
|
349
|
+
if(e.key==="n"){mode="new";render()}
|
|
350
|
+
if(e.key==="a"){all=!all;load()}
|
|
351
|
+
const oc=openKey&&S.cards.find(c=>key(c)===openKey);if(!oc)return;
|
|
352
|
+
if(e.key==="e"&&oc.kind==="idea"){close();mode="edit";modeKey=key(oc);render()}
|
|
353
|
+
if(e.key==="x"&&!["done","abandoned","starting"].includes(oc.status)){close();mode="reject";modeKey=key(oc);render()}
|
|
354
|
+
if(e.key==="r"&&oc.kind==="idea"){close();mode="run";modeKey=key(oc);render()}};
|
|
355
|
+
|
|
356
|
+
// ---- live
|
|
357
|
+
function listen(){const es=new EventSource("/events");es.onmessage=ev=>{if(ev.data==="changed")load()};es.onerror=()=>{es.close();setTimeout(listen,3000)}}
|
|
358
|
+
load();listen();
|
|
359
|
+
</script>
|
|
360
|
+
</body>
|
|
361
|
+
</html>
|