@hanzo/build 0.2.80 → 0.2.82

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 (168) hide show
  1. package/README.md +13 -0
  2. package/lib/activity.d.ts +3 -7
  3. package/lib/activity.js +18 -5
  4. package/lib/api/agents.d.ts +1 -20
  5. package/lib/api/agents.js +16 -8
  6. package/lib/api/auto.d.ts +3 -23
  7. package/lib/api/auto.js +39 -22
  8. package/lib/api/billing.d.ts +0 -35
  9. package/lib/api/billing.js +6 -5
  10. package/lib/api/call.d.ts +16 -8
  11. package/lib/api/call.js +32 -1
  12. package/lib/api/capabilities.d.ts +0 -11
  13. package/lib/api/capabilities.js +2 -1
  14. package/lib/api/codebases.d.ts +0 -10
  15. package/lib/api/codebases.js +3 -2
  16. package/lib/api/coding.d.ts +3 -32
  17. package/lib/api/coding.js +13 -7
  18. package/lib/api/connectors.d.ts +1 -17
  19. package/lib/api/connectors.js +5 -4
  20. package/lib/api/environment.d.ts +6 -20
  21. package/lib/api/environment.js +22 -19
  22. package/lib/api/github.d.ts +0 -22
  23. package/lib/api/github.js +3 -2
  24. package/lib/api/keys.d.ts +0 -14
  25. package/lib/api/keys.js +4 -3
  26. package/lib/api/library.d.ts +3 -17
  27. package/lib/api/library.js +23 -22
  28. package/lib/api/links.d.ts +1 -1
  29. package/lib/api/links.js +1 -1
  30. package/lib/api/machines.d.ts +0 -15
  31. package/lib/api/machines.js +6 -5
  32. package/lib/api/mcp.d.ts +0 -9
  33. package/lib/api/mcp.js +3 -2
  34. package/lib/api/members.d.ts +0 -15
  35. package/lib/api/members.js +4 -3
  36. package/lib/api/memory.d.ts +0 -11
  37. package/lib/api/memory.js +2 -1
  38. package/lib/api/places.d.ts +1 -9
  39. package/lib/api/places.js +12 -1
  40. package/lib/api/plugins.d.ts +1 -16
  41. package/lib/api/plugins.js +4 -3
  42. package/lib/api/profile.d.ts +0 -10
  43. package/lib/api/profile.js +5 -4
  44. package/lib/api/projects.d.ts +0 -14
  45. package/lib/api/projects.js +11 -10
  46. package/lib/api/provider.d.ts +2 -14
  47. package/lib/api/provider.js +12 -10
  48. package/lib/api/sandbox.d.ts +0 -17
  49. package/lib/api/sandbox.js +5 -4
  50. package/lib/api/sessions.d.ts +0 -17
  51. package/lib/api/sessions.js +5 -4
  52. package/lib/api/skills.d.ts +1 -15
  53. package/lib/api/skills.js +5 -4
  54. package/lib/api/studio.d.ts +0 -14
  55. package/lib/api/studio.js +6 -5
  56. package/lib/api/turn.d.ts +6 -28
  57. package/lib/api/turn.js +105 -49
  58. package/lib/api/verdict.d.ts +0 -9
  59. package/lib/api/verdict.js +2 -1
  60. package/lib/api/webhooks.d.ts +1 -14
  61. package/lib/api/webhooks.js +5 -4
  62. package/lib/ask.js +7 -4
  63. package/lib/automations.d.ts +5 -4
  64. package/lib/automations.js +95 -74
  65. package/lib/back.js +10 -4
  66. package/lib/builder.js +7 -5
  67. package/lib/carry.js +20 -16
  68. package/lib/customize/agents.js +78 -31
  69. package/lib/customize/connectors.js +62 -48
  70. package/lib/customize/index.js +43 -10
  71. package/lib/customize/plugins.js +34 -22
  72. package/lib/customize/say.d.ts +13 -1
  73. package/lib/customize/say.js +15 -21
  74. package/lib/customize/skills.js +31 -22
  75. package/lib/customize/ui.d.ts +5 -9
  76. package/lib/customize/ui.js +26 -16
  77. package/lib/data.d.ts +1 -1
  78. package/lib/data.js +3 -2
  79. package/lib/desk.js +45 -33
  80. package/lib/door.js +35 -10
  81. package/lib/environment.js +25 -19
  82. package/lib/files.js +43 -34
  83. package/lib/find.js +12 -9
  84. package/lib/foot.js +7 -4
  85. package/lib/forge.js +61 -40
  86. package/lib/gear.d.ts +2 -1
  87. package/lib/gear.js +6 -4
  88. package/lib/git.js +29 -19
  89. package/lib/landing.d.ts +5 -5
  90. package/lib/landing.js +52 -44
  91. package/lib/library.js +73 -51
  92. package/lib/merge.d.ts +2 -2
  93. package/lib/meter.d.ts +1 -1
  94. package/lib/meter.js +14 -5
  95. package/lib/mic.js +12 -5
  96. package/lib/mind.d.ts +1 -0
  97. package/lib/mind.js +5 -3
  98. package/lib/nav.d.ts +6 -5
  99. package/lib/nav.js +37 -30
  100. package/lib/panel.d.ts +2 -1
  101. package/lib/panel.js +52 -36
  102. package/lib/plan.d.ts +4 -20
  103. package/lib/plan.js +26 -10
  104. package/lib/plans.js +49 -30
  105. package/lib/plugins/design.js +3 -2
  106. package/lib/plugins/index.d.ts +23 -4
  107. package/lib/plugins/index.js +11 -26
  108. package/lib/plugins/pdf.js +2 -1
  109. package/lib/plugins/project.js +7 -4
  110. package/lib/plugins/research.js +3 -2
  111. package/lib/plugins/sketch.js +6 -3
  112. package/lib/plugins/slides.js +3 -2
  113. package/lib/plugins/template.js +6 -3
  114. package/lib/plus.js +25 -13
  115. package/lib/prefs.d.ts +0 -14
  116. package/lib/prefs.js +2 -1
  117. package/lib/project.js +69 -62
  118. package/lib/prompt.d.ts +12 -11
  119. package/lib/prompt.js +38 -20
  120. package/lib/publish.js +17 -12
  121. package/lib/reply.js +6 -3
  122. package/lib/run.js +72 -54
  123. package/lib/say.d.ts +2 -0
  124. package/lib/say.js +7 -0
  125. package/lib/scheduled.js +54 -43
  126. package/lib/section.d.ts +1 -0
  127. package/lib/section.js +8 -2
  128. package/lib/settings/account.d.ts +1 -0
  129. package/lib/settings/account.js +31 -21
  130. package/lib/settings/billing.js +55 -28
  131. package/lib/settings/capabilities.js +26 -15
  132. package/lib/settings/card.js +19 -12
  133. package/lib/settings/code.js +22 -11
  134. package/lib/settings/credits.d.ts +4 -4
  135. package/lib/settings/credits.js +34 -21
  136. package/lib/settings/environments.js +21 -14
  137. package/lib/settings/general.js +26 -14
  138. package/lib/settings/index.js +6 -3
  139. package/lib/settings/integrations.js +42 -28
  140. package/lib/settings/keys.js +37 -19
  141. package/lib/settings/list.d.ts +0 -5
  142. package/lib/settings/list.js +24 -15
  143. package/lib/settings/machines.d.ts +3 -0
  144. package/lib/settings/machines.js +63 -38
  145. package/lib/settings/members.js +23 -14
  146. package/lib/settings/memory.js +12 -6
  147. package/lib/settings/notifications.js +32 -19
  148. package/lib/settings/plan.d.ts +1 -1
  149. package/lib/settings/plan.js +40 -17
  150. package/lib/settings/privacy.js +26 -13
  151. package/lib/settings/ui.d.ts +3 -2
  152. package/lib/settings/ui.js +9 -3
  153. package/lib/settings/usage.js +7 -3
  154. package/lib/shelf.js +20 -17
  155. package/lib/side.d.ts +1 -26
  156. package/lib/side.js +11 -5
  157. package/lib/standing.d.ts +2 -10
  158. package/lib/standing.js +5 -3
  159. package/lib/suggest.d.ts +8 -28
  160. package/lib/suggest.js +39 -4
  161. package/lib/suggestions.js +14 -5
  162. package/lib/summary.d.ts +1 -1
  163. package/lib/summary.js +4 -1
  164. package/lib/sync.js +22 -12
  165. package/lib/tools.d.ts +4 -3
  166. package/lib/tools.js +10 -7
  167. package/lib/transcript.js +10 -8
  168. package/package.json +4 -2
@@ -15,8 +15,14 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
15
15
  * so the choices are the tools that are on; "every tool" is whatever the
16
16
  * fleet's MCP server serves when it runs. A budget is required: what it may
17
17
  * spend each period, and in one run.
18
+ *
19
+ * Its words are the reader's language (@hanzo/i18n `t`); an agent's name,
20
+ * description, instructions, model and runs, and the categories the host and
21
+ * the platform give their templates, are drawn as given.
18
22
  */
19
23
  import { SizableText, XStack, YStack } from '@hanzo/gui';
24
+ import { phrase } from '@hanzo/i18n';
25
+ import { useI18n } from '@hanzo/i18n/react';
20
26
  import { ArrowLeft, Upload } from '@hanzogui/lucide-icons-2';
21
27
  import { Button, Input, Switch, Textarea } from '@hanzo/ui';
22
28
  import { ModelPicker } from '@hanzo/ui/models';
@@ -31,7 +37,7 @@ import { say } from './say.js';
31
37
  import { Add, Choice, Confirm, Empty, Failed, Field, Grid, Line, Mark, matches, mono, Part, Sheet, Soft, Tile, useCount, Visitor } from './ui.js';
32
38
  /** How many cards Browse draws at a time. */
33
39
  const PAGE = 60;
34
- const ANY = 'All';
40
+ const ANY = phrase('All');
35
41
  /** Categories as Browse offers them: the default agents' and presets' first, as listed, then the rest by how many each holds. */
