@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
@@ -1,26 +1,10 @@
1
- /**
2
- * The plugins the composer offers, from `+` › Plugins and by typing `@`: only
3
- * those that run for this person, each on Hanzo's own services.
4
- *
5
- * Work in a project the org has a project (GET /v1/projects)
6
- * Deep research signed in: web search, page reading and a Hanzo model, as the person
7
- * Sketch always: it draws in the page
8
- * Template Creator an org admin: only an admin writes the org's skills
9
- * Presentations Studio runs its `slides` template (GET /v1/studio/templates)
10
- * PDF signed in: read and verify in the page; create where Studio runs `document`
11
- *
12
- * What is there is read once per platform, org and sign-in, and kept for the page.
13
- *
14
- * Deep research IS the Research switch: its row turns `Gear.research` on and
15
- * off, and `prep` asks it when that is on. Every other plugin that prepares is on
16
- * while its id is in `Gear.plugins`; one that opens something runs when picked.
17
- */
18
1
  import { useEffect, useState } from 'react';
19
2
  import { templates } from '../api/studio.js';
20
3
  import { projects } from '../api/projects.js';
21
4
  import { administers } from '../claims.js';
22
5
  import { say } from '../customize/say.js';
23
6
  import { path } from '../route.js';
7
+ import { english } from '../say.js';
24
8
  import { pdf } from './pdf.js';
25
9
  import { project } from './project.js';
26
10
  import { designer } from './design.js';
@@ -72,10 +56,10 @@ export function usePlugins(t) {
72
56
  }
73
57
  /** Whether a plugin that prepares is on in this conversation. */
74
58
  const on = (p, g) => (p.id === deep.id ? g.research : g.plugins.includes(p.id));
