@holdyapp/install 0.4.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 +129 -18
  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
  }
@@ -235,7 +238,7 @@ function injectNextPages(file, opts) {
235
238
  if (typeof window !== 'undefined' && !window.__holdyInit) { initHoldy({ apiKey: '${opts.apiKey}', ${opts.accountId ? `accountKey: '${opts.accountId}', ` : ""}autoCapture: ${opts.autoCapture} }); (window as unknown as { __holdyInit?: boolean }).__holdyInit = true }`
236
239
  );
237
240
  (0, import_node_fs2.writeFileSync)(file, patched);
238
- return { ok: true, modified: true, file, message: `Patched ${displayPath(file)} \xB7 rode npm install https://holdy.chat/sdk/holdyapp-sdk-1.5.0.tgz` };
241
+ return { ok: true, modified: true, file, message: `Patched ${displayPath(file)} \xB7 rode npm install @holdyapp/sdk` };
239
242
  }
240
243
  function injectViteReact(file, opts) {
241
244
  if (!file || !(0, import_node_fs2.existsSync)(file)) {
@@ -256,7 +259,7 @@ initHoldy({ apiKey: '${opts.apiKey}', ${opts.accountId ? `accountKey: '${opts.ac
256
259
 
257
260
  ${original}`;
258
261
  (0, import_node_fs2.writeFileSync)(file, patched);
259
- return { ok: true, modified: true, file, message: `Patched ${displayPath(file)} \xB7 rode npm install https://holdy.chat/sdk/holdyapp-sdk-1.5.0.tgz` };
262
+ return { ok: true, modified: true, file, message: `Patched ${displayPath(file)} \xB7 rode npm install @holdyapp/sdk` };
260
263
  }
261
264
  function injectViteVue(file, opts) {
262
265
  return injectViteReact(file, opts);
@@ -278,7 +281,7 @@ export default defineNuxtPlugin(() => {
278
281
  })
279
282
  `;
280
283
  (0, import_node_fs2.writeFileSync)(file, content);
281
- return { ok: true, modified: true, file, message: `Criado ${displayPath(file)} \xB7 rode npm install https://holdy.chat/sdk/holdyapp-sdk-1.5.0.tgz` };
284
+ return { ok: true, modified: true, file, message: `Criado ${displayPath(file)} \xB7 rode npm install @holdyapp/sdk` };
282
285
  }
283
286
  function injectRemix(file, opts) {
284
287
  if (!file || !(0, import_node_fs2.existsSync)(file)) {
@@ -734,8 +737,86 @@ function confirm(question) {
734
737
  });
735
738
  }
736
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
+
737
818
  // src/cli.ts
738
- var VERSION = "0.4.0";
819
+ var VERSION = "0.5.0";
739
820
  var DEFAULT_API_URL = "https://holdy.chat/api/v1";
740
821
  async function main() {
741
822
  const argv = process.argv.slice(2);
@@ -757,14 +838,30 @@ async function main() {
757
838
  log.warn('N\xE3o consegui identificar o framework. Sigo no modo "vanilla" (script tag).');
758
839
  }
759
840
  let apiKey = args.apiKey;
760
- if (!apiKey) {
841
+ let instrumentKey = null;
842
+ if (!apiKey && args.pasteKey) {
761
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;
762
859
  }
763
860
  if (!apiKey || !apiKey.startsWith("hol_")) {
764
861
  log.error("API key inv\xE1lida. Use formato hol_live_\u2026 ou hol_test_\u2026 \xB7 gere em holdy.chat/settings.");
765
862
  process.exit(1);
766
863
  }
767
- log.step("2", "Aplicando snippet no(s) arquivo(s)\u2026");
864
+ log.step("3", "Aplicando snippet no(s) arquivo(s)\u2026");
768
865
  const result = inject(detection, {
769
866
  apiKey,
770
867
  accountId: args.accountId,
@@ -786,19 +883,19 @@ async function main() {
786
883
  process.exit(2);
787
884
  }
788
885
  if (detection.usePackage && detection.framework !== "next-app") {
789
- log.step("3", "Falta instalar o pacote npm:");
886
+ log.step("4", "Falta instalar o pacote npm:");
790
887
  console.log();
791
- console.log(" npm install https://holdy.chat/sdk/holdyapp-sdk-1.5.0.tgz");
888
+ console.log(" npm install @holdyapp/sdk");
792
889
  console.log();
793
890
  } else {
794
- 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.");
795
892
  }
796
893
  if (args.instrument) {
797
894
  console.log();
798
- log.step("4", "Instrumentando eventos de produto\u2026");
895
+ log.step("5", "Instrumentando eventos de produto\u2026");
799
896
  const code = await runInstrument({
800
897
  cwd,
801
- apiKey,
898
+ apiKey: instrumentKey ?? apiKey,
802
899
  baseUrl: args.apiUrl,
803
900
  framework: detection.framework,
804
901
  sdkStyle: detection.usePackage ? "npm" : "script",
@@ -809,10 +906,10 @@ async function main() {
809
906
  });
810
907
  if (code !== 0) process.exit(code);
811
908
  console.log();
812
- 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.");
813
910
  process.exit(0);
814
911
  }
815
- log.step("4", "Pronto! Inicie o app e abra holdy.chat/onboarding \xB7 o primeiro evento aparece em segundos.");
912
+ log.step("5", "Pronto! Inicie o app e abra holdy.chat/overview \xB7 o primeiro evento aparece em segundos.");
816
913
  process.exit(0);
817
914
  }
818
915
  function parseArgs(argv) {
@@ -824,7 +921,8 @@ function parseArgs(argv) {
824
921
  dryRun: false,
825
922
  yes: false,
826
923
  maxEvents: 12,
827
- apiUrl: process.env.HOLDY_API_URL ?? DEFAULT_API_URL
924
+ apiUrl: process.env.HOLDY_API_URL ?? DEFAULT_API_URL,
925
+ pasteKey: false
828
926
  };
829
927
  for (let i = 0; i < argv.length; i++) {
830
928
  const a = argv[i];
@@ -844,6 +942,8 @@ function parseArgs(argv) {
844
942
  } else if (a === "--max-events") {
845
943
  const parsed = Number(argv[++i]);
846
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;
847
947
  } else if (a === "--api-url") {
848
948
  out.apiUrl = argv[++i] ?? out.apiUrl;
849
949
  } else if (!a.startsWith("--") && !out.apiKey) {
@@ -855,6 +955,14 @@ function parseArgs(argv) {
855
955
  }
856
956
  return out;
857
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
+ }
858
966
  function promptApiKey() {
859
967
  return new Promise((resolve) => {
860
968
  const rl = (0, import_node_readline2.createInterface)({ input: process.stdin, output: process.stdout });
@@ -870,8 +978,9 @@ function printHelp() {
870
978
  ${bold("@holdyapp/install")} v${VERSION} \xB7 ${isLive ?? "Holdy SDK installer"}
871
979
 
872
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
873
983
  npx @holdyapp/install <apiKey> [op\xE7\xF5es]
874
- npx @holdyapp/install # modo interativo
875
984
  HOLDY_API_KEY=hol_live_\u2026 npx @holdyapp/install # via env var
876
985
 
877
986
  ${bold("Argumentos:")}
@@ -884,6 +993,7 @@ ${bold("Op\xE7\xF5es:")}
884
993
  --dry-run S\xF3 lista os eventos propostos \xB7 n\xE3o escreve nada (implica --instrument)
885
994
  --max-events <n> Teto de eventos propostos \xB7 3 a 20 \xB7 default 12
886
995
  --yes, -y Aplica o diff sem perguntar
996
+ --paste-key Pede a chave no terminal em vez de abrir o navegador
887
997
  --api-url <url> Override da API (default https://holdy.chat/api/v1)
888
998
  --version, -v Mostra vers\xE3o
889
999
  --help, -h Mostra esta mensagem
@@ -900,7 +1010,8 @@ ${bold("Exemplos:")}
900
1010
 
901
1011
  ${bold("Sobre --instrument:")}
902
1012
  Manda trechos do seu c\xF3digo pra Holdy, que devolve onde inserir cada track().
903
- 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,
904
1015
  e nada \xE9 escrito sem confirma\xE7\xE3o. Pageview e rota j\xE1 s\xE3o autom\xE1ticos \u2014 o
905
1016
  planner s\xF3 prop\xF5e momentos em que a conta avan\xE7a no produto.
906
1017
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holdyapp/install",
3
- "version": "0.4.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",