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,68 @@
1
+ // Helpers the server, the `botons` CLI and the web app share. No imports beyond types: config.ts loads this without node_modules.
2
+ import type { HandoverKey, TokenUsage } from './types.ts';
3
+
4
+ /** How far back the runs-needing-attention overview (and `npm run doctor`) look for failed runs. */
5
+ export const ATTENTION_DAYS = 7;
6
+
7
+ /** The managed browser's connector (server/src/browser.ts): a Boton that has it gets the browser tools in its turns. */
8
+ export const BROWSER_SERVER = 'botons_browser';
9
+ export const BROWSER_CONNECTOR = `lib:${BROWSER_SERVER}`;
10
+ /** The most sites a Boton's browser list holds (browser-policy.ts checks it). */
11
+ export const MAX_BROWSER_SITES = 50;
12
+ /** The keys the owner can press in a browser handover: only these, never a shortcut with a modifier. */
13
+ export const HANDOVER_KEYS: readonly HandoverKey[] = ['Enter', 'Tab', 'Shift+Tab', 'Backspace', 'Delete', 'Escape', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown', 'Space'];
14
+
15
+ export function isValidTimezone(tz: string): boolean {
16
+ try {
17
+ new Intl.DateTimeFormat('en-US', { timeZone: tz });
18
+ return true;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+
24
+ /** A one-off schedule ("once <iso>") whose time has come. */
25
+ export function isPastOneOff(spec: string | null, now = new Date()): boolean {
26
+ return !!spec && spec.startsWith('once ') && new Date(spec.slice(5)).getTime() <= now.getTime();
27
+ }
28
+
29
+ export function totalTokens(u: TokenUsage): number {
30
+ return u.inputTokens + u.outputTokens + u.cacheReadTokens + u.cacheWriteTokens;
31
+ }
32
+
33
+ /** A copy's default name: the next number not `taken` (lowercased names): "Juniper" → "Juniper 2", "Juniper 2" → "Juniper 3". */
34
+ export function copyName(name: string, taken: Set<string>): string {
35
+ const m = /^(.*\S) (\d+)$/.exec(name);
36
+ const base = m ? m[1] : name;
37
+ for (let n = m ? Number(m[2]) + 1 : 2; ; n++) {
38
+ const suffix = ` ${n}`;
39
+ const candidate = `${base.slice(0, 60 - suffix.length).trimEnd()}${suffix}`;
40
+ if (!taken.has(candidate.toLowerCase())) return candidate;
41
+ }
42
+ }
43
+
44
+ /** The shortest time limit a routine may have: one second (the floor BOTONS_TURN_TIMEOUT_MS has too). */
45
+ export const MIN_TIME_LIMIT_MS = 1000;
46
+
47
+ /** A time limit for people: "20 minutes", "1 hour", "1.5 hours", "45 seconds". */
48
+ export function durationText(ms: number): string {
49
+ const unit = (n: number, word: string) => `${n} ${word}${n === 1 ? '' : 's'}`;
50
+ const round = (n: number) => Math.round(n * 10) / 10;
51
+ if (ms >= 3_600_000 && ms % 1_800_000 === 0) return unit(round(ms / 3_600_000), 'hour');
52
+ if (ms >= 60_000) return unit(round(ms / 60_000), 'minute');
53
+ return unit(round(ms / 1000), 'second');
54
+ }
55
+
56
+ /**
57
+ * A version number found in a CLI's `--version` output ("2.1.3 (Claude Code)", "codex-cli 0.46.0"), else null. Only
58
+ * that number is kept, so nothing else the output says (a path, an account) reaches what the owner copies.
59
+ */
60
+ export function versionNumber(text: string | null | undefined): string | null {
61
+ const m = /(?<![\w.@/-])v?(\d{1,4}\.\d{1,4}(?:\.\d{1,6})?(?:-[0-9A-Za-z]{1,16}(?:\.[0-9A-Za-z]{1,16}){0,3})?)(?![\w.@/-])/.exec(String(text ?? '').slice(0, 200));
62
+ return m ? m[1] : null;
63
+ }
64
+
65
+ /** A build id in its expected shape (a commit sha, 7 to 40 hex digits), else null. */
66
+ export function buildSha(text: string | null | undefined): string | null {
67
+ return typeof text === 'string' && /^[0-9a-f]{7,40}$/i.test(text) ? text.toLowerCase() : null;
68
+ }
@@ -0,0 +1,44 @@
1
+ /** Stable registry releases only. CLI output may prepend its product name. */
2
+ export function stableVersion(text: string | null): number[] | null {
3
+ const m = /^(?:codex-cli |Claude Code )?(\d+)\.(\d+)\.(\d+)(?: \(Claude Code\))?$/.exec(text?.trim() ?? '');
4
+ if (!m) return null;
5
+ const parts = m.slice(1).map(Number);
6
+ return parts.every(Number.isSafeInteger) ? parts : null;
7
+ }
8
+ export function newerRelease(installed: string | null, latest: string | null): boolean {
9
+ const a = stableVersion(installed), b = stableVersion(latest);
10
+ if (!a || !b) return false;
11
+ for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return b[i]! > a[i]!;
12
+ return false;
13
+ }
14
+
15
+
16
+ /** "0.167.0" from a CLI's `--version` ("codex-cli 0.167.0", "2.1.3 (Claude Code)"); null in any other shape. */
17
+ export function cliVersion(text: string | null): string | null {
18
+ return stableVersion(text)?.join('.') ?? null;
19
+ }
20
+
21
+ /** Which copy of a CLI Botons runs: its own (Settings → Brains or the installer put it there) or the system's. */
22
+ export interface CliCopy { managed: boolean; terminal: string | null }
23
+
24
+ const product = (id: string) => (id === 'codex-cli' ? 'Codex' : 'Claude Code');
25
+
26
+ /** One line for an installed CLI's copy: "Botons' own copy 0.167.0" or "System copy 2.1.3"; null without a version. */
27
+ export function copyLine(version: string | null, copy: CliCopy | undefined): string | null {
28
+ const v = cliVersion(version);
29
+ return copy && v ? `${copy.managed ? "Botons' own copy" : 'System copy'} ${v}` : null;
30
+ }
31
+
32
+ /** What a successful Update now did, in one line. */
33
+ export function updatedLine(id: string, version: string | null): string {
34
+ const v = cliVersion(version);
35
+ return `Botons now uses its own ${product(id)}${v ? ` ${v}` : ''}.`;
36
+ }
37
+
38
+ /** A note when a terminal on this machine runs an older copy than the one Botons uses; null otherwise. */
39
+ export function terminalNote(id: string, version: string | null, copy: CliCopy | undefined): string | null {
40
+ const t = cliVersion(copy?.terminal ?? null), v = cliVersion(version);
41
+ if (!copy?.managed || !t || !v || !newerRelease(t, v)) return null;
42
+ const cmd = id === 'codex-cli' ? 'codex' : 'claude';
43
+ return `Your terminal’s ${cmd} is still ${t}. Run ${cmd} update there if you want it updated too.`;
44
+ }
@@ -0,0 +1,164 @@
1
+ // Automatic wake-ups in a Boton's chat: which messages the server wrote to start a turn (not the owner), which of
2
+ // them ended [quiet] and so stay out of the transcript, and the short labels the others get above their reply.
3
+ // The messages themselves are never changed or dropped: the API returns them all, and turn and run history keep
4
+ // what each quiet turn noted.
5
+
6
+ import type { Message, WorkEvent, WorkEventKind } from './types.ts';
7
+
8
+ /** A reply that starts with [quiet] (optionally followed by a short note): the note, or null for any other reply. */
9
+ export function parseQuiet(reply: string): string | null {
10
+ const m = /^[\s*_`]*\[quiet\][*_`]*(?:[\s:\-–—]+([\s\S]*))?$/i.exec(reply.trim());
11
+ return m ? (m[1] ?? '').trim() : null;
12
+ }
13
+
14
+ /**
15
+ * A message that woke the Boton without the owner writing it: a connector's work update, another Boton's ask or reply,
16
+ * an approval decision's or a connector grant's follow-up, a reaction follow-up. The owner's own messages, their
17
+ * answer to a card (their words, in the chat) and their hand-back of the browser are never wake-ups.
18
+ */
19
+ export function isAutomaticWake(m: Pick<Message, 'role' | 'meta'>): boolean {
20
+ if (m.role !== 'user') return false;
21
+ if (m.meta?.fromBotId) return true;
22
+ const kind = m.meta?.kind;
23
+ return !!kind && kind !== 'card.answered' && kind !== 'browser.handed_back';
24
+ }
25
+
26
+ /**
27
+ * What a Boton does in the chat during a turn (a memory saved or forgotten, a file shared, a notification or an ask
28
+ * sent, a routine or skill made, changed, paused, resumed or deleted): the server tags each with the turn it came
29
+ * from (its own token's request while that turn runs), and a quiet turn's are left out with it. The same kinds done by
30
+ * the owner or the server are tagged as no turn's and always show, as do approvals, cards, refusals, stops and errors.
31
+ */
32
+ export const TURN_ACTIVITY: ReadonlySet<string> = new Set([
33
+ 'memory.saved',
34
+ 'memory.forgotten',
35
+ 'file.shared',
36
+ 'notify.sent',
37
+ 'ask.sent',
38
+ 'routine.created',
39
+ 'routine.updated',
40
+ 'routine.paused',
41
+ 'routine.resumed',
42
+ 'routine.deleted',
43
+ 'skill.created',
44
+ 'skill.updated',
45
+ ]);
46
+
47
+ /** Whether a message may be left out of the transcript as quiet: never the owner's, an error, an approval or a card. */
48
+ export function mayBeQuiet(m: Pick<Message, 'role' | 'meta'>): boolean {
49
+ if (m.role === 'user') return isAutomaticWake(m);
50
+ if (m.role === 'bot') return true;
51
+ if (m.role !== 'event') return false;
52
+ const kind = m.meta?.kind ?? '';
53
+ return kind === 'routine.run' || TURN_ACTIVITY.has(kind);
54
+ }
55
+
56
+ /**
57
+ * The ids of the messages the transcript leaves out: those the server marked quiet (a quiet turn's wake-ups and what
58
+ * it did, and from chat history its older quiet replies with their wake-ups or run chip). The server decides from
59
+ * its turn records, so a page reads the same whichever messages are loaded with it.
60
+ */
61
+ export function quietMessageIds(messages: readonly Message[]): Set<string> {
62
+ const hidden = new Set<string>();
63
+ for (const m of messages) if (m.meta?.quiet && mayBeQuiet(m)) hidden.add(m.id);
64
+ return hidden;
65
+ }
66
+
67
+ /** A job's title as the owner sees it: one plain line, at most 80 characters, or null. Never given to a Boton. */
68
+ export function jobTitle(v: unknown): string | null {
69
+ if (typeof v !== 'string') return null;
70
+ const t = v.replace(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]+/gu, ' ').replace(/\s+/g, ' ').trim();
71
+ if (!t) return null;
72
+ const chars = [...t];
73
+ return chars.length > 80 ? `${chars.slice(0, 79).join('').trimEnd()}…` : t;
74
+ }
75
+
76
+ const WORK_TEXT: Record<WorkEventKind, string> = {
77
+ finished: 'finished',
78
+ needs_input: 'needs input',
79
+ failed: 'failed',
80
+ cancelled: 'was cancelled',
81
+ delegated_ended: 'delegated work ended',
82
+ };
83
+
84
+ /** A connector's name from its id ("oauth:fleet" is fleet). */
85
+ export function connectorName(id: string | undefined): string {
86
+ if (!id) return '';
87
+ return id.slice(id.indexOf(':') + 1);
88
+ }
89
+
90
+ /** "PR #12" for a pull request link, else null. */
91
+ export function pullRequestOf(link: string | null | undefined): string | null {
92
+ const m = /^https:\/\/[^/\s]+\/[^/\s]+\/[^/\s]+\/(?:pull|pulls|merge_requests)\/(\d{1,9})(?:[/?#]|$)/.exec(link ?? '');
93
+ return m ? `PR #${m[1]}` : null;
94
+ }
95
+
96
+ export interface WakeLabel {
97
+ /** What shows: never a job, run or message id. */
98
+ text: string;
99
+ /** The tooltip: may name ids. */
100
+ detail: string;
101
+ warn: boolean;
102
+ }
103
+
104
+ /**
105
+ * A connector's work update, as a short label: the connector, the job's title (or the pull request when the link is
106
+ * one), and what happened.
107
+ */
108
+ export function workLabel(work: WorkEvent | undefined, fallback = 'Work update'): WakeLabel {
109
+ if (!work) return { text: fallback, detail: fallback, warn: false };
110
+ const name = connectorName(work.connectorId) || 'A connector';
111
+ const pr = pullRequestOf(work.link);
112
+ const title = jobTitle(work.title);
113
+ const what = WORK_TEXT[work.kind] ?? 'changed';
114
+ // Delegated work ending says its own subject.
115
+ const subject = title ? `“${title}”${pr ? ` (${pr})` : ''}` : (pr ?? (work.kind === 'delegated_ended' ? '' : 'work'));
116
+ const detail = [`Job ${work.jobId}`, work.state ? `state ${work.state}` : null, work.unowned ? 'not known to be any Boton’s' : null].filter(Boolean).join(' · ');
117
+ return { text: `${name}: ${subject ? `${subject} ` : ''}${what}`, detail, warn: work.kind === 'failed' };
118
+ }
119
+
120
+ /** A routine run's label: its name, how it was started when not by its schedule, and a retry's attempt. */
121
+ export function routineLabel(meta: Message['meta']): WakeLabel {
122
+ const name = meta?.routineName?.trim() || 'a routine';
123
+ const how = meta?.trigger && meta.trigger !== 'schedule' ? ` · ${meta.trigger}` : '';
124
+ const attempt = (meta?.attempt ?? 1) > 1 ? ` · attempt ${meta?.attempt}` : '';
125
+ return { text: `Routine: ${name}${how}${attempt}`, detail: meta?.runId ? `Run ${meta.runId}` : `Routine: ${name}`, warn: false };
126
+ }
127
+
128
+ /**
129
+ * The chat's background line: how many routine turns and wake-ups ran in the last hour (and how many ended quiet),
130
+ * and when the latest was (`when` writes that time). Null when the Boton has never had one.
131
+ */
132
+ export function backgroundText(a: { lastHour: number; quietLastHour: number; lastAt: string | null }, when: (iso: string) => string): string | null {
133
+ if (!a.lastAt) return null;
134
+ const n = a.lastHour;
135
+ const count = n === 0 ? 'No background updates in the last hour' : `${n} background ${n === 1 ? 'update' : 'updates'} in the last hour`;
136
+ const quiet = n > 0 && a.quietLastHour > 0 ? (a.quietLastHour === n ? (n === 1 ? ', quiet' : ', all quiet') : `, ${a.quietLastHour} quiet`) : '';
137
+ return `${count}${quiet} · last ${when(a.lastAt)}`;
138
+ }
139
+
140
+ /** An automatic wake-up's label in the Boton's background activity: what woke it, never an id. */
141
+ export function wakeLabel(m: Pick<Message, 'text' | 'meta'>): WakeLabel {
142
+ const meta = m.meta;
143
+ const from = meta?.fromBotName?.trim() || 'Another Boton';
144
+ switch (meta?.kind) {
145
+ case 'work.event':
146
+ return workLabel(meta.work);
147
+ case 'ask':
148
+ return { text: `${from} asked`, detail: `${from} asked`, warn: false };
149
+ case 'ask.reply':
150
+ return { text: `${from} replied to an ask`, detail: `${from} replied to an ask`, warn: false };
151
+ case 'approval.decided': {
152
+ const what = meta.decision === 'allowed' ? 'Allowed' : meta.decision === 'denied' ? 'Denied' : 'Decided';
153
+ const title = meta.approvalTitle?.trim();
154
+ return { text: `${what}${title ? `: ${title}` : ' an approval'}`, detail: meta.approvalId ? `Approval ${meta.approvalId}` : 'Approval decided', warn: false };
155
+ }
156
+ case 'connector.granted': {
157
+ const text = /^Connector .{1,80} added$/.test(m.text) ? m.text : 'Got a connector it asked for';
158
+ return { text, detail: meta.connectorId ?? text, warn: false };
159
+ }
160
+ case 'reaction.followup':
161
+ return { text: `Follow-up to your ${meta.emoji ?? ''} reaction`.replace(' ', ' '), detail: 'Reaction follow-up', warn: false };
162
+ }
163
+ return { text: meta?.fromBotId ? `Message from ${from}` : 'Automatic wake-up', detail: 'Automatic wake-up', warn: false };
164
+ }
@@ -0,0 +1 @@
1
+ import{t as e}from"./react-C1iE8mnY.js";var t=e();function n({size:e=18,children:n,...r}){return(0,t.jsx)(`svg`,{width:e,height:e,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:1.8,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,...r,children:n})}var r=e=>(0,t.jsxs)(n,{...e,children:[(0,t.jsx)(`circle`,{cx:`11`,cy:`11`,r:`7`}),(0,t.jsx)(`path`,{d:`m20 20-3.5-3.5`})]}),i=e=>(0,t.jsx)(n,{...e,children:(0,t.jsx)(`path`,{d:`M12 5v14M5 12h14`})}),a=e=>(0,t.jsxs)(n,{...e,children:[(0,t.jsx)(`circle`,{cx:`12`,cy:`12`,r:`9`}),(0,t.jsx)(`path`,{d:`M12 7v5l3 2`})]}),o=e=>(0,t.jsx)(n,{...e,children:(0,t.jsx)(`path`,{d:`M12 19V5M5 12l7-7 7 7`})}),s=e=>(0,t.jsx)(n,{...e,children:(0,t.jsx)(`rect`,{x:`7`,y:`7`,width:`10`,height:`10`,rx:`1.5`,fill:`currentColor`,stroke:`none`})}),c=e=>(0,t.jsx)(n,{...e,children:(0,t.jsx)(`path`,{d:`M6 6l12 12M18 6 6 18`})}),l=e=>(0,t.jsxs)(n,{...e,children:[(0,t.jsx)(`circle`,{cx:`12`,cy:`12`,r:`3`}),(0,t.jsx)(`path`,{d:`M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z`})]}),u=e=>(0,t.jsxs)(n,{...e,children:[(0,t.jsx)(`rect`,{x:`3`,y:`4`,width:`18`,height:`16`,rx:`2`}),(0,t.jsx)(`path`,{d:`M15 4v16`})]}),d=e=>(0,t.jsx)(n,{...e,children:(0,t.jsx)(`path`,{d:`m15 18-6-6 6-6`})}),f=e=>(0,t.jsxs)(n,{...e,children:[(0,t.jsx)(`rect`,{x:`9`,y:`9`,width:`12`,height:`12`,rx:`2`}),(0,t.jsx)(`path`,{d:`M5 15V5a2 2 0 0 1 2-2h10`})]}),p=e=>(0,t.jsx)(n,{...e,children:(0,t.jsx)(`path`,{d:`M7 5v14l11-7z`})}),m=e=>(0,t.jsx)(n,{...e,children:(0,t.jsx)(`path`,{d:`M8 5v14M16 5v14`})}),h=e=>(0,t.jsx)(n,{...e,children:(0,t.jsx)(`path`,{d:`M4 7h16M10 11v6M14 11v6M6 7l1 13h10l1-13M9 7V4h6v3`})}),g=e=>(0,t.jsx)(n,{...e,children:(0,t.jsx)(`path`,{d:`m5 12.5 4.5 4.5L19 7.5`})}),_=e=>(0,t.jsxs)(n,{...e,children:[(0,t.jsx)(`path`,{d:`M4 20h4L19 9l-4-4L4 16z`}),(0,t.jsx)(`path`,{d:`m14 6 4 4`})]}),v=e=>(0,t.jsxs)(n,{...e,children:[(0,t.jsx)(`path`,{d:`M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 0 1 2 17c.01-.7.2-1.4.57-2`}),(0,t.jsx)(`path`,{d:`m6 17 3.13-5.78c.53-.97.1-2.18-.5-3.1a4 4 0 1 1 6.89-4.06`}),(0,t.jsx)(`path`,{d:`m12 6 3.13 5.73C15.66 12.7 16.9 13 18 13a4 4 0 0 1 0 8`})]}),y=e=>(0,t.jsxs)(n,{...e,children:[(0,t.jsx)(`rect`,{x:`4`,y:`4`,width:`16`,height:`16`,rx:`3`}),(0,t.jsx)(`circle`,{cx:`9`,cy:`9`,r:`1`,fill:`currentColor`}),(0,t.jsx)(`circle`,{cx:`15`,cy:`15`,r:`1`,fill:`currentColor`}),(0,t.jsx)(`circle`,{cx:`15`,cy:`9`,r:`1`,fill:`currentColor`}),(0,t.jsx)(`circle`,{cx:`9`,cy:`15`,r:`1`,fill:`currentColor`})]}),b=e=>(0,t.jsxs)(n,{...e,children:[(0,t.jsx)(`path`,{d:`M12 5a3 3 0 0 0-5.9.6A3 3 0 0 0 4 11a3 3 0 0 0 1 5.2A3 3 0 0 0 12 18z`}),(0,t.jsx)(`path`,{d:`M12 5a3 3 0 0 1 5.9.6A3 3 0 0 1 20 11a3 3 0 0 1-1 5.2A3 3 0 0 1 12 18z`})]}),x=e=>(0,t.jsxs)(n,{...e,children:[(0,t.jsx)(`circle`,{cx:`12`,cy:`12`,r:`9`}),(0,t.jsx)(`path`,{d:`M12 8v5M12 16h.01`})]}),S=e=>(0,t.jsx)(n,{...e,children:(0,t.jsx)(`path`,{d:`M20 11a8 8 0 0 0-14.8-4M4 4v4h4M4 13a8 8 0 0 0 14.8 4M20 20v-4h-4`})}),C=e=>(0,t.jsx)(n,{...e,children:(0,t.jsx)(`path`,{d:`m21 11-8.6 8.6a5 5 0 0 1-7-7l8.6-8.6a3.3 3.3 0 0 1 4.7 4.7l-8.6 8.6a1.7 1.7 0 0 1-2.4-2.4L15.6 7`})}),w=e=>(0,t.jsx)(n,{...e,children:(0,t.jsx)(`path`,{d:`M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z`})}),T=e=>(0,t.jsxs)(n,{...e,children:[(0,t.jsx)(`path`,{d:`M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z`}),(0,t.jsx)(`path`,{d:`M14 3v5h5`})]}),E=e=>(0,t.jsx)(n,{...e,children:(0,t.jsx)(`path`,{d:`M12 4v11M7 10l5 5 5-5M5 20h14`})}),D=e=>(0,t.jsx)(n,{...e,children:(0,t.jsx)(`path`,{d:`M14 4h6v6M20 4l-9 9M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5`})}),O=e=>(0,t.jsxs)(n,{...e,children:[(0,t.jsx)(`path`,{d:`M9 4h6l-1 6 3 3v2H7v-2l3-3z`}),(0,t.jsx)(`path`,{d:`M12 15v5`})]}),k=e=>(0,t.jsx)(n,{...e,children:(0,t.jsx)(`path`,{d:`m12 3.5 2.6 5.3 5.9.9-4.3 4.1 1 5.8-5.2-2.7-5.2 2.7 1-5.8-4.3-4.1 5.9-.9z`})}),A=e=>(0,t.jsxs)(n,{...e,children:[(0,t.jsx)(`path`,{d:`M5 5a2 2 0 0 1 2-2h12v14H7a2 2 0 0 0-2 2zM5 19a2 2 0 0 0 2 2h12v-4`}),(0,t.jsx)(`path`,{d:`M9 7h6M9 10.5h4`})]}),j=e=>(0,t.jsxs)(n,{...e,children:[(0,t.jsx)(`path`,{d:`M12 3 5 6v5.5c0 4.3 2.9 8 7 9.5 4.1-1.5 7-5.2 7-9.5V6z`}),(0,t.jsx)(`path`,{d:`m9 12 2.2 2.2L15.5 10`})]}),M=e=>(0,t.jsx)(n,{...e,children:(0,t.jsx)(`path`,{d:`m6 9 6 6 6-6`})}),N=e=>(0,t.jsxs)(n,{...e,children:[(0,t.jsx)(`circle`,{cx:`12`,cy:`12`,r:`9`}),(0,t.jsx)(`path`,{d:`M8.5 14.5a4.5 4.5 0 0 0 7 0`}),(0,t.jsx)(`path`,{d:`M9 9.5h.01M15 9.5h.01`})]}),P=e=>(0,t.jsxs)(n,{...e,children:[(0,t.jsx)(`circle`,{cx:`5`,cy:`12`,r:`1.4`,fill:`currentColor`,stroke:`none`}),(0,t.jsx)(`circle`,{cx:`12`,cy:`12`,r:`1.4`,fill:`currentColor`,stroke:`none`}),(0,t.jsx)(`circle`,{cx:`19`,cy:`12`,r:`1.4`,fill:`currentColor`,stroke:`none`})]});export{s as A,i as C,j as D,o as E,v as M,N as O,p as S,r as T,P as _,g as a,m as b,c,E as d,_ as f,l as g,w as h,b as i,h as j,k,f as l,T as m,d as n,M as o,D as p,A as r,a as s,x as t,y as u,u as v,S as w,O as x,C as y};
@@ -0,0 +1 @@
1
+ import{i as e,t}from"./react-C1iE8mnY.js";import{L as n,_ as r,l as ee,u as i,y as a}from"./index-Dhwn-rf0.js";import{E as o,T as te,b as ne,i as s}from"./util-Cx5feSJu.js";import{d as re,f as ie,g as ae,u as oe}from"./Setup-C-3P7Mtn.js";import{M as se,c as ce,s as le}from"./Icons-nytVAa9g.js";import{i as ue,n as de,t as fe}from"./StarterSetup-Ce2V1_Cs.js";var c=e(),l=t(),u=[`Juniper`,`Rowan`,`Wren`,`Marlow`,`Hazel`,`Odile`,`Tamsin`,`Ines`,`Sorrel`,`Linden`,`Indigo`,`Fennel`,`Kestrel`,`Juno`,`Pax`,`Teo`];function d({groupId:e,onClose:t,onCreated:d}){let f=a(e=>e.providers),p=a(t=>e?t.sidebar?.groups.find(t=>t.id===e):void 0),m=f.find(e=>e.available&&e.id!==`mock`)??f.find(e=>e.available)??f[0],[h,g]=(0,c.useState)(``),[_,v]=(0,c.useState)(``),[y,b]=(0,c.useState)(``),[x,S]=(0,c.useState)(n),[C,w]=(0,c.useState)(m?.id??`claude-cli`),[T,E]=(0,c.useState)(m?.defaultModel??``),[D,O]=(0,c.useState)(``),[k,A]=(0,c.useState)(`workspace`),j=f.find(e=>e.id===C)?.workspaceProblem,[M,N]=(0,c.useState)(!1),[P,F]=(0,c.useState)(null),[I]=(0,c.useState)(()=>u[Math.floor(Math.random()*u.length)]),[L,R]=(0,c.useState)(null),[z,pe]=(0,c.useState)(null),[B,me]=(0,c.useState)(null),[V,H]=(0,c.useState)(!1),[U,W]=(0,c.useState)(null),[G,K]=(0,c.useState)({}),[q,J]=(0,c.useState)(null),[he,Y]=(0,c.useState)(0),[X,ge]=(0,c.useState)(null),[_e,Z]=(0,c.useState)(!1);(0,c.useEffect)(()=>{j&&A(`host`)},[j,C,B]);let Q=!!B?.needs;(0,c.useEffect)(()=>{r.loadProviders().catch(()=>{}),o.templates().then(R).catch(e=>pe(ne(e)))},[]);let $=e=>{me(e),F(null),g(e?.bot.name??``),v(e?.bot.title??``),b(e?.bot.instructions??``),S(e?.bot.look??n());let t=e?.bot.provider?f.find(t=>t.id===e.bot.provider):void 0;w(t?.id??m?.id??`claude-cli`),E(t?e.bot.model:m?.defaultModel??``),O(t?e.bot.effort??``:``),A((t??m)?.workspaceProblem?`host`:e?.needs?.access??e?.bot.access??`workspace`),K(Object.fromEntries((e?.needs?.connectors??[]).map(e=>[e.slot,null]))),J(null),H(!1)},ve=async()=>{N(!0),F(null);try{let e={name:h.trim()||I,title:_,instructions:y,look:x,provider:C,model:T.trim(),effort:ae(f.find(e=>e.id===C),T,D),access:k};if(B?.needs){if(!q||q.blocked.length)throw Error(`Review what it grants first`);let t=await r.createBot({...e,...p?{groupId:p.id}:{},templateId:B.id,starterConnectors:G,reviewKey:q.reviewKey});ge(t),N(!1);return}d(await r.createBot(B?{...e,...p?{groupId:p.id}:B.bot.group?{group:B.bot.group}:{},...B.bot.timezone?{timezone:B.bot.timezone}:{},templateId:B.id,startRoutines:V}:{...e,...p&&{groupId:p.id}}))}catch(e){F(e.message),e instanceof te&&e.status===409&&Y(e=>e+1),N(!1)}};return X&&B?(0,l.jsx)(i,{title:`Set up ${X.name}`,onClose:()=>d(X),wide:!0,children:(0,l.jsx)(de,{bot:X,template:B,onDone:()=>d(X)})}):(0,l.jsxs)(i,{title:`New Boton`,onClose:t,wide:!0,children:[(0,l.jsxs)(`form`,{className:`new-bot`,onSubmit:e=>{e.preventDefault(),ve()},children:[(0,l.jsxs)(`div`,{className:`template-picker`,role:`group`,"aria-label":`Start from`,children:[(0,l.jsx)(`span`,{className:`field-label`,children:`Start from`}),(0,l.jsxs)(`div`,{className:`template-chips`,children:[(0,l.jsx)(`button`,{type:`button`,className:s(`template-chip`,!B&&`on`),"aria-pressed":!B,onClick:()=>$(null),children:`Blank`}),L?.map(e=>(0,l.jsxs)(`span`,{className:s(`template-chip`,B?.id===e.id&&`on`),children:[(0,l.jsx)(`button`,{type:`button`,"aria-pressed":B?.id===e.id,title:e.description,onClick:()=>$(e),children:e.name}),!e.builtin&&(0,l.jsx)(`button`,{type:`button`,className:`template-remove`,"aria-label":`Delete template ${e.name}`,title:`Delete template`,onClick:()=>W(e),children:(0,l.jsx)(ce,{size:12})})]},e.id)),!L&&!z&&(0,l.jsx)(`span`,{className:`hint`,children:`Loading templates…`}),z&&(0,l.jsxs)(`span`,{className:`hint warn`,children:[`Couldn't load templates: `,z]})]}),B&&(0,l.jsx)(`p`,{className:`hint`,children:B.description})]}),(0,l.jsx)(oe,{look:x,onChange:S}),(0,l.jsxs)(`div`,{className:`look-row two`,children:[(0,l.jsxs)(`label`,{children:[(0,l.jsx)(`span`,{className:`field-label`,children:`Name`}),(0,l.jsx)(`input`,{"data-autofocus":!0,value:h,onChange:e=>g(e.target.value),placeholder:I,maxLength:60})]}),(0,l.jsxs)(`label`,{children:[(0,l.jsx)(`span`,{className:`field-label`,children:`Role`}),(0,l.jsx)(`input`,{value:_,onChange:e=>v(e.target.value),placeholder:`e.g. Morning briefer`,maxLength:80})]})]}),(0,l.jsxs)(`label`,{children:[(0,l.jsx)(`span`,{className:`field-label`,children:`Instructions (optional)`}),(0,l.jsx)(`textarea`,{value:y,onChange:e=>b(e.target.value),rows:4,placeholder:`What should this Boton own? You can also just tell it in the chat.`})]}),(0,l.jsx)(ie,{providers:f,provider:C,model:T,effort:D,onChange:(e,t)=>{w(e),E(t)},onEffort:O}),(0,l.jsx)(re,{problem:j,value:k,onChange:A}),Q&&B&&(0,l.jsx)(fe,{template:B,provider:C,access:k,slots:G,onSlots:K,onReview:J,refresh:he,onConnect:()=>Z(!0)}),B&&!Q&&B.routines.length>0&&(0,l.jsxs)(`div`,{className:`template-routines`,children:[(0,l.jsx)(`span`,{className:`field-label`,children:`Routines from the template`}),(0,l.jsx)(`ul`,{children:B.routines.map((e,t)=>(0,l.jsxs)(`li`,{children:[(0,l.jsx)(le,{size:14}),(0,l.jsx)(`span`,{children:e.name}),(0,l.jsxs)(`span`,{className:`hint`,children:[e.scheduleText??`No schedule`,e.webhook&&(0,l.jsxs)(l.Fragment,{children:[` · `,(0,l.jsx)(se,{size:12}),` `,e.webhookSource===`generic`?`webhook`:`webhook from ${e.webhookSource}`,` (a new URL and key`,e.webhookSource!==`generic`&&e.webhookSource!==`github`?`; save its signing secret again`:``,`)`]})]})]},`${t}-${e.name}`))}),(0,l.jsxs)(`label`,{className:`check`,children:[(0,l.jsx)(`input`,{type:`checkbox`,checked:V,onChange:e=>H(e.target.checked)}),(0,l.jsx)(`span`,{children:`Turn them on now (otherwise they start paused)`})]})]}),p&&(0,l.jsxs)(`p`,{className:`hint`,children:[`It goes in the group `,(0,l.jsx)(`strong`,{children:p.name}),`.`]}),P&&(0,l.jsx)(`p`,{className:`form-error`,children:P}),(0,l.jsxs)(`div`,{className:`form-actions`,children:[(0,l.jsx)(`button`,{type:`button`,className:`btn ghost`,onClick:t,children:`Cancel`}),Q?(0,l.jsx)(`button`,{type:`submit`,className:`btn primary`,disabled:M||!q||q.blocked.length>0,children:M?`Installing…`:`Install ${h.trim()||I}, paused`}):(0,l.jsx)(`button`,{type:`submit`,className:`btn primary`,disabled:M,children:M?`Creating…`:`Create ${h.trim()||I}`})]})]}),_e&&(0,l.jsx)(ue,{onClose:()=>{Z(!1),Y(e=>e+1)}}),U&&(0,l.jsx)(ee,{title:`Delete the template “${U.name}”?`,confirmLabel:`Delete template`,what:`delete the template`,danger:!0,onConfirm:async()=>{await o.deleteTemplate(U.id),R(e=>e?.filter(e=>e.id!==U.id)??null),B?.id===U.id&&$(null)},onClose:()=>W(null),children:(0,l.jsx)(`p`,{children:`New Botons can no longer start from it. Botons already made from it are not affected.`})})]})}export{d as NewBotDialog};
@@ -0,0 +1 @@
1
+ import{i as e,t}from"./react-C1iE8mnY.js";import{_ as n,k as r}from"./index-Dhwn-rf0.js";import{E as i,T as a}from"./util-Cx5feSJu.js";var o=e(),s=t(),c=[[/Edg\//,`Edge`],[/Firefox\//,`Firefox`],[/Chrome\/|CriOS\//,`Chrome`],[/Safari\//,`Safari`]],l=[[/iPhone/,`iPhone`],[/iPad/,`iPad`],[/Android/,`Android`],[/Mac OS X/,`Mac`],[/Windows/,`Windows`],[/Linux/,`Linux`]];function u(){let e=navigator.userAgent,t=c.find(([t])=>t.test(e))?.[1]??`Browser`,n=l.find(([t])=>t.test(e))?.[1];return n?`${t} on ${n}`:t}var d=2e3;function f({status:e}){let[t,c]=(0,o.useState)(null),[l,f]=(0,o.useState)(!1),[p,m]=(0,o.useState)(!1),[h,g]=(0,o.useState)(null),_=e.ownerPairing,v=(0,o.useRef)(!1),y=(0,o.useCallback)(async()=>{g(null),f(!1);try{c(await i.pairRequest(u()))}catch(e){g(e.message)}},[]);(0,o.useEffect)(()=>{y()},[y]);let b=async e=>{await i.pair({secret:e,label:u()}),v.current=!0,await n.init()};(0,o.useEffect)(()=>{if(!t||l)return;let e=!1,n=async()=>{if(!(e||v.current)){try{await b(t.secret);return}catch(e){if(e instanceof a&&e.status===409)Date.parse(t.expiresAt)<=Date.now()&&f(!0);else if(e instanceof a&&e.status===400){f(!0);return}else g(e.message)}e||(r=setTimeout(n,d))}},r=setTimeout(n,d);return()=>{e=!0,clearTimeout(r)}},[t,l]);let x=async()=>{m(!0),g(null);try{await b()}catch(e){g(e.message),m(!1)}};return(0,s.jsx)(`main`,{className:`pair-screen`,children:(0,s.jsxs)(`div`,{className:`pair-card`,children:[(0,s.jsx)(r,{size:56}),(0,s.jsx)(`h1`,{children:e.claimed?`Pair this device`:`Claim this server`}),e.claimed?(0,s.jsx)(`p`,{className:`welcome-copy`,children:`Botons only answers devices you have paired, so nobody else can drive your Botons, and they can't drive each other.`}):(0,s.jsxs)(`p`,{className:`welcome-copy`,children:[`This Botons server has no owner yet. The first device you approve becomes its owner's device. Prove you control the server by approving this device on `,(0,s.jsx)(`span`,{"data-server-hostname":!0,children:e.hostname}),`.`]}),_&&(0,s.jsxs)(`div`,{className:`pair-owner`,children:[(0,s.jsxs)(`button`,{className:`btn primary`,onClick:()=>void x(),disabled:p,autoFocus:!0,children:[`Pair as `,_.login]}),(0,s.jsxs)(`p`,{className:`hint`,children:[(0,s.jsx)(`strong`,{children:_.node}),` is signed in to your tailnet as `,_.login,`. Or approve the code below.`]})]}),t&&!l&&(0,s.jsxs)(`div`,{className:`pair-code-shown`,"aria-live":`polite`,children:[(0,s.jsx)(`span`,{className:`field-label`,children:`This device's code`}),(0,s.jsx)(`code`,{className:`big-code`,children:t.code}),(0,s.jsxs)(`p`,{className:`hint`,children:[e.claimed?`Approve it from a paired device (Settings → Devices → Approve a device), or on `:`Approve it on `,(0,s.jsx)(`span`,{"data-server-hostname":!0,children:e.hostname}),` with `,(0,s.jsx)(`code`,{children:t.approveCommand}),`. This page signs in by itself once you do; the code lasts 15 minutes.`]})]}),l&&(0,s.jsxs)(`div`,{className:`pair-code-shown`,children:[(0,s.jsx)(`p`,{className:`hint`,children:`That code expired before it was approved.`}),(0,s.jsx)(`button`,{type:`button`,className:`btn`,onClick:()=>void y(),children:`Get a new code`})]}),h&&(0,s.jsx)(`p`,{className:`form-error`,children:h}),(0,s.jsx)(`p`,{className:`hint`,children:`Only an approval from you pairs this device: seeing the code gets nobody in, so it is safe to leave this page open. Approve only a code you read off this screen.`})]})})}export{f as PairScreen};
@@ -0,0 +1,4 @@
1
+ import{i as e,r as t,t as n}from"./react-C1iE8mnY.js";import{A as r,B as i,C as a,E as o,F as s,L as c,P as l,R as u,T as d,_ as f,a as p,b as m,g as h,h as g,i as _,n as v,o as y,p as b,r as x,t as S,v as C}from"./util-Cx5feSJu.js";import{M as w,_ as T,d as E,g as D,h as O,i as k,l as A,m as j,r as M,t as N,u as P,y as F}from"./index-Dhwn-rf0.js";import{S as I,a as L,b as R,c as ee,d as te,f as z,i as B,l as ne,m as V,n as re,o as ie,r as ae,t as oe,u as se,x as H}from"./Steps-CKtB3GAM.js";import{_ as ce,c as le,d as U,f as ue,h as de,j as W,l as fe,m as pe,o as me,p as he,w as ge,x as _e}from"./Icons-nytVAa9g.js";import{_ as ve,a as G,b as K,c as ye,d as be,f as xe,g as Se,h as q,i as J,l as Ce,m as we,n as Te,o as Y,p as Ee,r as X,s as De,t as Oe,u as ke,y as Ae}from"./SettingRows-CvLomtuo.js";import{g as je,p as Me,u as Ne}from"./Setup-C-3P7Mtn.js";import{a as Pe}from"./StarterSetup-Ce2V1_Cs.js";var Z=e(),Q=n();function Fe({bot:e,focus:t,focusAt:n}){let[r,i]=(0,Z.useState)(`workspace`),[s,c]=(0,Z.useState)(``),[l,u]=(0,Z.useState)(null),[d,f]=(0,Z.useState)(null),[h,g]=(0,Z.useState)(0),[v,y]=(0,Z.useState)(null),[x,S]=(0,Z.useState)(null),[w,T]=(0,Z.useState)(!1),[E,D]=(0,Z.useState)(!1),[O,k]=(0,Z.useState)(new Set),[j,M]=(0,Z.useState)(null),[P,I]=(0,Z.useState)(null),L=F(e=>e.settings);(0,Z.useEffect)(()=>{let t=!0;return f(null),(r===`shared`?o.sharedFiles(s):o.files(e.id,s)).then(e=>t&&u({root:r,dir:s,listing:e}),e=>{t&&(u(null),f(m(e)),y(null))}),()=>{t=!1}},[e.id,r,s,h]),(0,Z.useEffect)(()=>{let t=!0;return o.botStorage(e.id).then(e=>t&&I(e),()=>t&&I(null)),()=>{t=!1}},[e.id,h]),(0,Z.useEffect)(()=>{if(v===null||l?.root!==r||l.dir!==se(v))return;let e=ie(v),t=l.listing.entries.find(t=>t.name===e);y(null),t?.isDir?(S(null),c(t.path)):S(t??{path:v,name:e,size:null,mtime:null})},[v,l,r]);let R=()=>{D(!1),k(new Set)},te=(e,t=r)=>{i(t),T(!1),R(),S(null),y(e),c(se(e))};(0,Z.useEffect)(()=>{t!==null&&te(t,`workspace`)},[t,n]);let z=e=>{T(!1),R(),S(null),y(null),e===s?g(e=>e+1):c(e)},B=()=>g(e=>e+1),ne=e=>{if(e===r)return z(``);T(!1),R(),S(null),y(null),c(``),i(e)},V=r===`shared`,re=l?.root===r&&l.dir===s?l.listing:null,ae=ee(s,V?`Shared folder`:`Workspace`),oe=!V&&s===`inbox`,le=e=>!V&&e.path===`inbox`,U=V?P?.sharedTrashItems:P?.trashItems,ue=L?.inboxCleanupDays??null,fe=t=>{if(!t.length)return;let n=async()=>{let n=t.map(e=>e.path),{trashed:r}=await(V?o.deleteShared(n):o.deleteFiles(e.id,n));R(),x&&t.some(e=>e.path===x.path)&&S(null),B(),a({kind:`info`,text:r.length===1?`Moved ${r[0].name} to the trash`:`Moved ${r.length} items to the trash`,action:{label:`Undo`,run:()=>C(`put them back`,()=>Promise.all(r.map(e=>o.restoreTrash(e.id))).then(B))}})};t.length===1&&!t[0].isDir?C(`delete ${t[0].name}`,n):M({title:t.length===1?`Delete the folder ${t[0].name}?`:`Delete ${t.length} items?`,body:`${t.length===1?`It goes`:`They go`} to the trash${t.some(e=>e.isDir)?`, folders with everything in them`:``}.${V?` Every Boton that uses the shared folder loses it too.`:``} You can restore ${t.length===1?`it`:`them`} from Trash for 30 days.`,label:`Move to trash`,what:`delete them`,run:n})},me=()=>M({title:`Clear the inbox?`,body:`Everything in ${e.name}'s inbox goes to the trash, except files you keep. You can restore them from Trash for 30 days.`,label:`Clear inbox`,what:`clear the inbox`,run:async()=>{let{trashed:t,kept:n}=await o.clearInbox(e.id);B();let r=n?` (${n} kept)`:``;a({kind:`info`,text:t.length?`Moved ${t.length===1?`1 item`:`${t.length} items`} to the trash${r}`:`Nothing to clear${r}`,...t.length&&{action:{label:`Undo`,run:()=>C(`put them back`,()=>Promise.all(t.map(e=>o.restoreTrash(e.id))).then(B))}}})}}),he=(t,n)=>void C(n?`keep ${t.name}`:`stop keeping ${t.name}`,async()=>{await o.pinInbox(e.id,t.path,n),B()}),ve=e=>k(t=>{let n=new Set(t);return n.delete(e)||n.add(e),n}),G=re?.entries??[],K=G.filter(e=>O.has(e.path)),ye=G.filter(e=>!le(e));return(0,Q.jsxs)(`div`,{className:`tab-body`,children:[(0,Q.jsxs)(`div`,{className:`segmented files-roots`,role:`group`,"aria-label":`Folder to browse`,children:[(0,Q.jsxs)(`button`,{type:`button`,className:_(!V&&`on`),"aria-pressed":!V,onClick:()=>ne(`workspace`),children:[e.name,`'s workspace`]}),(0,Q.jsx)(`button`,{type:`button`,className:_(V&&`on`),"aria-pressed":V,onClick:()=>ne(`shared`),children:`Shared folder`})]}),(0,Q.jsxs)(`div`,{className:`files-bar`,children:[w?(0,Q.jsxs)(`nav`,{className:`crumbs`,"aria-label":`Folder`,children:[(0,Q.jsx)(`span`,{className:`crumb`,children:(0,Q.jsx)(`button`,{type:`button`,className:`link-btn`,onClick:()=>z(s),children:ae[ae.length-1].name})}),(0,Q.jsxs)(`span`,{className:`crumb`,children:[(0,Q.jsx)(`span`,{className:`crumb-sep`,children:`/`}),(0,Q.jsx)(`span`,{className:`crumb-here`,"aria-current":`page`,children:`Trash`})]})]}):(0,Q.jsxs)(`nav`,{className:`crumbs`,"aria-label":`Folder`,children:[ae.map((e,t)=>(0,Q.jsxs)(`span`,{className:`crumb`,children:[t>0&&(0,Q.jsx)(`span`,{className:`crumb-sep`,children:`/`}),t===ae.length-1&&!x?(0,Q.jsx)(`span`,{className:`crumb-here`,"aria-current":`page`,children:e.name}):(0,Q.jsx)(`button`,{type:`button`,className:`link-btn`,onClick:()=>z(e.path),children:e.name})]},e.path)),x&&(0,Q.jsxs)(`span`,{className:`crumb`,children:[(0,Q.jsx)(`span`,{className:`crumb-sep`,children:`/`}),(0,Q.jsx)(`span`,{className:`crumb-here`,"aria-current":`page`,children:x.name})]})]}),(0,Q.jsxs)(`div`,{className:`files-bar-actions`,children:[!w&&!x&&G.length>0&&(0,Q.jsx)(`button`,{type:`button`,className:_(`btn small ghost`,E&&`on`),"aria-pressed":E,onClick:()=>E?R():D(!0),children:E?`Done`:`Select`}),(0,Q.jsxs)(`button`,{type:`button`,className:_(`icon-btn small files-trash-btn`,w&&`on`),"aria-label":U?`Trash, ${U} item${U===1?``:`s`}`:`Trash`,title:V?`The shared folder's trash`:`Trash`,"aria-pressed":w,onClick:()=>{R(),S(null),T(e=>!e)},children:[(0,Q.jsx)(W,{size:15}),!!U&&(0,Q.jsx)(`span`,{className:`files-count`,children:U})]}),(0,Q.jsx)(`button`,{type:`button`,className:`icon-btn small`,"aria-label":`Refresh`,title:`Refresh`,onClick:B,children:(0,Q.jsx)(ge,{size:15})})]})]}),w?(0,Q.jsx)(ze,{bot:e,shared:V,onChanged:B,onOpenPath:e=>te(e),confirm:M},`${r}:${h}`):x&&V?(0,Q.jsx)(Be,{file:x,onDelete:()=>fe([{path:x.path,name:x.name,isDir:!1}])}):x?(0,Q.jsx)(Ve,{bot:e,file:x,onOpenPath:te,onDelete:()=>fe([{path:x.path,name:x.name,isDir:!1}])},`${x.path}:${h}`):d?(0,Q.jsxs)(`p`,{className:`load-error`,role:`alert`,children:[(0,Q.jsxs)(`span`,{children:[`Couldn't open this folder: `,d]}),(0,Q.jsxs)(`button`,{type:`button`,className:`link-btn`,onClick:()=>z(``),children:[`Back to `,V?`the shared folder`:`the workspace`]})]}):re?(0,Q.jsxs)(Q.Fragment,{children:[oe&&(0,Q.jsxs)(`div`,{className:`files-tools`,children:[(0,Q.jsx)(`span`,{className:`hint`,children:ue===null?`Files you keep are marked with a pin.`:`Files older than ${ue} days go to the trash each day, unless you keep them.`}),(0,Q.jsx)(`button`,{type:`button`,className:`btn small`,onClick:me,disabled:!G.some(e=>!e.pinned),children:`Clear inbox`})]}),G.length===0?(0,Q.jsx)(`p`,{className:`empty-note`,children:s?`This folder is empty.`:V?`Nothing in the shared folder yet. Botons outside the Workspace sandbox can read and write it.`:`Nothing here yet. Files ${e.name} makes, and files you send it, show up here.`}):(0,Q.jsx)(`ul`,{className:_(`file-list`,E&&`selecting`),"aria-label":`Files`,"aria-multiselectable":E||void 0,children:G.map(e=>{let t=le(e),n=O.has(e.path);return(0,Q.jsxs)(`li`,{className:_(`file-item`,n&&`picked`),children:[E&&(t?(0,Q.jsx)(`span`,{className:`file-check`,"aria-hidden":`true`}):(0,Q.jsx)(`input`,{type:`checkbox`,className:`file-check`,checked:n,onChange:()=>ve(e.path),"aria-label":`Select ${e.name}`})),(0,Q.jsxs)(`button`,{type:`button`,className:`file-row`,"aria-pressed":E&&!t?n:void 0,onClick:()=>E?!t&&ve(e.path):e.isDir?z(e.path):S(e),children:[e.isDir?(0,Q.jsx)(de,{size:18,className:`file-icon dir`}):(0,Q.jsx)(pe,{size:18,className:`file-icon`}),(0,Q.jsxs)(`span`,{className:`file-name-wrap`,children:[(0,Q.jsx)(`span`,{className:`file-name`,children:e.name}),e.pinned&&(0,Q.jsxs)(`span`,{className:`file-kept`,title:`Kept: the inbox cleanup and Clear inbox leave it`,children:[(0,Q.jsx)(_e,{size:13}),(0,Q.jsx)(`span`,{className:`sr-only`,children:`, kept`})]})]}),(0,Q.jsxs)(`span`,{className:`file-meta`,title:p(e.mtime),children:[e.isDir?``:`${H(e.size)} · `,b(e.mtime)]})]}),!E&&t&&(0,Q.jsx)(`span`,{className:`file-menu-spacer`,"aria-hidden":`true`}),!E&&!t&&(0,Q.jsx)(N,{label:`Actions for ${e.name}`,className:`icon-btn small file-menu-btn`,entries:()=>[...oe?[{label:e.pinned?`Don't keep`:`Keep in inbox`,onSelect:()=>he(e,!e.pinned)}]:[],{label:`Select`,onSelect:()=>{D(!0),k(new Set([e.path]))}},`separator`,{label:`Delete`,danger:!0,onSelect:()=>fe([e])}],children:(0,Q.jsx)(ce,{size:16})})]},e.path)})}),re.truncated&&(0,Q.jsxs)(`p`,{className:`hint`,children:[`Showing the newest `,re.entries.length,`.`]}),E&&(0,Q.jsxs)(`div`,{className:`files-select-bar`,role:`toolbar`,"aria-label":`Selected files`,children:[(0,Q.jsx)(`span`,{className:`files-select-count`,"aria-live":`polite`,children:K.length?`${K.length} selected`:`Pick files and folders`}),(0,Q.jsx)(`button`,{type:`button`,className:`btn small ghost`,onClick:()=>k(K.length===ye.length?new Set:new Set(ye.map(e=>e.path))),children:K.length&&K.length===ye.length?`Select none`:`Select all`}),(0,Q.jsxs)(`button`,{type:`button`,className:`btn small danger-solid`,disabled:!K.length,onClick:()=>fe(K),children:[(0,Q.jsx)(W,{size:14}),` Delete`]})]}),s===``&&P&&(0,Q.jsx)(Ie,{storage:P,shared:V})]}):(0,Q.jsx)(`p`,{className:`muted`,children:`Loading…`}),j&&(0,Q.jsx)(A,{title:j.title,confirmLabel:j.label,what:j.what,danger:!0,onConfirm:j.run,onClose:()=>M(null),children:(0,Q.jsx)(`p`,{children:j.body})})]})}function Ie({storage:e,shared:t}){let n=e.partial?`at least `:``;return t?(0,Q.jsxs)(`p`,{className:`hint files-storage`,children:[`Shared folder `,n,H(e.sharedBytes),` · Its trash `,H(e.sharedTrashBytes)]}):(0,Q.jsxs)(`p`,{className:`hint files-storage`,children:[`Workspace `,n,H(e.workspaceBytes),` · Inbox `,H(e.inboxBytes),` · Trash `,H(e.trashBytes),` · Shared folder `,H(e.sharedBytes)]})}var Le={deleted:`deleted`,inbox_cleanup:`cleaned up`,inbox_cleared:`inbox cleared`};function Re(e){let t=Math.max(0,Math.ceil((Date.parse(e)-Date.now())/864e5));return t<=1?`deleted for good within a day`:`${t} days left`}function ze({bot:e,shared:t,onChanged:n,onOpenPath:r,confirm:i}){let[s,c]=(0,Z.useState)(null),[l,u]=(0,Z.useState)(null);(0,Z.useEffect)(()=>{let n=!0;return(t?o.sharedTrash():o.trash(e.id)).then(e=>n&&c(e),e=>n&&u(m(e))),()=>{n=!1}},[e.id,t]);let d=e=>void C(`restore ${e.name}`,async()=>{let t=await o.restoreTrash(e.id);n(),a({kind:`info`,text:t.folderGone?`The original folder is gone, so it was restored to ${t.path}`:t.renamed?`Restored to ${t.path}`:`Restored ${t.path}`,action:{label:`Show`,run:()=>r(t.path)}})}),f=e=>i({title:`Delete ${e.name} forever?`,body:`${e.isDir?`The folder and everything in it are`:`It is`} deleted for good. This can't be undone.`,label:`Delete forever`,what:`delete ${e.name}`,run:async()=>{await o.purgeTrash(e.id),n()}});return l?(0,Q.jsxs)(`p`,{className:`load-error`,role:`alert`,children:[`Couldn't open the trash: `,l]}):s?(0,Q.jsxs)(Q.Fragment,{children:[(0,Q.jsxs)(`div`,{className:`files-tools`,children:[(0,Q.jsxs)(`span`,{className:`hint`,children:[`Deleted files stay here `,30,` days, then are deleted for good.`]}),s.length>0&&(0,Q.jsx)(`button`,{type:`button`,className:`btn small`,onClick:()=>i({title:`Empty the trash?`,body:`${s?.length===1?`The item`:`All ${s?.length} items`} in ${t?`the shared folder's`:`${e.name}'s`} trash ${s?.length===1?`is`:`are`} deleted for good. This can't be undone.`,label:`Empty trash`,what:`empty the trash`,run:async()=>{await(t?o.emptySharedTrash():o.emptyTrash(e.id)),n()}}),children:`Empty trash`})]}),s.length===0?(0,Q.jsx)(`p`,{className:`empty-note`,children:`The trash is empty.`}):(0,Q.jsx)(`ul`,{className:`file-list`,"aria-label":`Trash`,children:s.map(e=>(0,Q.jsxs)(`li`,{className:`file-item trash-item`,children:[(0,Q.jsxs)(`div`,{className:`file-row static`,children:[e.isDir?(0,Q.jsx)(de,{size:18,className:`file-icon dir`}):(0,Q.jsx)(pe,{size:18,className:`file-icon`}),(0,Q.jsxs)(`span`,{className:`trash-text`,children:[(0,Q.jsx)(`span`,{className:`file-name`,children:e.name}),(0,Q.jsxs)(`span`,{className:`file-meta`,title:`Deleted ${p(e.deletedAt)}; deleted for good ${p(e.purgeAt)}`,children:[e.path.includes(`/`)?`${e.path.slice(0,e.path.lastIndexOf(`/`))}/ · `:``,H(e.size),` · `,Le[e.reason],` `,b(e.deletedAt),` · `,Re(e.purgeAt)]})]})]}),(0,Q.jsx)(`button`,{type:`button`,className:`btn small`,onClick:()=>d(e),disabled:e.botGone,title:e.botGone?`Its Boton was deleted`:void 0,children:`Restore`}),(0,Q.jsx)(N,{label:`More for ${e.name}`,className:`icon-btn small file-menu-btn`,entries:()=>[{label:`Delete forever`,danger:!0,onSelect:()=>f(e)}],children:(0,Q.jsx)(ce,{size:16})})]},e.id))})]}):(0,Q.jsx)(`p`,{className:`muted`,children:`Loading…`})}function Be({file:e,onDelete:t}){let n=I(e.name);return(0,Q.jsx)(`div`,{className:`file-preview`,children:(0,Q.jsxs)(`div`,{className:`file-card`,role:`group`,"aria-label":`${e.name} in the shared folder`,children:[(0,Q.jsx)(pe,{size:28,className:`file-card-icon`}),(0,Q.jsxs)(`div`,{className:`file-card-body`,children:[(0,Q.jsx)(`strong`,{className:`file-card-name`,children:e.name}),(0,Q.jsx)(`span`,{className:`hint`,title:e.mtime?p(e.mtime):void 0,children:[n===`application/octet-stream`?`Unknown type`:n,e.size===null?null:H(e.size),e.mtime?`changed ${b(e.mtime)}`:null].filter(Boolean).join(` · `)}),(0,Q.jsx)(`span`,{className:`file-card-reason`,children:`Shared folder files download rather than open here: any Boton can write them.`})]}),(0,Q.jsxs)(`div`,{className:`file-card-actions`,children:[(0,Q.jsxs)(`a`,{className:`btn small file-card-download`,href:V(e.path),download:e.name,children:[(0,Q.jsx)(U,{size:14}),` Download`]}),(0,Q.jsxs)(`button`,{type:`button`,className:`btn small`,onClick:t,children:[(0,Q.jsx)(W,{size:14}),` Delete`]})]})]})})}function Ve({bot:e,file:t,onOpenPath:n,onDelete:r}){let i=R(t.name),a=z(e.id,t.path),s=ne(t.name,t.size),c=!s&&(i===`markdown`||i===`text`),[l,u]=(0,Z.useState)(null),[d,f]=(0,Z.useState)(!1),h=Ue(e.id,t.path,!s&&i===`html`),g=h?.ok?h.link.size:h?.tooBig?null:t.size,v=g===t.size?t:{...t,size:g};(0,Z.useEffect)(()=>{if(!c)return;let n=!0;return o.fileText(e.id,t.path).then(e=>n&&u({ok:!0,text:e}),e=>n&&u({ok:!1,error:m(e)})),()=>{n=!1}},[e.id,t.path,c]);let y=[v.size===null?null:H(v.size),t.mtime?`changed ${b(t.mtime)}`:null].filter(Boolean).join(` · `),[x,S]=(0,Z.useState)(null),C=i===`html`?h?.ok&&!x?h.link.url:null:s?null:a;return(0,Q.jsxs)(`div`,{className:`file-preview`,children:[(0,Q.jsxs)(`div`,{className:`file-head`,children:[(0,Q.jsxs)(`div`,{className:`file-title`,children:[(0,Q.jsx)(`strong`,{children:t.name}),y&&(0,Q.jsx)(`span`,{className:`hint`,title:t.mtime?p(t.mtime):void 0,children:y})]}),(0,Q.jsxs)(`div`,{className:`file-actions`,children:[C&&(0,Q.jsx)(`a`,{className:`icon-btn small`,href:C,target:`_blank`,rel:`noreferrer noopener`,"aria-label":`Open in a new tab`,title:`Open in a new tab`,children:(0,Q.jsx)(he,{size:15})}),(0,Q.jsx)(`a`,{className:`icon-btn small`,href:z(e.id,t.path,!0),download:t.name,"aria-label":`Download`,title:`Download`,children:(0,Q.jsx)(U,{size:15})}),(0,Q.jsx)(`button`,{type:`button`,className:`icon-btn small`,"aria-label":`Delete`,title:`Delete (to the trash)`,onClick:r,children:(0,Q.jsx)(W,{size:15})})]})]}),s?(0,Q.jsx)(We,{bot:e,file:t,reason:s}):i===`image`||i===`svg`?d?(0,Q.jsx)(We,{bot:e,file:t,reason:i===`svg`?`Couldn't draw this SVG: it may be damaged, or not an SVG.`:`Couldn't load this image: it may be damaged, or not the kind its name says.`}):(0,Q.jsx)(`a`,{href:a,target:`_blank`,rel:`noreferrer noopener`,className:`file-image-link`,children:(0,Q.jsx)(`img`,{className:_(`file-image`,i===`svg`&&`svg`),src:a,alt:t.name,onError:()=>f(!0)})}):i===`html`?h?h.ok?x?(0,Q.jsx)(We,{bot:e,file:v,reason:x}):(0,Q.jsxs)(Q.Fragment,{children:[(0,Q.jsx)(He,{url:h.link.url,title:`Preview of ${t.name}`,onFail:S}),(0,Q.jsx)(`p`,{className:`hint`,children:`Shown in a sandbox: its scripts run, but can't read Botons' storage or pages, call /api, or fetch anything. It isn't cut off from the network: a script can still send what the page holds by loading another address in its frame (Botons then closes it) or over WebRTC.`})]}):(0,Q.jsx)(We,{bot:e,file:v,reason:h.error}):(0,Q.jsx)(`p`,{className:`muted`,children:`Loading…`}):i===`pdf`?(0,Q.jsxs)(`a`,{className:`btn small file-open`,href:a,target:`_blank`,rel:`noreferrer noopener`,children:[(0,Q.jsx)(he,{size:14}),` Open the PDF`]}):l?l.ok?i===`markdown`?(0,Q.jsx)(`div`,{className:`file-doc`,children:(0,Q.jsx)(D,{text:l.text,botId:e.id,workspace:e.workspace,dir:se(t.path),onOpenFile:n})}):(0,Q.jsx)(`pre`,{className:_(`file-text`,!l.text&&`empty`),children:l.text||`(empty file)`}):(0,Q.jsxs)(`p`,{className:`load-error`,role:`alert`,children:[`Couldn't open it: `,l.error]}):(0,Q.jsx)(`p`,{className:`muted`,children:`Loading…`})]})}function He({url:e,title:t,onFail:n}){let r=(0,Z.useRef)(null),i=(0,Z.useRef)(0);return(0,Z.useEffect)(()=>{let e=e=>{if(e.source!==r.current?.contentWindow)return;let t=te(e.data);t&&n(t)};return window.addEventListener(`message`,e),()=>window.removeEventListener(`message`,e)},[n]),(0,Q.jsx)(`iframe`,{ref:r,className:`file-frame`,src:e,sandbox:`allow-scripts`,referrerPolicy:`no-referrer`,title:t,onLoad:()=>{i.current+=1,i.current>1&&n(B)}})}function Ue(e,t,n){let[r,i]=(0,Z.useState)(null);return(0,Z.useEffect)(()=>{if(!n)return;let r=!0;return o.filePreview(e,t).then(e=>r&&i({ok:!0,link:e}),e=>r&&i(e instanceof d&&e.status===413?{ok:!1,error:L,tooBig:!0}:{ok:!1,error:`Couldn't open its preview: ${m(e)}`})),()=>{r=!1}},[e,t,n]),r}function We({bot:e,file:t,reason:n}){let r=I(t.name);return(0,Q.jsxs)(`div`,{className:`file-card`,role:`group`,"aria-label":`${t.name}: no preview`,children:[(0,Q.jsx)(pe,{size:28,className:`file-card-icon`}),(0,Q.jsxs)(`div`,{className:`file-card-body`,children:[(0,Q.jsx)(`strong`,{className:`file-card-name`,children:t.name}),(0,Q.jsx)(`span`,{className:`hint`,children:[r===`application/octet-stream`?`Unknown type`:r,t.size===null?null:H(t.size)].filter(Boolean).join(` · `)}),(0,Q.jsx)(`span`,{className:`file-card-reason`,children:n})]}),(0,Q.jsxs)(`a`,{className:`btn small file-card-download`,href:z(e.id,t.path,!0),download:t.name,children:[(0,Q.jsx)(U,{size:14}),` Download`]})]})}var Ge=e=>y(e)===`Today`?v(e):`${y(e)} ${v(e)}`;function Ke(e){return e.quiet?`Quiet: nothing in the chat`:e.outcome===`ok`?e.runId||e.routineId?`Ran`:`Replied in the chat`:e.outcome===`error`?`Failed`:e.outcome===`running`||e.outcome===null?`Not done yet`:e.outcome.charAt(0).toUpperCase()+e.outcome.slice(1)}function qe({bot:e,onOpenRoutine:t}){let[n,r]=(0,Z.useState)(null),[i,a]=(0,Z.useState)(null),s=(0,Z.useCallback)(()=>{a(null),o.background(e.id).then(t=>r({botId:e.id,a:t}),e=>a(m(e)))},[e.id]),c=e.lastMessage?.createdAt,l=e.status.state===`idle`;(0,Z.useEffect)(s,[s,l,c]);let u=n?.botId===e.id?n.a:null;return(0,Q.jsxs)(`div`,{className:`tab-body activity-tab`,children:[(0,Q.jsxs)(`p`,{className:`muted`,children:[`Routines, webhooks, connector updates, other Botons and follow-ups that woke `,e.name,` in the last day. A quiet one posts nothing in the chat; its note is kept here.`]}),i&&(0,Q.jsxs)(`p`,{className:`form-error`,children:[`Couldn't load the activity: `,i,` `,(0,Q.jsx)(`button`,{type:`button`,className:`link-btn`,onClick:s,children:`Retry`})]}),!u&&!i&&(0,Q.jsx)(`p`,{className:`hint`,children:`Loading…`}),u&&!u.recent.length&&(0,Q.jsx)(`p`,{className:`hint`,children:`Nothing in the last day.`}),u&&u.recent.length>0&&(0,Q.jsx)(`ol`,{className:`activity-list`,children:u.recent.map((e,n)=>(0,Q.jsxs)(`li`,{className:_(`activity-item`,e.quiet&&`quiet`,e.warn&&`warn`),children:[(0,Q.jsxs)(`div`,{className:`activity-head`,children:[e.routineId?(0,Q.jsx)(`button`,{type:`button`,className:`chip-link activity-label`,onClick:()=>t(e.routineId),title:e.detail,children:e.label}):(0,Q.jsx)(`span`,{className:`activity-label`,title:e.detail,children:e.label}),(0,Q.jsx)(`time`,{dateTime:e.at,title:p(e.at),children:Ge(e.at)})]}),(0,Q.jsxs)(`div`,{className:`activity-outcome muted`,children:[Ke(e),e.quiet&&e.note?(0,Q.jsxs)(Q.Fragment,{children:[` · `,(0,Q.jsx)(`span`,{className:`activity-note`,children:e.note})]}):null]}),e.steps>0&&(e.runId||e.stepsMessageId)&&(0,Q.jsx)(oe,{count:e.steps,load:e.runId?ae(e.runId):re(e.stepsMessageId)})]},`${e.at}-${n}`))})]})}var Je=e=>`botons.webAccessSuggestion.${e}`,Ye=()=>{try{return typeof window<`u`&&window.localStorage?window.localStorage:null}catch{return null}};function Xe(e,t=Ye()){try{return(t&&`setItem`in t&&`removeItem`in t?i(t,Je(e)):t?.getItem(Je(e))??null)===`done`}catch{return!1}}function Ze(e,t=Ye()){try{t?.setItem(Je(e),`done`)}catch{}}function Qe(e,t,n){return n||!e.available||!t||e.handover||$e(e.policy.sites)===null?!1:!(e.enabled&&e.policy.sites.includes(`*`))}function $e(e){if(e.includes(`*`))return e;let t=[...e.filter(e=>/:\d+$/.test(e)),`*`];return t.length>50?null:t}var et=e=>`Off: ${(e??`this server has no managed browser`).replace(/\s+/g,` `).trim()}`,tt=t();function nt(e){let[t,n]=(0,Z.useState)(!1),[r,i]=(0,Z.useState)(null),a=async()=>{n(!0),i(null);try{await e.onSubmit()}catch(e){i(e.message),n(!1)}};return(0,tt.createPortal)((0,Q.jsx)(P,{title:e.title,onClose:()=>!t&&e.onClose(),children:(0,Q.jsxs)(`form`,{className:`settings-form`,onSubmit:e=>{e.preventDefault(),a()},children:[e.children,r&&(0,Q.jsx)(`p`,{className:`form-error`,role:`alert`,children:r}),(0,Q.jsxs)(`div`,{className:`form-actions`,children:[(0,Q.jsx)(`button`,{type:`button`,className:`btn ghost`,onClick:e.onClose,disabled:t,children:`Cancel`}),(0,Q.jsx)(`button`,{type:`submit`,className:`btn primary`,disabled:t,children:t?e.busyLabel:e.submitLabel})]})]})}),document.body)}function rt({bot:e,onClose:t,onCreated:n}){let r=F(e=>e.bots),[i]=(0,Z.useState)(()=>l(e.name,new Set(r.map(e=>e.name.toLowerCase())))),[a,o]=(0,Z.useState)(``),[s,c]=(0,Z.useState)(!0),[u,d]=(0,Z.useState)(!1);return(0,Q.jsxs)(nt,{title:`Duplicate ${e.name}`,submitLabel:`Create ${a.trim()||i}`,busyLabel:`Duplicating…`,onSubmit:async()=>n(await T.duplicateBot(e.id,{name:a.trim()||void 0,withRoutines:s,withMemories:u})),onClose:t,children:[(0,Q.jsxs)(`p`,{className:`hint`,children:[`The copy gets `,e.name,`'s look, role, instructions, brain, access, group, timezone, connectors and skills, and a new workspace. Not its chat or conversation.`]}),(0,Q.jsxs)(`label`,{children:[(0,Q.jsx)(`span`,{className:`field-label`,children:`Name`}),(0,Q.jsx)(`input`,{"data-autofocus":!0,value:a,onChange:e=>o(e.target.value),placeholder:i,maxLength:60})]}),(0,Q.jsxs)(`label`,{className:`check`,children:[(0,Q.jsx)(`input`,{type:`checkbox`,checked:s,onChange:e=>c(e.target.checked)}),(0,Q.jsx)(`span`,{children:`Copy its routines (paused, with new webhook URLs and keys)`})]}),(0,Q.jsxs)(`label`,{className:`check`,children:[(0,Q.jsx)(`input`,{type:`checkbox`,checked:u,onChange:e=>d(e.target.checked)}),(0,Q.jsx)(`span`,{children:`Copy its memories`})]})]})}function it({bot:e,onClose:t}){let[n,r]=(0,Z.useState)(``),[i,s]=(0,Z.useState)(``);return(0,Q.jsxs)(nt,{title:`Save as template`,submitLabel:`Save template`,busyLabel:`Saving…`,onSubmit:async()=>{let r=await o.saveTemplate({fromBotId:e.id,name:n.trim()||void 0,description:i.trim()||void 0});a({kind:`info`,text:`Saved template “${r.name}”. Pick it under New Boton.`}),t()},onClose:t,children:[(0,Q.jsxs)(`p`,{className:`hint`,children:[`Saves `,e.name,`'s look, role, instructions, brain, access, group, timezone, connectors, skill choice and routines, so a new Boton can start from them. Not its chat, memories, workspace, webhook keys, signing secrets or callback URLs.`]}),(0,Q.jsxs)(`label`,{children:[(0,Q.jsx)(`span`,{className:`field-label`,children:`Template name`}),(0,Q.jsx)(`input`,{"data-autofocus":!0,value:n,onChange:e=>r(e.target.value),placeholder:e.name,maxLength:60})]}),(0,Q.jsxs)(`label`,{children:[(0,Q.jsx)(`span`,{className:`field-label`,children:`Description (optional)`}),(0,Q.jsx)(`input`,{value:i,onChange:e=>s(e.target.value),placeholder:e.title?`${e.title}, saved from ${e.name}.`:`Saved from ${e.name}.`,maxLength:300})]})]})}var at=e=>`${e.label} (${e.model||`its default model`})`,ot=e=>e||`the default`,st=e=>e.charAt(0).toUpperCase()+e.slice(1),$=(e,t,n=`${t}s`)=>`${e} ${e===1?t:n}`,ct=e=>e.toLocaleString();function lt(e){let t=e.replayNow,n=t.messages+t.routines?`now ${$(t.messages,`message`)}${t.routines?` and ${$(t.routines,`routine message`)}`:``}${t.since?`, back to ${p(t.since)}`:``}`:`nothing yet`,r=e.replay.pinned?`, plus ${$(e.replay.pinned,`pinned message`)}, whatever ${e.replay.pinned===1?`its`:`their`} age (a long one as its start and end)`:`; pin messages in the chat (up to ${e.replay.pinLimit}) to keep them whatever their age`,i=t.excerpts?`; ${$(t.excerpts,`long message`)} as ${t.excerpts===1?`an excerpt`:`excerpts`} (start and end, not a summary)`:``;return`This chat, replayed within ${ct(e.replay.budget)} tokens (estimated high, as bytes) shared with the handoff note, on top of what every session starts with: the latest messages that fit (${n})${r}${i}`}function ut(e){let{name:t,preview:n}=e,[r,i]=(0,Z.useState)(!1),a=r&&n.summary.available,o=n.sessions.routines.map(e=>`“${e.name}”`).join(`, `),c=[n.sessions.chat?`${t}'s chat session`:null,o?`the own ${n.sessions.routines.length===1?`session`:`sessions`} of routine ${o}`:null].filter(Boolean),l=[n.from.effort===n.to.effort?null:`Effort: ${ot(n.from.effort)} becomes ${ot(n.to.effort)}${n.effortReset?`: ${n.to.label} or this model doesn't have ${n.from.effort}`:``}.`,...n.routineModels.map(e=>`Routine “${e.name}” leaves ${e.model} (a ${n.from.label} model) for ${t}'s model.`),...n.routineEfforts.map(e=>`Routine “${e.name}” leaves effort ${e.effort} for ${t}'s.`),...n.connectors.map(e=>`Connector ${e.name} doesn't run on ${n.to.label}: it stays in ${t}'s settings but its turns go without it.`)].filter(e=>!!e),u=n.readiness.state===`ready`?null:n.readiness.state===`unavailable`?`${n.to.label} isn't usable on this server now${n.readiness.reason?`: ${n.readiness.reason}`:``}. Turns fail until it is.`:`Whether ${n.to.label} works here isn't known yet: the first turn shows it.`;return(0,Q.jsxs)(A,{title:`Move ${t} to ${n.to.label}?`,confirmLabel:`Move to ${n.to.label}`,busyLabel:a?`Waiting for the summary…`:void 0,what:`move ${t}`,onConfirm:()=>e.onConfirm(a),onClose:e.onClose,children:[(0,Q.jsxs)(`p`,{children:[`From `,at(n.from),` to `,at(n.to),`. `,st(c.join(` and `)),` on `,n.from.label,` `,c.length>1?`end`:`ends`,`: a session can't move between providers.`]}),(0,Q.jsxs)(`div`,{className:`switch-lists`,children:[(0,Q.jsxs)(`div`,{children:[(0,Q.jsx)(`h4`,{children:`Kept`}),(0,Q.jsxs)(`ul`,{children:[(0,Q.jsxs)(`li`,{children:[$(n.kept.memories,`memory`,`memories`),` and `,$(n.kept.routines,`routine`)]}),(0,Q.jsx)(`li`,{children:`The files in its workspace`}),(0,Q.jsx)(`li`,{children:lt(n)}),(0,Q.jsx)(`li`,{children:`A handoff note from Botons: the move, its waiting requests, runs and recent decisions`})]})]}),(0,Q.jsxs)(`div`,{children:[(0,Q.jsx)(`h4`,{children:`Lost`}),(0,Q.jsxs)(`ul`,{children:[(0,Q.jsxs)(`li`,{children:[`What lived only in the `,n.from.label,` session: tool results, files it read, its reasoning`]}),(0,Q.jsx)(`li`,{children:n.replayNow.olderLeftOut?`Older chat: it doesn’t fit the replay`:`Chat older than what fits the replay, as the chat grows`})]})]})]}),(0,Q.jsxs)(`div`,{className:`switch-summary`,children:[(0,Q.jsxs)(`label`,{className:n.summary.available?void 0:`disabled`,children:[(0,Q.jsx)(`input`,{type:`checkbox`,checked:a,disabled:!n.summary.available,onChange:e=>i(e.target.checked)}),`Ask its `,n.from.label,` session for a summary first`]}),(0,Q.jsx)(`p`,{className:`hint`,children:n.summary.available?`One more turn on ${n.from.label} (it counts toward its usage), with no tools, for up to ${s(n.summary.timeoutMs)}: it writes down its open work, promises and the IDs the new session needs. You and the new session see it in the chat, at most ${n.summary.maxChars.toLocaleString()} characters, as its own account. If it can't be written, nothing moves.`:`Not available: ${n.summary.reason}.`})]}),l.length>0&&(0,Q.jsx)(`ul`,{className:`switch-changes`,children:l.map(e=>(0,Q.jsx)(`li`,{children:e},e))}),n.working&&(0,Q.jsxs)(`p`,{className:`hint warn`,children:[t,` is in the middle of a turn on `,n.from.label,`. It finishes there, and what it does still happens (replies, files, actions it takes), but its session isn't kept: the next turn starts on `,n.to.label,`.`]}),n.queuedRuns>0&&(0,Q.jsxs)(`p`,{className:`hint`,children:[$(n.queuedRuns,`queued run`),` `,n.queuedRuns===1?`runs`:`run`,` on `,n.to.label,`.`]}),n.oldUsageLimitUntil&&(0,Q.jsxs)(`p`,{className:`hint`,children:[n.from.label,` is out of usage until `,p(n.oldUsageLimitUntil),`.`]}),n.readiness.usageLimitUntil&&(0,Q.jsxs)(`p`,{className:`hint warn`,children:[n.to.label,` is out of usage until `,p(n.readiness.usageLimitUntil),`.`]}),u&&(0,Q.jsx)(`p`,{className:`hint warn`,children:u})]})}function dt({bot:e,onDeleted:t,onOpenBot:n,onTab:i}){let s=F(e=>e.providers),c=F(e=>e.connectors),[l,u]=(0,Z.useState)({}),f=be(e,l),p=(0,Z.useRef)(f);p.current=f;let h=(0,Z.useMemo)(()=>xe(),[]),g=(0,Z.useRef)(0),_=(0,Z.useRef)(!0);(0,Z.useEffect)(()=>(_.current=!0,()=>void(_.current=!1)),[]);let v=(0,Z.useRef)(async()=>!1),y=t=>()=>{if(_.current)return void v.current(t,!0);if(t.access===`host`)return a({kind:`error`,text:`Open ${e.name}'s settings to give full computer access again.`},8e3);C(`undo that`,()=>T.updateBot(e.id,t))},[b,x]=(0,Z.useState)(null),[S,w]=(0,Z.useState)(null);(0,Z.useEffect)(()=>{c||T.loadConnectors().catch(()=>{})},[c]);let D=e=>e===`lib:botons_browser`?`Web browsing`:c?.connectors.find(t=>t.id===e)?.name??e,O=async(t,n,r={})=>{let i=p.current,o=Se(i,t),s=++g.current;u(e=>De(e,t,s));try{await h(()=>T.updateBot(e.id,{...t,...r}))}finally{u(e=>Ee(e,t,s))}n||a({kind:`info`,text:ye(i,t,D),action:{label:`Undo`,run:y(o)}})},k=(e,t)=>O(e,t).then(()=>!0,e=>(a({kind:`error`,text:`Couldn't save: ${m(e)}`},8e3),!1)),j=t=>o.switchPreview(e.id,{provider:t.provider,model:t.model,effort:t.effort,connections:t.connections}),M=async(e,t)=>{try{let n=await j(e);if(n.required)return w({preview:n,patch:e,undo:t});try{await O(e,t)}catch(n){if(!(n instanceof d&&n.status===409))throw n;let r=await j(e);if(!r.required)throw n;w({preview:r,patch:e,undo:t})}}catch(e){a({kind:`error`,text:`Couldn't change the brain: ${m(e)}`},8e3)}},N=async(e,t=!1)=>{let n=Ce(p.current,typeof e==`function`?e(p.current):e);return Object.keys(n).length?n.access===`host`?(x({patch:n,undo:t}),!1):n.provider===void 0?k(n,t):(await M(n,t),!1):!0};v.current=N;let P=async()=>{let t={connections:q(p.current.connections,r,!1)},n=++g.current;u(e=>De(e,t,n));try{return await h(()=>o.stopBrowser(e.id))}finally{u(e=>Ee(e,t,n))}},I=async e=>{if(S)try{let t=e?S.preview.summary.confirmation:S.preview.confirmation;await O(S.patch,S.undo,{switchConfirmation:t??void 0,...e?{switchSummary:!0}:{}})}catch(e){throw e instanceof d&&e.status===409&&w({...S,preview:await j(S.patch)}),e}},L=F(t=>t.routines[e.id]);(0,Z.useEffect)(()=>{L||T.loadRoutines(e.id).catch(()=>{})},[e.id,!L]);let R=s.find(e=>e.id===f.provider),ee=(c?.connectors.filter(e=>e.providers.includes(f.provider))??[]).some(e=>e.id===r);return(0,Q.jsxs)(`div`,{className:`tab-body boton-settings`,children:[(0,Q.jsx)(J,{title:`Identity`,children:(0,Q.jsx)(pt,{bot:f,apply:N})}),(0,Q.jsxs)(J,{title:`Brain`,children:[(0,Q.jsx)(ht,{bot:f,apply:N}),(0,Q.jsx)(E,{providers:s,provider:f.provider}),(0,Q.jsx)(Te,{label:`Advanced`,children:(0,Q.jsx)(Me,{provider:R,model:f.model,value:je(R,f.model,f.effort),onChange:e=>N({effort:e})})})]}),(0,Q.jsxs)(J,{title:`What it can do`,children:[(0,Q.jsx)(gt,{bot:f,problem:R?.workspaceProblem,apply:N}),(0,Q.jsx)(_t,{bot:e,shown:f,apply:N,stop:P,brainCan:!c||ee}),(0,Q.jsx)(yt,{bot:e,shown:f,apply:N,browserUsable:!c||ee}),(0,Q.jsx)(bt,{shown:f,apply:N}),(0,Q.jsxs)(Te,{label:`Advanced`,children:[(0,Q.jsx)(St,{bot:f,apply:N,askFirst:!!R?.askFirst}),(0,Q.jsx)(Ct,{bot:f,apply:N}),(0,Q.jsx)(wt,{bot:e}),(0,Q.jsxs)(`p`,{className:`set-help`,children:[`Its folder: `,(0,Q.jsx)(`code`,{children:e.workspace})]})]})]}),(0,Q.jsxs)(J,{title:`Schedule and routines`,children:[(0,Q.jsx)(Tt,{bot:f,apply:N}),(0,Q.jsx)(Oe,{label:`Routines`,value:ft(L),onClick:()=>i(`routines`)})]}),(0,Q.jsxs)(J,{title:`Usage and spending`,children:[(0,Q.jsx)(Oe,{label:`Usage`,value:`Tokens and runs`,onClick:()=>i(`usage`)}),(0,Q.jsx)(kt,{bot:e,apiBrain:R?.kind===`api`&&e.provider!==`mock`})]}),(0,Q.jsx)(J,{title:`Manage`,children:(0,Q.jsx)(jt,{bot:e,onDeleted:t,onOpenBot:n})}),b&&(0,Q.jsx)(A,{title:`Give ${e.name} full computer access?`,confirmLabel:`Give full access`,what:`give full computer access`,danger:!0,onConfirm:()=>O(b.patch,b.undo),onClose:()=>x(null),children:(0,Q.jsx)(`p`,{children:`It will run commands as the server's user: it can read and change any file that user can, your keys and logins included.`})}),S&&(0,Q.jsx)(ut,{name:e.name,preview:S.preview,onConfirm:I,onClose:()=>w(null)})]})}var ft=e=>{if(!e)return`Open`;if(!e.length)return`None yet`;let t=e.filter(e=>e.enabled).length;return`${e.length} routine${e.length===1?``:`s`}${t<e.length?`, ${t} on`:``}`};function pt({bot:e,apply:t}){let n=(0,Z.useId)(),[r,i]=(0,Z.useState)(!1),a=Y(e.name,e=>t({name:e.trim()}),e=>!!e.trim()),o=Y(e.title,e=>t({title:e.trim()})),s=Y(e.instructions,e=>t({instructions:e})),c=F(e=>e.sidebar?.groups)?.map(e=>({id:e.id,name:e.name}))??(e.groupId?[{id:e.groupId,name:e.group}]:[]);return(0,Q.jsxs)(Q.Fragment,{children:[(0,Q.jsxs)(`div`,{className:`set-identity`,children:[(0,Q.jsxs)(`button`,{type:`button`,className:`set-avatar`,"aria-label":`Change look`,title:`Change look`,onClick:()=>i(!0),children:[(0,Q.jsx)(w,{look:e.look,size:64,still:!0}),(0,Q.jsx)(`span`,{className:`set-avatar-edit`,"aria-hidden":!0,children:(0,Q.jsx)(ue,{size:12})})]}),(0,Q.jsxs)(`div`,{className:`set-identity-fields`,children:[(0,Q.jsx)(`input`,{"aria-label":`Name`,className:`set-name`,maxLength:60,required:!0,...a}),(0,Q.jsx)(`input`,{"aria-label":`Role`,placeholder:`Role, e.g. Inbox triage`,maxLength:80,...o})]})]}),(0,Q.jsxs)(`div`,{className:`set-row set-stack`,children:[(0,Q.jsx)(`label`,{className:`set-label`,htmlFor:`${n}-i`,children:`Personality and instructions`}),(0,Q.jsx)(`textarea`,{id:`${n}-i`,rows:4,placeholder:`What it owns, how you like the work done, and what it should never do.`,...s})]}),(0,Q.jsx)(X,{label:`Group`,htmlFor:`${n}-g`,control:(0,Q.jsxs)(`select`,{id:`${n}-g`,value:e.groupId??``,onChange:e=>t({groupId:e.target.value||null}),children:[(0,Q.jsx)(`option`,{value:``,children:`None`}),c.map(e=>(0,Q.jsx)(`option`,{value:e.id,children:e.name},e.id))]})}),(0,Q.jsx)(X,{label:`Pin to the top`,control:(0,Q.jsx)(G,{label:`Pin to the top`,on:e.pinned,onChange:e=>t({pinned:e})})}),r&&(0,Q.jsx)(mt,{look:e.look,name:e.name,onSave:e=>t({look:e}),onClose:()=>i(!1)})]})}function mt({look:e,name:t,onSave:n,onClose:r}){let[i,a]=(0,Z.useState)(e);return(0,Q.jsxs)(P,{title:`${t}'s look`,onClose:r,wide:!0,children:[(0,Q.jsx)(Ne,{look:i,onChange:a}),(0,Q.jsxs)(`div`,{className:`form-actions`,children:[(0,Q.jsx)(`button`,{type:`button`,className:`btn ghost`,onClick:r,children:`Cancel`}),(0,Q.jsx)(`button`,{type:`button`,className:`btn primary`,"data-autofocus":!0,onClick:()=>{n(i),r()},children:`Done`})]})]})}function ht({bot:e,apply:t}){let n=(0,Z.useId)(),r=F(e=>e.providers),i=r.find(t=>t.id===e.provider),a=!e.model.trim()||(i?.models.some(t=>t.id===e.model)??!1),[o,s]=(0,Z.useState)(!a),c=Y(e.model.trim(),e=>t({model:e.trim()}));return(0,Q.jsxs)(Q.Fragment,{children:[(0,Q.jsx)(X,{label:`Brain`,htmlFor:`${n}-p`,control:(0,Q.jsx)(`select`,{id:`${n}-p`,value:e.provider,onChange:e=>{let n=r.find(t=>t.id===e.target.value);s(!1),t({provider:e.target.value,model:n?.defaultModel??``})},children:r.map(e=>(0,Q.jsxs)(`option`,{value:e.id,children:[e.label,e.available?``:` (not set up)`]},e.id))})}),i?.models.length?(0,Q.jsx)(X,{label:`Model`,htmlFor:`${n}-m`,control:(0,Q.jsxs)(`select`,{id:`${n}-m`,value:a&&!o?e.model:`__custom`,onChange:e=>{if(e.target.value===`__custom`)return s(!0);s(!1),t({model:e.target.value})},children:[!e.model.trim()&&!i?.models.some(e=>!e.id)&&(0,Q.jsx)(`option`,{value:``,children:`Default`}),i?.models.map(e=>(0,Q.jsx)(`option`,{value:e.id,children:e.label},e.id||`__default`)),(0,Q.jsx)(`option`,{value:`__custom`,children:`Other…`})]}),children:(o||!a)&&(0,Q.jsx)(`input`,{className:`set-wide`,"aria-label":`Model id`,placeholder:`Model id, e.g. claude-opus-5-5`,...c})}):(0,Q.jsx)(X,{label:`Model`,htmlFor:`${n}-m`,children:(0,Q.jsx)(`input`,{id:`${n}-m`,className:`set-wide`,placeholder:`Its default`,...c})})]})}function gt({bot:e,problem:t,apply:n}){let r=e=>n({access:e});return(0,Q.jsxs)(X,{label:`Computer access`,help:e.access===`workspace`?`Changes only its own folder. Can't see your keys or logins.`:`Runs commands as the server user.`,children:[(0,Q.jsxs)(`div`,{className:`segmented set-wide`,role:`radiogroup`,"aria-label":`Computer access`,children:[(0,Q.jsx)(`button`,{type:`button`,role:`radio`,"aria-checked":e.access===`workspace`,className:_(e.access===`workspace`&&`on`),disabled:!!t&&e.access!==`workspace`,onClick:()=>r(`workspace`),children:`Workspace`}),(0,Q.jsx)(`button`,{type:`button`,role:`radio`,"aria-checked":e.access===`host`,className:_(e.access===`host`&&`on`),onClick:()=>r(`host`),children:`Full computer`})]}),t&&(0,Q.jsx)(Te,{label:`Why isn't Workspace available?`,children:(0,Q.jsx)(`p`,{className:`set-help`,children:t})})]})}function _t({bot:e,shown:t,apply:n,stop:i,brainCan:s}){let[c,l]=(0,Z.useState)(null),[u,d]=(0,Z.useState)(!1),[f,p]=(0,Z.useState)(!1),m=O()===e.id;(0,Z.useEffect)(()=>{m||o.browser(e.id).then(l,()=>{})},[m,e.id]);let[h,g]=(0,Z.useState)(null),{error:_,retry:v}=K(()=>o.browser(e.id).then(l),`${e.id}:${e.access}:${e.connections.join(`,`)}`),y=t.connections.includes(`lib:botons_browser`)&&s&&c?.available!==!1,x=async(e,t)=>{p(!0);let n=await C(e,t);return p(!1),n},w=async e=>{if(e)return n(e=>({connections:q(e.connections,r,!0)}));await x(`turn web browsing off`,async()=>l(await i()))&&a({kind:`info`,text:`Web browsing off`,action:{label:`Undo`,run:()=>n(e=>({connections:q(e.connections,r,!0)}),!0)}})},T=()=>j.open(e.id),[E,D]=(0,Z.useState)(()=>Xe(e.id)),k=()=>{Ze(e.id),D(!0)},A=()=>x(`turn on web access`,async()=>{let t=c&&$e(c.policy.sites);t&&(l(await o.setBrowser(e.id,{sites:t})),await n(e=>({connections:q(e.connections,`lib:botons_browser`,!0)}))&&k())}),M=c?.policy,N=c?.available??!0,P=c?.handover;return(0,Q.jsxs)(X,{label:`Web browsing`,help:N?!s&&!y?`Not available with this brain.`:y?`Asks you before submitting anything.`:`Lets it read websites for you.`:et(c?.why??null),control:(0,Q.jsx)(G,{label:`Web browsing`,on:y,disabled:f||!y&&(!N||!s),onChange:e=>void w(e)}),children:[_&&(0,Q.jsx)(Ae,{what:`web browsing`,error:_,onRetry:v}),c&&Qe(c,s,E)&&(0,Q.jsxs)(`div`,{className:`browser-suggestion`,role:`group`,"aria-label":`Suggestion`,children:[(0,Q.jsx)(`span`,{children:`Turn on web access for all public sites?`}),(0,Q.jsx)(`button`,{type:`button`,className:`btn small primary`,disabled:f,onClick:()=>void A(),children:`Turn on`}),(0,Q.jsx)(`button`,{type:`button`,className:`btn small ghost`,disabled:f,onClick:k,children:`Not now`})]}),y&&c&&M&&N&&(0,Q.jsxs)(`div`,{className:`set-actions`,children:[(0,Q.jsxs)(`button`,{type:`button`,className:`chip-btn`,onClick:()=>d(!0),children:[we(M.sites),` `,(0,Q.jsx)(me,{size:12})]}),!P&&M.sites.length>0&&(0,Q.jsx)(`button`,{type:`button`,className:`link-btn`,disabled:f,onClick:T,children:`Open its browser…`}),P?.byThisDevice&&(0,Q.jsx)(`button`,{type:`button`,className:`link-btn`,onClick:T,children:`Back to its browser`}),P&&!P.byThisDevice&&(0,Q.jsxs)(`span`,{className:`set-help`,children:[P.deviceLabel,` has its browser.`,` `,(0,Q.jsx)(`button`,{type:`button`,className:`link-btn`,disabled:f,onClick:()=>void x(`end the sign-in`,async()=>l(await o.finishHandover(e.id,P.id))),children:`End it`})]})]}),c?.available&&s&&(0,Q.jsxs)(Te,{label:`Details`,onOpen:()=>void o.browserJournal(e.id).then(g,()=>g([])),children:[(0,Q.jsxs)(`ul`,{className:`set-help set-bullets`,children:[(0,Q.jsx)(`li`,{children:`Opens and reads pages on its sites.`}),(0,Q.jsx)(`li`,{children:`Clicks and types, never into password, code or card fields.`}),(0,Q.jsx)(`li`,{children:`Anything it submits on a page waits for your OK.`}),(0,Q.jsx)(`li`,{children:`Never signs in itself: it asks you in the chat to take over.`}),(0,Q.jsx)(`li`,{children:`No downloads or uploads, and off in runs a webhook starts.`})]}),c.available&&!c.sandbox&&(0,Q.jsx)(`p`,{className:`set-help`,children:`Chromium's own sandbox is off on this server (BOTONS_BROWSER_SANDBOX=0).`}),h&&h.length>0&&(0,Q.jsx)(`span`,{className:`set-label`,children:`Recent steps`}),h&&h.length>0&&(0,Q.jsx)(`ul`,{className:`browser-journal`,children:h.slice(-30).reverse().map((e,t)=>(0,Q.jsxs)(`li`,{children:[(0,Q.jsx)(`span`,{className:`hint`,children:b(e.at)}),` `,e.action,e.url&&(0,Q.jsx)(`code`,{children:S(e.url,80)}),` `,e.outcome!==`done`&&(0,Q.jsx)(`strong`,{children:e.outcome}),` `,(0,Q.jsx)(`span`,{className:`hint`,children:S(e.detail,160)})]},`${e.at}-${t}`))})]}),u&&c&&(0,Q.jsx)(vt,{bot:t,status:c,onStatus:l,onClose:()=>d(!1)})]})}function vt({bot:e,status:t,onStatus:n,onClose:r}){let i=(0,Z.useId)(),[s,c]=(0,Z.useState)(``),[l,u]=(0,Z.useState)(null),[d,f]=(0,Z.useState)(!1),p=t.policy.sites,h=p.includes(`*`),g=p.filter(e=>e!==`*`),_=$e(p),v=async(t,r)=>{f(!0),u(null);try{return n(await o.setBrowser(e.id,{sites:t})),r&&a({kind:`info`,text:r.text,action:{label:`Undo`,run:()=>C(`put the sites back`,async()=>n(await o.setBrowser(e.id,{sites:r.back})))}}),!0}catch(e){return u(m(e)),!1}finally{f(!1)}},y=async()=>{let e=ke(s);if(e){if(p.includes(e))return c(``);await v([...p,e],{text:`Added ${e}`,back:p})&&c(``)}};return(0,Q.jsxs)(P,{title:`Sites ${e.name} can read`,onClose:r,children:[(0,Q.jsx)(`div`,{className:`set-card`,children:(0,Q.jsx)(X,{label:`All public sites`,help:h||_?`Any site on the internet; never this computer or your network.`:`The list is full: remove an address with a port first.`,control:(0,Q.jsx)(G,{label:`All public sites`,on:h,disabled:d||!h&&!_,onChange:e=>void(e?_&&v(_,{text:`All public sites on`,back:p}):v(g,{text:`All public sites off`,back:p}))})})}),g.length>0&&(0,Q.jsx)(`ul`,{className:`chip-list`,"aria-label":`Sites`,children:g.map(e=>(0,Q.jsxs)(`li`,{className:`template-chip`,children:[(0,Q.jsx)(`span`,{children:e}),(0,Q.jsx)(`button`,{type:`button`,className:`template-remove`,"aria-label":`Remove ${e}`,disabled:d,onClick:()=>void v(p.filter(t=>t!==e),{text:`Removed ${e}`,back:p}),children:(0,Q.jsx)(le,{size:12})})]},e))}),(0,Q.jsxs)(`form`,{className:`memory-add`,onSubmit:e=>{e.preventDefault(),y()},children:[(0,Q.jsx)(`input`,{id:`${i}-add`,value:s,onChange:e=>c(e.target.value),placeholder:`example.com`,"aria-label":`Add a site`,autoComplete:`off`,spellCheck:!1,autoCapitalize:`none`,inputMode:`url`,"data-autofocus":!0}),(0,Q.jsx)(`button`,{className:`btn small`,type:`submit`,disabled:d||!ke(s),children:`Add`})]}),l&&(0,Q.jsx)(`p`,{className:`form-error`,children:l}),(0,Q.jsx)(`p`,{className:`set-help`,children:`A site covers its subdomains. Use host:port for this computer or your network.`}),(0,Q.jsx)(`div`,{className:`form-actions`,children:(0,Q.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:r,children:`Done`})})]})}function yt({bot:e,shown:t,apply:n,browserUsable:i}){let a=F(e=>e.connectors);if(!a)return null;let o=a.connectors.filter(e=>e.providers.includes(t.provider)&&!e.managed),s=t.connections.filter(e=>e!==`lib:botons_browser`&&!a.connectors.some(n=>n.id===e&&n.providers.includes(t.provider))),c=(e,t)=>n(n=>({connections:q(n.connections,e,t)}));return(0,Q.jsxs)(Q.Fragment,{children:[(0,Q.jsx)(Pe,{botId:e.id,value:t.connections,onChange:e=>n({connections:e}),skip:i?[]:[r]}),o.map(e=>{let n=t.connections.includes(e.id),r=e.enabled?e.status===`needs-auth`?`Needs sign-in in Settings → Connectors.`:e.lastTest?.outcome===`failed`?`Last test failed: ${S(e.lastTest.problem??``,80)}`:null:`Turned off in Settings → Connectors.`;return(0,Q.jsx)(X,{label:e.name,help:r&&(0,Q.jsx)(`span`,{className:`warn`,children:r}),control:(0,Q.jsx)(G,{label:e.name,on:n,onChange:t=>c(e.id,t)})},e.id)}),s.map(e=>(0,Q.jsx)(X,{label:e,help:`Not found any more: its turns go without it.`,control:(0,Q.jsx)(G,{label:e,on:!0,onChange:()=>c(e,!1)})},e)),!o.length&&!s.length&&(0,Q.jsx)(X,{label:`Connected services`,help:`None yet. Add Slack, GitHub and more in Settings → Connectors.`})]})}function bt({shown:e,apply:t}){let n=(0,Z.useId)(),{skills:r,error:i}=M(),a=e.skills,o=a??[],s=o.filter(e=>r&&!r.some(t=>t.name===e));return r&&!r.length&&a===null?null:(0,Q.jsxs)(X,{label:`Skills`,htmlFor:`${n}-s`,control:(0,Q.jsxs)(`select`,{id:`${n}-s`,value:a===null?`all`:`some`,onChange:e=>t({skills:e.target.value===`all`?null:r?.map(e=>e.name)??[]}),children:[(0,Q.jsx)(`option`,{value:`all`,children:`All skills`}),(0,Q.jsx)(`option`,{value:`some`,children:`Only some`})]}),children:[i&&(0,Q.jsx)(`p`,{className:`form-error`,children:i}),a!==null&&(0,Q.jsxs)(`div`,{className:`set-sublist`,children:[r?.map(e=>(0,Q.jsx)(X,{label:e.name,help:k(e)?`Waiting for your approval`:S(e.description,90),control:(0,Q.jsx)(G,{label:e.name,on:o.includes(e.name),onChange:n=>t(t=>({skills:q(t.skills??[],e.name,n)}))})},e.name)),s.map(e=>(0,Q.jsx)(X,{label:e,help:`Not in the library any more.`,control:(0,Q.jsx)(G,{label:e,on:!0,onChange:()=>t(t=>({skills:q(t.skills??[],e,!1)}))})},e))]})]})}var xt=e=>[...new Set(e.split(`
2
+ `).map(e=>e.trim()).filter(Boolean))];function St({bot:e,apply:t,askFirst:n}){let r=(0,Z.useId)(),i=Y(e.askFirst.join(`
3
+ `),e=>t({askFirst:xt(e)}));return(0,Q.jsxs)(`div`,{className:`set-row set-stack`,children:[(0,Q.jsx)(`label`,{className:`set-label`,htmlFor:r,children:`Ask me first`}),(0,Q.jsx)(`span`,{className:`set-help`,children:n?`Claude Code permission rules, one per line. A matching step waits for your OK.`:`Only a Claude brain uses these rules.`}),(0,Q.jsx)(`textarea`,{id:r,className:`rules-input`,rows:Math.min(Math.max(e.askFirst.length+1,2),8),spellCheck:!1,placeholder:`Bash(git push:*)
4
+ mcp__slack__send_message`,...i})]})}function Ct({bot:e,apply:t}){return e.alwaysAllow.length?(0,Q.jsxs)(`div`,{className:`set-row set-stack`,children:[(0,Q.jsx)(`span`,{className:`set-label`,children:`Always allowed`}),(0,Q.jsx)(`span`,{className:`set-help`,children:`Done without asking, except in webhook runs. Remove one to be asked again.`}),(0,Q.jsx)(`ul`,{className:`chip-list`,children:e.alwaysAllow.map(e=>(0,Q.jsxs)(`li`,{className:`template-chip always-chip`,children:[(0,Q.jsx)(`code`,{children:e}),(0,Q.jsx)(`button`,{type:`button`,className:`template-remove`,"aria-label":`Ask again before “${e}”`,title:`Ask me again`,onClick:()=>t(t=>({alwaysAllow:t.alwaysAllow.filter(t=>t!==e)})),children:(0,Q.jsx)(le,{size:12})})]},e))})]}):null}function wt({bot:e}){let[t,n]=(0,Z.useState)(null),{error:r,retry:i}=K(()=>o.capabilities(e.id).then(n),`${e.id}:${e.updatedAt}`);return(0,Q.jsxs)(`div`,{className:`set-row set-stack capabilities`,children:[(0,Q.jsxs)(`span`,{className:`set-label`,children:[`What `,e.name,` can do now`]}),r&&(0,Q.jsx)(Ae,{what:`what it can do`,error:r,onRetry:i}),t&&(0,Q.jsx)(`ul`,{children:t.summary.map(e=>(0,Q.jsx)(`li`,{children:e},e))})]})}function Tt({bot:e,apply:t}){let n=(0,Z.useId)(),r=(0,Z.useMemo)(()=>g().map(e=>(0,Q.jsx)(`option`,{value:e},e)),[]),i=Y(e.timezone,e=>t({timezone:e.trim()}),e=>c(e.trim())),a=!c(i.value.trim());return(0,Q.jsxs)(X,{label:`Timezone`,htmlFor:n,help:a?(0,Q.jsx)(`span`,{className:`field-error`,children:`Not a timezone; it goes back when you leave the field.`}):`For its schedules and times.`,children:[(0,Q.jsx)(`input`,{id:n,className:`set-wide`,list:`${n}-zones`,autoComplete:`off`,spellCheck:!1,"aria-invalid":a,...i}),(0,Q.jsx)(`datalist`,{id:`${n}-zones`,children:r})]})}var Et=e=>`$${e<.01&&e>0?e.toFixed(4):e.toFixed(2)}`,Dt=e=>{let t=e.trim().replace(/^\$/,``);if(!t)return null;let n=Number(t);return Number.isFinite(n)&&n>=0?n:void 0},Ot=e=>e===null?``:String(e);function kt({bot:e,apiBrain:t}){let[n,r]=(0,Z.useState)(null),{error:i,retry:s}=K(()=>o.spending(e.id).then(r),`${e.id}:${e.updatedAt}`);if(i)return(0,Q.jsx)(Ae,{what:`its spending`,error:i,onRetry:s});if(!n)return null;let c=n.dailyCapUsd!==null||n.routines.some(e=>e.perRunCapUsd!==null);if(!t&&!c)return null;let l=async(t,n)=>{await C(`save the cap`,async()=>r(await o.setSpending(e.id,t)))&&a({kind:`info`,text:`Cap saved`,action:{label:`Undo`,run:()=>C(`put the cap back`,async()=>r(await o.setSpending(e.id,n)))}})};return(0,Q.jsxs)(`div`,{className:`spending`,children:[(0,Q.jsx)(At,{name:`spending-daily`,label:`Daily spending cap`,help:`${t?``:`For when it runs on an API key. `}Today: about ${Et(n.spentTodayUsd)}${n.unpricedToday?` + ${n.unpricedToday} unpriced`:``}.`,value:n.dailyCapUsd,onSave:e=>void l({dailyCapUsd:e},{dailyCapUsd:n.dailyCapUsd})}),(0,Q.jsxs)(Te,{label:n.routines.length?`Caps per routine run`:`How caps work`,children:[n.routines.map(e=>(0,Q.jsx)(At,{name:`spending-routine-${e.id}`,label:e.name,value:e.perRunCapUsd,onSave:t=>void l({routines:{[e.id]:t}},{routines:{[e.id]:e.perRunCapUsd}})},e.id)),(0,Q.jsx)(`p`,{className:`set-help`,children:n.rules})]})]})}function At({name:e,label:t,help:n,value:r,onSave:i}){let a=(0,Z.useId)(),o=Y(Ot(r),e=>i(Dt(e)),e=>Dt(e)!==void 0),s=Dt(o.value)===void 0;return(0,Q.jsx)(X,{label:t,htmlFor:a,help:s?(0,Q.jsx)(`span`,{className:`field-error`,children:`Dollars, or empty for no cap.`}):n,control:(0,Q.jsxs)(`span`,{className:`cap-input`,children:[(0,Q.jsx)(`span`,{"aria-hidden":!0,children:`$`}),(0,Q.jsx)(`input`,{id:a,name:e,inputMode:`decimal`,placeholder:`No cap`,"aria-invalid":s,...o})]})})}function jt({bot:e,onDeleted:t,onOpenBot:n}){let r=F(e=>e.settings?.primaryBotId??null),i=r===e.id,[s,c]=(0,Z.useState)(null),l=(t,n=!1)=>void C(t?`make ${e.name} primary`:`stop ${e.name} being primary`,async()=>{let i=r;await T.updateSettings({primaryBotId:t?e.id:null}),n||a({kind:`info`,text:t?`${e.name} is your primary Boton`:`${e.name} is no longer primary`,action:{label:`Undo`,run:()=>void C(`undo that`,()=>T.updateSettings({primaryBotId:i}))}})}),u=(t,n=!1)=>void C(t?`pause ${e.name}`:`resume ${e.name}`,async()=>{await T.pauseBot(e.id,t),n||a({kind:`info`,text:t?`${e.name} paused`:`${e.name} resumed`,action:{label:`Undo`,run:()=>u(!t,!0)}})});return(0,Q.jsxs)(Q.Fragment,{children:[(0,Q.jsx)(X,{label:`Primary Boton`,help:`Opens first and can hand work to the others.`,control:(0,Q.jsx)(G,{label:`Primary Boton`,on:i,onChange:e=>l(e)})}),(0,Q.jsx)(X,{label:`Paused`,help:e.paused?`Nothing runs; messages wait until you resume.`:`Stops everything it is doing until you resume.`,control:(0,Q.jsx)(G,{label:`Paused`,on:e.paused,onChange:e=>u(e)})}),(0,Q.jsx)(X,{label:`Copies`,control:(0,Q.jsxs)(`span`,{className:`set-buttons nowrap`,children:[(0,Q.jsxs)(`button`,{type:`button`,className:`btn small`,onClick:()=>c(`duplicate`),children:[(0,Q.jsx)(fe,{size:14}),` Duplicate`]}),(0,Q.jsx)(`button`,{type:`button`,className:`btn small`,onClick:()=>c(`template`),children:`Template`})]})}),(0,Q.jsx)(X,{label:`Start fresh`,help:`Forgets this chat's context. Memories and routines stay.`,control:(0,Q.jsx)(`button`,{type:`button`,className:`btn small`,onClick:()=>c(`reset`),children:`Start fresh`})}),(0,Q.jsx)(X,{label:`Delete ${e.name}`,control:(0,Q.jsxs)(`button`,{type:`button`,className:`btn small danger`,onClick:()=>c(`delete`),children:[(0,Q.jsx)(W,{size:14}),` Delete`]})}),s===`duplicate`&&(0,Q.jsx)(rt,{bot:e,onClose:()=>c(null),onCreated:e=>n(e.id)}),s===`template`&&(0,Q.jsx)(it,{bot:e,onClose:()=>c(null)}),s===`reset`&&(0,Q.jsx)(A,{title:`Start a fresh conversation with ${e.name}?`,confirmLabel:`Start fresh`,what:`start a fresh conversation`,onConfirm:()=>o.resetBot(e.id),onClose:()=>c(null),children:(0,Q.jsxs)(`p`,{children:[e.name,` forgets the context of this chat and starts a new session. The messages stay visible here, and its memories and routines are kept.`]})}),s===`delete`&&(0,Q.jsxs)(A,{title:`Delete ${e.name}?`,confirmLabel:`Delete ${e.name}`,what:`delete ${e.name}`,danger:!0,onConfirm:async()=>{await T.deleteBot(e.id),t()},onClose:()=>c(null),children:[(0,Q.jsxs)(`p`,{children:[`This deletes `,e.name,`'s chat, routines, run history and memories, and stops anything it is doing. Its webhooks stop working: callers will get 401. This can't be undone.`]}),(0,Q.jsxs)(`p`,{className:`hint`,children:[`The workspace folder stays on disk: `,(0,Q.jsx)(`code`,{children:e.workspace})]})]})]})}function Mt(e,t,n,r,i){let a=new Map;for(let e of t)a.set(n(e),{key:n(e),label:r(e),current:n(e)===i,day:null,week:e});for(let t of e){let e=a.get(n(t))??{key:n(t),label:r(t),current:n(t)===i,day:null,week:null};e.day=t,a.set(n(t),e)}let o=e=>e.week?u(e.week):0;return[...a.values()].sort((e,t)=>o(t)-o(e))}function Nt(e){if(!e)return`No turns`;let t=e.turns===1?`1 turn`:`${e.turns} turns`;return`${f(e)} · ${t}${e.costUsd?` · ${x(e.costUsd)}`:``}`}function Pt({row:e}){return(0,Q.jsx)(`td`,{title:Nt(e),className:_(!e&&`none`),children:e?h(u(e)):`—`})}function Ft({caption:e,first:t,rows:n}){return(0,Q.jsxs)(`table`,{className:`usage-table`,children:[(0,Q.jsx)(`caption`,{children:e}),(0,Q.jsx)(`tbody`,{className:`head`,children:(0,Q.jsxs)(`tr`,{children:[(0,Q.jsx)(`th`,{scope:`col`,children:t}),(0,Q.jsx)(`th`,{scope:`col`,children:`24 hours`}),(0,Q.jsx)(`th`,{scope:`col`,children:`7 days`})]})}),(0,Q.jsx)(`tbody`,{children:n.map(e=>(0,Q.jsxs)(`tr`,{className:_(e.current&&`current`),children:[(0,Q.jsx)(`th`,{scope:`row`,children:e.label}),(0,Q.jsx)(Pt,{row:e.day}),(0,Q.jsx)(Pt,{row:e.week})]},e.key))})]})}function It({bot:e}){let[t,n]=(0,Z.useState)(null),[r,i]=(0,Z.useState)(!1),a=async()=>{let[t,r,i,a]=await Promise.all([o.usage(`24h`,`bot`),o.usage(`7d`,`bot`),o.usage(`24h`,`routine`,e.id),o.usage(`7d`,`routine`,e.id)]);n({botId:e.id,botons:Mt(t.rows,r.rows,e=>e.botId,e=>e.botName,e.id),routines:Mt(i.rows,a.rows,e=>e.routineId??``,e=>e.routineId?e.routineName??`Deleted routine`:`Chat`)})},s=K(a,e.id),c=t?.botId===e.id?t:null;return(0,Q.jsxs)(`div`,{className:`tab-body`,children:[(0,Q.jsxs)(`div`,{className:`tab-intro`,children:[(0,Q.jsx)(`p`,{className:`muted`,children:`Tokens your Botons used, as Claude and Codex reported them, cached input included (hover a number for the breakdown). Turns are kept for 90 days.`}),(0,Q.jsx)(`button`,{className:`btn small`,disabled:r,onClick:async()=>{i(!0),await a().catch(()=>s.retry()),i(!1)},children:r?`Refreshing…`:`Refresh`})]}),!c&&(s.error?(0,Q.jsx)(Ae,{what:`usage`,error:s.error,onRetry:s.retry}):(0,Q.jsx)(`p`,{className:`muted`,children:`Loading…`})),c&&(0,Q.jsxs)(Q.Fragment,{children:[c.botons.length?(0,Q.jsx)(Ft,{caption:`All Botons`,first:`Boton`,rows:c.botons}):(0,Q.jsx)(`p`,{className:`empty-note`,children:`No turns in the last 7 days.`}),c.routines.length>0&&(0,Q.jsx)(Ft,{caption:`${e.name}, by routine`,first:`Routine`,rows:c.routines}),(0,Q.jsx)(`p`,{className:`hint`,children:`Cost, where shown, is the CLI's estimate at API prices; a subscription isn't billed per turn.`})]})]})}var Lt=[{id:`routines`,label:`Routines`},{id:`activity`,label:`Activity`},{id:`memory`,label:`Memory`},{id:`files`,label:`Files`},{id:`usage`,label:`Usage`},{id:`settings`,label:`Settings`}];function Rt(e){let t=(0,Z.useRef)(null);return(0,Z.useLayoutEffect)(()=>{let e=document.activeElement,n=e instanceof HTMLElement&&e!==document.body&&!e.closest(`input, textarea, select, [contenteditable="true"]`)?e:null,r=t.current;return()=>{let e=document.activeElement;n?.isConnected&&!r?.contains(n)&&(!e||e===document.body||r?.contains(e))&&n.focus({preventScroll:!0})}},[]),(0,Q.jsxs)(`aside`,{className:`panel`,ref:t,"aria-label":`${e.bot.name} details`,children:[(0,Q.jsxs)(`header`,{className:`panel-header`,children:[(0,Q.jsx)(`div`,{className:`tabs`,role:`tablist`,children:Lt.map(t=>(0,Q.jsx)(`button`,{role:`tab`,"aria-selected":e.tab===t.id,className:_(`tab`,e.tab===t.id&&`on`),onClick:()=>e.onTab(t.id),children:t.label},t.id))}),(0,Q.jsx)(`button`,{className:`icon-btn round`,"aria-label":`Close panel`,onClick:e.onClose,children:(0,Q.jsx)(le,{})})]}),(0,Q.jsxs)(`div`,{className:`panel-scroll`,children:[e.tab===`routines`&&(0,Q.jsx)(ve,{bot:e.bot,focusId:e.focus}),e.tab===`activity`&&(0,Q.jsx)(qe,{bot:e.bot,onOpenRoutine:t=>e.onTab(`routines`,t)}),e.tab===`memory`&&(0,Q.jsx)(zt,{bot:e.bot}),e.tab===`files`&&(0,Q.jsx)(Fe,{bot:e.bot,focus:e.focus,focusAt:e.focusAt},e.bot.id),e.tab===`usage`&&(0,Q.jsx)(It,{bot:e.bot}),e.tab===`settings`&&(0,Q.jsx)(dt,{bot:e.bot,onDeleted:e.onDeleted,onOpenBot:e.onOpenBot,onTab:e.onTab},e.bot.id)]})]})}function zt({bot:e}){let t=F(t=>t.memories[e.id]),[n,r]=(0,Z.useState)(``),[i,s]=(0,Z.useState)(null),c=K(()=>T.loadMemories(e.id),e.id),l=async e=>{await C(`forget that`,()=>T.forgetMemory(e))&&a({kind:`info`,text:`Forgot “${S(e.text,60)}”`,action:{label:`Undo`,run:()=>C(`put that memory back`,()=>o.addMemory(e.botId,e.text))}})};return(0,Q.jsxs)(`div`,{className:`tab-body`,children:[(0,Q.jsxs)(`p`,{className:`muted`,children:[`Things `,e.name,` remembers between conversations. It saves these itself when it learns something durable; you can add or remove them here.`]}),(0,Q.jsxs)(`form`,{className:`memory-add`,onSubmit:async t=>{if(t.preventDefault(),n.trim()){s(null);try{await o.addMemory(e.id,n),r(``)}catch(e){s(e.message)}}},children:[(0,Q.jsx)(`input`,{value:n,onChange:e=>r(e.target.value),placeholder:`e.g. I prefer bullet points over paragraphs`,"aria-label":`New memory`}),(0,Q.jsx)(`button`,{className:`btn small`,type:`submit`,disabled:!n.trim(),children:`Add`})]}),i&&(0,Q.jsx)(`p`,{className:`form-error`,children:i}),t===void 0&&(c.error?(0,Q.jsx)(Ae,{what:`memories`,error:c.error,onRetry:c.retry}):(0,Q.jsx)(`p`,{className:`muted`,children:`Loading…`})),t?.length===0&&(0,Q.jsx)(`p`,{className:`empty-note`,children:`Nothing remembered yet.`}),(0,Q.jsx)(`ul`,{className:`memory-list`,children:t?.map(e=>(0,Q.jsxs)(`li`,{children:[(0,Q.jsx)(`span`,{children:e.text}),(0,Q.jsx)(`span`,{className:`memory-age`,children:b(e.createdAt)}),(0,Q.jsx)(`button`,{className:`icon-btn small`,"aria-label":`Forget`,title:`Forget`,onClick:()=>void l(e),children:(0,Q.jsx)(W,{size:14})})]},e.id))})]})}export{Rt as Panel};
@@ -0,0 +1 @@
1
+ import{i as e,t}from"./react-C1iE8mnY.js";import{n,r}from"./reactions-rw0YeCmA.js";import{i}from"./util-Cx5feSJu.js";import{c as a,o,s}from"./index-Dhwn-rf0.js";var c=e(),l=t(),u=s(()=>o(()=>import(`./emoji-catalog-DfpPz8qw.js`),[])),d=e=>!!e.closest(`.msg-react[aria-expanded="true"]`);function f(e){let{m:t,botName:r,anchor:a,followUps:o,onPick:s,onClose:u}=e,f=(0,c.useRef)(null),[h,_]=(0,c.useState)(!1),v=new Set((t.reactions??[]).filter(e=>e.by===`owner`).map(e=>e.emoji)),y=(0,c.useRef)(!1),b=e=>{y.current||(y.current=!0,u(e))};(0,c.useLayoutEffect)(()=>{let e=f.current;if(!e)return;e.showPopover(),e.querySelector(`.quick-reactions [tabindex="0"]`)?.focus({preventScroll:!0});let t=t=>{let n=t.target;n&&!e.contains(n)&&!d(n)&&b(!1)};return addEventListener(`pointerdown`,t,!0),()=>{removeEventListener(`pointerdown`,t,!0),e.matches(`:popover-open`)&&e.hidePopover()}},[]),(0,c.useLayoutEffect)(()=>{let e=f.current;if(!e)return;let t=()=>{let t=a.getBoundingClientRect(),n=e.getBoundingClientRect(),r=Math.max(8,Math.min(t.left,window.innerWidth-n.width-8)),i=t.top-n.height-6,o=i>=8?i:Math.min(t.bottom+6,Math.max(8,window.innerHeight-n.height-8));e.style.left=`${r}px`,e.style.top=`${o}px`};t();let n=new ResizeObserver(t);n.observe(e),addEventListener(`resize`,t);let r=a.closest(`.messages`);return r?.addEventListener(`scroll`,t,{passive:!0}),()=>{n.disconnect(),removeEventListener(`resize`,t),r?.removeEventListener(`scroll`,t)}},[a]);let x=e=>{e.key===`Escape`&&(e.preventDefault(),e.stopPropagation(),b(!0))},S=`React to ${r}'s message`;return(0,l.jsxs)(`div`,{ref:f,popover:`manual`,role:`dialog`,"aria-label":S,className:i(`reaction-picker`,h&&`full`),onKeyDown:x,onBlur:e=>{let t=e.relatedTarget;t&&!e.currentTarget.contains(t)&&!d(t)&&b(!1)},children:[(0,l.jsxs)(`div`,{className:`quick-row`,children:[(0,l.jsx)(m,{className:`quick-reactions`,label:`Quick reactions`,columns:0,children:n.map((e,t)=>(0,l.jsx)(p,{emoji:e,on:v.has(e),followUp:o.includes(e),botName:r,first:t===0,onPick:s},e))}),(0,l.jsx)(`button`,{type:`button`,className:`reaction-more`,"aria-expanded":h,onClick:()=>_(e=>!e),children:h?`Less`:`More…`})]}),h&&(0,l.jsx)(g,{mine:v,followUps:o,botName:r,onPick:s}),(0,l.jsx)(`p`,{className:`reaction-note`,children:o.length?`Feedback for ${r}'s next turn. ${o.join(` `)} also ${o.length===1?`asks`:`ask`} for a short follow-up now.`:`Feedback for ${r}'s next turn: no reaction starts one.`})]})}function p(e){let{emoji:t,on:n,followUp:r,botName:a}=e,o=n?`Take off your ${t}`:r?`React ${t}: ${a} follows up on it`:`React ${t}`;return(0,l.jsx)(`button`,{type:`button`,className:i(`emoji-btn`,n&&`on`),"aria-pressed":n,"aria-label":r&&!n?`${t}, asks for a follow-up`:t,title:o,tabIndex:e.first?0:-1,onClick:()=>e.onPick(t),children:t})}function m(e){return(0,l.jsx)(`div`,{className:e.className,role:e.role??`toolbar`,"aria-label":e.label,onKeyDown:t=>{let n=[...t.currentTarget.querySelectorAll(`button`)],r=n.indexOf(document.activeElement);if(r<0)return;let i=-1;t.key===`ArrowRight`?i=Math.min(n.length-1,r+1):t.key===`ArrowLeft`?i=Math.max(0,r-1):t.key===`Home`?i=0:t.key===`End`?i=n.length-1:e.columns&&(t.key===`ArrowDown`||t.key===`ArrowUp`)&&(i=h(n,r,t.key===`ArrowDown`)),i<0||(t.preventDefault(),n[r].tabIndex=-1,n[i].tabIndex=0,n[i].focus(),n[i].scrollIntoView({block:`nearest`}))},children:e.children})}function h(e,t,n){let r=e[t].getBoundingClientRect(),i=t,a=1/0,o=1/0;return e.forEach((e,t)=>{let s=e.getBoundingClientRect(),c=n?s.top-r.top:r.top-s.top;if(c<r.height/2)return;let l=Math.abs(s.left-r.left);(c<a-1||Math.abs(c-a)<=1&&l<o)&&(a=c,o=l,i=t)}),i}function g(e){let{mod:t,failed:n,retry:i}=a(u),[o,s]=(0,c.useState)(``),[d,f]=(0,c.useState)(``),[h,g]=(0,c.useState)(null),_=(0,c.useRef)(null);(0,c.useEffect)(()=>{window.matchMedia(`(pointer: fine)`).matches&&_.current?.focus()},[]);let v=t&&o.trim()?t.searchEmoji(o):null,y=v?[{name:`Results for “${o.trim()}”`,emoji:v}]:t?.CATEGORIES??[],b=!0,x=t=>{let n=(0,l.jsx)(p,{emoji:t,on:e.mine.has(t),followUp:e.followUps.includes(t),botName:e.botName,first:b,onPick:e.onPick},t);return b=!1,n};return(0,l.jsxs)(`div`,{className:`full-picker`,children:[(0,l.jsx)(`input`,{ref:_,type:`search`,className:`emoji-search`,placeholder:`Search emoji`,"aria-label":`Search emoji`,value:o,onChange:e=>s(e.target.value),onKeyDown:t=>{if(t.key===`Enter`&&v?.length)t.preventDefault(),e.onPick(v[0].emoji);else if(t.key===`ArrowDown`){let e=t.currentTarget.parentElement?.querySelector(`.emoji-grid [tabindex="0"]`);e&&(t.preventDefault(),e.focus())}}}),(0,l.jsx)(`span`,{className:`sr-only`,role:`status`,children:v?`${v.length} emoji found`:``}),n?(0,l.jsxs)(`p`,{className:`muted emoji-empty`,role:`alert`,children:[`Couldn't load the emoji.`,` `,(0,l.jsx)(`button`,{type:`button`,className:`link-btn`,onClick:i,children:`Retry`})]}):t?v&&!v.length?(0,l.jsx)(`p`,{className:`muted emoji-empty`,children:`No emoji called that here: type or paste any emoji below.`}):(0,l.jsx)(m,{className:`emoji-grid`,label:`Emoji`,columns:1,role:`group`,children:y.map(e=>(0,l.jsxs)(`div`,{className:`emoji-group`,role:`group`,"aria-label":e.name,children:[(0,l.jsx)(`div`,{className:`emoji-group-name`,"aria-hidden":`true`,children:e.name}),(0,l.jsx)(`div`,{className:`emoji-cells`,children:e.emoji.map(e=>x(e.emoji))})]},e.name))}):(0,l.jsx)(`p`,{className:`muted emoji-empty`,role:`status`,children:`Loading…`}),(0,l.jsxs)(`form`,{className:`emoji-own`,onSubmit:t=>{t.preventDefault();let n=r(d);if(!n)return g(`One emoji, with no text: say 🦖`);g(null),e.onPick(n)},children:[(0,l.jsx)(`input`,{value:d,onChange:e=>{f(e.target.value),g(null)},placeholder:`Any emoji`,"aria-label":`Type or paste any emoji`,"aria-invalid":!!h,"aria-describedby":h?`emoji-own-error`:void 0,enterKeyHint:`send`,autoComplete:`off`}),(0,l.jsx)(`button`,{type:`submit`,className:`btn small`,disabled:!d.trim(),children:`React`}),h&&(0,l.jsx)(`span`,{id:`emoji-own-error`,className:`form-error`,role:`alert`,children:h})]})]})}export{f as ReactionPicker};
@@ -0,0 +1,4 @@
1
+ import{i as e,t}from"./react-C1iE8mnY.js";import{E as n,F as r,I as i,L as a,R as o,_ as s,a as c,d as l,g as u,i as d,l as f,p,r as m,s as h,t as g,u as _,v}from"./util-Cx5feSJu.js";import{C as y,r as b,t as x}from"./Steps-CKtB3GAM.js";import{C as S,M as C,S as w,b as T,f as ee,j as E,l as D,o as O}from"./Icons-nytVAa9g.js";import{_ as k,a as te,l as A,r as ne,u as re,y as j}from"./index-Dhwn-rf0.js";import{g as M,h as ie,l as ae,p as N}from"./Setup-C-3P7Mtn.js";import{n as oe}from"./StarterSetup-Ce2V1_Cs.js";var P=e(),F=t();function I(e,t,n=!1){let[r,i]=(0,P.useState)(null),[a,o]=(0,P.useState)(0);return(0,P.useEffect)(()=>{let t=!0;return n||i(null),e(()=>t).then(()=>{t&&i(null)}).catch(e=>{t&&i(e instanceof Error?e.message:String(e))}),()=>{t=!1}},[t,a,n]),{error:r,retry:()=>o(e=>e+1)}}function L({what:e,error:t,onRetry:n}){return(0,F.jsxs)(`p`,{className:`load-error`,role:`alert`,children:[(0,F.jsxs)(`span`,{children:[`Couldn't load `,e,`: `,t]}),(0,F.jsx)(`button`,{type:`button`,className:`link-btn`,onClick:n,children:`Retry`})]})}function R({bot:e,focusId:t}){let r=j(t=>t.routines[e.id]),[i,a]=(0,P.useState)(!1),[o,s]=(0,P.useState)(t),c=I(()=>k.loadRoutines(e.id),e.id),[l,u]=(0,P.useState)(null),[d,f]=(0,P.useState)(null),[p,m]=(0,P.useState)(null);(0,P.useEffect)(()=>{let t=!0;return n.starterInstall(e.id).then(e=>t&&u(e)).catch(()=>t&&u(null)),()=>{t=!1}},[e.id,r]);let h=()=>void v(`open its setup`,async()=>{let e=(await n.templates()).find(e=>e.id===l?.templateId&&e.needs);if(!e)throw Error(`Its starter (${l?.templateName}) is not on this server any more.`);f(e)},m);return(0,P.useEffect)(()=>{t&&s(t)},[t]),(0,F.jsxs)(`div`,{className:`tab-body`,children:[(0,F.jsxs)(`div`,{className:`tab-intro`,children:[(0,F.jsxs)(`p`,{className:`muted`,children:[`Routines run on a schedule, from a webhook, or on demand. Results land in the chat. You can also just ask `,e.name,` to set one up.`]}),!i&&(0,F.jsxs)(`button`,{className:`btn primary small`,onClick:()=>a(!0),children:[(0,F.jsx)(S,{size:16}),` New routine`]})]}),i&&(0,F.jsx)(me,{bot:e,onDone:e=>{a(!1),e&&s(e.id)}}),r===void 0&&(c.error?(0,F.jsx)(L,{what:`routines`,error:c.error,onRetry:c.retry}):(0,F.jsx)(`p`,{className:`muted`,children:`Loading…`})),r?.length===0&&!i&&(0,F.jsx)(`p`,{className:`empty-note`,children:`No routines yet.`}),(0,F.jsx)(`ul`,{className:`routine-list`,children:r?.map(t=>(0,F.jsx)(`li`,{children:(0,F.jsx)(z,{routine:t,bot:e,open:o===t.id,onToggle:()=>s(o===t.id?null:t.id),starterPending:!!l?.pending.includes(t.id),onSetup:h})},t.id))}),p&&(0,F.jsx)(`p`,{className:`form-error`,children:p}),d&&(0,F.jsx)(re,{title:`Set up ${e.name}`,onClose:()=>f(null),wide:!0,children:(0,F.jsx)(oe,{bot:e,template:d,onDone:()=>f(null)})})]})}function z(e){let{routine:t,bot:a,open:o,onToggle:s,starterPending:l,onSetup:u}=e,[f,m]=(0,P.useState)(!1),[h,g]=(0,P.useState)(null),[_,y]=(0,P.useState)(null),[b,x]=(0,P.useState)(!1),S=async(e,t,n)=>{g(e),y(null),await v(t,n,y),g(null)};if(f)return(0,F.jsx)(me,{bot:a,routine:t,onDone:()=>m(!1)});let D=!t.enabled&&i(t.schedule);return(0,F.jsxs)(`article`,{className:d(`routine-card`,o&&`open`,!t.enabled&&`paused`),children:[(0,F.jsxs)(`button`,{className:`routine-head`,onClick:s,"aria-expanded":o,children:[(0,F.jsxs)(`span`,{className:`routine-title`,children:[t.lastStatus&&(0,F.jsx)(`span`,{className:d(`status-dot`,t.lastStatus),title:`Last run ${t.lastStatus}`}),t.name]}),(0,F.jsxs)(`span`,{className:`routine-sub`,children:[t.scheduleText??(t.webhook?`Webhook only`:`On demand`),t.webhook&&t.schedule&&(0,F.jsxs)(`span`,{className:`badge`,title:`Has a webhook`,children:[(0,F.jsx)(C,{size:12}),` webhook`]})]}),(0,F.jsx)(`span`,{className:`routine-next`,children:D?(0,F.jsx)(`span`,{className:`pill`,children:`Done`}):t.enabled?t.nextRunAt?`Next ${p(t.nextRunAt)}`:t.lastRunAt?`Ran ${p(t.lastRunAt)}`:``:(0,F.jsx)(`span`,{className:`pill`,children:`Paused`})})]}),o&&(0,F.jsxs)(`div`,{className:`routine-body`,children:[(0,F.jsx)(`p`,{className:`routine-instruction`,children:t.instruction}),t.skill&&(0,F.jsxs)(`p`,{className:`hint`,children:[`Follows the skill `,(0,F.jsxs)(`code`,{children:[`/`,t.skill]})]}),(0,F.jsxs)(`div`,{className:`routine-actions`,children:[(0,F.jsxs)(`button`,{className:`btn small`,disabled:!!h,onClick:()=>S(`run`,`start a run`,()=>n.runRoutine(t.id)),children:[(0,F.jsx)(w,{size:14}),` `,h===`run`?`Starting…`:`Run now`]}),l&&!t.enabled?(0,F.jsxs)(`button`,{className:`btn small primary`,onClick:u,title:`It came with a starter: you turn it on from its setup, after a test run`,children:[(0,F.jsx)(w,{size:14}),` Set up to turn on`]}):!D&&(0,F.jsxs)(`button`,{className:`btn small`,disabled:!!h,onClick:()=>S(`toggle`,t.enabled?`pause the routine`:`resume the routine`,()=>n.updateRoutine(t.id,{enabled:!t.enabled})),children:[t.enabled?(0,F.jsx)(T,{size:14}):(0,F.jsx)(w,{size:14}),` `,t.enabled?`Pause`:`Resume`]}),(0,F.jsxs)(`button`,{className:`btn small`,onClick:()=>m(!0),children:[(0,F.jsx)(ee,{size:14}),` Edit`]}),(0,F.jsxs)(`button`,{className:`btn small danger`,disabled:!!h,onClick:()=>x(!0),children:[(0,F.jsx)(E,{size:14}),` Delete`]})]}),b&&(0,F.jsx)(A,{title:`Delete “${t.name}”?`,confirmLabel:`Delete routine`,what:`delete the routine`,danger:!0,onConfirm:()=>n.deleteRoutine(t.id),onClose:()=>x(!1),children:(0,F.jsxs)(`p`,{children:[`Its schedule stops`,t.webhook?` and its webhook stops working (callers will get 401)`:``,`, queued runs are cancelled, and its run history is deleted. A run already in progress still finishes and posts its reply.`]})}),_&&(0,F.jsx)(`p`,{className:`form-error`,children:_}),D&&(0,F.jsx)(`p`,{className:`hint`,children:`This one-off has run. Edit it and give it a new time to run it again.`}),(t.model||t.session===`own`)&&(0,F.jsx)(`p`,{className:`hint`,children:[t.model?`Runs on ${t.model}`:null,t.session===`own`?`in its own session (apart from the chat)`:null].filter(Boolean).join(` `)}),t.timeLimitMs!==null&&(0,F.jsxs)(`p`,{className:`hint`,children:[`Each run is stopped if it takes longer than `,r(t.timeLimitMs)]}),t.enabled&&t.nextRunAt&&(0,F.jsxs)(`p`,{className:`hint`,children:[`Next run `,c(t.nextRunAt,t.timezone),` (`,t.timezone,`)`]}),t.webhook?(0,F.jsx)(U,{routine:t,bot:a}):(0,F.jsxs)(`button`,{className:`link-btn`,disabled:!!h,onClick:()=>S(`hook`,`add a webhook`,()=>n.updateRoutine(t.id,{webhook:!0})),children:[(0,F.jsx)(C,{size:14}),` Add a webhook trigger`]}),(0,F.jsx)(G,{routineId:t.id})]})]})}function B({label:e,value:t,secret:n}){let[r,i]=(0,P.useState)(!n),[a,o]=(0,P.useState)(!1);return(0,F.jsxs)(`div`,{className:`copy-field`,children:[(0,F.jsx)(`span`,{className:`field-label`,children:e}),(0,F.jsxs)(`div`,{className:`copy-row`,children:[(0,F.jsx)(`code`,{className:`copy-value`,children:r?t:`•`.repeat(24)}),n&&(0,F.jsx)(`button`,{className:`link-btn`,onClick:()=>i(!r),children:r?`Hide`:`Show`}),(0,F.jsx)(`button`,{className:`icon-btn small`,"aria-label":`Copy ${e}`,title:`Copy`,onClick:async()=>{await y(t)&&(o(!0),setTimeout(()=>o(!1),1200))},children:a?`✓`:(0,F.jsx)(D,{size:14})})]})]})}var V=[{value:`generic`,label:`Anything (callers send the key)`},{value:`github`,label:`GitHub`,setup:`In the repository or organization: Settings → Webhooks → Add webhook. Payload URL: the URL above; Content type: application/json; Secret: the key above (or a secret of your own, saved in the routine editor). GitHub signs every delivery, and its ping is answered without a run.`},{value:`linear`,label:`Linear`,setup:`In Linear: Settings → API → Webhooks → New webhook, with the URL above. Then copy the signing secret Linear shows into the routine editor; until then deliveries are refused.`},{value:`slack`,label:`Slack`,setup:`Save your Slack app's Signing Secret (Basic Information) in the routine editor first, then use the URL above as the Request URL (Event Subscriptions, Slash Commands or Interactivity). Slack's URL check is answered without a run, and its retries are dropped.`},{value:`sentry`,label:`Sentry`,setup:`In Sentry: Settings → Developer Settings → a custom integration, with the URL above as its Webhook URL. Save the integration's Client Secret in the routine editor; until then deliveries are refused.`}];function H(e){let t=e.values.length===1?e.values[0]:`${e.values.slice(0,-1).join(`, `)} or ${e.values.at(-1)}`;return`Runs only when ${e.kind===`header`?`the ${e.name} header`:`the body's ${e.name}`} is ${t}; other deliveries are skipped.`}function U({routine:e,bot:t}){let r=e.webhook,[i,a]=(0,P.useState)(null),[o,s]=(0,P.useState)(!1),c=`curl -X POST '${r.url}' \\\n -H 'Authorization: Bearer ${r.key}' \\\n -H 'Content-Type: application/json' \\\n -d '{"hello": "world"}'`,l=r.source!==`generic`,u=V.find(e=>e.value===r.source),f=l&&r.source!==`github`&&!r.hasSecret;return(0,F.jsxs)(`div`,{className:`webhook-box`,children:[(0,F.jsxs)(`h4`,{children:[(0,F.jsx)(C,{size:14}),` Webhook`,l?` from ${u?.label}`:``]}),(0,F.jsx)(B,{label:`POST to`,value:r.url}),(0,F.jsx)(B,{label:r.source===`github`&&!r.hasSecret?`Key (also the GitHub secret)`:l?`Key`:`Key (Bearer token)`,value:r.key,secret:!0}),f&&(0,F.jsx)(`p`,{className:`hint warn`,children:`No signing secret saved yet: deliveries are refused (401) until you add it in the editor.`}),r.filter&&(0,F.jsx)(`p`,{className:`hint`,children:H(r.filter)}),r.callbackUrl&&(0,F.jsxs)(`p`,{className:`hint`,children:[`Each finished run’s result (not a test run’s) is POSTed to `,r.callbackUrl,`, signed with the key (X-Boton-Signature).`]}),l?(0,F.jsxs)(`details`,{children:[(0,F.jsx)(`summary`,{children:`Setup`}),(0,F.jsx)(`p`,{className:`hint`,children:u?.setup}),(0,F.jsx)(`p`,{className:`hint`,children:`Only deliveries with a valid signature run; the key alone is not enough.`})]}):(0,F.jsxs)(`details`,{children:[(0,F.jsx)(`summary`,{children:`Example`}),(0,F.jsx)(`pre`,{className:`code`,children:c}),(0,F.jsxs)(`p`,{className:`hint`,children:[`The body is handed to the Boton with the instruction, marked as data from outside (never as instructions from you). Returns 202 once the run is queued, with a `,(0,F.jsx)(`code`,{children:`Location`}),` to poll (GET it with the same key); add `,(0,F.jsx)(`code`,{children:`?wait=1`}),` to wait for the result.`]})]}),t.access===`host`&&(0,F.jsx)(`p`,{className:d(`hint`,!e.webhookWorkspace&&`warn`),children:e.webhookWorkspace?`Webhook runs use Workspace access (sandboxed).`:`Webhook runs use Full computer access.`}),o&&(0,F.jsx)(se,{routine:e,onClose:()=>s(!1)}),(0,F.jsxs)(`div`,{className:`routine-actions`,children:[!o&&(0,F.jsxs)(`button`,{className:`btn small`,onClick:()=>s(!0),children:[(0,F.jsx)(w,{size:14}),` Test`]}),(0,F.jsx)(`button`,{className:`btn small`,onClick:()=>a(`rotate`),children:`New key`}),(0,F.jsx)(`button`,{className:`btn small`,onClick:()=>a(`remove`),children:`Remove webhook`})]}),i===`rotate`&&(0,F.jsx)(A,{title:`Make a new webhook key?`,confirmLabel:`Make a new key`,what:`make a new key`,danger:!0,onConfirm:()=>n.updateRoutine(e.id,{rotateKey:!0}),onClose:()=>a(null),children:(0,F.jsx)(`p`,{children:`The current key stops working at once. Callers using the old key will get 401 until you give them the new one.`})}),i===`remove`&&(0,F.jsx)(A,{title:`Remove the webhook?`,confirmLabel:`Remove webhook`,what:`remove the webhook`,danger:!0,onConfirm:()=>n.updateRoutine(e.id,{webhook:!1}),onClose:()=>a(null),children:(0,F.jsxs)(`p`,{children:[`The URL stops accepting calls: callers will get 401. `,e.schedule?`The schedule keeps running.`:`The routine can still be run by hand.`,` Adding a webhook again makes a new key.`]})})]})}var W=`{
2
+ "hello": "world"
3
+ }`;function se({routine:e,onClose:t}){let[r,i]=(0,P.useState)(null),[a,o]=(0,P.useState)(null),[s,c]=(0,P.useState)(!1),[l,u]=(0,P.useState)(null),[d,f]=(0,P.useState)(!1);(0,P.useEffect)(()=>{let t=!0;return(async()=>{try{let r=(await n.runs(e.id)).find(e=>e.trigger===`webhook`&&e.payloadBytes>0);if(!r)return t&&i(W);let a=(await n.runPayload(r.id)).payload??``;try{a=JSON.stringify(JSON.parse(a),null,2)}catch{}if(!t)return;i(a),o(r.startedAt)}catch{t&&i(W)}})(),()=>{t=!1}},[e.id]);let m=async()=>{c(!0),u(null),await v(`start a test run`,()=>n.runRoutine(e.id,{payload:r??``}),u)&&f(!0),c(!1)};return(0,F.jsxs)(`div`,{className:`test-box`,children:[(0,F.jsx)(`span`,{className:`field-label`,children:`Test payload`}),r===null?(0,F.jsx)(`p`,{className:`hint`,children:`Loading the last delivery…`}):(0,F.jsx)(`textarea`,{value:r,onChange:e=>{i(e.target.value),f(!1)},rows:8,spellCheck:!1,"aria-label":`Test payload`}),(0,F.jsxs)(`p`,{className:`hint`,children:[a?`From the last webhook delivery (${p(a)}). `:``,`Runs like a webhook call, as a dry run: the Boton is told to avoid anything hard to undo. It doesn’t change the status dot, push to your phone or send its result to the callback URL.`]}),l&&(0,F.jsx)(`p`,{className:`form-error`,children:l}),d&&(0,F.jsx)(`p`,{className:`hint`,children:`Test run queued; its result shows in the run history and the chat.`}),(0,F.jsxs)(`div`,{className:`routine-actions`,children:[(0,F.jsxs)(`button`,{className:`btn small primary`,disabled:s||r===null,onClick:()=>void m(),children:[(0,F.jsx)(w,{size:14}),` `,s?`Starting…`:`Run test`]}),(0,F.jsx)(`button`,{className:`btn small ghost`,onClick:t,children:`Close`})]})]})}function G({routineId:e}){let t=j(t=>t.runs[e]),[n,r]=(0,P.useState)(null),i=I(()=>k.loadRuns(e),e);return t?(0,F.jsxs)(`div`,{className:`runs`,children:[(0,F.jsx)(`h4`,{children:`Run history`}),t.length===0&&(0,F.jsx)(`p`,{className:`hint`,children:`No runs yet. Try “Run now”.`}),(0,F.jsx)(`ul`,{children:t.map(e=>(0,F.jsxs)(`li`,{children:[(0,F.jsxs)(`button`,{className:`run-row`,onClick:()=>r(n===e.id?null:e.id),"aria-expanded":n===e.id,children:[(0,F.jsx)(`span`,{className:d(`status-dot`,e.quiet?`quiet`:e.meta?.cancelled?`skipped`:e.status)}),(0,F.jsx)(`span`,{children:Z(e)}),(0,F.jsx)(`span`,{className:`run-trigger`,children:e.trigger}),(0,F.jsx)(`span`,{className:`run-time`,title:c(e.startedAt),children:p(e.startedAt)})]}),n===e.id&&(e.status===`queued`||e.status===`running`||e.error||e.reply||e.payloadPreview||e.lastError||e.quiet||e.meta?.notifications?.length||e.meta?.callback||e.usage||e.steps>0)&&(0,F.jsxs)(`div`,{className:`run-detail`,children:[e.quiet&&(0,F.jsx)(`p`,{className:`hint`,children:`Nothing worth reporting, so nothing was posted in the chat.`}),e.status===`queued`&&e.retryAt&&(0,F.jsxs)(`p`,{className:`hint`,children:[`Trying again `,c(e.retryAt),e.lastError?`. Last attempt: ${e.lastError}`:``]}),(e.status===`queued`||e.status===`running`)&&(0,F.jsx)(q,{run:e}),e.status!==`queued`&&e.lastError&&e.lastError!==e.error&&(0,F.jsxs)(`p`,{className:`hint`,children:[`An earlier attempt failed: `,e.lastError]}),e.meta?.retryOf&&(0,F.jsxs)(`p`,{className:`hint`,children:[`Retry of an earlier run that failed`,e.meta.retryOf.error?`: ${e.meta.retryOf.error}`:``]}),e.meta?.cancelled?(0,F.jsxs)(`p`,{className:`hint`,children:[e.error??`Cancelled`,` `,c(e.meta.cancelled.at),`:`,` `,e.meta.cancelled.started?`an attempt at it ran, so it may have partly run.`:`it never started.`,` It is not tried again by itself.`]}):e.error&&(0,F.jsx)(`p`,{className:e.status===`skipped`?`hint`:`form-error`,children:e.error}),e.meta?.timedOut&&(0,F.jsx)(`p`,{className:`hint`,children:`It isn't tried again by itself. If it needs longer, edit the routine and raise its time limit.`}),e.status===`failed`&&(0,F.jsx)(K,{run:e}),e.payloadPreview!==null&&(0,F.jsx)(X,{run:e}),e.reply&&(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`span`,{className:`field-label`,children:e.quiet?`Note`:`Reply`}),(0,F.jsx)(`p`,{className:`run-reply`,children:g(e.reply,600)})]}),e.meta?.notifications?.map((e,t)=>(0,F.jsx)(`p`,{className:d(`hint`,!e.ok&&`warn`),title:c(e.at),children:ce(e)},t)),e.meta?.callback&&(0,F.jsx)(J,{run:e}),e.steps>0&&(0,F.jsx)(x,{count:e.steps,load:b(e.id)}),e.usage&&(0,F.jsxs)(`p`,{className:`hint`,children:[s(e.usage),e.model?` · ${e.model}`:``,e.usage.costUsd?` · ${m(e.usage.costUsd)}`:``,e.attempts>1?` (${e.attempts} attempts together)`:``]})]})]},e.id))})]}):i.error?(0,F.jsx)(L,{what:`run history`,error:i.error,onRetry:i.retry}):null}function K({run:e,onRetried:t}){let[r,i]=(0,P.useState)(!1),[a,o]=(0,P.useState)(null);if(e.meta?.retriedAs)return(0,F.jsx)(`p`,{className:`hint`,children:`Retried: see the newer run above.`});let s=!e.meta?.cancelled||e.meta.cancelled.started;return(0,F.jsxs)(F.Fragment,{children:[(0,F.jsxs)(`p`,{className:`hint`,children:[s?`It may have partly run. `:``,e.payloadBytes>0?`Retrying runs it again with the same payload`:`Retrying runs it again`,s?`, and the Boton is told to check what was already done.`:`.`,` `,(0,F.jsx)(`button`,{className:`link-btn`,onClick:()=>{i(!0),v(`retry the run`,()=>n.retryRun(e.id),o).then(e=>e&&t?.()).finally(()=>i(!1))},disabled:r,children:r?`Retrying…`:`Retry this run`})]}),a&&(0,F.jsx)(`p`,{className:`form-error`,children:a})]})}function q({run:e,onCancelled:t}){let[r,i]=(0,P.useState)(!1),[a,o]=(0,P.useState)(null);return(0,F.jsxs)(F.Fragment,{children:[(0,F.jsxs)(`p`,{className:`hint`,children:[e.status===`running`?`Cancelling stops its agent now; what it already did stays done.`:e.attempts>0?`Cancelling means it is not tried again; its earlier attempt may have partly run.`:`Cancelling means it never starts.`,` It is not tried again by itself.`,` `,(0,F.jsx)(`button`,{className:`link-btn`,onClick:()=>{i(!0),v(`cancel the run`,()=>n.cancelRun(e.id),o).then(e=>e&&t?.()).finally(()=>i(!1))},disabled:r,children:r?`Cancelling…`:`Cancel this run`})]}),a&&(0,F.jsx)(`p`,{className:`form-error`,children:a})]})}function J({run:e,onSent:t}){let[r,i]=(0,P.useState)(!1),[a,o]=(0,P.useState)(null),s=e.meta.callback;return(0,F.jsxs)(F.Fragment,{children:[(0,F.jsxs)(`p`,{className:d(`hint`,!s.ok&&!s.pending&&`warn`),title:c(s.at),children:[$(s),!s.ok&&!s.pending&&(0,F.jsxs)(F.Fragment,{children:[` `,(0,F.jsx)(`button`,{className:`link-btn`,onClick:()=>{i(!0),v(`resend the result`,()=>n.resendCallback(e.id),o).then(e=>e&&t?.()).finally(()=>i(!1))},disabled:r,children:r?`Sending…`:`Send it again`})]})]}),a&&(0,F.jsx)(`p`,{className:`form-error`,children:a})]})}function Y(){let[e,t]=(0,P.useState)(null),[r,i]=(0,P.useState)(0),a=ae();(0,P.useEffect)(()=>{let e=window.setInterval(()=>a.current&&i(e=>e+1),5e3);return()=>window.clearInterval(e)},[]);let o=I(async()=>t(await n.runsAttention()),`runs-attention:${r}`),s=()=>void n.runsAttention().then(t,()=>o.retry());return(0,F.jsxs)(`section`,{className:`server-info runs-to-check`,children:[(0,F.jsx)(`h4`,{children:`Runs to check`}),(0,F.jsxs)(`p`,{className:`hint`,children:[`Routine runs that failed in the last `,7,` days and were not retried, runs waiting to try again by themselves, and runs whose result never reached the callback URL. Test runs are not listed.`]}),!e&&(o.error?(0,F.jsx)(L,{what:`runs to check`,error:o.error,onRetry:o.retry}):(0,F.jsx)(`p`,{className:`hint`,children:`Loading…`})),e&&!e.failed.length&&!e.held.length&&!e.callbacks.length&&(0,F.jsx)(`p`,{className:`hint`,children:`Nothing to check.`}),e&&(e.failed.length>0||e.held.length>0||e.callbacks.length>0)&&(0,F.jsxs)(`ul`,{className:`device-list audit-list`,children:[e.failed.map(e=>(0,F.jsx)(`li`,{children:(0,F.jsxs)(`span`,{className:`device-text`,children:[(0,F.jsxs)(`span`,{children:[(0,F.jsx)(`span`,{className:`status-dot failed`,"aria-hidden":`true`}),` `,(0,F.jsx)(`strong`,{children:e.botName}),` · `,e.routineName]}),(0,F.jsxs)(`span`,{className:`hint`,children:[e.trigger,` run, failed`,` `,(0,F.jsx)(`time`,{dateTime:e.finishedAt??e.startedAt,title:c(e.finishedAt??e.startedAt),children:p(e.finishedAt??e.startedAt)})]}),e.error&&(0,F.jsx)(`span`,{className:`form-error`,children:g(e.error,300)}),(0,F.jsx)(K,{run:e,onRetried:s})]})},e.id)),e.held.map(e=>(0,F.jsx)(`li`,{children:(0,F.jsxs)(`span`,{className:`device-text`,children:[(0,F.jsxs)(`span`,{children:[(0,F.jsx)(`span`,{className:`status-dot queued`,"aria-hidden":`true`}),` `,(0,F.jsx)(`strong`,{children:e.botName}),` · `,e.routineName]}),(0,F.jsxs)(`span`,{className:`hint`,children:[e.trigger,` run, trying again `,e.retryAt?c(e.retryAt):`soon`,e.lastError?`. Last attempt: ${g(e.lastError,200)}`:``]}),(0,F.jsx)(q,{run:e,onCancelled:s})]})},e.id)),e.callbacks.map(e=>(0,F.jsx)(`li`,{children:(0,F.jsxs)(`span`,{className:`device-text`,children:[(0,F.jsxs)(`span`,{children:[(0,F.jsx)(`span`,{className:`status-dot failed`,"aria-hidden":`true`}),` `,(0,F.jsx)(`strong`,{children:e.botName}),` · `,e.routineName]}),(0,F.jsxs)(`span`,{className:`hint`,children:[e.trigger,` run, `,e.status,` `,(0,F.jsx)(`time`,{dateTime:e.finishedAt??e.startedAt,title:c(e.finishedAt??e.startedAt),children:p(e.finishedAt??e.startedAt)}),`, result not delivered`]}),(0,F.jsx)(J,{run:e,onSent:s})]})},`callback-${e.id}`))]})]})}function X({run:e}){let[t,r]=(0,P.useState)(null),[i,a]=(0,P.useState)(null),o=new TextEncoder().encode(e.payloadPreview??``).length<e.payloadBytes;return(0,F.jsxs)(F.Fragment,{children:[e.headers&&(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`span`,{className:`field-label`,children:`Headers`}),(0,F.jsx)(`pre`,{className:`code`,children:Object.entries(e.headers).map(([e,t])=>`${e}: ${t}`).join(`
4
+ `)})]}),(0,F.jsxs)(`span`,{className:`field-label`,children:[`Payload`,e.payloadBytes>=1024?` · ${Math.round(e.payloadBytes/1024)} KB`:``]}),(0,F.jsxs)(`pre`,{className:`code`,children:[t??e.payloadPreview,t===null&&o?`…`:``]}),t===null&&o&&(0,F.jsx)(`button`,{className:`link-btn`,onClick:()=>v(`load the payload`,async()=>r((await n.runPayload(e.id)).payload??``),a),children:`Show all of it`}),i&&(0,F.jsx)(`p`,{className:`form-error`,children:i})]})}function Z(e){let t=e.attempts>1?` · attempt ${e.attempts}`:``;if(e.status===`running`)return`Running${t}`;if(e.status===`queued`)return e.retryAt?`Waiting · tries again ${p(e.retryAt)}`:`Queued`;if(e.status===`skipped`)return`Skipped`;let n=e.finishedAt?` · ${h(new Date(e.finishedAt).getTime()-new Date(e.startedAt).getTime())}`:``,r=e.attempts>1?` · ${e.attempts} attempts`:``,i=e.usage?` · ${u(o(e.usage))} tokens`:``,a=e.quiet&&e.reply?` · ${g(e.reply,60)}`:``;return`${e.quiet?`Quiet`:e.status===`succeeded`?`Succeeded`:e.meta?.cancelled?`Cancelled`:e.meta?.timedOut?`Timed out`:`Failed`}${n}${r}${i}${a}`}var Q={failure:`about the failure`,result:`with the result`,notify:`from botons notify`,approval:`about an approval request`,test:`test`};function $(e){if(e.pending)return`Sending the result to ${e.url}…`;let t=e.attempts>1?` (${e.attempts} tries)`:``;return e.ok?`Sent the result to ${e.url}${t}`:`Couldn't send the result to ${e.url}${t}: ${e.error??`HTTP ${e.status}`}`}function ce(e){return e.ok?`Pushed to your phone ${Q[e.reason]}`:`Push ${Q[e.reason]} failed: ${e.error??`unknown error`}`}var le=[{value:`auto`,label:e=>`When a run fails, or ${e} sends one`},{value:`always`,label:()=>`Every result (not quiet runs)`},{value:`failures`,label:()=>`Only when a run fails`},{value:`never`,label:()=>`Never`}];function ue(e){return e?.schedule?e.schedule.startsWith(`cron `)?e.schedule.slice(5):e.schedule.startsWith(`once `)?`at ${e.schedule.slice(5)}`:e.schedule:``}function de({bot:e,providers:t,value:n,onChange:r}){let i=t.find(t=>t.id===e.provider),a=(i?.models??[]).filter(e=>e.id),o=i?.models.find(t=>t.id===e.model)?.label??(e.model||`its default`),[s,c]=(0,P.useState)(()=>!!n&&!a.some(e=>e.id===n)),l=a.some(e=>e.id===n);return(0,F.jsxs)(`label`,{children:[(0,F.jsx)(`span`,{className:`field-label`,children:`Model`}),(0,F.jsxs)(`select`,{value:s?`__custom`:l?n:``,onChange:e=>{let t=e.target.value;c(t===`__custom`),r(t===`__custom`?n:t)},children:[(0,F.jsx)(`option`,{value:``,children:`${e.name}'s model (${o})`}),a.map(e=>(0,F.jsx)(`option`,{value:e.id,children:e.label},e.id)),(0,F.jsx)(`option`,{value:`__custom`,children:`Other model…`})]}),s&&(0,F.jsx)(`input`,{"aria-label":`Model id`,placeholder:`Model id, e.g. claude-haiku-5-5`,value:n,onChange:e=>r(e.target.value)}),(0,F.jsxs)(`span`,{className:`hint`,children:[`A smaller model keeps frequent, simple runs cheap; your chat keeps `,e.name,`'s own.`]})]})}function fe({bot:e,value:t,onChange:n}){let i=j(e=>e.info?.turnTimeoutMs??null),a=i===null?`the server's limit`:r(i),[o,s]=(0,P.useState)(()=>l(t??1e3)),c=(0,P.useRef)(null),u=_(o.amount,o.unit,i),d=`error`in u?u.error:``;(0,P.useEffect)(()=>c.current?.setCustomValidity(d),[d,t]);let p=e=>{s(e);let t=_(e.amount,e.unit,i);`ms`in t&&n(t.ms)},{min:m,max:h}=f(o.unit,i);return(0,F.jsxs)(`div`,{className:`time-limit-field`,children:[(0,F.jsx)(`span`,{className:`field-label`,children:`Time limit`}),(0,F.jsxs)(`select`,{"aria-label":`Time limit`,value:t===null?`default`:`custom`,onChange:e=>{let t=e.target.value===`custom`?Math.min(6e5,i??6e5):null;t!==null&&s(l(t)),n(t)},children:[(0,F.jsx)(`option`,{value:`default`,children:`Default (${a})`}),(0,F.jsx)(`option`,{value:`custom`,children:`Shorter limit…`})]}),t!==null&&(0,F.jsxs)(`span`,{className:`time-limit-amount`,children:[(0,F.jsx)(`input`,{ref:c,type:`number`,"aria-label":`Time limit in ${o.unit}`,min:m,max:h,step:`any`,required:!0,value:o.amount,onChange:e=>p({...o,amount:e.target.value})}),(0,F.jsxs)(`select`,{"aria-label":`Time limit unit`,value:o.unit,onChange:e=>p({...o,unit:e.target.value}),children:[(0,F.jsx)(`option`,{value:`minutes`,children:`minutes`}),(0,F.jsx)(`option`,{value:`seconds`,children:`seconds`})]})]}),t!==null&&d&&(0,F.jsx)(`span`,{className:`form-error`,children:d}),(0,F.jsx)(`span`,{className:`hint`,children:t===null?`A run still going ${a} after ${e.name} starts on it is stopped and fails as timed out: the longest this server lets any turn run.`:`A run still going ${r(t)} after ${e.name} starts on it is stopped and fails as timed out. Time waiting its turn doesn't count, and it isn't tried again by itself. At most ${a}.`})]})}function pe(e){let{bot:t}=e,n=e.providers.find(e=>e.id===t.provider),r=e.model.trim(),i=e=>n?.effort.levels.find(t=>t.id===e)?.label??e,a=!e.value&&!!r&&!!t.effort&&!M(n,r,t.effort);return(0,F.jsx)(N,{provider:n,model:r||t.model,value:e.value,onChange:e.onChange,inherit:t.effort?i(t.effort):`its default`,note:a?`The Boton’s ${i(t.effort)} isn’t in this model’s list: give the routine one of its own.`:void 0})}function me({bot:e,routine:t,onDone:r}){let[i,o]=(0,P.useState)(t?.name??``),[s,l]=(0,P.useState)(t?.instruction??``),[u,d]=(0,P.useState)(ue(t)),[f,p]=(0,P.useState)(t?.timezone??e.timezone),[m,h]=(0,P.useState)(!!t?.webhook),[g,_]=(0,P.useState)(t?.notify??`auto`),[v,y]=(0,P.useState)(t?.webhookWorkspace??!0),b=(0,P.useId)(),[x,S]=(0,P.useState)(t?.webhook?.source??`generic`),[C,w]=(0,P.useState)(``),[T,ee]=(0,P.useState)(!1),[E,D]=(0,P.useState)(t?.webhook?.filter?.kind??`none`),[O,k]=(0,P.useState)(t?.webhook?.filter?.name??``),[A,re]=(0,P.useState)(t?.webhook?.filter?.values.join(`, `)??``),[M,ae]=(0,P.useState)(t?.webhook?.callbackUrl??``),[N,oe]=(0,P.useState)(t?.skill??``),I=te(e,ne().skills),[L,R]=(0,P.useState)(t?.model??``),[z,B]=(0,P.useState)(t?.effort??``),[H,U]=(0,P.useState)(t?.session??`chat`),[W,se]=(0,P.useState)(t?.timeLimitMs??null),G=j(e=>e.providers),K=j(e=>!!e.settings?.ntfy.enabled),[q,J]=(0,P.useState)(null),[Y,X]=(0,P.useState)(!1),[Z,Q]=(0,P.useState)(null),$=a(f);(0,P.useEffect)(()=>{if(!u.trim()||!$){J(null);return}let e=!0,t=setTimeout(()=>{n.previewSchedule(u,f).then(t=>e&&J(t),()=>e&&J(null))},250);return()=>{e=!1,clearTimeout(t)}},[u,f,$]);let ce=async()=>{X(!0),Q(null);try{let a=A.split(`,`).map(e=>e.trim()).filter(Boolean),o=m?{webhookSource:x,webhookFilter:E===`none`?null:{kind:E,name:O.trim(),values:a},callbackUrl:M.trim()||null,...C.trim()?{webhookSecret:C.trim()}:T?{webhookSecret:null}:{}}:{},c={name:i,instruction:s,schedule:u.trim()||null,timezone:f,webhook:m,notify:g,webhookWorkspace:v,skill:N||null,model:L.trim()||null,effort:z||null,session:H,timeLimitMs:W,...o};r(t?await n.updateRoutine(t.id,c):await n.createRoutine(e.id,c))}catch(e){Q(e.message)}finally{X(!1)}};return(0,F.jsxs)(`form`,{className:`routine-editor`,onSubmit:e=>{e.preventDefault(),ce()},children:[(0,F.jsx)(`h3`,{children:t?`Edit routine`:`New routine`}),(0,F.jsxs)(`label`,{children:[(0,F.jsx)(`span`,{className:`field-label`,children:`Name`}),(0,F.jsx)(`input`,{value:i,onChange:e=>o(e.target.value),placeholder:`Morning brief`,required:!0,maxLength:80})]}),(0,F.jsxs)(`label`,{children:[(0,F.jsxs)(`span`,{className:`field-label`,children:[`What `,e.name,` does each run`]}),(0,F.jsx)(`textarea`,{value:s,onChange:e=>l(e.target.value),rows:4,required:!0,placeholder:`Summarize anything new in ~/Projects/foo since the last run. Only report if something changed.`})]}),(0,F.jsxs)(`label`,{children:[(0,F.jsx)(`span`,{className:`field-label`,children:`Skill`}),(0,F.jsxs)(`select`,{value:N,onChange:e=>oe(e.target.value),children:[(0,F.jsx)(`option`,{value:``,children:`None`}),I.map(e=>(0,F.jsx)(`option`,{value:e.name,children:e.name},e.name)),N&&!I.some(e=>e.name===N)&&(0,F.jsxs)(`option`,{value:N,children:[N,` (not available)`]})]}),(0,F.jsx)(`span`,{className:`hint`,children:`Every run follows this procedure from the skills library, with the instruction above as its input.`})]}),(0,F.jsxs)(`label`,{children:[(0,F.jsx)(`span`,{className:`field-label`,children:`When`}),(0,F.jsx)(`input`,{value:u,onChange:e=>d(e.target.value),placeholder:`weekdays at 8am · every 2h · 0 9 * * 1 · leave empty for webhook/manual`})]}),q&&(q.ok?(0,F.jsxs)(`div`,{className:`hint schedule-preview`,children:[(0,F.jsx)(`span`,{children:q.text}),q.nextRuns.length>0?(0,F.jsx)(`ol`,{"aria-label":`Next runs`,children:q.nextRuns.map(e=>(0,F.jsx)(`li`,{children:c(e,f)},e))}):(0,F.jsx)(`span`,{children:` · no more runs`})]}):(0,F.jsx)(`p`,{className:`hint warn`,children:q.error})),(0,F.jsx)(ie,{label:`Timezone`,value:f,onChange:p}),(0,F.jsxs)(`label`,{className:`check`,children:[(0,F.jsx)(`input`,{type:`checkbox`,checked:m,onChange:e=>h(e.target.checked)}),(0,F.jsx)(`span`,{children:`Webhook trigger`})]}),m&&(0,F.jsxs)(F.Fragment,{children:[(0,F.jsxs)(`label`,{children:[(0,F.jsx)(`span`,{className:`field-label`,children:`Sent by`}),(0,F.jsx)(`select`,{value:x,onChange:e=>S(e.target.value),children:V.map(e=>(0,F.jsx)(`option`,{value:e.value,children:e.label},e.value))}),(0,F.jsx)(`span`,{className:`hint`,children:x===`generic`?`Callers send the routine’s key with each call.`:`Each delivery must carry a valid signature; the key alone is refused.`})]}),x!==`generic`&&(0,F.jsxs)(`div`,{className:`check-group`,children:[(0,F.jsxs)(`label`,{htmlFor:b,className:`field-label`,children:[`Signing secret`,x===`github`?` (optional)`:``]}),(0,F.jsx)(`input`,{id:b,type:`password`,autoComplete:`off`,value:C,onChange:e=>w(e.target.value),placeholder:t?.webhook?.hasSecret&&!T?`Saved. Type a new one to replace it`:x===`github`?`Leave empty to use the routine’s key`:`Paste the secret the sender shows`}),t?.webhook?.hasSecret&&!C&&(0,F.jsxs)(`label`,{className:`check`,children:[(0,F.jsx)(`input`,{type:`checkbox`,checked:T,onChange:e=>ee(e.target.checked)}),(0,F.jsx)(`span`,{children:`Remove the saved secret`})]})]}),(0,F.jsxs)(`label`,{children:[(0,F.jsx)(`span`,{className:`field-label`,children:`Run for`}),(0,F.jsxs)(`select`,{value:E,onChange:e=>D(e.target.value),children:[(0,F.jsx)(`option`,{value:`none`,children:`Every delivery`}),(0,F.jsx)(`option`,{value:`header`,children:`Deliveries with a header value`}),(0,F.jsx)(`option`,{value:`field`,children:`Deliveries with a JSON field value`})]})]}),E!==`none`&&(0,F.jsxs)(`div`,{className:`filter-fields`,children:[(0,F.jsx)(`input`,{value:O,onChange:e=>k(e.target.value),"aria-label":E===`header`?`Header name`:`Field path`,placeholder:E===`header`?x===`linear`?`Linear-Event`:`X-GitHub-Event`:`action or pull_request.base.ref`}),(0,F.jsx)(`input`,{value:A,onChange:e=>re(e.target.value),"aria-label":`Values`,placeholder:`push, pull_request`}),(0,F.jsx)(`span`,{className:`hint`,children:`Other deliveries are answered 200 with nothing run. Separate values with commas.`})]}),(0,F.jsxs)(`label`,{children:[(0,F.jsx)(`span`,{className:`field-label`,children:`Send results to (optional)`}),(0,F.jsx)(`input`,{type:`url`,value:M,onChange:e=>ae(e.target.value),placeholder:`https://example.com/boton-results`}),(0,F.jsx)(`span`,{className:`hint`,children:`Each finished run’s status and reply are POSTed here as JSON, signed with the routine’s key (X-Boton-Signature). Addresses on this machine or your private network are refused.`})]})]}),m&&e.access===`host`&&(0,F.jsxs)(`div`,{className:`check-group`,children:[(0,F.jsxs)(`label`,{className:`check`,children:[(0,F.jsx)(`input`,{type:`checkbox`,checked:v,onChange:e=>y(e.target.checked)}),(0,F.jsx)(`span`,{children:`Webhook runs use Workspace access`})]}),(0,F.jsx)(`span`,{className:`hint`,children:v?`Recommended. A webhook's payload comes from outside, so its runs are sandboxed even though ${e.name} has Full computer access.`:`Webhook runs get Full computer access. Anyone who can call the webhook can put text in front of ${e.name}.`})]}),(0,F.jsx)(de,{bot:e,providers:G,value:L,onChange:R}),(0,F.jsx)(pe,{bot:e,providers:G,model:L,value:z,onChange:B}),(0,F.jsxs)(`label`,{children:[(0,F.jsx)(`span`,{className:`field-label`,children:`Session`}),(0,F.jsxs)(`select`,{value:H,onChange:e=>U(e.target.value),children:[(0,F.jsx)(`option`,{value:`chat`,children:`${e.name}'s chat session`}),(0,F.jsx)(`option`,{value:`own`,children:`Its own session`})]}),(0,F.jsx)(`span`,{className:`hint`,children:H===`own`?`Runs remember each other but stay out of your chat's context; ${e.name} still sees their latest results when you chat. Results are posted in the chat either way.`:`Runs happen in your conversation with ${e.name}, so each one sees the chat and the chat sees it.`})]}),(0,F.jsx)(fe,{bot:e,value:W,onChange:se}),(0,F.jsxs)(`label`,{children:[(0,F.jsx)(`span`,{className:`field-label`,children:`Push to your phone`}),(0,F.jsx)(`select`,{value:g,onChange:e=>_(e.target.value),children:le.map(t=>(0,F.jsx)(`option`,{value:t.value,children:t.label(e.name)},t.value))}),(0,F.jsx)(`span`,{className:`hint`,children:K?`Runs with nothing to report stay quiet: no chat message and no push.`:`Push notifications are off. Set them up in Settings.`})]}),Z&&(0,F.jsx)(`p`,{className:`form-error`,children:Z}),(0,F.jsxs)(`div`,{className:`form-actions`,children:[(0,F.jsx)(`button`,{type:`button`,className:`btn ghost`,onClick:()=>r(null),children:`Cancel`}),(0,F.jsx)(`button`,{type:`submit`,className:`btn primary`,disabled:Y||!i.trim()||!s.trim()||!$||q!==null&&!q.ok||m&&E!==`none`&&(!O.trim()||!A.trim()),children:Y?`Saving…`:t?`Save`:`Create routine`})]})]})}var he=(e,t)=>JSON.stringify(e)===JSON.stringify(t);function ge(e,t){let n={...e};for(let[e,r]of Object.entries(t))r&&(n[e]=r.value);return n}function _e(e,t){let n={};for(let[r,i]of Object.entries(t))i!==void 0&&!he(e[r],i)&&(n[r]=i);return n}function ve(e,t){let n={},r=Object.keys(t);(r.includes(`provider`)||r.includes(`model`))&&!r.includes(`effort`)&&r.push(`effort`);for(let t of r)n[t]=e[t];return n}function ye(e,t,n){let r={...e};for(let[e,i]of Object.entries(t))r[e]={value:i,seq:n};return r}function be(e,t,n){let r={...e};for(let e of Object.keys(t))r[e]?.seq===n&&delete r[e];return r}function xe(){let e=Promise.resolve();return t=>{let n=e.then(t,t);return e=n.catch(()=>{}),n}}var Se=(e,t,n)=>n?e.includes(t)?[...e]:[...e,t]:e.filter(e=>e!==t);function Ce(e,t,n=e=>e){let r=Object.keys(t);if(r.length!==1)return r.includes(`provider`)||r.includes(`model`)?`Brain changed`:`Saved`;switch(r[0]){case`name`:return`Name saved`;case`title`:return`Role saved`;case`instructions`:return`Instructions saved`;case`look`:return`Look saved`;case`provider`:case`model`:return`Brain changed`;case`effort`:return`Effort changed`;case`access`:return t.access===`host`?`${e.name} has full computer access`:`${e.name} works in its own folder`;case`timezone`:return`Timezone saved`;case`pinned`:return t.pinned?`Pinned to the top`:`Unpinned`;case`groupId`:return`Group changed`;case`skills`:return`Skills updated`;case`askFirst`:case`alwaysAllow`:return`Approval rules saved`;case`connections`:{let r=t.connections??[],i=r.filter(t=>!e.connections.includes(t)),a=e.connections.filter(e=>!r.includes(e));return i.length===1&&!a.length?`${n(i[0])} on`:a.length===1&&!i.length?`${n(a[0])} off`:`Connectors updated`}default:return`Saved`}}function we(e){if(e.includes(`*`)){let t=e.length-1;return t?`All public sites + ${t} more`:`All public sites`}return e.length?e.length===1?e[0]:`${e.length} sites`:`No sites yet`}function Te(e){let t=e.trim().toLowerCase();return t=t.replace(/^[a-z][a-z0-9+.-]*:\/\//,``),t=t.replace(/[/?#].*$/,``),t.replace(/\.$/,``)}function Ee({title:e,children:t}){let n=(0,P.useId)();return(0,F.jsxs)(`section`,{className:`set-section`,"aria-labelledby":n,children:[(0,F.jsx)(`h3`,{id:n,children:e}),(0,F.jsx)(`div`,{className:`set-card`,children:t})]})}function De({label:e,help:t,control:n,children:r,htmlFor:i}){return(0,F.jsxs)(`div`,{className:`set-row`,children:[(0,F.jsxs)(`div`,{className:`set-row-main`,children:[(0,F.jsxs)(`div`,{className:`set-row-text`,children:[i?(0,F.jsx)(`label`,{className:`set-label`,htmlFor:i,children:e}):(0,F.jsx)(`span`,{className:`set-label`,children:e}),t&&(0,F.jsx)(`span`,{className:`set-help`,children:t})]}),n&&(0,F.jsx)(`div`,{className:`set-control`,children:n})]}),r]})}function Oe({label:e,value:t,onClick:n}){return(0,F.jsxs)(`button`,{type:`button`,className:`set-row set-link`,onClick:n,children:[(0,F.jsx)(`span`,{className:`set-label`,children:e}),(0,F.jsxs)(`span`,{className:`set-value`,children:[t,` `,(0,F.jsx)(O,{size:14})]})]})}function ke({label:e,children:t,onOpen:n}){return(0,F.jsxs)(`details`,{className:`set-more`,onToggle:e=>e.currentTarget.open&&n?.(),children:[(0,F.jsx)(`summary`,{children:e}),(0,F.jsx)(`div`,{className:`set-more-body`,children:t})]})}function Ae({on:e,label:t,onChange:n,disabled:r}){return(0,F.jsx)(`button`,{type:`button`,role:`switch`,"aria-checked":e,"aria-label":t,className:d(`toggle`,e&&`on`),disabled:r,onClick:()=>n(!e),children:(0,F.jsx)(`span`,{className:`toggle-knob`,"aria-hidden":!0})})}function je(e,t,n=()=>!0){let[r,i]=(0,P.useState)(e),a=(0,P.useRef)(!1),o=(0,P.useRef)({draft:r,value:e,save:t,valid:n});o.current={draft:r,value:e,save:t,valid:n},(0,P.useEffect)(()=>{a.current||i(e)},[e]);let s=()=>{a.current=!1;let{draft:e,value:t,save:n,valid:r}=o.current;if(e!==t){if(!r(e))return i(t);n(e)}};return(0,P.useEffect)(()=>()=>void(a.current&&s()),[]),{value:r,onChange:e=>{a.current=!0,i(e.target.value)},onBlur:s,onKeyDown:e=>{e.key===`Escape`?(e.preventDefault(),e.stopPropagation(),a.current=!1,i(o.current.value),requestAnimationFrame(()=>e.target.blur())):e.key===`Enter`&&e.currentTarget instanceof HTMLInputElement&&(e.preventDefault(),e.currentTarget.blur())}}}export{R as _,Ae as a,I as b,Ce as c,ge as d,xe as f,ve as g,Se as h,Ee as i,_e as l,we as m,ke as n,je as o,be as p,De as r,ye as s,Oe as t,Te as u,Y as v,L as y};