getbotons 0.0.0-stage → 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 (191) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +505 -2
  3. package/installer/admin.mjs +6 -0
  4. package/installer/botons.mjs +430 -0
  5. package/installer/brain-clis.mjs +28 -0
  6. package/installer/linger.mjs +39 -0
  7. package/installer/output.mjs +27 -0
  8. package/installer/revision.mjs +30 -0
  9. package/installer/shell-path.mjs +66 -0
  10. package/installer/source-lock.json +2891 -0
  11. package/package.json +26 -4
  12. package/payload/LICENSE +21 -0
  13. package/payload/SECURITY.md +33 -0
  14. package/payload/installer/admin.mjs +6 -0
  15. package/payload/installer/botons.mjs +430 -0
  16. package/payload/installer/brain-clis.mjs +28 -0
  17. package/payload/installer/linger.mjs +39 -0
  18. package/payload/installer/output.mjs +27 -0
  19. package/payload/installer/revision.mjs +30 -0
  20. package/payload/installer/shell-path.mjs +66 -0
  21. package/payload/installer/source-lock.json +2891 -0
  22. package/payload/npm-shrinkwrap.json +51 -0
  23. package/payload/package.json +13 -0
  24. package/payload/scripts/backup.ts +47 -0
  25. package/payload/scripts/botons-backup.service +15 -0
  26. package/payload/scripts/botons-backup.timer +11 -0
  27. package/payload/scripts/botons.service +36 -0
  28. package/payload/scripts/check-browser.ts +90 -0
  29. package/payload/scripts/doctor.ts +790 -0
  30. package/payload/scripts/install-backup-timer.ts +52 -0
  31. package/payload/scripts/install-service.ts +206 -0
  32. package/payload/scripts/login.ts +17 -0
  33. package/payload/scripts/migrate-names.ts +47 -0
  34. package/payload/scripts/pair.ts +163 -0
  35. package/payload/scripts/restore.ts +108 -0
  36. package/payload/server/bin/botons +4 -0
  37. package/payload/server/bin/legacy/minion +4 -0
  38. package/payload/server/bin/legacy/tots +4 -0
  39. package/payload/server/src/acting.ts +16 -0
  40. package/payload/server/src/api-keys.ts +544 -0
  41. package/payload/server/src/api.ts +1484 -0
  42. package/payload/server/src/app.ts +546 -0
  43. package/payload/server/src/approvals.ts +873 -0
  44. package/payload/server/src/ask-rules.ts +181 -0
  45. package/payload/server/src/audit.ts +158 -0
  46. package/payload/server/src/auth.ts +497 -0
  47. package/payload/server/src/backup.ts +382 -0
  48. package/payload/server/src/brain-login.ts +75 -0
  49. package/payload/server/src/browser-cert.ts +67 -0
  50. package/payload/server/src/browser-handoff.ts +119 -0
  51. package/payload/server/src/browser-handover.ts +175 -0
  52. package/payload/server/src/browser-policy.ts +210 -0
  53. package/payload/server/src/browser-proxy.ts +580 -0
  54. package/payload/server/src/browser-request.ts +335 -0
  55. package/payload/server/src/browser-screenshot.ts +56 -0
  56. package/payload/server/src/browser-send.ts +155 -0
  57. package/payload/server/src/browser.ts +2164 -0
  58. package/payload/server/src/callback.ts +218 -0
  59. package/payload/server/src/capabilities.ts +90 -0
  60. package/payload/server/src/cards.ts +243 -0
  61. package/payload/server/src/cli/approve-mcp.ts +119 -0
  62. package/payload/server/src/cli/botons.ts +629 -0
  63. package/payload/server/src/cli/browser-mcp.ts +168 -0
  64. package/payload/server/src/cli/workspace-file.ts +78 -0
  65. package/payload/server/src/cli-homes.ts +38 -0
  66. package/payload/server/src/cli-install.ts +104 -0
  67. package/payload/server/src/cli-updates.ts +112 -0
  68. package/payload/server/src/codex-config-repair.ts +80 -0
  69. package/payload/server/src/config.ts +473 -0
  70. package/payload/server/src/connector-access.ts +158 -0
  71. package/payload/server/src/connector-launch.ts +203 -0
  72. package/payload/server/src/connectors.ts +947 -0
  73. package/payload/server/src/disk-usage.ts +62 -0
  74. package/payload/server/src/engine.ts +2328 -0
  75. package/payload/server/src/env-init.ts +5 -0
  76. package/payload/server/src/events.ts +46 -0
  77. package/payload/server/src/files.ts +528 -0
  78. package/payload/server/src/github-access.ts +52 -0
  79. package/payload/server/src/health.ts +117 -0
  80. package/payload/server/src/held-dir.ts +96 -0
  81. package/payload/server/src/hooks.ts +141 -0
  82. package/payload/server/src/http.ts +207 -0
  83. package/payload/server/src/index.ts +139 -0
  84. package/payload/server/src/install-type.ts +11 -0
  85. package/payload/server/src/instructions.ts +204 -0
  86. package/payload/server/src/isolation.ts +234 -0
  87. package/payload/server/src/legacy-upgrade.ts +668 -0
  88. package/payload/server/src/limits.ts +44 -0
  89. package/payload/server/src/lock.ts +75 -0
  90. package/payload/server/src/log.ts +137 -0
  91. package/payload/server/src/managed-clis.ts +65 -0
  92. package/payload/server/src/mcp-client.ts +229 -0
  93. package/payload/server/src/notify.ts +193 -0
  94. package/payload/server/src/oauth-connections.ts +650 -0
  95. package/payload/server/src/oauth.ts +311 -0
  96. package/payload/server/src/outbound.ts +142 -0
  97. package/payload/server/src/portable.ts +1669 -0
  98. package/payload/server/src/prompt.ts +676 -0
  99. package/payload/server/src/providers/api-brain.ts +612 -0
  100. package/payload/server/src/providers/api-history.ts +131 -0
  101. package/payload/server/src/providers/api-tools.ts +481 -0
  102. package/payload/server/src/providers/api-wire.ts +690 -0
  103. package/payload/server/src/providers/claude-cli.ts +620 -0
  104. package/payload/server/src/providers/cli-check.ts +265 -0
  105. package/payload/server/src/providers/cli-turn.ts +20 -0
  106. package/payload/server/src/providers/codex-cli.ts +552 -0
  107. package/payload/server/src/providers/codex-workspace.ts +61 -0
  108. package/payload/server/src/providers/failures.ts +143 -0
  109. package/payload/server/src/providers/index.ts +131 -0
  110. package/payload/server/src/providers/mcp-stdio.ts +147 -0
  111. package/payload/server/src/providers/mock.ts +94 -0
  112. package/payload/server/src/providers/process.ts +268 -0
  113. package/payload/server/src/providers/readiness.ts +43 -0
  114. package/payload/server/src/providers/sse.ts +73 -0
  115. package/payload/server/src/providers/types.ts +173 -0
  116. package/payload/server/src/rates.ts +128 -0
  117. package/payload/server/src/reactions.ts +266 -0
  118. package/payload/server/src/recent-logs.ts +489 -0
  119. package/payload/server/src/rehearsal.ts +593 -0
  120. package/payload/server/src/rehearsals.ts +303 -0
  121. package/payload/server/src/replay.ts +165 -0
  122. package/payload/server/src/sandbox.ts +767 -0
  123. package/payload/server/src/schedule.ts +214 -0
  124. package/payload/server/src/scheduler.ts +24 -0
  125. package/payload/server/src/schema.ts +882 -0
  126. package/payload/server/src/secrets.ts +46 -0
  127. package/payload/server/src/setup.ts +356 -0
  128. package/payload/server/src/skill-packages.ts +363 -0
  129. package/payload/server/src/skills.ts +527 -0
  130. package/payload/server/src/spend.ts +255 -0
  131. package/payload/server/src/ssrf.ts +126 -0
  132. package/payload/server/src/starter-gate.ts +168 -0
  133. package/payload/server/src/starters.ts +610 -0
  134. package/payload/server/src/storage.ts +32 -0
  135. package/payload/server/src/store.ts +3087 -0
  136. package/payload/server/src/tailnet.ts +132 -0
  137. package/payload/server/src/templates.ts +454 -0
  138. package/payload/server/src/trash.ts +772 -0
  139. package/payload/server/src/util.ts +97 -0
  140. package/payload/server/src/webhooks.ts +216 -0
  141. package/payload/server/src/work-feed.ts +473 -0
  142. package/payload/shared/app-version.d.mts +1 -0
  143. package/payload/shared/app-version.mjs +3 -0
  144. package/payload/shared/boton.ts +323 -0
  145. package/payload/shared/brand.ts +107 -0
  146. package/payload/shared/cards.ts +427 -0
  147. package/payload/shared/effort.ts +91 -0
  148. package/payload/shared/files.ts +104 -0
  149. package/payload/shared/handoff.ts +97 -0
  150. package/payload/shared/install-commands.ts +7 -0
  151. package/payload/shared/legacy-names.d.mts +35 -0
  152. package/payload/shared/legacy-names.mjs +244 -0
  153. package/payload/shared/live-view.ts +94 -0
  154. package/payload/shared/look-critter.ts +189 -0
  155. package/payload/shared/look-tater.ts +127 -0
  156. package/payload/shared/look.ts +200 -0
  157. package/payload/shared/reactions.ts +43 -0
  158. package/payload/shared/seeded.ts +34 -0
  159. package/payload/shared/services.ts +90 -0
  160. package/payload/shared/types.ts +3016 -0
  161. package/payload/shared/util.ts +68 -0
  162. package/payload/shared/versions.ts +44 -0
  163. package/payload/shared/wake.ts +164 -0
  164. package/payload/web/dist/apple-touch-icon.png +0 -0
  165. package/payload/web/dist/assets/Icons-nytVAa9g.js +1 -0
  166. package/payload/web/dist/assets/NewBotDialog-DXr2XhDP.js +1 -0
  167. package/payload/web/dist/assets/PairScreen-CMcJO7cQ.js +1 -0
  168. package/payload/web/dist/assets/Panel-Bvqz3I3m.js +4 -0
  169. package/payload/web/dist/assets/ReactionPicker-B0SKsjiy.js +1 -0
  170. package/payload/web/dist/assets/SettingRows-CvLomtuo.js +4 -0
  171. package/payload/web/dist/assets/SettingsDialog-CQ41LV-P.js +8 -0
  172. package/payload/web/dist/assets/Setup-C-3P7Mtn.js +1 -0
  173. package/payload/web/dist/assets/Skills-DXMH69kH.js +1 -0
  174. package/payload/web/dist/assets/StarterSetup-Ce2V1_Cs.js +5 -0
  175. package/payload/web/dist/assets/Steps-CKtB3GAM.js +1 -0
  176. package/payload/web/dist/assets/emoji-catalog-DfpPz8qw.js +540 -0
  177. package/payload/web/dist/assets/index-BEU7FDxV.css +1 -0
  178. package/payload/web/dist/assets/index-Dhwn-rf0.js +3 -0
  179. package/payload/web/dist/assets/react-C1iE8mnY.js +9 -0
  180. package/payload/web/dist/assets/reactions-rw0YeCmA.js +1 -0
  181. package/payload/web/dist/assets/rolldown-runtime-XKyaHtl9.js +1 -0
  182. package/payload/web/dist/assets/util-Cx5feSJu.js +1 -0
  183. package/payload/web/dist/assets/vendor-B3qdcjGo.js +14 -0
  184. package/payload/web/dist/favicon.svg +1 -0
  185. package/payload/web/dist/icon-192.png +0 -0
  186. package/payload/web/dist/icon-512.png +0 -0
  187. package/payload/web/dist/icon.svg +1 -0
  188. package/payload/web/dist/index.html +67 -0
  189. package/payload/web/dist/manifest.webmanifest +12 -0
  190. package/shared/legacy-names.d.mts +35 -0
  191. package/shared/legacy-names.mjs +244 -0
