@hanzo/build 0.2.74 → 0.2.76

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.
@@ -13,8 +13,13 @@
13
13
  * signed and stop working at `expires`: read the item again for fresh ones.
14
14
  */
15
15
  import { type Target } from './call.ts';
16
- export declare const KINDS: readonly ['image', 'video', 'audio', 'document', 'slides', 'type', 'model', 'code', 'file'];
17
- export type Kind = (typeof KINDS)[number];
16
+ export declare const KIND_IDS: readonly ['image', 'video', 'audio', 'document', 'slides', 'type', 'model', 'code', 'file'];
17
+ export type Kind = (typeof KIND_IDS)[number];
18
+ /** The kinds a reader narrows to, in the order the Library lists them, '' being all. */
19
+ export declare const KINDS: readonly {
20
+ id: Kind | '';
21
+ label: string;
22
+ }[];
18
23
  export declare const SOURCES: readonly ['chat', 'dev', 'studio', 'workflow'];
19
24
  export type Source = (typeof SOURCES)[number];
20
25
  export type Scope = 'all' | 'mine' | 'shared';
@@ -13,7 +13,19 @@
13
13
  * signed and stop working at `expires`: read the item again for fresh ones.
14
14
  */
15
15
  import { call, query, seg } from './call.js';
16
- export const KINDS = ['image', 'video', 'audio', 'document', 'slides', 'type', 'model', 'code', 'file'];
16
+ export const KIND_IDS = ['image', 'video', 'audio', 'document', 'slides', 'type', 'model', 'code', 'file'];
17
+ /** The kinds a reader narrows to, in the order the Library lists them, '' being all. */
18
+ export const KINDS = [
19
+ { id: '', label: 'All' },
20
+ { id: 'image', label: 'Images' },
21
+ { id: 'video', label: 'Videos' },
22
+ { id: 'audio', label: 'Audio' },
23
+ { id: 'document', label: 'Documents' },
24
+ { id: 'slides', label: 'Slides' },
25
+ { id: 'model', label: '3D' },
26
+ { id: 'code', label: 'Code' },
27
+ { id: 'file', label: 'Files' },
28
+ ];
17
29
  export const SOURCES = ['chat', 'dev', 'studio', 'workflow'];
18
30
  /** A product, as a person names it. */
19
31
  export const SOURCE = { chat: 'Chat', dev: 'Dev', studio: 'Studio', workflow: 'Workflow' };