36
42
  export function kinds(first, rest) {
37
43
  const lead = [...new Set(first.map((c) => c.category))];
@@ -42,13 +48,40 @@ export function kinds(first, rest) {
42
48
  return [ANY, ...lead, ...[...count.keys()].sort((a, b) => count.get(b) - count.get(a) || a.localeCompare(b))];
43
49
  }
44
50
  const money = (m) => `$${(m / 1_000_000).toFixed(m % 10_000 ? 4 : 2).replace(/\.?0+$/, '')}`;
51
+ /** A category as Browse names it: All, and the platform's presets and people, in the reader's words; the rest are the host's, as given. */
52
+ const category = (c, t) => (c === ANY || c === 'Preset' || c === 'Person' ? t(c) : c);
53
+ /**
54
+ * A budget's sentences by its period ('' where the agent names none): whole
55
+ * sentences, so a language says the period its own way.
56
+ */
57
+ const SPENT = {
58
+ day: phrase('{spent} of {cap} this day'),
59
+ week: phrase('{spent} of {cap} this week'),
60
+ month: phrase('{spent} of {cap} this month'),
61
+ '': phrase('{spent} of {cap} this period'),
62
+ };
63
+ const BUDGET = {
64
+ day: phrase('{spent} of {cap} spent this day; at most {task} a run.'),
65
+ week: phrase('{spent} of {cap} spent this week; at most {task} a run.'),
66
+ month: phrase('{spent} of {cap} spent this month; at most {task} a run.'),
67
+ '': phrase('{spent} of {cap} spent this period; at most {task} a run.'),
68
+ };
69
+ const SO_FAR = {
70
+ day: phrase('Spent {spent} this day.'),
71
+ week: phrase('Spent {spent} this week.'),
72
+ month: phrase('Spent {spent} this month.'),
73
+ '': phrase('Spent {spent} this period.'),
74
+ };
75
+ const EACH = { day: phrase('Each day, USD'), week: phrase('Each week, USD'), month: phrase('Each month, USD') };
76
+ const PERIOD = { day: phrase('Day'), week: phrase('Week'), month: phrase('Month') };
45
77
  export function Agents({ view, q, adding, onAdding, onView, onCount }) {
78
+ const { t } = useI18n();
46
79
  const host = useHost();
47
- const t = useTarget();
80
+ const target = useTarget();
48
81
  const signed = Boolean(host.person);
49
82
  const browsing = view === 'discover';
50
- const mine = useLoad(signed ? () => agents(t) : null, [], [t, signed]);
51
- const offered = useLoad(browsing ? () => presets(t) : null, [], [t, view]);
83
+ const mine = useLoad(signed ? () => agents(target) : null, [], [target, signed]);
84
+ const offered = useLoad(browsing ? () => presets(target) : null, [], [target, view]);
52
85
  const figures = useLoad(browsing && host.personas ? host.personas : null, [], [view]);
53
86
  useCount(mine.value.length, onCount);
54
87
  const [note, setNote] = useState('');
@@ -62,10 +95,10 @@ export function Agents({ view, q, adding, onAdding, onView, onCount }) {
62
95
  setUsing(c);
63
96
  setNote('');
64
97
  try {
65
- opened(await use(t, c, window.location.href));
98
+ opened(await use(target, c, window.location.href));
66
99
  }
67
100
  catch (e) {
68
- setNote(say(e));
101
+ setNote(say(e, t));
69
102
  }
70
103
  finally {
71
104
  setUsing(null);
@@ -75,25 +108,33 @@ export function Agents({ view, q, adding, onAdding, onView, onCount }) {
75
108
  onAdding(false);
76
109
  onView('yours');
77
110
  mine.reload();
78
- setNote(`${a.name} is saved`);
111
+ setNote(t('{name} is saved', { name: a.name }));
79
112
  } })) : null;
80
113
  if (browsing) {
81
114
  const first = [...(host.cast ?? []), ...offered.value.map(fromPreset)];
82
115
  const all = [...first, ...figures.value];
83
116
  const shown = all.filter((c) => (kind === ANY || c.category === kind) && matches(q, c.title, c.name, c.description, c.category));
84
117
  const failed = figures.error ?? offered.error;
85
- return (_jsxs(YStack, { gap: "$3", children: [signed ? (_jsxs(XStack, { gap: "$2", flexWrap: "wrap", children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => onAdding(true), children: "Start from scratch" }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => setMaking('self'), children: "Clone yourself" }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => setMaking('person'), children: "Create from a person" })] })) : null, _jsx(XStack, { overflowX: "auto", overflowY: "hidden", pb: "$1", children: _jsx(Choice, { label: "Category", value: kind, options: kinds(first, figures.value).map((id) => ({ id, label: id })), onChange: setKind }) }), _jsx(Line, { children: note }), shown.length ? (_jsx(Grid, { label: "Agents to start from", children: shown.slice(0, more).map((c) => (_jsx(Tile, { title: c.title, detail: c.description === c.title || c.description === c.category ? undefined : c.description, meta: c.category, mark: _jsx(Mark, { name: c.title, size: 40, logo: c.avatar }), action: signed ? _jsx(Add, { name: c.title, label: "Use", added: false, busy: using !== null, onPress: () => void take(c) }) : undefined }, `${c.category}/${c.name}/${c.title}`))) })) : figures.loading || offered.loading ? null : (_jsx(Soft, { children: `No agent matches “${q.trim()}”.` })), shown.length > more ? (_jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => setMore(more + PAGE), children: "More" }) })) : null, failed ? _jsx(Failed, { error: failed, onRetry: figures.error ? figures.reload : offered.reload }) : figures.loading || offered.loading ? _jsx(Soft, { children: "Loading\u2026" }) : null, editor, making ? _jsx(Person, { self: making === 'self', onClose: () => setMaking(null), onMade: opened }) : null] }));
118
+ return (_jsxs(YStack, { gap: "$3", children: [signed ? (_jsxs(XStack, { gap: "$2", flexWrap: "wrap", children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => onAdding(true), children: t('Start from scratch') }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => setMaking('self'), children: t('Clone yourself') }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => setMaking('person'), children: t('Create from a person') })] })) : null, _jsx(XStack, { overflowX: "auto", overflowY: "hidden", pb: "$1", children: _jsx(Choice, { label: t('Category'), value: kind, options: kinds(first, figures.value).map((id) => ({ id, label: category(id, t) })), onChange: setKind }) }), _jsx(Line, { children: note }), shown.length ? (_jsx(Grid, { label: t('Agents to start from'), children: shown.slice(0, more).map((c) => (_jsx(Tile, { title: c.title, detail: c.description === c.title || c.description === c.category ? undefined : c.description, meta: category(c.category, t), mark: _jsx(Mark, { name: c.title, size: 40, logo: c.avatar }), action: signed ? _jsx(Add, { name: c.title, use: true, added: false, busy: using !== null, onPress: () => void take(c) }) : undefined }, `${c.category}/${c.name}/${c.title}`))) })) : figures.loading || offered.loading ? null : (_jsx(Soft, { children: t('No agent matches “{q}”.', { q: q.trim() }) })), shown.length > more ? (_jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => setMore(more + PAGE), children: t('More') }) })) : null, failed ? _jsx(Failed, { error: failed, onRetry: figures.error ? figures.reload : offered.reload }) : figures.loading || offered.loading ? _jsx(Soft, { children: t('Loading…') }) : null, editor, making ? _jsx(Person, { self: making === 'self', onClose: () => setMaking(null), onMade: opened }) : null] }));
86
119
  }
87
120
  if (!signed) {
88
- return _jsx(Visitor, { children: "Sign in to see your agents." });
121
+ return _jsx(Visitor, { children: t('Sign in to see your agents.') });
89
122
  }
90
123
  const shown = mine.value.filter((a) => matches(q, a.name, a.description, a.model));
91
- return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), mine.error && !mine.value.length ? (_jsx(Failed, { error: mine.error, onRetry: mine.reload })) : mine.loading && !mine.value.length ? (_jsx(Soft, { children: "Loading your agents\u2026" })) : !mine.value.length ? (_jsxs(Empty, { title: "No agents yet", detail: "", children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => onView('discover'), children: "Browse agents" }), _jsx(Button, { size: "sm", onPress: () => onAdding(true), children: "New agent" })] })) : !shown.length ? (_jsx(Soft, { children: `No agent of yours matches “${q.trim()}”.` })) : (_jsx(Grid, { label: "Your agents", children: shown.map((a) => (_jsx(Tile, { title: a.name, detail: a.description || 'No description.', meta: [a.model, `${a.runs} ${a.runs === 1 ? 'run' : 'runs'}`, reach(a), a.cap ? `${money(a.spent)} of ${money(a.cap)} this ${a.period || 'period'}` : ''].filter(Boolean).join(' · '), mark: _jsx(Face, { agent: a, cast: host.cast ?? [] }), onOpen: () => opened(a) }, a.id || a.name))) })), editor] }));
124
+ return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), mine.error && !mine.value.length ? (_jsx(Failed, { error: mine.error, onRetry: mine.reload })) : mine.loading && !mine.value.length ? (_jsx(Soft, { children: t('Loading your agents…') })) : !mine.value.length ? (_jsxs(Empty, { title: t('No agents yet'), detail: "", children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => onView('discover'), children: t('Browse agents') }), _jsx(Button, { size: "sm", onPress: () => onAdding(true), children: t('New agent') })] })) : !shown.length ? (_jsx(Soft, { children: t('No agent of yours matches “{q}”.', { q: q.trim() }) })) : (_jsx(Grid, { label: t('Your agents'), children: shown.map((a) => (_jsx(Tile, { title: a.name, detail: a.description || t('No description.'), meta: [
125
+ a.model,
126
+ a.runs === 1 ? t('1 run') : t('{n} runs', { n: a.runs }),
127
+ reach(a, t),
128
+ a.cap ? t(SPENT[a.period], { spent: money(a.spent), cap: money(a.cap) }) : '',
129
+ ]
130
+ .filter(Boolean)
131
+ .join(' · '), mark: _jsx(Face, { agent: a, cast: host.cast ?? [] }), onOpen: () => opened(a) }, a.id || a.name))) })), editor] }));
92
132
  }
