@holdyapp/install 0.3.0 → 0.5.0

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.
Files changed (3) hide show
  1. package/README.md +8 -3
  2. package/dist/cli.js +143 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -5,13 +5,16 @@ CLI de instalação do Holdy SDK. Detecta seu framework e wira o SDK em 1 passo.
5
5
  ## Uso
6
6
 
7
7
  ```bash
8
- npx @holdyapp/install <SUA_API_KEY>
8
+ npx @holdyapp/install
9
9
  ```
10
10
 
11
- Sem argumentos? Roda em modo interativo:
11
+ Abre o navegador pra você entrar na Holdy (ou criar a conta), conferir o código e autorizar. A chave chega sozinha no terminal, sem copiar nada. Com `--instrument`, a Holdy também lê seu código e propõe os eventos do produto, usando uma chave secreta que vale 1 hora.
12
+
13
+ Prefere colar a chave?
12
14
 
13
15
  ```bash
14
- npx @holdyapp/install
16
+ npx @holdyapp/install <SUA_API_KEY>
17
+ npx @holdyapp/install --paste-key
15
18
  ```
16
19
 
17
20
  ## Opções
@@ -19,6 +22,8 @@ npx @holdyapp/install
19
22
  ```
20
23
  npx @holdyapp/install <apiKey> [opções]
21
24
 
25
+ --instrument Lê o código e propõe os eventos do produto
26
+ --paste-key Pede a chave no terminal em vez de abrir o navegador
22
27
  --account <id> Account ID padrão pro Holdy.init
23
28
  --no-auto-capture Desabilita captura automática de page views
24
29
  --version, -v Mostra versão
package/dist/cli.js CHANGED
@@ -174,8 +174,11 @@ declare global {
174
174
  interface Window {
175
175
  Holdy?: {
176
176
  init: (opts: Record<string, unknown>) => void
177
- identify: (opts: Record<string, unknown>) => void
178
- track: (primitive: string, payload?: Record<string, unknown>) => void
177
+ identify: (userId: string, traits?: Record<string, unknown>) => void
178
+ group: (accountKey: string, traits?: Record<string, unknown>) => void
179
+ track: (event: string, properties?: Record<string, unknown>) => void
180
+ page: (name?: string, properties?: Record<string, unknown>) => void
181
+ reset: () => void
179
182
  }
180
183
  }
181
184
  }
@@ -592,11 +595,27 @@ async function runInstrument(opts) {
592
595
  `${patches.length} ${patches.length === 1 ? "evento inserido" : "eventos inseridos"} em ${written.length} ${written.length === 1 ? "arquivo" : "arquivos"}`
593
596
  );
594
597
  for (const file of written) ui.info(file);
598
+ printMetrics(plan.metrics, ui);
595
599
  console.log();
596
600
  ui.info("Os eventos se registram sozinhos na taxonomia no primeiro emit \u2014 n\xE3o precisa declarar nada na Holdy.");
597
- ui.info("Rode o app, use o produto e acompanhe em /setup/captura.");
601
+ ui.info("Rode o app, use o produto e acompanhe em holdy.chat/onboarding.");
598
602
  return 0;
599
603
  }
604
+ function printMetrics(metrics, ui) {
605
+ if (!metrics || metrics.length === 0) return;
606
+ const prontas = metrics.filter((m) => m.ready);
607
+ const faltando = metrics.filter((m) => !m.ready);
608
+ console.log();
609
+ console.log(` ${ui.bold("O que voc\xEA passa a medir")}`);
610
+ console.log();
611
+ for (const m of prontas) {
612
+ const extra = m.needs_choice ? ui.dim(` \xB7 ${m.needs_choice}`) : "";
613
+ console.log(` ${ui.green("\u2713")} ${m.label}${extra}`);
614
+ }
615
+ for (const m of faltando) {
616
+ console.log(` ${ui.dim("\xB7")} ${ui.dim(`${m.label} \u2014 falta evento de fase ${m.missing.join(" e ")}`)}`);
617
+ }
618
+ }
600
619
  async function requestPlan(opts, files) {
601
620
  const url = `${opts.baseUrl.replace(/\/$/, "")}/instrumentation/plan`;
602
621
  const response = await fetch(url, {
@@ -718,8 +737,86 @@ function confirm(question) {
718
737
  });
719
738
  }
720
739
 
740
+ // src/login.ts
741
+ var import_node_child_process = require("node:child_process");
742
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
743
+ function openBrowser(url) {
744
+ if (process.env.CI || process.env.HOLDY_NO_BROWSER) return false;
745
+ try {
746
+ const child = process.platform === "darwin" ? (0, import_node_child_process.spawn)("open", [url], { stdio: "ignore", detached: true }) : process.platform === "win32" ? (0, import_node_child_process.spawn)("cmd", ["/c", "start", '""', url], { stdio: "ignore", detached: true }) : (0, import_node_child_process.spawn)("xdg-open", [url], { stdio: "ignore", detached: true });
747
+ child.on("error", () => void 0);
748
+ child.unref();
749
+ return true;
750
+ } catch {
751
+ return false;
752
+ }
753
+ }
754
+ async function loginWithBrowser(opts) {
755
+ const { ui } = opts;
756
+ let session;
757
+ try {
758
+ const res = await fetch(`${opts.apiUrl}/cli/sessions`, {
759
+ method: "POST",
760
+ headers: { "Content-Type": "application/json" },
761
+ body: JSON.stringify({ instrument: opts.instrument, client: opts.client })
762
+ });
763
+ if (!res.ok) {
764
+ ui.error(`A Holdy n\xE3o abriu a sess\xE3o de login (HTTP ${res.status}).`);
765
+ return null;
766
+ }
767
+ session = await res.json();
768
+ } catch (err) {
769
+ ui.error(`N\xE3o consegui falar com a Holdy: ${err instanceof Error ? err.message : String(err)}`);
770
+ return null;
771
+ }
772
+ console.log();
773
+ console.log(` Seu c\xF3digo: ${ui.bold(session.user_code)}`);
774
+ const opened = openBrowser(session.verification_url);
775
+ console.log(
776
+ ` ${opened ? "Abri o navegador. Se n\xE3o abriu, acesse:" : "Abra no navegador:"} ${ui.cyan(session.verification_url)}`
777
+ );
778
+ console.log(` ${ui.dim("Entre ou crie sua conta, confira o c\xF3digo e clique em Autorizar.")}`);
779
+ console.log();
780
+ const deadline = Date.now() + session.expires_in * 1e3;
781
+ let interval = Math.max(1, session.interval) * 1e3;
782
+ while (Date.now() < deadline) {
783
+ await sleep(interval);
784
+ let res;
785
+ try {
786
+ res = await fetch(`${opts.apiUrl}/cli/sessions/poll`, {
787
+ method: "POST",
788
+ headers: { "Content-Type": "application/json" },
789
+ body: JSON.stringify({ device_code: session.device_code })
790
+ });
791
+ } catch {
792
+ continue;
793
+ }
794
+ if (res.status === 202) continue;
795
+ if (res.status === 429) {
796
+ interval = Math.min(interval * 2, 1e4);
797
+ continue;
798
+ }
799
+ if (res.status === 403) {
800
+ ui.error("Autoriza\xE7\xE3o cancelada no navegador.");
801
+ return null;
802
+ }
803
+ if (res.status === 410) break;
804
+ if (res.ok) {
805
+ const body = await res.json();
806
+ if (!body.public_key) break;
807
+ return {
808
+ publicKey: body.public_key,
809
+ secretKey: body.secret_key ?? null,
810
+ organizationName: body.organization_name ?? null
811
+ };
812
+ }
813
+ }
814
+ ui.error("O c\xF3digo expirou antes da autoriza\xE7\xE3o. Rode o comando de novo.");
815
+ return null;
816
+ }
817
+
721
818
  // src/cli.ts
722
- var VERSION = "0.3.0";
819
+ var VERSION = "0.5.0";
723
820
  var DEFAULT_API_URL = "https://holdy.chat/api/v1";
724
821
  async function main() {
725
822
  const argv = process.argv.slice(2);
@@ -741,14 +838,30 @@ async function main() {
741
838
  log.warn('N\xE3o consegui identificar o framework. Sigo no modo "vanilla" (script tag).');
742
839
  }
743
840
  let apiKey = args.apiKey;
744
- if (!apiKey) {
841
+ let instrumentKey = null;
842
+ if (!apiKey && args.pasteKey) {
745
843
  apiKey = await promptApiKey();
844
+ } else if (!apiKey) {
845
+ log.step("2", "Entrando na Holdy pelo navegador\u2026");
846
+ const login = await loginWithBrowser({
847
+ apiUrl: args.apiUrl,
848
+ instrument: args.instrument,
849
+ client: { framework: detection.framework, project: projectName(cwd), cli_version: VERSION },
850
+ ui: { ...log, bold, dim, cyan }
851
+ });
852
+ if (!login) {
853
+ log.info(`Prefere colar a chave? ${bold("npx @holdyapp/install --paste-key")}`);
854
+ process.exit(1);
855
+ }
856
+ log.success(login.organizationName ? `Autorizado em ${bold(login.organizationName)}` : "Autorizado");
857
+ apiKey = login.publicKey;
858
+ instrumentKey = login.secretKey;
746
859
  }
747
860
  if (!apiKey || !apiKey.startsWith("hol_")) {
748
861
  log.error("API key inv\xE1lida. Use formato hol_live_\u2026 ou hol_test_\u2026 \xB7 gere em holdy.chat/settings.");
749
862
  process.exit(1);
750
863
  }
751
- log.step("2", "Aplicando snippet no(s) arquivo(s)\u2026");
864
+ log.step("3", "Aplicando snippet no(s) arquivo(s)\u2026");
752
865
  const result = inject(detection, {
753
866
  apiKey,
754
867
  accountId: args.accountId,
@@ -769,20 +882,20 @@ async function main() {
769
882
  }
770
883
  process.exit(2);
771
884
  }
772
- if (detection.usePackage) {
773
- log.step("3", "Falta instalar o pacote npm:");
885
+ if (detection.usePackage && detection.framework !== "next-app") {
886
+ log.step("4", "Falta instalar o pacote npm:");
774
887
  console.log();
775
888
  console.log(" npm install @holdyapp/sdk");
776
889
  console.log();
777
890
  } else {
778
- log.step("3", "Sem npm install necess\xE1rio \xB7 script tag via CDN.");
891
+ log.step("4", "Sem npm install necess\xE1rio \xB7 script tag via CDN.");
779
892
  }
780
893
  if (args.instrument) {
781
894
  console.log();
782
- log.step("4", "Instrumentando eventos de produto\u2026");
895
+ log.step("5", "Instrumentando eventos de produto\u2026");
783
896
  const code = await runInstrument({
784
897
  cwd,
785
- apiKey,
898
+ apiKey: instrumentKey ?? apiKey,
786
899
  baseUrl: args.apiUrl,
787
900
  framework: detection.framework,
788
901
  sdkStyle: detection.usePackage ? "npm" : "script",
@@ -793,10 +906,10 @@ async function main() {
793
906
  });
794
907
  if (code !== 0) process.exit(code);
795
908
  console.log();
796
- log.step("5", "Pronto! Suba o app, use o produto e veja os eventos chegando em holdy.chat.");
909
+ log.step("6", "Pronto! Suba o app, use o produto e veja os eventos chegando em holdy.chat.");
797
910
  process.exit(0);
798
911
  }
799
- log.step("4", 'Pronto! Inicie o app e abra o widget de status em holdy.chat \xB7 voc\xEA deve ver "conectado" em segundos.');
912
+ log.step("5", "Pronto! Inicie o app e abra holdy.chat/overview \xB7 o primeiro evento aparece em segundos.");
800
913
  process.exit(0);
801
914
  }
802
915
  function parseArgs(argv) {
@@ -808,7 +921,8 @@ function parseArgs(argv) {
808
921
  dryRun: false,
809
922
  yes: false,
810
923
  maxEvents: 12,
811
- apiUrl: process.env.HOLDY_API_URL ?? DEFAULT_API_URL
924
+ apiUrl: process.env.HOLDY_API_URL ?? DEFAULT_API_URL,
925
+ pasteKey: false
812
926
  };
813
927
  for (let i = 0; i < argv.length; i++) {
814
928
  const a = argv[i];
@@ -828,6 +942,8 @@ function parseArgs(argv) {
828
942
  } else if (a === "--max-events") {
829
943
  const parsed = Number(argv[++i]);
830
944
  if (Number.isFinite(parsed)) out.maxEvents = Math.min(20, Math.max(3, Math.trunc(parsed)));
945
+ } else if (a === "--paste-key") {
946
+ out.pasteKey = true;
831
947
  } else if (a === "--api-url") {
832
948
  out.apiUrl = argv[++i] ?? out.apiUrl;
833
949
  } else if (!a.startsWith("--") && !out.apiKey) {
@@ -839,6 +955,14 @@ function parseArgs(argv) {
839
955
  }
840
956
  return out;
841
957
  }
958
+ function projectName(cwd) {
959
+ try {
960
+ const pkg = JSON.parse((0, import_node_fs5.readFileSync)((0, import_node_path5.join)(cwd, "package.json"), "utf8"));
961
+ if (pkg.name) return pkg.name;
962
+ } catch {
963
+ }
964
+ return (0, import_node_path5.basename)(cwd);
965
+ }
842
966
  function promptApiKey() {
843
967
  return new Promise((resolve) => {
844
968
  const rl = (0, import_node_readline2.createInterface)({ input: process.stdin, output: process.stdout });
@@ -854,8 +978,9 @@ function printHelp() {
854
978
  ${bold("@holdyapp/install")} v${VERSION} \xB7 ${isLive ?? "Holdy SDK installer"}
855
979
 
856
980
  ${bold("Uso:")}
981
+ npx @holdyapp/install # entra pelo navegador, sem colar chave
982
+ npx @holdyapp/install --instrument # idem, e j\xE1 prop\xF5e os eventos do seu produto
857
983
  npx @holdyapp/install <apiKey> [op\xE7\xF5es]
858
- npx @holdyapp/install # modo interativo
859
984
  HOLDY_API_KEY=hol_live_\u2026 npx @holdyapp/install # via env var
860
985
 
861
986
  ${bold("Argumentos:")}
@@ -868,6 +993,7 @@ ${bold("Op\xE7\xF5es:")}
868
993
  --dry-run S\xF3 lista os eventos propostos \xB7 n\xE3o escreve nada (implica --instrument)
869
994
  --max-events <n> Teto de eventos propostos \xB7 3 a 20 \xB7 default 12
870
995
  --yes, -y Aplica o diff sem perguntar
996
+ --paste-key Pede a chave no terminal em vez de abrir o navegador
871
997
  --api-url <url> Override da API (default https://holdy.chat/api/v1)
872
998
  --version, -v Mostra vers\xE3o
873
999
  --help, -h Mostra esta mensagem
@@ -884,7 +1010,8 @@ ${bold("Exemplos:")}
884
1010
 
885
1011
  ${bold("Sobre --instrument:")}
886
1012
  Manda trechos do seu c\xF3digo pra Holdy, que devolve onde inserir cada track().
887
- Exige key SECRETA (n\xE3o a p\xFAblica do browser). Voc\xEA v\xEA o diff antes de aplicar,
1013
+ Pelo navegador, a Holdy entrega uma chave secreta que vale 1 hora s\xF3 pra isso.
1014
+ Com chave colada, exige key SECRETA (n\xE3o a p\xFAblica do browser). Voc\xEA v\xEA o diff antes de aplicar,
888
1015
  e nada \xE9 escrito sem confirma\xE7\xE3o. Pageview e rota j\xE1 s\xE3o autom\xE1ticos \u2014 o
889
1016
  planner s\xF3 prop\xF5e momentos em que a conta avan\xE7a no produto.
890
1017
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holdyapp/install",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Holdy install CLI · detects your framework, wires the SDK and instruments product events",
5
5
  "license": "MIT",
6
6
  "homepage": "https://holdy.chat",