heyhank 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/README.md +40 -0
  2. package/bin/cli.ts +168 -0
  3. package/bin/ctl.ts +528 -0
  4. package/bin/generate-token.ts +28 -0
  5. package/dist/apple-touch-icon.png +0 -0
  6. package/dist/assets/AgentsPage-BPhirnCe.js +7 -0
  7. package/dist/assets/AssistantPage-DJ-cMQfb.js +1 -0
  8. package/dist/assets/CronManager-DDbz-yiT.js +1 -0
  9. package/dist/assets/HelpPage-DMfkzERp.js +1 -0
  10. package/dist/assets/IntegrationsPage-CrOitCmJ.js +1 -0
  11. package/dist/assets/MediaPage-CE5rdvkC.js +1 -0
  12. package/dist/assets/PlatformDashboard-Do6F0O2p.js +1 -0
  13. package/dist/assets/Playground-Fc5cdc5p.js +109 -0
  14. package/dist/assets/ProcessPanel-CslEiZkI.js +2 -0
  15. package/dist/assets/PromptsPage-D2EhsdNO.js +4 -0
  16. package/dist/assets/RunsPage-C5BZF5Rx.js +1 -0
  17. package/dist/assets/SandboxManager-a1AVI5q2.js +8 -0
  18. package/dist/assets/SettingsPage-DirhjQrJ.js +51 -0
  19. package/dist/assets/SocialMediaPage-DBuM28vD.js +1 -0
  20. package/dist/assets/TailscalePage-CHiFhZXF.js +1 -0
  21. package/dist/assets/TelephonyPage-x0VV0fOo.js +1 -0
  22. package/dist/assets/TerminalPage-Drwyrnfd.js +1 -0
  23. package/dist/assets/gemini-audio-t-TSU-To.js +17 -0
  24. package/dist/assets/gemini-live-client-C7rqAW7G.js +166 -0
  25. package/dist/assets/index-C8M_PUmX.css +32 -0
  26. package/dist/assets/index-CEqZnThB.js +204 -0
  27. package/dist/assets/sw-register-LSSpj6RU.js +1 -0
  28. package/dist/assets/time-ago-B6r_l9u1.js +1 -0
  29. package/dist/assets/workbox-window.prod.es5-BIl4cyR9.js +2 -0
  30. package/dist/favicon-32-original.png +0 -0
  31. package/dist/favicon-32.png +0 -0
  32. package/dist/favicon.ico +0 -0
  33. package/dist/favicon.svg +8 -0
  34. package/dist/fonts/MesloLGSNerdFontMono-Bold.woff2 +0 -0
  35. package/dist/fonts/MesloLGSNerdFontMono-Regular.woff2 +0 -0
  36. package/dist/heyhank-mascot-poster.png +0 -0
  37. package/dist/heyhank-mascot.mp4 +0 -0
  38. package/dist/heyhank-mascot.webm +0 -0
  39. package/dist/icon-192-original.png +0 -0
  40. package/dist/icon-192.png +0 -0
  41. package/dist/icon-512-original.png +0 -0
  42. package/dist/icon-512.png +0 -0
  43. package/dist/index.html +21 -0
  44. package/dist/logo-192.png +0 -0
  45. package/dist/logo-512.png +0 -0
  46. package/dist/logo-codex.svg +14 -0
  47. package/dist/logo-docker.svg +4 -0
  48. package/dist/logo-original.png +0 -0
  49. package/dist/logo.png +0 -0
  50. package/dist/logo.svg +14 -0
  51. package/dist/manifest.json +24 -0
  52. package/dist/push-sw.js +34 -0
  53. package/dist/sw.js +1 -0
  54. package/dist/workbox-d2a0910a.js +1 -0
  55. package/package.json +109 -0
  56. package/server/agent-cron-migrator.ts +85 -0
  57. package/server/agent-executor.ts +357 -0
  58. package/server/agent-store.ts +185 -0
  59. package/server/agent-timeout.ts +107 -0
  60. package/server/agent-types.ts +122 -0
  61. package/server/ai-validation-settings.ts +37 -0
  62. package/server/ai-validator.ts +181 -0
  63. package/server/anthropic-provider-migration.ts +48 -0
  64. package/server/assistant-store.ts +272 -0
  65. package/server/auth-manager.ts +150 -0
  66. package/server/auto-approve.ts +153 -0
  67. package/server/auto-namer.ts +36 -0
  68. package/server/backend-adapter.ts +54 -0
  69. package/server/cache-headers.ts +61 -0
  70. package/server/calendar-service.ts +434 -0
  71. package/server/claude-adapter.ts +889 -0
  72. package/server/claude-container-auth.ts +30 -0
  73. package/server/claude-session-discovery.ts +157 -0
  74. package/server/claude-session-history.ts +410 -0
  75. package/server/cli-launcher.ts +1303 -0
  76. package/server/codex-adapter.ts +3027 -0
  77. package/server/codex-container-auth.ts +24 -0
  78. package/server/codex-home.ts +27 -0
  79. package/server/codex-ws-proxy.cjs +226 -0
  80. package/server/commands-discovery.ts +81 -0
  81. package/server/constants.ts +7 -0
  82. package/server/container-manager.ts +1053 -0
  83. package/server/cost-tracker.ts +222 -0
  84. package/server/cron-scheduler.ts +243 -0
  85. package/server/cron-store.ts +148 -0
  86. package/server/cron-types.ts +63 -0
  87. package/server/email-service.ts +354 -0
  88. package/server/env-manager.ts +161 -0
  89. package/server/event-bus-types.ts +75 -0
  90. package/server/event-bus.ts +124 -0
  91. package/server/execution-store.ts +170 -0
  92. package/server/federation/node-connection.ts +190 -0
  93. package/server/federation/node-manager.ts +366 -0
  94. package/server/federation/node-store.ts +86 -0
  95. package/server/federation/node-types.ts +121 -0
  96. package/server/fs-utils.ts +15 -0
  97. package/server/git-utils.ts +421 -0
  98. package/server/github-pr.ts +379 -0
  99. package/server/google-media.ts +342 -0
  100. package/server/image-pull-manager.ts +279 -0
  101. package/server/index.ts +491 -0
  102. package/server/internal-ai.ts +237 -0
  103. package/server/kill-switch.ts +99 -0
  104. package/server/llm-providers.ts +342 -0
  105. package/server/logger.ts +259 -0
  106. package/server/mcp-registry.ts +401 -0
  107. package/server/message-bus.ts +271 -0
  108. package/server/message-delivery.ts +128 -0
  109. package/server/metrics-collector.ts +350 -0
  110. package/server/metrics-types.ts +108 -0
  111. package/server/middleware/managed-auth.ts +195 -0
  112. package/server/novnc-proxy.ts +99 -0
  113. package/server/path-resolver.ts +186 -0
  114. package/server/paths.ts +13 -0
  115. package/server/pr-poller.ts +162 -0
  116. package/server/prompt-manager.ts +211 -0
  117. package/server/protocol/claude-upstream/README.md +19 -0
  118. package/server/protocol/claude-upstream/sdk.d.ts.txt +1943 -0
  119. package/server/protocol/codex-upstream/ClientNotification.ts.txt +5 -0
  120. package/server/protocol/codex-upstream/ClientRequest.ts.txt +60 -0
  121. package/server/protocol/codex-upstream/README.md +18 -0
  122. package/server/protocol/codex-upstream/ServerNotification.ts.txt +41 -0
  123. package/server/protocol/codex-upstream/ServerRequest.ts.txt +16 -0
  124. package/server/protocol/codex-upstream/v2/DynamicToolCallParams.ts.txt +6 -0
  125. package/server/protocol/codex-upstream/v2/DynamicToolCallResponse.ts.txt +6 -0
  126. package/server/protocol-monitor.ts +50 -0
  127. package/server/provider-manager.ts +111 -0
  128. package/server/provider-registry.ts +393 -0
  129. package/server/push-notifications.ts +221 -0
  130. package/server/recorder.ts +374 -0
  131. package/server/recording-hub/compat-validator.ts +284 -0
  132. package/server/recording-hub/diagnostics.ts +299 -0
  133. package/server/recording-hub/hub-config.ts +19 -0
  134. package/server/recording-hub/hub-routes.ts +236 -0
  135. package/server/recording-hub/hub-store.ts +265 -0
  136. package/server/recording-hub/replay-adapter.ts +207 -0
  137. package/server/relay-client.ts +320 -0
  138. package/server/reminder-scheduler.ts +38 -0
  139. package/server/replay.ts +78 -0
  140. package/server/routes/agent-routes.ts +264 -0
  141. package/server/routes/assistant-routes.ts +90 -0
  142. package/server/routes/cron-routes.ts +103 -0
  143. package/server/routes/env-routes.ts +95 -0
  144. package/server/routes/federation-routes.ts +76 -0
  145. package/server/routes/fs-routes.ts +622 -0
  146. package/server/routes/git-routes.ts +97 -0
  147. package/server/routes/llm-routes.ts +166 -0
  148. package/server/routes/media-routes.ts +135 -0
  149. package/server/routes/metrics-routes.ts +13 -0
  150. package/server/routes/platform-routes.ts +1379 -0
  151. package/server/routes/prompt-routes.ts +67 -0
  152. package/server/routes/provider-routes.ts +109 -0
  153. package/server/routes/sandbox-routes.ts +127 -0
  154. package/server/routes/settings-routes.ts +285 -0
  155. package/server/routes/skills-routes.ts +100 -0
  156. package/server/routes/socialmedia-routes.ts +208 -0
  157. package/server/routes/system-routes.ts +228 -0
  158. package/server/routes/tailscale-routes.ts +22 -0
  159. package/server/routes/telephony-routes.ts +259 -0
  160. package/server/routes.ts +1379 -0
  161. package/server/sandbox-manager.ts +168 -0
  162. package/server/service.ts +718 -0
  163. package/server/session-creation-service.ts +457 -0
  164. package/server/session-git-info.ts +104 -0
  165. package/server/session-names.ts +67 -0
  166. package/server/session-orchestrator.ts +824 -0
  167. package/server/session-state-machine.ts +207 -0
  168. package/server/session-store.ts +146 -0
  169. package/server/session-types.ts +511 -0
  170. package/server/settings-manager.ts +149 -0
  171. package/server/shared-context.ts +157 -0
  172. package/server/socialmedia/adapter.ts +15 -0
  173. package/server/socialmedia/adapters/ayrshare-adapter.ts +169 -0
  174. package/server/socialmedia/adapters/buffer-adapter.ts +299 -0
  175. package/server/socialmedia/adapters/postiz-adapter.ts +298 -0
  176. package/server/socialmedia/manager.ts +227 -0
  177. package/server/socialmedia/store.ts +98 -0
  178. package/server/socialmedia/types.ts +89 -0
  179. package/server/tailscale-manager.ts +451 -0
  180. package/server/telephony/audio-bridge.ts +331 -0
  181. package/server/telephony/call-manager.ts +457 -0
  182. package/server/telephony/call-types.ts +108 -0
  183. package/server/telephony/telephony-store.ts +119 -0
  184. package/server/terminal-manager.ts +240 -0
  185. package/server/update-checker.ts +192 -0
  186. package/server/usage-limits.ts +225 -0
  187. package/server/web-push.d.ts +51 -0
  188. package/server/worktree-tracker.ts +84 -0
  189. package/server/ws-auth.ts +41 -0
  190. package/server/ws-bridge-browser-ingest.ts +72 -0
  191. package/server/ws-bridge-browser.ts +112 -0
  192. package/server/ws-bridge-cli-ingest.ts +81 -0
  193. package/server/ws-bridge-codex.ts +266 -0
  194. package/server/ws-bridge-controls.ts +20 -0
  195. package/server/ws-bridge-persist.ts +66 -0
  196. package/server/ws-bridge-publish.ts +79 -0
  197. package/server/ws-bridge-replay.ts +61 -0
  198. package/server/ws-bridge-types.ts +121 -0
  199. package/server/ws-bridge.ts +1240 -0
