@hanzo/build 0.2.18 → 0.2.19

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 CHANGED
@@ -193,7 +193,7 @@ picker and open-in-tab; Share and Publish; the console dock under it.
193
193
 
194
194
  | call | what |
195
195
  |---|---|
196
- | `POST /v1/agent/coding` | start a run → 202 `{sessionId, …}` |
196
+ | `POST /v1/agent/coding` | start a run → 202 `{sessionId, …}`; with no `repo` the platform starts a new project named from the ask |
197
197
  | `GET /v1/agent/sessions?kind=coding` | Recents, and a project's runs |
198
198
  | `GET /v1/agent/sessions/{id}` · `GET /v1/agent/sessions/stream?root=` | a run, and its live feed (SSE over fetch) |
199
199
  | `POST /v1/agent/sessions/{id}/message` · `/pause` · `/resume` · `/stop` | steer, pause, resume a machine's run, stop |
package/lib/builder.d.ts CHANGED
@@ -1,4 +1,14 @@
1
+ import { type RailLink } from '@hanzo/ui/chat';
1
2
  import { type Host } from './host.tsx';
3
+ /**
4
+ * The builder's places, as a rail draws them: four in view and the rest under
5
+ * More. Every one moves the builder through `go`, so a host that draws its own
6
+ * rail lists the same places, in the same words, as the builder's rail does.
7
+ */
8
+ export declare function nav(host: Host, go: (path: string) => void): {
9
+ links: RailLink[];
10
+ more: RailLink[];
11
+ };
2
12
  /**
3
13
  * The builder. Mount it under a gui root (`<Hanzo>`), in a box with a height.
4
14
  *
package/lib/builder.js CHANGED
@@ -24,8 +24,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
24
24
  * bar and panes — and its mark leads back here.
25
25
  *
26
26
  * A host with a rail of its own mounts `<Builder rail={false}>` and draws the
27
- * builder's places and runs in that rail with `DevSection` (section.tsx): one
28
- * left column, never two.
27
+ * builder's places (`nav`) and runs in that rail (`useSessions`, or `DevSection`,
28
+ * section.tsx): one left column, never two.
29
29
  */
30
30
  import { SizableText, XStack, YStack } from '@hanzo/gui';
31
31
  import { Blocks, BookOpen, CircleDot, Cpu, FolderGit2, Kanban, LayoutTemplate, Menu, Search, SlidersHorizontal, Workflow } from '@hanzogui/lucide-icons-2';
@@ -72,12 +72,31 @@ function Shell() {
72
72
  list.sort((a, b) => Number(b.status === 'running') - Number(a.status === 'running'));
73
73
  return list;
74
74
  }, [recents.value, order]);
75
- const screen = r.kind === 'screen' ? r.screen : '';
76
75
  // The top left, as on claude.ai: the builder's name, which leads to New, and under
77
76
  // it the search that finds a run. The organization is the account's, so it is
78
77
  // switched from the account menu at the foot. Beside the rail from md up, beside
79
78
  // the drawer's button below it — one place at a time.
80
79
  const name = _jsx(Name, { onPress: () => go('') });
80
+ const { links, more } = nav(host, go);
81
+ 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.' }), notice: host.person && !slack ? (_jsx(Slack, { onOpen: () => go(path({ kind: 'settings', section: 'integrations' })), onDismiss: () => setSlack(true) })) : undefined, account: host.person
82
+ ? {
83
+ // Who, and in which organization, as claude.ai says who and on which plan.
84
+ name: [host.person.name || host.person.email, host.org].filter(Boolean).join(' · '),
85
+ onPress: () => {
86
+ setDrawer(false);
87
+ setMenu(true);
88
+ },
89
+ }
90
+ : { 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: menu, onOpenChange: setMenu, 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); } })] }));
91
+ }
92
+ /**
93
+ * The builder's places, as a rail draws them: four in view and the rest under
94
+ * More. Every one moves the builder through `go`, so a host that draws its own
95
+ * rail lists the same places, in the same words, as the builder's rail does.
96
+ */
97
+ export function nav(host, go) {
98
+ const r = route(host.path);
99
+ const screen = r.kind === 'screen' ? r.screen : '';
81
100
  const links = [
82
101
  { id: 'projects', label: 'Projects', icon: _jsx(Kanban, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'projects' })), active: screen === 'projects' },
83
102
  { id: 'artifacts', label: 'Artifacts', icon: _jsx(Blocks, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'artifacts' })), active: screen === 'artifacts' },