93
133
  /** Clone yourself, or anyone named: a name, who they are, and writing of theirs, pasted or uploaded. */
94
134
  function Person({ self, onClose, onMade }) {
135
+ const { t } = useI18n();
95
136
  const host = useHost();
96
- const t = useTarget();
137
+ const target = useTarget();
97
138
  const me = self ? host.person : null;
98
139
  const [name, setName] = useState(me?.name ?? '');
99
140
  const [about, setAbout] = useState('');
@@ -109,22 +150,22 @@ function Person({ self, onClose, onMade }) {
109
150
  setWorking(true);
110
151
  setNote('');
111
152
  try {
112
- onMade(await use(t, person(name, about, writing, me?.avatar), window.location.href));
153
+ onMade(await use(target, person(name, about, writing, me?.avatar), window.location.href));
113
154
  }
114
155
  catch (e) {
115
- setNote(say(e));
156
+ setNote(say(e, t));
116
157
  setWorking(false);
117
158
  }
118
159
  };
119
- return (_jsxs(Sheet, { title: self ? 'Clone yourself' : 'Create from a person', open: true, onOpenChange: (o) => !o && onClose(), children: [_jsx(Field, { label: "Name", children: _jsx(Input, { value: name, onChangeText: setName, "aria-label": "Name" }) }), _jsx(Field, { label: "Description", children: _jsx(Textarea, { value: about, onChangeText: setAbout, "aria-label": "Description", rows: 3 }) }), _jsx(Field, { label: "Writing", children: _jsx(Textarea, { value: writing, onChangeText: setWriting, "aria-label": "Writing", rows: 6 }) }), _jsxs(XStack, { children: [_jsxs(Button, { size: "sm", variant: "ghost", onPress: () => file.current?.click(), children: [_jsx(Upload, { size: 14 }), " Upload"] }), _jsx("input", { ref: file, type: "file", multiple: true, accept: ".txt,.md,.markdown,.csv,.json,.html,text/*", hidden: true, "aria-label": "Upload writing", onChange: (e) => {
160
+ return (_jsxs(Sheet, { title: self ? t('Clone yourself') : t('Create from a person'), open: true, onOpenChange: (o) => !o && onClose(), children: [_jsx(Field, { label: t('Name'), children: _jsx(Input, { value: name, onChangeText: setName, "aria-label": t('Name') }) }), _jsx(Field, { label: t('Description'), children: _jsx(Textarea, { value: about, onChangeText: setAbout, "aria-label": t('Description'), rows: 3 }) }), _jsx(Field, { label: t('Writing'), children: _jsx(Textarea, { value: writing, onChangeText: setWriting, "aria-label": t('Writing'), rows: 6 }) }), _jsxs(XStack, { children: [_jsxs(Button, { size: "sm", variant: "ghost", onPress: () => file.current?.click(), children: [_jsx(Upload, { size: 14 }), " ", t('Upload')] }), _jsx("input", { ref: file, type: "file", multiple: true, accept: ".txt,.md,.markdown,.csv,.json,.html,text/*", hidden: true, "aria-label": t('Upload writing'), onChange: (e) => {
120
161
  void read(e.currentTarget.files);
121
162
  e.currentTarget.value = '';
122
- } })] }), _jsx(Line, { children: note }), _jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(XStack, { flex: 1 }), _jsx(Button, { size: "sm", variant: "ghost", onPress: onClose, children: "Cancel" }), _jsx(Button, { size: "sm", disabled: working || !name.trim(), onPress: () => void save(), children: "Create" })] })] }));
163
+ } })] }), _jsx(Line, { children: note }), _jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(XStack, { flex: 1 }), _jsx(Button, { size: "sm", variant: "ghost", onPress: onClose, children: t('Cancel') }), _jsx(Button, { size: "sm", disabled: working || !name.trim(), onPress: () => void save(), children: t('Create') })] })] }));
123
164
  }
124
- function reach(a) {
165
+ function reach(a, t) {
125
166
  if (a.tools.includes(ALL))
126
- return 'every tool';
127
- return a.tools.length === 1 ? '1 tool' : `${a.tools.length} tools`;
167
+ return t('every tool');
168
+ return a.tools.length === 1 ? t('1 tool') : t('{n} tools', { n: a.tools.length });
128
169
  }
129
170
  /** An agent's face: its picture, the default agent's of its name, its emoji, or its initial. */
130
171
  function Face({ agent: a, cast, size = 32 }) {
@@ -133,23 +174,25 @@ function Face({ agent: a, cast, size = 32 }) {
133
174
  }
134
175
  /** One agent's page: who it is, what it runs on and reads, what it may call and spend, and what it has done. */
135
176
  export function Profile({ name }) {
177
+ const { t } = useI18n();
136
178
  const host = useHost();
137
- const t = useTarget();
138
- const got = useLoad(host.person ? () => profile(t, name) : null, null, [t, name]);
179
+ const target = useTarget();
180
+ const got = useLoad(host.person ? () => profile(target, name) : null, null, [target, name]);
139
181
  const [editing, setEditing] = useState(false);
140
182
  const back = () => host.go(path({ kind: 'customize', tab: 'agents' }));
141
183
  const a = got.value?.agent;
142
- return (_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", "$max-md": { px: '$4', py: '$4' }, children: [_jsxs(YStack, { width: "100%", maxW: 880, mx: "auto", gap: "$5", children: [_jsxs(XStack, { render: "button", onPress: back, items: "center", gap: "$1.5", self: "flex-start", cursor: "pointer", "aria-label": "Back to agents", children: [_jsx(ArrowLeft, { size: 14, color: "$soft" }), _jsx(SizableText, { size: "$2", color: "$soft", children: "Agents" })] }), !host.person ? (_jsx(Visitor, { children: "Sign in to see this agent." })) : got.error && !a ? (_jsx(Failed, { error: got.error, onRetry: got.reload })) : !a ? (_jsx(Soft, { children: "Loading the agent\u2026" })) : (_jsxs(_Fragment, { children: [_jsxs(XStack, { items: "center", gap: "$4", children: [_jsx(Face, { agent: a, cast: host.cast ?? [], size: 64 }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$1", children: [_jsx(SizableText, { render: "h1", size: "$7", color: "$ink", numberOfLines: 1, children: a.name }), _jsx(SizableText, { size: "$3", color: "$soft", children: a.description || 'No role written yet.' })] }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => setEditing(true), children: "Edit" })] }), _jsx(Part, { title: "Model", detail: a.model || 'The deployment’s default' }), _jsx(Part, { title: "Instructions", detail: a.instructions ? '' : 'None written: it runs on its model alone.', children: a.instructions ? (_jsx(YStack, { p: "$3", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", maxH: 320, overflow: "scroll", children: _jsx(SizableText, { size: "$2", color: "$ink", style: { whiteSpace: 'pre-wrap' }, children: a.instructions }) })) : null }), _jsx(Part, { title: "Tools", detail: a.tools.includes(ALL) ? 'Every tool the fleet’s MCP server serves.' : a.tools.length ? a.tools.join(', ') : 'None: it calls no tool.' }), _jsx(Part, { title: "Budget", detail: a.cap ? `${money(a.spent)} of ${money(a.cap)} spent this ${a.period || 'period'}; at most ${money(a.task)} a run.` : 'No budget set.' }), _jsx(Part, { title: "Runs", detail: a.runs ? `${a.runs} in all; the latest ${Math.min(a.runs, got.value.runs.length)} below.` : 'It has not run yet.', children: got.value.runs.map((r) => (_jsxs(XStack, { gap: "$3", py: "$2", borderTopWidth: 1, borderColor: "$borderColor", items: "flex-start", children: [_jsx(SizableText, { size: "$1", color: r.status === 'ok' ? '$soft' : '$bad', width: 44, shrink: 0, children: r.status === 'ok' ? 'ok' : 'failed' }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: r.input || '(no input)' }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 2, children: r.status === 'ok' ? r.output : r.error })] }), _jsx(SizableText, { size: "$1", color: "$soft", shrink: 0, children: [r.model, r.at ? new Date(r.at).toLocaleString() : ''].filter(Boolean).join(' · ') })] }, r.id))) })] }))] }), editing && a ? (_jsx(Editor, { agent: a, onClose: () => setEditing(false), onSaved: () => {
184
+ return (_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", "$max-md": { px: '$4', py: '$4' }, children: [_jsxs(YStack, { width: "100%", maxW: 880, mx: "auto", gap: "$5", children: [_jsxs(XStack, { render: "button", onPress: back, items: "center", gap: "$1.5", self: "flex-start", cursor: "pointer", "aria-label": t('Back to agents'), children: [_jsx(ArrowLeft, { size: 14, color: "$soft", 'data-i18n-mirror': '' }), _jsx(SizableText, { size: "$2", color: "$soft", children: t('Agents') })] }), !host.person ? (_jsx(Visitor, { children: t('Sign in to see this agent.') })) : got.error && !a ? (_jsx(Failed, { error: got.error, onRetry: got.reload })) : !a ? (_jsx(Soft, { children: t('Loading the agent…') })) : (_jsxs(_Fragment, { children: [_jsxs(XStack, { items: "center", gap: "$4", children: [_jsx(Face, { agent: a, cast: host.cast ?? [], size: 64 }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$1", children: [_jsx(SizableText, { render: "h1", size: "$7", color: "$ink", numberOfLines: 1, children: a.name }), _jsx(SizableText, { size: "$3", color: "$soft", children: a.description || t('No role written yet.') })] }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => setEditing(true), children: t('Edit') })] }), _jsx(Part, { title: t('Model'), detail: a.model || t('The deployment’s default') }), _jsx(Part, { title: t('Instructions'), detail: a.instructions ? '' : t('None written: it runs on its model alone.'), children: a.instructions ? (_jsx(YStack, { p: "$3", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", maxH: 320, overflow: "scroll", children: _jsx(SizableText, { size: "$2", color: "$ink", style: { whiteSpace: 'pre-wrap' }, children: a.instructions }) })) : null }), _jsx(Part, { title: t('Tools'), detail: a.tools.includes(ALL) ? t('Every tool the fleet’s MCP server serves.') : a.tools.length ? a.tools.join(', ') : t('None: it calls no tool.') }), _jsx(Part, { title: t('Budget'), detail: a.cap ? t(BUDGET[a.period], { spent: money(a.spent), cap: money(a.cap), task: money(a.task) }) : t('No budget set.') }), _jsx(Part, { title: t('Runs'), detail: a.runs ? t('{runs} in all; the latest {n} below.', { runs: a.runs, n: Math.min(a.runs, got.value.runs.length) }) : t('It has not run yet.'), children: got.value.runs.map((r) => (_jsxs(XStack, { gap: "$3", py: "$2", borderTopWidth: 1, borderColor: "$borderColor", items: "flex-start", children: [_jsx(SizableText, { size: "$1", color: r.status === 'ok' ? '$soft' : '$bad', width: 44, shrink: 0, children: r.status === 'ok' ? t('ok') : t('failed') }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: r.input || t('(no input)') }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 2, children: r.status === 'ok' ? r.output : r.error })] }), _jsx(SizableText, { size: "$1", color: "$soft", shrink: 0, children: [r.model, r.at ? new Date(r.at).toLocaleString() : ''].filter(Boolean).join(' · ') })] }, r.id))) })] }))] }), editing && a ? (_jsx(Editor, { agent: a, onClose: () => setEditing(false), onSaved: () => {
143
185
  setEditing(false);
144
186
  got.reload();
145
187
  }, onDeleted: back })) : null] }));
