tweaklocal 0.1.0 → 0.1.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 +10 -0
- package/overlay/overlay.js +9 -1
- package/package.json +4 -1
- package/src/server.js +8 -1
package/README.md
CHANGED
|
@@ -29,6 +29,16 @@ npm i -D tweaklocal @tweaklocal/react
|
|
|
29
29
|
|
|
30
30
|
Works with Turbopack, webpack, and Vite — stamping happens in React's dev JSX runtime, not the bundler. Server components included. Production builds are untouched (the prod runtime is a passthrough).
|
|
31
31
|
|
|
32
|
+
## Telemetry
|
|
33
|
+
|
|
34
|
+
The daemon sends anonymous usage telemetry: package version, node version, OS platform, whether Tailwind was detected, and per-lane tweak counts. **Never** code, file paths, file names, prompts, or anything identifying. A disclosure prints the first time the daemon runs.
|
|
35
|
+
|
|
36
|
+
Opt out permanently:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
export TWEAKLOCAL_TELEMETRY=0 # or DO_NOT_TRACK=1 — both respected
|
|
40
|
+
```
|
|
41
|
+
|
|
32
42
|
## Options
|
|
33
43
|
|
|
34
44
|
- `npx tweaklocal --port 4101` (+ `<TweakLocalOverlay origin="http://localhost:4101" />`)
|
package/overlay/overlay.js
CHANGED
|
@@ -36,6 +36,8 @@
|
|
|
36
36
|
.twk-chip{font-size:10.5px !important;padding:2px 6px !important}
|
|
37
37
|
.twk-tray{position:fixed;right:14px;bottom:14px;display:flex;flex-direction:column;align-items:flex-end;gap:6px;pointer-events:auto}
|
|
38
38
|
.twk-total{background:#064e3b;color:#a7f3d0;border-radius:8px;padding:6px 11px;font-size:13px;box-shadow:0 4px 14px rgba(0,0,0,.3);white-space:nowrap;width:max-content;max-width:720px}
|
|
39
|
+
.twk-total a{color:#6ee7b7;margin-left:10px;text-decoration:underline;cursor:pointer}
|
|
40
|
+
.twk-total a:hover{color:#a7f3d0}
|
|
39
41
|
.twk-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}
|
|
40
42
|
.twk-dot{width:8px;height:8px;border-radius:50%;flex:none}
|
|
41
43
|
.twk-dot.done{background:#10b981}.twk-dot.queued,.twk-dot.running{background:#f59e0b;animation:twk-pulse 1s infinite}.twk-dot.error{background:#ef4444}.twk-dot.reverted{background:#6b7280}
|
|
@@ -684,13 +686,19 @@
|
|
|
684
686
|
root.appendChild(tray);
|
|
685
687
|
const totalBar = el('div', 'twk-total');
|
|
686
688
|
totalBar.style.display = 'none';
|
|
689
|
+
const totalText = el('span');
|
|
690
|
+
const reportLink = el('a', null, 'monthly report →');
|
|
691
|
+
reportLink.href = 'https://tweaklocal.dev/report';
|
|
692
|
+
reportLink.target = '_blank';
|
|
693
|
+
reportLink.rel = 'noopener';
|
|
694
|
+
totalBar.append(totalText, reportLink);
|
|
687
695
|
tray.appendChild(totalBar);
|
|
688
696
|
const tweaks = new Map();
|
|
689
697
|
|
|
690
698
|
function showTotals(t) {
|
|
691
699
|
if (!t || !t.count) return;
|
|
692
700
|
totalBar.style.display = '';
|
|
693
|
-
|
|
701
|
+
totalText.textContent = `≈ saved $${t.usd.toFixed(2)} · ${Math.round(t.ms / 1000)}s across ${t.count} tweak${t.count === 1 ? '' : 's'} (vs unscoped agent)`;
|
|
694
702
|
}
|
|
695
703
|
|
|
696
704
|
function addTweak(t) {
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tweaklocal",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.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",
|
|
7
7
|
"bin": { "tweaklocal": "bin/tweaklocal.js" },
|
|
8
8
|
"scripts": { "start": "node bin/tweaklocal.js" },
|
|
9
9
|
"files": ["bin", "src", "overlay", "README.md"],
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18"
|
|
12
|
+
},
|
|
10
13
|
"dependencies": {
|
|
11
14
|
"@babel/parser": "^7.26.0"
|
|
12
15
|
},
|
package/src/server.js
CHANGED
|
@@ -73,7 +73,7 @@ export function startServer({ root, port = 4100 }) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
if (req.method === 'GET' && url.pathname === '/api/health') {
|
|
76
|
-
return json(res, { ok: true, root, totals, tailwind });
|
|
76
|
+
return json(res, { ok: true, root, totals, tailwind, telemetry: !telemetry.disabled });
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
if (req.method === 'GET' && url.pathname === '/api/events') {
|
|
@@ -99,6 +99,7 @@ export function startServer({ root, port = 4100 }) {
|
|
|
99
99
|
const id = nextId++;
|
|
100
100
|
const write = applyTextEdit(root, body.loc, body.oldText, body.newText);
|
|
101
101
|
remember(id, write);
|
|
102
|
+
telemetry.record('copy');
|
|
102
103
|
broadcast({ type: 'tweak', id, kind: 'copy', status: 'done', tokens: 0, label: `copy: "${body.newText.slice(0, 40)}"`, ...recordSavings(0, 50) });
|
|
103
104
|
return json(res, { ok: true, id });
|
|
104
105
|
}
|
|
@@ -107,6 +108,7 @@ export function startServer({ root, port = 4100 }) {
|
|
|
107
108
|
const id = nextId++;
|
|
108
109
|
const write = applyClassEdit(root, body.loc, body.remove || [], body.add || []);
|
|
109
110
|
remember(id, write);
|
|
111
|
+
telemetry.record('style');
|
|
110
112
|
broadcast({ type: 'tweak', id, kind: 'style', status: 'done', tokens: 0, label: `style: ${(body.add || []).join(' ')}`, ...recordSavings(0, 50) });
|
|
111
113
|
return json(res, { ok: true, id });
|
|
112
114
|
}
|
|
@@ -118,6 +120,7 @@ export function startServer({ root, port = 4100 }) {
|
|
|
118
120
|
const desc = Object.entries(body.styles || {})
|
|
119
121
|
.map(([k, v]) => `${k}: ${v}`)
|
|
120
122
|
.join(', ');
|
|
123
|
+
telemetry.record('style');
|
|
121
124
|
broadcast({ type: 'tweak', id, kind: 'style', status: 'done', tokens: 0, label: `style: ${desc.slice(0, 50)}`, ...recordSavings(0, 50) });
|
|
122
125
|
return json(res, { ok: true, id });
|
|
123
126
|
}
|
|
@@ -131,6 +134,7 @@ export function startServer({ root, port = 4100 }) {
|
|
|
131
134
|
const target = describeTarget(root, body.loc);
|
|
132
135
|
const write = applyDeleteElement(root, body.loc);
|
|
133
136
|
remember(id, write);
|
|
137
|
+
telemetry.record('delete');
|
|
134
138
|
broadcast({ type: 'tweak', id, kind: 'delete', status: 'done', tokens: 0, label: `deleted <${target.tagName}>`, ...recordSavings(0, 50) });
|
|
135
139
|
return json(res, { ok: true, id });
|
|
136
140
|
}
|
|
@@ -151,6 +155,7 @@ export function startServer({ root, port = 4100 }) {
|
|
|
151
155
|
const route = classify(body.instruction);
|
|
152
156
|
const abs = path.resolve(root, file);
|
|
153
157
|
remember(id, { abs, before: fs.readFileSync(abs, 'utf8') });
|
|
158
|
+
telemetry.record('nl');
|
|
154
159
|
broadcast({ type: 'tweak', id, kind: route.kind, status: 'queued', model: route.model, label: body.instruction.slice(0, 60) });
|
|
155
160
|
json(res, { ok: true, id, model: route.model, kind: route.kind });
|
|
156
161
|
|
|
@@ -217,6 +222,8 @@ export function startServer({ root, port = 4100 }) {
|
|
|
217
222
|
});
|
|
218
223
|
server.listen(port, () => {
|
|
219
224
|
console.log(`[tweaklocal] daemon on http://localhost:${port} (root: ${root})`);
|
|
225
|
+
console.log('[tweaklocal] docs & updates → https://tweaklocal.dev');
|
|
226
|
+
if (telemetry.firstRun && !telemetry.disabled) console.log(DISCLOSURE);
|
|
220
227
|
});
|
|
221
228
|
return server;
|
|
222
229
|
}
|