@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,104 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Billing: the plan this organization is on and the way to change it, the cards
4
+ * it pays with, its invoices, and ending the plan. Changing plan is the Plans
5
+ * screen; this page says what is true now.
6
+ */
7
+ import { SizableText, XStack, YStack } from '@hanzo/gui';
8
+ import { CreditCard, Download, Plus, X } from '@hanzogui/lucide-icons-2';
9
+ import { Button, Dialog, DialogContent, DialogTitle } from '@hanzo/ui';
10
+ import { useState } from 'react';
11
+ import { cancel, current, detach, invoices, label, methods, money, pdf, reactivate, subscriptions, } from '../api/billing.js';
12
+ import { useRead } from '../data.js';
13
+ import { useHost, useTarget } from '../host.js';
14
+ import { path } from '../route.js';
15
+ import { AddCard } from './card.js';
16
+ import { Card, day, Group, Heading, Note, Row, Soft } from './ui.js';
17
+ export function Billing() {
18
+ const host = useHost();
19
+ const t = useTarget();
20
+ const signed = Boolean(host.person);
21
+ const subs = useRead(signed ? () => subscriptions(t) : null, [], [t, signed]);
22
+ const cards = useRead(signed ? () => methods(t) : null, [], [t, signed]);
23
+ const bills = useRead(signed ? () => invoices(t) : null, [], [t, signed]);
24
+ const [adding, setAdding] = useState(false);
25
+ const [ending, setEnding] = useState(false);
26
+ const [open, setOpen] = useState(null);
27
+ const [working, setWorking] = useState(false);
28
+ const [note, setNote] = useState('');
29
+ if (!signed) {
30
+ return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Billing" }), _jsx(Soft, { children: "Sign in to see this organization\u2019s billing." })] }));
31
+ }
32
+ const plan = current(subs.value);
33
+ const act = async (what, done, after) => {
34
+ setWorking(true);
35
+ setNote('');
36
+ try {
37
+ await what();
38
+ after();
39
+ setNote(done);
40
+ }
41
+ catch (e) {
42
+ setNote(e instanceof Error ? e.message : 'That did not work');
43
+ }
44
+ finally {
45
+ setWorking(false);
46
+ }
47
+ };
48
+ return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Billing", detail: `The plan ${host.org ?? 'this organization'} is on, the cards it pays with, and its invoices.` }), _jsxs(XStack, { items: "center", gap: "$3", px: "$4", py: "$4", borderWidth: 1, borderColor: "$borderColor", rounded: "$3", flexWrap: "wrap", children: [_jsxs(YStack, { flex: 1, minW: 200, gap: "$1", children: [_jsx(SizableText, { size: "$5", color: "$ink", children: subs.loading && !subs.value.length ? 'Reading your plan…' : plan ? `${plan.name} plan` : 'Free plan' }), _jsx(SizableText, { size: "$2", color: "$soft", children: subs.error
49
+ ? subs.error.message
50
+ : plan
51
+ ? terms(plan)
52
+ : 'No paid plan. Usage is paid from the balance.' })] }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => host.go(path({ kind: 'screen', screen: 'plans' })), children: "Adjust plan" })] }), _jsx(Group, { title: "Payment", detail: "The card a plan renews on and a top-up charges.", action: host.admin ? (_jsxs(Button, { size: "sm", variant: "outline", onPress: () => setAdding(true), children: [_jsx(Plus, { size: 14 }), " Add card"] })) : undefined, children: cards.error ? (_jsx(Soft, { children: cards.error.message })) : cards.loading && !cards.value.length ? (_jsx(Soft, { children: "Reading cards\u2026" })) : cards.value.length === 0 ? (_jsx(Card, { children: _jsx(Soft, { children: "No card on file." }) })) : (_jsx(Card, { children: cards.value.map((m, i) => (_jsx(Row, { first: i === 0, leading: _jsx(CreditCard, { size: 16 }), title: label(m), detail: [m.expires ? `Expires ${m.expires}` : '', m.default ? 'Default' : ''].filter(Boolean).join(' · ') || undefined, trailing: host.admin ? (_jsx(XStack, { render: "button", "aria-label": `Remove ${label(m)}`, p: "$1", onPress: () => void act(() => detach(t, m.id), `${label(m)} is removed`, cards.reload), children: _jsx(X, { size: 14 }) })) : undefined }, m.id))) })) }), _jsx(Group, { title: "Invoices", children: bills.error ? (_jsx(Soft, { children: bills.error.message })) : bills.loading && !bills.value.length ? (_jsx(Soft, { children: "Reading invoices\u2026" })) : bills.value.length === 0 ? (_jsx(Card, { children: _jsx(Soft, { children: "No invoices yet." }) })) : (_jsxs(Card, { children: [_jsxs(XStack, { px: "$3", py: "$2", gap: "$3", children: [_jsx(SizableText, { flex: 1, size: "$1", color: "$soft", children: "Date" }), _jsx(SizableText, { width: 90, size: "$1", color: "$soft", style: { textAlign: 'right' }, children: "Total" }), _jsx(SizableText, { width: 70, size: "$1", color: "$soft", "$max-sm": { display: 'none' }, children: "Status" }), _jsx(YStack, { width: 64 })] }), bills.value.map((b) => (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { flex: 1, size: "$2", color: "$ink", numberOfLines: 1, children: day(b.date) || b.number || b.id }), _jsx(SizableText, { width: 90, size: "$2", color: "$ink", style: { textAlign: 'right' }, children: money(b.total, b.currency) }), _jsx(SizableText, { width: 70, size: "$1", color: "$soft", "$max-sm": { display: 'none' }, children: word(b.status) }), _jsx(XStack, { width: 64, justify: "flex-end", children: _jsx(Button, { size: "sm", variant: "ghost", "aria-label": `View invoice ${b.number || day(b.date)}`, onPress: () => setOpen(b), children: "View" }) })] }, b.id)))] })) }), plan ? (_jsx(Group, { title: "Cancellation", children: _jsx(Card, { children: _jsx(Row, { first: true, title: plan.ending ? `Your plan ends on ${day(plan.ends)}` : 'Cancel plan', detail: plan.ending ? 'Keep it, and it renews as before.' : 'It runs to the end of the period you paid for, then ends.', trailing: !host.admin ? undefined : plan.ending ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void act(() => reactivate(t, plan.id), 'Your plan renews as before', subs.reload), children: "Keep plan" })) : (_jsx(Button, { size: "sm", variant: "destructive", disabled: working, onPress: () => setEnding(true), children: "Cancel" })) }) }) })) : null, _jsx(Note, { children: note }), _jsx(AddCard, { open: adding, onOpenChange: setAdding, onSaved: (m) => {
53
+ cards.reload();
54
+ setNote(`${label(m)} is saved`);
55
+ } }), _jsx(Ending, { plan: plan, open: ending, working: working, onOpenChange: setEnding, onConfirm: () => {
56
+ if (!plan)
57
+ return;
58
+ void act(() => cancel(t, plan.id), `Your plan ends on ${day(plan.ends)}`, () => {
59
+ setEnding(false);
60
+ subs.reload();
61
+ });
62
+ } }), _jsx(Bill, { invoice: open, onClose: () => setOpen(null) })] }));
63
+ }
64
+ /** What the plan costs, and when it renews or ends. */
65
+ function terms(p) {
66
+ const cost = `${money(p.price * p.seats, p.currency)} a ${p.interval}${p.seats > 1 ? ` for ${p.seats} seats` : ''}.`;
67
+ return p.ends ? `${cost} ${p.ending ? 'Ends' : 'Renews'} on ${day(p.ends)}.` : cost;
68
+ }
69
+ const word = (s) => (s ? s.charAt(0).toUpperCase() + s.slice(1).replace(/_/g, ' ') : '');
70
+ function Ending({ plan, open, working, onOpenChange, onConfirm, }) {
71
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: 420, showCloseButton: false, children: [_jsx(DialogTitle, { children: "Cancel your plan?" }), _jsxs(YStack, { gap: "$3", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: plan
72
+ ? `${plan.name} stays on until ${day(plan.ends) || 'the end of this period'}, and nothing is charged after that. You can keep it any time before then.`
73
+ : '' }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => onOpenChange(false), children: "Keep plan" }), _jsx(Button, { size: "sm", variant: "destructive", disabled: working, onPress: onConfirm, children: "Cancel plan" })] })] })] }) }));
74
+ }
75
+ /** One invoice, read in the page, with its PDF to keep. */
76
+ function Bill({ invoice, onClose }) {
77
+ const t = useTarget();
78
+ const [note, setNote] = useState('');
79
+ const [working, setWorking] = useState(false);
80
+ const b = invoice;
81
+ const keep = async () => {
82
+ if (!b)
83
+ return;
84
+ setWorking(true);
85
+ setNote('');
86
+ try {
87
+ const blob = await pdf(t, b.id);
88
+ const url = URL.createObjectURL(blob);
89
+ const a = document.createElement('a');
90
+ a.href = url;
91
+ a.download = `${b.number || b.id}.pdf`;
92
+ a.click();
93
+ window.setTimeout(() => URL.revokeObjectURL(url), 1000);
94
+ }
95
+ catch (e) {
96
+ setNote(e instanceof Error ? e.message : 'The PDF did not download');
97
+ }
98
+ finally {
99
+ setWorking(false);
100
+ }
101
+ };
102
+ const line = (what, cents, strong = false) => (_jsxs(XStack, { justify: "space-between", gap: "$3", children: [_jsx(SizableText, { size: "$2", color: strong ? '$ink' : '$soft', children: what }), _jsx(SizableText, { size: "$2", color: strong ? '$ink' : '$soft', children: b ? money(cents, b.currency) : '' })] }, what));
103
+ return (_jsx(Dialog, { open: Boolean(b), onOpenChange: (o) => (o ? undefined : onClose()), children: _jsxs(DialogContent, { maxW: 480, showCloseButton: false, children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [_jsx(DialogTitle, { children: b?.number ? `Invoice ${b.number}` : 'Invoice' }), _jsx(XStack, { render: "button", "aria-label": "Close", p: "$1", onPress: onClose, children: _jsx(X, { size: 16 }) })] }), b ? (_jsxs(YStack, { gap: "$3", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: [day(b.date), word(b.status), b.start && b.end ? `for ${day(b.start)} – ${day(b.end)}` : ''].filter(Boolean).join(' · ') }), _jsx(YStack, { gap: "$1.5", borderTopWidth: 1, borderColor: "$borderColor", pt: "$3", children: b.lines.length ? b.lines.map((l, i) => line(l.description || `Line ${i + 1}`, l.amount)) : line('Subtotal', b.subtotal) }), _jsxs(YStack, { gap: "$1.5", borderTopWidth: 1, borderColor: "$borderColor", pt: "$3", children: [b.discount ? line('Discount', -b.discount) : null, b.tax ? line('Tax', b.tax) : null, line('Total', b.total, true), b.credit ? line('Credit applied', -b.credit) : null, line('Paid', b.paid), b.due ? line('Due', b.due, true) : null] }), _jsx(Note, { children: note }), _jsx(XStack, { justify: "flex-end", children: _jsxs(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void keep(), children: [_jsx(Download, { size: 14 }), " Download PDF"] }) })] })) : null] }) }));
104
+ }
@@ -0,0 +1 @@
1
+ export declare function Capabilities(): import("react").JSX.Element;
@@ -0,0 +1,55 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Capabilities: what the agent may use, by where it comes from — connector
4
+ * actions, functions, services, agents, skills and the organization's own MCP
5
+ * servers. A tool that is off is still listed to the agent's discovery but is
6
+ * refused when called. Switching a kind on turns on every tool of that kind;
7
+ * off turns them all off. It is the organization's setting, not only yours.
8
+ */
9
+ import { SizableText, YStack } from '@hanzo/gui';
10
+ import { Switch } from '@hanzo/ui';
11
+ import { useState } from 'react';
12
+ import { activate, SOURCES, tools } from '../api/capabilities.js';
13
+ import { useRead } from '../data.js';
14
+ import { useHost, useTarget } from '../host.js';
15
+ import { Card, Group, Heading, Note, Row, Soft } from './ui.js';
16
+ const KINDS = {
17
+ connector: { label: 'Connector actions', about: 'Actions in the apps your organization connected' },
18
+ function: { label: 'Functions', about: 'Functions your organization wrote' },
19
+ 'zap-service': { label: 'Services', about: 'Routes of the services your organization runs' },
20
+ agent: { label: 'Agents', about: 'Your organization’s agents, called as tools' },
21
+ skill: { label: 'Skills', about: 'Skills attached to agents' },
22
+ mcp: { label: 'MCP servers', about: 'Tools on the MCP servers your organization added' },
23
+ };
24
+ export function Capabilities() {
25
+ const host = useHost();
26
+ const t = useTarget();
27
+ const signed = Boolean(host.person);
28
+ const list = useRead(signed ? () => tools(t) : null, [], [t, signed]);
29
+ const [busy, setBusy] = useState('');
30
+ const [note, setNote] = useState('');
31
+ if (!signed) {
32
+ return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Capabilities" }), _jsx(Soft, { children: "Sign in to see what the agent may use." })] }));
33
+ }
34
+ const kinds = SOURCES.map((s) => ({ source: s, all: list.value.filter((x) => x.source === s) })).filter((k) => k.all.length);
35
+ const flip = async (source, all, on) => {
36
+ setBusy(source);
37
+ setNote('');
38
+ try {
39
+ await activate(t, all.map((x) => x.name), on);
40
+ list.reload();
41
+ }
42
+ catch (e) {
43
+ setNote(e instanceof Error ? e.message : 'That was not saved');
44
+ }
45
+ finally {
46
+ setBusy('');
47
+ }
48
+ };
49
+ return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Capabilities", detail: `What the agent may use in ${host.org ?? 'this organization'}. A tool that is off is refused when the agent calls it.` }), _jsx(Group, { title: "Tools", detail: host.admin
50
+ ? 'Switching a kind on or off applies to every tool of that kind, for everyone in the organization.'
51
+ : 'An org admin switches these, for everyone in the organization.', children: list.error ? (_jsx(Soft, { children: list.error.message })) : list.loading && !list.value.length ? (_jsx(Soft, { children: "Reading tools\u2026" })) : !kinds.length ? (_jsx(Soft, { children: "No tools are available to this organization yet." })) : (_jsx(Card, { children: kinds.map(({ source, all }, i) => {
52
+ const on = all.filter((x) => x.activated).length;
53
+ return (_jsx(Row, { first: i === 0, title: KINDS[source].label, detail: `${KINDS[source].about}. ${on === all.length ? `All ${all.length} on` : on ? `${on} of ${all.length} on` : `${all.length} available, all off`}.`, trailing: host.admin ? (_jsx(Switch, { "aria-label": KINDS[source].label, checked: on === all.length, disabled: busy !== '', onCheckedChange: (v) => void flip(source, all, v) })) : (_jsx(SizableText, { size: "$1", color: "$soft", children: on === all.length ? 'On' : on ? 'Partly on' : 'Off' })) }, source));
54
+ }) })) }), _jsx(Note, { children: note })] }));
55
+ }
@@ -0,0 +1,31 @@
1
+ import { type Method } from '../api/billing.ts';
2
+ /** The part of Square's Web Payments SDK this uses. */
3
+ interface Field {
4
+ attach(el: HTMLElement): Promise<void>;
5
+ tokenize(): Promise<{
6
+ status: string;
7
+ token?: string;
8
+ errors?: {
9
+ message?: string;
10
+ }[];
11
+ }>;
12
+ destroy(): Promise<void>;
13
+ }
14
+ interface Sdk {
15
+ payments(application: string, location: string): {
16
+ card(options?: object): Promise<Field>;
17
+ };
18
+ }
19
+ declare global {
20
+ interface Window {
21
+ Square?: Sdk;
22
+ }
23
+ }
24
+ /** Anything but production loads the sandbox SDK, so a misread setting never arms real charges. */
25
+ export declare const sdk: (environment: string) => string;
26
+ export declare function AddCard({ open, onOpenChange, onSaved }: {
27
+ open: boolean;
28
+ onOpenChange: (o: boolean) => void;
29
+ onSaved: (m: Method) => void;
30
+ }): import("react").JSX.Element;
31
+ export {};
@@ -0,0 +1,105 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Adding a card. The card number is typed into the payment processor's own
4
+ * field — its iframe, served from its own host — and never reaches this page or
5
+ * the platform: the field hands back a single-use token, and the platform vaults
6
+ * the card from that. Which processor, and its sandbox or live account, is the
7
+ * platform's answer at /v1/billing/settings, so the card is saved to the account
8
+ * it will be charged on.
9
+ */
10
+ import { SizableText, XStack, YStack } from '@hanzo/gui';
11
+ import { X } from '@hanzogui/lucide-icons-2';
12
+ import { Button, Dialog, DialogContent, DialogTitle } from '@hanzo/ui';
13
+ import { useEffect, useRef, useState } from 'react';
14
+ import { processor, save } from '../api/billing.js';
15
+ import { useRead } from '../data.js';
16
+ import { useTarget } from '../host.js';
17
+ /** Anything but production loads the sandbox SDK, so a misread setting never arms real charges. */
18
+ export const sdk = (environment) => environment === 'production' ? 'https://web.squarecdn.com/v1/square.js' : 'https://sandbox.web.squarecdn.com/v1/square.js';
19
+ const loads = new Map();
20
+ function load(environment) {
21
+ if (window.Square)
22
+ return Promise.resolve(window.Square);
23
+ const src = sdk(environment);
24
+ const was = loads.get(src);
25
+ if (was)
26
+ return was;
27
+ const p = new Promise((resolve, reject) => {
28
+ const s = document.createElement('script');
29
+ s.src = src;
30
+ s.async = true;
31
+ s.onload = () => (window.Square ? resolve(window.Square) : reject(new Error('The card form did not load')));
32
+ s.onerror = () => {
33
+ loads.delete(src);
34
+ reject(new Error('The card form did not load'));
35
+ };
36
+ document.head.appendChild(s);
37
+ });
38
+ loads.set(src, p);
39
+ return p;
40
+ }
41
+ const ready = (p) => Boolean(p && p.provider === 'square' && p.application && p.location);
42
+ export function AddCard({ open, onOpenChange, onSaved }) {
43
+ const t = useTarget();
44
+ const cfg = useRead(open ? () => processor(t) : null, null, [t, open]);
45
+ const [box, setBox] = useState(null);
46
+ const field = useRef(null);
47
+ const [mounted, setMounted] = useState(false);
48
+ const [working, setWorking] = useState(false);
49
+ const [note, setNote] = useState('');
50
+ const p = cfg.value;
51
+ useEffect(() => {
52
+ if (!open || !box || !ready(p))
53
+ return;
54
+ let gone = false;
55
+ setMounted(false);
56
+ setNote('');
57
+ load(p.environment)
58
+ .then((square) => square.payments(p.application, p.location).card())
59
+ .then(async (f) => {
60
+ if (gone) {
61
+ void f.destroy().catch(() => { });
62
+ return;
63
+ }
64
+ await f.attach(box);
65
+ field.current = f;
66
+ setMounted(true);
67
+ })
68
+ .catch((e) => {
69
+ if (!gone)
70
+ setNote(e instanceof Error ? e.message : 'The card form did not load');
71
+ });
72
+ return () => {
73
+ gone = true;
74
+ const f = field.current;
75
+ field.current = null;
76
+ if (f)
77
+ void f.destroy().catch(() => { });
78
+ };
79
+ }, [open, box, p]);
80
+ const add = async () => {
81
+ const f = field.current;
82
+ if (!f)
83
+ return;
84
+ setWorking(true);
85
+ setNote('');
86
+ try {
87
+ const r = await f.tokenize();
88
+ if (r.status !== 'OK' || !r.token)
89
+ throw new Error(r.errors?.map((e) => e.message).filter(Boolean).join('; ') || 'The card details are incomplete');
90
+ const m = await save(t, r.token);
91
+ onSaved(m);
92
+ onOpenChange(false);
93
+ }
94
+ catch (e) {
95
+ setNote(e instanceof Error ? e.message : 'The card was not saved');
96
+ }
97
+ finally {
98
+ setWorking(false);
99
+ }
100
+ };
101
+ const why = cfg.error ? cfg.error.message : !cfg.loading && !ready(p) ? 'This deployment takes no cards yet.' : '';
102
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: 440, showCloseButton: false, children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [_jsx(DialogTitle, { children: "Add a card" }), _jsx(XStack, { render: "button", "aria-label": "Close", p: "$1", onPress: () => onOpenChange(false), children: _jsx(X, { size: 16 }) })] }), _jsxs(YStack, { gap: "$3", children: [why ? (_jsx(SizableText, { size: "$2", color: "$soft", children: why })) : (_jsxs(_Fragment, { children: [_jsxs(YStack, { minH: 96, "aria-label": "Card details", children: [_jsx("div", { ref: setBox, style: { minHeight: 96 } }), !mounted && !note ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "Loading the card form\u2026" })) : null] }), _jsx(SizableText, { size: "$1", color: "$soft", children: p && p.environment !== 'production'
103
+ ? 'Sandbox: use a test card. Nothing is charged.'
104
+ : 'The card number goes to the payment processor, never to Hanzo.' })] })), note ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note })) : null, _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => onOpenChange(false), children: "Cancel" }), _jsx(Button, { size: "sm", disabled: !mounted || working, onPress: () => void add(), children: "Save card" })] })] })] }) }));
105
+ }
@@ -0,0 +1 @@
1
+ export declare function Code(): import("react").JSX.Element;
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Code: where and how a new run starts — the model, the effort, the mode and
4
+ * the place — for anyone whose New has no choice of its own kept yet. Saved to
5
+ * the person's own settings. Beside them, the codebases' environments and the
6
+ * organization's machines, each on its own page.
7
+ */
8
+ import { YStack } from '@hanzo/gui';
9
+ import { ChevronRight } from '@hanzogui/lucide-icons-2';
10
+ import { ChipSelect } from '@hanzo/ui/product';
11
+ import { useState } from 'react';
12
+ import { unhonoured } from '../api/coding.js';
13
+ import { ENSO, models } from '../api/models.js';
14
+ import { SANDBOX } from '../api/places.js';
15
+ import { usePlaces, useRead } from '../data.js';
16
+ import { useHost, useTarget } from '../host.js';
17
+ import { EFFORTS, MODES } from '../landing.js';
18
+ import { usePrefs } from '../prefs.js';
19
+ import { path } from '../route.js';
20
+ import { Card, Group, Heading, Note, Row, Soft } from './ui.js';
21
+ export function Code() {
22
+ const host = useHost();
23
+ const t = useTarget();
24
+ const signed = Boolean(host.person);
25
+ const { prefs, save } = usePrefs();
26
+ const catalog = useRead(signed ? () => models(t) : null, [], [t, signed]);
27
+ const places = usePlaces(t, signed);
28
+ const [note, setNote] = useState('');
29
+ if (!signed) {
30
+ return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Code" }), _jsx(Soft, { children: "Sign in to set how your runs start." })] }));
31
+ }
32
+ const now = { model: ENSO, effort: 'medium', mode: 'build', place: '', ...prefs.code };
33
+ const keep = (change) => {
34
+ setNote('');
35
+ save({ code: { ...prefs.code, ...change } }).catch((e) => setNote(e instanceof Error ? e.message : 'That was not saved'));
36
+ };
37
+ const where = (places.value.length ? places.value : [SANDBOX]).map((p) => ({
38
+ id: p.id || 'sandbox',
39
+ label: p.id ? p.label : 'Default',
40
+ // A machine offline now can still be the default; its status shows beside it.
41
+ hint: p.id ? [p.status, p.capacity].filter(Boolean).join(' · ') : 'The platform’s own sandbox',
42
+ place: p.id,
43
+ }));
44
+ const place = where.find((w) => w.place === now.place) ?? { id: now.place, label: now.place || 'Default', place: now.place };
45
+ const model = catalog.value.find((m) => m.id === now.model) ?? { id: now.model, label: now.model === ENSO ? 'Enso' : now.model };
46
+ const go = (section) => host.go(path({ kind: 'settings', section }));
47
+ return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Code", detail: "How a new run starts. New begins from these until you choose otherwise there." }), _jsxs(Group, { title: "Defaults", children: [_jsxs(Card, { children: [_jsx(Row, { first: true, title: "Model", detail: "Enso picks one for each step; any other model runs them all.", trailing: _jsx(ChipSelect, { name: "Default model", label: model.label, chosen: model, items: catalog.value, onChange: (m) => keep({ model: m.id }), placeholder: "Search models\u2026", placement: "bottom-end", loading: catalog.loading, error: catalog.error?.message ?? null }) }), _jsx(Row, { title: "Effort", detail: "How long the agent thinks before it acts.", trailing: _jsx(ChipSelect, { name: "Default effort", label: EFFORTS.find((e) => e.id === now.effort).label, chosen: EFFORTS.find((e) => e.id === now.effort) ?? null, items: EFFORTS, onChange: (e) => keep({ effort: e.id }), placement: "bottom-end", width: 180 }) }), _jsx(Row, { title: "Mode", detail: MODES.find((m) => m.id === now.mode).hint, trailing: _jsx(ChipSelect, { name: "Default mode", label: MODES.find((m) => m.id === now.mode).label, chosen: MODES.find((m) => m.id === now.mode) ?? null, items: MODES, onChange: (m) => keep({ mode: m.id }), placement: "bottom-end", width: 260 }) }), _jsx(Row, { title: "Where it runs", detail: "The Hanzo sandbox, or one of your organization\u2019s machines.", trailing: _jsx(ChipSelect, { name: "Default place", label: place.label, chosen: place, items: where, onChange: (w) => keep({ place: w.place }), placeholder: "Search machines\u2026", placement: "bottom-end", loading: places.loading, error: places.error?.message ?? null }) })] }), _jsx(Note, { children: unhonoured(now.mode, now.place) })] }), _jsx(Group, { title: "Workspace", children: _jsxs(Card, { children: [_jsx(Row, { first: true, title: "Environments", detail: "What a run installs and starts in each codebase, and the secrets it exports.", trailing: _jsx(Link, { label: "Environments", onPress: () => go('environments') }) }), _jsx(Row, { title: "Machines", detail: "Your organization\u2019s own computers that take runs.", trailing: _jsx(Link, { label: "Machines", onPress: () => go('machines') }) })] }) }), _jsx(Note, { children: note })] }));
48
+ }
49
+ function Link({ label, onPress }) {
50
+ return (_jsx(YStack, { render: "button", "aria-label": `Open ${label}`, p: "$1", rounded: "$2", hoverStyle: { bg: '$hover' }, onPress: onPress, children: _jsx(ChevronRight, { size: 16 }) }));
51
+ }
@@ -0,0 +1 @@
1
+ export declare function Environments(): import("react").JSX.Element;
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Environments: every codebase in the organization that has one — what a sandbox
4
+ * run does to its checkout before the agent starts, and the secrets it exports.
5
+ * One opens in the same editor a run shows beside it. A member reads; an org
6
+ * admin saves, sets secrets and forgets an environment, which is the platform's
7
+ * rule and not a choice made here.
8
+ */
9
+ import { SizableText, XStack, YStack } from '@hanzo/gui';
10
+ import { ChevronLeft, ChevronRight } from '@hanzogui/lucide-icons-2';
11
+ import { Button } from '@hanzo/ui';
12
+ import { useState } from 'react';
13
+ import { environments, forget } from '../api/environment.js';
14
+ import { Confirm } from '../ask.js';
15
+ import { useRead } from '../data.js';
16
+ import { Environment } from '../environment.js';
17
+ import { useHost, useTarget } from '../host.js';
18
+ import { Card, Heading, Note, Row, Soft } from './ui.js';
19
+ const STATE = { none: 'Not set up', proposed: 'Proposed, waiting for review', ready: 'Ready' };
20
+ /** The line under a codebase: where it stands, its secrets, when it was saved. */
21
+ function about(e) {
22
+ const parts = [STATE[e.state]];
23
+ if (e.secrets.length)
24
+ parts.push(e.secrets.length === 1 ? '1 secret' : `${e.secrets.length} secrets`);
25
+ const when = e.updated ? new Date(e.updated) : null;
26
+ if (when && !Number.isNaN(when.getTime()))
27
+ parts.push(`Updated ${when.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}`);
28
+ return parts.join(' · ');
29
+ }
30
+ export function Environments() {
31
+ const host = useHost();
32
+ const t = useTarget();
33
+ const signed = Boolean(host.person);
34
+ const list = useRead(signed ? () => environments(t) : null, [], [t, signed]);
35
+ const [open, setOpen] = useState('');
36
+ const [forgetting, setForgetting] = useState(false);
37
+ const [note, setNote] = useState('');
38
+ if (!signed) {
39
+ return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Environments" }), _jsx(Soft, { children: "Sign in to see your organization\u2019s environments." })] }));
40
+ }
41
+ if (open) {
42
+ return (_jsxs(YStack, { gap: "$5", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsxs(Button, { size: "sm", variant: "ghost", onPress: () => setOpen(''), children: [_jsx(ChevronLeft, { size: 14 }), " All environments"] }), _jsx(XStack, { flex: 1 }), host.admin ? (_jsx(Button, { size: "sm", variant: "outline", onPress: () => setForgetting(true), children: "Forget" })) : null] }), _jsx(Environment, { repo: open, busy: false }), _jsx(Confirm, { open: forgetting, onOpenChange: setForgetting, title: `Forget ${open}’s environment?`, says: "Later runs on this codebase start bare: its scripts are dropped and every secret it holds is removed from KMS. This cannot be undone.", act: "Forget", run: async () => {
43
+ await forget(t, open);
44
+ setNote(`${open}’s environment is forgotten`);
45
+ setOpen('');
46
+ list.reload();
47
+ } })] }));
48
+ }
49
+ return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Environments", detail: "What a sandbox run does to a codebase before its agent starts: the install script, the start command, and the secrets it exports." }), list.error ? (_jsx(Soft, { children: list.error.message })) : list.loading && list.value.length === 0 ? (_jsx(Soft, { children: "Reading environments\u2026" })) : list.value.length === 0 ? (_jsx(Soft, { children: "No codebase has an environment yet. Set one up from New, or from a run\u2019s Environment tab." })) : (_jsx(Card, { children: list.value.map((e, i) => (_jsx(YStack, { render: "button", "aria-label": `Open ${e.repo}`, onPress: () => {
50
+ setNote('');
51
+ setOpen(e.repo);
52
+ }, cursor: "pointer", hoverStyle: { bg: '$hover' }, style: { textAlign: 'left' }, children: _jsx(Row, { first: i === 0, title: e.repo, detail: about(e), trailing: _jsx(ChevronRight, { size: 16, opacity: 0.6 }) }) }, e.repo))) })), !host.admin ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "An org admin saves an environment and sets its secrets." })) : null, _jsx(Note, { children: note })] }));
53
+ }
@@ -0,0 +1 @@
1
+ export declare function General(): import("react").JSX.Element;
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * General: how the builder looks and listens — the theme, the text size, the
4
+ * motion, and the language dictation hears. Each choice is saved to the
5
+ * person's own settings as it is made, so it follows them to every device.
6
+ */
7
+ import { YStack } from '@hanzo/gui';
8
+ import { ChipSelect } from '@hanzo/ui/product';
9
+ import { useState } from 'react';
10
+ import { useHost } from '../host.js';
11
+ import { usePrefs } from '../prefs.js';
12
+ import { LANGUAGES, spoken } from '../voice.js';
13
+ import { Card, Group, Heading, Note, Row, Soft } from './ui.js';
14
+ const THEMES = [
15
+ { id: 'system', label: 'System' },
16
+ { id: 'light', label: 'Light' },
17
+ { id: 'dark', label: 'Dark' },
18
+ ];
19
+ const TEXTS = [
20
+ { id: 'small', label: 'Small' },
21
+ { id: 'medium', label: 'Medium' },
22
+ { id: 'large', label: 'Large' },
23
+ ];
24
+ const MOTIONS = [
25
+ { id: 'system', label: 'System', hint: 'As this device is set' },
26
+ { id: 'reduced', label: 'Reduced', hint: 'No animation anywhere' },
27
+ ];
28
+ export function General() {
29
+ const host = useHost();
30
+ const { prefs, save } = usePrefs();
31
+ const [note, setNote] = useState('');
32
+ if (!host.person) {
33
+ return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "General" }), _jsx(Soft, { children: "Sign in to see your settings." })] }));
34
+ }
35
+ const keep = (patch) => {
36
+ setNote('');
37
+ save(patch).catch((e) => setNote(e instanceof Error ? e.message : 'That was not saved'));
38
+ };
39
+ const theme = prefs.theme ?? host.theme ?? 'dark';
40
+ const text = prefs.text ?? 'medium';
41
+ const motion = prefs.motion ?? 'system';
42
+ const language = LANGUAGES.find((l) => l.id === prefs.language) ?? LANGUAGES.find((l) => l.id === spoken());
43
+ return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "General", detail: "How Hanzo Build looks and listens. Saved to your account, so it follows you." }), _jsx(Group, { title: "Appearance", children: _jsxs(Card, { children: [host.chooseTheme ? (_jsx(Row, { first: true, title: "Theme", detail: "System follows this device\u2019s light or dark setting.", trailing: _jsx(ChipSelect, { name: "Theme", label: THEMES.find((x) => x.id === theme).label, chosen: THEMES.find((x) => x.id === theme) ?? null, items: THEMES, onChange: (x) => keep({ theme: x.id }), placement: "bottom-end", width: 200 }) })) : null, _jsx(Row, { first: !host.chooseTheme, title: "Text size", detail: "The transcript, the composer and every screen.", trailing: _jsx(ChipSelect, { name: "Text size", label: TEXTS.find((x) => x.id === text).label, chosen: TEXTS.find((x) => x.id === text) ?? null, items: TEXTS, onChange: (x) => keep({ text: x.id === 'medium' ? null : x.id }), placement: "bottom-end", width: 200 }) }), _jsx(Row, { title: "Motion", detail: "Reduced stops animations and transitions on this page.", trailing: _jsx(ChipSelect, { name: "Motion", label: MOTIONS.find((x) => x.id === motion).label, chosen: MOTIONS.find((x) => x.id === motion) ?? null, items: MOTIONS, onChange: (x) => keep({ motion: x.id === 'system' ? null : x.id }), placement: "bottom-end", width: 220 }) })] }) }), _jsx(Group, { title: "Voice", children: _jsx(Card, { children: _jsx(Row, { first: true, title: "Dictation language", detail: "What the microphone in the composer listens for.", trailing: _jsx(ChipSelect, { name: "Dictation language", label: language.label, chosen: language, items: LANGUAGES, onChange: (l) => keep({ language: l.id }), placeholder: "Search languages\u2026", placement: "bottom-end", width: 220 }) }) }) }), _jsx(Note, { children: note })] }));
44
+ }
@@ -0,0 +1,4 @@
1
+ import { type Section } from '../route.ts';
2
+ export declare function Settings({ section }: {
3
+ section: Section;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Settings: every setting the builder has, on this page — the account, the
4
+ * workspace's environments, machines and keys, what the agent brings to a run,
5
+ * and the organization's integrations, members, usage and notifications. A
6
+ * section is its own address, so it can be linked to and comes back on reload.
7
+ */
8
+ import { SizableText, XStack, YStack } from '@hanzo/gui';
9
+ import { useHost } from '../host.js';
10
+ import { path } from '../route.js';
11
+ import { ENTRIES, GROUPS } from './sections.js';
12
+ export function Settings({ section }) {
13
+ const host = useHost();
14
+ const entry = ENTRIES.find((e) => e.id === section) ?? ENTRIES[0];
15
+ const go = (id) => host.go(path({ kind: 'settings', section: id }));
16
+ return (_jsxs(XStack, { flex: 1, minH: 0, minW: 0, children: [_jsxs(YStack, { role: "navigation", "aria-label": "Settings", width: 220, shrink: 0, borderRightWidth: 1, borderColor: "$borderColor", px: "$2", py: "$4", gap: "$4", overflow: "scroll", "$max-md": { display: 'none' }, children: [_jsx(SizableText, { size: "$4", color: "$ink", px: "$2", children: "Settings" }), GROUPS.map((g) => {
17
+ const list = ENTRIES.filter((e) => e.group === g);
18
+ if (!list.length)
19
+ return null;
20
+ return (_jsxs(YStack, { gap: "$0.5", children: [_jsx(SizableText, { size: "$1", color: "$soft", px: "$2", pb: "$1", children: g }), list.map((e) => (_jsx(XStack, { render: "button", "aria-label": e.label, "aria-current": e.id === entry.id ? 'page' : undefined, onPress: () => go(e.id), px: "$2", py: "$1.5", rounded: "$2", bg: e.id === entry.id ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, children: _jsx(SizableText, { size: "$2", color: e.id === entry.id ? '$ink' : '$soft', children: e.label }) }, e.id)))] }, g));
21
+ })] }), _jsxs(YStack, { flex: 1, minW: 0, minH: 0, overflow: "scroll", children: [_jsx(XStack, { flexWrap: "wrap", gap: "$1.5", px: "$4", pt: "$3", $md: { display: 'none' }, children: ENTRIES.map((e) => (_jsx(XStack, { render: "button", "aria-label": e.label, onPress: () => go(e.id), px: "$2.5", py: "$1", rounded: "$10", borderWidth: 1, borderColor: e.id === entry.id ? '$ink' : '$borderColor', children: _jsx(SizableText, { size: "$1", color: e.id === entry.id ? '$ink' : '$soft', children: e.label }) }, e.id))) }), _jsx(YStack, { width: "100%", maxW: 760, self: "center", px: "$5", py: "$6", children: entry.body() })] })] }));
22
+ }
@@ -0,0 +1 @@
1
+ export declare function Integrations(): import("react").JSX.Element;
@@ -0,0 +1,81 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Integrations: your own GitHub connection, the GitHub accounts the
4
+ * organization has installed the platform's App on, and its Slack workspace.
5
+ *
6
+ * Connecting leaves this page once, for GitHub's or Slack's own consent screen.
7
+ * Anyone connects their own GitHub; installing the App and connecting Slack are
8
+ * an org admin's, which is the platform's rule.
9
+ */
10
+ import { SizableText, XStack, YStack } from '@hanzo/gui';
11
+ import { Github, Hash, Lock, Slack } from '@hanzogui/lucide-icons-2';
12
+ import { Button } from '@hanzo/ui';
13
+ import { useState } from 'react';
14
+ import { connect, connection, disconnect as unlinkGithub, installations } from '../api/github.js';
15
+ import { authorize, channels, disconnect, read } from '../api/provider.js';
16
+ import { useRead } from '../data.js';
17
+ import { useHost, useTarget } from '../host.js';
18
+ import { Card, day, Group, Heading, Note, Row, Soft } from './ui.js';
19
+ export function Integrations() {
20
+ const host = useHost();
21
+ const t = useTarget();
22
+ const signed = Boolean(host.person);
23
+ const mine = useRead(signed ? () => connection(t) : null, null, [t, signed]);
24
+ const installed = useRead(signed ? () => installations(t) : null, [], [t, signed]);
25
+ const slack = useRead(signed ? () => read(t, 'slack') : null, null, [t, signed]);
26
+ const joined = slack.value?.connected ?? false;
27
+ const rooms = useRead(signed && joined ? () => channels(t) : null, { channels: [], next: '' }, [t, signed, joined]);
28
+ const [working, setWorking] = useState(false);
29
+ const [note, setNote] = useState('');
30
+ if (!signed) {
31
+ return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Integrations" }), _jsx(Soft, { children: "Sign in to see this organization\u2019s integrations." })] }));
32
+ }
33
+ const act = async (what, done, after) => {
34
+ setWorking(true);
35
+ setNote('');
36
+ try {
37
+ await what();
38
+ after();
39
+ setNote(done);
40
+ }
41
+ catch (e) {
42
+ setNote(e instanceof Error ? e.message : 'That did not work');
43
+ }
44
+ finally {
45
+ setWorking(false);
46
+ }
47
+ };
48
+ /** Off to the provider's consent page; it is the one hop away from this page. */
49
+ const away = async (where) => {
50
+ setWorking(true);
51
+ setNote('');
52
+ try {
53
+ host.open(await where());
54
+ }
55
+ catch (e) {
56
+ setNote(e instanceof Error ? e.message : 'The connection did not start');
57
+ setWorking(false);
58
+ }
59
+ };
60
+ const me = mine.value;
61
+ const s = slack.value;
62
+ return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Integrations", detail: `What ${host.org ?? 'this organization'} and you have connected.` }), _jsxs(Group, { title: "GitHub", detail: "Your connection brings your repositories; the App brings an account\u2019s to the organization.", children: [_jsxs(Card, { children: [_jsx(Row, { first: true, leading: _jsx(Github, { size: 16 }), title: "Your GitHub", detail: mine.error
63
+ ? mine.error.message
64
+ : !me
65
+ ? 'Reading…'
66
+ : !me.configured
67
+ ? 'This deployment cannot connect GitHub yet.'
68
+ : me.connected
69
+ ? `Connected as @${me.login}`
70
+ : 'Not connected', trailing: me?.configured ? (me.connected ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void act(() => unlinkGithub(t), 'Your GitHub is disconnected', mine.reload), children: "Disconnect" })) : (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void away(() => connect(t)), children: "Connect" }))) : null }), installed.error ? (_jsx(Row, { title: "App installations", detail: installed.error.message })) : (installed.value.map((i) => (_jsx(Row, { leading: _jsx(Github, { size: 16 }), title: i.login, detail: [i.type, i.grant === 'all' ? 'every repository' : i.grant === 'selected' ? 'selected repositories' : ''].filter(Boolean).join(' · '), trailing: _jsx(SizableText, { size: "$1", color: i.connected ? '$ink' : '$soft', children: i.connected ? 'Installed' : 'Removed on GitHub' }) }, i.login)))), !installed.error && !installed.loading && installed.value.length === 0 ? (_jsx(Row, { title: "No GitHub accounts installed", detail: "Install the App on a GitHub account to bring its repositories here." })) : null] }), host.admin ? (_jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void away(() => authorize(t, 'github')), children: "Install on a GitHub account" }) })) : (_jsx(SizableText, { size: "$1", color: "$soft", children: "An org admin installs the App on a GitHub account." }))] }), _jsxs(Group, { title: "Slack", detail: "Talk to Hanzo from your workspace\u2019s channels.", children: [_jsxs(Card, { children: [_jsx(Row, { first: true, leading: _jsx(Slack, { size: 16 }), title: s?.connected && s.account ? s.account : 'Slack', detail: slack.error
71
+ ? slack.error.message
72
+ : !s
73
+ ? 'Reading…'
74
+ : s.connected
75
+ ? `Connected${s.since ? ` since ${day(s.since)}` : ''}`
76
+ : s.available
77
+ ? 'Not connected'
78
+ : s.note || 'This deployment cannot connect Slack yet.', trailing: host.admin && s && (s.connected || s.available) ? (s.connected ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void act(() => disconnect(t, 'slack'), 'Slack is disconnected', slack.reload), children: "Disconnect" })) : (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void away(() => authorize(t, 'slack')), children: "Connect" }))) : null }), joined && rooms.error ? _jsx(Row, { title: "Channels", detail: rooms.error.message }) : null, joined
79
+ ? rooms.value.channels.map((c) => (_jsx(Row, { leading: c.private ? _jsx(Lock, { size: 14 }) : _jsx(Hash, { size: 14 }), title: c.name || c.id, trailing: _jsx(SizableText, { size: "$1", color: c.member ? '$ink' : '$soft', children: c.member ? 'Joined' : 'Not joined' }) }, c.id)))
80
+ : null] }), !host.admin ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "An org admin connects the Slack workspace." })) : null] }), _jsx(Note, { children: note })] }));
81
+ }
@@ -0,0 +1 @@
1
+ export declare function Keys(): import("react").JSX.Element;