@hanzo/build 0.2.5 → 0.2.6
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 +3 -1
- package/lib/account.d.ts +3 -0
- package/lib/account.js +24 -1
- package/lib/builder.js +34 -4
- package/lib/forge.js +13 -1
- package/lib/host.d.ts +6 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,9 @@ the database, the sign-in and the storage already there.
|
|
|
5
5
|
|
|
6
6
|
The builder is one component, `<Builder host={…} />` from `@hanzo/build`, with
|
|
7
7
|
three hosts: this page (hanzo.build), and the Dev section of the Hanzo app at
|
|
8
|
-
hanzo.ai/dev and hanzo.app/dev. None of them forks it.
|
|
8
|
+
hanzo.ai/dev and hanzo.app/dev. None of them forks it. hanzo.build is this
|
|
9
|
+
frontend. A click stays on this origin — sign-in returns to `/auth/callback`
|
|
10
|
+
here — and github.com is opened only for the repository grant.
|
|
9
11
|
|
|
10
12
|
```
|
|
11
13
|
pnpm install
|
package/lib/account.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { RailSession } from '@hanzo/ui/chat';
|
|
2
|
+
/** The builder listens, so a settings control on this page opens the account dialog. */
|
|
3
|
+
export declare function onAccount(fn: () => void): () => void;
|
|
4
|
+
export declare function revealAccount(): void;
|
|
2
5
|
export declare function Account({ open, onOpenChange }: {
|
|
3
6
|
open: boolean;
|
|
4
7
|
onOpenChange: (o: boolean) => void;
|
package/lib/account.js
CHANGED
|
@@ -8,10 +8,33 @@ import { LogOut, Settings } from '@hanzogui/lucide-icons-2';
|
|
|
8
8
|
import { Button, Dialog, DialogContent, DialogTitle, Input } from '@hanzo/ui';
|
|
9
9
|
import { useMemo, useState } from 'react';
|
|
10
10
|
import { useHost } from './host.js';
|
|
11
|
+
let reveal = null;
|
|
12
|
+
/** The builder listens, so a settings control on this page opens the account dialog. */
|
|
13
|
+
export function onAccount(fn) {
|
|
14
|
+
reveal = fn;
|
|
15
|
+
return () => {
|
|
16
|
+
if (reveal === fn)
|
|
17
|
+
reveal = null;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function revealAccount() {
|
|
21
|
+
reveal?.();
|
|
22
|
+
}
|
|
23
|
+
/** A settings address on another host. This page's own account is the dialog itself. */
|
|
24
|
+
function accountSettings(href) {
|
|
25
|
+
if (typeof window === 'undefined')
|
|
26
|
+
return false;
|
|
27
|
+
try {
|
|
28
|
+
return new URL(href, window.location.origin).origin !== window.location.origin;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
11
34
|
export function Account({ open, onOpenChange }) {
|
|
12
35
|
const host = useHost();
|
|
13
36
|
const who = host.person;
|
|
14
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: 360, children: [_jsx(DialogTitle, { children: "Account" }), _jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$3", color: "$ink", numberOfLines: 1, children: who?.name || who?.email || 'Signed out' }), who?.email && who.name ? (_jsx(SizableText, { size: "$2", color: "$soft", numberOfLines: 1, children: who.email })) : null, host.org ? (_jsxs(SizableText, { size: "$1", color: "$soft", children: ["Organization: ", host.org] })) : null] }), _jsxs(XStack, { gap: "$2", justify: "flex-end", flexWrap: "wrap", children: [_jsxs(Button, { variant: "outline", size: "sm", onPress: () => host.open(host.links.settings), children: [_jsx(Settings, { size: 14 }), " Settings"] }), host.signOut ? (_jsxs(Button, { variant: "outline", size: "sm", onPress: () => host.signOut?.(), children: [_jsx(LogOut, { size: 14 }), " Sign out"] })) : null] })] }) }));
|
|
37
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { maxW: 360, children: [_jsx(DialogTitle, { children: "Account" }), _jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$3", color: "$ink", numberOfLines: 1, children: who?.name || who?.email || 'Signed out' }), who?.email && who.name ? (_jsx(SizableText, { size: "$2", color: "$soft", numberOfLines: 1, children: who.email })) : null, host.org ? (_jsxs(SizableText, { size: "$1", color: "$soft", children: ["Organization: ", host.org] })) : null] }), _jsxs(XStack, { gap: "$2", justify: "flex-end", flexWrap: "wrap", children: [accountSettings(host.links.settings) ? (_jsxs(Button, { variant: "outline", size: "sm", onPress: () => host.open(host.links.settings), children: [_jsx(Settings, { size: 14 }), " Settings"] })) : null, host.signOut ? (_jsxs(Button, { variant: "outline", size: "sm", onPress: () => host.signOut?.(), children: [_jsx(LogOut, { size: 14 }), " Sign out"] })) : null] })] }) }));
|
|
15
38
|
}
|
|
16
39
|
export function Find({ open, onOpenChange, recents, onOpen, }) {
|
|
17
40
|
const [q, setQ] = useState('');
|
package/lib/builder.js
CHANGED
|
@@ -29,7 +29,7 @@ import { Blocks, BookOpen, CircleDot, Cpu, FolderGit2, Kanban, LayoutTemplate, M
|
|
|
29
29
|
import { Button } from '@hanzo/ui';
|
|
30
30
|
import { SessionRail } from '@hanzo/ui/chat';
|
|
31
31
|
import { HanzoMark } from '@hanzo/ui/product';
|
|
32
|
-
import { useMemo, useState } from 'react';
|
|
32
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
33
33
|
import { pinBoard } from './choice.js';
|
|
34
34
|
import { useKept, useRecents } from './data.js';
|
|
35
35
|
import { Forge } from './forge.js';
|
|
@@ -40,7 +40,7 @@ import { path, route } from './route.js';
|
|
|
40
40
|
import { Run } from './run.js';
|
|
41
41
|
import { DOTS } from './section.js';
|
|
42
42
|
import { Artifacts, Templates } from './shelf.js';
|
|
43
|
-
import { Account, Find } from './account.js';
|
|
43
|
+
import { Account, Find, onAccount } from './account.js';
|
|
44
44
|
function Shell() {
|
|
45
45
|
const host = useHost();
|
|
46
46
|
const t = useTarget();
|
|
@@ -51,6 +51,7 @@ function Shell() {
|
|
|
51
51
|
const [order, setOrder] = useState('newest');
|
|
52
52
|
const [account, setAccount] = useState(false);
|
|
53
53
|
const [finding, setFinding] = useState(false);
|
|
54
|
+
useEffect(() => onAccount(() => setAccount(true)), []);
|
|
54
55
|
const go = (p) => {
|
|
55
56
|
setDrawer(false);
|
|
56
57
|
host.go(p);
|
|
@@ -81,12 +82,41 @@ function Shell() {
|
|
|
81
82
|
];
|
|
82
83
|
const more = [
|
|
83
84
|
{ id: 'templates', label: 'Templates', icon: _jsx(LayoutTemplate, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'templates' })), active: screen === 'templates' },
|
|
84
|
-
{
|
|
85
|
+
{
|
|
86
|
+
id: 'machines',
|
|
87
|
+
label: 'Machines',
|
|
88
|
+
icon: _jsx(Cpu, { size: 16 }),
|
|
89
|
+
onPress: () => {
|
|
90
|
+
const home = host.links.home.replace(/\/+$/, '');
|
|
91
|
+
const computers = `${home}/platform/computers`;
|
|
92
|
+
try {
|
|
93
|
+
if (new URL(computers, window.location.origin).origin === window.location.origin) {
|
|
94
|
+
host.go('');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
host.open(computers);
|
|
102
|
+
},
|
|
103
|
+
},
|
|
85
104
|
{ id: 'docs', label: 'Docs', icon: _jsx(BookOpen, { size: 16 }), onPress: () => window.open('https://docs.hanzo.ai/docs/dev', '_blank', 'noopener,noreferrer') },
|
|
86
105
|
];
|
|
87
106
|
return (_jsxs(XStack, { flex: 1, minH: 0, minW: 0, bg: "$background", children: [_jsxs(YStack, { position: "relative", shrink: 0, children: [_jsx(SessionRail, { onNew: () => go(''), fresh: r.kind === 'new', links: links, more: more, recents: rows, active: r.kind === 'run' ? r.id : null, onOpen: (id) => go(id), onSort: () => setOrder(order === 'newest' ? 'running' : 'newest'), sortLabel: order === 'newest' ? 'Show running first' : 'Show newest first', empty: _jsx(SizableText, { size: "$1", color: "$soft", px: "$2", children: !host.person ? 'Sign in to see your runs.' : recents.error ? recents.error.message : recents.loading ? 'Reading…' : 'No runs yet.' }), account: host.person
|
|
88
107
|
? { name: host.person.email || host.person.name, onPress: () => setAccount(true) }
|
|
89
|
-
: { name: 'Sign in', onPress: () => host.signIn?.() }, onSettings: () =>
|
|
108
|
+
: { name: 'Sign in', onPress: () => host.signIn?.() }, onSettings: () => {
|
|
109
|
+
try {
|
|
110
|
+
if (new URL(host.links.settings, window.location.origin).origin === window.location.origin) {
|
|
111
|
+
setAccount(true);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
host.open(host.links.settings);
|
|
119
|
+
}, onSearch: () => setFinding(true), collapsed: collapsed, onCollapse: setCollapsed, mark: _jsx(Brand, { org: host.org }), pt: collapsed ? undefined : 44, open: drawer, onOpenChange: setDrawer, label: "Runs" }), collapsed ? null : (_jsxs(XStack, { position: "absolute", t: 8, l: 8, r: 8, z: 2, items: "center", gap: "$2", render: "button", "aria-label": host.org ? `Organization ${host.org}` : 'Hanzo', onPress: () => go(''), children: [_jsx(Brand, { org: host.org }), _jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: host.org || 'Hanzo' })] }))] }), _jsxs(YStack, { flex: 1, minW: 0, minH: 0, position: "relative", children: [_jsx(XStack, { height: 44, px: "$2", items: "center", shrink: 0, $md: { display: 'none' }, children: _jsx(Button, { variant: "ghost", size: "icon-sm", onPress: () => setDrawer(true), "aria-label": "Open runs", children: _jsx(Menu, { size: 18 }) }) }), _jsx(Pane, { onStarted: () => recents.reload() })] }), _jsx(Account, { open: account, onOpenChange: setAccount }), _jsx(Find, { open: finding, onOpenChange: setFinding, recents: rows, onOpen: (id) => { setFinding(false); go(id); } })] }));
|
|
90
120
|
}
|
|
91
121
|
/** Whichever pane the address names, beside a rail — the builder's or the host's. */
|
|
92
122
|
function Pane({ onStarted }) {
|
package/lib/forge.js
CHANGED
|
@@ -10,6 +10,7 @@ import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
|
10
10
|
import { ChevronDown, GitBranch, Plus, Settings, Workflow } from '@hanzogui/lucide-icons-2';
|
|
11
11
|
import { Button, Dialog, DialogContent, DialogTitle, Input } from '@hanzo/ui';
|
|
12
12
|
import { useEffect, useState } from 'react';
|
|
13
|
+
import { revealAccount } from './account.js';
|
|
13
14
|
import { add, arm, flows } from './api/auto.js';
|
|
14
15
|
import { codebases, create } from './api/codebases.js';
|
|
15
16
|
import { boards, issues } from './api/work.js';
|
|
@@ -169,7 +170,18 @@ function Codebases() {
|
|
|
169
170
|
pinCodebase(host.org, c);
|
|
170
171
|
host.go('');
|
|
171
172
|
};
|
|
172
|
-
return (_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", children: [_jsxs(YStack, { width: "100%", maxW: 1040, mx: "auto", gap: "$4", children: [_jsxs(XStack, { justify: "space-between", items: "flex-start", gap: "$4", children: [_jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$6", fontWeight: "500", color: "$ink", children: host.org || 'Forge' }), _jsx(SizableText, { size: "$2", color: "$soft", children: "Create and browse this organization\u2019s repositories." })] }), _jsxs(XStack, { render: "button", "aria-label": "Settings", items: "center", gap: "$1.5", px: "$2", py: "$1", rounded: "$3", hoverStyle: { bg: '$hover' }, onPress: () =>
|
|
173
|
+
return (_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", children: [_jsxs(YStack, { width: "100%", maxW: 1040, mx: "auto", gap: "$4", children: [_jsxs(XStack, { justify: "space-between", items: "flex-start", gap: "$4", children: [_jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$6", fontWeight: "500", color: "$ink", children: host.org || 'Forge' }), _jsx(SizableText, { size: "$2", color: "$soft", children: "Create and browse this organization\u2019s repositories." })] }), _jsxs(XStack, { render: "button", "aria-label": "Settings", items: "center", gap: "$1.5", px: "$2", py: "$1", rounded: "$3", hoverStyle: { bg: '$hover' }, onPress: () => {
|
|
174
|
+
try {
|
|
175
|
+
if (new URL(host.links.settings, window.location.origin).origin === window.location.origin) {
|
|
176
|
+
revealAccount();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
host.open(host.links.settings);
|
|
184
|
+
}, children: [_jsx(Settings, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$soft", children: "Settings" })] })] }), _jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(XStack, { px: "$2.5", py: "$1.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", children: _jsx(SizableText, { size: "$2", color: "$ink", children: "All repos" }) }), _jsx(YStack, { flex: 1 }), _jsx(YStack, { width: 240, minW: 0, children: _jsx(Input, { value: q, onChangeText: setQ, placeholder: "Find repo\u2026", "aria-label": "Find a repository", disabled: !signed }) }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => host.go(path({ kind: 'screen', screen: 'sync' })), children: "Sync" }), _jsxs(Button, { size: "sm", disabled: !signed, onPress: () => setMaking(true), children: [_jsx(Plus, { size: 14 }), "New"] })] }), list.error ? (_jsx(Note, { children: list.error.message })) : !signed ? (_jsx(Note, { children: "Sign in to see this organization's repositories." })) : list.loading && list.value.length === 0 ? (_jsx(Note, { children: "Reading the forge\u2026" })) : shown.length === 0 ? (_jsx(Note, { children: list.value.length === 0 ? 'No repositories yet.' : 'Nothing matches.' })) : (_jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: [_jsxs(XStack, { px: "$3", py: "$2", gap: "$3", items: "center", children: [_jsxs(XStack, { flex: 1, render: "button", "aria-label": "Sort by name", items: "center", gap: "$1", onPress: () => setSort('name'), children: [_jsx(SizableText, { size: "$1", color: "$soft", children: "Name" }), sort === 'name' ? _jsx(ChevronDown, { size: 12 }) : null] }), _jsxs(XStack, { render: "button", "aria-label": "Sort by last updated", items: "center", justify: "flex-end", gap: "$1", width: 140, onPress: () => setSort('updated'), children: [_jsx(SizableText, { size: "$1", color: "$soft", children: "Last updated" }), sort === 'updated' ? _jsx(ChevronDown, { size: 12 }) : null] })] }), slice.map((c) => (_jsxs(XStack, { render: "button", "aria-label": `Work on ${c.name}`, onPress: () => open(c), items: "center", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, children: [_jsx(GitBranch, { size: 14 }), _jsx(SizableText, { flex: 1, size: "$2", color: "$ink", numberOfLines: 1, children: c.name }), _jsx(SizableText, { size: "$1", color: "$soft", width: 140, style: { textAlign: 'right' }, children: syncing.has(c.name) ? 'Syncing…' : when(c.updated) })] }, `${c.org}/${c.name}`))), _jsxs(XStack, { px: "$3", py: "$2", justify: "space-between", items: "center", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$1", color: "$soft", children: `Showing ${from}–${to} of ${shown.length}` }), _jsxs(XStack, { gap: "$3", items: "center", children: [_jsx(XStack, { render: "button", "aria-label": "Previous page", disabled: safePage === 0, onPress: () => setPage(safePage - 1), children: _jsx(SizableText, { size: "$1", color: safePage === 0 ? '$soft' : '$ink', children: "Prev" }) }), _jsx(SizableText, { size: "$1", color: "$soft", children: `${safePage + 1} / ${pages}` }), _jsx(XStack, { render: "button", "aria-label": "Next page", disabled: safePage >= pages - 1, onPress: () => setPage(safePage + 1), children: _jsx(SizableText, { size: "$1", color: safePage >= pages - 1 ? '$soft' : '$ink', children: "Next" }) })] })] })] }))] }), _jsx(NewRepo, { open: making, onOpenChange: setMaking, onCreate: async (name, description) => {
|
|
173
185
|
const row = await create(t, name, description);
|
|
174
186
|
pinCodebase(host.org, row);
|
|
175
187
|
setMaking(false);
|
package/lib/host.d.ts
CHANGED
|
@@ -32,18 +32,18 @@ export interface Host {
|
|
|
32
32
|
go: (path: string, how?: {
|
|
33
33
|
replace?: boolean;
|
|
34
34
|
}) => void;
|
|
35
|
-
/** Where the builder links out
|
|
35
|
+
/** Where the builder links out. On hanzo.build these are addresses of this page. */
|
|
36
36
|
links: {
|
|
37
|
-
/**
|
|
37
|
+
/** GitHub for this page: bringing repositories onto the forge. */
|
|
38
38
|
github: string;
|
|
39
|
-
/** Customize
|
|
39
|
+
/** Customize, an address of this host. */
|
|
40
40
|
customize: string;
|
|
41
|
-
/** Account settings. */
|
|
41
|
+
/** Account settings, an address of this host. */
|
|
42
42
|
settings: string;
|
|
43
|
-
/**
|
|
43
|
+
/** This host's home. On hanzo.build it is the page itself. */
|
|
44
44
|
home: string;
|
|
45
45
|
};
|
|
46
|
-
/**
|
|
46
|
+
/** Open a host address in this window. hanzo.build keeps the window on its own origin. */
|
|
47
47
|
open: (href: string) => void;
|
|
48
48
|
signIn?: () => void;
|
|
49
49
|
signOut?: () => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/build",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
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 page (hanzo.build) and as a component (hanzo.ai/dev).",
|