tracwell 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -329,8 +329,10 @@ stores the access and refresh tokens. `login --key`, `--key` on any command, or
329
329
  `TRACWELL_API_KEY` use an API key instead; keys cannot add websites. Credentials
330
330
  live in `$TRACWELL_CONFIG_DIR`, else `$XDG_CONFIG_HOME/tracwell` or
331
331
  `~/.config/tracwell` (`%APPDATA%\tracwell` on Windows), mode 0600. `TRACWELL_URL`
332
- points the CLI at another Tracwell origin. `logout` revokes the OAuth grant and
333
- deletes the file.
332
+ points the CLI at another Tracwell origin. `login` goes to `TRACWELL_URL` or
333
+ `https://tracwell.app`, never the saved login's address, so an old local login
334
+ cannot trap it; other commands use the address of the saved login. `logout`
335
+ revokes the OAuth grant and deletes the file.
334
336
 
335
337
  The CLI is built as a separate Node 20 entry (`dist/npm/cli.js`); the browser
336
338
  and `tracwell/server` entries never import it. `skills/tracwell/SKILL.md` is the
package/dist/npm/cli.js CHANGED
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import{randomUUID as ge}from"node:crypto";var S="0.6.0";function M(e){let t=[],r={};for(let i=0;i<e.length;i+=1){let a=e[i];if(a==="--"){t.push(...e.slice(i+1));break}if(a==="-h"){r.help=!0;continue}if(!a.startsWith("--")){t.push(a);continue}let c=a.indexOf("=");if(c!==-1){r[a.slice(2,c)]=a.slice(c+1);continue}let l=e[i+1];l!==void 0&&!l.startsWith("--")?(r[a.slice(2)]=l,i+=1):r[a.slice(2)]=!0}let[n,...s]=t;return{command:n,positional:s,flags:r}}function q(e){return e.replace(/-([a-z0-9])/gu,(t,r)=>r.toUpperCase())}import{chmod as he,mkdir as ye,readFile as ke,rm as be,writeFile as ve}from"node:fs/promises";import{homedir as Se}from"node:os";import{join as _}from"node:path";var xe="https://tracwell.app";function W(e=process.env){return e.TRACWELL_CONFIG_DIR?e.TRACWELL_CONFIG_DIR:process.platform==="win32"&&e.APPDATA?_(e.APPDATA,"tracwell"):_(e.XDG_CONFIG_HOME||_(Se(),".config"),"tracwell")}function I(){return _(W(),"config.json")}async function m(){try{return JSON.parse(await ke(I(),"utf8"))}catch(e){if(e.code==="ENOENT")return{};throw new Error(`Could not read ${I()}. Delete it and log in again.`,{cause:e})}}async function y(e){await ye(W(),{recursive:!0,mode:448});let t=I();await ve(t,`${JSON.stringify(e,null,2)}
3
- `,{mode:384}),await he(t,384)}async function K(){await be(I(),{force:!0})}function P(e,t){return(e.TRACWELL_URL||t.login?.baseUrl||xe).replace(/\/+$/u,"")}var o=class extends Error{constructor(r,n=1){super(r);this.exitCode=n}exitCode};function B(e,t){try{return JSON.parse(e).error?.message??t}catch{return t}}async function H(e,t,r,n){let s;try{s=await fetch(`${e}/mcp`,{method:"POST",headers:{accept:"application/json, text/event-stream",authorization:`Bearer ${t}`,"content-type":"application/json","user-agent":`tracwell-cli/${S}`},body:JSON.stringify({jsonrpc:"2.0",id:1,method:r,params:n})})}catch{throw new o(`Could not reach ${e}. Check your connection and try again.`)}let i=await s.text();if(s.status===401)throw new o(`${B(i,"Not signed in.")} Run \`tracwell login\`.`,3);if(!s.ok)throw new o(B(i,`Tracwell returned HTTP ${s.status}.`));let a=i.split(`
4
- `).find(l=>l.startsWith("data: "))?.slice(6)??i,c=JSON.parse(a);if(c.error)throw new o(c.error.message??"The request failed.");return c.result}var Te=e=>`${e.name.replaceAll("_"," ")} ${e.title??""} ${e.description??""}`.toLowerCase();function G(e,t){let r=t.toLowerCase().split(/\s+/u).filter(Boolean);if(!r.length)return[...e];let n=s=>r.filter(i=>s.name.toLowerCase().includes(i)).length;return e.filter(s=>r.every(i=>Te(s).includes(i))).toSorted((s,i)=>n(i)-n(s))}async function k(e,t){return(await H(e,t,"tools/list",{})).tools??[]}async function Y(e,t,r,n){return await H(e,t,"tools/call",{name:r,arguments:n})}function _e(e){let t=e.trim();if(!t)throw new o("A step is empty. Use a page like /pricing or an event like event:signup_completed.",2);if(t.startsWith("/"))return{type:"pageview",path:t};let r=t.startsWith("event:")?t.slice(6).trim():t;if(!r)throw new o("`event:` needs an event name, like event:signup_completed.",2);return{type:"event",name:r}}function z(e){let t=typeof e.page=="string"?e.page:void 0,r=typeof e.event=="string"?e.event:void 0;if(t&&r)throw new o("Pass --page or --event, not both.",2);if(t){if(!t.startsWith("/"))throw new o("--page must start with /, like /pricing.",2);return{type:"pageview",path:t}}if(r)return{type:"event",name:r};throw new o("Usage: tracwell goals add <name> --page /pricing (or --event signup_completed)",2)}function Z(e){if(typeof e!="string"||!e.trim())throw new o("Usage: tracwell funnels add <name> --steps /,/pricing,event:checkout_started",2);if(e.trim().startsWith("["))try{return JSON.parse(e)}catch{throw new o("--steps must be a comma list or a JSON array.",2)}let t=e.split(",").map(_e);if(t.length<2)throw new o("A funnel needs at least 2 steps.",2);return t}function L(e,t){return Object.fromEntries(Object.entries(e).filter(([r])=>!t.includes(r)))}import{access as Ie,mkdir as V,readFile as C,writeFile as X}from"node:fs/promises";import{dirname as Q,join as $,relative as Pe}from"node:path";var $e=new URL("../../skills/tracwell/SKILL.md",import.meta.url);function U(){return C($e,"utf8")}var Re=e=>Ie(e).then(()=>!0,()=>!1);async function Ae(e,t,r){let n;try{n=await C(e,"utf8")}catch{}return n===t?"unchanged":n!==void 0&&!r?"skipped":(await V(Q(e),{recursive:!0}),await X(e,t),n===void 0?"created":"updated")}async function N(e,t){let r=await U(),n=[$(e,".agents/skills/tracwell/SKILL.md")];return await Re($(e,".claude"))&&n.push($(e,".claude/skills/tracwell/SKILL.md")),Promise.all(n.map(async s=>({path:Pe(e,s),status:await Ae(s,r,t)})))}var ee="<!-- tracwell:start -->",O="<!-- tracwell:end -->",j=`${ee}
2
+ import{randomUUID as me}from"node:crypto";var S="0.6.1";function q(e){let t=[],r={};for(let i=0;i<e.length;i+=1){let a=e[i];if(a==="--"){t.push(...e.slice(i+1));break}if(a==="-h"){r.help=!0;continue}if(!a.startsWith("--")){t.push(a);continue}let c=a.indexOf("=");if(c!==-1){r[a.slice(2,c)]=a.slice(c+1);continue}let l=e[i+1];l!==void 0&&!l.startsWith("--")?(r[a.slice(2)]=l,i+=1):r[a.slice(2)]=!0}let[n,...s]=t;return{command:n,positional:s,flags:r}}function W(e){return e.replace(/-([a-z0-9])/gu,(t,r)=>r.toUpperCase())}import{chmod as be,mkdir as ve,readFile as Se,rm as xe,writeFile as Te}from"node:fs/promises";import{homedir as _e}from"node:os";import{join as I}from"node:path";var x="https://tracwell.app";function B(e=process.env){return e.TRACWELL_CONFIG_DIR?e.TRACWELL_CONFIG_DIR:process.platform==="win32"&&e.APPDATA?I(e.APPDATA,"tracwell"):I(e.XDG_CONFIG_HOME||I(_e(),".config"),"tracwell")}function P(){return I(B(),"config.json")}async function m(){try{return JSON.parse(await Se(P(),"utf8"))}catch(e){if(e.code==="ENOENT")return{};throw new Error(`Could not read ${P()}. Delete it and log in again.`,{cause:e})}}async function y(e){await ve(B(),{recursive:!0,mode:448});let t=P();await Te(t,`${JSON.stringify(e,null,2)}
3
+ `,{mode:384}),await be(t,384)}async function K(){await xe(P(),{force:!0})}var H=e=>e.replace(/\/+$/u,"");function G(e,t){return H(e.TRACWELL_URL||t.login?.baseUrl||x)}function L(e){return H(e.TRACWELL_URL||x)}var o=class extends Error{constructor(r,n=1){super(r);this.exitCode=n}exitCode};function Y(e,t){try{return JSON.parse(e).error?.message??t}catch{return t}}async function z(e,t,r,n){let s;try{s=await fetch(`${e}/mcp`,{method:"POST",headers:{accept:"application/json, text/event-stream",authorization:`Bearer ${t}`,"content-type":"application/json","user-agent":`tracwell-cli/${S}`},body:JSON.stringify({jsonrpc:"2.0",id:1,method:r,params:n})})}catch{throw new o(e===x?`Could not reach ${e}. Check your connection and try again.`:`Could not reach ${e}, the address of your saved login. Start that server, or run \`tracwell login\` to sign in to ${x}.`)}let i=await s.text();if(s.status===401)throw new o(`${Y(i,"Not signed in.")} Run \`tracwell login\`.`,3);if(!s.ok)throw new o(Y(i,`Tracwell returned HTTP ${s.status}.`));let a=i.split(`
4
+ `).find(l=>l.startsWith("data: "))?.slice(6)??i,c=JSON.parse(a);if(c.error)throw new o(c.error.message??"The request failed.");return c.result}var Ie=e=>`${e.name.replaceAll("_"," ")} ${e.title??""} ${e.description??""}`.toLowerCase();function Z(e,t){let r=t.toLowerCase().split(/\s+/u).filter(Boolean);if(!r.length)return[...e];let n=s=>r.filter(i=>s.name.toLowerCase().includes(i)).length;return e.filter(s=>r.every(i=>Ie(s).includes(i))).toSorted((s,i)=>n(i)-n(s))}async function k(e,t){return(await z(e,t,"tools/list",{})).tools??[]}async function V(e,t,r,n){return await z(e,t,"tools/call",{name:r,arguments:n})}function Pe(e){let t=e.trim();if(!t)throw new o("A step is empty. Use a page like /pricing or an event like event:signup_completed.",2);if(t.startsWith("/"))return{type:"pageview",path:t};let r=t.startsWith("event:")?t.slice(6).trim():t;if(!r)throw new o("`event:` needs an event name, like event:signup_completed.",2);return{type:"event",name:r}}function X(e){let t=typeof e.page=="string"?e.page:void 0,r=typeof e.event=="string"?e.event:void 0;if(t&&r)throw new o("Pass --page or --event, not both.",2);if(t){if(!t.startsWith("/"))throw new o("--page must start with /, like /pricing.",2);return{type:"pageview",path:t}}if(r)return{type:"event",name:r};throw new o("Usage: tracwell goals add <name> --page /pricing (or --event signup_completed)",2)}function Q(e){if(typeof e!="string"||!e.trim())throw new o("Usage: tracwell funnels add <name> --steps /,/pricing,event:checkout_started",2);if(e.trim().startsWith("["))try{return JSON.parse(e)}catch{throw new o("--steps must be a comma list or a JSON array.",2)}let t=e.split(",").map(Pe);if(t.length<2)throw new o("A funnel needs at least 2 steps.",2);return t}function j(e,t){return Object.fromEntries(Object.entries(e).filter(([r])=>!t.includes(r)))}import{access as $e,mkdir as ee,readFile as U,writeFile as te}from"node:fs/promises";import{dirname as ne,join as $,relative as Re}from"node:path";var Ae=new URL("../../skills/tracwell/SKILL.md",import.meta.url);function N(){return U(Ae,"utf8")}var Le=e=>$e(e).then(()=>!0,()=>!1);async function je(e,t,r){let n;try{n=await U(e,"utf8")}catch{}return n===t?"unchanged":n!==void 0&&!r?"skipped":(await ee(ne(e),{recursive:!0}),await te(e,t),n===void 0?"created":"updated")}async function E(e,t){let r=await N(),n=[$(e,".agents/skills/tracwell/SKILL.md")];return await Le($(e,".claude"))&&n.push($(e,".claude/skills/tracwell/SKILL.md")),Promise.all(n.map(async s=>({path:Re(e,s),status:await je(s,r,t)})))}var re="<!-- tracwell:start -->",C="<!-- tracwell:end -->",O=`${re}
5
5
  ## Analytics (Tracwell)
6
6
 
7
7
  This project uses Tracwell for analytics. Use the \`tracwell\` CLI (\`npx tracwell\`)
8
8
  to check the install and read reports: \`npx tracwell verify\`, \`npx tracwell overview\`,
9
9
  and \`npx tracwell help\` for everything else. Before analytics work, read
10
10
  \`.agents/skills/tracwell/SKILL.md\` (or run \`npx tracwell skill\`).
11
- ${O}`;function Le(e){let t=e.indexOf(ee),r=e.indexOf(O,t);return t!==-1&&r!==-1?`${e.slice(0,t)}${j}${e.slice(r+O.length)}`:e.trim()?`${e.replace(/\s*$/u,"")}
11
+ ${C}`;function Oe(e){let t=e.indexOf(re),r=e.indexOf(C,t);return t!==-1&&r!==-1?`${e.slice(0,t)}${O}${e.slice(r+C.length)}`:e.trim()?`${e.replace(/\s*$/u,"")}
12
12
 
13
- ${j}
14
- `:`${j}
15
- `}async function E(e,t="AGENTS.md"){let r=$(e,t),n="";try{n=await C(r,"utf8")}catch{}let s=Le(n);return s===n?{path:t,status:"unchanged"}:(await V(Q(r),{recursive:!0}),await X(r,s),{path:t,status:n?"updated":"created"})}import{spawn as je}from"node:child_process";import{createHash as Oe,randomBytes as te}from"node:crypto";import{createServer as Ce}from"node:http";var ne="analytics:read definitions:write websites:write offline_access",Ue=300*1e3,Ne=60*1e3,F=e=>e.toString("base64url");async function re(e,t){let r=await fetch(e,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams(t).toString()}),n=await r.text();if(!r.ok){let s=`Sign-in failed (HTTP ${r.status}).`;try{let i=JSON.parse(n);s=i.error_description??i.error??s}catch{}throw new o(`${s} Run \`tracwell login\` again.`,3)}return JSON.parse(n)}function se(e,t,r,n){let s=r.refresh_token??n;return{type:"oauth",baseUrl:e,clientId:t,accessToken:r.access_token,...s?{refreshToken:s}:{},expiresAt:Date.now()+(r.expires_in??3600)*1e3}}function Ee(e){let[t,r]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["rundll32",["url.dll,FileProtocolHandler",e]]:["xdg-open",[e]];try{let n=je(t,r,{detached:!0,stdio:"ignore"});n.on("error",()=>{}),n.unref()}catch{}}function Fe(){return new Promise((e,t)=>{let r=Ce();r.once("error",t),r.listen(0,"127.0.0.1",()=>{let n=r.address();n&&typeof n=="object"?e({server:r,port:n.port}):t(new o("Could not start the local sign-in callback."))})})}var De='<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>',Je='<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 7.5v5.5M12 16.5v.01"/></svg>',Me=(e,t,r=!1)=>`<!doctype html>
13
+ ${O}
14
+ `:`${O}
15
+ `}async function F(e,t="AGENTS.md"){let r=$(e,t),n="";try{n=await U(r,"utf8")}catch{}let s=Oe(n);return s===n?{path:t,status:"unchanged"}:(await ee(ne(r),{recursive:!0}),await te(r,s),{path:t,status:n?"updated":"created"})}import{spawn as Ce}from"node:child_process";import{createHash as Ue,randomBytes as se}from"node:crypto";import{createServer as Ne}from"node:http";var ie="analytics:read definitions:write websites:write offline_access",Ee=300*1e3,Fe=60*1e3,D=e=>e.toString("base64url");async function oe(e,t){let r=await fetch(e,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams(t).toString()}),n=await r.text();if(!r.ok){let s=`Sign-in failed (HTTP ${r.status}).`;try{let i=JSON.parse(n);s=i.error_description??i.error??s}catch{}throw new o(`${s} Run \`tracwell login\` again.`,3)}return JSON.parse(n)}function ae(e,t,r,n){let s=r.refresh_token??n;return{type:"oauth",baseUrl:e,clientId:t,accessToken:r.access_token,...s?{refreshToken:s}:{},expiresAt:Date.now()+(r.expires_in??3600)*1e3}}function De(e){let[t,r]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["rundll32",["url.dll,FileProtocolHandler",e]]:["xdg-open",[e]];try{let n=Ce(t,r,{detached:!0,stdio:"ignore"});n.on("error",()=>{}),n.unref()}catch{}}function Je(){return new Promise((e,t)=>{let r=Ne();r.once("error",t),r.listen(0,"127.0.0.1",()=>{let n=r.address();n&&typeof n=="object"?e({server:r,port:n.port}):t(new o("Could not start the local sign-in callback."))})})}var Me='<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>',qe='<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M12 7.5v5.5M12 16.5v.01"/></svg>',We=(e,t,r=!1)=>`<!doctype html>
16
16
  <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${e} \xB7 Tracwell</title>
17
17
  <style>
18
18
  :root{--bg:#fff;--fg:#111;--muted:#737373;--shell:#f5f5f5;--card:#fff;--line:#e5e5e5;--chip:#f0f0f0;--blue:#357dff}
@@ -29,8 +29,8 @@ p{margin:6px 0 0;color:var(--muted)}
29
29
  code{font:12px ui-monospace,SFMono-Regular,Menlo,monospace;background:var(--chip);padding:2px 6px;border-radius:6px;color:var(--fg);white-space:nowrap}
30
30
  @media (prefers-reduced-motion:no-preference){.card{animation:in .3s cubic-bezier(.16,1,.3,1) both}@keyframes in{from{opacity:0;transform:translateY(4px)}}}
31
31
  </style></head>
32
- <body><main class="shell${r?" ok":""}"><div class="card"><div class="icon">${r?De:Je}</div><h1>${e}</h1><p>${t}</p></div>
33
- <p class="foot">Tracwell CLI</p></main></body></html>`;function qe(e,t){return new Promise((r,n)=>{let s=setTimeout(()=>n(new o("Sign-in timed out. Run `tracwell login` again.",3)),Ue);e.on("request",(i,a)=>{let c=new URL(i.url??"/","http://127.0.0.1");if(c.pathname!=="/callback"){a.writeHead(404).end();return}let l=c.searchParams.get("error"),d=c.searchParams.get("code"),p=(T,v,h,w=!1)=>a.writeHead(T,{"content-type":"text/html; charset=utf-8"}).end(Me(v,h,w));if(c.searchParams.get("state")!==t){p(400,"This sign-in link is out of date","Go back to your terminal and run <code>tracwell login</code> again.");return}if(clearTimeout(s),l||!d){p(400,"Sign-in was cancelled","Nothing was connected. Run <code>tracwell login</code> to try again."),n(new o(c.searchParams.get("error_description")??"Sign-in was cancelled.",3));return}p(200,"You're signed in","The Tracwell CLI is connected. You can close this tab and go back to your terminal.",!0),r(d)})})}async function ie(e,t){let{server:r,port:n}=await Fe();try{let s=`http://127.0.0.1:${n}/callback`,i=await fetch(`${e}/api/auth/oauth2/register`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({client_name:"Tracwell CLI",redirect_uris:[s],token_endpoint_auth_method:"none",grant_types:["authorization_code","refresh_token"],response_types:["code"],scope:ne})}).catch(()=>{throw new o(`Could not reach ${e}. Check your connection and try again.`)});if(!i.ok)throw new o(`Could not start sign-in (HTTP ${i.status}).`,3);let{client_id:a}=await i.json(),c=F(te(32)),l=F(te(16)),d=new URL("/api/auth/oauth2/authorize",e);for(let[v,h]of Object.entries({client_id:a,redirect_uri:s,response_type:"code",scope:ne,state:l,code_challenge:F(Oe("sha256").update(c).digest()),code_challenge_method:"S256",resource:`${e}/mcp`}))d.searchParams.set(v,h);t.print("Sign in and pick your websites in the browser:"),t.print(d.href),t.open&&Ee(d.href);let p=await qe(r,l),T=await re(`${e}/api/auth/oauth2/token`,{grant_type:"authorization_code",code:p,client_id:a,redirect_uri:s,code_verifier:c,resource:`${e}/mcp`});return se(e,a,T)}finally{r.close()}}function oe(e,t=Date.now()){return e.expiresAt-Ne<=t}async function ae(e){if(!e.refreshToken)throw new o("Your sign-in expired. Run `tracwell login`.",3);let t=await re(`${e.baseUrl}/api/auth/oauth2/token`,{grant_type:"refresh_token",refresh_token:e.refreshToken,client_id:e.clientId,resource:`${e.baseUrl}/mcp`});return se(e.baseUrl,e.clientId,t,e.refreshToken)}async function ce(e){let t=e.refreshToken??e.accessToken;await fetch(`${e.baseUrl}/api/auth/oauth2/revoke`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({token:t,client_id:e.clientId}).toString()}).catch(()=>{})}var We=new Set(["json","site","days","compact","key","help","no-browser","dry-run","force"]),R=7,le=e=>e.toISOString().slice(0,10);function Ke(e,t){let r=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate()-(e-1)));return{from:le(r),to:le(t)}}function Be(e,t,r,n,s){let i="from"in e&&"to"in e,a=!i&&"date"in e;if(!i&&!a)return{};if(r.from!==void 0||r.to!==void 0||r.date!==void 0)return{};if(!(t.has("from")||t.has("date")||n.days!==void 0))return{};let c=n.days===void 0?R:Number(n.days);if(!Number.isInteger(c)||c<1)throw new o("--days must be a whole number of 1 or more.",2);let{from:l,to:d}=Ke(c,s);if(a)return{date:l,..."to"in e?{to:d}:{}};if(e.from?.format==="date-time"){let p=new Date(`${d}T00:00:00.000Z`);return p.setUTCDate(p.getUTCDate()+1),{from:`${l}T00:00:00.000Z`,to:p.toISOString()}}return{from:l,to:d}}function He(e){return e?.type?Array.isArray(e.type)?e.type:[e.type]:[]}function Ge(e,t,r){let n=He(r);if(t===!0){if(n.length&&!n.includes("boolean"))throw new o(`--${e} needs a value.`,2);return!0}if(n.includes("number")||n.includes("integer")){let s=Number(t);if(Number.isNaN(s))throw new o(`--${e} must be a number.`,2);return s}if(n.includes("boolean"))return t==="true";if(!n.length&&/^\s*[[{]/u.test(t))try{return JSON.parse(t)}catch{throw new o(`--${e} must be JSON.`,2)}if(n.includes("object")||n.includes("array"))try{return JSON.parse(t)}catch{throw new o(`--${e} must be JSON.`,2)}return t}function de(e,t,r){let n=e.inputSchema?.properties??{},s=new Set(e.inputSchema?.required??[]),i={};if(typeof t.json=="string")try{i=JSON.parse(t.json)}catch{throw new o("--json must be a JSON object.",2)}for(let[c,l]of Object.entries(t)){if(We.has(c))continue;let d=q(c);i[d]=Ge(c,l,n[d])}let a=typeof t.site=="string"?t.site:r.site;return"projectId"in n&&i.projectId===void 0&&a&&(i.projectId=a),Object.assign(i,Be(n,s,i,t,r.today)),"requestId"in n&&i.requestId===void 0&&(i.requestId=r.requestId()),i}var J={sites:{tool:"analytics_projects",summary:"List the websites you can read"},snippet:{tool:"install_snippet",summary:"Get the install snippet for a website"},verify:{tool:"install_verify",summary:"Check whether a website is receiving events"},overview:{tool:"analytics_overview",summary:"Traffic overview"},acquisition:{tool:"analytics_acquisition",summary:"Sources, referrers and campaigns"},revenue:{tool:"analytics_revenue",summary:"Revenue and where it came from"},events:{tool:"analytics_events",summary:"Event names and counts"},journeys:{tool:"analytics_journeys",summary:"Recorded sessions"},goals:{tool:"analytics_goals",summary:"Saved goals"},goal:{tool:"analytics_goal",summary:"One goal's report (--goal-id)"},funnels:{tool:"analytics_funnels",summary:"Saved funnels"},funnel:{tool:"analytics_funnel",summary:"One funnel's report (--funnel-id)"},notes:{tool:"notes_list",summary:"Chart notes"}},ue=`tracwell ${S} - Tracwell analytics from your terminal or coding agent
32
+ <body><main class="shell${r?" ok":""}"><div class="card"><div class="icon">${r?Me:qe}</div><h1>${e}</h1><p>${t}</p></div>
33
+ <p class="foot">Tracwell CLI</p></main></body></html>`;function Be(e,t){return new Promise((r,n)=>{let s=setTimeout(()=>n(new o("Sign-in timed out. Run `tracwell login` again.",3)),Ee);e.on("request",(i,a)=>{let c=new URL(i.url??"/","http://127.0.0.1");if(c.pathname!=="/callback"){a.writeHead(404).end();return}let l=c.searchParams.get("error"),d=c.searchParams.get("code"),p=(_,v,h,w=!1)=>a.writeHead(_,{"content-type":"text/html; charset=utf-8"}).end(We(v,h,w));if(c.searchParams.get("state")!==t){p(400,"This sign-in link is out of date","Go back to your terminal and run <code>tracwell login</code> again.");return}if(clearTimeout(s),l||!d){p(400,"Sign-in was cancelled","Nothing was connected. Run <code>tracwell login</code> to try again."),n(new o(c.searchParams.get("error_description")??"Sign-in was cancelled.",3));return}p(200,"You're signed in","The Tracwell CLI is connected. You can close this tab and go back to your terminal.",!0),r(d)})})}async function ce(e,t){let{server:r,port:n}=await Je();try{let s=`http://127.0.0.1:${n}/callback`,i=await fetch(`${e}/api/auth/oauth2/register`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({client_name:"Tracwell CLI",redirect_uris:[s],token_endpoint_auth_method:"none",grant_types:["authorization_code","refresh_token"],response_types:["code"],scope:ie})}).catch(()=>{throw new o(`Could not reach ${e}. Check your connection and try again.`)});if(!i.ok)throw new o(`Could not start sign-in (HTTP ${i.status}).`,3);let{client_id:a}=await i.json(),c=D(se(32)),l=D(se(16)),d=new URL("/api/auth/oauth2/authorize",e);for(let[v,h]of Object.entries({client_id:a,redirect_uri:s,response_type:"code",scope:ie,state:l,code_challenge:D(Ue("sha256").update(c).digest()),code_challenge_method:"S256",resource:`${e}/mcp`}))d.searchParams.set(v,h);t.print("Sign in and pick your websites in the browser:"),t.print(d.href),t.open&&De(d.href);let p=await Be(r,l),_=await oe(`${e}/api/auth/oauth2/token`,{grant_type:"authorization_code",code:p,client_id:a,redirect_uri:s,code_verifier:c,resource:`${e}/mcp`});return ae(e,a,_)}finally{r.close()}}function le(e,t=Date.now()){return e.expiresAt-Fe<=t}async function de(e){if(!e.refreshToken)throw new o("Your sign-in expired. Run `tracwell login`.",3);let t=await oe(`${e.baseUrl}/api/auth/oauth2/token`,{grant_type:"refresh_token",refresh_token:e.refreshToken,client_id:e.clientId,resource:`${e.baseUrl}/mcp`});return ae(e.baseUrl,e.clientId,t,e.refreshToken)}async function ue(e){let t=e.refreshToken??e.accessToken;await fetch(`${e.baseUrl}/api/auth/oauth2/revoke`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({token:t,client_id:e.clientId}).toString()}).catch(()=>{})}var Ke=new Set(["json","site","days","compact","key","help","no-browser","dry-run","force"]),R=7,pe=e=>e.toISOString().slice(0,10);function He(e,t){let r=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate()-(e-1)));return{from:pe(r),to:pe(t)}}function Ge(e,t,r,n,s){let i="from"in e&&"to"in e,a=!i&&"date"in e;if(!i&&!a)return{};if(r.from!==void 0||r.to!==void 0||r.date!==void 0)return{};if(!(t.has("from")||t.has("date")||n.days!==void 0))return{};let c=n.days===void 0?R:Number(n.days);if(!Number.isInteger(c)||c<1)throw new o("--days must be a whole number of 1 or more.",2);let{from:l,to:d}=He(c,s);if(a)return{date:l,..."to"in e?{to:d}:{}};if(e.from?.format==="date-time"){let p=new Date(`${d}T00:00:00.000Z`);return p.setUTCDate(p.getUTCDate()+1),{from:`${l}T00:00:00.000Z`,to:p.toISOString()}}return{from:l,to:d}}function Ye(e){return e?.type?Array.isArray(e.type)?e.type:[e.type]:[]}function ze(e,t,r){let n=Ye(r);if(t===!0){if(n.length&&!n.includes("boolean"))throw new o(`--${e} needs a value.`,2);return!0}if(n.includes("number")||n.includes("integer")){let s=Number(t);if(Number.isNaN(s))throw new o(`--${e} must be a number.`,2);return s}if(n.includes("boolean"))return t==="true";if(!n.length&&/^\s*[[{]/u.test(t))try{return JSON.parse(t)}catch{throw new o(`--${e} must be JSON.`,2)}if(n.includes("object")||n.includes("array"))try{return JSON.parse(t)}catch{throw new o(`--${e} must be JSON.`,2)}return t}function ge(e,t,r){let n=e.inputSchema?.properties??{},s=new Set(e.inputSchema?.required??[]),i={};if(typeof t.json=="string")try{i=JSON.parse(t.json)}catch{throw new o("--json must be a JSON object.",2)}for(let[c,l]of Object.entries(t)){if(Ke.has(c))continue;let d=W(c);i[d]=ze(c,l,n[d])}let a=typeof t.site=="string"?t.site:r.site;return"projectId"in n&&i.projectId===void 0&&a&&(i.projectId=a),Object.assign(i,Ge(n,s,i,t,r.today)),"requestId"in n&&i.requestId===void 0&&(i.requestId=r.requestId()),i}var M={sites:{tool:"analytics_projects",summary:"List the websites you can read"},snippet:{tool:"install_snippet",summary:"Get the install snippet for a website"},verify:{tool:"install_verify",summary:"Check whether a website is receiving events"},overview:{tool:"analytics_overview",summary:"Traffic overview"},acquisition:{tool:"analytics_acquisition",summary:"Sources, referrers and campaigns"},revenue:{tool:"analytics_revenue",summary:"Revenue and where it came from"},events:{tool:"analytics_events",summary:"Event names and counts"},journeys:{tool:"analytics_journeys",summary:"Recorded sessions"},goals:{tool:"analytics_goals",summary:"Saved goals"},goal:{tool:"analytics_goal",summary:"One goal's report (--goal-id)"},funnels:{tool:"analytics_funnels",summary:"Saved funnels"},funnel:{tool:"analytics_funnel",summary:"One funnel's report (--funnel-id)"},notes:{tool:"notes_list",summary:"Chart notes"}},fe=`tracwell ${S} - Tracwell analytics from your terminal or coding agent
34
34
 
35
35
  Start here
36
36
  tracwell init [--url <url>] [--name <name>]
@@ -48,7 +48,7 @@ Setup
48
48
  tracwell logout
49
49
 
50
50
  Reports (default: last ${R} days, UTC)
51
- ${Object.entries(J).filter(([e])=>!["sites","snippet","verify"].includes(e)).map(([e,{summary:t}])=>` tracwell ${e.padEnd(22)} ${t}`).join(`
51
+ ${Object.entries(M).filter(([e])=>!["sites","snippet","verify"].includes(e)).map(([e,{summary:t}])=>` tracwell ${e.padEnd(22)} ${t}`).join(`
52
52
  `)}
53
53
 
54
54
  Goals and funnels (need "Manage goals and funnels")
@@ -77,8 +77,8 @@ Common flags
77
77
 
78
78
  Output is JSON. Exit codes: 0 ok, 1 request failed, 2 bad input, 3 sign in needed.`,b=e=>{process.stdout.write(`${e}
79
79
  `)},A=e=>{process.stderr.write(`${e}
80
- `)};function f(e,t){b(typeof e=="string"?e:JSON.stringify(e,null,t.compact?void 0:2))}function Ye(e){if(e.structuredContent!==void 0)return e.structuredContent;let t=(e.content??[]).filter(r=>r.type==="text").map(r=>r.text??"").join(`
81
- `);try{return JSON.parse(t)}catch{return t}}async function u(e){let t=await m(),r=P(process.env,t),n=typeof e.key=="string"?e.key:process.env.TRACWELL_API_KEY;if(n)return{baseUrl:r,token:n,settings:t,viaKey:!0};let s=t.login;if(!s||s.baseUrl!==r)throw new o("Not signed in. Run `tracwell login`, or set TRACWELL_API_KEY.",3);if(s.type==="key")return{baseUrl:r,token:s.key,settings:t,viaKey:!0};if(!oe(s))return{baseUrl:r,token:s.accessToken,settings:t,viaKey:!1};let i=await ae(s),a={...t,login:i};return await y(a),{baseUrl:r,token:i.accessToken,settings:a,viaKey:!1}}async function pe(e){let t=await m(),r=await ie(P(process.env,t),{open:!e["no-browser"],print:A});await y({...t,login:r})}async function fe(e,t){let r=(await k(e.baseUrl,e.token)).find(n=>n.name===t);if(!r)throw new o(`This login cannot use \`${t}\`. Run \`tracwell tools\` to see what it can use.`,2);return r}async function x(e,t,r){let n=await Y(e.baseUrl,e.token,t,r),s=Ye(n);if(n.isError)throw new o(typeof s=="string"?s:JSON.stringify(s));return s}async function g(e,t,r,n={}){let s=await fe(e,t),i={...de(s,r,{...e.settings.site?{site:e.settings.site}:{},today:new Date,requestId:ge}),...n};if(r["dry-run"])return f({dryRun:!0,tool:t,writes:s.annotations?.readOnlyHint===!1,arguments:i},r);f(await x(e,t,i),r)}function ze(e,t){let r=e.inputSchema?.properties??{},n=new Set(e.inputSchema?.required??[]),s=Object.entries(r).map(([i,a])=>{let c=Array.isArray(a.type)?a.type.join("|"):a.type??"value",l=i==="projectId"?" (or --site, or `tracwell use`)":i==="requestId"?" (filled in for you)":i==="from"||i==="to"||i==="date"?` (or --days; default last ${R} days)`:"";return` --${i.padEnd(18)} ${c}${n.has(i)?", required":""}${l}${a.description?`
80
+ `)};function f(e,t){b(typeof e=="string"?e:JSON.stringify(e,null,t.compact?void 0:2))}function Ze(e){if(e.structuredContent!==void 0)return e.structuredContent;let t=(e.content??[]).filter(r=>r.type==="text").map(r=>r.text??"").join(`
81
+ `);try{return JSON.parse(t)}catch{return t}}async function u(e){let t=await m(),r=G(process.env,t),n=typeof e.key=="string"?e.key:process.env.TRACWELL_API_KEY;if(n)return{baseUrl:r,token:n,settings:t,viaKey:!0};let s=t.login;if(!s||s.baseUrl!==r)throw new o("Not signed in. Run `tracwell login`, or set TRACWELL_API_KEY.",3);if(s.type==="key")return{baseUrl:r,token:s.key,settings:t,viaKey:!0};if(!le(s))return{baseUrl:r,token:s.accessToken,settings:t,viaKey:!1};let i=await de(s),a={...t,login:i};return await y(a),{baseUrl:r,token:i.accessToken,settings:a,viaKey:!1}}async function we(e){let t=await m(),r=await ce(L(process.env),{open:!e["no-browser"],print:A});await y({...t,login:r})}async function he(e,t){let r=(await k(e.baseUrl,e.token)).find(n=>n.name===t);if(!r)throw new o(`This login cannot use \`${t}\`. Run \`tracwell tools\` to see what it can use.`,2);return r}async function T(e,t,r){let n=await V(e.baseUrl,e.token,t,r),s=Ze(n);if(n.isError)throw new o(typeof s=="string"?s:JSON.stringify(s));return s}async function g(e,t,r,n={}){let s=await he(e,t),i={...ge(s,r,{...e.settings.site?{site:e.settings.site}:{},today:new Date,requestId:me}),...n};if(r["dry-run"])return f({dryRun:!0,tool:t,writes:s.annotations?.readOnlyHint===!1,arguments:i},r);f(await T(e,t,i),r)}function Ve(e,t){let r=e.inputSchema?.properties??{},n=new Set(e.inputSchema?.required??[]),s=Object.entries(r).map(([i,a])=>{let c=Array.isArray(a.type)?a.type.join("|"):a.type??"value",l=i==="projectId"?" (or --site, or `tracwell use`)":i==="requestId"?" (filled in for you)":i==="from"||i==="to"||i==="date"?` (or --days; default last ${R} days)`:"";return` --${i.padEnd(18)} ${c}${n.has(i)?", required":""}${l}${a.description?`
82
82
  ${a.description}`:""}`});return[`tracwell ${t} (${e.name})`,"",e.description??"","","Arguments",...s].join(`
83
- `)}async function Ze(e,t){let r=async l=>await x(e,"install_snippet",{projectId:l});if(typeof t.site=="string")return{installation:await r(t.site),created:!1};let{projects:n}=await x(e,"analytics_projects",{limit:100}),s=typeof t.url=="string"?t.url:void 0;if(s){let l;try{l=new URL(s).origin}catch{throw new o("--url must be a full address, like https://acme.com.",2)}let p=(await Promise.all(n.map(w=>r(w.id)))).find(w=>w.origins.some(we=>new URL(we).origin===l));if(p)return{installation:p,created:!1};if(!(await k(e.baseUrl,e.token)).some(w=>w.name==="website_create"))throw new o(e.viaKey?'API keys cannot add websites. Run `tracwell login` and tick "Add websites", or add it in the dashboard and pass --site.':'This login cannot add websites. Run `tracwell login` again and tick "Add websites".',3);let v=typeof t.name=="string"?t.name:new URL(s).hostname.replace(/^www\./u,""),h=await x(e,"website_create",{requestId:ge(),name:v,websiteUrl:s});return{installation:h.website,created:h.created}}let i=n.find(l=>l.id===e.settings.site),a=n.length===1?n[0]:void 0,c=i??a;if(c)return{installation:await r(c.id),created:!1};throw n.length?new o(`Several websites: ${n.map(l=>`${l.name} (${l.id})`).join(", ")}. Pass --site <id> or --url <url>.`,2):new o("No websites yet. Run `tracwell init --url https://your-site.com` to add one.",2)}function D(e){return e.map(t=>`${t.path}: ${t.status==="skipped"?"exists and differs; rerun with --force to replace":t.status}`)}async function Ve(e){let{command:t,positional:r,flags:n}=M(e);if(n.version||t==="version")return b(S);if(!t||t==="help"&&!r[0])return b(ue);if(t==="help"||n.help&&!["skill","agents-md","init","search"].includes(t)){let s=t==="help"?r:[t,...r],i=s[0],a={goals:"goal_create",funnels:"funnel_create",sites:"website_create"},c=(s[1]==="add"?a[i]:void 0)??J[i]?.tool??(i==="call"?s[1]:i);if(!c)return b(ue);let l=await u(n),d=s[1]==="add"&&a[i]?`${i} add`:i;return b(ze(await fe(l,c),d))}switch(t){case"login":{if(typeof n.key=="string"){let s=await m(),i=P(process.env,s);await k(i,n.key),await y({...s,login:{type:"key",baseUrl:i,key:n.key}})}else await pe(n);A("Signed in. Run `tracwell sites` to see your websites.");return}case"logout":{let s=await m();s.login?.type==="oauth"&&await ce(s.login),await K(),A("Signed out.");return}case"use":{let s=r[0];if(!s)throw new o("Usage: tracwell use <website-id>. Run `tracwell sites` for IDs.",2);let i=await m();await y({...i,site:s}),A(`Default website set to ${s}.`);return}case"whoami":{let s=await u(n);return f({baseUrl:s.baseUrl,login:s.viaKey?"api key":"browser",defaultSite:s.settings.site??null,websites:await x(s,"analytics_projects",{})},n)}case"tools":{let s=await u(n),i=await k(s.baseUrl,s.token);return f(i.map(a=>({name:a.name,description:a.description,writes:a.annotations?.readOnlyHint===!1})),n)}case"search":{let s=r.join(" ");if(!s)throw new o("Usage: tracwell search <words>",2);let i=await u(n),a=G(await k(i.baseUrl,i.token),s);return f(a.map(c=>({name:c.name,description:c.description,writes:c.annotations?.readOnlyHint===!1})),n)}case"call":{let s=r[0];if(!s)throw new o("Usage: tracwell call <tool> [--arg value ...]. Run `tracwell tools` for names.",2);return g(await u(n),s,n)}case"goals":{if(r[0]!=="add")return g(await u(n),"analytics_goals",n);let s=r[1];if(!s)throw new o("Usage: tracwell goals add <name> --page /pricing (or --event signup_completed)",2);let i=z(n);return g(await u(n),"goal_create",L(n,["page","event"]),{name:s,match:i,...n.category===void 0?{category:"other"}:{}})}case"funnels":{if(r[0]!=="add")return g(await u(n),"analytics_funnels",n);let s=r[1];if(!s)throw new o("Usage: tracwell funnels add <name> --steps /,/pricing,event:checkout_started",2);let i=Z(n.steps),a=n["window-hours"]!==void 0||n.windowHours!==void 0;return g(await u(n),"funnel_create",L(n,["steps"]),{name:s,steps:i,...a?{}:{windowHours:24}})}case"sites":{if(r[0]==="add"){let[,s,i]=r;if(!s||!i)throw new o("Usage: tracwell sites add <name> <url>",2);return g(await u(n),"website_create",n,{name:s,websiteUrl:i})}return g(await u(n),"analytics_projects",n)}case"skill":{if(!r[0])return b(await U());if(r[0]!=="install")throw new o("Usage: tracwell skill [install [--force]]",2);return f({installed:D(await N(process.cwd(),!!n.force))},n)}case"agents-md":{if(r[0]!=="install")throw new o("Usage: tracwell agents-md install [--file AGENTS.md]",2);let s=typeof n.file=="string"?n.file:"AGENTS.md";return f({installed:D([await E(process.cwd(),s)])},n)}case"init":{let s;try{s=await u(n)}catch(l){if(!(l instanceof o)||l.exitCode!==3)throw l;await pe(n),s=await u(n)}let{installation:i,created:a}=await Ze(s,n);await y({...await m(),site:i.projectId});let c=[...await N(process.cwd(),!!n.force),await E(process.cwd())];return f({website:{id:i.projectId,name:i.name,origins:i.origins,created:a},snippet:{script:i.script,npm:i.npm,placement:i.placement},installed:D(c),next:"Add the script tag (or the npm setup) to the site, load a page, then run `tracwell verify`."},n)}default:{let s=J[t];if(!s)throw new o(`Unknown command \`${t}\`. Run \`tracwell help\`.`,2);return g(await u(n),s.tool,n)}}}Ve(process.argv.slice(2)).catch(e=>{let t=e instanceof o;process.stderr.write(`tracwell: ${t||e instanceof Error?e.message:String(e)}
83
+ `)}async function Xe(e,t){let r=async l=>await T(e,"install_snippet",{projectId:l});if(typeof t.site=="string")return{installation:await r(t.site),created:!1};let{projects:n}=await T(e,"analytics_projects",{limit:100}),s=typeof t.url=="string"?t.url:void 0;if(s){let l;try{l=new URL(s).origin}catch{throw new o("--url must be a full address, like https://acme.com.",2)}let p=(await Promise.all(n.map(w=>r(w.id)))).find(w=>w.origins.some(ye=>new URL(ye).origin===l));if(p)return{installation:p,created:!1};if(!(await k(e.baseUrl,e.token)).some(w=>w.name==="website_create"))throw new o(e.viaKey?'API keys cannot add websites. Run `tracwell login` and tick "Add websites", or add it in the dashboard and pass --site.':'This login cannot add websites. Run `tracwell login` again and tick "Add websites".',3);let v=typeof t.name=="string"?t.name:new URL(s).hostname.replace(/^www\./u,""),h=await T(e,"website_create",{requestId:me(),name:v,websiteUrl:s});return{installation:h.website,created:h.created}}let i=n.find(l=>l.id===e.settings.site),a=n.length===1?n[0]:void 0,c=i??a;if(c)return{installation:await r(c.id),created:!1};throw n.length?new o(`Several websites: ${n.map(l=>`${l.name} (${l.id})`).join(", ")}. Pass --site <id> or --url <url>.`,2):new o("No websites yet. Run `tracwell init --url https://your-site.com` to add one.",2)}function J(e){return e.map(t=>`${t.path}: ${t.status==="skipped"?"exists and differs; rerun with --force to replace":t.status}`)}async function Qe(e){let{command:t,positional:r,flags:n}=q(e);if(n.version||t==="version")return b(S);if(!t||t==="help"&&!r[0])return b(fe);if(t==="help"||n.help&&!["skill","agents-md","init","search"].includes(t)){let s=t==="help"?r:[t,...r],i=s[0],a={goals:"goal_create",funnels:"funnel_create",sites:"website_create"},c=(s[1]==="add"?a[i]:void 0)??M[i]?.tool??(i==="call"?s[1]:i);if(!c)return b(fe);let l=await u(n),d=s[1]==="add"&&a[i]?`${i} add`:i;return b(Ve(await he(l,c),d))}switch(t){case"login":{if(typeof n.key=="string"){let s=await m(),i=L(process.env);await k(i,n.key),await y({...s,login:{type:"key",baseUrl:i,key:n.key}})}else await we(n);A("Signed in. Run `tracwell sites` to see your websites.");return}case"logout":{let s=await m();s.login?.type==="oauth"&&await ue(s.login),await K(),A("Signed out.");return}case"use":{let s=r[0];if(!s)throw new o("Usage: tracwell use <website-id>. Run `tracwell sites` for IDs.",2);let i=await m();await y({...i,site:s}),A(`Default website set to ${s}.`);return}case"whoami":{let s=await u(n);return f({baseUrl:s.baseUrl,login:s.viaKey?"api key":"browser",defaultSite:s.settings.site??null,websites:await T(s,"analytics_projects",{})},n)}case"tools":{let s=await u(n),i=await k(s.baseUrl,s.token);return f(i.map(a=>({name:a.name,description:a.description,writes:a.annotations?.readOnlyHint===!1})),n)}case"search":{let s=r.join(" ");if(!s)throw new o("Usage: tracwell search <words>",2);let i=await u(n),a=Z(await k(i.baseUrl,i.token),s);return f(a.map(c=>({name:c.name,description:c.description,writes:c.annotations?.readOnlyHint===!1})),n)}case"call":{let s=r[0];if(!s)throw new o("Usage: tracwell call <tool> [--arg value ...]. Run `tracwell tools` for names.",2);return g(await u(n),s,n)}case"goals":{if(r[0]!=="add")return g(await u(n),"analytics_goals",n);let s=r[1];if(!s)throw new o("Usage: tracwell goals add <name> --page /pricing (or --event signup_completed)",2);let i=X(n);return g(await u(n),"goal_create",j(n,["page","event"]),{name:s,match:i,...n.category===void 0?{category:"other"}:{}})}case"funnels":{if(r[0]!=="add")return g(await u(n),"analytics_funnels",n);let s=r[1];if(!s)throw new o("Usage: tracwell funnels add <name> --steps /,/pricing,event:checkout_started",2);let i=Q(n.steps),a=n["window-hours"]!==void 0||n.windowHours!==void 0;return g(await u(n),"funnel_create",j(n,["steps"]),{name:s,steps:i,...a?{}:{windowHours:24}})}case"sites":{if(r[0]==="add"){let[,s,i]=r;if(!s||!i)throw new o("Usage: tracwell sites add <name> <url>",2);return g(await u(n),"website_create",n,{name:s,websiteUrl:i})}return g(await u(n),"analytics_projects",n)}case"skill":{if(!r[0])return b(await N());if(r[0]!=="install")throw new o("Usage: tracwell skill [install [--force]]",2);return f({installed:J(await E(process.cwd(),!!n.force))},n)}case"agents-md":{if(r[0]!=="install")throw new o("Usage: tracwell agents-md install [--file AGENTS.md]",2);let s=typeof n.file=="string"?n.file:"AGENTS.md";return f({installed:J([await F(process.cwd(),s)])},n)}case"init":{let s;try{s=await u(n)}catch(l){if(!(l instanceof o)||l.exitCode!==3)throw l;await we(n),s=await u(n)}let{installation:i,created:a}=await Xe(s,n);await y({...await m(),site:i.projectId});let c=[...await E(process.cwd(),!!n.force),await F(process.cwd())];return f({website:{id:i.projectId,name:i.name,origins:i.origins,created:a},snippet:{script:i.script,npm:i.npm,placement:i.placement},installed:J(c),next:"Add the script tag (or the npm setup) to the site, load a page, then run `tracwell verify`."},n)}default:{let s=M[t];if(!s)throw new o(`Unknown command \`${t}\`. Run \`tracwell help\`.`,2);return g(await u(n),s.tool,n)}}}Qe(process.argv.slice(2)).catch(e=>{let t=e instanceof o;process.stderr.write(`tracwell: ${t||e instanceof Error?e.message:String(e)}
84
84
  `),process.exitCode=t?e.exitCode:1});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tracwell",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Privacy-aware browser analytics, optional server-side AI crawler tracking, and the tracwell CLI for coding agents",
5
5
  "license": "MIT",
6
6
  "type": "module",