75
- /** The `+` menu's Plugins shelf. */
76
- export function shelf(p, g, c) {
59
+ /** The `+` menu's Plugins shelf, in the words of the caller's `t` (a plugin's name and note are marked where it is written). */
60
+ export function shelf(p, g, c, t = english) {
77
61
  return {
78
- rows: p.plugins.map((x) => ({ id: x.id, label: x.name, note: x.note, icon: x.icon, on: x.prepare ? on(x, g.gear) : undefined })),
62
+ rows: p.plugins.map((x) => ({ id: x.id, label: t(x.name), note: t(x.note), icon: x.icon, on: x.prepare ? on(x, g.gear) : undefined })),
79
63
  pick: (id) => {
80
64
  const x = p.plugins.find((y) => y.id === id);
81
65
  if (!x)
@@ -89,9 +73,9 @@ export function shelf(p, g, c) {
89
73
  },
90
74
  loading: p.loading,
91
75
  error: p.error,
92
- search: 'Type to search plugins',
93
- empty: 'No plugins here',
94
- create: { label: 'Create plugin', onPress: () => c.go(BUILD) },
76
+ search: t('Type to search plugins'),
77
+ empty: t('No plugins here'),
78
+ create: { label: t('Create plugin'), onPress: () => c.go(BUILD) },
95
79
  };
96
80
  }
97
81
  /** The plugins this message asks, in the menu's order. */
@@ -118,15 +102,16 @@ export function merge(preps) {
118
102
  /**
119
103
  * Ask every plugin that is on what the message carries. A plugin that fails
120
104
  * fails the message, with its name and its reason: a report asked for and not
121
- * researched must not arrive looking researched.
105
+ * researched must not arrive looking researched. Its name is said in the
106
+ * caller's `t`; the reason is `say`'s.
122
107
  */
123
- export async function prep(plugins, g, turn, c, signal, denied = []) {
108
+ export async function prep(plugins, g, turn, c, signal, denied = [], t = english) {
124
109
  const preps = await Promise.all(active(plugins, g)
125
110
  .filter((p) => !denied.includes(p.id))
126
111
  .map((p) => p.prepare(turn, c, signal).catch((e) => {
127
112
  if (signal.aborted)
128
113
  throw e;
129
- throw new Error(`${p.name}: ${say(e)}`);
114
+ throw new Error(t('{plugin}: {reason}', { plugin: t(p.name), reason: say(e) }));
130
115
  })));
131
116
  return merge(preps);
132
117
  }
@@ -7,6 +7,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
7
7
  * `document` template writes the ask up and renders it as a PDF, and the answer
8
8
  * is the file.
9
9
  */
10
+ import { phrase } from '@hanzo/i18n';
10
11
  import { FileText } from '@hanzogui/lucide-icons-2';
11
12
  import { json } from '../api/chat.js';
12
13
  import { make } from '../api/studio.js';
@@ -75,7 +76,7 @@ export function pdf(makes) {
75
76
  return {
76
77
  id: 'pdf',
77
78
  name: 'PDF',
78
- note: makes ? 'Read, create and verify PDFs' : 'Read and verify PDFs',
79
+ note: makes ? phrase('Read, create and verify PDFs') : phrase('Read and verify PDFs'),
79
80
  icon: _jsx(FileText, { size: 16 }),
80
81
  prepare: (turn, c, signal) => (turn.files.some(isPdf) || !makes ? study(turn.files) : create(turn, c.target, signal)),
81
82
  };
@@ -1,12 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /**
3
- * Work in a project: pick one of the org's projects (GET /v1/projects). A
3
+ * Work in a project: pick one of the org's projects (GET /v1/project). A
4
4
  * conversation does not belong to a project — work in one is a Dev run tagged
5
5
  * with it — so a message in the composer starts a Dev run on the picked project
6
6
  * (POST /v1/agent/coding, the person's saved model) and opens it; with nothing
7
7
  * written, the project's workspace opens to start one there.
8
8
  */
9
9
  import { SizableText, XStack, YStack } from '@hanzo/gui';
10
+ import { phrase } from '@hanzo/i18n';
11
+ import { useI18n } from '@hanzo/i18n/react';
10
12
  import { Folder } from '@hanzogui/lucide-icons-2';
11
13
  import { Button, Dialog, DialogContent, DialogDescription, DialogTitle, Input } from '@hanzo/ui';
12
14
  import { useEffect, useState } from 'react';
@@ -22,6 +24,7 @@ export function find(list, q) {
22
24
  });
23
25
  }
24
26
  function Picker({ c }) {
27
+ const { t } = useI18n();
25
28
  const [list, setList] = useState(null);
26
29
  const [q, setQ] = useState('');
27
30
  const [note, setNote] = useState('');
@@ -54,12 +57,12 @@ function Picker({ c }) {
54
57
  }
55
58
  };
56
59
  const shown = list ? find(list, q) : [];
57
- return (_jsx(Dialog, { open: true, onOpenChange: (o) => !o && c.show(null), children: _jsxs(DialogContent, { maxW: 520, width: "100%", children: [_jsx(DialogTitle, { children: "Work in a project" }), _jsx(DialogDescription, { children: ask ? 'Your message starts a Dev run on the project you pick.' : 'Open a project to start working in it.' }), _jsx(Input, { value: q, onChangeText: setQ, placeholder: "Search projects", "aria-label": "Search projects", autoFocus: true }), _jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", maxH: 360, style: { overflowY: 'auto' }, children: [!list && !note ? (_jsx(SizableText, { size: "$2", color: "$soft", p: "$3", children: "Reading your projects\u2026" })) : null, list && !shown.length ? (_jsx(SizableText, { size: "$2", color: "$soft", p: "$3", children: q ? 'Nothing matches' : 'No projects yet' })) : null, shown.map((p, i) => (_jsxs(XStack, { render: "button", "aria-label": ask ? `Start a run on ${p.name}` : `Open ${p.name}`, disabled: Boolean(busy), onPress: () => void pick(p), items: "center", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", cursor: busy ? 'default' : 'pointer', hoverStyle: { bg: '$hover' }, style: { textAlign: 'left' }, children: [_jsx(Folder, { size: 16, opacity: 0.7 }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: busy === p.slug ? 'Starting…' : p.name }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: p.repo ? address(p.repo) : 'No repository yet' })] })] }, p.slug)))] }), note ? (_jsx(SizableText, { size: "$2", color: "$soft", role: "status", children: note })) : null, _jsx(XStack, { justify: "flex-end", children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => c.show(null), children: "Cancel" }) })] }) }));
60
+ return (_jsx(Dialog, { open: true, onOpenChange: (o) => !o && c.show(null), children: _jsxs(DialogContent, { maxW: 520, width: "100%", children: [_jsx(DialogTitle, { children: t('Work in a project') }), _jsx(DialogDescription, { children: ask ? t('Your message starts a Dev run on the project you pick.') : t('Open a project to start working in it.') }), _jsx(Input, { value: q, onChangeText: setQ, placeholder: t('Search projects'), "aria-label": t('Search projects'), autoFocus: true }), _jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", maxH: 360, style: { overflowY: 'auto' }, children: [!list && !note ? (_jsx(SizableText, { size: "$2", color: "$soft", p: "$3", children: t('Reading your projects…') })) : null, list && !shown.length ? (_jsx(SizableText, { size: "$2", color: "$soft", p: "$3", children: q ? t('Nothing matches') : t('No projects yet') })) : null, shown.map((p, i) => (_jsxs(XStack, { render: "button", "aria-label": ask ? t('Start a run on {name}', { name: p.name }) : t('Open {name}', { name: p.name }), disabled: Boolean(busy), onPress: () => void pick(p), items: "center", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", cursor: busy ? 'default' : 'pointer', hoverStyle: { bg: '$hover' }, "$platform-web": { textAlign: 'start' }, children: [_jsx(Folder, { size: 16, opacity: 0.7 }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: busy === p.slug ? t('Starting…') : p.name }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: p.repo ? address(p.repo) : t('No repository yet') })] })] }, p.slug)))] }), note ? (_jsx(SizableText, { size: "$2", color: "$soft", role: "status", children: note })) : null, _jsx(XStack, { justify: "flex-end", children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => c.show(null), children: t('Cancel') }) })] }) }));
58
61
  }
