@hanzo/build 0.2.80 → 0.2.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/README.md +13 -0
  2. package/lib/activity.d.ts +3 -7
  3. package/lib/activity.js +18 -5
  4. package/lib/api/agents.d.ts +1 -20
  5. package/lib/api/agents.js +16 -8
  6. package/lib/api/auto.d.ts +3 -23
  7. package/lib/api/auto.js +39 -22
  8. package/lib/api/billing.d.ts +0 -35
  9. package/lib/api/billing.js +6 -5
  10. package/lib/api/call.d.ts +16 -8
  11. package/lib/api/call.js +32 -1
  12. package/lib/api/capabilities.d.ts +0 -11
  13. package/lib/api/capabilities.js +2 -1
  14. package/lib/api/codebases.d.ts +0 -10
  15. package/lib/api/codebases.js +3 -2
  16. package/lib/api/coding.d.ts +3 -32
  17. package/lib/api/coding.js +13 -7
  18. package/lib/api/connectors.d.ts +1 -17
  19. package/lib/api/connectors.js +5 -4
  20. package/lib/api/environment.d.ts +6 -20
  21. package/lib/api/environment.js +22 -19
  22. package/lib/api/github.d.ts +0 -22
  23. package/lib/api/github.js +3 -2
  24. package/lib/api/keys.d.ts +0 -14
  25. package/lib/api/keys.js +4 -3
  26. package/lib/api/library.d.ts +3 -17
  27. package/lib/api/library.js +23 -22
  28. package/lib/api/links.d.ts +1 -1
  29. package/lib/api/links.js +1 -1
  30. package/lib/api/machines.d.ts +0 -15
  31. package/lib/api/machines.js +6 -5
  32. package/lib/api/mcp.d.ts +0 -9
  33. package/lib/api/mcp.js +3 -2
  34. package/lib/api/members.d.ts +0 -15
  35. package/lib/api/members.js +4 -3
  36. package/lib/api/memory.d.ts +0 -11
  37. package/lib/api/memory.js +2 -1
  38. package/lib/api/places.d.ts +1 -9
  39. package/lib/api/places.js +12 -1
  40. package/lib/api/plugins.d.ts +1 -16
  41. package/lib/api/plugins.js +4 -3
  42. package/lib/api/profile.d.ts +0 -10
  43. package/lib/api/profile.js +5 -4
  44. package/lib/api/projects.d.ts +0 -14
  45. package/lib/api/projects.js +11 -10
  46. package/lib/api/provider.d.ts +2 -14
  47. package/lib/api/provider.js +12 -10
  48. package/lib/api/sandbox.d.ts +0 -17
  49. package/lib/api/sandbox.js +5 -4
  50. package/lib/api/sessions.d.ts +0 -17
  51. package/lib/api/sessions.js +5 -4
  52. package/lib/api/skills.d.ts +1 -15
  53. package/lib/api/skills.js +5 -4
  54. package/lib/api/studio.d.ts +0 -14
  55. package/lib/api/studio.js +6 -5
  56. package/lib/api/turn.d.ts +6 -28
  57. package/lib/api/turn.js +105 -49
  58. package/lib/api/verdict.d.ts +0 -9
  59. package/lib/api/verdict.js +2 -1
  60. package/lib/api/webhooks.d.ts +1 -14
  61. package/lib/api/webhooks.js +5 -4
  62. package/lib/ask.js +7 -4
  63. package/lib/automations.d.ts +5 -4
  64. package/lib/automations.js +95 -74
  65. package/lib/back.js +10 -4
  66. package/lib/builder.js +7 -5
  67. package/lib/carry.js +20 -16
  68. package/lib/customize/agents.js +78 -31
  69. package/lib/customize/connectors.js +62 -48
  70. package/lib/customize/index.js +43 -10
  71. package/lib/customize/plugins.js +34 -22
  72. package/lib/customize/say.d.ts +13 -1
  73. package/lib/customize/say.js +15 -21
  74. package/lib/customize/skills.js +31 -22
  75. package/lib/customize/ui.d.ts +5 -9
  76. package/lib/customize/ui.js +26 -16
  77. package/lib/data.d.ts +1 -1
  78. package/lib/data.js +3 -2
  79. package/lib/desk.js +45 -33
  80. package/lib/door.js +35 -10
  81. package/lib/environment.js +25 -19
  82. package/lib/files.js +43 -34
  83. package/lib/find.js +12 -9
  84. package/lib/foot.js +7 -4
  85. package/lib/forge.js +61 -40
  86. package/lib/gear.d.ts +2 -1
  87. package/lib/gear.js +6 -4
  88. package/lib/git.js +29 -19
  89. package/lib/landing.d.ts +5 -5
  90. package/lib/landing.js +52 -44
  91. package/lib/library.js +73 -51
  92. package/lib/merge.d.ts +2 -2
  93. package/lib/meter.d.ts +1 -1
  94. package/lib/meter.js +14 -5
  95. package/lib/mic.js +12 -5
  96. package/lib/mind.d.ts +1 -0
  97. package/lib/mind.js +5 -3
  98. package/lib/nav.d.ts +6 -5
  99. package/lib/nav.js +37 -30
  100. package/lib/panel.d.ts +2 -1
  101. package/lib/panel.js +52 -36
  102. package/lib/plan.d.ts +4 -20
  103. package/lib/plan.js +26 -10
  104. package/lib/plans.js +49 -30
  105. package/lib/plugins/design.js +3 -2
  106. package/lib/plugins/index.d.ts +23 -4
  107. package/lib/plugins/index.js +11 -26
  108. package/lib/plugins/pdf.js +2 -1
  109. package/lib/plugins/project.js +7 -4
  110. package/lib/plugins/research.js +3 -2
  111. package/lib/plugins/sketch.js +6 -3
  112. package/lib/plugins/slides.js +3 -2
  113. package/lib/plugins/template.js +6 -3
  114. package/lib/plus.js +25 -13
  115. package/lib/prefs.d.ts +0 -14
  116. package/lib/prefs.js +2 -1
  117. package/lib/project.js +69 -62
  118. package/lib/prompt.d.ts +12 -11
  119. package/lib/prompt.js +38 -20
  120. package/lib/publish.js +17 -12
  121. package/lib/reply.js +6 -3
  122. package/lib/run.js +72 -54
  123. package/lib/say.d.ts +2 -0
  124. package/lib/say.js +7 -0
  125. package/lib/scheduled.js +54 -43
  126. package/lib/section.d.ts +1 -0
  127. package/lib/section.js +8 -2
  128. package/lib/settings/account.d.ts +1 -0
  129. package/lib/settings/account.js +31 -21
  130. package/lib/settings/billing.js +55 -28
  131. package/lib/settings/capabilities.js +26 -15
  132. package/lib/settings/card.js +19 -12
  133. package/lib/settings/code.js +22 -11
  134. package/lib/settings/credits.d.ts +4 -4
  135. package/lib/settings/credits.js +34 -21
  136. package/lib/settings/environments.js +21 -14
  137. package/lib/settings/general.js +26 -14
  138. package/lib/settings/index.js +6 -3
  139. package/lib/settings/integrations.js +42 -28
  140. package/lib/settings/keys.js +37 -19
  141. package/lib/settings/list.d.ts +0 -5
  142. package/lib/settings/list.js +24 -15
  143. package/lib/settings/machines.d.ts +3 -0
  144. package/lib/settings/machines.js +63 -38
  145. package/lib/settings/members.js +23 -14
  146. package/lib/settings/memory.js +12 -6
  147. package/lib/settings/notifications.js +32 -19
  148. package/lib/settings/plan.d.ts +1 -1
  149. package/lib/settings/plan.js +40 -17
  150. package/lib/settings/privacy.js +26 -13
  151. package/lib/settings/ui.d.ts +3 -2
  152. package/lib/settings/ui.js +9 -3
  153. package/lib/settings/usage.js +7 -3
  154. package/lib/shelf.js +20 -17
  155. package/lib/side.d.ts +1 -26
  156. package/lib/side.js +11 -5
  157. package/lib/standing.d.ts +2 -10
  158. package/lib/standing.js +5 -3
  159. package/lib/suggest.d.ts +8 -28
  160. package/lib/suggest.js +39 -4
  161. package/lib/suggestions.js +14 -5
  162. package/lib/summary.d.ts +1 -1
  163. package/lib/summary.js +4 -1
  164. package/lib/sync.js +22 -12
  165. package/lib/tools.d.ts +4 -3
  166. package/lib/tools.js +10 -7
  167. package/lib/transcript.js +10 -8
  168. package/package.json +4 -2