146
188
  }
147
189
  /** Write a new agent, or change one; an existing agent is read whole first, for its instructions. */
148
190
  function Editor({ agent, onClose, onSaved, onDeleted, }) {
149
- const t = useTarget();
150
- const full = useLoad(agent ? () => one(t, agent.id || agent.name) : null, null, [t, agent?.id]);
151
- const catalog = useLoad(() => models(t), [], [t]);
152
- const on = useLoad(() => tools(t, { activated: true }), [], [t]);
191
+ const { t } = useI18n();
192
+ const target = useTarget();
193
+ const full = useLoad(agent ? () => one(target, agent.id || agent.name) : null, null, [target, agent?.id]);
194
+ const catalog = useLoad(() => models(target), [], [target]);
195
+ const on = useLoad(() => tools(target, { activated: true }), [], [target]);
153
196
  const [d, setD] = useState(EMPTY);
154
197
  const [ready, setReady] = useState(!agent);
155
198
  const [working, setWorking] = useState(false);
@@ -168,28 +211,32 @@ function Editor({ agent, onClose, onSaved, onDeleted, }) {
168
211
  const choices = useMemo(() => {
169
212
  const listed = on.value.map((x) => ({ name: x.name, source: x.source, description: x.description }));
170
213
  const seen = new Set(listed.map((x) => x.name));
171
- const kept = d.tools.filter((n) => n !== ALL && !seen.has(n)).map((name) => ({ name, source: 'not on', description: '' }));
214
+ const kept = d.tools.filter((n) => n !== ALL && !seen.has(n)).map((name) => ({ name, source: t('not on'), description: '' }));
172
215
  return [...kept, ...listed];
173
- }, [on.value, d.tools]);
216
+ }, [on.value, d.tools, t]);
174
217
  const shownTools = choices.filter((x) => matches(find, x.name, x.source, x.description));
175
218
  const save = async () => {
176
219
  setWorking(true);
177
220
  setNote('');
178
221
  try {
179
222
  const was = full.value;
180
- const saved = agent && was ? await update(t, was, d) : await create(t, d);
223
+ const saved = agent && was ? await update(target, was, d) : await create(target, d);
181
224
  onSaved(saved);
182
225
  }
183
226
  catch (e) {
184
- setNote(say(e));
227
+ setNote(say(e, t));
185
228
  }
186
229
  finally {
187
230
  setWorking(false);
188
231
  }
189
232
  };
190
233
  const pick = (name, yes) => set({ tools: yes ? [...d.tools, name] : d.tools.filter((n) => n !== name) });
191
- return (_jsx(Sheet, { title: agent ? agent.name : 'New agent', open: true, onOpenChange: (o) => !o && onClose(), width: 680, children: agent && full.error && !full.value ? (_jsx(Failed, { error: full.error, onRetry: full.reload })) : !ready ? (_jsx(Soft, { children: "Loading the agent\u2026" })) : (_jsxs(_Fragment, { children: [_jsx(Field, { label: "Name", hint: agent ? 'An agent keeps its name.' : 'Letters, digits, . _ or -. Other agents call it as agent_<name>.', children: _jsx(Input, { value: d.name, onChangeText: (v) => set({ name: v }), "aria-label": "Name", disabled: Boolean(agent), autoCapitalize: "none" }) }), _jsx(Field, { label: "Description", hint: "The line another agent reads to decide whether to call this one.", children: _jsx(Input, { value: d.description, onChangeText: (v) => set({ description: v }), placeholder: "Answers questions about our codebase", "aria-label": "Description" }) }), _jsx(Field, { label: "Model", children: _jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsx(ModelPicker, { size: "sm", name: "Model", placeholder: "The deployment\u2019s default", models: catalog.value, scope: "chat", value: d.model || undefined, onChange: (id) => set({ model: id }), loading: catalog.loading, error: catalog.error ? say(catalog.error) : null }), d.model && !agent ? (_jsx(Button, { size: "sm", variant: "ghost", onPress: () => set({ model: '' }), children: "Use the default" })) : null] }) }), _jsx(Field, { label: "Instructions", hint: "The system prompt: what the model reads before every run.", children: _jsx(Textarea, { value: d.instructions, onChangeText: (v) => set({ instructions: v }), placeholder: "Be terse, and cite the file you read.", "aria-label": "Instructions", rows: 8 }) }), _jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { items: "center", gap: "$3", children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", children: "Tools" }), _jsx(SizableText, { size: "$1", color: "$soft", children: every ? 'Every tool the fleet’s MCP server serves when it runs.' : d.tools.length ? `${d.tools.length} chosen. It can call only these.` : 'None chosen: it can call no tool.' })] }), _jsx(SizableText, { size: "$1", color: "$soft", children: "Every tool" }), _jsx(Switch, { checked: every, onCheckedChange: (v) => set({ tools: v ? [ALL] : [] }), "aria-label": "Every tool" })] }), every ? null : (_jsxs(_Fragment, { children: [_jsx(Input, { value: find, onChangeText: setFind, placeholder: "Find a tool\u2026", "aria-label": "Find a tool" }), _jsx(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", maxH: 240, overflow: "scroll", children: on.loading && !on.value.length ? (_jsx(Soft, { children: "Loading the tools that are on\u2026" })) : on.error && !on.value.length ? (_jsx(Failed, { error: on.error, onRetry: on.reload })) : !shownTools.length ? (_jsx(Soft, { children: choices.length ? 'No tool matches.' : 'No tool is on for your organization yet. Add skills or connectors first.' })) : (shownTools.map((x, i) => (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", style: mono, numberOfLines: 1, children: x.name }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: [x.source, x.description].filter(Boolean).join(' · ') })] }), _jsx(Switch, { checked: d.tools.includes(x.name), onCheckedChange: (v) => pick(x.name, v), "aria-label": `Use ${x.name}` })] }, x.name)))) })] }))] }), _jsxs(YStack, { gap: "$2", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: "Budget" }), _jsxs(XStack, { gap: "$3", flexWrap: "wrap", rowGap: "$2", items: "flex-end", children: [_jsxs(YStack, { gap: "$1", width: 140, children: [_jsxs(SizableText, { size: "$1", color: "$soft", children: ["Each ", d.period, ", USD"] }), _jsx(Input, { value: d.cap, onChangeText: (v) => set({ cap: v }), placeholder: "10", "aria-label": "Budget each period", inputMode: "decimal" })] }), _jsxs(YStack, { gap: "$1", width: 140, children: [_jsx(SizableText, { size: "$1", color: "$soft", children: "One run, USD" }), _jsx(Input, { value: d.task, onChangeText: (v) => set({ task: v }), placeholder: "1", "aria-label": "Budget for one run", inputMode: "decimal" })] }), _jsx(Choice, { label: "Period", value: d.period, options: PERIODS.map((p) => ({ id: p, label: p[0].toUpperCase() + p.slice(1) })), onChange: (p) => set({ period: p }) })] }), _jsxs(SizableText, { size: "$1", color: "$soft", children: [full.value && full.value.cap ? `Spent ${money(full.value.spent)} this ${full.value.period}. ` : '', "A run stops at either limit."] })] }), _jsx(Line, { children: note }), _jsxs(XStack, { gap: "$2", items: "center", children: [agent && onDeleted ? (_jsx(Button, { size: "sm", variant: "ghost", onPress: () => setAsking(true), children: "Delete" })) : null, _jsx(XStack, { flex: 1 }), _jsx(Button, { size: "sm", variant: "ghost", onPress: onClose, children: "Cancel" }), _jsx(Button, { size: "sm", disabled: working, onPress: () => void save(), children: agent ? 'Save' : 'Create' })] }), agent && onDeleted ? (_jsx(Confirm, { what: agent.name, says: "The agent and every run recorded against it are removed.", open: asking, onOpenChange: setAsking, onYes: async () => {
192
- await remove(t, agent.id || agent.name);
234
+ return (_jsx(Sheet, { title: agent ? agent.name : t('New agent'), open: true, onOpenChange: (o) => !o && onClose(), width: 680, children: agent && full.error && !full.value ? (_jsx(Failed, { error: full.error, onRetry: full.reload })) : !ready ? (_jsx(Soft, { children: t('Loading the agent…') })) : (_jsxs(_Fragment, { children: [_jsx(Field, { label: t('Name'), hint: agent ? t('An agent keeps its name.') : t('Letters, digits, . _ or -. Other agents call it as agent_<name>.'), children: _jsx(Input, { value: d.name, onChangeText: (v) => set({ name: v }), "aria-label": t('Name'), disabled: Boolean(agent), autoCapitalize: "none" }) }), _jsx(Field, { label: t('Description'), hint: t('The line another agent reads to decide whether to call this one.'), children: _jsx(Input, { value: d.description, onChangeText: (v) => set({ description: v }), placeholder: t('Answers questions about our codebase'), "aria-label": t('Description') }) }), _jsx(Field, { label: t('Model'), children: _jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsx(ModelPicker, { size: "sm", name: t('Model'), placeholder: t('The deployment’s default'), models: catalog.value, scope: "chat", value: d.model || undefined, onChange: (id) => set({ model: id }), loading: catalog.loading, error: catalog.error ? say(catalog.error, t) : null }), d.model && !agent ? (_jsx(Button, { size: "sm", variant: "ghost", onPress: () => set({ model: '' }), children: t('Use the default') })) : null] }) }), _jsx(Field, { label: t('Instructions'), hint: t('The system prompt: what the model reads before every run.'), children: _jsx(Textarea, { value: d.instructions, onChangeText: (v) => set({ instructions: v }), placeholder: t('Be terse, and cite the file you read.'), "aria-label": t('Instructions'), rows: 8 }) }), _jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { items: "center", gap: "$3", children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", children: t('Tools') }), _jsx(SizableText, { size: "$1", color: "$soft", children: every
235
+ ? t('Every tool the fleet’s MCP server serves when it runs.')
236
+ : d.tools.length
237
+ ? t('{n} chosen. It can call only these.', { n: d.tools.length })
238
+ : t('None chosen: it can call no tool.') })] }), _jsx(SizableText, { size: "$1", color: "$soft", children: t('Every tool') }), _jsx(Switch, { checked: every, onCheckedChange: (v) => set({ tools: v ? [ALL] : [] }), "aria-label": t('Every tool') })] }), every ? null : (_jsxs(_Fragment, { children: [_jsx(Input, { value: find, onChangeText: setFind, placeholder: t('Find a tool…'), "aria-label": t('Find a tool') }), _jsx(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", maxH: 240, overflow: "scroll", children: on.loading && !on.value.length ? (_jsx(Soft, { children: t('Loading the tools that are on…') })) : on.error && !on.value.length ? (_jsx(Failed, { error: on.error, onRetry: on.reload })) : !shownTools.length ? (_jsx(Soft, { children: choices.length ? t('No tool matches.') : t('No tool is on for your organization yet. Add skills or connectors first.') })) : (shownTools.map((x, i) => (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", style: mono, numberOfLines: 1, children: x.name }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: [x.source, x.description].filter(Boolean).join(' · ') })] }), _jsx(Switch, { checked: d.tools.includes(x.name), onCheckedChange: (v) => pick(x.name, v), "aria-label": t('Use {name}', { name: x.name }) })] }, x.name)))) })] }))] }), _jsxs(YStack, { gap: "$2", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: t('Budget') }), _jsxs(XStack, { gap: "$3", flexWrap: "wrap", rowGap: "$2", items: "flex-end", children: [_jsxs(YStack, { gap: "$1", width: 140, children: [_jsx(SizableText, { size: "$1", color: "$soft", children: t(EACH[d.period]) }), _jsx(Input, { value: d.cap, onChangeText: (v) => set({ cap: v }), placeholder: "10", "aria-label": t('Budget each period'), inputMode: "decimal" })] }), _jsxs(YStack, { gap: "$1", width: 140, children: [_jsx(SizableText, { size: "$1", color: "$soft", children: t('One run, USD') }), _jsx(Input, { value: d.task, onChangeText: (v) => set({ task: v }), placeholder: "1", "aria-label": t('Budget for one run'), inputMode: "decimal" })] }), _jsx(Choice, { label: t('Period'), value: d.period, options: PERIODS.map((p) => ({ id: p, label: t(PERIOD[p]) })), onChange: (p) => set({ period: p }) })] }), _jsx(SizableText, { size: "$1", color: "$soft", children: [full.value && full.value.cap ? t(SO_FAR[full.value.period], { spent: money(full.value.spent) }) : '', t('A run stops at either limit.')].filter(Boolean).join(' ') })] }), _jsx(Line, { children: note }), _jsxs(XStack, { gap: "$2", items: "center", children: [agent && onDeleted ? (_jsx(Button, { size: "sm", variant: "ghost", onPress: () => setAsking(true), children: t('Delete') })) : null, _jsx(XStack, { flex: 1 }), _jsx(Button, { size: "sm", variant: "ghost", onPress: onClose, children: t('Cancel') }), _jsx(Button, { size: "sm", disabled: working, onPress: () => void save(), children: agent ? t('Save') : t('Create') })] }), agent && onDeleted ? (_jsx(Confirm, { what: agent.name, says: t('The agent and every run recorded against it are removed.'), open: asking, onOpenChange: setAsking, onYes: async () => {
239
+ await remove(target, agent.id || agent.name);
193
240
  onDeleted();
194
241
  } })) : null] })) }));