59
62
  export const project = {
60
63
  id: 'project',
61
- name: 'Work in a project',
62
- note: 'Start a Dev run in a project',
64
+ name: phrase('Work in a project'),
65
+ note: phrase('Start a Dev run in a project'),
63
66
  icon: _jsx(Folder, { size: 16 }),
64
67
  open: (c) => c.show(_jsx(Picker, { c: c })),
65
68
  };
@@ -13,6 +13,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
13
13
  * POST /v1/websearch {q} → {results: [{url, title, content}]}
14
14
  * POST /v1/crawl {url} → {success, data: {markdown}}; a page it could not read is success:false
15
15
  */
16
+ import { phrase } from '@hanzo/i18n';
16
17
  import { Telescope } from '@hanzogui/lucide-icons-2';
17
18
  import { call } from '../api/call.js';
18
19
  import { ask, json } from '../api/chat.js';
@@ -139,8 +140,8 @@ export async function research(turn, t, signal, now = new Date()) {
139
140
  }
140
141
  export const deep = {
141
142
  id: 'research',
142
- name: 'Deep research',
143
- note: 'Get a detailed, cited report',
143
+ name: phrase('Deep research'),
144
+ note: phrase('Get a detailed, cited report'),
144
145
  icon: _jsx(Telescope, { size: 16 }),
145
146
  prepare: (turn, c, signal) => research(turn, c.target, signal),
146
147
  };
@@ -4,6 +4,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
4
  * paperclip attaches a file. Nothing leaves the page until the message goes.
5
5
  */
6
6
  import { XStack, YStack } from '@hanzo/gui';
7
+ import { phrase } from '@hanzo/i18n';
8
+ import { useI18n } from '@hanzo/i18n/react';
7
9
  import { Signature } from '@hanzogui/lucide-icons-2';
8
10
  import { Button, Dialog, DialogContent, DialogDescription, DialogTitle } from '@hanzo/ui';
9
11
  import { useEffect, useRef, useState } from 'react';
@@ -33,6 +35,7 @@ export function paint(g, strokes) {
33
35
  }
34
36
  }
35
37
  function Pad({ c }) {
38
+ const { t } = useI18n();
36
39
  const canvas = useRef(null);
37
40
  const strokes = useRef([]);
38
41
  const drawing = useRef(false);
@@ -87,12 +90,12 @@ function Pad({ c }) {
87
90
  c.show(null);
88
91
  }, 'image/png');
89
92
  };
90
- return (_jsx(Dialog, { open: true, onOpenChange: (o) => !o && c.show(null), children: _jsxs(DialogContent, { maxW: 720, width: "100%", children: [_jsx(DialogTitle, { children: "Sketch" }), _jsx(DialogDescription, { children: "Draw, then attach it to your message." }), _jsx(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: 8, overflow: "hidden", children: _jsx("canvas", { ref: canvas, width: W, height: H, "aria-label": "Sketch pad", "data-slot": "sketch-pad", onPointerDown: down, onPointerMove: move, onPointerUp: up, onPointerCancel: up, style: { display: 'block', width: '100%', aspectRatio: `${W} / ${H}`, touchAction: 'none', cursor: 'crosshair', background: PAPER } }) }), _jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(Button, { size: "sm", variant: "ghost", disabled: !count, onPress: undo, children: "Undo" }), _jsx(Button, { size: "sm", variant: "ghost", disabled: !count, onPress: clear, children: "Clear" }), _jsx(XStack, { flex: 1 }), _jsx(Button, { size: "sm", variant: "ghost", onPress: () => c.show(null), children: "Cancel" }), _jsx(Button, { size: "sm", disabled: !count || busy, onPress: attach, children: "Attach" })] })] }) }));
93
+ return (_jsx(Dialog, { open: true, onOpenChange: (o) => !o && c.show(null), children: _jsxs(DialogContent, { maxW: 720, width: "100%", children: [_jsx(DialogTitle, { children: t('Sketch') }), _jsx(DialogDescription, { children: t('Draw, then attach it to your message.') }), _jsx(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: 8, overflow: "hidden", children: _jsx("canvas", { ref: canvas, width: W, height: H, "aria-label": t('Sketch pad'), "data-slot": "sketch-pad", onPointerDown: down, onPointerMove: move, onPointerUp: up, onPointerCancel: up, style: { display: 'block', width: '100%', aspectRatio: `${W} / ${H}`, touchAction: 'none', cursor: 'crosshair', background: PAPER } }) }), _jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(Button, { size: "sm", variant: "ghost", disabled: !count, onPress: undo, children: t('Undo') }), _jsx(Button, { size: "sm", variant: "ghost", disabled: !count, onPress: clear, children: t('Clear') }), _jsx(XStack, { flex: 1 }), _jsx(Button, { size: "sm", variant: "ghost", onPress: () => c.show(null), children: t('Cancel') }), _jsx(Button, { size: "sm", disabled: !count || busy, onPress: attach, children: t('Attach') })] })] }) }));
91
94
  }