package/lib/sync.js CHANGED
@@ -6,8 +6,12 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
6
6
  * /v1/provider/github/repos/import, which mirrors `owner/name` into this
7
7
  * organization's forge in the background. An account named unread and holding
8
8
  * no repositories is shown as needing an organization admin.
9
+ *
10
+ * Its words are the reader's language (@hanzo/i18n `t`); account and
11
+ * repository names, and Forge, the forge's own name, are drawn as given.
9
12
  */
10
13
  import { SizableText, XStack, YStack } from '@hanzo/gui';
14
+ import { useI18n } from '@hanzo/i18n/react';
11
15
  import { Check, ChevronLeft, ChevronRight, Plus, X } from '@hanzogui/lucide-icons-2';
12
16
  import { Button, Input } from '@hanzo/ui';
13
17
  import { useEffect, useMemo, useState } from 'react';
@@ -22,7 +26,8 @@ import { path } from './route.js';
22
26
  const EMPTY = { repos: [], unread: [] };
23
27
  export function Sync() {
24
28
  const host = useHost();
25
- const t = useTarget();
29
+ const target = useTarget();
30
+ const { t } = useI18n();
26
31
  const signed = Boolean(host.person);
27
32
  const [q, setQ] = useState('');
28
33
  const [owner, setOwner] = useState('');
@@ -31,7 +36,7 @@ export function Sync() {
31
36
  const [busy, setBusy] = useState(false);
32
37
  const [syncing, setSyncing] = useState(false);
33
38
  const [error, setError] = useState('');
34
- const list = useRead(signed ? () => grants(t) : null, EMPTY, [t, signed]);
39
+ const list = useRead(signed ? () => grants(target) : null, EMPTY, [target, signed]);
35
40
  const rows = list.value.repos;
36
41
  const orgs = useMemo(() => accounts(list.value), [list.value]);
37
42
  const needle = q.trim().toLowerCase();
@@ -51,7 +56,7 @@ export function Sync() {
51
56
  setPicked((cur) => (all ? cur.filter((n) => !names.includes(n)) : [...new Set([...cur, ...names])]));
52
57
  };
53
58
  // Coming back from GitHub's App install lands here, says what happened, and reads the list again.
54
- useBack(t, signed, setError, list.reload);
59
+ useBack(target, signed, setError, list.reload);
55
60
  /**
56
61
  * Install the platform's GitHub App on another account — an organization admin's
57
62
  * act — and come back to this page: the App, not a person's own sign-in, is what
@@ -60,21 +65,21 @@ export function Sync() {
60
65
  const addOrg = async () => {
61
66
  setError('');
62
67
  if (!host.admin) {
63
- setError('An organization admin installs the GitHub App on another account.');
68
+ setError(t('An organization admin installs the GitHub App on another account.'));
64
69
  return;
65
70
  }
66
71
  try {
67
- host.open(await authorize(t, 'github', here()));
72
+ host.open(await authorize(target, 'github', here()));
68
73
  }
69
74
  catch (e) {
70
- setError(e instanceof Refusal && e.status === 403 ? 'An organization admin installs the GitHub App on another account.' : 'GitHub could not be reached to install the App. Try again.');
75
+ setError(e instanceof Refusal && e.status === 403 ? t('An organization admin installs the GitHub App on another account.') : t('GitHub could not be reached to install the App. Try again.'));
71
76
  }
72
77
  };
73
78
  const send = async () => {
74
79
  setBusy(true);
75
80
  setError('');
76
81
  try {
77
- const queued = await bring(t, picked);
82
+ const queued = await bring(target, picked);
78
83
  // Each waits under the name it takes on the forge, as the import says it, else as GitHub's listing says it will.
79
84
  const next = [
80
85
  ...readPending(host.org).filter((p) => !picked.includes(p.fullName)),
@@ -99,7 +104,7 @@ export function Sync() {
99
104
  let stop = false;
100
105
  const tick = async () => {
101
106
  try {
102
- const g = await grants(t);
107
+ const g = await grants(target);
103
108
  const landed = new Set(g.repos.filter((r) => r.imported).map((r) => r.fullName));
104
109
  const left = readPending(host.org).filter((p) => !landed.has(p.fullName));
105
110
  writePending(host.org, left);
@@ -115,11 +120,15 @@ export function Sync() {
115
120
  stop = true;
116
121
  window.clearInterval(id);
117
122
  };
118
- }, [syncing, t, host.org, host.go, picked]);
119
- return (_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", children: [_jsxs(XStack, { px: "$6", pt: "$5", pb: "$2", gap: "$2", items: "center", children: [_jsx(XStack, { render: "button", "aria-label": "Back to projects", onPress: back, children: _jsx(SizableText, { size: "$2", color: "$soft", children: "Projects" }) }), _jsx(SizableText, { size: "$2", color: "$soft", children: "/" }), _jsx(SizableText, { size: "$2", color: "$ink", children: "Sync" })] }), _jsxs(YStack, { flex: 1, items: "center", justify: "center", px: "$6", pb: "$10", gap: "$5", children: [_jsxs(XStack, { justify: "center", gap: "$6", children: [_jsx(Step, { label: "Select repositories", done: step === 'access', current: step === 'pick' }), _jsx(Step, { label: "Repository access", done: false, current: step === 'access' })] }), _jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$5", bg: "$panel", px: "$6", py: "$6", gap: "$4", width: "100%", maxW: 460, children: [step === 'pick' && !owner ? (_jsxs(_Fragment, { children: [_jsxs(YStack, { gap: "$1", items: "center", children: [_jsx(SizableText, { size: "$5", color: "$ink", children: "Select repositories" }), _jsxs(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: ["Choose repositories to bring into ", dest] })] }), _jsxs(Panel, { children: [_jsx(Input, { value: q, onChangeText: setQ, placeholder: "Search organizations", "aria-label": "Search organizations", disabled: !signed }), signed ? (_jsx(SizableText, { size: "$1", color: "$soft", children: `${orgs.length} ${orgs.length === 1 ? 'organization' : 'organizations'}` })) : null, list.error ? _jsx(Soft, { children: "The GitHub connection could not be read right now." }) : null, !signed ? (_jsx(Soft, { children: "Sign in to choose repositories the GitHub connection can read." })) : list.loading && rows.length === 0 && orgs.length === 0 ? (_jsx(Soft, { children: "Reading the connection\u2026" })) : shownOrgs.length === 0 ? (_jsx(Soft, { children: orgs.length === 0 ? 'No organizations on this connection yet.' : 'Nothing matches.' })) : (shownOrgs.map((a) => (_jsxs(XStack, { render: "button", "aria-label": a.blocked ? `${a.name} needs an organization admin` : `Open ${a.name}`, disabled: a.blocked, items: "center", gap: "$2", py: "$2", borderTopWidth: 1, borderColor: "$borderColor", onPress: () => {
123
+ }, [syncing, target, host.org, host.go, picked]);
124
+ return (_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", children: [_jsxs(XStack, { px: "$6", pt: "$5", pb: "$2", gap: "$2", items: "center", children: [_jsx(XStack, { render: "button", "aria-label": t('Back to projects'), onPress: back, children: _jsx(SizableText, { size: "$2", color: "$soft", children: t('Projects') }) }), _jsx(SizableText, { size: "$2", color: "$soft", children: "/" }), _jsx(SizableText, { size: "$2", color: "$ink", children: t('Sync') })] }), _jsxs(YStack, { flex: 1, items: "center", justify: "center", px: "$6", pb: "$10", gap: "$5", children: [_jsxs(XStack, { justify: "center", gap: "$6", children: [_jsx(Step, { label: t('Select repositories'), done: step === 'access', current: step === 'pick' }), _jsx(Step, { label: t('Repository access'), done: false, current: step === 'access' })] }), _jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$5", bg: "$panel", px: "$6", py: "$6", gap: "$4", width: "100%", maxW: 460, children: [step === 'pick' && !owner ? (_jsxs(_Fragment, { children: [_jsxs(YStack, { gap: "$1", items: "center", children: [_jsx(SizableText, { size: "$5", color: "$ink", children: t('Select repositories') }), _jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: t('Choose repositories to bring into {place}', { place: dest }) })] }), _jsxs(Panel, { children: [_jsx(Input, { value: q, onChangeText: setQ, placeholder: t('Search organizations'), "aria-label": t('Search organizations'), disabled: !signed }), signed ? (_jsx(SizableText, { size: "$1", color: "$soft", children: orgs.length === 1 ? t('1 organization') : t('{n} organizations', { n: orgs.length }) })) : null, list.error ? _jsx(Soft, { children: t('The GitHub connection could not be read right now.') }) : null, !signed ? (_jsx(Soft, { children: t('Sign in to choose repositories the GitHub connection can read.') })) : list.loading && rows.length === 0 && orgs.length === 0 ? (_jsx(Soft, { children: t('Reading the connection…') })) : shownOrgs.length === 0 ? (_jsx(Soft, { children: orgs.length === 0 ? t('No organizations on this connection yet.') : t('Nothing matches.') })) : (shownOrgs.map((a) => (_jsxs(XStack, { render: "button", "aria-label": a.blocked ? t('{name} needs an organization admin', { name: a.name }) : t('Open {name}', { name: a.name }), disabled: a.blocked, items: "center", gap: "$2", py: "$2", borderTopWidth: 1, borderColor: "$borderColor", onPress: () => {
120
125
  setOwner(a.name);
121
126
  setQ('');
122
- }, children: [_jsx(SizableText, { flex: 1, size: "$2", color: a.blocked ? '$soft' : '$ink', numberOfLines: 1, children: a.name }), a.blocked ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "Needs organization admin" })) : (_jsx(ChevronRight, { size: 14 }))] }, a.name)))), _jsxs(XStack, { render: "button", "aria-label": "Add organization", items: "center", gap: "$2", py: "$2", onPress: () => void addOrg(), disabled: !signed, children: [_jsx(Plus, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", children: "Add organization" })] })] })] })) : null, step === 'pick' && owner ? (_jsxs(_Fragment, { children: [_jsxs(YStack, { gap: "$1", items: "center", children: [_jsx(SizableText, { size: "$5", color: "$ink", children: "Select repositories" }), _jsxs(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: ["Choose repositories to bring into ", dest] })] }), _jsxs(Panel, { children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(XStack, { render: "button", "aria-label": "All organizations", onPress: () => setOwner(''), children: _jsx(ChevronLeft, { size: 16 }) }), _jsx(SizableText, { flex: 1, size: "$2", color: "$ink", numberOfLines: 1, children: owner })] }), _jsx(Input, { value: q, onChangeText: setQ, placeholder: "Search repositories", "aria-label": "Search repositories" }), _jsxs(XStack, { justify: "space-between", items: "center", children: [_jsxs(SizableText, { size: "$1", color: "$soft", children: [ofOwner.length, " ", ofOwner.length === 1 ? 'repository' : 'repositories'] }), _jsx(XStack, { render: "button", "aria-label": "Select all", onPress: selectShown, disabled: shownRepos.length === 0, children: _jsx(SizableText, { size: "$1", color: "$ink", children: "Select all" }) })] }), shownRepos.length === 0 ? (_jsx(Soft, { children: "Nothing matches." })) : (shownRepos.map((r) => (_jsx(RepoRow, { repo: r, on: picked.includes(r.fullName), onPress: () => toggle(r.fullName) }, r.fullName)))), _jsxs(XStack, { render: "button", "aria-label": "Grant more repositories", items: "center", gap: "$2", py: "$2", onPress: () => void addOrg(), children: [_jsx(Plus, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", children: "Add repositories" })] })] })] })) : null, step === 'access' ? (_jsxs(_Fragment, { children: [_jsxs(YStack, { gap: "$1", items: "center", children: [_jsx(SizableText, { size: "$5", color: "$ink", children: "Repository access" }), _jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: "Review who can read these repositories" })] }), _jsxs(XStack, { gap: "$2", items: "center", children: [_jsxs(XStack, { flex: 1, items: "center", gap: "$2", px: "$3", py: "$2", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(XStack, { render: "button", "aria-label": "Clear the selection", onPress: () => { setPicked([]); setStep('pick'); }, children: _jsx(X, { size: 12 }) }), _jsx(SizableText, { flex: 1, size: "$2", color: "$ink", numberOfLines: 1, children: more ? `${lead}, +${more} more` : lead })] }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => setStep('pick'), children: "Edit" })] }), _jsxs(XStack, { justify: "space-between", gap: "$4", children: [_jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: "Who gets access" }), _jsxs(SizableText, { size: "$2", color: "$ink", children: [host.person?.name || host.person?.email || 'You', " \u00B7 You"] })] }), _jsxs(YStack, { gap: "$1", items: "flex-end", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: "Role" }), _jsx(SizableText, { size: "$2", color: "$ink", children: host.admin ? 'Repository admin' : 'Member' })] })] }), _jsxs(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: ["Anyone with access to ", dest] })] })) : null, error ? _jsx(Soft, { children: error }) : null, step === 'pick' ? (_jsx(Button, { width: "100%", disabled: !owner || picked.length === 0, onPress: () => setStep('access'), children: "Continue" })) : (_jsxs(YStack, { gap: "$3", items: "center", width: "100%", children: [_jsx(Button, { width: "100%", disabled: busy || syncing || picked.length === 0, onPress: () => void send(), children: syncing || busy ? 'Syncing…' : `Sync ${picked.length} ${picked.length === 1 ? 'repository' : 'repositories'} to ${dest}` }), syncing ? null : (_jsx(XStack, { render: "button", "aria-label": "Sync later", onPress: back, children: _jsx(SizableText, { size: "$2", color: "$soft", children: "Sync later" }) }))] })), !signed ? (_jsx(Button, { width: "100%", onPress: () => host.signIn?.(), children: "Sign in" })) : null] })] })] }));
127
+ }, children: [_jsx(SizableText, { flex: 1, size: "$2", color: a.blocked ? '$soft' : '$ink', numberOfLines: 1, children: a.name }), a.blocked ? (_jsx(SizableText, { size: "$1", color: "$soft", children: t('Needs organization admin') })) : (_jsx(ChevronRight, { size: 14, 'data-i18n-mirror': '' }))] }, a.name)))), _jsxs(XStack, { render: "button", "aria-label": t('Add organization'), items: "center", gap: "$2", py: "$2", onPress: () => void addOrg(), disabled: !signed, children: [_jsx(Plus, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", children: t('Add organization') })] })] })] })) : null, step === 'pick' && owner ? (_jsxs(_Fragment, { children: [_jsxs(YStack, { gap: "$1", items: "center", children: [_jsx(SizableText, { size: "$5", color: "$ink", children: t('Select repositories') }), _jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: t('Choose repositories to bring into {place}', { place: dest }) })] }), _jsxs(Panel, { children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(XStack, { render: "button", "aria-label": t('All organizations'), onPress: () => setOwner(''), children: _jsx(ChevronLeft, { size: 16, 'data-i18n-mirror': '' }) }), _jsx(SizableText, { flex: 1, size: "$2", color: "$ink", numberOfLines: 1, children: owner })] }), _jsx(Input, { value: q, onChangeText: setQ, placeholder: t('Search repositories'), "aria-label": t('Search repositories') }), _jsxs(XStack, { justify: "space-between", items: "center", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: ofOwner.length === 1 ? t('1 repository') : t('{n} repositories', { n: ofOwner.length }) }), _jsx(XStack, { render: "button", "aria-label": t('Select all'), onPress: selectShown, disabled: shownRepos.length === 0, children: _jsx(SizableText, { size: "$1", color: "$ink", children: t('Select all') }) })] }), shownRepos.length === 0 ? (_jsx(Soft, { children: t('Nothing matches.') })) : (shownRepos.map((r) => (_jsx(RepoRow, { repo: r, on: picked.includes(r.fullName), onPress: () => toggle(r.fullName) }, r.fullName)))), _jsxs(XStack, { render: "button", "aria-label": t('Grant more repositories'), items: "center", gap: "$2", py: "$2", onPress: () => void addOrg(), children: [_jsx(Plus, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", children: t('Add repositories') })] })] })] })) : null, step === 'access' ? (_jsxs(_Fragment, { children: [_jsxs(YStack, { gap: "$1", items: "center", children: [_jsx(SizableText, { size: "$5", color: "$ink", children: t('Repository access') }), _jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: t('Review who can read these repositories') })] }), _jsxs(XStack, { gap: "$2", items: "center", children: [_jsxs(XStack, { flex: 1, items: "center", gap: "$2", px: "$3", py: "$2", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(XStack, { render: "button", "aria-label": t('Clear the selection'), onPress: () => { setPicked([]); setStep('pick'); }, children: _jsx(X, { size: 12 }) }), _jsx(SizableText, { flex: 1, size: "$2", color: "$ink", numberOfLines: 1, children: more ? t('{name}, +{n} more', { name: lead, n: more }) : lead })] }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => setStep('pick'), children: t('Edit') })] }), _jsxs(XStack, { justify: "space-between", gap: "$4", children: [_jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: t('Who gets access') }), _jsx(SizableText, { size: "$2", color: "$ink", children: t('{name} · You', { name: host.person?.name || host.person?.email || t('You') }) })] }), _jsxs(YStack, { gap: "$1", items: "flex-end", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: t('Role') }), _jsx(SizableText, { size: "$2", color: "$ink", children: host.admin ? t('Repository admin') : t('Member') })] })] }), _jsx(SizableText, { size: "$2", color: "$soft", style: { textAlign: 'center' }, children: t('Anyone with access to {place}', { place: dest }) })] })) : null, error ? _jsx(Soft, { children: error }) : null, step === 'pick' ? (_jsx(Button, { width: "100%", disabled: !owner || picked.length === 0, onPress: () => setStep('access'), children: t('Continue') })) : (_jsxs(YStack, { gap: "$3", items: "center", width: "100%", children: [_jsx(Button, { width: "100%", disabled: busy || syncing || picked.length === 0, onPress: () => void send(), children: syncing || busy
128
+ ? t('Syncing…')
129
+ : picked.length === 1
130
+ ? t('Sync 1 repository to {place}', { place: dest })
131
+ : t('Sync {n} repositories to {place}', { n: picked.length, place: dest }) }), syncing ? null : (_jsx(XStack, { render: "button", "aria-label": t('Sync later'), onPress: back, children: _jsx(SizableText, { size: "$2", color: "$soft", children: t('Sync later') }) }))] })), !signed ? (_jsx(Button, { width: "100%", onPress: () => host.signIn?.(), children: t('Sign in') })) : null] })] })] }));
123
132
  }
124
133
  function Step({ label, done, current }) {
125
134
  return (_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(YStack, { width: 14, height: 14, rounded: 999, bg: done ? '$green10' : 'transparent', borderWidth: 1, borderColor: done || current ? '$ink' : '$borderColor', items: "center", justify: "center", children: done ? _jsx(Check, { size: 10, color: "white" }) : null }), _jsx(SizableText, { size: "$1", color: current || done ? '$ink' : '$soft', children: label })] }));
@@ -131,5 +140,6 @@ function Soft({ children }) {
131
140
  return (_jsx(SizableText, { size: "$2", color: "$soft", children: children }));
132
141
  }
133
142
  function RepoRow({ repo, on, onPress }) {
134
- return (_jsxs(XStack, { render: "button", "aria-label": on ? `Selected ${repo.name}` : `Select ${repo.name}`, "aria-pressed": on, items: "center", gap: "$2", py: "$2", borderTopWidth: 1, borderColor: "$borderColor", onPress: onPress, children: [_jsx(YStack, { width: 14, height: 14, rounded: "$1", borderWidth: 1, borderColor: on ? '$ink' : '$borderColor', bg: on ? '$ink' : 'transparent' }), _jsx(SizableText, { flex: 1, size: "$2", color: "$ink", numberOfLines: 1, children: repo.name }), repo.imported || repo.status === 'pending' ? (_jsx(SizableText, { size: "$1", color: "$soft", children: repo.imported ? (repo.status === 'conflict' ? 'On Forge · diverged' : 'On Forge') : 'Syncing…' })) : null] }));
143
+ const { t } = useI18n();
144
+ return (_jsxs(XStack, { render: "button", "aria-label": on ? t('Selected {name}', { name: repo.name }) : t('Select {name}', { name: repo.name }), "aria-pressed": on, items: "center", gap: "$2", py: "$2", borderTopWidth: 1, borderColor: "$borderColor", onPress: onPress, children: [_jsx(YStack, { width: 14, height: 14, rounded: "$1", borderWidth: 1, borderColor: on ? '$ink' : '$borderColor', bg: on ? '$ink' : 'transparent' }), _jsx(SizableText, { flex: 1, size: "$2", color: "$ink", numberOfLines: 1, children: repo.name }), repo.imported || repo.status === 'pending' ? (_jsx(SizableText, { size: "$1", color: "$soft", children: repo.imported ? (repo.status === 'conflict' ? t('On Forge · diverged') : t('On Forge')) : t('Syncing…') })) : null] }));
135
145
  }
package/lib/tools.d.ts CHANGED
@@ -1,16 +1,17 @@
1
+ import { type Say } from '@hanzo/i18n/react';
1
2
  export interface Attached {
2
3
  name: string;
3
4
  text: string;
4
5
  }
5
6
  /** The ask with its attachments appended as fenced text. */
6
7
  export declare function compose(prompt: string, files: Attached[]): string;
7
- /** Read `list` into `have`, within the caps; answers the new list and what was refused. */
8
- export declare function read(have: Attached[], list: FileList): Promise<{
8
+ /** Read `list` into `have`, within the caps; answers the new list and what was refused, in the language of `t`. */
9
+ export declare function read(have: Attached[], list: FileList, t?: Say): Promise<{
9
10
  files: Attached[];
10
11
  note: string;
11
12
  }>;
12
13
  /** Ask the browser for files and read them in, within the caps; the picker is minted per press. */
13
- export declare function pick(have: Attached[], onFiles: (f: Attached[]) => void, onNote: (n: string) => void): void;
14
+ export declare function pick(have: Attached[], onFiles: (f: Attached[]) => void, onNote: (n: string) => void, t?: Say): void;
14
15
  /** The attached files as small removable marks. */
15
16
  export declare function Files({ files, onFiles }: {
16
17
  files: Attached[];
package/lib/tools.js CHANGED
@@ -5,8 +5,10 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
5
5
  * composer's (prompt.tsx); this reads what it picks and draws the chips.
6
6
  */
7
7
  import { SizableText, XStack } from '@hanzo/gui';
8
+ import { useI18n } from '@hanzo/i18n/react';
8
9
  import { Paperclip, X } from '@hanzogui/lucide-icons-2';
9
10
  import { ComposerTool } from '@hanzo/ui/chat';
11
+ import { english } from './say.js';
10
12
  const EACH = 100_000;
11
13
  const ALL = 200_000;
12
14
  /** The ask with its attachments appended as fenced text. */
@@ -15,19 +17,19 @@ export function compose(prompt, files) {
15
17
  return prompt;
16
18
  return prompt + files.map((f) => `\n\n\`${f.name}\`:\n\`\`\`\n${f.text}\n\`\`\``).join('');
17
19
  }
18
- /** Read `list` into `have`, within the caps; answers the new list and what was refused. */
19
- export async function read(have, list) {
20
+ /** Read `list` into `have`, within the caps; answers the new list and what was refused, in the language of `t`. */
21
+ export async function read(have, list, t = english) {
20
22
  const files = [...have];
21
23
  let total = files.reduce((n, f) => n + f.text.length, 0);
22
24
  let note = '';
23
25
  for (const file of Array.from(list)) {
24
26
  if (file.size > EACH) {
25
- note = `${file.name} is over 100 KB; attach a smaller file or point the run at the repository.`;
27
+ note = t('{file} is over 100 KB; attach a smaller file or point the run at the repository.', { file: file.name });
26
28
  continue;
27
29
  }
28
30
  const text = await file.text();
29
31
  if (total + text.length > ALL) {
30
- note = 'Attachments are capped at 200 KB in all.';
32
+ note = t('Attachments are capped at 200 KB in all.');
31
33
  break;
32
34
  }
33
35
  total += text.length;
@@ -36,7 +38,7 @@ export async function read(have, list) {
36
38
  return { files, note };
37
39
  }
38
40
  /** Ask the browser for files and read them in, within the caps; the picker is minted per press. */
39
- export function pick(have, onFiles, onNote) {
41
+ export function pick(have, onFiles, onNote, t = english) {
40
42
  if (typeof document === 'undefined')
41
43
  return;
42
44
  const ask = document.createElement('input');
@@ -45,7 +47,7 @@ export function pick(have, onFiles, onNote) {
45
47
  ask.onchange = () => {
46
48
  if (!ask.files?.length)
47
49
  return;
48
- void read(have, ask.files).then(({ files, note }) => {
50
+ void read(have, ask.files, t).then(({ files, note }) => {
49
51
  onFiles(files);
50
52
  onNote(note);
51
53
  });
@@ -54,5 +56,6 @@ export function pick(have, onFiles, onNote) {
54
56
  }
55
57
  /** The attached files as small removable marks. */
56
58
  export function Files({ files, onFiles }) {
57
- return (_jsx(_Fragment, { children: files.map((f) => (_jsxs(XStack, { items: "center", gap: "$1", px: "$2", height: 24, rounded: "$2", bg: "$raised", children: [_jsx(Paperclip, { size: 12 }), _jsx(SizableText, { size: "$1", color: "$ink", numberOfLines: 1, maxW: 140, children: f.name }), _jsx(ComposerTool, { label: `Remove ${f.name}`, icon: _jsx(X, { size: 12 }), onPress: () => onFiles(files.filter((x) => x !== f)) })] }, f.name))) }));
59
+ const { t } = useI18n();
60
+ return (_jsx(_Fragment, { children: files.map((f) => (_jsxs(XStack, { items: "center", gap: "$1", px: "$2", height: 24, rounded: "$2", bg: "$raised", children: [_jsx(Paperclip, { size: 12 }), _jsx(SizableText, { size: "$1", color: "$ink", numberOfLines: 1, maxW: 140, children: f.name }), _jsx(ComposerTool, { label: t('Remove {name}', { name: f.name }), icon: _jsx(X, { size: 12 }), onPress: () => onFiles(files.filter((x) => x !== f)) })] }, f.name))) }));
58
61
  }
package/lib/transcript.js CHANGED
@@ -17,6 +17,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
17
17
  * commands, edits and steps — are cards of their own between the turns.
18
18
  */
19
19
  import { SizableText, XStack, YStack } from '@hanzo/gui';
20
+ import { useI18n } from '@hanzo/i18n/react';
20
21
  import { Button } from '@hanzo/ui';
21
22
  import { Feedback } from '@hanzo/ui/agents';
22
23
  import { Code, Message, Step, Thread } from '@hanzo/ui/chat';
@@ -31,11 +32,12 @@ const mono = { fontFamily: 'var(--f-mono, ui-monospace, monospace)' };
31
32
  /** What a thought is about: its first line, without the markdown that bolds it. */
32
33
  const firstLine = (text) => text.trim().split('\n')[0].replace(/[*_#`]/g, '').slice(0, 120);
33
34
  export function Transcript({ cards, live, empty, header, onApprove, approving = false, onVerdict, onOpen, onDiff, listen, end, }) {
35
+ const { t } = useI18n();
34
36
  // A card the run never finished is cut off once the run has ended, not still going.
35
37
  const state = (ran) => (ran === 'running' && !live ? 'cancelled' : ran);
36
38
  // The answer still arriving: the last thing the agent said while the run works.
37
39
  const last = live ? [...cards].reverse().find((c) => c.kind === 'said' && c.who === 'agent')?.key : undefined;
38
- return (_jsxs(Thread, { gap: GAP, column: { maxW: MEASURE }, "aria-label": "Transcript", children: [header, cards.length === 0 ? _jsx(YStack, { py: "$2", children: empty }) : null, cards.map((c) => {
40
+ return (_jsxs(Thread, { gap: GAP, column: { maxW: MEASURE }, "aria-label": t('Transcript'), children: [header, cards.length === 0 ? _jsx(YStack, { py: "$2", children: empty }) : null, cards.map((c) => {
39
41
  switch (c.kind) {
40
42
  case 'said': {
41
43
  if (c.who === 'person')
@@ -45,19 +47,19 @@ export function Transcript({ cards, live, empty, header, onApprove, approving =
45
47
  return (_jsx(Message, { role: "assistant", busy: c.key === last, children: _jsxs(YStack, { gap: "$1", children: [_jsx(Prose, { text: c.text }), c.key === last ? null : (_jsx(Reply, { text: c.text, listen: listen, onOpen: first && onOpen ? () => onOpen(first) : undefined, children: _jsx(Judge, { onVerdict: onVerdict }) }))] }) }, c.key));
46
48
  }
47
49
  case 'think':
48
- return (_jsx(Step, { name: "Thought", detail: firstLine(c.text), status: "done", "aria-label": "Thought", children: _jsx(Prose, { text: c.text }) }, c.key));
50
+ return (_jsx(Step, { name: t('Thought'), detail: firstLine(c.text), status: "done", "aria-label": t('Thought'), children: _jsx(Prose, { text: c.text }) }, c.key));
49
51
  case 'todo':
50
- return (_jsx(YStack, { gap: "$1", px: "$3", py: "$2", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", "aria-label": "Plan checklist", children: c.items.map((x, i) => (_jsxs(XStack, { gap: "$2", items: "flex-start", children: [_jsx(SizableText, { size: "$2", color: x.done ? '$ink' : '$soft', children: x.done ? '✓' : '○' }), _jsx(SizableText, { flex: 1, size: "$2", color: x.done ? '$soft' : '$ink', children: x.text })] }, i))) }, c.key));
52
+ return (_jsx(YStack, { gap: "$1", px: "$3", py: "$2", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", "aria-label": t('Plan checklist'), children: c.items.map((x, i) => (_jsxs(XStack, { gap: "$2", items: "flex-start", children: [_jsx(SizableText, { size: "$2", color: x.done ? '$ink' : '$soft', children: x.done ? '✓' : '○' }), _jsx(SizableText, { flex: 1, size: "$2", color: x.done ? '$soft' : '$ink', children: x.text })] }, i))) }, c.key));
51
53
  case 'shell':
52
- return (_jsx(Step, { name: "Shell", detail: c.command, status: state(c.ran), "aria-label": `Command ${c.command}`, children: _jsx(Code, { language: "shell", value: c.output ? `$ ${c.command}\n${c.output}` : `$ ${c.command}`, children: c.output ? `$ ${c.command}\n${tail(c.output)}` : `$ ${c.command}` }) }, c.key));
54
+ return (_jsx(Step, { name: t('Shell'), detail: c.command, status: state(c.ran), "aria-label": t('Command {command}', { command: c.command }), children: _jsx(Code, { language: "shell", value: c.output ? `$ ${c.command}\n${c.output}` : `$ ${c.command}`, children: c.output ? `$ ${c.command}\n${tail(c.output)}` : `$ ${c.command}` }) }, c.key));
53
55
  case 'edit':
54
- return (_jsx(Step, { name: c.files.length === 1 ? 'Edited' : c.files.length ? `Edited ${c.files.length} files` : 'Edited files', detail: c.files.join(', '), status: state(c.ran), "aria-label": `Edited ${c.files.join(', ') || 'files'}`, children: _jsxs(YStack, { gap: "$2", items: "flex-start", children: [_jsx(SizableText, { size: "$1", color: "$soft", style: { whiteSpace: 'pre-wrap' }, children: c.files.length ? c.files.join('\n') : 'The agent named no files.' }), onDiff ? (_jsx(Button, { size: "sm", variant: "outline", onPress: onDiff, children: "Open the diff" })) : null] }) }, c.key));
56
+ return (_jsx(Step, { name: c.files.length === 1 ? t('Edited') : c.files.length ? t('Edited {n} files', { n: c.files.length }) : t('Edited files'), detail: c.files.join(', '), status: state(c.ran), "aria-label": c.files.length ? t('Edited {files}', { files: c.files.join(', ') }) : t('Edited files'), children: _jsxs(YStack, { gap: "$2", items: "flex-start", children: [_jsx(SizableText, { size: "$1", color: "$soft", style: { whiteSpace: 'pre-wrap' }, children: c.files.length ? c.files.join('\n') : t('The agent named no files.') }), onDiff ? (_jsx(Button, { size: "sm", variant: "outline", onPress: onDiff, children: t('Open the diff') })) : null] }) }, c.key));
55
57
  case 'step':
56
- return (_jsx(Step, { name: c.name, detail: c.detail, status: state(c.ran), defaultOpen: c.name === 'Error', "aria-label": `${c.name} ${c.detail}`.trim(), children: c.name === 'Error' ? (_jsx(SizableText, { size: "$2", color: "$ink", style: { ...mono, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }, children: c.output })) : c.output.trim() ? (_jsx(Code, { language: "output", value: c.output, children: tail(c.output.replace(/\n+$/, '')) })) : undefined }, c.key));
58
+ return (_jsx(Step, { name: t(c.name), detail: c.detail, status: state(c.ran), defaultOpen: c.name === 'Error', "aria-label": `${t(c.name)} ${c.detail}`.trim(), children: c.name === 'Error' ? (_jsx(SizableText, { size: "$2", color: "$ink", style: { ...mono, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }, children: c.output })) : c.output.trim() ? (_jsx(Code, { language: "output", value: c.output, children: tail(c.output.replace(/\n+$/, '')) })) : undefined }, c.key));
57
59
  case 'note':
58
- return (_jsx(SizableText, { size: "$1", color: "$soft", role: "note", children: c.text }, c.key));
60
+ return (_jsx(SizableText, { size: "$1", color: "$soft", role: "note", children: t(c.text) }, c.key));
59
61
  case 'plan':
60
- return (_jsxs(YStack, { gap: "$3", p: "$4", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", "aria-label": "Plan", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: "Plan" }), _jsx(Prose, { text: c.text }), _jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsx(Reply, { text: c.text, listen: listen }), onApprove ? (_jsx(Button, { size: "sm", disabled: approving, onPress: () => onApprove(c.text), children: approving ? 'Starting the build…' : 'Approve and build' })) : null, _jsx(Judge, { onVerdict: onVerdict })] })] }, c.key));
62
+ return (_jsxs(YStack, { gap: "$3", p: "$4", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", "aria-label": t('Plan'), children: [_jsx(SizableText, { size: "$2", color: "$soft", children: t('Plan') }), _jsx(Prose, { text: c.text }), _jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsx(Reply, { text: c.text, listen: listen }), onApprove ? (_jsx(Button, { size: "sm", disabled: approving, onPress: () => onApprove(c.text), children: approving ? t('Starting the build…') : t('Approve and build') })) : null, _jsx(Judge, { onVerdict: onVerdict })] })] }, c.key));
61
63
  }
62
64
  }), end] }));
63
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/build",
3
- "version": "0.2.80",
3
+ "version": "0.2.82",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "description": "Hanzo Build — say what you want, watch it built, deployed and running. The builder as a component, mounted by the Hanzo app at hanzo.ai.",
@@ -11,6 +11,7 @@
11
11
  "@hanzo/composer": "0.2.0",
12
12
  "@hanzo/font": "1.9.3",
13
13
  "@hanzo/gui": "8.3.5",
14
+ "@hanzo/i18n": "0.4.0",
14
15
  "@hanzo/iam": "0.21.28",
15
16
  "@hanzo/ui": "8.29.0",
16
17
  "@hanzo/vite": "0.1.4",
@@ -76,7 +77,8 @@
76
77
  "@hanzogui/lucide-icons-2": ">=8.3.5",
77
78
  "react": ">=19.2.8",
78
79
  "@hanzo/voice": ">=0.1.19",
79
- "@hanzo/composer": ">=0.2.0"
80
+ "@hanzo/composer": ">=0.2.0",
81
+ "@hanzo/i18n": ">=0.4.0"
80
82
  },
81
83
  "scripts": {
82
84
  "dev": "vite",