195
242
  }
@@ -11,8 +11,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  * all its tools on its card and one per tool inside; a tool is called only
12
12
  * while it is on. Adding a server switches its tools on. A server's secret is
13
13
  * sealed in KMS and never shown again.
14
+ *
15
+ * Its words are the reader's language (@hanzo/i18n `t`); a server's name,
16
+ * address, description and tools are the registry's or the org's, drawn as given.
14
17
  */
15
18
  import { SizableText, XStack, YStack } from '@hanzo/gui';
19
+ import { useI18n } from '@hanzo/i18n/react';
16
20
  import { ChevronDown, ExternalLink, KeyRound, Server as ServerIcon } from '@hanzogui/lucide-icons-2';
17
21
  import { Button, Input, Switch } from '@hanzo/ui';
18
22
  import { useEffect, useMemo, useState } from 'react';
@@ -24,19 +28,20 @@ import { Out } from '../out.js';
24
28
  import { route } from '../route.js';
25
29
  import { useLoad } from './load.js';
26
30
  import { say } from './say.js';
27
- import { Add, Confirm, Empty, Failed, Featured, Field, Grid, left, Line, Mark, matches, mono, Part, Sheet, Soft, Tile, useCount, Visitor } from './ui.js';
31
+ import { Add, Confirm, Empty, Failed, Featured, Field, Grid, Line, Mark, matches, mono, Part, Sheet, Soft, Tile, useCount, Visitor } from './ui.js';
28
32
  const PAGE = 48;
29
33
  /** The part of a URL a person reads: its host and path. */
