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