@@ -33,7 +45,7 @@ export function item(raw) {
33
45
  const id = str(o.id);
34
46
  if (!id)
35
47
  return null;
36
- const kind = KINDS.includes(str(o.kind)) ? o.kind : 'file';
48
+ const kind = KIND_IDS.includes(str(o.kind)) ? o.kind : 'file';
37
49
  const source = SOURCES.includes(str(o.source)) ? o.source : 'dev';
38
50
  return {
39
51
  id,
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, own, sections, tabs, type Group } from './nav.tsx';
28
+ export { library, nav, NEW, own, sections, tabs, useScheduled, 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, own, sections, tabs } from './nav.js';
28
+ export { library, nav, NEW, own, sections, tabs, useScheduled } 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/library.js CHANGED
@@ -18,26 +18,16 @@ import { Button, Dialog, DialogContent, DialogTitle, DropdownMenu, Input } from
18
18
  import { ChipSelect } from '@hanzo/ui/product';
19
19
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
20
20
  import { ago } from './ago.js';
21
- import { change, days, get, items as list, remove, size, SOURCE } from './api/library.js';
21
+ import { change, days, get, items as list, KINDS, remove, size, SOURCE } from './api/library.js';
22
22
  import { Confirm, Rename } from './ask.js';
23
23
  import { useKept, useProjects, useRead } from './data.js';
24
24
  import { useHost, useTarget } from './host.js';
25
+ import { KEPT } from './nav.js';
26
+ import { path, route } from './route.js';
25
27
  import { Panel } from './panel.js';
26
28
  import { gap, pane } from './pane.js';
27
29
  import { Sites } from './shelf.js';
28
30
  import { useDeck } from './tabs.js';
29
- /** The kinds a reader narrows to, in the order the chips draw them. */
30
- const KINDS = [
31
- { id: '', label: 'All' },
32
- { id: 'image', label: 'Images' },
33
- { id: 'video', label: 'Videos' },
34
- { id: 'audio', label: 'Audio' },
35
- { id: 'document', label: 'Documents' },
36
- { id: 'slides', label: 'Slides' },
37
- { id: 'model', label: '3D' },
38
- { id: 'code', label: 'Code' },
39
- { id: 'file', label: 'Files' },
40
- ];
41
31
  const FROM = [
42
32
  { id: '', label: 'Anywhere' },
43
33
  { id: 'chat', label: 'Chat' },
@@ -351,7 +341,10 @@ export function Library() {
351
341
  const t = useTarget();
352
342
  const signed = Boolean(host.person);
353
343
  const wide = useMedia().md;
354
- const [kind, setKind] = useState('');
344
+ // The kind is the address's (-/artifacts/<kind>), so the sidebar, a link and Back all name it.
345
+ const at = route(host.path);
346
+ const kind = at.kind === 'screen' && at.of ? at.of : '';
347
+ const setKind = (k) => host.go(path({ kind: 'screen', screen: 'artifacts', ...(k ? { of: k } : {}) }));
355
348
  const [source, setSource] = useState('');
356
349
  const [scope, setScope] = useState('all');
357
350
  const [typed, setTyped] = useState('');
@@ -368,6 +361,11 @@ export function Library() {
368
361
  const load = useMemo(() => (signed ? (cursor, signal) => list(t, { kind, source, scope, q, limit: PAGE, cursor }, { signal }) : null), [signed, t, kind, source, scope, q]);
369
362
  const pages = usePages(load, `${kind}|${source}|${scope}|${q}|${t.org}`);
370
363
  const end = useEnd(pages.more);
364
+ const reload = pages.reload;
365
+ useEffect(() => {
366
+ window.addEventListener(KEPT, reload);
367
+ return () => window.removeEventListener(KEPT, reload);
368
+ }, [reload]);
371
369
  const look = useCallback((x) => {
372
370
  side.open({ id: `${TAB}${x.id}`, kind: `${TAB}${x.id}`, title: x.name });
373
371
  setShown(true);
@@ -397,7 +395,7 @@ export function Library() {
397
395
  const open = shown && side.tabs.length > 0;
398
396
  const groups = days(pages.rows);
399
397
  const narrowed = Boolean(kind || source || q || scope !== 'all');
400
- return (_jsxs(XStack, { flex: 1, minH: 0, minW: 0, width: "100%", children: [_jsx(YStack, { flex: 1, minH: 0, minW: 0, overflow: "scroll", px: "$6", py: "$6", "$max-md": { px: '$4', py: '$4' }, children: _jsxs(YStack, { width: "100%", maxW: 1120, mx: "auto", gap: "$4", children: [_jsxs(XStack, { items: "center", gap: "$3", children: [_jsx(SizableText, { render: "h1", size: "$7", fontWeight: "600", color: "$ink", flex: 1, children: "Library" }), _jsxs(XStack, { role: "radiogroup", "aria-label": "View", gap: "$1", children: [_jsx(ViewMark, { label: "Grid", on: view === 'grid', onPress: () => setView('grid'), children: _jsx(LayoutGrid, { size: 16 }) }), _jsx(ViewMark, { label: "List", on: view === 'list', onPress: () => setView('list'), children: _jsx(List, { size: 16 }) })] })] }), _jsxs(XStack, { items: "center", gap: "$2", maxW: 480, children: [_jsx(Search, { size: 16, opacity: 0.6 }), _jsx(YStack, { flex: 1, minW: 0, children: _jsx(Input, { value: typed, onChangeText: setTyped, placeholder: "Search your library", "aria-label": "Search your library", disabled: !signed }) })] }), _jsxs(YStack, { gap: "$2", children: [_jsx(XStack, { role: "radiogroup", "aria-label": "Kind", gap: "$2", overflowX: "auto", pb: "$1", "$platform-web": { scrollbarWidth: 'none' }, children: KINDS.map((k) => (_jsx(Chip, { label: k.label, on: kind === k.id, onPress: () => setKind(k.id) }, k.id || 'all'))) }), _jsxs(XStack, { gap: "$2", flexWrap: "wrap", rowGap: "$2", children: [_jsx(ChipSelect, { name: "From", label: FROM.find((f) => f.id === source).label, chosen: FROM.find((f) => f.id === source), items: FROM, onChange: (f) => setSource(f.id), placement: "bottom-start", width: 200 }), _jsx(ChipSelect, { name: "Whose", label: WHOSE.find((w) => w.id === scope).label, chosen: WHOSE.find((w) => w.id === scope), items: WHOSE, onChange: (w) => setScope(w.id), placement: "bottom-start", width: 220 })] })] }), acts.note ? (_jsx(SizableText, { size: "$2", color: "$soft", role: "status", children: acts.note })) : null, !signed ? (_jsx(Said, { says: "Sign in to see your library.", children: _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: "Sign in" }) })) : pages.error && !pages.rows.length ? (_jsx(Said, { says: pages.error.message, children: _jsx(Button, { size: "sm", variant: "outline", onPress: pages.reload, children: "Try again" }) })) : pages.loading && !pages.rows.length ? (_jsx(Said, { says: "Reading\u2026" })) : !pages.rows.length ? (_jsx(Said, { says: narrowed ? 'Nothing matches.' : 'Nothing here yet. What you make in Chat, Dev and Studio lands here.' })) : (_jsxs(YStack, { gap: "$5", children: [groups.map((g) => (_jsxs(YStack, { gap: "$3", role: "group", "aria-label": g.label, children: [_jsx(SizableText, { size: "$2", color: "$soft", fontWeight: "500", children: g.label }), view === 'grid' ? (_jsx("div", { role: "list", "aria-label": `Made ${g.label.toLowerCase()}`, style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(min(46%, 168px), 1fr))', gap: 16 }, children: g.rows.map((x) => (_jsx(Tile, { x: x, onOpen: () => look(x), rows: acts.rows(x) }, x.id))) })) : (_jsx(YStack, { role: "list", "aria-label": `Made ${g.label.toLowerCase()}`, gap: "$0.5", children: g.rows.map((x) => (_jsx(Row, { x: x, onOpen: () => look(x), rows: acts.rows(x) }, x.id))) }))] }, g.label))), _jsx(YStack, { ref: end, height: 1 }), pages.loading ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "Reading more\u2026" })) : pages.error ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: pages.error.message })) : null] })), signed && !q && !kind && (source === '' || source === 'dev') && scope !== 'shared' ? (_jsx(YStack, { pt: "$6", borderTopWidth: 1, borderColor: "$borderColor", children: _jsx(Sites, {}) })) : null] }) }), open && !wide ? panel(true) : null, open && wide ? (_jsx(YStack, { "data-slot": "library-side", ...pane, ml: gap, shrink: 0, minH: 0, width: 440, maxW: "45%", children: panel(false) })) : null, acts.dialogs] }));
398
+ return (_jsxs(XStack, { flex: 1, minH: 0, minW: 0, width: "100%", children: [_jsx(YStack, { flex: 1, minH: 0, minW: 0, overflow: "scroll", px: "$6", py: "$6", "$max-md": { px: '$4', py: '$4' }, children: _jsxs(YStack, { width: "100%", maxW: 1120, mx: "auto", gap: "$4", children: [_jsxs(XStack, { items: "center", gap: "$3", children: [_jsx(SizableText, { render: "h1", size: "$7", fontWeight: "600", color: "$ink", flex: 1, children: KINDS.find((k) => k.id === kind)?.id ? KINDS.find((k) => k.id === kind).label : 'Library' }), _jsxs(XStack, { role: "radiogroup", "aria-label": "View", gap: "$1", children: [_jsx(ViewMark, { label: "Grid", on: view === 'grid', onPress: () => setView('grid'), children: _jsx(LayoutGrid, { size: 16 }) }), _jsx(ViewMark, { label: "List", on: view === 'list', onPress: () => setView('list'), children: _jsx(List, { size: 16 }) })] })] }), _jsxs(XStack, { items: "center", gap: "$2", maxW: 480, children: [_jsx(Search, { size: 16, opacity: 0.6 }), _jsx(YStack, { flex: 1, minW: 0, children: _jsx(Input, { value: typed, onChangeText: setTyped, placeholder: "Search your library", "aria-label": "Search your library", disabled: !signed }) })] }), _jsxs(YStack, { gap: "$2", children: [host.sections ? null : (_jsx(XStack, { role: "radiogroup", "aria-label": "Kind", gap: "$2", overflowX: "auto", pb: "$1", "$platform-web": { scrollbarWidth: 'none' }, children: KINDS.map((k) => (_jsx(Chip, { label: k.label, on: kind === k.id, onPress: () => setKind(k.id) }, k.id || 'all'))) })), _jsxs(XStack, { gap: "$2", flexWrap: "wrap", rowGap: "$2", children: [_jsx(ChipSelect, { name: "From", label: FROM.find((f) => f.id === source).label, chosen: FROM.find((f) => f.id === source), items: FROM, onChange: (f) => setSource(f.id), placement: "bottom-start", width: 200 }), _jsx(ChipSelect, { name: "Whose", label: WHOSE.find((w) => w.id === scope).label, chosen: WHOSE.find((w) => w.id === scope), items: WHOSE, onChange: (w) => setScope(w.id), placement: "bottom-start", width: 220 })] })] }), acts.note ? (_jsx(SizableText, { size: "$2", color: "$soft", role: "status", children: acts.note })) : null, !signed ? (_jsx(Said, { says: "Sign in to see your library.", children: _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: "Sign in" }) })) : pages.error && !pages.rows.length ? (_jsx(Said, { says: pages.error.message, children: _jsx(Button, { size: "sm", variant: "outline", onPress: pages.reload, children: "Try again" }) })) : pages.loading && !pages.rows.length ? (_jsx(Said, { says: "Reading\u2026" })) : !pages.rows.length ? (_jsx(Said, { says: narrowed ? 'Nothing matches.' : 'Nothing here yet. What you make in Chat, Dev and Studio lands here.' })) : (_jsxs(YStack, { gap: "$5", children: [groups.map((g) => (_jsxs(YStack, { gap: "$3", role: "group", "aria-label": g.label, children: [_jsx(SizableText, { size: "$2", color: "$soft", fontWeight: "500", children: g.label }), view === 'grid' ? (_jsx("div", { role: "list", "aria-label": `Made ${g.label.toLowerCase()}`, style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(min(46%, 168px), 1fr))', gap: 16 }, children: g.rows.map((x) => (_jsx(Tile, { x: x, onOpen: () => look(x), rows: acts.rows(x) }, x.id))) })) : (_jsx(YStack, { role: "list", "aria-label": `Made ${g.label.toLowerCase()}`, gap: "$0.5", children: g.rows.map((x) => (_jsx(Row, { x: x, onOpen: () => look(x), rows: acts.rows(x) }, x.id))) }))] }, g.label))), _jsx(YStack, { ref: end, height: 1 }), pages.loading ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "Reading more\u2026" })) : pages.error ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: pages.error.message })) : null] })), signed && !q && !kind && (source === '' || source === 'dev') && scope !== 'shared' ? (_jsx(YStack, { pt: "$6", borderTopWidth: 1, borderColor: "$borderColor", children: _jsx(Sites, {}) })) : null] }) }), open && !wide ? panel(true) : null, open && wide ? (_jsx(YStack, { "data-slot": "library-side", ...pane, ml: gap, shrink: 0, minH: 0, width: 440, maxW: "45%", children: panel(false) })) : null, acts.dialogs] }));
401
399
  }
402
400
  function ViewMark({ label, on, onPress, children }) {
403
401
  return (_jsx(XStack, { render: "button", role: "radio", "aria-checked": on, "aria-label": label, onPress: onPress, width: 32, height: 32, items: "center", justify: "center", rounded: "$3", bg: on ? '$hover' : 'transparent', hoverStyle: { bg: '$hover' }, cursor: "pointer", opacity: on ? 1 : 0.6, children: children }));
package/lib/nav.d.ts CHANGED
@@ -19,3 +19,9 @@ export declare const own: (host: Host, go: (path: string) => void) => Group[];
19
19
  export declare function sections(host: Host, go: (path: string) => void): Group[];
20
20
  /** Customize's tabs as a host's sidebar lists them, Agents first; an agent's profile lights Agents. */
21
21
  export declare function tabs(host: Host, go: (path: string) => void): Group[];
22
+ /** Fired on `window` once the sidebar's Upload has kept its files, for the Library to read again. */
23
+ export declare const KEPT = "hanzo:library:kept";
24
+ /** The Library's sidebar: Upload, then every kind it narrows to, the one in the address lit. */
25
+ export declare function library(host: Host, go: (path: string) => void): Group[];
26
+ /** Scheduled tasks' sidebar: New task, then each of the person's tasks, the one open lit. Read only while `on`. */
27
+ export declare function useScheduled(host: Host, go: (path: string) => void, on: boolean): Group[];
package/lib/nav.js CHANGED
@@ -9,8 +9,12 @@ 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 { 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';
12
+ import { AudioLines, Bell, BookOpen, Bot, Box, Brain, CircleDot, Clock, Code, Container, Cpu, CreditCard, File, FileText, FolderGit2, Gauge, Image, Key, LayoutTemplate, Library, Plug, Plus, Presentation, Puzzle, Settings, Shield, SlidersHorizontal, Sparkles, SquarePen, Upload, User, Users, Video } from '@hanzogui/lucide-icons-2';
13
+ import { useMemo } from 'react';
14
+ import { automations } from './api/auto.js';
15
+ import { KINDS, upload } from './api/library.js';
13
16
  import { pinBoard } from './choice.js';
17
+ import { useRead } from './data.js';
14
18
  import { DOCS } from './foot.js';
15
19
  import { path, route, TABS } from './route.js';
16
20
  import { GROUPS, heading, LIST } from './settings/list.js';
@@ -117,3 +121,73 @@ const SECTION_ICON = {
117
121
  integrations: _jsx(Plug, { size: 16 }),
118
122
  notifications: _jsx(Bell, { size: 16 }),
119
123
  };
124
+ const KIND_ICON = {
125
+ '': _jsx(Library, { size: 16 }),
126
+ image: _jsx(Image, { size: 16 }),
127
+ video: _jsx(Video, { size: 16 }),
128
+ audio: _jsx(AudioLines, { size: 16 }),
129
+ document: _jsx(FileText, { size: 16 }),
130
+ slides: _jsx(Presentation, { size: 16 }),
131
+ model: _jsx(Box, { size: 16 }),
132
+ code: _jsx(Code, { size: 16 }),
133
+ file: _jsx(File, { size: 16 }),
134
+ };
135
+ /** Fired on `window` once the sidebar's Upload has kept its files, for the Library to read again. */
136
+ export const KEPT = 'hanzo:library:kept';
137
+ /** Asks for files and keeps each in the Library; a refusal is said in the page's own words. */
138
+ function keep(host) {
139
+ const input = document.createElement('input');
140
+ input.type = 'file';
141
+ input.multiple = true;
142
+ input.onchange = async () => {
143
+ const t = { api: host.api, token: host.token, org: host.org };
144
+ for (const f of Array.from(input.files ?? [])) {
145
+ try {
146
+ await upload(t, { name: f.name, type: f.type, bytes: new Uint8Array(await f.arrayBuffer()) }, { source: 'chat' });
147
+ }
148
+ catch (e) {
149
+ window.alert(e instanceof Error ? e.message : `${f.name} was not kept`);
150
+ }
151
+ }
152
+ window.dispatchEvent(new Event(KEPT));
153
+ };
154
+ input.click();
155
+ }
156
+ /** The Library's sidebar: Upload, then every kind it narrows to, the one in the address lit. */
157
+ export function library(host, go) {
158
+ const r = route(host.path);
159
+ const at = r.kind === 'screen' && r.screen === 'artifacts' ? (r.of ?? '') : null;
160
+ return [
161
+ { id: 'library-do', label: '', links: [{ id: 'upload', label: 'Upload', icon: _jsx(Upload, { size: 16 }), onPress: () => keep(host) }] },
162
+ {
163
+ id: 'library-kinds',
164
+ label: '',
165
+ links: KINDS.map((k) => ({
166
+ id: `kind-${k.id || 'all'}`,
167
+ label: k.label,
168
+ icon: KIND_ICON[k.id],
169
+ onPress: () => go(path({ kind: 'screen', screen: 'artifacts', ...(k.id ? { of: k.id } : {}) })),
170
+ active: at === k.id,
171
+ })),
172
+ },
173
+ ];
174
+ }
175
+ /** Scheduled tasks' sidebar: New task, then each of the person's tasks, the one open lit. Read only while `on`. */
176
+ export function useScheduled(host, go, on) {
177
+ const t = useMemo(() => ({ api: host.api, token: host.token, org: host.org }), [host.api, host.token, host.org]);
178
+ const list = useRead(on && host.person ? () => automations(t) : null, [], [t, on, host.path]);
179
+ const r = route(host.path);
180
+ const at = r.kind === 'automation' ? r.id : '';
181
+ return [
182
+ {
183
+ id: 'scheduled-do',
184
+ label: '',
185
+ links: [{ id: 'new-task', label: 'New task', icon: _jsx(Plus, { size: 16 }), onPress: () => go(path({ kind: 'automation', id: 'new' })), active: at === 'new' }],
186
+ },
187
+ {
188
+ id: 'scheduled-tasks',
189
+ label: list.value.length ? 'Tasks' : '',
190
+ links: list.value.map((a) => ({ id: a.id, label: a.name || 'Untitled task', icon: _jsx(Clock, { size: 16 }), onPress: () => go(path({ kind: 'automation', id: a.id })), active: at === a.id })),
191
+ },
192
+ ];
193
+ }
package/lib/reply.js CHANGED
@@ -28,14 +28,13 @@ const suffix = (s) => {
28
28
  const dot = s.lastIndexOf('.');
29
29
  return dot > 0 ? s.slice(dot + 1).toLowerCase() : '';
30
30
  };
31
- /** An info string is a file name (a dot, no spaces) or a language. */
31
+ /** An info string is a language, a file name (a dot, no spaces), or both (`html index.html`): the file name wins. */
32
32
  const called = (info, n) => {
33
- const word = info.trim().split(/\s+/)[0] ?? '';
34
- if (!word)
35
- return `Block ${n}`;
36
- if (word.includes('.') && suffix(word))
37
- return word;
38
- return `${word} block`;
33
+ const words = info.trim().split(/\s+/).filter(Boolean);
34
+ const file = words.find((w) => w.includes('.') && suffix(w));
35
+ if (file)
36
+ return file;
37
+ return words[0] ? `${words[0]} block` : `Block ${n}`;
39
38
  };
40
39
  /** Every fenced block in `text`, in order. */
41
40
  export function artifacts(text) {
package/lib/route.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * '' the empty state: a new run
5
5
  * sess_<32 hex> one run
6
6
  * -/<screen> a builder screen
7
+ * -/artifacts/<kind> the Library narrowed to one kind (image, video, document, …)
7
8
  * -/automations/<id> one automation's editor and runs; `new` writes one
8
9
  * -/settings[/<section>] a section of Settings
9
10
  * -/customize[/<tab>] agents, skills, connectors and plugins: what the agent brings to a run
@@ -42,6 +43,7 @@ export type Route = {
42
43
  } | {
43
44
  kind: 'screen';
44
45
  screen: Screen;
46
+ of?: Of;
45
47
  } | {
46
48
  kind: 'settings';
47
49
  section: Section;
@@ -62,6 +64,9 @@ export type Route = {
62
64
  kind: 'automation';
63
65
  id: string;
64
66
  };
67
+ /** The kinds the Library narrows to by address (api/library.ts KIND_IDS). */
68
+ export declare const OF: readonly ['image', 'video', 'audio', 'document', 'slides', 'type', 'model', 'code', 'file'];
69
+ export type Of = (typeof OF)[number];
65
70
  export declare const SESSION: RegExp;
66
71
  export declare const SLUG: RegExp;
67
72
  /** An organization or a repository name on the forge (api/codebases.ts `NAME`). */
package/lib/route.js CHANGED
@@ -17,6 +17,8 @@ export const SECTIONS = [
17
17
  ];
18
18
  /** Customize, one tab each: what the agent brings to a run, yours and to discover. Agents first. */
19
19
  export const TABS = ['agents', 'skills', 'connectors', 'plugins'];
20
+ /** The kinds the Library narrows to by address (api/library.ts KIND_IDS). */
21
+ export const OF = ['image', 'video', 'audio', 'document', 'slides', 'type', 'model', 'code', 'file'];
20
22
  export const SESSION = /^sess_[0-9a-f]{32}$/;
21
23
  export const SLUG = /^[a-z0-9]([a-z0-9-]{0,38}[a-z0-9])?$/;
22
24
  /** An organization or a repository name on the forge (api/codebases.ts `NAME`). */
@@ -44,6 +46,10 @@ export function route(path) {
44
46
  const id = p.slice('-/automations/'.length);
45
47
  return AUTOMATION.test(id) ? { kind: 'automation', id } : { kind: 'new' };
46
48
  }
49
+ if (p.startsWith('-/artifacts/')) {
50
+ const of = p.slice('-/artifacts/'.length);
51
+ return OF.includes(of) ? { kind: 'screen', screen: 'artifacts', of } : { kind: 'new' };
52
+ }
47
53
  if (p.startsWith('-/')) {
48
54
  const s = p.slice(2);
49
55
  return SCREENS.includes(s) ? { kind: 'screen', screen: s } : { kind: 'new' };
@@ -103,7 +109,7 @@ export function path(r) {
103
109
  case 'run':
104
110
  return r.id;
105
111
  case 'screen':
106
- return `-/${r.screen}`;
112
+ return r.of ? `-/${r.screen}/${r.of}` : `-/${r.screen}`;
107
113
  case 'settings':
108
114
  return r.section === 'general' ? '-/settings' : `-/settings/${r.section}`;
109
115
  case 'customize':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/build",
3
- "version": "0.2.74",
3
+ "version": "0.2.76",
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.",