@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
@@ -7,9 +7,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
7
  * the review. While a setup run on the codebase is still working the scripts are
8
8
  * its to find, so they cannot be edited. A member reads; an org admin saves and
9
9
  * sets secrets, which is the platform's rule and not a choice made here.
10
+ *
11
+ * Its words are the reader's language (@hanzo/i18n `t`); a codebase's name, a
12
+ * secret's, the scripts and what the setup agent wrote are drawn as given.
10
13
  */
11
14
  import { SizableText, XStack, YStack } from '@hanzo/gui';
12
15
  import { Plus, X } from '@hanzogui/lucide-icons-2';
16
+ import { useI18n } from '@hanzo/i18n/react';
13
17
  import { Button, Dialog, DialogContent, DialogTitle, Input, Textarea } from '@hanzo/ui';
14
18
  import { useEffect, useState } from 'react';
15
19
  import { start } from './api/coding.js';
@@ -19,11 +23,12 @@ import { matches } from './merge.js';
19
23
  import { useHost, useTarget } from './host.js';
20
24
  const mono = { fontFamily: 'var(--f-mono, ui-monospace, monospace)' };
21
25
  export function Environment({ repo, busy }) {
26
+ const { t } = useI18n();
22
27
  const host = useHost();
23
- const t = useTarget();
28
+ const target = useTarget();
24
29
  const signed = Boolean(host.person);
25
30
  // Read again when a setup run on it stops, which is when its proposal lands.
26
- const env = useRead(signed && repo ? () => read(t, repo) : null, null, [t, repo, signed, busy]);
31
+ const env = useRead(signed && repo ? () => read(target, repo) : null, null, [target, repo, signed, busy]);
27
32
  const [install, setInstall] = useState('');
28
33
  const [startCmd, setStart] = useState('');
29
34
  const [adding, setAdding] = useState(false);
@@ -39,20 +44,20 @@ export function Environment({ repo, busy }) {
39
44
  setStart(shown.proposal?.start ?? shown.start);
40
45
  }, [shown]);
41
46
  if (!signed) {
42
- return _jsx(Soft, { children: "Sign in to see this codebase\u2019s environment." });
47
+ return _jsx(Soft, { children: t('Sign in to see this codebase’s environment.') });
43
48
  }
44
49
  if (!repo) {
45
- return _jsx(Soft, { children: "This run has no codebase, so it has no environment." });
50
+ return _jsx(Soft, { children: t('This run has no codebase, so it has no environment.') });
46
51
  }
47
52
  if (env.error) {
48
- return _jsx(Soft, { children: `${repo}’s environment could not be read right now.` });
53
+ return _jsx(Soft, { children: t('{repo}’s environment could not be read right now.', { repo }) });
49
54
  }
50
55
  if (!shown) {
51
- return _jsx(Soft, { children: "Reading the environment\u2026" });
56
+ return _jsx(Soft, { children: t('Reading the environment…') });
52
57
  }
53
58
  const changed = install !== shown.install || startCmd !== shown.start || shown.proposal !== null;
54
59
  const needed = (shown.proposal?.secrets ?? []).filter((n) => !shown.secrets.includes(n));
55
- const status = busy ? 'Setting up' : shown.state === 'proposed' ? 'Proposed' : shown.state === 'ready' ? 'Ready' : 'Not set up';
60
+ const status = busy ? t('Setting up') : shown.state === 'proposed' ? t('Proposed') : shown.state === 'ready' ? t('Ready') : t('Not set up');
56
61
  /** One write, then the record read again. Answers whether it took. */