30
34
  const where = (url) => url.replace(/^https?:\/\//, '').replace(/\/$/, '');
31
35
  export function Connectors({ view, q, adding, onAdding, onView, onCount }) {
36
+ const { t } = useI18n();
32
37
  const host = useHost();
33
- const t = useTarget();
38
+ const target = useTarget();
34
39
  const signed = Boolean(host.person);
35
40
  // What every run in the org connects to is an org admin's to change; a member reads it.
36
41
  const may = signed && host.admin;
37
- const mine = useLoad(signed ? () => servers(t) : null, [], [t, signed]);
42
+ const mine = useLoad(signed ? () => servers(target) : null, [], [target, signed]);
38
43
  // Every server's tools, for the switch on each card; read once for all of them.
39
- const plane = useLoad(signed && view === 'yours' && mine.value.length ? () => tools(t, { source: 'mcp' }) : null, [], [t, signed, view, mine.value]);
44
+ const plane = useLoad(signed && view === 'yours' && mine.value.length ? () => tools(target, { source: 'mcp' }) : null, [], [target, signed, view, mine.value]);
40
45
  const [opened, setOpened] = useState(null);
41
46
  const [listing, setListing] = useState(null);
42
47
  const [connecting, setConnecting] = useState(null);
@@ -51,12 +56,12 @@ export function Connectors({ view, q, adding, onAdding, onView, onCount }) {
51
56
  setBusy(s.id);
52
57
  setNote('');
53
58
  try {
54
- await toggle(t, next ? its : [], next ? [] : its);
59
+ await toggle(target, next ? its : [], next ? [] : its);
55
60
  plane.reload();
56
- setNote(next ? `${s.name} is on` : `${s.name} is off`);
61
+ setNote(next ? t('{name} is on', { name: s.name }) : t('{name} is off', { name: s.name }));
57
62
  }
58
63
  catch (e) {
59
- setNote(say(e));
64
+ setNote(say(e, t));
60
65
  }
61
66
  finally {
62
67
  setBusy('');
@@ -80,40 +85,41 @@ export function Connectors({ view, q, adding, onAdding, onView, onCount }) {
80
85
  return (_jsxs(YStack, { gap: "$6", children: [_jsx(Line, { children: note }), _jsx(Shelf, { q: q, signed: signed, may: may, added: new Set(mine.value.map((s) => s.listing).filter(Boolean)), onOpen: setListing, onAdd: setConnecting }), _jsx(Natives, { q: q }), detail, connect] }));
81
86
  }
82
87
  if (!signed) {
83
- return _jsx(Visitor, { children: "Sign in to see your connectors." });
88
+ return _jsx(Visitor, { children: t('Sign in to see your connectors.') });
84
89
  }
85
90
  const shown = mine.value.filter((s) => matches(q, s.name, s.url, s.listing));
86
- return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), mine.error && !mine.value.length ? (_jsx(Failed, { error: mine.error, onRetry: mine.reload })) : mine.loading && !mine.value.length ? (_jsx(Soft, { children: "Loading your connectors\u2026" })) : !mine.value.length ? (_jsxs(Empty, { title: "No connectors yet", detail: may ? 'Connect an app from Browse, or add any MCP server by its URL.' : 'An org admin adds connectors. Browse what there is.', children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => onView('discover'), children: "Browse connectors" }), may ? (_jsx(Button, { size: "sm", onPress: () => onAdding(true), children: "Add by URL" })) : null] })) : !shown.length ? (_jsx(Soft, { children: `No connector of yours matches “${q.trim()}”.` })) : (_jsx(Grid, { label: "Your connectors", children: shown.map((s) => {
91
+ return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), mine.error && !mine.value.length ? (_jsx(Failed, { error: mine.error, onRetry: mine.reload })) : mine.loading && !mine.value.length ? (_jsx(Soft, { children: t('Loading your connectors…') })) : !mine.value.length ? (_jsxs(Empty, { title: t('No connectors yet'), detail: may ? t('Connect an app from Browse, or add any MCP server by its URL.') : t('An org admin adds connectors. Browse what there is.'), children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => onView('discover'), children: t('Browse connectors') }), may ? (_jsx(Button, { size: "sm", onPress: () => onAdding(true), children: t('Add by URL') })) : null] })) : !shown.length ? (_jsx(Soft, { children: t('No connector of yours matches “{q}”.', { q: q.trim() }) })) : (_jsx(Grid, { label: t('Your connectors'), children: shown.map((s) => {
87
92
  const its = plane.value.filter((x) => owns(s, x.name));
88
93
  const lit = its.filter((x) => x.activated).length;
89
94
  return (_jsx(Tile, { title: s.name, detail: where(s.url), meta: [
90
- its.length ? `${lit} of ${its.length} tools on` : plane.loading ? '' : 'No tools listed yet',
91
- s.listing ? 'From Browse' : 'Added by URL',
92
- s.secret ? 'Secret in KMS' : '',
93
- s.admitted ? '' : 'Not in runs until an admin adds it again',
95
+ its.length ? t('{on} of {n} tools on', { on: lit, n: its.length }) : plane.loading ? '' : t('No tools listed yet'),
96
+ s.listing ? t('From Browse') : t('Added by URL'),
97
+ s.secret ? t('Secret in KMS') : '',
98
+ s.admitted ? '' : t('Not in runs until an admin adds it again'),
94
99
  ]
95
100
  .filter(Boolean)
96
- .join(' · '), mark: _jsx(Mark, { name: s.name }), onOpen: () => setOpened(s), action: may && its.length ? (_jsx(Switch, { checked: lit > 0, disabled: busy === s.id, onCheckedChange: (v) => void flip(s, v), "aria-label": `${s.name} on` })) : its.length ? (_jsx(SizableText, { size: "$1", color: "$soft", children: lit ? 'On' : 'Off' })) : undefined }, s.id));
97
- }) })), plane.error && mine.value.length ? _jsx(Line, { children: `Tools: ${say(plane.error)}` }) : null, opened ? (_jsx(Detail, { server: opened, may: may, onClose: () => {
101
+ .join(' · '), mark: _jsx(Mark, { name: s.name }), onOpen: () => setOpened(s), action: may && its.length ? (_jsx(Switch, { checked: lit > 0, disabled: busy === s.id, onCheckedChange: (v) => void flip(s, v), "aria-label": t('{name} on', { name: s.name }) })) : its.length ? (_jsx(SizableText, { size: "$1", color: "$soft", children: lit ? t('On') : t('Off') })) : undefined }, s.id));
102
+ }) })), plane.error && mine.value.length ? _jsx(Line, { children: t('Tools: {reason}', { reason: say(plane.error, t) }) }) : null, opened ? (_jsx(Detail, { server: opened, may: may, onClose: () => {
98
103
  setOpened(null);
99
104
  plane.reload();
100
105
  }, onDeleted: () => {
101
- setNote(`${opened.name} is removed`);
106
+ setNote(t('{name} is removed', { name: opened.name }));
102
107
  setOpened(null);
103
108
  mine.reload();
104
109
  } })) : null, connect] }));
105
110
  }
106
111
  /** The shelf: featured first, then by name, a page at a time. The platform searches it. */
107
112
  function Shelf({ q, signed, may, added, onOpen, onAdd }) {
108
- const t = useTarget();
113
+ const { t } = useI18n();
114
+ const target = useTarget();
109
115
  const text = useSettled(q.trim(), 300);
110
- const first = useLoad(signed ? () => shelf(t, { text, limit: PAGE }) : null, null, [t, signed, text]);
116
+ const first = useLoad(signed ? () => shelf(target, { text, limit: PAGE }) : null, null, [target, signed, text]);
111
117
  const [more, setMore] = useState([]);
112
118
  const [paging, setPaging] = useState(false);
113
119
  const [note, setNote] = useState('');
114
120
  useEffect(() => setMore([]), [first.value]);
115
121
  if (!signed) {
116
- return (_jsx(Part, { title: "Connect an app", detail: "Give runs the tools you already use.", children: _jsx(Visitor, { children: "Sign in to browse connectors." }) }));
122
+ return (_jsx(Part, { title: t('Connect an app'), detail: t('Give runs the tools you already use.'), children: _jsx(Visitor, { children: t('Sign in to browse connectors.') }) }));
117
123
  }
118
124
  const page = first.value;
119
125
  const list = [...(page?.listings ?? []), ...more];
@@ -125,20 +131,20 @@ function Shelf({ q, signed, may, added, onOpen, onAdd }) {
125
131
  setPaging(true);
126
132
  setNote('');
127
133
  try {
128
- const got = await shelf(t, { text, limit: PAGE, offset: list.length });
134
+ const got = await shelf(target, { text, limit: PAGE, offset: list.length });
129
135
  setMore((m) => [...m, ...got.listings]);
130
136
  }
131
137
  catch (e) {
132
- setNote(say(e));
138
+ setNote(say(e, t));
133
139
  }
134
140
  finally {
135
141
  setPaging(false);
136
142
  }
137
143
  };
138
- return (_jsx(Part, { title: "Connect an app", detail: "Give runs the tools you already use. Adding one turns its tools on for your organization.", children: first.error && !page ? (_jsx(Failed, { error: first.error, onRetry: first.reload })) : !page ? (_jsx(Soft, { children: "Loading connectors\u2026" })) : !list.length ? (_jsx(Soft, { children: text ? `No connector matches “${text}”.` : 'There are no connectors to add yet.' })) : (_jsxs(YStack, { gap: "$3", children: [hero ? (_jsx(Featured, { title: titleOf(hero), detail: hero.description, meta: facts(hero), mark: _jsx(Mark, { name: titleOf(hero), logo: hero.logo, size: 48 }), onOpen: () => onOpen(hero), action: action(hero) })) : null, _jsx(SizableText, { size: "$1", color: "$soft", children: list.length < page.total ? `${list.length} of ${page.total} connectors` : `${page.total} connectors` }), _jsx(Grid, { label: "Servers on the shelf", children: rest.map((l) => (_jsx(Tile, { title: titleOf(l), detail: l.description, meta: facts(l), mark: _jsx(Mark, { name: titleOf(l), logo: l.logo }), onOpen: () => onOpen(l), action: action(l) }, l.id))) }), _jsx(Line, { children: note }), list.length < page.total ? (_jsx(XStack, { justify: "center", children: _jsx(Button, { size: "sm", variant: "outline", disabled: paging, onPress: () => void next(), children: "Show more" }) })) : null] })) }));
144
+ return (_jsx(Part, { title: t('Connect an app'), detail: t('Give runs the tools you already use. Adding one turns its tools on for your organization.'), children: first.error && !page ? (_jsx(Failed, { error: first.error, onRetry: first.reload })) : !page ? (_jsx(Soft, { children: t('Loading connectors…') })) : !list.length ? (_jsx(Soft, { children: text ? t('No connector matches “{q}”.', { q: text }) : t('There are no connectors to add yet.') })) : (_jsxs(YStack, { gap: "$3", children: [hero ? (_jsx(Featured, { title: titleOf(hero), detail: hero.description, meta: facts(hero, t), mark: _jsx(Mark, { name: titleOf(hero), logo: hero.logo, size: 48 }), onOpen: () => onOpen(hero), action: action(hero) })) : null, _jsx(SizableText, { size: "$1", color: "$soft", children: list.length < page.total ? t('{shown} of {n} connectors', { shown: list.length, n: page.total }) : t('{n} connectors', { n: page.total }) }), _jsx(Grid, { label: t('Servers on the shelf'), children: rest.map((l) => (_jsx(Tile, { title: titleOf(l), detail: l.description, meta: facts(l, t), mark: _jsx(Mark, { name: titleOf(l), logo: l.logo }), onOpen: () => onOpen(l), action: action(l) }, l.id))) }), _jsx(Line, { children: note }), list.length < page.total ? (_jsx(XStack, { justify: "center", children: _jsx(Button, { size: "sm", variant: "outline", disabled: paging, onPress: () => void next(), children: t('Show more') }) })) : null] })) }));
139
145
  }
140
- /** The facts a card carries under its description. */
141
- const facts = (l) => [l.vendor, l.official ? 'Official' : '', ready(l) ? 'Hosted' : 'Package only: needs a place to run'].filter(Boolean).join(' · ');
146
+ /** The facts a card carries under its description, said in the reader's language. */
147
+ const facts = (l, t) => [l.vendor, l.official ? t('Official') : '', ready(l) ? t('Hosted') : t('Package only: needs a place to run')].filter(Boolean).join(' · ');
142
148
  /** `value` once it has held still for `ms`. */