92
95
  export const sketch = {
93
96
  id: 'sketch',
94
- name: 'Sketch',
95
- note: 'Draw and attach an image',
97
+ name: phrase('Sketch'),
98
+ note: phrase('Draw and attach an image'),
96
99
  icon: _jsx(Signature, { size: 16 }),
97
100
  open: (c) => c.show(_jsx(Pad, { c: c })),
98
101
  };
@@ -5,6 +5,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
5
5
  * the file, and the run in Studio, where it is edited. A later message in the
6
6
  * same conversation revises the last deck rather than starting over.
7
7
  */
8
+ import { phrase } from '@hanzo/i18n';
8
9
  import { Presentation } from '@hanzogui/lucide-icons-2';
9
10
  import { json } from '../api/chat.js';
10
11
  import { make } from '../api/studio.js';
@@ -47,8 +48,8 @@ export async function deck(turn, t, signal) {
47
48
  }
48
49
  export const slides = {
49
50
  id: 'slides',
50
- name: 'Presentations',
51
- note: 'Create and edit decks',
51
+ name: phrase('Presentations'),
52
+ note: phrase('Create and edit decks'),
52
53
  icon: _jsx(Presentation, { size: 16 }),
53
54
  prepare: (turn, c, signal) => deck(turn, c.target, signal),
54
55
  };
@@ -7,6 +7,8 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
7
7
  * it is saved. Writing a skill takes an org admin, so only an admin is offered it.
8
8
  */
9
9
  import { SizableText, XStack, YStack } from '@hanzo/gui';
10
+ import { phrase } from '@hanzo/i18n';
11
+ import { useI18n } from '@hanzo/i18n/react';
10
12
  import { LayoutTemplate } from '@hanzogui/lucide-icons-2';
11
13
  import { Button, Dialog, DialogContent, DialogDescription, DialogTitle, Input, Textarea } from '@hanzo/ui';
12
14
  import { useEffect, useState } from 'react';
@@ -44,6 +46,7 @@ export function document(d) {
44
46
  return `---\nname: ${d.name}\ndescription: ${JSON.stringify(d.description.replace(/\s+/g, ' ').trim())}\n---\n\n${d.body.trim()}\n`;
45
47
  }
46
48
  function Creator({ c }) {
49
+ const { t } = useI18n();
47
50
  const from = c.draft.trim();
48
51
  const [d, setD] = useState({ name: from ? slug(from.split(/\s+/).slice(0, 6).join(' ')) : '', description: first(from), body: from });
49
52
  const [drafting, setDrafting] = useState(Boolean(from));
@@ -81,12 +84,12 @@ function Creator({ c }) {
81
84
  setBusy(false);
82
85
  }
83
86
  };