57
62
  const act = async (what, done = '') => {
58
63
  setWorking(true);
@@ -64,7 +69,7 @@ export function Environment({ repo, busy }) {
64
69
  return true;
65
70
  }
66
71
  catch (e) {
67
- setNote(plain(e, repo, 'save'));
72
+ setNote(plain(e, repo, 'save', t));
68
73
  return false;
69
74
  }
70
75
  finally {
@@ -75,22 +80,22 @@ export function Environment({ repo, busy }) {
75
80
  setWorking(true);
76
81
  setNote('');
77
82
  try {
78
- const run = await start(t, { prompt: SETUP, repo, mode: 'setup' });
83
+ const run = await start(target, { prompt: SETUP, repo, mode: 'setup' });
79
84
  host.go(run.session);
80
85
  }
81
86
  catch (e) {
82
- setNote(plain(e, repo, 'start'));
87
+ setNote(plain(e, repo, 'start', t));
83
88
  setWorking(false);
84
89
  }
85
90
  };
86
91
  const add = () => {
87
92
  const n = name.trim();
88
- const why = refuse(n) || (value ? '' : 'A secret needs a value');
93
+ const why = refuse(n, t) || (value ? '' : t('A secret needs a value'));
89
94
  if (why) {
90
95
  setNote(why);
91
96
  return;
92
97
  }
93
- void act(() => setSecret(t, repo, n, value), `${n} is set`).then((took) => {
98
+ void act(() => setSecret(target, repo, n, value), t('{name} is set', { name: n })).then((took) => {
94
99
  if (!took)
95
100
  return;
96
101
  setAdding(false);
@@ -98,7 +103,7 @@ export function Environment({ repo, busy }) {
98
103
  setValue('');
99
104
  });
100
105
  };
101
- return (_jsxs(YStack, { gap: "$4", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { flex: 1, size: "$4", color: "$ink", numberOfLines: 1, children: repo }), _jsxs(XStack, { items: "center", gap: "$1.5", children: [_jsx(YStack, { width: 6, height: 6, rounded: 999, bg: busy ? '$ink' : shown.state === 'ready' ? '$ink' : '$soft', opacity: busy ? 1 : 0.7 }), _jsx(SizableText, { size: "$1", color: "$soft", children: status })] }), host.admin ? (_jsx(Button, { size: "sm", variant: "outline", disabled: busy || working || !changed, onPress: () => void act(() => save(t, repo, { install, start: startCmd }), 'Saved'), children: "Save" })) : null] }), shown.proposal?.note ? (_jsxs(YStack, { gap: "$1", px: "$3", py: "$2", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: "What the setup agent found" }), _jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 8, children: shown.proposal.note })] })) : null, _jsx(Script, { label: "Install Script", value: install, onChange: setInstall, locked: busy || !host.admin, hint: busy ? 'Install script editing will be available when the agent stops running' : 'pnpm install --frozen-lockfile' }), _jsx(Script, { label: "Start Script", value: startCmd, onChange: setStart, locked: busy || !host.admin, hint: busy ? 'Start script editing will be available when the agent stops running' : 'pnpm dev' }), _jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { size: "$2", color: "$ink", flex: 1, children: "Secrets" }), host.admin ? (_jsxs(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => setAdding(true), children: [_jsx(Plus, { size: 14 }), "New Secret"] })) : null] }), _jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: [shown.secrets.length === 0 && needed.length === 0 && !adding ? (_jsx(YStack, { py: "$5", items: "center", children: _jsx(SizableText, { size: "$2", color: "$soft", children: "No secrets yet" }) })) : null, shown.secrets.map((s, i) => (_jsxs(XStack, { items: "center", gap: "$2", px: "$3", py: "$2", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", children: [_jsx(SizableText, { flex: 1, size: "$2", color: "$ink", style: mono, children: s }), _jsx(SizableText, { size: "$1", color: "$soft", children: "set" }), host.admin ? (_jsx(XStack, { render: "button", "aria-label": `Remove ${s}`, px: "$1", onPress: () => void act(() => removeSecret(t, repo, s), `${s} is removed`), children: _jsx(X, { size: 14 }) })) : null] }, s))), needed.map((s) => (_jsxs(XStack, { items: "center", gap: "$2", px: "$3", py: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { flex: 1, size: "$2", color: "$ink", style: mono, children: s }), _jsx(SizableText, { size: "$1", color: "$soft", children: "needed, not set" }), host.admin ? (_jsx(XStack, { render: "button", "aria-label": `Set ${s}`, px: "$1", onPress: () => { setAdding(true); setName(s); }, children: _jsx(Plus, { size: 14 }) })) : null] }, s))), adding ? (_jsxs(YStack, { gap: "$2", px: "$3", py: "$3", borderTopWidth: shown.secrets.length || needed.length ? 1 : 0, borderColor: "$borderColor", children: [_jsx(Input, { value: name, onChangeText: setName, placeholder: "NAME", "aria-label": "Secret name", autoCapitalize: "characters" }), _jsx(Input, { value: value, onChangeText: setValue, placeholder: "Value", "aria-label": "Secret value", secureTextEntry: true }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => { setAdding(false); setName(''); setValue(''); }, children: "Cancel" }), _jsx(Button, { size: "sm", disabled: working, onPress: add, children: "Add secret" })] })] })) : null] }), _jsx(SizableText, { size: "$1", color: "$soft", children: "Values are sealed in KMS and exported to every run on this codebase. They are never shown again." })] }), shown.state === 'none' && !busy ? (_jsxs(YStack, { gap: "$2", items: "flex-start", children: [_jsx(Button, { size: "sm", disabled: working, onPress: () => void setup(), children: "Set up with an agent" }), _jsx(SizableText, { size: "$1", color: "$soft", children: "An agent explores the codebase, installs and checks it, and proposes the scripts for you to review and save." })] })) : null, !host.admin ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "An org admin saves the environment and sets its secrets." })) : null, note ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note })) : null] }));
106
+ return (_jsxs(YStack, { gap: "$4", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { flex: 1, size: "$4", color: "$ink", numberOfLines: 1, children: repo }), _jsxs(XStack, { items: "center", gap: "$1.5", children: [_jsx(YStack, { width: 6, height: 6, rounded: 999, bg: busy ? '$ink' : shown.state === 'ready' ? '$ink' : '$soft', opacity: busy ? 1 : 0.7 }), _jsx(SizableText, { size: "$1", color: "$soft", children: status })] }), host.admin ? (_jsx(Button, { size: "sm", variant: "outline", disabled: busy || working || !changed, onPress: () => void act(() => save(target, repo, { install, start: startCmd }), t('Saved')), children: t('Save') })) : null] }), shown.proposal?.note ? (_jsxs(YStack, { gap: "$1", px: "$3", py: "$2", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: t('What the setup agent found') }), _jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 8, children: shown.proposal.note })] })) : null, _jsx(Script, { label: t('Install Script'), value: install, onChange: setInstall, locked: busy || !host.admin, hint: busy ? t('Install script editing will be available when the agent stops running') : 'pnpm install --frozen-lockfile' }), _jsx(Script, { label: t('Start Script'), value: startCmd, onChange: setStart, locked: busy || !host.admin, hint: busy ? t('Start script editing will be available when the agent stops running') : 'pnpm dev' }), _jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { size: "$2", color: "$ink", flex: 1, children: t('Secrets') }), host.admin ? (_jsxs(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => setAdding(true), children: [_jsx(Plus, { size: 14 }), t('New Secret')] })) : null] }), _jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: [shown.secrets.length === 0 && needed.length === 0 && !adding ? (_jsx(YStack, { py: "$5", items: "center", children: _jsx(SizableText, { size: "$2", color: "$soft", children: t('No secrets yet') }) })) : null, shown.secrets.map((s, i) => (_jsxs(XStack, { items: "center", gap: "$2", px: "$3", py: "$2", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", children: [_jsx(SizableText, { flex: 1, size: "$2", color: "$ink", style: mono, children: s }), _jsx(SizableText, { size: "$1", color: "$soft", children: t('set') }), host.admin ? (_jsx(XStack, { render: "button", "aria-label": t('Remove {name}', { name: s }), px: "$1", onPress: () => void act(() => removeSecret(target, repo, s), t('{name} is removed', { name: s })), children: _jsx(X, { size: 14 }) })) : null] }, s))), needed.map((s) => (_jsxs(XStack, { items: "center", gap: "$2", px: "$3", py: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { flex: 1, size: "$2", color: "$ink", style: mono, children: s }), _jsx(SizableText, { size: "$1", color: "$soft", children: t('needed, not set') }), host.admin ? (_jsx(XStack, { render: "button", "aria-label": t('Set {name}', { name: s }), px: "$1", onPress: () => { setAdding(true); setName(s); }, children: _jsx(Plus, { size: 14 }) })) : null] }, s))), adding ? (_jsxs(YStack, { gap: "$2", px: "$3", py: "$3", borderTopWidth: shown.secrets.length || needed.length ? 1 : 0, borderColor: "$borderColor", children: [_jsx(Input, { value: name, onChangeText: setName, placeholder: t('NAME'), "aria-label": t('Secret name'), autoCapitalize: "characters" }), _jsx(Input, { value: value, onChangeText: setValue, placeholder: t('Value'), "aria-label": t('Secret value'), secureTextEntry: true }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => { setAdding(false); setName(''); setValue(''); }, children: t('Cancel') }), _jsx(Button, { size: "sm", disabled: working, onPress: add, children: t('Add secret') })] })] })) : null] }), _jsx(SizableText, { size: "$1", color: "$soft", children: t('Values are sealed in KMS and exported to every run on this codebase. They are never shown again.') })] }), shown.state === 'none' && !busy ? (_jsxs(YStack, { gap: "$2", items: "flex-start", children: [_jsx(Button, { size: "sm", disabled: working, onPress: () => void setup(), children: t('Set up with an agent') }), _jsx(SizableText, { size: "$1", color: "$soft", children: t('An agent explores the codebase, installs and checks it, and proposes the scripts for you to review and save.') })] })) : null, !host.admin ? (_jsx(SizableText, { size: "$1", color: "$soft", children: t('An org admin saves the environment and sets its secrets.') })) : null, note ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note })) : null] }));
102
107
  }
103
108
  /**
104
109
  * New's offer to set a codebase up. An agent onboards it, or an org admin saves
@@ -109,8 +114,9 @@ export function Environment({ repo, busy }) {
109
114
  * board's key handed over as a codebase — is said so and cannot be set up.
110
115
  */
111
116
  export function SetupDialog({ repo, homes, open, onOpenChange, onStart, onSaved, onRepo, }) {
117
+ const { t } = useI18n();
112
118
  const host = useHost();
113
- const t = useTarget();
119
+ const target = useTarget();
114
120
  const [chosen, setChosen] = useState(repo);
115
121
  const [q, setQ] = useState('');
116
122
  const [working, setWorking] = useState('');
@@ -140,7 +146,7 @@ export function SetupDialog({ repo, homes, open, onOpenChange, onStart, onSaved,
140
146
  setNote('');
141
147
  try {
142
148
  if (what === 'save') {
143
- await save(t, repo, { install: '', start: '' });
149
+ await save(target, repo, { install: '', start: '' });
144
150
  onSaved(repo);
145
151
  }
146
152
  else {
@@ -149,20 +155,20 @@ export function SetupDialog({ repo, homes, open, onOpenChange, onStart, onSaved,
149
155
  }
150
156
  }
151
157
  catch (e) {
152
- setNote(plain(e, repo, what));
158
+ setNote(plain(e, repo, what, t));
153
159
  }
154
160
  finally {
155
161
  setWorking('');
156
162
  }
157
163
  };
158
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: 480, showCloseButton: false, children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [_jsx(DialogTitle, { children: "Set up an environment" }), _jsx(XStack, { render: "button", "aria-label": "Close", p: "$1", onPress: () => onOpenChange(false), children: _jsx(X, { size: 16 }) })] }), _jsxs(YStack, { gap: "$3", children: [_jsxs(YStack, { gap: "$1.5", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: "Repository" }), _jsx(XStack, { px: "$3", py: "$2", rounded: "$3", borderWidth: 1, borderColor: stranger ? '$ink' : '$borderColor', items: "center", gap: "$2", "aria-label": "Chosen repository", children: _jsx(SizableText, { size: "$2", color: home || (chosen && !stranger) ? '$ink' : '$soft', numberOfLines: 1, flex: 1, minW: 0, children: home ? home.label : chosen || 'Choose a repository' }) }), stranger ? (_jsx(SizableText, { size: "$1", color: "$ink", role: "alert", children: `${chosen} is not one of this organization’s repositories. Choose one below.` })) : null, _jsx(Input, { value: q, onChangeText: setQ, placeholder: "Find a repository\u2026", "aria-label": "Find a repository" }), _jsx(YStack, { gap: 2, "aria-label": "Repositories", children: homes.loading && !list.length ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "Reading the repositories\u2026" })) : homes.error && !list.length ? (_jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: "The repository list could not be read right now." }), _jsx(Button, { size: "sm", variant: "outline", onPress: homes.reload, children: "Read again" })] })) : shown.length === 0 ? (_jsx(SizableText, { size: "$1", color: "$soft", children: list.length ? 'Nothing matches.' : 'This organization has no repositories on the forge yet.' })) : (shown.map((h) => {
164
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: 480, showCloseButton: false, children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [_jsx(DialogTitle, { children: t('Set up an environment') }), _jsx(XStack, { render: "button", "aria-label": t('Close'), p: "$1", onPress: () => onOpenChange(false), children: _jsx(X, { size: 16 }) })] }), _jsxs(YStack, { gap: "$3", children: [_jsxs(YStack, { gap: "$1.5", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: t('Repository') }), _jsx(XStack, { px: "$3", py: "$2", rounded: "$3", borderWidth: 1, borderColor: stranger ? '$ink' : '$borderColor', items: "center", gap: "$2", "aria-label": t('Chosen repository'), children: _jsx(SizableText, { size: "$2", color: home || (chosen && !stranger) ? '$ink' : '$soft', numberOfLines: 1, flex: 1, minW: 0, children: home ? home.label : chosen || t('Choose a repository') }) }), stranger ? (_jsx(SizableText, { size: "$1", color: "$ink", role: "alert", children: t('{repo} is not one of this organization’s repositories. Choose one below.', { repo: chosen }) })) : null, _jsx(Input, { value: q, onChangeText: setQ, placeholder: t('Find a repository…'), "aria-label": t('Find a repository') }), _jsx(YStack, { gap: 2, "aria-label": t('Repositories'), children: homes.loading && !list.length ? (_jsx(SizableText, { size: "$1", color: "$soft", children: t('Reading the repositories…') })) : homes.error && !list.length ? (_jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: t('The repository list could not be read right now.') }), _jsx(Button, { size: "sm", variant: "outline", onPress: homes.reload, children: t('Read again') })] })) : shown.length === 0 ? (_jsx(SizableText, { size: "$1", color: "$soft", children: list.length ? t('Nothing matches.') : t('This organization has no repositories on the forge yet.') })) : (shown.map((h) => {
159
165
  const on = h.name.toLowerCase() === chosen.toLowerCase();
160
- return (_jsx(XStack, { render: "button", "aria-pressed": on, "aria-label": `Choose ${h.label}`, onPress: () => {
166
+ return (_jsx(XStack, { render: "button", "aria-pressed": on, "aria-label": t('Choose {repo}', { repo: h.label }), onPress: () => {
161
167
  setChosen(h.name);
162
168
  setNote('');
163
169
  onRepo(h);
164
170
  }, px: "$2.5", py: "$1.5", rounded: "$2", bg: on ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, children: _jsx(SizableText, { size: "$2", color: on ? '$ink' : '$soft', numberOfLines: 1, children: h.label }) }, h.name));
165
- })) })] }), _jsx(SizableText, { size: "$2", color: "$soft", children: "An agent onboards the codebase: it explores it, writes the install and start scripts, and names the secrets it needs. It takes several minutes. Interrupt it anytime, or take over in the terminal." }), note ? (_jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsx(SizableText, { size: "$1", color: "$ink", role: "status", flex: 1, minW: 200, children: note }), last ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working !== '', onPress: () => void run(last), children: "Try again" })) : null] })) : null, _jsxs(XStack, { gap: "$2", justify: "space-between", items: "center", children: [host.admin ? (_jsx(Button, { size: "sm", variant: "secondary", disabled: working !== '' || !ready, onPress: () => void run('save'), children: working === 'save' ? 'Saving…' : 'Skip & save' })) : (_jsx(YStack, {})), _jsx(Button, { size: "sm", variant: "primary", disabled: working !== '' || !ready, onPress: () => void run('start'), children: working === 'start' ? 'Starting the agent…' : 'Start agent' })] })] })] }) }));
171
+ })) })] }), _jsx(SizableText, { size: "$2", color: "$soft", children: t('An agent onboards the codebase: it explores it, writes the install and start scripts, and names the secrets it needs. It takes several minutes. Interrupt it anytime, or take over in the terminal.') }), note ? (_jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsx(SizableText, { size: "$1", color: "$ink", role: "status", flex: 1, minW: 200, children: note }), last ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working !== '', onPress: () => void run(last), children: t('Try again') })) : null] })) : null, _jsxs(XStack, { gap: "$2", justify: "space-between", items: "center", children: [host.admin ? (_jsx(Button, { size: "sm", variant: "secondary", disabled: working !== '' || !ready, onPress: () => void run('save'), children: working === 'save' ? t('Saving…') : t('Skip & save') })) : (_jsx(YStack, {})), _jsx(Button, { size: "sm", variant: "primary", disabled: working !== '' || !ready, onPress: () => void run('start'), children: working === 'start' ? t('Starting the agent…') : t('Start agent') })] })] })] }) }));
166
172
  }