143
149
  function useSettled(value, ms) {
144
150
  const [settled, setSettled] = useState(value);
@@ -154,33 +160,36 @@ function useSettled(value, ms) {
154
160
  * at once on the old MCP address, which exists to show them.
155
161
  */
156
162
  function Natives({ q }) {
163
+ const { t } = useI18n();
157
164
  const host = useHost();
158
- const t = useTarget();
165
+ const target = useTarget();
159
166
  const r = route(host.path);
160
167
  const [asked, setAsked] = useState(r.kind === 'screen' && r.screen === 'mcp');
161
- const list = useLoad(asked ? () => natives(t) : null, [], [t, asked]);
168
+ const list = useLoad(asked ? () => natives(target) : null, [], [target, asked]);
162
169
  const [open, setOpen] = useState(null);
163
170
  const shown = list.value.filter((s) => matches(q, s.name, s.description, ...s.ops));
164
171
  const ops = shown.reduce((n, s) => n + s.ops.length, 0);
165
- return (_jsx(Part, { title: "Built in", detail: "Hanzo\u2019s own servers. Always on: every run can call them, and nothing needs adding.", action: asked ? undefined : (_jsx(Button, { size: "sm", variant: "outline", onPress: () => setAsked(true), children: "Show built-in servers" })), children: !asked ? null : list.loading && !list.value.length ? (_jsx(Soft, { children: "Loading built-in servers\u2026" })) : list.error && !list.value.length ? (_jsx(Failed, { error: list.error, onRetry: list.reload })) : !shown.length ? (_jsx(Soft, { children: list.value.length ? `No built-in server matches “${q.trim()}”.` : 'No built-in servers are listed.' })) : (_jsxs(YStack, { gap: "$1", children: [_jsxs(SizableText, { size: "$1", color: "$soft", children: [shown.length, " servers \u00B7 ", ops, " operations"] }), shown.map((s) => {
172
+ return (_jsx(Part, { title: t('Built in'), detail: t('Hanzo’s own servers. Always on: every run can call them, and nothing needs adding.'), action: asked ? undefined : (_jsx(Button, { size: "sm", variant: "outline", onPress: () => setAsked(true), children: t('Show built-in servers') })), children: !asked ? null : list.loading && !list.value.length ? (_jsx(Soft, { children: t('Loading built-in servers…') })) : list.error && !list.value.length ? (_jsx(Failed, { error: list.error, onRetry: list.reload })) : !shown.length ? (_jsx(Soft, { children: list.value.length ? t('No built-in server matches “{q}”.', { q: q.trim() }) : t('No built-in servers are listed.') })) : (_jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: t('{servers} servers · {ops} operations', { servers: shown.length, ops }) }), shown.map((s) => {
166
173
  const on = open === s.name;
167
- return (_jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: [_jsxs(XStack, { render: "button", "aria-expanded": on, "aria-label": s.name, items: "center", gap: "$3", px: "$3", py: "$2.5", hoverStyle: { bg: '$hover' }, style: left, onPress: () => setOpen(on ? null : s.name), children: [_jsxs(YStack, { flex: 1, minW: 0, gap: "$1", children: [_jsx(SizableText, { size: "$3", color: "$ink", children: s.name }), s.description ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: on ? undefined : 2, children: s.description })) : null] }), _jsx(SizableText, { size: "$1", color: "$soft", children: s.ops.length }), _jsx(ChevronDown, { size: 14, style: { transform: on ? 'rotate(180deg)' : undefined } })] }), on ? (_jsx(YStack, { px: "$3", py: "$2", gap: "$1", borderTopWidth: 1, borderColor: "$borderColor", children: s.ops.length ? (s.ops.map((op) => (_jsx(SizableText, { size: "$1", color: "$ink", style: mono, children: op }, op)))) : (_jsx(SizableText, { size: "$1", color: "$soft", children: "This server lists no operations." })) })) : null] }, s.name));
174
+ return (_jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: [_jsxs(XStack, { render: "button", "aria-expanded": on, "aria-label": s.name, items: "center", gap: "$3", px: "$3", py: "$2.5", hoverStyle: { bg: '$hover' }, "$platform-web": { textAlign: 'start' }, onPress: () => setOpen(on ? null : s.name), children: [_jsxs(YStack, { flex: 1, minW: 0, gap: "$1", children: [_jsx(SizableText, { size: "$3", color: "$ink", children: s.name }), s.description ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: on ? undefined : 2, children: s.description })) : null] }), _jsx(SizableText, { size: "$1", color: "$soft", children: s.ops.length }), _jsx(ChevronDown, { size: 14, style: { transform: on ? 'rotate(180deg)' : undefined } })] }), on ? (_jsx(YStack, { px: "$3", py: "$2", gap: "$1", borderTopWidth: 1, borderColor: "$borderColor", children: s.ops.length ? (s.ops.map((op) => (_jsx(SizableText, { size: "$1", color: "$ink", style: mono, children: op }, op)))) : (_jsx(SizableText, { size: "$1", color: "$soft", children: t('This server lists no operations.') })) })) : null] }, s.name));
168
175
  })] })) }));
169
176
  }
170
177
  /** One listing in full: what it is, where it is served, and adding it. */
171
178
  function About({ listing, added, signed, onAdd, onClose }) {
172
- const t = useTarget();
173
- const full = useLoad(() => one(t, listing.id), listing, [t, listing.id]);
179
+ const { t } = useI18n();
180
+ const target = useTarget();
181
+ const full = useLoad(() => one(target, listing.id), listing, [target, listing.id]);
174
182
  const l = full.value;
175
183
  const links = [l.site, l.repo].filter((u) => u.startsWith('https://'));
176
- return (_jsxs(Sheet, { title: titleOf(l), open: true, onOpenChange: (o) => !o && onClose(), children: [_jsxs(XStack, { gap: "$3", items: "center", children: [_jsx(Mark, { name: titleOf(l), logo: l.logo, size: 40 }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, style: mono, children: l.name }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: [l.vendor, l.version ? `v${l.version}` : '', l.official ? 'Official' : ''].filter(Boolean).join(' · ') })] })] }), l.description ? (_jsx(SizableText, { size: "$2", color: "$soft", children: l.description })) : null, l.remotes.length ? (_jsx(Field, { label: "Served at", children: l.remotes.map((r) => (_jsxs(SizableText, { size: "$1", color: "$ink", style: mono, numberOfLines: 1, children: [r.url, " \u00B7 ", r.transport] }, r.url))) })) : null, l.packages.length ? (_jsx(Field, { label: "Packages", hint: ready(l) ? undefined : 'It ships only as a package, so it needs a place to run before it can be added here.', children: l.packages.map((p) => (_jsx(SizableText, { size: "$1", color: "$ink", style: mono, numberOfLines: 1, children: [p.registry, p.identifier, p.version, p.runtime].filter(Boolean).join(' · ') }, `${p.registry}:${p.identifier}`))) })) : null, links.length ? (_jsx(XStack, { gap: "$4", flexWrap: "wrap", children: links.map((u) => (_jsxs(Out, { href: u, children: [_jsx(SizableText, { size: "$2", color: "$soft", children: where(u) }), _jsx(ExternalLink, { size: 12, opacity: 0.6 })] }, u))) })) : null, full.error ? _jsx(Line, { children: `Showing what Browse listed. ${say(full.error)}` }) : null, signed && ready(l) ? (_jsx(XStack, { justify: "flex-end", children: _jsx(Button, { size: "sm", disabled: added, onPress: onAdd, children: added ? 'Added' : 'Add to your connectors' }) })) : null] }));
184
+ return (_jsxs(Sheet, { title: titleOf(l), open: true, onOpenChange: (o) => !o && onClose(), children: [_jsxs(XStack, { gap: "$3", items: "center", children: [_jsx(Mark, { name: titleOf(l), logo: l.logo, size: 40 }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, style: mono, children: l.name }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: [l.vendor, l.version ? `v${l.version}` : '', l.official ? t('Official') : ''].filter(Boolean).join(' · ') })] })] }), l.description ? (_jsx(SizableText, { size: "$2", color: "$soft", children: l.description })) : null, l.remotes.length ? (_jsx(Field, { label: t('Served at'), children: l.remotes.map((r) => (_jsxs(SizableText, { size: "$1", color: "$ink", style: mono, numberOfLines: 1, children: [r.url, " \u00B7 ", r.transport] }, r.url))) })) : null, l.packages.length ? (_jsx(Field, { label: t('Packages'), hint: ready(l) ? undefined : t('It ships only as a package, so it needs a place to run before it can be added here.'), children: l.packages.map((p) => (_jsx(SizableText, { size: "$1", color: "$ink", style: mono, numberOfLines: 1, children: [p.registry, p.identifier, p.version, p.runtime].filter(Boolean).join(' · ') }, `${p.registry}:${p.identifier}`))) })) : null, links.length ? (_jsx(XStack, { gap: "$4", flexWrap: "wrap", children: links.map((u) => (_jsxs(Out, { href: u, children: [_jsx(SizableText, { size: "$2", color: "$soft", children: where(u) }), _jsx(ExternalLink, { size: 12, opacity: 0.6 })] }, u))) })) : null, full.error ? _jsx(Line, { children: t('Showing what Browse listed. {reason}', { reason: say(full.error, t) }) }) : null, signed && ready(l) ? (_jsx(XStack, { justify: "flex-end", children: _jsx(Button, { size: "sm", disabled: added, onPress: onAdd, children: added ? t('Added') : t('Add to your connectors') }) })) : null] }));
177
185
  }
178
186
  /**
179
187
  * Add a server: a listing off the shelf, or any MCP URL. The secret, when it
180
188
  * needs one, is sealed in KMS as it is added; then its tools are switched on.
181
189
  */
