@zenovay/cli 0.1.66 → 0.1.67
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/dist/{ai-Vpr5B5_M.js → ai-DbtIo6k2.js} +1 -1
- package/dist/bin.js +2 -2
- package/dist/init-BuyESMBj.js +79 -0
- package/dist/{init-CNwLaGfm.js → init-WmEUW4Em.js} +1 -1
- package/dist/{login-DuKqHRot.js → login-CaDs2Peq.js} +2 -2
- package/dist/{login-DPaqczTC.js → login-wpGuU_Zm.js} +1 -1
- package/dist/wizard-bin.js +1 -1
- package/package.json +20 -18
- package/dist/init-C_I6rfNw.js +0 -79
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readConfig as e}from"./config--JRu_jFq.js";import"./api-FCanTxsb.js";import{readToken as t}from"./token-store-BQuGH0z2.js";import{emit as n}from"./telemetry-DRh10SvO.js";import{ApiV2Client as r}from"./api-v2-CPUUcP1i.js";import{detectInstalled as i,initCommand as a}from"./init-
|
|
1
|
+
import{readConfig as e}from"./config--JRu_jFq.js";import"./api-FCanTxsb.js";import{readToken as t}from"./token-store-BQuGH0z2.js";import{emit as n}from"./telemetry-DRh10SvO.js";import{ApiV2Client as r}from"./api-v2-CPUUcP1i.js";import{detectInstalled as i,initCommand as a}from"./init-BuyESMBj.js";import"./formatter-DDG2TSOz.js";import{emit$1 as o,isHeadless as s}from"./emit-C9vAopcy.js";import"./secrets-DPf_7SkN.js";import{parseSseStream as c}from"./client-Dm8uGVCy.js";import"./open-url-BxXbZzWS.js";import"./login-CaDs2Peq.js";import"./theme-BuGrQHax.js";import{Panel as l,useTheme as u}from"./panel-B-XxdP-N.js";import{Banner as d}from"./banner-DdgwXGzt.js";import"./progress-row-5ns2IsFh.js";import{Select as f}from"./prompt-B4s3o9G7.js";import"./keybar-QgnmSion.js";import"./sparkline-CpoBjjlc.js";import{ChatPanel as p}from"./chat-panel-ZlKZ0W6J.js";import{resolveSiteId as m}from"./resolve-site-DQVcWhjG.js";import{Box as h,Static as g,Text as _,render as v,useApp as y,useInput as b}from"ink";import x,{useEffect as S,useRef as C,useState as w}from"react";import T from"ink-spinner";import E from"ink-text-input";const D=30,O=9e4;function k(e){return e.replace(/_/g,` `)}function A(e){if(!e)return``;let t=Object.entries(e).map(([e,t])=>`${e}: ${JSON.stringify(t)}`).join(`, `);return t.length>80?t.slice(0,77)+`…`:t}const j=({cliVersion:e,api:t,siteId:r,siteLabel:i,initialAllowAll:a=!1,onClose:o})=>{let{theme:s}=u(),{exit:f}=y(),[p,m]=w([]),[v,j]=w(``),[M,N]=w(!1),[P,F]=w(``),[I,L]=w([]),[R,z]=w(null),[B,V]=w(a),[H,U]=w(!1),[W,G]=w(0),K=C(``),q=C(null),J=C(Date.now());S(()=>{if(!M)return;let e=setInterval(()=>{K.current!==P&&F(K.current)},Math.floor(1e3/D));return()=>clearInterval(e)},[M,P]),S(()=>(n(`mcp.open`,{properties:{has_site:!!r}}),()=>{n(`mcp.close`,{duration_ms:Date.now()-J.current})}),[r]),b((e,t)=>{if(t.escape||t.ctrl&&e===`c`){q.current?.abort(),o();return}if(e===`a`||e===`A`){V(!0),H&&U(!1);return}if(e===`x`||e===`X`){q.current?.abort(),z(`Cancelled.`),U(!1),N(!1);return}if((t.return||e===`y`||e===`Y`)&&H){U(!1);return}if((e===`n`||e===`N`)&&H){q.current?.abort(),z(`Cancelled by user.`),U(!1),N(!1);return}});let Y=async e=>{let i=e.trim();if(!i||M||H)return;j(``),z(null),m(e=>[...e,{role:`user`,content:i}]),K.current=``,F(``),L([]),G(0),N(!0),q.current=new AbortController;let a=setTimeout(()=>{q.current?.abort(),z(`Request timed out after ${O/1e3}s`)},O);try{let e=p.map(e=>({role:e.role,content:e.content}));e.push({role:`user`,content:i});let a=await t.openAiStream(`mcp`,{messages:e,siteId:r},q.current.signal);if(!a.ok){z(`MCP request failed (${a.status})`);return}let o=0,s=new Map;for await(let e of c(a))if(e.type===`delta`)K.current+=e.content;else if(e.type===`tool_call`)s.set(e.id,e.arguments),o+=1,G(o),n(`mcp.tool_call`,{properties:{tool_name:e.name}}),L(t=>[...t,{id:e.id,name:e.name,argsPreview:A(e.arguments),status:`running`}]);else if(e.type===`tool_start`){let t=s.get(e.id);L(r=>r.some(t=>t.id===e.id)?r:(o+=1,G(o),n(`mcp.tool_call`,{properties:{tool_name:e.name}}),[...r,{id:e.id,name:e.name,argsPreview:A(t),status:`running`}]))}else if(e.type===`tool_result`)L(t=>t.map(t=>t.id===e.id?{...t,status:e.error?`error`:`done`,result:e.summary,error:e.error,name:e.name??t.name}:t));else if(e.type===`done`){let e=K.current;m(t=>[...t,{role:`assistant`,content:e||``}]),o>0&&!B&&U(!0);break}else if(e.type===`error`){z(e.message);break}}catch(e){e.name!==`AbortError`&&z(e.message??`unknown error`)}finally{clearTimeout(a),K.current=``,F(``),N(!1),q.current=null}},X=[`mcp`,i].filter(Boolean).join(` · `);return x.createElement(h,{flexDirection:`column`},x.createElement(d,{version:e,subtitle:X}),p.length===0&&!M?x.createElement(h,{marginTop:1,marginLeft:2,flexDirection:`column`},x.createElement(_,{color:s.muted},`Describe what you want to create or change. Examples:`),x.createElement(h,{marginTop:0,flexDirection:`column`},x.createElement(_,{color:s.dim},` · "Create a goal for newsletter signups"`),x.createElement(_,{color:s.dim},` · "Add a funnel: landing page → signup → dashboard"`),x.createElement(_,{color:s.dim},` · "Annotate today with our product launch"`),x.createElement(_,{color:s.dim},` · "Set up uptime monitoring for /api/health"`)),x.createElement(h,{marginTop:1},x.createElement(_,{color:s.dim},`MCP actions execute server-side. `),x.createElement(_,{color:s.warn},`Review each turn before continuing.`)),x.createElement(h,{marginTop:0},x.createElement(_,{color:s.dim},`[a] allow-all [Esc] close`))):null,x.createElement(g,{items:p},(e,t)=>x.createElement(h,{key:t,marginTop:1},x.createElement(l,{title:e.role===`user`?`you`:`Zenovay MCP`,pillColor:e.role===`user`?s.accent:s.info,state:`idle`},x.createElement(_,{color:s.fg,wrap:`wrap`},e.content)))),M?x.createElement(h,{flexDirection:`column`},I.length>0?x.createElement(h,{marginTop:1},x.createElement(l,{title:`Actions${B?` (allow-all)`:``}`,state:I.some(e=>e.status===`running`)?`busy`:`ok`},I.map(e=>x.createElement(h,{key:e.id,flexDirection:`column`,marginBottom:0},x.createElement(h,{flexDirection:`row`},x.createElement(_,{color:e.status===`error`?s.error:e.status===`done`?s.success:s.accent},e.status===`done`?`✓ `:e.status===`error`?`✗ `:`⋯ `),x.createElement(_,{color:s.fg,bold:!0},k(e.name))),e.argsPreview?x.createElement(h,{marginLeft:2},x.createElement(_,{color:s.dim},e.argsPreview)):null,e.status===`done`&&e.result?x.createElement(h,{marginLeft:2},x.createElement(_,{color:s.success},`→ `,e.result.slice(0,80))):null,e.status===`error`&&e.error?x.createElement(h,{marginLeft:2},x.createElement(_,{color:s.error},`✗ `,e.error.slice(0,80))):null)))):null,P.length>0?x.createElement(h,{marginTop:1},x.createElement(l,{title:`Zenovay MCP`,pillColor:s.info,state:`busy`},x.createElement(_,{color:s.fg,wrap:`wrap`},P))):I.every(e=>e.status!==`running`)||I.length===0?x.createElement(h,{marginTop:1,marginLeft:2,flexDirection:`row`},x.createElement(_,{color:s.accent},x.createElement(T,{type:`dots`})),x.createElement(_,{color:s.muted},` Planning actions…`)):null):null,H&&!M?x.createElement(h,{marginTop:1},x.createElement(l,{title:`Continue?`,state:`warn`,pillColor:s.warn},x.createElement(_,{color:s.muted},W,` action`,W===1?``:`s`,` executed this turn. Send another message?`),x.createElement(h,{marginTop:1,flexDirection:`row`},x.createElement(_,{color:s.success},`[Enter / y] `),x.createElement(_,{color:s.dim},`continue `),x.createElement(_,{color:s.error},`[n] `),x.createElement(_,{color:s.dim},`cancel `),x.createElement(_,{color:s.accent},`[a] `),x.createElement(_,{color:s.dim},`allow-all this session `),x.createElement(_,{color:s.warn},`[x] `),x.createElement(_,{color:s.dim},`abort`)))):null,R?x.createElement(h,{marginTop:1,marginLeft:2},x.createElement(_,{color:s.error},`✗ `,R)):null,x.createElement(h,{marginTop:1,marginLeft:2,flexDirection:`column`},x.createElement(h,{flexDirection:`row`},x.createElement(_,{color:M||H?s.dim:s.accent},M?`⋯ `:H?`? `:`> `),x.createElement(E,{value:v,onChange:j,onSubmit:e=>void Y(e),placeholder:M?`executing…`:H?`press Enter to continue, n to cancel…`:`describe what to create or change…`})),x.createElement(h,{marginTop:1},x.createElement(_,{color:s.dim},`[Enter] send [a] allow-all [x] abort [Esc] close`,B?` `:``),B?x.createElement(_,{color:s.success},` ● allow-all`):null)))};async function M(e){let t=await i(e.cwd),n;if(e.mode)n=e.mode;else if(!t.installed)n=`install`;else if(s(e))return o({type:`error`,code:`no_mode`,message:`Headless mode requires --mode chat|mcp|install`}),2;else n=await N(t);return n===`install`?a({cliVersion:e.cliVersion,cwd:e.cwd,json:e.json}):n===`chat`?P(e):F(e)}async function N(e){return new Promise(t=>{let n=[{label:`Chat with your analytics`,value:`chat`,description:`Ask natural-language questions about your data`},{label:`Manage via MCP`,value:`mcp`,description:`Create goals, funnels, sites, etc. in plain English`},{label:`Re-run install / repair`,value:`install`,description:`Run the install wizard again`}],{unmount:r}=v(x.createElement(h,{flexDirection:`column`},x.createElement(l,{title:`Zenovay detected${e.packageMarker?` (${e.packageMarker})`:``}`,state:`idle`},x.createElement(_,null,`What do you want to do?`)),x.createElement(f,{items:n,onSelect:e=>{r(),t(e)}})))})}async function P(n){let i=await e(),a=await t({strict:!1});if(!a)return process.stderr.write("Not logged in — run `zenovay login` first.\n"),2;let o=i.defaultSiteId,s=i.defaultSiteName,c=new r({config:i,cliVersion:n.cliVersion,token:a});return new Promise(e=>{let{unmount:t}=v(x.createElement(p,{cliVersion:n.cliVersion,api:c,dashboardContext:{commandName:`ai chat`,siteId:o,siteLabel:s,metricsSnapshot:null},onClose:()=>{t(),e(0)}}))})}async function F(n){let i=await e(),a=await t({strict:!1});if(!a)return process.stderr.write("Not logged in — run `zenovay login` first.\n"),2;let o=new r({config:i,cliVersion:n.cliVersion,token:a}),s,c;try{let e=await m(o,{explicit:void 0,headless:!1,cliVersion:n.cliVersion});s=e.siteId,c=e.site?.url?e.site.url.replace(/^https?:\/\//,``).replace(/\/$/,``):e.site?.name??s}catch{}return new Promise(e=>{let{unmount:t}=v(x.createElement(j,{cliVersion:n.cliVersion,api:o,siteId:s,siteLabel:c,onClose:()=>{t(),e(0)}}))})}export{M as aiCommand};
|
package/dist/bin.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import{configDir as e,readConfig as t}from"./config--JRu_jFq.js";import{manifestSchema as n}from"./check-D9G0N7NB.js";import{applyManifest as r}from"./apply-B19bFTz0.js";import"./api-FCanTxsb.js";import"./token-store-BQuGH0z2.js";import{normalizeError as i}from"./error-codes-C5caZH9p.js";import{emit as a,flush as o,initTelemetry as s}from"./telemetry-DRh10SvO.js";import{cac as c}from"cac";import{fork as l,spawn as u}from"node:child_process";import d from"node:path";import{fileURLToPath as ee}from"node:url";import{promises as f,readFileSync as p}from"node:fs";import{createInterface as m}from"node:readline";import h from"semver";import{z as g}from"zod";const _=2e3,v=3e4,y=5e3,b=5e3,x=36e5,te=new Set([`update`,`health`,`doctor`,`--version`,`-v`]),ne=g.object({checkedAt:g.number(),latest:g.string()});function S(){return d.join(e(),`update-cache.json`)}async function re(){try{let e=await f.readFile(S(),`utf8`),t=ne.safeParse(JSON.parse(e));return t.success?t.data:null}catch{return null}}async function C(t){try{await f.mkdir(e(),{recursive:!0,mode:448}),await f.writeFile(S(),JSON.stringify(t),{mode:384})}catch{}}const w=g.object({latest:g.string(),minSupported:g.string().optional(),platforms:g.record(g.string(),g.unknown()).optional(),downloads:g.record(g.string(),g.unknown()).optional()});async function T(e){if(E(e.argv))return;let t=null,n=await re();if(n&&Date.now()-n.checkedAt<x)t={latest:n.latest,platforms:{}};else{try{t=await D()}catch(e){P(`update check failed: ${e.message}`);return}t&&await C({checkedAt:Date.now(),latest:t.latest})}if(!t||!h.valid(e.currentVersion)||!h.valid(t.latest)||h.eq(e.currentVersion,t.latest)||h.lt(t.latest,e.currentVersion))return;let r=h.diff(e.currentVersion,t.latest);if(!r)return;let i=O();if(r===`patch`||r===`prepatch`){let n=await k(t,e,i);if(n&&i===`npm-global`)return;n&&N(e.argv);return}if(r===`minor`||r===`preminor`||r===`major`||r===`premajor`||r===`prerelease`){let n=await M(t.latest);if(!n){process.stderr.write(`Continuing with ${e.currentVersion}.\n`);return}let r=await k(t,e,i);if(r&&i===`npm-global`)return;r&&N(e.argv);return}}function E(e){if(process.env.ZENOVAY_NO_UPDATE_CHECK===`1`||process.env.CI===`true`||e.includes(`--no-update-check`))return!0;let t=e[2];return!!(t&&te.has(t))}async function D(){let e=O();if(e===`npm-global`||e===`npm-local`||e===`unknown`)try{let e=await fetch(`https://registry.npmjs.org/@zenovay/cli/latest`,{signal:AbortSignal.timeout(_),headers:{Accept:`application/json`}});if(!e.ok)return null;let t=await e.json();return t.version?{latest:t.version,platforms:{}}:null}catch{return null}let r=await t(),i=`https://zenovay-cli-distribution.zenovay.workers.dev`,a=r.cliBase===i?[r.cliBase]:[r.cliBase,i];for(let e of a)try{let t=await fetch(`${e}/version`,{signal:AbortSignal.timeout(_)});if(!t.ok)continue;let r=await t.json(),i=n.safeParse(r);if(i.success)return i.data;let a=w.safeParse(r);if(a.success)return{latest:a.data.latest,minSupported:a.data.minSupported,platforms:{}}}catch{}return null}function O(){let e=process.argv[1]??``;if(!e)return`unknown`;let t=e.replace(/\\/g,`/`);return t.includes(`/_npx/`)?`npm-local`:t.includes(`/node_modules/@zenovay/cli/`)?`npm-global`:t.includes(`/node_modules/.bin/zenovay`)?`npm-local`:t.includes(`/node_modules/`)?`unknown`:`binary`}async function k(e,n,i){if(i===`npm-local`)return process.stderr.write(`Update available — \`npm i -g @zenovay/cli@${e.latest}\` to upgrade.\n`),!1;if(i===`unknown`)return P(`unknown install method; skipping auto-update (latest ${e.latest})`),!1;if(i===`npm-global`){if(process.env.ZENOVAY_AUTO_UPDATE!==`1`){if(!process.stdin.isTTY||!process.stderr.isTTY)return process.stderr.write(`Update available (${e.latest}). Run \`npm i -g @zenovay/cli@${e.latest}\` to upgrade, or set ZENOVAY_AUTO_UPDATE=1 for unattended updates.\n`),!1;let t=await A(e.latest);if(!t)return process.stderr.write(`Skipped. Run \`npm i -g @zenovay/cli@${e.latest}\` to upgrade later.\n`),!1}return await j(e.latest)}try{let i=await t(),a=await Promise.race([r({currentVersion:n.currentVersion,cliBase:i.cliBase,binaryPath:n.binaryPath,manifest:e}),new Promise((e,t)=>setTimeout(()=>t(Error(`patch install timeout`)),y))]);return a.updated?!0:(P(`update skipped: ${a.reason??`unknown`}`),!1)}catch(e){return P(`update failed: ${e.message}`),!1}}function A(e){return new Promise(t=>{let n=m({input:process.stdin,output:process.stderr});n.question(`Update @zenovay/cli to ${e} via \`npm i -g\` (unverified npm install)? [y/N] `,e=>{n.close();let r=e.trim().toLowerCase();t(r===`y`||r===`yes`)})})}function j(e){return new Promise(t=>{process.stderr.write(` Updating @zenovay/cli to ${e}...\n`);let n=process.platform===`win32`?`npm.cmd`:`npm`,r=[`install`,`-g`,`@zenovay/cli@${e}`],i={stdio:[`ignore`,`ignore`,`pipe`]},a=``,o=!1,s=u(n,r,i),c=setTimeout(()=>{if(!o){o=!0;try{s.kill(`SIGKILL`)}catch{}P(`npm install timed out after ${v/1e3}s; continuing with current version`),t(!1)}},v);s.stderr?.on(`data`,e=>{a+=e.toString(`utf8`)}),s.on(`error`,e=>{o||(o=!0,clearTimeout(c),P(`npm install error: ${e.message}`),t(!1))}),s.on(`exit`,n=>{o||(o=!0,clearTimeout(c),n===0&&(process.stderr.write(` ✔ Updated to ${e}. Re-run your command to use the new version.\n`),process.exit(0)),/EACCES|permission denied/i.test(a)?process.stderr.write(`Auto-update needs sudo for the global npm prefix. Run: sudo npm i -g @zenovay/cli@${e}\n`):P(`npm install exited ${n}; continuing with current version`),t(!1))})})}function M(e){return new Promise(t=>{if(!process.stdin.isTTY||!process.stderr.isTTY){t(!0);return}process.stderr.write(`New version ${e} available. Update? [Y/n] (auto-yes 5s) `);let n=!1,r=e=>{if(n)return;let r=e.toString(`utf8`).trim().toLowerCase();n=!0,i(),process.stderr.write(`
|
|
3
3
|
`),t(r===``||r===`y`||r===`yes`)},i=()=>{clearTimeout(a),process.stdin.off(`data`,r);try{process.stdin.pause()}catch{}},a=setTimeout(()=>{n||(n=!0,i(),process.stderr.write(`
|
|
4
|
-
`),t(!0))},b);try{process.stdin.resume(),process.stdin.on(`data`,r)}catch{i(),t(!0)}})}function N(e){try{let t=e.slice(1),n=u(process.execPath,t,{stdio:`inherit`});n.on(`exit`,e=>{process.exit(typeof e==`number`?e:0)}),n.on(`error`,()=>{})}catch{}}function P(e){if(process.env.ZENOVAY_DEBUG_UPDATE===`1`)try{process.stderr.write(`zenovay: ${e}\n`)}catch{}}var F=`@zenovay/cli`,I=`0.1.
|
|
4
|
+
`),t(!0))},b);try{process.stdin.resume(),process.stdin.on(`data`,r)}catch{i(),t(!0)}})}function N(e){try{let t=e.slice(1),n=u(process.execPath,t,{stdio:`inherit`});n.on(`exit`,e=>{process.exit(typeof e==`number`?e:0)}),n.on(`error`,()=>{})}catch{}}function P(e){if(process.env.ZENOVAY_DEBUG_UPDATE===`1`)try{process.stderr.write(`zenovay: ${e}\n`)}catch{}}var F=`@zenovay/cli`,I=`0.1.67`,L=`Zenovay CLI — AI install wizard + full terminal analytics dashboard`,R=`module`,ie={zenovay:`dist/bin.js`,zv:`dist/bin.js`},z=[`dist/**/*.js`,`dist/**/*.cjs`,`dist/**/*.mjs`,`dist/**/*.d.ts`,`README.md`,`LICENSE`],B={dev:`tsx src/bin.ts`,build:`tsdown`,"build:binaries":`tsx scripts/build-binaries.ts`,typecheck:`tsc --noEmit`,lint:`eslint 'src/**/*.{ts,tsx}'`,test:`vitest run`,"test:watch":`vitest`,"test:all":`vitest run tests/unit tests/integration tests/security`,"test:release":`vitest run tests/e2e tests/update --testTimeout=600000`,"test:smoke":`vitest run tests/smoke --testTimeout=60000`,"test:update-golden":`UPDATE_GOLDEN=1 vitest run tests/e2e`,"render:globe":`tsx scripts/render-globe.ts`,smoke:`bash scripts/smoke-test.sh`,prepare:`husky install`},V={"@hono/node-server":`^1.14.0`,cac:`^6.7.14`,chalk:`^5.4.1`,diff:`^7.0.0`,globby:`^14.1.0`,ink:`^5.1.0`,"ink-gradient":`^3.0.0`,"ink-select-input":`^6.0.0`,"ink-spinner":`^5.0.0`,"ink-text-input":`^6.0.0`,open:`^10.1.0`,react:`^18.3.1`,semver:`^7.6.3`,ulid:`^2.3.0`,zod:`^3.23.8`},H={"@aws-sdk/client-s3":`^3.1036.0`,"@types/diff":`^7.0.0`,"@types/node":`^22.10.0`,"@types/react":`^18.3.12`,"@types/semver":`^7.5.8`,"@typescript-eslint/parser":`^8.61.0`,"@vitest/coverage-v8":`^2.1.8`,"csv-parse":`^6.2.1`,eslint:`^9.16.0`,husky:`^9.1.7`,"ink-testing-library":`^4.0.0`,"react-devtools-core":`^4.19.1`,tsdown:`^0.9.0`,tsx:`^4.19.2`,typescript:`^5.7.2`,vitest:`^2.1.8`},U={node:`>=20.0.0`},W=`pnpm@9.15.9`,G=`Zenovay <cli@zenovay.com>`,K=`SEE LICENSE IN LICENSE`,q=`https://cli.zenovay.com`,J={type:`git`,url:`https://github.com/zenovay/cli-zenovay.git`},Y={url:`https://cli.zenovay.com/feedback`},ae=[`zenovay`,`analytics`,`cli`,`tui`,`ai-agent`,`install-wizard`],oe={name:F,version:I,description:L,type:R,bin:ie,files:z,scripts:B,dependencies:V,devDependencies:H,engines:U,packageManager:W,author:G,license:K,homepage:q,repository:J,bugs:Y,keywords:ae};const se=d.dirname(ee(import.meta.url)),X=oe.version,Z=process.argv[0]??`zenovay`;function ce(){if(process.env.ZENOVAY_BACKGROUND_UPDATE===`1`&&!(process.env.ZENOVAY_NO_UPDATE_CHECK===`1`||process.env.CI===`true`))try{let e=d.join(se,`lib`,`update`,`worker.js`),t=l(e,[],{detached:!0,stdio:`ignore`,env:{...process.env,ZENOVAY_UPDATE_WORKER_FORK:`1`}});t.unref()}catch{}}const Q=c(`zenovay`);Q.version(X),Q.help(e=>{let t=e.filter(e=>{let t=e.title??``;return!/for more info/i.test(t)});return t.push({body:"Run `zenovay <command> --help` for command-specific options and examples."}),t}),Q.option(`-q, --quiet`,`Suppress spinners + interactive UI; implies --json`),Q.option(`--team <name-or-id>`,"Team override for this command (alternative to `zenovay teams switch`)"),(process.argv.includes(`-q`)||process.argv.includes(`--quiet`))&&(process.env.ZENOVAY_FORMAT=`json`,process.argv.includes(`--json`)||process.argv.push(`--json`));const $=process.argv.findIndex(e=>e===`--team`);if($!==-1&&process.argv[$+1]&&(process.env.ZENOVAY_TEAM_ID=process.argv[$+1]),!process.env.ZENOVAY_TEAM_ID)try{let e=process.env.HOME??process.env.USERPROFILE??``,t=d.join(e,`.config`,`zenovay`,`config.json`),n=p(t,`utf8`),r=JSON.parse(n);r.currentTeamId&&(process.env.ZENOVAY_TEAM_ID=r.currentTeamId)}catch{}Q.command(`init`,`AI install wizard — add Zenovay tracking to your codebase`).option(`--json`,`Emit NDJSON events to stdout`).option(`--yes`,`Auto-approve all diffs (for CI)`).option(`--skip-mcp`,`Skip MCP client install`).option(`--skip-verify`,`Skip first-event verification`).option(`--site-id <id>`,`Install for a specific site id (skips the picker)`).action(async e=>{let{initCommand:t}=await import(`./init-WmEUW4Em.js`),n=await t({cliVersion:X,cwd:process.cwd(),json:e.json,yes:e.yes,skipMcp:e.skipMcp,skipVerify:e.skipVerify,siteId:e.siteId});process.exitCode=n}),Q.command(`login`,`Authenticate via OAuth device flow`).option(`--json`,`Emit NDJSON events`).option(`--no-browser`,`Skip opening the browser automatically`).action(async e=>{let{loginCommand:t}=await import(`./login-wpGuU_Zm.js`),n=await t({cliVersion:X,json:e.json,openBrowser:e.browser!==!1,onCode:e=>{process.stdout.write(`\n Open this URL to authorize the CLI:\n ${e.verificationUriComplete??e.verificationUri}\n Or enter code: ${e.userCode}\n\n`)}});!e.json&&n.email?process.stdout.write(`\n ✔ Logged in as ${n.email}\n Try \`zenovay overview\` to see your dashboard, or \`zenovay --help\` for the full command list.\n\n`):e.json||process.stdout.write(`
|
|
5
5
|
✔ Logged in.
|
|
6
6
|
|
|
7
|
-
`)}),Q.command(`logout`,`Revoke current session and delete local auth`).option(`--json`,``).action(async e=>{let{logoutCommand:t}=await import(`./logout-BkeT6Hke.js`);await t({cliVersion:X,json:e.json})}),Q.command(`ai`,`3-mode AI assistant (install / chat / mcp)`).option(`--mode <mode>`,`install | chat | mcp`).option(`--json`,``).action(async e=>{let{aiCommand:t}=await import(`./ai-
|
|
7
|
+
`)}),Q.command(`logout`,`Revoke current session and delete local auth`).option(`--json`,``).action(async e=>{let{logoutCommand:t}=await import(`./logout-BkeT6Hke.js`);await t({cliVersion:X,json:e.json})}),Q.command(`ai`,`3-mode AI assistant (install / chat / mcp)`).option(`--mode <mode>`,`install | chat | mcp`).option(`--json`,``).action(async e=>{let{aiCommand:t}=await import(`./ai-DbtIo6k2.js`),n=await t({cliVersion:X,cwd:process.cwd(),mode:e.mode,json:e.json});process.exitCode=n}),Q.command(`watch`,"[ANALYTICS] Alias for `analytics` (dashboard summary)").option(`--site-id <id>`,`Site ID`).option(`--interval <ms>`,`Poll interval ms (legacy; ignored by analytics)`,{default:15e3}).option(`--json`,``).option(`--csv`,`Emit top-pages as CSV`).option(`--tsv`,`Emit top-pages as TSV`).option(`--ndjson`,`Emit top-pages as NDJSON (one row per line)`).action(async e=>{let{analyticsCommand:t}=await import(`./analytics-BO22Zeas.js`),n=await t({cliVersion:X,siteId:e.siteId,json:e.json,csv:e.csv,tsv:e.tsv,ndjson:e.ndjson});process.exitCode=n}),Q.command(`analytics`,`[ANALYTICS] Landing dashboard — visitors, pageviews, bounce, top pages`).option(`--site-id <id>`,`Site ID`).option(`--json`,``).option(`--csv`,`Emit top-pages as CSV`).option(`--tsv`,`Emit top-pages as TSV`).option(`--ndjson`,`Emit top-pages as NDJSON (one row per line)`).action(async e=>{let{analyticsCommand:t}=await import(`./analytics-BO22Zeas.js`),n=await t({cliVersion:X,siteId:e.siteId,json:e.json,csv:e.csv,tsv:e.tsv,ndjson:e.ndjson});process.exitCode=n}),Q.command(`overview`,"[ANALYTICS] Alias for `analytics`").option(`--site-id <id>`,`Site ID`).option(`--json`,``).option(`--csv`,`Emit top-pages as CSV`).option(`--tsv`,`Emit top-pages as TSV`).option(`--ndjson`,`Emit top-pages as NDJSON (one row per line)`).action(async e=>{let{analyticsCommand:t}=await import(`./analytics-BO22Zeas.js`),n=await t({cliVersion:X,siteId:e.siteId,json:e.json,csv:e.csv,tsv:e.tsv,ndjson:e.ndjson});process.exitCode=n}),Q.command(`home`,`[ACCOUNT] Top-level summary — sites + this-week deltas`).option(`--json`,``).action(async e=>{let{homeCommand:t}=await import(`./home-CXwRtMge.js`);process.exitCode=await t({cliVersion:X,json:e.json})}),Q.command(`live`,`[ANALYTICS] Real-time visitors + globe + recent events (2s polling)`).option(`--site-id <id>`,`Site ID`).option(`--json`,``).action(async e=>{let{liveCommand:t}=await import(`./live-CWSLgkC2.js`),n=await t({cliVersion:X,siteId:e.siteId,json:e.json});process.exitCode=n}),Q.command(`pages`,`[ANALYTICS] Top pages by views — last 24h`).option(`--site-id <id>`,`Site ID`).option(`--json`,``).option(`--csv`,`Emit pages as CSV`).option(`--tsv`,`Emit pages as TSV`).option(`--ndjson`,`Emit pages as NDJSON (one row per line)`).action(async e=>{let{pagesCommand:t}=await import(`./pages-CKs5X38q.js`),n=await t({cliVersion:X,siteId:e.siteId,json:e.json,csv:e.csv,tsv:e.tsv,ndjson:e.ndjson});process.exitCode=n}),Q.command(`devices`,`[ANALYTICS] Device / browser / OS breakdown — last 24h`).option(`--site-id <id>`,`Site ID`).option(`--json`,``).option(`--csv`,`Emit devices as CSV`).option(`--tsv`,`Emit devices as TSV`).option(`--ndjson`,`Emit devices as NDJSON (one row per line)`).action(async e=>{let{devicesCommand:t}=await import(`./devices-CrkIxfde.js`),n=await t({cliVersion:X,siteId:e.siteId,json:e.json,csv:e.csv,tsv:e.tsv,ndjson:e.ndjson});process.exitCode=n}),Q.command(`geo`,`[ANALYTICS] Countries + cities + globe — last 24h`).option(`--site-id <id>`,`Site ID`).option(`--json`,``).option(`--csv`,`Emit countries as CSV`).option(`--tsv`,`Emit countries as TSV`).option(`--ndjson`,`Emit countries as NDJSON (one row per line)`).action(async e=>{let{geoCommand:t}=await import(`./geo-C9OcRStg.js`),n=await t({cliVersion:X,siteId:e.siteId,json:e.json,csv:e.csv,tsv:e.tsv,ndjson:e.ndjson});process.exitCode=n}),Q.command(`retention`,`[BEHAVIOR] Cohort retention heatmap — D1 / D7 / D30 (Pro+)`).option(`--site-id <id>`,`Site ID`).option(`--json`,``).action(async e=>{let{retentionCommand:t}=await import(`./retention-B8bgiVux.js`),n=await t({cliVersion:X,siteId:e.siteId,json:e.json});process.exitCode=n}),Q.command(`uptime`,`[BEHAVIOR] Uptime monitors with status + p95 (Pro+)`).option(`--site-id <id>`,`Site ID`).option(`--json`,``).action(async e=>{let{uptimeCommand:t}=await import(`./uptime-CwG6bqir.js`),n=await t({cliVersion:X,siteId:e.siteId,json:e.json});process.exitCode=n}),Q.command(`sessions`,`[BEHAVIOR] Recent session-replay list (Pro+)`).option(`--site-id <id>`,`Site ID`).option(`--json`,``).action(async e=>{let{sessionsCommand:t}=await import(`./sessions-lUUiiNYc.js`),n=await t({cliVersion:X,siteId:e.siteId,json:e.json});process.exitCode=n}),Q.command(`heatmaps`,`[BEHAVIOR] Recent heatmap captures (Pro+)`).option(`--site-id <id>`,`Site ID`).option(`--json`,``).action(async e=>{let{heatmapsCommand:t}=await import(`./heatmaps-G3Nf95tV.js`),n=await t({cliVersion:X,siteId:e.siteId,json:e.json});process.exitCode=n}),Q.command(`journeys`,`[BEHAVIOR] Page-to-page transition flow (Pro+)`).option(`--site-id <id>`,`Site ID`).option(`--json`,``).action(async e=>{let{journeysCommand:t}=await import(`./journeys-BLxqoeFu.js`),n=await t({cliVersion:X,siteId:e.siteId,json:e.json});process.exitCode=n}),Q.command(`insights`,`[INTELLIGENCE] AI-generated anomaly / trend / recommendation cards (Pro+)`).option(`--site-id <id>`,`Site ID`).option(`--json`,``).action(async e=>{let{insightsCommand:t}=await import(`./insights-J99-8Rcu.js`),n=await t({cliVersion:X,siteId:e.siteId,json:e.json});process.exitCode=n}),Q.command(`events [action]`,`[OPERATIONS] events tail [--filter --forward-to --replay-24h] — stream live events`).option(`--site-id <id>`,`Site ID`,{default:``}).option(`--filter <expr>`,`Filter expression (e.g. 'type=pageview AND country=US')`).option(`--forward-to <url>`,`POST each event to a webhook with HMAC-SHA256 signature`).option(`--replay-24h`,`Replay the last 24h before going live`).option(`--set-forward-secret <hex>`,`Persist the HMAC secret used by --forward-to`).option(`--json`,``).action(async(e,t)=>{e!==`tail`&&(process.stderr.write(t.json?JSON.stringify({type:`error`,code:`validation_failed`,message:`Unknown events action: ${e??`(none)`}`,hint:`Usage: zenovay events tail`})+`
|
|
8
8
|
`:`\n✗ Unknown events action: '${e??`(none)`}'\n Usage: zenovay events tail. Run \`zenovay events tail --help\`\n`),process.exit(2));let{eventsTailCommand:n}=await import(`./events-tail-DGRZewwc.js`),r=await n({cliVersion:X,siteId:t.siteId,json:t.json,filter:t.filter,forwardTo:t.forwardTo,replay24h:t.replay24h,setForwardSecret:t.setForwardSecret});process.exitCode=r}),Q.command(`health`,`Run self-diagnostic checks`).option(`--json`,``).action(async e=>{let{healthCommand:t}=await import(`./health-vxoiMF8G.js`),n=await t({cliVersion:X,binaryPath:Z,json:e.json});process.exitCode=n}),Q.command(`annotation [action]`,`[OPERATIONS] annotation create [--type --message --occurred-at] (deploy / release / campaign / incident / custom)`).option(`--type <type>`,`One of: deploy, release, campaign, incident, custom`,{default:`custom`}).option(`--message <message>`,`Short description (1–500 chars)`,{default:``}).option(`--occurred-at <iso>`,`ISO 8601 timestamp (default: now)`).option(`--site-id <id>`,`Site ID`).option(`--json`,``).action(async(e,t)=>{let n=e??`create`;if(n!==`create`){process.stderr.write(`Unknown annotation action: ${n}. Try: zenovay annotation create --type=deploy --message="..."\n`),process.exitCode=2;return}let{annotationCreateCommand:r}=await import(`./annotation-CL1q8mjr.js`),i=await r({cliVersion:X,type:t.type,message:t.message,occurredAt:t.occurredAt,siteId:t.siteId,json:t.json});process.exitCode=i}),Q.command(`doctor`,`AI-assisted self-healing`).option(`--json`,``).action(async e=>{let{doctorCommand:t}=await import(`./doctor-D5HWdhgv.js`),n=await t({cliVersion:X,binaryPath:Z,json:e.json});process.exitCode=n}),Q.command(`stats`,`One-shot dashboard summary (scriptable by default)`).option(`--site-id <id>`,`Site ID`).option(`--range <range>`,`Time range: 24h | 7d | 30d`,{default:`24h`}).option(`--watch`,`Auto-refresh every 60s`).option(`--json`,``).option(`--csv`,``).option(`--tsv`,``).option(`--ndjson`,``).action(async e=>{let{statsCommand:t}=await import(`./stats-BzTqFpJL.js`),n=await t({cliVersion:X,siteId:e.siteId,range:e.range,watch:e.watch,json:e.json,csv:e.csv,tsv:e.tsv,ndjson:e.ndjson});process.exitCode=n}),Q.command(`sources`,`Traffic source breakdown with delta arrows`).option(`--site-id <id>`,`Site ID`).option(`--period <p>`,`Period: 7d | 30d | 90d | 1y`,{default:`30d`}).option(`--compare <c>`,`Comparison window (use "prev" for period-over-period)`).option(`--json`,``).option(`--csv`,``).option(`--tsv`,``).option(`--ndjson`,``).action(async e=>{let{sourcesCommand:t}=await import(`./sources-D-KqX0Sg.js`),n=await t({cliVersion:X,siteId:e.siteId,period:e.period,compare:e.compare,json:e.json,csv:e.csv,tsv:e.tsv,ndjson:e.ndjson});process.exitCode=n}),Q.command(`vitals`,`Core Web Vitals dashboard (LCP / INP / CLS / TTFB / FCP)`).option(`--site-id <id>`,`Site ID`).option(`--url <path>`,`Filter to a single URL`).option(`--device <type>`,`Filter by device: mobile | desktop`).option(`--window <w>`,`Time window: 24h | 7d | 30d | 90d`,{default:`7d`}).option(`--watch`,`Auto-refresh every 5 minutes`).option(`--json`,``).option(`--csv`,``).option(`--tsv`,``).option(`--ndjson`,``).action(async e=>{let{vitalsCommand:t}=await import(`./vitals-BgAwONID.js`),n=await t({cliVersion:X,siteId:e.siteId,url:e.url,device:e.device,window:e.window,watch:e.watch,json:e.json,csv:e.csv,tsv:e.tsv,ndjson:e.ndjson});process.exitCode=n}),Q.command(`revenue`,`MRR / ARR / transaction series`).option(`--site-id <id>`,`Site ID`).option(`--range <r>`,`Time range: 24h | 7d | 30d | 1y`,{default:`30d`}).option(`--watch`,`Auto-refresh every 5 minutes`).option(`--json`,``).option(`--csv`,``).option(`--tsv`,``).option(`--ndjson`,``).action(async e=>{let{revenueCommand:t}=await import(`./revenue-BiViSloE.js`),n=await t({cliVersion:X,siteId:e.siteId,range:e.range,watch:e.watch,json:e.json,csv:e.csv,tsv:e.tsv,ndjson:e.ndjson});process.exitCode=n}),Q.command(`funnels`,`List available funnels`).option(`--site-id <id>`,`Site ID`).option(`--json`,``).option(`--csv`,``).option(`--tsv`,``).option(`--ndjson`,``).action(async e=>{let{funnelCommand:t}=await import(`./funnel-C7DmrdUC.js`),n=await t({cliVersion:X,siteId:e.siteId,json:e.json,csv:e.csv,tsv:e.tsv,ndjson:e.ndjson});process.exitCode=n}),Q.command(`themes`,`List available themes with colored swatches`).option(`--validate <path>`,`Validate a custom theme JSON file`).option(`--json`,`Emit themes JSON to stdout`).action(async e=>{let{themesCommand:t}=await import(`./themes-gluHmb6E.js`),n=await t({cliVersion:X,validate:e.validate,json:e.json});process.exitCode=n}),Q.command(`completions [shell] [valuesType]`,`Emit shell-completion script (bash | zsh | fish | nushell | pwsh)`).option(`--list`,`List supported shells`).action(async(e,t,n)=>{let{completionsCommand:r}=await import(`./completions-Bem7-lcb.js`),i=await r({shell:e,valuesType:t,list:n.list});process.exitCode=i}),Q.command(`query [sql]`,`Restricted SQL REPL over the analytics schema (Scale+)`).option(`--site-id <id>`,`Site ID`).option(`--max-rows <n>`,`Cap result row count`,{default:1e3}).option(`--json`,``).option(`--csv`,``).option(`--tsv`,``).option(`--ndjson`,``).action(async(e,t)=>{let{queryCommand:n}=await import(`./query-BLHvu1IF.js`),r=await n({cliVersion:X,siteId:t.siteId,sql:e,maxRows:typeof t.maxRows==`string`?Number(t.maxRows):t.maxRows,json:t.json,csv:t.csv,tsv:t.tsv,ndjson:t.ndjson});process.exitCode=r}),Q.command(`companies`,`B2B identification feed (Scale+)`).option(`--site-id <id>`,`Site ID`).option(`--window <w>`,`Time window: 24h | 7d | 30d | 90d`,{default:`24h`}).option(`--limit <n>`,`Max rows`,{default:100}).option(`--new-only`,`Show only newly identified companies (first-seen in last hour)`).option(`--watch`,`Auto-refresh every 30s`).option(`--json`,``).option(`--csv`,``).option(`--tsv`,``).option(`--ndjson`,``).action(async e=>{let{companiesCommand:t}=await import(`./companies-CtZ2vGQs.js`),n=await t({cliVersion:X,siteId:e.siteId,window:e.window,limit:typeof e.limit==`string`?Number(e.limit):e.limit,newOnly:e.newOnly,watch:e.watch,json:e.json,csv:e.csv,tsv:e.tsv,ndjson:e.ndjson});process.exitCode=n}),Q.command(`projects`,`List your tracked projects`).option(`--site-id <id>`,`Site ID`).option(`--json`,``).option(`--csv`,``).option(`--tsv`,``).option(`--ndjson`,``).action(async e=>{let{projectsCommand:t}=await import(`./projects-hIQqeDYq.js`),n=await t({cliVersion:X,siteId:e.siteId,json:e.json,csv:e.csv,tsv:e.tsv,ndjson:e.ndjson});process.exitCode=n}),Q.command(`deploys`,`[OPERATIONS] Recent deploy markers overlaid on charts`).option(`--site-id <id>`,`Site ID`).option(`--branch <name>`,`Filter to a single branch (e.g. main, develop)`).option(`--since <iso>`,`Only commits after this ISO timestamp`).option(`--limit <n>`,`Max rows`,{default:50}).option(`--connect`,`Print the GitHub-integration deep link in app.zenovay.com`).option(`--json`,``).option(`--csv`,``).option(`--tsv`,``).option(`--ndjson`,``).action(async e=>{let{deploysCommand:t}=await import(`./deploys-CUbSa8S5.js`),n=await t({cliVersion:X,siteId:e.siteId,branch:e.branch,since:e.since,limit:typeof e.limit==`string`?Number(e.limit):e.limit,connect:e.connect,json:e.json,csv:e.csv,tsv:e.tsv,ndjson:e.ndjson});process.exitCode=n}),Q.command(`webhooks forward`,`[OPERATIONS] Forward webhook events to a local URL (stripe-listen-style tunnel)`).option(`--to <url>`,`Local URL to POST events to (e.g. localhost:3000/webhook)`).option(`--site-id <id>`,`Site ID`).option(`--sign`,`HMAC-sign each forwarded request with X-Zenovay-Signature`).option(`--secret <hex>`,`HMAC secret (auto-derived from tunnel key if --sign without --secret)`).option(`--max-retries <n>`,`Per-request retry count`,{default:3}).action(async e=>{let{webhooksForwardCommand:t}=await import(`./webhooks-forward-BDxi2TcF.js`),n=await t({cliVersion:X,to:e.to,siteId:e.siteId,sign:e.sign,secret:e.secret,maxRetries:typeof e.maxRetries==`string`?Number(e.maxRetries):e.maxRetries});process.exitCode=n}),Q.command(`errors`,`[BEHAVIOR] Recent JS / HTTP error snapshot`).option(`--site-id <id>`,`Site ID`).option(`--window <w>`,`Time window: 24h | 7d | 30d | 90d`,{default:`24h`}).option(`--limit <n>`,`Max errors to fetch`,{default:100}).option(`--search <q>`,`Filter by class or message substring`).option(`--watch`,`Auto-refresh every 30s`).option(`--json`,``).option(`--csv`,``).option(`--tsv`,``).option(`--ndjson`,``).action(async e=>{let{errorsCommand:t}=await import(`./errors-D3fS1kYu.js`),n=await t({cliVersion:X,siteId:e.siteId,window:e.window,limit:typeof e.limit==`string`?Number(e.limit):e.limit,search:e.search,watch:e.watch,json:e.json,csv:e.csv,tsv:e.tsv,ndjson:e.ndjson});process.exitCode=n}),Q.command(`funnel <id>`,`Funnel drop-off chart for a specific funnel`).option(`--site-id <id>`,`Site ID`).option(`--window <w>`,`Time window: 7d | 30d`,{default:`30d`}).option(`--json`,``).option(`--csv`,``).option(`--tsv`,``).option(`--ndjson`,``).action(async(e,t)=>{let{funnelCommand:n}=await import(`./funnel-C7DmrdUC.js`),r=await n({cliVersion:X,siteId:t.siteId,funnelId:e,window:t.window,json:t.json,csv:t.csv,tsv:t.tsv,ndjson:t.ndjson});process.exitCode=r}),Q.command(`api-keys [action] [id]`,`[MANAGE] api-keys list | create [--name --scope] | delete <id>`).option(`--name <n>`,`Human-readable label (create)`).option(`--scope <scope>`,`read | write (create)`,{default:`read`}).option(`--yes`,`Skip confirmation (delete)`).option(`--json`,``).action(async(e,t,n)=>{let{apiKeysCommand:r}=await import(`./api-keys-lfjOVUIe.js`),i=e??`list`;process.exitCode=await r({cliVersion:X,action:i,id:t,name:n.name,scope:n.scope===`write`?`write`:`read`,yes:n.yes,json:n.json})}),Q.command(`domains [action] [arg]`,`[MANAGE] domains list | add <url> [--name] | delete <id>`).option(`--name <n>`,`Friendly project name (add; default: domain itself)`).option(`--yes`,`Skip confirmation (delete)`).option(`--json`,``).action(async(e,t,n)=>{let{domainsCommand:r}=await import(`./domains-V4sD09io.js`),i=e??`list`;process.exitCode=await r({cliVersion:X,action:i,url:i===`add`?t:void 0,id:i===`delete`?t:void 0,name:n.name,yes:n.yes,json:n.json})}),Q.command(`team [action] [arg] [arg2]`,`[MANAGE] team list | invite <email> [--role] | remove <userId> | role <userId> <admin|member> (Pro+)`).option(`--role <role>`,`admin | member (invite)`,{default:`member`}).option(`--yes`,`Skip confirmation (remove)`).option(`--json`,``).action(async(e,t,n,r)=>{let{teamCommand:i}=await import(`./team-BRebgNMO.js`),a=e??`list`,o;a===`role`?o=n===`admin`?`admin`:`member`:a===`invite`&&(o=r.role===`admin`?`admin`:`member`),process.exitCode=await i({cliVersion:X,action:a,email:a===`invite`?t:void 0,userId:a===`remove`||a===`role`?t:void 0,role:o,yes:r.yes,json:r.json})}),Q.command(`profile [action]`,`[ACCOUNT] profile show | edit [--name --notifications]`).option(`--name <n>`,`New display name (edit)`).option(`--notifications <pref>`,`email | none (edit)`).option(`--json`,``).action(async(e,t)=>{let n=e??`show`,r=t.notifications===`email`||t.notifications===`none`?t.notifications:void 0,{profileCommand:i}=await import(`./profile-CD9QVZ2I.js`);process.exitCode=await i({cliVersion:X,action:n,name:t.name,notifications:r,json:t.json})}),Q.command(`plans [action] [arg] [arg2]`,`[ACCOUNT] plans info | upgrade <pro|scale> | cancel --yes | payment update`).option(`--yes`,"Required for `cancel`").option(`--json`,``).action(async(e,t,n,r)=>{let i=e??`info`,{plansCommand:a}=await import(`./plans-oxxMSOdB.js`),o;if(i===`upgrade`&&t){let e=String(t).toLowerCase();(e===`pro`||e===`scale`||e===`enterprise`)&&(o=e)}let s=i===`payment`&&t===`update`||i===`payment`?`update`:void 0;process.exitCode=await a({cliVersion:X,action:i,targetPlan:o,paymentAction:s,yes:r.yes,json:r.json})}),Q.command(`billing [action]`,"[ACCOUNT] Alias for `plans info`").option(`--json`,``).action(async(e,t)=>{let{plansCommand:n}=await import(`./plans-oxxMSOdB.js`);process.exitCode=await n({cliVersion:X,action:`info`,json:t.json})}),Q.command(`settings [action] [key] [value]`,`[ACCOUNT] settings get [key] | set <key> <value>`).option(`--json`,``).action(async(e,t,n,r)=>{let i=e??`get`,{settingsCommand:a}=await import(`./settings-CFFiWmGO.js`);process.exitCode=await a({cliVersion:X,action:i,key:t||void 0,value:n||void 0,json:r.json})}),Q.command(`integrations [action] [name]`,`[ACCOUNT] integrations list | connect <gsc|github> | disconnect <gsc|github>`).option(`--yes`,`Skip confirmation (disconnect)`).option(`--json`,``).action(async(e,t,n)=>{let r=e??`list`,i=t===`gsc`||t===`github`?t:void 0,{integrationsCommand:a}=await import(`./integrations-CWPrMJfZ.js`);process.exitCode=await a({cliVersion:X,action:r,name:i,yes:n.yes,json:n.json})}),Q.command(`goals [action] [id]`,`[ACCOUNT] goals list | create [--name --type --target] | delete <id> (Pro+)`).option(`--name <n>`,`Goal name (create)`).option(`--type <t>`,`pageview | event (create)`).option(`--target <v>`,`URL pattern or event name (create)`).option(`--yes`,`Skip confirmation (delete)`).option(`--json`,``).action(async(e,t,n)=>{let r=e??`list`,i=n.type===`event`||n.type===`pageview`?n.type:void 0,{goalsCommand:a}=await import(`./goals-CVRryIri.js`);process.exitCode=await a({cliVersion:X,action:r,id:t,name:n.name,type:i,target:n.target,yes:n.yes,json:n.json})}),Q.command(`notes [action] [id]`,`[ACCOUNT] notes list | create [--content --site-id] | delete <id>`).option(`--content <text>`,`Note body (create)`).option(`--site-id <id>`,`Attach to a specific site (create; defaults to current)`).option(`--yes`,`Skip confirmation (delete)`).option(`--json`,``).action(async(e,t,n)=>{let r=e??`list`,{notesCommand:i}=await import(`./notes-C8cba6QA.js`);process.exitCode=await i({cliVersion:X,action:r,id:t,content:n.content,siteId:n.siteId,yes:n.yes,json:n.json})}),Q.command(`agency [action] [arg] [arg2]`,`[ACCOUNT] agency clients list | clients add [--email --name] | clients remove <id> | report send <clientId> (Scale+)`).option(`--email <e>`,`Client contact email (clients add)`).option(`--name <n>`,`Client name (clients add)`).option(`--yes`,`Skip confirmation (clients remove)`).option(`--json`,``).action(async(e,t,n,r)=>{let{agencyCommand:i}=await import(`./agency-BxYMejNb.js`),a=`clients-list`,o;e===`clients`?t===`add`?a=`clients-add`:t===`remove`?(a=`clients-remove`,o=n):a=`clients-list`:e===`report`&&t===`send`&&(a=`report-send`,o=n),process.exitCode=await i({cliVersion:X,action:a,id:o,email:r.email,name:r.name,yes:r.yes,json:r.json})}),Q.command(`analytics live`,"[ANALYTICS] Alias for `live`").option(`--site-id <id>`,`Site ID`).option(`--json`,``).action(async e=>{let{liveCommand:t}=await import(`./live-CWSLgkC2.js`);process.exitCode=await t({cliVersion:X,siteId:e.siteId,json:e.json})}),Q.command(`analytics funnels`,"[ANALYTICS] Alias for `funnels`").option(`--site-id <id>`,`Site ID`).option(`--json`,``).action(async e=>{let{funnelCommand:t}=await import(`./funnel-C7DmrdUC.js`);process.exitCode=await t({cliVersion:X,siteId:e.siteId,json:e.json})}),Q.command(`analytics goals`,"[ANALYTICS] Alias for `goals list` (Pro+)").option(`--json`,``).action(async e=>{let{goalsCommand:t}=await import(`./goals-CVRryIri.js`);process.exitCode=await t({cliVersion:X,action:`list`,json:e.json})}),Q.command(`analytics insights`,"[ANALYTICS] Alias for `insights` (Pro+)").option(`--site-id <id>`,`Site ID`).option(`--json`,``).action(async e=>{let{insightsCommand:t}=await import(`./insights-J99-8Rcu.js`);process.exitCode=await t({cliVersion:X,siteId:e.siteId,json:e.json})}),Q.command(`analytics profiles`,"[ANALYTICS] User-profiles list (NOT `profile`, which is current-user)").option(`--site-id <id>`,`Site ID`).option(`--json`,``).action(async e=>{let{visitorsCommand:t}=await import(`./visitors-BtriXh9k.js`);process.exitCode=await t({cliVersion:X,siteId:e.siteId,json:e.json})}),Q.command(`analytics companies`,"[ANALYTICS] Alias for `companies` (Scale+)").option(`--site-id <id>`,`Site ID`).option(`--json`,``).action(async e=>{let{companiesCommand:t}=await import(`./companies-CtZ2vGQs.js`);process.exitCode=await t({cliVersion:X,siteId:e.siteId,json:e.json})}),Q.command(`use [site]`,`Set / show / reset your default site (used when --site-id is omitted)`).option(`--reset`,`Clear the stored default site`).option(`--show`,`Print the current default and exit`).option(`--json`,`Emit JSON`).action(async(e,t)=>{let{useCommand:n}=await import(`./use-baQFldqI.js`),r=await n({cliVersion:X,match:e,reset:t.reset,show:t.show,json:t.json});process.exitCode=r}),Q.command(`update`,`Manually install the latest version`).option(`--check`,`Don't install — just print latest`).option(`--force-downgrade`,`Allow downgrading to an older version`).option(`--no-update-check`,`Disable auto-update for this invocation`).option(`--json`,``).action(async e=>{let{updateCommand:t}=await import(`./update-BCgHj5O4.js`);await t({cliVersion:X,binaryPath:Z,check:e.check,forceDowngrade:e.forceDowngrade,json:e.json})}),Q.command(`usage`,`[ACCOUNT] Current month events vs plan limits + headcounts`).option(`--json`,``).action(async e=>{let{usageCommand:t}=await import(`./usage-BFCTeA8E.js`);process.exitCode=await t({cliVersion:X,json:e.json})}),Q.command(`globe`,`[ANALYTICS] Live 3D globe — visitors plotted on a Braille sphere`).option(`--site-id <id>`,`Site ID`).option(`--browser`,`Open the dashboard's Mapbox globe in your browser instead of the terminal version`).option(`--json`,``).action(async e=>{let{globeCommand:t}=await import(`./globe-PiPL3Bei.js`);process.exitCode=await t({cliVersion:X,siteId:e.siteId,browser:e.browser,json:e.json})}),Q.command(`webhooks [action] [arg]`,`[MANAGE] webhooks list | add <url> [--events] | delete <id> | rotate <id> | test <id>`).option(`--events <list>`,`Comma-separated event types (add)`).option(`--yes`,`Skip confirmation (delete/rotate)`).option(`--json`,``).action(async(e,t,n)=>{let r=e??`list`,{webhooksCommand:i}=await import(`./webhooks-PRWhMVgS.js`);process.exitCode=await i({cliVersion:X,action:r,url:r===`add`?t:void 0,id:r===`delete`||r===`rotate`||r===`test`?t:void 0,events:n.events,yes:n.yes,json:n.json})}),Q.command(`audit [action]`,`[ACCOUNT] audit list [--since --action --actor] | export [--format --out] (Pro+)`).option(`--since <window>`,`Window like 24h, 7d, 30d (list)`,{default:`7d`}).option(`--action <pattern>`,`Filter by action string (list)`).option(`--actor <userId>`,`Filter by actor user ID (list)`).option(`--format <fmt>`,`ndjson | csv | json (export)`,{default:`ndjson`}).option(`--out <file>`,`Output file path (export). Defaults to stdout`).option(`--json`,``).action(async(e,t)=>{let n=e??`list`,r=t.format??`ndjson`,{auditCommand:i}=await import(`./audit-DfKQdrug.js`);process.exitCode=await i({cliVersion:X,action:n,since:t.since,filterAction:t.action,actor:t.actor,format:r,out:t.out,json:t.json})}),Q.command(`share [action]`,`[ACCOUNT] share enable | disable | url — public dashboard share for the current site`).option(`--site-id <id>`,`Site ID (defaults to your current default site)`).option(`--yes`,`Skip confirmation (disable)`).option(`--json`,``).action(async(e,t)=>{let n=e??`url`,{shareCommand:r}=await import(`./share-Cx62AEP-.js`);process.exitCode=await r({cliVersion:X,action:n,siteId:t.siteId,yes:t.yes,json:t.json})}),Q.command(`commands`,`Emit the machine-readable command tree (for AI agents and shell-completion regen)`).option(`--json`,``).action(async e=>{let{commandsCommand:t}=await import(`./commands-NsH5CS-H.js`);process.exitCode=await t({cliVersion:X,json:e.json})}),Q.command(`teams [action] [arg]`,`[ACCOUNT] teams list | switch <name-or-id> | current | reset`).option(`--json`,``).action(async(e,t,n)=>{let r=e??`list`,{teamsCommand:i}=await import(`./teams-DhrkUkt8.js`);process.exitCode=await i({cliVersion:X,action:r,match:r===`switch`?t:void 0,json:n.json})}),Q.command(`self-test`,`[OPERATIONS] Functional smoke check across every read endpoint and your local install`).option(`--quick`,`Run a 10-test subset (faster)`).option(`--full`,`Include active security probes + CLI meta-tests (dev/CI use)`).option(`--verbose`,`Include full stdout/stderr per test`).option(`--parallel <n>`,`Concurrent subprocesses (default 4)`,{default:4}).option(`--json`,`Emit NDJSON one line per test result + final summary`).action(async e=>{let{selfTestCommand:t}=await import(`./self-test-CSST_zHj.js`);process.exitCode=await t({cliVersion:X,full:!!e.full,quick:!!e.quick,verbose:!!e.verbose,parallel:Number(e.parallel)||4,json:!!e.json})}),Q.command(`tour`,`[OPERATIONS] Interactive 8-step walkthrough of the CLI`).option(`--non-interactive`,`Print all 8 steps as a flat document (no prompts)`).action(async e=>{let{tourCommand:t}=await import(`./tour-STZ66G7K.js`);process.exitCode=await t({cliVersion:X,nonInteractive:!!(e[`non-interactive`]||e.nonInteractive)})}),Q.command(`alerts [action] [id]`,`[MANAGE] alerts list | create [--metric --op --threshold --window] | delete <id> (Pro+)`).option(`--site-id <id>`,`Site ID (defaults to your current default site)`).option(`--metric <name>`,`Metric to watch (visitors|pageviews|bounce_rate|errors|lcp|inp|cls|uptime|revenue)`).option(`--op <gt|lt>`,`Comparison operator`).option(`--threshold <num>`,`Numeric threshold`).option(`--window <e.g. 1h>`,`Rolling window (5m|1h|24h|7d)`).option(`--name <label>`,`Optional friendly label for the alert`).option(`--channel <chan>`,`Notification channel: in_app | email | webhook (default in_app)`).option(`--email <addr>`,`Notification email (required when --channel email)`).option(`--yes`,`Skip confirmation prompt on delete`).option(`--json`,``).action(async(e,t,n)=>{let r=e??`list`,i=new Set([`list`,`create`,`delete`]);i.has(r)||(process.stderr.write(n.json?JSON.stringify({type:`error`,code:`validation_failed`,message:`Unknown alerts action: ${e}`,hint:`Run zenovay alerts --help`})+`
|
|
9
9
|
`:`\n✗ Unknown action: '${e}'\n Valid: list | create | delete. Run \`zenovay alerts --help\`\n`),process.exit(2));let{alertsCommand:a}=await import(`./alerts-DKQa_K77.js`);process.exitCode=await a({cliVersion:X,action:r,siteId:n[`site-id`]??n.siteId,metric:n.metric,op:n.op,threshold:n.threshold,window:n.window,name:n.name,channel:n.channel,email:n.email,id:t,yes:!!n.yes,json:!!n.json})}),Q.command(`export <type>`,`[OPERATIONS] Export data: analytics | sessions (Pro+) | heatmaps (Pro+)`).option(`--site-id <id>`,`Site ID (defaults to your current default site)`).option(`--range <range>`,`Time range: 7d | 30d | 90d`,{default:`30d`}).option(`--format <fmt>`,`Output format: csv | json`,{default:`csv`}).option(`--out <file>`,`Write to file instead of stdout`).option(`--json`,``).action(async(e,t)=>{let n=new Set([`analytics`,`sessions`,`heatmaps`]);n.has(e)||(process.stderr.write(t.json?JSON.stringify({type:`error`,code:`validation_failed`,message:`Unknown export type: ${e}`,hint:`Valid: analytics | sessions | heatmaps`})+`
|
|
10
10
|
`:`\n✗ Unknown export type: '${e}'\n Valid: analytics | sessions | heatmaps. Run \`zenovay export --help\`\n`),process.exit(2));let r=t.format??`csv`,{exportCommand:i}=await import(`./export-FjEyji8r.js`);process.exitCode=await i({type:e,cliVersion:X,siteId:t[`site-id`]||t.siteId,range:t.range,format:r,out:t.out,json:!!t.json})}),Q.command(`visitors`,`[ANALYTICS] Live visitor table with sortable rows`).option(`--site-id <id>`,`Site ID`).option(`--min-duration <seconds>`,`Filter to sessions ≥ N seconds`,{default:0}).option(`--json`,``).option(`--csv`,`Emit visitors as CSV`).option(`--tsv`,`Emit visitors as TSV`).option(`--ndjson`,`Emit visitors as NDJSON (one row per line)`).action(async e=>{let{visitorsCommand:t}=await import(`./visitors-BtriXh9k.js`);process.exitCode=await t({cliVersion:X,siteId:e.siteId,minDuration:Number(e[`min-duration`]||e.minDuration)||0,json:!!e.json,csv:!!e.csv,tsv:!!e.tsv,ndjson:!!e.ndjson})}),Q.command(`status`,"[OPERATIONS] Alias of `health` — self-diagnostic checks").option(`--json`,``).action(async e=>{let{healthCommand:t}=await import(`./health-vxoiMF8G.js`);process.exitCode=await t({cliVersion:X,binaryPath:Z,json:!!e.json})}),Q.command(`telemetry [action]`,`[OPERATIONS] Manage anonymous CLI telemetry (on / off / status)`).option(`--json`,``).action(async(e,t)=>{let{telemetryCommand:n}=await import(`./telemetry-DC0Jw3JD.js`);process.exitCode=await n({cliVersion:X,action:e,json:!!t.json})}),Q.command(`status-agent`,`[OPERATIONS] Background daemon → tmux status bar + threshold alerts`).option(`--daemonize`,`Fork detached child + write PID file`).option(`--child`,`Internal flag — actual polling loop (used by --daemonize)`).option(`--stop`,`Stop the running daemon (read PID file → SIGTERM)`).option(`--install-tmux`,`Print the tmux conf snippet`).option(`--install-systemd`,`Print a systemd user-unit template`).option(`--install-launchd`,`Print a macOS launchd plist template`).option(`--notify-on <rules>`,`Comma-separated threshold rules, e.g. "error_rate>5,revenue_drop>20"`).option(`--site-id <id>`,`Site to poll (defaults to your saved default site)`).option(`--interval-ms <ms>`,`Poll interval ms (default 60000)`,{type:[Number]}).option(`--save-config`,`Persist --notify-on + --interval-ms to ~/.config/zenovay/config.json`).option(`--show-config`,`Print the persisted statusAgent config + exit`).action(async e=>{let{statusAgentCommand:t}=await import(`./status-agent-TIyEIKOj.js`);process.exitCode=await t({cliVersion:X,daemonize:!!e.daemonize,child:!!e.child,stop:!!e.stop,installTmux:!!e.installTmux,installSystemd:!!e.installSystemd,installLaunchd:!!e.installLaunchd,saveConfig:!!e.saveConfig,showConfig:!!e.showConfig,notifyOn:e.notifyOn,siteId:e.siteId,intervalMs:Array.isArray(e.intervalMs)?e.intervalMs[0]:e.intervalMs})}),Q.parse(process.argv,{run:!1}),(async()=>{await T({binaryPath:Z,currentVersion:X,argv:process.argv}),ce();let e,t;try{let t=process.env.HOME??process.env.USERPROFILE??``,n=d.join(t,`.config`,`zenovay`,`config.json`),r=p(n,`utf8`);e=JSON.parse(r).apiBase}catch{}try{let e=process.env.HOME??process.env.USERPROFILE??``,n=d.join(e,`.config`,`zenovay`,`auth.json`),r=p(n,`utf8`);t=JSON.parse(r).accessToken}catch{}await s({cliVersion:X,apiBase:e,token:t}).catch(()=>void 0);let n=Date.now(),r=(()=>{let e=Q.matchedCommand;return e?.name})();a(`cli.startup`,{command:r});try{let e=Q.matchedCommand,t=process.argv.slice(2).filter(e=>!e.startsWith(`-`)),i=process.argv.includes(`--help`)||process.argv.includes(`-h`);if(!e&&t.length>0&&!i&&(process.stderr.write(`Unknown command: \`${t.join(` `)}\`\nRun \`zenovay --help\` to see all commands.\n`),a(`cli.exit`,{command:r,exit_code:2,duration_ms:Date.now()-n,error_code:`unknown_command`}),await o().catch(()=>void 0),process.exit(2)),!e&&t.length===0&&!i){Q.outputHelp(),a(`cli.exit`,{command:`help`,exit_code:0,duration_ms:Date.now()-n}),await o().catch(()=>void 0);return}await Q.runMatchedCommand(),a(`cli.exit`,{command:r,exit_code:typeof process.exitCode==`number`?process.exitCode:0,duration_ms:Date.now()-n}),await o().catch(()=>void 0)}catch(e){let t=i(e),s=process.env.ZENOVAY_FORMAT===`json`||process.argv.includes(`--json`)||process.argv.includes(`-q`)||process.argv.includes(`--quiet`);s?process.stderr.write(JSON.stringify(t)+`
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import{readConfig as e,updateConfig as t}from"./config--JRu_jFq.js";import{ApiClient as n}from"./api-FCanTxsb.js";import{readToken as r}from"./token-store-BQuGH0z2.js";import{emit$1 as i,isHeadless as a}from"./emit-C9vAopcy.js";import{scrubSecrets as o}from"./secrets-DPf_7SkN.js";import{parseSseStream as s}from"./client-Dm8uGVCy.js";import{loginCommand as c,maybeInstallCompletions as l}from"./login-CaDs2Peq.js";import{Panel as u,useTheme as d}from"./panel-B-XxdP-N.js";import{Banner as f}from"./banner-DdgwXGzt.js";import{DiffView as p,ProgressRow as m}from"./progress-row-5ns2IsFh.js";import{Select as h}from"./prompt-B4s3o9G7.js";import{Keybar as g}from"./keybar-QgnmSion.js";import{spark as _}from"./sparkline-CpoBjjlc.js";import v from"node:path";import{promises as y}from"node:fs";import{z as b}from"zod";import x from"node:os";import S from"node:crypto";import{ulid as C}from"ulid";import{Box as w,Text as T,render as ee,useApp as te,useInput as E}from"ink";import D,{useEffect as O,useRef as k,useState as A}from"react";import{globby as j}from"globby";import M from"ink-spinner";import{applyPatch as N}from"diff";const P=[`@zenovay/tracker`,`@zenovay/next`,`@zenovay/nuxt`,`@zenovay/svelte`,`@zenovay/astro`,`@zenovay/remix`,`@zenovay/node`],F=[`zenovay.config.ts`,`zenovay.config.js`,`zenovay.config.mjs`,`zenovay.config.json`],ne=/<script[^>]*src\s*=\s*["']https:\/\/(?:api|cdn)\.zenovay\.com\/[^"']+["'][^>]*>\s*<\/script>/gi,re=/data-id\s*=\s*["'](ZV_[A-Za-z0-9]+)["']/i,I=/data-zenovay-site\s*=\s*["']([^"']+)["']/i,L=/\binit\s*\(\s*['"`](ZV_[A-Za-z0-9]+)['"`]/g,R=[{name:`PostHog`,patterns:[/posthog\.com\b/i,/posthog-js/,/window\.posthog/]},{name:`Plausible`,patterns:[/plausible\.io\/js/i,/window\.plausible/]},{name:`Google Analytics`,patterns:[/googletagmanager\.com\/gtag\/js/i,/www\.google-analytics\.com\/analytics\.js/i,/window\.gtag\b/]},{name:`Mixpanel`,patterns:[/cdn\.mxpnl\.com/i,/mixpanel-browser/]},{name:`Segment`,patterns:[/cdn\.segment\.com\/analytics\.js/i,/window\.analytics\b/]},{name:`Hotjar`,patterns:[/static\.hotjar\.com/i,/\(h,o,t,j,a,r\)/i]},{name:`Amplitude`,patterns:[/cdn\.amplitude\.com/i,/amplitude\.getInstance/]},{name:`Heap`,patterns:[/cdn\.heapanalytics\.com/i]},{name:`FullStory`,patterns:[/fullstory\.com\/s\/fs\.js/i,/window\.FS\.identify/]}];async function z(e,t){let n={zenovayInstalled:!1,correctTrackingCode:!1,foundTrackingCodes:[],zenovayFiles:[],competitors:[],summary:``,recommendation:`fresh-install`};try{let t=await y.readFile(v.join(e,`package.json`),`utf8`),r=JSON.parse(t),i={...r.dependencies,...r.devDependencies};for(let e of P)if(e in i){n.zenovayInstalled=!0,n.packageMarker=e;break}}catch{}for(let t of F)try{await y.access(v.join(e,t)),n.zenovayInstalled=!0,n.configFile=t;break}catch{}let r=await j([`**/*.{html,htm,tsx,jsx,vue,svelte,astro,ts,js,mjs}`,`!node_modules/**`,`!.git/**`,`!dist/**`,`!.next/**`,`!.nuxt/**`,`!build/**`,`!.output/**`],{cwd:e,gitignore:!0,deep:4,absolute:!0}).catch(()=>[]),i=new Map,a=new Set;for(let t of r.slice(0,300)){let r;try{r=await y.readFile(t,`utf8`)}catch{continue}let o=v.relative(e,t),s=r.match(ne);if(s&&s.length>0){n.zenovayInstalled=!0;let e=s[0].slice(0,200),t=e.match(re)??e.match(I),r=t?.[1]??null;r&&a.add(r),n.zenovayFiles.push({file:o,currentTrackingCode:r,scriptTagCount:s.length,matchSample:e})}if(!s&&/\.[jt]sx?$/.test(t)){L.lastIndex=0;let e;for(;(e=L.exec(r))!==null;){let t=e[1];a.add(t),n.zenovayInstalled=!0,n.zenovayFiles.push({file:o,currentTrackingCode:t,scriptTagCount:0,matchSample:e[0].slice(0,80)})}}for(let e of R)if(e.patterns.some(e=>e.test(r))){let t=i.get(e.name)??new Set;t.add(o),i.set(e.name,t)}}if(n.foundTrackingCodes=Array.from(a),n.competitors=Array.from(i.entries()).map(([e,t])=>({name:e,files:Array.from(t).slice(0,5)})),n.zenovayInstalled)if(n.foundTrackingCodes.length>0){let e=n.foundTrackingCodes.every(e=>e===t);n.correctTrackingCode=e,n.recommendation=e?`no-op`:`update-tracking-code`}else n.correctTrackingCode=!1,n.recommendation=`no-op`;else n.competitors.length>0?n.recommendation=`fresh-install-alongside-competitor`:n.recommendation=`fresh-install`;return n.summary=B(n,t),n}function B(e,t){let n=[];return e.zenovayInstalled?e.foundTrackingCodes.length===0?n.push(`Zenovay package or config detected but no <script> tag found.`):e.correctTrackingCode?n.push(`Zenovay tracker present in ${e.zenovayFiles.length} file(s) with correct ID ${t}.`):n.push(`Zenovay tracker present in ${e.zenovayFiles.length} file(s), but with mismatching ID(s): ${e.foundTrackingCodes.join(`, `)} (target: ${t}).`):n.push(`No Zenovay tracker found.`),e.competitors.length>0&&n.push(`Competitor tracker(s) present (DO NOT modify): ${e.competitors.map(e=>e.name).join(`, `)}.`),n.join(` `)}async function ie(e){let t=await z(e,``);return{installed:t.zenovayInstalled,reasons:t.summary?[t.summary]:[],packageMarker:t.packageMarker,configFile:t.configFile,scriptFile:t.zenovayFiles[0]?.file}}const ae=[/^package\.json$/,/^package-lock\.json$/,/^pnpm-lock\.yaml$/,/^yarn\.lock$/,/^bun\.lock$/,/^tsconfig.*\.json$/,/^(next|nuxt|vite|astro|remix|svelte|tailwind|postcss)\.config\.(ts|mts|cts|js|mjs|cjs)$/,/^app\/.*\.(tsx|ts|jsx|js|svelte|astro|vue)$/,/^pages\/.*\.(tsx|ts|jsx|js)$/,/^public\/index\.html$/,/^index\.html$/,/^src\/main\.(tsx|ts|jsx|js)$/,/^src\/App\.(tsx|ts|jsx|js|svelte|vue)$/,/^src\/routes\/\+layout\.(svelte|ts|js)$/,/^src\/routes\/root\..*$/,/^src\/layouts\/.*\.(astro|svelte|vue)$/,/^\.env\.example$/,/^README\.md$/,/^\.gitignore$/,/^nuxt\.config\.(ts|js|mjs)$/,/^plugins\/.*\.(ts|js)$/],oe=[/(^|\/)\.env($|\.(?!example$)[^/]+$)/,/(^|\/)\.git(\/|$)/,/(^|\/)node_modules\//,/(^|\/)\.ssh\//,/(^|\/)\.aws\//,/\.pem$/,/\.key$/,/(^|\/)id_rsa($|\.)/,/(^|\/)\.npmrc$/,/(^|\/)credentials(\.|$)/],V=256*1024,H=50,U=20,W=40;var G=class extends Error{constructor(e,t){super(t),this.code=e,this.name=`SandboxError`}};const K=/<script[^>]*src\s*=\s*["']https:\/\/(?:api|cdn)\.zenovay\.com\/[^"']+["'][^>]*>\s*<\/script>/i,se=[{name:`PostHog`,re:/posthog\.com\b|posthog-js|window\.posthog/i},{name:`Plausible`,re:/plausible\.io\/js|window\.plausible/i},{name:`Google Analytics`,re:/googletagmanager\.com\/gtag\/js|google-analytics\.com\/analytics\.js|window\.gtag\b/i},{name:`Mixpanel`,re:/cdn\.mxpnl\.com|mixpanel-browser/i},{name:`Segment`,re:/cdn\.segment\.com\/analytics\.js|window\.analytics\b/i},{name:`Hotjar`,re:/static\.hotjar\.com|\(h,o,t,j,a,r\)/i},{name:`Amplitude`,re:/cdn\.amplitude\.com|amplitude\.getInstance/i},{name:`Heap`,re:/cdn\.heapanalytics\.com/i},{name:`FullStory`,re:/fullstory\.com\/s\/fs\.js|window\.FS\.identify/i}];var q=class{sessionId;cwd;cwdReal;undoRoot;readCount=0;writeCount=0;roundCount=0;writtenPathsSet=new Set;constructor(e){this.sessionId=e.sessionId??C(),this.cwd=e.cwd,this.cwdReal=y.realpath(e.cwd),this.undoRoot=e.undoRoot??v.join(x.homedir(),`.zenovay`,`undo`)}incrementRound(){if(this.roundCount++,this.roundCount>W)throw new G(`round-cap`,`Tool-call recursion cap (${W}) exceeded.`)}stats(){return{sessionId:this.sessionId,reads:this.readCount,writes:this.writeCount,rounds:this.roundCount,writtenPaths:[...this.writtenPathsSet]}}async resolveForRead(e){return this.resolveSafe(e,`read`)}async resolveForWrite(e){return this.resolveSafe(e,`write`)}async resolveSafe(e,t){if(typeof e!=`string`||e.length===0)throw new G(`escape`,`Path must be a non-empty string.`);if(e.includes(`\0`))throw new G(`escape`,`Null byte in path.`);if(/^\\\\[?.]\\/.test(e)||/^\\\\[A-Za-z0-9]/.test(e))throw new G(`escape`,`Windows special path prefix: ${e}`);if(/%/.test(e))try{let t=decodeURIComponent(e);if(/%[0-9A-Fa-f]{2}/.test(t))throw new G(`escape`,`Double-encoded path: ${e}`);e=t}catch(t){throw t instanceof G?t:new G(`escape`,`Malformed percent-encoding: ${e}`)}if(e.includes(`\0`))throw new G(`escape`,`Null byte in decoded path.`);if(e.includes(`\\`))throw new G(`escape`,`Backslash in path (Windows-style separator not accepted): ${e}`);let n=await this.cwdReal,r=v.isAbsolute(e)?e:v.join(n,e),i=v.normalize(r),a=i;try{a=await y.realpath(i)}catch(e){if(e.code!==`ENOENT`)throw new G(`escape`,`realpath failed: ${e.message}`);let t=v.dirname(i);try{let e=await y.realpath(t);a=v.join(e,v.basename(i))}catch{}}if(!this.isWithin(a,n))throw new G(`escape`,`Path escapes cwd: ${e}`);let o=v.relative(n,a);for(let e of oe)if(e.test(o)){if(t===`read`&&/(^|\/)node_modules\//.test(o))continue;throw new G(`blocklisted`,`Path is on the hard blocklist: ${o}`)}if(t===`write`){let e=ae.some(e=>e.test(o));if(!e&&!/^\.env(\.local)?$/.test(o)&&!/^\.claude\//.test(o)&&!/^\.cursor\//.test(o))throw new G(`not-allowlisted`,`Write path not on allowlist: ${o}`)}return a}isWithin(e,t){let n=v.relative(t,e);return!n.startsWith(`..`)&&!v.isAbsolute(n)}async safeRead(e){if(this.readCount>=H)throw new G(`read-cap`,`Read cap (${H}) reached.`);let t=await this.resolveForRead(e),n=await y.stat(t);if(n.size>V)throw new G(`too-large`,`File exceeds ${V}B: ${e} (${n.size}B)`);let r=await y.readFile(t),i=r.subarray(0,Math.min(r.length,8192));if(i.includes(0))throw new G(`binary-rejected`,`Binary content in ${e}.`);return this.readCount++,r.toString(`utf8`)}async safeWrite(e,t){if(this.writeCount>=U)throw new G(`write-cap`,`Write cap (${U}) reached.`);if(t.length>V)throw new G(`too-large`,`Write exceeds ${V}B: ${e}`);let n=await this.resolveForWrite(e),r=await y.readFile(n,`utf8`).catch(e=>{if(e.code===`ENOENT`)return null;throw e});if(r!==null){if(K.test(r)&&!K.test(t))throw new G(`destructive-tracker-removal`,`Refusing to write ${e}: existing Zenovay tracker tag would be removed. If you want to change the data-id, use patch_file to swap the attribute value instead of overwriting the file.`);for(let{name:n,re:i}of se)if(i.test(r)&&!i.test(t))throw new G(`destructive-competitor-removal`,`Refusing to write ${e}: existing ${n} tracker would be removed. Zenovay installs alongside competitors — never replace them.`)}let i=await this.journalBackup(n);await y.mkdir(v.dirname(n),{recursive:!0}),await y.writeFile(n,t,`utf8`),this.writeCount++;let a=await this.cwdReal;return this.writtenPathsSet.add(v.relative(a,n)),{backupPath:i}}async journalBackup(e){let t=await y.readFile(e).catch(e=>{if(e.code===`ENOENT`)return null;throw e}),n=v.join(this.undoRoot,this.sessionId);await y.mkdir(n,{recursive:!0,mode:448});let r=S.createHash(`sha256`).update(e).digest(`hex`).slice(0,16),i=v.join(n,`${r}.bak`),a=v.join(n,`manifest.jsonl`);t&&await y.writeFile(i,t,{mode:384});let o=JSON.stringify({abs:e,backupPath:t?i:null,ts:Date.now()});return await y.appendFile(a,o+`
|
|
2
|
+
`,{mode:384}),t?i:null}static wrapForModel(e,t){let n=S.createHash(`sha256`).update(t).digest(`hex`).slice(0,16),r=t.replaceAll(`&`,`&`).replaceAll(`<`,`<`).replaceAll(`>`,`>`);return`<FILE path="${e}" sha256="${n}">\n${r}\n</FILE>`}static scrubSecrets(e){return o(e)}};const ce=[`astro.config.ts`,`astro.config.mjs`,`astro.config.js`];async function le(e){for(let t of ce)if(await e.exists(t))return{matched:!0,confidence:.95,reason:`Detected Astro via \`${t}\`.`};let t=await e.readFile(`package.json`);if(t)try{let e=JSON.parse(t);if(e.dependencies?.astro??e.devDependencies?.astro)return{matched:!0,confidence:.8,reason:"Detected Astro via `astro` dependency."}}catch{}return{matched:!1,confidence:0,reason:"No astro.config.* or `astro` dependency found."}}function ue(e){let t=e.trackingCode??e.siteId??``,n=`<script defer data-id="${t}" data-allow-localhost="true" src="https://api.zenovay.com/z.js" is:inline></script>`;return[{targetPath:`src/layouts/Layout.astro`,anchorKind:`head-tag`,code:n,description:`Inject the Zenovay tracker inside <head> of the primary layout.`},{targetPath:`src/layouts/BaseHead.astro`,anchorKind:`head-tag`,code:n,description:"Fallback target when the project uses a dedicated `BaseHead.astro`."}]}const de={id:`astro`,displayName:`Astro`,detect:le,files:[{path:`src/layouts/Layout.astro`,kind:`mutate`,required:!1},{path:`src/layouts/BaseHead.astro`,kind:`mutate`,required:!1},{path:`.env`,kind:`create`,required:!0}],envVarName:`PUBLIC_ZENOVAY_SITE`,buildSnippets:ue},fe=[`app/layout.tsx`,`app/layout.ts`,`app/layout.jsx`,`app/layout.js`];async function pe(e){for(let t of fe)if(await e.exists(t))return t;return null}async function me(e){let t=await e.readFile(`package.json`);if(!t)return!1;try{let e=JSON.parse(t);return!!(e.dependencies?.next??e.devDependencies?.next)}catch{return!1}}async function he(e){let t=await me(e),n=await e.exists(`app`),r=await pe(e);return t?n?r?{matched:!0,confidence:.95,reason:`Detected Next.js App Router via \`next\` dep + \`${r}\`.`}:{matched:!1,confidence:.4,reason:"Found `app/` but no root layout file (layout.tsx/ts/jsx/js)."}:{matched:!1,confidence:0,reason:"No `app/` directory found — not an App Router project."}:{matched:!1,confidence:0,reason:"package.json does not list `next` as a dependency."}}function ge(e,t=`script`){let n=`app/layout.tsx`,r=e.trackingCode??e.siteId??``;return t===`npm`?ve(n,r):_e(n,r)}function _e(e,t){return[{targetPath:e,anchorKind:`after-imports`,code:`import Script from 'next/script';`,description:`Import next/script to inject the Zenovay tracker.`},{targetPath:e,anchorKind:`wrap-children`,code:`<Script
|
|
3
|
+
defer
|
|
4
|
+
data-id="${t}"
|
|
5
|
+
data-allow-localhost="true"
|
|
6
|
+
src="https://api.zenovay.com/z.js"
|
|
7
|
+
strategy="afterInteractive"
|
|
8
|
+
/>
|
|
9
|
+
{children}`,description:`Add the Zenovay tracker <Script> next to children in the root layout.`}]}function ve(e,t){return[{targetPath:`components/zenovay-bootstrap.tsx`,anchorKind:`replace-file`,code:`'use client';
|
|
10
|
+
|
|
11
|
+
import { useEffect } from 'react';
|
|
12
|
+
import { init } from '@zenovay/tracker';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Mounts the Zenovay tracker exactly once on the client. Equivalent to the
|
|
16
|
+
* canonical <script src="https://api.zenovay.com/z.js"> tag — init() injects
|
|
17
|
+
* the same loader internally.
|
|
18
|
+
*/
|
|
19
|
+
export function ZenovayBootstrap() {
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
init('${t}', { allowLocalhost: true });
|
|
22
|
+
}, []);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
`,description:`Create a client-component bootstrap that calls init() from @zenovay/tracker.`},{targetPath:e,anchorKind:`after-imports`,code:`import { ZenovayBootstrap } from '@/components/zenovay-bootstrap';`,description:`Import the Zenovay tracker bootstrap into the root layout.`},{targetPath:e,anchorKind:`wrap-children`,code:`<ZenovayBootstrap />
|
|
26
|
+
{children}`,description:`Render the Zenovay tracker bootstrap alongside children in the root layout.`}]}const ye={id:`next-app-router`,displayName:`Next.js (App Router)`,detect:he,files:[{path:`app/layout.tsx`,kind:`mutate`,required:!0}],envVarName:`NEXT_PUBLIC_ZENOVAY_SITE`,packageDeps:[`@zenovay/tracker`],supportsNpmMode:!0,buildSnippets:ge},be=[`pages/_app.tsx`,`pages/_app.ts`,`pages/_app.jsx`,`pages/_app.js`,`src/pages/_app.tsx`,`src/pages/_app.ts`,`src/pages/_app.jsx`,`src/pages/_app.js`];async function xe(e){for(let t of be)if(await e.exists(t))return t;return null}async function Se(e){let t=await e.readFile(`package.json`);if(!t)return!1;try{let e=JSON.parse(t);return!!(e.dependencies?.next??e.devDependencies?.next)}catch{return!1}}async function Ce(e){let t=await Se(e);if(!t)return{matched:!1,confidence:0,reason:"package.json does not list `next` as a dependency."};let n=await xe(e);if(n)return{matched:!0,confidence:.9,reason:`Detected Next.js Pages Router via \`${n}\`.`};let r=await e.exists(`app`),i=await e.glob(`pages/**/*.{tsx,ts,jsx,js}`);return i.length>0&&!r?{matched:!0,confidence:.7,reason:"Found `pages/` directory with route files but no `_app` — will create one."}:{matched:!1,confidence:.2,reason:`Next.js detected, but no pages-router entry point found.`}}function we(e){let t=`pages/_app.tsx`,n=e.trackingCode??e.siteId??``,r=`import type { AppProps } from 'next/app';
|
|
27
|
+
import Script from 'next/script';
|
|
28
|
+
|
|
29
|
+
export default function App({ Component, pageProps }: AppProps) {
|
|
30
|
+
return (
|
|
31
|
+
<>
|
|
32
|
+
<Script
|
|
33
|
+
defer
|
|
34
|
+
data-id="${n}"
|
|
35
|
+
data-allow-localhost="true"
|
|
36
|
+
src="https://api.zenovay.com/z.js"
|
|
37
|
+
strategy="afterInteractive"
|
|
38
|
+
/>
|
|
39
|
+
<Component {...pageProps} />
|
|
40
|
+
</>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
`;return[{targetPath:t,anchorKind:`after-imports`,code:`import Script from 'next/script';`,description:"Import next/script in `_app`."},{targetPath:t,anchorKind:`wrap-children`,code:`<><Script defer data-id="${n}" data-allow-localhost="true" src="https://api.zenovay.com/z.js" strategy="afterInteractive" /><Component {...pageProps} /></>`,description:`Render the Zenovay tracker alongside the page component.`},{targetPath:t,anchorKind:`replace-file`,code:r,description:"Full replacement when `_app` does not exist yet."}]}const Te={id:`next-pages-router`,displayName:`Next.js (Pages Router)`,detect:Ce,files:[{path:`pages/_app.tsx`,kind:`mutate`,required:!0}],envVarName:`NEXT_PUBLIC_ZENOVAY_SITE`,buildSnippets:we},Ee=b.object({modules:b.array(b.string()).optional(),runtimeConfig:b.object({public:b.record(b.string(),b.unknown()).optional()}).optional()}).passthrough();async function De(e){let t=await e.readFile(`package.json`);if(!t)return{matched:!1,confidence:0,reason:`No package.json found.`};try{let e=JSON.parse(t),n=!!(e.dependencies?.nuxt??e.devDependencies?.nuxt??e.dependencies?.nuxt3??e.devDependencies?.nuxt3);if(n)return{matched:!0,confidence:.95,reason:"Detected Nuxt via `nuxt` / `nuxt3` dependency."}}catch{}let n=await e.exists(`nuxt.config.ts`)||await e.exists(`nuxt.config.js`)||await e.exists(`nuxt.config.mjs`);return n?{matched:!0,confidence:.6,reason:"Found `nuxt.config.*` but no explicit dependency listing."}:{matched:!1,confidence:0,reason:`No Nuxt dependency or config detected.`}}function Oe(e){let t=e.trackingCode??e.siteId??``,n=` app: {
|
|
44
|
+
head: {
|
|
45
|
+
script: [
|
|
46
|
+
{
|
|
47
|
+
defer: true,
|
|
48
|
+
src: 'https://api.zenovay.com/z.js',
|
|
49
|
+
'data-id': '${t}',
|
|
50
|
+
'data-allow-localhost': 'true',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
},`;return[{targetPath:`nuxt.config.ts`,anchorKind:`head-tag`,code:n,description:"Add the Zenovay tracker to Nuxt's `app.head.script` so every page loads it."}]}const ke={id:`nuxt`,displayName:`Nuxt`,detect:De,files:[{path:`nuxt.config.ts`,kind:`mutate`,required:!0}],envVarName:`NUXT_PUBLIC_ZENOVAY_SITE`,buildSnippets:Oe},Ae=[`index.html`,`public/index.html`,`dist/index.html`];async function je(e){for(let t of Ae)if(await e.exists(t))return{matched:!0,confidence:.5,reason:`Detected plain HTML project via \`${t}\`.`};let t=await e.glob(`**/*.html`);return t.length>0?{matched:!0,confidence:.3,reason:`Found ${t.length} HTML file(s); plain-HTML adapter is usable as fallback.`}:{matched:!1,confidence:0,reason:`No HTML files found in the project.`}}function Me(e){let t=e.siteId??``,n=`<script defer\n data-id="${t}"\n data-allow-localhost="true"\n src="https://api.zenovay.com/z.js">\n</script>`;return[{targetPath:`index.html`,anchorKind:`head-tag`,code:n,description:`Inject the Zenovay tracker inside <head>. Repeat for every HTML file that contains a <head>.`}]}const Ne={id:`plain-html`,displayName:`Plain HTML`,detect:je,files:[{path:`index.html`,kind:`mutate`,required:!0}],envVarName:`PUBLIC_ZENOVAY_SITE`,buildSnippets:Me};async function Pe(e){let t=await e.readFile(`package.json`);if(t)try{let e=JSON.parse(t),n={...e.dependencies??{},...e.devDependencies??{}},r=Object.keys(n).some(e=>e.startsWith(`@remix-run/`));if(r)return{matched:!0,confidence:.95,reason:"Detected Remix via `@remix-run/*` dependencies."}}catch{}return await e.exists(`remix.config.js`)||await e.exists(`remix.config.ts`)?{matched:!0,confidence:.8,reason:"Detected Remix via `remix.config.*`."}:{matched:!1,confidence:0,reason:`No Remix dependency or config detected.`}}function Fe(e){let t=e.trackingCode??e.siteId??``;return[{targetPath:`app/root.tsx`,anchorKind:`head-tag`,code:`<script defer data-id="${t}" data-allow-localhost="true" src="https://api.zenovay.com/z.js"></script>`,description:`Inline the Zenovay tracker <script> inside the document <head>.`}]}const Ie={id:`remix`,displayName:`Remix`,detect:Pe,files:[{path:`app/root.tsx`,kind:`mutate`,required:!0}],envVarName:`PUBLIC_ZENOVAY_SITE`,buildSnippets:Fe};async function Le(e){let t=await e.readFile(`package.json`);if(!t)return{matched:!1,confidence:0,reason:`No package.json found.`};try{let e=JSON.parse(t),n=!!(e.dependencies?.[`@sveltejs/kit`]??e.devDependencies?.[`@sveltejs/kit`]);return n?{matched:!0,confidence:.95,reason:"Detected SvelteKit via `@sveltejs/kit` dependency."}:{matched:!1,confidence:0,reason:"`@sveltejs/kit` not in dependencies."}}catch{return{matched:!1,confidence:0,reason:`package.json is not valid JSON.`}}}function Re(e){let t=e.trackingCode??e.siteId??``,n=`<svelte:head>
|
|
55
|
+
<script defer data-id="${t}" data-allow-localhost="true" src="https://api.zenovay.com/z.js"></script>
|
|
56
|
+
</svelte:head>
|
|
57
|
+
|
|
58
|
+
<slot />
|
|
59
|
+
`;return[{targetPath:`src/routes/+layout.svelte`,anchorKind:`replace-file`,code:n,description:`Root layout that loads the Zenovay tracker in every route via <svelte:head>.`}]}const ze={id:`sveltekit`,displayName:`SvelteKit`,detect:Le,files:[{path:`src/routes/+layout.svelte`,kind:`mutate`,required:!0}],envVarName:`PUBLIC_ZENOVAY_SITE`,buildSnippets:Re},Be=[ye,Te,ke,ze,de,Ie,Ne];async function Ve(e){let t=[];for(let n of Be){let r=await n.detect(e);t.push({adapter:n,result:r})}let n=[...t].sort((e,t)=>t.result.confidence-e.result.confidence),r=n[0],i=r&&r.result.matched?r.adapter:null;return{adapter:i,ranked:n}}function He(e){let t=t=>v.resolve(e,t);return{cwd:e,async readFile(e){try{return await y.readFile(t(e),`utf8`)}catch(e){let t=e.code;if(t===`ENOENT`||t===`EISDIR`)return null;throw e}},async exists(e){try{return await y.access(t(e)),!0}catch{return!1}},async glob(t){return j(t,{cwd:e,dot:!1,gitignore:!0,onlyFiles:!0,followSymbolicLinks:!1})}}}async function Ue(e,t){let n=He(e),{adapter:r,ranked:i}=await Ve(n),a=i[0];return r&&a&&a.result.confidence>=.7?r:t?t.pickInteractively(i):r}function We(e,t){let n=/[\s"'#=]/.test(t)||t===``,r=n?`"${t.replace(/\\/g,`\\\\`).replace(/"/g,`\\"`)}"`:t;return`${e}=${r}`}function Ge(e){let t=new Set;for(let n of e.split(/\r?\n/)){let e=n.trim();if(!e||e.startsWith(`#`))continue;let r=e.indexOf(`=`);if(r<=0)continue;let i=e.slice(0,r).trim();/^[A-Za-z_][A-Za-z0-9_]*$/.test(i)&&t.add(i)}return t}async function J(e,t,n={}){let r=n.filename??`.env.local`,i=v.resolve(e,r),a=``,o=!0;try{a=await y.readFile(i,`utf8`)}catch(e){if(e.code===`ENOENT`)o=!1;else throw e}let s=o?Ge(a):new Set,c=[],l=[],u=[];for(let[e,n]of Object.entries(t)){if(s.has(e)){l.push(e);continue}u.push(We(e,n)),c.push(e)}if(c.length===0)return{created:!1,existing:o,addedKeys:c,skippedKeys:l};let d=`# Added by Zenovay CLI`,f;if(o){let e=a.length>0&&!a.endsWith(`
|
|
60
|
+
`);f=a+(e?`
|
|
61
|
+
`:``)+`
|
|
62
|
+
`+d+`
|
|
63
|
+
`+u.join(`
|
|
64
|
+
`)+`
|
|
65
|
+
`}else f=d+`
|
|
66
|
+
`+u.join(`
|
|
67
|
+
`)+`
|
|
68
|
+
`;if(o)await y.writeFile(i,f,`utf8`);else{let e=await y.open(i,`w`,384);try{await e.writeFile(f,`utf8`)}finally{await e.close()}}return{created:!o,existing:o,addedKeys:c,skippedKeys:l}}const Ke=`https://api.zenovay.com/mcp`,qe=b.object({url:b.string().url(),headers:b.record(b.string(),b.string()).optional()}),Je=b.object({mcpServers:b.record(b.string(),qe).optional()}).passthrough();function Ye(e){return e.HOME??e.USERPROFILE??x.homedir()}function Xe(e){return e.APPDATA??null}function Ze(e,t){let n=Ye(t),r=Xe(t),i=[];return i.push({id:`cursor`,displayName:`Cursor`,configPath:v.join(n,`.cursor`,`mcp.json`)}),e===`win32`&&r&&i.push({id:`cursor`,displayName:`Cursor (AppData)`,configPath:v.join(r,`Cursor`,`User`,`globalStorage`,`cursor.mcp`,`mcp.json`)}),e===`darwin`?i.push({id:`claude-desktop`,displayName:`Claude Desktop`,configPath:v.join(n,`Library`,`Application Support`,`Claude`,`claude_desktop_config.json`)}):e===`win32`&&r?i.push({id:`claude-desktop`,displayName:`Claude Desktop`,configPath:v.join(r,`Claude`,`claude_desktop_config.json`)}):i.push({id:`claude-desktop`,displayName:`Claude Desktop`,configPath:v.join(n,`.config`,`Claude`,`claude_desktop_config.json`)}),i.push({id:`vscode`,displayName:`VS Code`,configPath:v.join(n,`.vscode`,`mcp.json`)}),i}async function Qe(e){try{return await y.access(e),!0}catch{return!1}}async function $e(e){try{let t=await y.readFile(e,`utf8`);if(t.trim().length===0)return{};let n=JSON.parse(t),r=Je.safeParse(n);return r.success?r.data:n&&typeof n==`object`?n:{}}catch{return{}}}function et(e){return{url:Ke,headers:{Authorization:`Bearer ${e}`}}}async function Y(e){let t=e.env??process.env,n=e.platform??process.platform,r=new Set(e.skip??[]),i=[],a=[],o=[],s=new Set,c=et(e.accessToken);for(let e of Ze(n,t)){if(r.has(e.id)){a.push(e.displayName);continue}if(s.has(e.configPath))continue;s.add(e.configPath);try{let t=await Qe(e.configPath);if(!t){a.push(e.displayName);continue}let n=await $e(e.configPath),r=`${e.configPath}.bak`;await y.copyFile(e.configPath,r);let o={...n,mcpServers:{...n.mcpServers??{},zenovay:c}};await y.mkdir(v.dirname(e.configPath),{recursive:!0}),await y.writeFile(e.configPath,JSON.stringify(o,null,2)+`
|
|
69
|
+
`,`utf8`),i.push(e.displayName)}catch(t){o.push({client:e.displayName,message:t instanceof Error?t.message:String(t)})}}return{installed:i,skipped:a,errors:o}}async function tt(e,t,n){let r=n.timeoutMs??18e4,i=n.pollIntervalMs??2e3,a=n.now??(()=>Date.now()),o=n.sleep??(e=>new Promise(t=>setTimeout(t,e))),s=a(),c=s+r;for(n.onTick?.(0);;){let l=await n.api.getFirstEvent({siteId:e,since:t});if(l.received)return{received:!0,event:l.event};let u=a()-s;if(u>=r)return{received:!1};let d=c-a(),f=Math.min(i,Math.max(0,d));if(f<=0)return{received:!1};await o(f),n.onTick?.(a()-s)}}const nt=b.object({path:b.string().min(1)}),rt=b.object({path:b.string().min(1),content:b.string(),reason:b.string().min(1)}),it=b.object({path:b.string().min(1),patch:b.string().min(1),reason:b.string().min(1)}),at=b.object({}),ot=b.object({question:b.string().min(1),choices:b.array(b.string()).optional()});async function st(e,t){let n=nt.safeParse(t);if(!n.success)return{ok:!1,content:null,error:`invalid_args`};try{let t=await e.safeRead(n.data.path),r=q.scrubSecrets(t);return{ok:!0,content:q.wrapForModel(n.data.path,r)}}catch(e){return{ok:!1,content:null,error:e.message}}}async function ct(e,t,n){let r=rt.safeParse(t);if(!r.success)return{ok:!1,content:null,error:`invalid_args`};let{path:i,content:a,reason:o}=r.data,s=``;try{s=await e.safeRead(i)}catch{s=``}let c=await n.approve({path:i,oldContent:s,newContent:a,reason:o});if(c===`n`)return{ok:!1,content:null,error:`skipped_by_user`};if(c===`e`)return{ok:!1,content:null,error:`deferred_to_user_edit`};try{let{backupPath:t}=await e.safeWrite(i,a);return{ok:!0,content:{path:i,backupPath:t}}}catch(e){return{ok:!1,content:null,error:e.message}}}async function lt(e,t,n){let r=it.safeParse(t);if(!r.success)return{ok:!1,content:null,error:`invalid_args`};let{path:i,patch:a,reason:o}=r.data;if(/^\*\*\*\s+Begin Patch/m.test(a)||/^\*\*\*\s+Update File:/m.test(a))return{ok:!1,content:null,error:'unsupported_patch_format: This tool only accepts STANDARD unified diff with `--- a/...`, `+++ b/...`, and `@@ -N,M +N,M @@` headers. The "*** Begin Patch / *** Update File:" V4A format is NOT supported. Please call write_file with the full new file content instead.'};let s;try{s=await e.safeRead(i)}catch(e){return{ok:!1,content:null,error:e.message}}let c;try{c=N(s,a)}catch(e){return{ok:!1,content:null,error:`patch_failed: ${e.message}. Please call write_file with the full new file content instead — write_file is more reliable than patch_file for this use case.`}}return c===!1||typeof c!=`string`?{ok:!1,content:null,error:`patch_did_not_apply_cleanly. The patch context may not match the file. Please call write_file with the full new file content instead.`}:ct(e,{path:i,content:c,reason:o},n)}async function ut(e){try{let t=He(e),{adapter:n,ranked:r}=await Ve(t);return{ok:!0,content:{matched:n!==null,adapter:n?.id,displayName:n?.displayName,ranked:r.map(e=>({id:e.adapter.id,confidence:e.result.confidence,reason:e.result.reason}))}}}catch(e){return{ok:!1,content:null,error:e.message}}}async function dt(e,t){let n=ot.safeParse(t);if(!n.success)return{ok:!1,content:null,error:`invalid_args`};try{let t=await e.ask(n.data);return{ok:!0,content:{answer:t}}}catch(e){return{ok:!1,content:null,error:e.message}}}async function ft(e){let{api:t,sandbox:n,approval:r,prompter:i,handlers:a}=e,o=[...e.initialMessages],c=``,l=0;for(;l<W;){n.incrementRound(),l++;let u=await t.openAiStream(`install`,{messages:o,cwd_fingerprint:n.sessionId,diagnosis:e.diagnosis,targetTrackingCode:e.targetTrackingCode}),d=[],f=``;for await(let e of s(u))switch(e.type){case`delta`:f+=e.content,a?.onDelta?.(e.content);break;case`tool_start`:a?.onToolStart?.({id:e.id,name:e.name});break;case`tool_call`:{let t=await pt(e.name,e.arguments,{sandbox:n,cwd:n.cwd,approval:r,prompter:i});d.push({id:e.id,name:e.name,arguments:e.arguments,result:t}),a?.onToolResult?.({id:e.id,name:e.name,result:t});break}case`usage`:a?.onUsage?.(e);break;case`error`:throw a?.onError?.(e),Error(`[${e.code}] ${e.message}`);case`done`:break}if(f&&(c+=f),d.length===0){f&&o.push({role:`assistant`,content:f});break}o.push({role:`assistant`,content:f,toolCalls:d.map(e=>({id:e.id,type:`function`,function:{name:e.name,arguments:JSON.stringify(e.arguments)}}))});for(let e of d)o.push({role:`tool`,toolCallId:e.id,toolName:e.name,content:JSON.stringify(e.result)})}return{finalText:c,rounds:l}}async function pt(e,t,n){try{switch(e){case`read_file`:return await st(n.sandbox,t);case`write_file`:return await ct(n.sandbox,t,n.approval);case`patch_file`:return await lt(n.sandbox,t,n.approval);case`detect_framework`:return await ut(n.cwd);case`ask_user`:return await dt(n.prompter,t);default:return{ok:!1,content:null,error:`unknown_tool:${e}`}}}catch(e){return{ok:!1,content:null,error:`tool_threw: ${e.message}`}}}const mt=e=>`<script defer\n data-id="${e}"\n data-allow-localhost="true"\n src="https://api.zenovay.com/z.js">\n</script>`;async function ht(e){let t={installed:[],skipped:[],errors:[]},n=await j([`**/*.html`,`!node_modules/**`,`!.git/**`,`!dist/**`,`!.next/**`,`!.nuxt/**`,`!build/**`,`!.output/**`],{cwd:e.cwd,gitignore:!0,absolute:!1});if(n.length===0)return t;let r=mt(e.trackingCode),i=RegExp(`data-id\\s*=\\s*["']${gt(e.trackingCode)}["']`);for(let a of n){let n=v.join(e.cwd,a),o;try{o=await y.readFile(n,`utf8`)}catch(e){t.errors.push({file:a,error:e.message});continue}if(K.test(o)&&i.test(o)){t.skipped.push({file:a,reason:`already installed with correct tracking code`});continue}let s;if(K.test(o))s=o.replace(/(<script[^>]*src\s*=\s*["']https:\/\/(?:api|cdn)\.zenovay\.com\/[^"']+["'][^>]*>)/i,t=>t.replace(/data-id\s*=\s*["']ZV_\w+["']/i,`data-id="${e.trackingCode}"`)),s===o&&(s=o.replace(/<script(\s)/i,`<script$1data-id="${e.trackingCode}"$1`));else if(e.updateOnly){t.skipped.push({file:a,reason:`updateOnly=true and no Zenovay tag present`});continue}else if(/<\/head>/i.test(o)){if(s=o.replace(/(\n[\t ]*)?<\/head>/i,(e,t)=>{let n=t??`
|
|
70
|
+
`,i=r.replace(/\n/g,`${n}`);return`${n}${i}${n}</head>`}),s===o){t.skipped.push({file:a,reason:`</head> regex did not match unexpectedly`});continue}}else{t.skipped.push({file:a,reason:`no <head>...</head> in file (fragment or non-page)`});continue}let c=null;for(let{name:e,re:t}of se)if(t.test(o)&&!t.test(s)){c=e;break}if(c){t.errors.push({file:a,error:`would remove ${c} tracker — refusing to write`});continue}try{await y.writeFile(n,s,`utf8`),t.installed.push(a)}catch(e){t.errors.push({file:a,error:e.message})}}return t}function gt(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function _t(e){return e.yes||!process.stdout.isTTY?`auto`:`interactive`}function vt(e){let t=null,n=null,r=!1,i=!1,a=0,o=new Set,s=()=>{for(let e of o)e(t)};return{async approve(o){if(i)return`n`;if(e.mode===`auto`||r)return`y`;let c=String(++a);return new Promise(e=>{t={id:c,...o},n=t=>{t===`a`&&(r=!0),e(t)},s()})},subscribe(e){return o.add(e),()=>{o.delete(e)}},current(){return t},resolve(e,r){if(!t||t.id!==e||!n)return;let i=n;t=null,n=null,s(),i(r)},abort(){if(i=!0,n){let e=n;t=null,n=null,s(),e(`n`)}}}}function yt(){let e=[12,15,18,22,28,35,41,50,58,64,72,78],t=_(e,24);return[` ↑`,` ${t}`,` └────────────────────────`,` May Jun Jul Aug Sep`]}function bt(){return[` Landing page ████████████████████ 100%`,` ↓ -28%`,` Pricing ██████████████░░░░░░ 72%`,` ↓ -50%`,` Checkout ███████░░░░░░░░░░░░░ 36%`,` ↓ -64%`,` Purchased ███░░░░░░░░░░░░░░░░░ 13%`]}function xt(){return[` ▇▇▇▇▇▇▇▇▇▎ 89 Sarah K. /pricing 🇺🇸`,` ▇▇▇▇▇▇░░░░ 61 anon /docs 🇩🇪`,` ▇▇▇░░░░░░░ 34 anon /changelog 🇯🇵`]}function St(){return[` LCP ● 2.1s good ████████████░░░░░░`,` INP ● 89ms good ██████████████████`,` CLS ⚠ 0.14 needs ████████████░░░░░░`,` TTFB ● 380ms good ███████████████░░░`]}const X=[{title:`Welcome`,body:[`The wizard is an agent — it handles the entire`,`Zenovay setup process on your behalf.`,``,`Reading your project, deciding where the tracker`,`belongs, and writing the snippet for you.`]},{title:`Events`,body:[`Events are the foundation of Zenovay analytics.`,``,`Every pageview, click, and custom action is an`,`event. Group them into Goals to track conversions.`]},{title:`Trends`,body:[`Use trends to measure growth.`,``,`See how visitors, signups, and revenue have`,`changed over the last week, month, or quarter.`],visual:yt},{title:`Funnels`,body:[`Funnels reveal where users drop off.`,``,`Define the steps you care about — landing,`,`pricing, checkout, purchase — and Zenovay shows`,`the per-step conversion rate.`],visual:bt},{title:`Live visitors`,body:[`Watch every visitor in real time, ranked by an`,`AI score that highlights high-intent traffic.`,``,"Use `zenovay visitors --live` after install."],visual:xt},{title:`Core Web Vitals`,body:[`LCP, INP, CLS — Google's page-experience metrics,`,`collected automatically once the tracker is in.`,``,"Use `zenovay vitals` to see them per page + device."],visual:St},{title:`MCP integration`,body:[`Your CLI session can install Zenovay's MCP server`,`into Cursor / Claude Desktop / VS Code.`,``,`Then ask your AI: "what were my top pages today?"`,`and it queries Zenovay directly.`]},{title:`AI insights`,body:[`Zenovay watches your data and surfaces anomalies`,`automatically — traffic spikes, error bursts,`,`revenue drops — without you needing to ask.`,``,"Run `zenovay ai` after install to chat with them."]},{title:`Privacy-first`,body:[`No cookies required. No third-party trackers.`,`Cookieless mode is one toggle in your dashboard.`,``,`GDPR, CCPA, and PECR-compliant out of the box.`]}],Ct=8e3,wt=({width:e,height:t})=>{let{theme:n}=d(),[r,i]=A(0);O(()=>{let e=setInterval(()=>{i(e=>(e+1)%X.length)},Ct);return()=>clearInterval(e)},[]);let a=X[r],o=a.visual?a.visual():[],s=X.map((e,t)=>t===r?`●`:`·`).join(` `);return D.createElement(w,{flexDirection:`column`,width:e,height:t,paddingX:2,paddingY:1},D.createElement(w,{marginBottom:1},D.createElement(T,{color:n.accent,bold:!0},`Learn`)),D.createElement(w,{marginBottom:1},D.createElement(T,{bold:!0,color:n.fg},a.title)),a.body.map((e,t)=>D.createElement(T,{key:t,color:n.muted},e||` `)),o.length>0&&D.createElement(w,{flexDirection:`column`,marginTop:1},o.map((e,t)=>D.createElement(T,{key:t,color:n.accent},e))),D.createElement(w,{flexGrow:1}),D.createElement(w,{marginTop:1},D.createElement(T,{color:n.dim,dimColor:!0},s,` · `,r+1,` / `,X.length)))},Tt={pending:`·`,active:`⠋`,done:`✓`,error:`✗`};function Et(e,t){switch(e){case`pending`:return t.dim;case`active`:return t.accent;case`done`:return t.success;case`error`:return t.error}}const Dt=({tasks:e,width:t,height:n,liveLine:r})=>{let{theme:i}=d(),a=e.filter(e=>e.status===`done`).length;return D.createElement(w,{flexDirection:`column`,width:t,height:n,paddingX:2,paddingY:1},D.createElement(w,{marginBottom:1},D.createElement(T,{color:i.accent,bold:!0},`Tasks`)),e.map(e=>D.createElement(w,{key:e.id,flexDirection:`column`},D.createElement(w,null,e.status===`active`?D.createElement(T,{color:i.accent},D.createElement(M,{type:`dots`})):D.createElement(T,{color:Et(e.status,i)},Tt[e.status]),D.createElement(T,null,` `),D.createElement(T,{color:e.status===`pending`?i.muted:i.fg},e.label)),e.detail&&D.createElement(w,{paddingLeft:2},D.createElement(T,{color:i.dim,dimColor:!0},e.detail)))),D.createElement(w,{flexGrow:1}),r&&D.createElement(w,{marginTop:1},D.createElement(T,{color:i.accent},`◆ `),D.createElement(T,{color:i.fg},r)),D.createElement(w,{marginTop:1},D.createElement(T,{color:i.dim,dimColor:!0},a,` / `,e.length)))};async function Ot(e){if(a(e))return kt(e);let t=`quit`,n=null,r=await new Promise(r=>{let{unmount:i,waitUntilExit:a}=ee(D.createElement(At,{opts:e,onExit:(e,a,o)=>{t=a??`quit`,n=o??null,i(),r(e)}}));a().catch(()=>r(1))});if(r===0&&await l().catch(()=>void 0),r!==0||t===`quit`||!n)return r;let i=n.id;if(t===`watch`){let{watchCommand:t}=await import(`./watch-Cv4FtwSJ.js`);return t({cliVersion:e.cliVersion,siteId:i})}if(t===`events`){let{eventsTailCommand:t}=await import(`./events-tail-DGRZewwc.js`);return t({cliVersion:e.cliVersion,siteId:i})}return 0}async function kt(t){let a=await import(`./telemetry-C1045t6Q.js`).catch(()=>null);a?.emit(`install.start`,{properties:{mode:`headless`}});try{let o=await e(),s=await Ue(t.cwd);if(!s)return i({type:`error`,code:`no_framework`,message:`Could not detect framework (headless mode has no picker).`}),a?.emit(`install.error`,{error_code:`no_framework`,properties:{mode:`headless`}}),2;i({type:`framework.detected`,framework:s.id,confidence:1}),a?.emit(`install.framework_detected`,{properties:{framework:s.id}});let c=await r({strict:!1});if(!c&&!process.env.ZENOVAY_API_TOKEN)return i({type:`error`,code:`not_logged_in`,message:"Run `zenovay login` first (or set ZENOVAY_API_TOKEN)."}),2;let l=new n({config:o,cliVersion:t.cliVersion,token:c}),[u,d]=await Promise.all([l.getSites(),z(t.cwd,``).catch(()=>null)]),f;if(t.siteId){if(f=u.find(e=>e.id===t.siteId),!f)return i({type:`error`,code:`site_not_found`,message:`Site ${t.siteId} not found.`}),2}else if(d?.foundTrackingCodes.length){for(let e of d.foundTrackingCodes){let t=u.find(t=>t.trackingCode===e);if(t){f=t;break}}f||=u[0]}else f=u[0];if(!f)return i({type:`error`,code:`no_site`,message:`No sites exist; create one at app.zenovay.com or re-run interactively.`}),2;let p=f.trackingCode??f.id;if(d&&(d.recommendation===`no-op`||d.foundTrackingCodes.includes(p)))return i({type:`info`,message:`Zenovay already installed correctly for ${f.name} (${p})`}),i({type:`done`,command:`init`,exitCode:0}),0;i({type:`info`,message:`Using site ${f.id} (${f.name}) trackingCode=${f.trackingCode??f.id}`});let m=await J(t.cwd,{[s.envVarName]:f.trackingCode??f.id});return i({type:`info`,message:`env vars added: ${m.addedKeys.join(`,`)}`}),i({type:`done`,command:`init`,exitCode:0}),a?.emit(`install.success`,{properties:{framework:s.id,mode:`headless`,env_keys_added:m.addedKeys.length}}),0}catch(e){return i({type:`error`,code:`init_failed`,message:e.message}),a?.emit(`install.error`,{error_code:`init_failed`,properties:{mode:`headless`,error:(e.message??`unknown`).slice(0,128)}}),1}}const At=({opts:t,onExit:i})=>{let{exit:a}=te(),{theme:o}=d(),[s,l]=A(`intro`),[p,m]=A(null),[h,_]=A([]),[v,y]=A(null),[b,x]=A(null),[S,C]=A(null),[ee,E]=A([]),[j,N]=A(void 0),[P,F]=A(null),[ne,re]=A(0),[I,L]=A(null),R=k(null),B=k(!1);return O(()=>(process.stdout.isTTY&&process.stdout.write(`\x1B[?1049h\x1B[H`),()=>{R.current?.abort(),process.stdout.isTTY&&process.stdout.write(`\x1B[?1049l`)}),[]),O(()=>{B.current||(B.current=!0,(async()=>{try{l(`detecting`);let i=await Ue(t.cwd);if(!i){C(`Could not detect your framework. Supported: Next.js, Nuxt, SvelteKit, Astro, Remix, plain HTML.`),l(`error`);return}m(i);let a=await r({strict:!1});!process.env.ZENOVAY_API_TOKEN&&!a&&(l(`login`),await c({cliVersion:t.cliVersion,offerCompletions:!1})),l(`loading-sites`);let o=await e(),s=await r({strict:!1}),u=new n({config:o,cliVersion:t.cliVersion,token:s}),[d,f]=await Promise.all([u.getSites(),z(t.cwd,``).catch(()=>null)]),p=await Gt(t.cwd);if(_(d),y(p),d.length===0){l(`create-site`);return}if(f?.foundTrackingCodes.length)for(let e of f.foundTrackingCodes){let t=d.find(t=>t.trackingCode===e);if(t){x(t),l(`already-installed`);return}}if(t.siteId){let e=d.find(e=>e.id===t.siteId);if(!e){C(`Site ${t.siteId} not found in your account.`),l(`error`);return}x(e),Z(e,i,t,{setPhase:l,setErrorMsg:C,setTasks:E,setLiveLine:N,setSandboxStats:F,setPendingApproval:L,registerBridge:e=>{R.current=e}});return}let h=Kt(d,p);if(t.yes&&h){x(h),Z(h,i,t,{setPhase:l,setErrorMsg:C,setTasks:E,setLiveLine:N,setSandboxStats:F,setPendingApproval:L,registerBridge:e=>{R.current=e}});return}l(`site-picker`)}catch(e){C(e.message),l(`error`)}})())},[]),O(()=>{s===`error`&&(R.current?.abort(),setTimeout(()=>{a(),i(1)},8e3))},[s]),D.createElement(w,{flexDirection:`column`},D.createElement(f,{version:t.cliVersion,subtitle:`AI install wizard`}),s===`intro`&&D.createElement(jt,null),s===`detecting`&&D.createElement(u,{title:`Scanning`,state:`busy`},D.createElement(w,null,D.createElement(T,{color:o.accent},D.createElement(M,{type:`dots`})),D.createElement(T,null,` Detecting framework…`))),s===`login`&&D.createElement(u,{title:`Authenticating`,state:`busy`},D.createElement(T,null,`Opening browser for login… (paste ZENOVAY_API_TOKEN env var to skip)`)),s===`loading-sites`&&D.createElement(u,{title:`Loading your sites`,state:`busy`},D.createElement(w,null,D.createElement(T,{color:o.accent},D.createElement(M,{type:`dots`})),D.createElement(T,null,` Fetching your Zenovay sites…`))),s===`already-installed`&&b&&D.createElement(Nt,{site:b,onContinue:()=>{l(`site-picker`)},onExit:()=>{a(),i(0,`quit`,b)}}),s===`site-picker`&&p&&D.createElement(Pt,{sites:h,preselectHint:v,onPick:e=>{if(e===`create-new`){l(`create-site`);return}x(e),Z(e,p,t,{setPhase:l,setErrorMsg:C,setTasks:E,setLiveLine:N,setSandboxStats:F,setPendingApproval:L,registerBridge:e=>{R.current=e}})}}),s===`create-site`&&D.createElement(u,{title:`Create a new site`,state:`warn`},D.createElement(T,{color:o.warn},"No site picked (or none exist yet). Visit https://app.zenovay.com/websites to create one, then re-run `zenovay init`."),D.createElement(T,{color:o.muted},`(Inline site creation from the CLI is scheduled for V1.1 — it requires a plan-limit check.)`)),s===`agent`&&p&&b&&D.createElement(Mt,{adapter:p,site:b,tasks:ee,liveLine:j,pendingApproval:I,onApprove:e=>{I&&R.current?.resolve(I.id,e)}}),s===`install-complete`&&p&&b&&D.createElement(Lt,{adapter:p,site:b,stats:P,onCommit:()=>l(`commit-prompt`),onSkipCommit:()=>l(`deploy-prompt`)}),s===`commit-prompt`&&b&&D.createElement(Rt,{cwd:t.cwd,onDone:e=>{e.committed&&N(`Pushed ${e.shortSha} to ${e.branch}`),l(`deploy-prompt`)},onError:e=>{C(e),l(`error`)}}),s===`deploy-prompt`&&b&&D.createElement(zt,{site:b,onDeployed:()=>l(`visit-prompt`),onSkip:()=>l(`verify`)}),s===`visit-prompt`&&b&&D.createElement(Bt,{site:b,onContinue:()=>l(`verify`)}),s===`verify`&&b&&D.createElement(Ht,{site:b,elapsed:ne,onTick:e=>re(e),onArrived:()=>l(`success`),onTimeout:()=>l(`success`)}),s===`success`&&p&&b&&D.createElement(It,{adapter:p,site:b,stats:P,onPick:e=>{a(),i(0,e,b)}}),s===`error`&&D.createElement(u,{title:`Install failed`,state:`err`},D.createElement(T,{color:o.error},S??`Unknown error`)),D.createElement(g,{items:[{key:`q`,label:`quit`},{key:`ctrl-c`,label:`abort`}]}))};async function Z(i,a,o,s){try{s.setPhase(`agent`);let c=await e(),l=await r({strict:!1}),u=new n({config:c,cliVersion:o.cliVersion,token:l}),d=new q({cwd:o.cwd}),f=i.trackingCode??i.id;await t({defaultSiteId:i.id,defaultSiteName:i.name}).catch(()=>void 0);let p=await z(o.cwd,f);if(p.recommendation===`no-op`){s.setTasks(e=>e.map(e=>e.id===`detect`||e.id===`read`||e.id===`write`?{...e,status:`done`,detail:`already installed correctly`}:e)),s.setLiveLine(p.summary),s.setSandboxStats?.({reads:0,writes:0,rounds:0}),!o.skipMcp&&l&&await Y({accessToken:l.accessToken}).catch(()=>void 0),s.setPhase(`install-complete`);return}p.competitors.length>0&&s.setLiveLine(`Detected ${p.competitors.map(e=>e.name).join(`, `)} — will install Zenovay alongside (won't touch their tags).`);let m=a.id===`plain-html`&&(p.recommendation===`fresh-install`||p.recommendation===`fresh-install-alongside-competitor`||p.recommendation===`update-tracking-code`);if(m){s.setTasks(e=>e.map(e=>e.id===`detect`?{...e,status:`done`,detail:`Plain HTML`}:e)),s.setLiveLine(`Plain HTML — using fast install path (no AI needed)…`);let e=await ht({cwd:o.cwd,trackingCode:f,updateOnly:p.recommendation===`update-tracking-code`});if(e.installed.length===0&&e.errors.length===0)throw Error(`No HTML files were modified. Either no .html files contain a <head>...</head> block, or every file already has the correct tracker.`);if(e.installed.length===0&&e.errors.length>0)throw Error(`Programmatic install failed for all ${e.errors.length} file(s). First error: ${e.errors[0].file}: ${e.errors[0].error}`);s.setTasks(t=>t.map(t=>t.id===`read`||t.id===`write`?{...t,status:`done`,detail:`${e.installed.length} file(s)`}:t)),s.setLiveLine(`Tracker added to ${e.installed.length} file(s). Skipped ${e.skipped.length}, errored ${e.errors.length}.`);let t=await J(o.cwd,{[a.envVarName]:f});if(s.setTasks(e=>e.map(e=>e.id===`env`?{...e,status:`done`,detail:`${t.addedKeys.length} key(s) added`}:e)),!o.skipMcp&&l){let e=await Y({accessToken:l.accessToken}).catch(()=>null);s.setTasks(t=>t.map(t=>t.id===`mcp`?{...t,status:`done`,detail:e&&e.installed.length>0?e.installed.join(`, `):`none detected`}:t))}else s.setTasks(e=>e.map(e=>e.id===`mcp`?{...e,status:`done`,detail:`skipped`}:e));s.setLiveLine(void 0),s.setSandboxStats?.({reads:e.installed.length,writes:e.installed.length,rounds:0}),s.setPhase(`install-complete`);return}let h=a.buildSnippets({trackingCode:f,siteId:i.id}),g=`Install Zenovay tracking into this ${a.displayName} project.\nSite: ${i.name} (trackingCode: ${f}).\n1. Call detect_framework to confirm the framework.\n2. Read the entry file(s) for this framework:\n`+a.files.map(e=>` - ${e.path}`).join(`
|
|
71
|
+
`)+`
|
|
72
|
+
3. Insert the tracker using the snippet below.
|
|
73
|
+
4. Confirm the change with a diff before writing.
|
|
74
|
+
|
|
75
|
+
Snippet reference (anchored by anchorKind — adapt to the actual file):
|
|
76
|
+
`+h.map(e=>` ${e.anchorKind} @ ${e.targetPath}:\n ${e.code.replace(/\n/g,`
|
|
77
|
+
`)}`).join(`
|
|
78
|
+
|
|
79
|
+
`);s.setTasks([{id:`detect`,label:`Detect framework`,status:`active`},{id:`read`,label:`Read project entry files`,status:`pending`},{id:`write`,label:`Insert tracker snippet`,status:`pending`},{id:`env`,label:`Write env vars (.env.local)`,status:`pending`},{id:`mcp`,label:`Install MCP clients (Cursor/Claude/VS Code)`,status:`pending`},{id:`verify`,label:`Wait for first event`,status:`pending`}]);let _=(e,t,n)=>{s.setTasks(r=>r.map(r=>r.id===e?{...r,status:t,detail:n??r.detail}:r))},v=vt({mode:_t(o)});s.registerBridge(v);let y=v.subscribe(e=>s.setPendingApproval(e)),b=null;try{await ft({api:u,sandbox:d,approval:v,prompter:{async ask({question:e}){return e}},handlers:{onToolStart:({name:e})=>{e===`detect_framework`?(_(`detect`,`active`),s.setLiveLine(`Inspecting package.json + config files…`)):e===`read_file`?(_(`detect`,`done`),_(`read`,`active`)):(e===`write_file`||e===`patch_file`)&&(_(`read`,`done`),_(`write`,`active`))},onToolResult:({name:e,result:t})=>{let n=t?.ok!==!1,r=t?.content??{};e===`detect_framework`&&n&&(_(`detect`,`done`,a.displayName),s.setLiveLine(`Detected ${a.displayName}`)),e===`read_file`&&n&&r.path&&s.setLiveLine(`Read ${r.path}`),(e===`write_file`||e===`patch_file`)&&n&&r.path&&s.setLiveLine(`Wrote ${r.path}`)},onDelta:e=>{let t=e.replace(/\s+/g,` `).trim();t.length>4&&s.setLiveLine(t.slice(0,80))}},initialMessages:[{role:`user`,content:g}],diagnosis:p,targetTrackingCode:f})}catch(e){b=e}finally{y(),s.setPendingApproval(null)}let x=d.stats();if(x.writes===0){let e=b?`Agent error: ${b.message}`:`Agent returned without writing any files (${x.reads} reads, ${x.rounds} rounds). The model may have refused or the AI Gateway may be misconfigured. Check api.zenovay.com logs.`;throw Error(e)}let S=a.files.map(e=>e.path),C=x.writtenPaths.some(e=>S.some(t=>e===t||e.endsWith(`/${t}`)));if(!C)throw Error(`Agent wrote to: [${x.writtenPaths.join(`, `)}] but never modified the framework's tracker target file(s): [${S.join(`, `)}]. The tracker is NOT installed. The model likely put the trackingCode into .env.local instead of into the HTML/JSX entry file.`);_(`write`,`done`),_(`env`,`active`),s.setLiveLine(`Adding env var to .env.local`);let w=await J(o.cwd,{[a.envVarName]:f});if(_(`env`,`done`,`${w.addedKeys.length} key(s) added`),!o.skipMcp&&l){_(`mcp`,`active`),s.setLiveLine(`Detecting Cursor / Claude Desktop / VS Code…`);let e=await Y({accessToken:l.accessToken}).catch(()=>null);e&&e.installed.length>0?_(`mcp`,`done`,e.installed.join(`, `)):_(`mcp`,`done`,`none detected`)}else _(`mcp`,`done`,`skipped`);s.setTasks(e=>e.map(e=>e.id===`detect`||e.id===`read`||e.id===`write`?{...e,status:e.status===`pending`?`done`:e.status}:e)),s.setLiveLine(void 0),s.setSandboxStats?.(x),s.setPhase(`install-complete`)}catch(e){s.setErrorMsg(e.message),s.setPhase(`error`)}}const jt=()=>{let{theme:e}=d();return D.createElement(u,{title:`Zenovay wants to set up your project`,state:`idle`},D.createElement(T,null,`We'll use AI to analyze your codebase and add the Zenovay tracker.`),D.createElement(T,{color:e.muted},`.env* contents stay on your machine. Only framework metadata reaches api.zenovay.com.`))},Mt=({adapter:e,site:t,tasks:n,liveLine:r,pendingApproval:i,onApprove:a})=>{let{theme:o}=d(),s=process.stdout.columns??100,c=(process.stdout.rows??30)-8,l=Math.floor(s/2)-1,u=Math.max(14,c);return D.createElement(w,{flexDirection:`column`},D.createElement(w,{flexDirection:`row`,minHeight:u,borderStyle:`round`,borderColor:o.border},D.createElement(w,{width:l,flexDirection:`column`},D.createElement(wt,{width:l,height:u})),D.createElement(w,{width:1,flexDirection:`column`},D.createElement(T,{color:o.border},`│`)),D.createElement(w,{width:l,flexDirection:`column`},i?D.createElement(w,{flexDirection:`column`,paddingX:2,paddingTop:1},D.createElement(T,{color:o.warn},`The agent wants to write `,i.path),i.reason?D.createElement(T,{color:o.muted},i.reason):null,D.createElement(p,{key:i.id,filePath:i.path,oldContent:i.oldContent,newContent:i.newContent,onApprove:a})):D.createElement(D.Fragment,null,D.createElement(w,{paddingX:2,paddingTop:1},D.createElement(m,{done:n.filter(e=>e.status===`done`).length,total:n.length,label:n.find(e=>e.status===`active`)?.label})),D.createElement(Dt,{tasks:n,liveLine:r,width:l,height:u})))),D.createElement(w,{marginTop:1},D.createElement(T,{color:o.muted},`Site: `,t.name,` · code: `,t.trackingCode??t.id,` · Framework: `,e.displayName)))},Nt=({site:e,onContinue:t,onExit:n})=>{let{theme:r}=d();E((e,r)=>{if(e===`q`||r.escape){n();return}if(e===`r`||e===`R`){t();return}});let i=e.url.replace(/^https?:\/\//,``).replace(/\/$/,``);return D.createElement(u,{title:`Already installed`,state:`ok`},D.createElement(w,{flexDirection:`column`,gap:1},D.createElement(w,{flexDirection:`row`,gap:1},D.createElement(T,{color:r.success},`✓`),D.createElement(T,{bold:!0},e.name),D.createElement(T,{color:r.muted},`(`,i,`)`)),D.createElement(T,{color:r.muted},`Zenovay is already tracking this project. No changes needed.`),e.trackingCode?D.createElement(T,{color:r.dim},`tracking code: `,e.trackingCode):null,D.createElement(w,{marginTop:1},D.createElement(T,{color:r.muted},`[`),D.createElement(T,{color:r.accent},`q`),D.createElement(T,{color:r.muted},` quit] [`),D.createElement(T,{color:r.accent},`r`),D.createElement(T,{color:r.muted},` re-run wizard anyway]`))))},Pt=({sites:e,preselectHint:t,onPick:n})=>{let r=Kt(e,t),i=e.filter(e=>e.id!==r?.id),a=r?[r,...i]:i,o=[...a.map(e=>({label:Wt(e,e.id===r?.id),value:e,description:e.trackingCode?`trackingCode: ${e.trackingCode}`:void 0})),{label:`+ Create new site on app.zenovay.com`,value:`create-new`,description:`Open the dashboard to add a new website`}];return D.createElement(u,{title:t?`Pick a site (best match: ${t})`:`Pick a site`,state:`idle`},D.createElement(h,{items:o,onSelect:n}))};function Ft(){let e=process.argv[1]??``;return e.includes(`/_npx/`)||e.includes(`\\_npx\\`)||process.env.npm_execpath?.includes(`npx`)?`npx`:`global`}const It=({adapter:e,site:t,stats:n,onPick:r})=>{let{theme:i}=d(),a=Ft(),o=a===`npx`?`npx @zenovay/cli`:`zenovay`;return E((e,t)=>{let n=e.toLowerCase();n===`w`||t.return?r(`watch`):n===`e`?r(`events`):(n===`s`||n===`q`||t.escape)&&r(`quit`)},{isActive:!0}),D.createElement(u,{title:`You're live on Zenovay`,state:`ok`},D.createElement(T,{color:i.success,bold:!0},`✔ Installed successfully`),D.createElement(w,{marginTop:1,flexDirection:`column`},D.createElement(T,null,`Framework: `,D.createElement(T,{color:i.accent},e.displayName)),D.createElement(T,null,`Site: `,D.createElement(T,{color:i.accent},t.name),` (`,t.url,`)`),t.trackingCode?D.createElement(T,{color:i.muted},`Tracker: `,t.trackingCode):null,n&&n.rounds>0?D.createElement(T,{color:i.muted},`Modified: `,n.writes,` file(s) across `,n.rounds,` agent round(s)`):n&&n.writes>0?D.createElement(T,{color:i.muted},`Modified: `,n.writes,` file(s) (programmatic install — no AI calls)`):null),D.createElement(w,{marginTop:1,flexDirection:`column`},D.createElement(T,{bold:!0},`What's next?`),D.createElement(T,null,` `),D.createElement(w,null,D.createElement(T,{color:i.accent},` [W] `),D.createElement(T,null,`Open the live dashboard `),D.createElement(T,{color:i.muted},`— real-time visitors, top pages, sources, vitals`)),D.createElement(w,null,D.createElement(T,{color:i.accent},` [E] `),D.createElement(T,null,`Stream events as they fire `),D.createElement(T,{color:i.muted},`— page views, custom events, in real time`)),D.createElement(w,null,D.createElement(T,{color:i.accent},` [S] `),D.createElement(T,null,`Exit to shell`))),a===`npx`?D.createElement(w,{marginTop:1,flexDirection:`column`,borderStyle:`single`,borderColor:i.warn,paddingX:1},D.createElement(T,{color:i.warn,bold:!0},`Heads up — you ran via npx`),D.createElement(T,null,D.createElement(T,null,`The `),D.createElement(T,{color:i.accent},`zenovay`),D.createElement(T,null,` command isn't on your PATH yet. After you exit, run any of:`)),D.createElement(T,null,` `),D.createElement(T,{color:i.accent},` npm i -g @zenovay/cli `),D.createElement(T,{color:i.muted},"→ then `zenovay watch` works directly"),D.createElement(T,{color:i.accent},` npx @zenovay/cli watch `),D.createElement(T,{color:i.muted},`→ no install, slower start`)):D.createElement(w,{marginTop:1},D.createElement(T,{color:i.muted},`From your shell: `),D.createElement(T,{color:i.accent},o,` watch`),D.createElement(T,{color:i.muted},` · `),D.createElement(T,{color:i.accent},o,` --help`)),D.createElement(w,{marginTop:1},D.createElement(T,{color:i.muted},`Dashboard URL: https://app.zenovay.com/sites/`,t.id)))},Lt=({adapter:e,site:t,stats:n,onCommit:r,onSkipCommit:i})=>{let{theme:a}=d();return Q(e=>{e===`y`||e===`Y`||e===`\r`?r():(e===`n`||e===`N`||e===`s`||e===`S`)&&i()}),D.createElement(u,{title:`Tracker installed`,state:`ok`},D.createElement(T,null,D.createElement(T,{color:a.success},`✔ `),`The Zenovay tracker is in your project.`),D.createElement(w,{marginTop:1},D.createElement(T,{color:a.muted},`Framework: `),D.createElement(T,null,e.displayName)),D.createElement(w,null,D.createElement(T,{color:a.muted},`Site: `),D.createElement(T,null,t.name,` (`,t.url,`)`)),n&&D.createElement(w,null,D.createElement(T,{color:a.muted},`Modified: `),D.createElement(T,null,n.writes,` file(s)`)),D.createElement(w,{marginTop:1},D.createElement(T,null,`Commit + push these changes to git? `),D.createElement(T,{color:a.accent},`[Y/n]`)))},Rt=({cwd:e,onDone:t,onError:n})=>{let{theme:r}=d(),[i,a]=A(`committing`),[o,s]=A(`Staging changes…`);return O(()=>{(async()=>{try{let n=await Ut(e,e=>s(e),e=>a(e));n.committed?s(`Pushed ${n.shortSha} to origin/${n.branch}`):s(`No changes to commit (already up-to-date).`),setTimeout(()=>t(n),800)}catch(e){n(e.message)}})()},[]),D.createElement(u,{title:`Committing to git`,state:`busy`},D.createElement(w,null,D.createElement(T,{color:r.accent},D.createElement(M,{type:`dots`})),D.createElement(T,null,` `,i===`pushing`?`Pushing to origin`:`Committing`,`…`)),D.createElement(w,{marginTop:1},D.createElement(T,{color:r.muted},o)))},zt=({site:e,onDeployed:t,onSkip:n})=>{let{theme:r}=d();return Q(e=>{e===`y`||e===`Y`||e===`\r`||e===`d`||e===`D`?t():(e===`n`||e===`N`||e===`s`||e===`S`)&&n()}),D.createElement(u,{title:`Ready to deploy?`,state:`idle`},D.createElement(T,null,`The tracker is installed locally. To capture events you need it on the live site:`),D.createElement(T,null,` `),D.createElement(w,null,D.createElement(T,{color:r.accent},` → `),D.createElement(T,null,e.url)),D.createElement(T,null,` `),D.createElement(T,{color:r.muted},`Push the change through your normal deploy pipeline (Vercel auto-deploy, CI/CD, etc.).`),D.createElement(w,{marginTop:1},D.createElement(T,null,`Once it's deployed, press `),D.createElement(T,{color:r.accent},`[Y]`),D.createElement(T,null,` to verify the tracker is firing. `),D.createElement(T,{color:r.muted},`[s] skip verification`)))},Bt=({site:e,onContinue:t})=>{let{theme:n}=d();return Q(()=>t()),D.createElement(u,{title:`Visit your site`,state:`idle`},D.createElement(T,null,`To send a test event, open any page on your site:`),D.createElement(T,null,` `),D.createElement(w,null,D.createElement(T,{color:n.accent},` → `),D.createElement(T,null,e.url)),D.createElement(T,null,` `),D.createElement(T,{color:n.muted},`The tracker fires automatically on page load. We'll verify the next event arrives.`),D.createElement(w,{marginTop:1},D.createElement(T,null,`Press `),D.createElement(T,{color:n.accent},`[any key]`),D.createElement(T,null,` when you've visited the page.`)))};async function Vt(e,t){try{let n=await fetch(e,{headers:{"User-Agent":`zenovay-cli/preflight`},signal:AbortSignal.timeout(8e3),redirect:`follow`});if(!n.ok)return{state:`tracker-missing`,httpStatus:n.status};let r=await n.text(),i=/<script[^>]*src\s*=\s*["']https:\/\/(?:api|cdn)\.zenovay\.com\/[^"']+["'][^>]*>\s*<\/script>/gi,a=/data-id\s*=\s*["'](ZV_[A-Za-z0-9]+)["']/i,o=r.match(i)??[];if(o.length===0)return{state:`tracker-missing`};let s=Array.from(new Set(o.map(e=>e.match(a)?.[1]).filter(e=>!!e)));return s.includes(t)?{state:`tracker-correct`}:{state:`tracker-wrong-id`,foundCodes:s}}catch(e){return{state:`tracker-missing`,networkError:e.message}}}const Ht=({site:t,elapsed:i,onTick:a,onArrived:o,onTimeout:s})=>{let{theme:c}=d(),[l,f]=A(`preflight`),[p,m]=A(``),[h,g]=A([]),_=k(!1),v=t.trackingCode??t.id,y=async()=>{f(`preflight`),m(`Fetching ${t.url}…`);let e=await Vt(t.url,v);if(!_.current){if(e.state===`tracker-correct`){f(`polling`),m(`Tracker detected on live site — waiting for first hit…`),b();return}if(e.state===`tracker-wrong-id`){f(`wrong-id-on-live`),g(e.foundCodes),m(`Live site has Zenovay but with id(s): ${e.foundCodes.join(`, `)}`);return}f(`awaiting-deploy`),m(e.networkError?`Couldn't reach ${t.url}: ${e.networkError}`:e.httpStatus?`${t.url} returned HTTP ${e.httpStatus}`:`No Zenovay tracker found on ${t.url}.`)}},b=async()=>{let i=Date.now()-10*60*1e3;try{let s=await e(),c=await r({strict:!1}),l=new n({config:s,cliVersion:`0`,token:c}),u=await tt(t.id,i,{api:l,timeoutMs:6e4,pollIntervalMs:2500,onTick:e=>{_.current||a(e)}});if(_.current)return;u.received?(f(`success`),o()):f(`timeout`)}catch{_.current||f(`timeout`)}};O(()=>(_.current=!1,y(),()=>{_.current=!0}),[]),E(e=>{(l===`awaiting-deploy`||l===`wrong-id-on-live`||l===`timeout`)&&(e===`r`||e===`R`?y():(e===`s`||e===`S`)&&s())},{isActive:!0});let x=Math.floor(i/1e3);return l===`preflight`?D.createElement(u,{title:`Verifying`,state:`busy`},D.createElement(w,null,D.createElement(T,{color:c.accent},D.createElement(M,{type:`dots`})),D.createElement(T,null,` Checking your live site for the tracker…`)),D.createElement(w,{marginTop:1},D.createElement(T,{color:c.muted},p))):l===`awaiting-deploy`?D.createElement(u,{title:`Tracker not yet on your live site`,state:`warn`},D.createElement(T,{color:c.warn},p),D.createElement(w,{marginTop:1,flexDirection:`column`},D.createElement(T,null,`The tracker is in your local repo but hasn't been deployed yet.`),D.createElement(T,{color:c.muted},`Push your changes and wait for your deploy to finish (Vercel / Cloudflare Pages / CI), then retry.`)),D.createElement(w,{marginTop:1},D.createElement(T,null,`Press `),D.createElement(T,{color:c.accent},`[r]`),D.createElement(T,null,` to re-check, `),D.createElement(T,{color:c.accent},`[s]`),D.createElement(T,null,` to skip verification.`))):l===`wrong-id-on-live`?D.createElement(u,{title:`Live site has a different tracking code`,state:`warn`},D.createElement(T,{color:c.warn},`Expected: `,D.createElement(T,{color:c.accent},v),` `,`Found on live: `,D.createElement(T,{color:c.accent},h.join(`, `))),D.createElement(w,{marginTop:1,flexDirection:`column`},D.createElement(T,null,`Your local install uses a different ID than what's on `,t.url,`.`),D.createElement(T,{color:c.muted},`Deploy the new local version, then retry — or skip if the existing ID is intentional.`)),D.createElement(w,{marginTop:1},D.createElement(T,null,`Press `),D.createElement(T,{color:c.accent},`[r]`),D.createElement(T,null,` to re-check, `),D.createElement(T,{color:c.accent},`[s]`),D.createElement(T,null,` to skip.`))):l===`timeout`?D.createElement(u,{title:`No event arrived in 3 minutes`,state:`warn`},D.createElement(T,{color:c.warn},`The tracker is on your live site but no hits reached api.zenovay.com.`),D.createElement(w,{marginTop:1},D.createElement(T,{color:c.muted},`Try opening `,t.url,` in a fresh browser tab (no ad-blocker), then retry.`)),D.createElement(w,{marginTop:1},D.createElement(T,null,`Press `),D.createElement(T,{color:c.accent},`[r]`),D.createElement(T,null,` to re-check, `),D.createElement(T,{color:c.accent},`[s]`),D.createElement(T,null,` to finish without verification.`))):D.createElement(u,{title:`Waiting for first event`,state:`busy`},D.createElement(w,null,D.createElement(T,{color:c.accent},D.createElement(M,{type:`dots`})),D.createElement(T,null,` Polling api.zenovay.com for the first hit on `),D.createElement(T,{color:c.accent},t.url),D.createElement(T,null,` (`,x,`s elapsed)`)),D.createElement(w,{marginTop:1},D.createElement(T,{color:c.muted},`Reload the page if nothing arrives within ~30 seconds.`)))};function Q(e){E((t,n)=>{e(t,{return:n.return,escape:n.escape})},{isActive:!0})}async function Ut(e,t,n){let{execFile:r}=await import(`node:child_process`),{promisify:i}=await import(`node:util`),a=i(r),o=async(t,n)=>{let{stdout:r}=await a(t,n,{cwd:e,maxBuffer:4e6});return r.trim()};try{await o(`git`,[`rev-parse`,`--is-inside-work-tree`])}catch{return t(`Not a git repository — skipped.`),{committed:!1}}let s=await o(`git`,[`status`,`--porcelain`]);if(!s)return{committed:!1};t(`Staging tracker changes…`),await o(`git`,[`add`,`-A`]),t(`Creating commit…`),await o(`git`,[`commit`,`-m`,`chore(zenovay): install tracking script via @zenovay/cli`]);let c=await o(`git`,[`rev-parse`,`--short`,`HEAD`]),l=await o(`git`,[`rev-parse`,`--abbrev-ref`,`HEAD`]);n(`pushing`),t(`Pushing to origin/${l}…`);try{await o(`git`,[`push`,`origin`,l])}catch(e){return t(`Commit ${c} created locally; push failed (${e.message.slice(0,80)}). Push manually with: git push origin ${l}`),{committed:!0,shortSha:c,branch:l}}return n(`done`),{committed:!0,shortSha:c,branch:l}}function Wt(e,t){let n=`${e.name}`.padEnd(30,` `).slice(0,30),r=e.url.replace(/^https?:\/\//,``).padEnd(28,` `).slice(0,28),i=t?` ← best match`:``;return`${n} ${r}${i}`}async function Gt(e){try{let t=await y.readFile(v.join(e,`package.json`),`utf8`),n=JSON.parse(t);if(n.name)return String(n.name).replace(/^@[^/]+\//,``)}catch{}try{let t=await y.readFile(v.join(e,`.git`,`config`),`utf8`),n=t.match(/url\s*=\s*.*[/:]([^/]+?)(?:\.git)?\s*$/m);if(n&&n[1])return n[1]}catch{}return v.basename(e)}function Kt(e,t){if(!t||e.length===0)return e[0]??null;let n=$(t),r=e.find(e=>$(e.name)===n);if(r)return r;let i=e.find(e=>{let t=e.url.replace(/^https?:\/\//,``).replace(/\/.*$/,``),[r]=t.split(`.`);return $(r??``)===n});if(i)return i;let a=e.find(e=>{let t=$(e.name),r=$(e.url);return t.includes(n)||r.includes(n)||n.includes(t)||n.includes(r)});return a??e[0]??null}function $(e){return e.toLowerCase().replace(/[^a-z0-9]/g,``)}export{ie as detectInstalled,Ot as initCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./config--JRu_jFq.js";import"./api-FCanTxsb.js";import"./token-store-BQuGH0z2.js";import{initCommand as e}from"./init-
|
|
1
|
+
import"./config--JRu_jFq.js";import"./api-FCanTxsb.js";import"./token-store-BQuGH0z2.js";import{initCommand as e}from"./init-BuyESMBj.js";import"./formatter-DDG2TSOz.js";import"./emit-C9vAopcy.js";import"./secrets-DPf_7SkN.js";import"./client-Dm8uGVCy.js";import"./open-url-BxXbZzWS.js";import"./login-CaDs2Peq.js";import"./theme-BuGrQHax.js";import"./panel-B-XxdP-N.js";import"./banner-DdgwXGzt.js";import"./progress-row-5ns2IsFh.js";import"./prompt-B4s3o9G7.js";import"./keybar-QgnmSion.js";import"./sparkline-CpoBjjlc.js";export{e as initCommand};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{readConfig as e}from"./config--JRu_jFq.js";import{ApiClient as t,pollDeviceToken as n,startDeviceAuthorization as r}from"./api-FCanTxsb.js";import{writeToken as i}from"./token-store-BQuGH0z2.js";import{emit$1 as a,isHeadless as o}from"./emit-C9vAopcy.js";import{openInBrowser as s}from"./open-url-BxXbZzWS.js";import c from"node:path";import{promises as l}from"node:fs";import u from"node:os";async function d(c){let l=await e(),u=o({json:c.json}),d=await r({apiBase:l.apiBase}),f=d.verification_uri_complete??d.verification_uri;u?a({type:`info`,message:`Open ${f} and enter code ${d.user_code}`}):c.onCode?c.onCode({userCode:d.user_code,verificationUri:d.verification_uri,verificationUriComplete:d.verification_uri_complete}):process.stdout.write(`\n Open this URL to authorize the CLI:\n ${f}\n Or enter code: ${d.user_code}\n\n`);let p=c.openBrowser??!u;if(p)try{await s(f)}catch{}let m=await n({apiBase:l.apiBase,deviceCode:d.device_code,interval:d.interval,expiresIn:d.expires_in});await i(m);let h=new t({config:l,cliVersion:c.cliVersion,token:m}),_=await h.me().catch(()=>null);if(_){let e={...m,userId:_.user.id,teamId:_.team?.id,email:_.user.email};await i(e)}return u?a({type:`done`,command:`login`,exitCode:0}):await g().catch(()=>void 0),{email:_?.user.email}}function f(){let e=process.env.SHELL??``,t=u.homedir();if(e.endsWith(`/zsh`)||e.endsWith(`zsh`))return{shell:`zsh`,rcPath:c.join(t,`.zshrc`)};if(e.endsWith(`/bash`)||e.endsWith(`bash`)){let e=process.platform===`darwin`?c.join(t,`.bash_profile`):c.join(t,`.bashrc`);return{shell:`bash`,rcPath:e}}return e.endsWith(`/fish`)||e.endsWith(`fish`)?{shell:`fish`,rcPath:c.join(t,`.config`,`fish`,`config.fish`)}:null}const p=/zenovay\s+completions/;async function m(e,t){try{let n=await l.readFile(e,`utf8`);return t.test(n)}catch{return!1}}function h(e){return process.stdin.isTTY?new Promise(t=>{process.stdout.write(e),process.stdin.setEncoding(`utf8`);let n=e=>{process.stdin.removeListener(`data`,n);try{process.stdin.pause()}catch{}let r=e.trim().toLowerCase();return t(r===``||r===`y`||r===`yes`)};process.stdin.resume(),process.stdin.on(`data`,n)}):Promise.resolve(!1)}async function g(){let e=f();if(!e||await m(e.rcPath,p))return;let t=await h(`Install shell completions for ${e.shell}? [Y/n] `);if(!t){process.stdout.write(`Skipped. You can install later with: zenovay completions ${e.shell} >> ${e.rcPath}\n`);return}let n=e.shell===`fish`?`zenovay completions fish | source
|
|
2
|
-
`:`eval "$(zenovay completions ${e.shell})"\n`;try{await l.mkdir(c.dirname(e.rcPath),{recursive:!0}),await l.appendFile(e.rcPath,`\n# Added by zenovay login\n${n}`,`utf8`),process.stdout.write(`✓ Completions installed. Restart your shell or source ${e.rcPath}.\n`)}catch(t){process.stdout.write(`Could not write ${e.rcPath} (${t.message}). Run manually: zenovay completions ${e.shell} >> ${e.rcPath}\n`)}}export{d as loginCommand};
|
|
1
|
+
import{readConfig as e}from"./config--JRu_jFq.js";import{ApiClient as t,pollDeviceToken as n,startDeviceAuthorization as r}from"./api-FCanTxsb.js";import{writeToken as i}from"./token-store-BQuGH0z2.js";import{emit$1 as a,isHeadless as o}from"./emit-C9vAopcy.js";import{openInBrowser as s}from"./open-url-BxXbZzWS.js";import c from"node:path";import{promises as l}from"node:fs";import u from"node:os";async function d(c){let l=await e(),u=o({json:c.json}),d=await r({apiBase:l.apiBase}),f=d.verification_uri_complete??d.verification_uri;u?a({type:`info`,message:`Open ${f} and enter code ${d.user_code}`}):c.onCode?c.onCode({userCode:d.user_code,verificationUri:d.verification_uri,verificationUriComplete:d.verification_uri_complete}):process.stdout.write(`\n Open this URL to authorize the CLI:\n ${f}\n Or enter code: ${d.user_code}\n\n`);let p=c.openBrowser??!u;if(p)try{await s(f)}catch{}let m=await n({apiBase:l.apiBase,deviceCode:d.device_code,interval:d.interval,expiresIn:d.expires_in});await i(m);let h=new t({config:l,cliVersion:c.cliVersion,token:m}),_=await h.me().catch(()=>null);if(_){let e={...m,userId:_.user.id,teamId:_.team?.id,email:_.user.email};await i(e)}return u?a({type:`done`,command:`login`,exitCode:0}):c.offerCompletions!==!1&&await g().catch(()=>void 0),{email:_?.user.email}}function f(){let e=process.env.SHELL??``,t=u.homedir();if(e.endsWith(`/zsh`)||e.endsWith(`zsh`))return{shell:`zsh`,rcPath:c.join(t,`.zshrc`)};if(e.endsWith(`/bash`)||e.endsWith(`bash`)){let e=process.platform===`darwin`?c.join(t,`.bash_profile`):c.join(t,`.bashrc`);return{shell:`bash`,rcPath:e}}return e.endsWith(`/fish`)||e.endsWith(`fish`)?{shell:`fish`,rcPath:c.join(t,`.config`,`fish`,`config.fish`)}:null}const p=/zenovay\s+completions/;async function m(e,t){try{let n=await l.readFile(e,`utf8`);return t.test(n)}catch{return!1}}function h(e){return process.stdin.isTTY?new Promise(t=>{process.stdout.write(e),process.stdin.setEncoding(`utf8`);let n=e=>{process.stdin.removeListener(`data`,n);try{process.stdin.pause()}catch{}let r=e.trim().toLowerCase();return t(r===``||r===`y`||r===`yes`)};process.stdin.resume(),process.stdin.on(`data`,n)}):Promise.resolve(!1)}async function g(){let e=f();if(!e||await m(e.rcPath,p))return;let t=await h(`Install shell completions for ${e.shell}? [Y/n] `);if(!t){process.stdout.write(`Skipped. You can install later with: zenovay completions ${e.shell} >> ${e.rcPath}\n`);return}let n=e.shell===`fish`?`zenovay completions fish | source
|
|
2
|
+
`:`eval "$(zenovay completions ${e.shell})"\n`;try{await l.mkdir(c.dirname(e.rcPath),{recursive:!0}),await l.appendFile(e.rcPath,`\n# Added by zenovay login\n${n}`,`utf8`),process.stdout.write(`✓ Completions installed. Restart your shell or source ${e.rcPath}.\n`)}catch(t){process.stdout.write(`Could not write ${e.rcPath} (${t.message}). Run manually: zenovay completions ${e.shell} >> ${e.rcPath}\n`)}}export{d as loginCommand,g as maybeInstallCompletions};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./config--JRu_jFq.js";import"./api-FCanTxsb.js";import"./token-store-BQuGH0z2.js";import"./formatter-DDG2TSOz.js";import"./emit-C9vAopcy.js";import"./open-url-BxXbZzWS.js";import{loginCommand as e}from"./login-
|
|
1
|
+
import"./config--JRu_jFq.js";import"./api-FCanTxsb.js";import"./token-store-BQuGH0z2.js";import"./formatter-DDG2TSOz.js";import"./emit-C9vAopcy.js";import"./open-url-BxXbZzWS.js";import{loginCommand as e,maybeInstallCompletions as t}from"./login-CaDs2Peq.js";export{e as loginCommand};
|
package/dist/wizard-bin.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import(`./init-
|
|
2
|
+
import(`./init-WmEUW4Em.js`).then(async({initCommand:e})=>{let{readFileSync:t}=await import(`node:fs`),n=await import(`node:path`),{fileURLToPath:r}=await import(`node:url`),i=n.dirname(r(import.meta.url)),a=JSON.parse(t(n.join(i,`..`,`package.json`),`utf8`)),o=process.argv.slice(2),s=o.includes(`--json`),c=o.includes(`--yes`),l=o.includes(`--skip-mcp`),u=o.includes(`--skip-verify`),d=await e({cliVersion:a.version,cwd:process.cwd(),json:s,yes:c,skipMcp:l,skipVerify:u});process.exit(d)});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenovay/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.67",
|
|
4
4
|
"description": "Zenovay CLI — AI install wizard + full terminal analytics dashboard",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,6 +15,22 @@
|
|
|
15
15
|
"README.md",
|
|
16
16
|
"LICENSE"
|
|
17
17
|
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"dev": "tsx src/bin.ts",
|
|
20
|
+
"build": "tsdown",
|
|
21
|
+
"build:binaries": "tsx scripts/build-binaries.ts",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"lint": "eslint 'src/**/*.{ts,tsx}'",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"test:all": "vitest run tests/unit tests/integration tests/security",
|
|
27
|
+
"test:release": "vitest run tests/e2e tests/update --testTimeout=600000",
|
|
28
|
+
"test:smoke": "vitest run tests/smoke --testTimeout=60000",
|
|
29
|
+
"test:update-golden": "UPDATE_GOLDEN=1 vitest run tests/e2e",
|
|
30
|
+
"render:globe": "tsx scripts/render-globe.ts",
|
|
31
|
+
"smoke": "bash scripts/smoke-test.sh",
|
|
32
|
+
"prepare": "husky install"
|
|
33
|
+
},
|
|
18
34
|
"dependencies": {
|
|
19
35
|
"@hono/node-server": "^1.14.0",
|
|
20
36
|
"cac": "^6.7.14",
|
|
@@ -53,6 +69,7 @@
|
|
|
53
69
|
"engines": {
|
|
54
70
|
"node": ">=20.0.0"
|
|
55
71
|
},
|
|
72
|
+
"packageManager": "pnpm@9.15.9",
|
|
56
73
|
"author": "Zenovay <cli@zenovay.com>",
|
|
57
74
|
"license": "SEE LICENSE IN LICENSE",
|
|
58
75
|
"homepage": "https://cli.zenovay.com",
|
|
@@ -70,20 +87,5 @@
|
|
|
70
87
|
"tui",
|
|
71
88
|
"ai-agent",
|
|
72
89
|
"install-wizard"
|
|
73
|
-
]
|
|
74
|
-
|
|
75
|
-
"dev": "tsx src/bin.ts",
|
|
76
|
-
"build": "tsdown",
|
|
77
|
-
"build:binaries": "tsx scripts/build-binaries.ts",
|
|
78
|
-
"typecheck": "tsc --noEmit",
|
|
79
|
-
"lint": "eslint 'src/**/*.{ts,tsx}'",
|
|
80
|
-
"test": "vitest run",
|
|
81
|
-
"test:watch": "vitest",
|
|
82
|
-
"test:all": "vitest run tests/unit tests/integration tests/security",
|
|
83
|
-
"test:release": "vitest run tests/e2e tests/update --testTimeout=600000",
|
|
84
|
-
"test:smoke": "vitest run tests/smoke --testTimeout=60000",
|
|
85
|
-
"test:update-golden": "UPDATE_GOLDEN=1 vitest run tests/e2e",
|
|
86
|
-
"render:globe": "tsx scripts/render-globe.ts",
|
|
87
|
-
"smoke": "bash scripts/smoke-test.sh"
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
+
]
|
|
91
|
+
}
|
package/dist/init-C_I6rfNw.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import{readConfig as e,updateConfig as t}from"./config--JRu_jFq.js";import{ApiClient as n}from"./api-FCanTxsb.js";import{readToken as r}from"./token-store-BQuGH0z2.js";import{emit$1 as i,isHeadless as a}from"./emit-C9vAopcy.js";import{scrubSecrets as o}from"./secrets-DPf_7SkN.js";import{parseSseStream as s}from"./client-Dm8uGVCy.js";import{loginCommand as c}from"./login-DuKqHRot.js";import{Panel as l,useTheme as u}from"./panel-B-XxdP-N.js";import{Banner as d}from"./banner-DdgwXGzt.js";import{DiffView as f,ProgressRow as p}from"./progress-row-5ns2IsFh.js";import{Select as m}from"./prompt-B4s3o9G7.js";import{Keybar as h}from"./keybar-QgnmSion.js";import{spark as g}from"./sparkline-CpoBjjlc.js";import _ from"node:path";import{promises as v}from"node:fs";import{z as y}from"zod";import b from"node:os";import x from"node:crypto";import{ulid as S}from"ulid";import{Box as C,Text as w,render as T,useApp as ee,useInput as E}from"ink";import D,{useEffect as O,useRef as k,useState as A}from"react";import{globby as j}from"globby";import M from"ink-spinner";import{applyPatch as te}from"diff";const N=[`@zenovay/tracker`,`@zenovay/next`,`@zenovay/nuxt`,`@zenovay/svelte`,`@zenovay/astro`,`@zenovay/remix`,`@zenovay/node`],P=[`zenovay.config.ts`,`zenovay.config.js`,`zenovay.config.mjs`,`zenovay.config.json`],F=/<script[^>]*src\s*=\s*["']https:\/\/(?:api|cdn)\.zenovay\.com\/[^"']+["'][^>]*>\s*<\/script>/gi,ne=/data-id\s*=\s*["'](ZV_[A-Za-z0-9]+)["']/i,re=/data-zenovay-site\s*=\s*["']([^"']+)["']/i,I=/\binit\s*\(\s*['"`](ZV_[A-Za-z0-9]+)['"`]/g,L=[{name:`PostHog`,patterns:[/posthog\.com\b/i,/posthog-js/,/window\.posthog/]},{name:`Plausible`,patterns:[/plausible\.io\/js/i,/window\.plausible/]},{name:`Google Analytics`,patterns:[/googletagmanager\.com\/gtag\/js/i,/www\.google-analytics\.com\/analytics\.js/i,/window\.gtag\b/]},{name:`Mixpanel`,patterns:[/cdn\.mxpnl\.com/i,/mixpanel-browser/]},{name:`Segment`,patterns:[/cdn\.segment\.com\/analytics\.js/i,/window\.analytics\b/]},{name:`Hotjar`,patterns:[/static\.hotjar\.com/i,/\(h,o,t,j,a,r\)/i]},{name:`Amplitude`,patterns:[/cdn\.amplitude\.com/i,/amplitude\.getInstance/]},{name:`Heap`,patterns:[/cdn\.heapanalytics\.com/i]},{name:`FullStory`,patterns:[/fullstory\.com\/s\/fs\.js/i,/window\.FS\.identify/]}];async function R(e,t){let n={zenovayInstalled:!1,correctTrackingCode:!1,foundTrackingCodes:[],zenovayFiles:[],competitors:[],summary:``,recommendation:`fresh-install`};try{let t=await v.readFile(_.join(e,`package.json`),`utf8`),r=JSON.parse(t),i={...r.dependencies,...r.devDependencies};for(let e of N)if(e in i){n.zenovayInstalled=!0,n.packageMarker=e;break}}catch{}for(let t of P)try{await v.access(_.join(e,t)),n.zenovayInstalled=!0,n.configFile=t;break}catch{}let r=await j([`**/*.{html,htm,tsx,jsx,vue,svelte,astro,ts,js,mjs}`,`!node_modules/**`,`!.git/**`,`!dist/**`,`!.next/**`,`!.nuxt/**`,`!build/**`,`!.output/**`],{cwd:e,gitignore:!0,deep:4,absolute:!0}).catch(()=>[]),i=new Map,a=new Set;for(let t of r.slice(0,300)){let r;try{r=await v.readFile(t,`utf8`)}catch{continue}let o=_.relative(e,t),s=r.match(F);if(s&&s.length>0){n.zenovayInstalled=!0;let e=s[0].slice(0,200),t=e.match(ne)??e.match(re),r=t?.[1]??null;r&&a.add(r),n.zenovayFiles.push({file:o,currentTrackingCode:r,scriptTagCount:s.length,matchSample:e})}if(!s&&/\.[jt]sx?$/.test(t)){I.lastIndex=0;let e;for(;(e=I.exec(r))!==null;){let t=e[1];a.add(t),n.zenovayInstalled=!0,n.zenovayFiles.push({file:o,currentTrackingCode:t,scriptTagCount:0,matchSample:e[0].slice(0,80)})}}for(let e of L)if(e.patterns.some(e=>e.test(r))){let t=i.get(e.name)??new Set;t.add(o),i.set(e.name,t)}}if(n.foundTrackingCodes=Array.from(a),n.competitors=Array.from(i.entries()).map(([e,t])=>({name:e,files:Array.from(t).slice(0,5)})),n.zenovayInstalled)if(n.foundTrackingCodes.length>0){let e=n.foundTrackingCodes.every(e=>e===t);n.correctTrackingCode=e,n.recommendation=e?`no-op`:`update-tracking-code`}else n.correctTrackingCode=!1,n.recommendation=`no-op`;else n.competitors.length>0?n.recommendation=`fresh-install-alongside-competitor`:n.recommendation=`fresh-install`;return n.summary=z(n,t),n}function z(e,t){let n=[];return e.zenovayInstalled?e.foundTrackingCodes.length===0?n.push(`Zenovay package or config detected but no <script> tag found.`):e.correctTrackingCode?n.push(`Zenovay tracker present in ${e.zenovayFiles.length} file(s) with correct ID ${t}.`):n.push(`Zenovay tracker present in ${e.zenovayFiles.length} file(s), but with mismatching ID(s): ${e.foundTrackingCodes.join(`, `)} (target: ${t}).`):n.push(`No Zenovay tracker found.`),e.competitors.length>0&&n.push(`Competitor tracker(s) present (DO NOT modify): ${e.competitors.map(e=>e.name).join(`, `)}.`),n.join(` `)}async function B(e){let t=await R(e,``);return{installed:t.zenovayInstalled,reasons:t.summary?[t.summary]:[],packageMarker:t.packageMarker,configFile:t.configFile,scriptFile:t.zenovayFiles[0]?.file}}const ie=[/^package\.json$/,/^package-lock\.json$/,/^pnpm-lock\.yaml$/,/^yarn\.lock$/,/^bun\.lock$/,/^tsconfig.*\.json$/,/^(next|nuxt|vite|astro|remix|svelte|tailwind|postcss)\.config\.(ts|mts|cts|js|mjs|cjs)$/,/^app\/.*\.(tsx|ts|jsx|js|svelte|astro|vue)$/,/^pages\/.*\.(tsx|ts|jsx|js)$/,/^public\/index\.html$/,/^index\.html$/,/^src\/main\.(tsx|ts|jsx|js)$/,/^src\/App\.(tsx|ts|jsx|js|svelte|vue)$/,/^src\/routes\/\+layout\.(svelte|ts|js)$/,/^src\/routes\/root\..*$/,/^src\/layouts\/.*\.(astro|svelte|vue)$/,/^\.env\.example$/,/^README\.md$/,/^\.gitignore$/,/^nuxt\.config\.(ts|js|mjs)$/,/^plugins\/.*\.(ts|js)$/],ae=[/(^|\/)\.env($|\.(?!example$)[^/]+$)/,/(^|\/)\.git(\/|$)/,/(^|\/)node_modules\//,/(^|\/)\.ssh\//,/(^|\/)\.aws\//,/\.pem$/,/\.key$/,/(^|\/)id_rsa($|\.)/,/(^|\/)\.npmrc$/,/(^|\/)credentials(\.|$)/],V=256*1024,H=50,U=20,W=40;var G=class extends Error{constructor(e,t){super(t),this.code=e,this.name=`SandboxError`}};const K=/<script[^>]*src\s*=\s*["']https:\/\/(?:api|cdn)\.zenovay\.com\/[^"']+["'][^>]*>\s*<\/script>/i,oe=[{name:`PostHog`,re:/posthog\.com\b|posthog-js|window\.posthog/i},{name:`Plausible`,re:/plausible\.io\/js|window\.plausible/i},{name:`Google Analytics`,re:/googletagmanager\.com\/gtag\/js|google-analytics\.com\/analytics\.js|window\.gtag\b/i},{name:`Mixpanel`,re:/cdn\.mxpnl\.com|mixpanel-browser/i},{name:`Segment`,re:/cdn\.segment\.com\/analytics\.js|window\.analytics\b/i},{name:`Hotjar`,re:/static\.hotjar\.com|\(h,o,t,j,a,r\)/i},{name:`Amplitude`,re:/cdn\.amplitude\.com|amplitude\.getInstance/i},{name:`Heap`,re:/cdn\.heapanalytics\.com/i},{name:`FullStory`,re:/fullstory\.com\/s\/fs\.js|window\.FS\.identify/i}];var q=class{sessionId;cwd;cwdReal;undoRoot;readCount=0;writeCount=0;roundCount=0;writtenPathsSet=new Set;constructor(e){this.sessionId=e.sessionId??S(),this.cwd=e.cwd,this.cwdReal=v.realpath(e.cwd),this.undoRoot=e.undoRoot??_.join(b.homedir(),`.zenovay`,`undo`)}incrementRound(){if(this.roundCount++,this.roundCount>W)throw new G(`round-cap`,`Tool-call recursion cap (${W}) exceeded.`)}stats(){return{sessionId:this.sessionId,reads:this.readCount,writes:this.writeCount,rounds:this.roundCount,writtenPaths:[...this.writtenPathsSet]}}async resolveForRead(e){return this.resolveSafe(e,`read`)}async resolveForWrite(e){return this.resolveSafe(e,`write`)}async resolveSafe(e,t){if(typeof e!=`string`||e.length===0)throw new G(`escape`,`Path must be a non-empty string.`);if(e.includes(`\0`))throw new G(`escape`,`Null byte in path.`);if(/^\\\\[?.]\\/.test(e)||/^\\\\[A-Za-z0-9]/.test(e))throw new G(`escape`,`Windows special path prefix: ${e}`);if(/%/.test(e))try{let t=decodeURIComponent(e);if(/%[0-9A-Fa-f]{2}/.test(t))throw new G(`escape`,`Double-encoded path: ${e}`);e=t}catch(t){throw t instanceof G?t:new G(`escape`,`Malformed percent-encoding: ${e}`)}if(e.includes(`\0`))throw new G(`escape`,`Null byte in decoded path.`);if(e.includes(`\\`))throw new G(`escape`,`Backslash in path (Windows-style separator not accepted): ${e}`);let n=await this.cwdReal,r=_.isAbsolute(e)?e:_.join(n,e),i=_.normalize(r),a=i;try{a=await v.realpath(i)}catch(e){if(e.code!==`ENOENT`)throw new G(`escape`,`realpath failed: ${e.message}`);let t=_.dirname(i);try{let e=await v.realpath(t);a=_.join(e,_.basename(i))}catch{}}if(!this.isWithin(a,n))throw new G(`escape`,`Path escapes cwd: ${e}`);let o=_.relative(n,a);for(let e of ae)if(e.test(o)){if(t===`read`&&/(^|\/)node_modules\//.test(o))continue;throw new G(`blocklisted`,`Path is on the hard blocklist: ${o}`)}if(t===`write`){let e=ie.some(e=>e.test(o));if(!e&&!/^\.env(\.local)?$/.test(o)&&!/^\.claude\//.test(o)&&!/^\.cursor\//.test(o))throw new G(`not-allowlisted`,`Write path not on allowlist: ${o}`)}return a}isWithin(e,t){let n=_.relative(t,e);return!n.startsWith(`..`)&&!_.isAbsolute(n)}async safeRead(e){if(this.readCount>=H)throw new G(`read-cap`,`Read cap (${H}) reached.`);let t=await this.resolveForRead(e),n=await v.stat(t);if(n.size>V)throw new G(`too-large`,`File exceeds ${V}B: ${e} (${n.size}B)`);let r=await v.readFile(t),i=r.subarray(0,Math.min(r.length,8192));if(i.includes(0))throw new G(`binary-rejected`,`Binary content in ${e}.`);return this.readCount++,r.toString(`utf8`)}async safeWrite(e,t){if(this.writeCount>=U)throw new G(`write-cap`,`Write cap (${U}) reached.`);if(t.length>V)throw new G(`too-large`,`Write exceeds ${V}B: ${e}`);let n=await this.resolveForWrite(e),r=await v.readFile(n,`utf8`).catch(e=>{if(e.code===`ENOENT`)return null;throw e});if(r!==null){if(K.test(r)&&!K.test(t))throw new G(`destructive-tracker-removal`,`Refusing to write ${e}: existing Zenovay tracker tag would be removed. If you want to change the data-id, use patch_file to swap the attribute value instead of overwriting the file.`);for(let{name:n,re:i}of oe)if(i.test(r)&&!i.test(t))throw new G(`destructive-competitor-removal`,`Refusing to write ${e}: existing ${n} tracker would be removed. Zenovay installs alongside competitors — never replace them.`)}let i=await this.journalBackup(n);await v.mkdir(_.dirname(n),{recursive:!0}),await v.writeFile(n,t,`utf8`),this.writeCount++;let a=await this.cwdReal;return this.writtenPathsSet.add(_.relative(a,n)),{backupPath:i}}async journalBackup(e){let t=await v.readFile(e).catch(e=>{if(e.code===`ENOENT`)return null;throw e}),n=_.join(this.undoRoot,this.sessionId);await v.mkdir(n,{recursive:!0,mode:448});let r=x.createHash(`sha256`).update(e).digest(`hex`).slice(0,16),i=_.join(n,`${r}.bak`),a=_.join(n,`manifest.jsonl`);t&&await v.writeFile(i,t,{mode:384});let o=JSON.stringify({abs:e,backupPath:t?i:null,ts:Date.now()});return await v.appendFile(a,o+`
|
|
2
|
-
`,{mode:384}),t?i:null}static wrapForModel(e,t){let n=x.createHash(`sha256`).update(t).digest(`hex`).slice(0,16),r=t.replaceAll(`&`,`&`).replaceAll(`<`,`<`).replaceAll(`>`,`>`);return`<FILE path="${e}" sha256="${n}">\n${r}\n</FILE>`}static scrubSecrets(e){return o(e)}};const se=[`astro.config.ts`,`astro.config.mjs`,`astro.config.js`];async function ce(e){for(let t of se)if(await e.exists(t))return{matched:!0,confidence:.95,reason:`Detected Astro via \`${t}\`.`};let t=await e.readFile(`package.json`);if(t)try{let e=JSON.parse(t);if(e.dependencies?.astro??e.devDependencies?.astro)return{matched:!0,confidence:.8,reason:"Detected Astro via `astro` dependency."}}catch{}return{matched:!1,confidence:0,reason:"No astro.config.* or `astro` dependency found."}}function le(e){let t=e.trackingCode??e.siteId??``,n=`<script defer data-id="${t}" data-allow-localhost="true" src="https://api.zenovay.com/z.js" is:inline></script>`;return[{targetPath:`src/layouts/Layout.astro`,anchorKind:`head-tag`,code:n,description:`Inject the Zenovay tracker inside <head> of the primary layout.`},{targetPath:`src/layouts/BaseHead.astro`,anchorKind:`head-tag`,code:n,description:"Fallback target when the project uses a dedicated `BaseHead.astro`."}]}const ue={id:`astro`,displayName:`Astro`,detect:ce,files:[{path:`src/layouts/Layout.astro`,kind:`mutate`,required:!1},{path:`src/layouts/BaseHead.astro`,kind:`mutate`,required:!1},{path:`.env`,kind:`create`,required:!0}],envVarName:`PUBLIC_ZENOVAY_SITE`,buildSnippets:le},de=[`app/layout.tsx`,`app/layout.ts`,`app/layout.jsx`,`app/layout.js`];async function fe(e){for(let t of de)if(await e.exists(t))return t;return null}async function pe(e){let t=await e.readFile(`package.json`);if(!t)return!1;try{let e=JSON.parse(t);return!!(e.dependencies?.next??e.devDependencies?.next)}catch{return!1}}async function me(e){let t=await pe(e),n=await e.exists(`app`),r=await fe(e);return t?n?r?{matched:!0,confidence:.95,reason:`Detected Next.js App Router via \`next\` dep + \`${r}\`.`}:{matched:!1,confidence:.4,reason:"Found `app/` but no root layout file (layout.tsx/ts/jsx/js)."}:{matched:!1,confidence:0,reason:"No `app/` directory found — not an App Router project."}:{matched:!1,confidence:0,reason:"package.json does not list `next` as a dependency."}}function he(e,t=`script`){let n=`app/layout.tsx`,r=e.trackingCode??e.siteId??``;return t===`npm`?_e(n,r):ge(n,r)}function ge(e,t){return[{targetPath:e,anchorKind:`after-imports`,code:`import Script from 'next/script';`,description:`Import next/script to inject the Zenovay tracker.`},{targetPath:e,anchorKind:`wrap-children`,code:`<Script
|
|
3
|
-
defer
|
|
4
|
-
data-id="${t}"
|
|
5
|
-
data-allow-localhost="true"
|
|
6
|
-
src="https://api.zenovay.com/z.js"
|
|
7
|
-
strategy="afterInteractive"
|
|
8
|
-
/>
|
|
9
|
-
{children}`,description:`Add the Zenovay tracker <Script> next to children in the root layout.`}]}function _e(e,t){return[{targetPath:`components/zenovay-bootstrap.tsx`,anchorKind:`replace-file`,code:`'use client';
|
|
10
|
-
|
|
11
|
-
import { useEffect } from 'react';
|
|
12
|
-
import { init } from '@zenovay/tracker';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Mounts the Zenovay tracker exactly once on the client. Equivalent to the
|
|
16
|
-
* canonical <script src="https://api.zenovay.com/z.js"> tag — init() injects
|
|
17
|
-
* the same loader internally.
|
|
18
|
-
*/
|
|
19
|
-
export function ZenovayBootstrap() {
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
init('${t}', { allowLocalhost: true });
|
|
22
|
-
}, []);
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
`,description:`Create a client-component bootstrap that calls init() from @zenovay/tracker.`},{targetPath:e,anchorKind:`after-imports`,code:`import { ZenovayBootstrap } from '@/components/zenovay-bootstrap';`,description:`Import the Zenovay tracker bootstrap into the root layout.`},{targetPath:e,anchorKind:`wrap-children`,code:`<ZenovayBootstrap />
|
|
26
|
-
{children}`,description:`Render the Zenovay tracker bootstrap alongside children in the root layout.`}]}const ve={id:`next-app-router`,displayName:`Next.js (App Router)`,detect:me,files:[{path:`app/layout.tsx`,kind:`mutate`,required:!0}],envVarName:`NEXT_PUBLIC_ZENOVAY_SITE`,packageDeps:[`@zenovay/tracker`],supportsNpmMode:!0,buildSnippets:he},ye=[`pages/_app.tsx`,`pages/_app.ts`,`pages/_app.jsx`,`pages/_app.js`,`src/pages/_app.tsx`,`src/pages/_app.ts`,`src/pages/_app.jsx`,`src/pages/_app.js`];async function be(e){for(let t of ye)if(await e.exists(t))return t;return null}async function xe(e){let t=await e.readFile(`package.json`);if(!t)return!1;try{let e=JSON.parse(t);return!!(e.dependencies?.next??e.devDependencies?.next)}catch{return!1}}async function Se(e){let t=await xe(e);if(!t)return{matched:!1,confidence:0,reason:"package.json does not list `next` as a dependency."};let n=await be(e);if(n)return{matched:!0,confidence:.9,reason:`Detected Next.js Pages Router via \`${n}\`.`};let r=await e.exists(`app`),i=await e.glob(`pages/**/*.{tsx,ts,jsx,js}`);return i.length>0&&!r?{matched:!0,confidence:.7,reason:"Found `pages/` directory with route files but no `_app` — will create one."}:{matched:!1,confidence:.2,reason:`Next.js detected, but no pages-router entry point found.`}}function Ce(e){let t=`pages/_app.tsx`,n=e.trackingCode??e.siteId??``,r=`import type { AppProps } from 'next/app';
|
|
27
|
-
import Script from 'next/script';
|
|
28
|
-
|
|
29
|
-
export default function App({ Component, pageProps }: AppProps) {
|
|
30
|
-
return (
|
|
31
|
-
<>
|
|
32
|
-
<Script
|
|
33
|
-
defer
|
|
34
|
-
data-id="${n}"
|
|
35
|
-
data-allow-localhost="true"
|
|
36
|
-
src="https://api.zenovay.com/z.js"
|
|
37
|
-
strategy="afterInteractive"
|
|
38
|
-
/>
|
|
39
|
-
<Component {...pageProps} />
|
|
40
|
-
</>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
`;return[{targetPath:t,anchorKind:`after-imports`,code:`import Script from 'next/script';`,description:"Import next/script in `_app`."},{targetPath:t,anchorKind:`wrap-children`,code:`<><Script defer data-id="${n}" data-allow-localhost="true" src="https://api.zenovay.com/z.js" strategy="afterInteractive" /><Component {...pageProps} /></>`,description:`Render the Zenovay tracker alongside the page component.`},{targetPath:t,anchorKind:`replace-file`,code:r,description:"Full replacement when `_app` does not exist yet."}]}const we={id:`next-pages-router`,displayName:`Next.js (Pages Router)`,detect:Se,files:[{path:`pages/_app.tsx`,kind:`mutate`,required:!0}],envVarName:`NEXT_PUBLIC_ZENOVAY_SITE`,buildSnippets:Ce},Te=y.object({modules:y.array(y.string()).optional(),runtimeConfig:y.object({public:y.record(y.string(),y.unknown()).optional()}).optional()}).passthrough();async function Ee(e){let t=await e.readFile(`package.json`);if(!t)return{matched:!1,confidence:0,reason:`No package.json found.`};try{let e=JSON.parse(t),n=!!(e.dependencies?.nuxt??e.devDependencies?.nuxt??e.dependencies?.nuxt3??e.devDependencies?.nuxt3);if(n)return{matched:!0,confidence:.95,reason:"Detected Nuxt via `nuxt` / `nuxt3` dependency."}}catch{}let n=await e.exists(`nuxt.config.ts`)||await e.exists(`nuxt.config.js`)||await e.exists(`nuxt.config.mjs`);return n?{matched:!0,confidence:.6,reason:"Found `nuxt.config.*` but no explicit dependency listing."}:{matched:!1,confidence:0,reason:`No Nuxt dependency or config detected.`}}function De(e){let t=e.trackingCode??e.siteId??``,n=` app: {
|
|
44
|
-
head: {
|
|
45
|
-
script: [
|
|
46
|
-
{
|
|
47
|
-
defer: true,
|
|
48
|
-
src: 'https://api.zenovay.com/z.js',
|
|
49
|
-
'data-id': '${t}',
|
|
50
|
-
'data-allow-localhost': 'true',
|
|
51
|
-
},
|
|
52
|
-
],
|
|
53
|
-
},
|
|
54
|
-
},`;return[{targetPath:`nuxt.config.ts`,anchorKind:`head-tag`,code:n,description:"Add the Zenovay tracker to Nuxt's `app.head.script` so every page loads it."}]}const Oe={id:`nuxt`,displayName:`Nuxt`,detect:Ee,files:[{path:`nuxt.config.ts`,kind:`mutate`,required:!0}],envVarName:`NUXT_PUBLIC_ZENOVAY_SITE`,buildSnippets:De},ke=[`index.html`,`public/index.html`,`dist/index.html`];async function Ae(e){for(let t of ke)if(await e.exists(t))return{matched:!0,confidence:.5,reason:`Detected plain HTML project via \`${t}\`.`};let t=await e.glob(`**/*.html`);return t.length>0?{matched:!0,confidence:.3,reason:`Found ${t.length} HTML file(s); plain-HTML adapter is usable as fallback.`}:{matched:!1,confidence:0,reason:`No HTML files found in the project.`}}function je(e){let t=e.siteId??``,n=`<script defer\n data-id="${t}"\n data-allow-localhost="true"\n src="https://api.zenovay.com/z.js">\n</script>`;return[{targetPath:`index.html`,anchorKind:`head-tag`,code:n,description:`Inject the Zenovay tracker inside <head>. Repeat for every HTML file that contains a <head>.`}]}const Me={id:`plain-html`,displayName:`Plain HTML`,detect:Ae,files:[{path:`index.html`,kind:`mutate`,required:!0}],envVarName:`PUBLIC_ZENOVAY_SITE`,buildSnippets:je};async function Ne(e){let t=await e.readFile(`package.json`);if(t)try{let e=JSON.parse(t),n={...e.dependencies??{},...e.devDependencies??{}},r=Object.keys(n).some(e=>e.startsWith(`@remix-run/`));if(r)return{matched:!0,confidence:.95,reason:"Detected Remix via `@remix-run/*` dependencies."}}catch{}return await e.exists(`remix.config.js`)||await e.exists(`remix.config.ts`)?{matched:!0,confidence:.8,reason:"Detected Remix via `remix.config.*`."}:{matched:!1,confidence:0,reason:`No Remix dependency or config detected.`}}function Pe(e){let t=e.trackingCode??e.siteId??``;return[{targetPath:`app/root.tsx`,anchorKind:`head-tag`,code:`<script defer data-id="${t}" data-allow-localhost="true" src="https://api.zenovay.com/z.js"></script>`,description:`Inline the Zenovay tracker <script> inside the document <head>.`}]}const Fe={id:`remix`,displayName:`Remix`,detect:Ne,files:[{path:`app/root.tsx`,kind:`mutate`,required:!0}],envVarName:`PUBLIC_ZENOVAY_SITE`,buildSnippets:Pe};async function Ie(e){let t=await e.readFile(`package.json`);if(!t)return{matched:!1,confidence:0,reason:`No package.json found.`};try{let e=JSON.parse(t),n=!!(e.dependencies?.[`@sveltejs/kit`]??e.devDependencies?.[`@sveltejs/kit`]);return n?{matched:!0,confidence:.95,reason:"Detected SvelteKit via `@sveltejs/kit` dependency."}:{matched:!1,confidence:0,reason:"`@sveltejs/kit` not in dependencies."}}catch{return{matched:!1,confidence:0,reason:`package.json is not valid JSON.`}}}function Le(e){let t=e.trackingCode??e.siteId??``,n=`<svelte:head>
|
|
55
|
-
<script defer data-id="${t}" data-allow-localhost="true" src="https://api.zenovay.com/z.js"></script>
|
|
56
|
-
</svelte:head>
|
|
57
|
-
|
|
58
|
-
<slot />
|
|
59
|
-
`;return[{targetPath:`src/routes/+layout.svelte`,anchorKind:`replace-file`,code:n,description:`Root layout that loads the Zenovay tracker in every route via <svelte:head>.`}]}const Re={id:`sveltekit`,displayName:`SvelteKit`,detect:Ie,files:[{path:`src/routes/+layout.svelte`,kind:`mutate`,required:!0}],envVarName:`PUBLIC_ZENOVAY_SITE`,buildSnippets:Le},ze=[ve,we,Oe,Re,ue,Fe,Me];async function Be(e){let t=[];for(let n of ze){let r=await n.detect(e);t.push({adapter:n,result:r})}let n=[...t].sort((e,t)=>t.result.confidence-e.result.confidence),r=n[0],i=r&&r.result.matched?r.adapter:null;return{adapter:i,ranked:n}}function Ve(e){let t=t=>_.resolve(e,t);return{cwd:e,async readFile(e){try{return await v.readFile(t(e),`utf8`)}catch(e){let t=e.code;if(t===`ENOENT`||t===`EISDIR`)return null;throw e}},async exists(e){try{return await v.access(t(e)),!0}catch{return!1}},async glob(t){return j(t,{cwd:e,dot:!1,gitignore:!0,onlyFiles:!0,followSymbolicLinks:!1})}}}async function He(e,t){let n=Ve(e),{adapter:r,ranked:i}=await Be(n),a=i[0];return r&&a&&a.result.confidence>=.7?r:t?t.pickInteractively(i):r}function Ue(e,t){let n=/[\s"'#=]/.test(t)||t===``,r=n?`"${t.replace(/\\/g,`\\\\`).replace(/"/g,`\\"`)}"`:t;return`${e}=${r}`}function We(e){let t=new Set;for(let n of e.split(/\r?\n/)){let e=n.trim();if(!e||e.startsWith(`#`))continue;let r=e.indexOf(`=`);if(r<=0)continue;let i=e.slice(0,r).trim();/^[A-Za-z_][A-Za-z0-9_]*$/.test(i)&&t.add(i)}return t}async function J(e,t,n={}){let r=n.filename??`.env.local`,i=_.resolve(e,r),a=``,o=!0;try{a=await v.readFile(i,`utf8`)}catch(e){if(e.code===`ENOENT`)o=!1;else throw e}let s=o?We(a):new Set,c=[],l=[],u=[];for(let[e,n]of Object.entries(t)){if(s.has(e)){l.push(e);continue}u.push(Ue(e,n)),c.push(e)}if(c.length===0)return{created:!1,existing:o,addedKeys:c,skippedKeys:l};let d=`# Added by Zenovay CLI`,f;if(o){let e=a.length>0&&!a.endsWith(`
|
|
60
|
-
`);f=a+(e?`
|
|
61
|
-
`:``)+`
|
|
62
|
-
`+d+`
|
|
63
|
-
`+u.join(`
|
|
64
|
-
`)+`
|
|
65
|
-
`}else f=d+`
|
|
66
|
-
`+u.join(`
|
|
67
|
-
`)+`
|
|
68
|
-
`;if(o)await v.writeFile(i,f,`utf8`);else{let e=await v.open(i,`w`,384);try{await e.writeFile(f,`utf8`)}finally{await e.close()}}return{created:!o,existing:o,addedKeys:c,skippedKeys:l}}const Ge=`https://api.zenovay.com/mcp`,Ke=y.object({url:y.string().url(),headers:y.record(y.string(),y.string()).optional()}),qe=y.object({mcpServers:y.record(y.string(),Ke).optional()}).passthrough();function Je(e){return e.HOME??e.USERPROFILE??b.homedir()}function Ye(e){return e.APPDATA??null}function Xe(e,t){let n=Je(t),r=Ye(t),i=[];return i.push({id:`cursor`,displayName:`Cursor`,configPath:_.join(n,`.cursor`,`mcp.json`)}),e===`win32`&&r&&i.push({id:`cursor`,displayName:`Cursor (AppData)`,configPath:_.join(r,`Cursor`,`User`,`globalStorage`,`cursor.mcp`,`mcp.json`)}),e===`darwin`?i.push({id:`claude-desktop`,displayName:`Claude Desktop`,configPath:_.join(n,`Library`,`Application Support`,`Claude`,`claude_desktop_config.json`)}):e===`win32`&&r?i.push({id:`claude-desktop`,displayName:`Claude Desktop`,configPath:_.join(r,`Claude`,`claude_desktop_config.json`)}):i.push({id:`claude-desktop`,displayName:`Claude Desktop`,configPath:_.join(n,`.config`,`Claude`,`claude_desktop_config.json`)}),i.push({id:`vscode`,displayName:`VS Code`,configPath:_.join(n,`.vscode`,`mcp.json`)}),i}async function Ze(e){try{return await v.access(e),!0}catch{return!1}}async function Qe(e){try{let t=await v.readFile(e,`utf8`);if(t.trim().length===0)return{};let n=JSON.parse(t),r=qe.safeParse(n);return r.success?r.data:n&&typeof n==`object`?n:{}}catch{return{}}}function $e(e){return{url:Ge,headers:{Authorization:`Bearer ${e}`}}}async function Y(e){let t=e.env??process.env,n=e.platform??process.platform,r=new Set(e.skip??[]),i=[],a=[],o=[],s=new Set,c=$e(e.accessToken);for(let e of Xe(n,t)){if(r.has(e.id)){a.push(e.displayName);continue}if(s.has(e.configPath))continue;s.add(e.configPath);try{let t=await Ze(e.configPath);if(!t){a.push(e.displayName);continue}let n=await Qe(e.configPath),r=`${e.configPath}.bak`;await v.copyFile(e.configPath,r);let o={...n,mcpServers:{...n.mcpServers??{},zenovay:c}};await v.mkdir(_.dirname(e.configPath),{recursive:!0}),await v.writeFile(e.configPath,JSON.stringify(o,null,2)+`
|
|
69
|
-
`,`utf8`),i.push(e.displayName)}catch(t){o.push({client:e.displayName,message:t instanceof Error?t.message:String(t)})}}return{installed:i,skipped:a,errors:o}}async function et(e,t,n){let r=n.timeoutMs??18e4,i=n.pollIntervalMs??2e3,a=n.now??(()=>Date.now()),o=n.sleep??(e=>new Promise(t=>setTimeout(t,e))),s=a(),c=s+r;for(n.onTick?.(0);;){let l=await n.api.getFirstEvent({siteId:e,since:t});if(l.received)return{received:!0,event:l.event};let u=a()-s;if(u>=r)return{received:!1};let d=c-a(),f=Math.min(i,Math.max(0,d));if(f<=0)return{received:!1};await o(f),n.onTick?.(a()-s)}}const tt=y.object({path:y.string().min(1)}),nt=y.object({path:y.string().min(1),content:y.string(),reason:y.string().min(1)}),rt=y.object({path:y.string().min(1),patch:y.string().min(1),reason:y.string().min(1)}),it=y.object({}),at=y.object({question:y.string().min(1),choices:y.array(y.string()).optional()});async function ot(e,t){let n=tt.safeParse(t);if(!n.success)return{ok:!1,content:null,error:`invalid_args`};try{let t=await e.safeRead(n.data.path),r=q.scrubSecrets(t);return{ok:!0,content:q.wrapForModel(n.data.path,r)}}catch(e){return{ok:!1,content:null,error:e.message}}}async function st(e,t,n){let r=nt.safeParse(t);if(!r.success)return{ok:!1,content:null,error:`invalid_args`};let{path:i,content:a,reason:o}=r.data,s=``;try{s=await e.safeRead(i)}catch{s=``}let c=await n.approve({path:i,oldContent:s,newContent:a,reason:o});if(c===`n`)return{ok:!1,content:null,error:`skipped_by_user`};if(c===`e`)return{ok:!1,content:null,error:`deferred_to_user_edit`};try{let{backupPath:t}=await e.safeWrite(i,a);return{ok:!0,content:{path:i,backupPath:t}}}catch(e){return{ok:!1,content:null,error:e.message}}}async function ct(e,t,n){let r=rt.safeParse(t);if(!r.success)return{ok:!1,content:null,error:`invalid_args`};let{path:i,patch:a,reason:o}=r.data;if(/^\*\*\*\s+Begin Patch/m.test(a)||/^\*\*\*\s+Update File:/m.test(a))return{ok:!1,content:null,error:'unsupported_patch_format: This tool only accepts STANDARD unified diff with `--- a/...`, `+++ b/...`, and `@@ -N,M +N,M @@` headers. The "*** Begin Patch / *** Update File:" V4A format is NOT supported. Please call write_file with the full new file content instead.'};let s;try{s=await e.safeRead(i)}catch(e){return{ok:!1,content:null,error:e.message}}let c;try{c=te(s,a)}catch(e){return{ok:!1,content:null,error:`patch_failed: ${e.message}. Please call write_file with the full new file content instead — write_file is more reliable than patch_file for this use case.`}}return c===!1||typeof c!=`string`?{ok:!1,content:null,error:`patch_did_not_apply_cleanly. The patch context may not match the file. Please call write_file with the full new file content instead.`}:st(e,{path:i,content:c,reason:o},n)}async function lt(e){try{let t=Ve(e),{adapter:n,ranked:r}=await Be(t);return{ok:!0,content:{matched:n!==null,adapter:n?.id,displayName:n?.displayName,ranked:r.map(e=>({id:e.adapter.id,confidence:e.result.confidence,reason:e.result.reason}))}}}catch(e){return{ok:!1,content:null,error:e.message}}}async function ut(e,t){let n=at.safeParse(t);if(!n.success)return{ok:!1,content:null,error:`invalid_args`};try{let t=await e.ask(n.data);return{ok:!0,content:{answer:t}}}catch(e){return{ok:!1,content:null,error:e.message}}}async function dt(e){let{api:t,sandbox:n,approval:r,prompter:i,handlers:a}=e,o=[...e.initialMessages],c=``,l=0;for(;l<W;){n.incrementRound(),l++;let u=await t.openAiStream(`install`,{messages:o,cwd_fingerprint:n.sessionId,diagnosis:e.diagnosis,targetTrackingCode:e.targetTrackingCode}),d=[],f=``;for await(let e of s(u))switch(e.type){case`delta`:f+=e.content,a?.onDelta?.(e.content);break;case`tool_start`:a?.onToolStart?.({id:e.id,name:e.name});break;case`tool_call`:{let t=await ft(e.name,e.arguments,{sandbox:n,cwd:n.cwd,approval:r,prompter:i});d.push({id:e.id,name:e.name,arguments:e.arguments,result:t}),a?.onToolResult?.({id:e.id,name:e.name,result:t});break}case`usage`:a?.onUsage?.(e);break;case`error`:throw a?.onError?.(e),Error(`[${e.code}] ${e.message}`);case`done`:break}if(f&&(c+=f),d.length===0){f&&o.push({role:`assistant`,content:f});break}o.push({role:`assistant`,content:f,toolCalls:d.map(e=>({id:e.id,type:`function`,function:{name:e.name,arguments:JSON.stringify(e.arguments)}}))});for(let e of d)o.push({role:`tool`,toolCallId:e.id,toolName:e.name,content:JSON.stringify(e.result)})}return{finalText:c,rounds:l}}async function ft(e,t,n){try{switch(e){case`read_file`:return await ot(n.sandbox,t);case`write_file`:return await st(n.sandbox,t,n.approval);case`patch_file`:return await ct(n.sandbox,t,n.approval);case`detect_framework`:return await lt(n.cwd);case`ask_user`:return await ut(n.prompter,t);default:return{ok:!1,content:null,error:`unknown_tool:${e}`}}}catch(e){return{ok:!1,content:null,error:`tool_threw: ${e.message}`}}}const pt=e=>`<script defer\n data-id="${e}"\n data-allow-localhost="true"\n src="https://api.zenovay.com/z.js">\n</script>`;async function mt(e){let t={installed:[],skipped:[],errors:[]},n=await j([`**/*.html`,`!node_modules/**`,`!.git/**`,`!dist/**`,`!.next/**`,`!.nuxt/**`,`!build/**`,`!.output/**`],{cwd:e.cwd,gitignore:!0,absolute:!1});if(n.length===0)return t;let r=pt(e.trackingCode),i=RegExp(`data-id\\s*=\\s*["']${ht(e.trackingCode)}["']`);for(let a of n){let n=_.join(e.cwd,a),o;try{o=await v.readFile(n,`utf8`)}catch(e){t.errors.push({file:a,error:e.message});continue}if(K.test(o)&&i.test(o)){t.skipped.push({file:a,reason:`already installed with correct tracking code`});continue}let s;if(K.test(o))s=o.replace(/(<script[^>]*src\s*=\s*["']https:\/\/(?:api|cdn)\.zenovay\.com\/[^"']+["'][^>]*>)/i,t=>t.replace(/data-id\s*=\s*["']ZV_\w+["']/i,`data-id="${e.trackingCode}"`)),s===o&&(s=o.replace(/<script(\s)/i,`<script$1data-id="${e.trackingCode}"$1`));else if(e.updateOnly){t.skipped.push({file:a,reason:`updateOnly=true and no Zenovay tag present`});continue}else if(/<\/head>/i.test(o)){if(s=o.replace(/(\n[\t ]*)?<\/head>/i,(e,t)=>{let n=t??`
|
|
70
|
-
`,i=r.replace(/\n/g,`${n}`);return`${n}${i}${n}</head>`}),s===o){t.skipped.push({file:a,reason:`</head> regex did not match unexpectedly`});continue}}else{t.skipped.push({file:a,reason:`no <head>...</head> in file (fragment or non-page)`});continue}let c=null;for(let{name:e,re:t}of oe)if(t.test(o)&&!t.test(s)){c=e;break}if(c){t.errors.push({file:a,error:`would remove ${c} tracker — refusing to write`});continue}try{await v.writeFile(n,s,`utf8`),t.installed.push(a)}catch(e){t.errors.push({file:a,error:e.message})}}return t}function ht(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function gt(e){return e.yes||!process.stdout.isTTY?`auto`:`interactive`}function _t(e){let t=null,n=null,r=!1,i=!1,a=0,o=new Set,s=()=>{for(let e of o)e(t)};return{async approve(o){if(i)return`n`;if(e.mode===`auto`||r)return`y`;let c=String(++a);return new Promise(e=>{t={id:c,...o},n=t=>{t===`a`&&(r=!0),e(t)},s()})},subscribe(e){return o.add(e),()=>{o.delete(e)}},current(){return t},resolve(e,r){if(!t||t.id!==e||!n)return;let i=n;t=null,n=null,s(),i(r)},abort(){if(i=!0,n){let e=n;t=null,n=null,s(),e(`n`)}}}}function vt(){let e=[12,15,18,22,28,35,41,50,58,64,72,78],t=g(e,24);return[` ↑`,` ${t}`,` └────────────────────────`,` May Jun Jul Aug Sep`]}function yt(){return[` Landing page ████████████████████ 100%`,` ↓ -28%`,` Pricing ██████████████░░░░░░ 72%`,` ↓ -50%`,` Checkout ███████░░░░░░░░░░░░░ 36%`,` ↓ -64%`,` Purchased ███░░░░░░░░░░░░░░░░░ 13%`]}function bt(){return[` ▇▇▇▇▇▇▇▇▇▎ 89 Sarah K. /pricing 🇺🇸`,` ▇▇▇▇▇▇░░░░ 61 anon /docs 🇩🇪`,` ▇▇▇░░░░░░░ 34 anon /changelog 🇯🇵`]}function xt(){return[` LCP ● 2.1s good ████████████░░░░░░`,` INP ● 89ms good ██████████████████`,` CLS ⚠ 0.14 needs ████████████░░░░░░`,` TTFB ● 380ms good ███████████████░░░`]}const X=[{title:`Welcome`,body:[`The wizard is an agent — it handles the entire`,`Zenovay setup process on your behalf.`,``,`Reading your project, deciding where the tracker`,`belongs, and writing the snippet for you.`]},{title:`Events`,body:[`Events are the foundation of Zenovay analytics.`,``,`Every pageview, click, and custom action is an`,`event. Group them into Goals to track conversions.`]},{title:`Trends`,body:[`Use trends to measure growth.`,``,`See how visitors, signups, and revenue have`,`changed over the last week, month, or quarter.`],visual:vt},{title:`Funnels`,body:[`Funnels reveal where users drop off.`,``,`Define the steps you care about — landing,`,`pricing, checkout, purchase — and Zenovay shows`,`the per-step conversion rate.`],visual:yt},{title:`Live visitors`,body:[`Watch every visitor in real time, ranked by an`,`AI score that highlights high-intent traffic.`,``,"Use `zenovay visitors --live` after install."],visual:bt},{title:`Core Web Vitals`,body:[`LCP, INP, CLS — Google's page-experience metrics,`,`collected automatically once the tracker is in.`,``,"Use `zenovay vitals` to see them per page + device."],visual:xt},{title:`MCP integration`,body:[`Your CLI session can install Zenovay's MCP server`,`into Cursor / Claude Desktop / VS Code.`,``,`Then ask your AI: "what were my top pages today?"`,`and it queries Zenovay directly.`]},{title:`AI insights`,body:[`Zenovay watches your data and surfaces anomalies`,`automatically — traffic spikes, error bursts,`,`revenue drops — without you needing to ask.`,``,"Run `zenovay ai` after install to chat with them."]},{title:`Privacy-first`,body:[`No cookies required. No third-party trackers.`,`Cookieless mode is one toggle in your dashboard.`,``,`GDPR, CCPA, and PECR-compliant out of the box.`]}],St=8e3,Ct=({width:e,height:t})=>{let{theme:n}=u(),[r,i]=A(0);O(()=>{let e=setInterval(()=>{i(e=>(e+1)%X.length)},St);return()=>clearInterval(e)},[]);let a=X[r],o=a.visual?a.visual():[],s=X.map((e,t)=>t===r?`●`:`·`).join(` `);return D.createElement(C,{flexDirection:`column`,width:e,height:t,paddingX:2,paddingY:1},D.createElement(C,{marginBottom:1},D.createElement(w,{color:n.accent,bold:!0},`Learn`)),D.createElement(C,{marginBottom:1},D.createElement(w,{bold:!0,color:n.fg},a.title)),a.body.map((e,t)=>D.createElement(w,{key:t,color:n.muted},e||` `)),o.length>0&&D.createElement(C,{flexDirection:`column`,marginTop:1},o.map((e,t)=>D.createElement(w,{key:t,color:n.accent},e))),D.createElement(C,{flexGrow:1}),D.createElement(C,{marginTop:1},D.createElement(w,{color:n.dim,dimColor:!0},s,` · `,r+1,` / `,X.length)))},wt={pending:`·`,active:`⠋`,done:`✓`,error:`✗`};function Tt(e,t){switch(e){case`pending`:return t.dim;case`active`:return t.accent;case`done`:return t.success;case`error`:return t.error}}const Et=({tasks:e,width:t,height:n,liveLine:r})=>{let{theme:i}=u(),a=e.filter(e=>e.status===`done`).length;return D.createElement(C,{flexDirection:`column`,width:t,height:n,paddingX:2,paddingY:1},D.createElement(C,{marginBottom:1},D.createElement(w,{color:i.accent,bold:!0},`Tasks`)),e.map(e=>D.createElement(C,{key:e.id,flexDirection:`column`},D.createElement(C,null,e.status===`active`?D.createElement(w,{color:i.accent},D.createElement(M,{type:`dots`})):D.createElement(w,{color:Tt(e.status,i)},wt[e.status]),D.createElement(w,null,` `),D.createElement(w,{color:e.status===`pending`?i.muted:i.fg},e.label)),e.detail&&D.createElement(C,{paddingLeft:2},D.createElement(w,{color:i.dim,dimColor:!0},e.detail)))),D.createElement(C,{flexGrow:1}),r&&D.createElement(C,{marginTop:1},D.createElement(w,{color:i.accent},`◆ `),D.createElement(w,{color:i.fg},r)),D.createElement(C,{marginTop:1},D.createElement(w,{color:i.dim,dimColor:!0},a,` / `,e.length)))};async function Dt(e){if(a(e))return Ot(e);let t=`quit`,n=null,r=await new Promise(r=>{let{unmount:i,waitUntilExit:a}=T(D.createElement(kt,{opts:e,onExit:(e,a,o)=>{t=a??`quit`,n=o??null,i(),r(e)}}));a().catch(()=>r(1))});if(r!==0||t===`quit`||!n)return r;let i=n.id;if(t===`watch`){let{watchCommand:t}=await import(`./watch-Cv4FtwSJ.js`);return t({cliVersion:e.cliVersion,siteId:i})}if(t===`events`){let{eventsTailCommand:t}=await import(`./events-tail-DGRZewwc.js`);return t({cliVersion:e.cliVersion,siteId:i})}return 0}async function Ot(t){let a=await import(`./telemetry-C1045t6Q.js`).catch(()=>null);a?.emit(`install.start`,{properties:{mode:`headless`}});try{let o=await e(),s=await He(t.cwd);if(!s)return i({type:`error`,code:`no_framework`,message:`Could not detect framework (headless mode has no picker).`}),a?.emit(`install.error`,{error_code:`no_framework`,properties:{mode:`headless`}}),2;i({type:`framework.detected`,framework:s.id,confidence:1}),a?.emit(`install.framework_detected`,{properties:{framework:s.id}});let c=await r({strict:!1});if(!c&&!process.env.ZENOVAY_API_TOKEN)return i({type:`error`,code:`not_logged_in`,message:"Run `zenovay login` first (or set ZENOVAY_API_TOKEN)."}),2;let l=new n({config:o,cliVersion:t.cliVersion,token:c}),[u,d]=await Promise.all([l.getSites(),R(t.cwd,``).catch(()=>null)]),f;if(t.siteId){if(f=u.find(e=>e.id===t.siteId),!f)return i({type:`error`,code:`site_not_found`,message:`Site ${t.siteId} not found.`}),2}else if(d?.foundTrackingCodes.length){for(let e of d.foundTrackingCodes){let t=u.find(t=>t.trackingCode===e);if(t){f=t;break}}f||=u[0]}else f=u[0];if(!f)return i({type:`error`,code:`no_site`,message:`No sites exist; create one at app.zenovay.com or re-run interactively.`}),2;let p=f.trackingCode??f.id;if(d&&(d.recommendation===`no-op`||d.foundTrackingCodes.includes(p)))return i({type:`info`,message:`Zenovay already installed correctly for ${f.name} (${p})`}),i({type:`done`,command:`init`,exitCode:0}),0;i({type:`info`,message:`Using site ${f.id} (${f.name}) trackingCode=${f.trackingCode??f.id}`});let m=await J(t.cwd,{[s.envVarName]:f.trackingCode??f.id});return i({type:`info`,message:`env vars added: ${m.addedKeys.join(`,`)}`}),i({type:`done`,command:`init`,exitCode:0}),a?.emit(`install.success`,{properties:{framework:s.id,mode:`headless`,env_keys_added:m.addedKeys.length}}),0}catch(e){return i({type:`error`,code:`init_failed`,message:e.message}),a?.emit(`install.error`,{error_code:`init_failed`,properties:{mode:`headless`,error:(e.message??`unknown`).slice(0,128)}}),1}}const kt=({opts:t,onExit:i})=>{let{exit:a}=ee(),{theme:o}=u(),[s,f]=A(`intro`),[p,m]=A(null),[g,_]=A([]),[v,y]=A(null),[b,x]=A(null),[S,T]=A(null),[E,j]=A([]),[te,N]=A(void 0),[P,F]=A(null),[ne,re]=A(0),[I,L]=A(null),z=k(null),B=k(!1);return O(()=>(process.stdout.isTTY&&process.stdout.write(`\x1B[?1049h\x1B[H`),()=>{z.current?.abort(),process.stdout.isTTY&&process.stdout.write(`\x1B[?1049l`)}),[]),O(()=>{B.current||(B.current=!0,(async()=>{try{f(`detecting`);let i=await He(t.cwd);if(!i){T(`Could not detect your framework. Supported: Next.js, Nuxt, SvelteKit, Astro, Remix, plain HTML.`),f(`error`);return}m(i);let a=await r({strict:!1});!process.env.ZENOVAY_API_TOKEN&&!a&&(f(`login`),await c({cliVersion:t.cliVersion})),f(`loading-sites`);let o=await e(),s=await r({strict:!1}),l=new n({config:o,cliVersion:t.cliVersion,token:s}),[u,d]=await Promise.all([l.getSites(),R(t.cwd,``).catch(()=>null)]),p=await Wt(t.cwd);if(_(u),y(p),u.length===0){f(`create-site`);return}if(d?.foundTrackingCodes.length)for(let e of d.foundTrackingCodes){let t=u.find(t=>t.trackingCode===e);if(t){x(t),f(`already-installed`);return}}if(t.siteId){let e=u.find(e=>e.id===t.siteId);if(!e){T(`Site ${t.siteId} not found in your account.`),f(`error`);return}x(e),Z(e,i,t,{setPhase:f,setErrorMsg:T,setTasks:j,setLiveLine:N,setSandboxStats:F,setPendingApproval:L,registerBridge:e=>{z.current=e}});return}let h=Gt(u,p);if(t.yes&&h){x(h),Z(h,i,t,{setPhase:f,setErrorMsg:T,setTasks:j,setLiveLine:N,setSandboxStats:F,setPendingApproval:L,registerBridge:e=>{z.current=e}});return}f(`site-picker`)}catch(e){T(e.message),f(`error`)}})())},[]),O(()=>{s===`error`&&(z.current?.abort(),setTimeout(()=>{a(),i(1)},8e3))},[s]),D.createElement(C,{flexDirection:`column`},D.createElement(d,{version:t.cliVersion,subtitle:`AI install wizard`}),s===`intro`&&D.createElement(At,null),s===`detecting`&&D.createElement(l,{title:`Scanning`,state:`busy`},D.createElement(C,null,D.createElement(w,{color:o.accent},D.createElement(M,{type:`dots`})),D.createElement(w,null,` Detecting framework…`))),s===`login`&&D.createElement(l,{title:`Authenticating`,state:`busy`},D.createElement(w,null,`Opening browser for login… (paste ZENOVAY_API_TOKEN env var to skip)`)),s===`loading-sites`&&D.createElement(l,{title:`Loading your sites`,state:`busy`},D.createElement(C,null,D.createElement(w,{color:o.accent},D.createElement(M,{type:`dots`})),D.createElement(w,null,` Fetching your Zenovay sites…`))),s===`already-installed`&&b&&D.createElement(Mt,{site:b,onContinue:()=>{f(`site-picker`)},onExit:()=>{a(),i(0,`quit`,b)}}),s===`site-picker`&&p&&D.createElement(Nt,{sites:g,preselectHint:v,onPick:e=>{if(e===`create-new`){f(`create-site`);return}x(e),Z(e,p,t,{setPhase:f,setErrorMsg:T,setTasks:j,setLiveLine:N,setSandboxStats:F,setPendingApproval:L,registerBridge:e=>{z.current=e}})}}),s===`create-site`&&D.createElement(l,{title:`Create a new site`,state:`warn`},D.createElement(w,{color:o.warn},"No site picked (or none exist yet). Visit https://app.zenovay.com/websites to create one, then re-run `zenovay init`."),D.createElement(w,{color:o.muted},`(Inline site creation from the CLI is scheduled for V1.1 — it requires a plan-limit check.)`)),s===`agent`&&p&&b&&D.createElement(jt,{adapter:p,site:b,tasks:E,liveLine:te,pendingApproval:I,onApprove:e=>{I&&z.current?.resolve(I.id,e)}}),s===`install-complete`&&p&&b&&D.createElement(It,{adapter:p,site:b,stats:P,onCommit:()=>f(`commit-prompt`),onSkipCommit:()=>f(`deploy-prompt`)}),s===`commit-prompt`&&b&&D.createElement(Lt,{cwd:t.cwd,onDone:e=>{e.committed&&N(`Pushed ${e.shortSha} to ${e.branch}`),f(`deploy-prompt`)},onError:e=>{T(e),f(`error`)}}),s===`deploy-prompt`&&b&&D.createElement(Rt,{site:b,onDeployed:()=>f(`visit-prompt`),onSkip:()=>f(`verify`)}),s===`visit-prompt`&&b&&D.createElement(zt,{site:b,onContinue:()=>f(`verify`)}),s===`verify`&&b&&D.createElement(Vt,{site:b,elapsed:ne,onTick:e=>re(e),onArrived:()=>f(`success`),onTimeout:()=>f(`success`)}),s===`success`&&p&&b&&D.createElement(Ft,{adapter:p,site:b,stats:P,onPick:e=>{a(),i(0,e,b)}}),s===`error`&&D.createElement(l,{title:`Install failed`,state:`err`},D.createElement(w,{color:o.error},S??`Unknown error`)),D.createElement(h,{items:[{key:`q`,label:`quit`},{key:`ctrl-c`,label:`abort`}]}))};async function Z(i,a,o,s){try{s.setPhase(`agent`);let c=await e(),l=await r({strict:!1}),u=new n({config:c,cliVersion:o.cliVersion,token:l}),d=new q({cwd:o.cwd}),f=i.trackingCode??i.id;await t({defaultSiteId:i.id,defaultSiteName:i.name}).catch(()=>void 0);let p=await R(o.cwd,f);if(p.recommendation===`no-op`){s.setTasks(e=>e.map(e=>e.id===`detect`||e.id===`read`||e.id===`write`?{...e,status:`done`,detail:`already installed correctly`}:e)),s.setLiveLine(p.summary),s.setSandboxStats?.({reads:0,writes:0,rounds:0}),!o.skipMcp&&l&&await Y({accessToken:l.accessToken}).catch(()=>void 0),s.setPhase(`install-complete`);return}p.competitors.length>0&&s.setLiveLine(`Detected ${p.competitors.map(e=>e.name).join(`, `)} — will install Zenovay alongside (won't touch their tags).`);let m=a.id===`plain-html`&&(p.recommendation===`fresh-install`||p.recommendation===`fresh-install-alongside-competitor`||p.recommendation===`update-tracking-code`);if(m){s.setTasks(e=>e.map(e=>e.id===`detect`?{...e,status:`done`,detail:`Plain HTML`}:e)),s.setLiveLine(`Plain HTML — using fast install path (no AI needed)…`);let e=await mt({cwd:o.cwd,trackingCode:f,updateOnly:p.recommendation===`update-tracking-code`});if(e.installed.length===0&&e.errors.length===0)throw Error(`No HTML files were modified. Either no .html files contain a <head>...</head> block, or every file already has the correct tracker.`);if(e.installed.length===0&&e.errors.length>0)throw Error(`Programmatic install failed for all ${e.errors.length} file(s). First error: ${e.errors[0].file}: ${e.errors[0].error}`);s.setTasks(t=>t.map(t=>t.id===`read`||t.id===`write`?{...t,status:`done`,detail:`${e.installed.length} file(s)`}:t)),s.setLiveLine(`Tracker added to ${e.installed.length} file(s). Skipped ${e.skipped.length}, errored ${e.errors.length}.`);let t=await J(o.cwd,{[a.envVarName]:f});if(s.setTasks(e=>e.map(e=>e.id===`env`?{...e,status:`done`,detail:`${t.addedKeys.length} key(s) added`}:e)),!o.skipMcp&&l){let e=await Y({accessToken:l.accessToken}).catch(()=>null);s.setTasks(t=>t.map(t=>t.id===`mcp`?{...t,status:`done`,detail:e&&e.installed.length>0?e.installed.join(`, `):`none detected`}:t))}else s.setTasks(e=>e.map(e=>e.id===`mcp`?{...e,status:`done`,detail:`skipped`}:e));s.setLiveLine(void 0),s.setSandboxStats?.({reads:e.installed.length,writes:e.installed.length,rounds:0}),s.setPhase(`install-complete`);return}let h=a.buildSnippets({trackingCode:f,siteId:i.id}),g=`Install Zenovay tracking into this ${a.displayName} project.\nSite: ${i.name} (trackingCode: ${f}).\n1. Call detect_framework to confirm the framework.\n2. Read the entry file(s) for this framework:\n`+a.files.map(e=>` - ${e.path}`).join(`
|
|
71
|
-
`)+`
|
|
72
|
-
3. Insert the tracker using the snippet below.
|
|
73
|
-
4. Confirm the change with a diff before writing.
|
|
74
|
-
|
|
75
|
-
Snippet reference (anchored by anchorKind — adapt to the actual file):
|
|
76
|
-
`+h.map(e=>` ${e.anchorKind} @ ${e.targetPath}:\n ${e.code.replace(/\n/g,`
|
|
77
|
-
`)}`).join(`
|
|
78
|
-
|
|
79
|
-
`);s.setTasks([{id:`detect`,label:`Detect framework`,status:`active`},{id:`read`,label:`Read project entry files`,status:`pending`},{id:`write`,label:`Insert tracker snippet`,status:`pending`},{id:`env`,label:`Write env vars (.env.local)`,status:`pending`},{id:`mcp`,label:`Install MCP clients (Cursor/Claude/VS Code)`,status:`pending`},{id:`verify`,label:`Wait for first event`,status:`pending`}]);let _=(e,t,n)=>{s.setTasks(r=>r.map(r=>r.id===e?{...r,status:t,detail:n??r.detail}:r))},v=_t({mode:gt(o)});s.registerBridge(v);let y=v.subscribe(e=>s.setPendingApproval(e)),b=null;try{await dt({api:u,sandbox:d,approval:v,prompter:{async ask({question:e}){return e}},handlers:{onToolStart:({name:e})=>{e===`detect_framework`?(_(`detect`,`active`),s.setLiveLine(`Inspecting package.json + config files…`)):e===`read_file`?(_(`detect`,`done`),_(`read`,`active`)):(e===`write_file`||e===`patch_file`)&&(_(`read`,`done`),_(`write`,`active`))},onToolResult:({name:e,result:t})=>{let n=t?.ok!==!1,r=t?.content??{};e===`detect_framework`&&n&&(_(`detect`,`done`,a.displayName),s.setLiveLine(`Detected ${a.displayName}`)),e===`read_file`&&n&&r.path&&s.setLiveLine(`Read ${r.path}`),(e===`write_file`||e===`patch_file`)&&n&&r.path&&s.setLiveLine(`Wrote ${r.path}`)},onDelta:e=>{let t=e.replace(/\s+/g,` `).trim();t.length>4&&s.setLiveLine(t.slice(0,80))}},initialMessages:[{role:`user`,content:g}],diagnosis:p,targetTrackingCode:f})}catch(e){b=e}finally{y(),s.setPendingApproval(null)}let x=d.stats();if(x.writes===0){let e=b?`Agent error: ${b.message}`:`Agent returned without writing any files (${x.reads} reads, ${x.rounds} rounds). The model may have refused or the AI Gateway may be misconfigured. Check api.zenovay.com logs.`;throw Error(e)}let S=a.files.map(e=>e.path),C=x.writtenPaths.some(e=>S.some(t=>e===t||e.endsWith(`/${t}`)));if(!C)throw Error(`Agent wrote to: [${x.writtenPaths.join(`, `)}] but never modified the framework's tracker target file(s): [${S.join(`, `)}]. The tracker is NOT installed. The model likely put the trackingCode into .env.local instead of into the HTML/JSX entry file.`);_(`write`,`done`),_(`env`,`active`),s.setLiveLine(`Adding env var to .env.local`);let w=await J(o.cwd,{[a.envVarName]:f});if(_(`env`,`done`,`${w.addedKeys.length} key(s) added`),!o.skipMcp&&l){_(`mcp`,`active`),s.setLiveLine(`Detecting Cursor / Claude Desktop / VS Code…`);let e=await Y({accessToken:l.accessToken}).catch(()=>null);e&&e.installed.length>0?_(`mcp`,`done`,e.installed.join(`, `)):_(`mcp`,`done`,`none detected`)}else _(`mcp`,`done`,`skipped`);s.setTasks(e=>e.map(e=>e.id===`detect`||e.id===`read`||e.id===`write`?{...e,status:e.status===`pending`?`done`:e.status}:e)),s.setLiveLine(void 0),s.setSandboxStats?.(x),s.setPhase(`install-complete`)}catch(e){s.setErrorMsg(e.message),s.setPhase(`error`)}}const At=()=>{let{theme:e}=u();return D.createElement(l,{title:`Zenovay wants to set up your project`,state:`idle`},D.createElement(w,null,`We'll use AI to analyze your codebase and add the Zenovay tracker.`),D.createElement(w,{color:e.muted},`.env* contents stay on your machine. Only framework metadata reaches api.zenovay.com.`))},jt=({adapter:e,site:t,tasks:n,liveLine:r,pendingApproval:i,onApprove:a})=>{let{theme:o}=u(),s=process.stdout.columns??100,c=(process.stdout.rows??30)-8,l=Math.floor(s/2)-1,d=Math.max(14,c);return D.createElement(C,{flexDirection:`column`},D.createElement(C,{flexDirection:`row`,minHeight:d,borderStyle:`round`,borderColor:o.border},D.createElement(C,{width:l,flexDirection:`column`},D.createElement(Ct,{width:l,height:d})),D.createElement(C,{width:1,flexDirection:`column`},D.createElement(w,{color:o.border},`│`)),D.createElement(C,{width:l,flexDirection:`column`},i?D.createElement(C,{flexDirection:`column`,paddingX:2,paddingTop:1},D.createElement(w,{color:o.warn},`The agent wants to write `,i.path),i.reason?D.createElement(w,{color:o.muted},i.reason):null,D.createElement(f,{key:i.id,filePath:i.path,oldContent:i.oldContent,newContent:i.newContent,onApprove:a})):D.createElement(D.Fragment,null,D.createElement(C,{paddingX:2,paddingTop:1},D.createElement(p,{done:n.filter(e=>e.status===`done`).length,total:n.length,label:n.find(e=>e.status===`active`)?.label})),D.createElement(Et,{tasks:n,liveLine:r,width:l,height:d})))),D.createElement(C,{marginTop:1},D.createElement(w,{color:o.muted},`Site: `,t.name,` · code: `,t.trackingCode??t.id,` · Framework: `,e.displayName)))},Mt=({site:e,onContinue:t,onExit:n})=>{let{theme:r}=u();E((e,r)=>{if(e===`q`||r.escape){n();return}if(e===`r`||e===`R`){t();return}});let i=e.url.replace(/^https?:\/\//,``).replace(/\/$/,``);return D.createElement(l,{title:`Already installed`,state:`ok`},D.createElement(C,{flexDirection:`column`,gap:1},D.createElement(C,{flexDirection:`row`,gap:1},D.createElement(w,{color:r.success},`✓`),D.createElement(w,{bold:!0},e.name),D.createElement(w,{color:r.muted},`(`,i,`)`)),D.createElement(w,{color:r.muted},`Zenovay is already tracking this project. No changes needed.`),e.trackingCode?D.createElement(w,{color:r.dim},`tracking code: `,e.trackingCode):null,D.createElement(C,{marginTop:1},D.createElement(w,{color:r.muted},`[`),D.createElement(w,{color:r.accent},`q`),D.createElement(w,{color:r.muted},` quit] [`),D.createElement(w,{color:r.accent},`r`),D.createElement(w,{color:r.muted},` re-run wizard anyway]`))))},Nt=({sites:e,preselectHint:t,onPick:n})=>{let r=Gt(e,t),i=e.filter(e=>e.id!==r?.id),a=r?[r,...i]:i,o=[...a.map(e=>({label:Ut(e,e.id===r?.id),value:e,description:e.trackingCode?`trackingCode: ${e.trackingCode}`:void 0})),{label:`+ Create new site on app.zenovay.com`,value:`create-new`,description:`Open the dashboard to add a new website`}];return D.createElement(l,{title:t?`Pick a site (best match: ${t})`:`Pick a site`,state:`idle`},D.createElement(m,{items:o,onSelect:n}))};function Pt(){let e=process.argv[1]??``;return e.includes(`/_npx/`)||e.includes(`\\_npx\\`)||process.env.npm_execpath?.includes(`npx`)?`npx`:`global`}const Ft=({adapter:e,site:t,stats:n,onPick:r})=>{let{theme:i}=u(),a=Pt(),o=a===`npx`?`npx @zenovay/cli`:`zenovay`;return E((e,t)=>{let n=e.toLowerCase();n===`w`||t.return?r(`watch`):n===`e`?r(`events`):(n===`s`||n===`q`||t.escape)&&r(`quit`)},{isActive:!0}),D.createElement(l,{title:`You're live on Zenovay`,state:`ok`},D.createElement(w,{color:i.success,bold:!0},`✔ Installed successfully`),D.createElement(C,{marginTop:1,flexDirection:`column`},D.createElement(w,null,`Framework: `,D.createElement(w,{color:i.accent},e.displayName)),D.createElement(w,null,`Site: `,D.createElement(w,{color:i.accent},t.name),` (`,t.url,`)`),t.trackingCode?D.createElement(w,{color:i.muted},`Tracker: `,t.trackingCode):null,n&&n.rounds>0?D.createElement(w,{color:i.muted},`Modified: `,n.writes,` file(s) across `,n.rounds,` agent round(s)`):n&&n.writes>0?D.createElement(w,{color:i.muted},`Modified: `,n.writes,` file(s) (programmatic install — no AI calls)`):null),D.createElement(C,{marginTop:1,flexDirection:`column`},D.createElement(w,{bold:!0},`What's next?`),D.createElement(w,null,` `),D.createElement(C,null,D.createElement(w,{color:i.accent},` [W] `),D.createElement(w,null,`Open the live dashboard `),D.createElement(w,{color:i.muted},`— real-time visitors, top pages, sources, vitals`)),D.createElement(C,null,D.createElement(w,{color:i.accent},` [E] `),D.createElement(w,null,`Stream events as they fire `),D.createElement(w,{color:i.muted},`— page views, custom events, in real time`)),D.createElement(C,null,D.createElement(w,{color:i.accent},` [S] `),D.createElement(w,null,`Exit to shell`))),a===`npx`?D.createElement(C,{marginTop:1,flexDirection:`column`,borderStyle:`single`,borderColor:i.warn,paddingX:1},D.createElement(w,{color:i.warn,bold:!0},`Heads up — you ran via npx`),D.createElement(w,null,D.createElement(w,null,`The `),D.createElement(w,{color:i.accent},`zenovay`),D.createElement(w,null,` command isn't on your PATH yet. After you exit, run any of:`)),D.createElement(w,null,` `),D.createElement(w,{color:i.accent},` npm i -g @zenovay/cli `),D.createElement(w,{color:i.muted},"→ then `zenovay watch` works directly"),D.createElement(w,{color:i.accent},` npx @zenovay/cli watch `),D.createElement(w,{color:i.muted},`→ no install, slower start`)):D.createElement(C,{marginTop:1},D.createElement(w,{color:i.muted},`From your shell: `),D.createElement(w,{color:i.accent},o,` watch`),D.createElement(w,{color:i.muted},` · `),D.createElement(w,{color:i.accent},o,` --help`)),D.createElement(C,{marginTop:1},D.createElement(w,{color:i.muted},`Dashboard URL: https://app.zenovay.com/sites/`,t.id)))},It=({adapter:e,site:t,stats:n,onCommit:r,onSkipCommit:i})=>{let{theme:a}=u();return Q(e=>{e===`y`||e===`Y`||e===`\r`?r():(e===`n`||e===`N`||e===`s`||e===`S`)&&i()}),D.createElement(l,{title:`Tracker installed`,state:`ok`},D.createElement(w,null,D.createElement(w,{color:a.success},`✔ `),`The Zenovay tracker is in your project.`),D.createElement(C,{marginTop:1},D.createElement(w,{color:a.muted},`Framework: `),D.createElement(w,null,e.displayName)),D.createElement(C,null,D.createElement(w,{color:a.muted},`Site: `),D.createElement(w,null,t.name,` (`,t.url,`)`)),n&&D.createElement(C,null,D.createElement(w,{color:a.muted},`Modified: `),D.createElement(w,null,n.writes,` file(s)`)),D.createElement(C,{marginTop:1},D.createElement(w,null,`Commit + push these changes to git? `),D.createElement(w,{color:a.accent},`[Y/n]`)))},Lt=({cwd:e,onDone:t,onError:n})=>{let{theme:r}=u(),[i,a]=A(`committing`),[o,s]=A(`Staging changes…`);return O(()=>{(async()=>{try{let n=await Ht(e,e=>s(e),e=>a(e));n.committed?s(`Pushed ${n.shortSha} to origin/${n.branch}`):s(`No changes to commit (already up-to-date).`),setTimeout(()=>t(n),800)}catch(e){n(e.message)}})()},[]),D.createElement(l,{title:`Committing to git`,state:`busy`},D.createElement(C,null,D.createElement(w,{color:r.accent},D.createElement(M,{type:`dots`})),D.createElement(w,null,` `,i===`pushing`?`Pushing to origin`:`Committing`,`…`)),D.createElement(C,{marginTop:1},D.createElement(w,{color:r.muted},o)))},Rt=({site:e,onDeployed:t,onSkip:n})=>{let{theme:r}=u();return Q(e=>{e===`y`||e===`Y`||e===`\r`||e===`d`||e===`D`?t():(e===`n`||e===`N`||e===`s`||e===`S`)&&n()}),D.createElement(l,{title:`Ready to deploy?`,state:`idle`},D.createElement(w,null,`The tracker is installed locally. To capture events you need it on the live site:`),D.createElement(w,null,` `),D.createElement(C,null,D.createElement(w,{color:r.accent},` → `),D.createElement(w,null,e.url)),D.createElement(w,null,` `),D.createElement(w,{color:r.muted},`Push the change through your normal deploy pipeline (Vercel auto-deploy, CI/CD, etc.).`),D.createElement(C,{marginTop:1},D.createElement(w,null,`Once it's deployed, press `),D.createElement(w,{color:r.accent},`[Y]`),D.createElement(w,null,` to verify the tracker is firing. `),D.createElement(w,{color:r.muted},`[s] skip verification`)))},zt=({site:e,onContinue:t})=>{let{theme:n}=u();return Q(()=>t()),D.createElement(l,{title:`Visit your site`,state:`idle`},D.createElement(w,null,`To send a test event, open any page on your site:`),D.createElement(w,null,` `),D.createElement(C,null,D.createElement(w,{color:n.accent},` → `),D.createElement(w,null,e.url)),D.createElement(w,null,` `),D.createElement(w,{color:n.muted},`The tracker fires automatically on page load. We'll verify the next event arrives.`),D.createElement(C,{marginTop:1},D.createElement(w,null,`Press `),D.createElement(w,{color:n.accent},`[any key]`),D.createElement(w,null,` when you've visited the page.`)))};async function Bt(e,t){try{let n=await fetch(e,{headers:{"User-Agent":`zenovay-cli/preflight`},signal:AbortSignal.timeout(8e3),redirect:`follow`});if(!n.ok)return{state:`tracker-missing`,httpStatus:n.status};let r=await n.text(),i=/<script[^>]*src\s*=\s*["']https:\/\/(?:api|cdn)\.zenovay\.com\/[^"']+["'][^>]*>\s*<\/script>/gi,a=/data-id\s*=\s*["'](ZV_[A-Za-z0-9]+)["']/i,o=r.match(i)??[];if(o.length===0)return{state:`tracker-missing`};let s=Array.from(new Set(o.map(e=>e.match(a)?.[1]).filter(e=>!!e)));return s.includes(t)?{state:`tracker-correct`}:{state:`tracker-wrong-id`,foundCodes:s}}catch(e){return{state:`tracker-missing`,networkError:e.message}}}const Vt=({site:t,elapsed:i,onTick:a,onArrived:o,onTimeout:s})=>{let{theme:c}=u(),[d,f]=A(`preflight`),[p,m]=A(``),[h,g]=A([]),_=k(!1),v=t.trackingCode??t.id,y=async()=>{f(`preflight`),m(`Fetching ${t.url}…`);let e=await Bt(t.url,v);if(!_.current){if(e.state===`tracker-correct`){f(`polling`),m(`Tracker detected on live site — waiting for first hit…`),b();return}if(e.state===`tracker-wrong-id`){f(`wrong-id-on-live`),g(e.foundCodes),m(`Live site has Zenovay but with id(s): ${e.foundCodes.join(`, `)}`);return}f(`awaiting-deploy`),m(e.networkError?`Couldn't reach ${t.url}: ${e.networkError}`:e.httpStatus?`${t.url} returned HTTP ${e.httpStatus}`:`No Zenovay tracker found on ${t.url}.`)}},b=async()=>{let i=Date.now()-10*60*1e3;try{let s=await e(),c=await r({strict:!1}),l=new n({config:s,cliVersion:`0`,token:c}),u=await et(t.id,i,{api:l,timeoutMs:6e4,pollIntervalMs:2500,onTick:e=>{_.current||a(e)}});if(_.current)return;u.received?(f(`success`),o()):f(`timeout`)}catch{_.current||f(`timeout`)}};O(()=>(_.current=!1,y(),()=>{_.current=!0}),[]),E(e=>{(d===`awaiting-deploy`||d===`wrong-id-on-live`||d===`timeout`)&&(e===`r`||e===`R`?y():(e===`s`||e===`S`)&&s())},{isActive:!0});let x=Math.floor(i/1e3);return d===`preflight`?D.createElement(l,{title:`Verifying`,state:`busy`},D.createElement(C,null,D.createElement(w,{color:c.accent},D.createElement(M,{type:`dots`})),D.createElement(w,null,` Checking your live site for the tracker…`)),D.createElement(C,{marginTop:1},D.createElement(w,{color:c.muted},p))):d===`awaiting-deploy`?D.createElement(l,{title:`Tracker not yet on your live site`,state:`warn`},D.createElement(w,{color:c.warn},p),D.createElement(C,{marginTop:1,flexDirection:`column`},D.createElement(w,null,`The tracker is in your local repo but hasn't been deployed yet.`),D.createElement(w,{color:c.muted},`Push your changes and wait for your deploy to finish (Vercel / Cloudflare Pages / CI), then retry.`)),D.createElement(C,{marginTop:1},D.createElement(w,null,`Press `),D.createElement(w,{color:c.accent},`[r]`),D.createElement(w,null,` to re-check, `),D.createElement(w,{color:c.accent},`[s]`),D.createElement(w,null,` to skip verification.`))):d===`wrong-id-on-live`?D.createElement(l,{title:`Live site has a different tracking code`,state:`warn`},D.createElement(w,{color:c.warn},`Expected: `,D.createElement(w,{color:c.accent},v),` `,`Found on live: `,D.createElement(w,{color:c.accent},h.join(`, `))),D.createElement(C,{marginTop:1,flexDirection:`column`},D.createElement(w,null,`Your local install uses a different ID than what's on `,t.url,`.`),D.createElement(w,{color:c.muted},`Deploy the new local version, then retry — or skip if the existing ID is intentional.`)),D.createElement(C,{marginTop:1},D.createElement(w,null,`Press `),D.createElement(w,{color:c.accent},`[r]`),D.createElement(w,null,` to re-check, `),D.createElement(w,{color:c.accent},`[s]`),D.createElement(w,null,` to skip.`))):d===`timeout`?D.createElement(l,{title:`No event arrived in 3 minutes`,state:`warn`},D.createElement(w,{color:c.warn},`The tracker is on your live site but no hits reached api.zenovay.com.`),D.createElement(C,{marginTop:1},D.createElement(w,{color:c.muted},`Try opening `,t.url,` in a fresh browser tab (no ad-blocker), then retry.`)),D.createElement(C,{marginTop:1},D.createElement(w,null,`Press `),D.createElement(w,{color:c.accent},`[r]`),D.createElement(w,null,` to re-check, `),D.createElement(w,{color:c.accent},`[s]`),D.createElement(w,null,` to finish without verification.`))):D.createElement(l,{title:`Waiting for first event`,state:`busy`},D.createElement(C,null,D.createElement(w,{color:c.accent},D.createElement(M,{type:`dots`})),D.createElement(w,null,` Polling api.zenovay.com for the first hit on `),D.createElement(w,{color:c.accent},t.url),D.createElement(w,null,` (`,x,`s elapsed)`)),D.createElement(C,{marginTop:1},D.createElement(w,{color:c.muted},`Reload the page if nothing arrives within ~30 seconds.`)))};function Q(e){E((t,n)=>{e(t,{return:n.return,escape:n.escape})},{isActive:!0})}async function Ht(e,t,n){let{execFile:r}=await import(`node:child_process`),{promisify:i}=await import(`node:util`),a=i(r),o=async(t,n)=>{let{stdout:r}=await a(t,n,{cwd:e,maxBuffer:4e6});return r.trim()};try{await o(`git`,[`rev-parse`,`--is-inside-work-tree`])}catch{return t(`Not a git repository — skipped.`),{committed:!1}}let s=await o(`git`,[`status`,`--porcelain`]);if(!s)return{committed:!1};t(`Staging tracker changes…`),await o(`git`,[`add`,`-A`]),t(`Creating commit…`),await o(`git`,[`commit`,`-m`,`chore(zenovay): install tracking script via @zenovay/cli`]);let c=await o(`git`,[`rev-parse`,`--short`,`HEAD`]),l=await o(`git`,[`rev-parse`,`--abbrev-ref`,`HEAD`]);n(`pushing`),t(`Pushing to origin/${l}…`);try{await o(`git`,[`push`,`origin`,l])}catch(e){return t(`Commit ${c} created locally; push failed (${e.message.slice(0,80)}). Push manually with: git push origin ${l}`),{committed:!0,shortSha:c,branch:l}}return n(`done`),{committed:!0,shortSha:c,branch:l}}function Ut(e,t){let n=`${e.name}`.padEnd(30,` `).slice(0,30),r=e.url.replace(/^https?:\/\//,``).padEnd(28,` `).slice(0,28),i=t?` ← best match`:``;return`${n} ${r}${i}`}async function Wt(e){try{let t=await v.readFile(_.join(e,`package.json`),`utf8`),n=JSON.parse(t);if(n.name)return String(n.name).replace(/^@[^/]+\//,``)}catch{}try{let t=await v.readFile(_.join(e,`.git`,`config`),`utf8`),n=t.match(/url\s*=\s*.*[/:]([^/]+?)(?:\.git)?\s*$/m);if(n&&n[1])return n[1]}catch{}return _.basename(e)}function Gt(e,t){if(!t||e.length===0)return e[0]??null;let n=$(t),r=e.find(e=>$(e.name)===n);if(r)return r;let i=e.find(e=>{let t=e.url.replace(/^https?:\/\//,``).replace(/\/.*$/,``),[r]=t.split(`.`);return $(r??``)===n});if(i)return i;let a=e.find(e=>{let t=$(e.name),r=$(e.url);return t.includes(n)||r.includes(n)||n.includes(t)||n.includes(r)});return a??e[0]??null}function $(e){return e.toLowerCase().replace(/[^a-z0-9]/g,``)}export{B as detectInstalled,Dt as initCommand};
|