@@ -106,16 +125,7 @@ function Shell() {
106
125
  },
107
126
  { id: 'docs', label: 'Docs', icon: _jsx(BookOpen, { size: 16 }), onPress: () => window.open(DOCS, '_blank', 'noopener,noreferrer') },
108
127
  ];
109
- 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.' }), notice: host.person && !slack ? (_jsx(Slack, { onOpen: () => go(path({ kind: 'settings', section: 'integrations' })), onDismiss: () => setSlack(true) })) : undefined, account: host.person
110
- ? {
111
- // Who, and in which organization, as claude.ai says who and on which plan.
112
- name: [host.person.name || host.person.email, host.org].filter(Boolean).join(' · '),
113
- onPress: () => {
114
- setDrawer(false);
115
- setMenu(true);
116
- },
117
- }
118
- : { 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: menu, onOpenChange: setMenu, 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); } })] }));
128
+ return { links, more };
119
129
  }
120
130
  /** The wordmark: the product's name, as its host calls it, which leads to New. */
121
131
  function Name({ onPress }) {
package/lib/index.d.ts CHANGED
@@ -3,12 +3,16 @@
3
3
  *
4
4
  * Mount `<Builder host={…} />` under a gui root in a box with a height. The
5
5
  * host says where the platform is, who is signed in, and how to move; the
6
- * builder does everything else. hanzo.build hosts it whole; hanzo.ai and the
7
- * Hanzo App mount it at /dev with `rail={false}` and list its places and runs in
8
- * their own rail with `DevSection`.
6
+ * builder does everything else. hanzo.build hosts it whole. A host with a rail
7
+ * of its own mounts it with `rail={false}` and draws the builder's parts in that
8
+ * rail: its places (`nav`), its runs (`useSessions`, or `DevSection` for both),
9
+ * and at the foot the Slack card and the account menu (`Slack`, `Who`), under
10
+ * `HostProvider`.
9
11
  */
10
- export { Builder } from './builder.tsx';
12
+ export { Builder, nav } from './builder.tsx';
11
13
  export { DevSection, DOTS, useSessions } from './section.tsx';
14
+ export { HostProvider } from './host.tsx';
15
+ export { Slack, Who } from './foot.tsx';
12
16
  export { administers } from './claims.ts';
13
17
  export type { Host, Person } from './host.tsx';
14
18
  export type { Session, Status } from './api/sessions.ts';
package/lib/index.js CHANGED
@@ -3,11 +3,15 @@
3
3
  *
4
4
  * Mount `<Builder host={…} />` under a gui root in a box with a height. The
5
5
  * host says where the platform is, who is signed in, and how to move; the
6
- * builder does everything else. hanzo.build hosts it whole; hanzo.ai and the
7
- * Hanzo App mount it at /dev with `rail={false}` and list its places and runs in
8
- * their own rail with `DevSection`.
6
+ * builder does everything else. hanzo.build hosts it whole. A host with a rail
7
+ * of its own mounts it with `rail={false}` and draws the builder's parts in that
8
+ * rail: its places (`nav`), its runs (`useSessions`, or `DevSection` for both),
9
+ * and at the foot the Slack card and the account menu (`Slack`, `Who`), under
10
+ * `HostProvider`.
9
11
  */
10
- export { Builder } from './builder.js';
12
+ export { Builder, nav } from './builder.js';
11
13
  export { DevSection, DOTS, useSessions } from './section.js';
14
+ export { HostProvider } from './host.js';
15
+ export { Slack, Who } from './foot.js';
12
16
  export { administers } from './claims.js';
13
17
  export { path, route, SESSION, SLUG } from './route.js';
package/lib/landing.js CHANGED
@@ -6,8 +6,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
6
6
  * which repository and branch it starts from, and the ask itself.
7
7
  * Under it: attach, dictate, the mode, and the model and effort on the right.
8
8
  *
9
- * Sending starts a coding run and opens it. The choices a person made here are
10
- * kept in this browser, per org, so the next New starts where the last one did.
9
+ * Sending starts a coding run and opens it. The codebase is optional: in the
10
+ * sandbox a run with none starts a new project named from the ask, as the
11
+ * platform does for a run that names no repository; a machine works in its own
12
+ * checkout, so a run there names one. The choices a person made here are kept in
13
+ * this browser, per org, so the next New starts where the last one did.
11
14
  */
12
15
  import { SizableText, XStack, YStack } from '@hanzo/gui';
13
16
  import { Cloud, Monitor } from '@hanzogui/lucide-icons-2';
@@ -105,8 +108,9 @@ export function Landing({ onStarted }) {
105
108
  host.signIn?.();
106
109
  return;
107
110
  }
108
- if (!isForge(kept.repo)) {
109
- setNote('Choose a codebase on the forge.');
111
+ const repo = isForge(kept.repo) ? kept.repo : null;
112
+ if (!repo && place.id) {
113
+ setNote(`A run on ${place.label} works in its own checkout: choose the codebase, or run it in Cloud.`);
110
114
  return;
111
115
  }
112
116
  setBusy(true);
@@ -115,9 +119,10 @@ export function Landing({ onStarted }) {
115
119
  const run = await start(t, {
116
120
  prompt: compose(draft.trim(), files),
117
121
  // The name alone. The org rides the request, and a slash is not a repo name.
122
+ // No codebase is not sent, and the run starts a new project.
118
123
  // An empty base or place is not sent: the default branch, and the sandbox.
119
- repo: kept.repo.name,
120
- base: kept.branch,
124
+ repo: repo?.name,
125
+ base: repo ? kept.branch : undefined,
121
126
  targetId: place.id,
122
127
  mode: kept.mode,
123
128
  model: kept.model === ENSO ? undefined : kept.model,
@@ -172,7 +177,7 @@ export function Landing({ onStarted }) {
172
177
  name: r.name,
173
178
  });
174
179
  },
175
- }, placeholder: "Codebase", disabled: !signed }, round), isForge(kept.repo) ? _jsx(BranchSelect, { value: branch, onChange: (b) => set({ branch: b }), load: loadBranches }) : null, _jsx(Files, { files: files, onFiles: setFiles })] }));
180
+ }, placeholder: "New project", disabled: !signed }, round), isForge(kept.repo) ? _jsx(BranchSelect, { value: branch, onChange: (b) => set({ branch: b }), load: loadBranches }) : null, _jsx(Files, { files: files, onFiles: setFiles })] }));
176
181
  const foot = (_jsxs(XStack, { flex: 1, items: "center", gap: "$2", flexWrap: "wrap", rowGap: "$1", children: [_jsx(Attach, { files: files, onFiles: setFiles, onNote: setNote }), _jsx(Dictate, { onText: (said) => setDraft((d) => (d ? `${d} ${said}` : said)), onNote: setNote }), _jsx(ModeSelect, { modes: MODES, value: kept.mode, onChange: (m) => set({ mode: m }), bg: "transparent", minH: 24, px: "$1.5", self: "center" }), _jsx(XStack, { flex: 1 }), _jsx(ChipSelect, { quiet: true, name: "Model", label: catalog.value.find((m) => m.id === kept.model)?.label ?? 'Enso', chosen: catalog.value.find((m) => m.id === kept.model) ?? null, items: catalog.value, onChange: (m) => set({ model: m.id }), placeholder: "Search models\u2026", placement: "top-end", loading: catalog.loading, error: catalog.error?.message ?? null }), _jsx(ChipSelect, { quiet: true, name: "Effort", label: EFFORTS.find((e) => e.id === kept.effort)?.label ?? 'Medium', chosen: EFFORTS.find((e) => e.id === kept.effort) ?? null, items: EFFORTS, onChange: (e) => set({ effort: e.id }), placement: "top-end", width: 180 })] }));
177
182
  return (_jsxs(YStack, { flex: 1, minH: 0, minW: 0, items: "center", justify: "center", px: "$4", pb: "$10", overflow: "scroll", children: [_jsxs(YStack, { width: "100%", maxW: COLUMN, gap: "$5", children: [_jsxs(XStack, { role: "heading", "aria-level": 1, justify: "center", items: "center", gap: "$3", flexWrap: "wrap", children: [_jsx(HanzoMark, { size: 26 }), _jsx(SizableText, { size: "$8", color: "$ink", style: { textAlign: 'center' }, children: prefs.callName ? `What’s up next, ${prefs.callName}?` : 'What’s up next?' })] }), _jsxs(YStack, { gap: "$2", children: [env.value && env.value.state !== 'ready' && !place.id ? (_jsxs(XStack, { items: "center", justify: "space-between", gap: "$3", px: "$3", py: "$2", rounded: "$10", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$2", color: "$ink", flex: 1, minW: 0, children: env.value.state === 'proposed'
178
183
  ? `A proposed environment for ${codebase} is waiting for review.`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/build",
3
- "version": "0.2.18",
3
+ "version": "0.2.19",
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).",