@@ -0,0 +1,8 @@
1
+ import{A as e,C as t,D as n,E as r,O as i,S as a,T as o,_ as s,b as c,f as l,j as u,n as d,p as f,u as p,v as m,w as h,y as g}from"./index-Dhwn-rf0.js";import{i as _,t as v}from"./react-C1iE8mnY.js";import{C as y,E as b,L as x,N as S,a as C,b as w,h as T,i as E,p as D,t as O,v as k,z as A}from"./util-Cx5feSJu.js";import{C as j,_ as ee,h as te,s as ne,v as re,x as M}from"./Steps-CKtB3GAM.js";import{a as ie,b as ae,f as oe,i as se,n as ce,o as le,r as N,v as ue,y as de}from"./SettingRows-CvLomtuo.js";import{c as fe,l as P,n as pe,o as F,r as me,s as he}from"./Setup-C-3P7Mtn.js";import{c as ge,n as _e,o as ve}from"./Icons-nytVAa9g.js";import{c as ye,o as be,r as xe,s as Se}from"./StarterSetup-Ce2V1_Cs.js";import{r as Ce,t as I}from"./reactions-rw0YeCmA.js";var L=_(),R=v(),z={ok:`The key works`,invalid:`The provider refused this key (401). Check it was copied whole, or create a new one.`,forbidden:`The provider knows this key but it may not list models (403). Check its permissions or project.`,rate_limited:`The provider is limiting requests from this key (429). Wait a minute, then test again.`,unavailable:`The provider had a problem answering (5xx). Try again later.`,unreachable:`No answer from the provider. Check this server's network, DNS and proxy.`,unexpected:`The provider's answer wasn't one Botons could read.`},B={yes:`yes`,no:`no`,unknown:`unknown`};function we(e){let t=[e.capabilities.realtimeVoice,e.capabilities.speechToText,e.capabilities.textToSpeech];return t.every(e=>e===t[0])?B[t[0]]:`voice ${B[t[0]]}, speech-to-text ${B[t[1]]}, text-to-speech ${B[t[2]]}`}function Te(e){return e.source===`saved`?e.environment?`Saved key in use (it takes the place of ${e.envVar} in the server's environment)`:`Saved key in use`:e.source===`environment`?`Using ${e.envVar} from the server's environment`:`No key`}function Ee({observed:e}){if(!e.length)return(0,R.jsx)(`p`,{className:`hint`,children:`No turns on this key yet: what its models can do is only what the model list says.`});let t=e=>[e.chat&&`chat (${D(e.chat)})`,e.tools&&`tool use (${D(e.tools)})`,e.vision&&`images (${D(e.vision)})`].filter(Boolean).join(`, `);return(0,R.jsxs)(`div`,{className:`api-key-observed`,children:[(0,R.jsx)(`p`,{className:`hint`,children:`Seen working in this key's own turns (first seen; something not seen yet is unknown, not "no"):`}),(0,R.jsx)(`ul`,{children:e.map(e=>(0,R.jsxs)(`li`,{className:`hint`,children:[(0,R.jsx)(`code`,{children:e.model}),`: `,t(e)]},e.model))})]})}function De({test:e}){let t=e.outcome===`ok`;return(0,R.jsxs)(`div`,{className:`api-key-test`,"data-outcome":e.outcome,children:[(0,R.jsxs)(`p`,{className:E(`hint`,!t&&`warn`),children:[t?`${z.ok}: it can list ${e.models.length} model${e.models.length===1?``:`s`}`:z[e.outcome],` · `,e.source===`saved`?`saved key`:`environment key`,`, tested `,D(e.checkedAt),!e.current&&` · the key changed while this ran, so it says nothing about the key there is now`]}),t&&e.models.length>0&&(0,R.jsx)(Oe,{provider:e.provider,models:e.models,omitted:e.omitted,truncated:e.truncated})]})}function Oe({provider:e,models:t,omitted:n,truncated:r}){return(0,R.jsxs)(`details`,{className:`api-key-models`,children:[(0,R.jsx)(`summary`,{className:`hint`,children:`Models and what the provider's list says about them`}),(0,R.jsxs)(`p`,{className:`hint`,children:[e===`openai`?`OpenAI's model list doesn't say what each model can do, so every capability here is unknown.`:`Anthropic's model list says whether a model takes images and which effort levels it has. It doesn't say whether it does chat, tool use, realtime voice or speech, so those are unknown.`,` `,`Botons doesn't guess a capability from a model's name.`,n>0&&` ${n} left out (an id not of the expected shape, or past the first 500).`,r&&` The list had more pages than Botons reads.`]}),(0,R.jsxs)(`table`,{className:`api-key-model-table`,children:[(0,R.jsx)(`thead`,{children:(0,R.jsxs)(`tr`,{children:[(0,R.jsx)(`th`,{scope:`col`,children:`Model`}),(0,R.jsx)(`th`,{scope:`col`,children:`Vision`}),(0,R.jsx)(`th`,{scope:`col`,children:`Tools`}),(0,R.jsx)(`th`,{scope:`col`,title:`Realtime voice, speech-to-text, text-to-speech`,children:`Voice and speech`}),(0,R.jsx)(`th`,{scope:`col`,children:`Effort`})]})}),(0,R.jsx)(`tbody`,{children:t.map(e=>(0,R.jsxs)(`tr`,{children:[(0,R.jsxs)(`td`,{children:[(0,R.jsx)(`code`,{children:e.id}),e.displayName&&(0,R.jsxs)(`span`,{className:`hint`,children:[` `,e.displayName]}),e.lifecycle&&e.lifecycle!==`active`&&(0,R.jsxs)(`span`,{className:`hint warn`,children:[` `,e.lifecycle]})]}),(0,R.jsx)(`td`,{children:B[e.capabilities.vision]}),(0,R.jsx)(`td`,{children:B[e.capabilities.tools]}),(0,R.jsx)(`td`,{children:we(e)}),(0,R.jsx)(`td`,{children:e.effort===null?`unknown`:e.effort.length?e.effort.join(`, `):`none`})]},e.id))})]})]})}function ke({k:e}){let[t,n]=(0,L.useState)(``),[r,i]=(0,L.useState)(null),[a,o]=(0,L.useState)(!1),[c,l]=(0,L.useState)(null),[u,d]=(0,L.useState)(null),[f,p]=(0,L.useState)(null),m=async(e,t)=>{i(e),l(null),d(null);try{d(await t())}catch(e){l(e.message)}finally{i(null)}},h=f&&!f.current?f:e.test,g=e.provider===`anthropic`?`sk-ant-…`:`sk-…`;return(0,R.jsxs)(`li`,{className:`api-key-item`,"data-provider":e.provider,"data-source":e.source,children:[(0,R.jsxs)(`p`,{className:`brain-head`,children:[(0,R.jsx)(`strong`,{children:e.label}),`: `,(0,R.jsx)(`span`,{className:E(`brain-status`,e.source===`none`?`unknown`:`ok`),children:Te(e)}),e.savedAt&&(0,R.jsxs)(`span`,{className:`hint`,children:[` · saved `,D(e.savedAt)]})]}),(0,R.jsx)(`p`,{className:`hint api-key-brain`,children:e.brain.available?`Botons can run on this key: pick “${e.provider===`anthropic`?`Claude (Anthropic API key)`:`OpenAI (API key)`}” as a Boton's brain.`:e.brain.reason}),e.testing&&(0,R.jsx)(`p`,{className:`hint`,children:`Testing…`}),h&&(0,R.jsx)(De,{test:h}),e.source!==`none`&&(0,R.jsx)(Ee,{observed:e.observed}),(0,R.jsx)(`form`,{className:`api-key-form`,onSubmit:r=>{r.preventDefault(),m(`save`,async()=>{let r=await b.saveApiKey(e.provider,t,e.generation);return n(``),p(null),s.apiKeyChanged(r),e.saved?`Replaced the saved key. Test it to see what it can use.`:`Saved. Test it to see what it can use.`})},children:(0,R.jsxs)(`label`,{children:[(0,R.jsx)(`span`,{className:`field-label`,children:e.saved?`Replace the saved ${e.label} key`:`Save a key for ${e.label}`}),(0,R.jsxs)(`span`,{className:`topic-row`,children:[(0,R.jsx)(`input`,{type:`password`,name:`api-key-${e.provider}`,value:t,onChange:e=>n(e.target.value),placeholder:e.saved?`Saved. Paste a new key to replace it`:g,autoComplete:`off`,spellCheck:!1,"data-1p-ignore":!0,"data-lpignore":`true`}),(0,R.jsx)(`button`,{type:`submit`,className:`btn small primary`,disabled:!!r||!t.trim(),children:r===`save`?`Saving…`:`Save`})]})]})}),(0,R.jsx)(`p`,{className:`hint`,children:e.testDescription}),(0,R.jsxs)(`div`,{className:`form-actions api-key-actions`,children:[e.saved&&!a&&(0,R.jsx)(`button`,{type:`button`,className:`btn small ghost`,disabled:!!r,onClick:()=>o(!0),children:`Remove the saved key`}),(0,R.jsx)(`button`,{type:`button`,className:`btn small`,disabled:!!r||e.testing||e.source===`none`,onClick:()=>m(`test`,async()=>{let t=await b.testApiKey(e.provider);return p(t),null}),children:r===`test`?`Testing…`:`Test key`})]}),a&&(0,R.jsxs)(`div`,{className:`api-key-confirm`,role:`group`,"aria-label":`Remove the saved ${e.label} key`,children:[(0,R.jsxs)(`p`,{className:`hint warn`,children:[e.environment?`Remove the saved key? From then on Botons uses ${e.envVar} from the server's environment.`:`Remove the saved key? There will be no ${e.label} key.`,` `,`Revoke it with `,e.label,` too if it may have leaked: removing it here doesn't, and older backups still hold it.`]}),(0,R.jsxs)(`div`,{className:`form-actions`,children:[(0,R.jsx)(`button`,{type:`button`,className:`btn small ghost`,onClick:()=>o(!1),children:`Keep it`}),(0,R.jsx)(`button`,{type:`button`,className:`btn small danger`,disabled:!!r,onClick:()=>m(`remove`,async()=>{let t=await b.removeApiKey(e.provider,e.generation);return o(!1),p(null),s.apiKeyChanged(t),t.source===`environment`?`Removed. Now using ${t.envVar} from the server's environment.`:`Removed. There is no key now.`}),children:r===`remove`?`Removing…`:`Remove`})]})]}),e.source===`environment`&&!e.saved&&(0,R.jsxs)(`p`,{className:`hint`,children:[`To stop using it, remove `,e.envVar,` from the server's environment and restart the server. Saving a key here takes its place.`]}),c&&(0,R.jsx)(`p`,{className:`form-error`,children:c}),u&&(0,R.jsx)(`p`,{className:`hint`,children:u})]})}function Ae(){let e=g(e=>e.apiKeys),[t,n]=(0,L.useState)(null);return(0,L.useEffect)(()=>{s.loadApiKeys().catch(e=>n(e.message))},[]),(0,R.jsxs)(`section`,{className:`server-info api-keys`,children:[(0,R.jsx)(`h4`,{children:`API keys`}),(0,R.jsx)(`p`,{className:`hint`,children:`Your own Anthropic and OpenAI API keys, for brains that use the providers' APIs directly. Botons runs their agent loop itself: their commands and file edits run on this server (sandboxed for Workspace access), their connectors are Botons' own, and each turn's tokens and estimated cost are shown with its reply. Caps on what a Boton spends are in its settings, under Spending.`}),(0,R.jsx)(`p`,{className:`hint`,children:`A saved key is kept in Botons' database, like sign-in connector tokens: it is in every backup, it is never shown again, and Workspace Botons can't read it (Full computer Botons run as the server's user, so they could). Without a saved key, Botons uses ANTHROPIC_API_KEY or OPENAI_API_KEY from the server's environment, if set; agent CLIs are never given either.`}),t&&(0,R.jsx)(`p`,{className:`form-error`,children:t}),e&&(0,R.jsx)(`ul`,{className:`brain-list api-key-list`,children:e.providers.map(e=>(0,R.jsx)(ke,{k:e},e.provider))})]})}var je=8e3,Me=3e4;function Ne({onOpenBot:e,onPage:n}){let r=g(e=>e.providers),i=g(e=>e.bots),o=g(e=>e.apiKeys),l=g(e=>e.setup?.exposure.container??!1),[u,d]=(0,L.useState)([]),[f,p]=(0,L.useState)({}),[m,h]=(0,L.useState)({codex:!1,claude:!1}),[_,v]=(0,L.useState)({}),y=P(),x=(0,L.useRef)(new Map),S=(e,t)=>{clearTimeout(x.current.get(e)),p(n=>({...n,[e]:t})),t?.ok&&x.current.set(e,setTimeout(()=>p(n=>n[e]===t?{...n,[e]:null}:n),je))},C=e=>{u.includes(e)?(d(u.filter(t=>t!==e)),S(e,null)):d([...u,e])},w=()=>b.brainHomes().then(h).catch(()=>{});(0,L.useEffect)(()=>{let e=!0,t=()=>{s.loadProviders(!0).catch(()=>{}),b.brainHomes().then(t=>e&&h(t)).catch(()=>{})};s.loadApiKeys().catch(()=>{}),t();let n=setInterval(()=>y.current&&t(),5e3),r=x.current;return()=>{e=!1,clearInterval(n);for(let e of r.values())clearTimeout(e)}},[]);let T=i.filter(e=>c.some(t=>t.key&&t.id===e.provider)).map(e=>e.id),E=T.join(`,`);(0,L.useEffect)(()=>{if(!T.length)return;let e=!0,t=()=>void Promise.allSettled(T.map(e=>b.spending(e))).then(t=>{if(!e)return;let n={};for(let e of t)e.status===`fulfilled`&&(n[e.value.botId]=e.value);v(n)});t();let n=setInterval(()=>y.current&&t(),Me);return()=>{e=!1,clearInterval(n)}},[E]);let D=(0,L.useRef)(new Map);if((0,L.useEffect)(()=>{for(let e of r){if(e.kind!==`cli`)continue;let t=a(e,void 0);D.current.get(e.id)===!1&&t&&e.readiness.state===`ready`&&S(e.id,{ok:!0,text:`✓ Signed in.`}),D.current.set(e.id,t)}},[r]),!r.length)return(0,R.jsx)(`p`,{className:`hint`,children:`Checking brains…`});let O=c.map(e=>{let n=r.find(t=>t.id===e.id),s=e.key?o?.providers.find(t=>t.provider===e.key):void 0;return{id:e.id,name:e.name,blurb:e.blurb,p:n,k:s,apiKey:!!e.key,users:t(e.id,i),setUp:a(n,s)}});for(let e of r){if(c.some(t=>t.id===e.id))continue;let n=t(e.id,i);(e.readiness.state===`ready`||n.length)&&O.push({id:e.id,name:e.label,blurb:``,p:e,k:void 0,apiKey:!1,users:n,setUp:!0})}let k=O.filter(e=>e.setUp||e.users.length),A=O.filter(e=>!k.includes(e)),j=(t,r)=>(0,R.jsx)(Pe,{b:t,adding:r,open:u.includes(t.id),onToggle:()=>C(t.id),note:f[t.id]??null,setNote:e=>S(t.id,e),homes:m,refreshHomes:w,container:l,spend:_,onOpenBot:e,onPage:n},t.id);return(0,R.jsxs)(`div`,{className:`brains brain-settings`,children:[(0,R.jsx)(`p`,{className:`hint`,children:`Each Boton picks its brain in its own settings. Adding a brain doesn't change existing Botons.`}),(0,R.jsx)(`h4`,{children:`Your brains`}),k.length?(0,R.jsx)(`ul`,{className:`brain-list`,children:k.map(e=>j(e,!1))}):(0,R.jsx)(`p`,{className:`hint`,children:`None yet. Add one below.`}),A.length>0&&(0,R.jsxs)(R.Fragment,{children:[(0,R.jsx)(`h4`,{children:`Add a brain`}),(0,R.jsx)(`ul`,{className:`brain-list`,children:A.map(e=>j(e,!0))})]})]})}function Pe({b:e,adding:t,open:n,onToggle:r,note:a,setNote:o,homes:s,refreshHomes:c,container:l,spend:u,onOpenBot:d,onPage:f}){let p=(0,L.useId)(),m=e.p?h(e.p,{container:l}):null,g=e.apiKey&&e.k?.source===`none`,_=e.p?t?e.p.readiness.state===`not-installed`?`Not installed`:`Not set up`:g?`No key`:m.status:`Not on this server`,v=e.p?t?`unknown`:g?`bad`:m.tone:`bad`,y=e.apiKey?Fe(e.users,u):null;return(0,R.jsxs)(`li`,{className:`brain-card`,"data-brain":e.id,"data-state":e.p?.readiness.state,children:[(0,R.jsxs)(`button`,{type:`button`,className:`brain-card-head`,"aria-expanded":n,"aria-controls":n?p:void 0,onClick:r,children:[(0,R.jsx)(`span`,{className:`brain-card-name`,children:e.name}),(0,R.jsx)(`span`,{className:E(`brain-status`,v),children:_}),(0,R.jsx)(ve,{size:14,className:E(`brain-card-chevron`,n&&`on`)})]}),t?(0,R.jsx)(`p`,{className:`hint brain-card-meta`,children:e.blurb}):(0,R.jsxs)(`p`,{className:`hint brain-card-meta`,children:[d&&e.users.length?(0,R.jsxs)(R.Fragment,{children:[`Used by`,` `,e.users.map((e,t)=>(0,R.jsxs)(`span`,{children:[t>0&&`, `,(0,R.jsx)(`button`,{type:`button`,className:`link-btn`,title:`Open ${e.name}'s settings`,onClick:()=>d(e.id),children:e.name}),e.paused&&` (paused)`]},e.id))]}):i(e.users),y&&(0,R.jsxs)(R.Fragment,{children:[` · `,y]})]}),a&&(0,R.jsx)(`p`,{role:`status`,className:E(`brain-card-note`,a.ok?`ok`:`form-error`),children:a.text}),n&&(0,R.jsx)(`div`,{id:p,className:`brain-card-body`,children:e.apiKey?e.k?(0,R.jsx)(ze,{k:e.k,users:e.users,setNote:o,onPage:f}):(0,R.jsx)(`p`,{className:`hint`,children:`Loading…`}):e.p&&(e.p.id===`claude-cli`||e.p.id===`codex-cli`)?(0,R.jsx)(Le,{p:e.p,setUp:e.setUp,pending:e.p.id===`codex-cli`?s.codex:s.claude,refreshHomes:c,setNote:o,container:l}):m&&(0,R.jsx)(Ie,{view:m})})]})}function Fe(e,t){let r=e.filter(e=>t[e.id]);if(!r.length)return null;let i=e=>`about ${n(e.spentTodayUsd)}${e.unpricedToday?` + ${e.unpricedToday} unpriced`:``}${e.dailyCapUsd===null?``:` of ${n(e.dailyCapUsd)} cap`}`;return e.length===1?`Today ${i(t[r[0].id])}`:`Today: ${r.map(e=>`${e.name} ${i(t[e.id])}`).join(`, `)}`}function Ie({view:e,skipFix:t}){return!e.why&&!e.fix?null:(0,R.jsxs)(R.Fragment,{children:[e.why&&(0,R.jsx)(`p`,{className:`hint`,children:f(e.why)}),e.fix&&!t&&(0,R.jsxs)(`p`,{className:`hint`,children:[f(e.fix.text),e.fix.command&&(0,R.jsxs)(R.Fragment,{children:[` `,(0,R.jsx)(`code`,{children:e.fix.command}),` `,(0,R.jsx)(F,{text:e.fix.command})]})]})]})}function Le({p:e,setUp:t,pending:n,refreshHomes:r,setNote:i,container:a}){let c=e.id===`codex-cli`,[u,d]=(0,L.useState)(!1),[f,p]=(0,L.useState)(!1),m=h(e,{container:a}),g=e.readiness.state===`ready`,_=e.lastError&&(!e.lastSuccessAt||e.lastError.at>e.lastSuccessAt)?e.lastError:null,v=o(e.id,a)??``,y=u||!t&&!n,x=async()=>{p(!0),i(null);try{await b.importCodexSignIn(),await r(),await s.loadProviders(!0),i({ok:!0,text:`✓ Botons now has its own Codex sign-in.`})}catch{i({ok:!1,text:`Could not copy the sign-in. Try signing in just for Botons.`})}finally{p(!1)}};return(0,R.jsxs)(R.Fragment,{children:[!g&&(0,R.jsx)(Ie,{view:m,skipFix:e.readiness.state===`signed-out`}),_&&(0,R.jsxs)(`p`,{className:`hint warn`,children:[`Last turn failed: `,_.message]}),(0,R.jsx)(he,{provider:e.id}),y?c?(0,R.jsx)(Re,{command:v,setNote:i,onDone:()=>d(!1)}):(0,R.jsxs)(`div`,{className:`brain-signin`,children:[(0,R.jsx)(`p`,{className:`hint`,children:`Claude signs in from a terminal on this server. Run:`}),(0,R.jsxs)(`p`,{children:[(0,R.jsx)(`code`,{children:v}),` `,(0,R.jsx)(F,{text:v})]}),(0,R.jsx)(`p`,{className:`hint`,children:`Leave this page open; it checks by itself.`})]}):n&&c?(0,R.jsxs)(`div`,{className:`brain-signin`,children:[(0,R.jsx)(`p`,{className:`hint`,children:`It uses your existing Codex sign-in. Give Botons its own copy, or sign in just for Botons.`}),(0,R.jsxs)(`div`,{className:`form-actions`,children:[(0,R.jsx)(`button`,{type:`button`,className:`btn small`,disabled:f,onClick:()=>void x(),children:`Use existing sign-in`}),(0,R.jsx)(`button`,{type:`button`,className:`btn small`,disabled:f,onClick:()=>d(!0),children:`Sign in just for Botons`})]}),(0,R.jsx)(`p`,{className:`hint`,children:`Copies only the Codex sign-in file, never your instructions, skills or settings.`})]}):n?(0,R.jsxs)(`p`,{className:`hint`,children:[`It uses your existing Claude sign-in. To sign in just for Botons, run `,(0,R.jsx)(`code`,{children:v}),` `,(0,R.jsx)(F,{text:v}),` on the server.`]}):(0,R.jsx)(`div`,{className:`form-actions`,children:(0,R.jsx)(`button`,{type:`button`,className:`btn small ghost`,onClick:()=>d(!0),children:`Change sign-in`})}),!g&&m.fix&&(0,R.jsx)(l,{})]})}function Re({command:e,setNote:t,onDone:n}){let[i,a]=(0,L.useState)(null),[o,c]=(0,L.useState)(!1),l=P(),u=(0,L.useRef)(void 0);(0,L.useEffect)(()=>{let e=!0,t=()=>void b.brainLogin().then(t=>e&&a(t)).catch(()=>{});t();let n=setInterval(()=>l.current&&t(),3e3);return()=>{e=!1,clearInterval(n)}},[]),(0,L.useEffect)(()=>{r(u.current,i?.state)&&(t({ok:!0,text:`✓ Signed in.`}),s.loadProviders(!0).catch(()=>{}),n()),u.current=i?.state},[i?.state]);let d=async e=>{c(!0),t(null);try{a(await e())}catch(e){t({ok:!1,text:e.message})}finally{c(!1)}},f=i?.state===`waiting`;return(0,R.jsxs)(`div`,{className:`brain-signin`,children:[f?(0,R.jsxs)(`div`,{role:`status`,children:[(0,R.jsx)(`p`,{className:`hint`,children:`Waiting for you to approve sign-in…`}),i.url&&i.code&&(0,R.jsxs)(`p`,{children:[(0,R.jsx)(`code`,{children:i.code}),` `,(0,R.jsx)(F,{text:i.code}),` `,(0,R.jsx)(`a`,{href:i.url,target:`_blank`,rel:`noreferrer`,children:`Open sign-in page`})]}),(0,R.jsx)(`button`,{type:`button`,className:`btn small`,disabled:o,onClick:()=>void d(()=>b.cancelBrainLogin()),children:`Cancel sign-in`})]}):(0,R.jsx)(`div`,{className:`form-actions`,children:(0,R.jsx)(`button`,{type:`button`,className:`btn small primary`,disabled:o,onClick:()=>void d(()=>b.startBrainLogin()),children:`Sign in to ChatGPT`})}),i&&[`fallback`,`timed-out`].includes(i.state)&&(0,R.jsx)(`p`,{className:`hint warn`,children:`Browser sign-in could not finish. Try again, or use the terminal.`}),(0,R.jsxs)(`p`,{className:`hint`,children:[`Or on the server: `,(0,R.jsx)(`code`,{children:e}),` `,(0,R.jsx)(F,{text:e})]})]})}function ze({k:e,users:t,setNote:n,onPage:r}){let[i,a]=(0,L.useState)(``),[o,c]=(0,L.useState)(null),[l,u]=(0,L.useState)(!1),d=async(e,t)=>{c(e),n(null);try{let e=await t();e&&n({ok:!0,text:e})}catch(e){n({ok:!1,text:e.message})}finally{c(null)}},f=e.test,p=(0,L.useId)();return(0,R.jsxs)(R.Fragment,{children:[e.source!==`none`&&(0,R.jsxs)(`p`,{className:`hint`,children:[Te(e),e.savedAt&&` · saved ${D(e.savedAt)}`]}),e.testing?(0,R.jsx)(`p`,{className:`hint`,children:`Testing…`}):f&&(0,R.jsxs)(`p`,{className:E(`hint`,f.outcome!==`ok`&&`warn`),children:[f.outcome===`ok`?`✓ ${z.ok}`:z[f.outcome],` · tested `,D(f.checkedAt)]}),!e.brain.available&&e.brain.reason&&(t.length>0||e.source!==`none`)&&(0,R.jsx)(`p`,{className:`hint warn`,children:e.brain.reason}),(0,R.jsxs)(`form`,{className:`api-key-form`,onSubmit:n=>{n.preventDefault(),d(`save`,async()=>{let n=await b.saveApiKey(e.provider,i,e.generation);return a(``),s.apiKeyChanged(n),await s.loadProviders(!0),`✓ Key ${e.saved?`replaced`:`saved`}. ${t.length?`Botons already on this brain use it now; no Boton changes brain.`:`A Boton uses it once you pick it in its settings.`}`})},children:[(0,R.jsx)(`label`,{htmlFor:p,children:(0,R.jsx)(`span`,{className:`field-label`,children:e.saved?`Replace the key`:`API key`})}),(0,R.jsxs)(`span`,{className:`topic-row`,children:[(0,R.jsx)(`input`,{id:p,type:`password`,name:`brain-key-${e.provider}`,value:i,onChange:e=>a(e.target.value),placeholder:e.saved?`Paste a new key`:e.provider===`anthropic`?`sk-ant-…`:`sk-…`,autoComplete:`off`,spellCheck:!1,"data-1p-ignore":!0,"data-lpignore":`true`}),(0,R.jsx)(`button`,{type:`submit`,className:`btn small primary`,disabled:!!o||!i.trim(),children:o===`save`?`Saving…`:`Save`})]})]}),(0,R.jsxs)(`div`,{className:`form-actions`,children:[e.source!==`none`&&(0,R.jsx)(`button`,{type:`button`,className:`btn small`,disabled:!!o||e.testing,onClick:()=>void d(`test`,async()=>(await b.testApiKey(e.provider),await s.loadApiKeys(),null)),children:o===`test`?`Testing…`:`Test key`}),e.saved&&!l&&(0,R.jsx)(`button`,{type:`button`,className:`btn small ghost`,disabled:!!o,onClick:()=>u(!0),children:`Remove key`}),r&&e.source!==`none`&&(0,R.jsx)(`button`,{type:`button`,className:`link-btn`,onClick:()=>r(`api-keys`),children:`Key details`})]}),l&&(0,R.jsxs)(`div`,{className:`api-key-confirm`,role:`group`,"aria-label":`Remove the saved ${e.label} key`,children:[(0,R.jsxs)(`p`,{className:`hint warn`,children:[e.environment?`Remove the saved key? Botons then uses ${e.envVar} from the server's environment.`:`Remove the saved key?${t.length?` ${t.length===1?t[0].name:`Its Botons`} can't think until there is a key again.`:``}`,` `,`If it may have leaked, revoke it with the provider too.`]}),(0,R.jsxs)(`div`,{className:`form-actions`,children:[(0,R.jsx)(`button`,{type:`button`,className:`btn small ghost`,onClick:()=>u(!1),children:`Keep it`}),(0,R.jsx)(`button`,{type:`button`,className:`btn small danger`,disabled:!!o,onClick:()=>void d(`remove`,async()=>{let t=await b.removeApiKey(e.provider,e.generation);return u(!1),s.apiKeyChanged(t),await s.loadProviders(!0),t.source===`environment`?`✓ Removed. Now using ${t.envVar} from the server's environment.`:`✓ Key removed.`}),children:o===`remove`?`Removing…`:`Remove`})]})]}),(0,R.jsx)(`p`,{className:`hint`,children:`Each Boton on it sets its own model and daily cap.`})]})}function V(e,t,n=``){return t===`repair`?e===`service`?`npm run build`:e===`docker`?`docker compose up -d --build botons`:`~/.local/bin/botons repair`:(e===`docker`?`docker compose exec botons botons ${t}`:e===`managed`?`~/.local/bin/botons ${t}`:`npm run ${t}`)+(n?`${e===`service`?` --`:``} ${n}`:``)}var Be=16777216,Ve={create:`Add it`,replace:`Replace this server's`,keep:`Keep this server's`,skip:`Leave it out`},He={create:`Add it`,skip:`Leave it out`},Ue={create:`Add it (turned off)`,keep:`Keep this server's`,skip:`Leave it out`},We={replace:`Arrange the sidebar as the file has it (groups the file lacks are removed; no Boton is)`,merge:`Put the new Botons in the file's groups, adding the groups this server lacks`,skip:`Leave the sidebar as it is (new Botons go among the ungrouped)`},Ge=e=>e===`host`?`Full computer`:`Workspace`,H=(e,t)=>`${e} ${t}${e===1?``:`s`}`,U=e=>e.length?e.join(`, `):`none`,W=e=>e===null?`none`:`$${e}`;function Ke(e,t){let n=URL.createObjectURL(new Blob([`${JSON.stringify(t,null,2)}\n`],{type:`application/json`})),r=document.createElement(`a`);r.href=n,r.download=e,document.body.append(r),r.click(),r.remove(),setTimeout(()=>URL.revokeObjectURL(n),1e4)}function qe({m:e,onChange:t}){let n=e.action===`create`,r=[e.caps.daily.file!==null&&`${W(e.caps.daily.file)} a day`,...e.routines.filter(e=>e.perRunCap.file!==null).map(e=>`${W(e.perRunCap.file)} a run for “${e.name}”`)].filter(Boolean);return(0,R.jsxs)(`li`,{className:`config-item`,"data-ref":e.ref,children:[(0,R.jsxs)(`label`,{className:`config-choice`,children:[(0,R.jsx)(`input`,{type:`checkbox`,checked:n,onChange:e=>t({action:e.target.checked?`create`:`skip`})}),(0,R.jsx)(`strong`,{children:e.name}),e.conflict&&(0,R.jsxs)(`span`,{className:`hint warn`,children:[` · a Boton here already has this name`,n?`: this adds another`:``]})]}),n&&(0,R.jsxs)(`div`,{className:`config-details`,children:[(0,R.jsxs)(`label`,{className:`config-inline`,children:[(0,R.jsx)(`span`,{className:`field-label`,children:`Brain`}),(0,R.jsx)(`select`,{value:e.provider.imported,onChange:e=>t({provider:e.target.value}),children:e.provider.options.map(e=>(0,R.jsx)(`option`,{value:e,children:e},e))}),!e.provider.available&&(0,R.jsxs)(`span`,{className:`hint warn`,children:[` `,e.provider.file,` isn't on this server`]})]}),(0,R.jsxs)(`ul`,{className:`hint config-facts`,children:[(0,R.jsxs)(`li`,{children:[`Model: `,e.model.imported||`the brain’s default`,e.model.file!==e.model.imported&&` (the file has ${e.model.file||`the default`})`,`; effort: `,e.effort.imported||`default`,e.effort.file!==e.effort.imported&&` (the file has ${e.effort.file}, which this brain or model doesn't take)`]}),(0,R.jsxs)(`li`,{children:[`Computer access: `,Ge(e.access.imported),e.access.file===`host`&&(0,R.jsxs)(`label`,{className:`config-inline`,children:[` `,(0,R.jsx)(`input`,{type:`checkbox`,checked:e.access.imported===`host`,onChange:e=>t({fullAccess:e.target.checked})}),` give it Full computer access, as the file has`]})]}),(0,R.jsxs)(`li`,{children:[`Always allow: `,U(e.alwaysAllow.imported),e.alwaysAllow.file.length>0&&(0,R.jsxs)(`label`,{className:`config-inline`,children:[` `,(0,R.jsx)(`input`,{type:`checkbox`,checked:e.alwaysAllow.imported.length>0,onChange:e=>t({alwaysAllow:e.target.checked})}),` keep the file's`,` `,U(e.alwaysAllow.file)]})]}),(0,R.jsxs)(`li`,{children:[`Ask first: `,U(e.askFirst)]}),(0,R.jsxs)(`li`,{className:`config-caps`,children:[`Spending caps: `,e.caps.daily.imported===null?`none a day`:`${W(e.caps.daily.imported)} a day`,`, starting at $0 here`,!e.caps.applies&&r.length>0&&` (they count only API-key turns, and this brain isn’t one)`,e.caps.unpriced&&(0,R.jsx)(`span`,{className:`warn`,children:` · a capped model has no known price here: see the spending notes`}),r.length>0&&` · the file has ${r.join(`, `)}`,r.length>0&&(0,R.jsxs)(`label`,{className:`config-inline`,children:[` `,(0,R.jsx)(`input`,{type:`checkbox`,checked:e.caps.choice===`file`,onChange:e=>t({caps:e.target.checked?`file`:`none`})}),` keep the file's caps`]})]}),(0,R.jsxs)(`li`,{children:[`Connectors: `,U(e.connections.imported),e.connections.live.length>0&&(0,R.jsxs)(`span`,{className:`warn`,children:[` · usable at once with this server's own setup: `,e.connections.live.join(`, `)]}),e.connections.dropped.map(e=>` · without ${e.id} (${e.why})`).join(``)]}),(0,R.jsxs)(`li`,{children:[`Skills: `,e.skills.imported===null?`every skill in the library`:U(e.skills.imported),e.skills.dropped.map(e=>` · without ${e.name} (${e.why})`).join(``)]}),(e.browserSites.imported.length>0||e.browserSites.dropped.length>0)&&(0,R.jsxs)(`li`,{children:[`Browser sites: `,U(e.browserSites.imported),e.browserSites.dropped.length>0&&` · without ${e.browserSites.dropped.join(`, `)} (not allowed with ${Ge(e.access.imported)} access here)`]}),e.paused&&(0,R.jsx)(`li`,{children:`Paused, as in the file`}),e.routines.map(e=>(0,R.jsxs)(`li`,{children:[`Routine “`,e.name,`”: `,e.schedule??`no schedule`,e.webhook&&`, webhook (new URL and key${e.fullAccessWebhook.imported?`, runs with Full computer access`:e.fullAccessWebhook.file?`, with Workspace access here`:``})`,e.skill.file&&(e.skill.imported?`, follows ${e.skill.imported}`:`, without its skill ${e.skill.file}`),e.model.file!==e.model.imported&&`, model ${e.model.imported??`the Boton's`}`,e.effort.file!==e.effort.imported&&`, effort ${e.effort.imported??`the Boton's`}`,e.perRunCap.imported!==null&&`, at most ${W(e.perRunCap.imported)} a run`,`, arrives off`]},e.name))]})]})]})}function Je({preview:e,busy:t,onChoices:n,onImport:r}){let i=e.choices,a=e.botons.filter(e=>e.action===`create`).length;return(0,R.jsxs)(`div`,{className:`config-preview`,"aria-label":`What importing would do`,children:[(0,R.jsxs)(`p`,{className:`hint`,children:[`Made by Botons `,e.source.app,` (schema `,e.source.schema,`) on `,new Date(e.source.exportedAt).toLocaleString(),`, format version `,e.source.version,e.upgradedFrom!==null&&`, upgraded to this server's version to read it`,`. Nothing changes until you import.`]}),e.textWarnings.length>0&&(0,R.jsxs)(`p`,{className:`hint warn`,children:[`Something shaped like a credential is in: `,e.textWarnings.join(`; `),`. It is imported as written: check it after.`]}),(0,R.jsxs)(`h5`,{children:[`Botons (`,a,` of `,e.botons.length,`)`]}),e.spending.notes.length>0&&(0,R.jsx)(`ul`,{className:`hint config-facts config-spending`,children:e.spending.notes.map(e=>(0,R.jsx)(`li`,{children:e},e))}),(0,R.jsx)(`ul`,{className:`config-list`,children:e.botons.map(e=>(0,R.jsx)(qe,{m:e,onChange:t=>n({...i,botons:{...i.botons,[e.ref]:{...i.botons[e.ref],...t}}})},e.ref))}),e.skills.length>0&&(0,R.jsxs)(R.Fragment,{children:[(0,R.jsx)(`h5`,{children:`Skills`}),(0,R.jsx)(`p`,{className:`hint`,children:`A skill is text a Boton follows: it grants nothing. Fields like allowed-tools are listed, never applied.`}),(0,R.jsx)(`ul`,{className:`config-list`,children:e.skills.map(e=>(0,R.jsxs)(`li`,{className:`config-item`,children:[(0,R.jsxs)(`label`,{className:`config-inline`,children:[(0,R.jsx)(`strong`,{children:e.name}),` `,(0,R.jsx)(`select`,{value:e.action,onChange:t=>n({...i,skills:{...i.skills,[e.name]:t.target.value}}),children:(e.exists?[`keep`,`replace`,`skip`]:[`create`,`skip`]).map(e=>(0,R.jsx)(`option`,{value:e,children:Ve[e]},e))})]}),(0,R.jsxs)(`span`,{className:`hint`,children:[e.exists&&(e.sameText?` · the same text is here`:` · this server has another text under this name`),e.disabled&&` · turned off`,e.pending&&` · written by a Boton, waits for your approval`,e.source&&` · from the package ${e.source}`,e.history>0&&` · ${e.history} earlier text${e.history===1?``:`s`}`,e.asks.length>0&&` · asks for or points at: ${e.asks.join(`, `)}`]})]},e.name))})]}),e.connectors.length>0&&(0,R.jsxs)(R.Fragment,{children:[(0,R.jsx)(`h5`,{children:`Connectors`}),(0,R.jsx)(`p`,{className:`hint`,children:`New ones arrive turned off and without their secrets or sign-ins, which are never in the file.`}),(0,R.jsx)(`ul`,{className:`config-list`,children:e.connectors.map(e=>(0,R.jsxs)(`li`,{className:`config-item`,children:[(0,R.jsxs)(`label`,{className:`config-inline`,children:[(0,R.jsx)(`strong`,{children:e.name}),` `,(0,R.jsx)(`select`,{value:e.action,onChange:t=>n({...i,connectors:{...i.connectors,[e.id]:t.target.value}}),children:(e.exists?[`keep`,`skip`]:[`create`,`skip`]).map(e=>(0,R.jsx)(`option`,{value:e,children:Ue[e]},e))})]}),(0,R.jsxs)(`span`,{className:`hint`,children:[` `,e.kind===`oauth`?`sign-in connector`:e.runsCommand?`runs a command in the turns of Botons that have it:`:`MCP server at`,` `,(0,R.jsx)(`code`,{children:e.target}),e.secrets.length>0&&` · needs ${e.secrets.join(`, `)}`,e.onRequest&&` · Botons may ask for it (${e.grant===`immediate`?`given at once`:`with your approval`})`]})]},e.id))})]}),e.templates.length>0&&(0,R.jsxs)(R.Fragment,{children:[(0,R.jsx)(`h5`,{children:`Saved templates`}),(0,R.jsx)(`p`,{className:`hint`,children:`A template only fills in the New Boton form: nothing in it runs or is given to a Boton until you make one from it.`}),(0,R.jsx)(`ul`,{className:`config-list`,children:e.templates.map(e=>(0,R.jsxs)(`li`,{className:`config-item`,children:[(0,R.jsxs)(`label`,{className:`config-inline`,children:[(0,R.jsx)(`strong`,{children:e.name}),` `,(0,R.jsx)(`select`,{value:e.action,onChange:t=>n({...i,templates:{...i.templates,[e.ref]:t.target.value}}),children:[`create`,`skip`].map(e=>(0,R.jsx)(`option`,{value:e,children:He[e]},e))})]}),(0,R.jsxs)(`span`,{className:`hint`,children:[` `,H(e.routines,`routine`),e.same&&` · the same template is saved here`,e.narrowed.length>0&&` · without ${e.narrowed.join(`; `)}`]})]},e.ref))})]}),(0,R.jsx)(`h5`,{children:`Settings and sidebar`}),(0,R.jsxs)(`label`,{className:`config-inline`,children:[(0,R.jsx)(`input`,{type:`checkbox`,checked:i.settings,onChange:e=>n({...i,settings:e.target.checked})}),` Use the file's settings`]}),i.settings&&(0,R.jsxs)(`ul`,{className:`hint config-facts`,children:[e.settings.changes.length?e.settings.changes.map(e=>(0,R.jsx)(`li`,{children:`${e.field}: ${e.from||`(none)`} → ${e.to||`(none)`}`},e.field)):(0,R.jsx)(`li`,{children:`The same as here.`}),e.settings.notes.map(e=>(0,R.jsx)(`li`,{children:e},e))]}),(0,R.jsxs)(`label`,{className:`config-inline`,children:[(0,R.jsx)(`span`,{className:`field-label`,children:`Sidebar`}),(0,R.jsx)(`select`,{value:i.sidebar,onChange:e=>n({...i,sidebar:e.target.value}),children:[`replace`,`merge`,`skip`].map(e=>(0,R.jsx)(`option`,{value:e,children:We[e]},e))})]}),e.reconnect.length>0&&(0,R.jsxs)(R.Fragment,{children:[(0,R.jsx)(`h5`,{children:`To do by hand after importing`}),(0,R.jsx)(`ul`,{className:`hint config-facts config-reconnect`,children:e.reconnect.map((e,t)=>(0,R.jsx)(`li`,{children:e.text},t))})]}),(0,R.jsx)(`div`,{className:`form-actions`,children:(0,R.jsx)(`button`,{type:`button`,className:`btn primary`,disabled:t,onClick:r,children:t?`Working…`:`Import`})})]})}function Ye(){let e=g(e=>e.info?.install??`service`),[t,n]=(0,L.useState)(null),[r,i]=(0,L.useState)(null),[a,o]=(0,L.useState)(null),[c,l]=(0,L.useState)(null),[u,d]=(0,L.useState)(!1),[f,p]=(0,L.useState)(null),m=(0,L.useRef)(null),h=(0,L.useRef)(null);(0,L.useEffect)(()=>{c&&h.current?.scrollIntoView({block:`nearest`})},[c]);let _=async e=>{d(!0),p(null);try{await e()}catch(e){p(e.message)}finally{d(!1)}},v=e=>_(async()=>o(await b.previewConfigImport({file:r,choices:e})));return(0,R.jsxs)(`section`,{className:`server-info config-transfer`,children:[(0,R.jsx)(`h4`,{children:`Move to another server`}),(0,R.jsxs)(`p`,{className:`hint`,children:[`Export your configuration (Botons with their routines, rules and browser sites, skills, connectors, saved templates, settings and the sidebar) as a file, and import it on another server or after a reinstall. It never holds chats, memories, workspace files, connector secrets, sign-ins, tokens, webhook keys or API keys: a full backup (`,(0,R.jsx)(`code`,{children:V(e,`backup`)}),`, restored with `,(0,R.jsx)(`code`,{children:V(e,`restore`)}),`) holds everything, and stays the way to recover a whole server.`]}),(0,R.jsxs)(`div`,{className:`form-actions`,children:[(0,R.jsx)(`button`,{type:`button`,className:`btn small`,disabled:u,onClick:()=>_(async()=>{let e=await b.exportConfig();n(e),Ke(`botons-config-${e.file.exportedAt.slice(0,10)}.json`,e.file)}),children:`Export configuration`}),(0,R.jsx)(`button`,{type:`button`,className:`btn small`,disabled:u,onClick:()=>m.current?.click(),children:`Import a configuration…`}),(0,R.jsx)(`input`,{ref:m,type:`file`,accept:`application/json,.json`,hidden:!0,"aria-label":`Configuration file`,onChange:e=>{let t=e.target.files?.[0];e.target.value=``,t&&(l(null),o(null),_(async()=>{if(t.size>Be)throw Error(`That file is larger than a configuration can be (16 MB).`);let e;try{e=new TextDecoder(`utf-8`,{fatal:!0}).decode(await t.arrayBuffer())}catch{throw Error(`That file is not UTF-8 text: choose a file made by Export configuration.`)}let n;try{n=JSON.parse(e)}catch{throw Error(`That file is not JSON: choose a file made by Export configuration.`)}i(n),o(await b.previewConfigImport({file:n}))}))}})]}),t&&(0,R.jsxs)(`div`,{className:`config-exported`,children:[(0,R.jsxs)(`p`,{className:`hint`,children:[`Downloaded `,H(t.file.botons.length,`Boton`),`, `,H(t.file.skills.length,`skill`),` and `,H(t.file.connectors.length,`connector`),`. Left out, with placeholders where they were:`]}),(0,R.jsx)(`ul`,{className:`hint config-facts`,children:t.leftOut.map(e=>(0,R.jsx)(`li`,{children:e},e))}),t.textWarnings.length>0&&(0,R.jsxs)(`p`,{className:`hint warn`,children:[`Something shaped like a credential is in text you wrote, which is exported as written: `,t.textWarnings.join(`; `),`. Check it before you share the file.`]})]}),f&&(0,R.jsx)(`p`,{className:`form-error`,children:f}),a&&!c&&(0,R.jsx)(Je,{preview:a,busy:u,onChoices:e=>void v(e),onImport:()=>_(async()=>{let e=await b.importConfig({file:r,choices:a.choices,fingerprint:a.fingerprint});l(e),o(null),i(null),await Promise.all([s.loadConnectors(),s.loadMcpServers(),s.loadSkills(),s.loadOAuth()]).catch(()=>{})})}),c&&(0,R.jsxs)(`div`,{className:`config-result`,role:`status`,ref:h,children:[(0,R.jsxs)(`p`,{className:`hint`,children:[`Imported `,H(c.botons.length,`Boton`),` (`,U(c.botons.map(e=>e.name)),`), `,H(c.skills.length,`skill`),`,`,` `,H(c.templates.length,`saved template`),` and `,H(c.connectors.length,`connector`),c.settings?`, and the settings`:``,`. Every routine it made is off.`]}),c.reconnect.length>0&&(0,R.jsxs)(R.Fragment,{children:[(0,R.jsx)(`h5`,{children:`Still to do`}),(0,R.jsx)(`ul`,{className:`hint config-facts config-reconnect`,children:c.reconnect.map((e,t)=>(0,R.jsx)(`li`,{children:e.text},t))})]})]})]})}function Xe(){let[e,t]=(0,L.useState)(null),[n,r]=(0,L.useState)(!1),[i,a]=(0,L.useState)(``),[o,c]=(0,L.useState)(null),[l,u]=(0,L.useState)(null),[d,f]=(0,L.useState)(null),p=()=>b.devices().then(t).catch(e=>f(e.message));(0,L.useEffect)(()=>{p()},[]),(0,L.useEffect)(()=>{if(!o)return;let e=setTimeout(()=>void p(),4e3);return()=>clearTimeout(e)},[o]);let m=async e=>{f(null);try{await e()}catch(e){f(e.message)}},h=e=>m(async()=>{if(u(null),e.current)return s.signOut();await b.revokeDevice(e.id),await p()}),g=()=>m(async()=>{let e=await b.approvePairing({code:i});c(e.label),a(``),r(!1)});return(0,R.jsxs)(`section`,{className:`server-info devices`,children:[(0,R.jsx)(`h4`,{children:`Devices`}),(0,R.jsx)(`p`,{className:`hint`,children:`Paired devices can use Botons. Revoking one signs it out at once.`}),e===null&&!d&&(0,R.jsx)(`p`,{className:`hint`,children:`Loading…`}),e&&(0,R.jsx)(`ul`,{className:`device-list`,children:e.map(e=>(0,R.jsxs)(`li`,{children:[(0,R.jsxs)(`span`,{className:`device-text`,children:[(0,R.jsxs)(`span`,{className:`device-name`,children:[e.label,e.current&&(0,R.jsx)(`span`,{className:`device-tag`,children:`this device`})]}),(0,R.jsxs)(`span`,{className:`hint`,children:[`Paired `,D(e.createdAt),e.lastUsedAt&&!e.current?` · used ${D(e.lastUsedAt)}`:``]})]}),l===e.id?(0,R.jsxs)(`span`,{className:`device-actions`,children:[(0,R.jsx)(`button`,{type:`button`,className:`btn small ghost`,onClick:()=>u(null),children:`Cancel`}),(0,R.jsx)(`button`,{type:`button`,className:`btn small danger`,onClick:()=>h(e),children:e.current?`Sign out`:`Revoke`})]}):(0,R.jsx)(`button`,{type:`button`,className:`btn small ghost`,onClick:()=>u(e.id),children:e.current?`Sign out`:`Revoke`})]},e.id))}),o&&(0,R.jsxs)(`p`,{className:`hint`,children:[`Approved “`,o,`”. It signs in within a few seconds.`]}),n?(0,R.jsxs)(`form`,{className:`pair-code`,onSubmit:e=>{e.preventDefault(),g()},children:[(0,R.jsxs)(`label`,{children:[(0,R.jsx)(`span`,{className:`field-label`,children:`The code the new device shows`}),(0,R.jsx)(`input`,{value:i,onChange:e=>a(e.target.value),placeholder:`XXXX-XXXX`,autoCapitalize:`characters`,autoComplete:`off`,autoCorrect:`off`,spellCheck:!1,autoFocus:!0})]}),(0,R.jsx)(`p`,{className:`hint warn`,children:`Type the code from the screen of the device you are holding. A Boton can ask to pair too: never approve a code that reached you in a chat or a message.`}),(0,R.jsxs)(`span`,{className:`device-actions`,children:[(0,R.jsx)(`button`,{type:`button`,className:`btn small ghost`,onClick:()=>r(!1),children:`Cancel`}),(0,R.jsx)(`button`,{type:`submit`,className:`btn small`,disabled:!i.trim(),children:`Approve`})]})]}):(0,R.jsx)(`button`,{type:`button`,className:`btn small`,onClick:()=>{c(null),r(!0)},children:`Approve a device`}),(0,R.jsx)(`p`,{className:`hint`,children:`To pair a new device, open Botons on it: it shows a code to approve here.`}),d&&(0,R.jsx)(`p`,{className:`form-error`,children:d})]})}var G={service:{backup:V(`service`,`backup`),restore:[`systemctl --user stop botons`,V(`service`,`restore`,`--list`),V(`service`,`restore`,`latest`),V(`service`,`restore`,`latest --yes`),`systemctl --user start botons`].join(`
2
+ `),doc:`README.md, Backups`,log:`journalctl --user -u botons`},managed:{backup:V(`managed`,`backup`),restore:[`systemctl --user stop botons`,V(`managed`,`restore`,`--list`),V(`managed`,`restore`,`latest`),V(`managed`,`restore`,`latest --yes`),`systemctl --user start botons`].join(`
3
+ `),doc:`README.md, Backups`,log:`journalctl --user -u botons`},docker:{backup:V(`docker`,`backup`),restore:`docker compose stop botons
4
+ docker compose run --rm --no-deps botons restore --list
5
+ docker compose run --rm --no-deps botons restore latest
6
+ docker compose run --rm --no-deps botons restore latest --yes
7
+ docker compose start botons`,doc:`docs/DOCKER.md, Backups and restore`,log:`docker compose logs botons`}};function K({ok:e,label:t,children:n}){return(0,R.jsxs)(`li`,{children:[(0,R.jsx)(`span`,{className:`status-dot ${e?`succeeded`:`failed`}`,"aria-hidden":`true`}),` `,(0,R.jsx)(`strong`,{children:t}),(0,R.jsx)(`span`,{className:`sr-only`,children:e?` (working)`:` (needs attention)`}),`: `,(0,R.jsx)(`span`,{className:e?void 0:`operations-problem`,children:n})]})}function Ze(e){return e.schemaVersion===null?`its manifest is missing or unreadable`:e.workspacesFailed?`database only (schema ${e.schemaVersion}): archiving the workspaces failed`:`database${e.workspaces?` and workspaces`:``} (schema ${e.schemaVersion})`}function Qe(e,t){let n=e.schemaVersion;return n===null?null:n===t.schema?{ok:!0,text:`this release restores it`}:t.restorableSchemas.includes(n)?{ok:!0,text:`this release restores it and upgrades it to schema ${t.schema} at the next start`}:n>t.schema?{ok:!1,text:`newer than this release (schema ${t.schema}): restore it with the release that made it`}:{ok:!1,text:`older than this release restores (schema ${t.restorableSchemas.join(` or `)})`}}var q={"not-found":`the backup is no longer there, or is not a backup folder: pick another`,"not-a-backup":`it holds no Botons database where one belongs, or a link or special file in its place: a restore would refuse it, so rehearse another backup`,"database-too-large":`its database is larger than a rehearsal from the app reads: check it on the server with the restore command without --yes, which changes nothing`,"no-space":`there is not room for a temporary copy where the data lives: free space on the server (Disk space above), then rehearse again`,"damaged-database":`its database failed SQLite's integrity check: do not restore it; rehearse an older backup`,"newer-schema":`it was made by a newer Botons: restore it with the release that made it, not this one`,"older-schema":`it is older than this release restores: restore it with the release that made it first`,"upgrade-failed":`this release could not upgrade its database, so the server would not start after restoring it: keep the backup, rehearse an older one, and send the status summary below with a bug report`,"damaged-archive":`its workspaces archive is cut short or damaged, so a restore would stop part-way: rehearse an older backup`,"unsafe-archive":`its workspaces archive holds links, special files or paths outside the folders a backup archives, which a Botons backup never does: do not restore it`,"archive-too-large":`its workspaces archive is larger than a rehearsal from the app reads: check it on the server with the restore command without --yes, which changes nothing`,"timed-out":`it ran past its time limit and was stopped: check the backup on the server with the restore command without --yes`,interrupted:`the server stopped or restarted while it ran: rehearse again`,failed:`it stopped on something it did not expect: rehearse again, and if it fails again see the server's raw log`},J=e=>(0,R.jsx)(`time`,{dateTime:e,title:C(e),children:D(e)});function $e(e,t){if(e.outcome===`running`)return(0,R.jsxs)(R.Fragment,{children:[`Rehearsing `,(0,R.jsx)(`code`,{children:e.backup}),`, started `,J(e.startedAt),`…`]});let n=e.finishedAt??e.startedAt;if(e.outcome===`failed`)return(0,R.jsxs)(`span`,{className:`operations-problem`,children:[(0,R.jsx)(`code`,{children:e.backup}),` failed `,J(n),`: `,q[e.problem??`failed`]??q.failed,`.`]});let r=e.fromSchema!==null&&e.toSchema!==null&&e.fromSchema!==e.toSchema;return(0,R.jsxs)(R.Fragment,{children:[(0,R.jsx)(`code`,{children:e.backup}),` passed `,J(n),`: its database (schema `,e.fromSchema,`) passed SQLite's integrity check`,r&&(0,R.jsxs)(R.Fragment,{children:[`, and upgraded to schema `,e.toSchema,` as the server's next start would`]}),`;`,` `,e.workspaces===`checked`?`its workspaces archive (${e.files} files in ${e.folders} folders, ${M(e.bytes??0)}) read through, holding only regular files and folders where a backup puts them`:`it holds the database only, no workspaces`,`.`,e.matchesManifest===!1&&` Its tables hold a different number of rows than its manifest says, so it changed after it was made.`,e.ranBy.schema!==t&&` That ran on schema ${e.ranBy.schema}; this server is at ${t}, so rehearse again.`]})}function et({data:e,onReload:t}){let n=e.backups.backups.map(e=>e.name),[r,i]=(0,L.useState)(``),[a,o]=(0,L.useState)(!1),[s,c]=(0,L.useState)(null),l=e.rehearsal.last,u=l?.outcome===`running`,d=n.includes(r)?r:n[0]??``;return(0,R.jsxs)(`div`,{className:`operations-rehearsal`,children:[(0,R.jsx)(`span`,{className:`field-label`,children:`Restore rehearsal`}),(0,R.jsx)(`p`,{className:`hint`,role:`status`,children:l?$e(l,e.schema):`None yet.`}),n.length>0&&(0,R.jsxs)(`p`,{className:`operations-rehearse`,children:[(0,R.jsxs)(`label`,{children:[(0,R.jsx)(`span`,{className:`sr-only`,children:`Backup to rehearse`}),(0,R.jsx)(`select`,{value:d,disabled:a||u,onChange:e=>i(e.target.value),children:n.map((e,t)=>(0,R.jsxs)(`option`,{value:e,children:[e,t===0?` (newest)`:``]},e))})]}),` `,(0,R.jsx)(`button`,{type:`button`,className:`link-btn`,disabled:a||u||!d,onClick:async()=>{o(!0),c(null);try{await b.rehearseRestore(d)}catch(e){c(`Couldn't start a rehearsal: ${O(e.message,200)}`)}finally{o(!1),t()}},children:u?`Rehearsing…`:`Rehearse a restore`}),s&&(0,R.jsxs)(`span`,{className:`hint`,role:`alert`,children:[` `,s]})]}),(0,R.jsxs)(`p`,{className:`hint`,children:[`A rehearsal copies the backup into a private temporary folder on the server, checks its database, upgrades the copy to this server's schema as a restore and the next start would, reads its workspaces archive through and checks every file in it, then deletes the copy. The data folder, the backups and the running server are not changed. One at a time, at most `,e.rehearsal.limitPerHour,` an hour, each in the audit log.`]})]})}var tt={refused:`the ntfy server refused it (401 or 403): check the access token, or that the topic allows it`,rejected:`the ntfy server rejected it: check the topic name and the server URL`,redirected:`the ntfy server answered with a redirect: check the server URL (https, no extra path)`,"server-error":`the ntfy server had a problem (5xx): send a test later, or check that server`,unreachable:`this server could not reach the ntfy server: check the server URL and the server's network`,timeout:`the ntfy server did not answer within 10 seconds: send a test later, or check that server`},Y={busy:`it is locked by another process: check that only one Botons server uses this data folder`,corrupt:`the file is damaged: stop the server and restore the newest backup (How to back up and restore below)`,"read-only":`it can't be written: check the data folder's permissions and that its disk is not mounted read-only`,"disk-full":`the disk is full: free space on the server (Disk space below)`,"cannot-open":`the file can't be opened: check that the data folder exists and the server's user can read it`,"io-error":`the disk reported an error: check the disk, then run the doctor on the server`,other:`it didn't answer: run the doctor on the server, and see Server events below`},nt={"not-installed":`not found, or its --version failed`,"signed-out":`not signed in`,unavailable:`not ready`};function rt(e){return`${e.label}: ${e.problem?nt[e.problem]:`ready`}`}function it(e){return/^[a-z0-9-]{1,32}$/.test(e)?e:`other`}function at(e){return e.sinceSettingsChange?`since the push settings changed`:`since the server started`}function ot(e){return e.state!==`on`||!e.last||e.last.ok}function st(e){if(e.state===`unset`)return`not set up: pushes about failures and approvals need an ntfy server and topic (Push notifications below)`;let t=e.last,n=e=>(0,R.jsx)(`time`,{dateTime:e,title:C(e),children:D(e)}),r=t?t.ok?(0,R.jsxs)(R.Fragment,{children:[`last `,t.reason===`test`?`test `:``,`push went through `,n(t.at)]}):(0,R.jsxs)(R.Fragment,{children:[`the last `,t.reason===`test`?`test `:``,`push failed `,n(t.at),e.failuresInARow>1&&` (${e.failuresInARow} in a row)`,`: `,t.problem?tt[t.problem]:`see Server events below`,e.lastSuccessAt?(0,R.jsxs)(R.Fragment,{children:[`. The last one that went through was `,n(e.lastSuccessAt)]}):`. None has gone through ${at(e)}`]}):`none sent ${at(e)}`;return e.state===`off`?(0,R.jsxs)(R.Fragment,{children:[`turned off, so nobody is told about failures or approvals; `,r]}):r}function ct(e){if(e.unreadable)return`free space could not be read; the database is ${M(e.databaseBytes)}`;let t=Math.round(e.freeBytes/e.totalBytes*100),n=`${M(e.freeBytes)} free of ${M(e.totalBytes)} (${t}%); the database is ${M(e.databaseBytes)}`;return e.low?`${n}. That is low: when the disk fills, chats, runs and backups stop being saved. Free space on the server's disk, for example by keeping fewer backups or clearing large files from Boton workspaces`:n}function lt(e,t){let{details:n}=e.health,r=e=>e?`ok`:`NEEDS ATTENTION`,i=e.backups,a=e.push,o=e.storage;return[`Botons ${A(t)??`unknown version`}, build ${S(e.health.build)??`unknown`}, schema ${Number.isSafeInteger(e.schema)&&e.schema>0?e.schema:`unknown`}, ${e.install===`docker`?`Docker image`:`service`}`,`Health: ${e.health.ok?`ok`:`failing: ${e.health.failing.filter(e=>/^[a-z]{1,20}$/.test(e)).join(`, `)}`}`,`Database: ${r(n.database.ok)}${n.database.problem&&Y[n.database.problem]?` (${n.database.problem})`:``}`,`Scheduler: ${r(n.scheduler.ok)}${n.scheduler.lastTickAt?`, last tick ${n.scheduler.lastTickAt}`:`, no tick yet`}`,...n.providers.map(e=>`Brain ${it(e.id)}: ${e.available?`ready`:`not ready${e.problem&&nt[e.problem]?` (${e.problem})`:``}`}, installed ${e.installed?`yes`:`no`}, signed in ${e.loggedIn===null?`unknown`:e.loggedIn?`yes`:`no`}, version ${A(e.version)??`unknown`}${e.usageLimitUntil?`, usage limit until ${e.usageLimitUntil}`:``}${e.lastErrorAt?`, last turn failed ${e.lastErrorAt}`:``}`),`Accepting work: ${n.draining?`no (draining)`:`yes`}`,`Backups: ${i.unreadable?`folder unreadable`:`${i.total} kept, newest ${i.backups[0]?.createdAt??`none`}${i.stale?` (stale)`:``}${i.partial?`, ${i.partial} partial`:``}`}`,`Push: ${a.state}${a.last?`, last ${a.last.ok?`ok`:`failed (${a.last.problem??`unknown`}${a.last.status?`, HTTP ${a.last.status}`:``})`} ${a.last.at}`:`, none ${a.sinceSettingsChange?`since its settings changed`:`since start`}`}${a.failuresInARow>1?`, ${a.failuresInARow} failures in a row`:``}`,`Disk: ${o.unreadable?`unreadable`:`${M(o.freeBytes)} free of ${M(o.totalBytes)}${o.low?` (LOW)`:``}`}, database ${M(o.databaseBytes)}`,`Restores schemas: ${(e.restorableSchemas??[]).filter(e=>Number.isSafeInteger(e)&&e>0).join(`, `)||`unknown`}`,`Restore rehearsal: ${dt(e.rehearsal?.last??null)}`].join(`
8
+ `)}var ut=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/,X=e=>e&&ut.test(e)?e:`unknown time`,Z=e=>e!==null&&Number.isSafeInteger(e)&&e>0?String(e):`?`;function dt(e){if(!e)return`none`;if(e.outcome===`running`)return`running since ${X(e.startedAt)}`;let t=`schema ${Z(e.fromSchema)}${e.toSchema!==null&&e.toSchema!==e.fromSchema?` -> ${Z(e.toSchema)}`:``}`,n=e.workspaces===`checked`||e.workspaces===`none`?e.workspaces:`not checked`;if(e.outcome===`passed`)return`passed ${X(e.finishedAt)} (${t}, workspaces ${n}${e.matchesManifest===!1?`, rows differ from manifest`:``}), ran on schema ${Z(e.ranBy.schema)}`;let r=e.problem&&e.problem in q?e.problem:`failed`;return`FAILED ${X(e.finishedAt??e.startedAt)} (${r}${e.fromSchema===null?``:`, ${t}`}), ran on schema ${Z(e.ranBy.schema)}`}function ft({onDone:e}){let[t,n]=(0,L.useState)(!1),[r,i]=(0,L.useState)(null);return(0,R.jsxs)(R.Fragment,{children:[(0,R.jsx)(`button`,{type:`button`,className:`link-btn`,disabled:t,onClick:async()=>{n(!0),i(null);try{let e=await b.testPush();i(e.ok?`Sent. Check your phone.`:`The test failed too; the check above says where to look.`)}catch(e){i(`Couldn't send a test: ${O(e.message,200)}`)}finally{n(!1),e()}},children:t?`Sending a test…`:`Send a test push`}),r&&(0,R.jsxs)(`span`,{className:`hint`,role:`status`,children:[` `,r]})]})}function pt({text:e,log:t}){let[n,r]=(0,L.useState)(null);return(0,R.jsxs)(`details`,{children:[(0,R.jsx)(`summary`,{children:`Status summary for asking for help`}),(0,R.jsxs)(`p`,{className:`hint`,children:[`Paste this into a bug report or a message to whoever runs the server. It names no Botons, people, paths, addresses or error text. Those are only in the server's raw log, on its machine (`,(0,R.jsx)(`code`,{children:t}),`): Server events below leave them out too.`]}),(0,R.jsx)(`pre`,{className:`code`,children:e}),(0,R.jsx)(`button`,{type:`button`,className:`link-btn`,onClick:async()=>r(await j(e)),children:`Copy`}),n!==null&&(0,R.jsxs)(`span`,{className:`hint`,role:`status`,children:[` `,n?`Copied.`:`Couldn't copy; select the text instead.`]})]})}function mt(){let[e,t]=(0,L.useState)(null),[n,r]=(0,L.useState)(0),i=P();(0,L.useEffect)(()=>{let e=window.setInterval(()=>i.current&&r(e=>e+1),3e4);return()=>window.clearInterval(e)},[]);let a=ae(async e=>{let n=await b.operations();e()&&t({data:n,at:new Date().toISOString()})},`operations:${n}`,!0),o=g(e=>e.info?.version??null),s=g(e=>e.settings?.ntfy),c=(0,L.useRef)(s);(0,L.useEffect)(()=>{s!==c.current&&(c.current=s,r(e=>e+1))},[s]);let l=e?.data.rehearsal.last?.outcome===`running`,u=(0,L.useContext)(fe);return(0,L.useEffect)(()=>{if(!l||!u)return;let e=window.setTimeout(()=>r(e=>e+1),2e3);return()=>window.clearTimeout(e)},[l,e,u]),(0,R.jsx)(ht,{loaded:e,error:a.error,onRetry:a.retry,onReload:()=>r(e=>e+1),version:o})}function ht({loaded:e,error:t,onRetry:n,onReload:r=n,version:i=null}){return(0,R.jsxs)(`section`,{className:`server-info operations`,"aria-labelledby":`operations-title`,children:[(0,R.jsx)(`h4`,{id:`operations-title`,children:`Health and backups`}),!e&&(t?(0,R.jsx)(de,{what:`health and backups`,error:t,onRetry:n}):(0,R.jsx)(`p`,{className:`hint`,children:`Loading…`})),e&&t&&(0,R.jsxs)(`p`,{className:`load-error`,role:`alert`,children:[(0,R.jsxs)(`span`,{children:[`Couldn't refresh health and backups: `,O(t,200),`. This is the status from`,` `,(0,R.jsx)(`time`,{dateTime:e.at,title:C(e.at),children:D(e.at)}),`.`]}),(0,R.jsx)(`button`,{type:`button`,className:`link-btn`,onClick:n,children:`Retry`})]}),e&&(0,R.jsx)(gt,{data:e.data,onReload:r,version:i}),e&&(0,R.jsx)(pt,{text:lt(e.data,i),log:(G[e.data.install]??G.service).log}),e&&!t&&(0,R.jsxs)(`p`,{className:`hint`,children:[`Updated`,` `,(0,R.jsx)(`time`,{dateTime:e.at,title:C(e.at),children:D(e.at)}),`; refreshes every 30 seconds.`]})]})}function gt({data:e,onReload:t,version:n}){let{details:r}=e.health,i=r.providers.filter(e=>e.available).length,{backups:a}=e,o=a.backups[0],s=G[e.install],c=S(e.health.build);return(0,R.jsxs)(R.Fragment,{children:[(0,R.jsxs)(`p`,{className:`hint`,children:[`This server runs Botons `,A(n)??`(version unknown)`,c?`, build ${c.slice(0,12)}`:``,`, database schema `,e.schema,`. Its restore takes backups at schema `,e.restorableSchemas.join(` and `),e.restorableSchemas.some(t=>t<e.schema)&&` (older ones are upgraded at the next start)`,`.`]}),(0,R.jsxs)(`ul`,{className:`operations-checks`,children:[(0,R.jsx)(K,{ok:r.database.ok,label:`Database`,children:r.database.ok?`opens and answers`:`cannot be read: ${Y[r.database.problem??`other`]??Y.other}`}),(0,R.jsx)(K,{ok:r.scheduler.ok,label:`Scheduler`,children:r.scheduler.lastTickAt?`${r.scheduler.ok?`last checked routines`:`has not checked routines since`} ${D(r.scheduler.lastTickAt)}`:r.scheduler.ok?`starting`:`has not run since the server started: scheduled routines are not starting`}),(0,R.jsx)(K,{ok:i>0,label:`Brains`,children:i>0?`${i} ready`:`none is ready${r.providers.length?` (${r.providers.map(rt).join(`; `)})`:``}: install or sign a CLI in on the server (see Brains above)`}),(0,R.jsx)(K,{ok:!r.draining,label:`Accepting work`,children:r.draining?`no: the server is shutting down and finishing running turns`:`yes`}),(0,R.jsxs)(K,{ok:ot(e.push),label:`Push notifications`,children:[st(e.push),e.push.state!==`unset`&&(0,R.jsxs)(R.Fragment,{children:[` `,(0,R.jsx)(ft,{onDone:t})]})]}),(0,R.jsx)(K,{ok:!e.storage.low&&!e.storage.unreadable,label:`Disk space`,children:ct(e.storage)}),(0,R.jsx)(K,{ok:!a.stale&&!a.unreadable,label:`Backups`,children:a.unreadable?`the backups folder cannot be read on the server: run the doctor there to see why`:o?(0,R.jsxs)(R.Fragment,{children:[`newest`,` `,(0,R.jsx)(`time`,{dateTime:o.createdAt,title:C(o.createdAt),children:D(o.createdAt)}),`, `,a.total,` kept (the newest `,a.keep,` stay)`,a.stale&&`. That is over ${a.staleAfterHours/24} days old: check the backup timer or cron job`]}):a.stale?`none yet`:`none yet; there is nothing to back up`})]}),a.partial>0&&(0,R.jsxs)(`p`,{className:`hint warn`,children:[a.partial===1?`A backup`:`${a.partial} backups`,` stopped part-way (a `,(0,R.jsx)(`code`,{children:`.partial`}),` folder). The next backup deletes it after a day; run a backup on the server to see why it failed.`]}),o&&(0,R.jsxs)(`details`,{children:[(0,R.jsx)(`summary`,{children:a.backups.length<a.total?`The newest ${a.backups.length} of ${a.total} backups`:`All ${a.total} backups`}),(0,R.jsx)(`ul`,{className:`operations-backups`,children:a.backups.map(t=>{let n=Qe(t,e);return(0,R.jsxs)(`li`,{children:[(0,R.jsx)(`code`,{children:t.name}),(0,R.jsxs)(`span`,{className:t.workspacesFailed||t.schemaVersion===null?`hint warn`:`hint`,children:[` `,Ze(t)]}),n&&(0,R.jsxs)(`span`,{className:n.ok?`hint`:`hint warn`,children:[`; `,n.text]})]},t.name)})})]}),(0,R.jsx)(et,{data:e,onReload:t}),(0,R.jsxs)(`details`,{children:[(0,R.jsx)(`summary`,{children:`How to back up and restore`}),(0,R.jsx)(`p`,{className:`hint`,children:`Run these on the server's machine, from where Botons is installed. A restore needs the server stopped, so the app cannot do it.`}),(0,R.jsx)(`span`,{className:`field-label`,children:`Back up now`}),(0,R.jsx)(`pre`,{className:`code`,children:s.backup}),(0,R.jsx)(`span`,{className:`field-label`,children:`Restore`}),(0,R.jsx)(`pre`,{className:`code`,children:s.restore}),(0,R.jsxs)(`p`,{className:`hint`,children:[(0,R.jsx)(`code`,{children:`--list`}),` shows the backups, oldest first. Without `,(0,R.jsx)(`code`,{children:`--yes`}),` a restore only says what it would do; use a name from the list instead of `,(0,R.jsx)(`code`,{children:`latest`}),` for an older backup. A restore deletes nothing: the current database and folders move to `,(0,R.jsx)(`code`,{children:`before-restore-<time>`}),` in the data folder first, and a restore that fails part-way puts them back. If it says recovery is incomplete, keep the folders it names and do not start the server. More in `,s.doc,`.`]})]})]})}var _t=()=>{let e=crypto.getRandomValues(new Uint8Array(12));return`botons-${Array.from(e,e=>(e%36).toString(36)).join(``)}`},Q=e=>{try{return e?new URL(e).origin:``}catch{return``}};function vt(){let e=g(e=>e.settings?.ntfy),[t,n]=(0,L.useState)(e?.enabled??!1),[r,i]=(0,L.useState)(e?.server??``),[a,o]=(0,L.useState)(e?.topic??``),[c,l]=(0,L.useState)(``),[u,d]=(0,L.useState)(e?.preview??!1),[f,p]=(0,L.useState)(null),[m,h]=(0,L.useState)(null),[_,v]=(0,L.useState)(null),y=!!e&&(t!==e.enabled||r.trim()!==e.server||a.trim()!==e.topic||u!==e.preview||c.trim()!==``),x=async(e,t)=>{p(e),h(null),v(null);try{v(await t())}catch(e){h(e.message)}finally{p(null)}},S=()=>s.updateSettings({ntfy:{enabled:t,server:r.trim(),topic:a.trim(),preview:u,...c.trim()?{token:c.trim()}:{}}});return(0,R.jsxs)(`form`,{className:`server-info push-settings`,onSubmit:e=>{e.preventDefault(),x(`save`,async()=>(await S(),l(``),`Saved.`))},children:[(0,R.jsx)(`h4`,{children:`Push notifications`}),(0,R.jsxs)(`p`,{className:`hint`,children:[`Failed routine runs, and whatever a Boton sends with `,(0,R.jsx)(`code`,{children:`botons notify`}),`, reach your phone through`,` `,(0,R.jsx)(`a`,{href:`https://ntfy.sh`,target:`_blank`,rel:`noreferrer`,children:`ntfy`}),`. Install the ntfy app, subscribe to the topic below, and tap a push to open that Boton's chat.`]}),(0,R.jsxs)(`label`,{className:`check`,children:[(0,R.jsx)(`input`,{type:`checkbox`,checked:t,onChange:e=>n(e.target.checked)}),(0,R.jsx)(`span`,{children:`Send push notifications`})]}),(0,R.jsxs)(`label`,{children:[(0,R.jsx)(`span`,{className:`field-label`,children:`ntfy server`}),(0,R.jsx)(`input`,{value:r,onChange:e=>i(e.target.value),placeholder:`https://ntfy.sh`,inputMode:`url`,autoComplete:`off`})]}),(0,R.jsxs)(`label`,{children:[(0,R.jsx)(`span`,{className:`field-label`,children:`Topic`}),(0,R.jsxs)(`span`,{className:`topic-row`,children:[(0,R.jsx)(`input`,{value:a,onChange:e=>o(e.target.value),placeholder:`botons-…`,autoComplete:`off`,spellCheck:!1,maxLength:64}),(0,R.jsx)(`button`,{type:`button`,className:`btn small ghost`,onClick:()=>o(_t()),children:`Random`})]}),(0,R.jsx)(`span`,{className:`hint`,children:`On a public server anyone who knows the topic can read it, so pick a long random one, or run ntfy on your tailnet.`})]}),(0,R.jsxs)(`label`,{children:[(0,R.jsx)(`span`,{className:`field-label`,children:`Access token (optional)`}),(0,R.jsx)(`input`,{type:`password`,value:c,onChange:e=>l(e.target.value),placeholder:e?.hasToken?`Saved. Type a new one to replace it`:`tk_… for a protected topic`,autoComplete:`off`}),e?.hasToken&&!c.trim()&&Q(r.trim())!==Q(e.server)&&(0,R.jsxs)(`span`,{className:`hint warn`,children:[`The saved token is for `,Q(e.server)||`another server`,`: saving this server without a new token removes it, so it is never sent anywhere else.`]}),e?.hasToken&&(0,R.jsx)(`button`,{type:`button`,className:`link-btn`,disabled:!!f,onClick:()=>x(`token`,async()=>(await s.updateSettings({ntfy:{token:null}}),`Removed the token.`)),children:`Remove the saved token`})]}),(0,R.jsxs)(`label`,{className:`check`,children:[(0,R.jsx)(`input`,{type:`checkbox`,checked:u,onChange:e=>d(e.target.checked)}),(0,R.jsx)(`span`,{children:`Show text in pushes`})]}),(0,R.jsx)(`p`,{className:`hint`,children:u?`Pushes include up to 200 characters of the error, result or notification text (never a webhook payload).`:`Pushes only name the Boton and say what happened, such as “A routine failed.” Open the chat for the rest.`}),m&&(0,R.jsx)(`p`,{className:`form-error`,children:m}),_&&(0,R.jsx)(`p`,{className:`hint`,children:_}),(0,R.jsxs)(`div`,{className:`form-actions`,children:[(0,R.jsx)(`button`,{type:`button`,className:`btn ghost`,disabled:!!f||!r.trim()||!a.trim(),onClick:()=>x(`test`,async()=>{y&&await S(),l(``);let e=await b.testPush();return e.ok?`Sent a test push. Check your phone.`:`The test push failed: ${e.error}`}),children:f===`test`?`Sending…`:`Send a test`}),(0,R.jsx)(`button`,{type:`submit`,className:`btn primary`,disabled:!!f||!y,children:f===`save`?`Saving…`:`Save`})]})]})}var yt=[`👎`,`😕`,`❓`];function bt(){let e=g(e=>e.settings?.reactionFollowUps),[t,n]=(0,L.useState)(``),[r,i]=(0,L.useState)(!1),[a,o]=(0,L.useState)(null),c=e??[],l=async e=>{i(!0),o(null);try{return await s.updateSettings({reactionFollowUps:e}),!0}catch(e){return o(`Couldn't save the follow-up reactions: ${w(e)}`),!1}finally{i(!1)}},u=async e=>{let t=Ce(e);if(!t)return o(`One emoji, with no text: say 👎`);if(c.includes(t))return o(`${t} already asks for a follow-up`);if(c.length>=12)return o(`At most 12: take one off first`);await l([...c,t])&&n(``)},d=c.length===I.length&&c.every((e,t)=>e===I[t]);return(0,R.jsxs)(`section`,{className:`server-info reaction-settings`,"aria-labelledby":`reaction-settings-title`,children:[(0,R.jsx)(`h4`,{id:`reaction-settings-title`,children:`Reactions`}),(0,R.jsx)(`p`,{className:`hint`,children:`A reaction on a Boton's message is feedback: the Boton is given it on its next turn. It starts no turn and sends no notification, and it never answers an approval or a card, which have their own buttons.`}),(0,R.jsx)(`p`,{className:`hint`,children:c.length?`These reactions also ask the Boton for a short follow-up about that message, once per message:`:`No reaction asks for a follow-up.`}),c.length>0&&(0,R.jsx)(`ul`,{className:`follow-up-list`,"aria-label":`Reactions that ask for a follow-up`,children:c.map(e=>(0,R.jsxs)(`li`,{className:`follow-up-emoji`,children:[(0,R.jsx)(`span`,{"aria-hidden":`true`,children:e}),(0,R.jsx)(`button`,{type:`button`,className:`icon-btn`,disabled:r,"aria-label":`Stop ${e} asking for a follow-up`,title:`Stop ${e} asking for a follow-up`,onClick:()=>void l(c.filter(t=>t!==e)),children:(0,R.jsx)(ge,{size:14})})]},e))}),(0,R.jsxs)(`form`,{className:`follow-up-add`,onSubmit:e=>{e.preventDefault(),u(t)},children:[yt.filter(e=>!c.includes(e)).map(e=>(0,R.jsxs)(`button`,{type:`button`,className:`btn small`,disabled:r,"aria-label":`Add ${e}`,onClick:()=>void u(e),children:[`+ `,e]},e)),(0,R.jsx)(`input`,{value:t,onChange:e=>{n(e.target.value),o(null)},placeholder:`Any emoji`,"aria-label":`Add an emoji that asks for a follow-up`,autoComplete:`off`}),(0,R.jsx)(`button`,{type:`submit`,className:`btn small`,disabled:r||!t.trim(),children:`Add`})]}),(0,R.jsxs)(`div`,{className:`follow-up-actions`,children:[c.length>0&&(0,R.jsx)(`button`,{type:`button`,className:`link-btn`,disabled:r,onClick:()=>void l([]),children:`Turn follow-ups off`}),!d&&(0,R.jsxs)(`button`,{type:`button`,className:`link-btn`,disabled:r,onClick:()=>void l([...I]),children:[`Back to `,I.join(` `),` alone`]})]}),a&&(0,R.jsx)(`p`,{className:`form-error`,role:`alert`,children:a})]})}var xt=class{#e;#t;#n=new Set;#r=0;#i=null;#a=null;constructor(e,t={level:`warn`,q:``,boton:``}){this.#e={debounceMs:300,setTimer:(e,t)=>setTimeout(e,t),clearTimer:e=>clearTimeout(e),now:()=>new Date,...e},this.#t={filters:t,data:null,entries:[],error:null,loading:!0,loadedAt:null,restartedAt:null}}get state(){return this.#t}subscribe=e=>(this.#n.add(e),()=>void this.#n.delete(e));#o(e){this.#t={...this.#t,...e};for(let e of this.#n)e()}start(){this.#s(0)}stop(){this.#r++,this.#c()}setFilters(e){let t=this.#t.filters;(t.level!==e.level||t.q!==e.q||t.boton!==e.boton)&&(this.#r++,this.#o({filters:e,data:null,entries:[],error:null,loading:!0}),this.#s(e.q?this.#e.debounceMs:0))}refresh(){this.#c(),this.#l()}older(){let e=this.#t.entries.at(-1)?.seq;e!==void 0&&this.#t.data?.more&&(this.#c(),this.#l(e))}#s(e){this.#c();let t=this.#r;this.#i=this.#e.setTimer(()=>{this.#i=null,t===this.#r&&this.#l()},e)}#c(){this.#i!==null&&this.#e.clearTimer(this.#i),this.#i=null}async#l(e){let t=++this.#r,n=this.#t.filters;this.#o({loading:!0,error:null});let r;try{r=await this.#e.fetch(n,e)}catch(e){t===this.#r&&this.#o({loading:!1,error:this.#e.errorText(e)});return}if(t!==this.#r)return;let i=this.#a!==null&&r.since!==this.#a;if(this.#a=r.since,i&&e!==void 0)return this.#o({restartedAt:r.since,data:null,entries:[]}),this.#l();let a=e===void 0?r.entries:[...this.#t.entries,...r.entries.filter(t=>t.seq<e)];this.#o({data:r,entries:a,loading:!1,loadedAt:this.#e.now().toISOString(),...i?{restartedAt:r.since}:{}})}},St={info:`Info`,warn:`Warning`,error:`Error`},Ct={reconnect:`Open Sign-in connectors below and sign the connector in again.`,connector:`Open its connector below and test it; sign it in again if it asks.`,push:`Check Push notifications below and send a test.`,tailscale:`Other devices cannot reach the server until Tailscale is up on its machine; it keeps checking by itself.`,"usage-limit":`Its Botons wait for the reset by themselves. To carry on sooner, move a Boton to another brain in its settings.`,brain:`Sign that CLI in on the server’s machine (see Brains above).`,run:`Open the routine’s run history (or Runs to check, above, when it lists the run) to retry it or send its result again.`,bug:`This looks like a problem in Botons itself. If it repeats, copy the event into a bug report, with the matching lines from the server’s full log.`,"auth-off":`Set BOTONS_AUTH back on in the server’s settings and restart it, unless you mean to run without pairing.`};function wt(e){return e.help?Ct[e.help]:e.event===`turn.ended`&&e.fields.runId&&[`error`,`timeout`,`interrupted`].includes(String(e.fields.outcome))?Ct.run:e.event===`unclassified`?e.level===`error`?`The details are only in the server’s full log (its journal or container log), from around this time.`:null:e.level===`error`?`If it repeats, copy the event into a bug report, with what you were doing at the time.`:null}var Tt={botId:`Boton`,fromBotId:`From Boton`,routineId:`Routine`,runId:`Run`,retryOf:`Retry of`,approvalId:`Approval`,connectionId:`Connector`,threadId:`Thread`,deviceId:`Device`,templateId:`Template`,provider:`Brain`,errorCategory:`Error kind`,oauthError:`OAuth error`,durationMs:`Took`,timeLimitMs:`Time limit`,retryInMs:`Retry in`,exitCode:`Exit code`,status:`Status`};function Et(e,t){return typeof t==`number`&&e.endsWith(`Ms`)?t<1e3?`${t} ms`:`${Math.round(t/100)/10} s`:typeof t==`boolean`?t?`yes`:`no`:String(t)}function Dt(){let e=g(e=>e.bots),[t]=(0,L.useState)(()=>new xt({fetch:(e,t)=>b.logs({...e,before:t}),errorText:w})),n=(0,L.useSyncExternalStore)(t.subscribe,()=>t.state);return(0,L.useEffect)(()=>(t.start(),()=>t.stop()),[t]),(0,R.jsx)(Ot,{...n,botons:e.map(e=>({id:e.id,name:e.name})),onFilters:e=>t.setFilters(e),onRefresh:()=>t.refresh(),onOlder:()=>t.older()})}function Ot(e){let{data:t,entries:n,error:r,filters:i}=e,a=i.level!==`info`||!!i.q.trim()||!!i.boton,o=new Map(e.botons.map(e=>[e.id,e.name]));return(0,R.jsxs)(`section`,{className:`server-info server-log`,"aria-labelledby":`server-log-title`,children:[(0,R.jsx)(`h4`,{id:`server-log-title`,children:`Server events`}),(0,R.jsx)(`p`,{className:`hint`,children:`Known events from what the server logged recently, for spotting what failed and what to do about it. This is a selection, not the log: each event shows a fixed name and only checked details (ids, kinds, counts, times, an error kind). Error text and anything else a line holds are left out, and events the server doesn’t know read as unclassified. The full log, with more detail, is in the server’s journal or container log. Kept in memory only, so a restart starts it again.`}),(0,R.jsxs)(`div`,{className:`server-log-filters`,role:`search`,children:[(0,R.jsxs)(`label`,{children:[(0,R.jsx)(`span`,{className:`field-label`,children:`Show`}),(0,R.jsxs)(`select`,{value:i.level,onChange:t=>e.onFilters({...i,level:t.target.value}),children:[(0,R.jsx)(`option`,{value:`error`,children:`Errors`}),(0,R.jsx)(`option`,{value:`warn`,children:`Warnings and errors`}),(0,R.jsx)(`option`,{value:`info`,children:`Everything`})]})]}),(0,R.jsxs)(`label`,{children:[(0,R.jsx)(`span`,{className:`field-label`,children:`Boton`}),(0,R.jsxs)(`select`,{value:i.boton,onChange:t=>e.onFilters({...i,boton:t.target.value}),children:[(0,R.jsx)(`option`,{value:``,children:`Any or none`}),e.botons.map(e=>(0,R.jsx)(`option`,{value:e.id,children:e.name},e.id))]})]}),(0,R.jsxs)(`label`,{className:`server-log-search`,children:[(0,R.jsx)(`span`,{className:`field-label`,children:`Search`}),(0,R.jsx)(`input`,{type:`search`,value:i.q,maxLength:100,placeholder:`e.g. OAuth, push, run_…`,onChange:t=>e.onFilters({...i,q:t.target.value})})]}),(0,R.jsx)(`button`,{type:`button`,className:`btn small ghost`,onClick:e.onRefresh,disabled:e.loading,children:e.loading?`Loading…`:`Refresh`})]}),r&&(0,R.jsxs)(`p`,{className:`load-error`,role:`alert`,children:[(0,R.jsxs)(`span`,{children:[`Couldn't `,t?`refresh`:`load`,` the server events: `,O(r,200),t&&e.loadedAt&&(0,R.jsxs)(R.Fragment,{children:[`. These events are from`,` `,(0,R.jsx)(`time`,{dateTime:e.loadedAt,title:C(e.loadedAt),children:D(e.loadedAt)})]}),`.`]}),(0,R.jsx)(`button`,{type:`button`,className:`link-btn`,onClick:e.onRefresh,children:`Retry`})]}),!t&&!r&&(0,R.jsx)(`p`,{className:`hint`,children:`Loading…`}),e.restartedAt&&(0,R.jsxs)(`p`,{className:`hint warn`,children:[`The server restarted`,` `,(0,R.jsx)(`time`,{dateTime:e.restartedAt,title:C(e.restartedAt),children:D(e.restartedAt)}),`: events from before then are only in its full log on the server.`]}),t&&(0,R.jsx)(kt,{data:t}),t&&!n.length&&(0,R.jsxs)(`p`,{className:`hint`,children:[a?`No kept event matches these filters.`:`Nothing logged yet since the server started.`,i.level!==`info`&&t.kept>0&&` Choose Everything to see the rest.`]}),n.length>0&&(0,R.jsx)(`ol`,{className:`server-log-list`,children:n.map(e=>(0,R.jsx)(At,{entry:e,names:o},e.seq))}),t?.more&&n.length>0&&(0,R.jsx)(`div`,{children:(0,R.jsx)(`button`,{type:`button`,className:`btn small ghost`,onClick:e.onOlder,disabled:e.loading,children:`Show older`})})]})}function kt({data:e}){let{counts:t}=e;return(0,R.jsxs)(`p`,{className:`hint`,children:[`Since the server started`,` `,(0,R.jsx)(`time`,{dateTime:e.since,title:C(e.since),children:D(e.since)}),`: `,t.error,` `,t.error===1?`error`:`errors`,`, `,t.warn,` `,t.warn===1?`warning`:`warnings`,` and `,t.info,` info `,t.info===1?`event`:`events`,` kept`,e.dropped>0&&(0,R.jsxs)(R.Fragment,{children:[`; `,e.dropped,` older `,e.dropped===1?`event was`:`events were`,` let go to keep within `,e.limits.entries,` events and `,Math.round(e.limits.bytes/1024),` KB`,e.oldestAt&&(0,R.jsxs)(R.Fragment,{children:[`, so the oldest kept is from`,` `,(0,R.jsx)(`time`,{dateTime:e.oldestAt,title:C(e.oldestAt),children:D(e.oldestAt)})]})]}),`.`,e.threshold!==`info`&&` BOTONS_LOG_LEVEL is ${e.threshold}, so less severe lines are not logged at all.`]})}function At({entry:e,names:t}){let[n,r]=(0,L.useState)(!1),i=wt(e),a=Object.entries(e.fields);return(0,R.jsxs)(`li`,{className:`server-log-line ${e.level}`,children:[(0,R.jsxs)(`div`,{className:`server-log-head`,children:[(0,R.jsx)(`span`,{className:`server-log-level ${e.level}`,children:St[e.level]}),(0,R.jsx)(`time`,{dateTime:e.ts,title:C(e.ts),children:new Date(e.ts).toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`,second:`2-digit`})}),(0,R.jsx)(`span`,{className:`server-log-msg`,children:e.label}),e.event!==`unclassified`&&(0,R.jsx)(`code`,{className:`server-log-event`,children:e.event})]}),a.length>0&&(0,R.jsx)(`dl`,{className:`server-log-fields`,children:a.map(([e,n])=>(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`dt`,{children:Tt[e]??e}),(0,R.jsxs)(`dd`,{children:[Et(e,n),(e===`botId`||e===`fromBotId`)&&typeof n==`string`&&t.has(n)&&(0,R.jsxs)(`span`,{className:`hint`,children:[` (`,t.get(n),`)`]})]})]},e))}),i&&(0,R.jsx)(`p`,{className:`server-log-step`,children:i}),(0,R.jsx)(`button`,{type:`button`,className:`link-btn`,onClick:()=>void j(JSON.stringify({ts:e.ts,level:e.level,event:e.event,label:e.label,...e.fields})).then(r),children:n?`Copied`:`Copy event`})]})}function jt(){let e=(0,L.useId)(),t=g(e=>e.settings?.inboxCleanupDays),[n,r]=(0,L.useState)(null),[i,a]=(0,L.useState)(null),[o,c]=(0,L.useState)(!1);(0,L.useEffect)(()=>{let e=!0;return b.storage().then(t=>e&&r(t),t=>e&&a(`Couldn't count the files: ${w(t)}`)),()=>{e=!1}},[]);let l=async(e,t)=>{c(!0);let n=await k(`save the inbox cleanup`,()=>s.updateSettings({inboxCleanupDays:e}));c(!1),n&&t!==void 0&&y({kind:`info`,text:ne(e),action:{label:`Undo`,run:()=>void l(t)}})},u=n&&te(n),d=n?.partial?`at least `:``;return(0,R.jsxs)(`div`,{className:`settings-parts storage-settings`,children:[(0,R.jsx)(`div`,{className:`set-card`,children:(0,R.jsx)(N,{label:`Clean up old inbox files`,htmlFor:`${e}-c`,help:`Untouched inbox files go to the trash. Files you keep stay.`,control:(0,R.jsxs)(`select`,{id:`${e}-c`,value:t===null?`off`:String(t??30),disabled:o||t===void 0,onChange:e=>void l(e.target.value===`off`?null:Number(e.target.value),t??null),children:[(0,R.jsx)(`option`,{value:`off`,children:`Off`}),ee.map(e=>(0,R.jsxs)(`option`,{value:e,children:[`After `,e,` days`]},e))]}),children:(0,R.jsx)(ce,{label:`Details`,children:(0,R.jsxs)(`p`,{className:`set-help`,children:[`It runs once a day. Workspaces, the shared folder and anything a Boton moved out of its inbox are never cleaned up. The trash keeps everything`,` `,n?.trashKeepDays??30,` days.`]})})})}),i&&(0,R.jsx)(`p`,{className:`form-error`,role:`alert`,children:i}),!n||!u?!i&&(0,R.jsx)(`p`,{className:`muted`,children:`Counting…`}):(0,R.jsxs)(`div`,{className:`set-card`,children:[(0,R.jsx)(N,{label:`All files`,help:u.big?(0,R.jsx)(`span`,{className:`warn`,children:`That's a lot: Clear inbox and Empty trash in a Boton's Files.`}):void 0,control:(0,R.jsx)(`span`,{className:`set-value`,children:d+M(u.total)})}),(0,R.jsx)(N,{label:`Shared folder`,control:(0,R.jsx)(`span`,{className:`set-value`,children:M(n.sharedBytes)})}),(0,R.jsx)(N,{label:`Trash`,control:(0,R.jsx)(`span`,{className:`set-value`,children:M(n.trashBytes)})}),n.bots.map(e=>(0,R.jsx)(N,{label:e.name,help:(0,R.jsxs)(`span`,{className:e.inboxBytes>re?`warn`:void 0,children:[`Inbox `,M(e.inboxBytes),` · Trash `,M(e.trashBytes)]}),control:(0,R.jsxs)(`span`,{className:`set-value`,children:[`Workspace `,M(e.workspaceBytes)]})},e.botId))]})]})}var Mt=[{title:`You`,pages:[{id:`you`,label:`Your name and timezone`},{id:`appearance`,label:`Appearance`},{id:`notifications`,label:`Notifications`}]},{title:`Botons`,pages:[{id:`brains`,label:`Brains`},{id:`api-keys`,label:`API keys`},{id:`connectors`,label:`Connectors`}]},{title:`This server`,pages:[{id:`access`,label:`Who can connect`},{id:`devices`,label:`Devices`},{id:`storage`,label:`Storage`},{id:`health`,label:`Health and backups`}]},{title:`More`,pages:[{id:`advanced`,label:`Advanced`}]}],Nt=Object.fromEntries(Mt.flatMap(e=>e.pages.map(e=>[e.id,e.label]))),Pt=`(min-width: 760px)`,Ft=e=>{let t=matchMedia(Pt);return t.addEventListener(`change`,e),()=>t.removeEventListener(`change`,e)};function It({onClose:e,onOpenBot:t}){let n=(0,L.useSyncExternalStore)(Ft,()=>matchMedia(Pt).matches,()=>!0),[r,i]=(0,L.useState)(null),a=r??(n?`you`:null),[o,c]=(0,L.useState)([]);a&&!o.includes(a)&&c([...o,a]);let l=(0,L.useRef)(null),[u,d]=(0,L.useState)(null),f=(0,L.useRef)(!1),m=()=>{(a?l.current?.querySelector(`[data-page="${a}"] .settings-page-title`):l.current?.querySelector(`.settings-nav [data-section="${u}"]`)??l.current?.querySelector(`.settings-nav [data-section]`))?.focus()};(0,L.useLayoutEffect)(()=>{f.current&&(f.current=!1,m())},[a,u]);let h=(0,L.useRef)(n);(0,L.useLayoutEffect)(()=>{if(h.current===n)return;h.current=n;let e=document.activeElement;e instanceof HTMLElement&&e!==document.body&&l.current?.closest(`dialog`)?.contains(e)&&!e.closest(`[hidden]`)||m()},[n]);let g=e=>{n||(f.current=!0),e||d(a),i(e)};return(0,L.useEffect)(()=>{s.loadSetup().catch(()=>{}),s.loadConnectors().catch(()=>{}),s.loadApiKeys().catch(()=>{})},[]),(0,R.jsx)(p,{title:`Settings`,onClose:e,className:`settings-dialog`,children:(0,R.jsxs)(`div`,{ref:l,className:E(`settings-shell`,a?`has-page`:`no-page`),children:[(n||!a)&&(0,R.jsx)(Lt,{current:n?a:null,onPick:g}),(0,R.jsxs)(`div`,{className:`settings-page`,hidden:!a,children:[a&&!n&&(0,R.jsxs)(`button`,{type:`button`,className:`link-btn settings-back`,onClick:()=>g(null),children:[(0,R.jsx)(_e,{size:14}),` All settings`]}),o.map(e=>(0,R.jsxs)(`div`,{className:`settings-page-body`,"data-page":e,hidden:e!==a,children:[(0,R.jsx)(`h3`,{className:`settings-page-title`,tabIndex:-1,children:Nt[e]}),(0,R.jsx)(fe.Provider,{value:e===a,children:(0,R.jsx)(Bt,{page:e,onPage:g,onOpenBot:t})})]},e))]})]})})}function Lt({current:e,onPick:t}){let n=Rt();return(0,R.jsxs)(`nav`,{className:`settings-nav`,"aria-label":`Settings sections`,children:[Mt.map(r=>(0,R.jsx)(se,{title:r.title,children:r.pages.map(r=>(0,R.jsxs)(`button`,{type:`button`,className:E(`set-row set-link`,e===r.id&&`on`),"data-section":r.id,"aria-current":e===r.id?`page`:void 0,onClick:()=>t(r.id),children:[(0,R.jsx)(`span`,{className:`set-label`,children:r.label}),(0,R.jsxs)(`span`,{className:`set-value`,children:[(0,R.jsx)(`span`,{className:`settings-summary`,children:n[r.id]}),` `,(0,R.jsx)(ve,{size:14})]})]},r.id))},r.title)),(0,R.jsx)(zt,{})]})}function Rt(){let e=g(e=>e.settings),t=g(e=>e.providers),n=g(e=>e.apiKeys),r=g(e=>e.connectors),i=g(e=>e.setup),a=t.filter(e=>e.readiness.state===`ready`&&e.id!==`mock`),o=n?.providers.filter(e=>e.source!==`none`).length,s=r?.connectors.filter(e=>!e.managed).length,c=i?.owner.devices;return{you:e?.ownerName||void 0,brains:t.length?a.length?`${a.map(e=>e.label.replace(/ \(.*\)$/,``)).join(`, `)} ready`:`None ready`:void 0,"api-keys":o===void 0?void 0:o?`${o} in use`:`None`,connectors:s===void 0?void 0:s?`${s} service${s===1?``:`s`}`:`None yet`,access:i?.modes.find(e=>e.mode===i.accessMode)?.label,devices:c===void 0?void 0:`${c} paired`,storage:e?ne(e.inboxCleanupDays):void 0}}function zt(){let e=g(e=>e.settings?.paused??!1),t=(e,n=!1)=>void k(e?`pause all Botons`:`resume all Botons`,async()=>{await s.pauseAll(e),n||y({kind:`info`,text:e?`All Botons paused`:`All Botons resumed`,action:{label:`Undo`,run:()=>t(!e,!0)}})});return(0,R.jsx)(`div`,{className:`set-card settings-pause`,children:(0,R.jsx)(N,{label:`Pause all Botons`,help:e?`Nothing runs until you resume, even after a restart.`:`Stops every running turn and holds all work.`,control:(0,R.jsx)(ie,{label:`Pause all Botons`,on:e,onChange:e=>t(e)})})})}function Bt({page:e,onPage:t,onOpenBot:n}){switch(e){case`you`:return(0,R.jsx)(Vt,{});case`appearance`:return(0,R.jsxs)($,{children:[(0,R.jsx)(`div`,{className:`set-card`,children:(0,R.jsx)(`div`,{className:`set-row`,children:(0,R.jsx)(d,{})})}),(0,R.jsx)(u,{}),(0,R.jsx)(bt,{})]});case`notifications`:return(0,R.jsx)(vt,{});case`brains`:return(0,R.jsx)(Ne,{onPage:t,onOpenBot:n});case`api-keys`:return(0,R.jsx)(Ae,{});case`connectors`:return(0,R.jsxs)($,{children:[(0,R.jsx)(xe,{}),(0,R.jsx)(ye,{}),(0,R.jsx)(Se,{}),(0,R.jsx)(be,{})]});case`access`:return(0,R.jsx)(pe,{});case`devices`:return(0,R.jsx)(Xe,{});case`storage`:return(0,R.jsx)(jt,{});case`health`:return(0,R.jsxs)($,{children:[(0,R.jsx)(mt,{}),(0,R.jsx)(ue,{}),(0,R.jsx)(Dt,{})]});case`advanced`:return(0,R.jsxs)($,{children:[(0,R.jsx)(Ht,{}),(0,R.jsx)(Ye,{}),(0,R.jsx)(me,{})]})}}function $({children:e}){return(0,R.jsx)(`div`,{className:`settings-parts`,children:e})}function Vt(){let e=g(e=>e.settings),t=(0,L.useId)(),n=(0,L.useMemo)(()=>T().map(e=>(0,R.jsx)(`option`,{value:e},e)),[]),r=(0,L.useMemo)(()=>oe(),[]),i=(e,t,n=!1)=>void k(`save that`,async()=>{let a=await r(async()=>{let n=m().settings?.[e]??``;return await s.updateSettings({[e]:t}),n});!n&&a!==t&&y({kind:`info`,text:e===`ownerName`?`Name saved`:`Timezone saved`,action:{label:`Undo`,run:()=>i(e,a,!0)}})}),a=le(e?.ownerName??``,e=>i(`ownerName`,e.trim()),e=>!!e.trim()),o=le(e?.timezone??``,e=>i(`timezone`,e.trim()),e=>x(e.trim())),c=!x(o.value.trim());return(0,R.jsxs)(`div`,{className:`set-card settings-form`,children:[(0,R.jsx)(N,{label:`Your name`,htmlFor:`${t}-n`,help:`Botons use it when they talk about you.`,children:(0,R.jsx)(`input`,{id:`${t}-n`,className:`set-wide`,maxLength:60,autoComplete:`name`,...a})}),(0,R.jsxs)(N,{label:`Timezone for new Botons`,htmlFor:`${t}-t`,help:c?(0,R.jsx)(`span`,{className:`field-error`,children:`Not a timezone; it goes back when you leave the field.`}):`Each Boton can have its own.`,children:[(0,R.jsx)(`input`,{id:`${t}-t`,className:`set-wide`,list:`${t}-zones`,autoComplete:`off`,spellCheck:!1,"aria-invalid":c,...o}),(0,R.jsx)(`datalist`,{id:`${t}-zones`,children:n})]})]})}function Ht(){let t=g(e=>e.info);return t?(0,R.jsxs)(`section`,{className:`server-info`,children:[(0,R.jsx)(`h4`,{children:`About`}),(0,R.jsx)(e,{height:30}),(0,R.jsxs)(`p`,{children:[`Botons `,t.version]}),(0,R.jsxs)(`p`,{className:`hint`,children:[`Build `,t.build?.slice(0,12)||`development`]}),(0,R.jsxs)(`p`,{children:[(0,R.jsx)(`a`,{href:`https://github.com/boundsj/botons/blob/main/LICENSE`,target:`_blank`,rel:`noreferrer`,children:`MIT license`}),` · `,(0,R.jsx)(`a`,{href:`https://github.com/boundsj/botons`,target:`_blank`,rel:`noreferrer`,children:`Project`})]}),(0,R.jsx)(`p`,{children:(0,R.jsx)(`a`,{href:`https://github.com/boundsj/botons/security/advisories`,target:`_blank`,rel:`noreferrer`,children:`Report a security issue`})}),(0,R.jsx)(`h4`,{children:`This server`}),(0,R.jsxs)(`p`,{className:`hint`,children:[t.hostname,` · v`,t.version]}),(0,R.jsx)(`ul`,{children:t.urls.map(e=>(0,R.jsx)(`li`,{children:(0,R.jsx)(`code`,{children:e})},e))})]}):null}export{It as SettingsDialog};
@@ -0,0 +1 @@
1
+ import{n as e}from"./rolldown-runtime-XKyaHtl9.js";import{i as t,t as n}from"./react-C1iE8mnY.js";import{E as r,F as i,I as a,L as o,M as s,N as c,P as l,T as u,_ as d,d as f,k as p,p as m,w as h,x as g,y as _}from"./index-Dhwn-rf0.js";import{E as v,L as y,b,h as x,i as S,p as C}from"./util-Cx5feSJu.js";import{u as w}from"./Icons-nytVAa9g.js";var T=t(),E=n(),D=(e,t=e[0].toUpperCase()+e.slice(1))=>({id:e,label:t}),O={"claude-cli":{name:`Claude Code`,levels:[D(`low`),D(`medium`),D(`high`),D(`xhigh`,`Extra high`),D(`max`)],via:`Claude Code’s --effort`,defaultText:`Claude Code’s default for the model`},"codex-cli":{name:`Codex`,levels:[D(`none`),D(`minimal`),D(`low`),D(`medium`),D(`high`),D(`xhigh`,`Extra high`),D(`max`),D(`ultra`)],via:`Codex’s model_reasoning_effort`,defaultText:`Codex’s default for the model`},"anthropic-api":{name:`The Anthropic API`,levels:[D(`low`),D(`medium`),D(`high`),D(`xhigh`,`Extra high`),D(`max`)],via:`the Anthropic API’s output_config.effort`,defaultText:`the model’s default`},"openai-api":{name:`The OpenAI API`,levels:[D(`none`),D(`minimal`),D(`low`),D(`medium`),D(`high`),D(`xhigh`,`Extra high`),D(`max`)],via:`the OpenAI API’s reasoning.effort`,defaultText:`the model’s default`},mock:{name:`The mock provider`,levels:[D(`low`),D(`medium`),D(`high`)],via:`the mock provider`,defaultText:`the mock’s default`}};[...new Set(Object.values(O).flatMap(e=>e.levels.map(e=>e.id)))];var k=e=>O[e]??null;function A(e,t,n){if(t===``)return null;let r=k(e);if(!r)return`${e} has no effort setting: leave it at the provider's default ("")`;if(!r.levels.some(e=>e.id===t))return`${r.name} has no effort level "${t}": use one of ${r.levels.map(e=>e.id).join(`, `)}, or "" for its default`;if(n&&!n.levels.includes(t)){let e=r.levels.filter(e=>n.levels.includes(e.id)).map(e=>e.id),i=n.label&&n.label!==n.model?`${n.label} (${n.model})`:n.model,a=n.defaultEffort?`, or "" for its default (${n.defaultEffort}, as the list says)`:`, or "" for its default`;return`${r.name}'s model list has no effort level "${t}" for ${i}: use one of ${e.join(`, `)}${a}`}return null}function j(e,t){let n=e?.models.find(e=>e.id===t.trim());return n?.efforts?.length?{model:n.id,label:n.label,levels:n.efforts,...n.defaultEffort?{defaultEffort:n.defaultEffort}:{}}:null}function M(e,t,n){return!n||!e||A(e.id,n,j(e,t))?``:n}function N(e){return(0,E.jsxs)(`div`,{className:`segmented-field`,children:[(0,E.jsx)(`span`,{className:`field-label`,children:e.label}),(0,E.jsx)(`div`,{className:`segmented`,role:`radiogroup`,"aria-label":e.label,children:e.options.map(t=>(0,E.jsx)(`button`,{type:`button`,role:`radio`,"aria-checked":t.value===e.value,className:S(t.value===e.value&&`on`),onClick:()=>e.onChange(t.value),children:t.label},String(t.value)))})]})}function P(e){let t=(0,T.useId)(),n=e.providers.find(t=>t.id===e.provider),r=n?.models.some(t=>t.id===e.model)??!1,i=M(n,e.model,e.effort);return(0,E.jsxs)(`div`,{className:`brain-picker`,children:[(0,E.jsx)(`label`,{htmlFor:`${t}-p`,children:(0,E.jsx)(`span`,{className:`field-label`,children:`Brain`})}),(0,E.jsxs)(`div`,{className:`brain-row`,children:[(0,E.jsx)(`select`,{id:`${t}-p`,value:e.provider,onChange:t=>{let n=e.providers.find(e=>e.id===t.target.value);e.onChange(t.target.value,n?.defaultModel??``)},children:e.providers.map(e=>(0,E.jsxs)(`option`,{value:e.id,children:[e.label,e.available?``:` (not set up)`]},e.id))}),(0,E.jsxs)(`select`,{"aria-label":`Model`,value:r?e.model:`__custom`,onChange:t=>e.onChange(e.provider,t.target.value===`__custom`?e.model||` `:t.target.value),children:[n?.models.map(e=>(0,E.jsx)(`option`,{value:e.id,children:e.label},e.id||`__default`)),(0,E.jsx)(`option`,{value:`__custom`,children:`Custom…`})]})]}),!r&&(0,E.jsx)(`input`,{"aria-label":`Model id`,placeholder:`Model id, e.g. claude-opus-5-5`,value:e.model.trim(),onChange:t=>e.onChange(e.provider,t.target.value)}),(0,E.jsx)(L,{provider:n,model:e.model,value:i,onChange:e.onEffort,note:e.effort&&i!==e.effort?`${F(n,e.effort)} isn’t a level ${I(n,e.model)} has, so it goes back to the default${e.droppedText?` ${e.droppedText}`:``}.`:void 0}),(0,E.jsx)(f,{providers:e.providers,provider:e.provider})]})}var F=(e,t)=>e?.effort.levels.find(e=>e.id===t)?.label??t,I=(e,t)=>{let n=j(e,t);return n?`${n.label} (in ${e?.label??`its`} model list)`:e?.label??`this brain`};function L(e){let t=(0,T.useId)(),n=e.provider?.effort,r=j(e.provider,e.model),i=n?.levels??[],a=r?i.filter(e=>r.levels.includes(e.id)):i,o=!i.length,s=e.value&&!a.some(t=>t.id===e.value),c=r?.defaultEffort?`${n?.defaultText??`its default`}: ${r.defaultEffort} in its model list`:n?.defaultText??`its default`,l=o?n?.reason??`This brain has no effort setting, so it runs at its own default.`:r?`Requested with ${n?.via??`the CLI`}; these are the levels its model list gives this model.`:`Requested with ${n?.via??`the CLI`}. Which of these this model takes isn’t known here: the CLI may run another level for one it lacks.`;return(0,E.jsxs)(`div`,{className:`effort-field`,children:[(0,E.jsx)(`label`,{htmlFor:t,children:(0,E.jsx)(`span`,{className:`field-label`,children:`Effort`})}),(0,E.jsxs)(`select`,{id:t,value:e.value,disabled:o&&!e.value,onChange:t=>e.onChange(t.target.value),"aria-describedby":`${t}-hint`,children:[(0,E.jsx)(`option`,{value:``,children:e.inherit===void 0?`Provider default (${c})`:`Boton's effort (${e.inherit})`}),a.map(e=>(0,E.jsx)(`option`,{value:e.id,children:e.label},e.id)),s&&(0,E.jsxs)(`option`,{value:e.value,children:[F(e.provider,e.value),` — not listed for this model`]})]}),(0,E.jsxs)(`span`,{className:`hint`,id:`${t}-hint`,children:[e.note&&(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(`strong`,{children:e.note}),` `]}),`How hard it thinks before answering; higher is slower and uses more tokens. `,l]})]})}function ee({value:e,onChange:t,problem:n}){return(0,E.jsxs)(`div`,{className:`access-picker`,role:`radiogroup`,"aria-label":`Computer access`,children:[(0,E.jsx)(`span`,{className:`field-label`,children:`Computer access`}),(0,E.jsxs)(`button`,{type:`button`,role:`radio`,"aria-checked":e===`workspace`,disabled:!!n,className:S(`access-option`,e===`workspace`&&`on`),onClick:()=>t(`workspace`),children:[(0,E.jsx)(`strong`,{children:`Workspace`}),(0,E.jsx)(`span`,{children:`Sandboxed: writes only inside its own folder and can't read your keys, logins or other Botons' data. It can read other files, and with Codex its commands can reach the network.`})]}),n&&(0,E.jsxs)(`div`,{className:`hint`,role:`status`,children:[n,(0,E.jsxs)(`details`,{children:[(0,E.jsx)(`summary`,{children:`Why?`}),(0,E.jsx)(`p`,{children:`Workspace protects keys, logins and other Botons’ data, and limits writes to its folder. Full computer runs as the server user. On a dedicated cloud VM, the VM itself is the main boundary.`})]})]}),(0,E.jsxs)(`button`,{type:`button`,role:`radio`,"aria-checked":e===`host`,className:S(`access-option`,e===`host`&&`on`),onClick:()=>t(`host`),children:[(0,E.jsx)(`strong`,{children:`Full computer`}),(0,E.jsx)(`span`,{children:`Runs commands as the server user.`})]})]})}function R({label:e,value:t,onChange:n}){let r=(0,T.useId)(),i=(0,T.useMemo)(()=>x().map(e=>(0,E.jsx)(`option`,{value:e},e)),[]),a=y(t);return(0,E.jsxs)(`label`,{children:[(0,E.jsx)(`span`,{className:`field-label`,children:e}),(0,E.jsx)(`input`,{value:t,onChange:e=>n(e.target.value),list:`${r}-zones`,placeholder:`e.g. America/New_York`,autoComplete:`off`,spellCheck:!1,"aria-invalid":!a,"aria-describedby":a?void 0:`${r}-error`}),(0,E.jsx)(`datalist`,{id:`${r}-zones`,children:i}),!a&&(0,E.jsx)(`span`,{id:`${r}-error`,className:`field-error`,children:t.trim()?`Unknown timezone “${t}”`:`Choose a timezone`})]})}var z={idle:`Idle`,working:`Thinking`,approval:`Needs you`,error:`Error`,asleep:`No brain`},B=[{id:`body`,label:`Body`,parts:[`shape`,`shell`,`glow`]},{id:`face`,label:`Face`,parts:[`eyes`,`brows`,`mouth`,`cheeks`]},{id:`parts`,label:`Parts`,parts:[`top`,`ears`,`visor`,`panel`]},{id:`scene`,label:`Pose`,parts:[`arms`,`backdrop`]}],V={shape:`Shape`,shell:`Shell`,glow:`Glow`,cheeks:`Cheeks`,eyes:`Eyes`,brows:`Brows`,mouth:`Mouth`,top:`Top`,ears:`Ears`,visor:`Visor`,panel:`Panel`,arms:`Arms`,backdrop:`Backdrop`};function H(e,t,n,r){let i=t.indexOf(n),a={ArrowRight:i+1,ArrowDown:i+1,ArrowLeft:i-1,ArrowUp:i-1,Home:0,End:t.length-1}[e.key];if(a===void 0)return;e.preventDefault();let o=t[(a+t.length)%t.length];r(o);let s=e.currentTarget.closest(`[role="radiogroup"], [role="tablist"]`);requestAnimationFrame(()=>(s?.querySelector(`[tabindex="0"]`))?.focus())}function U({part:e,look:t,onPick:n}){let r=i[e],a=l[e],o=t[e],c=r=>n({...t,[e]:r});return(0,E.jsxs)(`div`,{className:`part-picker`,role:`radiogroup`,"aria-label":V[e],children:[(0,E.jsx)(`span`,{className:`field-label`,"aria-hidden":`true`,children:V[e]}),(0,E.jsx)(`div`,{className:`part-options`,children:r.map(n=>(0,E.jsxs)(`button`,{type:`button`,role:`radio`,"aria-checked":n===o,tabIndex:n===o?0:-1,className:S(`part-option`,n===o&&`on`),onClick:()=>c(n),onKeyDown:e=>H(e,r,o,c),children:[(0,E.jsx)(s,{look:{...t,[e]:n},size:44,still:!0}),(0,E.jsx)(`span`,{children:a[n]})]},n))})]})}function te({look:e,onPick:t}){let n=[!0,!1],r=n=>t({...e,cheeks:n});return(0,E.jsxs)(`div`,{className:`part-picker`,role:`radiogroup`,"aria-label":`Cheeks`,children:[(0,E.jsx)(`span`,{className:`field-label`,"aria-hidden":`true`,children:`Cheeks`}),(0,E.jsx)(`div`,{className:`part-options`,children:n.map(t=>(0,E.jsxs)(`button`,{type:`button`,role:`radio`,"aria-checked":e.cheeks===t,tabIndex:e.cheeks===t?0:-1,className:S(`part-option`,e.cheeks===t&&`on`),onClick:()=>r(t),onKeyDown:t=>H(t,n,e.cheeks,r),children:[(0,E.jsx)(s,{look:{...e,cheeks:t},size:44,still:!0}),(0,E.jsx)(`span`,{children:t?`Rosy`:`Plain`})]},String(t)))})]})}function ne({look:e,onChange:t,compact:n}){let r=(0,T.useId)(),[i,l]=(0,T.useState)(B[0].id),[u,d]=(0,T.useState)(`idle`),f=B.find(e=>e.id===i)??B[0],p=(0,T.useMemo)(()=>a(e),[e]);return(0,E.jsxs)(`div`,{className:S(`boton-builder`,n&&`compact`),children:[(0,E.jsxs)(`div`,{className:`boton-stage`,children:[(0,E.jsx)(s,{look:e,size:n?96:128,state:u,seed:`preview`,label:`Preview, ${z[u].toLowerCase()}: ${p}`}),(0,E.jsx)(`div`,{className:`stage-states`,role:`radiogroup`,"aria-label":`Preview a state`,children:c.map(e=>(0,E.jsx)(`button`,{type:`button`,role:`radio`,"aria-checked":e===u,tabIndex:e===u?0:-1,className:S(`chip`,e===u&&`on`),onClick:()=>d(e),onKeyDown:e=>H(e,c,u,d),children:z[e]},e))}),(0,E.jsxs)(`button`,{type:`button`,className:`btn ghost small`,onClick:()=>t(o()),children:[(0,E.jsx)(w,{size:16}),` Surprise me`]})]}),(0,E.jsxs)(`div`,{className:`boton-parts`,children:[(0,E.jsx)(`div`,{className:`boton-tabs`,role:`tablist`,"aria-label":`Parts`,children:B.map(e=>(0,E.jsx)(`button`,{type:`button`,role:`tab`,id:`${r}-tab-${e.id}`,"aria-controls":`${r}-panel`,"aria-selected":e.id===f.id,tabIndex:e.id===f.id?0:-1,className:S(`boton-tab`,e.id===f.id&&`on`),onClick:()=>l(e.id),onKeyDown:e=>H(e,B.map(e=>e.id),f.id,l),children:e.label},e.id))}),(0,E.jsx)(`div`,{className:`boton-panel`,role:`tabpanel`,id:`${r}-panel`,"aria-labelledby":`${r}-tab-${f.id}`,children:f.parts.map(n=>n===`cheeks`?(0,E.jsx)(te,{look:e,onPick:t},n):(0,E.jsx)(U,{part:n,look:e,onPick:t},n))})]})]})}function W(e){let t=/^(?:codex-cli |Claude Code )?(\d+)\.(\d+)\.(\d+)(?: \(Claude Code\))?$/.exec(e?.trim()??``);if(!t)return null;let n=t.slice(1).map(Number);return n.every(Number.isSafeInteger)?n:null}function G(e,t){let n=W(e),r=W(t);if(!n||!r)return!1;for(let e=0;e<3;e++)if(n[e]!==r[e])return r[e]>n[e];return!1}function K(e){return W(e)?.join(`.`)??null}var q=e=>e===`codex-cli`?`Codex`:`Claude Code`;function J(e,t){let n=K(e);return t&&n?`${t.managed?`Botons' own copy`:`System copy`} ${n}`:null}function Y(e,t){let n=K(t);return`Botons now uses its own ${q(e)}${n?` ${n}`:``}.`}function X(e,t,n){let r=K(n?.terminal??null),i=K(t);if(!n?.managed||!r||!i||!G(r,i))return null;let a=e===`codex-cli`?`codex`:`claude`;return`Your terminal’s ${a} is still ${r}. Run ${a} update there if you want it updated too.`}var Z=(0,T.createContext)(!0);function Q(){let e=(0,T.useContext)(Z),t=(0,T.useRef)(e);return t.current=e,t}function re({provider:e}){let[t,n]=(0,T.useState)(null),[r,i]=(0,T.useState)(!1),[a,o]=(0,T.useState)(!1),[s,c]=(0,T.useState)(``),[l,u]=(0,T.useState)(!1),f=_(t=>t.providers.find(t=>t.id===e)),p=Q();(0,T.useEffect)(()=>{i(!1),c(``),u(!1)},[e]),(0,T.useEffect)(()=>{let e=!0,t=()=>{d.loadProviders().catch(()=>{}),v.brainClis().then(t=>{e&&n(t)}).catch(()=>{e&&c(`Could not check CLI installation options. Reopen Brains to try again.`)})};t();let r=setInterval(()=>p.current&&t(),5e3);return()=>{e=!1,clearInterval(r)}},[]);let m=t?.checks[e],h=t?.installs?.find(t=>t.provider===e),g=f?.readiness.problem===`incompatible-version`,y=f?.readiness.state===`ready`,b=f?.readiness.state===`not-installed`,x=G(f?.version??null,m?.latest??null),S=X(e,f?.version??null,f?.copy),C=G(t?.botonsVersion??null,t?.checks.botons?.latest??null),w=async()=>{if(h?.available){o(!0),c(``),u(!1);try{await v.installBrainCli(e,h.command),await d.loadProviders(!0),n(await v.brainClis()),i(!1),u(!0)}catch(e){c(e.message),i(!1);try{n(await v.brainClis())}catch{}}finally{o(!1)}}};return(0,E.jsxs)(`div`,{className:`cli-actions`,children:[(g||x)&&(0,E.jsxs)(`p`,{role:`status`,className:`hint warn`,children:[(0,E.jsx)(`strong`,{children:g?`Needs update`:`Update available`}),m?.latest?` · Latest available: ${m.latest}`:``,m?.failed&&` · Last known release; the latest check failed.`]}),l&&(0,E.jsxs)(`p`,{role:`status`,children:[`✓ `,Y(e,f?.version??null),!y&&` Sign in below if needed.`]}),l&&S&&(0,E.jsx)(`p`,{className:`hint`,children:S}),h?.available&&(g||x||b)&&!r&&(0,E.jsx)(`button`,{type:`button`,className:`btn small`,disabled:a||t?.installing,onClick:()=>i(!0),children:f?.installed?`Update now`:`Install CLI`}),r&&(g||x||b)&&h?.available&&(0,E.jsxs)(`div`,{className:`setup-field`,children:[(0,E.jsxs)(`p`,{children:[`Install the official `,e===`codex-cli`?`Codex`:`Claude Code`,` package for this server user, then use that copy in Botons? No sudo is needed. Any system copy stays in place.`]}),(0,E.jsx)(`p`,{children:`Exact command:`}),(0,E.jsx)(`code`,{children:h.command}),(0,E.jsx)(`p`,{children:`Running turns finish first. Queued work waits, then resumes. Sign-in stays with the provider. Updates never run automatically.`}),(0,E.jsxs)(`div`,{className:`form-actions`,children:[(0,E.jsx)(`button`,{type:`button`,className:`btn`,disabled:a,onClick:()=>i(!1),children:`Cancel`}),(0,E.jsx)(`button`,{type:`button`,className:`btn primary`,disabled:a||t?.installing,onClick:()=>void w(),children:`Install and use this copy`})]})]}),a&&(0,E.jsx)(`p`,{role:`status`,children:`Waiting for running turns, then installing… Keep this page open.`}),h&&!h.available&&(g||x||b)&&(0,E.jsx)(`p`,{className:`hint`,children:h.reason}),s&&(0,E.jsx)(`p`,{role:`alert`,className:`form-error`,children:s}),C&&(0,E.jsxs)(`p`,{role:`status`,className:`hint`,children:[`A newer Botons release is available (`,t?.checks.botons?.latest,`). For installer installs, run `,(0,E.jsx)(`code`,{children:`botons upgrade`}),` with the release source. Botons never upgrades itself automatically.`]})]})}function ie({onSeparate:e,open:t,onOpen:n}){let[r,i]=(0,T.useState)({codex:!1,claude:!1}),[a,o]=(0,T.useState)(!1),[s,c]=(0,T.useState)(``),l={codex:_(e=>e.providers.find(e=>e.id===`codex-cli`)?.readiness.state===`ready`),claude:_(e=>e.providers.find(e=>e.id===`claude-cli`)?.readiness.state===`ready`)},u=Q();(0,T.useEffect)(()=>{let e=!0,t=()=>void v.brainHomes().then(t=>{e&&i(t)}).catch(()=>{});t();let n=setInterval(()=>u.current&&t(),5e3);return()=>{e=!1,clearInterval(n)}},[]);let f=async()=>{o(!0);try{await v.importCodexSignIn(),i(await v.brainHomes()),c(`Botons now has its own Codex sign-in.`),n(!1),await d.loadProviders(!0)}catch{c(`Could not copy the sign-in. Try signing in separately.`)}finally{o(!1)}};if((0,T.useEffect)(()=>{if(!s)return;let e=setTimeout(()=>c(``),8e3);return()=>clearTimeout(e)},[s]),!r.codex&&!r.claude&&!s)return null;let p=!t&&(r.codex?l.codex:l.claude);return(0,E.jsxs)(`section`,{"aria-label":`Botons sign-in`,children:[r.codex&&!p&&(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(`p`,{children:`Botons can use your existing Codex sign-in, or one just for Botons.`}),(0,E.jsxs)(`div`,{className:`form-actions`,children:[(0,E.jsx)(`button`,{type:`button`,className:`btn`,disabled:a,onClick:()=>void f(),children:`Use existing sign-in`}),(0,E.jsx)(`button`,{type:`button`,className:`btn`,disabled:a,onClick:()=>{n(!1),e()},children:`Sign in just for Botons`})]}),(0,E.jsx)(`p`,{className:`hint`,children:`Copies only the Codex sign-in file, never your instructions, skills or settings.`})]}),!r.codex&&r.claude&&!p&&(0,E.jsxs)(`p`,{className:`hint`,children:[`To sign in to Claude just for Botons, run `,(0,E.jsx)(`code`,{children:`botons login claude`}),` on the server.`]}),s&&(0,E.jsx)(`p`,{role:`status`,children:s})]})}function ae({text:e}){let[t,n]=(0,T.useState)(!1);return(0,E.jsx)(`button`,{type:`button`,className:`btn small`,onClick:()=>void navigator.clipboard.writeText(e).then(()=>n(!0)).catch(()=>n(!1)),children:t?`Copied`:`Copy`})}function oe({onChoose:e}){let t=_(e=>e.providers),n=_(e=>e.bots),i=_(e=>e.settings?.paused??!1),a=_(e=>e.apiKeys),o=_(e=>e.setup?.exposure.container??!1),[s,c]=(0,T.useState)(null),[l,f]=(0,T.useState)(`codex-cli`),[p,y]=(0,T.useState)(`anthropic`),[b,x]=(0,T.useState)(``),[S,C]=(0,T.useState)(``),[w,D]=(0,T.useState)(``),[O,k]=(0,T.useState)(!1),[A,j]=(0,T.useState)(``),[M,N]=(0,T.useState)(!1),[P,F]=(0,T.useState)(!1),[I,L]=(0,T.useState)(null),ee=Q(),R=(0,T.useRef)(I?.state);(0,T.useEffect)(()=>{r(R.current,I?.state)&&(F(!1),N(!1)),R.current=I?.state},[I?.state]);let z=t.find(e=>e.id===l),B=a?.providers.find(e=>e.provider===p);(0,T.useEffect)(()=>{let e=!0,t=async()=>{try{await d.loadProviders(!0);let t=await v.brainLogin();e&&L(t)}catch{}};d.loadApiKeys().catch(()=>{}),t();let n=setInterval(()=>ee.current&&void t(),5e3);return()=>{e=!1,clearInterval(n)}},[]);let V=async e=>{k(!0),j(``);try{await e()}catch(e){j(e.message)}finally{k(!1)}},H=u(l,o)??``,U=(t=S)=>e({provider:s===`api`?`${p}-api`:l,model:s===`api`?t:``,cap:w?Number(w):null});return(0,E.jsxs)(`div`,{className:`brains`,children:[(0,E.jsx)(`h2`,{children:`Give your Botons their brains`}),(0,E.jsx)(`p`,{children:`A Boton is the body; the brain is the AI model it thinks with: your Claude or ChatGPT plan, an API key, or the Claude Code and Codex CLIs. You need just one brain, and you can add others later in Settings → Brains.`}),(0,E.jsxs)(`div`,{className:`setup-field`,"aria-label":`Brain options`,children:[(0,E.jsxs)(`button`,{type:`button`,className:`access-option ${s===`subscription`?`on`:``}`,"aria-pressed":s===`subscription`,onClick:()=>{c(`subscription`),e({provider:l,model:``,cap:null})},children:[(0,E.jsx)(`strong`,{children:`Use my Claude or ChatGPT subscription`}),(0,E.jsx)(`span`,{children:`Sign in to your account. Your plan’s limits apply.`})]}),(0,E.jsxs)(`button`,{type:`button`,className:`access-option ${s===`api`?`on`:``}`,"aria-pressed":s===`api`,onClick:()=>{c(`api`),e({provider:`${p}-api`,model:S,cap:w?Number(w):null})},children:[(0,E.jsx)(`strong`,{children:`Use an API key`}),(0,E.jsx)(`span`,{children:`Pay per use, with an optional daily spending cap.`})]})]}),s===`subscription`&&(0,E.jsxs)(`div`,{className:`setup-field`,children:[(0,E.jsxs)(`label`,{children:[`Subscription`,(0,E.jsxs)(`select`,{value:l,onChange:t=>{f(t.target.value),e({provider:t.target.value,model:``,cap:null})},children:[(0,E.jsx)(`option`,{value:`codex-cli`,children:`ChatGPT`}),(0,E.jsx)(`option`,{value:`claude-cli`,children:`Claude`})]})]}),(0,E.jsx)(re,{provider:l}),z&&!M&&!P&&I?.state!==`waiting`&&(z.readiness.state===`ready`||z.readiness.state===`usage-limited`)?(0,E.jsxs)(`p`,{role:`status`,children:[z.readiness.state===`ready`?`✓ Signed in`:h(z,{container:o}).why,` · `,(0,E.jsx)(`button`,{type:`button`,className:`link-btn`,"aria-expanded":`false`,onClick:()=>F(!0),children:`Change sign-in`})]}):(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(`p`,{className:`hint`,children:`Approve sign-in with the provider. Botons never sees your password; the provider’s app keeps the credential on this server.`}),l===`codex-cli`&&(0,E.jsx)(`button`,{type:`button`,className:`btn primary`,disabled:O||I?.state===`waiting`,onClick:()=>void V(async()=>{L(await v.startBrainLogin()),U()}),children:`Sign in to ChatGPT`}),l===`codex-cli`&&I?.state===`waiting`&&(0,E.jsxs)(`div`,{role:`status`,children:[(0,E.jsx)(`p`,{children:`Waiting for you to approve sign-in…`}),I.url&&I.code&&(0,E.jsxs)(E.Fragment,{children:[(0,E.jsxs)(`p`,{children:[(0,E.jsx)(`code`,{children:I.code}),` `,(0,E.jsx)(ae,{text:I.code})]}),(0,E.jsx)(`a`,{href:I.url,target:`_blank`,rel:`noreferrer`,children:`Open sign-in page`})]}),(0,E.jsx)(`button`,{type:`button`,className:`btn small`,onClick:()=>void V(async()=>L(await v.cancelBrainLogin())),children:`Cancel sign-in`})]}),(0,E.jsx)(`p`,{children:`In a terminal on this server (e.g. your ssh session) run:`}),(0,E.jsxs)(`p`,{children:[(0,E.jsx)(`code`,{children:H}),` `,(0,E.jsx)(ae,{text:H})]}),l===`claude-cli`&&(0,E.jsx)(`p`,{className:`hint`,children:`Claude’s sign-in needs an interactive terminal. Follow its link and paste its approval code there. Leave this page open; it checks automatically.`}),I&&[`fallback`,`timed-out`].includes(I.state)&&(0,E.jsx)(`p`,{role:`status`,children:`Browser sign-in could not finish. Try again or use the terminal command above.`})]})]}),s===`subscription`&&(0,E.jsx)(ie,{open:P,onOpen:F,onSeparate:()=>{N(!0),c(`subscription`),f(`codex-cli`),V(async()=>{L(await v.startBrainLogin())})}}),s===`api`&&(0,E.jsxs)(`div`,{className:`setup-field`,children:[(0,E.jsxs)(`label`,{children:[`Provider`,(0,E.jsxs)(`select`,{value:p,onChange:t=>{y(t.target.value),x(``),C(``),e({provider:`${t.target.value}-api`,model:``,cap:w?Number(w):null})},children:[(0,E.jsx)(`option`,{value:`anthropic`,children:`Anthropic (Claude)`}),(0,E.jsx)(`option`,{value:`openai`,children:`OpenAI`})]})]}),(0,E.jsxs)(`label`,{children:[`API key`,(0,E.jsx)(`input`,{type:`password`,value:b,onChange:e=>x(e.target.value),autoComplete:`off`,spellCheck:!1,"data-1p-ignore":!0})]}),(0,E.jsx)(`p`,{className:`hint`,children:`Saved keys are never shown again. They are stored in the server database and included in backups.`}),(0,E.jsxs)(`div`,{className:`form-actions`,children:[(0,E.jsx)(`button`,{type:`button`,className:`btn primary`,disabled:O||!b.trim()||!B,onClick:()=>void V(async()=>{let e=await v.saveApiKey(p,b,B.generation);x(``),d.apiKeyChanged(e),await d.loadProviders(!0)}),children:`Save key`}),(0,E.jsx)(`button`,{type:`button`,className:`btn`,disabled:O||!B||B.source===`none`,onClick:()=>void V(async()=>{await v.testApiKey(p),await d.loadApiKeys()}),children:`Test key`})]}),B?.source!==`none`&&B&&(0,E.jsxs)(`p`,{role:`status`,children:[`✓ Key saved`,B.test?` · ${B.test.outcome===`ok`?`The key works`:`Test: `+B.test.outcome}`:``]}),(0,E.jsxs)(`label`,{children:[`Model`,(0,E.jsx)(`input`,{list:`brain-models`,value:S,placeholder:`Provider’s model ID`,onChange:e=>{C(e.target.value),U(e.target.value)}})]}),(0,E.jsx)(`datalist`,{id:`brain-models`,children:B?.test?.models.map(e=>(0,E.jsx)(`option`,{value:e.id,children:e.displayName??e.id},e.id))}),(0,E.jsxs)(`label`,{children:[`Daily cap (USD, optional)`,(0,E.jsx)(`input`,{type:`number`,min:`0`,max:`100000`,step:`0.01`,value:w,onChange:t=>{D(t.target.value),e({provider:`${p}-api`,model:S,cap:t.target.value?Number(t.target.value):null})}})]}),(0,E.jsx)(`p`,{className:`hint`,children:B?.testDescription})]}),(0,E.jsxs)(`details`,{children:[(0,E.jsx)(`summary`,{children:`Details`}),(0,E.jsx)(`ul`,{className:`brain-list`,children:t.map(e=>{let t=h(e,{container:o}),r=e.lastError&&(!e.lastSuccessAt||e.lastError.at>e.lastSuccessAt)?e.lastError:null,a=J(e.version,e.copy),s=X(e.id,e.version,e.copy);return(0,E.jsxs)(`li`,{"data-brain":e.id,"data-state":e.readiness.state,children:[(0,E.jsxs)(`p`,{children:[(0,E.jsx)(`strong`,{children:e.label}),`: `,t.status,a&&` · ${a}`]}),s&&(0,E.jsx)(`p`,{className:`hint`,children:s}),(0,E.jsx)(`p`,{children:t.why&&m(t.why)}),t.fix&&(0,E.jsxs)(`p`,{children:[m(t.fix.text),` `,t.fix.command&&(0,E.jsx)(`code`,{children:t.fix.command})]}),(0,E.jsx)(`p`,{children:t.work}),r&&(0,E.jsxs)(`p`,{className:`hint warn`,children:[`Last turn failed: `,r.message]}),(0,E.jsx)(`p`,{children:g(e.id,n,i).text}),e.reason&&(0,E.jsx)(`p`,{children:e.reason})]},e.id)})})]}),A&&(0,E.jsx)(`p`,{className:`form-error`,role:`alert`,children:A})]})}var se=e({ACTIONS:()=>he,AccessModePicker:()=>ce,AccessSettings:()=>me,AuditLog:()=>ge,SetupWarnings:()=>ue,SetupWizard:()=>pe,detailText:()=>$});function ce({setup:e,value:t,onChange:n}){let r=(0,T.useId)(),i=e.modes.find(e=>e.mode===t)??null,a=r=>{let i=e.modes.find(e=>e.mode===r);return(0,E.jsxs)(`button`,{type:`button`,role:`radio`,"aria-checked":t===r,disabled:!i.available,className:S(`access-option`,t===r&&`on`,!i.available&&`unavailable`),onClick:()=>n(r),children:[(0,E.jsxs)(`strong`,{children:[i.label,i.available&&r===e.recommendedMode&&(0,E.jsx)(`span`,{className:`device-tag`,children:`recommended`})]}),(0,E.jsx)(`span`,{children:i.available?i.summary:i.reason})]},r)};return(0,E.jsxs)(`div`,{className:`access-picker`,role:`radiogroup`,"aria-labelledby":`${r}-label`,onKeyDown:e=>{if(![`ArrowDown`,`ArrowRight`,`ArrowUp`,`ArrowLeft`].includes(e.key))return;let t=Array.from(e.currentTarget.querySelectorAll(`[role="radio"]`)).filter(e=>!e.disabled&&e.getClientRects().length),n=t.indexOf(document.activeElement);if(n<0||!t.length)return;e.preventDefault();let r=t[(n+(e.key===`ArrowDown`||e.key===`ArrowRight`?1:-1)+t.length)%t.length];r.focus(),r.click()},children:[(0,E.jsx)(`span`,{className:`field-label`,id:`${r}-label`,children:`Who can connect`}),(0,E.jsx)(`span`,{className:`hint`,children:e.recommendedMode===`public`?`This connection appears forwarded or public; HTTPS access is recommended.`:e.recommendedMode===`tailscale`?`This connection uses a Tailscale address.`:e.recommendedMode===`lan`?`This connection uses a private network address.`:e.recommendedMode===`local`?`This connection appears local to the server.`:`This connection appears forwarded or public; set up HTTPS to continue.`}),[`public`,`tailscale`].filter(n=>n!==`tailscale`||e.exposure.tailscale||e.recommendedMode===`tailscale`||t===`tailscale`).map(a),(0,E.jsxs)(`details`,{open:t===`local`||t===`lan`||void 0,children:[(0,E.jsx)(`summary`,{children:`More options`}),[`lan`,`local`].map(a)]}),(0,E.jsxs)(`details`,{className:`setup-steps`,children:[(0,E.jsx)(`summary`,{children:`Details`}),(0,E.jsxs)(`p`,{className:`hint`,children:[`Listeners: `,e.exposure.listeners.join(`, `),` · `,e.exposure.publicUrl]}),(0,E.jsx)(ue,{warnings:e.warnings,except:i?.limits}),!e.modes.find(e=>e.mode===`public`)?.available&&i?.mode!==`public`&&(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(`h5`,{children:`Set up HTTPS access`}),(0,E.jsx)(`ol`,{children:e.modes.find(e=>e.mode===`public`)?.steps.map(e=>(0,E.jsx)(`li`,{children:(0,E.jsx)(le,{text:e})},e))})]}),i&&(0,E.jsxs)(E.Fragment,{children:[(0,E.jsx)(`ul`,{children:i.limits.map(e=>(0,E.jsx)(`li`,{children:e},e))}),(0,E.jsx)(`ol`,{children:i.steps.map(e=>(0,E.jsx)(`li`,{children:(0,E.jsx)(le,{text:e})},e))})]})]})]})}function le({text:e}){return(0,E.jsx)(E.Fragment,{children:e.split(/(`[^`]+`)/).map((e,t)=>e.startsWith("`")&&e.endsWith("`")?(0,E.jsx)(`code`,{children:e.slice(1,-1)},t):e)})}function ue({warnings:e,except:t=[]}){let n=e.filter(e=>!t.includes(e.text));return n.length?(0,E.jsx)(`ul`,{className:`setup-warnings`,children:n.map(e=>(0,E.jsx)(`li`,{className:S(`setup-warning`,e.level),children:(0,E.jsx)(le,{text:e.text})},e.text))}):null}var de=[`owner`,`access`,`brains`,`boton`,`done`],fe={owner:`Welcome to Botons`,access:`Who can connect`,brains:`Bring the brains`,boton:`Your first Boton`,done:`Lights on`};function pe({onDone:e}){let t=_(e=>e.setup),n=_(e=>e.settings),r=_(e=>e.providers),i=_(e=>e.bots),[a,c]=(0,T.useState)(`owner`),[l,u]=(0,T.useState)(n?.ownerName??``),[f,m]=(0,T.useState)(t.modes.some(e=>e.mode===t.accessMode&&e.available)?t.accessMode:t.recommendedMode),[h,g]=(0,T.useState)(``),[y,x]=(0,T.useState)(()=>o()),w=r.filter(e=>e.readiness.state===`ready`),D=r.some(e=>e.readiness.state===`ready`),[O,k]=(0,T.useState)(``),[A,j]=(0,T.useState)(!1),[M,N]=(0,T.useState)(!1),P=r.find(e=>e.id===O)?.workspaceProblem,[F,I]=(0,T.useState)(`workspace`);(0,T.useEffect)(()=>{I(P?`host`:`workspace`)},[P,O]);let[L,R]=(0,T.useState)(``),[z,B]=(0,T.useState)(null),[V,H]=(0,T.useState)(null),[U,te]=(0,T.useState)(null),[W,G]=(0,T.useState)(!1),[K,q]=(0,T.useState)(null),J=de.indexOf(a),Y=V??i[0]??null;(0,T.useEffect)(()=>{a===`boton`&&M&&!A&&!w.some(e=>e.id===O)&&(k(w[0]?.id??``),R(``),B(null))},[a,O,r,M,A]);let X=async()=>{j(!0),q(null);try{await d.loadProviders(!0),N(!0)}catch(e){N(!1),q(b(e))}finally{j(!1)}};(0,T.useEffect)(()=>{N(!1),a===`boton`&&X()},[a]),(0,T.useEffect)(()=>{if(a!==`done`)return;let e=!0,t=()=>v.health().then(t=>e&&te(t),()=>{});t();let n=setInterval(t,5e3);return()=>{e=!1,clearInterval(n)}},[a]);let Z=async(e,t)=>{G(!0),q(null);try{await e(),c(t)}catch(e){q(b(e))}finally{G(!1)}},Q=async()=>{G(!0),q(null);try{await d.updateSetup({complete:!0}),e(Y?.id??null)}catch(e){q(b(e)),G(!1)}};return(0,E.jsx)(`main`,{className:`pair-screen setup-screen`,children:(0,E.jsxs)(`form`,{className:`pair-card setup-card`,onSubmit:e=>{if(e.preventDefault(),a===`owner`)Z(()=>d.updateSettings({ownerName:l.trim()}),`access`);else if(a===`access`)Z(()=>d.updateSetup({accessMode:f}),`brains`);else if(a===`brains`)c(`boton`);else if(a===`boton`){if(i.length&&!h.trim())return c(`done`);if(!w.some(e=>e.id===O)){q(`No brain is ready yet — go back to Brains.`);return}Z(async()=>{let e=await d.createBot({name:h.trim(),look:y,provider:O,model:L,dailyCapUsd:z,access:F});H(e),await d.updateSettings({primaryBotId:e.id})},`done`)}else Q()},children:[(0,E.jsx)(p,{size:48}),(0,E.jsxs)(`p`,{className:`hint`,"aria-live":`polite`,children:[`Step `,J+1,` of `,de.length]}),(0,E.jsx)(`h1`,{children:fe[a]}),a===`owner`&&(0,E.jsxs)(E.Fragment,{children:[(0,E.jsxs)(`p`,{className:`welcome-copy`,children:[`This device claimed the server`,t.owner.claimedAt?` ${C(t.owner.claimedAt)}`:``,`: it is yours now, and only devices you pair can use it.`]}),(0,E.jsxs)(`label`,{className:`setup-field`,children:[(0,E.jsx)(`span`,{className:`field-label`,children:`Your name`}),(0,E.jsx)(`input`,{value:l,onChange:e=>u(e.target.value),required:!0,maxLength:60,autoComplete:`name`,"data-autofocus":!0,autoFocus:!0}),(0,E.jsx)(`span`,{className:`hint`,children:`Botons use it when they talk about you.`})]})]}),a===`access`&&(0,E.jsx)(`div`,{className:`setup-field`,children:(0,E.jsx)(ce,{setup:t,value:f,onChange:m})}),a===`brains`&&(0,E.jsxs)(`div`,{className:`setup-field`,children:[(0,E.jsxs)(`div`,{className:`brains-stage`,children:[(0,E.jsx)(s,{look:y,size:88,state:D?`idle`:`asleep`,seed:`setup`}),(0,E.jsx)(`p`,{role:`status`,children:D?`Awake: a brain is ready, so your Botons can think.`:`No brain yet. Connect one below to wake your Botons up. You can go on, but they can't answer until one is ready.`})]}),(0,E.jsx)(oe,{onChoose:e=>{k(e.provider),R(e.model),B(e.cap)}})]}),a===`boton`&&(0,E.jsxs)(`div`,{className:`setup-field`,children:[i.length>0&&(0,E.jsxs)(`p`,{className:`hint`,children:[`You already have `,i.length===1?`a Boton`:`${i.length} Botons`,`. Add another, or leave the name empty to go on.`]}),(0,E.jsxs)(`label`,{children:[(0,E.jsx)(`span`,{className:`field-label`,children:`Name`}),(0,E.jsx)(`input`,{value:h,onChange:e=>g(e.target.value),required:!i.length,maxLength:60,placeholder:`e.g. Juniper`,autoFocus:!0})]}),(0,E.jsx)(ne,{look:y,onChange:x,compact:!0}),(0,E.jsxs)(`label`,{children:[(0,E.jsx)(`span`,{className:`field-label`,children:`Brain`}),(0,E.jsxs)(`select`,{value:O,onChange:e=>{k(e.target.value),R(``),B(null)},disabled:!w.length||A,children:[!w.length&&(0,E.jsx)(`option`,{value:``,children:`No brain is ready`}),w.map(e=>(0,E.jsx)(`option`,{value:e.id,children:e.label},e.id))]})]}),(0,E.jsx)(`button`,{type:`button`,className:`btn small`,disabled:A,onClick:()=>void X(),children:A?`Checking brains…`:`Check again`}),!w.length&&(0,E.jsxs)(`p`,{role:`status`,children:[`No brain is ready yet — `,(0,E.jsx)(`button`,{type:`button`,className:`btn ghost`,onClick:()=>c(`brains`),children:`go back to Brains`}),`.`]}),(0,E.jsx)(ee,{value:F,onChange:I,problem:P})]}),a===`done`&&(0,E.jsxs)(`div`,{className:`setup-field`,children:[(0,E.jsx)(`ul`,{className:`setup-providers`,children:[`database`,`scheduler`,`providers`].map(e=>{let t=U?!U.failing.includes(e):null;return(0,E.jsxs)(`li`,{children:[(0,E.jsx)(`span`,{className:S(`status-dot`,t===null?`skipped`:t?`succeeded`:`failed`),"aria-hidden":!0}),(0,E.jsxs)(`span`,{className:`device-text`,children:[(0,E.jsx)(`span`,{className:`device-name`,children:{database:`Database`,scheduler:`Scheduler`,providers:`A brain is ready`}[e]}),(0,E.jsx)(`span`,{className:`hint`,children:t===null?`Checking…`:t?`OK`:`Not yet`})]})]},e)})}),(0,E.jsxs)(`p`,{className:`hint`,children:[Y?`Say hello to ${Y.name}.`:`Create a Boton any time with New Boton.`,` Access, devices and the audit log are in Settings.`]})]}),K&&(0,E.jsx)(`p`,{className:`form-error`,role:`alert`,children:K}),(0,E.jsxs)(`div`,{className:`form-actions setup-actions`,children:[J>0&&a!==`done`&&(0,E.jsx)(`button`,{type:`button`,className:`btn ghost`,disabled:W,onClick:()=>c(de[J-1]),children:`Back`}),(0,E.jsx)(`button`,{type:`submit`,className:`btn primary`,disabled:W||a===`access`&&!t.modes.some(e=>e.mode===f&&e.available)||a===`boton`&&(!M||A||(!i.length||!!h.trim())&&!w.some(e=>e.id===O)),children:a===`done`?Y?`Open ${Y.name}`:`Finish`:a===`boton`&&(!i.length||h.trim())?`Create`:`Continue`})]}),a!==`done`&&(0,E.jsx)(`button`,{type:`button`,className:`link-btn hint`,disabled:W,onClick:()=>void Q(),children:`Skip setup`})]})})}function me(){let e=_(e=>e.setup),[t,n]=(0,T.useState)(e?.modes.some(t=>t.mode===e.accessMode&&t.available)?e.accessMode:e?.recommendedMode??null);(0,T.useEffect)(()=>{e&&n(t=>e.modes.some(e=>e.mode===t&&e.available)?t:e.recommendedMode)},[e]);let[r,i]=(0,T.useState)(null),[a,o]=(0,T.useState)(!1);if((0,T.useEffect)(()=>{d.loadSetup().catch(()=>{})},[]),!e)return null;let s=async()=>{o(!0),i(null);try{await d.updateSetup({accessMode:t})}catch(e){i(b(e))}finally{o(!1)}},{owner:c}=e;return(0,E.jsxs)(`section`,{className:`server-info access-settings`,children:[(0,E.jsx)(`h4`,{children:`Access`}),(0,E.jsxs)(`p`,{className:`hint`,children:[`Owner: `,c.name||`not named`,c.claimedAt?` · claimed ${C(c.claimedAt)}${c.claimedBy?` from ${c.claimedBy}`:``}`:``,` · `,c.devices,` paired device`,c.devices===1?``:`s`]}),(0,E.jsx)(ce,{setup:e,value:t,onChange:n}),r&&(0,E.jsx)(`p`,{className:`form-error`,role:`alert`,children:r}),(0,E.jsx)(`div`,{children:(0,E.jsx)(`button`,{type:`button`,className:`btn small`,disabled:a||t===e.accessMode||!e.modes.some(e=>e.mode===t&&e.available),onClick:()=>void s(),children:`Save access mode`})})]})}var he={"device.paired":`paired a device`,"device.revoked":`revoked a device`,"devices.revoked_all":`revoked every device`,"pairing_request.approved":`approved a device to pair`,"access_mode.changed":`changed the access mode`,"setup.completed":`finished setup`,"settings.notifications_changed":`changed notifications`,"notifications.tested":`sent a test push`,"backup.rehearsed":`rehearsed restoring a backup`,"boton.created":`created a Boton`,"boton.deleted":`deleted a Boton`,"boton.access_changed":`changed a Boton's computer access`,"boton.spending_changed":`changed a Boton's spending caps`,"boton.permissions_changed":`changed a Boton's connectors, skills or rules`,"routine.permissions_changed":`changed a routine's webhook access or callback`,"routine.starter_activated":`turned on a starter's routine`,"run.retried":`retried a failed routine run`,"run.cancelled":`cancelled a routine run`,"run.callback_resent":`resent a routine run's result to its callback URL`,"approval.decided":`decided an approval`,"card.answered":`answered a card in the chat`,"skill.approved":`approved a skill`,"skill.installed":`installed or updated a skill from its package`,"skill.restored":`put back an earlier text of a skill`,"skill.enabled":`turned a skill on`,"skill.disabled":`turned a skill off`,"connector.policy_changed":`changed who may ask for a connector`,"connector.granted":`got a connector it asked for (granted at once by policy)`,"connector.tested":`tested a connector`,"connector.disabled":`turned a connector off (its setup, sign-in and grants kept)`,"connector.enabled":`turned a connector back on`,"mcp_server.created":`added an MCP server`,"mcp_server.changed":`changed an MCP server`,"mcp_server.deleted":`removed an MCP server`,"oauth.created":`added a sign-in connector`,"oauth.deleted":`removed a sign-in connector`,"oauth.signin_started":`started a connector sign-in`,"oauth.connected":`connected a sign-in connector`,"brain.cli_install_started":`started an owner-confirmed CLI installation`,"brain.cli_install_succeeded":`installed and selected a managed CLI`,"brain.cli_install_failed":`could not finish a CLI installation`,"brain.signin_started":`started a brain sign-in`,"brain.signin_cancelled":`cancelled a brain sign-in`,"brain.signin_import_confirmed":`confirmed using an existing sign-in`,"brain.signin_import_completed":`copied a sign-in for Botons`,"brain.config_repair_confirmed":`confirmed a brain config repair`,"brain.config_repair_completed":`repaired a brain config link`,"brain.config_repair_failed":`could not repair a brain config link`,"api_key.saved":`saved an API key`,"api_key.removed":`removed a saved API key`,"api_key.tested":`tested an API key`,"browser.sites_changed":`changed the sites a Boton's browser may read`,"browser.stopped":`stopped a Boton's browser`,"browser.handover_started":`took a Boton's browser over (to sign it in)`,"browser.handover_ended":`gave a Boton's browser back`,"config.exported":`exported the configuration`,"config.imported":`imported a configuration`,"files.trashed":`moved a Boton's files to the trash`,"files.restored":`restored a file from the trash`,"files.purged":`deleted files from the trash for good`,"inbox.cleared":`cleared a Boton's inbox`,"inbox.cleaned_up":`moved a Boton's old inbox files to the trash`};function $(e){return Object.entries(e).filter(([,e])=>e!==null&&e!==``&&!(Array.isArray(e)&&!e.length)).map(([e,t])=>`${e}: ${Array.isArray(t)?t.join(`, `):String(t)}`).join(` · `)}function ge(){let[e,t]=(0,T.useState)(null),[n,r]=(0,T.useState)(!0),[i,a]=(0,T.useState)(null),o=async e=>{a(null);try{let n=await v.audit(e);t(t=>e&&t?[...t,...n]:n),r(n.length===50)}catch(e){a(b(e))}};return(0,T.useEffect)(()=>{o()},[]),(0,E.jsxs)(`section`,{className:`server-info audit-log`,children:[(0,E.jsx)(`h4`,{children:`Audit log`}),(0,E.jsx)(`p`,{className:`hint`,children:`Pairings, access changes, approvals and what Botons were given. It never records secrets or message text.`}),e===null&&!i&&(0,E.jsx)(`p`,{className:`hint`,children:`Loading…`}),e&&!e.length&&(0,E.jsx)(`p`,{className:`hint`,children:`Nothing yet.`}),e&&e.length>0&&(0,E.jsx)(`ul`,{className:`device-list audit-list`,children:e.map(e=>(0,E.jsx)(`li`,{children:(0,E.jsxs)(`span`,{className:`device-text`,children:[(0,E.jsxs)(`span`,{children:[(0,E.jsx)(`strong`,{children:e.actor.label}),` `,he[e.action]??e.action,e.target?`: ${e.target.label||e.target.id}`:``]}),(0,E.jsxs)(`span`,{className:`hint`,children:[(0,E.jsx)(`time`,{dateTime:e.at,title:new Date(e.at).toLocaleString(),children:C(e.at)}),$(e.details)&&` · ${$(e.details)}`]})]})},e.seq))}),e&&n&&(0,E.jsx)(`div`,{children:(0,E.jsx)(`button`,{type:`button`,className:`btn small ghost`,onClick:()=>void o(e.at(-1)?.seq),children:`Show older`})}),i&&(0,E.jsx)(`p`,{className:`form-error`,children:i})]})}export{$ as a,Z as c,ee as d,P as f,M as g,R as h,se as i,Q as l,N as m,me as n,ae as o,L as p,ge as r,re as s,he as t,ne as u};
@@ -0,0 +1 @@
1
+ import{i as e,t}from"./react-C1iE8mnY.js";import{E as n,b as r,i,p as a,v as o}from"./util-Cx5feSJu.js";import{_ as s,i as c,l,r as u,u as d,y as f}from"./index-Dhwn-rf0.js";import{C as p,a as m,f as h,j as g}from"./Icons-nytVAa9g.js";import{a as _,t as v}from"./Setup-C-3P7Mtn.js";var y=e(),b=t(),x={new:`not installed`,current:`installed, up to date`,update:`update available`,changed:`changed here since installing`,conflict:`name taken`,invalid:`can't be installed`},S=e=>e.status===409,C=e=>e.map(e=>e.name).join(`, `),w=2;function T({lines:e,label:t}){let n=e.some(e=>e.op!==`same`),r=[];for(let t=0;t<e.length;){if(e[t].op!==`same`){r.push(e[t++]);continue}let i=t;for(;i<e.length&&e[i].op===`same`;)i++;let a=t===0?0:w,o=i===e.length?0:w,s=i-t-a-o;n&&s>1?r.push(...e.slice(t,t+a),{op:`fold`,count:s},...e.slice(i-o,i)):r.push(...e.slice(t,i)),t=i}return(0,b.jsxs)(`figure`,{className:`skill-diff`,children:[(0,b.jsxs)(`figcaption`,{className:`field-label`,children:[t,` `,!n&&(0,b.jsx)(`span`,{className:`badge muted-badge`,children:`unchanged`})]}),(0,b.jsx)(`pre`,{"aria-label":`${t}: lines removed start with a minus, lines added with a plus`,children:r.map((e,t)=>e.op===`fold`?(0,b.jsx)(`span`,{className:`diff-fold`,children:` … ${e.count} unchanged line${e.count===1?``:`s`}\n`},t):(0,b.jsx)(`span`,{className:`diff-${e.op}`,children:`${e.op===`add`?`+`:e.op===`remove`?`−`:` `} ${e.text}\n`},t))})]})}function E(e){return e.botons.length?(0,b.jsx)(`ul`,{className:`connector-access-list`,children:e.botons.map(t=>{let n=e.draft.has(t.botId);return(0,b.jsx)(`li`,{children:(0,b.jsxs)(`label`,{className:`check connector`,children:[(0,b.jsx)(`input`,{type:`checkbox`,checked:n,disabled:e.busy,onChange:n=>e.onToggle(t.botId,n.target.checked)}),(0,b.jsxs)(`span`,{className:`connector-text`,children:[(0,b.jsxs)(`span`,{className:`connector-name`,children:[t.name,` `,t.has&&!n?(0,b.jsx)(`span`,{className:`badge warn-badge`,children:`loses it`}):!t.has&&n?(0,b.jsx)(`span`,{className:`badge`,children:`gets it`}):t.has?(0,b.jsx)(`span`,{className:`badge`,children:`has it`}):null]}),t.every&&(0,b.jsx)(`span`,{className:n?`hint`:`hint warn`,children:n?`Uses every skill, including new ones.`:`Uses every skill now: without ${e.skill} it gets a list of every other skill, and no new ones unless you give them.`})]})]})},t.botId)})}):(0,b.jsx)(`p`,{className:`empty-note`,children:`No Botons yet.`})}var D=e=>(t,n)=>e(e=>{let r=new Set(e);return n?r.add(t):r.delete(t),r}),O=e=>new Set(e.filter(e=>e.has).map(e=>e.botId));function k({onDone:e}){let[t,i]=(0,y.useState)(null),[a,o]=(0,y.useState)(null),[s,c]=(0,y.useState)(null),l=async()=>{o(null);try{i(await n.skillPackages())}catch(e){o(r(e))}};return(0,y.useEffect)(()=>void l(),[]),s?(0,b.jsx)(A,{folder:s,onBack:()=>(c(null),void l())}):(0,b.jsxs)(`section`,{className:`skill-form skill-packages`,"aria-labelledby":`skill-packages-title`,children:[(0,b.jsxs)(`div`,{className:`skill-head`,children:[(0,b.jsx)(`h3`,{id:`skill-packages-title`,className:`field-label`,children:`Install from a package`}),(0,b.jsxs)(`span`,{className:`skill-actions`,children:[(0,b.jsx)(`button`,{type:`button`,className:`btn ghost small`,onClick:()=>void l(),children:`Look again`}),(0,b.jsx)(`button`,{type:`button`,className:`btn ghost small`,onClick:e,children:`Close`})]})]}),(0,b.jsxs)(`p`,{className:`hint`,children:[`Put each package in its own folder on the server, holding a SKILL.md: `,(0,b.jsx)(`code`,{className:`path`,children:t?.dir??`…`}),`. Botons reads only SKILL.md's text: it never runs, unpacks or follows anything there, and installs nothing until you review exactly what changes and who gets it.`]}),a&&(0,b.jsx)(`p`,{className:`form-error`,children:a}),t&&!t.exists&&(0,b.jsx)(`p`,{className:`empty-note`,children:`That folder doesn't exist yet. Make it, put a package folder in it, then look again.`}),t?.exists&&t.packages.length===0&&(0,b.jsx)(`p`,{className:`empty-note`,children:`No packages there yet.`}),t&&t.packages.length>0&&(0,b.jsx)(`ul`,{className:`skill-list`,children:t.packages.map(e=>(0,b.jsxs)(`li`,{className:`skill-item`,children:[(0,b.jsxs)(`div`,{className:`skill-head`,children:[(0,b.jsx)(`code`,{className:`skill-name`,children:e.folder}),e.label&&(0,b.jsx)(`span`,{className:`badge muted-badge`,children:e.label}),(0,b.jsx)(`span`,{className:e.state===`invalid`||e.state===`conflict`?`badge warn-badge`:e.state===`current`?`badge muted-badge`:`badge`,children:x[e.state]}),(0,b.jsx)(`span`,{className:`skill-actions`,children:e.state!==`invalid`&&(0,b.jsx)(`button`,{type:`button`,className:`btn small`,onClick:()=>c(e.folder),"aria-label":`Review ${e.folder}`,children:`Review`})})]}),e.description&&(0,b.jsx)(`p`,{className:`skill-description`,children:e.description}),e.problems.map(e=>(0,b.jsx)(`p`,{className:`hint warn`,children:e},e))]},e.folder))})]})}function A({folder:e,onBack:t}){let[i,a]=(0,y.useState)(null),[o,c]=(0,y.useState)(new Set),[l,u]=(0,y.useState)(!1),[d,f]=(0,y.useState)(!1),[p,m]=(0,y.useState)(null),[h,g]=(0,y.useState)(!1),[_,v]=(0,y.useState)(null),w=async()=>{m(null),g(!1),u(!1);try{let t=await n.reviewSkillPackage(e);a(t),c(O(t.botons))}catch(e){m(r(e))}};if((0,y.useEffect)(()=>void w(),[e]),!i)return p?(0,b.jsx)(`p`,{className:`form-error`,children:p}):(0,b.jsx)(`p`,{className:`hint`,children:`Loading…`});let k=i.state===`new`||i.state===`update`||i.state===`changed`,A=async()=>{f(!0),m(null);try{let t=await n.installSkillPackage(e,{version:i.version,fingerprint:i.fingerprint,current:i.installed?.version??null,accept:i.newAsks,botIds:[...o],expected:[...O(i.botons)]});s.skillChanged(t),v(t)}catch(e){m(r(e)),S(e)&&g(!0)}finally{f(!1)}},j=i.botons.filter(e=>o.has(e.botId)),M=i.installed?`Update`:`Install`;return(0,b.jsxs)(`section`,{className:`skill-form skill-review`,"aria-labelledby":`skill-review-title`,children:[(0,b.jsxs)(`div`,{className:`skill-head`,children:[(0,b.jsxs)(`h3`,{id:`skill-review-title`,className:`field-label`,children:[M===`Update`?`Update`:`Install`,` `,(0,b.jsxs)(`code`,{className:`skill-name`,children:[`/`,e]})]}),i.label&&(0,b.jsx)(`span`,{className:`badge muted-badge`,children:i.label}),(0,b.jsx)(`span`,{className:`badge`,children:x[i.state]})]}),_?(0,b.jsxs)(b.Fragment,{children:[(0,b.jsxs)(`p`,{className:`hint`,role:`status`,children:[M===`Update`?`Updated`:`Installed`,` /`,_.name,`. `,j.length?`${C(j)} can use it from their next turn on.`:`No Boton has it yet.`,` Its earlier text is kept: you can put it back from the skill's details.`]}),(0,b.jsx)(`div`,{className:`form-actions`,children:(0,b.jsx)(`button`,{type:`button`,className:`btn primary small`,onClick:t,"data-autofocus":!0,children:`Back to packages`})})]}):(0,b.jsxs)(b.Fragment,{children:[i.problems.map(e=>(0,b.jsx)(`p`,{className:`hint warn`,children:e},e)),k&&i.installed?.source&&i.installed.source.installed!==i.installed.version&&(0,b.jsx)(`p`,{className:`hint warn`,children:`You changed this skill here since installing it. Installing the package again replaces your changes; they stay in its earlier texts.`}),i.installed?.disabled&&(0,b.jsx)(`p`,{className:`hint`,children:`The skill is turned off, and stays off.`}),(0,b.jsx)(T,{label:`Description`,lines:i.diff.description}),(0,b.jsx)(T,{label:`Procedure`,lines:i.diff.body}),i.diff.details.length>0&&(0,b.jsx)(T,{label:`Package details`,lines:i.diff.details}),(0,b.jsxs)(`div`,{children:[(0,b.jsx)(`span`,{className:`field-label`,children:`What it asks for or points at`}),i.asks.length===0?(0,b.jsx)(`p`,{className:`hint`,children:`Nothing beyond its text.`}):(0,b.jsx)(`ul`,{className:`skill-asks`,children:i.asks.map(e=>(0,b.jsxs)(`li`,{children:[e,` `,i.newAsks.includes(e)&&(0,b.jsx)(`span`,{className:`badge warn-badge`,children:i.installed?`new in this version`:`new`})]},e))}),(0,b.jsx)(`p`,{className:`hint`,children:`A skill grants nothing: a Boton can only do what its access and connectors let it. Fields like allowed-tools are shown, never applied.`})]}),i.otherFiles.length>0&&(0,b.jsxs)(`details`,{className:`connector-history`,children:[(0,b.jsxs)(`summary`,{children:[`Not installed: `,i.otherFiles.length,` other file`,i.otherFiles.length===1?``:`s`,` in the folder`]}),(0,b.jsx)(`p`,{className:`hint`,children:`Only SKILL.md's text is installed. A procedure that relies on these files won't find them.`}),(0,b.jsx)(`ul`,{className:`skill-asks`,children:i.otherFiles.map(e=>(0,b.jsx)(`li`,{children:(0,b.jsx)(`code`,{children:e})},e))})]}),k&&(0,b.jsxs)(`fieldset`,{className:`connections`,children:[(0,b.jsx)(`legend`,{className:`field-label`,children:`Who can use it`}),(0,b.jsx)(E,{botons:i.botons,draft:o,busy:d,skill:e,onToggle:D(c)})]}),k&&i.newAsks.length>0&&(0,b.jsxs)(`label`,{className:`check`,children:[(0,b.jsx)(`input`,{type:`checkbox`,checked:l,onChange:e=>u(e.target.checked),disabled:d}),(0,b.jsx)(`span`,{children:`I've read what's new in what it asks for or points at`})]}),p&&(0,b.jsx)(`p`,{className:`form-error`,role:`alert`,children:p}),(0,b.jsxs)(`div`,{className:`form-actions`,children:[(0,b.jsx)(`button`,{type:`button`,className:`btn ghost`,onClick:h?()=>void w():t,disabled:d,children:h?`Review again`:`Back`}),k&&!h&&(0,b.jsx)(`button`,{type:`button`,className:`btn primary`,disabled:d||i.newAsks.length>0&&!l,onClick:()=>void A(),children:d?`Saving…`:`${M} for ${j.length===1?`1 Boton`:`${j.length} Botons`}`})]})]})]})}function j({name:e,onDone:t}){let[i,o]=(0,y.useState)(null),[c,l]=(0,y.useState)(new Set),[u,d]=(0,y.useState)(!1),[f,p]=(0,y.useState)(null),[m,h]=(0,y.useState)(!1),[g,x]=(0,y.useState)(null),C=async()=>{p(null),h(!1);try{let t=await n.skillDetails(e);o(t),l(O(t.access.botons))}catch(e){p(r(e))}};if((0,y.useEffect)(()=>void C(),[e]),!i)return f?(0,b.jsx)(`p`,{className:`form-error`,children:f}):(0,b.jsx)(`p`,{className:`hint`,children:`Loading…`});let{skill:w,access:T,history:k,audit:A}=i,j=async(e,t)=>{d(!0),p(null),x(null);try{await e(),await C(),x(t)}catch(e){p(r(e)),S(e)&&h(!0)}finally{d(!1)}},M=O(T.botons),N=T.botons.filter(e=>!e.has&&c.has(e.botId)),P=T.botons.filter(e=>e.has&&!c.has(e.botId)),F=w.source;return(0,b.jsxs)(`div`,{className:`skill-form skill-manage`,children:[(0,b.jsx)(`p`,{className:`hint`,children:F?(0,b.jsxs)(b.Fragment,{children:[`Installed from the package `,(0,b.jsx)(`code`,{children:F.folder}),F.label&&` (${F.label})`,` `,a(F.installedAt),F.installed!==w.version&&`, and changed here since`,`.`]}):`Written here, not installed from a package.`}),(0,b.jsxs)(`label`,{className:`check`,children:[(0,b.jsx)(`input`,{type:`checkbox`,checked:!w.disabled,disabled:u,onChange:t=>{let r=t.target.checked;j(async()=>s.skillChanged(await n.setSkillEnabled(e,r)),r?`Turned on.`:`Turned off.`)}}),(0,b.jsx)(`span`,{children:`On: Botons it is given to see it and may follow it`})]}),w.disabled&&(0,b.jsxs)(`p`,{className:`hint warn`,children:[`Turned off: no Boton sees it, /`,e,` is plain text, and routines that follow it run without it.`]}),(0,b.jsxs)(`fieldset`,{className:`connections`,children:[(0,b.jsx)(`legend`,{className:`field-label`,children:`Who can use it`}),(0,b.jsx)(E,{botons:T.botons,draft:c,busy:u,skill:e,onToggle:D(l)}),(N.length>0||P.length>0)&&!m&&(0,b.jsxs)(`div`,{className:`form-actions`,children:[(0,b.jsx)(`button`,{type:`button`,className:`btn ghost small`,disabled:u,onClick:()=>l(M),children:`Undo`}),(0,b.jsx)(`button`,{type:`button`,className:`btn primary small`,disabled:u,onClick:()=>void j(()=>n.setSkillAccess(e,{botIds:[...c],expected:[...M]}),`Saved who can use it.`),children:u?`Saving…`:`Save who can use it`})]})]}),(0,b.jsxs)(`details`,{className:`connector-history`,children:[(0,b.jsxs)(`summary`,{children:[`Earlier texts (`,k.length,`)`]}),k.length===0?(0,b.jsx)(`p`,{className:`hint`,children:`None yet: when it is changed, updated or put back, the text it had is kept here.`}):(0,b.jsx)(`ul`,{className:`audit-list`,children:k.map(t=>(0,b.jsxs)(`li`,{children:[(0,b.jsxs)(`span`,{children:[t.source?`From ${t.source.folder}${t.source.label?` (${t.source.label})`:``}`:`Written here`,` · replaced `,a(t.replacedAt),t.version===w.version&&` · the same as now`]}),(0,b.jsxs)(`details`,{className:`skill-procedure`,children:[(0,b.jsx)(`summary`,{children:`Read it`}),(0,b.jsx)(`p`,{className:`skill-description`,children:t.description}),(0,b.jsx)(`pre`,{className:`file-text`,children:t.body})]}),t.version!==w.version&&(0,b.jsx)(`button`,{type:`button`,className:`btn ghost small`,disabled:u||m,onClick:()=>void j(async()=>s.skillChanged(await n.restoreSkill(e,t.id,w.version)),`Put back. The text it replaced is kept too.`),children:`Put this back`})]},t.id))})]}),(0,b.jsxs)(`details`,{className:`connector-history`,children:[(0,b.jsxs)(`summary`,{children:[`History (`,A.length,`)`]}),A.length===0?(0,b.jsx)(`p`,{className:`hint`,children:`Nothing recorded yet.`}):(0,b.jsx)(`ul`,{className:`audit-list`,children:A.map(e=>(0,b.jsxs)(`li`,{children:[(0,b.jsxs)(`span`,{children:[(0,b.jsx)(`strong`,{children:e.actor.label}),` `,v[e.action]??e.action,e.target?`: ${e.target.label||e.target.id}`:``]}),(0,b.jsxs)(`span`,{className:`hint`,children:[(0,b.jsx)(`time`,{dateTime:e.at,title:new Date(e.at).toLocaleString(),children:a(e.at)}),_(e.details)&&` · ${_(e.details)}`]})]},e.seq))})]}),g&&(0,b.jsx)(`p`,{className:`hint`,role:`status`,children:g}),f&&(0,b.jsx)(`p`,{className:`form-error`,role:`alert`,children:f}),(0,b.jsxs)(`div`,{className:`form-actions`,children:[m&&(0,b.jsx)(`button`,{type:`button`,className:`btn ghost small`,onClick:()=>void C(),children:`Look again`}),(0,b.jsx)(`button`,{type:`button`,className:`btn ghost small`,onClick:t,children:`Close`})]})]})}var M=(e,t)=>e.kind===`boton`?e.name:t;function N(e,t){let n=M(e.createdBy,t),r=M(e.updatedBy,t),i=e.updatedAt!==e.createdAt,o=e.createdBy.kind===`boton`?`Written by ${n} (a Boton)`:`Written by ${n}`;return i?`${o} · ${e.createdBy.kind===`boton`&&e.updatedBy.kind===`owner`?`approved by ${r}`:r===n&&e.updatedBy.kind===e.createdBy.kind?`edited`:`edited by ${r}`} ${a(e.updatedAt)}`:`${o} ${a(e.createdAt)}`}function P(e){let t=/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(e);if(!t)return null;let n=e=>RegExp(`^${e}:\\s*(.*)$`,`mi`).exec(t[1])?.[1].trim().replace(/^(["'])(.*)\1$/,`$2`)||void 0;return{name:n(`name`),description:n(`description`),body:t[2].trim()}}function F({onClose:e}){let{skills:t,error:n}=u(),r=f(e=>e.settings?.ownerName??`you`),[a,c]=(0,y.useState)(null),[_,v]=(0,y.useState)(null),[x,S]=(0,y.useState)(!1),[C,w]=(0,y.useState)(null);return(0,b.jsxs)(d,{title:`Skills`,onClose:e,wide:!0,children:[(0,b.jsxs)(`div`,{className:`skills-screen`,children:[(0,b.jsxs)(`p`,{className:`hint`,children:[`Procedures you write once and Botons follow: each Boton sees the name and description of the skills enabled for it (in its settings), reads one when it fits, follows it when you send `,(0,b.jsx)(`code`,{children:`/name`}),` in its chat, and every run of a routine with that skill follows it. Botons can save their own; only the Boton that wrote one can use it until you approve it (or edit it). Skills from a package install only after you review exactly what changes and who gets it.`]}),n&&(0,b.jsx)(`p`,{className:`form-error`,children:n}),a===`new`?(0,b.jsx)(I,{onDone:()=>c(null)}):x?(0,b.jsx)(k,{onDone:()=>S(!1)}):(0,b.jsxs)(`div`,{className:`skills-add`,children:[(0,b.jsxs)(`button`,{type:`button`,className:`btn small`,onClick:()=>c(`new`),"data-autofocus":!0,children:[(0,b.jsx)(p,{size:14}),` New skill`]}),(0,b.jsx)(`button`,{type:`button`,className:`btn ghost small`,onClick:()=>S(!0),children:`Install from a package`})]}),t?.length===0&&a!==`new`&&(0,b.jsx)(`p`,{className:`empty-note`,children:`No skills yet.`}),(0,b.jsx)(`ul`,{className:`skill-list`,children:t?.map(e=>a!==`new`&&a?.name===e.name?(0,b.jsx)(`li`,{children:(0,b.jsx)(I,{skill:e,onDone:()=>c(null)})},e.name):(0,b.jsxs)(`li`,{className:i(`skill-item`,e.updatedBy.kind===`boton`&&`by-boton`),children:[(0,b.jsxs)(`div`,{className:`skill-head`,children:[(0,b.jsxs)(`code`,{className:`skill-name`,children:[`/`,e.name]}),e.updatedBy.kind===`boton`&&(0,b.jsxs)(`span`,{className:`badge`,children:[`by `,e.updatedBy.name]}),e.pending&&(0,b.jsx)(`span`,{className:`badge warn-badge`,children:`waiting for approval`}),e.disabled&&(0,b.jsx)(`span`,{className:`badge muted-badge`,children:`off`}),e.source&&(0,b.jsxs)(`span`,{className:`badge muted-badge`,title:`Installed from the package ${e.source.folder}`,children:[`package`,e.source.label?` ${e.source.label}`:``,e.source.installed===e.version?``:`, changed here`]}),(0,b.jsxs)(`span`,{className:`skill-actions`,children:[e.pending&&(0,b.jsx)(`button`,{type:`button`,className:`icon-btn small`,"aria-label":`Approve ${e.name}`,title:`Approve: let every Boton it is enabled for use it`,onClick:()=>void o(`approve ${e.name}`,()=>s.approveSkill(e.name,e.version)),children:(0,b.jsx)(m,{size:14})}),(0,b.jsx)(`button`,{type:`button`,className:`btn ghost small`,"aria-expanded":C===e.name,"aria-label":`Details of ${e.name}: who can use it, on or off, earlier texts`,onClick:()=>w(C===e.name?null:e.name),children:`Details`}),(0,b.jsx)(`button`,{type:`button`,className:`icon-btn small`,"aria-label":`Edit ${e.name}`,title:`Edit`,onClick:()=>c(e),children:(0,b.jsx)(h,{size:14})}),(0,b.jsx)(`button`,{type:`button`,className:`icon-btn small`,"aria-label":`Delete ${e.name}`,title:`Delete`,onClick:()=>v(e),children:(0,b.jsx)(g,{size:14})})]})]}),(0,b.jsx)(`p`,{className:`skill-description`,children:e.description}),e.pending&&(0,b.jsxs)(`details`,{className:`skill-procedure`,children:[(0,b.jsx)(`summary`,{children:`Read the procedure`}),(0,b.jsx)(`pre`,{className:`file-text`,children:e.body})]}),C===e.name&&(0,b.jsx)(j,{name:e.name,onDone:()=>w(null)}),(0,b.jsxs)(`span`,{className:`hint`,children:[N(e,r),e.pending&&e.updatedBy.kind===`boton`&&` · only ${e.updatedBy.name} can use it until you approve it`]})]},e.name))})]}),_&&(0,b.jsx)(l,{title:`Delete the skill ${_.name}?`,confirmLabel:`Delete`,what:`delete ${_.name}`,danger:!0,onConfirm:()=>s.deleteSkill(_.name),onClose:()=>v(null),children:(0,b.jsxs)(`p`,{children:[`Botons stop seeing it from their next turn on, and /`,_.name,` becomes plain text. A skill that a routine follows can't be deleted; change the routine first.`]})})]})}function I({skill:e,onDone:t}){let[n,i]=(0,y.useState)(e?.name??``),[a,o]=(0,y.useState)(e?.description??``),[c,l]=(0,y.useState)(e?.body??``),[u,d]=(0,y.useState)(!1),[f,p]=(0,y.useState)(null),m=t=>{let r=P(t);if(!r)return l(t);!e&&r.name&&!n&&i(r.name),r.description&&!a&&o(r.description),l(r.body)},h=async()=>{d(!0),p(null);try{await s.saveSkill(e?.name??null,{name:n.trim(),description:a,body:c}),t()}catch(e){p(r(e))}finally{d(!1)}};return(0,b.jsxs)(`div`,{className:`skill-form`,children:[!e&&(0,b.jsxs)(`label`,{children:[(0,b.jsx)(`span`,{className:`field-label`,children:`Name`}),(0,b.jsx)(`input`,{value:n,onChange:e=>i(e.target.value),placeholder:`weekly-report`,maxLength:64,autoComplete:`off`,spellCheck:!1}),(0,b.jsxs)(`span`,{className:`hint`,children:[`Lowercase words joined by hyphens. You run it with /`,n.trim()||`name`,`.`]})]}),(0,b.jsxs)(`label`,{children:[(0,b.jsx)(`span`,{className:`field-label`,children:`Description`}),(0,b.jsx)(`textarea`,{value:a,onChange:e=>o(e.target.value),rows:2,maxLength:1024,placeholder:`What it does and when to use it, e.g. Write the weekly report for a repository.`})]}),(0,b.jsxs)(`label`,{children:[(0,b.jsx)(`span`,{className:`field-label`,children:`Procedure`}),(0,b.jsx)(`textarea`,{className:`skill-body`,value:c,onChange:e=>m(e.target.value),rows:12,placeholder:`The steps, in Markdown. Pasting a SKILL.md fills in the name and description too.`})]}),f&&(0,b.jsx)(`p`,{className:`form-error`,role:`alert`,children:f}),(0,b.jsxs)(`div`,{className:`form-actions`,children:[(0,b.jsx)(`button`,{type:`button`,className:`btn ghost`,onClick:t,disabled:u,children:`Cancel`}),(0,b.jsx)(`button`,{type:`button`,className:`btn primary`,disabled:u||!a.trim()||!c.trim()||!e&&!n.trim(),onClick:()=>void h(),children:u?`Saving…`:e?`Save`:`Add skill`})]})]})}function L({value:e,onChange:t}){let{skills:n,error:r}=u(),i=(0,y.useId)(),a=e??[],o=a.filter(e=>n&&!n.some(t=>t.name===e));return(0,b.jsxs)(`fieldset`,{className:`connections`,children:[(0,b.jsx)(`legend`,{className:`field-label`,children:`Skills`}),(0,b.jsx)(`p`,{className:`hint`,children:`Which skills from the library this Boton sees and may follow. Changing them keeps its conversation.`}),(0,b.jsxs)(`label`,{className:`check`,children:[(0,b.jsx)(`input`,{type:`radio`,name:`${i}-skills`,checked:e===null,onChange:()=>t(null)}),(0,b.jsx)(`span`,{children:`Every skill, including new ones`})]}),(0,b.jsxs)(`label`,{className:`check`,children:[(0,b.jsx)(`input`,{type:`radio`,name:`${i}-skills`,checked:e!==null,onChange:()=>t(e??[])}),(0,b.jsx)(`span`,{children:`Only these`})]}),e!==null&&(0,b.jsxs)(`div`,{className:`connections-group`,children:[n?.length===0&&(0,b.jsx)(`p`,{className:`empty-note`,children:`No skills in the library yet.`}),n?.map(e=>(0,b.jsxs)(`label`,{className:`check connector`,children:[(0,b.jsx)(`input`,{type:`checkbox`,checked:a.includes(e.name),onChange:n=>t(n.target.checked?[...a,e.name]:a.filter(t=>t!==e.name))}),(0,b.jsxs)(`span`,{className:`connector-text`,children:[(0,b.jsx)(`span`,{className:`connector-name`,children:e.name}),(0,b.jsx)(`span`,{className:`connector-target`,children:e.description}),c(e)&&(0,b.jsxs)(`span`,{className:`hint`,children:[`Waiting for your approval: until then only `,c(e).name,` can use it`]})]})]},e.name)),o.map(e=>(0,b.jsxs)(`label`,{className:`check connector`,children:[(0,b.jsx)(`input`,{type:`checkbox`,checked:!0,onChange:()=>t(a.filter(t=>t!==e))}),(0,b.jsxs)(`span`,{className:`connector-text`,children:[(0,b.jsx)(`span`,{className:`connector-name`,children:e}),(0,b.jsx)(`span`,{className:`hint`,children:`Not in the library any more`})]})]},e))]}),r&&(0,b.jsx)(`p`,{className:`form-error`,children:r})]})}export{F as SkillsDialog,L as SkillsField};
@@ -0,0 +1,5 @@
1
+ import{i as e,r as t,t as n}from"./react-C1iE8mnY.js";import{E as r,b as i,i as a,p as o,v as s}from"./util-Cx5feSJu.js";import{C as c,S as l,f as u,j as d,t as f}from"./Icons-nytVAa9g.js";import{_ as p,l as m,p as h,u as g,v as _,y as v}from"./index-Dhwn-rf0.js";import{a as y,m as b,t as x}from"./Setup-C-3P7Mtn.js";var S=e(),C=[{id:`github`,label:`GitHub`,name:`github`,url:`https://api.githubcopilot.com/mcp/`,hosts:[`api.githubcopilot.com`],secretLabel:`Personal access token`,secretPlaceholder:`github_pat_…`,help:{text:`Create a fine-grained token`,url:`https://github.com/settings/personal-access-tokens/new`},note:`Give it only the repositories and permissions Botons needs.`}];function w(e){try{return new URL(e).hostname.toLowerCase()}catch{return``}}function T(e){let t=w(e);return t&&C.find(e=>e.hosts.includes(t))||null}function E(e){let t=e.trim().replace(/^authorization\s*:\s*/i,``);return/^(bearer|token|basic)\s+\S/i.test(t)?t.replace(/^(\S+)\s+/,(e,t)=>`${t[0].toUpperCase()}${t.slice(1).toLowerCase()} `):(t=t.replace(/\s+/g,``),t?`Bearer ${t}`:``)}var D=`That looks like a token, not a header name. Paste your token into the API key field above, or use Authorization as the header name.`;function O(e){let t=e.trim();return t.length>64||/^(authorization\s*:|(bearer|token|basic)\s)/i.test(t)||/^npm_[A-Za-z0-9]{36}$/.test(t)||t.length>=16&&/^(gh[pousr]_|github_pat_|glpat-|xox[abposr]-|sk-|(sk|rk|pk)_(live|test)_|AKIA[0-9A-Z]|ASIA[0-9A-Z]|AIza|ya29\.|eyJ|hf_|shpat_|lin_api_|sntrys_)/.test(t)?!0:t.split(/[-_]/).some(e=>e.length>=24||e.length>=16&&/[A-Za-z]/.test(e)&&/\d/.test(e))}var k=/^[A-Za-z0-9!#$%&'*+.^_`|~-]{1,64}$/;function ee(e,t,n,r){let i={},a=(e,t)=>{let n=t=>t.toLowerCase()===e.toLowerCase();if(Object.keys(i).some(e=>n(e)&&i[e]!==null))throw Error(`A header is set twice. The key above is sent as Authorization.`);for(let t of r)t!==e&&n(t)&&(i[t]=null);i[e]=t};for(let e of n){let t=e.name.trim(),n=e.value.trim();if(t||n){if(O(t))throw Error(D);if(!k.test(t))throw Error(`A header name can have letters, digits and - only, with no spaces or ':'.`);if(e.remove)e.saved&&a(t,null);else{if(!n){if(e.saved)continue;throw Error(`Each new header needs a value.`)}if(/[\r\n]/.test(e.value.trim()))throw Error(`Header values must be one line.`);a(t,t.toLowerCase()===`authorization`?E(n):n)}}}let o=e.trim(),s=r.find(e=>e.toLowerCase()===`authorization`);return t?s&&a(s,null):o&&a(s??`Authorization`,E(o)),i}var A=t(),j=n(),M=e=>e.map(e=>e.name).join(`, `);function N(e,t){let n=e.botons.filter(e=>!e.has&&t.has(e.botId)),r=e.botons.filter(e=>e.has&&!t.has(e.botId));return!n.length&&!r.length?null:`Saving ${[n.length?`gives ${e.name} to ${M(n)}`:``,r.length?`takes it from ${M(r)}`:``].filter(Boolean).join(` and `)}, from their next turn on.`}function P({m:e,wanted:t}){return e.has&&!t?(0,j.jsx)(`span`,{className:`badge warn-badge`,children:`loses it when you save`}):!e.has&&t?(0,j.jsx)(`span`,{className:`badge`,children:`gets it when you save`}):e.has?(0,j.jsx)(`span`,{className:`badge`,children:`has it`}):e.brainCanUse?e.mayAsk?(0,j.jsx)(`span`,{className:`badge muted-badge`,children:`may ask for it`}):null:(0,j.jsx)(`span`,{className:`badge muted-badge`,children:`its brain can't use connectors`})}function F(e){let{access:t,draft:n}=e,r=t.botons.filter(e=>e.has),i=N(t,n);return(0,j.jsxs)(`div`,{className:`connector-access`,children:[!t.enabled&&(0,j.jsxs)(`p`,{className:`hint warn`,role:`note`,children:[(0,j.jsx)(f,{size:12,className:`warn-icon`}),` Turned off: the Botons checked here keep it, but no turn gets it and none may ask for it until you turn it on again.`]}),(0,j.jsxs)(`p`,{className:`hint`,role:`status`,children:[r.length?`${r.length===1?`1 Boton has`:`${r.length} Botons have`} it: ${M(r)}.`:`No Boton has it yet.`,` `,t.managed?`Give or take the browser in each Boton's settings, under Browser, where its sites are.`:t.enabled?t.onRequest?`Others may ask for it, and ${t.grant===`immediate`?`get it at once`:`you approve each request`}.`:`Only the Botons checked here get it; none can ask for it.`:`Changes you save here take effect once it is on.`]}),t.botons.length===0&&(0,j.jsx)(`p`,{className:`empty-note`,children:`No Botons yet.`}),(0,j.jsx)(`ul`,{className:`connector-access-list`,children:t.botons.map(r=>(0,j.jsx)(`li`,{children:t.managed?(0,j.jsx)(`span`,{className:`connector-text`,children:(0,j.jsxs)(`span`,{className:`connector-name`,children:[r.name,` `,(0,j.jsx)(P,{m:r,wanted:r.has})]})}):(0,j.jsxs)(`label`,{className:`check connector`,children:[(0,j.jsx)(`input`,{type:`checkbox`,checked:n.has(r.botId),disabled:e.busy||!r.brainCanUse&&!r.has,onChange:t=>e.onToggle(r.botId,t.target.checked)}),(0,j.jsxs)(`span`,{className:`connector-text`,children:[(0,j.jsxs)(`span`,{className:`connector-name`,children:[r.name,` `,(0,j.jsx)(P,{m:r,wanted:n.has(r.botId)})]}),r.withoutReason&&(0,j.jsxs)(`span`,{className:`hint warn`,children:[(0,j.jsx)(f,{size:12,className:`warn-icon`}),` Its last turn went without it: `,r.withoutReason,`.`]})]})]})},r.botId))}),i&&(0,j.jsx)(`p`,{className:`hint`,children:i}),e.error&&(0,j.jsx)(`p`,{className:`form-error`,role:`alert`,children:e.error}),!t.managed&&(i||e.stale)&&(0,j.jsxs)(`div`,{className:`form-actions`,children:[(0,j.jsx)(`button`,{type:`button`,className:`btn ghost small`,disabled:e.busy,onClick:e.onReset,children:e.stale?`Review again`:`Undo`}),!e.stale&&(0,j.jsx)(`button`,{type:`button`,className:`btn primary small`,disabled:e.busy,onClick:e.onSave,children:e.busy?`Saving…`:`Save who has it`})]}),(0,j.jsxs)(`details`,{className:`connector-history`,children:[(0,j.jsxs)(`summary`,{children:[`History (`,t.history.length,`)`]}),t.history.length===0?(0,j.jsx)(`p`,{className:`hint`,children:`Nothing recorded yet.`}):(0,j.jsx)(`ul`,{className:`audit-list`,children:t.history.map(e=>(0,j.jsxs)(`li`,{children:[(0,j.jsxs)(`span`,{children:[(0,j.jsx)(`strong`,{children:e.actor.label}),` `,x[e.action]??e.action,e.target?`: ${e.target.label||e.target.id}`:``]}),(0,j.jsxs)(`span`,{className:`hint`,children:[(0,j.jsx)(`time`,{dateTime:e.at,title:new Date(e.at).toLocaleString(),children:o(e.at)}),y(e.details)&&` · ${y(e.details)}`]})]},e.seq))})]})]})}var I=e=>new Set(e.botons.filter(e=>e.has).map(e=>e.botId));function L({id:e}){let t=v(t=>t.connectors?.connectors.find(t=>t.id===e)?.enabled),[n,a]=(0,S.useState)(null),[o,s]=(0,S.useState)(new Set),[c,l]=(0,S.useState)(!1),[u,d]=(0,S.useState)(null),[f,p]=(0,S.useState)(!1),m=e=>{a(e),s(I(e)),p(!1)},h=async()=>{d(null);try{m(await r.connectorAccess(e))}catch(e){d(i(e))}};if((0,S.useEffect)(()=>{a(null),h()},[e,t]),!n)return u?(0,j.jsx)(`p`,{className:`form-error`,children:u}):(0,j.jsx)(`p`,{className:`hint`,children:`Loading…`});let g=async()=>{l(!0),d(null);try{m(await r.setConnectorAccess(e,{botIds:[...o],expected:[...I(n)]}))}catch(e){d(i(e)),e.status===409&&p(!0)}finally{l(!1)}};return(0,j.jsx)(F,{access:n,draft:o,busy:c,error:u,stale:f,onToggle:(e,t)=>s(n=>{let r=new Set(n);return t?r.add(e):r.delete(e),r}),onSave:()=>void g(),onReset:()=>f?void h():s(I(n))})}function R({id:e}){let[t,n]=(0,S.useState)(!1);return(0,j.jsxs)(`details`,{className:`connector-botons`,onToggle:e=>n(e.target.open),children:[(0,j.jsx)(`summary`,{children:`Which Botons can use it`}),t&&(0,j.jsx)(L,{id:e})]})}var z=e=>e.length<=1?e.join(``):`${e.slice(0,-1).join(`, `)} and ${e.at(-1)}`;function B({id:e}){let t=v(t=>t.connectors?.connectors.find(t=>t.id===e)),[n,r]=(0,S.useState)(!1),[i,a]=(0,S.useState)(null);return(0,S.useEffect)(()=>{t||s(`load the connectors`,()=>p.loadConnectors(),a)},[t]),t?t.managed?null:(0,j.jsxs)(`span`,{className:t.enabled?`connector-power`:`connector-power off`,children:[t.enabled?(0,j.jsx)(`span`,{className:`hint`,children:`On: Botons that have it get it in their turns.`}):(0,j.jsxs)(`span`,{className:`hint warn`,children:[(0,j.jsx)(f,{size:12,className:`warn-icon`}),` Off since `,o(t.off?.at??``),t.off?.reason?` (${t.off.reason})`:``,`: no turn gets it and none may ask for it. Its setup`,t.source===`oauth`?`, sign-in`:``,` and grants are kept.`]}),(0,j.jsx)(`button`,{type:`button`,className:`btn small`,onClick:()=>r(!0),"aria-label":`${t.enabled?`Turn off`:`Turn on`} ${t.name}`,children:t.enabled?`Turn off…`:`Turn on…`}),n&&(0,j.jsx)(V,{id:e,name:t.name,oauth:t.source===`oauth`,onClose:()=>r(!1)})]}):i?(0,j.jsx)(`span`,{className:`form-error`,children:i}):null}function V(e){let[t,n]=(0,S.useState)(null),[a,o]=(0,S.useState)(``),[s,c]=(0,S.useState)(!1),[l,u]=(0,S.useState)(null),[d,m]=(0,S.useState)(!1),h=async()=>{u(null),m(!1);try{n(await r.connectorAccess(e.id))}catch(e){u(i(e))}};(0,S.useEffect)(()=>{h()},[e.id]);let _=t?.enabled??!0,v=t?.botons.filter(e=>e.has)??[],y=t?.running??[],b=async()=>{if(t){c(!0),u(null);try{await r.setConnectorEnabled(e.id,{enabled:!t.enabled,...t.enabled&&a.trim()?{reason:a.trim()}:{},expected:{enabled:t.enabled,botIds:v.map(e=>e.botId),running:y.map(e=>e.turnId)}}),await p.loadConnectors(),e.onClose()}catch(e){u(i(e)),e.status===409&&m(!0)}finally{c(!1)}}},x=v.length?z(v.map(e=>e.name)):null;return(0,A.createPortal)((0,j.jsxs)(g,{title:`${_?`Turn off`:`Turn on`} ${e.name}?`,onClose:()=>!s&&e.onClose(),children:[(0,j.jsxs)(`div`,{className:`confirm-body`,children:[!t&&!l&&(0,j.jsx)(`p`,{className:`hint`,children:`Loading…`}),t&&_&&(0,j.jsxs)(j.Fragment,{children:[(0,j.jsxs)(`p`,{children:[`Nothing is deleted: its setup`,e.oauth?`, its sign-in (Keep connected still refreshes it)`:``,` and who has it are kept, so turning it on again puts back exactly what it had.`]}),(0,j.jsxs)(`p`,{children:[x?`${x} ${v.length===1?`keeps`:`keep`} it, but ${v.length===1?`its`:`their`} turns go without it from the next one.`:`No Boton has it now.`,` `,`No Boton may ask for it, and requests waiting on a card can't be allowed`,e.oauth?`; its work feed is not read`:``,`.`]}),y.length>0&&(0,j.jsxs)(`p`,{className:`hint warn`,role:`note`,children:[(0,j.jsx)(f,{size:12,className:`warn-icon`}),` `,z(y.map(e=>e.name)),` `,y.length===1?`has a turn`:`have turns`,` running with it now. Turning it off doesn't reach into `,y.length===1?`that turn`:`those turns`,`: `,y.length===1?`it keeps`:`they keep`,` `,e.name,` until`,` `,y.length===1?`it ends`:`they end`,e.oauth?`, with the access token it was given (Botons can't take that back)`:``,`. To cut it sooner, stop `,y.length===1?`that turn`:`those turns`,` in the chat.`]}),(0,j.jsxs)(`label`,{className:`oauth-unknown`,children:[(0,j.jsx)(`span`,{className:`field-label`,children:`Why (optional, kept in its history)`}),(0,j.jsx)(`input`,{type:`text`,value:a,maxLength:200,onChange:e=>o(e.target.value),placeholder:`e.g. Rotating its key`})]})]}),t&&!_&&(0,j.jsxs)(j.Fragment,{children:[(0,j.jsx)(`p`,{children:x?`${x} ${v.length===1?`gets`:`get`} it back from ${v.length===1?`its`:`their`} next turn: exactly the Botons that had it, no one else.`:`No Boton has it, so no turn gets it until you give it to one.`}),(0,j.jsx)(`p`,{className:`hint`,children:t.onRequest?`Others may ask for it again (${t.grant===`immediate`?`granted at once`:`you approve each request`}). Requests made before it was turned off stay refused.`:`None may ask for it. Requests made before it was turned off stay refused.`})]})]}),l&&(0,j.jsx)(`p`,{className:`form-error`,role:`alert`,children:l}),(0,j.jsxs)(`div`,{className:`form-actions`,children:[(0,j.jsx)(`button`,{type:`button`,className:`btn ghost`,onClick:e.onClose,disabled:s,children:`Cancel`}),d?(0,j.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:()=>void h(),disabled:s,children:`Review again`}):(0,j.jsx)(`button`,{type:`button`,className:_?`btn danger-solid`:`btn primary`,onClick:()=>void b(),disabled:s||!t,children:s?`Saving…`:_?`Turn it off`:`Turn it on`})]})]}),document.body)}var H=(e,t)=>`${e} ${t}${e===1?``:`s`}`,te={library:`Your MCP servers (Settings)`,oauth:`Signed in with OAuth (Settings)`};function ne(e){let[t,n]=(0,S.useState)(null),[i,a]=(0,S.useState)(null);(0,S.useEffect)(()=>{let t=!0;return n(null),s(`load the notices about its connectors`,async()=>{let i=await r.connectorNotices(e.botId);t&&n(i)},a),()=>{t=!1}},[e.botId]);let c=t=>void s(`dismiss the notice`,async()=>{await r.dismissConnectorNotice(e.botId,t.connectorId),n(e=>e?.filter(e=>e.connectorId!==t.connectorId)??null)},a);if(i)return(0,j.jsx)(`p`,{className:`form-error`,children:i});let l=t?.filter(t=>!e.skip?.includes(t.connectorId));return l?.length?(0,j.jsxs)(`div`,{className:`connections-group connector-notices`,role:`status`,children:[(0,j.jsx)(`span`,{className:`connections-source`,children:`Needs your attention`}),l.map(t=>(0,j.jsxs)(`div`,{className:`connector-notice`,children:[(0,j.jsxs)(`span`,{className:`connector-text`,children:[(0,j.jsxs)(`span`,{className:`connector-name`,children:[(0,j.jsx)(f,{size:12,className:`warn-icon`}),` `,t.connectorId===`lib:botons_browser`?`Web browsing`:t.name,t.kind===`removed`?(0,j.jsx)(`span`,{className:`badge warn-badge`,children:`removed`}):(0,j.jsx)(`span`,{className:`badge warn-badge`,children:`left out`})]}),t.target&&(0,j.jsx)(`span`,{className:`connector-target`,title:t.target,children:t.target}),(0,j.jsx)(`span`,{className:`hint`,children:t.kind===`skipped`?`Its last turn (${o(t.at)}) went without it: ${t.reason}.`:h(t.reason)}),t.kind===`removed`&&!t.offer&&(0,j.jsx)(`span`,{className:`hint`,children:`No Botons connector has this name or address: add one in Settings → Connectors to give it back.`})]}),(0,j.jsxs)(`span`,{className:`connector-notice-actions`,children:[t.offer&&(e.value.includes(t.offer.id)?(0,j.jsxs)(`span`,{className:`hint`,children:[t.offer.name,` is on.`]}):(0,j.jsxs)(`button`,{type:`button`,className:`btn small`,onClick:()=>e.onChange([...e.value,t.offer.id]),title:t.offer.target,children:[`Use `,t.offer.name,` (`,t.offer.id.startsWith(`oauth:`)?`OAuth`:`your MCP server`,`)`]})),t.kind===`removed`&&(0,j.jsx)(`button`,{type:`button`,className:`btn small ghost`,onClick:()=>c(t),children:`Dismiss`})]})]},t.connectorId))]}):null}function U({id:e,label:t,testable:n,autoRun:r}){let a=v(t=>t.connectors?.connectors.find(t=>t.id===e)),[o,s]=(0,S.useState)(!1),[c,l]=(0,S.useState)(null),[u,d]=(0,S.useState)(null),f=async()=>{s(!0),l(null),d(null);try{let t=await p.testConnector(e);_().connectors?.connectors.find(t=>t.id===e)?.lastTest?.at!==t.at&&d(t.problem)}catch(e){l(i(e))}finally{s(!1)}},m=(0,S.useRef)(!1);(0,S.useEffect)(()=>{r&&n&&!m.current&&(m.current=!0,f())},[r,n]);let g=a?.lastTest??null;return(0,j.jsxs)(`div`,{className:`connector-test`,"aria-live":`polite`,children:[!n&&a&&(0,j.jsx)(`span`,{className:`hint`,children:h(a.basis)}),n&&(0,j.jsx)(`button`,{type:`button`,className:`btn small ghost`,disabled:o,onClick:()=>void f(),"aria-label":`Test ${t}`,children:o?`Testing…`:`Test`}),n&&g&&!o&&(0,j.jsx)(re,{test:g}),u&&!o&&(0,j.jsx)(`span`,{className:`hint`,children:u}),c&&(0,j.jsx)(`span`,{className:`form-error`,children:c})]})}function re({test:e}){return(0,j.jsxs)(`div`,{className:`connector-test-result`,children:[e.outcome===`reached`&&(0,j.jsxs)(`span`,{className:`hint`,children:[`Reached `,o(e.at),`: `,H(e.toolCount,`tool`),e.toolCount>e.tools.length?` (the first ${e.tools.length} below)`:``,`.`]}),e.outcome===`reached`&&e.problem&&(0,j.jsxs)(`span`,{className:`hint warn`,children:[(0,j.jsx)(f,{size:12,className:`warn-icon`}),` `,e.problem]}),e.outcome===`failed`&&(0,j.jsxs)(`span`,{className:`hint warn`,children:[(0,j.jsx)(f,{size:12,className:`warn-icon`}),` Test failed `,o(e.at),`: `,e.problem]}),e.outcome===`untestable`&&(0,j.jsxs)(`span`,{className:`hint`,children:[`Not tested.`,e.problem?` ${e.problem}`:``]}),(0,j.jsx)(`span`,{className:`hint`,children:h(e.scope)}),e.tools.length>0&&(0,j.jsxs)(`details`,{children:[(0,j.jsx)(`summary`,{children:`Its tools`}),(0,j.jsx)(`ul`,{className:`connector-tools`,children:e.tools.map((e,t)=>(0,j.jsx)(`li`,{children:(0,j.jsx)(`code`,{children:e})},`${t}-${e}`))})]})]})}function W(){let e=v(e=>e.mcpServers),[t,n]=(0,S.useState)(null),[r,i]=(0,S.useState)(null),[a,o]=(0,S.useState)(null);return(0,S.useEffect)(()=>{e||s(`load your MCP servers`,()=>p.loadMcpServers(),o)},[e]),(0,j.jsxs)(`section`,{className:`server-info mcp-library`,children:[(0,j.jsx)(`h4`,{children:`MCP servers`}),(0,j.jsx)(`p`,{className:`hint`,children:`Services added by a command or a URL. Give them to Botons in each Boton's settings.`}),(0,j.jsxs)(`details`,{className:`set-more`,children:[(0,j.jsx)(`summary`,{children:`Details`}),(0,j.jsx)(`p`,{className:`hint`,children:`Servers Botons can be given as connectors, in each Boton's settings: a command to start (stdio) or a URL (http). Environment variables and headers are kept on this computer and only passed to the agent; they are never shown again. Botons doesn't use the MCP servers in your own Claude Code or Codex settings: add a server here (or sign in to one below) for Botons to use it, with either brain.`})]}),a&&(0,j.jsx)(`p`,{className:`form-error`,children:a}),e?.length===0&&t===null&&(0,j.jsx)(`p`,{className:`empty-note`,children:`None yet.`}),(0,j.jsx)(`ul`,{className:`mcp-list`,children:e?.map(e=>t!==`new`&&t?.name===e.name?(0,j.jsx)(`li`,{children:(0,j.jsx)(J,{server:e,onDone:()=>n(null)})},e.name):(0,j.jsxs)(`li`,{className:`mcp-item`,children:[(0,j.jsxs)(`span`,{className:`connector-text`,children:[(0,j.jsx)(`span`,{className:`connector-name`,children:e.name}),(0,j.jsx)(`span`,{className:`connector-target`,children:e.transport===`stdio`?[e.command,...e.args].join(` `):e.url}),(e.envKeys.length>0||e.headerKeys.length>0)&&(0,j.jsxs)(`span`,{className:`hint`,children:[e.transport===`stdio`?`Environment`:`Headers`,`: `,(e.transport===`stdio`?e.envKeys:e.headerKeys).join(`, `)]}),(0,j.jsx)(B,{id:`lib:${e.name}`}),(0,j.jsx)(U,{id:`lib:${e.name}`,label:e.name,testable:e.transport===`http`}),(0,j.jsx)(R,{id:`lib:${e.name}`})]}),(0,j.jsx)(`button`,{type:`button`,className:`icon-btn small`,"aria-label":`Edit ${e.name}`,title:`Edit`,onClick:()=>n(e),children:(0,j.jsx)(u,{size:14})}),(0,j.jsx)(`button`,{type:`button`,className:`icon-btn small`,"aria-label":`Delete ${e.name}`,title:`Delete`,onClick:()=>i(e),children:(0,j.jsx)(d,{size:14})})]},e.name))}),t===`new`?(0,j.jsx)(J,{onDone:()=>n(null)}):(0,j.jsxs)(`button`,{type:`button`,className:`btn small`,onClick:()=>n(`new`),children:[(0,j.jsx)(c,{size:14}),` Add an MCP server`]}),r&&(0,j.jsx)(m,{title:`Delete ${r.name}?`,confirmLabel:`Delete`,what:`delete ${r.name}`,danger:!0,onConfirm:()=>p.deleteMcpServer(r.name),onClose:()=>i(null),children:(0,j.jsx)(`p`,{children:`Every Boton that has it loses it from its next turn on. Its saved environment variables or headers are deleted too.`})})]})}function G(e,t){let n={};for(let r of e.split(`
2
+ `)){let e=r.trim();if(!e)continue;let i=e.indexOf(t),a=(i<0?e:e.slice(0,i)).trim(),o=i<0?``:e.slice(i+1).trim();n[a]=o===``?null:o}return n}function K(e){let[t,n]=(0,S.useState)(!1);return(0,j.jsxs)(`span`,{className:`secret-input`,children:[(0,j.jsx)(`input`,{type:t?`text`:`password`,"aria-label":e.label,"aria-describedby":e.describedBy,value:e.value,onChange:t=>e.onChange(t.target.value),placeholder:e.placeholder,autoComplete:`off`,autoCapitalize:`off`,spellCheck:!1,"data-1p-ignore":!0,"data-lpignore":`true`}),(0,j.jsx)(`button`,{type:`button`,className:`btn small ghost`,"aria-pressed":t,"aria-label":`${t?`Hide`:`Show`} ${e.label}`,onClick:()=>n(!t),children:t?`Hide`:`Show`})]})}function q({rows:e,onChange:t}){let n=(n,r)=>t(e.map((e,t)=>t===n?{...e,...r}:e));return(0,j.jsxs)(`div`,{className:`header-rows`,children:[e.map((r,i)=>(0,j.jsxs)(`div`,{className:r.remove?`header-row removed`:`header-row`,children:[r.saved?(0,j.jsx)(`code`,{className:`header-name`,children:r.name}):(0,j.jsx)(`input`,{"aria-label":`Header name`,value:r.name,onChange:e=>n(i,{name:e.target.value}),placeholder:`X-Api-Key`,autoComplete:`off`,autoCapitalize:`off`,spellCheck:!1}),r.remove?(0,j.jsx)(`span`,{className:`hint`,children:`Removed when you save.`}):(0,j.jsx)(K,{label:r.saved?`New value for ${r.name}`:`Header value`,value:r.value,onChange:e=>n(i,{value:e}),placeholder:r.saved?`Saved (hidden)`:`Value`}),(0,j.jsx)(`button`,{type:`button`,className:`btn small ghost`,onClick:()=>r.saved?n(i,{remove:!r.remove,value:``}):t(e.filter((e,t)=>t!==i)),children:r.remove?`Keep`:`Remove`})]},i)),(0,j.jsxs)(`button`,{type:`button`,className:`btn small ghost`,onClick:()=>t([...e,{name:``,value:``,saved:!1,remove:!1}]),children:[(0,j.jsx)(c,{size:14}),` Add a header`]})]})}function J({server:e,onDone:t,fixed:n,preset:r,onSaved:a}){let[o,s]=(0,S.useState)(e?.name??r?.name??``),[c,l]=(0,S.useState)(e?.transport??n??`stdio`),[u,d]=(0,S.useState)(e?.command??``),[f,m]=(0,S.useState)((e?.args??[]).join(`
3
+ `)),[h,g]=(0,S.useState)(e?.url??r?.url??``),[_,v]=(0,S.useState)(``),y=e?.transport===`http`?e.headerKeys:[],x=y.find(e=>e.toLowerCase()===`authorization`),[C,w]=(0,S.useState)(``),[E,D]=(0,S.useState)(!1),[O,k]=(0,S.useState)(()=>y.filter(e=>e!==x).map(e=>({name:e,value:``,saved:!0,remove:!1}))),[A,M]=(0,S.useState)(!1),[N,P]=(0,S.useState)(null),F=r??T(h),I=(0,S.useId)(),L=async()=>{M(!0),P(null);try{let n=c===`stdio`?{transport:c,command:u.trim(),args:f.split(`
4
+ `).map(e=>e.trim()).filter(Boolean),env:G(_,`=`)}:{transport:c,url:h.trim(),headers:ee(C,E,O,y)};await p.saveMcpServer(e?.name??null,e?n:{...n,name:o.trim()}),w(``),a?a(e?.name??o.trim()):t()}catch(e){P(i(e))}finally{M(!1)}};return(0,j.jsxs)(`form`,{className:`mcp-form`,onSubmit:e=>{e.preventDefault(),L()},children:[!e&&(0,j.jsxs)(`label`,{children:[(0,j.jsx)(`span`,{className:`field-label`,children:`Name`}),(0,j.jsx)(`input`,{value:o,onChange:e=>s(e.target.value),placeholder:`github`,maxLength:40,autoComplete:`off`,autoCapitalize:`off`,spellCheck:!1}),(0,j.jsx)(`span`,{className:`hint`,children:`Lowercase letters, digits, - and _. Botons see its tools under this name.`})]}),!n&&(0,j.jsx)(b,{label:`Type`,options:[{value:`stdio`,label:`Command (stdio)`},{value:`http`,label:`URL (http)`}],value:c,onChange:l}),c===`stdio`?(0,j.jsxs)(j.Fragment,{children:[(0,j.jsxs)(`label`,{children:[(0,j.jsx)(`span`,{className:`field-label`,children:`Command`}),(0,j.jsx)(`input`,{value:u,onChange:e=>d(e.target.value),placeholder:`npx`,autoComplete:`off`,spellCheck:!1})]}),(0,j.jsxs)(`label`,{children:[(0,j.jsx)(`span`,{className:`field-label`,children:`Arguments, one per line`}),(0,j.jsx)(`textarea`,{value:f,onChange:e=>m(e.target.value),rows:3,placeholder:`-y
5
+ @modelcontextprotocol/server-github`,spellCheck:!1})]}),(0,j.jsxs)(`label`,{children:[(0,j.jsx)(`span`,{className:`field-label`,children:`Environment variables`}),(0,j.jsx)(`textarea`,{value:_,onChange:e=>v(e.target.value),rows:2,placeholder:`GITHUB_PERSONAL_ACCESS_TOKEN=ghp_…`,autoComplete:`off`,spellCheck:!1}),(0,j.jsxs)(`span`,{className:`hint`,children:[e?.envKeys.length?`Saved: ${e.envKeys.join(`, `)} (values hidden). `:``,`One per line, NAME=value. A line with only a name removes it; names you leave out keep their saved values.`]})]})]}):(0,j.jsxs)(j.Fragment,{children:[(0,j.jsxs)(`label`,{children:[(0,j.jsx)(`span`,{className:`field-label`,children:`URL`}),(0,j.jsx)(`input`,{value:h,onChange:e=>g(e.target.value),placeholder:`https://example.com/mcp`,inputMode:`url`,autoComplete:`off`,spellCheck:!1})]}),(0,j.jsxs)(`div`,{className:`secret-field`,children:[(0,j.jsx)(`span`,{className:`field-label`,children:F?.secretLabel??`API key or token`}),E?(0,j.jsx)(`span`,{className:`hint`,children:`Removed when you save.`}):(0,j.jsx)(K,{label:F?.secretLabel??`API key or token`,value:C,onChange:w,placeholder:x?`Saved (hidden). Type a new one to replace it.`:F?.secretPlaceholder??`Paste it here`,describedBy:I}),(0,j.jsxs)(`span`,{className:`hint`,id:I,children:[`Sent as `,(0,j.jsx)(`code`,{children:`Authorization: Bearer …`}),`. Kept on this computer, never shown again.`,F&&(0,j.jsxs)(j.Fragment,{children:[` `,F.note,` `,(0,j.jsx)(`a`,{href:F.help.url,target:`_blank`,rel:`noreferrer noopener`,children:F.help.text})]})]}),x&&(0,j.jsx)(`button`,{type:`button`,className:`btn small ghost secret-remove`,onClick:()=>{w(``),D(!E)},children:E?`Keep the saved key`:`Remove the saved key`})]}),(0,j.jsxs)(`details`,{className:`set-more`,open:O.some(e=>e.saved)||void 0,children:[(0,j.jsx)(`summary`,{children:`Advanced: other headers`}),(0,j.jsx)(`p`,{className:`hint`,children:`For a service that wants its key in another header, such as X-Api-Key. Values are hidden.`}),(0,j.jsx)(q,{rows:O,onChange:k})]})]}),N&&(0,j.jsx)(`p`,{className:`form-error`,role:`alert`,children:N}),(0,j.jsxs)(`div`,{className:`form-actions`,children:[(0,j.jsx)(`button`,{type:`button`,className:`btn ghost`,onClick:t,disabled:A,children:n?`Back`:`Cancel`}),(0,j.jsx)(`button`,{type:`submit`,className:`btn primary`,disabled:A||!e&&!o.trim(),children:A?`Saving…`:a&&c===`http`?`Save and test`:e?`Save`:`Add`})]})]})}var Y={new:`not signed in`,connected:`connected`,reconnect:`sign in again`};function ie(){let e=v(e=>e.oauth),[t,n]=(0,S.useState)(``),[r,a]=(0,S.useState)(``),[c,l]=(0,S.useState)(null),[u,f]=(0,S.useState)(null),[h,g]=(0,S.useState)(null);(0,S.useEffect)(()=>{e||s(`load your OAuth connectors`,()=>p.loadOAuth(),f)},[e]);let _=async e=>{l(e),f(null);try{await p.connectOAuth(e)}catch(e){f(i(e)),l(null)}},y=async e=>{e.preventDefault(),l(`new`),f(null);let o;try{o=await p.addOAuth({name:t.trim(),url:r.trim()})}catch(e){f(i(e)),l(null);return}n(``),a(``),await _(o.id)},b=!!e?.redirectUri;return(0,j.jsxs)(`section`,{className:`server-info mcp-library oauth-connectors`,children:[(0,j.jsx)(`h4`,{children:`Sign-in connectors (OAuth)`}),(0,j.jsx)(`p`,{className:`hint`,children:`Services you sign in to with your account.`}),(0,j.jsxs)(`details`,{className:`set-more`,children:[(0,j.jsx)(`summary`,{children:`Details`}),(0,j.jsx)(`p`,{className:`hint`,children:`Remote MCP servers you sign in to, such as a fleet gateway. Botons registers itself with the server, you approve it on the server's own page, and its tokens stay on this computer: Botons you give it to get only a short-lived access token for each turn.`})]}),e&&!b&&(0,j.jsxs)(`p`,{className:`hint warn`,role:`note`,children:[`Signing in needs an HTTPS address for the sign-in to come back to. Set `,(0,j.jsx)(`code`,{children:`BOTONS_OAUTH_PUBLIC_URL`}),` (see "OAuth connectors" in the README) and restart Botons.`]}),b&&(0,j.jsxs)(`p`,{className:`hint`,children:[`Sign-ins come back to `,(0,j.jsx)(`code`,{children:e.redirectUri}),`.`]}),u&&(0,j.jsx)(`p`,{className:`form-error`,role:`alert`,children:u}),(0,j.jsx)(`ul`,{className:`mcp-list`,children:e?.connections.map(e=>(0,j.jsxs)(`li`,{className:`mcp-item oauth-item`,children:[(0,j.jsxs)(`span`,{className:`connector-text`,children:[(0,j.jsxs)(`span`,{className:`connector-name`,children:[e.name,(0,j.jsx)(`span`,{className:e.status===`connected`?`badge`:`badge warn-badge`,children:Y[e.status]}),e.feed&&(0,j.jsx)(`span`,{className:`badge`,children:`work feed`})]}),(0,j.jsx)(`span`,{className:`connector-target`,title:e.url,children:e.url}),e.status===`reconnect`&&e.statusReason&&(0,j.jsx)(`span`,{className:`hint warn`,children:e.statusReason}),e.refreshError&&(0,j.jsxs)(`span`,{className:`hint warn`,children:[`The last token refresh failed and will be tried again: `,e.refreshError]}),e.feedError&&(0,j.jsxs)(`span`,{className:`hint warn`,children:[`Work feed: `,e.feedError]}),e.status===`connected`&&(0,j.jsxs)(`span`,{className:`hint`,children:[e.issuer?`Signed in at ${e.issuer}`:`Signed in`,e.refreshedAt?`, token refreshed ${o(e.refreshedAt)}`:``,`.`]}),(0,j.jsx)(B,{id:`oauth:${e.name}`}),e.status===`connected`&&(0,j.jsx)(U,{id:`oauth:${e.name}`,label:e.name,testable:!0}),(0,j.jsx)(R,{id:`oauth:${e.name}`}),(0,j.jsxs)(`label`,{className:`check`,children:[(0,j.jsx)(`input`,{type:`checkbox`,checked:e.keepConnected,onChange:t=>void s(`change keep connected`,()=>p.updateOAuth(e.id,{keepConnected:t.target.checked}),f)}),(0,j.jsx)(`span`,{children:`Keep connected (refresh weekly, so an unused sign-in doesn't lapse)`})]}),e.feed&&(0,j.jsxs)(`label`,{className:`oauth-unknown`,children:[(0,j.jsx)(`span`,{className:`field-label`,children:`Updates on work no Boton is known to have started`}),(0,j.jsxs)(`select`,{value:e.unknownJobs,onChange:t=>void s(`change where updates go`,()=>p.updateOAuth(e.id,{unknownJobs:t.target.value}),f),children:[(0,j.jsx)(`option`,{value:`nowhere`,children:`Ignore them`}),(0,j.jsx)(`option`,{value:`primary`,children:`Send them to the primary Boton`})]})]})]}),(0,j.jsxs)(`span`,{className:`oauth-actions`,children:[(0,j.jsx)(`button`,{type:`button`,className:e.status===`connected`?`btn small ghost`:`btn small primary`,disabled:!b||c!==null,onClick:()=>void _(e.id),children:c===e.id?`Opening…`:e.status===`connected`?`Sign in again`:e.status===`reconnect`?`Reconnect`:`Connect`}),(0,j.jsx)(`button`,{type:`button`,className:`icon-btn small`,"aria-label":`Delete ${e.name}`,title:`Delete`,onClick:()=>g(e),children:(0,j.jsx)(d,{size:14})})]})]},e.id))}),(0,j.jsxs)(`form`,{className:`mcp-form oauth-form`,onSubmit:e=>void y(e),children:[(0,j.jsxs)(`label`,{children:[(0,j.jsx)(`span`,{className:`field-label`,children:`Name`}),(0,j.jsx)(`input`,{value:t,onChange:e=>n(e.target.value),placeholder:`fleet`,maxLength:40,autoComplete:`off`,autoCapitalize:`off`,spellCheck:!1,required:!0}),(0,j.jsx)(`span`,{className:`hint`,children:`Lowercase letters, digits, - and _. Botons see its tools under this name.`})]}),(0,j.jsxs)(`label`,{children:[(0,j.jsx)(`span`,{className:`field-label`,children:`MCP server URL`}),(0,j.jsx)(`input`,{type:`url`,value:r,onChange:e=>a(e.target.value),placeholder:`https://mcp.example.com/mcp`,inputMode:`url`,autoComplete:`off`,spellCheck:!1,required:!0})]}),(0,j.jsx)(`div`,{className:`form-actions`,children:(0,j.jsx)(`button`,{type:`submit`,className:`btn primary`,disabled:!b||c!==null,children:c===`new`?`Connecting…`:`Add and connect`})})]}),h&&(0,j.jsx)(m,{title:`Delete ${h.name}?`,confirmLabel:`Delete`,what:`delete ${h.name}`,danger:!0,onConfirm:()=>p.deleteOAuth(h.id),onClose:()=>g(null),children:(0,j.jsx)(`p`,{children:`Every Boton that has it loses it from its next turn on. Its tokens are deleted from this computer (sign out at the server too, to revoke them there).`})})]})}function ae(){let e=v(e=>e.connectors),[t,n]=(0,S.useState)(null);(0,S.useEffect)(()=>{e||s(`load the connectors`,()=>p.loadConnectors(),n)},[e]);let r=(e,t)=>void s(`change ${e.name}`,()=>p.setConnectorPolicy(e.id,t),n);return(0,j.jsxs)(`section`,{className:`server-info mcp-library connector-requests`,children:[(0,j.jsx)(`h4`,{children:`Connectors Botons can ask for`}),(0,j.jsx)(`p`,{className:`hint`,children:`Services Botons may ask you for when they need one.`}),(0,j.jsxs)(`details`,{className:`set-more`,children:[(0,j.jsx)(`summary`,{children:`Details`}),(0,j.jsxs)(`p`,{className:`hint`,children:[`A Boton uses only the connectors checked in its settings. Turn one on here and Botons can ask for it with `,(0,j.jsx)(`code`,{children:`botons connector request`}),`: you approve each request on a card, or let it be granted at once. Runs started by a webhook always need your approval.`]})]}),t&&(0,j.jsx)(`p`,{className:`form-error`,children:t}),e&&!e.connectors.some(e=>!e.managed)&&(0,j.jsx)(`p`,{className:`empty-note`,children:`No connectors yet.`}),(0,j.jsx)(`ul`,{className:`mcp-list`,children:e?.connectors.filter(e=>!e.managed).map(e=>(0,j.jsxs)(`li`,{className:`mcp-item`,children:[(0,j.jsxs)(`label`,{className:`check connector`,children:[(0,j.jsx)(`input`,{type:`checkbox`,checked:e.onRequest,onChange:t=>r(e,{onRequest:t.target.checked})}),(0,j.jsxs)(`span`,{className:`connector-text`,children:[(0,j.jsx)(`span`,{className:`connector-name`,children:e.name}),(0,j.jsx)(`span`,{className:`hint`,children:te[e.source]})]})]}),e.onRequest&&(0,j.jsxs)(`select`,{"aria-label":`How a request for ${e.name} is granted`,value:e.grant,onChange:t=>r(e,{grant:t.target.value}),children:[(0,j.jsx)(`option`,{value:`approval`,children:`Ask me`}),(0,j.jsx)(`option`,{value:`immediate`,children:`Grant at once`})]})]},e.id))})]})}var oe=[{kind:`oauth`,label:`Sign in to it`,text:`A remote MCP server with its own sign-in page (OAuth). You approve Botons there; its tokens stay on this computer.`},{kind:`http`,label:`A URL and an API key`,text:`A remote MCP server you reach with a key or token, sent in a header. The key is kept here and never shown again.`},{kind:`stdio`,label:`A command on this computer`,text:`A local MCP server (stdio) started inside each turn of a Boton that has it, in the Workspace sandbox whatever that Boton's access: it sees this computer's programs, the folders its command and arguments name and its own empty /tmp, with this computer's network, but no workspace, shared folder or data folder. Give the full path to a program kept outside those. Botons doesn't run it to test it.`}],se=[`How it connects`,`Its details`,`Test it and choose Botons`];function ce({step:e}){return(0,j.jsx)(`ol`,{className:`service-steps`,"aria-label":`Steps`,children:se.map((t,n)=>(0,j.jsxs)(`li`,{className:n===e?`current`:n<e?`done`:``,"aria-current":n===e?`step`:void 0,children:[n+1,`. `,t]},t))})}function le({onPick:e,onPreset:t,oauthReady:n}){return(0,j.jsxs)(`div`,{className:`service-kinds`,children:[t&&(0,j.jsx)(`div`,{className:`service-presets`,role:`group`,"aria-label":`Known services`,children:C.map(e=>(0,j.jsxs)(`button`,{type:`button`,className:`service-kind service-preset`,onClick:()=>t(e),children:[(0,j.jsx)(`span`,{className:`connector-name`,children:e.label}),(0,j.jsx)(`span`,{className:`hint`,children:e.secretLabel})]},e.id))}),oe.map(t=>(0,j.jsxs)(`button`,{type:`button`,className:`service-kind`,disabled:t.kind===`oauth`&&!n,onClick:()=>e(t.kind),children:[(0,j.jsx)(`span`,{className:`connector-name`,children:t.label}),(0,j.jsx)(`span`,{className:`hint`,children:t.text}),t.kind===`oauth`&&!n&&(0,j.jsxs)(`span`,{className:`hint warn`,children:[`Needs `,(0,j.jsx)(`code`,{children:`BOTONS_OAUTH_PUBLIC_URL`}),`, an HTTPS address for the sign-in to come back to (see "OAuth connectors" in the README).`]})]},t.kind))]})}function X(){let[e,t]=(0,S.useState)(!1);return(0,j.jsxs)(`section`,{className:`server-info connect-service`,children:[(0,j.jsx)(`h4`,{children:`Connect a service`}),(0,j.jsx)(`p`,{className:`hint`,children:`Add a service Botons can use, such as GitHub, Linear or Slack, test it and choose exactly which Botons get it.`}),(0,j.jsxs)(`button`,{type:`button`,className:`btn small primary`,onClick:()=>t(!0),children:[(0,j.jsx)(c,{size:14}),` Connect a service`]}),e&&(0,j.jsx)(Z,{onClose:()=>t(!1)})]})}function Z({onClose:e}){let t=v(e=>e.oauth),[n,r]=(0,S.useState)(null),[a,o]=(0,S.useState)(null),[c,l]=(0,S.useState)(null),[u,d]=(0,S.useState)(``),[f,m]=(0,S.useState)(``),[h,_]=(0,S.useState)(!1),[y,b]=(0,S.useState)(null);(0,S.useEffect)(()=>{t||s(`load your OAuth connectors`,()=>p.loadOAuth(),b)},[t]);let x=n?c?2:1:0,C=c?.startsWith(`oauth:`)?t?.connections.find(e=>`oauth:${e.name}`===c):null,w=async e=>{e.preventDefault(),_(!0),b(null);try{let e=await p.addOAuth({name:u.trim(),url:f.trim()});l(`oauth:${e.name}`)}catch(e){b(i(e))}finally{_(!1)}},T=async()=>{if(C){_(!0),b(null);try{await p.connectOAuth(C.id)}catch(e){b(i(e)),_(!1)}}};return(0,j.jsxs)(g,{title:`Connect a service`,onClose:e,wide:!0,children:[(0,j.jsx)(ce,{step:x}),x===0&&(0,j.jsx)(le,{oauthReady:!!t?.redirectUri,onPick:e=>{o(null),r(e)},onPreset:e=>{o(e),r(`http`)}}),x===1&&n===`oauth`&&(0,j.jsxs)(`form`,{className:`mcp-form`,onSubmit:e=>void w(e),children:[(0,j.jsxs)(`label`,{children:[(0,j.jsx)(`span`,{className:`field-label`,children:`Name`}),(0,j.jsx)(`input`,{value:u,onChange:e=>d(e.target.value),placeholder:`fleet`,maxLength:40,autoComplete:`off`,autoCapitalize:`off`,spellCheck:!1,required:!0}),(0,j.jsx)(`span`,{className:`hint`,children:`Lowercase letters, digits, - and _. Botons see its tools under this name.`})]}),(0,j.jsxs)(`label`,{children:[(0,j.jsx)(`span`,{className:`field-label`,children:`MCP server URL`}),(0,j.jsx)(`input`,{type:`url`,value:f,onChange:e=>m(e.target.value),placeholder:`https://mcp.example.com/mcp`,inputMode:`url`,autoComplete:`off`,spellCheck:!1,required:!0})]}),y&&(0,j.jsx)(`p`,{className:`form-error`,role:`alert`,children:y}),(0,j.jsxs)(`div`,{className:`form-actions`,children:[(0,j.jsx)(`button`,{type:`button`,className:`btn ghost`,onClick:()=>r(null),disabled:h,children:`Back`}),(0,j.jsx)(`button`,{type:`submit`,className:`btn primary`,disabled:h,children:h?`Adding…`:`Add it`})]})]}),x===1&&n!==`oauth`&&(0,j.jsx)(J,{fixed:n,preset:a??void 0,onDone:()=>r(null),onSaved:e=>l(`lib:${e}`)},a?.id??n),x===2&&c&&(0,j.jsxs)(`div`,{className:`service-check`,children:[n===`oauth`?C?.status===`connected`?(0,j.jsx)(U,{id:c,label:C.name,testable:!0}):(0,j.jsxs)(`div`,{className:`connector-test`,children:[(0,j.jsx)(`span`,{className:`hint`,children:`Not signed in yet: Botons you give it to go without it until you sign in. Sign in opens the server's own page and comes back to Settings.`}),(0,j.jsx)(`button`,{type:`button`,className:`btn small primary`,disabled:h||!C,onClick:()=>void T(),children:h?`Opening…`:`Sign in`})]}):(0,j.jsx)(U,{id:c,label:c.slice(4),testable:n===`http`,autoRun:!0}),(0,j.jsx)(`h5`,{children:`Which Botons can use it`}),(0,j.jsx)(L,{id:c}),y&&(0,j.jsx)(`p`,{className:`form-error`,role:`alert`,children:y}),(0,j.jsx)(`p`,{className:`hint`,children:`Save grant changes above before Done. Unsaved choices are discarded when you close.`}),(0,j.jsx)(`div`,{className:`form-actions`,children:(0,j.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:e,children:`Done`})})]})]})}var ue={ok:`OK`,warning:`Note`,blocked:`Blocked`,problem:`Problem`,unverified:`Not verified`};function Q({state:e}){return(0,j.jsx)(`span`,{className:a(`starter-state`,`is-${e}`),children:ue[e]})}function de(e){let{template:t,provider:n,access:a,slots:o,onSlots:s,onReview:c,refresh:l,onConnect:u}=e,d=t.needs,[f,p]=(0,S.useState)(null),[m,h]=(0,S.useState)(null),[g,_]=(0,S.useState)(!1),[v,y]=(0,S.useState)(0),b=(0,S.useRef)(0),x=(0,S.useCallback)(async()=>{let e=++b.current;_(!0),c(null);try{let i=await r.reviewTemplate(t.id,{provider:n,access:a,connectors:o});if(e!==b.current)return;p(i),h(null),c(i)}catch(t){if(e!==b.current)return;p(null),h(i(t))}finally{e===b.current&&_(!1)}},[t.id,n,a,o,c]);return(0,S.useEffect)(()=>{x()},[x,l,v]),(0,j.jsxs)(`section`,{className:`starter-review`,"aria-label":`Review ${t.name}`,children:[(0,j.jsxs)(`span`,{className:`field-label`,children:[`What `,t.name,` needs`]}),(0,j.jsxs)(`dl`,{className:`starter-needs`,children:[(0,j.jsx)(`dt`,{children:`Brain`}),(0,j.jsx)(`dd`,{children:d.brain.text}),(0,j.jsx)(`dt`,{children:`Access`}),(0,j.jsxs)(`dd`,{children:[d.access===`workspace`?`Workspace access`:`Full computer access`,`. `,d.accessWhy]}),(0,j.jsx)(`dt`,{children:`Schedule`}),(0,j.jsx)(`dd`,{children:d.schedule}),(0,j.jsx)(`dt`,{children:`Output`}),(0,j.jsx)(`dd`,{children:d.output.text}),(0,j.jsx)(`dt`,{children:`If a run fails`}),(0,j.jsx)(`dd`,{children:d.failure})]}),d.connectors.length>0&&(0,j.jsxs)(`div`,{className:`starter-slots`,children:[(0,j.jsx)(`span`,{className:`field-label`,children:`Connectors`}),d.connectors.map(e=>{let t=f?.slots.find(t=>t.slot===e.slot);return(0,j.jsxs)(`div`,{className:`starter-slot`,"data-slot":e.slot,children:[(0,j.jsxs)(`label`,{children:[(0,j.jsxs)(`span`,{children:[e.label,` `,e.required?`(required)`:`(optional)`,`: `,e.purpose]}),(0,j.jsxs)(`select`,{value:o[e.slot]??``,onChange:t=>s({...o,[e.slot]:t.target.value||null}),"aria-label":`Connector for ${e.label}`,children:[(0,j.jsx)(`option`,{value:``,children:e.required?`Choose one of yours…`:`None`}),t?.candidates.map(t=>(0,j.jsxs)(`option`,{value:t.id,disabled:!!t.problem&&o[e.slot]!==t.id,children:[t.name,t.suggested?` (suggested)`:``,t.problem?`: ${t.problem}`:``]},t.id))]})]}),t&&(0,j.jsxs)(`p`,{className:`hint`,children:[(0,j.jsx)(Q,{state:t.state}),` `,t.text]}),t?.guidance&&(0,j.jsxs)(`div`,{className:`starter-guidance`,role:`note`,children:[(0,j.jsx)(`p`,{children:t.guidance}),(0,j.jsxs)(`div`,{className:`routine-actions`,children:[(0,j.jsx)(`button`,{type:`button`,className:`btn small`,onClick:u,children:`Connect a service`}),(0,j.jsx)(`button`,{type:`button`,className:`btn small ghost`,onClick:()=>y(e=>e+1),children:`Check again`})]})]})]},e.slot)})]}),(0,j.jsxs)(`div`,{className:`starter-grants`,"aria-live":`polite`,children:[(0,j.jsx)(`span`,{className:`field-label`,children:`Exactly what installing it grants`}),g&&!f&&(0,j.jsx)(`p`,{className:`hint`,children:`Reviewing…`}),m&&(0,j.jsxs)(`p`,{className:`form-error`,children:[`Couldn't review it: `,m]}),f&&(0,j.jsxs)(j.Fragment,{children:[(0,j.jsxs)(`ul`,{children:[(0,j.jsxs)(`li`,{children:[(0,j.jsx)(Q,{state:f.brain.state}),` `,f.brain.text]}),(0,j.jsxs)(`li`,{children:[(0,j.jsx)(Q,{state:f.access.state}),` `,f.access.text]}),f.grants.slice(2).map(e=>(0,j.jsx)(`li`,{children:e},e))]}),f.blocked.length>0&&(0,j.jsxs)(`div`,{className:`form-error`,role:`alert`,children:[(0,j.jsx)(`p`,{children:`It can't be installed yet:`}),(0,j.jsx)(`ul`,{children:f.blocked.map(e=>(0,j.jsx)(`li`,{children:e},e))})]}),(0,j.jsx)(`p`,{className:`hint`,children:`Its text asks for things; only what is listed here is granted. Its routines start paused: after installing, check it, test it, then turn them on.`})]})]})]})}var $=e=>e.status===`succeeded`||e.status===`failed`||e.status===`skipped`;function fe({bot:e,template:t,onDone:n}){let[a,o]=(0,S.useState)(null),[s,c]=(0,S.useState)(null),[l,u]=(0,S.useState)(null),[d,f]=(0,S.useState)(!1),p=(0,S.useCallback)(async()=>{f(!0);try{let[t,n]=await Promise.all([r.setupCheck(e.id),r.routines(e.id)]);o(t),c(n),u(null)}catch(e){u(i(e))}finally{f(!1)}},[e.id]);return(0,S.useEffect)(()=>{p()},[p]),(0,j.jsxs)(`div`,{className:`starter-setup`,children:[(0,j.jsxs)(`p`,{children:[(0,j.jsx)(`strong`,{children:e.name}),` is installed from `,t.name,`.`,` `,t.routines.length?`Its routines stay paused until you turn them on here: nothing else, the Boton included, can turn them on first.`:`It has no routines: it works when asked in chat.`]}),(0,j.jsxs)(`section`,{"aria-label":`Setup check`,children:[(0,j.jsxs)(`div`,{className:`starter-head`,children:[(0,j.jsx)(`span`,{className:`field-label`,children:`Setup check`}),(0,j.jsx)(`button`,{type:`button`,className:`btn small ghost`,onClick:()=>void p(),disabled:d,children:d?`Checking…`:`Check again`})]}),l&&(0,j.jsx)(`p`,{className:`form-error`,children:l}),a&&(0,j.jsxs)(j.Fragment,{children:[(0,j.jsx)(`ul`,{className:`starter-check`,children:a.items.map(e=>(0,j.jsxs)(`li`,{"data-key":e.key,children:[(0,j.jsx)(Q,{state:e.state}),` `,(0,j.jsx)(`strong`,{children:e.label}),`: `,e.text]},e.key))}),(0,j.jsx)(`p`,{className:`hint`,children:a.scope}),(0,j.jsxs)(`details`,{children:[(0,j.jsx)(`summary`,{children:`What no check here can verify`}),(0,j.jsx)(`ul`,{children:a.notChecked.map(e=>(0,j.jsx)(`li`,{children:e},e))})]})]})]}),s?.map(e=>(0,j.jsx)(pe,{routine:e,sample:t.needs?.samplePayload??null,check:a,onChanged:p},e.id)),(0,j.jsx)(`div`,{className:`form-actions`,children:(0,j.jsxs)(`button`,{type:`button`,className:`btn primary`,onClick:n,children:[`Open `,e.name]})})]})}function pe({routine:e,sample:t,check:n,onChanged:o}){let[s,c]=(0,S.useState)(t??``),[u,d]=(0,S.useState)(null),[f,p]=(0,S.useState)(!1),[m,h]=(0,S.useState)(null),[g,_]=(0,S.useState)(!1),v=n?.routines.find(t=>t.routineId===e.id)??null,y=!!u&&!$(u)||!!v?.testing,b=!!v?.tested&&!y;(0,S.useEffect)(()=>{if(!u||$(u))return;let t=!0,n=setInterval(async()=>{try{let n=(await r.runs(e.id)).find(e=>e.id===u.id);if(!t||!n)return;d(n),$(n)&&o()}catch{}},700);return()=>{t=!1,clearInterval(n)}},[u,e.id,o]);let x=async()=>{p(!0),h(null);try{d(await r.runRoutine(e.id,e.webhook?{test:!0,payload:s}:{test:!0})),o()}catch(e){h(i(e))}finally{p(!1)}},C=async()=>{p(!0),h(null);try{await r.activateRoutine(e.id,b?{}:{override:!0}),_(!1),await o()}catch(e){h(i(e))}finally{p(!1)}};return(0,j.jsxs)(`section`,{className:`starter-routine`,"aria-label":`Routine ${e.name}`,"data-routine":e.name,children:[(0,j.jsxs)(`span`,{className:`field-label`,children:[`Routine: `,e.name]}),(0,j.jsxs)(`p`,{className:`hint`,children:[`A test run is a real turn on the Boton's brain. It is told to treat it as a dry run (read and check, but do nothing hard to undo or outside this computer, and say what it would have done), but nothing enforces that: it can use every tool it has. It pushes nothing and sends no callback.`,e.webhook?` Like a webhook run, it is held to Workspace access and gets no connectors.`:` It has the Boton's access and connectors, so what it reads, and anything it does, is real.`]}),e.webhook&&(0,j.jsxs)(`label`,{children:[(0,j.jsx)(`span`,{className:`field-label`,children:`Sample request`}),(0,j.jsx)(`textarea`,{value:s,onChange:e=>c(e.target.value),rows:4,spellCheck:!1,"aria-label":`Sample request for ${e.name}`})]}),u&&(0,j.jsx)(`div`,{className:a(`starter-run`,`is-${u.status}`),role:`status`,children:$(u)?u.status===`succeeded`?(0,j.jsxs)(j.Fragment,{children:[(0,j.jsxs)(`p`,{children:[(0,j.jsx)(`strong`,{children:`Test run finished without an error.`}),` That shows the brain ran it, not that the reply is right: read it.`]}),(0,j.jsx)(`pre`,{children:(u.reply??``).slice(0,2e3)})]}):(0,j.jsxs)(`p`,{children:[(0,j.jsxs)(`strong`,{children:[`Test run `,u.status,`:`]}),` `,u.error??`no reason given`,`. Fix what it says (its chat has the details), then test again.`]}):(0,j.jsxs)(`p`,{children:[`Test run `,u.status,`…`]})}),m&&(0,j.jsx)(`p`,{className:`form-error`,children:m}),(0,j.jsxs)(`div`,{className:`routine-actions`,children:[(0,j.jsxs)(`button`,{type:`button`,className:`btn small`,onClick:()=>void x(),disabled:f||!!u&&!$(u),children:[(0,j.jsx)(l,{size:14}),` `,u?`Test again`:`Run a test`]}),e.enabled?(0,j.jsx)(`span`,{className:`starter-on`,children:`On`}):(0,j.jsxs)(`button`,{type:`button`,className:`btn small primary`,onClick:()=>void C(),disabled:f||!v||y||!b&&!g,children:[`Turn on `,e.name]})]}),v&&!e.enabled&&(0,j.jsx)(`p`,{className:`hint`,children:v.text}),!e.enabled&&v?.pending&&!b&&!y&&(0,j.jsxs)(`label`,{className:`check`,children:[(0,j.jsx)(`input`,{type:`checkbox`,checked:g,onChange:e=>_(e.target.checked)}),(0,j.jsx)(`span`,{children:`Turn it on without a test run that passed on its current settings (recorded as an override)`})]})]})}export{ne as a,ie as c,Z as i,fe as n,ae as o,X as r,W as s,de as t};
@@ -0,0 +1 @@
1
+ import{i as e,t}from"./react-C1iE8mnY.js";import{E as n,O as r,b as i,i as a,k as o,n as s}from"./util-Cx5feSJu.js";var c=new Set([`work.event`,`connector.granted`,`reaction.followup`,`approval.decided`,`browser.handed_back`]);function l(e){return e.role!==`user`&&e.role!==`bot`||e.meta?.kind&&c.has(e.meta.kind)?null:e.text.trim()?e.text:null}var u=()=>({clipboard:globalThis.navigator?.clipboard,document});async function d(e,t=u()){if(t.clipboard?.writeText)try{return await t.clipboard.writeText(e),!0}catch{}return f(e,t.document)}function f(e,t){let n=t.activeElement,r=t.getSelection?.()??null,i=[];for(let e=0;r&&e<r.rangeCount;e++)i.push(r.getRangeAt(e));let a=t.createElement(`textarea`);a.value=e,a.setAttribute(`readonly`,``),a.setAttribute(`aria-hidden`,`true`),a.tabIndex=-1,Object.assign(a.style,{position:`fixed`,top:`0`,left:`0`,width:`1px`,height:`1px`,padding:`0`,border:`0`,margin:`0`,opacity:`0`,fontSize:`16px`,pointerEvents:`none`}),t.body.appendChild(a);let o=!1;try{a.focus({preventScroll:!0}),a.select(),a.setSelectionRange(0,e.length),o=t.execCommand(`copy`)}catch{o=!1}finally{if(a.remove(),n&&n!==t.body&&typeof n.focus==`function`&&n.focus({preventScroll:!0}),r&&i.length){r.removeAllRanges();for(let e of i)r.addRange(e)}}return o}var p=26214400,m=26214400,h={png:`image/png`,jpg:`image/jpeg`,jpeg:`image/jpeg`,gif:`image/gif`,webp:`image/webp`,avif:`image/avif`,bmp:`image/bmp`,ico:`image/x-icon`},g=new Set([`md`,`markdown`,`mdown`,`mkd`]),_=new Set(`txt.text.log.out.err.csv.tsv.json.jsonl.ndjson.yaml.yml.toml.ini.cfg.conf.env.properties.rst.adoc.org.tex.bib.srt.vtt.diff.patch.sql.graphql.proto.ts.tsx.mts.cts.js.jsx.mjs.cjs.py.rb.go.rs.java.kt.kts.swift.c.h.cc.cpp.hpp.cs.php.pl.lua.r.scala.sh.bash.zsh.fish.ps1.css.scss.less.vue.svelte.dockerfile.gitignore.lock`.split(`.`)),v={html:`text/html`,htm:`text/html`,svg:`image/svg+xml`,xml:`application/xml`,zip:`application/zip`,gz:`application/gzip`,tar:`application/x-tar`,mp3:`audio/mpeg`,m4a:`audio/mp4`,wav:`audio/wav`,mp4:`video/mp4`,mov:`video/quicktime`,webm:`video/webm`,heic:`image/heic`,heif:`image/heif`,docx:`application/vnd.openxmlformats-officedocument.wordprocessingml.document`,xlsx:`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`,pptx:`application/vnd.openxmlformats-officedocument.presentationml.presentation`};function y(e){let t=e.slice(e.lastIndexOf(`/`)+1),n=t.lastIndexOf(`.`);return n>0?t.slice(n+1).toLowerCase():``}function b(e){let t=y(e);return t in h?`image`:t===`svg`?`svg`:t===`html`||t===`htm`?`html`:t===`pdf`?`pdf`:g.has(t)?`markdown`:_.has(t)?`text`:`other`}function x(e){let t=y(e);return t in h?h[t]:t===`pdf`?`application/pdf`:g.has(t)?`text/markdown`:t===`json`?`application/json`:t===`csv`?`text/csv`:_.has(t)?`text/plain`:v[t]??`application/octet-stream`}function S(e){if(e<1024)return`${e} B`;let t=[`KB`,`MB`,`GB`,`TB`],n=e/1024,r=0;for(;n>=1024&&r<t.length-1;)n/=1024,r++;return`${n<10?n.toFixed(1).replace(/\.0$/,``):Math.round(n)} ${t[r]}`}var C=[7,14,30,60,90],w=1024**3,T=5*1024**3,E=1048576,D=encodeURIComponent;function O(e,t,n=!1){return o(`/api/bots/${D(e)}/files/raw?path=${D(t)}${n?`&download=1`:``}`,r()?.files)}function k(e){return o(`/api/shared/files/raw?path=${D(e)}`,r()?.files)}function A(e,t){let n=b(e);return n===`other`?`Botons doesn't preview this kind of file.`:t!==null&&t>26214400?`It's over ${S(m)}, too big to preview here.`:(n===`markdown`||n===`text`)&&t!==null&&t>1048576?`It's over ${S(E)} of text, too big to show here.`:null}var j=`It's over ${S(m)}, too big to preview here.`,M=`It loaded another page in its frame, so Botons closed it. Open it again to start over, or download it.`;function N(e){return e===401?`Its preview link expired or is no longer valid. Refresh to open it again.`:e===404?`It was moved or deleted after it was opened. Refresh the folder to see what is there now.`:e===413?j:e===400?`It's no longer a file the viewer can show as a page.`:`Couldn't load its preview (error ${e}).`}function P(e){let t=e;return t?.botonsPreview!==`failed`||typeof t.status!=`number`||!Number.isInteger(t.status)||t.status<400||t.status>599?null:N(t.status)}function F(...e){let t=e.filter(Boolean).join(`/`),n=t.startsWith(`/`),r=[];for(let e of t.split(`/`))if(e&&e!==`.`){if(e===`..`&&r.length&&r[r.length-1]!==`..`)r.pop();else if(e===`..`&&n)continue;else r.push(e)}return(n?`/`:``)+r.join(`/`)}function I(e){let t=e.replace(/\/+$/,``).lastIndexOf(`/`);return t<0?``:e.slice(0,t)||`/`}function L(e){let t=e.replace(/\/+$/,``);return t.slice(t.lastIndexOf(`/`)+1)}function R(e,t,n=``){if(!e||e.startsWith(`#`)||e.startsWith(`?`)||e.startsWith(`//`)||/^[a-z][a-z0-9+.-]*:/i.test(e))return null;let r=e.split(/[?#]/,1)[0];try{r=decodeURIComponent(r)}catch{}if(!r)return null;if(r.startsWith(`/`)){let e=t.replace(/\/+$/,``);return r===e?``:r.startsWith(`${e}/`)?F(r.slice(e.length+1)):F(r)}return F(n,r)}function z(e,t=`Workspace`){let n=[{name:t,path:``}],r=``;for(let t of e.split(`/`).filter(Boolean))r=r?`${r}/${t}`:t,n.push({name:t,path:r});return n}function B(e){let t=e.meta?.kind===`file.shared`?e.meta.file:void 0;if(!t)return null;let n=b(t.name);return n===`image`||n===`svg`?t:null}function V(e){let t=e.bots.reduce((e,t)=>e+t.workspaceBytes,0)+e.trashBytes+e.sharedBytes,n=e.bots.filter(e=>e.inboxBytes>w).map(e=>e.name);return{total:t,big:t>T||n.length>0,fullInboxes:n}}var H=e=>e===null?`Cleanup off`:`Cleanup after ${e??30} days`,U=e(),W=t(),G={command:`Ran`,read:`Read`,edit:`Edited`,search:`Searched`,web:`Web`,tool:`Tool`};function K({turn:e}){return(0,W.jsxs)(W.Fragment,{children:[(0,W.jsx)(`ol`,{className:`step-list`,children:e.steps.map((e,t)=>(0,W.jsxs)(`li`,{title:`${e.label} · ${s(e.at)}`,children:[(0,W.jsx)(`span`,{className:a(`step-kind`,e.kind),children:G[e.kind]??e.kind}),(0,W.jsx)(`code`,{className:`step-detail`,children:e.detail||e.label})]},t))}),e.total>e.steps.length&&(0,W.jsxs)(`p`,{className:`hint`,children:[`… and `,e.total-e.steps.length,` more (only the first `,e.steps.length,` are kept).`]})]})}function q({count:e,load:t}){let[n,r]=(0,U.useState)(!1),[o,s]=(0,U.useState)(null),[c,l]=(0,U.useState)(null),u=async()=>{l(null);try{s(await t())}catch(e){l(i(e))}};return(0,W.jsxs)(`div`,{className:a(`steps`,n&&`open`),children:[(0,W.jsxs)(`button`,{type:`button`,className:`steps-toggle`,"aria-expanded":n,onClick:()=>{r(!n),!n&&!o&&u()},children:[(0,W.jsx)(`span`,{className:`steps-caret`,"aria-hidden":`true`,children:`›`}),e===1?`Ran 1 step`:`Ran ${e} steps`]}),n&&(0,W.jsxs)(`div`,{className:`steps-body`,children:[c&&(0,W.jsxs)(`p`,{className:`form-error`,children:[`Couldn't load the steps: `,c,` `,(0,W.jsx)(`button`,{type:`button`,className:`link-btn`,onClick:()=>void u(),children:`Retry`})]}),!o&&!c&&(0,W.jsx)(`p`,{className:`hint`,children:`Loading…`}),o?.map((e,t)=>(0,W.jsxs)(`div`,{children:[o.length>1&&(0,W.jsxs)(`p`,{className:`steps-attempt`,children:[`Attempt `,t+1,e.outcome===`ok`?``:` · ${e.outcome}`]}),e.steps.length?(0,W.jsx)(K,{turn:e}):(0,W.jsx)(`p`,{className:`hint`,children:`No steps.`})]},e.turnId))]})]})}var J=e=>()=>n.messageSteps(e).then(e=>[e]),Y=e=>()=>n.runSteps(e);export{d as C,x as S,C as _,j as a,b,z as c,P as d,O as f,R as g,V as h,M as i,A as l,k as m,J as n,L as o,B as p,Y as r,H as s,q as t,I as u,w as v,l as w,S as x,p as y};