167
173
  function Script({ label, value, onChange, locked, hint }) {
168
174
  return (_jsxs(YStack, { gap: "$2", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: label }), _jsx(Textarea, { value: value, onChangeText: onChange, disabled: locked, placeholder: hint, "aria-label": label, rows: 3, style: mono })] }));
package/lib/files.js CHANGED
@@ -6,8 +6,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
6
  * or changed and the whole change as a patch, kept after its sandbox is gone; its
7
7
  * previews, pull request and deployments — then its published site, its branch,
8
8
  * its environment proposal and its project.
9
+ *
10
+ * Its words are the reader's language (@hanzo/i18n `t`); names, paths and a
11
+ * file's contents 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 { Download, ExternalLink, File, FileDiff, Folder, Globe, GitBranch, GitPullRequest, Layers, MonitorPlay, Rocket, Settings2 } from '@hanzogui/lucide-icons-2';
12
16
  import { useState } from 'react';
13
17
  import { blob, tree } from './api/changes.js';
@@ -18,25 +22,27 @@ import { useHost, useTarget } from './host.js';
18
22
  import { away, Out } from './out.js';
19
23
  const monoWrap = { whiteSpace: 'pre-wrap', fontFamily: 'var(--f-mono, ui-monospace, monospace)' };
20
24
  export function Files({ session, repo, branch, sandbox, live, }) {
25
+ const { t } = useI18n();
21
26
  const reachable = live && Boolean(sandbox);
22
27
  // Live once the sandbox is reachable, unless the branch was picked.
23
28
  const [source, setSource] = useState(null);
24
29
  const from = reachable ? (source ?? 'live') : 'branch';
25
- return (_jsxs(YStack, { gap: "$3", children: [reachable ? (_jsxs(XStack, { gap: "$1", items: "center", flexWrap: "wrap", children: [_jsx(Pick, { on: from === 'live', onPress: () => setSource('live'), children: "Live" }), _jsx(Pick, { on: from === 'branch', onPress: () => setSource('branch'), children: "Branch" })] })) : null, from === 'live' ? _jsx(Live, { sandbox: sandbox }) : _jsx(Branch, { session: session, label: `${repo}${branch ? `@${branch}` : ''}` })] }));
30
+ return (_jsxs(YStack, { gap: "$3", children: [reachable ? (_jsxs(XStack, { gap: "$1", items: "center", flexWrap: "wrap", children: [_jsx(Pick, { on: from === 'live', onPress: () => setSource('live'), children: t('Live') }), _jsx(Pick, { on: from === 'branch', onPress: () => setSource('branch'), children: t('Branch') })] })) : null, from === 'live' ? _jsx(Live, { sandbox: sandbox }) : _jsx(Branch, { session: session, label: `${repo}${branch ? `@${branch}` : ''}` })] }));
26
31
  }
