@hanzo/build 0.2.9 → 0.2.11

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 (152) hide show
  1. package/README.md +126 -23
  2. package/lib/api/agents.d.ts +80 -0
  3. package/lib/api/agents.js +148 -0
  4. package/lib/api/billing.d.ts +205 -0
  5. package/lib/api/billing.js +300 -0
  6. package/lib/api/call.d.ts +6 -0
  7. package/lib/api/call.js +11 -0
  8. package/lib/api/capabilities.d.ts +25 -0
  9. package/lib/api/capabilities.js +40 -0
  10. package/lib/api/changes.d.ts +56 -0
  11. package/lib/api/changes.js +63 -0
  12. package/lib/api/coding.d.ts +42 -4
  13. package/lib/api/coding.js +62 -3
  14. package/lib/api/connectors.d.ts +90 -0
  15. package/lib/api/connectors.js +109 -0
  16. package/lib/api/consent.d.ts +22 -0
  17. package/lib/api/consent.js +28 -0
  18. package/lib/api/environment.d.ts +57 -0
  19. package/lib/api/environment.js +85 -0
  20. package/lib/api/git.d.ts +6 -2
  21. package/lib/api/git.js +13 -5
  22. package/lib/api/github.d.ts +13 -0
  23. package/lib/api/github.js +9 -0
  24. package/lib/api/harness.d.ts +44 -0
  25. package/lib/api/harness.js +348 -0
  26. package/lib/api/keys.d.ts +41 -0
  27. package/lib/api/keys.js +55 -0
  28. package/lib/api/machines.d.ts +56 -0
  29. package/lib/api/machines.js +84 -0
  30. package/lib/api/members.d.ts +47 -0
  31. package/lib/api/members.js +88 -0
  32. package/lib/api/memory.d.ts +25 -0
  33. package/lib/api/memory.js +34 -0
  34. package/lib/api/places.d.ts +2 -2
  35. package/lib/api/places.js +3 -26
  36. package/lib/api/plugins.d.ts +55 -0
  37. package/lib/api/plugins.js +62 -0
  38. package/lib/api/pref.d.ts +49 -0
  39. package/lib/api/pref.js +68 -0
  40. package/lib/api/profile.d.ts +21 -0
  41. package/lib/api/profile.js +40 -0
  42. package/lib/api/projects.d.ts +19 -2
  43. package/lib/api/projects.js +29 -4
  44. package/lib/api/provider.d.ts +42 -0
  45. package/lib/api/provider.js +58 -0
  46. package/lib/api/sandbox.d.ts +30 -0
  47. package/lib/api/sandbox.js +49 -0
  48. package/lib/api/sessions.d.ts +39 -4
  49. package/lib/api/sessions.js +58 -6
  50. package/lib/api/skills.d.ts +70 -0
  51. package/lib/api/skills.js +90 -0
  52. package/lib/api/tools.d.ts +34 -0
  53. package/lib/api/tools.js +37 -0
  54. package/lib/api/turn.d.ts +84 -16
  55. package/lib/api/turn.js +224 -4
  56. package/lib/api/webhooks.d.ts +60 -0
  57. package/lib/api/webhooks.js +81 -0
  58. package/lib/ask.d.ts +18 -0
  59. package/lib/ask.js +69 -0
  60. package/lib/builder.js +39 -19
  61. package/lib/customize/agents.d.ts +2 -0
  62. package/lib/customize/agents.js +125 -0
  63. package/lib/customize/connectors.d.ts +2 -0
  64. package/lib/customize/connectors.js +197 -0
  65. package/lib/customize/index.d.ts +6 -0
  66. package/lib/customize/index.js +50 -0
  67. package/lib/customize/plugins.d.ts +2 -0
  68. package/lib/customize/plugins.js +93 -0
  69. package/lib/customize/skills.d.ts +2 -0
  70. package/lib/customize/skills.js +125 -0
  71. package/lib/customize/ui.d.ts +110 -0
  72. package/lib/customize/ui.js +99 -0
  73. package/lib/desk.d.ts +8 -1
  74. package/lib/desk.js +29 -35
  75. package/lib/door.d.ts +7 -0
  76. package/lib/door.js +89 -0
  77. package/lib/environment.d.ts +15 -0
  78. package/lib/environment.js +132 -0
  79. package/lib/files.d.ts +14 -0
  80. package/lib/files.js +111 -0
  81. package/lib/find.d.ts +7 -0
  82. package/lib/find.js +77 -0
  83. package/lib/foot.d.ts +16 -0
  84. package/lib/foot.js +36 -0
  85. package/lib/forge.js +8 -4
  86. package/lib/git.d.ts +9 -0
  87. package/lib/git.js +74 -0
  88. package/lib/host.d.ts +5 -0
  89. package/lib/landing.d.ts +10 -0
  90. package/lib/landing.js +47 -14
  91. package/lib/markdown.d.ts +49 -0
  92. package/lib/markdown.js +123 -0
  93. package/lib/plans.d.ts +1 -0
  94. package/lib/plans.js +113 -0
  95. package/lib/prefs.d.ts +30 -0
  96. package/lib/prefs.js +83 -0
  97. package/lib/prose.d.ts +4 -0
  98. package/lib/prose.js +53 -0
  99. package/lib/route.d.ts +16 -1
  100. package/lib/route.js +32 -1
  101. package/lib/run.js +207 -28
  102. package/lib/section.js +2 -2
  103. package/lib/settings/account.d.ts +5 -0
  104. package/lib/settings/account.js +111 -0
  105. package/lib/settings/billing.d.ts +1 -0
  106. package/lib/settings/billing.js +104 -0
  107. package/lib/settings/capabilities.d.ts +1 -0
  108. package/lib/settings/capabilities.js +55 -0
  109. package/lib/settings/card.d.ts +31 -0
  110. package/lib/settings/card.js +105 -0
  111. package/lib/settings/code.d.ts +1 -0
  112. package/lib/settings/code.js +51 -0
  113. package/lib/settings/environments.d.ts +1 -0
  114. package/lib/settings/environments.js +53 -0
  115. package/lib/settings/general.d.ts +1 -0
  116. package/lib/settings/general.js +44 -0
  117. package/lib/settings/index.d.ts +4 -0
  118. package/lib/settings/index.js +22 -0
  119. package/lib/settings/integrations.d.ts +1 -0
  120. package/lib/settings/integrations.js +81 -0
  121. package/lib/settings/keys.d.ts +1 -0
  122. package/lib/settings/keys.js +102 -0
  123. package/lib/settings/machines.d.ts +1 -0
  124. package/lib/settings/machines.js +118 -0
  125. package/lib/settings/members.d.ts +1 -0
  126. package/lib/settings/members.js +67 -0
  127. package/lib/settings/memory.d.ts +1 -0
  128. package/lib/settings/memory.js +56 -0
  129. package/lib/settings/notifications.d.ts +1 -0
  130. package/lib/settings/notifications.js +97 -0
  131. package/lib/settings/privacy.d.ts +1 -0
  132. package/lib/settings/privacy.js +63 -0
  133. package/lib/settings/sections.d.ts +16 -0
  134. package/lib/settings/sections.js +32 -0
  135. package/lib/settings/ui.d.ts +49 -0
  136. package/lib/settings/ui.js +47 -0
  137. package/lib/settings/usage.d.ts +1 -0
  138. package/lib/settings/usage.js +114 -0
  139. package/lib/shelf.d.ts +6 -0
  140. package/lib/shelf.js +86 -16
  141. package/lib/switch.js +1 -2
  142. package/lib/transcript.d.ts +12 -0
  143. package/lib/transcript.js +56 -0
  144. package/lib/voice.d.ts +3 -1
  145. package/lib/voice.js +10 -4
  146. package/package.json +1 -1
  147. package/lib/account.d.ts +0 -14
  148. package/lib/account.js +0 -38
  149. package/lib/e2e/site.spec.d.ts +0 -1
  150. package/lib/e2e/site.spec.js +0 -125
  151. package/lib/mcp.d.ts +0 -1
  152. package/lib/mcp.js +0 -28
