hanzi-browse 2.2.1 → 2.2.3

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.
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Telemetry for the managed backend (api.hanzilla.co).
3
+ * Gated by SENTRY_DSN and POSTHOG_API_KEY env vars — no-op in dev.
4
+ */
5
+ import * as Sentry from "@sentry/node";
6
+ import { PostHog } from "posthog-node";
7
+ let posthog = null;
8
+ let initialized = false;
9
+ export function initManagedTelemetry() {
10
+ if (initialized)
11
+ return;
12
+ initialized = true;
13
+ const sentryDsn = process.env.SENTRY_DSN;
14
+ const posthogKey = process.env.POSTHOG_API_KEY;
15
+ if (sentryDsn) {
16
+ Sentry.init({
17
+ dsn: sentryDsn,
18
+ environment: process.env.NODE_ENV || "development",
19
+ tracesSampleRate: 0.2,
20
+ beforeSend(event) {
21
+ delete event.server_name;
22
+ return event;
23
+ },
24
+ });
25
+ }
26
+ if (posthogKey) {
27
+ posthog = new PostHog(posthogKey, {
28
+ host: process.env.POSTHOG_HOST || "https://us.i.posthog.com",
29
+ flushAt: 10,
30
+ flushInterval: 30000,
31
+ });
32
+ }
33
+ }
34
+ export function trackManagedEvent(name, workspaceId, properties) {
35
+ if (!posthog)
36
+ return;
37
+ posthog.capture({
38
+ distinctId: workspaceId,
39
+ event: name,
40
+ properties,
41
+ });
42
+ }
43
+ export function captureManagedError(error, context) {
44
+ Sentry.withScope((scope) => {
45
+ if (context) {
46
+ scope.setContext("task", context);
47
+ // Set searchable tags for key identifiers
48
+ if (context.workspace_id)
49
+ scope.setTag("workspace_id", context.workspace_id);
50
+ if (context.task_id)
51
+ scope.setTag("task_id", context.task_id);
52
+ }
53
+ scope.captureException(error);
54
+ });
55
+ }
56
+ export async function shutdownManagedTelemetry() {
57
+ await Promise.all([
58
+ Sentry.close(2000),
59
+ posthog?.shutdown(),
60
+ ]);
61
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Anonymous telemetry for local MCP users.
3
+ *
4
+ * Collects error reports and usage stats to improve Hanzi.
5
+ * Opt out: `hanzi-browse telemetry off` or set DO_NOT_TRACK=1
6
+ *
7
+ * Never sends: task content, URLs, API keys, file paths, PII.
8
+ */
9
+ export declare function isTelemetryEnabled(): boolean;
10
+ export declare function setTelemetryEnabled(value: boolean): void;
11
+ export declare function initTelemetry(): void;
12
+ export declare function trackEvent(name: string, properties?: Record<string, any>): void;
13
+ export declare function captureException(error: Error, context?: Record<string, string>): void;
14
+ export declare function shutdownTelemetry(): Promise<void>;
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Anonymous telemetry for local MCP users.
3
+ *
4
+ * Collects error reports and usage stats to improve Hanzi.
5
+ * Opt out: `hanzi-browse telemetry off` or set DO_NOT_TRACK=1
6
+ *
7
+ * Never sends: task content, URLs, API keys, file paths, PII.
8
+ */
9
+ import * as Sentry from "@sentry/node";
10
+ import { PostHog } from "posthog-node";
11
+ import { homedir } from "os";
12
+ import { join, dirname } from "path";
13
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
14
+ import { randomUUID } from "crypto";
15
+ import { fileURLToPath } from "url";
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = dirname(__filename);
18
+ const CONFIG_DIR = join(homedir(), ".hanzi-browse");
19
+ const CONFIG_PATH = join(CONFIG_DIR, "config.json");
20
+ const SENTRY_DSN = "https://2d5504c5db572b0b2709e64f03bdfcc6@o4511120870932480.ingest.us.sentry.io/4511120907698176";
21
+ const POSTHOG_KEY = "phc_SNXFKD8YOBPvBNWWZnuCe7stDsJJNJ5WS8MujKhajIF";
22
+ const POSTHOG_HOST = "https://us.i.posthog.com";
23
+ let posthog = null;
24
+ let anonymousId = null;
25
+ let enabled = false;
26
+ let initialized = false;
27
+ let version = "0.0.0";
28
+ function readConfig() {
29
+ try {
30
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
31
+ }
32
+ catch {
33
+ return {};
34
+ }
35
+ }
36
+ function writeConfig(config) {
37
+ mkdirSync(CONFIG_DIR, { recursive: true });
38
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
39
+ }
40
+ export function isTelemetryEnabled() {
41
+ if (process.env.DO_NOT_TRACK === "1")
42
+ return false;
43
+ if (process.env.HANZI_TELEMETRY === "0")
44
+ return false;
45
+ const config = readConfig();
46
+ return config.telemetry !== false;
47
+ }
48
+ export function setTelemetryEnabled(value) {
49
+ const config = readConfig();
50
+ config.telemetry = value;
51
+ writeConfig(config);
52
+ }
53
+ function getAnonymousId() {
54
+ const config = readConfig();
55
+ if (config.anonymousId)
56
+ return config.anonymousId;
57
+ const id = randomUUID();
58
+ config.anonymousId = id;
59
+ if (config.telemetry === undefined) {
60
+ config.telemetry = true;
61
+ console.error('\x1b[2mHanzi collects anonymous error reports and usage stats to improve the tool.\n' +
62
+ 'Run "hanzi-browse telemetry off" to disable.\x1b[0m');
63
+ }
64
+ writeConfig(config);
65
+ return id;
66
+ }
67
+ export function initTelemetry() {
68
+ if (initialized)
69
+ return;
70
+ initialized = true;
71
+ // Read version from package.json
72
+ try {
73
+ const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
74
+ version = pkg.version || "0.0.0";
75
+ }
76
+ catch { }
77
+ // Don't init SDKs if placeholders haven't been replaced
78
+ if (SENTRY_DSN.startsWith("__") || POSTHOG_KEY.startsWith("__"))
79
+ return;
80
+ enabled = isTelemetryEnabled();
81
+ if (!enabled)
82
+ return;
83
+ anonymousId = getAnonymousId();
84
+ Sentry.init({
85
+ dsn: SENTRY_DSN,
86
+ environment: "local",
87
+ release: `hanzi-browse@${version}`,
88
+ beforeSend(event) {
89
+ if (event.exception?.values) {
90
+ for (const ex of event.exception.values) {
91
+ if (ex.stacktrace?.frames) {
92
+ for (const frame of ex.stacktrace.frames) {
93
+ if (frame.filename) {
94
+ const match = frame.filename.match(/hanzi-browse\/(.+)/);
95
+ frame.filename = match ? match[1] : "<scrubbed>";
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ delete event.user;
102
+ delete event.server_name;
103
+ event.tags = { ...event.tags, anonymousId };
104
+ return event;
105
+ },
106
+ });
107
+ Sentry.setTag("os", process.platform);
108
+ Sentry.setTag("node_version", process.version);
109
+ posthog = new PostHog(POSTHOG_KEY, { host: POSTHOG_HOST, flushAt: 5, flushInterval: 30000 });
110
+ }
111
+ export function trackEvent(name, properties) {
112
+ if (!enabled || !posthog || !anonymousId)
113
+ return;
114
+ posthog.capture({
115
+ distinctId: anonymousId,
116
+ event: name,
117
+ properties: {
118
+ version,
119
+ os: process.platform,
120
+ node_version: process.version,
121
+ ...properties,
122
+ },
123
+ });
124
+ }
125
+ export function captureException(error, context) {
126
+ if (!enabled)
127
+ return;
128
+ Sentry.withScope((scope) => {
129
+ if (context)
130
+ scope.setContext("extra", context);
131
+ scope.captureException(error);
132
+ });
133
+ }
134
+ export async function shutdownTelemetry() {
135
+ if (!enabled)
136
+ return;
137
+ await Promise.all([
138
+ Sentry.close(2000),
139
+ posthog?.shutdown(),
140
+ ]);
141
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hanzi-browse",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
4
4
  "description": "Give your AI agent a real browser — click, type, fill forms, test workflows, post content, and read authenticated pages",
5
5
  "license": "PolyForm-Noncommercial-1.0.0",
6
6
  "author": "hanzili",
@@ -32,8 +32,11 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@modelcontextprotocol/sdk": "^1.25.3",
35
+ "@sentry/node": "^10.46.0",
35
36
  "better-auth": "^1.5.5",
37
+ "cron-parser": "^5.5.0",
36
38
  "pg": "^8.11.3",
39
+ "posthog-node": "^5.21.2",
37
40
  "stripe": "^20.4.1",
38
41
  "undici": "^7.24.5",
39
42
  "ws": "^8.19.0"
@@ -1,46 +0,0 @@
1
- (function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))a(i);new MutationObserver(i=>{for(const o of i)if(o.type==="childList")for(const l of o.addedNodes)l.tagName==="LINK"&&l.rel==="modulepreload"&&a(l)}).observe(document,{childList:!0,subtree:!0});function n(i){const o={};return i.integrity&&(o.integrity=i.integrity),i.referrerPolicy&&(o.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?o.credentials="include":i.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function a(i){if(i.ep)return;i.ep=!0;const o=n(i);fetch(i.href,o)}})();var _e,w,Ue,G,Te,Oe,Ge,Me,ye,pe,fe,ie={},ae=[],Qe=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,de=Array.isArray;function U(t,e){for(var n in e)t[n]=e[n];return t}function ve(t){t&&t.parentNode&&t.parentNode.removeChild(t)}function s(t,e,n){var a,i,o,l={};for(o in e)o=="key"?a=e[o]:o=="ref"?i=e[o]:l[o]=e[o];if(arguments.length>2&&(l.children=arguments.length>3?_e.call(arguments,2):n),typeof t=="function"&&t.defaultProps!=null)for(o in t.defaultProps)l[o]===void 0&&(l[o]=t.defaultProps[o]);return ne(t,l,a,i,null)}function ne(t,e,n,a,i){var o={type:t,props:e,key:n,ref:a,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:i??++Ue,__i:-1,__u:0};return i==null&&w.vnode!=null&&w.vnode(o),o}function j(t){return t.children}function se(t,e){this.props=t,this.context=e}function q(t,e){if(e==null)return t.__?q(t.__,t.__i+1):null;for(var n;e<t.__k.length;e++)if((n=t.__k[e])!=null&&n.__e!=null)return n.__e;return typeof t.type=="function"?q(t):null}function et(t){if(t.__P&&t.__d){var e=t.__v,n=e.__e,a=[],i=[],o=U({},e);o.__v=e.__v+1,w.vnode&&w.vnode(o),ge(t.__P,o,e,t.__n,t.__P.namespaceURI,32&e.__u?[n]:null,a,n??q(e),!!(32&e.__u),i),o.__v=e.__v,o.__.__k[o.__i]=o,We(a,o,i),e.__e=e.__=null,o.__e!=n&&Be(o)}}function Be(t){if((t=t.__)!=null&&t.__c!=null)return t.__e=t.__c.base=null,t.__k.some(function(e){if(e!=null&&e.__e!=null)return t.__e=t.__c.base=e.__e}),Be(t)}function Se(t){(!t.__d&&(t.__d=!0)&&G.push(t)&&!re.__r++||Te!=w.debounceRendering)&&((Te=w.debounceRendering)||Oe)(re)}function re(){try{for(var t,e=1;G.length;)G.length>e&&G.sort(Ge),t=G.shift(),e=G.length,et(t)}finally{G.length=re.__r=0}}function Fe(t,e,n,a,i,o,l,d,_,c,p){var r,u,f,g,T,x,m,y=a&&a.__k||ae,P=e.length;for(_=tt(n,e,y,_,P),r=0;r<P;r++)(f=n.__k[r])!=null&&(u=f.__i!=-1&&y[f.__i]||ie,f.__i=r,x=ge(t,f,u,i,o,l,d,_,c,p),g=f.__e,f.ref&&u.ref!=f.ref&&(u.ref&&be(u.ref,null,f),p.push(f.ref,f.__c||g,f)),T==null&&g!=null&&(T=g),(m=!!(4&f.__u))||u.__k===f.__k?_=Ke(f,_,t,m):typeof f.type=="function"&&x!==void 0?_=x:g&&(_=g.nextSibling),f.__u&=-7);return n.__e=T,_}function tt(t,e,n,a,i){var o,l,d,_,c,p=n.length,r=p,u=0;for(t.__k=new Array(i),o=0;o<i;o++)(l=e[o])!=null&&typeof l!="boolean"&&typeof l!="function"?(typeof l=="string"||typeof l=="number"||typeof l=="bigint"||l.constructor==String?l=t.__k[o]=ne(null,l,null,null,null):de(l)?l=t.__k[o]=ne(j,{children:l},null,null,null):l.constructor===void 0&&l.__b>0?l=t.__k[o]=ne(l.type,l.props,l.key,l.ref?l.ref:null,l.__v):t.__k[o]=l,_=o+u,l.__=t,l.__b=t.__b+1,d=null,(c=l.__i=nt(l,n,_,r))!=-1&&(r--,(d=n[c])&&(d.__u|=2)),d==null||d.__v==null?(c==-1&&(i>p?u--:i<p&&u++),typeof l.type!="function"&&(l.__u|=4)):c!=_&&(c==_-1?u--:c==_+1?u++:(c>_?u--:u++,l.__u|=4))):t.__k[o]=null;if(r)for(o=0;o<p;o++)(d=n[o])!=null&&!(2&d.__u)&&(d.__e==a&&(a=q(d)),qe(d,d));return a}function Ke(t,e,n,a){var i,o;if(typeof t.type=="function"){for(i=t.__k,o=0;i&&o<i.length;o++)i[o]&&(i[o].__=t,e=Ke(i[o],e,n,a));return e}t.__e!=e&&(a&&(e&&t.type&&!e.parentNode&&(e=q(t)),n.insertBefore(t.__e,e||null)),e=t.__e);do e=e&&e.nextSibling;while(e!=null&&e.nodeType==8);return e}function nt(t,e,n,a){var i,o,l,d=t.key,_=t.type,c=e[n],p=c!=null&&(2&c.__u)==0;if(c===null&&d==null||p&&d==c.key&&_==c.type)return n;if(a>(p?1:0)){for(i=n-1,o=n+1;i>=0||o<e.length;)if((c=e[l=i>=0?i--:o++])!=null&&!(2&c.__u)&&d==c.key&&_==c.type)return l}return-1}function Ce(t,e,n){e[0]=="-"?t.setProperty(e,n??""):t[e]=n==null?"":typeof n!="number"||Qe.test(e)?n:n+"px"}function te(t,e,n,a,i){var o,l;e:if(e=="style")if(typeof n=="string")t.style.cssText=n;else{if(typeof a=="string"&&(t.style.cssText=a=""),a)for(e in a)n&&e in n||Ce(t.style,e,"");if(n)for(e in n)a&&n[e]==a[e]||Ce(t.style,e,n[e])}else if(e[0]=="o"&&e[1]=="n")o=e!=(e=e.replace(Me,"$1")),l=e.toLowerCase(),e=l in t||e=="onFocusOut"||e=="onFocusIn"?l.slice(2):e.slice(2),t.l||(t.l={}),t.l[e+o]=n,n?a?n.u=a.u:(n.u=ye,t.addEventListener(e,o?fe:pe,o)):t.removeEventListener(e,o?fe:pe,o);else{if(i=="http://www.w3.org/2000/svg")e=e.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(e!="width"&&e!="height"&&e!="href"&&e!="list"&&e!="form"&&e!="tabIndex"&&e!="download"&&e!="rowSpan"&&e!="colSpan"&&e!="role"&&e!="popover"&&e in t)try{t[e]=n??"";break e}catch{}typeof n=="function"||(n==null||n===!1&&e[4]!="-"?t.removeAttribute(e):t.setAttribute(e,e=="popover"&&n==1?"":n))}}function Ae(t){return function(e){if(this.l){var n=this.l[e.type+t];if(e.t==null)e.t=ye++;else if(e.t<n.u)return;return n(w.event?w.event(e):e)}}}function ge(t,e,n,a,i,o,l,d,_,c){var p,r,u,f,g,T,x,m,y,P,N,E,H,L,z,A=e.type;if(e.constructor!==void 0)return null;128&n.__u&&(_=!!(32&n.__u),o=[d=e.__e=n.__e]),(p=w.__b)&&p(e);e:if(typeof A=="function")try{if(m=e.props,y=A.prototype&&A.prototype.render,P=(p=A.contextType)&&a[p.__c],N=p?P?P.props.value:p.__:a,n.__c?x=(r=e.__c=n.__c).__=r.__E:(y?e.__c=r=new A(m,N):(e.__c=r=new se(m,N),r.constructor=A,r.render=ot),P&&P.sub(r),r.state||(r.state={}),r.__n=a,u=r.__d=!0,r.__h=[],r._sb=[]),y&&r.__s==null&&(r.__s=r.state),y&&A.getDerivedStateFromProps!=null&&(r.__s==r.state&&(r.__s=U({},r.__s)),U(r.__s,A.getDerivedStateFromProps(m,r.__s))),f=r.props,g=r.state,r.__v=e,u)y&&A.getDerivedStateFromProps==null&&r.componentWillMount!=null&&r.componentWillMount(),y&&r.componentDidMount!=null&&r.__h.push(r.componentDidMount);else{if(y&&A.getDerivedStateFromProps==null&&m!==f&&r.componentWillReceiveProps!=null&&r.componentWillReceiveProps(m,N),e.__v==n.__v||!r.__e&&r.shouldComponentUpdate!=null&&r.shouldComponentUpdate(m,r.__s,N)===!1){e.__v!=n.__v&&(r.props=m,r.state=r.__s,r.__d=!1),e.__e=n.__e,e.__k=n.__k,e.__k.some(function(b){b&&(b.__=e)}),ae.push.apply(r.__h,r._sb),r._sb=[],r.__h.length&&l.push(r);break e}r.componentWillUpdate!=null&&r.componentWillUpdate(m,r.__s,N),y&&r.componentDidUpdate!=null&&r.__h.push(function(){r.componentDidUpdate(f,g,T)})}if(r.context=N,r.props=m,r.__P=t,r.__e=!1,E=w.__r,H=0,y)r.state=r.__s,r.__d=!1,E&&E(e),p=r.render(r.props,r.state,r.context),ae.push.apply(r.__h,r._sb),r._sb=[];else do r.__d=!1,E&&E(e),p=r.render(r.props,r.state,r.context),r.state=r.__s;while(r.__d&&++H<25);r.state=r.__s,r.getChildContext!=null&&(a=U(U({},a),r.getChildContext())),y&&!u&&r.getSnapshotBeforeUpdate!=null&&(T=r.getSnapshotBeforeUpdate(f,g)),L=p!=null&&p.type===j&&p.key==null?je(p.props.children):p,d=Fe(t,de(L)?L:[L],e,n,a,i,o,l,d,_,c),r.base=e.__e,e.__u&=-161,r.__h.length&&l.push(r),x&&(r.__E=r.__=null)}catch(b){if(e.__v=null,_||o!=null)if(b.then){for(e.__u|=_?160:128;d&&d.nodeType==8&&d.nextSibling;)d=d.nextSibling;o[o.indexOf(d)]=null,e.__e=d}else{for(z=o.length;z--;)ve(o[z]);he(e)}else e.__e=n.__e,e.__k=n.__k,b.then||he(e);w.__e(b,e,n)}else o==null&&e.__v==n.__v?(e.__k=n.__k,e.__e=n.__e):d=e.__e=st(n.__e,e,n,a,i,o,l,_,c);return(p=w.diffed)&&p(e),128&e.__u?void 0:d}function he(t){t&&(t.__c&&(t.__c.__e=!0),t.__k&&t.__k.some(he))}function We(t,e,n){for(var a=0;a<n.length;a++)be(n[a],n[++a],n[++a]);w.__c&&w.__c(e,t),t.some(function(i){try{t=i.__h,i.__h=[],t.some(function(o){o.call(i)})}catch(o){w.__e(o,i.__v)}})}function je(t){return typeof t!="object"||t==null||t.__b>0?t:de(t)?t.map(je):U({},t)}function st(t,e,n,a,i,o,l,d,_){var c,p,r,u,f,g,T,x=n.props||ie,m=e.props,y=e.type;if(y=="svg"?i="http://www.w3.org/2000/svg":y=="math"?i="http://www.w3.org/1998/Math/MathML":i||(i="http://www.w3.org/1999/xhtml"),o!=null){for(c=0;c<o.length;c++)if((f=o[c])&&"setAttribute"in f==!!y&&(y?f.localName==y:f.nodeType==3)){t=f,o[c]=null;break}}if(t==null){if(y==null)return document.createTextNode(m);t=document.createElementNS(i,y,m.is&&m),d&&(w.__m&&w.__m(e,o),d=!1),o=null}if(y==null)x===m||d&&t.data==m||(t.data=m);else{if(o=o&&_e.call(t.childNodes),!d&&o!=null)for(x={},c=0;c<t.attributes.length;c++)x[(f=t.attributes[c]).name]=f.value;for(c in x)f=x[c],c=="dangerouslySetInnerHTML"?r=f:c=="children"||c in m||c=="value"&&"defaultValue"in m||c=="checked"&&"defaultChecked"in m||te(t,c,null,f,i);for(c in m)f=m[c],c=="children"?u=f:c=="dangerouslySetInnerHTML"?p=f:c=="value"?g=f:c=="checked"?T=f:d&&typeof f!="function"||x[c]===f||te(t,c,f,x[c],i);if(p)d||r&&(p.__html==r.__html||p.__html==t.innerHTML)||(t.innerHTML=p.__html),e.__k=[];else if(r&&(t.innerHTML=""),Fe(e.type=="template"?t.content:t,de(u)?u:[u],e,n,a,y=="foreignObject"?"http://www.w3.org/1999/xhtml":i,o,l,o?o[0]:n.__k&&q(n,0),d,_),o!=null)for(c=o.length;c--;)ve(o[c]);d||(c="value",y=="progress"&&g==null?t.removeAttribute("value"):g!=null&&(g!==t[c]||y=="progress"&&!g||y=="option"&&g!=x[c])&&te(t,c,g,x[c],i),c="checked",T!=null&&T!=t[c]&&te(t,c,T,x[c],i))}return t}function be(t,e,n){try{if(typeof t=="function"){var a=typeof t.__u=="function";a&&t.__u(),a&&e==null||(t.__u=t(e))}else t.current=e}catch(i){w.__e(i,n)}}function qe(t,e,n){var a,i;if(w.unmount&&w.unmount(t),(a=t.ref)&&(a.current&&a.current!=t.__e||be(a,null,e)),(a=t.__c)!=null){if(a.componentWillUnmount)try{a.componentWillUnmount()}catch(o){w.__e(o,e)}a.base=a.__P=null}if(a=t.__k)for(i=0;i<a.length;i++)a[i]&&qe(a[i],e,n||typeof t.type!="function");n||ve(t.__e),t.__c=t.__=t.__e=void 0}function ot(t,e,n){return this.constructor(t,n)}function it(t,e,n){var a,i,o,l;e==document&&(e=document.documentElement),w.__&&w.__(t,e),i=(a=!1)?null:e.__k,o=[],l=[],ge(e,t=e.__k=s(j,null,[t]),i||ie,ie,e.namespaceURI,i?null:e.firstChild?_e.call(e.childNodes):null,o,i?i.__e:e.firstChild,a,l),We(o,t,l)}_e=ae.slice,w={__e:function(t,e,n,a){for(var i,o,l;e=e.__;)if((i=e.__c)&&!i.__)try{if((o=i.constructor)&&o.getDerivedStateFromError!=null&&(i.setState(o.getDerivedStateFromError(t)),l=i.__d),i.componentDidCatch!=null&&(i.componentDidCatch(t,a||{}),l=i.__d),l)return i.__E=i}catch(d){t=d}throw t}},Ue=0,se.prototype.setState=function(t,e){var n;n=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=U({},this.state),typeof t=="function"&&(t=t(U({},n),this.props)),t&&U(n,t),t!=null&&this.__v&&(e&&this._sb.push(e),Se(this))},se.prototype.forceUpdate=function(t){this.__v&&(this.__e=!0,t&&this.__h.push(t),Se(this))},se.prototype.render=j,G=[],Oe=typeof Promise=="function"?Promise.prototype.then.bind(Promise.resolve()):setTimeout,Ge=function(t,e){return t.__v.__b-e.__v.__b},re.__r=0,Me=/(PointerCapture)$|Capture$/i,ye=0,pe=Ae(!1),fe=Ae(!0);var X,S,ue,Pe,le=0,Ye=[],C=w,$e=C.__b,ze=C.__r,Ne=C.diffed,Ie=C.__c,Ee=C.unmount,He=C.__;function ke(t,e){C.__h&&C.__h(S,t,le||e),le=0;var n=S.__H||(S.__H={__:[],__h:[]});return t>=n.__.length&&n.__.push({}),n.__[t]}function k(t){return le=1,at(Ze,t)}function at(t,e,n){var a=ke(X++,2);if(a.t=t,!a.__c&&(a.__=[Ze(void 0,e),function(d){var _=a.__N?a.__N[0]:a.__[0],c=a.t(_,d);_!==c&&(a.__N=[c,a.__[1]],a.__c.setState({}))}],a.__c=S,!S.__f)){var i=function(d,_,c){if(!a.__c.__H)return!0;var p=a.__c.__H.__.filter(function(u){return u.__c});if(p.every(function(u){return!u.__N}))return!o||o.call(this,d,_,c);var r=a.__c.props!==d;return p.some(function(u){if(u.__N){var f=u.__[0];u.__=u.__N,u.__N=void 0,f!==u.__[0]&&(r=!0)}}),o&&o.call(this,d,_,c)||r};S.__f=!0;var o=S.shouldComponentUpdate,l=S.componentWillUpdate;S.componentWillUpdate=function(d,_,c){if(this.__e){var p=o;o=void 0,i(d,_,c),o=p}l&&l.call(this,d,_,c)},S.shouldComponentUpdate=i}return a.__N||a.__}function Q(t,e){var n=ke(X++,3);!C.__s&&Ve(n.__H,e)&&(n.__=t,n.u=e,S.__H.__h.push(n))}function rt(t,e){var n=ke(X++,7);return Ve(n.__H,e)&&(n.__=t(),n.__H=e,n.__h=t),n.__}function J(t,e){return le=8,rt(function(){return t},e)}function lt(){for(var t;t=Ye.shift();){var e=t.__H;if(t.__P&&e)try{e.__h.some(oe),e.__h.some(me),e.__h=[]}catch(n){e.__h=[],C.__e(n,t.__v)}}}C.__b=function(t){S=null,$e&&$e(t)},C.__=function(t,e){t&&e.__k&&e.__k.__m&&(t.__m=e.__k.__m),He&&He(t,e)},C.__r=function(t){ze&&ze(t),X=0;var e=(S=t.__c).__H;e&&(ue===S?(e.__h=[],S.__h=[],e.__.some(function(n){n.__N&&(n.__=n.__N),n.u=n.__N=void 0})):(e.__h.some(oe),e.__h.some(me),e.__h=[],X=0)),ue=S},C.diffed=function(t){Ne&&Ne(t);var e=t.__c;e&&e.__H&&(e.__H.__h.length&&(Ye.push(e)!==1&&Pe===C.requestAnimationFrame||((Pe=C.requestAnimationFrame)||ct)(lt)),e.__H.__.some(function(n){n.u&&(n.__H=n.u),n.u=void 0})),ue=S=null},C.__c=function(t,e){e.some(function(n){try{n.__h.some(oe),n.__h=n.__h.filter(function(a){return!a.__||me(a)})}catch(a){e.some(function(i){i.__h&&(i.__h=[])}),e=[],C.__e(a,n.__v)}}),Ie&&Ie(t,e)},C.unmount=function(t){Ee&&Ee(t);var e,n=t.__c;n&&n.__H&&(n.__H.__.some(function(a){try{oe(a)}catch(i){e=i}}),n.__H=void 0,e&&C.__e(e,n.__v))};var De=typeof requestAnimationFrame=="function";function ct(t){var e,n=function(){clearTimeout(a),De&&cancelAnimationFrame(e),setTimeout(t)},a=setTimeout(n,35);De&&(e=requestAnimationFrame(n))}function oe(t){var e=S,n=t.__c;typeof n=="function"&&(t.__c=void 0,n()),S=e}function me(t){var e=S;t.__c=t.__(),S=e}function Ve(t,e){return!t||t.length!==e.length||e.some(function(n,a){return n!==t[a]})}function Ze(t,e){return typeof e=="function"?e(t):e}const _t="";async function I(t,e,n){const a={method:t,headers:{"Content-Type":"application/json"},credentials:"include"};n&&(a.body=JSON.stringify(n));const i=await fetch(_t+e,a);if(i.status===401)return{status:401,data:null,unauthorized:!0};const o=await i.json().catch(()=>null);return{status:i.status,data:o}}async function dt(){try{const e=await(await fetch("/api/auth/sign-in/social",{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify({provider:"google",callbackURL:"/dashboard"})})).json().catch(()=>null);if(e!=null&&e.url){window.location.href=e.url;return}}catch{}window.location.href="/api/auth/sign-in/social?provider=google&callbackURL=/dashboard"}function ce({text:t,label:e="Copy"}){const[n,a]=k(!1);return s("button",{class:"btn-copy",onClick:()=>{navigator.clipboard.writeText(t),a(!0),setTimeout(()=>a(!1),2e3)}},n?"✓ Copied!":e)}function Je(t){if(!t)return"never";const e=Math.floor((Date.now()-t)/1e3);return e<60?`${e}s ago`:e<3600?`${Math.floor(e/60)}m ago`:e<86400?`${Math.floor(e/3600)}h ago`:`${Math.floor(e/86400)}d ago`}function ut(){var $,K,ee;const[t,e]=k(!0),[n,a]=k(null),[i,o]=k([]),[l,d]=k([]),[_,c]=k(null),[p,r]=k(null),[u,f]=k(null),[g,T]=k("start"),[x,m]=k(!1),[y,P]=k(!1),[N,E]=k(!1),[H,L]=k(!1),z=J(async()=>{const v=await I("GET","/v1/me");if(v!=null&&v.unauthorized){L(!0);return}v!=null&&v.data&&a(v.data)},[]),A=J(async()=>{var R;const v=await I("GET","/v1/api-keys");v&&o(((R=v.data)==null?void 0:R.api_keys)||[])},[]),b=J(async()=>{var R;const v=await I("GET","/v1/browser-sessions");v&&d(((R=v.data)==null?void 0:R.sessions)||[])},[]),O=J(async()=>{const v=await I("GET","/v1/usage");v&&c(v.data)},[]),D=J(async()=>{const v=await I("GET","/v1/billing/credits");v&&r(v.data)},[]);if(Q(()=>{Promise.all([z(),A(),b(),O(),D()]).then(()=>e(!1));const v=setInterval(b,5e3);return()=>clearInterval(v)},[]),Q(()=>{const v=R=>{var V,Z;((V=R.data)==null?void 0:V.type)==="HANZI_EXTENSION_READY"&&m(!0),((Z=R.data)==null?void 0:Z.type)==="HANZI_PAIR_RESULT"&&(P(!1),R.data.success?(E(!0),b()):f("Pairing failed: "+(R.data.error||"unknown")))};return window.addEventListener("message",v),window.postMessage({type:"HANZI_PING"},"*"),()=>window.removeEventListener("message",v)},[]),t)return s(Le,null);if(H)return dt(),s(Le,null);const M=((K=($=n==null?void 0:n.user)==null?void 0:$.name)==null?void 0:K.split(" ")[0])||"there",Y=(ee=n==null?void 0:n.user)!=null&&ee.name?`${n.user.name}'s workspace`:"Your workspace",B=i.length>0,F=l.find(v=>v.status==="connected"),h=!!F||N;return s("div",{class:"page"},s("div",{class:"header"},s("div",null,s("h1",null,Y),s("div",{class:"subtitle"},"Hi, ",M)),s("div",{style:{display:"flex",alignItems:"center",gap:12}},p&&s("div",{style:{textAlign:"right",fontSize:13,color:"var(--muted)"}},s("div",null,s("strong",{style:{color:"var(--ink)",fontSize:16}},(p.free_remaining||0)+(p.credit_balance||0))," tasks left"),s("div",null,p.free_remaining||0," free + ",p.credit_balance||0," credits")),s("button",{class:"signout",onClick:vt},"Sign out"))),s("div",{class:"tabs"},s("button",{class:`tab ${g==="start"?"active":""}`,onClick:()=>T("start")},"Getting Started"),s("button",{class:`tab ${g==="sessions"?"active":""}`,onClick:()=>T("sessions")},"Sessions",l.length>0&&s("span",{class:"tab-count"},l.filter(v=>v.status==="connected").length)),s("button",{class:`tab ${g==="settings"?"active":""}`,onClick:()=>T("settings")},"Settings")),g==="start"&&s(pt,{keys:i,loadKeys:A,setError:f,extensionReady:x,pairing:y,paired:N,setPairing:P,setPaired:E,hasKeys:B,hasConnected:h,connectedSession:F,sessions:l,loadSessions:b,loadUsage:O,setTab:T}),g==="sessions"&&s(ht,{sessions:l,onRefresh:b,usage:_}),g==="settings"&&s(mt,{keys:i,loadKeys:A,setError:f,profile:n,credits:p,loadCredits:D}),u&&s("div",{class:"error-toast",onClick:()=>f(null)},u))}function pt({keys:t,loadKeys:e,setError:n,extensionReady:a,pairing:i,paired:o,setPairing:l,setPaired:d,hasKeys:_,hasConnected:c,connectedSession:p,sessions:r,loadSessions:u,loadUsage:f,setTab:g}){var F;const[T,x]=k(""),[m,y]=k(null),[P,N]=k("Go to example.com and tell me the page title"),[E,H]=k(null),[L,z]=k(""),[A,b]=k(0),O=E==="complete",D=async()=>{var $;if(!T.trim())return;const h=await I("POST","/v1/api-keys",{name:T.trim()});(h==null?void 0:h.status)===201?(y(h.data.key),x(""),await e()):n((($=h==null?void 0:h.data)==null?void 0:$.error)||"Failed")},M=async()=>{var $;l(!0);const h=await I("POST","/v1/browser-sessions/pair",{label:"Developer testing"});if(!h||h.status!==201){l(!1),n((($=h==null?void 0:h.data)==null?void 0:$.error)||"Failed");return}window.postMessage({type:"HANZI_PAIR",token:h.data.pairing_token,apiUrl:location.origin},"*"),setTimeout(()=>l(K=>K&&(n("Extension did not respond."),!1)),5e3)},Y=async()=>{var v,R,V,Z,we,xe;const h=(p==null?void 0:p.id)||((v=r.find(W=>W.status==="connected"))==null?void 0:v.id);if(!P.trim())return;if(!h){H("error"),z("No connected browser session found. Try refreshing the page.");return}H("running"),z(""),b(0);const $=await I("POST","/v1/tasks",{task:P.trim(),browser_session_id:h});if(!$||$.status!==201){H("error"),z(((R=$==null?void 0:$.data)==null?void 0:R.error)||"Failed");return}const K=$.data.id,ee=Date.now()+3*60*1e3;for(;Date.now()<ee;){await new Promise(Xe=>setTimeout(Xe,2e3));const W=await I("GET",`/v1/tasks/${K}`);if(!W)break;if(b(((V=W.data)==null?void 0:V.steps)||0),((Z=W.data)==null?void 0:Z.status)!=="running"){H(((we=W.data)==null?void 0:we.status)||"error"),z(((xe=W.data)==null?void 0:xe.answer)||"No answer."),f();return}}H("error"),z("Timed out after 3 minutes.")},B=`Add browser automation to this project using the Hanzi API. Read the codebase first, then ask me:
2
-
3
- 1. What browser task should Hanzi automate? (e.g. "read patient chart", "fill out a form", "extract data from a web portal")
4
- 2. Where in the UI should the browser pairing flow go? (e.g. settings page, onboarding, a dedicated page)
5
- 3. Where should task results appear? (e.g. inline in the app, a chat interface, a dashboard)
6
-
7
- Then build the integration using this API reference:
8
-
9
- ## Hanzi API (base URL: https://api.hanzilla.co)
10
-
11
- Auth: \`Authorization: Bearer ${m||((F=t[0])==null?void 0:F.key_prefix)||"hic_live_..."}\` header on all requests.
12
-
13
- ### Core flow
14
- 1. Create pairing token → show user a link → they connect their browser
15
- 2. Run tasks against their connected browser → poll for results
16
- 3. Show the answer in your app
17
-
18
- ### Endpoints
19
-
20
- POST /v1/browser-sessions/pair
21
- Body: {"label": "User Name", "external_user_id": "your_user_id"}
22
- Returns: {"pairing_token": "hic_pair_...", "expires_in_seconds": 300}
23
- → Build link: https://api.hanzilla.co/pair/{pairing_token}
24
-
25
- GET /v1/browser-sessions
26
- Returns: {"sessions": [{"id": "...", "status": "connected", "label": "..."}]}
27
-
28
- POST /v1/tasks
29
- Body: {"task": "description", "browser_session_id": "...", "url": "optional", "context": "optional"}
30
- Returns: {"id": "task_id", "status": "running"}
31
- → Poll GET /v1/tasks/:id every 2s until status != "running". Typical: 10-60s.
32
-
33
- GET /v1/tasks/:id
34
- Returns: {"status": "running|complete|error", "answer": "...", "steps": 4}
35
-
36
- POST /v1/tasks/:id/cancel
37
-
38
- GET /v1/tasks/:id/steps
39
- Returns: {"steps": [{"step": 1, "status": "tool_use", "toolName": "navigate", ...}]}
40
-
41
- ### Key details
42
- - 20 free tasks/month, then $0.05/completed task. Errors are free.
43
- - User needs the Hanzi Chrome extension: https://chromewebstore.google.com/detail/iklpkemlmbhemkiojndpbhoakgikpmcd
44
- - Sample app: https://github.com/hanzili/hanzi-browse/tree/main/examples/partner-quickstart
45
-
46
- Read the codebase to understand the stack and project structure, then ask me the 3 questions above. After I answer, build the full integration.`;return s("div",null,!_&&s("div",{class:"card"},s("h3",null,"Create your API key"),s("p",{class:"step-explain"},"You need this to call the Hanzi API from your backend."),s("div",{class:"inline-form"},s("input",{value:T,onInput:h=>x(h.target.value),placeholder:"Key name (e.g. dev)",maxLength:100,onKeyDown:h=>h.key==="Enter"&&D()}),s("button",{class:"btn-primary",onClick:D,disabled:!T.trim()},"Create key"))),m&&s("div",{class:"card"},s("h3",null,"Your API key"),s("div",{class:"key-created"},s("div",{class:"mono-with-copy"},s("div",{class:"mono"},m),s(ce,{text:m,label:"Copy key"})),s("div",{class:"warning"},"Save this key — it won't be shown again.")),s("p",{class:"step-explain",style:{marginTop:12}},"Verify it works:"),s("div",{class:"mono-with-copy",style:{marginTop:4}},s("div",{class:"mono",style:{fontSize:11}},`curl ${location.origin}/v1/billing/credits -H "Authorization: Bearer ${m}"`),s(ce,{text:`curl ${location.origin}/v1/billing/credits -H "Authorization: Bearer ${m}"`,label:"Copy"}))),_&&s("div",{class:"card",style:{background:"#f5f1e8"}},s("h3",null,"Build the integration"),s("p",{class:"step-explain"},"Copy this prompt into Claude Code, Cursor, or any AI coding agent. It has the full API reference and will ask you 3 questions before building."),s("div",{style:{display:"flex",gap:8,marginTop:10}},s("button",{class:"btn-primary",onClick:()=>{navigator.clipboard.writeText(B)},style:{fontSize:13},ref:h=>{h&&(h.onclick=()=>{navigator.clipboard.writeText(B),h.textContent="Copied!",setTimeout(()=>h.textContent="Copy integration prompt",1500)})}},"Copy integration prompt"),s("a",{href:"/docs.html#build-with-hanzi",class:"btn-secondary",style:{textDecoration:"none",padding:"6px 14px",borderRadius:8,fontSize:13}},"Read the docs"))),_&&s(ft,{sessions:r,loadSessions:u,extensionReady:a,pairing:i,paired:o,pairBrowser:M,hasConnected:c,connectedSession:p,taskInput:P,setTaskInput:N,taskStatus:E,taskSteps:A,taskAnswer:L,testComplete:O,runTask:Y,setTaskStatus:H,setTaskAnswer:z,setTab:g}))}function ft({sessions:t,loadSessions:e,extensionReady:n,pairing:a,paired:i,pairBrowser:o,hasConnected:l,connectedSession:d,taskInput:_,setTaskInput:c,taskStatus:p,taskSteps:r,taskAnswer:u,testComplete:f,runTask:g,setTaskStatus:T,setTaskAnswer:x,setTab:m}){const[y,P]=k(null),[N,E]=k(!1),[H,L]=k(null),[z,A]=k(null),[b,O]=k(!1),[D,M]=k(null),Y=async()=>{E(!0),O(!1);const h=await I("POST","/v1/browser-sessions/pair",{label:"User pairing link"});E(!1),(h==null?void 0:h.status)===201&&(P(`${location.origin}/pair/${h.data.pairing_token}`),L(Date.now()),A(t.length),M(300))};Q(()=>{if(D===null||D<=0)return;const h=setTimeout(()=>M($=>$-1),1e3);return()=>clearTimeout(h)},[D]),Q(()=>{H&&z!==null&&t.length>z&&!b&&O(!0)},[t,H,z,b]);const B=D!==null&&D<=0&&!b,F=h=>`${Math.floor(h/60)}:${String(h%60).padStart(2,"0")}`;return s(j,null,s("div",{class:"section-label",style:{marginTop:28}},"Try it out"),s("p",{class:"section-desc"},"Pair a browser, then run a task to see it work."),s("div",{class:"card"},s("div",{class:"step-row"},s("span",{class:`step-badge ${l||b?"done":"active"}`},l||b?"✓":"1"),s("div",{class:"step-content"},s("h3",null,l||b?"Browser paired":"Pair a browser"),l||b?s("p",{class:"step-explain"},b?"A user paired via your link.":"Your Chrome is connected for testing."," ",s("button",{class:"btn-secondary",onClick:()=>m("sessions"),style:{fontSize:11,padding:"2px 8px"}},"View sessions")):y?s("div",null,s("div",{class:"pairing-tracker"},s("div",{class:"pairing-step done"},s("div",{class:"pairing-dot done"}),s("span",null,"Generated")),s("div",{class:"pairing-line"}),s("div",{class:`pairing-step ${b?"done":"waiting"}`},s("div",{class:`pairing-dot ${b?"done":"waiting"}`}),s("span",null,b?"Paired":"Waiting for click..."))),s("div",{class:"mono-with-copy",style:{marginTop:12}},s("div",{class:"mono",style:{fontSize:12}},y),s(ce,{text:y,label:"Copy link"})),s("div",{style:{display:"flex",gap:8,marginTop:8,alignItems:"center"}},s("a",{href:y,target:"_blank",rel:"noreferrer",class:"btn-primary",style:{display:"inline-block",textDecoration:"none",color:"white",padding:"6px 14px",borderRadius:8,fontSize:13}},"Open it"),s("button",{class:"btn-secondary",onClick:()=>{P(null),L(null),A(null),M(null),O(!1)},style:{fontSize:12}},"New link"),!B&&D>0&&s("span",{style:{fontSize:12,color:D<60?"var(--red)":"var(--muted)"}},"Expires in ",F(D)),B&&s("span",{style:{fontSize:12,color:"var(--red)",fontWeight:600}},"Expired")),s("p",{class:"step-explain",style:{marginTop:8}},"User needs the ",s("a",{href:"https://chromewebstore.google.com/detail/hanzi-browse/iklpkemlmbhemkiojndpbhoakgikpmcd",target:"_blank"},"Hanzi extension")," installed.",n&&!b&&s(j,null," ","Or ",s("button",{style:{background:"none",border:"none",color:"var(--accent)",textDecoration:"underline",cursor:"pointer",padding:0,font:"inherit",fontSize:"inherit"},onClick:o,disabled:a},a?"connecting...":"connect this browser")," instead."))):s("div",null,s("p",{class:"step-explain"},"Generate a pairing link to share, or connect this browser directly."),s("div",{style:{display:"flex",gap:8,marginTop:4}},s("button",{class:"btn-primary",onClick:Y,disabled:N},N?"Generating...":"Generate a link"),n&&s("button",{class:"btn-secondary",onClick:o,disabled:a},a?"Connecting...":"Connect this browser")),!n&&s("p",{class:"step-explain",style:{marginTop:8}},"To connect this browser directly, ",s("a",{href:"https://chromewebstore.google.com/detail/hanzi-browse/iklpkemlmbhemkiojndpbhoakgikpmcd",target:"_blank"},"install the extension")," first."),s("p",{class:"step-explain",style:{marginTop:8,fontSize:12,color:"#999"}},"In production, your backend calls ",s("code",null,"POST /v1/browser-sessions/pair")," to generate links programmatically."))))),(l||b)&&s("div",{class:"card"},s("div",{class:"step-row"},s("span",{class:`step-badge ${f?"done":"active"}`},f?"✓":"2"),s("div",{class:"step-content"},s("h3",null,"Run a test task"),s("p",{class:"step-explain"},"Tell Hanzi what to do in the paired browser."),p?p==="running"?s("div",{class:"task-running"},s("div",{class:"task-spinner"}),s("span",null,"Running... (",r," step",r!==1?"s":"",")")):s("div",{class:"task-result"},s("div",{class:`task-status-label ${p}`},p==="complete"?"✓ Complete":"✗ "+p,r>0&&` · ${r} steps`),s("div",{class:"task-answer"},u),s("button",{class:"btn-secondary",onClick:()=>{T(null),x("")},style:{marginTop:8}},"Run another")):s("div",{class:"inline-form"},s("input",{value:_,onInput:h=>c(h.target.value),placeholder:"What should Hanzi do?",onKeyDown:h=>h.key==="Enter"&&g()}),s("button",{class:"btn-primary",onClick:g,disabled:!_.trim()},"Run"))))))}function ht({sessions:t,onRefresh:e,usage:n}){const a=t.filter(_=>_.status==="connected"),i=t.filter(_=>_.status==="disconnected"),o=async _=>{await I("DELETE",`/v1/browser-sessions/${_}`),e()},l=async()=>{for(const _ of i)await I("DELETE",`/v1/browser-sessions/${_.id}`);e()},d=_=>_>999999?(_/1e6).toFixed(1)+"M":_>999?(_/1e3).toFixed(1)+"K":String(_||0);return s("div",null,s("div",{class:"summary-bar"},s("span",{class:"summary-stat"},s("strong",null,a.length)," connected"),s("span",{class:"summary-stat"},s("strong",null,i.length)," disconnected"),s("span",{class:"summary-stat"},s("strong",null,(n==null?void 0:n.taskCount)||0)," tasks run")),a.length>0&&s("div",{class:"card"},s("h3",{style:{color:"var(--green)"}},"Connected"),a.map(_=>s(Re,{key:_.id,session:_}))),i.length>0&&s("div",{class:"card"},s("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center"}},s("h3",{style:{color:"var(--muted)"}},"Disconnected"),s("button",{class:"btn-secondary",onClick:l,style:{fontSize:11,padding:"3px 10px"}},"Remove all")),i.map(_=>s(Re,{key:_.id,session:_,onRemove:()=>o(_.id)})),s("p",{class:"step-explain",style:{marginTop:6}},"Sessions reconnect automatically when the browser reopens.")),t.length===0&&s("div",{class:"card"},s("p",{class:"step-explain"},"No sessions yet. Go to Getting Started to pair a browser.")),s("div",{class:"card"},s("h3",null,"Usage"),s("div",{class:"usage-grid"},s("div",{class:"usage-stat"},s("div",{class:"num"},(n==null?void 0:n.taskCount)||0),s("div",{class:"label"},"Tasks")),s("div",{class:"usage-stat"},s("div",{class:"num"},d(n==null?void 0:n.totalApiCalls)),s("div",{class:"label"},"API calls")),s("div",{class:"usage-stat"},s("div",{class:"num"},d(((n==null?void 0:n.totalInputTokens)||0)+((n==null?void 0:n.totalOutputTokens)||0))),s("div",{class:"label"},"Tokens")))),s("button",{class:"btn-secondary",onClick:e,style:{marginTop:8,fontSize:12}},"Refresh"))}function Re({session:t,onRemove:e}){const n=t.label||t.external_user_id||"Unnamed";return s("div",{class:"session-row"},s("span",{class:"session-info"},s("span",{class:`status-dot ${t.status}`}),s("span",{class:"session-label"},n),t.external_user_id&&t.label&&s("span",{class:"session-meta"},t.external_user_id)),s("span",{class:"session-id-group"},s("span",{class:"session-time"},Je(t.last_heartbeat)),s("code",null,t.id.slice(0,8),"..."),e&&s("button",{class:"btn-danger",onClick:e,style:{padding:"2px 8px",fontSize:11}},"Remove")))}function mt({keys:t,loadKeys:e,setError:n,profile:a,credits:i,loadCredits:o}){const[l,d]=k(""),[_,c]=k(null),p=async()=>{var f;if(!l.trim())return;const u=await I("POST","/v1/api-keys",{name:l.trim()});(u==null?void 0:u.status)===201?(c(u.data.key),d(""),await e()):n(((f=u==null?void 0:u.data)==null?void 0:f.error)||"Failed")},r=async u=>{confirm("Delete this API key?")&&(await I("DELETE",`/v1/api-keys/${u}`),c(null),await e())};return s("div",null,s("div",{class:"card"},s("h3",null,"API Keys"),t.map(u=>s("div",{class:"key-row",key:u.id},s("span",null,s("strong",null,u.name)," ",s("code",{class:"key-prefix"},u.key_prefix),u.last_used_at&&s("span",{class:"session-meta"}," · used ",Je(u.last_used_at))),s("button",{class:"btn-danger",onClick:()=>r(u.id)},"Delete"))),_&&s("div",{class:"key-created"},s("div",{class:"mono-with-copy"},s("div",{class:"mono"},_),s(ce,{text:_,label:"Copy key"})),s("div",{class:"warning"},"Save this key — it won't be shown again.")),s("div",{class:"inline-form",style:{marginTop:8}},s("input",{value:l,onInput:u=>d(u.target.value),placeholder:"Key name",maxLength:100,onKeyDown:u=>u.key==="Enter"&&p()}),s("button",{class:"btn-primary",onClick:p,disabled:!l.trim()},"Create key"))),s("div",{class:"card"},s("h3",null,"Credits & Usage"),i?s("div",null,s("div",{style:{display:"grid",gridTemplateColumns:"1fr 1fr",gap:12,margin:"8px 0 12px"}},s("div",{style:{padding:12,background:"#f5f1e8",borderRadius:8,textAlign:"center"}},s("div",{style:{fontSize:28,fontWeight:700}},i.free_remaining||0),s("div",{style:{fontSize:12,color:"var(--muted)"}},"free tasks left"),s("div",{style:{fontSize:11,color:"var(--muted)"}},"of ",i.free_tasks_per_month,"/month")),s("div",{style:{padding:12,background:"#f5f1e8",borderRadius:8,textAlign:"center"}},s("div",{style:{fontSize:28,fontWeight:700}},i.credit_balance||0),s("div",{style:{fontSize:12,color:"var(--muted)"}},"purchased credits"),s("div",{style:{fontSize:11,color:"var(--muted)"}},"$0.05/task"))),s("p",{class:"step-explain"},"You only pay for completed tasks. Errors and timeouts are free."),s(yt,{loadCredits:o,setError:n})):s("p",{class:"step-explain"},"Loading...")),s("div",{class:"card",style:{background:"#f5f1e8"}},s("h3",null,"Building a product with Hanzi?"),s("p",{class:"step-explain"},"Need volume pricing, custom SLAs, or dedicated support? We offer wholesale rates starting at $0.02/task for partners."),s("a",{href:"mailto:hanzili0217@gmail.com?subject=Partner%20pricing&body=Hi%20Hanzi%20team%2C%0A%0AI%27m%20building%20a%20product%20that%20uses%20browser%20automation.%0A%0AExpected%20volume%3A%20%0AUse%20case%3A%20%0A",class:"btn-primary",style:{display:"inline-block",textDecoration:"none",color:"white",padding:"8px 16px",borderRadius:8,fontSize:13,marginTop:8}},"Contact us for partner pricing")),s("div",{class:"card"},s("h3",null,"Resources"),s("div",{style:{display:"flex",flexDirection:"column",gap:6}},s("a",{href:"/docs.html#build-with-hanzi"},"API Documentation"),s("a",{href:"https://github.com/hanzili/hanzi-browse/tree/main/examples/partner-quickstart",target:"_blank"},"Sample App (GitHub)"),s("a",{href:"https://github.com/hanzili/hanzi-browse/tree/main/sdk",target:"_blank"},"SDK Source"),s("a",{href:"https://discord.gg/hahgu5hcA5",target:"_blank"},"Discord Community"))))}function yt({loadCredits:t,setError:e}){const[n,a]=k(!1),i=async o=>{var d,_;a(!0);const l=await I("POST","/v1/billing/checkout",{credits:o,success_url:location.origin+"/dashboard?checkout=success",cancel_url:location.origin+"/dashboard"});a(!1),(d=l==null?void 0:l.data)!=null&&d.url?window.location.href=l.data.url:e(((_=l==null?void 0:l.data)==null?void 0:_.error)||"Billing not available yet")};return Q(()=>{location.search.includes("checkout=success")&&(t(),history.replaceState(null,"","/dashboard"))},[]),s("div",{style:{display:"flex",gap:8,marginTop:8}},s("button",{class:"btn-primary",onClick:()=>i(100),disabled:n,style:{fontSize:13}},"100 credits — $5"),s("button",{class:"btn-secondary",onClick:()=>i(500),disabled:n,style:{fontSize:13}},"500 — $20"),s("button",{class:"btn-secondary",onClick:()=>i(1500),disabled:n,style:{fontSize:13}},"1500 — $50"))}function Le(){return s("div",{class:"page"},s("div",{class:"skeleton skeleton-header"}),s("div",{class:"skeleton skeleton-subtitle"}),s("div",{class:"skeleton skeleton-card"}),s("div",{class:"skeleton skeleton-card"}))}async function vt(){await fetch("/api/auth/sign-out",{method:"POST",credentials:"include"}),window.location.href="https://browse.hanzilla.co"}it(s(ut,null),document.getElementById("app"));