@nuvio/cli 0.5.3 → 0.5.5

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
@@ -1,10 +1,22 @@
1
1
  # @nuvio/cli
2
2
 
3
- One-command onboarding for Nuvio in Vite + React + Tailwind projects.
3
+ One-command onboarding for nuvio in Vite + React + Tailwind projects.
4
4
 
5
5
  ```bash
6
6
  pnpm dlx @nuvio/cli init
7
7
  pnpm dev
8
8
  ```
9
9
 
10
- See [Nuvio docs](https://github.com/ehah/Nuvio/blob/main/docs/nuvioUser.md).
10
+ See [nuvio docs](https://github.com/ehah/Nuvio/blob/main/docs/nuvioUser.md).
11
+
12
+ ## Telemetry
13
+
14
+ nuvio collects anonymous usage metrics to improve onboarding and reliability. No source code, file contents, file paths, project names, emails, or personal data are sent.
15
+
16
+ Disable anytime with:
17
+
18
+ ```bash
19
+ NUVIO_TELEMETRY=0
20
+ ```
21
+
22
+ See [PostHog_telemetry.md](https://github.com/ehah/Nuvio/blob/main/docs/PostHog_telemetry.md).
package/dist/cli-entry.js CHANGED
@@ -13,14 +13,19 @@ import { join } from "path";
13
13
  // src/messages.ts
14
14
  var MSG = {
15
15
  noPackageJson: "Run this from your app folder (the one with package.json).",
16
- noVite: "Nuvio works with React + Vite projects. I couldn't find a Vite config here.",
17
- noReact: "Nuvio needs React. Add react to this project first.",
18
- noViteDep: "Nuvio needs Vite. Add vite to this project first.",
19
- strictTailwind: "Nuvio expects Tailwind CSS for class edits. Install tailwindcss or pass --skip-tailwind-check.",
20
- monorepoRoot: "This looks like the Nuvio monorepo. Run init in your app folder, not the tooling repo.",
16
+ noVite: "nuvio works with React + Vite projects. I couldn't find a Vite config here.",
17
+ noReact: "nuvio needs React. Add react to this project first.",
18
+ noViteDep: "nuvio needs Vite. Add vite to this project first.",
19
+ strictTailwind: "nuvio expects Tailwind CSS for class edits. Install tailwindcss or pass --skip-tailwind-check.",
20
+ monorepoRoot: "This looks like the nuvio monorepo. Run init in your app folder, not the tooling repo.",
21
21
  cliPackage: "Cannot init inside @nuvio/cli package.",
22
- partialHelp: "Nuvio set up what it could safely. Finish the steps in nuvio/SETUP_TODO.md, then run your dev server.",
23
- noHeading: 'Nuvio is wired, but I could not find a heading to mark editable. Add data-nuvio-id="page.title" to one visible element (see nuvio/START_HERE.md).'
22
+ partialHelp: "nuvio set up what it could safely. Finish the steps in nuvio/SETUP_TODO.md, then run your dev server.",
23
+ noHeading: 'nuvio is wired, but I could not find a heading to mark editable. Add data-nuvio-id="page.title" to one visible element (see nuvio/START_HERE.md).',
24
+ telemetryNotice: `nuvio collects anonymous usage metrics to improve onboarding and reliability.
25
+ No source code, file contents, file paths, project names, emails, or personal data are sent.
26
+
27
+ Disable anytime with:
28
+ NUVIO_TELEMETRY=0`
24
29
  };
25
30
 
26
31
  // src/detect-project.ts
@@ -692,6 +697,216 @@ function printVerification(v) {
692
697
  console.log(` Starter id page.title \u2014 ${v.starterId}`);
693
698
  }
694
699
 
700
+ // src/telemetry.ts
701
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
702
+ import { homedir } from "os";
703
+ import { join as join7 } from "path";
704
+ import { randomUUID } from "crypto";
705
+ import os from "os";
706
+ import { PostHog } from "posthog-node";
707
+
708
+ // src/nuvio-posthog-token.ts
709
+ var NUVIO_POSTHOG_TOKEN = "phc_CJnWrLU4hB4aA88DJrPnma2WBMQqVHxUMVvrsye3R6x2";
710
+
711
+ // src/telemetry.ts
712
+ var POSTHOG_HOST = "https://us.i.posthog.com";
713
+ function telemetryFilePath() {
714
+ return join7(homedir(), ".nuvio", "telemetry.json");
715
+ }
716
+ var FORBIDDEN_PROP_KEYS = /* @__PURE__ */ new Set([
717
+ "cwd",
718
+ "root",
719
+ "file",
720
+ "path",
721
+ "name",
722
+ "message",
723
+ "stack"
724
+ ]);
725
+ var SHUTDOWN_TIMEOUT_MS = 3e3;
726
+ var client = null;
727
+ var sessionAnonymousId = null;
728
+ var shutdownDone = false;
729
+ var signalHandlersRegistered = false;
730
+ function telemetryDebug(message, detail) {
731
+ if (process.env.NUVIO_TELEMETRY_DEBUG !== "1") return;
732
+ if (detail !== void 0) {
733
+ console.error(`[nuvio telemetry] ${message}`, detail);
734
+ return;
735
+ }
736
+ console.error(`[nuvio telemetry] ${message}`);
737
+ }
738
+ function isTelemetryEnabled() {
739
+ const flag = process.env.NUVIO_TELEMETRY;
740
+ if (flag === "0") return false;
741
+ if (flag?.toLowerCase() === "false") return false;
742
+ return true;
743
+ }
744
+ function posthogToken() {
745
+ return process.env.NUVIO_POSTHOG_TOKEN ?? NUVIO_POSTHOG_TOKEN;
746
+ }
747
+ function tokenIsConfigured(token) {
748
+ return Boolean(token && token.startsWith("phc_"));
749
+ }
750
+ function readOrCreateAnonymousId() {
751
+ if (sessionAnonymousId) return sessionAnonymousId;
752
+ try {
753
+ const raw = readFileSync10(telemetryFilePath(), "utf8");
754
+ const parsed = JSON.parse(raw);
755
+ if (parsed.anonymousId) {
756
+ sessionAnonymousId = parsed.anonymousId;
757
+ return parsed.anonymousId;
758
+ }
759
+ } catch {
760
+ }
761
+ const id = randomUUID();
762
+ sessionAnonymousId = id;
763
+ try {
764
+ mkdirSync2(join7(homedir(), ".nuvio"), { recursive: true, mode: 448 });
765
+ writeFileSync6(
766
+ telemetryFilePath(),
767
+ JSON.stringify({ anonymousId: id }, null, 2),
768
+ { mode: 384 }
769
+ );
770
+ } catch {
771
+ }
772
+ return id;
773
+ }
774
+ function getClient() {
775
+ if (!isTelemetryEnabled()) return null;
776
+ const token = posthogToken();
777
+ if (!tokenIsConfigured(token)) return null;
778
+ if (!client) {
779
+ client = new PostHog(token, {
780
+ host: POSTHOG_HOST,
781
+ flushAt: 1,
782
+ flushInterval: 0
783
+ });
784
+ telemetryDebug("PostHog client initialized", {
785
+ host: POSTHOG_HOST,
786
+ tokenPrefix: `${token.slice(0, 8)}\u2026`
787
+ });
788
+ }
789
+ return client;
790
+ }
791
+ function sanitizeProps(props) {
792
+ if (!props) return void 0;
793
+ const out = {};
794
+ for (const [key, value] of Object.entries(props)) {
795
+ if (FORBIDDEN_PROP_KEYS.has(key)) continue;
796
+ if (value === void 0) continue;
797
+ if (typeof value === "string" && /[/\\]/.test(value)) continue;
798
+ out[key] = value;
799
+ }
800
+ return Object.keys(out).length > 0 ? out : void 0;
801
+ }
802
+ function resolveCliInvokedCommand(help, command) {
803
+ if (help) return "help";
804
+ if (!command) return "none";
805
+ if (command === "init") return "init";
806
+ return "unknown";
807
+ }
808
+ function buildCliInvokedProps(command, pmOverride) {
809
+ const props = {
810
+ nuvio_version: NUVIO_VERSION,
811
+ os: process.platform,
812
+ arch: os.arch(),
813
+ node: process.version,
814
+ command
815
+ };
816
+ if (pmOverride) props.package_manager = pmOverride;
817
+ return props;
818
+ }
819
+ function buildCliTelemetryProps(pm, project) {
820
+ const props = {
821
+ nuvio_version: NUVIO_VERSION,
822
+ os: process.platform,
823
+ arch: os.arch(),
824
+ node: process.version
825
+ };
826
+ if (pm) props.package_manager = pm;
827
+ if (project) {
828
+ props.has_react = true;
829
+ props.has_vite = true;
830
+ props.has_tailwind = project.tailwindOk;
831
+ }
832
+ return props;
833
+ }
834
+ function preflightErrorCode(message) {
835
+ if (message === MSG.noPackageJson) return "preflight_no_package_json";
836
+ if (message === MSG.noVite) return "preflight_no_vite";
837
+ if (message === MSG.noReact) return "preflight_no_react";
838
+ if (message === MSG.noViteDep) return "preflight_no_vite_dep";
839
+ if (message === MSG.monorepoRoot || message === MSG.cliPackage) {
840
+ return "preflight_monorepo";
841
+ }
842
+ return "preflight_unknown";
843
+ }
844
+ function captureCliInvoked(command, pmOverride) {
845
+ captureCliEvent("nuvio_cli_invoked", buildCliInvokedProps(command, pmOverride));
846
+ }
847
+ function captureCliEvent(event, props) {
848
+ try {
849
+ if (!isTelemetryEnabled()) {
850
+ telemetryDebug(`skipped ${event} (telemetry disabled)`);
851
+ return;
852
+ }
853
+ const ph = getClient();
854
+ if (!ph) {
855
+ telemetryDebug(`skipped ${event} (no PostHog client \u2014 check token)`);
856
+ return;
857
+ }
858
+ const distinctId = readOrCreateAnonymousId();
859
+ ph.capture({
860
+ distinctId,
861
+ event,
862
+ properties: sanitizeProps(props)
863
+ });
864
+ telemetryDebug(`captured ${event}`, { distinctId });
865
+ } catch (error) {
866
+ telemetryDebug(`capture failed for ${event}`, error);
867
+ }
868
+ }
869
+ async function flushAndShutdownClient() {
870
+ if (!client) return;
871
+ const active = client;
872
+ client = null;
873
+ await Promise.race([
874
+ (async () => {
875
+ await active.flush();
876
+ await active.shutdown();
877
+ })(),
878
+ new Promise((_, reject) => {
879
+ setTimeout(
880
+ () => reject(new Error("telemetry shutdown timed out")),
881
+ SHUTDOWN_TIMEOUT_MS
882
+ );
883
+ })
884
+ ]);
885
+ }
886
+ async function shutdownTelemetry() {
887
+ if (shutdownDone) return;
888
+ shutdownDone = true;
889
+ try {
890
+ await flushAndShutdownClient();
891
+ telemetryDebug("flush + shutdown complete");
892
+ } catch (error) {
893
+ telemetryDebug("shutdown failed", error);
894
+ }
895
+ }
896
+ function registerTelemetrySignalHandlers() {
897
+ if (signalHandlersRegistered) return;
898
+ signalHandlersRegistered = true;
899
+ const onSignal = (signal) => {
900
+ void (async () => {
901
+ await shutdownTelemetry();
902
+ const code2 = signal === "SIGINT" ? 130 : 143;
903
+ process.exit(code2);
904
+ })();
905
+ };
906
+ process.once("SIGINT", onSignal);
907
+ process.once("SIGTERM", onSignal);
908
+ }
909
+
695
910
  // src/init.ts
696
911
  function isAutoYes(opts) {
697
912
  if (opts.yes) return true;
@@ -725,14 +940,14 @@ function computeTier(installOk, viteOk, appOk, starterOk) {
725
940
  function printSuccess(plan, checks) {
726
941
  if (checks.install) {
727
942
  console.log(
728
- `\u2705 Nuvio packages targeted (@nuvio/vite-plugin@${NUVIO_VERSION}, @nuvio/overlay@${NUVIO_VERSION})`
943
+ `\u2705 nuvio packages targeted (@nuvio/vite-plugin@${NUVIO_VERSION}, @nuvio/overlay@${NUVIO_VERSION})`
729
944
  );
730
945
  }
731
946
  if (checks.vite) console.log("\u2705 Vite plugin added");
732
947
  else if (plan.failedSteps.some((s) => s.includes("vite"))) {
733
948
  console.log("\u26A0 Vite plugin \u2014 see nuvio/SETUP_TODO.md");
734
949
  }
735
- if (checks.app) console.log("\u2705 Nuvio editor mounted");
950
+ if (checks.app) console.log("\u2705 nuvio editor mounted");
736
951
  else console.log("\u26A0 App shell \u2014 see nuvio/SETUP_TODO.md");
737
952
  if (checks.starter) {
738
953
  console.log(
@@ -753,31 +968,56 @@ Next:
753
968
  ${MSG.partialHelp}`);
754
969
  } else if (plan.tier === "partial") {
755
970
  console.log(
756
- "\nNuvio helped you as far as it safely could. See warnings above."
971
+ "\nnuvio helped you as far as it safely could. See warnings above."
757
972
  );
758
973
  }
974
+ console.log(`
975
+ ${MSG.telemetryNotice}`);
759
976
  }
760
977
  async function runInit(opts) {
761
978
  const root = opts.cwd;
979
+ const pm = detectPackageManager(root, opts.pm);
980
+ if (!opts.dryRun) {
981
+ captureCliEvent(
982
+ "nuvio_init_started",
983
+ buildCliTelemetryProps(pm)
984
+ );
985
+ }
762
986
  let project;
763
987
  try {
764
988
  project = detectProject(root);
765
989
  } catch (e) {
766
990
  if (e instanceof PreflightError) {
767
991
  console.error(e.message);
992
+ if (!opts.dryRun) {
993
+ captureCliEvent("nuvio_init_failed", {
994
+ ...buildCliTelemetryProps(pm),
995
+ error_code: preflightErrorCode(e.message)
996
+ });
997
+ }
768
998
  return 1;
769
999
  }
770
1000
  if (opts.verbose) console.error(e);
771
1001
  console.error("Something went wrong. Run with --verbose for details.");
1002
+ if (!opts.dryRun) {
1003
+ captureCliEvent("nuvio_init_failed", {
1004
+ ...buildCliTelemetryProps(pm),
1005
+ error_code: "unexpected_error"
1006
+ });
1007
+ }
772
1008
  return 2;
773
1009
  }
774
- const pm = detectPackageManager(root, opts.pm);
1010
+ const projectProps = buildCliTelemetryProps(pm, project);
775
1011
  const plan = createPlan(root, pm);
776
1012
  plan.installCommand = opts.noInstall ? "(skipped \u2014 --no-install)" : installCommand(pm, NUVIO_VERSION);
777
1013
  if (project.tailwindWarn && !opts.skipTailwindCheck) {
778
1014
  const msg = "Tailwind CSS not detected. Class/style edits may not work until Tailwind is installed.";
779
1015
  if (opts.strict) {
780
1016
  console.error(MSG.strictTailwind);
1017
+ captureCliEvent("nuvio_init_failed", {
1018
+ ...projectProps,
1019
+ error_code: "strict_tailwind"
1020
+ });
781
1021
  return 1;
782
1022
  }
783
1023
  plan.warnings.push(msg);
@@ -808,6 +1048,10 @@ async function runInit(opts) {
808
1048
  const ok = await confirm(plan);
809
1049
  if (!ok) {
810
1050
  console.log("Cancelled.");
1051
+ captureCliEvent("nuvio_init_failed", {
1052
+ ...projectProps,
1053
+ error_code: "user_cancelled"
1054
+ });
811
1055
  return 1;
812
1056
  }
813
1057
  }
@@ -817,10 +1061,14 @@ async function runInit(opts) {
817
1061
  const result = runInstall(root, pm, NUVIO_VERSION);
818
1062
  if (!result.ok) {
819
1063
  console.error(result.message ?? "Install failed.");
1064
+ captureCliEvent("nuvio_init_failed", {
1065
+ ...projectProps,
1066
+ error_code: "install_failed"
1067
+ });
820
1068
  return 1;
821
1069
  }
822
1070
  } else {
823
- console.log("\u2705 Nuvio packages already installed");
1071
+ console.log("\u2705 nuvio packages already installed");
824
1072
  }
825
1073
  } else {
826
1074
  console.log("(skipped install \u2014 --no-install)");
@@ -889,13 +1137,24 @@ async function runInit(opts) {
889
1137
  project.viteConfigPath
890
1138
  );
891
1139
  printVerification(verification);
892
- if (plan.tier === "failed") return 1;
1140
+ if (plan.tier === "failed") {
1141
+ captureCliEvent("nuvio_init_failed", {
1142
+ ...projectProps,
1143
+ error_code: "init_tier_failed",
1144
+ result_tier: "failed"
1145
+ });
1146
+ return 1;
1147
+ }
1148
+ captureCliEvent("nuvio_init_completed", {
1149
+ ...projectProps,
1150
+ result_tier: plan.tier
1151
+ });
893
1152
  return plan.tier === "partial" || plan.tier === "full" ? 0 : 1;
894
1153
  }
895
1154
 
896
1155
  // src/cli.ts
897
1156
  function printHelp() {
898
- console.log(`nuvio \u2014 Nuvio CLI
1157
+ console.log(`nuvio \u2014 CLI for React + Vite
899
1158
 
900
1159
  Usage:
901
1160
  nuvio init [options]
@@ -951,26 +1210,35 @@ function parseArgs(argv) {
951
1210
  return { command, opts, help };
952
1211
  }
953
1212
  async function runCli(argv) {
1213
+ registerTelemetrySignalHandlers();
954
1214
  const { command, opts, help } = parseArgs(argv);
955
- if (help) {
956
- printHelp();
957
- return 0;
958
- }
959
- if (!command) {
960
- printHelp();
961
- return 1;
962
- }
963
- if (command !== "init") {
964
- console.error(`Unknown command: ${command}`);
965
- printHelp();
966
- return 1;
967
- }
1215
+ captureCliInvoked(resolveCliInvokedCommand(help, command), opts.pm);
968
1216
  try {
1217
+ if (help) {
1218
+ printHelp();
1219
+ return 0;
1220
+ }
1221
+ if (!command) {
1222
+ printHelp();
1223
+ return 1;
1224
+ }
1225
+ if (command !== "init") {
1226
+ console.error(`Unknown command: ${command}`);
1227
+ printHelp();
1228
+ return 1;
1229
+ }
969
1230
  return await runInit(opts);
970
1231
  } catch (e) {
1232
+ const pm = detectPackageManager(opts.cwd, opts.pm);
1233
+ captureCliEvent("nuvio_init_failed", {
1234
+ ...buildCliTelemetryProps(pm),
1235
+ error_code: "unexpected_error"
1236
+ });
971
1237
  if (opts.verbose) console.error(e);
972
1238
  else console.error("Something went wrong. Run with --verbose for details.");
973
1239
  return 2;
1240
+ } finally {
1241
+ await shutdownTelemetry();
974
1242
  }
975
1243
  }
976
1244
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuvio/cli",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "Nuvio CLI: one-command Vite + React onboarding (nuvio init).",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,7 +36,8 @@
36
36
  "@babel/parser": "^7.26.9",
37
37
  "@babel/traverse": "^7.26.9",
38
38
  "@babel/types": "^7.26.9",
39
- "fast-glob": "^3.3.3"
39
+ "fast-glob": "^3.3.3",
40
+ "posthog-node": "^5.36.2"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@types/babel__generator": "^7.6.8",
@@ -1,9 +1,9 @@
1
1
  <!-- nuvio-cli-template: 1 -->
2
- # Nuvio agent instructions
2
+ # nuvio agent instructions
3
3
 
4
- This project uses [Nuvio](https://www.npmjs.com/org/nuvio) (dev-only visual editor).
4
+ This project uses [nuvio](https://www.npmjs.com/org/nuvio) (dev-only visual editor).
5
5
 
6
- When the user asks to make UI editable or wire Nuvio:
6
+ When the user asks to make UI editable or wire nuvio:
7
7
 
8
8
  1. Do **not** change unrelated files.
9
9
  2. Add **string literal** `data-nuvio-id="region.name"` on JSX elements they should click in the browser.
@@ -1,5 +1,5 @@
1
1
  <!-- nuvio-cli-template: 1 -->
2
- # Nuvio
2
+ # nuvio
3
3
 
4
4
  **Start here:** [START_HERE.md](./START_HERE.md)
5
5
 
@@ -1,5 +1,5 @@
1
1
  <!-- nuvio-cli-template: 1 -->
2
- # Nuvio setup — manual steps
2
+ # nuvio setup — manual steps
3
3
 
4
4
  @nuvio/cli could not safely patch: {{FAILED_STEPS}}
5
5
 
@@ -1,5 +1,5 @@
1
1
  <!-- nuvio-cli-template: 1 -->
2
- # Start here — Nuvio in this project
2
+ # Start here — nuvio in this project
3
3
 
4
4
  Installed with @nuvio/cli@{{NUVIO_VERSION}}.
5
5
 
@@ -7,7 +7,7 @@ Installed with @nuvio/cli@{{NUVIO_VERSION}}.
7
7
 
8
8
  **Then:**
9
9
  1. Open the localhost URL from the terminal
10
- 2. Turn **Edit** on (Nuvio chip)
10
+ 2. Turn **Edit** on (nuvio chip)
11
11
  3. Click the starter element (usually the page title — id `page.title`)
12
12
  4. **Preview Changes** → **Apply to Code**
13
13