84
- return (_jsx(Dialog, { open: true, onOpenChange: (o) => !o && c.show(null), children: _jsxs(DialogContent, { maxW: 640, width: "100%", children: [_jsx(DialogTitle, { children: "Template Creator" }), _jsx(DialogDescription, { children: saved ? `Saved as ${saved}. It is under + › Skills.` : drafting ? 'Hanzo is drafting the template from your message…' : 'A reusable template, saved as one of your skills.' }), saved ? null : (_jsxs(YStack, { gap: "$3", children: [_jsx(Input, { value: d.name, onChangeText: (name) => setD({ ...d, name }), placeholder: "weekly-update", "aria-label": "Name", autoCapitalize: "none" }), _jsx(Input, { value: d.description, onChangeText: (description) => setD({ ...d, description }), placeholder: "When to use it", "aria-label": "Description" }), _jsx(Textarea, { value: d.body, onChangeText: (body) => setD({ ...d, body }), placeholder: '# Weekly update\n\nInputs: {{team}}, {{week}}\n\n1. …', "aria-label": "Template", rows: 12, style: { maxHeight: '40vh', overflowY: 'auto' } })] })), note ? (_jsx(SizableText, { size: "$2", color: "$soft", role: "status", children: note })) : null, _jsx(XStack, { gap: "$2", justify: "flex-end", children: saved ? (_jsx(Button, { size: "sm", onPress: () => c.show(null), children: "Done" })) : (_jsxs(_Fragment, { children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => c.show(null), children: "Cancel" }), _jsx(Button, { size: "sm", disabled: busy || drafting || !d.name || !d.body.trim(), onPress: () => void save(), children: "Save" })] })) })] }) }));
87
+ return (_jsx(Dialog, { open: true, onOpenChange: (o) => !o && c.show(null), children: _jsxs(DialogContent, { maxW: 640, width: "100%", children: [_jsx(DialogTitle, { children: t('Template Creator') }), _jsx(DialogDescription, { children: saved ? t('Saved as {name}. It is under + › Skills.', { name: saved }) : drafting ? t('Hanzo is drafting the template from your message…') : t('A reusable template, saved as one of your skills.') }), saved ? null : (_jsxs(YStack, { gap: "$3", children: [_jsx(Input, { value: d.name, onChangeText: (name) => setD({ ...d, name }), placeholder: "weekly-update", "aria-label": t('Name'), autoCapitalize: "none" }), _jsx(Input, { value: d.description, onChangeText: (description) => setD({ ...d, description }), placeholder: t('When to use it'), "aria-label": t('Description') }), _jsx(Textarea, { value: d.body, onChangeText: (body) => setD({ ...d, body }), placeholder: '# Weekly update\n\nInputs: {{team}}, {{week}}\n\n1. …', "aria-label": t('Template'), rows: 12, maxH: "40vh", overflowY: "auto" })] })), note ? (_jsx(SizableText, { size: "$2", color: "$soft", role: "status", children: note })) : null, _jsx(XStack, { gap: "$2", justify: "flex-end", children: saved ? (_jsx(Button, { size: "sm", onPress: () => c.show(null), children: t('Done') })) : (_jsxs(_Fragment, { children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => c.show(null), children: t('Cancel') }), _jsx(Button, { size: "sm", disabled: busy || drafting || !d.name || !d.body.trim(), onPress: () => void save(), children: t('Save') })] })) })] }) }));
85
88
  }
86
89
  export const template = {
87
90
  id: 'template',
88
- name: 'Template Creator',
89
- note: 'Templates saved as skills',
91
+ name: phrase('Template Creator'),
92
+ note: phrase('Templates saved as skills'),
90
93
  icon: _jsx(LayoutTemplate, { size: 16 }),
91
94
  open: (c) => c.show(_jsx(Creator, { c: c })),
92
95
  };
package/lib/plus.js CHANGED
@@ -8,39 +8,51 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
8
8
  *
9
9
  * On a laptop each shelf opens beside the menu; on a phone it is a page of the
10
10
  * same menu, with Back.
11
+ *
12
+ * The menu's own words are the reader's language (@hanzo/i18n `t`); what a
13
+ * shelf holds — its rows, its links, its search and empty words — is the
14
+ * surface's, drawn as given.
11
15
  */
12
16
  import { SizableText, useMedia, XStack } from '@hanzo/gui';
17
+ import { phrase } from '@hanzo/i18n';
18
+ import { useI18n } from '@hanzo/i18n/react';
13
19
  import { Blocks, Bot, Briefcase, Brain, Monitor, Palette, Paperclip, Plug, Plus as PlusIcon, ScrollText, Telescope, } from '@hanzogui/lucide-icons-2';
