@hanzo/build 0.2.72 → 0.2.74
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/lib/api/agents.d.ts +40 -0
- package/lib/api/agents.js +31 -0
- package/lib/builder.js +4 -1
- package/lib/carry.d.ts +1 -1
- package/lib/carry.js +2 -2
- package/lib/customize/agents.d.ts +4 -0
- package/lib/customize/agents.js +44 -18
- package/lib/customize/index.js +6 -5
- package/lib/host.d.ts +5 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/nav.d.ts +12 -2
- package/lib/nav.js +48 -4
- package/lib/plugins/index.d.ts +1 -1
- package/lib/plugins/index.js +5 -1
- package/lib/route.d.ts +7 -3
- package/lib/route.js +10 -4
- package/lib/settings/index.js +4 -3
- package/lib/settings/list.d.ts +15 -0
- package/lib/settings/list.js +19 -0
- package/lib/settings/sections.d.ts +2 -2
- package/lib/settings/sections.js +19 -17
- package/package.json +1 -1
package/lib/api/agents.d.ts
CHANGED
|
@@ -69,6 +69,46 @@ export declare const EMPTY: Draft;
|
|
|
69
69
|
export declare function refuse(d: Draft): string;
|
|
70
70
|
export declare function agents(t: Target): Promise<Agent[]>;
|
|
71
71
|
export declare function one(t: Target, ref: string): Promise<Agent>;
|
|
72
|
+
/** One run of an agent, as its profile lists it: ok or error, what it was given and what it said. */
|
|
73
|
+
export interface Run {
|
|
74
|
+
id: string;
|
|
75
|
+
status: string;
|
|
76
|
+
model: string;
|
|
77
|
+
input: string;
|
|
78
|
+
output: string;
|
|
79
|
+
error: string;
|
|
80
|
+
ms: number;
|
|
81
|
+
at: string;
|
|
82
|
+
}
|
|
83
|
+
export declare function run(raw: unknown): Run;
|
|
84
|
+
/** An agent with its instructions and its 20 latest runs, newest first (`recentRuns`). */
|
|
85
|
+
export declare function profile(t: Target, ref: string): Promise<{
|
|
86
|
+
agent: Agent;
|
|
87
|
+
runs: Run[];
|
|
88
|
+
}>;
|
|
89
|
+
/** A default agent a host offers: a name, its line, its model and its instructions. */
|
|
90
|
+
export interface Cast {
|
|
91
|
+
name: string;
|
|
92
|
+
description: string;
|
|
93
|
+
model: string;
|
|
94
|
+
instructions: string;
|
|
95
|
+
/** Its face, an address of the host, when it has one. */
|
|
96
|
+
avatar?: string;
|
|
97
|
+
}
|
|
98
|
+
/** What a default agent may spend until its owner says otherwise, in dollars: each month, and one run. */
|
|
99
|
+
export declare const CAST_CAP = "10";
|
|
100
|
+
export declare const CAST_TASK = "1";
|
|
101
|
+
/** The cast members the org has no agent for, by name in any case. */
|
|
102
|
+
export declare const missing: (cast: readonly Cast[], have: readonly {
|
|
103
|
+
name: string;
|
|
104
|
+
}[]) => Cast[];
|
|
105
|
+
/** Create every cast member the org lacks, one at a time; the names made, and the first refusal, if any. */
|
|
106
|
+
export declare function hire(t: Target, cast: readonly Cast[], have: readonly {
|
|
107
|
+
name: string;
|
|
108
|
+
}[]): Promise<{
|
|
109
|
+
made: string[];
|
|
110
|
+
error: unknown;
|
|
111
|
+
}>;
|
|
72
112
|
export declare function create(t: Target, d: Draft): Promise<Agent>;
|
|
73
113
|
/** The fields that differ from `was`, and nothing else: an omitted field keeps its value. */
|
|
74
114
|
export declare function changes(was: Agent, d: Draft): Record<string, unknown>;
|
package/lib/api/agents.js
CHANGED
|
@@ -86,6 +86,37 @@ export async function agents(t) {
|
|
|
86
86
|
export async function one(t, ref) {
|
|
87
87
|
return agent(await call(t, 'GET', `/v1/agent/${seg(ref)}`));
|
|
88
88
|
}
|
|
89
|
+
export function run(raw) {
|
|
90
|
+
const o = obj(raw);
|
|
91
|
+
return { id: str(o.id), status: str(o.status), model: str(o.model), input: str(o.input), output: str(o.output), error: str(o.error), ms: num(o.durationMs), at: str(o.createdAt) };
|
|
92
|
+
}
|
|
93
|
+
/** An agent with its instructions and its 20 latest runs, newest first (`recentRuns`). */
|
|
94
|
+
export async function profile(t, ref) {
|
|
95
|
+
const raw = await call(t, 'GET', `/v1/agent/${seg(ref)}`);
|
|
96
|
+
return { agent: agent(raw), runs: rows(obj(raw).recentRuns).map(run).filter((r) => r.id) };
|
|
97
|
+
}
|
|
98
|
+
/** What a default agent may spend until its owner says otherwise, in dollars: each month, and one run. */
|
|
99
|
+
export const CAST_CAP = '10';
|
|
100
|
+
export const CAST_TASK = '1';
|
|
101
|
+
/** The cast members the org has no agent for, by name in any case. */
|
|
102
|
+
export const missing = (cast, have) => {
|
|
103
|
+
const names = new Set(have.map((a) => a.name.toLowerCase()));
|
|
104
|
+
return cast.filter((c) => !names.has(c.name.toLowerCase()));
|
|
105
|
+
};
|
|
106
|
+
/** Create every cast member the org lacks, one at a time; the names made, and the first refusal, if any. */
|
|
107
|
+
export async function hire(t, cast, have) {
|
|
108
|
+
const made = [];
|
|
109
|
+
for (const c of missing(cast, have)) {
|
|
110
|
+
try {
|
|
111
|
+
await create(t, { ...EMPTY, name: c.name, description: c.description, model: c.model, instructions: c.instructions, cap: CAST_CAP, task: CAST_TASK, period: 'month' });
|
|
112
|
+
made.push(c.name);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
return { made, error };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { made, error: null };
|
|
119
|
+
}
|
|
89
120
|
export async function create(t, d) {
|
|
90
121
|
const why = refuse(d);
|
|
91
122
|
if (why)
|
package/lib/builder.js
CHANGED
|
@@ -34,6 +34,7 @@ import { SessionRail } from '@hanzo/ui/chat';
|
|
|
34
34
|
import { HanzoMark } from '@hanzo/ui/product';
|
|
35
35
|
import { useMemo, useState } from 'react';
|
|
36
36
|
import { safe } from './api/projects.js';
|
|
37
|
+
import { Profile } from './customize/agents.js';
|
|
37
38
|
import { Customize } from './customize/index.js';
|
|
38
39
|
import { Editor } from './automations.js';
|
|
39
40
|
import { useKept, useRecents } from './data.js';
|
|
@@ -107,7 +108,9 @@ function Pane({ onStarted }) {
|
|
|
107
108
|
if (r.kind === 'settings')
|
|
108
109
|
return _jsx(Settings, { section: r.section });
|
|
109
110
|
if (r.kind === 'customize')
|
|
110
|
-
return _jsx(Customize, { tab: r.tab });
|
|
111
|
+
return _jsx(Customize, { tab: r.tab }, r.tab);
|
|
112
|
+
if (r.kind === 'agent')
|
|
113
|
+
return _jsx(Profile, { name: r.name }, r.name);
|
|
111
114
|
if (r.kind === 'automation')
|
|
112
115
|
return _jsx(Editor, { id: r.id }, r.id);
|
|
113
116
|
if (r.kind === 'screen') {
|
package/lib/carry.d.ts
CHANGED
|
@@ -39,4 +39,4 @@ export interface Asked {
|
|
|
39
39
|
* `customize(tab)` opens Dev's Customize on that tab; `modes` are the ones this
|
|
40
40
|
* surface offers (a choice it does not offer reads as Auto).
|
|
41
41
|
*/
|
|
42
|
-
export declare function useCarry(t: Target, key: string, customize: (tab: 'skills') => void, lent: Lent, modes
|
|
42
|
+
export declare function useCarry(t: Target, key: string, customize: (tab: 'skills') => void, lent: Lent, modes?: readonly Mode[]): Carry;
|
package/lib/carry.js
CHANGED
|
@@ -25,7 +25,7 @@ import { active, authored, document, nameOf as skillOf } from './api/skills.js';
|
|
|
25
25
|
import { brand, brands } from './api/studio.js';
|
|
26
26
|
import { useRead } from './data.js';
|
|
27
27
|
import { said as designSaid } from './design.js';
|
|
28
|
-
import { useGear } from './gear.js';
|
|
28
|
+
import { DEV_MODES, useGear } from './gear.js';
|
|
29
29
|
import { active as lighted, prep, shelf as pluginShelf, usePlugins } from './plugins/index.js';
|
|
30
30
|
/** The most of each skill a message carries. */
|
|
31
31
|
const SKILL_MAX = 16_000;
|
|
@@ -72,7 +72,7 @@ function Allow({ what, onAnswer }) {
|
|
|
72
72
|
* `customize(tab)` opens Dev's Customize on that tab; `modes` are the ones this
|
|
73
73
|
* surface offers (a choice it does not offer reads as Auto).
|
|
74
74
|
*/
|
|
75
|
-
export function useCarry(t, key, customize, lent, modes) {
|
|
75
|
+
export function useCarry(t, key, customize, lent, modes = DEV_MODES) {
|
|
76
76
|
const gear = useGear(t, key);
|
|
77
77
|
const [dialog, show] = useState(null);
|
|
78
78
|
// The composer a plugin acts on, read when it acts: the draft moves under it.
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
import { type Pane } from './ui.tsx';
|
|
2
2
|
export declare function Agents({ view, q, adding, onAdding, onView, onCount }: Pane): import("react").JSX.Element;
|
|
3
|
+
/** One agent's page: who it is, what it runs on and reads, what it may call and spend, and what it has done. */
|
|
4
|
+
export declare function Profile({ name }: {
|
|
5
|
+
name: string;
|
|
6
|
+
}): import("react").JSX.Element;
|
package/lib/customize/agents.js
CHANGED
|
@@ -5,21 +5,25 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
5
5
|
*
|
|
6
6
|
* Browse is the platform's presets whose tool calls run on the platform;
|
|
7
7
|
* starting from one opens a new agent written from it. Yours lists the org's
|
|
8
|
-
* agents
|
|
8
|
+
* agents, and Add default agents makes every member of the host's cast the org
|
|
9
|
+
* has none for (an org starts with none). Opening one is its profile page
|
|
10
|
+
* (`-/customize/agents/<name>`): its face, part, model, instructions, tools,
|
|
11
|
+
* budget and runs, edited in place, and only what changed is sent. An
|
|
9
12
|
* agent calls only the tools it names, and only those that are on for the org,
|
|
10
13
|
* so the choices are the tools that are on; "every tool" is whatever the
|
|
11
14
|
* fleet's MCP server serves when it runs. A budget is required: what it may
|
|
12
15
|
* spend each period, and in one run.
|
|
13
16
|
*/
|
|
14
17
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
15
|
-
import { Bot } from '@hanzogui/lucide-icons-2';
|
|
18
|
+
import { ArrowLeft, Bot, Users } from '@hanzogui/lucide-icons-2';
|
|
16
19
|
import { Button, Input, Switch, Textarea } from '@hanzo/ui';
|
|
17
20
|
import { ModelPicker } from '@hanzo/ui/models';
|
|
18
21
|
import { useEffect, useMemo, useState } from 'react';
|
|
19
|
-
import { agents, ALL, create, draft, EMPTY, fromPreset, one, PERIODS, presets, remove, update } from '../api/agents.js';
|
|
22
|
+
import { agents, ALL, create, draft, EMPTY, fromPreset, hire, missing, one, PERIODS, presets, profile, remove, update } from '../api/agents.js';
|
|
20
23
|
import { models } from '../api/models.js';
|
|
21
24
|
import { tools } from '../api/tools.js';
|
|
22
25
|
import { useHost, useTarget } from '../host.js';
|
|
26
|
+
import { path } from '../route.js';
|
|
23
27
|
import { useLoad } from './load.js';
|
|
24
28
|
import { say } from './say.js';
|
|
25
29
|
import { Add, Choice, Confirm, Empty, Failed, Field, Grid, Line, Mark, matches, mono, Part, Sheet, Soft, Tile, useCount, Visitor } from './ui.js';
|
|
@@ -31,9 +35,21 @@ export function Agents({ view, q, adding, onAdding, onView, onCount }) {
|
|
|
31
35
|
const mine = useLoad(signed ? () => agents(t) : null, [], [t, signed]);
|
|
32
36
|
const offered = useLoad(view === 'discover' ? () => presets(t) : null, [], [t, view]);
|
|
33
37
|
useCount(mine.value.length, onCount);
|
|
34
|
-
const [opened, setOpened] = useState(null);
|
|
35
38
|
const [start, setStart] = useState(null);
|
|
36
39
|
const [note, setNote] = useState('');
|
|
40
|
+
const [hiring, setHiring] = useState(false);
|
|
41
|
+
const cast = host.cast ?? [];
|
|
42
|
+
const lacking = missing(cast, mine.value);
|
|
43
|
+
// The host's cast, made in one press: each member the org has no agent for.
|
|
44
|
+
const addCast = async () => {
|
|
45
|
+
setHiring(true);
|
|
46
|
+
setNote('');
|
|
47
|
+
const { made, error } = await hire(t, cast, mine.value);
|
|
48
|
+
setHiring(false);
|
|
49
|
+
mine.reload();
|
|
50
|
+
setNote(error ? `${made.length ? `Added ${made.length}. ` : ''}${say(error)}` : `Added ${made.length} default ${made.length === 1 ? 'agent' : 'agents'}`);
|
|
51
|
+
};
|
|
52
|
+
const castButton = signed && lacking.length ? (_jsxs(Button, { size: "sm", variant: "outline", disabled: hiring, onPress: () => void addCast(), children: [_jsx(Users, { size: 14 }), " ", hiring ? 'Adding…' : lacking.length === cast.length ? 'Add default agents' : `Add ${lacking.length} default ${lacking.length === 1 ? 'agent' : 'agents'}`] })) : null;
|
|
37
53
|
const closeNew = () => {
|
|
38
54
|
setStart(null);
|
|
39
55
|
onAdding(false);
|
|
@@ -43,14 +59,6 @@ export function Agents({ view, q, adding, onAdding, onView, onCount }) {
|
|
|
43
59
|
onView('yours');
|
|
44
60
|
mine.reload();
|
|
45
61
|
setNote(`${a.name} is saved`);
|
|
46
|
-
} })) : opened ? (_jsx(Editor, { agent: opened, onClose: () => setOpened(null), onSaved: (a) => {
|
|
47
|
-
setOpened(null);
|
|
48
|
-
mine.reload();
|
|
49
|
-
setNote(`${a.name} is saved`);
|
|
50
|
-
}, onDeleted: () => {
|
|
51
|
-
setNote(`${opened.name} is deleted`);
|
|
52
|
-
setOpened(null);
|
|
53
|
-
mine.reload();
|
|
54
62
|
} })) : null;
|
|
55
63
|
if (view === 'discover') {
|
|
56
64
|
const names = new Set(mine.value.map((a) => a.name));
|
|
@@ -61,12 +69,30 @@ export function Agents({ view, q, adding, onAdding, onView, onCount }) {
|
|
|
61
69
|
return _jsx(Visitor, { children: "Sign in to see your agents." });
|
|
62
70
|
}
|
|
63
71
|
const shown = mine.value.filter((a) => matches(q, a.name, a.description, a.model));
|
|
64
|
-
return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), mine.error && !mine.value.length ? (_jsx(Failed, { error: mine.error, onRetry: mine.reload })) : mine.loading && !mine.value.length ? (_jsx(Soft, { children: "Loading your agents\u2026" })) : !mine.value.length ? (_jsxs(Empty, { title: "No agents yet", detail:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return
|
|
69
|
-
}
|
|
72
|
+
return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), mine.error && !mine.value.length ? (_jsx(Failed, { error: mine.error, onRetry: mine.reload })) : mine.loading && !mine.value.length ? (_jsx(Soft, { children: "Loading your agents\u2026" })) : !mine.value.length ? (_jsxs(Empty, { title: "No agents yet", detail: cast.length ? 'Add the default team, start from a preset, or give a new one a model, instructions and tools.' : 'Start from a preset, or give a new one a model, instructions and tools.', children: [castButton, _jsx(Button, { size: "sm", variant: "outline", onPress: () => onView('discover'), children: "Browse presets" }), _jsx(Button, { size: "sm", onPress: () => onAdding(true), children: "New agent" })] })) : !shown.length ? (_jsx(Soft, { children: `No agent of yours matches “${q.trim()}”.` })) : (_jsxs(_Fragment, { children: [castButton ? _jsx(XStack, { children: castButton }) : null, _jsx(Grid, { label: "Your agents", children: shown.map((a) => (_jsx(Tile, { title: a.name, detail: a.description || 'No description.', meta: [a.model, `${a.runs} ${a.runs === 1 ? 'run' : 'runs'}`, reach(a), a.cap ? `${money(a.spent)} of ${money(a.cap)} this ${a.period || 'period'}` : ''].filter(Boolean).join(' · '), mark: _jsx(Face, { agent: a, cast: cast }), onOpen: () => host.go(path({ kind: 'agent', name: a.name })) }, a.id || a.name))) })] })), editor] }));
|
|
73
|
+
}
|
|
74
|
+
function reach(a) {
|
|
75
|
+
if (a.tools.includes(ALL))
|
|
76
|
+
return 'every tool';
|
|
77
|
+
return a.tools.length === 1 ? '1 tool' : `${a.tools.length} tools`;
|
|
78
|
+
}
|
|
79
|
+
/** An agent's face: the cast member's picture, its emoji, or its initial. */
|
|
80
|
+
function Face({ agent: a, cast, size = 32 }) {
|
|
81
|
+
const avatar = cast.find((c) => c.name.toLowerCase() === a.name.toLowerCase())?.avatar;
|
|
82
|
+
return _jsx(Mark, { name: a.name, size: size, logo: avatar, icon: a.emoji ? _jsx(SizableText, { size: size > 40 ? '$8' : '$4', children: a.emoji }) : undefined });
|
|
83
|
+
}
|
|
84
|
+
/** One agent's page: who it is, what it runs on and reads, what it may call and spend, and what it has done. */
|
|
85
|
+
export function Profile({ name }) {
|
|
86
|
+
const host = useHost();
|
|
87
|
+
const t = useTarget();
|
|
88
|
+
const got = useLoad(host.person ? () => profile(t, name) : null, null, [t, name]);
|
|
89
|
+
const [editing, setEditing] = useState(false);
|
|
90
|
+
const back = () => host.go(path({ kind: 'customize', tab: 'agents' }));
|
|
91
|
+
const a = got.value?.agent;
|
|
92
|
+
return (_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", "$max-md": { px: '$4', py: '$4' }, children: [_jsxs(YStack, { width: "100%", maxW: 880, mx: "auto", gap: "$5", children: [_jsxs(XStack, { render: "button", onPress: back, items: "center", gap: "$1.5", self: "flex-start", cursor: "pointer", "aria-label": "Back to agents", children: [_jsx(ArrowLeft, { size: 14, color: "$soft" }), _jsx(SizableText, { size: "$2", color: "$soft", children: "Agents" })] }), !host.person ? (_jsx(Visitor, { children: "Sign in to see this agent." })) : got.error && !a ? (_jsx(Failed, { error: got.error, onRetry: got.reload })) : !a ? (_jsx(Soft, { children: "Loading the agent\u2026" })) : (_jsxs(_Fragment, { children: [_jsxs(XStack, { items: "center", gap: "$4", children: [_jsx(Face, { agent: a, cast: host.cast ?? [], size: 64 }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$1", children: [_jsx(SizableText, { render: "h1", size: "$7", color: "$ink", numberOfLines: 1, children: a.name }), _jsx(SizableText, { size: "$3", color: "$soft", children: a.description || 'No role written yet.' })] }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => setEditing(true), children: "Edit" })] }), _jsx(Part, { title: "Model", detail: a.model || 'The deployment’s default' }), _jsx(Part, { title: "Instructions", detail: a.instructions ? '' : 'None written: it runs on its model alone.', children: a.instructions ? (_jsx(YStack, { p: "$3", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", maxH: 320, overflow: "scroll", children: _jsx(SizableText, { size: "$2", color: "$ink", style: { whiteSpace: 'pre-wrap' }, children: a.instructions }) })) : null }), _jsx(Part, { title: "Tools", detail: a.tools.includes(ALL) ? 'Every tool the fleet’s MCP server serves.' : a.tools.length ? a.tools.join(', ') : 'None: it calls no tool.' }), _jsx(Part, { title: "Budget", detail: a.cap ? `${money(a.spent)} of ${money(a.cap)} spent this ${a.period || 'period'}; at most ${money(a.task)} a run.` : 'No budget set.' }), _jsx(Part, { title: "Runs", detail: a.runs ? `${a.runs} in all; the latest ${Math.min(a.runs, got.value.runs.length)} below.` : 'It has not run yet.', children: got.value.runs.map((r) => (_jsxs(XStack, { gap: "$3", py: "$2", borderTopWidth: 1, borderColor: "$borderColor", items: "flex-start", children: [_jsx(SizableText, { size: "$1", color: r.status === 'ok' ? '$soft' : '$bad', width: 44, shrink: 0, children: r.status === 'ok' ? 'ok' : 'failed' }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: r.input || '(no input)' }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 2, children: r.status === 'ok' ? r.output : r.error })] }), _jsx(SizableText, { size: "$1", color: "$soft", shrink: 0, children: [r.model, r.at ? new Date(r.at).toLocaleString() : ''].filter(Boolean).join(' · ') })] }, r.id))) })] }))] }), editing && a ? (_jsx(Editor, { agent: a, onClose: () => setEditing(false), onSaved: () => {
|
|
93
|
+
setEditing(false);
|
|
94
|
+
got.reload();
|
|
95
|
+
}, onDeleted: back })) : null] }));
|
|
70
96
|
}
|
|
71
97
|
/** Write a new agent, or change one; an existing agent is read whole first, for its instructions. */
|
|
72
98
|
function Editor({ agent, start, onClose, onSaved, onDeleted, }) {
|
package/lib/customize/index.js
CHANGED
|
@@ -29,10 +29,11 @@ const LABEL = {
|
|
|
29
29
|
plugins: { title: 'Plugins', says: 'Connectors you build in TypeScript.', add: 'Build plugin', find: 'Search plugins', icon: _jsx(Puzzle, { size: 15 }) },
|
|
30
30
|
agents: { title: 'Agents', says: 'Agents with their own model, instructions and budget.', add: 'New agent', find: 'Search agents', icon: _jsx(Bot, { size: 15 }) },
|
|
31
31
|
};
|
|
32
|
+
/** Where a tab opens: an org's own agents (and the cast to add) for Agents; Browse for the rest, what there is to add being the page. */
|
|
33
|
+
const opens = (tab) => (tab === 'agents' ? 'yours' : 'discover');
|
|
32
34
|
export function Customize({ tab, view: first }) {
|
|
33
35
|
const host = useHost();
|
|
34
|
-
|
|
35
|
-
const [view, setView] = useState(first ?? 'discover');
|
|
36
|
+
const [view, setView] = useState(first ?? opens(tab));
|
|
36
37
|
const [q, setQ] = useState('');
|
|
37
38
|
const [adding, setAdding] = useState(false);
|
|
38
39
|
const [count, setCount] = useState(0);
|
|
@@ -40,17 +41,17 @@ export function Customize({ tab, view: first }) {
|
|
|
40
41
|
const pick = (next) => {
|
|
41
42
|
setQ('');
|
|
42
43
|
setAdding(false);
|
|
43
|
-
setView(
|
|
44
|
+
setView(opens(next));
|
|
44
45
|
setCount(0);
|
|
45
46
|
host.go(path({ kind: 'customize', tab: next }));
|
|
46
47
|
};
|
|
47
48
|
const pane = { view, q, adding, onAdding: setAdding, onView: setView, onCount };
|
|
48
49
|
const kit = ORG_KIT.includes(tab);
|
|
49
50
|
const may = Boolean(host.person) && (host.admin || !kit);
|
|
50
|
-
return (_jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", "$max-md": { px: '$4', py: '$4' }, children: _jsxs(YStack, { width: "100%", maxW: 1080, mx: "auto", gap: "$4", children: [_jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { render: "h1", size: "$6", color: "$ink", children:
|
|
51
|
+
return (_jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", "$max-md": { px: '$4', py: '$4' }, children: _jsxs(YStack, { width: "100%", maxW: 1080, mx: "auto", gap: "$4", children: [_jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { render: "h1", size: "$6", color: "$ink", children: host.sections ? LABEL[tab].title : 'Customize' }), _jsx(SizableText, { size: "$2", color: "$soft", children: host.sections ? LABEL[tab].says : 'What your agent can use in every run.' })] }), host.sections ? null : (_jsx(XStack, { role: "tablist", "aria-label": "Customize", gap: "$4", borderBottomWidth: 1, borderColor: "$borderColor", "$max-md": { gap: '$3' }, children: TABS.map((id) => {
|
|
51
52
|
const on = id === tab;
|
|
52
53
|
return (_jsxs(XStack, { role: "tab", render: "button", "aria-selected": on, onPress: () => pick(id), items: "center", gap: "$1.5", pb: "$2", mb: -1, borderBottomWidth: 2, borderColor: on ? '$ink' : 'transparent', hoverStyle: { borderColor: on ? '$ink' : '$edge' }, children: [_jsx(XStack, { opacity: on ? 1 : 0.6, "$max-md": { display: 'none' }, children: LABEL[id].icon }), _jsx(SizableText, { size: "$3", color: on ? '$ink' : '$soft', children: LABEL[id].title })] }, id));
|
|
53
|
-
}) }), _jsx(SizableText, { size: "$2", color: "$soft", children: LABEL[tab].says }), _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Choice, { label: "Show", value: view, options: [
|
|
54
|
+
}) })), host.sections ? null : (_jsx(SizableText, { size: "$2", color: "$soft", children: LABEL[tab].says })), _jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(Choice, { label: "Show", value: view, options: [
|
|
54
55
|
{ id: 'discover', label: 'Browse' },
|
|
55
56
|
{ id: 'yours', label: 'Yours', count },
|
|
56
57
|
], onChange: setView }), _jsx(XStack, { flex: 1 }), may ? (_jsxs(Button, { size: "sm", onPress: () => setAdding(true), children: [_jsx(Plus, { size: 14 }), " ", LABEL[tab].add] })) : null] }), _jsx(Input, { value: q, onChangeText: setQ, placeholder: `${LABEL[tab].find}…`, "aria-label": LABEL[tab].find }), host.person && kit && !host.admin ? (_jsxs(SizableText, { size: "$1", color: "$soft", children: ["Only an org admin can add or change ", LABEL[tab].title.toLowerCase(), ". You can see what is on."] })) : null, tab === 'skills' ? (_jsx(Skills, { ...pane })) : tab === 'connectors' ? (_jsx(Connectors, { ...pane })) : tab === 'plugins' ? (_jsx(Plugins, { ...pane })) : (_jsx(Agents, { ...pane }))] }) }));
|
package/lib/host.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* as values, so no host forks the builder and the builder imports no host.
|
|
9
9
|
*/
|
|
10
10
|
import { type ComponentType, type ReactNode } from 'react';
|
|
11
|
+
import type { Cast } from './api/agents.ts';
|
|
11
12
|
import type { Target } from './api/call.ts';
|
|
12
13
|
import type { Theme } from './api/pref.ts';
|
|
13
14
|
export interface Person {
|
|
@@ -104,6 +105,10 @@ export interface Host {
|
|
|
104
105
|
browse?: Browse;
|
|
105
106
|
/** What the host's browser does to a page, row by row in the tab's menu. */
|
|
106
107
|
browser?: Browser;
|
|
108
|
+
/** The host's sidebar lists Settings' sections and Customize's tabs, so the pages draw neither. */
|
|
109
|
+
sections?: boolean;
|
|
110
|
+
/** The default agents an org may add in one press (Customize › Agents). Absent, none are offered. */
|
|
111
|
+
cast?: readonly Cast[];
|
|
107
112
|
}
|
|
108
113
|
export declare function HostProvider({ host, children }: {
|
|
109
114
|
host: Host;
|
package/lib/index.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export { Library, Outputs, Preview, Thumb, want } from './library.tsx';
|
|
|
25
25
|
export { SOURCE, type Item, type Kind as ItemKind, type Source } from './api/library.ts';
|
|
26
26
|
export { Scheduled, schedule } from './scheduled.tsx';
|
|
27
27
|
export { Editor } from './automations.tsx';
|
|
28
|
-
export { nav, NEW, type Group } from './nav.tsx';
|
|
28
|
+
export { nav, NEW, own, sections, tabs, type Group } from './nav.tsx';
|
|
29
29
|
export { DevSection, DOTS, useSessions } from './section.tsx';
|
|
30
30
|
export { HostProvider } from './host.tsx';
|
|
31
31
|
export { Slack, useWho, Who } from './foot.tsx';
|
package/lib/index.js
CHANGED
|
@@ -25,7 +25,7 @@ export { Library, Outputs, Preview, Thumb, want } from './library.js';
|
|
|
25
25
|
export { SOURCE } from './api/library.js';
|
|
26
26
|
export { Scheduled, schedule } from './scheduled.js';
|
|
27
27
|
export { Editor } from './automations.js';
|
|
28
|
-
export { nav, NEW } from './nav.js';
|
|
28
|
+
export { nav, NEW, own, sections, tabs } from './nav.js';
|
|
29
29
|
export { DevSection, DOTS, useSessions } from './section.js';
|
|
30
30
|
export { HostProvider } from './host.js';
|
|
31
31
|
export { Slack, useWho, Who } from './foot.js';
|
package/lib/nav.d.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import type { RailLink } from '@hanzo/ui/chat';
|
|
2
2
|
import type { Host } from './host.tsx';
|
|
3
|
-
/** A run of places under one quiet label. */
|
|
3
|
+
/** A run of places under one quiet label; '' draws none. */
|
|
4
4
|
export interface Group {
|
|
5
|
-
id:
|
|
5
|
+
id: string;
|
|
6
6
|
label: string;
|
|
7
7
|
links: RailLink[];
|
|
8
8
|
}
|
|
9
9
|
/** The id of the row that starts a new run: a rail with a New control of its own draws that instead. */
|
|
10
10
|
export declare const NEW = "new";
|
|
11
11
|
export declare function nav(host: Host, go: (path: string) => void): Group[];
|
|
12
|
+
/** Dev's own places: what a host whose rail holds the rest (Library, Scheduled tasks, Customize) lists in Dev's sidebar. */
|
|
13
|
+
export declare const own: (host: Host, go: (path: string) => void) => Group[];
|
|
14
|
+
/**
|
|
15
|
+
* Settings' sections as a host's sidebar lists them: one group per part, a
|
|
16
|
+
* part's label drawn only where it is not its first row's name (Code leads
|
|
17
|
+
* Code's part, so the part is not labelled twice).
|
|
18
|
+
*/
|
|
19
|
+
export declare function sections(host: Host, go: (path: string) => void): Group[];
|
|
20
|
+
/** Customize's tabs as a host's sidebar lists them, Agents first; an agent's profile lights Agents. */
|
|
21
|
+
export declare function tabs(host: Host, go: (path: string) => void): Group[];
|
package/lib/nav.js
CHANGED
|
@@ -9,10 +9,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
9
9
|
* Every row moves the builder through `go` with the address `path` writes, so
|
|
10
10
|
* where a place lives is said here and in route.ts and nowhere else.
|
|
11
11
|
*/
|
|
12
|
-
import { BookOpen, CircleDot, Clock, Container, Cpu, FolderGit2, LayoutTemplate, Library, SlidersHorizontal, SquarePen } from '@hanzogui/lucide-icons-2';
|
|
12
|
+
import { Bell, BookOpen, Bot, Brain, CircleDot, Clock, Code, Container, Cpu, CreditCard, FolderGit2, Gauge, Key, LayoutTemplate, Library, Plug, Puzzle, Settings, Shield, SlidersHorizontal, Sparkles, SquarePen, User, Users } from '@hanzogui/lucide-icons-2';
|
|
13
13
|
import { pinBoard } from './choice.js';
|
|
14
14
|
import { DOCS } from './foot.js';
|
|
15
|
-
import { path, route } from './route.js';
|
|
15
|
+
import { path, route, TABS } from './route.js';
|
|
16
|
+
import { GROUPS, heading, LIST } from './settings/list.js';
|
|
16
17
|
/** The id of the row that starts a new run: a rail with a New control of its own draws that instead. */
|
|
17
18
|
export const NEW = 'new';
|
|
18
19
|
export function nav(host, go) {
|
|
@@ -44,6 +45,7 @@ export function nav(host, go) {
|
|
|
44
45
|
},
|
|
45
46
|
active: screen === 'issues',
|
|
46
47
|
},
|
|
48
|
+
{ id: 'templates', label: 'Templates', icon: _jsx(LayoutTemplate, { size: 16 }), onPress: to(path({ kind: 'screen', screen: 'templates' })), active: screen === 'templates' },
|
|
47
49
|
],
|
|
48
50
|
},
|
|
49
51
|
{
|
|
@@ -51,7 +53,6 @@ export function nav(host, go) {
|
|
|
51
53
|
label: 'Make',
|
|
52
54
|
links: [
|
|
53
55
|
{ id: 'artifacts', label: 'Library', icon: _jsx(Library, { size: 16 }), onPress: to(path({ kind: 'screen', screen: 'artifacts' })), active: screen === 'artifacts' },
|
|
54
|
-
{ id: 'templates', label: 'Templates', icon: _jsx(LayoutTemplate, { size: 16 }), onPress: to(path({ kind: 'screen', screen: 'templates' })), active: screen === 'templates' },
|
|
55
56
|
{ id: 'automations', label: 'Scheduled tasks', icon: _jsx(Clock, { size: 16 }), onPress: to(path({ kind: 'screen', screen: 'automations' })), active: screen === 'automations' || r.kind === 'automation' },
|
|
56
57
|
],
|
|
57
58
|
},
|
|
@@ -67,9 +68,52 @@ export function nav(host, go) {
|
|
|
67
68
|
id: 'setup',
|
|
68
69
|
label: 'Setup',
|
|
69
70
|
links: [
|
|
70
|
-
{ id: 'customize', label: 'Customize', icon: _jsx(SlidersHorizontal, { size: 16 }), onPress: to(path({ kind: 'customize', tab: '
|
|
71
|
+
{ id: 'customize', label: 'Customize', icon: _jsx(SlidersHorizontal, { size: 16 }), onPress: to(path({ kind: 'customize', tab: 'agents' })), active: r.kind === 'customize' || r.kind === 'agent' || screen === 'mcp' },
|
|
71
72
|
{ id: 'docs', label: 'Docs', icon: _jsx(BookOpen, { size: 16 }), onPress: () => window.open(DOCS, '_blank', 'noopener,noreferrer') },
|
|
72
73
|
],
|
|
73
74
|
},
|
|
74
75
|
];
|
|
75
76
|
}
|
|
77
|
+
/** Dev's own places: what a host whose rail holds the rest (Library, Scheduled tasks, Customize) lists in Dev's sidebar. */
|
|
78
|
+
export const own = (host, go) => nav(host, go).filter((g) => g.id === 'work');
|
|
79
|
+
/**
|
|
80
|
+
* Settings' sections as a host's sidebar lists them: one group per part, a
|
|
81
|
+
* part's label drawn only where it is not its first row's name (Code leads
|
|
82
|
+
* Code's part, so the part is not labelled twice).
|
|
83
|
+
*/
|
|
84
|
+
export function sections(host, go) {
|
|
85
|
+
const r = route(host.path);
|
|
86
|
+
const at = r.kind === 'settings' ? r.section : '';
|
|
87
|
+
return GROUPS.map((g) => {
|
|
88
|
+
const rows = LIST.filter((e) => e.group === g);
|
|
89
|
+
return {
|
|
90
|
+
id: g,
|
|
91
|
+
label: heading(g),
|
|
92
|
+
links: rows.map((e) => ({ id: e.id, label: e.label, icon: SECTION_ICON[e.id], onPress: () => go(path({ kind: 'settings', section: e.id })), active: e.id === at })),
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** Customize's tabs as a host's sidebar lists them, Agents first; an agent's profile lights Agents. */
|
|
97
|
+
export function tabs(host, go) {
|
|
98
|
+
const r = route(host.path);
|
|
99
|
+
const at = r.kind === 'customize' ? r.tab : r.kind === 'agent' ? 'agents' : '';
|
|
100
|
+
return [{ id: 'customize', label: '', links: TABS.map((t) => ({ id: t, label: TAB_LABEL[t], icon: TAB_ICON[t], onPress: () => go(path({ kind: 'customize', tab: t })), active: t === at })) }];
|
|
101
|
+
}
|
|
102
|
+
const TAB_LABEL = { agents: 'Agents', skills: 'Skills', connectors: 'Connectors', plugins: 'Plugins' };
|
|
103
|
+
const TAB_ICON = { agents: _jsx(Bot, { size: 16 }), skills: _jsx(BookOpen, { size: 16 }), connectors: _jsx(Plug, { size: 16 }), plugins: _jsx(Puzzle, { size: 16 }) };
|
|
104
|
+
const SECTION_ICON = {
|
|
105
|
+
general: _jsx(Settings, { size: 16 }),
|
|
106
|
+
account: _jsx(User, { size: 16 }),
|
|
107
|
+
privacy: _jsx(Shield, { size: 16 }),
|
|
108
|
+
billing: _jsx(CreditCard, { size: 16 }),
|
|
109
|
+
usage: _jsx(Gauge, { size: 16 }),
|
|
110
|
+
capabilities: _jsx(Sparkles, { size: 16 }),
|
|
111
|
+
memory: _jsx(Brain, { size: 16 }),
|
|
112
|
+
code: _jsx(Code, { size: 16 }),
|
|
113
|
+
environments: _jsx(Container, { size: 16 }),
|
|
114
|
+
machines: _jsx(Cpu, { size: 16 }),
|
|
115
|
+
keys: _jsx(Key, { size: 16 }),
|
|
116
|
+
members: _jsx(Users, { size: 16 }),
|
|
117
|
+
integrations: _jsx(Plug, { size: 16 }),
|
|
118
|
+
notifications: _jsx(Bell, { size: 16 }),
|
|
119
|
+
};
|
package/lib/plugins/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export declare function usePlugins(t: Target): Plugins;
|
|
|
17
17
|
export declare function shelf(p: Plugins, g: GearState, c: Composer): Shelf;
|
|
18
18
|
/** The plugins this message asks, in the menu's order. */
|
|
19
19
|
export declare const active: (plugins: readonly Plugin[], g: Gear) => Plugin[];
|
|
20
|
-
/** Several plugins' preparations as one: system parts and replies in order, files together. */
|
|
20
|
+
/** Several plugins' preparations as one: system parts and replies in order, files together, the design system one made. */
|
|
21
21
|
export declare function merge(preps: readonly Prep[]): Prep;
|
|
22
22
|
/**
|
|
23
23
|
* Ask every plugin that is on what the message carries. A plugin that fails
|
package/lib/plugins/index.js
CHANGED
|
@@ -96,19 +96,23 @@ export function shelf(p, g, c) {
|
|
|
96
96
|
}
|
|
97
97
|
/** The plugins this message asks, in the menu's order. */
|
|
98
98
|
export const active = (plugins, g) => plugins.filter((p) => p.prepare && on(p, g));
|
|
99
|
-
/** Several plugins' preparations as one: system parts and replies in order, files together. */
|
|
99
|
+
/** Several plugins' preparations as one: system parts and replies in order, files together, the design system one made. */
|
|
100
100
|
export function merge(preps) {
|
|
101
101
|
const join = (k) => preps.map((p) => p[k]?.trim()).filter(Boolean).join('\n\n') || undefined;
|
|
102
102
|
const files = preps.flatMap((p) => p.files ?? []);
|
|
103
103
|
const out = {};
|
|
104
104
|
const system = join('system');
|
|
105
105
|
const reply = join('reply');
|
|
106
|
+
// A design system a plugin made is attached by the host (carry.tsx); dropped here, it never was.
|
|
107
|
+
const design = preps.map((p) => p.design).filter(Boolean).at(-1);
|
|
106
108
|
if (system)
|
|
107
109
|
out.system = system;
|
|
108
110
|
if (reply)
|
|
109
111
|
out.reply = reply;
|
|
110
112
|
if (files.length)
|
|
111
113
|
out.files = files;
|
|
114
|
+
if (design)
|
|
115
|
+
out.design = design;
|
|
112
116
|
return out;
|
|
113
117
|
}
|
|
114
118
|
/**
|
package/lib/route.d.ts
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* -/<screen> a builder screen
|
|
7
7
|
* -/automations/<id> one automation's editor and runs; `new` writes one
|
|
8
8
|
* -/settings[/<section>] a section of Settings
|
|
9
|
-
* -/customize[/<tab>] skills, connectors and plugins: what the agent brings to a run
|
|
9
|
+
* -/customize[/<tab>] agents, skills, connectors and plugins: what the agent brings to a run
|
|
10
|
+
* -/customize/agents/<name> one agent's profile
|
|
10
11
|
* -/plans the plans an organization can be on
|
|
11
12
|
* <org>/<repo> a repository's workspace: a project is a repository on the forge
|
|
12
13
|
* <slug> a deployed site's workspace (Artifacts)
|
|
@@ -30,8 +31,8 @@ export type Screen = 'artifacts' | 'templates' | 'projects' | 'issues' | 'automa
|
|
|
30
31
|
/** Settings, one section each. Every setting the builder has lives on this page, none elsewhere. */
|
|
31
32
|
export declare const SECTIONS: readonly ['general', 'account', 'privacy', 'billing', 'usage', 'capabilities', 'memory', 'code', 'environments', 'machines', 'keys', 'members', 'integrations', 'notifications'];
|
|
32
33
|
export type Section = (typeof SECTIONS)[number];
|
|
33
|
-
/** Customize, one tab each: what the agent brings to a run, yours and to discover. */
|
|
34
|
-
export declare const TABS: readonly ['
|
|
34
|
+
/** Customize, one tab each: what the agent brings to a run, yours and to discover. Agents first. */
|
|
35
|
+
export declare const TABS: readonly ['agents', 'skills', 'connectors', 'plugins'];
|
|
35
36
|
export type Tab = (typeof TABS)[number];
|
|
36
37
|
export type Route = {
|
|
37
38
|
kind: 'new';
|
|
@@ -47,6 +48,9 @@ export type Route = {
|
|
|
47
48
|
} | {
|
|
48
49
|
kind: 'customize';
|
|
49
50
|
tab: Tab;
|
|
51
|
+
} | {
|
|
52
|
+
kind: 'agent';
|
|
53
|
+
name: string;
|
|
50
54
|
} | {
|
|
51
55
|
kind: 'project';
|
|
52
56
|
slug: string;
|
package/lib/route.js
CHANGED
|
@@ -15,8 +15,8 @@ export const SECTIONS = [
|
|
|
15
15
|
'integrations',
|
|
16
16
|
'notifications',
|
|
17
17
|
];
|
|
18
|
-
/** Customize, one tab each: what the agent brings to a run, yours and to discover. */
|
|
19
|
-
export const TABS = ['
|
|
18
|
+
/** Customize, one tab each: what the agent brings to a run, yours and to discover. Agents first. */
|
|
19
|
+
export const TABS = ['agents', 'skills', 'connectors', 'plugins'];
|
|
20
20
|
export const SESSION = /^sess_[0-9a-f]{32}$/;
|
|
21
21
|
export const SLUG = /^[a-z0-9]([a-z0-9-]{0,38}[a-z0-9])?$/;
|
|
22
22
|
/** An organization or a repository name on the forge (api/codebases.ts `NAME`). */
|
|
@@ -32,8 +32,12 @@ export function route(path) {
|
|
|
32
32
|
const section = (p.slice('-/settings/'.length) || 'general');
|
|
33
33
|
return SECTIONS.includes(section) ? { kind: 'settings', section } : { kind: 'new' };
|
|
34
34
|
}
|
|
35
|
+
if (p.startsWith('-/customize/agents/')) {
|
|
36
|
+
const name = p.slice('-/customize/agents/'.length);
|
|
37
|
+
return NAME.test(name) ? { kind: 'agent', name } : { kind: 'new' };
|
|
38
|
+
}
|
|
35
39
|
if (p === '-/customize' || p.startsWith('-/customize/')) {
|
|
36
|
-
const tab = (p.slice('-/customize/'.length) || '
|
|
40
|
+
const tab = (p.slice('-/customize/'.length) || 'agents');
|
|
37
41
|
return TABS.includes(tab) ? { kind: 'customize', tab } : { kind: 'new' };
|
|
38
42
|
}
|
|
39
43
|
if (p.startsWith('-/automations/')) {
|
|
@@ -103,7 +107,9 @@ export function path(r) {
|
|
|
103
107
|
case 'settings':
|
|
104
108
|
return r.section === 'general' ? '-/settings' : `-/settings/${r.section}`;
|
|
105
109
|
case 'customize':
|
|
106
|
-
return r.tab === '
|
|
110
|
+
return r.tab === 'agents' ? '-/customize' : `-/customize/${r.tab}`;
|
|
111
|
+
case 'agent':
|
|
112
|
+
return `-/customize/agents/${r.name}`;
|
|
107
113
|
case 'project':
|
|
108
114
|
return r.slug;
|
|
109
115
|
case 'repo':
|
package/lib/settings/index.js
CHANGED
|
@@ -8,14 +8,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
9
9
|
import { useHost } from '../host.js';
|
|
10
10
|
import { path } from '../route.js';
|
|
11
|
+
import { heading } from './list.js';
|
|
11
12
|
import { ENTRIES, GROUPS } from './sections.js';
|
|
12
13
|
export function Settings({ section }) {
|
|
13
14
|
const host = useHost();
|
|
14
15
|
// Every section the route grammar names has an entry.
|
|
15
16
|
const entry = ENTRIES.find((e) => e.id === section);
|
|
16
17
|
const go = (id) => host.go(path({ kind: 'settings', section: id }));
|
|
17
|
-
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) => {
|
|
18
|
+
return (_jsxs(XStack, { flex: 1, minH: 0, minW: 0, children: [host.sections ? null : (_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) => {
|
|
18
19
|
const list = ENTRIES.filter((e) => e.group === g);
|
|
19
|
-
return (_jsxs(YStack, { gap: "$0.5", children: [g
|
|
20
|
-
})] }), _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() })] })] }));
|
|
20
|
+
return (_jsxs(YStack, { gap: "$0.5", children: [heading(g) ? (_jsx(SizableText, { size: "$1", color: "$soft", px: "$2", pb: "$1", children: g })) : null, 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() })] })] }));
|
|
21
22
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sections of Settings, in the order the page lists them, by name and part
|
|
3
|
+
* alone: what a sidebar lists without loading the sections themselves
|
|
4
|
+
* (sections.tsx pairs each with its body).
|
|
5
|
+
*/
|
|
6
|
+
import type { Section } from '../route.ts';
|
|
7
|
+
export type Group = 'Settings' | 'Code' | 'Organization';
|
|
8
|
+
export declare const GROUPS: readonly Group[];
|
|
9
|
+
export declare const LIST: readonly {
|
|
10
|
+
id: Section;
|
|
11
|
+
label: string;
|
|
12
|
+
group: Group;
|
|
13
|
+
}[];
|
|
14
|
+
/** A part's label as a list draws it: none for the first part, or where the part's first row already says it (Code). */
|
|
15
|
+
export declare const heading: (g: Group) => string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const GROUPS = ['Settings', 'Code', 'Organization'];
|
|
2
|
+
export const LIST = [
|
|
3
|
+
{ id: 'general', label: 'General', group: 'Settings' },
|
|
4
|
+
{ id: 'account', label: 'Account', group: 'Settings' },
|
|
5
|
+
{ id: 'privacy', label: 'Privacy', group: 'Settings' },
|
|
6
|
+
{ id: 'billing', label: 'Billing', group: 'Settings' },
|
|
7
|
+
{ id: 'usage', label: 'Usage', group: 'Settings' },
|
|
8
|
+
{ id: 'capabilities', label: 'Capabilities', group: 'Settings' },
|
|
9
|
+
{ id: 'memory', label: 'Memory', group: 'Settings' },
|
|
10
|
+
{ id: 'code', label: 'Code', group: 'Code' },
|
|
11
|
+
{ id: 'environments', label: 'Environments', group: 'Code' },
|
|
12
|
+
{ id: 'machines', label: 'Machines', group: 'Code' },
|
|
13
|
+
{ id: 'keys', label: 'API keys', group: 'Code' },
|
|
14
|
+
{ id: 'members', label: 'Members', group: 'Organization' },
|
|
15
|
+
{ id: 'integrations', label: 'Integrations', group: 'Organization' },
|
|
16
|
+
{ id: 'notifications', label: 'Notifications', group: 'Organization' },
|
|
17
|
+
];
|
|
18
|
+
/** A part's label as a list draws it: none for the first part, or where the part's first row already says it (Code). */
|
|
19
|
+
export const heading = (g) => (g === 'Settings' || LIST.find((e) => e.group === g)?.label === g ? '' : g);
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { ReactNode } from 'react';
|
|
7
7
|
import type { Section } from '../route.ts';
|
|
8
|
-
|
|
9
|
-
export
|
|
8
|
+
import { type Group } from './list.ts';
|
|
9
|
+
export { GROUPS, type Group } from './list.ts';
|
|
10
10
|
export interface Entry {
|
|
11
11
|
id: Section;
|
|
12
12
|
label: string;
|
package/lib/settings/sections.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { LIST } from './list.js';
|
|
2
3
|
import { Account } from './account.js';
|
|
3
4
|
import { Billing } from './billing.js';
|
|
4
5
|
import { Capabilities } from './capabilities.js';
|
|
@@ -13,20 +14,21 @@ import { Memory } from './memory.js';
|
|
|
13
14
|
import { Notifications } from './notifications.js';
|
|
14
15
|
import { Privacy } from './privacy.js';
|
|
15
16
|
import { Usage } from './usage.js';
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
export { GROUPS } from './list.js';
|
|
18
|
+
const BODY = {
|
|
19
|
+
general: () => _jsx(General, {}),
|
|
20
|
+
account: () => _jsx(Account, {}),
|
|
21
|
+
privacy: () => _jsx(Privacy, {}),
|
|
22
|
+
billing: () => _jsx(Billing, {}),
|
|
23
|
+
usage: () => _jsx(Usage, {}),
|
|
24
|
+
capabilities: () => _jsx(Capabilities, {}),
|
|
25
|
+
memory: () => _jsx(Memory, {}),
|
|
26
|
+
code: () => _jsx(Code, {}),
|
|
27
|
+
environments: () => _jsx(Environments, {}),
|
|
28
|
+
machines: () => _jsx(Machines, {}),
|
|
29
|
+
keys: () => _jsx(Keys, {}),
|
|
30
|
+
members: () => _jsx(Members, {}),
|
|
31
|
+
integrations: () => _jsx(Integrations, {}),
|
|
32
|
+
notifications: () => _jsx(Notifications, {}),
|
|
33
|
+
};
|
|
34
|
+
export const ENTRIES = LIST.map((e) => ({ ...e, body: BODY[e.id] }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/build",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.74",
|
|
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.",
|