27
32
  /** The sandbox's working tree, as it is right now. */
28
33
  function Live({ sandbox }) {
29
- const t = useTarget();
34
+ const target = useTarget();
35
+ const { t } = useI18n();
30
36
  const [dir, setDir] = useState('');
31
37
  const [file, setFile] = useState('');
32
- const list = useRead(() => look(t, sandbox, dir), null, [t, sandbox, dir]);
33
- const body = useRead(file ? () => look(t, sandbox, file) : null, null, [t, sandbox, file]);
38
+ const list = useRead(() => look(target, sandbox, dir), null, [target, sandbox, dir]);
39
+ const body = useRead(file ? () => look(target, sandbox, file) : null, null, [target, sandbox, file]);
34
40
  const at = (name) => (dir ? `${dir}/${name}` : name);
35
41
  // A name is only a name until it is read: the answer says whether it was a directory.
36
42
  const open = async (name) => {
37
43
  const path = at(name);
38
44
  try {
39
- const n = await look(t, sandbox, path);
45
+ const n = await look(target, sandbox, path);
40
46
  if (n.dir) {
41
47
  setDir(path);
42
48
  setFile('');
@@ -53,23 +59,24 @@ function Live({ sandbox }) {
53
59
  ? body.error.message
54
60
  : body.value
55
61
  ? body.value.binary
56
- ? 'Binary file'
62
+ ? t('Binary file')
57
63
  : body.value.truncated
58
- ? 'Too large to show'
59
- : body.value.text || 'Empty file'
64
+ ? t('Too large to show')
65
+ : body.value.text || t('Empty file')
60
66
  : '' }));
61
67
  }
62
68
  /** The run's branch on the forge; its base until it has pushed. */
63
69
  function Branch({ session, label }) {
64
70
  const host = useHost();
65
- const t = useTarget();
71
+ const target = useTarget();
72
+ const { t } = useI18n();
66
73
  const signed = Boolean(host.person);
67
74
  const [dir, setDir] = useState('');
68
75
  const [file, setFile] = useState('');
69
- const list = useRead(signed ? () => tree(t, session, dir) : null, [], [t, session, dir, signed]);
70
- const body = useRead(file ? () => blob(t, session, file) : null, null, [t, session, file]);
76
+ const list = useRead(signed ? () => tree(target, session, dir) : null, [], [target, session, dir, signed]);
77
+ const body = useRead(file ? () => blob(target, session, file) : null, null, [target, session, file]);
71
78
  if (!signed)
72
- return _jsx(Soft, { children: "Sign in to read this run\u2019s files." });
79
+ return _jsx(Soft, { children: t('Sign in to read this run’s files.') });
73
80
  return (_jsx(Listing, { where: `${label}${dir ? `/${dir}` : ''}`, up: dir ? () => (setDir(dir.includes('/') ? dir.slice(0, dir.lastIndexOf('/')) : ''), setFile('')) : undefined, error: list.error?.message ?? '', loading: list.loading && list.value.length === 0, rows: list.value.map((e) => ({
74
81
  key: e.path,
75
82
  name: e.name,
@@ -79,34 +86,36 @@ function Branch({ session, label }) {
79
86
  ? body.error.message
80
87
  : body.value
81
88
  ? body.value.binary
82
- ? 'Binary file'
89
+ ? t('Binary file')
83
90
  : body.value.truncated
84
- ? 'Too large to show'
85
- : body.value.text || 'Empty file'
91
+ ? t('Too large to show')
92
+ : body.value.text || t('Empty file')
86
93
  : '' }));
87
94
  }
88
95
  function Listing({ where, up, error, loading, rows, file, body, }) {
89
- return (_jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { gap: "$2", items: "center", children: [up ? (_jsx(XStack, { render: "button", "aria-label": "Up one directory", onPress: up, px: "$1", py: "$1", children: _jsx(SizableText, { size: "$1", color: "$soft", children: "Up" }) })) : null, _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: where })] }), error ? (_jsx(SizableText, { size: "$2", color: "$soft", children: error })) : loading ? (_jsx(SizableText, { size: "$2", color: "$soft", children: "Reading\u2026" })) : rows.length === 0 ? (_jsx(SizableText, { size: "$2", color: "$soft", children: "This directory is empty." })) : (rows.map((r) => (_jsxs(XStack, { render: "button", "aria-label": r.dir === false ? `Read ${r.name}` : `Open ${r.name}`, onPress: r.onPress, items: "center", gap: "$2", py: "$1", hoverStyle: { bg: '$hover' }, children: [r.dir === false ? _jsx(File, { size: 14 }) : r.dir ? _jsx(Folder, { size: 14 }) : _jsx(File, { size: 14, opacity: 0.6 }), _jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, style: { textAlign: 'left' }, children: r.name })] }, r.key)))), file && body ? (_jsxs(YStack, { gap: "$1", pt: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: file }), _jsx(SizableText, { size: "$1", color: "$ink", style: monoWrap, children: body })] })) : null] }));
96
+ const { t } = useI18n();
97
+ return (_jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { gap: "$2", items: "center", children: [up ? (_jsx(XStack, { render: "button", "aria-label": t('Up one directory'), onPress: up, px: "$1", py: "$1", children: _jsx(SizableText, { size: "$1", color: "$soft", children: t('Up') }) })) : null, _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: where })] }), error ? (_jsx(SizableText, { size: "$2", color: "$soft", children: error })) : loading ? (_jsx(SizableText, { size: "$2", color: "$soft", children: t('Reading…') })) : rows.length === 0 ? (_jsx(SizableText, { size: "$2", color: "$soft", children: t('This directory is empty.') })) : (rows.map((r) => (_jsxs(XStack, { render: "button", "aria-label": r.dir === false ? t('Read {name}', { name: r.name }) : t('Open {name}', { name: r.name }), onPress: r.onPress, items: "center", gap: "$2", py: "$1", hoverStyle: { bg: '$hover' }, children: [r.dir === false ? _jsx(File, { size: 14 }) : r.dir ? _jsx(Folder, { size: 14 }) : _jsx(File, { size: 14, opacity: 0.6 }), _jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, text: "start", children: r.name })] }, r.key)))), file && body ? (_jsxs(YStack, { gap: "$1", pt: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: file }), _jsx(SizableText, { size: "$1", color: "$ink", style: monoWrap, children: body })] })) : null] }));
90
98
  }
