cmdzero 0.7.0 → 0.7.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 +4 -0
- package/overlay/overlay.js +40 -20
- package/package.json +1 -1
- package/src/server.js +3 -0
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/adamcwade/cmdzero/main/assets/hero.png" alt="CmdZero — tweak your UI live in the browser; changes land in your source" width="900">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# CmdZero
|
|
2
6
|
|
|
3
7
|
Tweak your UI live in the browser — the changes are written straight to your source files.
|
package/overlay/overlay.js
CHANGED
|
@@ -85,8 +85,8 @@
|
|
|
85
85
|
.cz-banner .cz-idle{color:#64748b}
|
|
86
86
|
.cz-wrap{display:flex;flex-direction:column;align-items:flex-end;gap:6px}
|
|
87
87
|
.cz-wrap.cz-expanded{max-height:calc(100vh - 90px);overflow-y:auto;overflow-x:hidden;padding:2px}
|
|
88
|
-
.cz-wrap:not(.cz-expanded) > .cz-tweak:nth-child(n+4){display:none}
|
|
89
|
-
.cz-fade{height:
|
|
88
|
+
.cz-wrap:not(.cz-expanded) > .cz-tweak:nth-last-child(n+4){display:none}
|
|
89
|
+
.cz-fade{height:14px;width:160px;background:linear-gradient(to top,rgba(17,24,39,0),rgba(17,24,39,.5));pointer-events:none;margin-bottom:-8px}
|
|
90
90
|
.cz-history{background:#0b1220;color:#93c5fd;border:1px solid #263041;border-radius:8px;padding:4px 10px;font-size:11.5px;cursor:pointer;pointer-events:auto;box-shadow:0 4px 14px rgba(0,0,0,.3)}
|
|
91
91
|
.cz-history:hover{border-color:#6366f1}
|
|
92
92
|
.cz-tweak{background:#111827;color:#e5e7eb;border-radius:8px;padding:7px 11px;font-size:13px;display:flex;gap:8px;align-items:center;box-shadow:0 4px 14px rgba(0,0,0,.3);white-space:nowrap;width:max-content;max-width:720px;overflow:hidden;text-overflow:ellipsis}
|
|
@@ -546,10 +546,14 @@
|
|
|
546
546
|
multiBar._draft = '';
|
|
547
547
|
clearMulti();
|
|
548
548
|
for (const t of targets) {
|
|
549
|
+
const tempId = 'nl-' + Date.now() + t.loc;
|
|
550
|
+
addTweak({ id: tempId, status: 'queued', label: `${shortLoc(t.loc)}: ${instruction.slice(0, 40)}` });
|
|
549
551
|
try {
|
|
550
552
|
const r = await api('nl', { loc: t.loc, instruction, model: state.model });
|
|
553
|
+
removeTweak(tempId);
|
|
551
554
|
addTweak({ id: r.id, status: 'queued', model: r.model, label: `${shortLoc(t.loc)}: ${instruction.slice(0, 40)}` });
|
|
552
555
|
} catch (e) {
|
|
556
|
+
removeTweak(tempId);
|
|
553
557
|
addTweak({ id: 'x' + Date.now() + t.loc, status: 'error', label: `${shortLoc(t.loc)}: ${e.message}` });
|
|
554
558
|
}
|
|
555
559
|
}
|
|
@@ -962,7 +966,7 @@
|
|
|
962
966
|
|
|
963
967
|
const nlWrap = el('div', 'cz-nlwrap');
|
|
964
968
|
const input = el('textarea');
|
|
965
|
-
input.placeholder = 'Describe a change… (
|
|
969
|
+
input.placeholder = 'Describe a change… (↵ to send · ⇧↵ newline)';
|
|
966
970
|
input.rows = 3;
|
|
967
971
|
const go = el('button', 'cz-primary', 'Go →');
|
|
968
972
|
const send = async () => {
|
|
@@ -970,16 +974,23 @@
|
|
|
970
974
|
if (!instruction) return;
|
|
971
975
|
input.value = '';
|
|
972
976
|
input.blur(); // so the reload after the model finishes isn't blocked by focus
|
|
977
|
+
// Immediate feedback — the server runs the router (which may call the
|
|
978
|
+
// classifier for ~1-3s) before it broadcasts its own 'queued', so show a
|
|
979
|
+
// placeholder now and swap in the real task once the request returns.
|
|
980
|
+
const tempId = 'nl-' + Date.now();
|
|
981
|
+
addTweak({ id: tempId, status: 'queued', label: instruction.slice(0, 60) });
|
|
973
982
|
try {
|
|
974
983
|
const r = await api('nl', { loc: s.loc, instruction, model: state.model });
|
|
984
|
+
removeTweak(tempId);
|
|
975
985
|
addTweak({ id: r.id, status: 'queued', model: r.model, label: instruction.slice(0, 60) });
|
|
976
986
|
} catch (e) {
|
|
977
|
-
|
|
987
|
+
removeTweak(tempId);
|
|
988
|
+
addTweak({ id: 'x' + Date.now(), status: 'error', label: `${instruction.slice(0, 40)}: ${e.message}` });
|
|
978
989
|
}
|
|
979
990
|
};
|
|
980
991
|
go.onclick = send;
|
|
981
|
-
//
|
|
982
|
-
input.onkeydown = (e) => { if (e.key === 'Enter' &&
|
|
992
|
+
// Enter sends; Shift+Enter inserts a newline for multi-line prompts.
|
|
993
|
+
input.onkeydown = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } e.stopPropagation(); };
|
|
983
994
|
const goRow = el('div', 'cz-row');
|
|
984
995
|
goRow.style.justifyContent = 'flex-end';
|
|
985
996
|
goRow.appendChild(go);
|
|
@@ -1093,18 +1104,18 @@
|
|
|
1093
1104
|
// ---------- tray ----------
|
|
1094
1105
|
const tray = el('div', 'cz-tray');
|
|
1095
1106
|
root.appendChild(tray);
|
|
1096
|
-
//
|
|
1097
|
-
//
|
|
1098
|
-
//
|
|
1099
|
-
const tweaksWrap = el('div', 'cz-wrap');
|
|
1100
|
-
tray.appendChild(tweaksWrap);
|
|
1101
|
-
const fade = el('div', 'cz-fade');
|
|
1102
|
-
fade.style.display = 'none';
|
|
1103
|
-
tray.appendChild(fade);
|
|
1107
|
+
// Reads like a log: newest alert sits at the bottom-right corner, older ones
|
|
1108
|
+
// stack upward. The 3 newest stay visible (compressed; hover to expand); the
|
|
1109
|
+
// rest fold behind an expander that sits ABOVE them.
|
|
1104
1110
|
const historyChip = el('div', 'cz-history');
|
|
1105
1111
|
historyChip.style.display = 'none';
|
|
1112
|
+
const tweaksWrap = el('div', 'cz-wrap');
|
|
1106
1113
|
historyChip.onclick = () => { tweaksWrap.classList.toggle('cz-expanded'); updateHistoryUI(); };
|
|
1107
1114
|
tray.appendChild(historyChip);
|
|
1115
|
+
const fade = el('div', 'cz-fade');
|
|
1116
|
+
fade.style.display = 'none';
|
|
1117
|
+
tray.appendChild(fade);
|
|
1118
|
+
tray.appendChild(tweaksWrap);
|
|
1108
1119
|
const tweaks = new Map();
|
|
1109
1120
|
const tweakData = new Map(); // id -> merged record, persisted across reloads
|
|
1110
1121
|
const HKEY = 'cz-history';
|
|
@@ -1160,6 +1171,15 @@
|
|
|
1160
1171
|
}
|
|
1161
1172
|
showTotals(null); // idle banner on load until /api/health arrives
|
|
1162
1173
|
|
|
1174
|
+
function removeTweak(id) {
|
|
1175
|
+
const key = String(id);
|
|
1176
|
+
const row = tweaks.get(key);
|
|
1177
|
+
if (row) row.remove();
|
|
1178
|
+
tweaks.delete(key);
|
|
1179
|
+
tweakData.delete(key);
|
|
1180
|
+
updateHistoryUI();
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1163
1183
|
function addTweak(t, hydrate) {
|
|
1164
1184
|
const key = String(t.id);
|
|
1165
1185
|
let row = tweaks.get(key);
|
|
@@ -1185,13 +1205,13 @@
|
|
|
1185
1205
|
} catch (e) { row._meta.textContent = e.message; }
|
|
1186
1206
|
};
|
|
1187
1207
|
row.append(row._dot, row._label, row._meta, row._cancel, row._undo);
|
|
1188
|
-
tweaksWrap.
|
|
1208
|
+
tweaksWrap.appendChild(row); // newest at the end (bottom of the tray)
|
|
1189
1209
|
tweaks.set(key, row);
|
|
1190
1210
|
while (tweaksWrap.children.length > MAX_TWEAKS) {
|
|
1191
|
-
const
|
|
1192
|
-
tweaks.delete(
|
|
1193
|
-
tweakData.delete(
|
|
1194
|
-
|
|
1211
|
+
const oldest = tweaksWrap.firstChild;
|
|
1212
|
+
tweaks.delete(oldest._id);
|
|
1213
|
+
tweakData.delete(oldest._id);
|
|
1214
|
+
oldest.remove();
|
|
1195
1215
|
}
|
|
1196
1216
|
}
|
|
1197
1217
|
if (t.label) row._label.textContent = t.label;
|
|
@@ -1234,7 +1254,7 @@
|
|
|
1234
1254
|
(function hydrateTweaks() {
|
|
1235
1255
|
let saved;
|
|
1236
1256
|
try { saved = JSON.parse(localStorage.getItem(HKEY) || '[]'); } catch { saved = []; }
|
|
1237
|
-
for (const rec of saved) addTweak(rec, true); //
|
|
1257
|
+
for (const rec of saved) addTweak(rec, true); // chronological replay → newest ends at the bottom
|
|
1238
1258
|
})();
|
|
1239
1259
|
|
|
1240
1260
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cmdzero",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Tweak your UI live in the browser and write the changes straight to source. Copy and Tailwind edits cost zero tokens; everything else routes to a right-sized model via headless claude.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/server.js
CHANGED
|
@@ -70,6 +70,9 @@ export function startServer({ root, port = 4100 }) {
|
|
|
70
70
|
try {
|
|
71
71
|
if (req.method === 'GET' && url.pathname === '/overlay.js') {
|
|
72
72
|
res.setHeader('Content-Type', 'text/javascript');
|
|
73
|
+
// Never cache the overlay — the daemon is the source of truth, so a
|
|
74
|
+
// reload always picks up the latest build (no stale-overlay confusion).
|
|
75
|
+
res.setHeader('Cache-Control', 'no-store, must-revalidate');
|
|
73
76
|
return res.end(fs.readFileSync(OVERLAY_PATH));
|
|
74
77
|
}
|
|
75
78
|
|