@@ -0,0 +1,125 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Agents: the org's own — a model, instructions, the tools it may call, and
4
+ * what it may spend.
5
+ *
6
+ * Yours lists them; opening one edits it in place, and only what changed is
7
+ * sent. An agent calls only the tools it names, and only those that are on for
8
+ * the org, so the choices are the tools that are on; "every tool" is whatever
9
+ * the fleet's MCP server serves when it runs. A budget is required: what it may
10
+ * spend each period, and in one run. Discover is the platform's presets whose
11
+ * tool calls run on the platform; adding one opens a new agent written from it.
12
+ */
13
+ import { SizableText, XStack, YStack } from '@hanzo/gui';
14
+ import { Bot } from '@hanzogui/lucide-icons-2';
15
+ import { Button, Input, Switch, Textarea } from '@hanzo/ui';
16
+ import { ChipSelect } from '@hanzo/ui/product';
17
+ import { useEffect, useMemo, useState } from 'react';
18
+ import { agents, ALL, create, draft, EMPTY, fromPreset, one, PERIODS, presets, remove, update } from '../api/agents.js';
19
+ import { label as named, models } from '../api/models.js';
20
+ import { tools } from '../api/tools.js';
21
+ import { useRead } from '../data.js';
22
+ import { useHost, useTarget } from '../host.js';
23
+ import { Add, Choice, Confirm, Field, Grid, Line, Mark, matches, mono, Sheet, Soft, Tile } from './ui.js';
24
+ /** The model a new agent runs on when none is chosen: the deployment's own default. */
25
+ const DEFAULT = 'default';
26
+ const money = (m) => `$${(m / 1_000_000).toFixed(m % 10_000 ? 4 : 2).replace(/\.?0+$/, '') || '0'}`;
27
+ export function Agents({ view, q, adding, onAdding, onView }) {
28
+ const host = useHost();
29
+ const t = useTarget();
30
+ const signed = Boolean(host.person);
31
+ const mine = useRead(signed ? () => agents(t) : null, [], [t, signed]);
32
+ const offered = useRead(view === 'discover' ? () => presets(t) : null, [], [t, view]);
33
+ const [opened, setOpened] = useState(null);
34
+ const [start, setStart] = useState(null);
35
+ const [note, setNote] = useState('');
36
+ const closeNew = () => {
37
+ setStart(null);
38
+ onAdding(false);
39
+ };
40
+ const editor = adding || start ? (_jsx(Editor, { start: start ?? EMPTY, onClose: closeNew, onSaved: (a) => {
41
+ closeNew();
42
+ onView('yours');
43
+ mine.reload();
44
+ setNote(`${a.name} is saved`);
45
+ } })) : opened ? (_jsx(Editor, { agent: opened, onClose: () => setOpened(null), onSaved: (a) => {
46
+ setOpened(null);
47
+ mine.reload();
48
+ setNote(`${a.name} is saved`);
49
+ }, onDeleted: () => {
50
+ setNote(`${opened.name} is deleted`);
51
+ setOpened(null);
52
+ mine.reload();
53
+ } })) : null;
54
+ if (view === 'discover') {
55
+ const names = new Set(mine.value.map((a) => a.name));
56
+ const shown = offered.value.filter((p) => matches(q, p.id, p.title, p.prompt));
57
+ return (_jsxs(YStack, { gap: "$3", children: [offered.error && !offered.value.length ? (_jsx(Soft, { children: offered.error.message })) : offered.loading && !offered.value.length ? (_jsx(Soft, { children: "Reading the presets\u2026" })) : !shown.length ? (_jsx(Soft, { children: offered.value.length ? 'No preset matches.' : 'The platform offers no presets.' })) : (_jsxs(_Fragment, { children: [_jsx(SizableText, { size: "$1", color: "$soft", children: "Presets from the platform. Adding one opens a new agent written from it, to change before it is saved." }), _jsx(Grid, { label: "Presets", children: shown.map((p) => (_jsx(Tile, { title: p.title || p.id, detail: p.prompt, meta: `Preset ${p.id}`, mark: _jsx(Mark, { name: p.title || p.id, icon: _jsx(Bot, { size: 15 }) }), action: signed ? _jsx(Add, { name: p.title || p.id, added: names.has(p.id), onPress: () => setStart(fromPreset(p)) }) : undefined }, p.id))) })] })), editor] }));
58
+ }
59
+ if (!signed) {
60
+ return _jsx(Soft, { action: host.signIn ? _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: "Sign in" }) : undefined, children: "Sign in to see your agents." });
61
+ }
62
+ const shown = mine.value.filter((a) => matches(q, a.name, a.description, a.model));
63
+ return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), mine.error && !mine.value.length ? (_jsx(Soft, { children: mine.error.message })) : mine.loading && !mine.value.length ? (_jsx(Soft, { children: "Reading your agents\u2026" })) : !mine.value.length ? (_jsx(Soft, { action: _jsx(Button, { size: "sm", variant: "outline", onPress: () => onAdding(true), children: "New agent" }), children: "No agents yet. Give one a model, instructions and the tools it may call." })) : !shown.length ? (_jsx(Soft, { children: "No agent matches." })) : (_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(Mark, { name: a.name, icon: a.emoji ? _jsx(SizableText, { size: "$4", children: a.emoji }) : undefined }), onOpen: () => setOpened(a) }, a.id || a.name))) })), editor] }));
64
+ function reach(a) {
65
+ if (a.tools.includes(ALL))
66
+ return 'every tool';
67
+ return a.tools.length === 1 ? '1 tool' : `${a.tools.length} tools`;
68
+ }
69
+ }
70
+ /** Write a new agent, or change one; an existing agent is read whole first, for its instructions. */
71
+ function Editor({ agent, start, onClose, onSaved, onDeleted, }) {
72
+ const t = useTarget();
73
+ const full = useRead(agent ? () => one(t, agent.id || agent.name) : null, null, [t, agent?.id]);
74
+ const catalog = useRead(() => models(t), [], [t]);
75
+ const on = useRead(() => tools(t, { activated: true }), [], [t]);
76
+ const [d, setD] = useState(start ?? EMPTY);
77
+ const [ready, setReady] = useState(!agent);
78
+ const [working, setWorking] = useState(false);
79
+ const [note, setNote] = useState('');
80
+ const [asking, setAsking] = useState(false);
81
+ const [find, setFind] = useState('');
82
+ useEffect(() => {
83
+ if (full.value) {
84
+ setD(draft(full.value));
85
+ setReady(true);
86
+ }
87
+ }, [full.value]);
88
+ const set = (p) => setD((x) => ({ ...x, ...p }));
89
+ const every = d.tools.includes(ALL);
90
+ // The tools that are on, and any the agent already names that are not.
91
+ const choices = useMemo(() => {
92
+ const listed = on.value.map((x) => ({ name: x.name, source: x.source, description: x.description }));
93
+ const seen = new Set(listed.map((x) => x.name));
94
+ const kept = d.tools.filter((n) => n !== ALL && !seen.has(n)).map((name) => ({ name, source: 'not on', description: '' }));
95
+ return [...kept, ...listed];
96
+ }, [on.value, d.tools]);
97
+ const shownTools = choices.filter((x) => matches(find, x.name, x.source, x.description));
98
+ const items = useMemo(() => {
99
+ const list = [{ id: DEFAULT, label: 'Default', hint: 'the deployment’s' }, ...catalog.value.map((m) => ({ id: m.id, label: m.label }))];
100
+ if (d.model && !list.some((m) => m.id === d.model))
101
+ list.splice(1, 0, { id: d.model, label: named(d.model) });
102
+ return agent ? list.filter((m) => m.id !== DEFAULT) : list;
103
+ }, [catalog.value, d.model, agent]);
104
+ const chosen = items.find((m) => m.id === (d.model || DEFAULT)) ?? null;
105
+ const save = async () => {
106
+ setWorking(true);
107
+ setNote('');
108
+ try {
109
+ const was = full.value;
110
+ const saved = agent && was ? await update(t, was, d) : await create(t, d);
111
+ onSaved(saved);
112
+ }
113
+ catch (e) {
114
+ setNote(e instanceof Error ? e.message : 'The agent was not saved');
115
+ }
116
+ finally {
117
+ setWorking(false);
118
+ }
119
+ };
120
+ const pick = (name, yes) => set({ tools: yes ? [...d.tools, name] : d.tools.filter((n) => n !== name) });
121
+ return (_jsx(Sheet, { title: agent ? agent.name : 'New agent', open: true, onOpenChange: (o) => !o && onClose(), width: 680, children: agent && full.error && !full.value ? (_jsx(Soft, { children: full.error.message })) : !ready ? (_jsx(Soft, { children: "Reading 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 }), placeholder: "helper", "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: _jsx(XStack, { children: _jsx(ChipSelect, { name: "Model", label: chosen?.label ?? 'Default', chosen: chosen, items: items, onChange: (m) => set({ model: m.id === DEFAULT ? '' : m.id }), placeholder: "Search models\u2026", placement: "bottom-start", loading: catalog.loading, error: catalog.error?.message ?? 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: "Reading the tools that are on\u2026" })) : on.error && !on.value.length ? (_jsx(Soft, { children: on.error.message })) : !shownTools.length ? (_jsx(Soft, { children: choices.length ? 'No tool matches.' : 'No tool is on for your organization. Switch skills and connectors on 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 () => {
122
+ await remove(t, agent.id || agent.name);
123
+ onDeleted();
124
+ } })) : null] })) }));
125
+ }
@@ -0,0 +1,2 @@
1
+ import { type Pane } from './ui.tsx';
2
+ export declare function Connectors({ view, q, adding, onAdding, onView }: Pane): import("react").JSX.Element;
@@ -0,0 +1,197 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Connectors: MCP servers whose tools an agent calls.
4
+ *
5
+ * Yours is the servers the org added — by URL, or off the shelf — each with the
6
+ * tools it brings and a switch for each; a tool is called only while it is on.
7
+ * Adding a server switches its tools on. Discover is the shelf (our copy of the
8
+ * public MCP registry) and the fleet's own servers, which every run can call and
9
+ * nothing adds. A listing is added here only when it serves streamable HTTP; one
10
+ * that ships only a package needs somewhere to run it first, so it has no plus.
11
+ * A server's secret is sealed in KMS and never shown again.
12
+ */
13
+ import { SizableText, XStack, YStack } from '@hanzo/gui';
14
+ import { ChevronDown, ExternalLink, KeyRound, Server as ServerIcon } from '@hanzogui/lucide-icons-2';
15
+ import { Button, Input, Switch } from '@hanzo/ui';
16
+ import { useEffect, useMemo, useState } from 'react';
17
+ import { add, one, owns, ready, remove, servers, shelf, titleOf } from '../api/connectors.js';
18
+ import { natives } from '../api/mcp.js';
19
+ import { toggle, tools } from '../api/tools.js';
20
+ import { useRead } from '../data.js';
21
+ import { useHost, useTarget } from '../host.js';
22
+ import { Out } from '../out.js';
23
+ import { Add, Confirm, day, Featured, Field, Grid, left, Line, Mark, matches, mono, Part, Sheet, Soft, Tile } from './ui.js';
24
+ const PAGE = 48;
25
+ /** The part of a URL a person reads: its host and path. */
26
+ const where = (url) => url.replace(/^https?:\/\//, '').replace(/\/$/, '');
27
+ export function Connectors({ view, q, adding, onAdding, onView }) {
28
+ const host = useHost();
29
+ const t = useTarget();
30
+ const signed = Boolean(host.person);
31
+ const mine = useRead(signed ? () => servers(t) : null, [], [t, signed]);
32
+ const [opened, setOpened] = useState(null);
33
+ const [listing, setListing] = useState(null);
34
+ const [connecting, setConnecting] = useState(null);
35
+ const [note, setNote] = useState('');
36
+ const connect = adding || connecting ? (_jsx(Connect, { listing: connecting, onClose: () => {
37
+ setConnecting(null);
38
+ onAdding(false);
39
+ }, onAdded: (said) => {
40
+ setConnecting(null);
41
+ onAdding(false);
42
+ mine.reload();
43
+ onView('yours');
44
+ setNote(said);
45
+ } })) : null;
46
+ const detail = listing ? (_jsx(About, { listing: listing, added: mine.value.some((s) => s.listing === listing.id), signed: signed, onAdd: () => {
47
+ setConnecting(listing);
48
+ setListing(null);
49
+ }, onClose: () => setListing(null) })) : null;
50
+ if (view === 'discover') {
51
+ return (_jsxs(YStack, { gap: "$6", children: [_jsx(Line, { children: note }), _jsx(Shelf, { q: q, signed: signed, added: new Set(mine.value.map((s) => s.listing).filter(Boolean)), onOpen: setListing, onAdd: setConnecting }), _jsx(Natives, { q: q }), detail, connect] }));
52
+ }
53
+ if (!signed) {
54
+ return _jsx(Soft, { action: host.signIn ? _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: "Sign in" }) : undefined, children: "Sign in to see your connectors." });
55
+ }
56
+ const shown = mine.value.filter((s) => matches(q, s.name, s.url, s.listing));
57
+ return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), mine.error && !mine.value.length ? (_jsx(Soft, { children: mine.error.message })) : mine.loading && !mine.value.length ? (_jsx(Soft, { children: "Reading your connectors\u2026" })) : !mine.value.length ? (_jsx(Soft, { action: _jsx(Button, { size: "sm", variant: "outline", onPress: () => onView('discover'), children: "Discover connectors" }), children: "No connectors yet. Add an MCP server by its URL, or pick one off the shelf." })) : !shown.length ? (_jsx(Soft, { children: "No connector matches." })) : (_jsx(Grid, { label: "Your connectors", children: shown.map((s) => (_jsx(Tile, { title: s.name, detail: where(s.url), meta: [s.listing ? 'From the shelf' : 'Added by URL', s.secret ? 'secret sealed in KMS' : '', day(s.created)].filter(Boolean).join(' · '), mark: _jsx(Mark, { name: s.name }), onOpen: () => setOpened(s) }, s.id))) })), opened ? (_jsx(Detail, { server: opened, onClose: () => setOpened(null), onDeleted: () => {
58
+ setNote(`${opened.name} is removed`);
59
+ setOpened(null);
60
+ mine.reload();
61
+ } })) : null, connect] }));
62
+ }
63
+ /** The shelf: featured first, then by name, a page at a time. The platform searches it. */
64
+ function Shelf({ q, signed, added, onOpen, onAdd }) {
65
+ const t = useTarget();
66
+ const host = useHost();
67
+ const text = useSettled(q.trim(), 300);
68
+ const first = useRead(signed ? () => shelf(t, { text, limit: PAGE }) : null, null, [t, signed, text]);
69
+ const [more, setMore] = useState([]);
70
+ const [paging, setPaging] = useState(false);
71
+ const [note, setNote] = useState('');
72
+ useEffect(() => setMore([]), [first.value]);
73
+ if (!signed) {
74
+ return (_jsx(Part, { title: "The shelf", detail: "MCP servers the public registries publish.", children: _jsx(Soft, { action: host.signIn ? _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: "Sign in" }) : undefined, children: "Sign in to browse the shelf." }) }));
75
+ }
76
+ const page = first.value;
77
+ const list = [...(page?.listings ?? []), ...more];
78
+ const hero = !text ? list.find((l) => l.featured) : undefined;
79
+ const rest = hero ? list.filter((l) => l !== hero) : list;
80
+ const action = (l) => ready(l) ? (_jsx(Add, { name: titleOf(l), added: added.has(l.id), onPress: () => onAdd(l) })) : (_jsx(SizableText, { size: "$1", color: "$soft", shrink: 0, pt: "$1.5", children: "Package" }));
81
+ const next = async () => {
82
+ setPaging(true);
83
+ setNote('');
84
+ try {
85
+ const got = await shelf(t, { text, limit: PAGE, offset: list.length });
86
+ setMore((m) => [...m, ...got.listings]);
87
+ }
88
+ catch (e) {
89
+ setNote(e instanceof Error ? e.message : 'The next page did not load');
90
+ }
91
+ finally {
92
+ setPaging(false);
93
+ }
94
+ };
95
+ return (_jsx(Part, { title: "The shelf", detail: "MCP servers the public registries publish. A plus adds one to your organization.", children: first.error && !page ? (_jsx(Soft, { children: first.error.message })) : !page ? (_jsx(Soft, { children: "Reading the shelf\u2026" })) : !list.length ? (_jsx(Soft, { children: text ? 'Nothing on the shelf matches.' : 'The shelf is empty.' })) : (_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} servers` : `${page.total} servers` }), _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] })) }));
96
+ }
97
+ /** The facts a card carries under its description. */
98
+ const facts = (l) => [l.vendor, l.official ? 'Official' : '', ready(l) ? 'Hosted' : 'Package only'].filter(Boolean).join(' · ');
99
+ /** `value` once it has held still for `ms`. */
100
+ function useSettled(value, ms) {
101
+ const [settled, setSettled] = useState(value);
102
+ useEffect(() => {
103
+ const id = setTimeout(() => setSettled(value), ms);
104
+ return () => clearTimeout(id);
105
+ }, [value, ms]);
106
+ return settled;
107
+ }
108
+ /** The fleet's own servers, from the MCP server's tools/list. Every run can call them. */
109
+ function Natives({ q }) {
110
+ const t = useTarget();
111
+ const list = useRead(() => natives(t), [], [t]);
112
+ const [open, setOpen] = useState(null);
113
+ const shown = list.value.filter((s) => matches(q, s.name, s.description, ...s.ops));
114
+ const ops = shown.reduce((n, s) => n + s.ops.length, 0);
115
+ return (_jsx(Part, { title: "Built in", detail: "The fleet\u2019s own servers. A run starts one when it calls it, and nothing needs adding.", children: list.loading && !list.value.length ? (_jsx(Soft, { children: "Reading the servers\u2026" })) : list.error && !list.value.length ? (_jsx(Soft, { children: list.error.message })) : !shown.length ? (_jsx(Soft, { children: list.value.length ? 'No built-in server matches.' : 'The MCP server listed no native servers.' })) : (_jsxs(YStack, { gap: "$1", children: [_jsxs(SizableText, { size: "$1", color: "$soft", children: [shown.length, " servers \u00B7 ", ops, " operations"] }), shown.map((s) => {
116
+ const on = open === s.name;
117
+ 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 named no operations." })) })) : null] }, s.name));
118
+ })] })) }));
119
+ }
120
+ /** One listing in full: what it is, where it is served, and adding it. */
121
+ function About({ listing, added, signed, onAdd, onClose }) {
122
+ const t = useTarget();
123
+ const full = useRead(() => one(t, listing.id), listing, [t, listing.id]);
124
+ const l = full.value;
125
+ const links = [l.site, l.repo].filter((u) => u.startsWith('https://'));
126
+ 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 somewhere 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: full.error.message }) : 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] }));
127
+ }
128
+ /**
129
+ * Add a server: a listing off the shelf, or any MCP URL. The secret, when it
130
+ * needs one, is sealed in KMS as it is added; then its tools are switched on.
131
+ */
132
+ function Connect({ listing, onClose, onAdded }) {
133
+ const t = useTarget();
134
+ const [name, setName] = useState('');
135
+ const [url, setUrl] = useState('');
136
+ const [header, setHeader] = useState('Authorization');
137
+ const [secret, setSecret] = useState('');
138
+ const [working, setWorking] = useState(false);
139
+ const [note, setNote] = useState('');
140
+ const save = async () => {
141
+ setWorking(true);
142
+ setNote('');
143
+ const a = listing ? { listing: listing.id, name, header, secret } : { name, url, header, secret };
144
+ try {
145
+ const s = await add(t, a);
146
+ let said = `${s.name} is added.`;
147
+ try {
148
+ const its = (await tools(t, { source: 'mcp' })).filter((x) => owns(s, x.name)).map((x) => x.name);
149
+ if (its.length)
150
+ await toggle(t, its);
151
+ said = its.length ? `${s.name} is added, and its ${its.length} tools are on.` : `${s.name} is added. It listed no tools yet.`;
152
+ }
153
+ catch (e) {
154
+ said = `${s.name} is added, but its tools could not be switched on: ${e instanceof Error ? e.message : 'no answer'}`;
155
+ }
156
+ onAdded(said);
157
+ }
158
+ catch (e) {
159
+ setNote(e instanceof Error ? e.message : 'The connector was not added');
160
+ }
161
+ finally {
162
+ setWorking(false);
163
+ }
164
+ };
165
+ return (_jsxs(Sheet, { title: listing ? `Add ${titleOf(listing)}` : 'Add connector', 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 join your organization\u2019s and 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. Sealed in KMS and sent in the header below on every call; 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" })] })] }));
166
+ }
167
+ /** One of the org's servers: where it is, its secret, its tools and their switches, and removing it. */
168
+ function Detail({ server, onClose, onDeleted }) {
169
+ const t = useTarget();
170
+ const list = useRead(() => tools(t, { source: 'mcp' }), [], [t, server.id]);
171
+ const its = useMemo(() => list.value.filter((x) => owns(server, x.name)), [list.value, server]);
172
+ const [busy, setBusy] = useState(false);
173
+ const [note, setNote] = useState('');
174
+ const [asking, setAsking] = useState(false);
175
+ const off = its.filter((x) => !x.activated).map((x) => x.name);
176
+ const flip = async (on, offNames) => {
177
+ setBusy(true);
178
+ setNote('');
179
+ try {
180
+ await toggle(t, on, offNames);
181
+ list.reload();
182
+ }
183
+ catch (e) {
184
+ setNote(e instanceof Error ? e.message : 'That did not work');
185
+ }
186
+ finally {
187
+ setBusy(false);
188
+ }
189
+ };
190
+ 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 ? (_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: "Asking the server for its tools\u2026" })) : list.error && !list.value.length ? (_jsx(Soft, { children: list.error.message })) : !its.length ? (_jsx(Soft, { children: "The server listed no tools. One that does not answer is left out until it does." })) : (_jsx(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: its.map((x, i) => {
191
+ const short = x.name.slice(server.id.length + 1);
192
+ 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] }), _jsx(Switch, { checked: x.activated, disabled: busy, onCheckedChange: (v) => void flip(v ? [x.name] : [], v ? [] : [x.name]), "aria-label": `${short} on` })] }, x.name));
193
+ }) })), _jsx(Line, { children: note }), _jsx(XStack, { justify: "flex-start", children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => setAsking(true), children: "Remove connector" }) }), _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 () => {
194
+ await remove(t, server.id);
195
+ onDeleted();
196
+ } })] }));
197
+ }
@@ -0,0 +1,6 @@
1
+ import { type Tab } from '../route.ts';
2
+ import { type View } from './ui.tsx';
3
+ export declare function Customize({ tab, view: first }: {
4
+ tab: Tab;
5
+ view?: View;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Customize: what the agent brings to a run — the skills it knows, the
4
+ * connectors it calls, the plugins it runs, and agents of the org's own. Each
5
+ * tab is its own address; in each, Yours is what the org has and Discover is
6
+ * what it can add, with one search box and one Add over both.
7
+ *
8
+ * Nothing here is gated to org admins, because the platform gates none of it:
9
+ * every write on these tabs is open to any member of the org.
10
+ */
11
+ import { SizableText, XStack, YStack } from '@hanzo/gui';
12
+ import { BookOpen, Bot, Plug, Plus, Puzzle } from '@hanzogui/lucide-icons-2';
13
+ import { Button, Input } from '@hanzo/ui';
14
+ import { useState } from 'react';
15
+ import { useHost } from '../host.js';
16
+ import { path, TABS } from '../route.js';
17
+ import { Agents } from './agents.js';
18
+ import { Connectors } from './connectors.js';
19
+ import { Plugins } from './plugins.js';
20
+ import { Skills } from './skills.js';
21
+ import { Choice } from './ui.js';
22
+ const LABEL = {
23
+ skills: { title: 'Skills', add: 'New skill', find: 'Search skills', icon: _jsx(BookOpen, { size: 15 }) },
24
+ connectors: { title: 'Connectors', add: 'Add connector', find: 'Search connectors', icon: _jsx(Plug, { size: 15 }) },
25
+ plugins: { title: 'Plugins', add: 'Build plugin', find: 'Search plugins', icon: _jsx(Puzzle, { size: 15 }) },
26
+ agents: { title: 'Agents', add: 'New agent', find: 'Search agents', icon: _jsx(Bot, { size: 15 }) },
27
+ };
28
+ const VIEWS = [
29
+ { id: 'yours', label: 'Yours' },
30
+ { id: 'discover', label: 'Discover' },
31
+ ];
32
+ export function Customize({ tab, view: first }) {
33
+ const host = useHost();
34
+ // Until a person picks, a visitor sees what there is to add and a member what is theirs:
35
+ // sign-in lands after the first draw, so the default follows it rather than being fixed then.
36
+ const [picked, setView] = useState(first ?? null);
37
+ const view = picked ?? (host.person ? 'yours' : 'discover');
38
+ const [q, setQ] = useState('');
39
+ const [adding, setAdding] = useState(false);
40
+ const pick = (next) => {
41
+ setQ('');
42
+ setAdding(false);
43
+ host.go(path({ kind: 'customize', tab: next }));
44
+ };
45
+ const pane = { view, q, adding, onAdding: setAdding, onView: setView };
46
+ return (_jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", "$max-md": { px: '$4', py: '$4' }, children: _jsxs(YStack, { width: "100%", maxW: 1080, mx: "auto", gap: "$4", children: [_jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { render: "h1", size: "$6", color: "$ink", children: "Customize" }), _jsx(SizableText, { size: "$2", color: "$soft", children: "What the agent brings to a run: the skills it knows, the connectors it calls, the plugins it runs, and agents of your own." })] }), _jsx(XStack, { role: "tablist", "aria-label": "Customize", gap: "$4", borderBottomWidth: 1, borderColor: "$borderColor", "$max-md": { gap: '$3' }, children: TABS.map((id) => {
47
+ const on = id === tab;
48
+ return (_jsxs(XStack, { role: "tab", render: "button", "aria-selected": on, onPress: () => pick(id), items: "center", gap: "$1.5", pb: "$2", mb: -1, borderBottomWidth: 2, borderColor: on ? '$ink' : 'transparent', hoverStyle: { borderColor: on ? '$ink' : '$edge' }, children: [_jsx(XStack, { opacity: on ? 1 : 0.6, "$max-md": { display: 'none' }, children: LABEL[id].icon }), _jsx(SizableText, { size: "$3", color: on ? '$ink' : '$soft', children: LABEL[id].title })] }, id));
49
+ }) }), _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Choice, { label: "Show", value: view, options: VIEWS, onChange: setView }), _jsx(XStack, { flex: 1 }), host.person ? (_jsxs(Button, { size: "sm", onPress: () => setAdding(true), children: [_jsx(Plus, { size: 14 }), " ", LABEL[tab].add] })) : null] }), _jsx(Input, { value: q, onChangeText: setQ, placeholder: `${LABEL[tab].find}…`, "aria-label": LABEL[tab].find }), tab === 'skills' ? (_jsx(Skills, { ...pane })) : tab === 'connectors' ? (_jsx(Connectors, { ...pane })) : tab === 'plugins' ? (_jsx(Plugins, { ...pane })) : (_jsx(Agents, { ...pane }))] }) }));
50
+ }
@@ -0,0 +1,2 @@
1
+ import { type Pane } from './ui.tsx';
2
+ export declare function Plugins({ view, q, adding, onAdding, onView }: Pane): import("react").JSX.Element;
@@ -0,0 +1,93 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Plugins: TypeScript connectors the org builds for the runtime to run.
4
+ *
5
+ * Yours is what the org built, each with the source that runs. Building is the
6
+ * gate — the platform bundles and compiles the source and keeps it only if both
7
+ * succeed, and a failure says why in the bundler's words. Describe an API
8
+ * instead of writing the TypeScript and a model writes it; the result opens to
9
+ * be read. A plugin carries no credential: it names the connector whose
10
+ * credential it reads when it runs. Discover lists what this deployment mounts,
11
+ * which is read-only.
12
+ */
13
+ import { SizableText, XStack, YStack } from '@hanzo/gui';
14
+ import { Puzzle } from '@hanzogui/lucide-icons-2';
15
+ import { Button, Input, Textarea } from '@hanzo/ui';
16
+ import { Code } from '@hanzo/ui/chat';
17
+ import { useState } from 'react';
18
+ import { authored, build, mounted, remove } from '../api/plugins.js';
19
+ import { useRead } from '../data.js';
20
+ import { useHost, useTarget } from '../host.js';
21
+ import { Choice, Confirm, day, Field, Grid, Line, Mark, matches, mono, Sheet, Soft, Tile } from './ui.js';
22
+ const kb = (n) => (n < 1024 ? `${n} B` : `${(n / 1024).toFixed(1)} KB`);
23
+ export function Plugins({ view, q, adding, onAdding, onView }) {
24
+ const host = useHost();
25
+ const t = useTarget();
26
+ const signed = Boolean(host.person);
27
+ const mine = useRead(signed && view === 'yours' ? () => authored(t) : null, [], [t, signed, view]);
28
+ const mounts = useRead(signed && view === 'discover' ? () => mounted(t) : null, [], [t, signed, view]);
29
+ const [opened, setOpened] = useState(null);
30
+ const [note, setNote] = useState('');
31
+ const sheets = (_jsxs(_Fragment, { children: [adding ? (_jsx(Build, { onClose: () => onAdding(false), onBuilt: (p, said) => {
32
+ onAdding(false);
33
+ onView('yours');
34
+ mine.reload();
35
+ setNote(said);
36
+ setOpened(p);
37
+ } })) : null, opened ? (_jsx(Source, { plugin: opened, onClose: () => setOpened(null), onDeleted: () => {
38
+ setNote(`${opened.name} is deleted`);
39
+ setOpened(null);
40
+ mine.reload();
41
+ } })) : null] }));
42
+ if (!signed) {
43
+ return _jsx(Soft, { action: host.signIn ? _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: "Sign in" }) : undefined, children: view === 'discover' ? 'Sign in to see what this deployment mounts.' : 'Sign in to see your plugins.' });
44
+ }
45
+ if (view === 'discover') {
46
+ const shown = mounts.value.filter((m) => matches(q, m.name, ...m.prefixes));
47
+ return (_jsxs(YStack, { gap: "$3", children: [mounts.error && !mounts.value.length ? (_jsx(Soft, { children: mounts.error.message })) : mounts.loading && !mounts.value.length ? (_jsx(Soft, { children: "Reading what is mounted\u2026" })) : !shown.length ? (_jsx(Soft, { children: mounts.value.length ? 'Nothing mounted matches.' : 'This deployment reports nothing mounted.' })) : (_jsxs(_Fragment, { children: [_jsxs(SizableText, { size: "$1", color: "$soft", children: [shown.length, " subsystems mounted in this deployment. They are part of the platform, so there is nothing to add."] }), _jsx(Grid, { label: "Mounted subsystems", children: shown.map((m) => (_jsx(Tile, { title: m.name, detail: m.prefixes.join(' '), mark: _jsx(Mark, { name: m.name, icon: _jsx(Puzzle, { size: 15 }) }) }, m.name))) })] })), sheets] }));
48
+ }
49
+ const shown = mine.value.filter((p) => matches(q, p.name, p.provider));
50
+ return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), mine.error && !mine.value.length ? (_jsx(Soft, { children: mine.error.message })) : mine.loading && !mine.value.length ? (_jsx(Soft, { children: "Reading your plugins\u2026" })) : !mine.value.length ? (_jsx(Soft, { action: _jsx(Button, { size: "sm", variant: "outline", onPress: () => onAdding(true), children: "Build a plugin" }), children: "No plugins yet. Write a connector in TypeScript, or describe an API and have one written." })) : !shown.length ? (_jsx(Soft, { children: "No plugin matches." })) : (_jsx(Grid, { label: "Your plugins", children: shown.map((p) => (_jsx(Tile, { title: p.name, detail: p.provider ? `Reads the ${p.provider} connector’s credential when it runs.` : 'Needs no credential.', meta: day(p.built) ? `Built ${day(p.built)}` : undefined, mark: _jsx(Mark, { name: p.name }), onOpen: () => setOpened(p) }, p.id))) })), sheets] }));
51
+ }
52
+ const WAYS = [
53
+ { id: 'source', label: 'TypeScript' },
54
+ { id: 'spec', label: 'Describe an API' },
55
+ ];
56
+ /** Build a plugin from its TypeScript, or from a description of the API it calls. */
57
+ function Build({ onClose, onBuilt }) {
58
+ const t = useTarget();
59
+ const [name, setName] = useState('');
60
+ const [provider, setProvider] = useState('');
61
+ const [way, setWay] = useState('source');
62
+ // Each way keeps its own text, so switching between them loses neither.
63
+ const [source, setSource] = useState('');
64
+ const [spec, setSpec] = useState('');
65
+ const [working, setWorking] = useState(false);
66
+ const [note, setNote] = useState('');
67
+ const go = async () => {
68
+ setWorking(true);
69
+ setNote(way === 'spec' ? 'Writing and building it…' : 'Building it…');
70
+ try {
71
+ const out = await build(t, { name: name.trim(), provider, source: way === 'source' ? source : '', spec: way === 'spec' ? spec : '' });
72
+ onBuilt(out.plugin, `${out.plugin.name} built: ${kb(out.bytes)}${out.generated ? ', written from your description — read it below' : ''}.`);
73
+ }
74
+ catch (e) {
75
+ setNote(e instanceof Error ? e.message : 'The build failed');
76
+ }
77
+ finally {
78
+ setWorking(false);
79
+ }
80
+ };
81
+ return (_jsxs(Sheet, { title: "Build a plugin", open: true, onOpenChange: (o) => !o && onClose(), width: 680, children: [_jsx(Field, { label: "Name", hint: "One lowercase word: letters, digits, _ or -. The runtime loads it by this name.", children: _jsx(Input, { value: name, onChangeText: setName, placeholder: "acme", "aria-label": "Name", autoCapitalize: "none" }) }), _jsx(Field, { label: "Connector", hint: "Optional. The connector whose credential the plugin reads when it runs. A plugin never holds a key itself.", children: _jsx(Input, { value: provider, onChangeText: setProvider, placeholder: "acme", "aria-label": "Connector", autoCapitalize: "none" }) }), _jsx(Choice, { label: "Build from", value: way, options: WAYS, onChange: setWay }), _jsx(Field, { label: way === 'source' ? 'Source' : 'The API', hint: way === 'source'
82
+ ? 'It is bundled and compiled in the runtime that will run it, and kept only if both succeed.'
83
+ : 'An OpenAPI document, or prose naming the endpoints. A model writes the TypeScript; you read it before it runs.', children: _jsx(Textarea, { value: way === 'source' ? source : spec, onChangeText: way === 'source' ? setSource : setSpec, "aria-label": way === 'source' ? 'Source' : 'The API', placeholder: way === 'source' ? 'export const acme = createProvider({ … })' : 'POST /v1/things creates a thing…', rows: 14, style: mono }) }), _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 go(), children: "Build" })] })] }));
84
+ }
85
+ /** A built plugin's source, as the runtime will run it, and deleting it. */
86
+ function Source({ plugin, onClose, onDeleted }) {
87
+ const t = useTarget();
88
+ const [asking, setAsking] = useState(false);
89
+ return (_jsxs(Sheet, { title: plugin.name, open: true, onOpenChange: (o) => !o && onClose(), width: 760, children: [_jsxs(SizableText, { size: "$2", color: "$soft", children: [plugin.provider ? `Reads the ${plugin.provider} connector’s credential when it runs.` : 'Needs no credential.', day(plugin.built) ? ` Built ${day(plugin.built)}.` : ''] }), _jsx(Code, { language: "TypeScript", value: plugin.source, children: _jsx(SizableText, { size: "$1", color: "$ink", style: { ...mono, whiteSpace: 'pre' }, children: plugin.source }) }), _jsx(YStack, { items: "flex-start", children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => setAsking(true), children: "Delete plugin" }) }), _jsx(Confirm, { what: plugin.name, says: "The runtime can no longer load it.", open: asking, onOpenChange: setAsking, onYes: async () => {
90
+ await remove(t, plugin.id);
91
+ onDeleted();
92
+ } })] }));
93
+ }
@@ -0,0 +1,2 @@
1
+ import { type Pane } from './ui.tsx';
2
+ export declare function Skills({ view, q, adding, onAdding, onView }: Pane): import("react").JSX.Element;