91
99
  /** A stored artifact's size, as a person reads it. */
92
100
  const size = (n) => (n < 1024 ? `${n} B` : n < 1 << 20 ? `${(n / 1024).toFixed(1)} KB` : `${(n / (1 << 20)).toFixed(1)} MB`);
93
- /** What each kind of artifact is drawn with, and says it is. */
101
+ /** What each kind of artifact is drawn with, and says it is, in the reader's language. */
94
102
  const KIND = {
95
- file: { icon: _jsx(File, { size: 16 }), note: (a) => `Changed file · ${size(a.size)}` },
96
- patch: { icon: _jsx(FileDiff, { size: 16 }), note: (a) => `The whole change as a patch · ${size(a.size)}` },
97
- preview: { icon: _jsx(MonitorPlay, { size: 16 }), note: (a) => `What the run served on port ${a.port}` },
98
- pull: { icon: _jsx(GitPullRequest, { size: 16 }), note: () => 'Pull request' },
99
- deploy: { icon: _jsx(Rocket, { size: 16 }), note: () => 'Deployment' },
103
+ file: { icon: _jsx(File, { size: 16 }), note: (a, t) => t('Changed file · {size}', { size: size(a.size) }) },
104
+ patch: { icon: _jsx(FileDiff, { size: 16 }), note: (a, t) => t('The whole change as a patch · {size}', { size: size(a.size) }) },
105
+ preview: { icon: _jsx(MonitorPlay, { size: 16 }), note: (a, t) => t('What the run served on port {port}', { port: a.port }) },
106
+ pull: { icon: _jsx(GitPullRequest, { size: 16 }), note: (_, t) => t('Pull request') },
107
+ deploy: { icon: _jsx(Rocket, { size: 16 }), note: (_, t) => t('Deployment') },
100
108
  };
