@hanzo/build 0.2.10 → 0.2.11
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 +97 -25
- package/lib/api/agents.d.ts +80 -0
- package/lib/api/agents.js +148 -0
- package/lib/api/billing.d.ts +205 -0
- package/lib/api/billing.js +300 -0
- package/lib/api/call.d.ts +6 -0
- package/lib/api/call.js +11 -0
- package/lib/api/capabilities.d.ts +25 -0
- package/lib/api/capabilities.js +40 -0
- package/lib/api/coding.d.ts +31 -1
- package/lib/api/coding.js +50 -1
- package/lib/api/connectors.d.ts +90 -0
- package/lib/api/connectors.js +109 -0
- package/lib/api/consent.d.ts +22 -0
- package/lib/api/consent.js +28 -0
- package/lib/api/github.d.ts +13 -0
- package/lib/api/github.js +9 -0
- package/lib/api/harness.d.ts +44 -0
- package/lib/api/harness.js +348 -0
- package/lib/api/keys.d.ts +41 -0
- package/lib/api/keys.js +55 -0
- package/lib/api/machines.d.ts +56 -0
- package/lib/api/machines.js +84 -0
- package/lib/api/members.d.ts +47 -0
- package/lib/api/members.js +88 -0
- package/lib/api/memory.d.ts +25 -0
- package/lib/api/memory.js +34 -0
- package/lib/api/places.d.ts +2 -2
- package/lib/api/places.js +3 -26
- package/lib/api/plugins.d.ts +55 -0
- package/lib/api/plugins.js +62 -0
- package/lib/api/pref.d.ts +49 -0
- package/lib/api/pref.js +68 -0
- package/lib/api/profile.d.ts +21 -0
- package/lib/api/profile.js +40 -0
- package/lib/api/projects.d.ts +19 -2
- package/lib/api/projects.js +29 -4
- package/lib/api/provider.d.ts +42 -0
- package/lib/api/provider.js +58 -0
- package/lib/api/sessions.d.ts +32 -4
- package/lib/api/sessions.js +42 -6
- package/lib/api/skills.d.ts +70 -0
- package/lib/api/skills.js +90 -0
- package/lib/api/tools.d.ts +34 -0
- package/lib/api/tools.js +37 -0
- package/lib/api/turn.d.ts +84 -16
- package/lib/api/turn.js +221 -4
- package/lib/api/webhooks.d.ts +60 -0
- package/lib/api/webhooks.js +81 -0
- package/lib/ask.d.ts +18 -0
- package/lib/ask.js +69 -0
- package/lib/builder.js +39 -19
- package/lib/customize/agents.d.ts +2 -0
- package/lib/customize/agents.js +125 -0
- package/lib/customize/connectors.d.ts +2 -0
- package/lib/customize/connectors.js +197 -0
- package/lib/customize/index.d.ts +6 -0
- package/lib/customize/index.js +50 -0
- package/lib/customize/plugins.d.ts +2 -0
- package/lib/customize/plugins.js +93 -0
- package/lib/customize/skills.d.ts +2 -0
- package/lib/customize/skills.js +125 -0
- package/lib/customize/ui.d.ts +110 -0
- package/lib/customize/ui.js +99 -0
- package/lib/desk.d.ts +4 -1
- package/lib/desk.js +3 -16
- package/lib/find.d.ts +7 -0
- package/lib/find.js +77 -0
- package/lib/foot.d.ts +16 -0
- package/lib/foot.js +36 -0
- package/lib/forge.js +4 -4
- package/lib/git.d.ts +4 -0
- package/lib/git.js +6 -2
- package/lib/host.d.ts +5 -0
- package/lib/landing.d.ts +10 -0
- package/lib/landing.js +30 -14
- package/lib/markdown.d.ts +49 -0
- package/lib/markdown.js +123 -0
- package/lib/plans.d.ts +1 -0
- package/lib/plans.js +113 -0
- package/lib/prefs.d.ts +30 -0
- package/lib/prefs.js +83 -0
- package/lib/prose.d.ts +4 -0
- package/lib/prose.js +53 -0
- package/lib/route.d.ts +16 -1
- package/lib/route.js +32 -1
- package/lib/run.js +184 -32
- package/lib/section.js +2 -2
- package/lib/settings/account.d.ts +5 -0
- package/lib/settings/account.js +111 -0
- package/lib/settings/billing.d.ts +1 -0
- package/lib/settings/billing.js +104 -0
- package/lib/settings/capabilities.d.ts +1 -0
- package/lib/settings/capabilities.js +55 -0
- package/lib/settings/card.d.ts +31 -0
- package/lib/settings/card.js +105 -0
- package/lib/settings/code.d.ts +1 -0
- package/lib/settings/code.js +51 -0
- package/lib/settings/environments.d.ts +1 -0
- package/lib/settings/environments.js +53 -0
- package/lib/settings/general.d.ts +1 -0
- package/lib/settings/general.js +44 -0
- package/lib/settings/index.d.ts +4 -0
- package/lib/settings/index.js +22 -0
- package/lib/settings/integrations.d.ts +1 -0
- package/lib/settings/integrations.js +81 -0
- package/lib/settings/keys.d.ts +1 -0
- package/lib/settings/keys.js +102 -0
- package/lib/settings/machines.d.ts +1 -0
- package/lib/settings/machines.js +118 -0
- package/lib/settings/members.d.ts +1 -0
- package/lib/settings/members.js +67 -0
- package/lib/settings/memory.d.ts +1 -0
- package/lib/settings/memory.js +56 -0
- package/lib/settings/notifications.d.ts +1 -0
- package/lib/settings/notifications.js +97 -0
- package/lib/settings/privacy.d.ts +1 -0
- package/lib/settings/privacy.js +63 -0
- package/lib/settings/sections.d.ts +16 -0
- package/lib/settings/sections.js +32 -0
- package/lib/settings/ui.d.ts +49 -0
- package/lib/settings/ui.js +47 -0
- package/lib/settings/usage.d.ts +1 -0
- package/lib/settings/usage.js +114 -0
- package/lib/shelf.d.ts +6 -0
- package/lib/shelf.js +86 -16
- package/lib/switch.js +1 -2
- package/lib/transcript.d.ts +12 -0
- package/lib/transcript.js +56 -0
- package/lib/voice.d.ts +3 -1
- package/lib/voice.js +10 -4
- package/package.json +1 -1
- package/lib/account.d.ts +0 -14
- package/lib/account.js +0 -38
- package/lib/mcp.d.ts +0 -1
- package/lib/mcp.js +0 -28
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Account: who is signed in and how Hanzo should know them — the photo and
|
|
4
|
+
* full name on their Hanzo identity, what to call them, what they work on and
|
|
5
|
+
* the instructions every coding run reads — then the organizations they belong
|
|
6
|
+
* to, the one this page acts in, a way to act in another, and logging out.
|
|
7
|
+
*
|
|
8
|
+
* The email is how the person signs in, so it is shown and not edited here.
|
|
9
|
+
*/
|
|
10
|
+
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
11
|
+
import { Check, Copy, LogOut } from '@hanzogui/lucide-icons-2';
|
|
12
|
+
import { Button, Input, Textarea } from '@hanzo/ui';
|
|
13
|
+
import { ChipSelect } from '@hanzo/ui/product';
|
|
14
|
+
import { useEffect, useRef, useState } from 'react';
|
|
15
|
+
import { PHOTO, photo, rename } from '../api/profile.js';
|
|
16
|
+
import { useHost, useTarget } from '../host.js';
|
|
17
|
+
import { usePrefs } from '../prefs.js';
|
|
18
|
+
import { Card, Field, Group, Heading, Note, Row, Soft } from './ui.js';
|
|
19
|
+
export const WORK = [
|
|
20
|
+
{ id: 'engineering', label: 'Software engineering' },
|
|
21
|
+
{ id: 'data', label: 'Data science' },
|
|
22
|
+
{ id: 'design', label: 'Design' },
|
|
23
|
+
{ id: 'product', label: 'Product management' },
|
|
24
|
+
{ id: 'research', label: 'Research' },
|
|
25
|
+
{ id: 'writing', label: 'Writing' },
|
|
26
|
+
{ id: 'education', label: 'Education' },
|
|
27
|
+
{ id: 'operations', label: 'Operations' },
|
|
28
|
+
{ id: 'marketing', label: 'Marketing and sales' },
|
|
29
|
+
{ id: 'other', label: 'Something else' },
|
|
30
|
+
];
|
|
31
|
+
/** Long enough for real guidance, short enough to sit in the settings document beside everything else. */
|
|
32
|
+
const INSTRUCTIONS = 3000;
|
|
33
|
+
export function Account() {
|
|
34
|
+
const host = useHost();
|
|
35
|
+
const t = useTarget();
|
|
36
|
+
const { prefs, save } = usePrefs();
|
|
37
|
+
const who = host.person;
|
|
38
|
+
const [name, setName] = useState(who?.name ?? '');
|
|
39
|
+
const [shownName, setShownName] = useState(who?.name ?? '');
|
|
40
|
+
const [face, setFace] = useState(who?.avatar ?? '');
|
|
41
|
+
const [callName, setCallName] = useState('');
|
|
42
|
+
const [work, setWork] = useState('');
|
|
43
|
+
const [instructions, setInstructions] = useState('');
|
|
44
|
+
const [busy, setBusy] = useState(false);
|
|
45
|
+
const [note, setNote] = useState('');
|
|
46
|
+
const picker = useRef(null);
|
|
47
|
+
const [copied, setCopied] = useState(false);
|
|
48
|
+
// The identity can arrive after the page does.
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
setName(who?.name ?? '');
|
|
51
|
+
setShownName(who?.name ?? '');
|
|
52
|
+
setFace(who?.avatar ?? '');
|
|
53
|
+
}, [who?.name, who?.avatar]);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
setCallName(prefs.callName ?? '');
|
|
56
|
+
setWork(prefs.work ?? '');
|
|
57
|
+
setInstructions(prefs.instructions ?? '');
|
|
58
|
+
}, [prefs.callName, prefs.work, prefs.instructions]);
|
|
59
|
+
if (!who) {
|
|
60
|
+
return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Account" }), _jsx(Soft, { children: "Sign in to see your account." }), host.signIn ? (_jsx(XStack, { justify: "center", children: _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: "Sign in" }) })) : null] }));
|
|
61
|
+
}
|
|
62
|
+
const patch = {};
|
|
63
|
+
if (callName.trim() !== (prefs.callName ?? ''))
|
|
64
|
+
patch.callName = callName.trim() || null;
|
|
65
|
+
if (work !== (prefs.work ?? ''))
|
|
66
|
+
patch.work = work || null;
|
|
67
|
+
if (instructions.trim() !== (prefs.instructions ?? '').trim())
|
|
68
|
+
patch.instructions = instructions.trim() || null;
|
|
69
|
+
const renamed = name.trim() !== shownName.trim();
|
|
70
|
+
const changed = renamed || Object.keys(patch).length > 0;
|
|
71
|
+
const submit = async () => {
|
|
72
|
+
setBusy(true);
|
|
73
|
+
setNote('');
|
|
74
|
+
try {
|
|
75
|
+
if (renamed)
|
|
76
|
+
setShownName((await rename(t, name)).displayName || name.trim());
|
|
77
|
+
if (Object.keys(patch).length)
|
|
78
|
+
await save(patch);
|
|
79
|
+
setNote('Saved');
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
setNote(e instanceof Error ? e.message : 'That was not saved');
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
setBusy(false);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const upload = async (file) => {
|
|
89
|
+
setBusy(true);
|
|
90
|
+
setNote('');
|
|
91
|
+
try {
|
|
92
|
+
setFace(await photo(t, file));
|
|
93
|
+
setNote('Your photo is saved');
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
setNote(e instanceof Error ? e.message : 'The photo was not saved');
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
setBusy(false);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const orgs = host.memberships ?? (host.org ? [host.org] : []);
|
|
103
|
+
const chosenWork = WORK.find((w) => w.id === work) ?? (work ? { id: work, label: work } : null);
|
|
104
|
+
return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Account", detail: "Your Hanzo identity, and what Hanzo should know about you.", action: host.signOut ? (_jsxs(Button, { size: "sm", variant: "outline", onPress: () => host.signOut?.(), children: [_jsx(LogOut, { size: 14 }), " Log out"] })) : undefined }), _jsx(Group, { title: "Profile", children: _jsxs(YStack, { gap: "$4", children: [_jsxs(XStack, { items: "center", gap: "$3", children: [face ? (_jsx("img", { src: face, alt: "", width: 48, height: 48, style: { borderRadius: 999, objectFit: 'cover' } })) : (_jsx(YStack, { width: 48, height: 48, rounded: 999, bg: "$raised", items: "center", justify: "center", children: _jsx(SizableText, { size: "$5", color: "$ink", children: (shownName || who.email || '?').charAt(0).toUpperCase() }) })), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$4", color: "$ink", numberOfLines: 1, children: shownName || who.email }), _jsx(SizableText, { size: "$2", color: "$soft", numberOfLines: 1, children: who.email })] }), _jsx(Button, { size: "sm", variant: "outline", disabled: busy, onPress: () => picker.current?.click(), children: "Change photo" }), _jsx("input", { ref: picker, type: "file", accept: PHOTO, hidden: true, style: { display: 'none' }, tabIndex: -1, "aria-label": "Photo", onChange: (e) => {
|
|
105
|
+
const input = e.currentTarget;
|
|
106
|
+
const file = input.files?.[0];
|
|
107
|
+
input.value = '';
|
|
108
|
+
if (file)
|
|
109
|
+
void upload(file);
|
|
110
|
+
} })] }), _jsx(Field, { label: "Full name", children: _jsx(Input, { value: name, onChangeText: setName, "aria-label": "Full name", placeholder: "Your name" }) }), _jsx(Field, { label: "Email", hint: "You sign in with this address, so it is not changed here.", children: _jsx(SizableText, { size: "$2", color: "$soft", numberOfLines: 1, children: who.email || 'No email on this account' }) }), _jsx(Field, { label: "What should Hanzo call you?", children: _jsx(Input, { value: callName, onChangeText: setCallName, "aria-label": "What should Hanzo call you?", placeholder: shownName.split(' ')[0] || 'A first name' }) }), _jsx(Field, { label: "What best describes your work?", children: _jsx(XStack, { children: _jsx(ChipSelect, { name: "Work", label: chosenWork?.label ?? 'Choose one', chosen: chosenWork, items: WORK, onChange: (w) => setWork(w.id), placement: "bottom-start", width: 260 }) }) }), _jsx(Field, { label: "Instructions for Hanzo", hint: `Every coding run reads these before it starts. ${instructions.length}/${INSTRUCTIONS}`, children: _jsx(Textarea, { value: instructions, onChangeText: (v) => setInstructions(v.slice(0, INSTRUCTIONS)), "aria-label": "Instructions for Hanzo", placeholder: "How you like to work: the languages and tools you prefer, the style to follow, what to avoid.", rows: 5 }) }), _jsxs(XStack, { items: "center", gap: "$3", children: [_jsx(Button, { size: "sm", disabled: busy || !changed, onPress: () => void submit(), children: "Save" }), _jsx(Note, { children: note })] })] }) }), host.org ? (_jsx(Group, { title: "Organization", detail: "The organization this page acts in. Runs, codebases and settings belong to it.", children: _jsx(Card, { children: _jsx(Row, { first: true, mono: true, title: host.org, detail: `Organization ID${host.admin ? ' · you are an admin' : ''}`, trailing: _jsx(Button, { size: "icon-sm", variant: "ghost", "aria-label": copied ? 'Organization ID copied' : 'Copy organization ID', onPress: () => void navigator.clipboard?.writeText(host.org ?? '').then(() => setCopied(true)), children: copied ? _jsx(Check, { size: 14 }) : _jsx(Copy, { size: 14 }) }) }) }) })) : null, _jsx(Group, { title: "Organizations", detail: "Every organization you belong to. Switch to act in another.", children: orgs.length ? (_jsx(Card, { children: orgs.map((o, i) => (_jsx(Row, { first: i === 0, title: o, detail: o === host.org ? `Acting in ${o}${host.admin ? ' as an admin' : ''}` : undefined, trailing: o === host.org ? (_jsx(Check, { size: 16 })) : host.chooseOrg ? (_jsx(Button, { size: "sm", variant: "ghost", onPress: () => host.chooseOrg?.(o), children: "Switch" })) : null }, o))) })) : (_jsx(Soft, { children: "You belong to no organization yet." })) })] }));
|
|
111
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function Billing(): import("react").JSX.Element;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Billing: the plan this organization is on and the way to change it, the cards
|
|
4
|
+
* it pays with, its invoices, and ending the plan. Changing plan is the Plans
|
|
5
|
+
* screen; this page says what is true now.
|
|
6
|
+
*/
|
|
7
|
+
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
8
|
+
import { CreditCard, Download, Plus, X } from '@hanzogui/lucide-icons-2';
|
|
9
|
+
import { Button, Dialog, DialogContent, DialogTitle } from '@hanzo/ui';
|
|
10
|
+
import { useState } from 'react';
|
|
11
|
+
import { cancel, current, detach, invoices, label, methods, money, pdf, reactivate, subscriptions, } from '../api/billing.js';
|
|
12
|
+
import { useRead } from '../data.js';
|
|
13
|
+
import { useHost, useTarget } from '../host.js';
|
|
14
|
+
import { path } from '../route.js';
|
|
15
|
+
import { AddCard } from './card.js';
|
|
16
|
+
import { Card, day, Group, Heading, Note, Row, Soft } from './ui.js';
|
|
17
|
+
export function Billing() {
|
|
18
|
+
const host = useHost();
|
|
19
|
+
const t = useTarget();
|
|
20
|
+
const signed = Boolean(host.person);
|
|
21
|
+
const subs = useRead(signed ? () => subscriptions(t) : null, [], [t, signed]);
|
|
22
|
+
const cards = useRead(signed ? () => methods(t) : null, [], [t, signed]);
|
|
23
|
+
const bills = useRead(signed ? () => invoices(t) : null, [], [t, signed]);
|
|
24
|
+
const [adding, setAdding] = useState(false);
|
|
25
|
+
const [ending, setEnding] = useState(false);
|
|
26
|
+
const [open, setOpen] = useState(null);
|
|
27
|
+
const [working, setWorking] = useState(false);
|
|
28
|
+
const [note, setNote] = useState('');
|
|
29
|
+
if (!signed) {
|
|
30
|
+
return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Billing" }), _jsx(Soft, { children: "Sign in to see this organization\u2019s billing." })] }));
|
|
31
|
+
}
|
|
32
|
+
const plan = current(subs.value);
|
|
33
|
+
const act = async (what, done, after) => {
|
|
34
|
+
setWorking(true);
|
|
35
|
+
setNote('');
|
|
36
|
+
try {
|
|
37
|
+
await what();
|
|
38
|
+
after();
|
|
39
|
+
setNote(done);
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
setNote(e instanceof Error ? e.message : 'That did not work');
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
setWorking(false);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Billing", detail: `The plan ${host.org ?? 'this organization'} is on, the cards it pays with, and its invoices.` }), _jsxs(XStack, { items: "center", gap: "$3", px: "$4", py: "$4", borderWidth: 1, borderColor: "$borderColor", rounded: "$3", flexWrap: "wrap", children: [_jsxs(YStack, { flex: 1, minW: 200, gap: "$1", children: [_jsx(SizableText, { size: "$5", color: "$ink", children: subs.loading && !subs.value.length ? 'Reading your plan…' : plan ? `${plan.name} plan` : 'Free plan' }), _jsx(SizableText, { size: "$2", color: "$soft", children: subs.error
|
|
49
|
+
? subs.error.message
|
|
50
|
+
: plan
|
|
51
|
+
? terms(plan)
|
|
52
|
+
: 'No paid plan. Usage is paid from the balance.' })] }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => host.go(path({ kind: 'screen', screen: 'plans' })), children: "Adjust plan" })] }), _jsx(Group, { title: "Payment", detail: "The card a plan renews on and a top-up charges.", action: host.admin ? (_jsxs(Button, { size: "sm", variant: "outline", onPress: () => setAdding(true), children: [_jsx(Plus, { size: 14 }), " Add card"] })) : undefined, children: cards.error ? (_jsx(Soft, { children: cards.error.message })) : cards.loading && !cards.value.length ? (_jsx(Soft, { children: "Reading cards\u2026" })) : cards.value.length === 0 ? (_jsx(Card, { children: _jsx(Soft, { children: "No card on file." }) })) : (_jsx(Card, { children: cards.value.map((m, i) => (_jsx(Row, { first: i === 0, leading: _jsx(CreditCard, { size: 16 }), title: label(m), detail: [m.expires ? `Expires ${m.expires}` : '', m.default ? 'Default' : ''].filter(Boolean).join(' · ') || undefined, trailing: host.admin ? (_jsx(XStack, { render: "button", "aria-label": `Remove ${label(m)}`, p: "$1", onPress: () => void act(() => detach(t, m.id), `${label(m)} is removed`, cards.reload), children: _jsx(X, { size: 14 }) })) : undefined }, m.id))) })) }), _jsx(Group, { title: "Invoices", children: bills.error ? (_jsx(Soft, { children: bills.error.message })) : bills.loading && !bills.value.length ? (_jsx(Soft, { children: "Reading invoices\u2026" })) : bills.value.length === 0 ? (_jsx(Card, { children: _jsx(Soft, { children: "No invoices yet." }) })) : (_jsxs(Card, { children: [_jsxs(XStack, { px: "$3", py: "$2", gap: "$3", children: [_jsx(SizableText, { flex: 1, size: "$1", color: "$soft", children: "Date" }), _jsx(SizableText, { width: 90, size: "$1", color: "$soft", style: { textAlign: 'right' }, children: "Total" }), _jsx(SizableText, { width: 70, size: "$1", color: "$soft", "$max-sm": { display: 'none' }, children: "Status" }), _jsx(YStack, { width: 64 })] }), bills.value.map((b) => (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { flex: 1, size: "$2", color: "$ink", numberOfLines: 1, children: day(b.date) || b.number || b.id }), _jsx(SizableText, { width: 90, size: "$2", color: "$ink", style: { textAlign: 'right' }, children: money(b.total, b.currency) }), _jsx(SizableText, { width: 70, size: "$1", color: "$soft", "$max-sm": { display: 'none' }, children: word(b.status) }), _jsx(XStack, { width: 64, justify: "flex-end", children: _jsx(Button, { size: "sm", variant: "ghost", "aria-label": `View invoice ${b.number || day(b.date)}`, onPress: () => setOpen(b), children: "View" }) })] }, b.id)))] })) }), plan ? (_jsx(Group, { title: "Cancellation", children: _jsx(Card, { children: _jsx(Row, { first: true, title: plan.ending ? `Your plan ends on ${day(plan.ends)}` : 'Cancel plan', detail: plan.ending ? 'Keep it, and it renews as before.' : 'It runs to the end of the period you paid for, then ends.', trailing: !host.admin ? undefined : plan.ending ? (_jsx(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void act(() => reactivate(t, plan.id), 'Your plan renews as before', subs.reload), children: "Keep plan" })) : (_jsx(Button, { size: "sm", variant: "destructive", disabled: working, onPress: () => setEnding(true), children: "Cancel" })) }) }) })) : null, _jsx(Note, { children: note }), _jsx(AddCard, { open: adding, onOpenChange: setAdding, onSaved: (m) => {
|
|
53
|
+
cards.reload();
|
|
54
|
+
setNote(`${label(m)} is saved`);
|
|
55
|
+
} }), _jsx(Ending, { plan: plan, open: ending, working: working, onOpenChange: setEnding, onConfirm: () => {
|
|
56
|
+
if (!plan)
|
|
57
|
+
return;
|
|
58
|
+
void act(() => cancel(t, plan.id), `Your plan ends on ${day(plan.ends)}`, () => {
|
|
59
|
+
setEnding(false);
|
|
60
|
+
subs.reload();
|
|
61
|
+
});
|
|
62
|
+
} }), _jsx(Bill, { invoice: open, onClose: () => setOpen(null) })] }));
|
|
63
|
+
}
|
|
64
|
+
/** What the plan costs, and when it renews or ends. */
|
|
65
|
+
function terms(p) {
|
|
66
|
+
const cost = `${money(p.price * p.seats, p.currency)} a ${p.interval}${p.seats > 1 ? ` for ${p.seats} seats` : ''}.`;
|
|
67
|
+
return p.ends ? `${cost} ${p.ending ? 'Ends' : 'Renews'} on ${day(p.ends)}.` : cost;
|
|
68
|
+
}
|
|
69
|
+
const word = (s) => (s ? s.charAt(0).toUpperCase() + s.slice(1).replace(/_/g, ' ') : '');
|
|
70
|
+
function Ending({ plan, open, working, onOpenChange, onConfirm, }) {
|
|
71
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: 420, showCloseButton: false, children: [_jsx(DialogTitle, { children: "Cancel your plan?" }), _jsxs(YStack, { gap: "$3", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: plan
|
|
72
|
+
? `${plan.name} stays on until ${day(plan.ends) || 'the end of this period'}, and nothing is charged after that. You can keep it any time before then.`
|
|
73
|
+
: '' }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => onOpenChange(false), children: "Keep plan" }), _jsx(Button, { size: "sm", variant: "destructive", disabled: working, onPress: onConfirm, children: "Cancel plan" })] })] })] }) }));
|
|
74
|
+
}
|
|
75
|
+
/** One invoice, read in the page, with its PDF to keep. */
|
|
76
|
+
function Bill({ invoice, onClose }) {
|
|
77
|
+
const t = useTarget();
|
|
78
|
+
const [note, setNote] = useState('');
|
|
79
|
+
const [working, setWorking] = useState(false);
|
|
80
|
+
const b = invoice;
|
|
81
|
+
const keep = async () => {
|
|
82
|
+
if (!b)
|
|
83
|
+
return;
|
|
84
|
+
setWorking(true);
|
|
85
|
+
setNote('');
|
|
86
|
+
try {
|
|
87
|
+
const blob = await pdf(t, b.id);
|
|
88
|
+
const url = URL.createObjectURL(blob);
|
|
89
|
+
const a = document.createElement('a');
|
|
90
|
+
a.href = url;
|
|
91
|
+
a.download = `${b.number || b.id}.pdf`;
|
|
92
|
+
a.click();
|
|
93
|
+
window.setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
setNote(e instanceof Error ? e.message : 'The PDF did not download');
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
setWorking(false);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const line = (what, cents, strong = false) => (_jsxs(XStack, { justify: "space-between", gap: "$3", children: [_jsx(SizableText, { size: "$2", color: strong ? '$ink' : '$soft', children: what }), _jsx(SizableText, { size: "$2", color: strong ? '$ink' : '$soft', children: b ? money(cents, b.currency) : '' })] }, what));
|
|
103
|
+
return (_jsx(Dialog, { open: Boolean(b), onOpenChange: (o) => (o ? undefined : onClose()), children: _jsxs(DialogContent, { maxW: 480, showCloseButton: false, children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [_jsx(DialogTitle, { children: b?.number ? `Invoice ${b.number}` : 'Invoice' }), _jsx(XStack, { render: "button", "aria-label": "Close", p: "$1", onPress: onClose, children: _jsx(X, { size: 16 }) })] }), b ? (_jsxs(YStack, { gap: "$3", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: [day(b.date), word(b.status), b.start && b.end ? `for ${day(b.start)} – ${day(b.end)}` : ''].filter(Boolean).join(' · ') }), _jsx(YStack, { gap: "$1.5", borderTopWidth: 1, borderColor: "$borderColor", pt: "$3", children: b.lines.length ? b.lines.map((l, i) => line(l.description || `Line ${i + 1}`, l.amount)) : line('Subtotal', b.subtotal) }), _jsxs(YStack, { gap: "$1.5", borderTopWidth: 1, borderColor: "$borderColor", pt: "$3", children: [b.discount ? line('Discount', -b.discount) : null, b.tax ? line('Tax', b.tax) : null, line('Total', b.total, true), b.credit ? line('Credit applied', -b.credit) : null, line('Paid', b.paid), b.due ? line('Due', b.due, true) : null] }), _jsx(Note, { children: note }), _jsx(XStack, { justify: "flex-end", children: _jsxs(Button, { size: "sm", variant: "outline", disabled: working, onPress: () => void keep(), children: [_jsx(Download, { size: 14 }), " Download PDF"] }) })] })) : null] }) }));
|
|
104
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function Capabilities(): import("react").JSX.Element;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Capabilities: what the agent may use, by where it comes from — connector
|
|
4
|
+
* actions, functions, services, agents, skills and the organization's own MCP
|
|
5
|
+
* servers. A tool that is off is still listed to the agent's discovery but is
|
|
6
|
+
* refused when called. Switching a kind on turns on every tool of that kind;
|
|
7
|
+
* off turns them all off. It is the organization's setting, not only yours.
|
|
8
|
+
*/
|
|
9
|
+
import { SizableText, YStack } from '@hanzo/gui';
|
|
10
|
+
import { Switch } from '@hanzo/ui';
|
|
11
|
+
import { useState } from 'react';
|
|
12
|
+
import { activate, SOURCES, tools } from '../api/capabilities.js';
|
|
13
|
+
import { useRead } from '../data.js';
|
|
14
|
+
import { useHost, useTarget } from '../host.js';
|
|
15
|
+
import { Card, Group, Heading, Note, Row, Soft } from './ui.js';
|
|
16
|
+
const KINDS = {
|
|
17
|
+
connector: { label: 'Connector actions', about: 'Actions in the apps your organization connected' },
|
|
18
|
+
function: { label: 'Functions', about: 'Functions your organization wrote' },
|
|
19
|
+
'zap-service': { label: 'Services', about: 'Routes of the services your organization runs' },
|
|
20
|
+
agent: { label: 'Agents', about: 'Your organization’s agents, called as tools' },
|
|
21
|
+
skill: { label: 'Skills', about: 'Skills attached to agents' },
|
|
22
|
+
mcp: { label: 'MCP servers', about: 'Tools on the MCP servers your organization added' },
|
|
23
|
+
};
|
|
24
|
+
export function Capabilities() {
|
|
25
|
+
const host = useHost();
|
|
26
|
+
const t = useTarget();
|
|
27
|
+
const signed = Boolean(host.person);
|
|
28
|
+
const list = useRead(signed ? () => tools(t) : null, [], [t, signed]);
|
|
29
|
+
const [busy, setBusy] = useState('');
|
|
30
|
+
const [note, setNote] = useState('');
|
|
31
|
+
if (!signed) {
|
|
32
|
+
return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Capabilities" }), _jsx(Soft, { children: "Sign in to see what the agent may use." })] }));
|
|
33
|
+
}
|
|
34
|
+
const kinds = SOURCES.map((s) => ({ source: s, all: list.value.filter((x) => x.source === s) })).filter((k) => k.all.length);
|
|
35
|
+
const flip = async (source, all, on) => {
|
|
36
|
+
setBusy(source);
|
|
37
|
+
setNote('');
|
|
38
|
+
try {
|
|
39
|
+
await activate(t, all.map((x) => x.name), on);
|
|
40
|
+
list.reload();
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
setNote(e instanceof Error ? e.message : 'That was not saved');
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
setBusy('');
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Capabilities", detail: `What the agent may use in ${host.org ?? 'this organization'}. A tool that is off is refused when the agent calls it.` }), _jsx(Group, { title: "Tools", detail: host.admin
|
|
50
|
+
? 'Switching a kind on or off applies to every tool of that kind, for everyone in the organization.'
|
|
51
|
+
: 'An org admin switches these, for everyone in the organization.', children: list.error ? (_jsx(Soft, { children: list.error.message })) : list.loading && !list.value.length ? (_jsx(Soft, { children: "Reading tools\u2026" })) : !kinds.length ? (_jsx(Soft, { children: "No tools are available to this organization yet." })) : (_jsx(Card, { children: kinds.map(({ source, all }, i) => {
|
|
52
|
+
const on = all.filter((x) => x.activated).length;
|
|
53
|
+
return (_jsx(Row, { first: i === 0, title: KINDS[source].label, detail: `${KINDS[source].about}. ${on === all.length ? `All ${all.length} on` : on ? `${on} of ${all.length} on` : `${all.length} available, all off`}.`, trailing: host.admin ? (_jsx(Switch, { "aria-label": KINDS[source].label, checked: on === all.length, disabled: busy !== '', onCheckedChange: (v) => void flip(source, all, v) })) : (_jsx(SizableText, { size: "$1", color: "$soft", children: on === all.length ? 'On' : on ? 'Partly on' : 'Off' })) }, source));
|
|
54
|
+
}) })) }), _jsx(Note, { children: note })] }));
|
|
55
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type Method } from '../api/billing.ts';
|
|
2
|
+
/** The part of Square's Web Payments SDK this uses. */
|
|
3
|
+
interface Field {
|
|
4
|
+
attach(el: HTMLElement): Promise<void>;
|
|
5
|
+
tokenize(): Promise<{
|
|
6
|
+
status: string;
|
|
7
|
+
token?: string;
|
|
8
|
+
errors?: {
|
|
9
|
+
message?: string;
|
|
10
|
+
}[];
|
|
11
|
+
}>;
|
|
12
|
+
destroy(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
interface Sdk {
|
|
15
|
+
payments(application: string, location: string): {
|
|
16
|
+
card(options?: object): Promise<Field>;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
declare global {
|
|
20
|
+
interface Window {
|
|
21
|
+
Square?: Sdk;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Anything but production loads the sandbox SDK, so a misread setting never arms real charges. */
|
|
25
|
+
export declare const sdk: (environment: string) => string;
|
|
26
|
+
export declare function AddCard({ open, onOpenChange, onSaved }: {
|
|
27
|
+
open: boolean;
|
|
28
|
+
onOpenChange: (o: boolean) => void;
|
|
29
|
+
onSaved: (m: Method) => void;
|
|
30
|
+
}): import("react").JSX.Element;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Adding a card. The card number is typed into the payment processor's own
|
|
4
|
+
* field — its iframe, served from its own host — and never reaches this page or
|
|
5
|
+
* the platform: the field hands back a single-use token, and the platform vaults
|
|
6
|
+
* the card from that. Which processor, and its sandbox or live account, is the
|
|
7
|
+
* platform's answer at /v1/billing/settings, so the card is saved to the account
|
|
8
|
+
* it will be charged on.
|
|
9
|
+
*/
|
|
10
|
+
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
11
|
+
import { X } from '@hanzogui/lucide-icons-2';
|
|
12
|
+
import { Button, Dialog, DialogContent, DialogTitle } from '@hanzo/ui';
|
|
13
|
+
import { useEffect, useRef, useState } from 'react';
|
|
14
|
+
import { processor, save } from '../api/billing.js';
|
|
15
|
+
import { useRead } from '../data.js';
|
|
16
|
+
import { useTarget } from '../host.js';
|
|
17
|
+
/** Anything but production loads the sandbox SDK, so a misread setting never arms real charges. */
|
|
18
|
+
export const sdk = (environment) => environment === 'production' ? 'https://web.squarecdn.com/v1/square.js' : 'https://sandbox.web.squarecdn.com/v1/square.js';
|
|
19
|
+
const loads = new Map();
|
|
20
|
+
function load(environment) {
|
|
21
|
+
if (window.Square)
|
|
22
|
+
return Promise.resolve(window.Square);
|
|
23
|
+
const src = sdk(environment);
|
|
24
|
+
const was = loads.get(src);
|
|
25
|
+
if (was)
|
|
26
|
+
return was;
|
|
27
|
+
const p = new Promise((resolve, reject) => {
|
|
28
|
+
const s = document.createElement('script');
|
|
29
|
+
s.src = src;
|
|
30
|
+
s.async = true;
|
|
31
|
+
s.onload = () => (window.Square ? resolve(window.Square) : reject(new Error('The card form did not load')));
|
|
32
|
+
s.onerror = () => {
|
|
33
|
+
loads.delete(src);
|
|
34
|
+
reject(new Error('The card form did not load'));
|
|
35
|
+
};
|
|
36
|
+
document.head.appendChild(s);
|
|
37
|
+
});
|
|
38
|
+
loads.set(src, p);
|
|
39
|
+
return p;
|
|
40
|
+
}
|
|
41
|
+
const ready = (p) => Boolean(p && p.provider === 'square' && p.application && p.location);
|
|
42
|
+
export function AddCard({ open, onOpenChange, onSaved }) {
|
|
43
|
+
const t = useTarget();
|
|
44
|
+
const cfg = useRead(open ? () => processor(t) : null, null, [t, open]);
|
|
45
|
+
const [box, setBox] = useState(null);
|
|
46
|
+
const field = useRef(null);
|
|
47
|
+
const [mounted, setMounted] = useState(false);
|
|
48
|
+
const [working, setWorking] = useState(false);
|
|
49
|
+
const [note, setNote] = useState('');
|
|
50
|
+
const p = cfg.value;
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!open || !box || !ready(p))
|
|
53
|
+
return;
|
|
54
|
+
let gone = false;
|
|
55
|
+
setMounted(false);
|
|
56
|
+
setNote('');
|
|
57
|
+
load(p.environment)
|
|
58
|
+
.then((square) => square.payments(p.application, p.location).card())
|
|
59
|
+
.then(async (f) => {
|
|
60
|
+
if (gone) {
|
|
61
|
+
void f.destroy().catch(() => { });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
await f.attach(box);
|
|
65
|
+
field.current = f;
|
|
66
|
+
setMounted(true);
|
|
67
|
+
})
|
|
68
|
+
.catch((e) => {
|
|
69
|
+
if (!gone)
|
|
70
|
+
setNote(e instanceof Error ? e.message : 'The card form did not load');
|
|
71
|
+
});
|
|
72
|
+
return () => {
|
|
73
|
+
gone = true;
|
|
74
|
+
const f = field.current;
|
|
75
|
+
field.current = null;
|
|
76
|
+
if (f)
|
|
77
|
+
void f.destroy().catch(() => { });
|
|
78
|
+
};
|
|
79
|
+
}, [open, box, p]);
|
|
80
|
+
const add = async () => {
|
|
81
|
+
const f = field.current;
|
|
82
|
+
if (!f)
|
|
83
|
+
return;
|
|
84
|
+
setWorking(true);
|
|
85
|
+
setNote('');
|
|
86
|
+
try {
|
|
87
|
+
const r = await f.tokenize();
|
|
88
|
+
if (r.status !== 'OK' || !r.token)
|
|
89
|
+
throw new Error(r.errors?.map((e) => e.message).filter(Boolean).join('; ') || 'The card details are incomplete');
|
|
90
|
+
const m = await save(t, r.token);
|
|
91
|
+
onSaved(m);
|
|
92
|
+
onOpenChange(false);
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
setNote(e instanceof Error ? e.message : 'The card was not saved');
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
setWorking(false);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const why = cfg.error ? cfg.error.message : !cfg.loading && !ready(p) ? 'This deployment takes no cards yet.' : '';
|
|
102
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: 440, showCloseButton: false, children: [_jsxs(XStack, { items: "center", justify: "space-between", gap: "$2", children: [_jsx(DialogTitle, { children: "Add a card" }), _jsx(XStack, { render: "button", "aria-label": "Close", p: "$1", onPress: () => onOpenChange(false), children: _jsx(X, { size: 16 }) })] }), _jsxs(YStack, { gap: "$3", children: [why ? (_jsx(SizableText, { size: "$2", color: "$soft", children: why })) : (_jsxs(_Fragment, { children: [_jsxs(YStack, { minH: 96, "aria-label": "Card details", children: [_jsx("div", { ref: setBox, style: { minHeight: 96 } }), !mounted && !note ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "Loading the card form\u2026" })) : null] }), _jsx(SizableText, { size: "$1", color: "$soft", children: p && p.environment !== 'production'
|
|
103
|
+
? 'Sandbox: use a test card. Nothing is charged.'
|
|
104
|
+
: 'The card number goes to the payment processor, never to Hanzo.' })] })), note ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note })) : null, _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => onOpenChange(false), children: "Cancel" }), _jsx(Button, { size: "sm", disabled: !mounted || working, onPress: () => void add(), children: "Save card" })] })] })] }) }));
|
|
105
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function Code(): import("react").JSX.Element;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Code: where and how a new run starts — the model, the effort, the mode and
|
|
4
|
+
* the place — for anyone whose New has no choice of its own kept yet. Saved to
|
|
5
|
+
* the person's own settings. Beside them, the codebases' environments and the
|
|
6
|
+
* organization's machines, each on its own page.
|
|
7
|
+
*/
|
|
8
|
+
import { YStack } from '@hanzo/gui';
|
|
9
|
+
import { ChevronRight } from '@hanzogui/lucide-icons-2';
|
|
10
|
+
import { ChipSelect } from '@hanzo/ui/product';
|
|
11
|
+
import { useState } from 'react';
|
|
12
|
+
import { unhonoured } from '../api/coding.js';
|
|
13
|
+
import { ENSO, models } from '../api/models.js';
|
|
14
|
+
import { SANDBOX } from '../api/places.js';
|
|
15
|
+
import { usePlaces, useRead } from '../data.js';
|
|
16
|
+
import { useHost, useTarget } from '../host.js';
|
|
17
|
+
import { EFFORTS, MODES } from '../landing.js';
|
|
18
|
+
import { usePrefs } from '../prefs.js';
|
|
19
|
+
import { path } from '../route.js';
|
|
20
|
+
import { Card, Group, Heading, Note, Row, Soft } from './ui.js';
|
|
21
|
+
export function Code() {
|
|
22
|
+
const host = useHost();
|
|
23
|
+
const t = useTarget();
|
|
24
|
+
const signed = Boolean(host.person);
|
|
25
|
+
const { prefs, save } = usePrefs();
|
|
26
|
+
const catalog = useRead(signed ? () => models(t) : null, [], [t, signed]);
|
|
27
|
+
const places = usePlaces(t, signed);
|
|
28
|
+
const [note, setNote] = useState('');
|
|
29
|
+
if (!signed) {
|
|
30
|
+
return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Code" }), _jsx(Soft, { children: "Sign in to set how your runs start." })] }));
|
|
31
|
+
}
|
|
32
|
+
const now = { model: ENSO, effort: 'medium', mode: 'build', place: '', ...prefs.code };
|
|
33
|
+
const keep = (change) => {
|
|
34
|
+
setNote('');
|
|
35
|
+
save({ code: { ...prefs.code, ...change } }).catch((e) => setNote(e instanceof Error ? e.message : 'That was not saved'));
|
|
36
|
+
};
|
|
37
|
+
const where = (places.value.length ? places.value : [SANDBOX]).map((p) => ({
|
|
38
|
+
id: p.id || 'sandbox',
|
|
39
|
+
label: p.id ? p.label : 'Default',
|
|
40
|
+
// A machine offline now can still be the default; its status shows beside it.
|
|
41
|
+
hint: p.id ? [p.status, p.capacity].filter(Boolean).join(' · ') : 'The platform’s own sandbox',
|
|
42
|
+
place: p.id,
|
|
43
|
+
}));
|
|
44
|
+
const place = where.find((w) => w.place === now.place) ?? { id: now.place, label: now.place || 'Default', place: now.place };
|
|
45
|
+
const model = catalog.value.find((m) => m.id === now.model) ?? { id: now.model, label: now.model === ENSO ? 'Enso' : now.model };
|
|
46
|
+
const go = (section) => host.go(path({ kind: 'settings', section }));
|
|
47
|
+
return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Code", detail: "How a new run starts. New begins from these until you choose otherwise there." }), _jsxs(Group, { title: "Defaults", children: [_jsxs(Card, { children: [_jsx(Row, { first: true, title: "Model", detail: "Enso picks one for each step; any other model runs them all.", trailing: _jsx(ChipSelect, { name: "Default model", label: model.label, chosen: model, items: catalog.value, onChange: (m) => keep({ model: m.id }), placeholder: "Search models\u2026", placement: "bottom-end", loading: catalog.loading, error: catalog.error?.message ?? null }) }), _jsx(Row, { title: "Effort", detail: "How long the agent thinks before it acts.", trailing: _jsx(ChipSelect, { name: "Default effort", label: EFFORTS.find((e) => e.id === now.effort).label, chosen: EFFORTS.find((e) => e.id === now.effort) ?? null, items: EFFORTS, onChange: (e) => keep({ effort: e.id }), placement: "bottom-end", width: 180 }) }), _jsx(Row, { title: "Mode", detail: MODES.find((m) => m.id === now.mode).hint, trailing: _jsx(ChipSelect, { name: "Default mode", label: MODES.find((m) => m.id === now.mode).label, chosen: MODES.find((m) => m.id === now.mode) ?? null, items: MODES, onChange: (m) => keep({ mode: m.id }), placement: "bottom-end", width: 260 }) }), _jsx(Row, { title: "Where it runs", detail: "The Hanzo sandbox, or one of your organization\u2019s machines.", trailing: _jsx(ChipSelect, { name: "Default place", label: place.label, chosen: place, items: where, onChange: (w) => keep({ place: w.place }), placeholder: "Search machines\u2026", placement: "bottom-end", loading: places.loading, error: places.error?.message ?? null }) })] }), _jsx(Note, { children: unhonoured(now.mode, now.place) })] }), _jsx(Group, { title: "Workspace", children: _jsxs(Card, { children: [_jsx(Row, { first: true, title: "Environments", detail: "What a run installs and starts in each codebase, and the secrets it exports.", trailing: _jsx(Link, { label: "Environments", onPress: () => go('environments') }) }), _jsx(Row, { title: "Machines", detail: "Your organization\u2019s own computers that take runs.", trailing: _jsx(Link, { label: "Machines", onPress: () => go('machines') }) })] }) }), _jsx(Note, { children: note })] }));
|
|
48
|
+
}
|
|
49
|
+
function Link({ label, onPress }) {
|
|
50
|
+
return (_jsx(YStack, { render: "button", "aria-label": `Open ${label}`, p: "$1", rounded: "$2", hoverStyle: { bg: '$hover' }, onPress: onPress, children: _jsx(ChevronRight, { size: 16 }) }));
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function Environments(): import("react").JSX.Element;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Environments: every codebase in the organization that has one — what a sandbox
|
|
4
|
+
* run does to its checkout before the agent starts, and the secrets it exports.
|
|
5
|
+
* One opens in the same editor a run shows beside it. A member reads; an org
|
|
6
|
+
* admin saves, sets secrets and forgets an environment, which is the platform's
|
|
7
|
+
* rule and not a choice made here.
|
|
8
|
+
*/
|
|
9
|
+
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
10
|
+
import { ChevronLeft, ChevronRight } from '@hanzogui/lucide-icons-2';
|
|
11
|
+
import { Button } from '@hanzo/ui';
|
|
12
|
+
import { useState } from 'react';
|
|
13
|
+
import { environments, forget } from '../api/environment.js';
|
|
14
|
+
import { Confirm } from '../ask.js';
|
|
15
|
+
import { useRead } from '../data.js';
|
|
16
|
+
import { Environment } from '../environment.js';
|
|
17
|
+
import { useHost, useTarget } from '../host.js';
|
|
18
|
+
import { Card, Heading, Note, Row, Soft } from './ui.js';
|
|
19
|
+
const STATE = { none: 'Not set up', proposed: 'Proposed, waiting for review', ready: 'Ready' };
|
|
20
|
+
/** The line under a codebase: where it stands, its secrets, when it was saved. */
|
|
21
|
+
function about(e) {
|
|
22
|
+
const parts = [STATE[e.state]];
|
|
23
|
+
if (e.secrets.length)
|
|
24
|
+
parts.push(e.secrets.length === 1 ? '1 secret' : `${e.secrets.length} secrets`);
|
|
25
|
+
const when = e.updated ? new Date(e.updated) : null;
|
|
26
|
+
if (when && !Number.isNaN(when.getTime()))
|
|
27
|
+
parts.push(`Updated ${when.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}`);
|
|
28
|
+
return parts.join(' · ');
|
|
29
|
+
}
|
|
30
|
+
export function Environments() {
|
|
31
|
+
const host = useHost();
|
|
32
|
+
const t = useTarget();
|
|
33
|
+
const signed = Boolean(host.person);
|
|
34
|
+
const list = useRead(signed ? () => environments(t) : null, [], [t, signed]);
|
|
35
|
+
const [open, setOpen] = useState('');
|
|
36
|
+
const [forgetting, setForgetting] = useState(false);
|
|
37
|
+
const [note, setNote] = useState('');
|
|
38
|
+
if (!signed) {
|
|
39
|
+
return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Environments" }), _jsx(Soft, { children: "Sign in to see your organization\u2019s environments." })] }));
|
|
40
|
+
}
|
|
41
|
+
if (open) {
|
|
42
|
+
return (_jsxs(YStack, { gap: "$5", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsxs(Button, { size: "sm", variant: "ghost", onPress: () => setOpen(''), children: [_jsx(ChevronLeft, { size: 14 }), " All environments"] }), _jsx(XStack, { flex: 1 }), host.admin ? (_jsx(Button, { size: "sm", variant: "outline", onPress: () => setForgetting(true), children: "Forget" })) : null] }), _jsx(Environment, { repo: open, busy: false }), _jsx(Confirm, { open: forgetting, onOpenChange: setForgetting, title: `Forget ${open}’s environment?`, says: "Later runs on this codebase start bare: its scripts are dropped and every secret it holds is removed from KMS. This cannot be undone.", act: "Forget", run: async () => {
|
|
43
|
+
await forget(t, open);
|
|
44
|
+
setNote(`${open}’s environment is forgotten`);
|
|
45
|
+
setOpen('');
|
|
46
|
+
list.reload();
|
|
47
|
+
} })] }));
|
|
48
|
+
}
|
|
49
|
+
return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Environments", detail: "What a sandbox run does to a codebase before its agent starts: the install script, the start command, and the secrets it exports." }), list.error ? (_jsx(Soft, { children: list.error.message })) : list.loading && list.value.length === 0 ? (_jsx(Soft, { children: "Reading environments\u2026" })) : list.value.length === 0 ? (_jsx(Soft, { children: "No codebase has an environment yet. Set one up from New, or from a run\u2019s Environment tab." })) : (_jsx(Card, { children: list.value.map((e, i) => (_jsx(YStack, { render: "button", "aria-label": `Open ${e.repo}`, onPress: () => {
|
|
50
|
+
setNote('');
|
|
51
|
+
setOpen(e.repo);
|
|
52
|
+
}, cursor: "pointer", hoverStyle: { bg: '$hover' }, style: { textAlign: 'left' }, children: _jsx(Row, { first: i === 0, title: e.repo, detail: about(e), trailing: _jsx(ChevronRight, { size: 16, opacity: 0.6 }) }) }, e.repo))) })), !host.admin ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "An org admin saves an environment and sets its secrets." })) : null, _jsx(Note, { children: note })] }));
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function General(): import("react").JSX.Element;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* General: how the builder looks and listens — the theme, the text size, the
|
|
4
|
+
* motion, and the language dictation hears. Each choice is saved to the
|
|
5
|
+
* person's own settings as it is made, so it follows them to every device.
|
|
6
|
+
*/
|
|
7
|
+
import { YStack } from '@hanzo/gui';
|
|
8
|
+
import { ChipSelect } from '@hanzo/ui/product';
|
|
9
|
+
import { useState } from 'react';
|
|
10
|
+
import { useHost } from '../host.js';
|
|
11
|
+
import { usePrefs } from '../prefs.js';
|
|
12
|
+
import { LANGUAGES, spoken } from '../voice.js';
|
|
13
|
+
import { Card, Group, Heading, Note, Row, Soft } from './ui.js';
|
|
14
|
+
const THEMES = [
|
|
15
|
+
{ id: 'system', label: 'System' },
|
|
16
|
+
{ id: 'light', label: 'Light' },
|
|
17
|
+
{ id: 'dark', label: 'Dark' },
|
|
18
|
+
];
|
|
19
|
+
const TEXTS = [
|
|
20
|
+
{ id: 'small', label: 'Small' },
|
|
21
|
+
{ id: 'medium', label: 'Medium' },
|
|
22
|
+
{ id: 'large', label: 'Large' },
|
|
23
|
+
];
|
|
24
|
+
const MOTIONS = [
|
|
25
|
+
{ id: 'system', label: 'System', hint: 'As this device is set' },
|
|
26
|
+
{ id: 'reduced', label: 'Reduced', hint: 'No animation anywhere' },
|
|
27
|
+
];
|
|
28
|
+
export function General() {
|
|
29
|
+
const host = useHost();
|
|
30
|
+
const { prefs, save } = usePrefs();
|
|
31
|
+
const [note, setNote] = useState('');
|
|
32
|
+
if (!host.person) {
|
|
33
|
+
return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "General" }), _jsx(Soft, { children: "Sign in to see your settings." })] }));
|
|
34
|
+
}
|
|
35
|
+
const keep = (patch) => {
|
|
36
|
+
setNote('');
|
|
37
|
+
save(patch).catch((e) => setNote(e instanceof Error ? e.message : 'That was not saved'));
|
|
38
|
+
};
|
|
39
|
+
const theme = prefs.theme ?? host.theme ?? 'dark';
|
|
40
|
+
const text = prefs.text ?? 'medium';
|
|
41
|
+
const motion = prefs.motion ?? 'system';
|
|
42
|
+
const language = LANGUAGES.find((l) => l.id === prefs.language) ?? LANGUAGES.find((l) => l.id === spoken());
|
|
43
|
+
return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "General", detail: "How Hanzo Build looks and listens. Saved to your account, so it follows you." }), _jsx(Group, { title: "Appearance", children: _jsxs(Card, { children: [host.chooseTheme ? (_jsx(Row, { first: true, title: "Theme", detail: "System follows this device\u2019s light or dark setting.", trailing: _jsx(ChipSelect, { name: "Theme", label: THEMES.find((x) => x.id === theme).label, chosen: THEMES.find((x) => x.id === theme) ?? null, items: THEMES, onChange: (x) => keep({ theme: x.id }), placement: "bottom-end", width: 200 }) })) : null, _jsx(Row, { first: !host.chooseTheme, title: "Text size", detail: "The transcript, the composer and every screen.", trailing: _jsx(ChipSelect, { name: "Text size", label: TEXTS.find((x) => x.id === text).label, chosen: TEXTS.find((x) => x.id === text) ?? null, items: TEXTS, onChange: (x) => keep({ text: x.id === 'medium' ? null : x.id }), placement: "bottom-end", width: 200 }) }), _jsx(Row, { title: "Motion", detail: "Reduced stops animations and transitions on this page.", trailing: _jsx(ChipSelect, { name: "Motion", label: MOTIONS.find((x) => x.id === motion).label, chosen: MOTIONS.find((x) => x.id === motion) ?? null, items: MOTIONS, onChange: (x) => keep({ motion: x.id === 'system' ? null : x.id }), placement: "bottom-end", width: 220 }) })] }) }), _jsx(Group, { title: "Voice", children: _jsx(Card, { children: _jsx(Row, { first: true, title: "Dictation language", detail: "What the microphone in the composer listens for.", trailing: _jsx(ChipSelect, { name: "Dictation language", label: language.label, chosen: language, items: LANGUAGES, onChange: (l) => keep({ language: l.id }), placeholder: "Search languages\u2026", placement: "bottom-end", width: 220 }) }) }) }), _jsx(Note, { children: note })] }));
|
|
44
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Settings: every setting the builder has, on this page — the account, the
|
|
4
|
+
* workspace's environments, machines and keys, what the agent brings to a run,
|
|
5
|
+
* and the organization's integrations, members, usage and notifications. A
|
|
6
|
+
* section is its own address, so it can be linked to and comes back on reload.
|
|
7
|
+
*/
|
|
8
|
+
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
9
|
+
import { useHost } from '../host.js';
|
|
10
|
+
import { path } from '../route.js';
|
|
11
|
+
import { ENTRIES, GROUPS } from './sections.js';
|
|
12
|
+
export function Settings({ section }) {
|
|
13
|
+
const host = useHost();
|
|
14
|
+
const entry = ENTRIES.find((e) => e.id === section) ?? ENTRIES[0];
|
|
15
|
+
const go = (id) => host.go(path({ kind: 'settings', section: id }));
|
|
16
|
+
return (_jsxs(XStack, { flex: 1, minH: 0, minW: 0, children: [_jsxs(YStack, { role: "navigation", "aria-label": "Settings", width: 220, shrink: 0, borderRightWidth: 1, borderColor: "$borderColor", px: "$2", py: "$4", gap: "$4", overflow: "scroll", "$max-md": { display: 'none' }, children: [_jsx(SizableText, { size: "$4", color: "$ink", px: "$2", children: "Settings" }), GROUPS.map((g) => {
|
|
17
|
+
const list = ENTRIES.filter((e) => e.group === g);
|
|
18
|
+
if (!list.length)
|
|
19
|
+
return null;
|
|
20
|
+
return (_jsxs(YStack, { gap: "$0.5", children: [_jsx(SizableText, { size: "$1", color: "$soft", px: "$2", pb: "$1", children: g }), list.map((e) => (_jsx(XStack, { render: "button", "aria-label": e.label, "aria-current": e.id === entry.id ? 'page' : undefined, onPress: () => go(e.id), px: "$2", py: "$1.5", rounded: "$2", bg: e.id === entry.id ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, children: _jsx(SizableText, { size: "$2", color: e.id === entry.id ? '$ink' : '$soft', children: e.label }) }, e.id)))] }, g));
|
|
21
|
+
})] }), _jsxs(YStack, { flex: 1, minW: 0, minH: 0, overflow: "scroll", children: [_jsx(XStack, { flexWrap: "wrap", gap: "$1.5", px: "$4", pt: "$3", $md: { display: 'none' }, children: ENTRIES.map((e) => (_jsx(XStack, { render: "button", "aria-label": e.label, onPress: () => go(e.id), px: "$2.5", py: "$1", rounded: "$10", borderWidth: 1, borderColor: e.id === entry.id ? '$ink' : '$borderColor', children: _jsx(SizableText, { size: "$1", color: e.id === entry.id ? '$ink' : '$soft', children: e.label }) }, e.id))) }), _jsx(YStack, { width: "100%", maxW: 760, self: "center", px: "$5", py: "$6", children: entry.body() })] })] }));
|
|
22
|
+
}
|