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 +4 -2
- package/dist/npm/cli.js +13 -13
- package/package.json +1 -1
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. `
|
|
333
|
-
|
|
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
|
|
3
|
-
`,{mode:384}),await
|
|
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
|
|
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
|
-
${
|
|
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
|
-
${
|
|
14
|
-
`:`${
|
|
15
|
-
`}async function
|
|
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?
|
|
33
|
-
<p class="foot">Tracwell CLI</p></main></body></html>`;function
|
|
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(
|
|
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
|
|
81
|
-
`);try{return JSON.parse(t)}catch{return t}}async function u(e){let t=await m(),r=
|
|
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
|
|
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