@@ -0,0 +1 @@
1
+ import{_ as f}from"./index-CEqZnThB.js";function h(s={}){const{immediate:t=!1,onNeedRefresh:a,onOfflineReady:i,onRegistered:n,onRegisteredSW:r,onRegisterError:o}=s;let c,l;const p=async(e=!0)=>{await l};async function d(){if("serviceWorker"in navigator){if(c=await f(async()=>{const{Workbox:e}=await import("./workbox-window.prod.es5-BIl4cyR9.js");return{Workbox:e}},[]).then(({Workbox:e})=>new e("/sw.js",{scope:"/",type:"classic"})).catch(e=>{o==null||o(e)}),!c)return;c.addEventListener("activated",e=>{(e.isUpdate||e.isExternal)&&window.location.reload()}),c.addEventListener("installed",e=>{e.isUpdate||i==null||i()}),c.register({immediate:t}).then(e=>{r?r("/sw.js",e):n==null||n(e)}).catch(e=>{o==null||o(e)})}}return l=d(),p}let u;h({onRegisteredSW(s,t){u=t,t&&setInterval(()=>{t.update()},3600*1e3)},onOfflineReady(){console.log("[SW] Offline-ready: all assets precached")}});function b(s){const t="=".repeat((4-s.length%4)%4),a=(s+t).replace(/-/g,"+").replace(/_/g,"/"),i=atob(a),n=new Uint8Array(i.length);for(let r=0;r<i.length;r++)n[r]=i.charCodeAt(r);return n}async function w(){var o;const s=u??await((o=navigator.serviceWorker)==null?void 0:o.ready);if(!s)return console.warn("[push] No service worker registration available"),null;const t=await s.pushManager.getSubscription();if(t)return console.log("[push] Already subscribed to push notifications"),t;const a=await fetch("/api/push/vapid-key");if(!a.ok)return console.error("[push] Failed to fetch VAPID key:",a.status),null;const{publicKey:i}=await a.json(),n=await s.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:b(i)}),r=await fetch("/api/push/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({subscription:n.toJSON()})});return r.ok?console.log("[push] Successfully subscribed to push notifications"):console.error("[push] Failed to send subscription to server:",r.status),n}async function y(){var i;const s=u??await((i=navigator.serviceWorker)==null?void 0:i.ready);if(!s)return!1;const t=await s.pushManager.getSubscription();if(!t)return!0;const a=await t.unsubscribe();return a&&console.log("[push] Successfully unsubscribed from push notifications"),a}export{w as subscribeToPush,y as unsubscribeFromPush};
@@ -0,0 +1 @@
1
+ function s(n){const r=Date.now()-n,o=Math.floor(r/6e4);if(o<1)return"just now";if(o<60)return`${o}m ago`;const t=Math.floor(o/60);return t<24?`${t}h ago`:`${Math.floor(t/24)}d ago`}export{s as t};
@@ -0,0 +1,2 @@
1
+ try{self["workbox:window:7.3.0"]&&_()}catch{}function b(n,r){return new Promise((function(t){var o=new MessageChannel;o.port1.onmessage=function(f){t(f.data)},n.postMessage(r,[o.port2])}))}function P(n,r){(r==null||r>n.length)&&(r=n.length);for(var t=0,o=Array(r);t<r;t++)o[t]=n[t];return o}function j(n,r){for(var t=0;t<r.length;t++){var o=r[t];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(n,W(o.key),o)}}function S(n,r){var t=typeof Symbol<"u"&&n[Symbol.iterator]||n["@@iterator"];if(t)return(t=t.call(n)).next.bind(t);if(Array.isArray(n)||(t=(function(f,c){if(f){if(typeof f=="string")return P(f,c);var a={}.toString.call(f).slice(8,-1);return a==="Object"&&f.constructor&&(a=f.constructor.name),a==="Map"||a==="Set"?Array.from(f):a==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(a)?P(f,c):void 0}})(n))||r){t&&(n=t);var o=0;return function(){return o>=n.length?{done:!0}:{done:!1,value:n[o++]}}}throw new TypeError(`Invalid attempt to iterate non-iterable instance.
2
+ In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function w(n,r){return w=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,o){return t.__proto__=o,t},w(n,r)}function W(n){var r=(function(t,o){if(typeof t!="object"||!t)return t;var f=t[Symbol.toPrimitive];if(f!==void 0){var c=f.call(t,o);if(typeof c!="object")return c;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(t)})(n,"string");return typeof r=="symbol"?r:r+""}try{self["workbox:core:7.3.0"]&&_()}catch{}var m=function(){var n=this;this.promise=new Promise((function(r,t){n.resolve=r,n.reject=t}))};function y(n,r){var t=location.href;return new URL(n,t).href===new URL(r,t).href}var d=function(n,r){this.type=n,Object.assign(this,r)};function l(n,r,t){return t?r?r(n):n:(n&&n.then||(n=Promise.resolve(n)),r?n.then(r):n)}function k(){}var L={type:"SKIP_WAITING"};function E(n,r){return n&&n.then?n.then(k):Promise.resolve()}var O=(function(n){function r(c,a){var e,i;return a===void 0&&(a={}),(e=n.call(this)||this).nn={},e.tn=0,e.rn=new m,e.en=new m,e.on=new m,e.un=0,e.an=new Set,e.cn=function(){var u=e.fn,s=u.installing;e.tn>0||!y(s.scriptURL,e.sn.toString())||performance.now()>e.un+6e4?(e.vn=s,u.removeEventListener("updatefound",e.cn)):(e.hn=s,e.an.add(s),e.rn.resolve(s)),++e.tn,s.addEventListener("statechange",e.ln)},e.ln=function(u){var s=e.fn,v=u.target,h=v.state,p=v===e.vn,g={sw:v,isExternal:p,originalEvent:u};!p&&e.mn&&(g.isUpdate=!0),e.dispatchEvent(new d(h,g)),h==="installed"?e.wn=self.setTimeout((function(){h==="installed"&&s.waiting===v&&e.dispatchEvent(new d("waiting",g))}),200):h==="activating"&&(clearTimeout(e.wn),p||e.en.resolve(v))},e.yn=function(u){var s=e.hn,v=s!==navigator.serviceWorker.controller;e.dispatchEvent(new d("controlling",{isExternal:v,originalEvent:u,sw:s,isUpdate:e.mn})),v||e.on.resolve(s)},e.gn=(i=function(u){var s=u.data,v=u.ports,h=u.source;return l(e.getSW(),(function(){e.an.has(h)&&e.dispatchEvent(new d("message",{data:s,originalEvent:u,ports:v,sw:h}))}))},function(){for(var u=[],s=0;s<arguments.length;s++)u[s]=arguments[s];try{return Promise.resolve(i.apply(this,u))}catch(v){return Promise.reject(v)}}),e.sn=c,e.nn=a,navigator.serviceWorker.addEventListener("message",e.gn),e}var t,o;o=n,(t=r).prototype=Object.create(o.prototype),t.prototype.constructor=t,w(t,o);var f=r.prototype;return f.register=function(c){var a=(c===void 0?{}:c).immediate,e=a!==void 0&&a;try{var i=this;return l((function(u,s){var v=u();return v&&v.then?v.then(s):s(v)})((function(){if(!e&&document.readyState!=="complete")return E(new Promise((function(u){return window.addEventListener("load",u)})))}),(function(){return i.mn=!!navigator.serviceWorker.controller,i.dn=i.pn(),l(i.bn(),(function(u){i.fn=u,i.dn&&(i.hn=i.dn,i.en.resolve(i.dn),i.on.resolve(i.dn),i.dn.addEventListener("statechange",i.ln,{once:!0}));var s=i.fn.waiting;return s&&y(s.scriptURL,i.sn.toString())&&(i.hn=s,Promise.resolve().then((function(){i.dispatchEvent(new d("waiting",{sw:s,wasWaitingBeforeRegister:!0}))})).then((function(){}))),i.hn&&(i.rn.resolve(i.hn),i.an.add(i.hn)),i.fn.addEventListener("updatefound",i.cn),navigator.serviceWorker.addEventListener("controllerchange",i.yn),i.fn}))})))}catch(u){return Promise.reject(u)}},f.update=function(){try{return this.fn?l(E(this.fn.update())):l()}catch(c){return Promise.reject(c)}},f.getSW=function(){return this.hn!==void 0?Promise.resolve(this.hn):this.rn.promise},f.messageSW=function(c){try{return l(this.getSW(),(function(a){return b(a,c)}))}catch(a){return Promise.reject(a)}},f.messageSkipWaiting=function(){this.fn&&this.fn.waiting&&b(this.fn.waiting,L)},f.pn=function(){var c=navigator.serviceWorker.controller;return c&&y(c.scriptURL,this.sn.toString())?c:void 0},f.bn=function(){try{var c=this;return l((function(a,e){try{var i=a()}catch(u){return e(u)}return i&&i.then?i.then(void 0,e):i})((function(){return l(navigator.serviceWorker.register(c.sn,c.nn),(function(a){return c.un=performance.now(),a}))}),(function(a){throw a})))}catch(a){return Promise.reject(a)}},(function(c,a,e){return a&&j(c.prototype,a),Object.defineProperty(c,"prototype",{writable:!1}),c})(r,[{key:"active",get:function(){return this.en.promise}},{key:"controlling",get:function(){return this.on.promise}}])})((function(){function n(){this.Pn=new Map}var r=n.prototype;return r.addEventListener=function(t,o){this.jn(t).add(o)},r.removeEventListener=function(t,o){this.jn(t).delete(o)},r.dispatchEvent=function(t){t.target=this;for(var o,f=S(this.jn(t.type));!(o=f()).done;)(0,o.value)(t)},r.jn=function(t){return this.Pn.has(t)||this.Pn.set(t,new Set),this.Pn.get(t)},n})());export{O as Workbox,d as WorkboxEvent,b as messageSW};
Binary file
Binary file
Binary file
@@ -0,0 +1,8 @@
1
+ <svg width="47" height="38" viewBox="0 0 47 38" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M5.08191 10.0769V0.938461H9.37422V10.0769H5.08191ZM9.23305 10.0769V0.938461H13.5254V10.0769H9.23305ZM13.3842 10.0769V0.938461H17.6765V10.0769H13.3842ZM17.5353 10.0769V0.938461H21.8276V10.0769H17.5353ZM21.6865 10.0769V0.938461H25.9788V10.0769H21.6865ZM25.8376 10.0769V0.938461H30.1299V10.0769H25.8376ZM29.9888 10.0769V0.938461H34.2811V10.0769H29.9888ZM34.1399 10.0769V0.938461H38.4322V10.0769H34.1399ZM38.291 10.0769V0.938461H42.5834V10.0769H38.291ZM0.930769 19.0769V9.93846H5.22308V19.0769H0.930769ZM5.08191 19.0769V9.93846H9.37422V19.0769H5.08191ZM9.23305 19.0769V14.5077H13.5254V19.0769H9.23305ZM13.3842 19.0769V9.93846H17.6765V19.0769H13.3842ZM17.5353 19.0769V9.93846H21.8276V19.0769H17.5353ZM21.6865 19.0769V9.93846H25.9788V19.0769H21.6865ZM25.8376 19.0769V9.93846H30.1299V19.0769H25.8376ZM29.9888 19.0769V9.93846H34.2811V19.0769H29.9888ZM34.1399 19.0769V14.5077H38.4322V19.0769H34.1399ZM38.291 19.0769V9.93846H42.5834V19.0769H38.291ZM42.4422 19.0769V9.93846H46.7345V19.0769H42.4422ZM5.08191 28.0769V18.9385H9.37422V28.0769H5.08191ZM9.23305 28.0769V18.9385H13.5254V28.0769H9.23305ZM13.3842 28.0769V18.9385H17.6765V28.0769H13.3842ZM17.5353 28.0769V18.9385H21.8276V28.0769H17.5353ZM21.6865 28.0769V18.9385H25.9788V28.0769H21.6865ZM25.8376 28.0769V18.9385H30.1299V28.0769H25.8376ZM29.9888 28.0769V18.9385H34.2811V28.0769H29.9888ZM34.1399 28.0769V18.9385H38.4322V28.0769H34.1399ZM38.291 28.0769V18.9385H42.5834V28.0769H38.291ZM5.08191 37.0769V27.9385H9.37422V37.0769H5.08191ZM13.3842 37.0769V27.9385H17.6765V37.0769H13.3842ZM29.9888 37.0769V27.9385H34.2811V37.0769H29.9888ZM38.291 37.0769V27.9385H42.5834V37.0769H38.291Z" fill="#5BA8A0"/>
3
+ <rect x="5.08" y="9.94" width="37.5" height="4.57" fill="#2D2D2D"/>
4
+ <rect x="5.08" y="9.94" width="12.6" height="4.57" fill="#1A1A1A"/>
5
+ <rect x="29.99" y="9.94" width="12.6" height="4.57" fill="#1A1A1A"/>
6
+ <rect x="6.3" y="10.5" width="3.1" height="1.5" fill="#444"/>
7
+ <rect x="31.2" y="10.5" width="3.1" height="1.5" fill="#444"/>
8
+ </svg>
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,21 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="bg-cc-bg">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, viewport-fit=cover, interactive-widget=resizes-content" />
6
+ <link rel="icon" href="/favicon.ico" />
7
+ <link rel="manifest" href="/manifest.json" />
8
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
9
+ <meta name="apple-mobile-web-app-capable" content="yes" />
10
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
11
+ <meta name="apple-mobile-web-app-title" content="HeyHank" />
12
+ <meta name="theme-color" content="#262624" />
13
+ <title>HeyHank</title>
14
+ <script type="module" crossorigin src="/assets/index-CEqZnThB.js"></script>
15
+ <link rel="stylesheet" crossorigin href="/assets/index-C8M_PUmX.css">
16
+ </head>
17
+ <body class="bg-cc-bg text-cc-fg">
18
+ <div id="root"></div>
19
+ <div id="voice-chat-root"></div>
20
+ </body>
21
+ </html>
Binary file
Binary file
@@ -0,0 +1,14 @@
1
+ <svg width="47" height="38" viewBox="0 0 47 38" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <!-- Clawd body in blue for Codex -->
3
+ <path d="M5.08191 10.0769V0.938461H9.37422V10.0769H5.08191ZM9.23305 10.0769V0.938461H13.5254V10.0769H9.23305ZM13.3842 10.0769V0.938461H17.6765V10.0769H13.3842ZM17.5353 10.0769V0.938461H21.8276V10.0769H17.5353ZM21.6865 10.0769V0.938461H25.9788V10.0769H21.6865ZM25.8376 10.0769V0.938461H30.1299V10.0769H25.8376ZM29.9888 10.0769V0.938461H34.2811V10.0769H29.9888ZM34.1399 10.0769V0.938461H38.4322V10.0769H34.1399ZM38.291 10.0769V0.938461H42.5834V10.0769H38.291ZM0.930769 19.0769V9.93846H5.22308V19.0769H0.930769ZM5.08191 19.0769V9.93846H9.37422V19.0769H5.08191ZM9.23305 19.0769V14.5077H13.5254V19.0769H9.23305ZM13.3842 19.0769V9.93846H17.6765V19.0769H13.3842ZM17.5353 19.0769V9.93846H21.8276V19.0769H17.5353ZM21.6865 19.0769V9.93846H25.9788V19.0769H21.6865ZM25.8376 19.0769V9.93846H30.1299V19.0769H25.8376ZM29.9888 19.0769V9.93846H34.2811V19.0769H29.9888ZM34.1399 19.0769V14.5077H38.4322V19.0769H34.1399ZM38.291 19.0769V9.93846H42.5834V19.0769H38.291ZM42.4422 19.0769V9.93846H46.7345V19.0769H42.4422ZM5.08191 28.0769V18.9385H9.37422V28.0769H5.08191ZM9.23305 28.0769V18.9385H13.5254V28.0769H9.23305ZM13.3842 28.0769V18.9385H17.6765V28.0769H13.3842ZM17.5353 28.0769V18.9385H21.8276V28.0769H17.5353ZM21.6865 28.0769V18.9385H25.9788V28.0769H21.6865ZM25.8376 28.0769V18.9385H30.1299V28.0769H25.8376ZM29.9888 28.0769V18.9385H34.2811V28.0769H29.9888ZM34.1399 28.0769V18.9385H38.4322V28.0769H34.1399ZM38.291 28.0769V18.9385H42.5834V28.0769H38.291ZM5.08191 37.0769V27.9385H9.37422V37.0769H5.08191ZM13.3842 37.0769V27.9385H17.6765V37.0769H13.3842ZM29.9888 37.0769V27.9385H34.2811V37.0769H29.9888ZM38.291 37.0769V27.9385H42.5834V37.0769H38.291Z" fill="#3B82F6"/>
4
+ <!-- Sunglasses: dark bar across eye row covering eye gaps + bridge -->
5
+ <rect x="5.08" y="9.94" width="37.5" height="4.57" rx="0" fill="#2D2D2D"/>
6
+ <!-- Left lens (slightly lighter to show reflection) -->
7
+ <rect x="5.08" y="9.94" width="12.6" height="4.57" fill="#1A1A1A"/>
8
+ <!-- Right lens -->
9
+ <rect x="29.99" y="9.94" width="12.6" height="4.57" fill="#1A1A1A"/>
10
+ <!-- Lens shine - left -->
11
+ <rect x="6.3" y="10.5" width="3.1" height="1.5" fill="#444"/>
12
+ <!-- Lens shine - right -->
13
+ <rect x="31.2" y="10.5" width="3.1" height="1.5" fill="#444"/>
14
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
2
+ <path fill="#2496ED" d="M23 27h6v6h-6zm8 0h6v6h-6zm8 0h6v6h-6zM15 35h6v6h-6zm8 0h6v6h-6zm8 0h6v6h-6zm8 0h6v6h-6zm8 0h6v6h-6zM23 43h6v6h-6zm8 0h6v6h-6zm8 0h6v6h-6zm8 0h6v6h-6z"/>
3
+ <path fill="#2496ED" d="M57.8 33.4c-1.3-.9-4.3-1-6.1-.7-.3-2-1.2-3.7-2.8-4.9l-.9-.6-.6 1c-.9 1.6-1.1 3.7-.6 5.6-1 .5-2.6.8-4.5.8H13c0 5.4 2.8 9.4 8.3 11.9 3.6 1.6 8.5 2.1 14.6 1.7 5-.3 9.4-2.1 12.9-5.3 2.9-2.7 4.8-6 5.2-8.9 1.4.1 4.5.1 6.4-1.4.5-.4 2-1.9 1.4-3.3-.1-.3-.4-.6-.8-.9Z"/>
4
+ </svg>
Binary file
package/dist/logo.png ADDED
Binary file
package/dist/logo.svg ADDED
@@ -0,0 +1,14 @@
1
+ <svg width="47" height="38" viewBox="0 0 47 38" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <!-- Clawd body in teal -->
3
+ <path d="M5.08191 10.0769V0.938461H9.37422V10.0769H5.08191ZM9.23305 10.0769V0.938461H13.5254V10.0769H9.23305ZM13.3842 10.0769V0.938461H17.6765V10.0769H13.3842ZM17.5353 10.0769V0.938461H21.8276V10.0769H17.5353ZM21.6865 10.0769V0.938461H25.9788V10.0769H21.6865ZM25.8376 10.0769V0.938461H30.1299V10.0769H25.8376ZM29.9888 10.0769V0.938461H34.2811V10.0769H29.9888ZM34.1399 10.0769V0.938461H38.4322V10.0769H34.1399ZM38.291 10.0769V0.938461H42.5834V10.0769H38.291ZM0.930769 19.0769V9.93846H5.22308V19.0769H0.930769ZM5.08191 19.0769V9.93846H9.37422V19.0769H5.08191ZM9.23305 19.0769V14.5077H13.5254V19.0769H9.23305ZM13.3842 19.0769V9.93846H17.6765V19.0769H13.3842ZM17.5353 19.0769V9.93846H21.8276V19.0769H17.5353ZM21.6865 19.0769V9.93846H25.9788V19.0769H21.6865ZM25.8376 19.0769V9.93846H30.1299V19.0769H25.8376ZM29.9888 19.0769V9.93846H34.2811V19.0769H29.9888ZM34.1399 19.0769V14.5077H38.4322V19.0769H34.1399ZM38.291 19.0769V9.93846H42.5834V19.0769H38.291ZM42.4422 19.0769V9.93846H46.7345V19.0769H42.4422ZM5.08191 28.0769V18.9385H9.37422V28.0769H5.08191ZM9.23305 28.0769V18.9385H13.5254V28.0769H9.23305ZM13.3842 28.0769V18.9385H17.6765V28.0769H13.3842ZM17.5353 28.0769V18.9385H21.8276V28.0769H17.5353ZM21.6865 28.0769V18.9385H25.9788V28.0769H21.6865ZM25.8376 28.0769V18.9385H30.1299V28.0769H25.8376ZM29.9888 28.0769V18.9385H34.2811V28.0769H29.9888ZM34.1399 28.0769V18.9385H38.4322V28.0769H34.1399ZM38.291 28.0769V18.9385H42.5834V28.0769H38.291ZM5.08191 37.0769V27.9385H9.37422V37.0769H5.08191ZM13.3842 37.0769V27.9385H17.6765V37.0769H13.3842ZM29.9888 37.0769V27.9385H34.2811V37.0769H29.9888ZM38.291 37.0769V27.9385H42.5834V37.0769H38.291Z" fill="#5BA8A0"/>
4
+ <!-- Sunglasses: dark bar across eye row covering eye gaps + bridge -->
5
+ <rect x="5.08" y="9.94" width="37.5" height="4.57" rx="0" fill="#2D2D2D"/>
6
+ <!-- Left lens (slightly lighter to show reflection) -->
7
+ <rect x="5.08" y="9.94" width="12.6" height="4.57" fill="#1A1A1A"/>
8
+ <!-- Right lens -->
9
+ <rect x="29.99" y="9.94" width="12.6" height="4.57" fill="#1A1A1A"/>
10
+ <!-- Lens shine - left -->
11
+ <rect x="6.3" y="10.5" width="3.1" height="1.5" fill="#444"/>
12
+ <!-- Lens shine - right -->
13
+ <rect x="31.2" y="10.5" width="3.1" height="1.5" fill="#444"/>
14
+ </svg>
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "HeyHank",
3
+ "short_name": "HeyHank",
4
+ "description": "Multi-Agent Platform with AI-powered coding, monitoring, and personal assistant",
5
+ "start_url": "/",
6
+ "scope": "/",
7
+ "display": "standalone",
8
+ "background_color": "#262624",
9
+ "theme_color": "#d97757",
10
+ "icons": [
11
+ {
12
+ "src": "/logo-192.png",
13
+ "sizes": "192x192",
14
+ "type": "image/png",
15
+ "purpose": "any"
16
+ },
17
+ {
18
+ "src": "/logo-512.png",
19
+ "sizes": "512x512",
20
+ "type": "image/png",
21
+ "purpose": "any"
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,34 @@
1
+ // Push notification handler for service worker
2
+ self.addEventListener("push", (event) => {
3
+ if (!event.data) return;
4
+ try {
5
+ const data = event.data.json();
6
+ const options = {
7
+ body: data.body || "",
8
+ icon: data.icon || "/icon-192.png",
9
+ badge: data.badge || "/icon-192.png",
10
+ tag: data.tag || "default",
11
+ data: data.data || {},
12
+ vibrate: [200, 100, 200],
13
+ requireInteraction: true,
14
+ };
15
+ event.waitUntil(self.registration.showNotification(data.title || "Maxx Agent", options));
16
+ } catch {
17
+ event.waitUntil(self.registration.showNotification("Maxx Agent", { body: event.data.text() }));
18
+ }
19
+ });
20
+
21
+ self.addEventListener("notificationclick", (event) => {
22
+ event.notification.close();
23
+ const url = event.notification.data?.url || "/";
24
+ event.waitUntil(
25
+ clients.matchAll({ type: "window", includeUncontrolled: true }).then((windowClients) => {
26
+ for (const client of windowClients) {
27
+ if (client.url.includes(self.location.origin) && "focus" in client) {
28
+ return client.focus();
29
+ }
30
+ }
31
+ return clients.openWindow(url);
32
+ })
33
+ );
34
+ });
package/dist/sw.js ADDED
@@ -0,0 +1 @@
1
+ if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const l=e||("document"in self?document.currentScript.src:"")||location.href;if(s[l])return;let o={};const a=e=>i(e,l),c={module:{uri:l},exports:o,require:a};s[l]=Promise.all(n.map(e=>c[e]||a(e))).then(e=>(r(...e),o))}}define(["./workbox-d2a0910a"],function(e){"use strict";importScripts("/push-sw.js"),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"push-sw.js",revision:"e7d98c64c00feed7b0214cf2e5e787cd"},{url:"logo.svg",revision:"fab346dacb0b9b9af12554e9a59f004d"},{url:"logo.png",revision:"5a16f5b0f42fe0c2d4f75b82a45839d6"},{url:"logo-original.png",revision:"60122e0a837058e5b8c3a91dbd970c5c"},{url:"logo-docker.svg",revision:"623840baec64a3438663496bb1050d30"},{url:"logo-codex.svg",revision:"c5c8611b432190ec28522f1a2dfcbb7f"},{url:"logo-512.png",revision:"03cd101bc48ed09c226a5f03af5044a1"},{url:"logo-192.png",revision:"3efeab6d679952e55978d3ae014f47ea"},{url:"index.html",revision:"916476dd8cf3802d31289cced19cfb11"},{url:"icon-512.png",revision:"83af6345269cc3e9bd922214cfb76a1a"},{url:"icon-512-original.png",revision:"c808430c9b8341ed3097be08a39f108f"},{url:"icon-192.png",revision:"15928a9084a4eb69315de042412030b5"},{url:"icon-192-original.png",revision:"98c58c6552b0881c014c7086858cf7d8"},{url:"heyhank-mascot-poster.png",revision:"f3b788ea04efbcc173b75ab79f92e869"},{url:"favicon.svg",revision:"c64ca8ae8596d4ffe13d0f591beb2274"},{url:"favicon-32.png",revision:"ffbd83395efeaf52a3d8c2eac8c21221"},{url:"favicon-32-original.png",revision:"7b0c13ce130b70c9056ea1e956d172a8"},{url:"apple-touch-icon.png",revision:"5ac7804878366b2ba47ea30482c3237d"},{url:"fonts/MesloLGSNerdFontMono-Regular.woff2",revision:"83304194a3c8be2b5b61242eeb1c1046"},{url:"fonts/MesloLGSNerdFontMono-Bold.woff2",revision:"60fafe18cbcb717c51cdabf87f9490f0"},{url:"assets/workbox-window.prod.es5-BIl4cyR9.js",revision:null},{url:"assets/time-ago-B6r_l9u1.js",revision:null},{url:"assets/sw-register-LSSpj6RU.js",revision:null},{url:"assets/index-CEqZnThB.js",revision:null},{url:"assets/index-C8M_PUmX.css",revision:null},{url:"assets/gemini-live-client-C7rqAW7G.js",revision:null},{url:"assets/gemini-audio-t-TSU-To.js",revision:null},{url:"assets/TerminalPage-Drwyrnfd.js",revision:null},{url:"assets/TelephonyPage-x0VV0fOo.js",revision:null},{url:"assets/TailscalePage-CHiFhZXF.js",revision:null},{url:"assets/SocialMediaPage-DBuM28vD.js",revision:null},{url:"assets/SettingsPage-DirhjQrJ.js",revision:null},{url:"assets/SandboxManager-a1AVI5q2.js",revision:null},{url:"assets/RunsPage-C5BZF5Rx.js",revision:null},{url:"assets/PromptsPage-D2EhsdNO.js",revision:null},{url:"assets/ProcessPanel-CslEiZkI.js",revision:null},{url:"assets/Playground-Fc5cdc5p.js",revision:null},{url:"assets/PlatformDashboard-Do6F0O2p.js",revision:null},{url:"assets/MediaPage-CE5rdvkC.js",revision:null},{url:"assets/IntegrationsPage-CrOitCmJ.js",revision:null},{url:"assets/HelpPage-DMfkzERp.js",revision:null},{url:"assets/CronManager-DDbz-yiT.js",revision:null},{url:"assets/AssistantPage-DJ-cMQfb.js",revision:null},{url:"assets/AgentsPage-BPhirnCe.js",revision:null}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html"),{denylist:[/^\/api/,/^\/ws/]})),e.registerRoute(/^\/api\//,new e.NetworkOnly,"GET"),e.registerRoute(/\.(?:png|jpg|jpeg|svg|gif|webp|ico)$/,new e.StaleWhileRevalidate({cacheName:"images",plugins:[new e.ExpirationPlugin({maxEntries:60,maxAgeSeconds:2592e3})]}),"GET"),e.registerRoute(/\.(?:woff|woff2|ttf|otf)$/,new e.CacheFirst({cacheName:"fonts",plugins:[new e.ExpirationPlugin({maxEntries:20,maxAgeSeconds:31536e3})]}),"GET")});
@@ -0,0 +1 @@
1
+ define(["exports"],function(t){"use strict";try{self["workbox:core:7.3.0"]&&_()}catch(t){}const e=(t,...e)=>{let s=t;return e.length>0&&(s+=` :: ${JSON.stringify(e)}`),s};class s extends Error{constructor(t,s){super(e(t,s)),this.name=t,this.details=s}}try{self["workbox:routing:7.3.0"]&&_()}catch(t){}const n=t=>t&&"object"==typeof t?t:{handle:t};class r{constructor(t,e,s="GET"){this.handler=n(e),this.match=t,this.method=s}setCatchHandler(t){this.catchHandler=n(t)}}class i extends r{constructor(t,e,s){super(({url:e})=>{const s=t.exec(e.href);if(s&&(e.origin===location.origin||0===s.index))return s.slice(1)},e,s)}}class a{constructor(){this.t=new Map,this.i=new Map}get routes(){return this.t}addFetchListener(){self.addEventListener("fetch",t=>{const{request:e}=t,s=this.handleRequest({request:e,event:t});s&&t.respondWith(s)})}addCacheListener(){self.addEventListener("message",t=>{if(t.data&&"CACHE_URLS"===t.data.type){const{payload:e}=t.data,s=Promise.all(e.urlsToCache.map(e=>{"string"==typeof e&&(e=[e]);const s=new Request(...e);return this.handleRequest({request:s,event:t})}));t.waitUntil(s),t.ports&&t.ports[0]&&s.then(()=>t.ports[0].postMessage(!0))}})}handleRequest({request:t,event:e}){const s=new URL(t.url,location.href);if(!s.protocol.startsWith("http"))return;const n=s.origin===location.origin,{params:r,route:i}=this.findMatchingRoute({event:e,request:t,sameOrigin:n,url:s});let a=i&&i.handler;const o=t.method;if(!a&&this.i.has(o)&&(a=this.i.get(o)),!a)return;let c;try{c=a.handle({url:s,request:t,event:e,params:r})}catch(t){c=Promise.reject(t)}const h=i&&i.catchHandler;return c instanceof Promise&&(this.o||h)&&(c=c.catch(async n=>{if(h)try{return await h.handle({url:s,request:t,event:e,params:r})}catch(t){t instanceof Error&&(n=t)}if(this.o)return this.o.handle({url:s,request:t,event:e});throw n})),c}findMatchingRoute({url:t,sameOrigin:e,request:s,event:n}){const r=this.t.get(s.method)||[];for(const i of r){let r;const a=i.match({url:t,sameOrigin:e,request:s,event:n});if(a)return r=a,(Array.isArray(r)&&0===r.length||a.constructor===Object&&0===Object.keys(a).length||"boolean"==typeof a)&&(r=void 0),{route:i,params:r}}return{}}setDefaultHandler(t,e="GET"){this.i.set(e,n(t))}setCatchHandler(t){this.o=n(t)}registerRoute(t){this.t.has(t.method)||this.t.set(t.method,[]),this.t.get(t.method).push(t)}unregisterRoute(t){if(!this.t.has(t.method))throw new s("unregister-route-but-not-found-with-method",{method:t.method});const e=this.t.get(t.method).indexOf(t);if(!(e>-1))throw new s("unregister-route-route-not-registered");this.t.get(t.method).splice(e,1)}}let o;const c=()=>(o||(o=new a,o.addFetchListener(),o.addCacheListener()),o);function h(t,e,n){let a;if("string"==typeof t){const s=new URL(t,location.href);a=new r(({url:t})=>t.href===s.href,e,n)}else if(t instanceof RegExp)a=new i(t,e,n);else if("function"==typeof t)a=new r(t,e,n);else{if(!(t instanceof r))throw new s("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});a=t}return c().registerRoute(a),a}function u(t){return new Promise(e=>setTimeout(e,t))}const l={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:"undefined"!=typeof registration?registration.scope:""},f=t=>[l.prefix,t,l.suffix].filter(t=>t&&t.length>0).join("-"),w=t=>t||f(l.precache),d=t=>t||f(l.runtime);function p(t,e){const s=new URL(t);for(const t of e)s.searchParams.delete(t);return s.href}class y{constructor(){this.promise=new Promise((t,e)=>{this.resolve=t,this.reject=e})}}const m=new Set;try{self["workbox:strategies:7.3.0"]&&_()}catch(t){}function g(t){return"string"==typeof t?new Request(t):t}class R{constructor(t,e){this.h={},Object.assign(this,e),this.event=e.event,this.u=t,this.l=new y,this.p=[],this.m=[...t.plugins],this.R=new Map;for(const t of this.m)this.R.set(t,{});this.event.waitUntil(this.l.promise)}async fetch(t){const{event:e}=this;let n=g(t);if("navigate"===n.mode&&e instanceof FetchEvent&&e.preloadResponse){const t=await e.preloadResponse;if(t)return t}const r=this.hasCallback("fetchDidFail")?n.clone():null;try{for(const t of this.iterateCallbacks("requestWillFetch"))n=await t({request:n.clone(),event:e})}catch(t){if(t instanceof Error)throw new s("plugin-error-request-will-fetch",{thrownErrorMessage:t.message})}const i=n.clone();try{let t;t=await fetch(n,"navigate"===n.mode?void 0:this.u.fetchOptions);for(const s of this.iterateCallbacks("fetchDidSucceed"))t=await s({event:e,request:i,response:t});return t}catch(t){throw r&&await this.runCallbacks("fetchDidFail",{error:t,event:e,originalRequest:r.clone(),request:i.clone()}),t}}async fetchAndCachePut(t){const e=await this.fetch(t),s=e.clone();return this.waitUntil(this.cachePut(t,s)),e}async cacheMatch(t){const e=g(t);let s;const{cacheName:n,matchOptions:r}=this.u,i=await this.getCacheKey(e,"read"),a=Object.assign(Object.assign({},r),{cacheName:n});s=await caches.match(i,a);for(const t of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await t({cacheName:n,matchOptions:r,cachedResponse:s,request:i,event:this.event})||void 0;return s}async cachePut(t,e){const n=g(t);await u(0);const r=await this.getCacheKey(n,"write");if(!e)throw new s("cache-put-with-no-response",{url:(i=r.url,new URL(String(i),location.href).href.replace(new RegExp(`^${location.origin}`),""))});var i;const a=await this.v(e);if(!a)return!1;const{cacheName:o,matchOptions:c}=this.u,h=await self.caches.open(o),l=this.hasCallback("cacheDidUpdate"),f=l?await async function(t,e,s,n){const r=p(e.url,s);if(e.url===r)return t.match(e,n);const i=Object.assign(Object.assign({},n),{ignoreSearch:!0}),a=await t.keys(e,i);for(const e of a)if(r===p(e.url,s))return t.match(e,n)}(h,r.clone(),["__WB_REVISION__"],c):null;try{await h.put(r,l?a.clone():a)}catch(t){if(t instanceof Error)throw"QuotaExceededError"===t.name&&await async function(){for(const t of m)await t()}(),t}for(const t of this.iterateCallbacks("cacheDidUpdate"))await t({cacheName:o,oldResponse:f,newResponse:a.clone(),request:r,event:this.event});return!0}async getCacheKey(t,e){const s=`${t.url} | ${e}`;if(!this.h[s]){let n=t;for(const t of this.iterateCallbacks("cacheKeyWillBeUsed"))n=g(await t({mode:e,request:n,event:this.event,params:this.params}));this.h[s]=n}return this.h[s]}hasCallback(t){for(const e of this.u.plugins)if(t in e)return!0;return!1}async runCallbacks(t,e){for(const s of this.iterateCallbacks(t))await s(e)}*iterateCallbacks(t){for(const e of this.u.plugins)if("function"==typeof e[t]){const s=this.R.get(e),n=n=>{const r=Object.assign(Object.assign({},n),{state:s});return e[t](r)};yield n}}waitUntil(t){return this.p.push(t),t}async doneWaiting(){for(;this.p.length;){const t=this.p.splice(0),e=(await Promise.allSettled(t)).find(t=>"rejected"===t.status);if(e)throw e.reason}}destroy(){this.l.resolve(null)}async v(t){let e=t,s=!1;for(const t of this.iterateCallbacks("cacheWillUpdate"))if(e=await t({request:this.request,response:e,event:this.event})||void 0,s=!0,!e)break;return s||e&&200!==e.status&&(e=void 0),e}}class v{constructor(t={}){this.cacheName=d(t.cacheName),this.plugins=t.plugins||[],this.fetchOptions=t.fetchOptions,this.matchOptions=t.matchOptions}handle(t){const[e]=this.handleAll(t);return e}handleAll(t){t instanceof FetchEvent&&(t={event:t,request:t.request});const e=t.event,s="string"==typeof t.request?new Request(t.request):t.request,n="params"in t?t.params:void 0,r=new R(this,{event:e,request:s,params:n}),i=this.q(r,s,e);return[i,this.D(i,r,s,e)]}async q(t,e,n){let r;await t.runCallbacks("handlerWillStart",{event:n,request:e});try{if(r=await this.U(e,t),!r||"error"===r.type)throw new s("no-response",{url:e.url})}catch(s){if(s instanceof Error)for(const i of t.iterateCallbacks("handlerDidError"))if(r=await i({error:s,event:n,request:e}),r)break;if(!r)throw s}for(const s of t.iterateCallbacks("handlerWillRespond"))r=await s({event:n,request:e,response:r});return r}async D(t,e,s,n){let r,i;try{r=await t}catch(i){}try{await e.runCallbacks("handlerDidRespond",{event:n,request:s,response:r}),await e.doneWaiting()}catch(t){t instanceof Error&&(i=t)}if(await e.runCallbacks("handlerDidComplete",{event:n,request:s,response:r,error:i}),e.destroy(),i)throw i}}function b(t){t.then(()=>{})}function q(){return q=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var s=arguments[e];for(var n in s)({}).hasOwnProperty.call(s,n)&&(t[n]=s[n])}return t},q.apply(null,arguments)}let D,U;const x=new WeakMap,E=new WeakMap,L=new WeakMap,I=new WeakMap,C=new WeakMap;let N={get(t,e,s){if(t instanceof IDBTransaction){if("done"===e)return E.get(t);if("objectStoreNames"===e)return t.objectStoreNames||L.get(t);if("store"===e)return s.objectStoreNames[1]?void 0:s.objectStore(s.objectStoreNames[0])}return B(t[e])},set:(t,e,s)=>(t[e]=s,!0),has:(t,e)=>t instanceof IDBTransaction&&("done"===e||"store"===e)||e in t};function O(t){return t!==IDBDatabase.prototype.transaction||"objectStoreNames"in IDBTransaction.prototype?(U||(U=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])).includes(t)?function(...e){return t.apply(T(this),e),B(x.get(this))}:function(...e){return B(t.apply(T(this),e))}:function(e,...s){const n=t.call(T(this),e,...s);return L.set(n,e.sort?e.sort():[e]),B(n)}}function k(t){return"function"==typeof t?O(t):(t instanceof IDBTransaction&&function(t){if(E.has(t))return;const e=new Promise((e,s)=>{const n=()=>{t.removeEventListener("complete",r),t.removeEventListener("error",i),t.removeEventListener("abort",i)},r=()=>{e(),n()},i=()=>{s(t.error||new DOMException("AbortError","AbortError")),n()};t.addEventListener("complete",r),t.addEventListener("error",i),t.addEventListener("abort",i)});E.set(t,e)}(t),e=t,(D||(D=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction])).some(t=>e instanceof t)?new Proxy(t,N):t);var e}function B(t){if(t instanceof IDBRequest)return function(t){const e=new Promise((e,s)=>{const n=()=>{t.removeEventListener("success",r),t.removeEventListener("error",i)},r=()=>{e(B(t.result)),n()},i=()=>{s(t.error),n()};t.addEventListener("success",r),t.addEventListener("error",i)});return e.then(e=>{e instanceof IDBCursor&&x.set(e,t)}).catch(()=>{}),C.set(e,t),e}(t);if(I.has(t))return I.get(t);const e=k(t);return e!==t&&(I.set(t,e),C.set(e,t)),e}const T=t=>C.get(t);const M=["get","getKey","getAll","getAllKeys","count"],P=["put","add","delete","clear"],W=new Map;function j(t,e){if(!(t instanceof IDBDatabase)||e in t||"string"!=typeof e)return;if(W.get(e))return W.get(e);const s=e.replace(/FromIndex$/,""),n=e!==s,r=P.includes(s);if(!(s in(n?IDBIndex:IDBObjectStore).prototype)||!r&&!M.includes(s))return;const i=async function(t,...e){const i=this.transaction(t,r?"readwrite":"readonly");let a=i.store;return n&&(a=a.index(e.shift())),(await Promise.all([a[s](...e),r&&i.done]))[0]};return W.set(e,i),i}N=(t=>q({},t,{get:(e,s,n)=>j(e,s)||t.get(e,s,n),has:(e,s)=>!!j(e,s)||t.has(e,s)}))(N);try{self["workbox:expiration:7.3.0"]&&_()}catch(t){}const S="cache-entries",K=t=>{const e=new URL(t,location.href);return e.hash="",e.href};class A{constructor(t){this._=null,this.L=t}I(t){const e=t.createObjectStore(S,{keyPath:"id"});e.createIndex("cacheName","cacheName",{unique:!1}),e.createIndex("timestamp","timestamp",{unique:!1})}C(t){this.I(t),this.L&&function(t,{blocked:e}={}){const s=indexedDB.deleteDatabase(t);e&&s.addEventListener("blocked",t=>e(t.oldVersion,t)),B(s).then(()=>{})}(this.L)}async setTimestamp(t,e){const s={url:t=K(t),timestamp:e,cacheName:this.L,id:this.N(t)},n=(await this.getDb()).transaction(S,"readwrite",{durability:"relaxed"});await n.store.put(s),await n.done}async getTimestamp(t){const e=await this.getDb(),s=await e.get(S,this.N(t));return null==s?void 0:s.timestamp}async expireEntries(t,e){const s=await this.getDb();let n=await s.transaction(S).store.index("timestamp").openCursor(null,"prev");const r=[];let i=0;for(;n;){const s=n.value;s.cacheName===this.L&&(t&&s.timestamp<t||e&&i>=e?r.push(n.value):i++),n=await n.continue()}const a=[];for(const t of r)await s.delete(S,t.id),a.push(t.url);return a}N(t){return this.L+"|"+K(t)}async getDb(){return this._||(this._=await function(t,e,{blocked:s,upgrade:n,blocking:r,terminated:i}={}){const a=indexedDB.open(t,e),o=B(a);return n&&a.addEventListener("upgradeneeded",t=>{n(B(a.result),t.oldVersion,t.newVersion,B(a.transaction),t)}),s&&a.addEventListener("blocked",t=>s(t.oldVersion,t.newVersion,t)),o.then(t=>{i&&t.addEventListener("close",()=>i()),r&&t.addEventListener("versionchange",t=>r(t.oldVersion,t.newVersion,t))}).catch(()=>{}),o}("workbox-expiration",1,{upgrade:this.C.bind(this)})),this._}}class F{constructor(t,e={}){this.O=!1,this.k=!1,this.B=e.maxEntries,this.T=e.maxAgeSeconds,this.M=e.matchOptions,this.L=t,this.P=new A(t)}async expireEntries(){if(this.O)return void(this.k=!0);this.O=!0;const t=this.T?Date.now()-1e3*this.T:0,e=await this.P.expireEntries(t,this.B),s=await self.caches.open(this.L);for(const t of e)await s.delete(t,this.M);this.O=!1,this.k&&(this.k=!1,b(this.expireEntries()))}async updateTimestamp(t){await this.P.setTimestamp(t,Date.now())}async isURLExpired(t){if(this.T){const e=await this.P.getTimestamp(t),s=Date.now()-1e3*this.T;return void 0===e||e<s}return!1}async delete(){this.k=!1,await this.P.expireEntries(1/0)}}const $={cacheWillUpdate:async({response:t})=>200===t.status||0===t.status?t:null};function H(t,e){const s=e();return t.waitUntil(s),s}try{self["workbox:precaching:7.3.0"]&&_()}catch(t){}function G(t){if(!t)throw new s("add-to-cache-list-unexpected-type",{entry:t});if("string"==typeof t){const e=new URL(t,location.href);return{cacheKey:e.href,url:e.href}}const{revision:e,url:n}=t;if(!n)throw new s("add-to-cache-list-unexpected-type",{entry:t});if(!e){const t=new URL(n,location.href);return{cacheKey:t.href,url:t.href}}const r=new URL(n,location.href),i=new URL(n,location.href);return r.searchParams.set("__WB_REVISION__",e),{cacheKey:r.href,url:i.href}}class V{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:t,state:e})=>{e&&(e.originalRequest=t)},this.cachedResponseWillBeUsed=async({event:t,state:e,cachedResponse:s})=>{if("install"===t.type&&e&&e.originalRequest&&e.originalRequest instanceof Request){const t=e.originalRequest.url;s?this.notUpdatedURLs.push(t):this.updatedURLs.push(t)}return s}}}class J{constructor({precacheController:t}){this.cacheKeyWillBeUsed=async({request:t,params:e})=>{const s=(null==e?void 0:e.cacheKey)||this.W.getCacheKeyForURL(t.url);return s?new Request(s,{headers:t.headers}):t},this.W=t}}let Q,z;async function X(t,e){let n=null;if(t.url){n=new URL(t.url).origin}if(n!==self.location.origin)throw new s("cross-origin-copy-response",{origin:n});const r=t.clone(),i={headers:new Headers(r.headers),status:r.status,statusText:r.statusText},a=e?e(i):i,o=function(){if(void 0===Q){const t=new Response("");if("body"in t)try{new Response(t.body),Q=!0}catch(t){Q=!1}Q=!1}return Q}()?r.body:await r.blob();return new Response(o,a)}class Y extends v{constructor(t={}){t.cacheName=w(t.cacheName),super(t),this.j=!1!==t.fallbackToNetwork,this.plugins.push(Y.copyRedirectedCacheableResponsesPlugin)}async U(t,e){const s=await e.cacheMatch(t);return s||(e.event&&"install"===e.event.type?await this.S(t,e):await this.K(t,e))}async K(t,e){let n;const r=e.params||{};if(!this.j)throw new s("missing-precache-entry",{cacheName:this.cacheName,url:t.url});{const s=r.integrity,i=t.integrity,a=!i||i===s;n=await e.fetch(new Request(t,{integrity:"no-cors"!==t.mode?i||s:void 0})),s&&a&&"no-cors"!==t.mode&&(this.A(),await e.cachePut(t,n.clone()))}return n}async S(t,e){this.A();const n=await e.fetch(t);if(!await e.cachePut(t,n.clone()))throw new s("bad-precaching-response",{url:t.url,status:n.status});return n}A(){let t=null,e=0;for(const[s,n]of this.plugins.entries())n!==Y.copyRedirectedCacheableResponsesPlugin&&(n===Y.defaultPrecacheCacheabilityPlugin&&(t=s),n.cacheWillUpdate&&e++);0===e?this.plugins.push(Y.defaultPrecacheCacheabilityPlugin):e>1&&null!==t&&this.plugins.splice(t,1)}}Y.defaultPrecacheCacheabilityPlugin={cacheWillUpdate:async({response:t})=>!t||t.status>=400?null:t},Y.copyRedirectedCacheableResponsesPlugin={cacheWillUpdate:async({response:t})=>t.redirected?await X(t):t};class Z{constructor({cacheName:t,plugins:e=[],fallbackToNetwork:s=!0}={}){this.F=new Map,this.$=new Map,this.H=new Map,this.u=new Y({cacheName:w(t),plugins:[...e,new J({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this.u}precache(t){this.addToCacheList(t),this.G||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this.G=!0)}addToCacheList(t){const e=[];for(const n of t){"string"==typeof n?e.push(n):n&&void 0===n.revision&&e.push(n.url);const{cacheKey:t,url:r}=G(n),i="string"!=typeof n&&n.revision?"reload":"default";if(this.F.has(r)&&this.F.get(r)!==t)throw new s("add-to-cache-list-conflicting-entries",{firstEntry:this.F.get(r),secondEntry:t});if("string"!=typeof n&&n.integrity){if(this.H.has(t)&&this.H.get(t)!==n.integrity)throw new s("add-to-cache-list-conflicting-integrities",{url:r});this.H.set(t,n.integrity)}if(this.F.set(r,t),this.$.set(r,i),e.length>0){const t=`Workbox is precaching URLs without revision info: ${e.join(", ")}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(t)}}}install(t){return H(t,async()=>{const e=new V;this.strategy.plugins.push(e);for(const[e,s]of this.F){const n=this.H.get(s),r=this.$.get(e),i=new Request(e,{integrity:n,cache:r,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:s},request:i,event:t}))}const{updatedURLs:s,notUpdatedURLs:n}=e;return{updatedURLs:s,notUpdatedURLs:n}})}activate(t){return H(t,async()=>{const t=await self.caches.open(this.strategy.cacheName),e=await t.keys(),s=new Set(this.F.values()),n=[];for(const r of e)s.has(r.url)||(await t.delete(r),n.push(r.url));return{deletedURLs:n}})}getURLsToCacheKeys(){return this.F}getCachedURLs(){return[...this.F.keys()]}getCacheKeyForURL(t){const e=new URL(t,location.href);return this.F.get(e.href)}getIntegrityForCacheKey(t){return this.H.get(t)}async matchPrecache(t){const e=t instanceof Request?t.url:t,s=this.getCacheKeyForURL(e);if(s){return(await self.caches.open(this.strategy.cacheName)).match(s)}}createHandlerBoundToURL(t){const e=this.getCacheKeyForURL(t);if(!e)throw new s("non-precached-url",{url:t});return s=>(s.request=new Request(t),s.params=Object.assign({cacheKey:e},s.params),this.strategy.handle(s))}}const tt=()=>(z||(z=new Z),z);class et extends r{constructor(t,e){super(({request:s})=>{const n=t.getURLsToCacheKeys();for(const r of function*(t,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:s="index.html",cleanURLs:n=!0,urlManipulation:r}={}){const i=new URL(t,location.href);i.hash="",yield i.href;const a=function(t,e=[]){for(const s of[...t.searchParams.keys()])e.some(t=>t.test(s))&&t.searchParams.delete(s);return t}(i,e);if(yield a.href,s&&a.pathname.endsWith("/")){const t=new URL(a.href);t.pathname+=s,yield t.href}if(n){const t=new URL(a.href);t.pathname+=".html",yield t.href}if(r){const t=r({url:i});for(const e of t)yield e.href}}(s.url,e)){const e=n.get(r);if(e){return{cacheKey:e,integrity:t.getIntegrityForCacheKey(e)}}}},t.strategy)}}t.CacheFirst=class extends v{async U(t,e){let n,r=await e.cacheMatch(t);if(!r)try{r=await e.fetchAndCachePut(t)}catch(t){t instanceof Error&&(n=t)}if(!r)throw new s("no-response",{url:t.url,error:n});return r}},t.ExpirationPlugin=class{constructor(t={}){this.cachedResponseWillBeUsed=async({event:t,request:e,cacheName:s,cachedResponse:n})=>{if(!n)return null;const r=this.V(n),i=this.J(s);b(i.expireEntries());const a=i.updateTimestamp(e.url);if(t)try{t.waitUntil(a)}catch(t){}return r?n:null},this.cacheDidUpdate=async({cacheName:t,request:e})=>{const s=this.J(t);await s.updateTimestamp(e.url),await s.expireEntries()},this.X=t,this.T=t.maxAgeSeconds,this.Y=new Map,t.purgeOnQuotaError&&function(t){m.add(t)}(()=>this.deleteCacheAndMetadata())}J(t){if(t===d())throw new s("expire-custom-caches-only");let e=this.Y.get(t);return e||(e=new F(t,this.X),this.Y.set(t,e)),e}V(t){if(!this.T)return!0;const e=this.Z(t);if(null===e)return!0;return e>=Date.now()-1e3*this.T}Z(t){if(!t.headers.has("date"))return null;const e=t.headers.get("date"),s=new Date(e).getTime();return isNaN(s)?null:s}async deleteCacheAndMetadata(){for(const[t,e]of this.Y)await self.caches.delete(t),await e.delete();this.Y=new Map}},t.NavigationRoute=class extends r{constructor(t,{allowlist:e=[/./],denylist:s=[]}={}){super(t=>this.tt(t),t),this.et=e,this.st=s}tt({url:t,request:e}){if(e&&"navigate"!==e.mode)return!1;const s=t.pathname+t.search;for(const t of this.st)if(t.test(s))return!1;return!!this.et.some(t=>t.test(s))}},t.NetworkOnly=class extends v{constructor(t={}){super(t),this.nt=t.networkTimeoutSeconds||0}async U(t,e){let n,r;try{const s=[e.fetch(t)];if(this.nt){const t=u(1e3*this.nt);s.push(t)}if(r=await Promise.race(s),!r)throw new Error(`Timed out the network response after ${this.nt} seconds.`)}catch(t){t instanceof Error&&(n=t)}if(!r)throw new s("no-response",{url:t.url,error:n});return r}},t.StaleWhileRevalidate=class extends v{constructor(t={}){super(t),this.plugins.some(t=>"cacheWillUpdate"in t)||this.plugins.unshift($)}async U(t,e){const n=e.fetchAndCachePut(t).catch(()=>{});e.waitUntil(n);let r,i=await e.cacheMatch(t);if(i);else try{i=await n}catch(t){t instanceof Error&&(r=t)}if(!i)throw new s("no-response",{url:t.url,error:r});return i}},t.cleanupOutdatedCaches=function(){self.addEventListener("activate",t=>{const e=w();t.waitUntil((async(t,e="-precache-")=>{const s=(await self.caches.keys()).filter(s=>s.includes(e)&&s.includes(self.registration.scope)&&s!==t);return await Promise.all(s.map(t=>self.caches.delete(t))),s})(e).then(t=>{}))})},t.clientsClaim=function(){self.addEventListener("activate",()=>self.clients.claim())},t.createHandlerBoundToURL=function(t){return tt().createHandlerBoundToURL(t)},t.precacheAndRoute=function(t,e){!function(t){tt().precache(t)}(t),function(t){const e=tt();h(new et(e,t))}(e)},t.registerRoute=h});
package/package.json ADDED
@@ -0,0 +1,109 @@
1
+ {
2
+ "name": "heyhank",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Self-hosted web UI for running Claude Code and Codex agents",
6
+ "license": "MIT",
7
+ "author": "Markus Stoeger",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/heyhank-app/heyhank"
11
+ },
12
+ "bin": {
13
+ "heyhank": "./bin/cli.ts"
14
+ },
15
+ "files": [
16
+ "bin/",
17
+ "server/",
18
+ "dist/",
19
+ "!server/**/*.test.ts",
20
+ "!server/**/*.test.tsx",
21
+ "!server/**/*.spec.ts"
22
+ ],
23
+ "engines": {
24
+ "bun": ">=1.0.0"
25
+ },
26
+ "keywords": [
27
+ "claude",
28
+ "claude-code",
29
+ "ai",
30
+ "agent",
31
+ "web-ui",
32
+ "vibe-coding"
33
+ ],
34
+ "scripts": {
35
+ "dev": "bun dev.ts",
36
+ "dev:api": "bun --watch server/index.ts",
37
+ "dev:vite": "vite",
38
+ "build": "vite build",
39
+ "start": "NODE_ENV=production bun server/index.ts",
40
+ "prepublishOnly": "bun run build",
41
+ "typecheck": "tsc --noEmit",
42
+ "deadcode:check": "tsc --project tsconfig.deadcode.json",
43
+ "dry:check": "jscpd src server --min-lines 20 --threshold 1.5 --reporters console --ignore \"**/*.test.ts,**/*.test.tsx,**/*.spec.ts,**/*.spec.tsx\"",
44
+ "test": "vitest run",
45
+ "test:watch": "vitest",
46
+ "test:a11y": "vitest run --testNamePattern='axe accessibility|accessible names|aria-expanded'",
47
+ "test:codex-contract": "vitest run server/codex-protocol-*.test.ts",
48
+ "generate-token": "bun bin/generate-token.ts"
49
+ },
50
+ "dependencies": {
51
+ "@codemirror/lang-cpp": "^6.0.3",
52
+ "@codemirror/lang-css": "^6.3.1",
53
+ "@codemirror/lang-html": "^6.4.11",
54
+ "@codemirror/lang-java": "^6.0.2",
55
+ "@codemirror/lang-javascript": "6.2.4",
56
+ "@codemirror/lang-json": "^6.0.2",
57
+ "@codemirror/lang-markdown": "^6.5.0",
58
+ "@codemirror/lang-python": "^6.2.1",
59
+ "@codemirror/lang-rust": "^6.0.2",
60
+ "@codemirror/lang-sql": "^6.10.0",
61
+ "@codemirror/lang-xml": "^6.1.0",
62
+ "@codemirror/lang-yaml": "^6.1.2",
63
+ "@codemirror/view": "6.39.15",
64
+ "@uiw/react-codemirror": "4.25.4",
65
+ "axe-core": "^4.11.1",
66
+ "croner": "^10.0.1",
67
+ "diff": "^8.0.3",
68
+ "fzf": "^0.5.2",
69
+ "hono": "^4.7.0",
70
+ "ical-generator": "^10.1.0",
71
+ "ical.js": "^2.2.1",
72
+ "imapflow": "^1.2.18",
73
+ "nodemailer": "^8.0.4",
74
+ "posthog-js": "^1.347.2",
75
+ "qrcode": "^1.5.4",
76
+ "tsdav": "^2.1.8",
77
+ "web-push": "^3.6.7",
78
+ "ws": "^8.19.0"
79
+ },
80
+ "devDependencies": {
81
+ "@tailwindcss/vite": "^4.0.0",
82
+ "@testing-library/jest-dom": "^6.9.1",
83
+ "@testing-library/react": "^16.3.2",
84
+ "@testing-library/user-event": "^14.6.1",
85
+ "@types/bun": "^1.2.5",
86
+ "@types/nodemailer": "^7.0.11",
87
+ "@types/qrcode": "^1.5.6",
88
+ "@types/react": "^19.0.0",
89
+ "@types/react-dom": "^19.0.0",
90
+ "@types/ws": "^8.18.1",
91
+ "@vitejs/plugin-react": "^4.4.0",
92
+ "@vitest/coverage-v8": "^4.0.18",
93
+ "@xterm/addon-fit": "^0.11.0",
94
+ "@xterm/xterm": "^6.0.0",
95
+ "jscpd": "^4.0.8",
96
+ "jsdom": "^28.0.0",
97
+ "react": "^19.0.0",
98
+ "react-dom": "^19.0.0",
99
+ "react-markdown": "^10.1.0",
100
+ "remark-gfm": "^4.0.1",
101
+ "tailwindcss": "^4.0.0",
102
+ "typescript": "^5.9.3",
103
+ "vite": "^6.3.0",
104
+ "vite-plugin-pwa": "^1.2.0",
105
+ "vitest": "^4.0.18",
106
+ "vitest-axe": "^0.1.0",
107
+ "zustand": "^5.0.0"
108
+ }
109
+ }
@@ -0,0 +1,85 @@
1
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import * as agentStore from "./agent-store.js";
4
+ import type { CronJob } from "./cron-types.js";
5
+ import { HEYHANK_HOME } from "./paths.js";
6
+
7
+ const CRON_DIR = join(HEYHANK_HOME, "cron");
8
+ const MIGRATION_FLAG = join(HEYHANK_HOME, ".cron-migrated");
9
+
10
+ /**
11
+ * One-time migration: convert existing cron jobs into agents with schedule triggers.
12
+ * Safe to call multiple times — only runs once (uses a flag file).
13
+ */
14
+ export function migrateCronJobsToAgents(): { migrated: number; skipped: number } {
15
+ // Skip if already migrated
16
+ if (existsSync(MIGRATION_FLAG)) {
17
+ return { migrated: 0, skipped: 0 };
18
+ }
19
+
20
+ // Skip if no cron directory
21
+ if (!existsSync(CRON_DIR)) {
22
+ // Mark as migrated (nothing to migrate)
23
+ writeFileSync(MIGRATION_FLAG, new Date().toISOString(), "utf-8");
24
+ return { migrated: 0, skipped: 0 };
25
+ }
26
+
27
+ const files = readdirSync(CRON_DIR).filter((f) => f.endsWith(".json"));
28
+ let migrated = 0;
29
+ let skipped = 0;
30
+
31
+ for (const file of files) {
32
+ try {
33
+ const raw = readFileSync(join(CRON_DIR, file), "utf-8");
34
+ const job: CronJob = JSON.parse(raw);
35
+
36
+ // Check if an agent with this name already exists
37
+ const existingAgents = agentStore.listAgents();
38
+ const alreadyExists = existingAgents.some(
39
+ (a) => a.name.toLowerCase() === job.name.toLowerCase(),
40
+ );
41
+ if (alreadyExists) {
42
+ console.log(`[cron-migrator] Skipping "${job.name}" — agent with same name already exists`);
43
+ skipped++;
44
+ continue;
45
+ }
46
+
47
+ agentStore.createAgent({
48
+ version: 1,
49
+ name: job.name,
50
+ description: `Migrated from scheduled job: ${job.name}`,
51
+ icon: "⏰",
52
+ backendType: job.backendType,
53
+ model: job.model,
54
+ permissionMode: job.permissionMode,
55
+ cwd: job.cwd,
56
+ envSlug: job.envSlug,
57
+ codexInternetAccess: job.codexInternetAccess,
58
+ prompt: job.prompt,
59
+ triggers: {
60
+ schedule: {
61
+ enabled: job.enabled,
62
+ expression: job.schedule,
63
+ recurring: job.recurring,
64
+ },
65
+ },
66
+ enabled: job.enabled,
67
+ });
68
+
69
+ migrated++;
70
+ console.log(`[cron-migrator] Migrated cron job "${job.name}" to agent`);
71
+ } catch (err) {
72
+ console.error(`[cron-migrator] Failed to migrate ${file}:`, err);
73
+ skipped++;
74
+ }
75
+ }
76
+
77
+ // Mark migration as complete
78
+ writeFileSync(MIGRATION_FLAG, new Date().toISOString(), "utf-8");
79
+
80
+ if (migrated > 0 || skipped > 0) {
81
+ console.log(`[cron-migrator] Migration complete: ${migrated} migrated, ${skipped} skipped`);
82
+ }
83
+
84
+ return { migrated, skipped };
85
+ }