@hacksmith/doraval 0.2.35 โ†’ 0.2.42

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.
@@ -0,0 +1,284 @@
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>dora โ€ข local dashboard</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ :root { color-scheme: dark; }
10
+ body { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
11
+ .section { @apply bg-zinc-900 border border-zinc-800 rounded-2xl p-5; }
12
+ .entry { @apply border-l-4 pl-3 py-1.5; }
13
+ .pb-strong { border-color: #f87171; }
14
+ .pb-friction { border-color: #facc15; }
15
+ .pb-nudge { border-color: #4ade80; }
16
+ .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
17
+ .push { font-variant-numeric: tabular-nums; }
18
+ pre.context { white-space: pre-wrap; font-size: 13px; line-height: 1.45; }
19
+ .staged { background: #27251f; }
20
+ </style>
21
+ </head>
22
+ <body class="bg-zinc-950 text-zinc-200">
23
+ <div class="max-w-6xl mx-auto p-6">
24
+ <div class="flex items-center justify-between mb-6">
25
+ <div class="flex items-center gap-3">
26
+ <div class="text-2xl">๐Ÿฑ</div>
27
+ <div>
28
+ <div class="font-semibold tracking-tight text-xl">doraval</div>
29
+ <div class="text-xs text-zinc-500 -mt-0.5">local dashboard</div>
30
+ </div>
31
+ </div>
32
+ <div class="flex items-center gap-3 text-sm">
33
+ <div id="project-badge" class="px-3 py-1 rounded-full bg-zinc-900 border border-zinc-800 text-xs"></div>
34
+ <button onclick="refreshAll()" class="px-3 py-1 rounded-lg bg-zinc-900 hover:bg-zinc-800 border border-zinc-800 text-xs">Refresh</button>
35
+ <button onclick="window.open('https://github.com/saif-shines/doraval','_blank')" class="px-3 py-1 rounded-lg bg-zinc-900 hover:bg-zinc-800 border border-zinc-800 text-xs">GitHub</button>
36
+ </div>
37
+ </div>
38
+
39
+ <div class="grid grid-cols-1 lg:grid-cols-5 gap-4">
40
+ <!-- Capture -->
41
+ <div class="lg:col-span-2 section">
42
+ <div class="uppercase text-xs tracking-[1px] text-zinc-500 mb-2">Capture decision</div>
43
+ <div class="space-y-3">
44
+ <input id="title" class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2 text-sm placeholder:text-zinc-600 focus:outline-none focus:border-zinc-700" placeholder="Title (e.g. Prefer pure functions for new code)">
45
+
46
+ <div>
47
+ <div class="flex justify-between text-xs mb-1.5 text-zinc-400">
48
+ <div>Pushback</div>
49
+ <div id="pb-value" class="push font-medium">4</div>
50
+ </div>
51
+ <input id="pushback" type="range" min="1" max="10" step="1" value="4" class="w-full accent-zinc-400">
52
+ <div class="flex justify-between text-[10px] text-zinc-500 mt-0.5">
53
+ <div>Nudge</div><div>Friction</div><div>Strong</div>
54
+ </div>
55
+ </div>
56
+
57
+ <input id="tags" class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2 text-sm placeholder:text-zinc-600 focus:outline-none focus:border-zinc-700" placeholder="tags (comma separated) e.g. architecture,cli">
58
+
59
+ <textarea id="rationale" rows="3" class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-4 py-2 text-sm placeholder:text-zinc-600 focus:outline-none focus:border-zinc-700" placeholder="Why does this matter? (optional but recommended)"></textarea>
60
+
61
+ <button onclick="addDecision()"
62
+ class="w-full py-2.5 rounded-2xl bg-white text-black font-medium text-sm active:scale-[0.985] transition">Add to pending</button>
63
+ <div id="add-status" class="text-xs text-emerald-400 h-4"></div>
64
+ </div>
65
+ </div>
66
+
67
+ <!-- What agents see -->
68
+ <div class="lg:col-span-3 section">
69
+ <div class="flex items-center justify-between mb-2">
70
+ <div>
71
+ <span class="uppercase text-xs tracking-[1px] text-zinc-500">What agents will see on next session</span>
72
+ </div>
73
+ <button onclick="copyContext()" class="text-xs px-2.5 py-1 rounded-lg border border-zinc-800 hover:bg-zinc-900">Copy</button>
74
+ </div>
75
+ <pre id="context" class="context bg-black/60 border border-zinc-800 rounded-2xl p-4 text-zinc-300 overflow-auto max-h-[260px]"></pre>
76
+ <div class="mt-2 text-[11px] text-zinc-500">This is the exact payload from <span class="font-mono">dora journal context</span>.</div>
77
+ </div>
78
+
79
+ <!-- Hooks -->
80
+ <div class="lg:col-span-2 section">
81
+ <div class="uppercase text-xs tracking-[1px] text-zinc-500 mb-3">Journal injection hooks</div>
82
+ <div id="hooks" class="space-y-2 text-sm"></div>
83
+ <div class="mt-3 text-[11px] text-zinc-500">Toggles call the same logic as <span class="font-mono">dora journal hook enable/disable</span>. Restart your agent after changes.</div>
84
+ </div>
85
+
86
+ <!-- Journal -->
87
+ <div class="lg:col-span-3 section">
88
+ <div class="flex items-baseline justify-between mb-2">
89
+ <div class="uppercase text-xs tracking-[1px] text-zinc-500">Journal entries</div>
90
+ <div class="text-xs text-zinc-500" id="entry-count"></div>
91
+ </div>
92
+ <div id="entries" class="space-y-2 text-sm max-h-[380px] overflow-auto pr-1"></div>
93
+ </div>
94
+ </div>
95
+
96
+ <div class="mt-6 flex flex-wrap gap-2 text-xs">
97
+ <button onclick="doAction('sync')" class="px-3 py-1.5 rounded-2xl border border-zinc-800 hover:bg-zinc-900">Sync pending โ†’ remote</button>
98
+ <button onclick="doAction('update')" class="px-3 py-1.5 rounded-2xl border border-zinc-800 hover:bg-zinc-900">Update local cache</button>
99
+ <button onclick="doAction('open-dir')" class="px-3 py-1.5 rounded-2xl border border-zinc-800 hover:bg-zinc-900">Open ~/.doraval</button>
100
+ <div class="flex-1"></div>
101
+ <div class="text-zinc-500 self-center">Tip: use the CLI for heavy sync/agent enrichment. This UI is for speed.</div>
102
+ </div>
103
+ </div>
104
+
105
+ <script>
106
+ const $ = (id) => document.getElementById(id);
107
+
108
+ async function fetchJSON(url, opts = {}) {
109
+ const res = await fetch(url, opts);
110
+ if (!res.ok) throw new Error(await res.text());
111
+ return res.json();
112
+ }
113
+
114
+ function setProjectBadge(project) {
115
+ const el = $('project-badge');
116
+ el.textContent = project ? project : 'no project';
117
+ el.className = 'px-3 py-1 rounded-full bg-zinc-900 border border-zinc-800 text-xs ' + (project ? '' : 'text-amber-400');
118
+ }
119
+
120
+ let currentProject = null;
121
+
122
+ async function loadStatus() {
123
+ const s = await fetchJSON('/api/status');
124
+ currentProject = s.project;
125
+ setProjectBadge(currentProject);
126
+ }
127
+
128
+ async function loadContext() {
129
+ const c = await fetchJSON('/api/context');
130
+ $('context').textContent = c.text || '(no active high-pushback decisions yet)';
131
+ }
132
+
133
+ async function loadEntries() {
134
+ const data = await fetchJSON('/api/entries');
135
+ const container = $('entries');
136
+ container.innerHTML = '';
137
+ const all = [...(data.staged || []), ...(data.committed || [])];
138
+
139
+ $('entry-count').textContent = all.length + ' entries';
140
+
141
+ if (all.length === 0) {
142
+ container.innerHTML = '<div class="text-zinc-500 text-sm py-4">No entries. Add your first decision on the left.</div>';
143
+ return;
144
+ }
145
+
146
+ all.forEach(e => {
147
+ const pb = e.pushback ?? 0;
148
+ let cls = 'pb-nudge';
149
+ if (pb >= 7) cls = 'pb-strong';
150
+ else if (pb >= 4) cls = 'pb-friction';
151
+
152
+ const div = document.createElement('div');
153
+ div.className = 'entry ' + cls + (e._staged ? ' staged rounded-r-xl' : '');
154
+ div.innerHTML = `
155
+ <div class="flex gap-2 items-start">
156
+ <div class="push w-6 text-right font-semibold text-zinc-400">${pb}</div>
157
+ <div class="flex-1 min-w-0">
158
+ <div class="font-medium">${e.title}${e._staged ? ' <span class="text-amber-400 text-xs">(staged)</span>' : ''}</div>
159
+ <div class="text-xs text-zinc-500 mt-0.5 line-clamp-2">${(e.rationale||'').replace(/\s+/g,' ').slice(0,160)}</div>
160
+ <div class="mt-1 flex gap-2 text-[10px] text-zinc-500">
161
+ <span>${(e.tags||[]).join(', ') || 'no tags'}</span>
162
+ <span class="text-zinc-600">ยท</span>
163
+ <span>${e.author || 'human'}</span>
164
+ </div>
165
+ </div>
166
+ </div>`;
167
+ container.appendChild(div);
168
+ });
169
+ }
170
+
171
+ async function loadHooks() {
172
+ const h = await fetchJSON('/api/hooks/status');
173
+ const el = $('hooks');
174
+ el.innerHTML = '';
175
+
176
+ const mk = (label, info, isGlobal) => {
177
+ const row = document.createElement('div');
178
+ row.className = 'flex items-center justify-between gap-2 bg-zinc-950 border border-zinc-800 rounded-2xl px-4 py-2';
179
+ row.innerHTML = `
180
+ <div>
181
+ <div class="font-medium">${label}</div>
182
+ <div class="text-[10px] text-zinc-500 font-mono truncate max-w-[260px]">${info.path}</div>
183
+ </div>
184
+ <div>
185
+ <button data-action="${info.enabled ? 'disable' : 'enable'}" data-global="${isGlobal}"
186
+ class="text-xs px-3 py-1 rounded-xl border ${info.enabled ? 'border-emerald-800 text-emerald-400 hover:bg-emerald-950' : 'border-zinc-700 hover:bg-zinc-900'}">
187
+ ${info.enabled ? 'Disable' : 'Enable'}
188
+ </button>
189
+ </div>
190
+ `;
191
+ const btn = row.querySelector('button');
192
+ btn.onclick = async () => {
193
+ const action = btn.dataset.action;
194
+ const g = btn.dataset.global === 'true';
195
+ try {
196
+ await fetchJSON('/api/hooks/' + action, {
197
+ method: 'POST',
198
+ headers: {'content-type': 'application/json'},
199
+ body: JSON.stringify({ global: g })
200
+ });
201
+ await loadHooks();
202
+ } catch (e) {
203
+ alert('Hook change failed: ' + e.message);
204
+ }
205
+ };
206
+ el.appendChild(row);
207
+ };
208
+
209
+ mk('Claude (local)', h.local, false);
210
+ mk('Claude (global ~/.claude)', h.global, true);
211
+ }
212
+
213
+ async function addDecision() {
214
+ const title = $('title').value.trim();
215
+ if (!title) { $('add-status').textContent = 'Title is required'; return; }
216
+ const pushback = parseInt($('pushback').value, 10);
217
+ const tags = $('tags').value.split(',').map(t => t.trim()).filter(Boolean);
218
+ const rationale = $('rationale').value.trim();
219
+
220
+ $('add-status').textContent = 'Saving...';
221
+ try {
222
+ await fetchJSON('/api/add', {
223
+ method: 'POST',
224
+ headers: {'content-type': 'application/json'},
225
+ body: JSON.stringify({ title, pushback, tags, rationale })
226
+ });
227
+ $('add-status').textContent = 'โœ“ Added to pending';
228
+ $('title').value = ''; $('tags').value = ''; $('rationale').value = '';
229
+ await Promise.all([loadEntries(), loadContext()]);
230
+ setTimeout(() => { $('add-status').textContent = ''; }, 1400);
231
+ } catch (e) {
232
+ $('add-status').textContent = 'Error: ' + e.message;
233
+ }
234
+ }
235
+
236
+ async function copyContext() {
237
+ const text = $('context').textContent;
238
+ await navigator.clipboard.writeText(text);
239
+ const t = document.createElement('span');
240
+ t.textContent = ' copied!';
241
+ t.className = 'text-emerald-400 ml-2 text-xs';
242
+ $('context').after(t);
243
+ setTimeout(() => t.remove(), 900);
244
+ }
245
+
246
+ async function refreshAll() {
247
+ await Promise.all([loadEntries(), loadContext(), loadHooks()]);
248
+ }
249
+
250
+ async function doAction(name) {
251
+ if (name === 'open-dir') {
252
+ const s = await fetchJSON('/api/status');
253
+ alert('Open in Finder/Terminal: ' + s.doravalDir);
254
+ return;
255
+ }
256
+ if (name === 'sync' || name === 'update') {
257
+ alert('Use the CLI for now: dora journal ' + name + ' (full git + gh flow)');
258
+ return;
259
+ }
260
+ }
261
+
262
+ function setupSlider() {
263
+ const slider = $('pushback');
264
+ const val = $('pb-value');
265
+ const update = () => { val.textContent = slider.value; };
266
+ slider.addEventListener('input', update);
267
+ update();
268
+ }
269
+
270
+ async function boot() {
271
+ setupSlider();
272
+ await loadStatus();
273
+ await Promise.all([loadContext(), loadEntries(), loadHooks()]);
274
+ // gentle polling so the UI feels alive when you add via CLI too
275
+ setInterval(() => {
276
+ loadEntries().catch(()=>{});
277
+ loadContext().catch(()=>{});
278
+ }, 8000);
279
+ }
280
+
281
+ boot().catch(console.error);
282
+ </script>
283
+ </body>
284
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hacksmith/doraval",
3
- "version": "0.2.35",
3
+ "version": "0.2.42",
4
4
  "author": "Saif",
5
5
  "repository": {
6
6
  "type": "git",
@@ -41,7 +41,7 @@
41
41
  "apps/*"
42
42
  ],
43
43
  "scripts": {
44
- "build": "bun build ./src/cli/index.ts --outfile ./bin/doraval.js --target bun",
44
+ "build": "bun build ./src/cli/index.ts --outfile ./bin/doraval.js --target bun && rm -rf bin/ui && cp -r src/ui bin/ui",
45
45
  "dev": "bun run ./src/cli/index.ts",
46
46
  "test": "bun test",
47
47
  "typecheck": "bunx tsc --noEmit --skipLibCheck",