101
109
  export function Artifacts({ session, sandbox, repo, branch, mode, project, site, pr, onEnvironment, }) {
102
110
  const host = useHost();
103
- const t = useTarget();
104
- const left = useRead(host.person ? () => artifacts(t, session) : null, { saved: '', artifacts: [] }, [t, session, host.person]);
111
+ const target = useTarget();
112
+ const { t } = useI18n();
113
+ const left = useRead(host.person ? () => artifacts(target, session) : null, { saved: '', artifacts: [] }, [target, session, host.person]);
105
114
  const [note, setNote] = useState('');
106
115
  // Bytes come with the person's bearer, so a stored artifact is fetched, then saved.
107
116
  const save = async (a) => {
108
117
  try {
109
- const url = URL.createObjectURL(await artifact(t, session, a.name));
118
+ const url = URL.createObjectURL(await artifact(target, session, a.name));
110
119
  const link = document.createElement('a');
111
120
  link.href = url;
112
121
  link.download = a.name.split('/').pop() || a.name;
@@ -118,7 +127,7 @@ export function Artifacts({ session, sandbox, repo, branch, mode, project, site,
118
127
  }
119
128
  };
120
129
  // A preview is opened with a fresh ticket, in a tab of its own.
121
- const open = (a) => away(preview(t, sandbox, a.port)).catch((e) => setNote(e.message));
130
+ const open = (a) => away(preview(target, sandbox, a.port)).catch((e) => setNote(e.message));
122
131
  const items = [];
123
132
  for (const a of left.value.artifacts) {
124
133
  const stored = a.kind === 'file' || a.kind === 'patch';
@@ -129,26 +138,26 @@ export function Artifacts({ session, sandbox, repo, branch, mode, project, site,
129
138
  key: `${a.kind}:${a.name}`,
130
139
  icon: KIND[a.kind].icon,
131
140
  title: link ? a.url.replace(/^https:\/\//, '') : a.name,
132
- note: KIND[a.kind].note(a),
141
+ note: KIND[a.kind].note(a, t),
133
142
  href: link ? a.url : undefined,
134
143
  onPress: stored ? () => void save(a) : a.kind === 'preview' ? () => void open(a) : undefined,
135
144
  action: stored ? _jsx(Download, { size: 14 }) : a.kind === 'preview' ? _jsx(ExternalLink, { size: 14 }) : undefined,
136
145
  });
137
146
  }
138
147
  if (site)
139
- items.push({ key: 'site', icon: _jsx(Globe, { size: 16 }), title: site.replace(/^https:\/\//, ''), note: 'Where this run is published', href: site });
148
+ items.push({ key: 'site', icon: _jsx(Globe, { size: 16 }), title: site.replace(/^https:\/\//, ''), note: t('Where this run is published'), href: site });
140
149
  if (pr.href)
141
- items.push({ key: 'pr', icon: _jsx(GitPullRequest, { size: 16 }), title: `Pull request ${pr.label}`, note: 'Opened on the forge', href: pr.href });
150
+ items.push({ key: 'pr', icon: _jsx(GitPullRequest, { size: 16 }), title: t('Pull request {pr}', { pr: pr.label }), note: t('Opened on the forge'), href: pr.href });
142
151
  if (branch)
143
- items.push({ key: 'branch', icon: _jsx(GitBranch, { size: 16 }), title: branch, note: repo ? `The branch this run pushes to in ${repo}` : 'The branch this run pushes to' });
152
+ items.push({ key: 'branch', icon: _jsx(GitBranch, { size: 16 }), title: branch, note: repo ? t('The branch this run pushes to in {repo}', { repo }) : t('The branch this run pushes to') });
144
153
  if (mode === 'setup')
145
- items.push({ key: 'environment', icon: _jsx(Settings2, { size: 16 }), title: 'Environment proposal', note: 'The install and start scripts this run found', onPress: onEnvironment });
154
+ items.push({ key: 'environment', icon: _jsx(Settings2, { size: 16 }), title: t('Environment proposal'), note: t('The install and start scripts this run found'), onPress: onEnvironment });
146
155
  if (project)
147
- items.push({ key: 'project', icon: _jsx(Layers, { size: 16 }), title: project, note: 'The project this run builds', onPress: () => host.go(project) });
156
+ items.push({ key: 'project', icon: _jsx(Layers, { size: 16 }), title: project, note: t('The project this run builds'), onPress: () => host.go(project) });
148
157
  if (!items.length)
149
- return _jsx(Soft, { children: left.error?.message || (left.loading ? 'Reading what this run left…' : 'This run has produced no artifacts yet.') });
158
+ return _jsx(Soft, { children: left.error?.message || (left.loading ? t('Reading what this run left…') : t('This run has produced no artifacts yet.')) });
150
159
  return (_jsxs(YStack, { gap: "$2", children: [note ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note })) : null, _jsx(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: items.map((a, i) => {
151
- const row = (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", hoverStyle: a.href || a.onPress ? { bg: '$hover' } : undefined, children: [a.icon, _jsxs(YStack, { flex: 1, minW: 0, style: { textAlign: 'left' }, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: a.title }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: a.note })] }), a.action ?? (a.href ? _jsx(ExternalLink, { size: 14 }) : null)] }));
160
+ const row = (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", hoverStyle: a.href || a.onPress ? { bg: '$hover' } : undefined, children: [a.icon, _jsxs(YStack, { flex: 1, minW: 0, "$platform-web": { textAlign: 'start' }, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: a.title }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: a.note })] }), a.action ?? (a.href ? _jsx(ExternalLink, { size: 14 }) : null)] }));
152
161
  return a.href ? (_jsx(Out, { href: a.href, label: a.title, children: _jsx(YStack, { width: "100%", children: row }) }, a.key)) : a.onPress ? (_jsx(YStack, { render: "button", "aria-label": a.title, onPress: a.onPress, children: row }, a.key)) : (_jsx(YStack, { children: row }, a.key));
153
162
  }) })] }));
154
163
  }
package/lib/find.js CHANGED
@@ -6,20 +6,23 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
6
  * the first page lands it searches the rail's own recents.
7
7
  */
8
8
  import { ScrollView, SizableText, XStack, YStack } from '@hanzo/gui';
9
+ import { phrase } from '@hanzo/i18n';
10
+ import { useI18n } from '@hanzo/i18n/react';
9
11
  import { Button, Dialog, DialogContent, DialogTitle, Input } from '@hanzo/ui';
10
12
  import { useEffect, useMemo, useRef, useState } from 'react';
11
13
  import { page } from './api/sessions.js';
12
14
  import { useHost, useTarget } from './host.js';
13
15
  /** The statuses the platform filters on, and every run. */
14
16
  const FILTERS = [
15
- { id: '', label: 'All' },
16
- { id: 'running', label: 'Running' },
17
- { id: 'paused', label: 'Paused' },
18
- { id: 'done', label: 'Done' },
19
- { id: 'error', label: 'Error' },
17
+ { id: '', label: phrase('All') },
18
+ { id: 'running', label: phrase('Running') },
19
+ { id: 'paused', label: phrase('Paused') },
20
+ { id: 'done', label: phrase('Done') },
21
+ { id: 'error', label: phrase('Error') },
20
22
  ];
21
23
  const PAGE = 50;
22
24
  export function Find({ open, onOpenChange, recents, onOpen, }) {
25
+ const { t: say } = useI18n();
23
26
  const host = useHost();
24
27
  const t = useTarget();
25
28
  const signed = Boolean(host.person);
@@ -64,14 +67,14 @@ export function Find({ open, onOpenChange, recents, onOpen, }) {
64
67
  const hits = useMemo(() => {
65
68
  const n = q.trim().toLowerCase();
66
69
  const all = rows
67
- ? rows.map((s) => ({ id: s.id, title: s.title || 'Untitled run', line: [s.repo, s.status].filter(Boolean).join(' · ') }))
70
+ ? rows.map((s) => ({ id: s.id, title: s.title || say('Untitled run'), line: [s.repo, s.status].filter(Boolean).join(' · ') }))
68
71
  : status
69
72
  ? []
70
73
  : recents.map((r) => ({ id: r.id, title: r.title, line: '' }));
71
74
  return n ? all.filter((h) => `${h.title} ${h.line}`.toLowerCase().includes(n)) : all;
72
- }, [q, rows, recents, status]);
73
- return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: 520, children: [_jsx(DialogTitle, { children: "Find a run" }), _jsx(Input, { autoFocus: true, value: q, onChangeText: setQ, placeholder: "Search runs\u2026", "aria-label": "Search runs", onKeyDown: (e) => {
75
+ }, [q, rows, recents, status, say]);
76
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: 520, children: [_jsx(DialogTitle, { children: say('Find a run') }), _jsx(Input, { autoFocus: true, value: q, onChangeText: setQ, placeholder: say('Search runs…'), "aria-label": say('Search runs'), onKeyDown: (e) => {
74
77
  if (e.key === 'Enter' && hits[0])
75
78
  onOpen(hits[0].id);
76
- } }), _jsx(XStack, { gap: "$1", flexWrap: "wrap", role: "group", "aria-label": "Status", children: FILTERS.map((f) => (_jsx(XStack, { render: "button", "aria-label": f.label, "aria-pressed": status === f.id, onPress: () => setStatus(f.id), px: "$2.5", py: "$1", rounded: "$10", borderWidth: 1, borderColor: status === f.id ? '$ink' : '$borderColor', bg: status === f.id ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, children: _jsx(SizableText, { size: "$1", color: status === f.id ? '$ink' : '$soft', children: f.label }) }, f.id || 'all'))) }), _jsx(ScrollView, { maxH: 360, children: _jsxs(YStack, { gap: "$1", role: "list", children: [hits.map((h) => (_jsxs(XStack, { role: "listitem", render: "button", "aria-label": h.title, onPress: () => onOpen(h.id), px: "$2", py: "$1.5", rounded: "$3", hoverStyle: { bg: '$hover' }, items: "center", gap: "$3", children: [_jsx(SizableText, { flex: 1, minW: 0, size: "$2", color: "$ink", numberOfLines: 1, style: { textAlign: 'left' }, children: h.title }), h.line ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, shrink: 0, maxW: "45%", children: h.line })) : null] }, h.id))), !loading && !error && hits.length === 0 ? (_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", children: rows && next ? 'No run read so far matches.' : 'No run matches.' })) : null] }) }), error ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: error })) : null, loading ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: "Reading runs\u2026" })) : next ? (_jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "outline", onPress: () => void read(next), children: "Older runs" }) })) : null] }) }));
79
+ } }), _jsx(XStack, { gap: "$1", flexWrap: "wrap", role: "group", "aria-label": say('Status'), children: FILTERS.map((f) => (_jsx(XStack, { render: "button", "aria-label": say(f.label), "aria-pressed": status === f.id, onPress: () => setStatus(f.id), px: "$2.5", py: "$1", rounded: "$10", borderWidth: 1, borderColor: status === f.id ? '$ink' : '$borderColor', bg: status === f.id ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, children: _jsx(SizableText, { size: "$1", color: status === f.id ? '$ink' : '$soft', children: say(f.label) }) }, f.id || 'all'))) }), _jsx(ScrollView, { maxH: 360, children: _jsxs(YStack, { gap: "$1", role: "list", children: [hits.map((h) => (_jsxs(XStack, { role: "listitem", render: "button", "aria-label": h.title, onPress: () => onOpen(h.id), px: "$2", py: "$1.5", rounded: "$3", hoverStyle: { bg: '$hover' }, items: "center", gap: "$3", children: [_jsx(SizableText, { flex: 1, minW: 0, size: "$2", color: "$ink", numberOfLines: 1, style: { textAlign: 'left' }, children: h.title }), h.line ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, shrink: 0, maxW: "45%", children: h.line })) : null] }, h.id))), !loading && !error && hits.length === 0 ? (_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", children: rows && next ? say('No run read so far matches.') : say('No run matches.') })) : null] }) }), error ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: error })) : null, loading ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: say('Reading runs…') })) : next ? (_jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "outline", onPress: () => void read(next), children: say('Older runs') }) })) : null] }) }));
77
80
  }
package/lib/foot.js CHANGED
@@ -9,6 +9,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
9
9
  */
10
10
  import { useCallback, useEffect, useRef, useState } from 'react';
11
11
  import { Popover, SizableText, XStack, YStack } from '@hanzo/gui';
12
+ import { useI18n } from '@hanzo/i18n/react';
12
13
  import { CreditCard, Gauge, LifeBuoy, LogOut, Settings, Slack as Mark, Sparkles, X } from '@hanzogui/lucide-icons-2';
13
14
  import { MenuLabel, MenuRow, MenuRule } from '@hanzo/ui/product';
14
15
  import { useHost, useTarget } from './host.js';
@@ -18,7 +19,8 @@ import { path } from './route.js';
18
19
  export const DOCS = 'https://docs.hanzo.ai/docs/dev';
19
20
  /** Hanzo in Slack is set up under Settings; the card goes there, and once dismissed stays gone. */
20
21
  export function Slack({ onOpen, onDismiss }) {
21
- return (_jsxs(XStack, { items: "center", gap: "$2.5", px: "$3", py: "$2.5", rounded: "var(--radius-lg)", borderWidth: 1, borderColor: "$borderColor", bg: "var(--glass)", children: [_jsx(Mark, { size: 16 }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: "Try Hanzo in Slack" }), _jsx(XStack, { render: "button", "aria-label": "Set up Hanzo in Slack", onPress: onOpen, cursor: "pointer", children: _jsx(SizableText, { size: "$1", color: "$soft", textDecorationLine: "underline", children: "Set up" }) })] }), _jsx(XStack, { render: "button", "aria-label": "Dismiss", onPress: onDismiss, p: "$1", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(X, { size: 14 }) })] }));
22
+ const { t } = useI18n();
23
+ return (_jsxs(XStack, { items: "center", gap: "$2.5", px: "$3", py: "$2.5", rounded: "var(--radius-lg)", borderWidth: 1, borderColor: "$borderColor", bg: "var(--glass)", children: [_jsx(Mark, { size: 16 }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: t('Try Hanzo in Slack') }), _jsx(XStack, { render: "button", "aria-label": t('Set up Hanzo in Slack'), onPress: onOpen, cursor: "pointer", children: _jsx(SizableText, { size: "$1", color: "$soft", textDecorationLine: "underline", children: t('Set up') }) })] }), _jsx(XStack, { render: "button", "aria-label": t('Dismiss'), onPress: onDismiss, p: "$1", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(X, { size: 14 }) })] }));
22
24
  }
23
25
  /**
24
26
  * The account menu's open state, for whatever row opens it.
@@ -54,6 +56,7 @@ export function useWho() {
54
56
  * open rail, beside it on the collapsed one.
55
57
  */
56
58
  export function Who({ open, onOpenChange, narrow }) {
59
+ const { t } = useI18n();
57
60
  const host = useHost();
58
61
  const standing = useStanding(useTarget(), open);
59
62
  // The organizations this person acts in; choosing one reloads the page scoped to it.
@@ -62,14 +65,14 @@ export function Who({ open, onOpenChange, narrow }) {
62
65
  onOpenChange(false);
63
66
  host.go(p);
64
67
  };
65
- return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, placement: narrow ? 'right-end' : 'top-start', allowFlip: true, stayInFrame: true, offset: 6, children: [_jsx(Popover.Anchor, { position: "absolute", l: 8, b: 8, width: narrow ? 48 : 256, height: 36, pointerEvents: "none" }), _jsx(Popover.Content, { role: "menu", "aria-label": "Account", items: "stretch", borderWidth: 1, elevation: "$2", px: "$1", py: "$1", width: 260, bg: "$panel", borderColor: "$borderColor", children: _jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", py: "$1.5", numberOfLines: 1, children: host.person?.email || host.person?.name || '' }), _jsx(MenuRule, {}), _jsx(Meter, { read: standing, onPlans: () => go(path({ kind: 'screen', screen: 'plans' })), onBilling: () => go(path({ kind: 'settings', section: 'billing' })) }), orgs.length ? (_jsxs(_Fragment, { children: [_jsx(MenuRule, {}), _jsx(MenuLabel, { children: "Organization" }), _jsx(YStack, { role: "radiogroup", "aria-label": "Organizations", gap: "$1", children: orgs.map((o) => (_jsx(MenuRow, { label: o, active: o === host.org, onPress: () => {
68
+ return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, placement: narrow ? 'right-end' : 'top-start', allowFlip: true, stayInFrame: true, offset: 6, children: [_jsx(Popover.Anchor, { position: "absolute", l: 8, b: 8, width: narrow ? 48 : 256, height: 36, pointerEvents: "none" }), _jsx(Popover.Content, { role: "menu", "aria-label": t('Account'), items: "stretch", borderWidth: 1, elevation: "$2", px: "$1", py: "$1", width: 260, bg: "$panel", borderColor: "$borderColor", children: _jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", py: "$1.5", numberOfLines: 1, children: host.person?.email || host.person?.name || '' }), _jsx(MenuRule, {}), _jsx(Meter, { read: standing, onPlans: () => go(path({ kind: 'screen', screen: 'plans' })), onBilling: () => go(path({ kind: 'settings', section: 'billing' })) }), orgs.length ? (_jsxs(_Fragment, { children: [_jsx(MenuRule, {}), _jsx(MenuLabel, { children: t('Organization') }), _jsx(YStack, { role: "radiogroup", "aria-label": t('Organizations'), gap: "$1", children: orgs.map((o) => (_jsx(MenuRow, { label: o, active: o === host.org, onPress: () => {
66
69
  onOpenChange(false);
67
70
  if (o !== host.org)
68
71
  host.chooseOrg?.(o);
69
- } }, o))) })] })) : null, _jsx(MenuRule, {}), _jsx(MenuRow, { label: "Settings", icon: _jsx(Settings, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'general' })) }), _jsx(MenuRow, { label: "Usage", icon: _jsx(Gauge, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'usage' })) }), _jsx(MenuRow, { label: "Billing", icon: _jsx(CreditCard, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'billing' })) }), _jsx(MenuRow, { label: "View all plans", icon: _jsx(Sparkles, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'plans' })) }), _jsx(MenuRow, { label: "Get help", icon: _jsx(LifeBuoy, { size: 16 }), onPress: () => {
72
+ } }, o))) })] })) : null, _jsx(MenuRule, {}), _jsx(MenuRow, { label: t('Settings'), icon: _jsx(Settings, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'general' })) }), _jsx(MenuRow, { label: t('Usage'), icon: _jsx(Gauge, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'usage' })) }), _jsx(MenuRow, { label: t('Billing'), icon: _jsx(CreditCard, { size: 16 }), onPress: () => go(path({ kind: 'settings', section: 'billing' })) }), _jsx(MenuRow, { label: t('View all plans'), icon: _jsx(Sparkles, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'plans' })) }), _jsx(MenuRow, { label: t('Get help'), icon: _jsx(LifeBuoy, { size: 16 }), onPress: () => {
70
73
  onOpenChange(false);
71
74
  window.open(DOCS, '_blank', 'noopener,noreferrer');
72
- } }), host.signOut ? (_jsxs(_Fragment, { children: [_jsx(MenuRule, {}), _jsx(MenuRow, { label: "Log out", icon: _jsx(LogOut, { size: 16 }), onPress: () => {
75
+ } }), host.signOut ? (_jsxs(_Fragment, { children: [_jsx(MenuRule, {}), _jsx(MenuRow, { label: t('Log out'), icon: _jsx(LogOut, { size: 16 }), onPress: () => {
73
76
  onOpenChange(false);
74
77
  host.signOut?.();
75
78
  } })] })) : null] }) })] }));