cmdzero 0.8.1 → 0.8.2

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
@@ -65,9 +65,15 @@ Opt out permanently:
65
65
  export CMDZERO_TELEMETRY=0 # or DO_NOT_TRACK=1 — both respected
66
66
  ```
67
67
 
68
+ Telemetry never blocks or breaks the daemon: it's fire-and-forget with a 3s
69
+ timeout and no retries. It's also silent about its own failures by default — set
70
+ `CMDZERO_TELEMETRY_DEBUG=1` to log delivery errors, or read
71
+ `curl -s localhost:4100/api/health | jq` for the last send and last error.
72
+
68
73
  ## Options
69
74
 
70
75
  - `npx cmdzero --port 4101` (+ `<CmdZeroOverlay origin="http://localhost:4101" />`)
71
76
  - `CMDZERO_FAST_MODEL` / `CMDZERO_SMART_MODEL` — model aliases for the router (default `haiku` / `sonnet`)
72
77
  - `CMDZERO_BASELINE_COST` / `CMDZERO_BASELINE_MS` — baseline for the savings counter
78
+ - `CMDZERO_TELEMETRY=0` / `DO_NOT_TRACK=1` — opt out of telemetry; `CMDZERO_TELEMETRY_DEBUG=1` logs delivery failures
73
79
  - Natural-language tweaks require the [Claude Code CLI](https://claude.com/claude-code) (`claude`) on your PATH; copy/style lanes work without it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cmdzero",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
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",
@@ -8,7 +8,8 @@
8
8
  "cmdzero": "bin/cmdzero.js"
9
9
  },
10
10
  "scripts": {
11
- "start": "node bin/cmdzero.js"
11
+ "start": "node bin/cmdzero.js",
12
+ "test": "node --test test/*.test.js"
12
13
  },
13
14
  "files": [
14
15
  "bin",
package/src/server.js CHANGED
@@ -77,7 +77,17 @@ export function startServer({ root, port = 4100 }) {
77
77
  }
78
78
 
79
79
  if (req.method === 'GET' && url.pathname === '/api/health') {
80
- return json(res, { ok: true, version: VERSION, root, totals, tailwind, telemetry: !telemetry.disabled });
80
+ const { lastSentAt, lastError } = telemetry.status();
81
+ return json(res, {
82
+ ok: true,
83
+ version: VERSION,
84
+ root,
85
+ totals,
86
+ tailwind,
87
+ telemetry: !telemetry.disabled,
88
+ telemetryLastSentAt: lastSentAt,
89
+ telemetryLastError: lastError,
90
+ });
81
91
  }
82
92
 
83
93
  if (req.method === 'GET' && url.pathname === '/api/events') {
@@ -317,8 +327,10 @@ export function startServer({ root, port = 4100 }) {
317
327
  });
318
328
  server.listen(port, () => {
319
329
  console.log(`[cmdzero] daemon on http://localhost:${port} (root: ${root})`);
320
- console.log('[cmdzero] docs & updates → https://cmdzero.dev');
330
+ console.log('[cmdzero] docs & updates → https://cmdzero.xyz');
321
331
  if (telemetry.firstRun && !telemetry.disabled) console.log(DISCLOSURE);
332
+ // After the disclosure, never before it.
333
+ telemetry.start();
322
334
  });
323
335
  return server;
324
336
  }
package/src/telemetry.js CHANGED
@@ -9,9 +9,13 @@ import path from 'node:path';
9
9
  import crypto from 'node:crypto';
10
10
 
11
11
  const ENDPOINT =
12
- process.env.CMDZERO_TELEMETRY_URL || 'https://telemetry.cmdzero.dev/v1/events';
12
+ process.env.CMDZERO_TELEMETRY_URL || 'https://telemetry.cmdzero.xyz/v1/events';
13
13
  const CONFIG_PATH = path.join(os.homedir(), '.cmdzero', 'telemetry.json');
14
14
  const FLUSH_MS = 60_000;
15
+ // Off by default: this runs in other people's dev terminals, and telemetry that
16
+ // complains about itself is worse than telemetry that fails. Delivery status is
17
+ // always readable via /api/health regardless of this flag.
18
+ const DEBUG = process.env.CMDZERO_TELEMETRY_DEBUG === '1';
15
19
 
16
20
  export function telemetryDisabled() {
17
21
  const v = String(process.env.CMDZERO_TELEMETRY ?? '').toLowerCase();
@@ -55,6 +59,19 @@ export function initTelemetry({ version, tailwind }) {
55
59
  const disabled = telemetryDisabled();
56
60
  const counts = { copy: 0, style: 0, delete: 0, nl: 0, move: 0, deploy: 0 };
57
61
  let flushTimer = null;
62
+ let lastSentAt = null;
63
+ let lastError = null;
64
+
65
+ // Must never throw: it runs inside the promise handlers below.
66
+ function note(what, err) {
67
+ if (!err) {
68
+ lastSentAt = new Date().toISOString();
69
+ lastError = null;
70
+ return;
71
+ }
72
+ lastError = { at: new Date().toISOString(), message: `${what}: ${err}` };
73
+ if (DEBUG) console.error(`[cmdzero] telemetry ${what} failed: ${err}`);
74
+ }
58
75
 
59
76
  function send(event, extra = {}) {
60
77
  if (disabled) return;
@@ -73,9 +90,15 @@ export function initTelemetry({ version, tailwind }) {
73
90
  ...extra,
74
91
  }),
75
92
  signal: AbortSignal.timeout(3000),
76
- }).catch(() => {});
77
- } catch {
78
- /* fire and forget */
93
+ })
94
+ .then((res) => note(event, res.ok ? null : `HTTP ${res.status}`))
95
+ // Terminal, and it has to stay that way. If .then() were the tail of this
96
+ // chain a network failure would surface as an unhandled rejection, which
97
+ // Node turns into a dead daemon in someone else's terminal — the
98
+ // .catch(() => {}) this replaces was the only thing preventing that.
99
+ .catch((err) => note(event, err?.message || err));
100
+ } catch (err) {
101
+ note(event, err?.message || err);
79
102
  }
80
103
  }
81
104
 
@@ -92,6 +115,12 @@ export function initTelemetry({ version, tailwind }) {
92
115
  }
93
116
  }
94
117
 
95
- send('boot');
96
- return { firstRun, disabled, record };
118
+ // Not fired here. server.js calls start() once the first-run disclosure has
119
+ // actually printed, so a first-time user's first event never precedes the
120
+ // notice that tells them it's coming.
121
+ function start() {
122
+ send('boot');
123
+ }
124
+
125
+ return { firstRun, disabled, record, start, status: () => ({ lastSentAt, lastError }) };
97
126
  }