182
190
  function Connect({ listing, onClose, onAdded }) {
183
- const t = useTarget();
191
+ const { t } = useI18n();
192
+ const target = useTarget();
184
193
  const [name, setName] = useState('');
185
194
  const [url, setUrl] = useState('');
186
195
  const [header, setHeader] = useState('Authorization');
@@ -192,32 +201,33 @@ function Connect({ listing, onClose, onAdded }) {
192
201
  setNote('');
193
202
  const a = listing ? { listing: listing.id, name, header, secret } : { name, url, header, secret };
194
203
  try {
195
- const s = await add(t, a);
196
- let said = `${s.name} is added.`;
204
+ const s = await add(target, a);
205
+ let said = t('{name} is added.', { name: s.name });
197
206
  try {
198
- const its = (await tools(t, { source: 'mcp' })).filter((x) => owns(s, x.name)).map((x) => x.name);
207
+ const its = (await tools(target, { source: 'mcp' })).filter((x) => owns(s, x.name)).map((x) => x.name);
199
208
  if (its.length)
200
- await toggle(t, its);
201
- said = its.length ? `${s.name} is added, and its ${its.length} tools are on.` : `${s.name} is added. It listed no tools yet.`;
209
+ await toggle(target, its);
210
+ said = its.length ? t('{name} is added, and its {n} tools are on.', { name: s.name, n: its.length }) : t('{name} is added. It listed no tools yet.', { name: s.name });
202
211
  }
203
212
  catch (e) {
204
- said = `${s.name} is added, but its tools could not be switched on. ${say(e)}`;
213
+ said = t('{name} is added, but its tools could not be switched on. {reason}', { name: s.name, reason: say(e, t) });
205
214
  }
206
215
  onAdded(said);
207
216
  }
208
217
  catch (e) {
209
- setNote(say(e));
218
+ setNote(say(e, t));
210
219
  }
211
220
  finally {
212
221
  setWorking(false);
213
222
  }
214
223
  };
215
- return (_jsxs(Sheet, { title: listing ? `Add ${titleOf(listing)}` : 'Add by URL', open: true, onOpenChange: (o) => !o && onClose(), width: 500, children: [listing ? (_jsx(SizableText, { size: "$2", color: "$soft", children: listing.description || listing.name })) : (_jsx(SizableText, { size: "$2", color: "$soft", children: "Any MCP server that speaks HTTP. Its tools are on once it is added." })), _jsx(Field, { label: "Name", hint: listing ? 'Leave it empty to use the listing’s own.' : undefined, children: _jsx(Input, { value: name, onChangeText: setName, placeholder: listing ? titleOf(listing) : 'Docs', "aria-label": "Name" }) }), listing ? null : (_jsx(Field, { label: "URL", hint: "A public http(s) address. Private and loopback addresses are refused.", children: _jsx(Input, { value: url, onChangeText: setUrl, placeholder: "https://mcp.example.com/mcp", "aria-label": "URL", autoCapitalize: "none" }) })), _jsx(Field, { label: "Secret", hint: "Optional. Kept sealed in KMS, sent on every call, and never shown again.", children: _jsx(Input, { value: secret, onChangeText: setSecret, placeholder: "Bearer \u2026", "aria-label": "Secret", secureTextEntry: true }) }), secret ? (_jsx(Field, { label: "Header", children: _jsx(Input, { value: header, onChangeText: setHeader, "aria-label": "Header", autoCapitalize: "none" }) })) : null, _jsx(Line, { children: note }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: onClose, children: "Cancel" }), _jsx(Button, { size: "sm", disabled: working, onPress: () => void save(), children: "Add" })] })] }));
224
+ return (_jsxs(Sheet, { title: listing ? t('Add {name}', { name: titleOf(listing) }) : t('Add by URL'), open: true, onOpenChange: (o) => !o && onClose(), width: 500, children: [listing ? (_jsx(SizableText, { size: "$2", color: "$soft", children: listing.description || listing.name })) : (_jsx(SizableText, { size: "$2", color: "$soft", children: t('Any MCP server that speaks HTTP. Its tools are on once it is added.') })), _jsx(Field, { label: t('Name'), hint: listing ? t('Leave it empty to use the listing’s own.') : undefined, children: _jsx(Input, { value: name, onChangeText: setName, placeholder: listing ? titleOf(listing) : t('Docs'), "aria-label": t('Name') }) }), listing ? null : (_jsx(Field, { label: "URL", hint: t('A public http(s) address. Private and loopback addresses are refused.'), children: _jsx(Input, { value: url, onChangeText: setUrl, placeholder: "https://mcp.example.com/mcp", "aria-label": "URL", autoCapitalize: "none" }) })), _jsx(Field, { label: t('Secret'), hint: t('Optional. Kept sealed in KMS, sent on every call, and never shown again.'), children: _jsx(Input, { value: secret, onChangeText: setSecret, placeholder: "Bearer \u2026", "aria-label": t('Secret'), secureTextEntry: true }) }), secret ? (_jsx(Field, { label: t('Header'), children: _jsx(Input, { value: header, onChangeText: setHeader, "aria-label": t('Header'), autoCapitalize: "none" }) })) : null, _jsx(Line, { children: note }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: onClose, children: t('Cancel') }), _jsx(Button, { size: "sm", disabled: working, onPress: () => void save(), children: t('Add') })] })] }));
216
225
  }
217
226
  /** One of the org's servers: where it is, its secret, its tools and their switches, and removing it. */
218
227
  function Detail({ server, may, onClose, onDeleted }) {
219
- const t = useTarget();
220
- const list = useLoad(() => tools(t, { source: 'mcp' }), [], [t, server.id]);
228
+ const { t } = useI18n();
229
+ const target = useTarget();
230
+ const list = useLoad(() => tools(target, { source: 'mcp' }), [], [target, server.id]);
221
231
  const its = useMemo(() => list.value.filter((x) => owns(server, x.name)), [list.value, server]);
222
232
  const [busy, setBusy] = useState(false);
223
233
  const [note, setNote] = useState('');
@@ -227,21 +237,25 @@ function Detail({ server, may, onClose, onDeleted }) {
227
237
  setBusy(true);
228
238
  setNote('');
229
239
  try {
230
- await toggle(t, on, offNames);
240
+ await toggle(target, on, offNames);
231
241
  list.reload();
232
242
  }
233
243
  catch (e) {
234
- setNote(say(e));
244
+ setNote(say(e, t));
235
245
  }
236
246
  finally {
237
247
  setBusy(false);
238
248
  }
239
249
  };
240
- return (_jsxs(Sheet, { title: server.name, open: true, onOpenChange: (o) => !o && onClose(), children: [_jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$1", color: "$ink", style: mono, numberOfLines: 2, children: server.url }), _jsxs(XStack, { items: "center", gap: "$1.5", children: [server.secret ? _jsx(KeyRound, { size: 12, opacity: 0.7 }) : _jsx(ServerIcon, { size: 12, opacity: 0.7 }), _jsx(SizableText, { size: "$1", color: "$soft", children: server.secret ? `A secret is sealed in KMS and sent in ${server.header || 'its header'}.` : 'No secret: it is called as it is.' })] })] }), _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { size: "$3", color: "$ink", flex: 1, children: "Tools" }), its.length && may ? (_jsx(Button, { size: "sm", variant: "outline", disabled: busy, onPress: () => void (off.length ? flip(off, []) : flip([], its.map((x) => x.name))), children: off.length ? 'Turn all on' : 'Turn all off' })) : null] }), list.loading && !list.value.length ? (_jsx(Soft, { children: "Loading its tools\u2026" })) : list.error && !list.value.length ? (_jsx(Failed, { error: list.error, onRetry: list.reload })) : !its.length ? (_jsx(Soft, { children: "No tools listed yet. A server that does not answer shows its tools once it does." })) : (_jsx(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: its.map((x, i) => {
250
+ return (_jsxs(Sheet, { title: server.name, open: true, onOpenChange: (o) => !o && onClose(), children: [_jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$1", color: "$ink", style: mono, numberOfLines: 2, children: server.url }), _jsxs(XStack, { items: "center", gap: "$1.5", children: [server.secret ? _jsx(KeyRound, { size: 12, opacity: 0.7 }) : _jsx(ServerIcon, { size: 12, opacity: 0.7 }), _jsx(SizableText, { size: "$1", color: "$soft", children: server.secret
251
+ ? server.header
252
+ ? t('A secret is sealed in KMS and sent in {header}.', { header: server.header })
253
+ : t('A secret is sealed in KMS and sent in its header.')
254
+ : t('No secret: it is called as it is.') })] })] }), _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { size: "$3", color: "$ink", flex: 1, children: t('Tools') }), its.length && may ? (_jsx(Button, { size: "sm", variant: "outline", disabled: busy, onPress: () => void (off.length ? flip(off, []) : flip([], its.map((x) => x.name))), children: off.length ? t('Turn all on') : t('Turn all off') })) : null] }), list.loading && !list.value.length ? (_jsx(Soft, { children: t('Loading its tools…') })) : list.error && !list.value.length ? (_jsx(Failed, { error: list.error, onRetry: list.reload })) : !its.length ? (_jsx(Soft, { children: t('No tools listed yet. A server that does not answer shows its tools once it does.') })) : (_jsx(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: its.map((x, i) => {
241
255
  const short = x.name.slice(server.id.length + 1);
242
- return (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", children: [_jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", style: mono, numberOfLines: 1, children: short }), x.description ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 2, children: x.description })) : null] }), may ? (_jsx(Switch, { checked: x.activated, disabled: busy, onCheckedChange: (v) => void flip(v ? [x.name] : [], v ? [] : [x.name]), "aria-label": `${short} on` })) : (_jsx(SizableText, { size: "$1", color: "$soft", children: x.activated ? 'On' : 'Off' }))] }, x.name));
243
- }) })), _jsx(Line, { children: note || (may ? '' : 'Only an org admin can switch or remove connectors.') }), may ? (_jsx(XStack, { justify: "flex-start", children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => setAsking(true), children: "Remove connector" }) })) : null, _jsx(Confirm, { what: server.name, says: "Its tools leave your organization and every agent, and its sealed secret is destroyed.", open: asking, onOpenChange: setAsking, onYes: async () => {
244
- await remove(t, server.id);
256
+ return (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", children: [_jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", style: mono, numberOfLines: 1, children: short }), x.description ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 2, children: x.description })) : null] }), may ? (_jsx(Switch, { checked: x.activated, disabled: busy, onCheckedChange: (v) => void flip(v ? [x.name] : [], v ? [] : [x.name]), "aria-label": t('{name} on', { name: short }) })) : (_jsx(SizableText, { size: "$1", color: "$soft", children: x.activated ? t('On') : t('Off') }))] }, x.name));
257
+ }) })), _jsx(Line, { children: note || (may ? '' : t('Only an org admin can switch or remove connectors.')) }), may ? (_jsx(XStack, { justify: "flex-start", children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => setAsking(true), children: t('Remove connector') }) })) : null, _jsx(Confirm, { what: server.name, says: t('Its tools leave your organization and every agent, and its sealed secret is destroyed.'), open: asking, onOpenChange: setAsking, onYes: async () => {
258
+ await remove(target, server.id);
245
259
  onDeleted();
246
260
  } })] }));
247
261
  }