14
20
  import { DropdownMenu, DropdownMenuBack, DropdownMenuContent, DropdownMenuEmpty, DropdownMenuItem, DropdownMenuLoading, DropdownMenuPage, DropdownMenuSearch, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from '@hanzo/ui';
15
21
  import { Check } from '@hanzogui/lucide-icons-2';
16
22
  import { useMemo, useState } from 'react';
17
23
  const ICON = 16;
18
- /** The shelves, in the menu's order, with their names and marks. */
24
+ /**
25
+ * The shelves, in the menu's order, with their names and marks, and the three
26
+ * sentences a shelf says of itself: its search's name, while it reads, and
27
+ * when it holds nothing. Whole sentences, so a language puts the noun where it goes.
28
+ */
19
29
  const SHELVES = [
20
- { key: 'agents', label: 'Agents', icon: _jsx(Bot, { size: ICON }) },
21
- { key: 'skills', label: 'Skills', icon: _jsx(ScrollText, { size: ICON }) },
22
- { key: 'connectors', label: 'Connectors', icon: _jsx(Blocks, { size: ICON }) },
23
- { key: 'design', label: 'Design system', icon: _jsx(Palette, { size: ICON }) },
24
- { key: 'plugins', label: 'Plugins', icon: _jsx(Plug, { size: ICON }) },
25
- { key: 'devices', label: 'Devices', icon: _jsx(Monitor, { size: ICON }) },
30
+ { key: 'agents', label: phrase('Agents'), search: phrase('Search agents'), reading: phrase('Reading agents'), none: phrase('No agents yet'), icon: _jsx(Bot, { size: ICON }) },
31
+ { key: 'skills', label: phrase('Skills'), search: phrase('Search skills'), reading: phrase('Reading skills'), none: phrase('No skills yet'), icon: _jsx(ScrollText, { size: ICON }) },
32
+ { key: 'connectors', label: phrase('Connectors'), search: phrase('Search connectors'), reading: phrase('Reading connectors'), none: phrase('No connectors yet'), icon: _jsx(Blocks, { size: ICON }) },
33
+ { key: 'design', label: phrase('Design system'), search: phrase('Search design system'), reading: phrase('Reading design system'), none: phrase('No design system yet'), icon: _jsx(Palette, { size: ICON }) },
34
+ { key: 'plugins', label: phrase('Plugins'), search: phrase('Search plugins'), reading: phrase('Reading plugins'), none: phrase('No plugins yet'), icon: _jsx(Plug, { size: ICON }) },
35
+ { key: 'devices', label: phrase('Devices'), search: phrase('Search devices'), reading: phrase('Reading devices'), none: phrase('No devices yet'), icon: _jsx(Monitor, { size: ICON }) },
26
36
  ];
27
37
  /** A row's anatomy: its mark, its label, a dim note, and a check when it is on. */
28
38
  function Line({ icon, label, note, on, tail }) {
29
39
  return (_jsxs(XStack, { flex: 1, minW: 0, items: "center", gap: 10, children: [icon ? (_jsx(XStack, { width: ICON, height: ICON, items: "center", justify: "center", shrink: 0, "aria-hidden": true, children: icon })) : null, _jsx(SizableText, { fontSize: 14, color: "$ink", numberOfLines: 1, shrink: 0, children: label }), note ? (_jsx(SizableText, { fontSize: 13, color: "$hint", numberOfLines: 1, flex: 1, minW: 0, children: note })) : (_jsx(XStack, { flex: 1 })), tail, on ? _jsx(Check, { size: ICON, color: "var(--primary)", "aria-hidden": true }) : null] }));
30
40
  }
31
41
  /** A shelf's rows, its status, then its ways to more. */
32
- function Contents({ shelf, name, close }) {
42
+ function Contents({ shelf, words, close }) {
43
+ const { t } = useI18n();
33
44
  const [q, setQ] = useState('');
34
45
  const needle = q.trim().toLowerCase();
35
46
  const rows = needle ? shelf.rows.filter((r) => `${r.label} ${r.note ?? ''}`.toLowerCase().includes(needle)) : shelf.rows;
36
47
  const links = [shelf.create, shelf.manage, shelf.browse].filter((l) => Boolean(l));
37
- return (_jsxs(_Fragment, { children: [shelf.search ? _jsx(DropdownMenuSearch, { value: q, onValueChange: setQ, placeholder: shelf.search, "aria-label": `Search ${name.toLowerCase()}` }) : null, shelf.loading && !shelf.rows.length ? _jsxs(DropdownMenuLoading, { children: ["Reading ", name.toLowerCase()] }) : null, shelf.error ? _jsx(DropdownMenuEmpty, { children: shelf.error }) : null, !shelf.loading && !shelf.error && !rows.length ? _jsx(DropdownMenuEmpty, { children: needle ? 'Nothing matches' : (shelf.empty ?? `No ${name.toLowerCase()} yet`) }) : null, rows.map((r) => (_jsx(DropdownMenuItem, { preventCloseOnSelect: true, onSelect: () => shelf.pick(r.id), "aria-checked": r.on ?? false, "data-slot": "plus-row", children: _jsx(Line, { icon: r.icon, label: r.label, note: r.note, on: r.on }) }, r.id))), links.length ? _jsx(DropdownMenuSeparator, {}) : null, links.map((l, i) => (_jsx(DropdownMenuItem, { onSelect: () => {
48
+ return (_jsxs(_Fragment, { children: [shelf.search ? _jsx(DropdownMenuSearch, { value: q, onValueChange: setQ, placeholder: shelf.search, "aria-label": t(words.search) }) : null, shelf.loading && !shelf.rows.length ? _jsx(DropdownMenuLoading, { children: t(words.reading) }) : null, shelf.error ? _jsx(DropdownMenuEmpty, { children: shelf.error }) : null, !shelf.loading && !shelf.error && !rows.length ? _jsx(DropdownMenuEmpty, { children: needle ? t('Nothing matches') : (shelf.empty ?? t(words.none)) }) : null, rows.map((r) => (_jsx(DropdownMenuItem, { preventCloseOnSelect: true, onSelect: () => shelf.pick(r.id), "aria-checked": r.on ?? false, "data-slot": "plus-row", children: _jsx(Line, { icon: r.icon, label: r.label, note: r.note, on: r.on }) }, r.id))), links.length ? _jsx(DropdownMenuSeparator, {}) : null, links.map((l, i) => (_jsx(DropdownMenuItem, { onSelect: () => {
38
49
  close();
39
50
  l.onPress();
40
51
  }, children: _jsx(Line, { icon: l === shelf.manage ? _jsx(Briefcase, { size: ICON }) : _jsx(PlusIcon, { size: ICON }), label: l.label }) }, l.label)))] }));
41
52
  }
42
53
  /** The `+` and its menu. */
43
54
  export function Plus(p) {
55
+ const { t } = useI18n();
44
56
  const media = useMedia();
45
57
  const wide = media.sm;
46
58
  const [open, setOpen] = useState(false);
@@ -49,12 +61,12 @@ export function Plus(p) {
49
61
  const close = () => setOpen(false);
50
62
  const shelves = useMemo(() => SHELVES.filter((s) => p[s.key]), [p]);
51
63
  const switches = [
52
- p.research ? { key: 'research', label: 'Research', icon: _jsx(Telescope, { size: ICON }), s: p.research } : null,
53
- p.memory ? { key: 'memory', label: 'Memory', icon: _jsx(Brain, { size: ICON }), s: p.memory } : null,
64
+ p.research ? { key: 'research', label: t('Research'), icon: _jsx(Telescope, { size: ICON }), s: p.research } : null,
65
+ p.memory ? { key: 'memory', label: t('Memory'), icon: _jsx(Brain, { size: ICON }), s: p.memory } : null,
54
66
  ].filter((x) => x !== null);
55
67
  const main = (_jsxs(_Fragment, { children: [p.upload ? (_jsx(DropdownMenuItem, { "data-slot": "plus-upload", onSelect: () => {
56
68
  close();
57
69
  p.upload?.();
58
- }, children: _jsx(Line, { icon: _jsx(Paperclip, { size: ICON }), label: "Upload or add files", tail: _jsx(DropdownMenuShortcut, { children: "Ctrl+U" }) }) })) : null, p.upload && shelves.length ? _jsx(DropdownMenuSeparator, {}) : null, shelves.map((s) => wide ? (_jsxs(DropdownMenuSub, { open: sub === s.key, onOpenChange: (o) => setSub(o ? s.key : sub === s.key ? null : sub), children: [_jsx(DropdownMenuSubTrigger, { "data-slot": `plus-${s.key}`, onPress: () => setSub(s.key), children: _jsx(Line, { icon: s.icon, label: s.label }) }), sub === s.key ? (_jsx(DropdownMenuSubContent, { minW: 200, maxW: 340, maxH: 420, pointerEvents: "auto", style: { overflowY: "auto" }, children: _jsx(Contents, { shelf: p[s.key], name: s.label, close: close }) })) : null] }, s.key)) : (_jsx(DropdownMenuItem, { page: s.key, "data-slot": `plus-${s.key}`, children: _jsx(Line, { icon: s.icon, label: s.label }) }, s.key))), switches.length && (p.upload || shelves.length) ? _jsx(DropdownMenuSeparator, {}) : null, switches.map((w) => (_jsx(DropdownMenuItem, { preventCloseOnSelect: true, onSelect: () => w.s.set(!w.s.on), role: 'menuitemcheckbox', "aria-checked": w.s.on, "data-slot": `plus-${w.key}`, children: _jsx(Line, { icon: w.icon, label: w.label, on: w.s.on }) }, w.key)))] }));
59
- return (_jsxs(DropdownMenu, { open: open, onOpenChange: setOpen, ...(wide ? null : { defaultPage: 'main' }), children: [_jsx(DropdownMenuTrigger, { asChild: true, disabled: p.disabled, children: _jsx(XStack, { render: "button", "data-slot": "plus", "aria-label": "Add files, agents, skills and more", width: 32, height: 32, rounded: 8, items: "center", justify: "center", shrink: 0, cursor: p.disabled ? 'default' : 'pointer', opacity: p.disabled ? 0.4 : 1, bg: open ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineColor: '$outlineColor', outlineWidth: 2, outlineStyle: 'solid' }, "$max-sm": { width: 44, height: 44, rounded: 22 }, children: _jsx(PlusIcon, { size: 18, color: "$ink" }) }) }), _jsx(DropdownMenuContent, { align: "start", minW: 240, "data-slot": "plus-menu", children: wide ? (main) : (_jsxs(_Fragment, { children: [_jsx(DropdownMenuPage, { id: "main", children: main }), shelves.map((s) => (_jsxs(DropdownMenuPage, { id: s.key, children: [_jsx(DropdownMenuBack, { children: s.label }), _jsx(Contents, { shelf: p[s.key], name: s.label, close: close })] }, s.key)))] })) })] }));
70
+ }, children: _jsx(Line, { icon: _jsx(Paperclip, { size: ICON }), label: t('Upload or add files'), tail: _jsx(DropdownMenuShortcut, { "data-i18n-ltr": true, children: "Ctrl+U" }) }) })) : null, p.upload && shelves.length ? _jsx(DropdownMenuSeparator, {}) : null, shelves.map((s) => wide ? (_jsxs(DropdownMenuSub, { open: sub === s.key, onOpenChange: (o) => setSub(o ? s.key : sub === s.key ? null : sub), children: [_jsx(DropdownMenuSubTrigger, { "data-slot": `plus-${s.key}`, onPress: () => setSub(s.key), children: _jsx(Line, { icon: s.icon, label: t(s.label) }) }), sub === s.key ? (_jsx(DropdownMenuSubContent, { minW: 200, maxW: 340, maxH: 420, pointerEvents: "auto", style: { overflowY: "auto" }, children: _jsx(Contents, { shelf: p[s.key], words: s, close: close }) })) : null] }, s.key)) : (_jsx(DropdownMenuItem, { page: s.key, "data-slot": `plus-${s.key}`, children: _jsx(Line, { icon: s.icon, label: t(s.label) }) }, s.key))), switches.length && (p.upload || shelves.length) ? _jsx(DropdownMenuSeparator, {}) : null, switches.map((w) => (_jsx(DropdownMenuItem, { preventCloseOnSelect: true, onSelect: () => w.s.set(!w.s.on), role: 'menuitemcheckbox', "aria-checked": w.s.on, "data-slot": `plus-${w.key}`, children: _jsx(Line, { icon: w.icon, label: w.label, on: w.s.on }) }, w.key)))] }));
71
+ return (_jsxs(DropdownMenu, { open: open, onOpenChange: setOpen, ...(wide ? null : { defaultPage: 'main' }), children: [_jsx(DropdownMenuTrigger, { asChild: true, disabled: p.disabled, children: _jsx(XStack, { render: "button", "data-slot": "plus", "aria-label": t('Add files, agents, skills and more'), width: 32, height: 32, rounded: 8, items: "center", justify: "center", shrink: 0, cursor: p.disabled ? 'default' : 'pointer', opacity: p.disabled ? 0.4 : 1, bg: open ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineColor: '$outlineColor', outlineWidth: 2, outlineStyle: 'solid' }, "$max-sm": { width: 44, height: 44, rounded: 22 }, children: _jsx(PlusIcon, { size: 18, color: "$ink" }) }) }), _jsx(DropdownMenuContent, { align: "start", minW: 240, "data-slot": "plus-menu", children: wide ? (main) : (_jsxs(_Fragment, { children: [_jsx(DropdownMenuPage, { id: "main", children: main }), shelves.map((s) => (_jsxs(DropdownMenuPage, { id: s.key, children: [_jsx(DropdownMenuBack, { children: t(s.label) }), _jsx(Contents, { shelf: p[s.key], words: s, close: close })] }, s.key)))] })) })] }));
60
72
  }
package/lib/prefs.d.ts CHANGED
@@ -1,17 +1,3 @@
1
- /**
2
- * The person's own settings, read once for the whole builder and shared by
3
- * everything that follows them: Settings writes them, New starts from the
4
- * coding defaults and says the name, dictation listens in the chosen language,
5
- * and the page takes the theme, the text size and the motion.
6
- *
7
- * The last document read is kept in this browser, per org, so the page is
8
- * drawn the person's way before the platform answers. A save shows at once and
9
- * is taken back, key by key, if the platform refuses it.
10
- *
11
- * A host mounts it too, above the builder (the Hanzo app, for Chat's side
12
- * panel), and the builder's own then reads through that one: one document, one
13
- * read. The look (`Look`) is the builder's alone: a host has its own.
14
- */
15
1
  import { type ReactNode } from 'react';
16
2
  import { type Patch, type Prefs } from './api/pref.ts';
17
3
  export interface PrefState {
package/lib/prefs.js CHANGED
@@ -13,6 +13,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
13
13
  * panel), and the builder's own then reads through that one: one document, one
14
14
  * read. The look (`Look`) is the builder's alone: a host has its own.
15
15
  */
16
+ import { phrase } from '@hanzo/i18n';
16
17
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef } from 'react';
17
18
  import { merge, read, save } from './api/pref.js';
18
19
  import { useKept, useRead } from './data.js';
@@ -36,7 +37,7 @@ function Keep({ children }) {
36
37
  }, [doc.value, keep]);
37
38
  const write = useCallback(async (patch) => {
38
39
  if (!signed)
39
- throw new Error('Sign in to save your settings.');
40
+ throw new Error(phrase('Sign in to save your settings.'));
40
41
  const before = now.current;
41
42
  now.current = merge(before, patch);
42
43
  keep(now.current);