@hanzo/build 0.2.23 → 0.2.25
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/members.d.ts +3 -0
- package/lib/api/members.js +6 -0
- package/lib/api/projects.d.ts +4 -0
- package/lib/api/projects.js +2 -0
- package/lib/builder.js +7 -3
- package/lib/host.d.ts +2 -0
- package/lib/project.js +15 -3
- package/package.json +1 -1
package/lib/api/members.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* GET /v1/iam/invitations?owner= {invitations, total} (org admin)
|
|
7
7
|
* POST /v1/iam/invitations issue one (org admin)
|
|
8
8
|
* DELETE /v1/iam/invitations/{owner}/{name} withdraw one (org admin)
|
|
9
|
+
* GET /v1/iam/organizations/admin/{org} the org's record, whose `logo` is its mark (member)
|
|
9
10
|
*
|
|
10
11
|
* IAM decides the scope from the credential: a member reads the roster of an org
|
|
11
12
|
* they belong to, and only an admin of the org reads or writes its invitations.
|
|
@@ -45,3 +46,5 @@ export declare const EMAIL: RegExp;
|
|
|
45
46
|
/** Invite one address into the org: one seat, redeemable now, pinned to that address. */
|
|
46
47
|
export declare function invite(t: Target, org: string, email: string): Promise<Invitation>;
|
|
47
48
|
export declare function revoke(t: Target, i: Pick<Invitation, 'owner' | 'name'>): Promise<void>;
|
|
49
|
+
/** The org's own logo address as IAM holds it, or '' when it has none. */
|
|
50
|
+
export declare function logo(t: Target, org: string): Promise<string>;
|
package/lib/api/members.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* GET /v1/iam/invitations?owner= {invitations, total} (org admin)
|
|
7
7
|
* POST /v1/iam/invitations issue one (org admin)
|
|
8
8
|
* DELETE /v1/iam/invitations/{owner}/{name} withdraw one (org admin)
|
|
9
|
+
* GET /v1/iam/organizations/admin/{org} the org's record, whose `logo` is its mark (member)
|
|
9
10
|
*
|
|
10
11
|
* IAM decides the scope from the credential: a member reads the roster of an org
|
|
11
12
|
* they belong to, and only an admin of the org reads or writes its invitations.
|
|
@@ -86,3 +87,8 @@ export async function invite(t, org, email) {
|
|
|
86
87
|
export async function revoke(t, i) {
|
|
87
88
|
await call(t, 'DELETE', `/v1/iam/invitations/${seg(i.owner)}/${seg(i.name)}`);
|
|
88
89
|
}
|
|
90
|
+
/** The org's own logo address as IAM holds it, or '' when it has none. */
|
|
91
|
+
export async function logo(t, org) {
|
|
92
|
+
const raw = obj(await call(t, 'GET', `/v1/iam/organizations/admin/${encodeURIComponent(org)}`));
|
|
93
|
+
return str(obj(raw.data ?? raw).logo);
|
|
94
|
+
}
|
package/lib/api/projects.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface Project {
|
|
|
24
24
|
live: string;
|
|
25
25
|
/** `public` or `private`, or '' when the platform did not say. */
|
|
26
26
|
visibility: '' | Visibility;
|
|
27
|
+
/** The catalog template's slug, or `<org>/<slug>` of the published project, this was taken from; '' for neither. */
|
|
28
|
+
forked: string;
|
|
27
29
|
/** Unix seconds; 0 when unknown. A deploy does not move `updated`. */
|
|
28
30
|
created: number;
|
|
29
31
|
updated: number;
|
|
@@ -51,6 +53,8 @@ export interface Template {
|
|
|
51
53
|
framework: string;
|
|
52
54
|
/** The repository the starter is cut from. */
|
|
53
55
|
source: string;
|
|
56
|
+
/** The starter's own live page, or '' when it has none the preview may frame. */
|
|
57
|
+
demo: string;
|
|
54
58
|
}
|
|
55
59
|
/** The public catalog. It is reference content: no bearer, no org. */
|
|
56
60
|
export declare function templates(t: Target): Promise<Template[]>;
|
package/lib/api/projects.js
CHANGED
|
@@ -44,6 +44,7 @@ export function project(raw) {
|
|
|
44
44
|
status: str(p.status),
|
|
45
45
|
live: safe(str(p.liveUrl)),
|
|
46
46
|
visibility: p.visibility === 'public' || p.visibility === 'private' ? p.visibility : '',
|
|
47
|
+
forked: str(p.forkedFrom),
|
|
47
48
|
created: num(p.createdAt),
|
|
48
49
|
updated: num(p.updatedAt),
|
|
49
50
|
};
|
|
@@ -92,6 +93,7 @@ export async function templates(t) {
|
|
|
92
93
|
description: str(o.description),
|
|
93
94
|
framework: str(o.framework),
|
|
94
95
|
source: str(o.source),
|
|
96
|
+
demo: safe(str(o.demo)),
|
|
95
97
|
};
|
|
96
98
|
})
|
|
97
99
|
.filter((x) => x.slug);
|
package/lib/builder.js
CHANGED
|
@@ -33,6 +33,7 @@ import { Button } from '@hanzo/ui';
|
|
|
33
33
|
import { SessionRail } from '@hanzo/ui/chat';
|
|
34
34
|
import { HanzoMark } from '@hanzo/ui/product';
|
|
35
35
|
import { useMemo, useState } from 'react';
|
|
36
|
+
import { safe } from './api/projects.js';
|
|
36
37
|
import { pinBoard } from './choice.js';
|
|
37
38
|
import { Customize } from './customize/index.js';
|
|
38
39
|
import { useKept, useRecents } from './data.js';
|
|
@@ -85,7 +86,7 @@ function Shell() {
|
|
|
85
86
|
who.toggle();
|
|
86
87
|
},
|
|
87
88
|
}
|
|
88
|
-
: { name: 'Sign in', onPress: () => host.signIn?.() }, collapsed: collapsed, onCollapse: setCollapsed, mark: _jsx(Brand, { org: host.org }), pt: collapsed ? undefined : 92, open: drawer, onOpenChange: setDrawer, label: "Runs" }), collapsed ? null : (_jsxs(YStack, { position: "absolute", t: 10, l: 8, r: 8, z: 2, gap: "$2.5", "$max-md": { display: 'none' }, children: [_jsx(XStack, { px: "$2.5", children: name }), _jsxs(XStack, { render: "button", "aria-label": "Search runs", onPress: () => setFinding(true), items: "center", gap: "$2", px: "$2.5", height: 34, rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", hoverStyle: { bg: '$hover' }, children: [_jsx(Search, { size: 15, color: "$soft" }), _jsx(SizableText, { size: "$2", color: "$soft", children: "Search" })] })] })), host.person ? _jsx(Who, { open: who.open, onOpenChange: who.onOpenChange, narrow: collapsed }) : null] }), _jsxs(YStack, { flex: 1, minW: 0, minH: 0, position: "relative", children: [_jsxs(XStack, { height: 44, px: "$2", gap: "$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 }) }), name, _jsx(XStack, { flex: 1 }), _jsx(Button, { variant: "ghost", size: "icon-sm", onPress: () => setFinding(true), "aria-label": "Search runs", children: _jsx(Search, { size: 18 }) })] }), _jsx(Pane, { onStarted: () => recents.reload() })] }), _jsx(Find, { open: finding, onOpenChange: setFinding, recents: rows, onOpen: (id) => { setFinding(false); go(id); } })] }));
|
|
89
|
+
: { name: 'Sign in', onPress: () => host.signIn?.() }, collapsed: collapsed, onCollapse: setCollapsed, mark: _jsx(Brand, { org: host.org, logo: host.logo }), pt: collapsed ? undefined : 92, open: drawer, onOpenChange: setDrawer, label: "Runs" }), collapsed ? null : (_jsxs(YStack, { position: "absolute", t: 10, l: 8, r: 8, z: 2, gap: "$2.5", "$max-md": { display: 'none' }, children: [_jsx(XStack, { px: "$2.5", children: name }), _jsxs(XStack, { render: "button", "aria-label": "Search runs", onPress: () => setFinding(true), items: "center", gap: "$2", px: "$2.5", height: 34, rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", hoverStyle: { bg: '$hover' }, children: [_jsx(Search, { size: 15, color: "$soft" }), _jsx(SizableText, { size: "$2", color: "$soft", children: "Search" })] })] })), host.person ? _jsx(Who, { open: who.open, onOpenChange: who.onOpenChange, narrow: collapsed }) : null] }), _jsxs(YStack, { flex: 1, minW: 0, minH: 0, position: "relative", children: [_jsxs(XStack, { height: 44, px: "$2", gap: "$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 }) }), name, _jsx(XStack, { flex: 1 }), _jsx(Button, { variant: "ghost", size: "icon-sm", onPress: () => setFinding(true), "aria-label": "Search runs", children: _jsx(Search, { size: 18 }) })] }), _jsx(Pane, { onStarted: () => recents.reload() })] }), _jsx(Find, { open: finding, onOpenChange: setFinding, recents: rows, onOpen: (id) => { setFinding(false); go(id); } })] }));
|
|
89
90
|
}
|
|
90
91
|
/**
|
|
91
92
|
* The builder's places, as a rail draws them: four in view and the rest under
|
|
@@ -154,8 +155,11 @@ function Pane({ onStarted }) {
|
|
|
154
155
|
host.go(id);
|
|
155
156
|
} }));
|
|
156
157
|
}
|
|
157
|
-
/** The Hanzo mark
|
|
158
|
-
function Brand({ org }) {
|
|
158
|
+
/** The org's mark: the logo it chose; else the Hanzo mark for Hanzo or a personal account, and an organization's initial. */
|
|
159
|
+
function Brand({ org, logo }) {
|
|
160
|
+
const chosen = logo ? safe(logo) : '';
|
|
161
|
+
if (chosen)
|
|
162
|
+
return _jsx("img", { src: chosen, alt: "", width: 18, height: 18, style: { borderRadius: 4, objectFit: 'cover' } });
|
|
159
163
|
if (!org || org === 'hanzo')
|
|
160
164
|
return _jsx(HanzoMark, { size: 18 });
|
|
161
165
|
const letter = org.charAt(0).toUpperCase();
|
package/lib/host.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface Host {
|
|
|
25
25
|
token: () => string | null;
|
|
26
26
|
/** The org every call is scoped to. */
|
|
27
27
|
org: string | null;
|
|
28
|
+
/** The org's own logo, an https address, when it has chosen one; its mark is drawn from it. */
|
|
29
|
+
logo?: string;
|
|
28
30
|
/** The organizations this person belongs to. The switcher lists these and no others. */
|
|
29
31
|
memberships?: readonly string[];
|
|
30
32
|
/** Stay on this page and scope every read to `org`. */
|
package/lib/project.js
CHANGED
|
@@ -28,7 +28,7 @@ import { HanzoMark } from '@hanzo/ui/product';
|
|
|
28
28
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
29
29
|
import { start } from './api/coding.js';
|
|
30
30
|
import { blob, tree } from './api/git.js';
|
|
31
|
-
import { name as repoName, ours } from './api/projects.js';
|
|
31
|
+
import { name as repoName, ours, templates } from './api/projects.js';
|
|
32
32
|
import { list, message, stop } from './api/sessions.js';
|
|
33
33
|
import { outcome, pull, said, who } from './api/turn.js';
|
|
34
34
|
import { verdict as record } from './api/verdict.js';
|
|
@@ -124,7 +124,13 @@ export function Project({ slug }) {
|
|
|
124
124
|
.filter((e) => !e.dir && /\.html?$/i.test(e.name) && !/^index\.html?$/i.test(e.name))
|
|
125
125
|
.map((e) => ({ id: `/${e.path}`, label: e.name.replace(/\.html?$/i, '') })),
|
|
126
126
|
], [root.value]);
|
|
127
|
-
|
|
127
|
+
// A project taken from a starter and not yet published shows the starter's own
|
|
128
|
+
// live page, which is what the copy is until its first publish.
|
|
129
|
+
const forked = project && !project.live ? project.forked : '';
|
|
130
|
+
const starters = useRead(forked ? () => templates(t) : null, [], [t.api, forked]);
|
|
131
|
+
const starter = forked ? starters.value.find((s) => s.slug === forked && s.demo) : undefined;
|
|
132
|
+
const shown = project?.live || starter?.demo || '';
|
|
133
|
+
const src = shown ? new URL(page, shown).toString() : null;
|
|
128
134
|
const lines = useMemo(() => [
|
|
129
135
|
...run.events
|
|
130
136
|
.filter((e) => e.kind === 'log' || e.kind === 'tool-call' || e.kind === 'status')
|
|
@@ -221,7 +227,13 @@ export function Project({ slug }) {
|
|
|
221
227
|
}
|
|
222
228
|
const chat = (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$3", py: "$4", gap: "$4", children: ordered.length === 0 ? (_jsx(SizableText, { size: "$2", color: "$ink", px: "$2", children: runs.loading
|
|
223
229
|
? 'Reading this project’s runs…'
|
|
224
|
-
:
|
|
230
|
+
: project?.live
|
|
231
|
+
? `${project.name} is loaded — it is in the preview, and every run on it is under the clock above. Say what to change and it gets built.`
|
|
232
|
+
: starter
|
|
233
|
+
? `${project?.name} is loaded — the preview shows the ${starter.title} starter until your copy is published. Say what to change and it gets built.`
|
|
234
|
+
: project
|
|
235
|
+
? `${project.name} is loaded and nothing is published yet. Say what to change and it gets built.`
|
|
236
|
+
: `${slug} is loaded. Say what to change and it gets built.` })) : (ordered.map((r) => (_jsxs(YStack, { gap: "$2", children: [_jsx(Turn, { run: r, open: r.id === current, onOpen: () => setChosen(r.id), project: slug }), r.id === current ? (_jsxs(YStack, { gap: "$1", pl: "$2", borderLeftWidth: 1, borderColor: "$borderColor", children: [fold(run.events.map((e) => ({ kind: e.kind, actor: who(e.actor), seq: e.seq, id: e.id, text: said(e, run.record?.mode) })).filter((b) => b.text)).map((b) => (_jsx(SizableText, { size: "$1", color: "$soft", children: b.text }, b.key))), pr.href ? (_jsxs(Out, { href: pr.href, label: `Open pull request ${pr.label}`, children: [_jsxs(SizableText, { size: "$1", color: "$ink", textDecorationLine: "underline", children: ["Pull request ", pr.label] }), _jsx(ExternalLink, { size: 11 })] })) : null] })) : null] }, r.id)))) }), _jsxs(YStack, { px: "$3", pb: "$3", gap: "$2", children: [!dismissed && !running ? _jsx(Suggestions, { items: SUGGESTIONS, onPick: (s) => void send(s), onDismiss: () => setDismissed(true) }) : null, note ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note })) : null, picked.length ? _jsx(Attachments, { items: picked, onRemove: () => setPicked([]) }) : null, attached.length ? (_jsx(XStack, { gap: 6, flexWrap: "wrap", children: _jsx(Files, { files: attached, onFiles: setAttached }) })) : null, _jsxs(Composer, { value: draft, onChange: setDraft, onSend: () => void (current && running ? steer(current) : send()), onStop: current
|
|
225
237
|
? () => void stop(t, current).then(() => setNote('Stop requested — the run keeps its work on its branch'), (e) => setNote(e.message))
|
|
226
238
|
: undefined, busy: busy || (running && !draft.trim()), rows: 3, placeholder: running ? 'Steer this run' : 'Ask Hanzo for edits', label: "Ask Hanzo for edits", children: [_jsx(Attach, { files: attached, onFiles: setAttached, onNote: setNote }), _jsx(XStack, { flex: 1 }), _jsx(ModeSelect, { modes: MODES, value: mode, onChange: (m) => setMode(m), self: "center" }), _jsx(Dictate, { onText: (said) => setDraft((d) => (d ? `${d} ${said}` : said)), onNote: setNote })] })] })] }));
|
|
227
239
|
const body = view === 'preview' ? (_jsx(PreviewFrame, { ref: frame, src: src, title: `${project?.name ?? slug} preview`, device: device, onBridge: onBridge, empty: _jsxs(YStack, { items: "center", gap: "$2", p: "$6", children: [_jsx(SizableText, { size: "$3", color: "$ink", children: "Nothing deployed yet" }), _jsx(SizableText, { size: "$2", color: "$soft", text: "center", children: "Publish this project and its page appears here." })] }), toolbar: bridge ? (_jsx(XStack, { render: "button", "aria-pressed": editing, "aria-label": editing ? 'Stop picking elements' : 'Pick an element to edit', onPress: () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/build",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.25",
|
|
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.",
|