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 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" />`)
@@ -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
- totalBar.textContent = `≈ saved $${t.usd.toFixed(2)} · ${Math.round(t.ms / 1000)}s across ${t.count} tweak${t.count === 1 ? '' : 's'} (vs unscoped agent)`;
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.0",
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
  }