@zenovay/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Zenovay CLI License — All Rights Reserved
2
+
3
+ Copyright (c) 2026 Zenovay GmbH
4
+
5
+ This source code and accompanying distribution (the "Software") are proprietary
6
+ to Zenovay GmbH. While compiled binaries of the Software may be distributed
7
+ publicly via npm and https://cli.zenovay.com, the source code is not open source
8
+ and is not licensed for redistribution, modification, or use outside the scope
9
+ of its intended end-user purpose.
10
+
11
+ Permission is granted, free of charge, to any person obtaining a compiled
12
+ binary of this Software, to USE the Software for the purpose of integrating
13
+ and interacting with the Zenovay platform (https://zenovay.com), subject to
14
+ the Zenovay Terms of Service.
15
+
16
+ No permission is granted to:
17
+ - Reverse-engineer, decompile, or disassemble the Software
18
+ - Redistribute modified versions of the Software
19
+ - Use the Software to access systems other than those operated by Zenovay GmbH
20
+ - Remove or alter any copyright, trademark, or attribution notices
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED. IN NO EVENT SHALL ZENOVAY GMBH BE LIABLE FOR ANY CLAIM, DAMAGES, OR
24
+ OTHER LIABILITY ARISING FROM THE USE OF THE SOFTWARE.
25
+
26
+ For licensing inquiries: legal@zenovay.com
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # Zenovay CLI
2
+
3
+ The Zenovay terminal — AI install wizard, live analytics, and full MCP integration.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ # one-liner (macOS, Linux, Windows via WSL/Git Bash)
9
+ curl -fsSL https://cli.zenovay.com/install.sh | sh
10
+
11
+ # Windows PowerShell 7+
12
+ iex ((iwr https://cli.zenovay.com/install.ps1).Content)
13
+
14
+ # npm
15
+ npm install -g @zenovay/cli
16
+
17
+ # one-shot install wizard
18
+ npx @zenovay/wizard
19
+ ```
20
+
21
+ ## Commands
22
+
23
+ ```
24
+ zenovay init # AI install wizard — adds tracking to your codebase
25
+ zenovay login # OAuth device flow — works over SSH, in IDE terminals, in CI
26
+ zenovay ai # 3-mode AI assistant (install / chat / MCP management)
27
+ zenovay watch # live TUI dashboard
28
+ zenovay events tail # stream every analytics event in real time
29
+ zenovay health # self-diagnostic
30
+ zenovay doctor # AI-assisted self-healing
31
+ zenovay update # manual update (auto-update runs implicitly)
32
+ ```
33
+
34
+ Every command supports `--json` / `--ndjson` for piping. Auto-detects `CI=true` and headless stdout.
35
+
36
+ ## Documentation
37
+
38
+ - CLI reference: https://docs.zenovay.com/cli
39
+ - Changelog: https://cli.zenovay.com/changelog
40
+ - Feedback: https://cli.zenovay.com/feedback
41
+
42
+ ## Status
43
+
44
+ **Private repository.** Compiled binaries are public; source is not.
45
+
46
+ Licensed under the Zenovay CLI License — see [LICENSE](./LICENSE).
@@ -0,0 +1,11 @@
1
+ import{emit as e,isHeadless as t,readConfig as n}from"./emit-CSYxxLsy.js";import{ApiClient as r,readToken as i}from"./api-CvIiXSON.js";import{parseSseStream as a}from"./client-ClN0aAAQ.js";import{Select as o,initCommand as s}from"./init-BSlCaa_B.js";import"./login-BMG01YNm.js";import{Panel as c}from"./panel-BIYvFJmj.js";import"./banner-BUAXN9wM.js";import"./keybar-WbLSMIEj.js";import l from"node:path";import{promises as u}from"node:fs";import d from"react";import{Box as f,Text as p,render as m}from"ink";import{globby as h}from"globby";const g=[`@zenovay/tracker`,`@zenovay/next`,`@zenovay/nuxt`,`@zenovay/svelte`,`@zenovay/astro`,`@zenovay/remix`,`@zenovay/node`],_=[`zenovay.config.ts`,`zenovay.config.js`,`zenovay.config.mjs`,`zenovay.config.json`],v=/api\.zenovay\.com\/z\.js|cdn\.zenovay\.com\/t\.js/,y=/data-zenovay-site|data-id\s*=\s*"ZV_/;async function b(e){let t=[],n={installed:!1,reasons:t};try{let r=await u.readFile(l.join(e,`package.json`),`utf8`),i=JSON.parse(r),a={...i.dependencies,...i.devDependencies};for(let e of g)if(e in a){t.push(`dep:${e}`),n.packageMarker=e,n.installed=!0;break}}catch{}for(let r of _)try{await u.access(l.join(e,r)),t.push(`config:${r}`),n.configFile=r,n.installed=!0;break}catch{}try{let r=await h([`**/*.{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});for(let i of r.slice(0,200))try{let r=await u.readFile(i,`utf8`);if(v.test(r)||y.test(r)){t.push(`script:${l.relative(e,i)}`),n.scriptFile=l.relative(e,i),n.installed=!0;break}}catch{}}catch{}return n}async function x(n){let r=await b(n.cwd),i;if(n.mode)i=n.mode;else if(!r.installed)i=`install`;else if(t(n))return e({type:`error`,code:`no_mode`,message:`Headless mode requires --mode chat|mcp|install`}),2;else i=await S(r);return i===`install`?s({cliVersion:n.cliVersion,cwd:n.cwd,json:n.json}):i===`chat`?C(n):w(n)}async function S(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}=m(d.createElement(f,{flexDirection:`column`},d.createElement(c,{title:`Zenovay detected${e.packageMarker?` (${e.packageMarker})`:``}`,state:`idle`},d.createElement(p,null,`What do you want to do?`)),d.createElement(o,{items:n,onSelect:e=>{r(),t(e)}})))})}async function C(e){let t=await n(),o=await i({strict:!1});if(!o)return process.stderr.write("Not logged in — run `zenovay login` first.\n"),2;let s=new r({config:t,cliVersion:e.cliVersion,token:o});process.stdout.write(`
2
+ Zenovay · chat mode — type your question then press Enter. Ctrl-C to exit.
3
+
4
+ > `);let c=await T();if(!c)return 0;let l=await s.openAiStream(`chat`,{messages:[{role:`user`,content:c}]});for await(let e of a(l)){if(e.type===`delta`&&process.stdout.write(e.content),e.type===`done`){process.stdout.write(`
5
+ `);break}if(e.type===`error`)return process.stderr.write(`\n[${e.code}] ${e.message}\n`),1}return 0}async function w(e){let t=await n(),o=await i({strict:!1});if(!o)return process.stderr.write("Not logged in — run `zenovay login` first.\n"),2;let s=new r({config:t,cliVersion:e.cliVersion,token:o});process.stdout.write(`
6
+ Zenovay · MCP mode — describe what you want to create or change. Ctrl-C to exit.
7
+
8
+ > `);let c=await T();if(!c)return 0;let l=await s.openAiStream(`mcp`,{messages:[{role:`user`,content:c}]});for await(let e of a(l)){if(e.type===`delta`&&process.stdout.write(e.content),e.type===`tool_start`&&process.stdout.write(`\n[tool: ${e.name}] `),e.type===`tool_result`&&process.stdout.write(`
9
+ [result]`),e.type===`done`){process.stdout.write(`
10
+ `);break}if(e.type===`error`)return process.stderr.write(`\n[${e.code}] ${e.message}\n`),1}return 0}function T(){return new Promise(e=>{let t=``,n=r=>{let i=r.toString(`utf8`);i.includes(`
11
+ `)||i.includes(`\r`)?(t+=i.replace(/[\r\n]/g,``),process.stdin.off(`data`,n),process.stdin.pause(),e(t)):t+=i};process.stdin.resume(),process.stdin.on(`data`,n)})}export{x as aiCommand};
@@ -0,0 +1 @@
1
+ import{configDir as e}from"./emit-CSYxxLsy.js";import t from"node:path";import{promises as n}from"node:fs";import{z as r}from"zod";const i=r.object({accessToken:r.string().min(1),refreshToken:r.string().min(1),expiresAt:r.number(),tokenType:r.literal(`Bearer`).default(`Bearer`),scope:r.string().optional(),userId:r.string().optional(),teamId:r.string().optional(),email:r.string().email().optional(),region:r.string().optional(),cliVersionAtIssue:r.string().optional()});function a(){return t.join(e(),`auth.json`)}async function o(e){let r=a(),o;try{o=await n.stat(r)}catch(e){if(e.code===`ENOENT`)return null;throw e}if(process.platform!==`win32`){let n=o.mode&511;if(n&63){let i=e?.strict??!0;if(i)throw Error(`Refusing to read ${r}: file has insecure permissions ${n.toString(8).padStart(3,`0`)} (expected 600). Fix: chmod 600 "${r}" && chmod 700 "${t.dirname(r)}"`)}}let s=await n.readFile(r,`utf8`),c;try{c=JSON.parse(s)}catch{throw Error(`Auth file at ${r} is not valid JSON. Run \`zenovay login\` again.`)}let l=i.safeParse(c);if(!l.success)throw Error(`Auth file at ${r} is malformed. Run \`zenovay login\` again.`);return l.data}async function s(t){let r=e();await n.mkdir(r,{recursive:!0,mode:448});let o=a(),s=i.parse(t),c=JSON.stringify(s,null,2);if(await n.writeFile(o,c,{mode:384}),process.platform!==`win32`){await n.chmod(o,384);try{await n.chmod(r,448)}catch{}}}async function c(){try{await n.unlink(a())}catch(e){if(e.code!==`ENOENT`)throw e}}function l(e,t=3e4){return Date.now()>=e.expiresAt-t}const u=r.object({device_code:r.string(),user_code:r.string(),verification_uri:r.string().url(),verification_uri_complete:r.string().url().optional(),expires_in:r.number().int().positive(),interval:r.number().int().positive().default(5)}),d=r.object({access_token:r.string(),refresh_token:r.string(),token_type:r.literal(`Bearer`).default(`Bearer`),expires_in:r.number().int().positive(),scope:r.string().optional()}),f=r.object({error:r.enum([`authorization_pending`,`slow_down`,`access_denied`,`expired_token`,`invalid_client`,`invalid_grant`,`invalid_scope`]),error_description:r.string().optional()});async function p(e){let{apiBase:t,clientId:n=`cli-zenovay`,scope:r=`cli:full`}=e,i=e.fetchImpl??fetch,a=new URLSearchParams({client_id:n,scope:r}),o=await i(`${t}/oauth/device_authorization`,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:a.toString()});if(!o.ok){let e=await o.text();throw Error(`Device authorization failed: ${o.status} ${e}`)}let s=await o.json();return u.parse(s)}async function m(e){let{apiBase:t,deviceCode:n,clientId:r=`cli-zenovay`,expiresIn:i}=e,a=e.fetchImpl??fetch,o=e.sleep??(e=>new Promise(t=>setTimeout(t,e))),s=e.interval,c=Date.now(),l=c+i*1e3;for(;Date.now()<l;){if(e.signal?.aborted)throw Error(`Polling aborted`);await o(s*1e3),e.onTick?.(Date.now()-c);let i=new URLSearchParams({grant_type:`urn:ietf:params:oauth:grant-type:device_code`,device_code:n,client_id:r}),l=await a(`${t}/oauth/token`,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:i.toString()}),u=await l.json().catch(()=>({}));if(l.ok){let e=d.parse(u);return{accessToken:e.access_token,refreshToken:e.refresh_token,expiresAt:Date.now()+e.expires_in*1e3,tokenType:`Bearer`,scope:e.scope}}let p=f.safeParse(u);if(!p.success)throw Error(`Token poll failed (${l.status}): ${JSON.stringify(u)}`);let{error:m}=p.data;if(m!==`authorization_pending`){if(m===`slow_down`){s=Math.min(s*2,30);continue}throw m===`access_denied`?Error(`Device authorization denied by user.`):m===`expired_token`?Error("Device code expired — run `zenovay login` again."):Error(`Device token error: ${m}`)}}throw Error("Device code expired — run `zenovay login` again.")}async function h(e){let t=await o({strict:!0});return t?l(t)?g(t,e):t:null}async function g(e,t){let{apiBase:n,clientId:r=`cli-zenovay`}=t,i=t.fetchImpl??fetch,a=new URLSearchParams({grant_type:`refresh_token`,refresh_token:e.refreshToken,client_id:r}),o=await i(`${n}/oauth/token`,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:a.toString()}),l=await o.json().catch(()=>({}));if(!o.ok){let e=l&&typeof l==`object`&&`error`in l?l.error:String(l);throw e===`invalid_grant`?(await c(),Error("Refresh token rejected by server — your session has been revoked. Run `zenovay login` again.")):Error(`Token refresh failed (${o.status}): ${JSON.stringify(l)}`)}let u=d.parse(l),f={...e,accessToken:u.access_token,refreshToken:u.refresh_token,expiresAt:Date.now()+u.expires_in*1e3,tokenType:`Bearer`,scope:u.scope??e.scope};return await s(f),f}const _=r.object({id:r.string(),name:r.string(),url:r.string().url(),trackingCode:r.string().optional(),lastActiveAt:r.string().nullable().optional()}),v=r.object({user:r.object({id:r.string(),email:r.string().email(),name:r.string().optional()}),team:r.object({id:r.string(),name:r.string(),plan:r.string()}).optional()});var y=class extends Error{constructor(e,t,n){super(n),this.status=e,this.body=t,this.name=`ApiError`}},b=class{fetchImpl;token;constructor(e){this.opts=e,this.fetchImpl=e.fetchImpl??fetch,this.token=e.token??null}get apiBase(){return this.opts.config.apiBase}async authedHeaders(e={}){let t=process.env.ZENOVAY_API_TOKEN;if(t&&t.trim())return{Accept:`application/json`,"User-Agent":`zenovay-cli/${this.opts.cliVersion}`,"X-Zenovay-CLI-Version":this.opts.cliVersion,Authorization:`Bearer ${t.trim()}`,...e};this.token||=await h({apiBase:this.apiBase});let n={Accept:`application/json`,"User-Agent":`zenovay-cli/${this.opts.cliVersion}`,"X-Zenovay-CLI-Version":this.opts.cliVersion,...e};return this.token&&(n.Authorization=`${this.token.tokenType} ${this.token.accessToken}`),n}async request(e,t){let n=`${this.apiBase}${e}`,r=await this.fetchImpl(n,{...t,headers:{...await this.authedHeaders(),...t.headers}});if(r.status===401&&this.token&&(this.token=null,r=await this.fetchImpl(n,{...t,headers:{...await this.authedHeaders(),...t.headers}})),!r.ok){let n=await r.text().catch(()=>``);throw new y(r.status,n,`${t.method??`GET`} ${e} → ${r.status}`)}let i=await r.json();return t.schema.parse(i)}async getSites(){return this.request(`/v1/cli/sites`,{schema:r.array(_)})}async createSite(e){return this.request(`/v1/cli/sites`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e),schema:r.object({id:r.string()})})}async getFirstEvent(e){let t=new URLSearchParams({site_id:e.siteId,since:String(e.since)});return this.request(`/v1/cli/first-event?${t.toString()}`,{schema:r.object({received:r.boolean(),event:r.unknown().optional()})})}async me(){return this.request(`/v1/cli/me`,{schema:v})}async revokeCurrentToken(){this.token&&await this.fetchImpl(`${this.apiBase}/oauth/revoke`,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`,...await this.authedHeaders()},body:new URLSearchParams({token:this.token.refreshToken,token_type_hint:`refresh_token`,client_id:`cli-zenovay`}).toString()})}async openAiStream(e,t,n){let r=await this.authedHeaders({"Content-Type":`application/json`,Accept:`text/event-stream`}),i=await this.fetchImpl(`${this.apiBase}/ai/cli/${e}`,{method:`POST`,headers:r,body:JSON.stringify(t),signal:n});if(!i.ok){let t=await i.text().catch(()=>``);throw new y(i.status,t,`POST /ai/cli/${e} → ${i.status}`)}return i}async openEventsStream(e,t){let n=await this.authedHeaders({Accept:`text/event-stream`}),r=await this.fetchImpl(`${this.apiBase}/v1/cli/events/tail?site_id=${e}`,{method:`GET`,headers:n,signal:t});if(!r.ok){let e=await r.text().catch(()=>``);throw new y(r.status,e,`GET events tail → ${r.status}`)}return r}async getDashboardSnapshot(e){return this.request(`/v1/cli/dashboard?site_id=${e}`,{schema:r.unknown()})}async ping(){let e=Date.now(),t=await this.fetchImpl(`${this.apiBase}/v1/cli/ping`,{headers:await this.authedHeaders()});if(!t.ok)throw new y(t.status,await t.text().catch(()=>``),`ping failed`);return{ok:!0,latencyMs:Date.now()-e}}};export{b as ApiClient,c as clearToken,m as pollDeviceToken,o as readToken,p as startDeviceAuthorization,s as writeToken};
@@ -0,0 +1,3 @@
1
+ import{BRAND_GRADIENT as e,useTheme as t}from"./panel-BIYvFJmj.js";import n from"react";import{Box as r,Text as i}from"ink";function a(e){let t=e.replace(`#`,``).trim(),n=t.length===3?t.split(``).map(e=>e+e).join(``):t,r=parseInt(n.slice(0,2),16),i=parseInt(n.slice(2,4),16),a=parseInt(n.slice(4,6),16);return{r:Number.isNaN(r)?0:r,g:Number.isNaN(i)?0:i,b:Number.isNaN(a)?0:a}}function o({r:e,g:t,b:n}){let r=e=>{let t=Math.max(0,Math.min(255,Math.round(e)));return t.toString(16).padStart(2,`0`)};return`#${r(e)}${r(t)}${r(n)}`}function s(e,t,n){return{r:e.r+(t.r-e.r)*n,g:e.g+(t.g-e.g)*n,b:e.b+(t.b-e.b)*n}}function c(e,t,r,a){if(e.length===0)return[];if(e.length===1)return[n.createElement(i,{key:`${a}-0`,color:o(t)},e)];let c=Array.from(e),l=c.length-1;return c.map((e,c)=>{let u=c/l,d=o(s(t,r,u));return n.createElement(i,{key:`${a}-${c}`,color:d},e)})}function l({from:e,to:t,children:r}){let o=a(e),s=a(t),l=r.split(`
2
+ `),u=[];return l.forEach((e,t)=>{t>0&&u.push(n.createElement(i,{key:`nl-${t}`},`
3
+ `));let r=c(e,o,s,`l${t}`);u.push(...r)}),n.createElement(i,null,u)}function u({children:t}){return n.createElement(l,{from:e.from,to:e.to},t)}function d(){let e=process.stdout.columns;return typeof e==`number`&&e>0?e:80}function f({version:e,subtitle:a}){let{theme:o}=t(),s=d(),c=`ZENOVAY · v${e}`,l=`╭─ `,f=` `,p=`─╮`,m=l.length+c.length+f.length+p.length,h=Math.max(2,s-m),g=`─`.repeat(h),_=`─`.repeat(Math.max(2,s-2)),v=a??``,y=v.slice(0,Math.max(0,s-4)),b=` `.repeat(Math.max(1,s-2-1-y.length-1));return n.createElement(r,{flexDirection:`column`},n.createElement(r,{flexDirection:`row`},n.createElement(i,{color:o.border},l),n.createElement(u,null,c),n.createElement(i,{color:o.border},f,g,p)),a?n.createElement(r,{flexDirection:`row`},n.createElement(i,{color:o.border},`│ `),n.createElement(i,{color:o.muted},y),n.createElement(i,{color:o.border},b,`│`)):null,n.createElement(r,{flexDirection:`row`},n.createElement(i,{color:o.border},`╰`,_,`╯`)))}export{f as Banner,u as BrandGradient};
package/dist/bin.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{cac as e}from"cac";import{fork as t}from"node:child_process";import n from"node:path";import{fileURLToPath as r}from"node:url";import{readFileSync as i}from"node:fs";const a=n.dirname(r(import.meta.url)),o=n.join(a,`..`,`package.json`),s=JSON.parse(i(o,`utf8`)),c=s.version,l=process.argv[0]??`zenovay`;function u(){if(!(process.env.ZENOVAY_NO_UPDATE_CHECK===`1`||process.env.CI===`true`))try{let e=n.join(a,`lib`,`update`,`worker.js`),r=t(e,[],{detached:!0,stdio:`ignore`});r.unref()}catch{}}const d=e(`zenovay`);d.version(c),d.help(),d.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`).action(async e=>{let{initCommand:t}=await import(`./init-DvqdybQR.js`),n=await t({cliVersion:c,cwd:process.cwd(),json:e.json,yes:e.yes,skipMcp:e.skipMcp,skipVerify:e.skipVerify});process.exitCode=n}),d.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-DU_S1gLP.js`);await t({cliVersion:c,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`)}})}),d.command(`logout`,`Revoke current session and delete local auth`).option(`--json`,``).action(async e=>{let{logoutCommand:t}=await import(`./logout-CNEOiRGt.js`);await t({cliVersion:c,json:e.json})}),d.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-CDfD1HWy.js`),n=await t({cliVersion:c,cwd:process.cwd(),mode:e.mode,json:e.json});process.exitCode=n}),d.command(`watch`,`Live TUI dashboard`).option(`--site-id <id>`,`Site ID`).option(`--interval <ms>`,`Poll interval ms`,{default:15e3}).option(`--json`,``).action(async e=>{let{watchCommand:t}=await import(`./watch-DK3uUs55.js`),n=await t({cliVersion:c,siteId:e.siteId,intervalMs:Number(e.interval),json:e.json});process.exitCode=n}),d.command(`events tail`,`Stream live events`).option(`--site-id <id>`,`Site ID`,{default:``}).option(`--json`,``).action(async e=>{let{eventsTailCommand:t}=await import(`./events-tail-QAoqCYLS.js`),n=await t({cliVersion:c,siteId:e.siteId,json:e.json});process.exitCode=n}),d.command(`health`,`Run self-diagnostic checks`).option(`--json`,``).action(async e=>{let{healthCommand:t}=await import(`./health-blDNqzHp.js`),n=await t({cliVersion:c,binaryPath:l,json:e.json});process.exitCode=n}),d.command(`doctor`,`AI-assisted self-healing`).option(`--json`,``).action(async e=>{let{doctorCommand:t}=await import(`./doctor-BsM2Crta.js`),n=await t({cliVersion:c,binaryPath:l,json:e.json});process.exitCode=n}),d.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-CH7-2eUz.js`);await t({cliVersion:c,binaryPath:l,check:e.check,forceDowngrade:e.forceDowngrade,json:e.json})}),d.parse(process.argv,{run:!1}),(async()=>{u();try{await d.runMatchedCommand()}catch(e){process.stderr.write(`\n✗ ${e.message}\n`),process.exit(1)}})();
@@ -0,0 +1 @@
1
+ import{z as e}from"zod";import t from"semver";const n=e.object({url:e.string().url(),sha256:e.string().regex(/^[0-9a-f]{64}$/i),sig:e.string()}),r=e.object({latest:e.string(),minSupported:e.string().optional(),platforms:e.record(e.string(),n),changelog:e.string().url().optional(),releasedAt:e.string().datetime().optional()});function i(e,n){if(!t.valid(e)||!t.valid(n)||t.eq(e,n))return`none`;if(t.lt(n,e))return`downgrade`;let r=t.diff(e,n);return r===`patch`||r===`prepatch`?`patch`:r===`minor`||r===`preminor`?`minor`:`major`}function a(e=process.platform,t=process.arch){let n=e===`darwin`?`darwin`:e===`linux`?`linux`:e===`win32`?`win32`:e,r=t===`x64`?`x64`:t===`arm64`?`arm64`:t;return`${n}-${r}`}async function o(e){let t=e.fetchImpl??fetch,n=await t(`${e.cliBase}/version`,{signal:e.signal});if(!n.ok)throw Error(`Manifest fetch failed: ${n.status}`);let i=await n.json();return r.parse(i)}export{i as classifyBump,o as fetchManifest,a as platformKey};
@@ -0,0 +1,5 @@
1
+ import{z as e}from"zod";const t=e.union([e.object({type:e.literal(`delta`),content:e.string()}),e.object({type:e.literal(`tool_call`),id:e.string(),name:e.string(),arguments:e.record(e.string(),e.unknown())}),e.object({type:e.literal(`tool_result`),id:e.string(),content:e.unknown(),isError:e.boolean().optional()}),e.object({type:e.literal(`tool_start`),id:e.string(),name:e.string()}),e.object({type:e.literal(`usage`),inputTokens:e.number(),outputTokens:e.number(),quotaRemaining:e.number().optional()}),e.object({type:e.literal(`done`)}),e.object({type:e.literal(`error`),code:e.string(),message:e.string()})]);async function*n(e){if(!e.body)throw Error(`SSE response has no body`);let t=e.body.getReader(),n=new TextDecoder,i=``;try{for(;;){let{done:e,value:a}=await t.read();if(e)break;i+=n.decode(a,{stream:!0});let o;for(;(o=i.indexOf(`
2
+
3
+ `))!==-1;){let e=i.slice(0,o);i=i.slice(o+2);let t=r(e);t&&(yield t)}}if(i.trim().length>0){let e=r(i);e&&(yield e)}}finally{t.releaseLock()}}function r(e){let n=e.split(`
4
+ `),r=``;for(let e of n)e.startsWith(`data:`)&&(r+=e.slice(5).trimStart()+`
5
+ `);if(r=r.trim(),!r||r===`[DONE]`)return null;let i;try{i=JSON.parse(r)}catch{return null}let a=t.safeParse(i);return a.success?a.data:null}export{n as parseSseStream};
@@ -0,0 +1,13 @@
1
+ import{emit as e,isHeadless as t,readConfig as n}from"./emit-CSYxxLsy.js";import{ApiClient as r,readToken as i}from"./api-CvIiXSON.js";import{parseSseStream as a}from"./client-ClN0aAAQ.js";import"./panel-BIYvFJmj.js";import"./check-0Gyvd8t_.js";import{runHealthChecks as o}from"./health-BDxxidqH.js";async function s(s){let c=await o({cliVersion:s.cliVersion,binaryPath:s.binaryPath}),l=c.filter(e=>e.status===`fail`);if(l.length===0)return t(s)||process.stdout.write(`All checks pass — no healing needed.
2
+ `),0;let u=await n(),d=await i({strict:!1});if(!d){for(let e of l)process.stderr.write(`[${e.name}] ${e.detail}\n`);return process.stderr.write(`
3
+ Log in with \`zenovay login\` for AI-assisted diagnosis.
4
+ `),1}let f=new r({config:u,cliVersion:s.cliVersion,token:d}),p={platform:process.platform,arch:process.arch,node:process.version,cliVersion:s.cliVersion},m=`The Zenovay CLI failed the following health checks. Diagnose and propose a fix.
5
+
6
+ Environment: ${JSON.stringify(p)}
7
+ Failed checks: ${JSON.stringify(l,null,2)}
8
+
9
+ Respond with (1) a one-paragraph diagnosis, (2) a proposed fix command if applicable. Do not invent commands that could affect unrelated files. If the fix is system-level (e.g., chmod), print the exact command so the user can copy it.`,h=await f.openAiStream(`chat`,{messages:[{role:`user`,content:m}]});t(s)||process.stdout.write(`
10
+ Zenovay doctor — AI diagnosis:
11
+
12
+ `);for await(let n of a(h)){if(n.type===`delta`&&(t(s)?e({type:`ai.token`,mode:`chat`,content:n.content}):process.stdout.write(n.content)),n.type===`error`)return t(s)?e({type:`error`,code:n.code,message:n.message}):process.stderr.write(`\n[${n.code}] ${n.message}\n`),1;if(n.type===`done`)break}return t(s)||process.stdout.write(`
13
+ `),0}export{s as doctorCommand};
@@ -0,0 +1 @@
1
+ import e from"node:path";import{promises as t}from"node:fs";import n from"node:os";import{z as r}from"zod";const i=r.object({theme:r.string().optional(),apiBase:r.string().url().default(`https://api.zenovay.com`),cliBase:r.string().url().default(`https://cli.zenovay.com`),region:r.enum([`us`,`eu`,`auto`]).default(`auto`),telemetry:r.boolean().default(!1),noUpdateCheck:r.boolean().default(!1),lastUpdateNotice:r.number().optional()}),a={apiBase:`https://api.zenovay.com`,cliBase:`https://cli.zenovay.com`,region:`auto`,telemetry:!1,noUpdateCheck:!1};function o(){if(process.platform===`win32`){let t=process.env.APPDATA??e.join(n.homedir(),`AppData`,`Roaming`);return e.join(t,`zenovay`)}let t=process.env.XDG_CONFIG_HOME??e.join(n.homedir(),`.config`);return e.join(t,`zenovay`)}function s(){return e.join(o(),`config.json`)}async function c(){let e=s();try{let n=await t.readFile(e,`utf8`),r=i.safeParse(JSON.parse(n));return r.success?r.data:a}catch(e){if(e.code===`ENOENT`)return a;throw e}}function l(e={}){return e.json||process.env.CI===`true`||process.env.CI===`1`||process.env.ZENOVAY_FORMAT===`ndjson`?!0:!process.stdout.isTTY}function u(e){let t={ts:new Date().toISOString(),...e};process.stdout.write(`${JSON.stringify(t)}\n`)}export{o as configDir,s as configPath,u as emit,l as isHeadless,c as readConfig};
@@ -0,0 +1 @@
1
+ import{emit as e,isHeadless as t,readConfig as n}from"./emit-CSYxxLsy.js";import{ApiClient as r,readToken as i}from"./api-CvIiXSON.js";import{parseSseStream as a}from"./client-ClN0aAAQ.js";import{Panel as o}from"./panel-BIYvFJmj.js";import{Banner as s}from"./banner-BUAXN9wM.js";import c,{useEffect as l,useState as u}from"react";import{Box as d,Text as f,render as p,useApp as m,useInput as h}from"ink";async function g(e){let a=await n(),o=await i({strict:!1});if(!o)return process.stderr.write("Not logged in — run `zenovay login` first.\n"),2;let s=new r({config:a,cliVersion:e.cliVersion,token:o});return t(e)?_(s,e):new Promise(t=>{let{unmount:n}=p(c.createElement(v,{api:s,opts:e,onExit:e=>{n(),t(e)}}))})}async function _(t,n){let r=new AbortController;process.on(`SIGINT`,()=>r.abort());try{let i=await t.openEventsStream(n.siteId,r.signal);for await(let t of a(i)){if(t.type===`delta`||t.type===`tool_call`||t.type===`tool_result`||t.type===`tool_start`||t.type===`usage`)continue;if(t.type===`error`)return e({type:`error`,code:t.code,message:t.message}),1;if(t.type===`done`)return 0}return 0}catch(t){return e({type:`error`,code:`stream_failed`,message:t.message}),1}}const v=({api:e,opts:t,onExit:n})=>{let{exit:r}=m(),[i,p]=u([]);return h(e=>{e===`q`&&(r(),n(0))}),l(()=>{let n=new AbortController;return(async()=>{try{let r=await e.openEventsStream(t.siteId,n.signal);for await(let e of a(r))e.type===`error`&&p(t=>[...t.slice(-100),`[ERR ${e.code}] ${e.message}`])}catch(e){p(t=>[...t.slice(-100),`[DISCONNECT] ${e.message}`])}})(),()=>n.abort()},[e,t.siteId]),c.createElement(d,{flexDirection:`column`},c.createElement(s,{version:t.cliVersion,subtitle:`events tail · ${t.siteId}`}),c.createElement(o,{title:`Live event stream`,state:`busy`},i.length===0?c.createElement(f,{color:`gray`},`Waiting for events…`):i.slice(-20).map((e,t)=>c.createElement(f,{key:t},e))))};export{g as eventsTailCommand};
@@ -0,0 +1 @@
1
+ import{configPath as e,emit as t,isHeadless as n,readConfig as r}from"./emit-CSYxxLsy.js";import{ApiClient as i,readToken as a}from"./api-CvIiXSON.js";import{Panel as o}from"./panel-BIYvFJmj.js";import{fetchManifest as s}from"./check-0Gyvd8t_.js";import c from"node:path";import{promises as l}from"node:fs";import u from"react";import{Box as d,Text as f,render as p}from"ink";import m from"node:os";const h=6*60*60*1e3;function g(){return c.join(m.homedir(),`.zenovay`,`update.json`)}async function _(){try{let e=await l.readFile(g(),`utf8`);return JSON.parse(e)}catch{return null}}async function v(e){let t=g();await l.mkdir(c.dirname(t),{recursive:!0,mode:448}),await l.writeFile(t,JSON.stringify(e,null,2))}async function y(){let e=await r();if(e.noUpdateCheck||process.env.ZENOVAY_NO_UPDATE_CHECK===`1`||process.env.CI===`true`)return null;let t=await _();if(t&&Date.now()-t.fetchedAt<h)return t;try{let n=await s({cliBase:e.cliBase}),r={manifest:n,fetchedAt:Date.now(),lastNoticeAt:t?.lastNoticeAt};return await v(r),r}catch{return t}}async function b(){try{await y()}catch{}process.exit(0)}import.meta.url===`file://${process.argv[1]}`&&b();async function x(t){let n=[],o=await r().catch(()=>null);n.push({name:`Binary`,status:`pass`,detail:`v${t.cliVersion} (${t.binaryPath})`}),n.push({name:`Config file`,status:o?`pass`:`warn`,detail:e()});let s=await a({strict:!1}).catch(()=>null);if(n.push({name:`Auth`,status:s?`pass`:`fail`,detail:s?`logged in${s.email?` as ${s.email}`:``}`:`not logged in`}),o)try{let e=new i({config:o,cliVersion:t.cliVersion,token:s}),r=await e.ping();n.push({name:`API connection`,status:`pass`,detail:`${o.apiBase.replace(/^https?:\/\//,``)} · ${r.latencyMs}ms`})}catch(e){n.push({name:`API connection`,status:`fail`,detail:`${e.message}`})}let c=await _().catch(()=>null);return n.push({name:`Auto-update`,status:o?.noUpdateCheck?`warn`:`pass`,detail:o?.noUpdateCheck?`disabled`:c?`latest=${c.manifest.latest}`:`manifest not yet fetched`}),n}async function S(e){let r=await x(e),i=r.filter(e=>e.status===`fail`).length,a=i>0?1:0;return n({json:e.json})?(t({type:`info`,message:JSON.stringify({checks:r,exitCode:a})}),a):new Promise(e=>{let{unmount:t}=p(u.createElement(C,{checks:r}),{exitOnCtrlC:!0});setImmediate(()=>{t(),e(a)})})}const C=({checks:e})=>u.createElement(o,{title:`Zenovay CLI health check`,state:`idle`},e.map(e=>u.createElement(d,{key:e.name},u.createElement(f,{color:T(e.status)},w(e.status)),u.createElement(f,null,` `),u.createElement(f,{bold:!0},e.name.padEnd(18)),u.createElement(f,{color:`gray`},e.detail))));function w(e){return e===`pass`?`✔`:e===`warn`?`⚠`:`✗`}function T(e){return e===`pass`?`green`:e===`warn`?`yellow`:`red`}export{C as HealthReport,S as healthCommand,x as runHealthChecks};
@@ -0,0 +1 @@
1
+ import"./emit-CSYxxLsy.js";import"./api-CvIiXSON.js";import"./panel-BIYvFJmj.js";import"./check-0Gyvd8t_.js";import{HealthReport as e,healthCommand as t,runHealthChecks as n}from"./health-BDxxidqH.js";export{t as healthCommand};
@@ -0,0 +1,61 @@
1
+ import{emit as e,isHeadless as t,readConfig as n}from"./emit-CSYxxLsy.js";import{ApiClient as r,readToken as i}from"./api-CvIiXSON.js";import{parseSseStream as a}from"./client-ClN0aAAQ.js";import{loginCommand as o}from"./login-BMG01YNm.js";import{Panel as s,useTheme as c}from"./panel-BIYvFJmj.js";import{Banner as l,BrandGradient as u}from"./banner-BUAXN9wM.js";import{Keybar as d}from"./keybar-WbLSMIEj.js";import f from"node:path";import{promises as p}from"node:fs";import m,{useCallback as h,useEffect as g,useRef as ee,useState as _}from"react";import{Box as v,Text as y,render as b,useApp as te,useInput as ne}from"ink";import x from"node:os";import{z as S}from"zod";import{globby as C}from"globby";import w from"ink-spinner";import T from"node:crypto";import{ulid as E}from"ulid";import{applyPatch as D}from"diff";import re from"ink-select-input";import"ink-text-input";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($|\.[^/]+$)/,/^\.git(\/|$)/,/(^|\/)node_modules\//,/(^|\/)\.ssh\//,/(^|\/)\.aws\//,/\.pem$/,/\.key$/,/(^|\/)id_rsa($|\.)/,/(^|\/)\.npmrc$/,/(^|\/)credentials(\.|$)/],O=256*1024,k=50,A=20,j=40;var M=class extends Error{constructor(e,t){super(t),this.code=e,this.name=`SandboxError`}},N=class{sessionId;cwd;cwdReal;undoRoot;readCount=0;writeCount=0;roundCount=0;constructor(e){this.sessionId=e.sessionId??E(),this.cwd=e.cwd,this.cwdReal=p.realpath(e.cwd),this.undoRoot=e.undoRoot??f.join(x.homedir(),`.zenovay`,`undo`)}incrementRound(){if(this.roundCount++,this.roundCount>j)throw new M(`round-cap`,`Tool-call recursion cap (${j}) exceeded.`)}stats(){return{sessionId:this.sessionId,reads:this.readCount,writes:this.writeCount,rounds:this.roundCount}}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 M(`escape`,`Path must be a non-empty string.`);if(e.includes(`\0`))throw new M(`escape`,`Null byte in path.`);let n=await this.cwdReal,r=f.isAbsolute(e)?e:f.join(n,e),i=f.normalize(r),a=i;try{a=await p.realpath(i)}catch(e){if(e.code!==`ENOENT`)throw new M(`escape`,`realpath failed: ${e.message}`);let t=f.dirname(i);try{let e=await p.realpath(t);a=f.join(e,f.basename(i))}catch{}}if(!this.isWithin(a,n))throw new M(`escape`,`Path escapes cwd: ${e}`);let o=f.relative(n,a);for(let e of ae)if(e.test(o)){if(t===`read`&&/(^|\/)node_modules\//.test(o))continue;throw new M(`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 M(`not-allowlisted`,`Write path not on allowlist: ${o}`)}return a}isWithin(e,t){let n=f.relative(t,e);return!n.startsWith(`..`)&&!f.isAbsolute(n)}async safeRead(e){if(this.readCount>=k)throw new M(`read-cap`,`Read cap (${k}) reached.`);let t=await this.resolveForRead(e),n=await p.stat(t);if(n.size>O)throw new M(`too-large`,`File exceeds ${O}B: ${e} (${n.size}B)`);let r=await p.readFile(t),i=r.subarray(0,Math.min(r.length,8192));if(i.includes(0))throw new M(`binary-rejected`,`Binary content in ${e}.`);return this.readCount++,r.toString(`utf8`)}async safeWrite(e,t){if(this.writeCount>=A)throw new M(`write-cap`,`Write cap (${A}) reached.`);if(t.length>O)throw new M(`too-large`,`Write exceeds ${O}B: ${e}`);let n=await this.resolveForWrite(e),r=await this.journalBackup(n);return await p.mkdir(f.dirname(n),{recursive:!0}),await p.writeFile(n,t,`utf8`),this.writeCount++,{backupPath:r}}async journalBackup(e){let t=await p.readFile(e).catch(e=>{if(e.code===`ENOENT`)return null;throw e}),n=f.join(this.undoRoot,this.sessionId);await p.mkdir(n,{recursive:!0,mode:448});let r=T.createHash(`sha256`).update(e).digest(`hex`).slice(0,16),i=f.join(n,`${r}.bak`),a=f.join(n,`manifest.jsonl`);t&&await p.writeFile(i,t,{mode:384});let o=JSON.stringify({abs:e,backupPath:t?i:null,ts:Date.now()});return await p.appendFile(a,o+`
2
+ `,{mode:384}),t?i:null}static wrapForModel(e,t){let n=T.createHash(`sha256`).update(t).digest(`hex`).slice(0,16),r=t.replaceAll(`&`,`&amp;`).replaceAll(`<`,`&lt;`).replaceAll(`>`,`&gt;`);return`<FILE path="${e}" sha256="${n}">\n${r}\n</FILE>`}static scrubSecrets(e){return e.replace(/\bsk-[A-Za-z0-9_-]{20,}/g,`[REDACTED_OPENAI]`).replace(/\bghp_[A-Za-z0-9]{20,}/g,`[REDACTED_GITHUB]`).replace(/\bgithub_pat_[A-Za-z0-9_]{20,}/g,`[REDACTED_GITHUB_PAT]`).replace(/\bAKIA[0-9A-Z]{16}\b/g,`[REDACTED_AWS]`).replace(/\bsk_(test|live)_[A-Za-z0-9]{20,}/g,`[REDACTED_STRIPE]`).replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g,`[REDACTED_JWT]`)}};const oe=[`astro.config.ts`,`astro.config.mjs`,`astro.config.js`];async function se(e){for(let t of oe)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 ce(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 le={id:`astro`,displayName:`Astro`,detect:se,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:ce},ue=[`app/layout.tsx`,`app/layout.ts`,`app/layout.jsx`,`app/layout.js`];async function P(e){for(let t of ue)if(await e.exists(t))return t;return null}async function F(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 I(e){let t=await F(e),n=await e.exists(`app`),r=await P(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 L(e){let t=`app/layout.tsx`,n=e.trackingCode??e.siteId??``;return[{targetPath:t,anchorKind:`after-imports`,code:`import Script from 'next/script';`,description:`Import next/script to inject the Zenovay tracker.`},{targetPath:t,anchorKind:`wrap-children`,code:`<Script
3
+ defer
4
+ data-id="${n}"
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.`}]}const R={id:`next-app-router`,displayName:`Next.js (App Router)`,detect:I,files:[{path:`app/layout.tsx`,kind:`mutate`,required:!0}],envVarName:`NEXT_PUBLIC_ZENOVAY_SITE`,buildSnippets:L},z=[`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 B(e){for(let t of z)if(await e.exists(t))return t;return null}async function V(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 H(e){let t=await V(e);if(!t)return{matched:!1,confidence:0,reason:"package.json does not list `next` as a dependency."};let n=await B(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 U(e){let t=`pages/_app.tsx`,n=e.trackingCode??e.siteId??``,r=`import type { AppProps } from 'next/app';
10
+ import Script from 'next/script';
11
+
12
+ export default function App({ Component, pageProps }: AppProps) {
13
+ return (
14
+ <>
15
+ <Script
16
+ defer
17
+ data-id="${n}"
18
+ data-allow-localhost="true"
19
+ src="https://api.zenovay.com/z.js"
20
+ strategy="afterInteractive"
21
+ />
22
+ <Component {...pageProps} />
23
+ </>
24
+ );
25
+ }
26
+ `;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 de={id:`next-pages-router`,displayName:`Next.js (Pages Router)`,detect:H,files:[{path:`pages/_app.tsx`,kind:`mutate`,required:!0}],envVarName:`NEXT_PUBLIC_ZENOVAY_SITE`,buildSnippets:U},fe=S.object({modules:S.array(S.string()).optional(),runtimeConfig:S.object({public:S.record(S.string(),S.unknown()).optional()}).optional()}).passthrough();async function pe(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 me(e){let t=e.trackingCode??e.siteId??``,n=` app: {
27
+ head: {
28
+ script: [
29
+ {
30
+ defer: true,
31
+ src: 'https://api.zenovay.com/z.js',
32
+ 'data-id': '${t}',
33
+ 'data-allow-localhost': 'true',
34
+ },
35
+ ],
36
+ },
37
+ },`;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 he={id:`nuxt`,displayName:`Nuxt`,detect:pe,files:[{path:`nuxt.config.ts`,kind:`mutate`,required:!0}],envVarName:`NUXT_PUBLIC_ZENOVAY_SITE`,buildSnippets:me},ge=[`index.html`,`public/index.html`,`dist/index.html`];async function _e(e){for(let t of ge)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 ve(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 ye={id:`plain-html`,displayName:`Plain HTML`,detect:_e,files:[{path:`index.html`,kind:`mutate`,required:!0}],envVarName:`PUBLIC_ZENOVAY_SITE`,buildSnippets:ve};async function be(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 xe(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 Se={id:`remix`,displayName:`Remix`,detect:be,files:[{path:`app/root.tsx`,kind:`mutate`,required:!0}],envVarName:`PUBLIC_ZENOVAY_SITE`,buildSnippets:xe};async function Ce(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 we(e){let t=e.trackingCode??e.siteId??``,n=`<svelte:head>
38
+ <script defer data-id="${t}" data-allow-localhost="true" src="https://api.zenovay.com/z.js"></script>
39
+ </svelte:head>
40
+
41
+ <slot />
42
+ `;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 Te={id:`sveltekit`,displayName:`SvelteKit`,detect:Ce,files:[{path:`src/routes/+layout.svelte`,kind:`mutate`,required:!0}],envVarName:`PUBLIC_ZENOVAY_SITE`,buildSnippets:we},Ee=[R,de,he,Te,le,Se,ye];async function W(e){let t=[];for(let n of Ee){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 G(e){let t=t=>f.resolve(e,t);return{cwd:e,async readFile(e){try{return await p.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 p.access(t(e)),!0}catch{return!1}},async glob(t){return C(t,{cwd:e,dot:!1,gitignore:!0,onlyFiles:!0,followSymbolicLinks:!1})}}}async function K(e,t){let n=G(e),{adapter:r,ranked:i}=await W(n),a=i[0];return r&&a&&a.result.confidence>=.7?r:t?t.pickInteractively(i):r}function De(e,t){let n=/[\s"'#=]/.test(t)||t===``,r=n?`"${t.replace(/\\/g,`\\\\`).replace(/"/g,`\\"`)}"`:t;return`${e}=${r}`}function Oe(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 q(e,t,n={}){let r=n.filename??`.env.local`,i=f.resolve(e,r),a=``,o=!0;try{a=await p.readFile(i,`utf8`)}catch(e){if(e.code===`ENOENT`)o=!1;else throw e}let s=o?Oe(a):new Set,c=[],l=[],u=[];for(let[e,n]of Object.entries(t)){if(s.has(e)){l.push(e);continue}u.push(De(e,n)),c.push(e)}if(c.length===0)return{created:!1,existing:o,addedKeys:c,skippedKeys:l};let d=`# Added by Zenovay CLI`,m;if(o){let e=a.length>0&&!a.endsWith(`
43
+ `);m=a+(e?`
44
+ `:``)+`
45
+ `+d+`
46
+ `+u.join(`
47
+ `)+`
48
+ `}else m=d+`
49
+ `+u.join(`
50
+ `)+`
51
+ `;if(o)await p.writeFile(i,m,`utf8`);else{let e=await p.open(i,`w`,384);try{await e.writeFile(m,`utf8`)}finally{await e.close()}}return{created:!o,existing:o,addedKeys:c,skippedKeys:l}}const ke=`https://api.zenovay.com/mcp`,Ae=S.object({url:S.string().url(),headers:S.record(S.string(),S.string()).optional()}),je=S.object({mcpServers:S.record(S.string(),Ae).optional()}).passthrough();function Me(e){return e.HOME??e.USERPROFILE??x.homedir()}function Ne(e){return e.APPDATA??null}function Pe(e,t){let n=Me(t),r=Ne(t),i=[];return i.push({id:`cursor`,displayName:`Cursor`,configPath:f.join(n,`.cursor`,`mcp.json`)}),e===`win32`&&r&&i.push({id:`cursor`,displayName:`Cursor (AppData)`,configPath:f.join(r,`Cursor`,`User`,`globalStorage`,`cursor.mcp`,`mcp.json`)}),e===`darwin`?i.push({id:`claude-desktop`,displayName:`Claude Desktop`,configPath:f.join(n,`Library`,`Application Support`,`Claude`,`claude_desktop_config.json`)}):e===`win32`&&r?i.push({id:`claude-desktop`,displayName:`Claude Desktop`,configPath:f.join(r,`Claude`,`claude_desktop_config.json`)}):i.push({id:`claude-desktop`,displayName:`Claude Desktop`,configPath:f.join(n,`.config`,`Claude`,`claude_desktop_config.json`)}),i.push({id:`vscode`,displayName:`VS Code`,configPath:f.join(n,`.vscode`,`mcp.json`)}),i}async function Fe(e){try{return await p.access(e),!0}catch{return!1}}async function Ie(e){try{let t=await p.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 Le(e){return{url:ke,headers:{Authorization:`Bearer ${e}`}}}async function Re(e){let t=e.env??process.env,n=e.platform??process.platform,r=new Set(e.skip??[]),i=[],a=[],o=[],s=new Set,c=Le(e.accessToken);for(let e of Pe(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 Fe(e.configPath);if(!t){a.push(e.displayName);continue}let n=await Ie(e.configPath),r=`${e.configPath}.bak`;await p.copyFile(e.configPath,r);let o={...n,mcpServers:{...n.mcpServers??{},zenovay:c}};await p.mkdir(f.dirname(e.configPath),{recursive:!0}),await p.writeFile(e.configPath,JSON.stringify(o,null,2)+`
52
+ `,`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 ze(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 Be=S.object({path:S.string().min(1)}),Ve=S.object({path:S.string().min(1),content:S.string(),reason:S.string().min(1)}),He=S.object({path:S.string().min(1),patch:S.string().min(1),reason:S.string().min(1)}),Ue=S.object({}),We=S.object({question:S.string().min(1),choices:S.array(S.string()).optional()});async function Ge(e,t){let n=Be.safeParse(t);if(!n.success)return{ok:!1,content:null,error:`invalid_args`};try{let t=await e.safeRead(n.data.path),r=N.scrubSecrets(t);return{ok:!0,content:N.wrapForModel(n.data.path,r)}}catch(e){return{ok:!1,content:null,error:e.message}}}async function J(e,t,n){let r=Ve.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 Ke(e,t,n){let r=He.safeParse(t);if(!r.success)return{ok:!1,content:null,error:`invalid_args`};let{path:i,patch:a,reason:o}=r.data,s;try{s=await e.safeRead(i)}catch(e){return{ok:!1,content:null,error:e.message}}let c=D(s,a);return c===!1||typeof c!=`string`?{ok:!1,content:null,error:`patch_did_not_apply_cleanly`}:J(e,{path:i,content:c,reason:o},n)}async function qe(e){try{let t=G(e),{adapter:n,ranked:r}=await W(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 Je(e,t){let n=We.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 Ye(e){let{api:t,sandbox:n,approval:r,prompter:i,handlers:o}=e,s=[...e.initialMessages],c=``,l=0;for(;l<j;){n.incrementRound(),l++;let e=await t.openAiStream(`install`,{messages:s,cwd_fingerprint:n.sessionId}),u=[],d=``;for await(let t of a(e))switch(t.type){case`delta`:d+=t.content,o?.onDelta?.(t.content);break;case`tool_start`:o?.onToolStart?.({id:t.id,name:t.name});break;case`tool_call`:{let e=await Xe(t.name,t.arguments,{sandbox:n,cwd:n.cwd,approval:r,prompter:i});u.push({id:t.id,content:e}),o?.onToolResult?.({id:t.id,name:t.name,result:e});break}case`usage`:o?.onUsage?.(t);break;case`error`:throw o?.onError?.(t),Error(`[${t.code}] ${t.message}`);case`done`:break}if(d&&(c+=d),u.length===0){d&&s.push({role:`assistant`,content:d});break}d&&s.push({role:`assistant`,content:d});for(let e of u)s.push({role:`tool`,toolCallId:e.id,content:JSON.stringify(e.content)})}return{finalText:c,rounds:l}}async function Xe(e,t,n){switch(e){case`read_file`:return Ge(n.sandbox,t);case`write_file`:return J(n.sandbox,t,n.approval);case`patch_file`:return Ke(n.sandbox,t,n.approval);case`detect_framework`:return qe(n.cwd);case`ask_user`:return Je(n.prompter,t);default:return{ok:!1,content:null,error:`unknown_tool:${e}`}}}function Ze({hints:e}){let{theme:t}=c();return m.createElement(y,{color:t.dim},e)}function Y({items:e,onSelect:t}){let{theme:n}=c(),[r,i]=_(e[0]),a=h(e=>{t(e.value)},[t]),o=h(e=>{i(e)},[]);return m.createElement(v,{flexDirection:`column`},m.createElement(re,{items:e.map((e,t)=>({key:`${t}`,label:e.label,value:e.value})),onSelect:t=>{let n=e.find(e=>e.value===t.value);n&&a(n)},onHighlight:t=>{let n=e.find(e=>e.value===t.value);n&&o(n)}}),r?.description?m.createElement(v,{marginTop:1},m.createElement(y,{color:n.muted},r.description)):null,m.createElement(v,{marginTop:1},m.createElement(Ze,{hints:`[↑↓/jk nav] [enter select]`})))}async function X(e){return t(e)?Qe(e):new Promise(t=>{let{unmount:n,waitUntilExit:r}=b(m.createElement($e,{opts:e,onExit:e=>{n(),t(e)}}));r().catch(()=>t(1))})}async function Qe(t){try{let a=await n(),o=await K(t.cwd);if(!o)return e({type:`error`,code:`no_framework`,message:`Could not detect framework (headless mode has no picker).`}),2;e({type:`framework.detected`,framework:o.id,confidence:1});let s=await i({strict:!1});if(!s&&!process.env.ZENOVAY_API_TOKEN)return e({type:`error`,code:`not_logged_in`,message:"Run `zenovay login` first (or set ZENOVAY_API_TOKEN)."}),2;let c=new r({config:a,cliVersion:t.cliVersion,token:s}),l=await c.getSites(),u=t.siteId?l.find(e=>e.id===t.siteId):l[0];if(!u&&t.siteId)return e({type:`error`,code:`site_not_found`,message:`Site ${t.siteId} not found.`}),2;if(!u)return e({type:`error`,code:`no_site`,message:`No sites exist; create one at app.zenovay.com or re-run interactively.`}),2;e({type:`info`,message:`Using site ${u.id} (${u.name}) trackingCode=${u.trackingCode??u.id}`});let d=await q(t.cwd,{[o.envVarName]:u.trackingCode??u.id});return e({type:`info`,message:`env vars added: ${d.addedKeys.join(`,`)}`}),e({type:`done`,command:`init`,exitCode:0}),0}catch(t){return e({type:`error`,code:`init_failed`,message:t.message}),1}}const $e=({opts:e,onExit:t})=>{let{exit:a}=te(),[c,u]=_(`intro`),[f,p]=_(null),[h,b]=_([]),[ne,x]=_(null),[S,C]=_(null),[T,E]=_(null),D=ee(!1);return g(()=>{D.current||(D.current=!0,(async()=>{try{u(`detecting`);let t=await K(e.cwd);if(!t){E(`Could not detect your framework. Supported: Next.js, Nuxt, SvelteKit, Astro, Remix, plain HTML.`),u(`error`);return}p(t);let a=await i({strict:!1});!process.env.ZENOVAY_API_TOKEN&&!a&&(u(`login`),await o({cliVersion:e.cliVersion})),u(`loading-sites`);let s=await n(),c=await i({strict:!1}),l=new r({config:s,cliVersion:e.cliVersion,token:c}),d=await l.getSites(),f=await it(e.cwd),m=Q(d,f);if(b(d),x(f),d.length===0){u(`create-site`);return}if(e.siteId){let n=d.find(t=>t.id===e.siteId);if(!n){E(`Site ${e.siteId} not found in your account.`),u(`error`);return}C(n),Z(n,t,e,{setPhase:u,setErrorMsg:E});return}if(e.yes&&m){C(m),Z(m,t,e,{setPhase:u,setErrorMsg:E});return}u(`site-picker`)}catch(e){E(e.message),u(`error`)}})())},[]),g(()=>{c===`success`&&setTimeout(()=>{a(),t(0)},300),c===`error`&&setTimeout(()=>{a(),t(1)},300)},[c]),m.createElement(v,{flexDirection:`column`},m.createElement(l,{version:e.cliVersion,subtitle:`AI install wizard`}),c===`intro`&&m.createElement(et,null),c===`detecting`&&m.createElement(s,{title:`Scanning`,state:`busy`},m.createElement(v,null,m.createElement(y,{color:`magenta`},m.createElement(w,{type:`dots`})),m.createElement(y,null,` Detecting framework…`))),c===`login`&&m.createElement(s,{title:`Authenticating`,state:`busy`},m.createElement(y,null,`Opening browser for login… (paste ZENOVAY_API_TOKEN env var to skip)`)),c===`loading-sites`&&m.createElement(s,{title:`Loading your sites`,state:`busy`},m.createElement(v,null,m.createElement(y,{color:`magenta`},m.createElement(w,{type:`dots`})),m.createElement(y,null,` Fetching your Zenovay sites…`))),c===`site-picker`&&f&&m.createElement(tt,{sites:h,preselectHint:ne,onPick:t=>{if(t===`create-new`){u(`create-site`);return}C(t),Z(t,f,e,{setPhase:u,setErrorMsg:E})}}),c===`create-site`&&m.createElement(s,{title:`Create a new site`,state:`warn`},m.createElement(y,{color:`yellow`},"No site picked (or none exist yet). Visit https://app.zenovay.com/websites to create one, then re-run `zenovay init`."),m.createElement(y,{color:`gray`},`(Inline site creation from the CLI is scheduled for V1.1 — it requires a plan-limit check.)`)),c===`agent`&&f&&S&&m.createElement(s,{title:`Installing into ${f.displayName}`,state:`busy`},m.createElement(v,null,m.createElement(y,{color:`magenta`},m.createElement(w,{type:`dots`})),m.createElement(y,null,` Agent writing tracker into your project…`)),m.createElement(y,{color:`gray`},`Site: `,S.name,` · code: `,S.trackingCode??S.id)),c===`verify`&&m.createElement(s,{title:`Waiting for first event`,state:`busy`},m.createElement(v,null,m.createElement(y,{color:`magenta`},m.createElement(w,{type:`dots`})),m.createElement(y,null,` Start your dev server and visit any page…`))),c===`success`&&f&&S&&m.createElement(nt,{adapter:f,site:S}),c===`error`&&m.createElement(s,{title:`Install failed`,state:`err`},m.createElement(y,{color:`red`},T??`Unknown error`)),m.createElement(d,{items:[{key:`q`,label:`quit`},{key:`ctrl-c`,label:`abort`}]}))};async function Z(e,t,a,o){try{o.setPhase(`agent`);let s=await n(),c=await i({strict:!1}),l=new r({config:s,cliVersion:a.cliVersion,token:c}),u=new N({cwd:a.cwd}),d=e.trackingCode??e.id,f=t.buildSnippets({trackingCode:d,siteId:e.id}),p=`Install Zenovay tracking into this ${t.displayName} project.\nSite: ${e.name} (trackingCode: ${d}).\n1. Call detect_framework to confirm the framework.\n2. Read the entry file(s) for this framework:\n`+t.files.map(e=>` - ${e.path}`).join(`
53
+ `)+`
54
+ 3. Insert the tracker using the snippet below.
55
+ 4. Confirm the change with a diff before writing.
56
+
57
+ Snippet reference (anchored by anchorKind — adapt to the actual file):
58
+ `+f.map(e=>` ${e.anchorKind} @ ${e.targetPath}:\n ${e.code.replace(/\n/g,`
59
+ `)}`).join(`
60
+
61
+ `);await Ye({api:l,sandbox:u,approval:{async approve(){return`y`}},prompter:{async ask({question:e}){return e}},initialMessages:[{role:`user`,content:p}]}),await q(a.cwd,{[t.envVarName]:d}),!a.skipMcp&&c&&(o.setPhase(`mcp-install`),await Re({accessToken:c.accessToken}).catch(()=>void 0)),a.skipVerify||(o.setPhase(`verify`),await ze(e.id,Date.now(),{api:l,timeoutMs:12e4}).catch(()=>void 0)),o.setPhase(`success`)}catch(e){o.setErrorMsg(e.message),o.setPhase(`error`)}}const et=()=>m.createElement(s,{title:`Zenovay wants to set up your project`,state:`idle`},m.createElement(y,null,`We'll use AI to analyze your codebase and add the Zenovay tracker.`),m.createElement(y,{color:`gray`},`.env* contents stay on your machine. Only framework metadata reaches api.zenovay.com.`)),tt=({sites:e,preselectHint:t,onPick:n})=>{let r=Q(e,t),i=e.filter(e=>e.id!==r?.id),a=r?[r,...i]:i,o=[...a.map(e=>({label:rt(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 m.createElement(s,{title:t?`Pick a site (best match: ${t})`:`Pick a site`,state:`idle`},m.createElement(Y,{items:o,onSelect:n}))},nt=({adapter:e,site:t})=>m.createElement(s,{title:`You're live on Zenovay`,state:`ok`},m.createElement(y,null,m.createElement(u,null,`✔ Installed successfully`)),m.createElement(y,null,`Framework: `,e.displayName),m.createElement(y,null,`Site: `,t.name,` (`,t.url,`)`),t.trackingCode?m.createElement(y,{color:`gray`},`Tracker code: `,t.trackingCode):null,m.createElement(y,{color:`gray`},`Dashboard: https://app.zenovay.com/sites/`,t.id),m.createElement(y,{color:`gray`},"Next: `zenovay watch` for a live TUI."));function rt(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 it(e){try{let t=await p.readFile(f.join(e,`package.json`),`utf8`),n=JSON.parse(t);if(n.name)return String(n.name).replace(/^@[^/]+\//,``)}catch{}try{let t=await p.readFile(f.join(e,`.git`,`config`),`utf8`),n=t.match(/url\s*=\s*.*[/:]([^/]+?)(?:\.git)?\s*$/m);if(n&&n[1])return n[1]}catch{}return f.basename(e)}function Q(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=>$(e.name).includes(n)||$(e.url).includes(n));return a??e[0]??null}function $(e){return e.toLowerCase().replace(/[^a-z0-9]/g,``)}export{Y as Select,X as initCommand};
@@ -0,0 +1 @@
1
+ import"./emit-CSYxxLsy.js";import"./api-CvIiXSON.js";import"./client-ClN0aAAQ.js";import{initCommand as e}from"./init-BSlCaa_B.js";import"./login-BMG01YNm.js";import"./panel-BIYvFJmj.js";import"./banner-BUAXN9wM.js";import"./keybar-WbLSMIEj.js";export{e as initCommand};
@@ -0,0 +1 @@
1
+ import{useTheme as e}from"./panel-BIYvFJmj.js";import t from"react";import{Box as n,Text as r}from"ink";function i({items:i}){let{theme:a}=e();return t.createElement(n,{flexDirection:`row`},i.map((e,i)=>t.createElement(n,{key:`${e.key}-${i}`,flexDirection:`row`},i>0?t.createElement(r,{color:a.dim},` `):null,t.createElement(r,{color:a.muted},`[`),t.createElement(r,{color:a.accent},e.key),t.createElement(r,{color:a.muted},` `,e.label),t.createElement(r,{color:a.muted},`]`))))}export{i as Keybar};
@@ -0,0 +1 @@
1
+ import{emit as e,isHeadless as t,readConfig as n}from"./emit-CSYxxLsy.js";import{ApiClient as r,pollDeviceToken as i,startDeviceAuthorization as a,writeToken as o}from"./api-CvIiXSON.js";import s from"open";async function c(c){let l=await n(),u=t({json:c.json}),d=await a({apiBase:l.apiBase}),f=d.verification_uri_complete??d.verification_uri;u?e({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 i({apiBase:l.apiBase,deviceCode:d.device_code,interval:d.interval,expiresIn:d.expires_in});await o(m);let h=new r({config:l,cliVersion:c.cliVersion,token:m}),g=await h.me().catch(()=>null);if(g){let e={...m,userId:g.user.id,teamId:g.team?.id,email:g.user.email};await o(e)}return u&&e({type:`done`,command:`login`,exitCode:0}),{email:g?.user.email}}export{c as loginCommand};
@@ -0,0 +1 @@
1
+ import"./emit-CSYxxLsy.js";import"./api-CvIiXSON.js";import{loginCommand as e}from"./login-BMG01YNm.js";export{e as loginCommand};
@@ -0,0 +1,2 @@
1
+ import{emit as e,isHeadless as t,readConfig as n}from"./emit-CSYxxLsy.js";import{ApiClient as r,clearToken as i,readToken as a}from"./api-CvIiXSON.js";async function o(o){let s=await n(),c=await a({strict:!1}).catch(()=>null);if(c)try{let e=new r({config:s,cliVersion:o.cliVersion,token:c});await e.revokeCurrentToken()}catch{}await i(),t({json:o.json})?e({type:`done`,command:`logout`,exitCode:0}):process.stdout.write(`Logged out.
2
+ `)}export{o as logoutCommand};
@@ -0,0 +1 @@
1
+ import e,{createContext as t,useContext as n,useEffect as r,useState as i}from"react";import{Box as a,Text as o}from"ink";const s={name:`mocha`,bg:`#1E1E2E`,fg:`#CDD6F4`,muted:`#6C7086`,accent:`#B4BEFE`,accent2:`#F5C2E7`,success:`#A6E3A1`,warn:`#F9E2AF`,error:`#F38BA8`,border:`#45475A`,dim:`#585B70`},c={from:`#7D56F4`,to:`#FF5FD1`},l=t({theme:s});function u(){return n(l)}function d(e,t){switch(t){case`busy`:return e.accent;case`ok`:return e.success;case`warn`:return e.warn;case`err`:return e.error;case`idle`:default:return e.border}}function f(){let e=process.stdout.columns;return typeof e==`number`&&e>0?e:80}function p({title:t,state:n=`idle`,pillColor:r,width:i,children:s}){let{theme:c}=u(),l=d(c,n),p=i??f(),m=r??l,h=`╭─ `,g=` ─╮`,_=t??``,v=t?_.length:0,y=t?h.length+v+g.length:4,b=Math.max(2,p-y),x=t?e.createElement(a,{flexDirection:`row`},e.createElement(o,{color:l},h),e.createElement(o,{color:m,bold:!0},_),e.createElement(o,{color:l},g,`─`.repeat(b))):e.createElement(a,{flexDirection:`row`},e.createElement(o,{color:l},`╭`,`─`.repeat(Math.max(2,p-2)),`╮`)),S=e.createElement(a,{flexDirection:`row`},e.createElement(o,{color:l},`╰`,`─`.repeat(Math.max(2,p-2)),`╯`));return e.createElement(a,{flexDirection:`column`},x,e.createElement(a,{flexDirection:`row`},e.createElement(o,{color:l},`│ `),e.createElement(a,{flexDirection:`column`,flexGrow:1},s),e.createElement(o,{color:l},` │`)),S)}export{c as BRAND_GRADIENT,p as Panel,u as useTheme};
@@ -0,0 +1 @@
1
+ import{emit as e,isHeadless as t,readConfig as n}from"./emit-CSYxxLsy.js";import{classifyBump as r,fetchManifest as i,platformKey as a}from"./check-0Gyvd8t_.js";import o from"node:path";import{promises as s}from"node:fs";import c from"node:os";import l from"node:crypto";import u from"semver";const d=`AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=`;async function f(e){let t=await i({cliBase:e.cliBase,fetchImpl:e.fetchImpl});return p({...e,manifest:t})}async function p(e){let{manifest:t,currentVersion:n,forceDowngrade:i}=e,o=r(n,t.latest);if(o===`none`)return{updated:!1,from:n,to:t.latest,reason:`already-latest`};if(o===`downgrade`&&!i)return{updated:!1,from:n,to:t.latest,reason:`downgrade-blocked`};t.minSupported&&u.lt(n,t.minSupported);let c=a(),l=t.platforms[c];if(!l)throw Error(`No release binary for platform ${c}`);let d=await m(l,e.fetchImpl);try{return await h(d,l.sha256),await g(l,t.latest,c),await _(d,e.binaryPath),{updated:!0,from:n,to:t.latest}}catch(e){throw await s.unlink(d).catch(()=>void 0),e}}async function m(e,t=fetch){let n=await t(e.url);if(!n.ok)throw Error(`Download failed: ${n.status}`);let r=Buffer.from(await n.arrayBuffer()),i=await s.mkdtemp(o.join(c.tmpdir(),`zenovay-update-`)),a=o.join(i,`zenovay.new`);return await s.writeFile(a,r,{mode:493}),a}async function h(e,t){let n=await s.readFile(e),r=l.createHash(`sha256`).update(n).digest(`hex`);if(r.toLowerCase()!==t.toLowerCase())throw Error(`Checksum mismatch: expected ${t}, got ${r}. Update REFUSED.`)}async function g(e,t,n){let r=Buffer.from(d,`base64`);if(r.length!==32)throw Error(`Invalid release public key — update REFUSED.`);let i=Buffer.from(`${e.sha256}|${t}|${n}`),a=Buffer.from(e.sig,`base64`),o=l.createPublicKey({key:Buffer.concat([Buffer.from(`302a300506032b6570032100`,`hex`),r]),format:`der`,type:`spki`}),s=l.verify(null,i,o,a);if(!s)throw Error(`Signature verification FAILED. Update REFUSED.`)}async function _(e,t){let n=`${t}.old-${Date.now()}`;try{await s.rename(t,n)}catch(e){if(e.code!==`ENOENT`)throw e}try{await s.rename(e,t),process.platform!==`win32`&&await s.chmod(t,493),await s.unlink(n).catch(()=>void 0)}catch(e){try{await s.rename(n,t)}catch{}throw e}}async function v(a){let o=await n(),s=await i({cliBase:o.cliBase}),c=r(a.cliVersion,s.latest);if(a.check)return t({json:a.json})?e({type:`info`,message:`latest=${s.latest} current=${a.cliVersion} bump=${c}`}):process.stdout.write(`Current: ${a.cliVersion}\nLatest: ${s.latest}\nBump: ${c}\n`),{updated:!1,from:a.cliVersion,to:s.latest};if(c===`none`)return t({json:a.json})||process.stdout.write(`Already on latest (${a.cliVersion}).\n`),{updated:!1,from:a.cliVersion,to:s.latest};if(c===`downgrade`&&!a.forceDowngrade)throw Error(`Refused to downgrade ${a.cliVersion} → ${s.latest}. Pass --force-downgrade if you really want this.`);let l=await f({currentVersion:a.cliVersion,cliBase:o.cliBase,binaryPath:a.binaryPath,forceDowngrade:a.forceDowngrade});return t({json:a.json})?l.updated&&e({type:`update.applied`,from:l.from,to:l.to}):l.updated?process.stdout.write(`Updated ${l.from} → ${l.to}\n`):process.stdout.write(`Not updated (${l.reason??`unknown`}).\n`),l}export{v as updateCommand};
@@ -0,0 +1,2 @@
1
+ import{emit as e,isHeadless as t,readConfig as n}from"./emit-CSYxxLsy.js";import{ApiClient as r,readToken as i}from"./api-CvIiXSON.js";import{Panel as a}from"./panel-BIYvFJmj.js";import{Banner as o}from"./banner-BUAXN9wM.js";import{Keybar as s}from"./keybar-WbLSMIEj.js";import c,{useEffect as l,useState as u}from"react";import{Box as d,Text as f,render as p,useApp as m,useInput as h}from"ink";import g from"ink-spinner";async function _(a){let o=await n(),s=await i({strict:!1});if(!s)return process.stderr.write("Not logged in — run `zenovay login` first.\n"),2;let l=new r({config:o,cliVersion:a.cliVersion,token:s}),u=a.siteId??s.teamId;if(!u)return process.stderr.write(`No site selected — pass --site-id.
2
+ `),2;if(t(a)){let t=await l.getDashboardSnapshot(u);return e({type:`watch.tick`,snapshot:t}),0}return new Promise(e=>{let{unmount:t}=p(c.createElement(v,{api:l,siteId:u,intervalMs:a.intervalMs??15e3,cliVersion:a.cliVersion,onExit:n=>{t(),e(n)}}))})}const v=({api:e,siteId:t,intervalMs:n,cliVersion:r,onExit:i})=>{let{exit:p}=m(),[_,v]=u(null),[y,b]=u(!0),[x,S]=u(null);return h(e=>{e===`q`&&(p(),i(0))}),l(()=>{let r=!1,i=async()=>{try{let n=await e.getDashboardSnapshot(t);r||(v(n),b(!1),S(null))}catch(e){r||S(e.message)}};i();let a=setInterval(i,n);return()=>{r=!0,clearInterval(a)}},[e,t,n]),c.createElement(d,{flexDirection:`column`},c.createElement(o,{version:r,subtitle:`watch · ${t}`}),y?c.createElement(a,{title:`Loading`,state:`busy`},c.createElement(d,null,c.createElement(f,{color:`magenta`},c.createElement(g,{type:`dots`})),c.createElement(f,null,` Fetching snapshot…`))):x?c.createElement(a,{title:`Error`,state:`err`},c.createElement(f,{color:`red`},x)):c.createElement(a,{title:`Live snapshot`,state:`ok`},c.createElement(f,{color:`gray`},`Snapshot received. Polling every `,Math.round(n/1e3),`s.`),c.createElement(f,{color:`gray`},`Press q to quit.`)),c.createElement(s,{items:[{key:`q`,label:`quit`},{key:`r`,label:`refresh`}]}))};export{_ as watchCommand};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import(`./init-DvqdybQR.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 ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@zenovay/cli",
3
+ "version": "0.1.0",
4
+ "description": "Zenovay CLI — AI install wizard + full terminal analytics dashboard",
5
+ "type": "module",
6
+ "bin": {
7
+ "zenovay": "dist/bin.js",
8
+ "zv": "dist/bin.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "dev": "tsx src/bin.ts",
17
+ "build": "tsdown",
18
+ "build:binaries": "tsx scripts/build-binaries.ts",
19
+ "typecheck": "tsc --noEmit",
20
+ "lint": "eslint src tests",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "test:all": "vitest run tests/unit tests/integration tests/security",
24
+ "test:release": "vitest run tests/e2e tests/update --testTimeout=600000",
25
+ "test:smoke": "vitest run tests/smoke --testTimeout=60000",
26
+ "test:update-golden": "UPDATE_GOLDEN=1 vitest run tests/e2e",
27
+ "prepare": "husky install"
28
+ },
29
+ "dependencies": {
30
+ "@hono/node-server": "^1.14.0",
31
+ "cac": "^6.7.14",
32
+ "chalk": "^5.4.1",
33
+ "diff": "^7.0.0",
34
+ "globby": "^14.1.0",
35
+ "ink": "^5.1.0",
36
+ "ink-gradient": "^3.0.0",
37
+ "ink-select-input": "^6.0.0",
38
+ "ink-spinner": "^5.0.0",
39
+ "ink-text-input": "^6.0.0",
40
+ "open": "^10.1.0",
41
+ "react": "^18.3.1",
42
+ "semver": "^7.6.3",
43
+ "ulid": "^2.3.0",
44
+ "zod": "^3.23.8"
45
+ },
46
+ "devDependencies": {
47
+ "@aws-sdk/client-s3": "^3.1036.0",
48
+ "@types/diff": "^7.0.0",
49
+ "@types/node": "^22.10.0",
50
+ "@types/react": "^18.3.12",
51
+ "@types/semver": "^7.5.8",
52
+ "@vitest/coverage-v8": "^2.1.8",
53
+ "eslint": "^9.16.0",
54
+ "husky": "^9.1.7",
55
+ "tsdown": "^0.9.0",
56
+ "tsx": "^4.19.2",
57
+ "typescript": "^5.7.2",
58
+ "vitest": "^2.1.8"
59
+ },
60
+ "engines": {
61
+ "node": ">=20.0.0"
62
+ },
63
+ "packageManager": "pnpm@9.15.9",
64
+ "author": "Zenovay <cli@zenovay.com>",
65
+ "license": "SEE LICENSE IN LICENSE",
66
+ "homepage": "https://cli.zenovay.com",
67
+ "repository": {
68
+ "type": "git",
69
+ "url": "https://github.com/zenovay/cli-zenovay.git"
70
+ },
71
+ "bugs": {
72
+ "url": "https://cli.zenovay.com/feedback"
73
+ },
74
+ "keywords": [
75
+ "zenovay",
76
+ "analytics",
77
+ "cli",
78
+ "tui",
79
+ "ai-agent",
80
+ "install-wizard"
81
+ ]
82
+ }