tracwell 0.6.0 → 0.6.2

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 he}from"node:crypto";var x="0.6.2";function q(e){let t=[],n={};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"){n.help=!0;continue}if(!a.startsWith("--")){t.push(a);continue}let c=a.indexOf("=");if(c!==-1){n[a.slice(2,c)]=a.slice(c+1);continue}let l=e[i+1];l!==void 0&&!l.startsWith("--")?(n[a.slice(2)]=l,i+=1):n[a.slice(2)]=!0}let[r,...s]=t;return{command:r,positional:s,flags:n}}function M(e){return e.replace(/-([a-z0-9])/gu,(t,n)=>n.toUpperCase())}import{chmod as ve,mkdir as Se,readFile as xe,rm as Te,writeFile as _e}from"node:fs/promises";import{homedir as Ie}from"node:os";import{join as $}from"node:path";var T="https://tracwell.app";function B(e=process.env){return e.TRACWELL_CONFIG_DIR?e.TRACWELL_CONFIG_DIR:process.platform==="win32"&&e.APPDATA?$(e.APPDATA,"tracwell"):$(e.XDG_CONFIG_HOME||$(Ie(),".config"),"tracwell")}function P(){return $(B(),"config.json")}async function g(){try{return JSON.parse(await xe(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 h(e){await Se(B(),{recursive:!0,mode:448});let t=P();await _e(t,`${JSON.stringify(e,null,2)}
3
+ `,{mode:384}),await ve(t,384)}async function K(){await Te(P(),{force:!0})}var H=e=>e.replace(/\/+$/u,"");function G(e,t){return H(e.TRACWELL_URL||t.login?.baseUrl||T)}function A(e){return H(e.TRACWELL_URL||T)}var o=class extends Error{constructor(n,r=1){super(n);this.exitCode=r}exitCode};function Y(e,t){try{return JSON.parse(e).error?.message??t}catch{return t}}async function z(e,t,n,r){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/${x}`},body:JSON.stringify({jsonrpc:"2.0",id:1,method:n,params:r})})}catch{throw new o(e===T?`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 ${T}.`)}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 $e=e=>`${e.name.replaceAll("_"," ")} ${e.title??""} ${e.description??""}`.toLowerCase();function Z(e,t){let n=t.toLowerCase().split(/\s+/u).filter(Boolean);if(!n.length)return[...e];let r=s=>n.filter(i=>s.name.toLowerCase().includes(i)).length;return e.filter(s=>n.every(i=>$e(s).includes(i))).toSorted((s,i)=>r(i)-r(s))}async function k(e,t){return(await z(e,t,"tools/list",{})).tools??[]}async function V(e,t,n,r){return await z(e,t,"tools/call",{name:n,arguments:r})}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 n=t.startsWith("event:")?t.slice(6).trim():t;if(!n)throw new o("`event:` needs an event name, like event:signup_completed.",2);return{type:"event",name:n}}function X(e){let t=typeof e.page=="string"?e.page:void 0,n=typeof e.event=="string"?e.event:void 0;if(t&&n)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(n)return{type:"event",name:n};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 L(e,t){return Object.fromEntries(Object.entries(e).filter(([n])=>!t.includes(n)))}import{access as je,mkdir as ee,readFile as U,writeFile as te}from"node:fs/promises";import{dirname as ne,join as j,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=>je(e).then(()=>!0,()=>!1);async function Oe(e,t,n){let r;try{r=await U(e,"utf8")}catch{}return r===t?"unchanged":r!==void 0&&!n?"skipped":(await ee(ne(e),{recursive:!0}),await te(e,t),r===void 0?"created":"updated")}async function E(e,t){let n=await N(),r=[j(e,".agents/skills/tracwell/SKILL.md")];return await Le(j(e,".claude"))&&r.push(j(e,".claude/skills/tracwell/SKILL.md")),Promise.all(r.map(async s=>({path:Re(e,s),status:await Oe(s,n,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 Ce(e){let t=e.indexOf(re),n=e.indexOf(C,t);return t!==-1&&n!==-1?`${e.slice(0,t)}${O}${e.slice(n+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 n=j(e,t),r="";try{r=await U(n,"utf8")}catch{}let s=Ce(r);return s===r?{path:t,status:"unchanged"}:(await ee(ne(n),{recursive:!0}),await te(n,s),{path:t,status:r?"updated":"created"})}import{spawn as Ue}from"node:child_process";import{createHash as Ne,randomBytes as se}from"node:crypto";import{createServer as Ee}from"node:http";var ie="analytics:read definitions:write websites:write offline_access",Fe=300*1e3,De=60*1e3,D=e=>e.toString("base64url");async function oe(e,t){let n=await fetch(e,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams(t).toString()}),r=await n.text();if(!n.ok){let s=`Sign-in failed (HTTP ${n.status}).`;try{let i=JSON.parse(r);s=i.error_description??i.error??s}catch{}throw new o(`${s} Run \`tracwell login\` again.`,3)}return JSON.parse(r)}function ae(e,t,n,r){let s=n.refresh_token??r;return{type:"oauth",baseUrl:e,clientId:t,accessToken:n.access_token,...s?{refreshToken:s}:{},expiresAt:Date.now()+(n.expires_in??3600)*1e3}}function We(e){let[t,n]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["rundll32",["url.dll,FileProtocolHandler",e]]:["xdg-open",[e]];try{let r=Ue(t,n,{detached:!0,stdio:"ignore"});r.on("error",()=>{}),r.unref()}catch{}}function Je(){return new Promise((e,t)=>{let n=Ee();n.once("error",t),n.listen(0,"127.0.0.1",()=>{let r=n.address();r&&typeof r=="object"?e({server:n,port:r.port}):t(new o("Could not start the local sign-in callback."))})})}var qe='<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>',Me='<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>',Be=(e,t,n=!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,10 @@ 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${n?" ok":""}"><div class="card"><div class="icon">${n?qe:Me}</div><h1>${e}</h1><p>${t}</p></div>
33
+ <p class="foot">Tracwell CLI</p></main></body></html>`;function Ke(e,t){return new Promise((n,r)=>{let s=setTimeout(()=>r(new o("Sign-in timed out. Run `tracwell login` again.",3)),Fe);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=(I,S,y,m=!1)=>a.writeHead(I,{"content-type":"text/html; charset=utf-8"}).end(Be(S,y,m));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."),r(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),n(d)})})}async function ce(e,t){let{server:n,port:r}=await Je();try{let s=`http://127.0.0.1:${r}/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[S,y]of Object.entries({client_id:a,redirect_uri:s,response_type:"code",scope:ie,state:l,code_challenge:D(Ne("sha256").update(c).digest()),code_challenge_method:"S256",resource:`${e}/mcp`}))d.searchParams.set(S,y);t.print("Sign in and pick your websites in the browser:"),t.print(d.href),t.open&&We(d.href);let p=await Ke(n,l),I=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,I)}finally{n.close()}}function le(e,t=Date.now()){return e.expiresAt-De<=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(()=>{})}function pe(e){let[t]=e;if(e.length===1&&t)return t;if(!e.length)throw new o("This login has no websites. Add one with `tracwell sites add <name> <url>`, or run `tracwell init --url <url>`.",2);let n=e.map(r=>` ${r.name} ${r.id}`).join(`
34
+ `);throw new o(`Pick a website with \`tracwell use <id>\` or pass --site <id>:
35
+ ${n}`,2)}var He=new Set(["json","site","days","compact","key","help","no-browser","dry-run","force"]),R=7,ge=e=>e.toISOString().slice(0,10);function Ge(e,t){let n=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate()-(e-1)));return{from:ge(n),to:ge(t)}}function Ye(e,t,n,r,s){let i="from"in e&&"to"in e,a=!i&&"date"in e;if(!i&&!a)return{};if(n.from!==void 0||n.to!==void 0||n.date!==void 0)return{};if(!(t.has("from")||t.has("date")||r.days!==void 0))return{};let c=r.days===void 0?R:Number(r.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}=Ge(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 ze(e){return e?.type?Array.isArray(e.type)?e.type:[e.type]:[]}function Ze(e,t,n){let r=ze(n);if(t===!0){if(r.length&&!r.includes("boolean"))throw new o(`--${e} needs a value.`,2);return!0}if(r.includes("number")||r.includes("integer")){let s=Number(t);if(Number.isNaN(s))throw new o(`--${e} must be a number.`,2);return s}if(r.includes("boolean"))return t==="true";if(!r.length&&/^\s*[[{]/u.test(t))try{return JSON.parse(t)}catch{throw new o(`--${e} must be JSON.`,2)}if(r.includes("object")||r.includes("array"))try{return JSON.parse(t)}catch{throw new o(`--${e} must be JSON.`,2)}return t}function fe(e,t,n){let r=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(He.has(c))continue;let d=M(c);i[d]=Ze(c,l,r[d])}let a=typeof t.site=="string"?t.site:n.site;return"projectId"in r&&i.projectId===void 0&&a&&(i.projectId=a),Object.assign(i,Ye(r,s,i,t,n.today)),"requestId"in r&&i.requestId===void 0&&(i.requestId=n.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"}},we=`tracwell ${x} - Tracwell analytics from your terminal or coding agent
34
36
 
35
37
  Start here
36
38
  tracwell init [--url <url>] [--name <name>]
@@ -76,9 +78,9 @@ Common flags
76
78
  --key <key> API key for this command (or set TRACWELL_API_KEY)
77
79
 
78
80
  Output is JSON. Exit codes: 0 ok, 1 request failed, 2 bad input, 3 sign in needed.`,b=e=>{process.stdout.write(`${e}
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?`
81
+ `)},_=e=>{process.stderr.write(`${e}
82
+ `)};function w(e,t){b(typeof e=="string"?e:JSON.stringify(e,null,t.compact?void 0:2))}function Ve(e){if(e.structuredContent!==void 0)return e.structuredContent;let t=(e.content??[]).filter(n=>n.type==="text").map(n=>n.text??"").join(`
83
+ `);try{return JSON.parse(t)}catch{return t}}async function u(e){let t=await g(),n=G(process.env,t),r=typeof e.key=="string"?e.key:process.env.TRACWELL_API_KEY;if(r)return{baseUrl:n,token:r,settings:t,viaKey:!0};let s=t.login;if(!s||s.baseUrl!==n)throw new o("Not signed in. Run `tracwell login`, or set TRACWELL_API_KEY.",3);if(s.type==="key")return{baseUrl:n,token:s.key,settings:t,viaKey:!0};if(!le(s))return{baseUrl:n,token:s.accessToken,settings:t,viaKey:!1};let i=await de(s),a={...t,login:i};return await h(a),{baseUrl:n,token:i.accessToken,settings:a,viaKey:!1}}async function me(e){let t=await g(),n=await ce(A(process.env),{open:!e["no-browser"],print:_});await h({...t,login:n})}async function ye(e,t){let n=(await k(e.baseUrl,e.token)).find(r=>r.name===t);if(!n)throw new o(`This login cannot use \`${t}\`. Run \`tracwell tools\` to see what it can use.`,2);return n}async function v(e,t,n){let r=await V(e.baseUrl,e.token,t,n),s=Ve(r);if(r.isError)throw new o(typeof s=="string"?s:JSON.stringify(s));return s}async function Xe(e){let{projects:t}=await v(e,"analytics_projects",{limit:100}),n=pe(t);return await h({...await g(),site:n.id}),_(`Using ${n.name} (${n.id}), now your default website.`),n.id}async function f(e,t,n,r={}){let s=await ye(e,t),i=e.settings.site;(s.inputSchema?.required??[]).includes("projectId")&&typeof n.site!="string"&&!i&&r.projectId===void 0&&!(typeof n.json=="string"&&n.json.includes("projectId"))&&n.projectId===void 0&&n["project-id"]===void 0&&(i=await Xe(e));let c={...fe(s,n,{...i?{site:i}:{},today:new Date,requestId:he}),...r};if(n["dry-run"])return w({dryRun:!0,tool:t,writes:s.annotations?.readOnlyHint===!1,arguments:c},n);w(await v(e,t,c),n)}function Qe(e,t){let n=e.inputSchema?.properties??{},r=new Set(e.inputSchema?.required??[]),s=Object.entries(n).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}${r.has(i)?", required":""}${l}${a.description?`
82
84
  ${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)}
85
+ `)}async function et(e,t){let n=async l=>await v(e,"install_snippet",{projectId:l});if(typeof t.site=="string")return{installation:await n(t.site),created:!1};let{projects:r}=await v(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(r.map(m=>n(m.id)))).find(m=>m.origins.some(ke=>new URL(ke).origin===l));if(p)return{installation:p,created:!1};if(!(await k(e.baseUrl,e.token)).some(m=>m.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 S=typeof t.name=="string"?t.name:new URL(s).hostname.replace(/^www\./u,""),y=await v(e,"website_create",{requestId:he(),name:S,websiteUrl:s});return{installation:y.website,created:y.created}}let i=r.find(l=>l.id===e.settings.site),a=r.length===1?r[0]:void 0,c=i??a;if(c)return{installation:await n(c.id),created:!1};throw r.length?new o(`Several websites: ${r.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 W(e){return e.map(t=>`${t.path}: ${t.status==="skipped"?"exists and differs; rerun with --force to replace":t.status}`)}async function tt(e){let{command:t,positional:n,flags:r}=q(e);if(r.version||t==="version")return b(x);if(!t||t==="help"&&!n[0])return b(we);if(t==="help"||r.help&&!["skill","agents-md","init","search"].includes(t)){let s=t==="help"?n:[t,...n],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(we);let l=await u(r),d=s[1]==="add"&&a[i]?`${i} add`:i;return b(Qe(await ye(l,c),d))}switch(t){case"login":{if(typeof r.key=="string"){let s=await g(),i=A(process.env);await k(i,r.key),await h({...s,login:{type:"key",baseUrl:i,key:r.key}})}else await me(r);_("Signed in. Run `tracwell sites` to see your websites.");return}case"logout":{let s=await g();s.login?.type==="oauth"&&await ue(s.login),await K(),_("Signed out.");return}case"use":{let s=n[0];if(!s)throw new o("Usage: tracwell use <website-id>. Run `tracwell sites` for IDs.",2);let i=await g();await h({...i,site:s}),_(`Default website set to ${s}.`);return}case"whoami":{let s=await u(r);return w({baseUrl:s.baseUrl,login:s.viaKey?"api key":"browser",defaultSite:s.settings.site??null,websites:await v(s,"analytics_projects",{})},r)}case"tools":{let s=await u(r),i=await k(s.baseUrl,s.token);return w(i.map(a=>({name:a.name,description:a.description,writes:a.annotations?.readOnlyHint===!1})),r)}case"search":{let s=n.join(" ");if(!s)throw new o("Usage: tracwell search <words>",2);let i=await u(r),a=Z(await k(i.baseUrl,i.token),s);return w(a.map(c=>({name:c.name,description:c.description,writes:c.annotations?.readOnlyHint===!1})),r)}case"call":{let s=n[0];if(!s)throw new o("Usage: tracwell call <tool> [--arg value ...]. Run `tracwell tools` for names.",2);return f(await u(r),s,r)}case"goals":{if(n[0]!=="add")return f(await u(r),"analytics_goals",r);let s=n[1];if(!s)throw new o("Usage: tracwell goals add <name> --page /pricing (or --event signup_completed)",2);let i=X(r);return f(await u(r),"goal_create",L(r,["page","event"]),{name:s,match:i,...r.category===void 0?{category:"other"}:{}})}case"funnels":{if(n[0]!=="add")return f(await u(r),"analytics_funnels",r);let s=n[1];if(!s)throw new o("Usage: tracwell funnels add <name> --steps /,/pricing,event:checkout_started",2);let i=Q(r.steps),a=r["window-hours"]!==void 0||r.windowHours!==void 0;return f(await u(r),"funnel_create",L(r,["steps"]),{name:s,steps:i,...a?{}:{windowHours:24}})}case"sites":{if(n[0]==="add"){let[,s,i]=n;if(!s||!i)throw new o("Usage: tracwell sites add <name> <url>",2);return f(await u(r),"website_create",r,{name:s,websiteUrl:i})}return f(await u(r),"analytics_projects",r)}case"skill":{if(!n[0])return b(await N());if(n[0]!=="install")throw new o("Usage: tracwell skill [install [--force]]",2);return w({installed:W(await E(process.cwd(),!!r.force))},r)}case"agents-md":{if(n[0]!=="install")throw new o("Usage: tracwell agents-md install [--file AGENTS.md]",2);let s=typeof r.file=="string"?r.file:"AGENTS.md";return w({installed:W([await F(process.cwd(),s)])},r)}case"init":{let s;try{s=await u(r)}catch(l){if(!(l instanceof o)||l.exitCode!==3)throw l;await me(r),s=await u(r)}let{installation:i,created:a}=await et(s,r);await h({...await g(),site:i.projectId});let c=[...await E(process.cwd(),!!r.force),await F(process.cwd())];return w({website:{id:i.projectId,name:i.name,origins:i.origins,created:a},snippet:{script:i.script,npm:i.npm,placement:i.placement},installed:W(c),next:"Add the script tag (or the npm setup) to the site, load a page, then run `tracwell verify`."},r)}default:{let s=J[t];if(!s)throw new o(`Unknown command \`${t}\`. Run \`tracwell help\`.`,2);return f(await u(r),s.tool,r)}}}tt(process.argv.slice(2)).catch(e=>{let t=e instanceof o;process.stderr.write(`tracwell: ${t||e instanceof Error?e.message:String(e)}
84
86
  `),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.2",
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",