@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/merge.d.ts CHANGED
@@ -42,7 +42,7 @@ export interface Project {
42
42
  synced: string;
43
43
  /** Its link's state: `synced`, `conflict`, `pending`, `paused`, `linked` when its forge copy could not be read; '' with no link. */
44
44
  status: string;
45
- /** The org's project (GET /v1/projects) that builds from it, by slug, or ''. */
45
+ /** The org's project (GET /v1/project) that builds from it, by slug, or ''. */
46
46
  slug: string;
47
47
  /** The name GitHub's listing says it takes on the forge once brought there, or ''. */
48
48
  codebase: string;
@@ -124,7 +124,7 @@ export interface Linked {
124
124
  status: string;
125
125
  branch: string;
126
126
  }
127
- /** One of the org's projects (GET /v1/projects) that builds from a repository. */
127
+ /** One of the org's projects (GET /v1/project) that builds from a repository. */
128
128
  export interface Site {
129
129
  slug: string;
130
130
  /** Its clone address. */
package/lib/meter.d.ts CHANGED
@@ -7,7 +7,7 @@ export declare const INK: {
7
7
  readonly near: '$yellow10';
8
8
  readonly limited: '$red10';
9
9
  };
10
- /** A plan's name with its rung beside it, small: `Max` `20x`. */
10
+ /** A plan's name with its rung beside it, small: `Max` `20x`. A family's name is a name; Free and the like are words. */
11
11
  export declare function Title({ label, size }: {
12
12
  label: Label;
13
13
  size?: '$3' | '$5' | '$6';
package/lib/meter.js CHANGED
@@ -13,14 +13,19 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  * balance, because that balance is what it spends.
14
14
  */
15
15
  import { SizableText, XStack, YStack } from '@hanzo/gui';
16
+ import { phrase } from '@hanzo/i18n';
17
+ import { useI18n } from '@hanzo/i18n/react';
16
18
  import { money } from './api/billing.js';
17
19
  import { left, rows } from './standing.js';
18
20
  import { when } from './plan.js';
19
21
  /** The ink a share is said in: quiet until it is close, and the one red when it is used. */
20
22
  export const INK = { ok: '$soft', near: '$yellow10', limited: '$red10' };
21
- /** A plan's name with its rung beside it, small: `Max` `20x`. */
23
+ /** A window's name, in English; drawn through `t`. */
24
+ const WINDOW = { Session: phrase('Session'), Today: phrase('Today'), Month: phrase('Month') };
25
+ /** A plan's name with its rung beside it, small: `Max` `20x`. A family's name is a name; Free and the like are words. */
22
26
  export function Title({ label, size = '$3' }) {
23
- return (_jsxs(XStack, { items: "baseline", gap: "$1.5", minW: 0, "data-slot": "plan-name", children: [_jsx(SizableText, { size: size, color: "$ink", style: { fontWeight: 600 }, numberOfLines: 1, children: label.name }), label.tag ? (_jsx(SizableText, { size: "$1", color: "$soft", px: "$1.5", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", "data-slot": "plan-tag", children: label.tag })) : null] }));
27
+ const { t } = useI18n();
28
+ return (_jsxs(XStack, { items: "baseline", gap: "$1.5", minW: 0, "data-slot": "plan-name", children: [_jsx(SizableText, { size: size, color: "$ink", style: { fontWeight: 600 }, numberOfLines: 1, children: t(label.name) }), label.tag ? (_jsx(SizableText, { size: "$1", color: "$soft", px: "$1.5", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", "data-slot": "plan-tag", children: t(label.tag) })) : null] }));
24
29
  }
25
30
  /** A quiet text button. */
26
31
  function Act({ label, onPress }) {
@@ -28,10 +33,13 @@ function Act({ label, onPress }) {
28
33
  }
29
34
  /** The standing, drawn: what `Meter` shows once it has read. */
30
35
  export function Reading({ s, onPlans, onBilling, now }) {
31
- const windows = s.kind === 'plan' ? rows(s.limits, now) : [];
36
+ const { t } = useI18n();
37
+ const windows = s.kind === 'plan' ? rows(s.limits, now, t) : [];
32
38
  const share = s.kind === 'free' ? left(s.allowance) : null;
33
39
  const resets = s.allowance ? when(s.allowance.resets, now) : '';
34
- return (_jsxs(YStack, { px: "$2", py: "$1.5", gap: "$1", "data-slot": "meter", "data-kind": s.kind, children: [_jsxs(XStack, { items: "center", gap: "$2", minW: 0, children: [_jsx(XStack, { flex: 1, minW: 0, children: s.label ? _jsx(Title, { label: s.label }) : null }), s.kind === 'free' ? _jsx(Act, { label: "Upgrade", onPress: onPlans }) : s.kind === 'credits' ? _jsx(Act, { label: "Add funds", onPress: onBilling }) : null] }), windows.map((w) => (_jsxs(XStack, { items: "baseline", gap: "$2", "data-slot": "meter-window", "data-state": w.state, children: [_jsx(SizableText, { size: "$1", color: "$soft", width: 56, shrink: 0, children: w.name }), _jsxs(SizableText, { size: "$1", color: INK[w.state], style: { fontVariantNumeric: 'tabular-nums' }, shrink: 0, children: [w.percent, "%"] }), _jsx(SizableText, { size: "$1", color: "$soft", flex: 1, minW: 0, numberOfLines: 1, text: "right", children: w.resets })] }, w.name))), s.kind === 'free' && share !== null ? (_jsx(SizableText, { size: "$1", color: share === 0 ? '$red10' : '$soft', "data-slot": "meter-free", children: `${share}% of today’s free usage left${resets ? ` · Resets ${resets}` : ''}` })) : null, s.kind === 'credits' && s.balance !== null ? (_jsx(SizableText, { size: "$1", color: "$soft", "data-slot": "meter-balance", children: `${money(s.balance)} in credits` })) : null] }));
40
+ return (_jsxs(YStack, { px: "$2", py: "$1.5", gap: "$1", "data-slot": "meter", "data-kind": s.kind, children: [_jsxs(XStack, { items: "center", gap: "$2", minW: 0, children: [_jsx(XStack, { flex: 1, minW: 0, children: s.label ? _jsx(Title, { label: s.label }) : null }), s.kind === 'free' ? _jsx(Act, { label: t('Upgrade'), onPress: onPlans }) : s.kind === 'credits' ? _jsx(Act, { label: t('Add funds'), onPress: onBilling }) : null] }), windows.map((w) => (_jsxs(XStack, { items: "baseline", gap: "$2", "data-slot": "meter-window", "data-state": w.state, children: [_jsx(SizableText, { size: "$1", color: "$soft", width: 56, shrink: 0, children: t(WINDOW[w.name]) }), _jsxs(SizableText, { size: "$1", color: INK[w.state], style: { fontVariantNumeric: 'tabular-nums' }, shrink: 0, children: [w.percent, "%"] }), _jsx(SizableText, { size: "$1", color: "$soft", flex: 1, minW: 0, numberOfLines: 1, text: "right", children: w.resets })] }, w.name))), s.kind === 'free' && share !== null ? (_jsx(SizableText, { size: "$1", color: share === 0 ? '$red10' : '$soft', "data-slot": "meter-free", children: resets
41
+ ? `${t('{n}% of today’s free usage left', { n: share })} · ${t('Resets {time}', { time: resets })}`
42
+ : t('{n}% of today’s free usage left', { n: share }) })) : null, s.kind === 'credits' && s.balance !== null ? (_jsx(SizableText, { size: "$1", color: "$soft", "data-slot": "meter-balance", children: t('{amount} in credits', { amount: money(s.balance) }) })) : null] }));
35
43
  }
36
44
  /**
37
45
  * The block an account menu leads with: `read` is `useStanding`, asked while
@@ -39,7 +47,8 @@ export function Reading({ s, onPlans, onBilling, now }) {
39
47
  * live on; the host says where each is.
40
48
  */
41
49
  export function Meter({ read, onPlans, onBilling }) {
50
+ const { t } = useI18n();
42
51
  if (read.value)
43
52
  return _jsx(Reading, { s: read.value, onPlans: onPlans, onBilling: onBilling });
44
- return (_jsx(SizableText, { size: "$1", color: "$soft", px: "$2", py: "$1.5", "data-slot": "meter", children: read.error ? 'Your plan could not be read.' : 'Reading your plan…' }));
53
+ return (_jsx(SizableText, { size: "$1", color: "$soft", px: "$2", py: "$1.5", "data-slot": "meter", children: read.error ? t('Your plan could not be read.') : t('Reading your plan…') }));
45
54
  }
package/lib/mic.js CHANGED
@@ -9,10 +9,14 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
9
9
  * every capture opens it; unchosen, the system's own input. The meter listens
10
10
  * only while the menu is open, and only where the person has already let the
11
11
  * page use a microphone: opening a menu never asks for permission.
12
+ *
13
+ * Its words are the reader's language (@hanzo/i18n `t`); a microphone's name
14
+ * is the device's and a language's is its own, drawn as given.
12
15
  */
13
16
  import { SizableText, XStack, YStack } from '@hanzo/gui';
14
17
  import { Check, ChevronDown, Mic as MicIcon } from '@hanzogui/lucide-icons-2';
15
18
  import { Control } from '@hanzo/composer';
19
+ import { useI18n } from '@hanzo/i18n/react';
16
20
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from '@hanzo/ui';
17
21
  import { choose, Voice } from '@hanzo/voice';
18
22
  import { useEffect, useRef, useState } from 'react';
@@ -69,6 +73,8 @@ function useInputs(open) {
69
73
  }
70
74
  /** The chosen microphone's level while the menu is open, drawn without re-rendering. */
71
75
  function Meter({ device, on }) {
76
+ // The level grows from the line's start: the right edge in a right-to-left language.
77
+ const { dir } = useI18n();
72
78
  const bar = useRef(null);
73
79
  useEffect(() => {
74
80
  if (!on)
@@ -108,11 +114,12 @@ function Meter({ device, on }) {
108
114
  };
109
115
  }, [device, on]);
110
116
  return (_jsxs(XStack, { items: "center", gap: 10, px: 8, py: 8, "data-slot": "mic-meter", "aria-hidden": true, children: [_jsx(MicIcon, { size: 14, color: "$hint" }), _jsx(YStack, { flex: 1, height: 4, rounded: 2, bg: "$hover", overflow: "hidden", children: _jsx(YStack, { ref: bar, height: 4, width: "100%", bg: "$ink",
111
- // inline-style: the level is written per frame onto the node; a style prop would mint a class per value.
112
- style: { transform: 'scaleX(0)', transformOrigin: 'left' } }) })] }));
117
+ // Empty at rest; the level is written per frame onto the node's transform, so no class is minted per value.
118
+ scaleX: 0, transformOrigin: dir === 'rtl' ? 'right' : 'left' }) })] }));
113
119
  }
114
120
  /** The mic and its chevron, one control on the composer's row. */
115
121
  export function Dictation({ voice, disabled = false, narrow = false, language = null, onLanguage }) {
122
+ const { t } = useI18n();
116
123
  const [open, setOpen] = useState(false);
117
124
  // The Language list, drawn only while open (see plus.tsx).
118
125
  const [lang, setLang] = useState(false);
@@ -122,11 +129,11 @@ export function Dictation({ voice, disabled = false, narrow = false, language =
122
129
  // The chevron is drawn while the mic is reached for (pointer, keyboard, menu open); at rest the row is the mic alone.
123
130
  const [near, setNear] = useState(false);
124
131
  const shown = open || near;
125
- return (_jsxs(XStack, { "data-slot": "dictation", items: "center", rounded: 8, borderWidth: 1, borderColor: open ? '$borderColor' : 'transparent', hoverStyle: { borderColor: '$borderColor' }, shrink: 0, onPointerEnter: () => setNear(true), onPointerLeave: () => setNear(false), onFocus: () => setNear(true), onBlur: () => setNear(false), children: [_jsx(Control, { asChild: true, size: narrow ? 44 : 30, ...PLAIN, children: _jsx(Voice, { voice: voice, disabled: disabled, says: { idle: 'Dictate', listening: 'Dictating — click to stop' }, children: (state) => _jsx(MicIcon, { size: 16, color: "var(--foreground)", fill: state === 'idle' ? 'none' : 'var(--foreground)' }) }) }), _jsxs(DropdownMenu, { open: open, onOpenChange: setOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, disabled: disabled, children: _jsx(XStack, { render: "button", "aria-label": "Dictation settings", "data-slot": "dictation-menu", width: 16, height: 30, items: "center", justify: "center", rounded: 6, cursor: "pointer", bg: open ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, ...(narrow ? { width: 44, height: 44 } : null), children: _jsx(ChevronDown, { size: 14, color: "$soft", opacity: shown || narrow ? 1 : 0 }) }) }), _jsxs(DropdownMenuContent, { align: "end", minW: 200, "data-slot": "dictation-panel", children: [_jsx(Meter, { device: device, on: open && allowed }), inputs.length === 0 ? (_jsx(DropdownMenuLabel, { children: _jsx(SizableText, { fontSize: 14, color: "$hint", children: "No devices found" }) })) : (inputs.map((d, i) => {
132
+ return (_jsxs(XStack, { "data-slot": "dictation", items: "center", rounded: 8, borderWidth: 1, borderColor: open ? '$borderColor' : 'transparent', hoverStyle: { borderColor: '$borderColor' }, shrink: 0, onPointerEnter: () => setNear(true), onPointerLeave: () => setNear(false), onFocus: () => setNear(true), onBlur: () => setNear(false), children: [_jsx(Control, { asChild: true, size: narrow ? 44 : 30, ...PLAIN, children: _jsx(Voice, { voice: voice, disabled: disabled, says: { idle: t('Dictate'), listening: t('Dictating — click to stop') }, children: (state) => _jsx(MicIcon, { size: 16, color: "var(--foreground)", fill: state === 'idle' ? 'none' : 'var(--foreground)' }) }) }), _jsxs(DropdownMenu, { open: open, onOpenChange: setOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, disabled: disabled, children: _jsx(XStack, { render: "button", "aria-label": t('Dictation settings'), "data-slot": "dictation-menu", width: 16, height: 30, items: "center", justify: "center", rounded: 6, cursor: "pointer", bg: open ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, ...(narrow ? { width: 44, height: 44 } : null), children: _jsx(ChevronDown, { size: 14, color: "$soft", opacity: shown || narrow ? 1 : 0 }) }) }), _jsxs(DropdownMenuContent, { align: "end", minW: 200, "data-slot": "dictation-panel", children: [_jsx(Meter, { device: device, on: open && allowed }), inputs.length === 0 ? (_jsx(DropdownMenuLabel, { children: _jsx(SizableText, { fontSize: 14, color: "$hint", children: t('No devices found') }) })) : (inputs.map((d, i) => {
126
133
  const on = device ? d.deviceId === device : d.groupId === using;
127
134
  return (_jsx(DropdownMenuItem, { preventCloseOnSelect: true, onSelect: () => {
128
135
  setDevice(d.deviceId);
129
136
  keep(d.deviceId);
130
- }, children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", numberOfLines: 1, flex: 1, children: d.label || `Microphone ${i + 1}` }), on ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) }, d.deviceId || i));
131
- })), onLanguage ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuLabel, { children: _jsx(SizableText, { fontSize: 13, color: "$hint", children: "Dictation settings" }) }), _jsxs(DropdownMenuSub, { open: lang, onOpenChange: setLang, children: [_jsx(DropdownMenuSubTrigger, { "data-slot": "dictation-language", onPress: () => setLang(true), children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", flex: 1, children: "Language" }), _jsx(SizableText, { fontSize: 14, color: "$hint", children: spoken?.label ?? 'Auto' })] }) }), lang ? (_jsx(DropdownMenuSubContent, { minW: 160, pointerEvents: "auto", children: [{ id: '', label: 'Auto' }, ...LANGUAGES].map((l) => (_jsx(DropdownMenuItem, { onSelect: () => onLanguage(l.id || null), children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", flex: 1, children: l.label }), (language ?? '') === l.id ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) }, l.id || 'auto'))) })) : null] })] })) : null] })] })] }));
137
+ }, children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", numberOfLines: 1, flex: 1, children: d.label || t('Microphone {n}', { n: i + 1 }) }), on ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) }, d.deviceId || i));
138
+ })), onLanguage ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuLabel, { children: _jsx(SizableText, { fontSize: 13, color: "$hint", children: t('Dictation settings') }) }), _jsxs(DropdownMenuSub, { open: lang, onOpenChange: setLang, children: [_jsx(DropdownMenuSubTrigger, { "data-slot": "dictation-language", onPress: () => setLang(true), children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", flex: 1, children: t('Language') }), _jsx(SizableText, { fontSize: 14, color: "$hint", children: spoken?.label ?? t('Auto') })] }) }), lang ? (_jsx(DropdownMenuSubContent, { minW: 160, pointerEvents: "auto", children: [{ id: '', label: t('Auto') }, ...LANGUAGES].map((l) => (_jsx(DropdownMenuItem, { onSelect: () => onLanguage(l.id || null), children: _jsxs(XStack, { flex: 1, items: "center", gap: 8, children: [_jsx(SizableText, { fontSize: 14, color: "$ink", flex: 1, children: l.label }), (language ?? '') === l.id ? _jsx(Check, { size: 14, color: "var(--primary)", "aria-hidden": true }) : null] }) }, l.id || 'auto'))) })) : null] })] })) : null] })] })] }));
132
139
  }
package/lib/mind.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type Target } from './api/call.ts';
2
2
  import { type Model } from './api/models.ts';
3
3
  export type Effort = 'low' | 'medium' | 'high';
4
+ /** The efforts, in English and marked (`phrase`): the composer draws each word through `t`. */
4
5
  export declare const EFFORTS: readonly {
5
6
  id: Effort;
6
7
  label: string;
package/lib/mind.js CHANGED
@@ -16,15 +16,17 @@
16
16
  * lists as not reasoning offers none; Enso and a model the catalog does not
17
17
  * describe offer all three.
18
18
  */
19
+ import { phrase } from '@hanzo/i18n';
19
20
  import { parseModels } from '@hanzo/ui/models/catalog';
20
21
  import { useEffect, useState, useSyncExternalStore } from 'react';
21
22
  import { Refusal } from './api/call.js';
22
23
  import { ENSO, models as read } from './api/models.js';
23
24
  import { usePick } from './pick.js';
25
+ /** The efforts, in English and marked (`phrase`): the composer draws each word through `t`. */
24
26
  export const EFFORTS = [
25
- { id: 'low', label: 'Low', note: 'A quick pass' },
26
- { id: 'medium', label: 'Medium', note: 'Thinks before it answers' },
27
- { id: 'high', label: 'High', note: 'Slow and careful, for hard problems' },
27
+ { id: 'low', label: phrase('Low'), note: phrase('A quick pass') },
28
+ { id: 'medium', label: phrase('Medium'), note: phrase('Thinks before it answers') },
29
+ { id: 'high', label: phrase('High'), note: phrase('Slow and careful, for hard problems') },
28
30
  ];
29
31
  export const FIRST = { model: ENSO, effort: 'medium' };
30
32
  /** What Free answers on: the catalog's free lane, Enso's first. `free` is an alias of it. */
package/lib/nav.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type Say } from '@hanzo/i18n/react';
1
2
  import type { RailLink } from '@hanzo/ui/chat';
2
3
  import type { Host } from './host.tsx';
3
4
  /** A run of places under one quiet label; '' draws none. */
@@ -8,20 +9,20 @@ export interface Group {
8
9
  }
9
10
  /** The id of the row that starts a new run: a rail with a New control of its own draws that instead. */
10
11
  export declare const NEW = "new";
11
- export declare function nav(host: Host, go: (path: string) => void): Group[];
12
+ export declare function nav(host: Host, go: (path: string) => void, t?: Say): Group[];
12
13
  /** Dev's own places: what a host whose rail holds the rest (Library, Scheduled tasks, Customize) lists in Dev's sidebar. */
13
- export declare const own: (host: Host, go: (path: string) => void) => Group[];
14
+ export declare const own: (host: Host, go: (path: string) => void, t?: Say) => Group[];
14
15
  /**
15
16
  * Settings' sections as a host's sidebar lists them: one group per part, a
16
17
  * part's label drawn only where it is not its first row's name (Code leads
17
18
  * Code's part, so the part is not labelled twice).
18
19
  */
19
- export declare function sections(host: Host, go: (path: string) => void): Group[];
20
+ export declare function sections(host: Host, go: (path: string) => void, t?: Say): Group[];
20
21
  /** Customize's tabs as a host's sidebar lists them, Agents first; an agent's profile lights Agents. */
21
- export declare function tabs(host: Host, go: (path: string) => void): Group[];
22
+ export declare function tabs(host: Host, go: (path: string) => void, t?: Say): Group[];
22
23
  /** Fired on `window` once the sidebar's Upload has kept its files, for the Library to read again. */
23
24
  export declare const KEPT = "hanzo:library:kept";
24
25
  /** The Library's sidebar: Upload, then every kind it narrows to, the one in the address lit. */
25
- export declare function library(host: Host, go: (path: string) => void): Group[];
26
+ export declare function library(host: Host, go: (path: string) => void, t?: Say): Group[];
26
27
  /** Scheduled tasks' sidebar: New task, then each of the person's tasks, the one open lit. Read only while `on`. */
27
28
  export declare function useScheduled(host: Host, go: (path: string) => void, on: boolean): Group[];
package/lib/nav.js CHANGED
@@ -8,8 +8,13 @@ import { jsx as _jsx } from "react/jsx-runtime";
8
8
  *
9
9
  * Every row moves the builder through `go` with the address `path` writes, so
10
10
  * where a place lives is said here and in route.ts and nowhere else.
11
+ *
12
+ * The rows' words are the reader's language when the caller hands in its
13
+ * @hanzo/i18n `t` (English without one); a task's name is the person's own.
11
14
  */
12
15
  import { AudioLines, Bell, BookOpen, Bot, Box, Brain, CircleDot, Clock, Code, Container, Cpu, CreditCard, File, FileText, FolderGit2, Gauge, Image, Key, LayoutTemplate, Library, Plug, Plus, Presentation, Puzzle, Settings, Shield, SlidersHorizontal, Sparkles, SquarePen, Upload, User, Users, Video } from '@hanzogui/lucide-icons-2';
16
+ import { phrase } from '@hanzo/i18n';
17
+ import { useI18n } from '@hanzo/i18n/react';
13
18
  import { useMemo } from 'react';
14
19
  import { automations } from './api/auto.js';
15
20
  import { KINDS, upload } from './api/library.js';
@@ -17,10 +22,11 @@ import { pinBoard } from './choice.js';
17
22
  import { useRead } from './data.js';
18
23
  import { DOCS } from './foot.js';
19
24
  import { path, route, TABS } from './route.js';
25
+ import { english } from './say.js';
20
26
  import { GROUPS, heading, LIST } from './settings/list.js';
21
27
  /** The id of the row that starts a new run: a rail with a New control of its own draws that instead. */
22
28
  export const NEW = 'new';
23
- export function nav(host, go) {
29
+ export function nav(host, go, t = english) {
24
30
  const r = route(host.path);
25
31
  const screen = r.kind === 'screen' ? r.screen : '';
26
32
  const section = r.kind === 'settings' ? r.section : '';
@@ -28,20 +34,20 @@ export function nav(host, go) {
28
34
  return [
29
35
  {
30
36
  id: 'work',
31
- label: 'Work',
37
+ label: t('Work'),
32
38
  links: [
33
- { id: NEW, label: 'New run', icon: _jsx(SquarePen, { size: 16 }), onPress: to(''), active: r.kind === 'new' },
39
+ { id: NEW, label: t('New run'), icon: _jsx(SquarePen, { size: 16 }), onPress: to(''), active: r.kind === 'new' },
34
40
  // A project is a repository: its list, linking one from GitHub, and one's workspace.
35
41
  {
36
42
  id: 'projects',
37
- label: 'Projects',
43
+ label: t('Projects'),
38
44
  icon: _jsx(FolderGit2, { size: 16 }),
39
45
  onPress: to(path({ kind: 'screen', screen: 'projects' })),
40
46
  active: screen === 'projects' || screen === 'sync' || r.kind === 'repo',
41
47
  },
42
48
  {
43
49
  id: 'issues',
44
- label: 'Issues',
50
+ label: t('Issues'),
45
51
  icon: _jsx(CircleDot, { size: 16 }),
46
52
  onPress: () => {
47
53
  pinBoard(host.org, '');
@@ -49,61 +55,61 @@ export function nav(host, go) {
49
55
  },
50
56
  active: screen === 'issues',
51
57
  },
52
- { id: 'templates', label: 'Templates', icon: _jsx(LayoutTemplate, { size: 16 }), onPress: to(path({ kind: 'screen', screen: 'templates' })), active: screen === 'templates' },
58
+ { id: 'templates', label: t('Templates'), icon: _jsx(LayoutTemplate, { size: 16 }), onPress: to(path({ kind: 'screen', screen: 'templates' })), active: screen === 'templates' },
53
59
  ],
54
60
  },
55
61
  {
56
62
  id: 'make',
57
- label: 'Make',
63
+ label: t('Make'),
58
64
  links: [
59
- { id: 'artifacts', label: 'Library', icon: _jsx(Library, { size: 16 }), onPress: to(path({ kind: 'screen', screen: 'artifacts' })), active: screen === 'artifacts' },
60
- { id: 'automations', label: 'Scheduled tasks', icon: _jsx(Clock, { size: 16 }), onPress: to(path({ kind: 'screen', screen: 'automations' })), active: screen === 'automations' || r.kind === 'automation' },
65
+ { id: 'artifacts', label: t('Library'), icon: _jsx(Library, { size: 16 }), onPress: to(path({ kind: 'screen', screen: 'artifacts' })), active: screen === 'artifacts' },
66
+ { id: 'automations', label: t('Scheduled tasks'), icon: _jsx(Clock, { size: 16 }), onPress: to(path({ kind: 'screen', screen: 'automations' })), active: screen === 'automations' || r.kind === 'automation' },
61
67
  ],
62
68
  },
63
69
  {
64
70
  id: 'run',
65
- label: 'Run',
71
+ label: t('Run'),
66
72
  links: [
67
- { id: 'machines', label: 'Machines', icon: _jsx(Cpu, { size: 16 }), onPress: to(path({ kind: 'settings', section: 'machines' })), active: section === 'machines' },
68
- { id: 'environments', label: 'Environments', icon: _jsx(Container, { size: 16 }), onPress: to(path({ kind: 'settings', section: 'environments' })), active: section === 'environments' },
73
+ { id: 'machines', label: t('Machines'), icon: _jsx(Cpu, { size: 16 }), onPress: to(path({ kind: 'settings', section: 'machines' })), active: section === 'machines' },
74
+ { id: 'environments', label: t('Environments'), icon: _jsx(Container, { size: 16 }), onPress: to(path({ kind: 'settings', section: 'environments' })), active: section === 'environments' },
69
75
  ],
70
76
  },
71
77
  {
72
78
  id: 'setup',
73
- label: 'Setup',
79
+ label: t('Setup'),
74
80
  links: [
75
- { id: 'customize', label: 'Customize', icon: _jsx(SlidersHorizontal, { size: 16 }), onPress: to(path({ kind: 'customize', tab: 'agents' })), active: r.kind === 'customize' || r.kind === 'agent' || screen === 'mcp' },
76
- { id: 'docs', label: 'Docs', icon: _jsx(BookOpen, { size: 16 }), onPress: () => window.open(DOCS, '_blank', 'noopener,noreferrer') },
81
+ { id: 'customize', label: t('Customize'), icon: _jsx(SlidersHorizontal, { size: 16 }), onPress: to(path({ kind: 'customize', tab: 'agents' })), active: r.kind === 'customize' || r.kind === 'agent' || screen === 'mcp' },
82
+ { id: 'docs', label: t('Docs'), icon: _jsx(BookOpen, { size: 16 }), onPress: () => window.open(DOCS, '_blank', 'noopener,noreferrer') },
77
83
  ],
78
84
  },
79
85
  ];
80
86
  }
81
87
  /** Dev's own places: what a host whose rail holds the rest (Library, Scheduled tasks, Customize) lists in Dev's sidebar. */
82
- export const own = (host, go) => nav(host, go).filter((g) => g.id === 'work');
88
+ export const own = (host, go, t = english) => nav(host, go, t).filter((g) => g.id === 'work');
83
89
  /**
84
90
  * Settings' sections as a host's sidebar lists them: one group per part, a
85
91
  * part's label drawn only where it is not its first row's name (Code leads
86
92
  * Code's part, so the part is not labelled twice).
87
93
  */
88
- export function sections(host, go) {
94
+ export function sections(host, go, t = english) {
89
95
  const r = route(host.path);
90
96
  const at = r.kind === 'settings' ? r.section : '';
91
97
  return GROUPS.map((g) => {
92
98
  const rows = LIST.filter((e) => e.group === g);
93
99
  return {
94
100
  id: g,
95
- label: heading(g),
96
- links: rows.map((e) => ({ id: e.id, label: e.label, icon: SECTION_ICON[e.id], onPress: () => go(path({ kind: 'settings', section: e.id })), active: e.id === at })),
101
+ label: t(heading(g)),
102
+ links: rows.map((e) => ({ id: e.id, label: t(e.label), icon: SECTION_ICON[e.id], onPress: () => go(path({ kind: 'settings', section: e.id })), active: e.id === at })),
97
103
  };
98
104
  });
99
105
  }
100
106
  /** Customize's tabs as a host's sidebar lists them, Agents first; an agent's profile lights Agents. */
101
- export function tabs(host, go) {
107
+ export function tabs(host, go, t = english) {
102
108
  const r = route(host.path);
103
109
  const at = r.kind === 'customize' ? r.tab : r.kind === 'agent' ? 'agents' : '';
104
- return [{ id: 'customize', label: '', links: TABS.map((t) => ({ id: t, label: TAB_LABEL[t], icon: TAB_ICON[t], onPress: () => go(path({ kind: 'customize', tab: t })), active: t === at })) }];
110
+ return [{ id: 'customize', label: '', links: TABS.map((tab) => ({ id: tab, label: t(TAB_LABEL[tab]), icon: TAB_ICON[tab], onPress: () => go(path({ kind: 'customize', tab })), active: tab === at })) }];
105
111
  }
106
- const TAB_LABEL = { agents: 'Agents', skills: 'Skills', connectors: 'Connectors', plugins: 'Plugins' };
112
+ const TAB_LABEL = { agents: phrase('Agents'), skills: phrase('Skills'), connectors: phrase('Connectors'), plugins: phrase('Plugins') };
107
113
  const TAB_ICON = { agents: _jsx(Bot, { size: 16 }), skills: _jsx(BookOpen, { size: 16 }), connectors: _jsx(Plug, { size: 16 }), plugins: _jsx(Puzzle, { size: 16 }) };
108
114
  const SECTION_ICON = {
109
115
  general: _jsx(Settings, { size: 16 }),
@@ -135,7 +141,7 @@ const KIND_ICON = {
135
141
  /** Fired on `window` once the sidebar's Upload has kept its files, for the Library to read again. */
136
142
  export const KEPT = 'hanzo:library:kept';
137
143
  /** Asks for files and keeps each in the Library; a refusal is said in the page's own words. */
138
- function keep(host) {
144
+ function keep(host, say) {
139
145
  const input = document.createElement('input');
140
146
  input.type = 'file';
141
147
  input.multiple = true;
@@ -146,7 +152,7 @@ function keep(host) {
146
152
  await upload(t, { name: f.name, type: f.type, bytes: new Uint8Array(await f.arrayBuffer()) }, { source: 'chat' });
147
153
  }
148
154
  catch (e) {
149
- window.alert(e instanceof Error ? e.message : `${f.name} was not kept`);
155
+ window.alert(e instanceof Error ? e.message : say('{file} was not kept', { file: f.name }));
150
156
  }
151
157
  }
152
158
  window.dispatchEvent(new Event(KEPT));
@@ -154,17 +160,17 @@ function keep(host) {
154
160
  input.click();
155
161
  }
156
162
  /** The Library's sidebar: Upload, then every kind it narrows to, the one in the address lit. */
157
- export function library(host, go) {
163
+ export function library(host, go, t = english) {
158
164
  const r = route(host.path);
159
165
  const at = r.kind === 'screen' && r.screen === 'artifacts' ? (r.of ?? '') : null;
160
166
  return [
161
- { id: 'library-do', label: '', links: [{ id: 'upload', label: 'Upload', icon: _jsx(Upload, { size: 16 }), onPress: () => keep(host) }] },
167
+ { id: 'library-do', label: '', links: [{ id: 'upload', label: t('Upload'), icon: _jsx(Upload, { size: 16 }), onPress: () => keep(host, t) }] },
162
168
  {
163
169
  id: 'library-kinds',
164
170
  label: '',
165
171
  links: KINDS.map((k) => ({
166
172
  id: `kind-${k.id || 'all'}`,
167
- label: k.label,
173
+ label: t(k.label),
168
174
  icon: KIND_ICON[k.id],
169
175
  onPress: () => go(path({ kind: 'screen', screen: 'artifacts', ...(k.id ? { of: k.id } : {}) })),
170
176
  active: at === k.id,
@@ -174,6 +180,7 @@ export function library(host, go) {
174
180
  }
175
181
  /** Scheduled tasks' sidebar: New task, then each of the person's tasks, the one open lit. Read only while `on`. */
176
182
  export function useScheduled(host, go, on) {
183
+ const { t: say } = useI18n();
177
184
  const t = useMemo(() => ({ api: host.api, token: host.token, org: host.org }), [host.api, host.token, host.org]);
178
185
  const list = useRead(on && host.person ? () => automations(t) : null, [], [t, on, host.path]);
179
186
  const r = route(host.path);
@@ -182,12 +189,12 @@ export function useScheduled(host, go, on) {
182
189
  {
183
190
  id: 'scheduled-do',
184
191
  label: '',
185
- links: [{ id: 'new-task', label: 'New task', icon: _jsx(Plus, { size: 16 }), onPress: () => go(path({ kind: 'automation', id: 'new' })), active: at === 'new' }],
192
+ links: [{ id: 'new-task', label: say('New task'), icon: _jsx(Plus, { size: 16 }), onPress: () => go(path({ kind: 'automation', id: 'new' })), active: at === 'new' }],
186
193
  },
187
194
  {
188
195
  id: 'scheduled-tasks',
189
- label: list.value.length ? 'Tasks' : '',
190
- links: list.value.map((a) => ({ id: a.id, label: a.name || 'Untitled task', icon: _jsx(Clock, { size: 16 }), onPress: () => go(path({ kind: 'automation', id: a.id })), active: at === a.id })),
196
+ label: list.value.length ? say('Tasks') : '',
197
+ links: list.value.map((a) => ({ id: a.id, label: a.name || say('Untitled task'), icon: _jsx(Clock, { size: 16 }), onPress: () => go(path({ kind: 'automation', id: a.id })), active: at === a.id })),
191
198
  },
192
199
  ];
193
200
  }
package/lib/panel.d.ts CHANGED
@@ -20,7 +20,8 @@ export interface Suggestion {
20
20
  /**
21
21
  * The header's control for the tabs, while they are hidden: an icon among the
22
22
  * header's icons. Open, the strip's own toggle stands at the panel's end and
23
- * this one is not drawn, so the page carries one toggle at a time.
23
+ * this one is not drawn, so the page carries one toggle at a time. Its words
24
+ * are the reader's language; the key chord is an argument, as this platform writes it.
24
25
  */
25
26
  export declare function PanelToggle({ open, onToggle }: {
26
27
  open: boolean;