@hanzo/rooms 0.1.38 → 0.1.40
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 +2 -2
- package/dist/Account.js +18 -9
- package/dist/Chat.js +125 -202
- package/dist/Column.js +16 -15
- package/dist/Directory.d.ts +4 -9
- package/dist/Directory.js +60 -155
- package/dist/Me.js +20 -16
- package/dist/Prose.js +5 -4
- package/dist/RightPane.d.ts +20 -28
- package/dist/RightPane.js +107 -327
- package/dist/Settings.js +42 -28
- package/dist/Shell.js +19 -22
- package/dist/dev.js +42 -26
- package/dist/host.d.ts +5 -9
- package/dist/host.js +5 -6
- package/dist/lib/destination.d.ts +6 -6
- package/dist/lib/destination.js +6 -6
- package/dist/lib/host.d.ts +50 -31
- package/dist/lib/host.js +98 -48
- package/dist/lib/limits.d.ts +13 -9
- package/dist/lib/limits.js +20 -0
- package/dist/lib/plans.d.ts +2 -13
- package/dist/lib/plans.js +2 -19
- package/dist/lib/session.d.ts +9 -12
- package/dist/lib/session.js +7 -19
- package/dist/lib/web.d.ts +46 -38
- package/dist/lib/web.js +480 -213
- package/dist/pane.d.ts +4 -57
- package/dist/pane.js +13 -159
- package/package.json +3 -5
- package/dist/Billing.d.ts +0 -1
- package/dist/Billing.js +0 -32
- package/dist/enso.d.ts +0 -72
- package/dist/enso.js +0 -112
- package/dist/model.d.ts +0 -4
- package/dist/model.js +0 -44
package/dist/Column.js
CHANGED
|
@@ -4,17 +4,20 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
// New, Dev's places, the Recents of the mode it is in, and at the foot the
|
|
5
5
|
// account (components/workspace/Me.tsx).
|
|
6
6
|
//
|
|
7
|
+
// Dev's places are @hanzo/build's `nav`, the one list every rail draws, each a
|
|
8
|
+
// row of its own under a quiet group label: nothing waits behind a More.
|
|
9
|
+
//
|
|
7
10
|
// It is a pane on the frame's ground (app/_web.tsx), sized by the edge in the
|
|
8
11
|
// gutter to its right: dragged, keyed or double-clicked back (@hanzo/build's
|
|
9
12
|
// `Grip`), kept per browser. Pulled in past its floor it collapses to a rail of
|
|
10
13
|
// marks, kept under the key the builder's rail kept it under, so a reader's
|
|
11
14
|
// choice outlives the move.
|
|
12
15
|
import { Text, XStack, YStack } from '@hanzo/gui';
|
|
13
|
-
import {
|
|
14
|
-
import { Grip, nav,
|
|
16
|
+
import { PanelLeft, Plus, Search } from 'lucide-react';
|
|
17
|
+
import { Grip, nav, NEW } from '@hanzo/build';
|
|
15
18
|
import { Sidebar, SidebarIconButton, SidebarItem } from '@hanzo/ui/chat';
|
|
16
19
|
import { HanzoMark } from '@hanzogui/shell';
|
|
17
|
-
import { useState } from 'react';
|
|
20
|
+
import { useId, useState } from 'react';
|
|
18
21
|
import { ModeMark } from './ModeMark.js';
|
|
19
22
|
import { useKept } from './kept.js';
|
|
20
23
|
import { Made, Me } from './Me.js';
|
|
@@ -49,22 +52,20 @@ const keys = (press) => ({
|
|
|
49
52
|
press();
|
|
50
53
|
},
|
|
51
54
|
});
|
|
52
|
-
/** A row of the column: a pill, lit where the reader is. */
|
|
53
|
-
const ROW = { rounded: '$4', minH:
|
|
55
|
+
/** A row of the column: a pill, lit where the reader is. Compact, so every place fits a laptop's height with Recents under them. */
|
|
56
|
+
const ROW = { rounded: '$4', minH: 30, py: 0 };
|
|
54
57
|
export function Column({ host, mode, thread, onChat, onDev, go, drawer = false, }) {
|
|
55
58
|
const [kept, setCollapsed] = useKept('hanzo.build.rail', false);
|
|
56
59
|
const collapsed = kept && !drawer;
|
|
57
|
-
const [unfolded, setUnfolded] = useState(false);
|
|
58
60
|
const [words, setWords] = useState('');
|
|
61
|
+
// The desktop column and a phone's drawer can both be in the page: each names its own group labels.
|
|
62
|
+
const uid = useId();
|
|
59
63
|
const span = useSpan(SPAN, WIDTH, FLOOR, CEIL);
|
|
60
64
|
const dev = (p) => host.go(p);
|
|
61
|
-
const
|
|
62
|
-
const fresh = mode === 'chat' ? !thread : r.kind === 'new';
|
|
63
|
-
const { links, more } = nav(host, dev);
|
|
65
|
+
const groups = nav(host, dev);
|
|
64
66
|
const open = (row) => (row.kind === 'chat' ? go(talk(row.id)) : dev(row.id));
|
|
67
|
+
// Chat's New; Dev's is the first of its places.
|
|
65
68
|
const create = () => {
|
|
66
|
-
if (mode === 'dev')
|
|
67
|
-
return dev('');
|
|
68
69
|
empty();
|
|
69
70
|
go(talk());
|
|
70
71
|
};
|
|
@@ -74,15 +75,15 @@ export function Column({ host, mode, thread, onChat, onDev, go, drawer = false,
|
|
|
74
75
|
window.location.assign(mode === 'dev' ? app('') : talk());
|
|
75
76
|
};
|
|
76
77
|
if (collapsed)
|
|
77
|
-
return (_jsx(YStack, { "data-slot": "app-column", "data-collapsed": "", position: "relative", shrink: 0, width: RAIL, "$max-md": { display: 'none' }, children: _jsxs(Sidebar, { "data-slot": "app-rail", "data-collapsed": "true", role: "navigation", "aria-label": brand(), ...pane(), ...FILL, px: "$1.5", py: "$2.5", gap: "$1.5", items: "center", children: [_jsx(SidebarIconButton, { label: "Expand sidebar", onPress: () => setCollapsed(false), width: 36, height: 36, rounded: 999, children: _jsx(PanelLeft, { size: 16 }) }), _jsx(ModeMark, { mode: mode, onChat: onChat, onDev: onDev, stacked: true }),
|
|
78
|
+
return (_jsx(YStack, { "data-slot": "app-column", "data-collapsed": "", position: "relative", shrink: 0, width: RAIL, "$max-md": { display: 'none' }, children: _jsxs(Sidebar, { "data-slot": "app-rail", "data-collapsed": "true", role: "navigation", "aria-label": brand(), ...pane(), ...FILL, px: "$1.5", py: "$2.5", gap: "$1.5", items: "center", children: [_jsx(SidebarIconButton, { label: "Expand sidebar", onPress: () => setCollapsed(false), width: 36, height: 36, rounded: 999, children: _jsx(PanelLeft, { size: 16 }) }), _jsx(ModeMark, { mode: mode, onChat: onChat, onDev: onDev, stacked: true }), mode === 'chat' ? (_jsx(SidebarIconButton, { label: "New chat", onPress: create, width: 36, height: 36, rounded: 999, children: _jsx(Plus, { size: 16 }) })) : (
|
|
79
|
+
// Every place, as its mark: collapsed is narrower, never shorter of a destination.
|
|
80
|
+
groups.map((g) => (_jsx(YStack, { role: "group", "aria-label": g.label, items: "center", gap: 2, pt: "$1", children: g.links.map((link) => (_jsx(SidebarIconButton, { label: link.label, onPress: () => link.onPress?.(), width: 36, height: 32, rounded: 999, "aria-current": link.active ? 'page' : undefined, children: link.icon }, link.id))) }, g.id)))), _jsx(YStack, { flex: 1 }), _jsx(Me, { host: host, narrow: true, onOrg: org })] }) }));
|
|
78
81
|
return (_jsxs(YStack, { "data-slot": "app-column", ref: drawer ? undefined : span.ref,
|
|
79
82
|
// inline-style: `--span`, the width a drag rewrites on the element itself (span.ts); no prop sets a variable.
|
|
80
83
|
style: drawer ? undefined : span.style, position: "relative", shrink: 0, height: "100%", width: drawer ? '100%' : 'var(--span)', ...(drawer ? {} : { '$max-md': { display: 'none' } }), children: [_jsxs(Sidebar, { "data-slot": "app-rail", "data-collapsed": "false", role: "navigation", "aria-label": brand(), ...pane(), ...FILL, px: "$2.5", pt: "$3", pb: "$2", gap: "$0", children: [_jsxs(XStack, { "data-slot": "mode-switch", items: "center", gap: "$2", px: "$1.5", pb: "$3", minW: 0, children: [_jsx(HanzoMark, { size: 18 }), _jsx(ModeMark, { mode: mode, onChat: onChat, onDev: onDev })] }), _jsxs(XStack, { "data-slot": "app-filter", items: "center", gap: "$2", px: "$3", height: 36, rounded: 999, borderWidth: 1, borderColor: "$borderColor", bg: "$edge", shrink: 0, children: [_jsx(Search, { size: 15, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }), _jsx(Text, { render: _jsx("input", { value: words, onChange: (e) => setWords(e.target.value), onKeyDown: (e) => {
|
|
81
84
|
if (e.key === 'Escape')
|
|
82
85
|
setWords('');
|
|
83
|
-
}, placeholder:
|
|
84
|
-
? links.map((link) => (_jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { ...ROW, active: link.active, icon: link.icon, onPress: link.onPress, ...keys(() => link.onPress?.()), children: link.label }) }, link.id)))
|
|
85
|
-
: null, mode === 'dev' ? (_jsxs(YStack, { role: "listitem", children: [_jsx(SidebarItem, { ...ROW, "aria-expanded": unfolded, icon: unfolded ? _jsx(ChevronUp, { size: 16, "aria-hidden": true }) : _jsx(ChevronDown, { size: 16, "aria-hidden": true }), onPress: () => setUnfolded((v) => !v), ...keys(() => setUnfolded((v) => !v)), children: "More" }), unfolded ? (_jsx(YStack, { role: "list", gap: 2, children: more.map((link) => (_jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { ...ROW, active: link.active, icon: link.icon, onPress: link.onPress, ...keys(() => link.onPress?.()), children: link.label }) }, link.id))) })) : null] })) : null] }), _jsx(XStack, { height: 1, bg: "$borderColor", mx: "$1.5", mt: "$3", shrink: 0 }), _jsx(Recents, { host: host, mode: mode, thread: thread, words: words, onOpen: open, onNew: create }), _jsxs(YStack, { gap: "$2", pt: "$2", shrink: 0, children: [_jsx(Made, {}), _jsx(Setup, {})] }), _jsxs(XStack, { "data-slot": "app-foot", items: "center", gap: "$1", pt: "$2", shrink: 0, children: [_jsx(YStack, { flex: 1, minW: 0, children: _jsx(Me, { host: host, onOrg: org }) }), drawer ? null : (_jsx(SidebarIconButton, { label: "Collapse sidebar", onPress: () => setCollapsed(true), width: 32, height: 32, rounded: 999, children: _jsx(PanelLeft, { size: 16 }) }))] })] }), drawer ? null : (_jsx(Grip, { side: "left", span: span.span, floor: FLOOR, ceil: CEIL, reset: WIDTH, onSpan: span.move, onKeep: span.keep, onShut: () => setCollapsed(true), label: "Resize sidebar",
|
|
86
|
+
}, placeholder: mode === 'chat' ? 'Filter chats' : 'Filter runs', "aria-label": "Filter recents" }), flex: 1, minW: 0, bg: "transparent", borderWidth: 0, outlineStyle: "none", color: "inherit", fontSize: "$2" })] }), mode === 'chat' ? (_jsx(YStack, { role: "list", mt: "$2.5", gap: 2, shrink: 0, children: _jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { "data-slot": "app-new", ...ROW, active: !thread, icon: _jsx(Plus, { size: 16, "aria-hidden": true }), onPress: create, ...keys(create), children: "New chat" }) }) })) : (_jsx(YStack, { "data-slot": "app-places", mt: "$1.5", shrink: 0, children: groups.map((g) => (_jsxs(YStack, { pt: "$1.5", children: [_jsx(Text, { id: `${uid}-${g.id}`, pl: "$2", pb: 2, fontSize: "$1", color: "$soft", fontWeight: "600", textTransform: "uppercase", letterSpacing: 0.6, children: g.label }), _jsx(YStack, { role: "list", "aria-labelledby": `${uid}-${g.id}`, gap: 1, children: g.links.map((link) => (_jsx(YStack, { role: "listitem", children: _jsx(SidebarItem, { ...ROW, ...(link.id === NEW ? { 'data-slot': 'app-new' } : {}), active: link.active, icon: link.icon, onPress: link.onPress, ...keys(() => link.onPress?.()), children: link.label }) }, link.id))) })] }, g.id))) })), _jsx(XStack, { height: 1, bg: "$borderColor", mx: "$1.5", mt: "$3", shrink: 0 }), _jsx(Recents, { host: host, mode: mode, thread: thread, words: words, onOpen: open, onNew: create }), _jsxs(YStack, { gap: "$2", pt: "$2", shrink: 0, children: [_jsx(Made, {}), _jsx(Setup, {})] }), _jsxs(XStack, { "data-slot": "app-foot", items: "center", gap: "$1", pt: "$2", shrink: 0, children: [_jsx(YStack, { flex: 1, minW: 0, children: _jsx(Me, { host: host, onOrg: org }) }), drawer ? null : (_jsx(SidebarIconButton, { label: "Collapse sidebar", onPress: () => setCollapsed(true), width: 32, height: 32, rounded: 999, children: _jsx(PanelLeft, { size: 16 }) }))] })] }), drawer ? null : (_jsx(Grip, { side: "left", span: span.span, floor: FLOOR, ceil: CEIL, reset: WIDTH, onSpan: span.move, onKeep: span.keep, onShut: () => setCollapsed(true), label: "Resize sidebar",
|
|
86
87
|
// In the gutter between the column and the pane, not over the column's own rows.
|
|
87
88
|
r: GUTTER_END, width: "var(--pane-gap)" }))] }));
|
|
88
89
|
}
|
package/dist/Directory.d.ts
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* which named the wire rather than what a reader installs. CHANNELS are
|
|
6
|
-
* transports: where a conversation reaches you. PLUGINS extend the client.
|
|
7
|
-
* SKILLS extend what an agent knows how to do.
|
|
8
|
-
*
|
|
9
|
-
* Order is the ruling's: Apps, Channels, Plugins, Skills.
|
|
2
|
+
* What this directory lists. APPS are what Hanzo works with — the public
|
|
3
|
+
* catalog, each row a page. CHANNELS are transports: where a conversation
|
|
4
|
+
* reaches you. Skills and plugins are Customize's (Settings links there).
|
|
10
5
|
*/
|
|
11
|
-
export type Tab = 'apps' | 'channels'
|
|
6
|
+
export type Tab = 'apps' | 'channels';
|
|
12
7
|
export declare function Directory({ open, onClose, tab: first, }: {
|
|
13
8
|
open: boolean;
|
|
14
9
|
onClose: () => void;
|
package/dist/Directory.js
CHANGED
|
@@ -20,11 +20,13 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
20
20
|
// row shown as a small badge, never a section boundary and never a second
|
|
21
21
|
// install flow — one `+` everywhere, branching internally.
|
|
22
22
|
//
|
|
23
|
-
//
|
|
23
|
+
// TWO TABS, and the channels are tiles. A channel is a provider the org can
|
|
24
24
|
// hold an account with, read from `/v1/integrations` — which says whether this
|
|
25
25
|
// deployment can open a connection and whether this org has one — and every
|
|
26
26
|
// tile's button is a call to that registry. The Inbox's "Connect an app" opens
|
|
27
|
-
// this same directory on this tab; there is no second grid.
|
|
27
|
+
// this same directory on this tab; there is no second grid. Skills, plugins and
|
|
28
|
+
// connectors are what an agent brings to a run, and they have one home, Dev's
|
|
29
|
+
// Customize (@hanzo/build); Settings links there rather than drawing a copy.
|
|
28
30
|
//
|
|
29
31
|
// COUNTS ARE DERIVED. The catalog is counted, never typed. None of these
|
|
30
32
|
// registries serves a download figure, so no card draws one: the rows carried
|
|
@@ -33,12 +35,12 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
33
35
|
// deleted a "600+ integrations" claim for the same reason.
|
|
34
36
|
//
|
|
35
37
|
// SIGNED IN, EACH TAB READS ITS OWN REGISTRY. Apps is this site's public catalog
|
|
36
|
-
// AND the org's connected accounts; Channels
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
38
|
+
// AND the org's connected accounts; Channels is the org's own list at
|
|
39
|
+
// `/v1/integrations`. That one needs the bearer, so signed-out asks the reader to
|
|
40
|
+
// sign in rather than drawing a catalogue nobody can connect from, and a
|
|
41
|
+
// registry that cannot be reached says so rather than claiming the list is
|
|
42
|
+
// empty. Nothing stands in for an answer: no row is drawn that the registry did
|
|
43
|
+
// not send, and none is marked Connected that it did not mark.
|
|
42
44
|
import { useEffect, useMemo, useState } from 'react';
|
|
43
45
|
import { ArrowUpRight, Check, Plus, Search, X } from 'lucide-react';
|
|
44
46
|
import { Box, Dialog, DialogContent, DialogTitle, Input, Text, XStack, YStack, } from '@hanzo/ui';
|
|
@@ -53,31 +55,12 @@ import { Action } from '@hanzo/ui/marketing';
|
|
|
53
55
|
import { tap } from './lib/tap.js';
|
|
54
56
|
import { GOOD } from './lib/mix.js';
|
|
55
57
|
import { site } from './where.js';
|
|
56
|
-
/**
|
|
57
|
-
* The console, which is a PRODUCT ADDRESS rather than an environment.
|
|
58
|
-
*
|
|
59
|
-
* The gateway is `api()` because it moves — a local cloud, the dev server's own
|
|
60
|
-
* proxy — and nine modules got that wrong by freezing it into a const. This one
|
|
61
|
-
* does not move: `platform.hanzo.ai` is written as a literal in
|
|
62
|
-
* `lib/constants/products-metadata.ts`, `lib/data/oss-catalog.ts` and
|
|
63
|
-
* `pricing.json`, no env names it, and `e2e/directory.spec.ts` asserts this
|
|
64
|
-
* exact origin on the links below.
|
|
65
|
-
*/
|
|
66
|
-
const CONSOLE = 'https://platform.hanzo.ai';
|
|
67
58
|
const TABS = [
|
|
68
59
|
{ id: 'apps', label: 'Works with' },
|
|
69
60
|
{ id: 'channels', label: 'Channels' },
|
|
70
|
-
{ id: 'plugins', label: 'Plugins' },
|
|
71
|
-
{ id: 'skills', label: 'Skills' },
|
|
72
61
|
];
|
|
73
|
-
/** The org
|
|
74
|
-
const
|
|
75
|
-
// The org-plane catalogue: every provider the platform can connect an account
|
|
76
|
-
// to, with THIS org's connection and whether the deployment can open one.
|
|
77
|
-
channels: '/v1/integrations',
|
|
78
|
-
plugins: '/v1/tools/plugins',
|
|
79
|
-
skills: '/v1/tools/skills',
|
|
80
|
-
};
|
|
62
|
+
/** The org-plane catalogue: every provider the platform can connect an account to, with THIS org's connection and whether the deployment can open one. */
|
|
63
|
+
const CHANNELS = '/v1/integrations';
|
|
81
64
|
/**
|
|
82
65
|
* The channels a reader reaches for first, in the order they were asked for.
|
|
83
66
|
* The rest of the registry follows in its own order. A rank, not a filter: a
|
|
@@ -100,99 +83,33 @@ const apps = () => (listing ??= import('./lib/integrations.js')
|
|
|
100
83
|
kind: 'native',
|
|
101
84
|
})))
|
|
102
85
|
.catch(() => []));
|
|
103
|
-
|
|
104
|
-
{ id: 'slack', name: 'Slack', publisher: 'Hanzo Auto', description: 'Bidirectional sync with FoundationDAO & workspace Slack channels and threads.', kind: 'native', available: true, connected: true },
|
|
105
|
-
{ id: 'discord', name: 'Discord', publisher: 'Hanzo Auto', description: 'Bot and webhook integration for community server announcements and chat.', kind: 'native', available: true, connected: true },
|
|
106
|
-
{ id: 'telegram', name: 'Telegram', publisher: 'Hanzo Auto', description: 'Direct messaging bot with instant agent responses and notifications.', kind: 'native', available: true, connected: true },
|
|
107
|
-
{ id: 'teams', name: 'Microsoft Teams', publisher: 'Hanzo Auto', description: 'Enterprise collaboration connector for channels, meetings, and team chats.', kind: 'native', available: true, connected: false },
|
|
108
|
-
{ id: 'whatsapp', name: 'WhatsApp Business', publisher: 'Hanzo Auto', description: 'Conversational agent support over official WhatsApp Business API.', kind: 'native', available: true, connected: false },
|
|
109
|
-
{ id: 'email', name: 'Email (SMTP/IMAP)', publisher: 'Hanzo Auto', description: 'Inbound conversation routing and outbound transactional dispatch.', kind: 'native', available: true, connected: false },
|
|
110
|
-
{ id: 'webhooks', name: 'Webhooks', publisher: 'Hanzo Core', description: 'Custom HTTP payload delivery for real-time external event ingest.', kind: 'native', available: true, connected: true },
|
|
111
|
-
{ id: 'sms', name: 'SMS (Twilio)', publisher: 'Hanzo Auto', description: 'Two-way text message delivery and conversational verification.', kind: 'native', available: true, connected: false },
|
|
112
|
-
{ id: 'x', name: 'X / Twitter', publisher: 'Hanzo Auto', description: 'Direct messages, mentions, and timeline thread monitoring.', kind: 'native', available: true, connected: false },
|
|
113
|
-
{ id: 'linkedin', name: 'LinkedIn', publisher: 'Hanzo Auto', description: 'Company page updates, comments, and direct message handling.', kind: 'native', available: true, connected: false },
|
|
114
|
-
];
|
|
115
|
-
const PLUGINS_CATALOG = [
|
|
116
|
-
{ id: 'mcp-filesystem', name: 'Filesystem MCP', publisher: 'Model Context Protocol', description: 'Secure local and virtual file access, directory reading, and atomic edits.', kind: 'MCP', connected: true },
|
|
117
|
-
{ id: 'mcp-git', name: 'Git MCP', publisher: 'Model Context Protocol', description: 'Repository inspection, branch management, commit diffing, and git blame.', kind: 'MCP', connected: true },
|
|
118
|
-
{ id: 'mcp-code', name: 'Code & LSP MCP', publisher: 'Hanzo AI', description: 'Language server protocol diagnostics, symbol jump, and semantic search.', kind: 'MCP', connected: true },
|
|
119
|
-
{ id: 'mcp-memory', name: 'Memory & Knowledge Graph MCP', publisher: 'Hanzo AI', description: 'Persistent entity memory, semantic vector store, and recall graph.', kind: 'MCP', connected: true },
|
|
120
|
-
{ id: 'mcp-zen', name: 'Zen Architecture & Review MCP', publisher: 'Hanzo AI', description: 'Frontier reasoning engine, architecture planning, and rigorous code reviews.', kind: 'MCP', connected: true },
|
|
121
|
-
{ id: 'mcp-browser', name: 'Browser & CDP MCP', publisher: 'Hanzo AI', description: 'Headless Chrome DevTools Protocol automation and interactive web tasks.', kind: 'MCP', connected: true },
|
|
122
|
-
{ id: 'mcp-terminal', name: 'Terminal & PTY MCP', publisher: 'Hanzo AI', description: 'Safe shell execution, sandbox command runner, and real-time streaming.', kind: 'MCP', connected: true },
|
|
123
|
-
{ id: 'mcp-playwright', name: 'Playwright MCP', publisher: 'Microsoft', description: 'End-to-end browser automation, screenshots, and visual regression testing.', kind: 'MCP', connected: true },
|
|
124
|
-
{ id: 'mcp-fetch', name: 'Fetch & Curl MCP', publisher: 'Hanzo AI', description: 'Fast HTTP/HTTPS content extraction and markdown web page synthesis.', kind: 'MCP', connected: true },
|
|
125
|
-
{ id: 'mcp-datastore', name: 'Datastore MCP', publisher: 'Hanzo Datastore', description: 'ClickHouse high-performance analytical queries and telemetry exploration.', kind: 'MCP', connected: true },
|
|
126
|
-
];
|
|
127
|
-
const SKILLS_CATALOG = [
|
|
128
|
-
{ id: 'skill-code-search', name: 'Codebase Search & Navigation', publisher: 'Hanzo Core', description: 'Deep semantic search, rip-grep, and symbol indexing across all project repositories.', kind: 'native', connected: true },
|
|
129
|
-
{ id: 'skill-data-analysis', name: 'Data Science & Statistical Analysis', publisher: 'Hanzo Core', description: 'Automated data exploration, tabular summaries, charting, and statistical tests.', kind: 'native', connected: true },
|
|
130
|
-
{ id: 'skill-git-ops', name: 'Git Workflow & Release Management', publisher: 'Hanzo Core', description: 'Branch creation, conventional commits, PR generation, and release drafting.', kind: 'native', connected: true },
|
|
131
|
-
{ id: 'skill-swarm', name: 'Autonomous Agent Swarm Orchestration', publisher: 'Hanzo Core', description: 'Divide complex tasks across specialized subagents with asynchronous coordination.', kind: 'native', connected: true },
|
|
132
|
-
{ id: 'skill-web-research', name: 'Web Research & Academic Synthesis', publisher: 'Hanzo Core', description: 'Multi-source fact-checking, literature review, and technical paper extraction.', kind: 'native', connected: true },
|
|
133
|
-
{ id: 'skill-sandbox', name: 'Python Sandbox Execution', publisher: 'Hanzo Core', description: 'Isolated execution of Python scripts with visualization and data artifact export.', kind: 'native', connected: true },
|
|
134
|
-
{ id: 'skill-media', name: 'Image & Media Generation', publisher: 'Hanzo Core', description: 'Diffusion generation, UI mockup creation, and media asset transformations.', kind: 'native', connected: true },
|
|
135
|
-
{ id: 'skill-web3', name: 'Smart Contract & EVM Interaction', publisher: 'Hanzo', description: 'Solidity compilation, ABI inspection, EVM deployment, and chain reads.', kind: 'native', connected: true },
|
|
136
|
-
];
|
|
137
|
-
function catalogFor(tab) {
|
|
138
|
-
switch (tab) {
|
|
139
|
-
case 'apps':
|
|
140
|
-
return [];
|
|
141
|
-
case 'channels':
|
|
142
|
-
return CHANNELS_CATALOG;
|
|
143
|
-
case 'plugins':
|
|
144
|
-
return PLUGINS_CATALOG;
|
|
145
|
-
case 'skills':
|
|
146
|
-
return SKILLS_CATALOG;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
/** One title-cased word from a channel/plugin id. */
|
|
86
|
+
/** One title-cased word from a channel id. */
|
|
150
87
|
const title = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
151
|
-
/**
|
|
152
|
-
function
|
|
88
|
+
/** The channel registry's own shape, as the one Row the card renders. */
|
|
89
|
+
function channelsOf(j) {
|
|
153
90
|
const o = (j ?? {});
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
description: String(p.description ?? ''),
|
|
170
|
-
kind: 'native',
|
|
171
|
-
connected: Boolean(p.connected),
|
|
172
|
-
available: Boolean(p.available),
|
|
173
|
-
account,
|
|
174
|
-
};
|
|
175
|
-
})
|
|
176
|
-
.sort((a, b) => rank(a.id) - rank(b.id));
|
|
177
|
-
}
|
|
178
|
-
if (tab === 'plugins') {
|
|
179
|
-
return (o.plugins ?? []).map((p) => ({
|
|
180
|
-
id: String(p.name),
|
|
181
|
-
name: title(String(p.name)),
|
|
182
|
-
publisher: 'Hanzo',
|
|
183
|
-
description: `Serves ${(p.prefixes ?? []).join(', ') || 'this workspace'}.`,
|
|
91
|
+
const rank = (id) => {
|
|
92
|
+
const i = LEAD.indexOf(id);
|
|
93
|
+
return i < 0 ? LEAD.length : i;
|
|
94
|
+
};
|
|
95
|
+
return (o.providers ?? [])
|
|
96
|
+
.map((p) => {
|
|
97
|
+
const link = (p.connection ?? {});
|
|
98
|
+
const account = String(link.account ?? link.label ?? '');
|
|
99
|
+
return {
|
|
100
|
+
id: String(p.id),
|
|
101
|
+
name: String(p.name ?? title(String(p.id))),
|
|
102
|
+
// What the registry knows, in the reader's order of interest: who is
|
|
103
|
+
// connected, else whether a connection can be opened here at all.
|
|
104
|
+
publisher: p.connected ? account || 'Connected' : p.available ? String(p.category ?? '') : 'Needs app credentials',
|
|
105
|
+
description: String(p.description ?? ''),
|
|
184
106
|
kind: 'native',
|
|
185
|
-
connected: p.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
name: String(t.name ?? t.id),
|
|
192
|
-
publisher: String(t.publisher ?? t.author ?? 'You'),
|
|
193
|
-
description: String(t.description ?? t.summary ?? 'A skill your agents can reach for.'),
|
|
194
|
-
kind: (t.kind === 'MCP' ? 'MCP' : 'native'),
|
|
195
|
-
}));
|
|
107
|
+
connected: Boolean(p.connected),
|
|
108
|
+
available: Boolean(p.available),
|
|
109
|
+
account,
|
|
110
|
+
};
|
|
111
|
+
})
|
|
112
|
+
.sort((a, b) => rank(a.id) - rank(b.id));
|
|
196
113
|
}
|
|
197
114
|
/**
|
|
198
115
|
* A rejection `say()` can read.
|
|
@@ -218,18 +135,21 @@ export function Directory({ open, onClose, tab: first = 'apps', }) {
|
|
|
218
135
|
* why it is exported and takes the tab as a prop.
|
|
219
136
|
*/
|
|
220
137
|
export function Catalog({ tab }) {
|
|
221
|
-
const { isAuthenticated,
|
|
138
|
+
const { isAuthenticated, accessToken } = useIam();
|
|
222
139
|
const [query, setQuery] = useState('');
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
140
|
+
// null until the list is read: unknown is never drawn as empty.
|
|
141
|
+
const [rows, setRows] = useState(null);
|
|
142
|
+
const [wrong, setWrong] = useState(null);
|
|
143
|
+
// Bumped when a card changed a connection, or a failed read is asked again,
|
|
144
|
+
// so the registry is read again and the card shows what it now holds.
|
|
226
145
|
const [turn, setTurn] = useState(0);
|
|
227
146
|
const label = TABS.find((t) => t.id === tab)?.label ?? tab;
|
|
147
|
+
const signed = isAuthenticated && Boolean(accessToken);
|
|
228
148
|
useEffect(() => {
|
|
229
149
|
let live = true;
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const auth =
|
|
150
|
+
setRows(null);
|
|
151
|
+
setWrong(null);
|
|
152
|
+
const auth = signed ? scope() : null;
|
|
233
153
|
// Apps is this site's own public catalog — real for everyone. Signed in, we
|
|
234
154
|
// also mark which providers the org has actually connected, from the one
|
|
235
155
|
// read that names them.
|
|
@@ -257,28 +177,18 @@ export function Catalog({ tab }) {
|
|
|
257
177
|
}
|
|
258
178
|
if (!auth)
|
|
259
179
|
return;
|
|
260
|
-
fetch(`${api()}${
|
|
261
|
-
.then((r) =>
|
|
262
|
-
.
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const liveRows = rowsFor(tab, j);
|
|
266
|
-
if (liveRows && liveRows.length > 0) {
|
|
267
|
-
const map = new Map();
|
|
268
|
-
for (const r of fallback)
|
|
269
|
-
map.set(r.id, r);
|
|
270
|
-
for (const r of liveRows)
|
|
271
|
-
map.set(r.id, { ...(map.get(r.id) || {}), ...r });
|
|
272
|
-
setRows(Array.from(map.values()));
|
|
273
|
-
}
|
|
180
|
+
fetch(`${api()}${CHANNELS}`, { headers: auth })
|
|
181
|
+
.then((r) => {
|
|
182
|
+
if (!r.ok)
|
|
183
|
+
throw refusal(r);
|
|
184
|
+
return r.json();
|
|
274
185
|
})
|
|
275
|
-
.
|
|
276
|
-
|
|
277
|
-
});
|
|
186
|
+
.then((j) => live && setRows(channelsOf(j)))
|
|
187
|
+
.catch((e) => live && setWrong(e));
|
|
278
188
|
return () => {
|
|
279
189
|
live = false;
|
|
280
190
|
};
|
|
281
|
-
}, [tab,
|
|
191
|
+
}, [tab, signed, turn]); // eslint-disable-line react-hooks/exhaustive-deps -- scope() reads the same token
|
|
282
192
|
// A connection finishes in the provider's own tab. Coming back to this one is
|
|
283
193
|
// the moment to ask the registry again.
|
|
284
194
|
useEffect(() => {
|
|
@@ -293,14 +203,10 @@ export function Catalog({ tab }) {
|
|
|
293
203
|
}, [tab]);
|
|
294
204
|
const shown = useMemo(() => {
|
|
295
205
|
const q = query.trim().toLowerCase();
|
|
296
|
-
|
|
297
|
-
return list.filter((r) => {
|
|
298
|
-
if (!q)
|
|
299
|
-
return true;
|
|
300
|
-
return r.name.toLowerCase().includes(q) || r.description.toLowerCase().includes(q);
|
|
301
|
-
});
|
|
206
|
+
return (rows ?? []).filter((r) => !q || r.name.toLowerCase().includes(q) || r.description.toLowerCase().includes(q));
|
|
302
207
|
}, [rows, query]);
|
|
303
|
-
|
|
208
|
+
const quiet = (text, action) => (_jsxs(YStack, { gap: "$3", py: "$4", items: "flex-start", children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: text }), action] }));
|
|
209
|
+
return (_jsxs(YStack, { gap: "$4", children: [_jsx(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: _jsxs(XStack, { items: "center", gap: "$2", flex: 1, minW: 180, children: [_jsx(Search, { size: 14, "aria-hidden": true }), _jsx(Input, { value: query, onChangeText: setQuery, placeholder: `Search ${label.toLowerCase()}`, "aria-label": `Search ${label.toLowerCase()}`, flex: 1 })] }) }), tab === 'channels' && !signed ? (quiet('Sign in to see the channels your organization can connect.', _jsx(Action, { render: "button", onPress: () => enter(), $touchable: { minH: 44, minW: 44 }, children: "Sign in" }))) : wrong ? (quiet(say(wrong, 'the channels'), _jsx(Action, { render: "button", onPress: () => setTurn((n) => n + 1), $touchable: { minH: 44, minW: 44 }, children: "Try again" }))) : rows === null ? (quiet(`Loading ${label.toLowerCase()}…`)) : shown.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs(Text, { fontSize: "$3", color: "$soft", children: [shown.length, " ", shown.length === 1 ? 'result' : 'results'] }), _jsx(YStack, { maxH: 380, overflowY: "auto", overflowX: "hidden", pr: "$1", width: "100%", children: _jsx(Grid, { columns: tab === 'channels' ? { min: 168, max: 4 } : { min: 240, max: 2 }, gap: 12, children: shown.map((r) => tab === 'channels' ? (_jsx(Channel, { row: r, onChange: () => setTurn((n) => n + 1) }, r.id)) : (_jsx(RowCard, { row: r, tab: tab }, r.id))) }) })] })) : (quiet(query ? `Nothing matches “${query}”.` : tab === 'channels' ? 'No channel can be connected here yet.' : 'Nothing is listed yet.'))] }));
|
|
304
210
|
}
|
|
305
211
|
function RowCard({ row, tab }) {
|
|
306
212
|
const { isAuthenticated } = useIam();
|
|
@@ -315,10 +221,9 @@ function RowCard({ row, tab }) {
|
|
|
315
221
|
const back = encodeURIComponent(`${window.location.origin}/inbox`);
|
|
316
222
|
window.open(`${api()}/v1/channels/${encodeURIComponent(row.id)}/oauth/authorize?redirect_uri=${back}`, '_blank', 'noopener,noreferrer');
|
|
317
223
|
};
|
|
318
|
-
// Apps open the page this site serves
|
|
319
|
-
//
|
|
320
|
-
|
|
321
|
-
const href = tab === 'apps' ? site(`/integrations/${row.id}`) : `${CONSOLE}/${tab}`;
|
|
224
|
+
// Apps open the page this site serves. Only a channel has a flow of its own,
|
|
225
|
+
// and only that one is a button.
|
|
226
|
+
const href = site(`/integrations/${row.id}`);
|
|
322
227
|
return (_jsxs(YStack, { gap: "$2", p: "$3", borderWidth: 1, borderColor: "$borderColor", rounded: "$3", height: "100%", bg: "$raised", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(ProviderMark, { org: row.id.replace(/-sdk$/, ''), size: 20 }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(Text, { fontSize: "$3", fontWeight: "500", color: "$ink", numberOfLines: 1, children: row.name }), _jsx(Text, { fontSize: "$2", color: "$soft", numberOfLines: 1, children: row.publisher })] }), row.connected ? (_jsxs(XStack, { items: "center", gap: "$1", px: "$2", py: "$1", rounded: "$2", bg: "$hover", children: [_jsx(Check, { size: 12, color: GOOD, "aria-hidden": true }), _jsx(Text, { fontSize: "$1", color: GOOD, fontWeight: "600", children: "Connected" })] })) : tab === 'channels' ? (_jsx(Box, { render: "button", onClick: connect, "aria-label": `Connect ${row.name}`, px: "$2", py: "$1", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", bg: "var(--white-05)", hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$1", color: "$color", fontWeight: "600", children: "Connect" }) })) : (_jsx(Box, { render: _jsx("a", { href: href, target: tab === 'apps' ? undefined : '_blank', rel: "noreferrer", "aria-label": `Open ${row.name}` }), ...tap, children: _jsxs(XStack, { items: "center", gap: "$1", px: "$2", py: "$1", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", bg: "var(--white-05)", hoverStyle: { bg: '$hover' }, children: [_jsx(Text, { fontSize: "$1", color: "$color", fontWeight: "600", children: tab === 'apps' ? 'Docs' : 'Install' }), _jsx(ArrowUpRight, { size: 11, "aria-hidden": true })] }) }))] }), _jsx(Text, { fontSize: "$2", color: "$soft", numberOfLines: 2, flex: 1, children: row.description }), _jsx(XStack, { items: "center", pt: "$1", children: _jsx(Text, { fontFamily: "$mono", fontVariant: ['tabular-nums'], fontSize: "$1", color: "$soft", children: row.kind }) })] }));
|
|
323
228
|
}
|
|
324
229
|
/**
|
package/dist/Me.js
CHANGED
|
@@ -6,31 +6,34 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
6
6
|
// name, and their role in the organization they work in. The menu lists every
|
|
7
7
|
// organization the token names — the personal one first, with its plan — and
|
|
8
8
|
// switches with `pick()` (lib/auth/session.ts), the one switch the app has.
|
|
9
|
+
//
|
|
10
|
+
// THE PLAN LEADS: @hanzo/build's `Meter`, the block every account menu opens
|
|
11
|
+
// with — the plan by its family (`Max` with `20x` beside it) and, on a plan, its
|
|
12
|
+
// windows as shares; the free allowance left today on Free; the balance only
|
|
13
|
+
// for an account with no plan, whose meter it is. Credits are Billing's, so a
|
|
14
|
+
// plan never sits beside a balance here.
|
|
15
|
+
//
|
|
9
16
|
// Under the list: Create organization, Organization settings, and Hanzo Team —
|
|
10
17
|
// opened in the same organization for a team, offered as "Create a team" for a
|
|
11
|
-
// personal one. Then
|
|
12
|
-
//
|
|
18
|
+
// personal one. Then Settings, Usage, Billing, API keys, the plans, help and Log
|
|
19
|
+
// out. A SuperAdmin also gets All organizations (support mode).
|
|
13
20
|
//
|
|
14
21
|
// IAM is the source of every fact here: names and roles from the token's `orgs`
|
|
15
22
|
// claim, display names and which org is personal from `GET /v1/iam/organizations`,
|
|
16
|
-
// the plan and
|
|
23
|
+
// the plan from billing and the limits. An organization is created by the same
|
|
17
24
|
// `POST /v1/account/orgs` the no-organization gate uses (components/workspace/
|
|
18
25
|
// Orgs.tsx); the next load mints a token that carries it.
|
|
19
|
-
import { useEffect, useRef, useState } from 'react';
|
|
26
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
20
27
|
import { Popover, SizableText, View, XStack, YStack } from '@hanzo/gui';
|
|
21
|
-
import { Building2, ChevronsUpDown, ExternalLink, Gauge, KeyRound, LifeBuoy, LogOut, Plus, Settings, Sparkles, Users, X } from 'lucide-react';
|
|
22
|
-
import { path, useWho } from '@hanzo/build';
|
|
28
|
+
import { Building2, ChevronsUpDown, CreditCard, ExternalLink, Gauge, KeyRound, LifeBuoy, LogOut, Plus, Settings, Sparkles, Users, X } from 'lucide-react';
|
|
29
|
+
import { label, Meter, path, said as spoken, useStanding, useWho } from '@hanzo/build';
|
|
23
30
|
import { useOrganizations } from '@hanzo/iam/react';
|
|
24
31
|
import { Button, Dialog, DialogContent, DialogTitle, Input } from '@hanzo/ui';
|
|
25
32
|
import { glass } from '@hanzo/ui/glass';
|
|
26
|
-
import { formatCents } from '@hanzo/usage';
|
|
27
33
|
import { api } from './lib/api.js';
|
|
28
34
|
import { createOrg, say } from './lib/org.js';
|
|
29
|
-
import { useTier } from './lib/tier.js';
|
|
30
35
|
import { list } from './lib/list.js';
|
|
31
36
|
import { org as current, orgs, pick, renew, scope, superAdmin } from './lib/session.js';
|
|
32
|
-
import { payPage } from './lib/pay.js';
|
|
33
|
-
import { planName } from './lib/plans.js';
|
|
34
37
|
import { ENTRY } from './lib/host.js';
|
|
35
38
|
import { where } from './where.js';
|
|
36
39
|
import { SupportPicker } from './Support.js';
|
|
@@ -52,8 +55,8 @@ export const initials = (name) => name
|
|
|
52
55
|
.slice(0, 2)
|
|
53
56
|
.map((w) => w.charAt(0).toUpperCase())
|
|
54
57
|
.join('') || '?';
|
|
55
|
-
/** The plan
|
|
56
|
-
const planOf = (tier) => (tier ?
|
|
58
|
+
/** The plan by its family, `Max 20x`, from a tier answer; '' with no plan. */
|
|
59
|
+
const planOf = (tier) => (tier ? spoken(label(tier.plan, tier.tier?.name)) : '');
|
|
57
60
|
/**
|
|
58
61
|
* The token's organizations with IAM's rows beside them: display name, and which
|
|
59
62
|
* one is the person's own. The personal organization leads, then the token's order.
|
|
@@ -139,9 +142,10 @@ export function Me({ host, narrow = false, onOrg, }) {
|
|
|
139
142
|
const all = useOrgs(current);
|
|
140
143
|
const here = all.find((o) => o.name === current) ?? null;
|
|
141
144
|
const personal = all.find((o) => o.personal) ?? null;
|
|
142
|
-
const {
|
|
145
|
+
const target = useMemo(() => ({ api: host.api, token: host.token, org: host.org }), [host.api, host.token, host.org]);
|
|
146
|
+
const read = useStanding(target, menu.open);
|
|
143
147
|
const other = usePlan(personal && personal.name !== current ? personal.name : null, menu.open);
|
|
144
|
-
const planFor = (o) => (o.name === current ?
|
|
148
|
+
const planFor = (o) => (o.name === current ? spoken(read.value?.label ?? null) : o.personal ? other : '');
|
|
145
149
|
const who = host.person?.name || host.person?.email || '';
|
|
146
150
|
const standing = [said(here?.role ?? ''), here?.display ?? current].filter(Boolean).join(' · ');
|
|
147
151
|
// The organization in use takes focus once the menu is drawn — after the
|
|
@@ -164,11 +168,11 @@ export function Me({ host, narrow = false, onOrg, }) {
|
|
|
164
168
|
return null;
|
|
165
169
|
return (_jsxs(_Fragment, { children: [_jsxs(Popover, { open: menu.open, onOpenChange: menu.onOpenChange, placement: narrow ? 'right-end' : 'top-start', allowFlip: true, stayInFrame: true, offset: 8, children: [_jsx(Popover.Anchor, { width: "100%", children: _jsxs(XStack, { ref: (el) => {
|
|
166
170
|
row.current = el instanceof HTMLElement ? el : null;
|
|
167
|
-
}, render: "button", "data-slot": "me", "aria-label": `Account: ${[who, current].filter(Boolean).join(' · ')}`, "aria-haspopup": "menu", "aria-expanded": menu.open, onPress: menu.toggle, width: narrow ? 40 : '100%', minH: narrow ? 40 : 48, px: narrow ? 0 : '$2', gap: "$2.5", rounded: narrow ? 999 : '$5', items: "center", justify: narrow ? 'center' : 'flex-start', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, cursor: "pointer", children: [_jsx(Face, { host: host, size: narrow ? 30 : 32 }), narrow ? null : (_jsxs(_Fragment, { children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$3", fontWeight: "600", color: "$ink", numberOfLines: 1, text: "left", children: who }), standing ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, text: "left", children: standing })) : null] }), _jsx(ChevronsUpDown, { size: 14, "aria-hidden": true, opacity: 0.6 })] }))] }) }), _jsx(Popover.Content, { ...glass(2), items: "stretch", borderWidth: 1, rounded: "$6", p: "$1.5", width: 288, maxW: "calc(100vw - 16px)", maxH: "calc(100dvh - 32px)", overflowY: "auto", overflowX: "hidden", children: _jsx(View, { render: _jsx("div", { ref: land, role: "menu", "aria-label": "Account", onKeyDown: (e) => rove(e, close) }), self: "stretch", minW: 0, children: _jsxs(YStack, { gap: 2, children: [_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", py: "$1.5", numberOfLines: 1, children: host.person.email || who }), _jsx(Rule, {}), _jsx(Label, { children: "Organizations" }), _jsx(YStack, { role: "group", "aria-label": "Organizations", gap: 2, children: all.map((o) => (_jsx(Item, { icon: _jsx(Mark, { org: o }), label: o.display, sub: [o.personal ? 'Personal' : said(o.role), planFor(o)].filter(Boolean).join(' · '), checked: o.name === current, onPress: () => {
|
|
171
|
+
}, render: "button", "data-slot": "me", "aria-label": `Account: ${[who, current].filter(Boolean).join(' · ')}`, "aria-haspopup": "menu", "aria-expanded": menu.open, onPress: menu.toggle, width: narrow ? 40 : '100%', minH: narrow ? 40 : 48, px: narrow ? 0 : '$2', gap: "$2.5", rounded: narrow ? 999 : '$5', items: "center", justify: narrow ? 'center' : 'flex-start', hoverStyle: { bg: '$hover' }, focusVisibleStyle: { outlineWidth: 2, outlineStyle: 'solid', outlineColor: '$outlineColor' }, cursor: "pointer", children: [_jsx(Face, { host: host, size: narrow ? 30 : 32 }), narrow ? null : (_jsxs(_Fragment, { children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$3", fontWeight: "600", color: "$ink", numberOfLines: 1, text: "left", children: who }), standing ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, text: "left", children: standing })) : null] }), _jsx(ChevronsUpDown, { size: 14, "aria-hidden": true, opacity: 0.6 })] }))] }) }), _jsx(Popover.Content, { ...glass(2), items: "stretch", borderWidth: 1, rounded: "$6", p: "$1.5", width: 288, maxW: "calc(100vw - 16px)", maxH: "calc(100dvh - 32px)", overflowY: "auto", overflowX: "hidden", children: _jsx(View, { render: _jsx("div", { ref: land, role: "menu", "aria-label": "Account", onKeyDown: (e) => rove(e, close) }), self: "stretch", minW: 0, children: _jsxs(YStack, { gap: 2, children: [_jsx(SizableText, { size: "$2", color: "$soft", px: "$2", py: "$1.5", numberOfLines: 1, children: host.person.email || who }), _jsx(Rule, {}), _jsx(Meter, { read: read, onPlans: () => go(path({ kind: 'screen', screen: 'plans' })), onBilling: () => go(path({ kind: 'settings', section: 'billing' })) }), _jsx(Rule, {}), _jsx(Label, { children: "Organizations" }), _jsx(YStack, { role: "group", "aria-label": "Organizations", gap: 2, children: all.map((o) => (_jsx(Item, { icon: _jsx(Mark, { org: o }), label: o.display, sub: [o.personal ? 'Personal' : said(o.role), planFor(o)].filter(Boolean).join(' · '), checked: o.name === current, onPress: () => {
|
|
168
172
|
menu.onOpenChange(false);
|
|
169
173
|
if (o.name !== current)
|
|
170
174
|
onOrg(o.name);
|
|
171
|
-
} }, o.name))) }), _jsx(Item, { icon: _jsx(Plus, { size: 15, "aria-hidden": true }), label: "Create organization", onPress: () => (menu.onOpenChange(false), setCreating(true)) }), _jsx(Item, { icon: _jsx(Settings, { size: 15, "aria-hidden": true }), label: "Organization settings", onPress: () => (menu.onOpenChange(false), window.location.assign(ORG_SETTINGS)) }), where().team ? null : here && !here.personal ? (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Open in Hanzo Team", trail: _jsx(ExternalLink, { size: 13, "aria-hidden": true, opacity: 0.6 }), onPress: () => (menu.onOpenChange(false), window.location.assign(`${ENTRY}&org=${encodeURIComponent(here.name)}`)) })) : (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Create a team", onPress: () => (menu.onOpenChange(false), setCreating(true)) })), superAdmin() ? (_jsx(Item, { icon: _jsx(Building2, { size: 15, "aria-hidden": true }), label: "All organizations", onPress: () => (menu.onOpenChange(false), setSupporting(true)) })) : null, _jsx(Rule, {}), _jsx(Item, { icon: _jsx(
|
|
175
|
+
} }, o.name))) }), _jsx(Item, { icon: _jsx(Plus, { size: 15, "aria-hidden": true }), label: "Create organization", onPress: () => (menu.onOpenChange(false), setCreating(true)) }), _jsx(Item, { icon: _jsx(Settings, { size: 15, "aria-hidden": true }), label: "Organization settings", onPress: () => (menu.onOpenChange(false), window.location.assign(ORG_SETTINGS)) }), where().team ? null : here && !here.personal ? (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Open in Hanzo Team", trail: _jsx(ExternalLink, { size: 13, "aria-hidden": true, opacity: 0.6 }), onPress: () => (menu.onOpenChange(false), window.location.assign(`${ENTRY}&org=${encodeURIComponent(here.name)}`)) })) : (_jsx(Item, { icon: _jsx(Users, { size: 15, "aria-hidden": true }), label: "Create a team", onPress: () => (menu.onOpenChange(false), setCreating(true)) })), superAdmin() ? (_jsx(Item, { icon: _jsx(Building2, { size: 15, "aria-hidden": true }), label: "All organizations", onPress: () => (menu.onOpenChange(false), setSupporting(true)) })) : null, _jsx(Rule, {}), _jsx(Item, { icon: _jsx(Settings, { size: 15, "aria-hidden": true }), label: "Settings", onPress: () => go(path({ kind: 'settings', section: 'general' })) }), _jsx(Item, { icon: _jsx(Gauge, { size: 15, "aria-hidden": true }), label: "Usage", onPress: () => go(path({ kind: 'settings', section: 'usage' })) }), _jsx(Item, { icon: _jsx(CreditCard, { size: 15, "aria-hidden": true }), label: "Billing", onPress: () => go(path({ kind: 'settings', section: 'billing' })) }), _jsx(Item, { icon: _jsx(KeyRound, { size: 15, "aria-hidden": true }), label: "API keys", onPress: () => (menu.onOpenChange(false), window.location.assign(KEYS)) }), _jsx(Item, { icon: _jsx(Sparkles, { size: 15, "aria-hidden": true }), label: "View all plans", onPress: () => go(path({ kind: 'screen', screen: 'plans' })) }), _jsx(Item, { icon: _jsx(LifeBuoy, { size: 15, "aria-hidden": true }), label: "Get help", onPress: () => (menu.onOpenChange(false), window.open(DOCS, '_blank', 'noopener,noreferrer')) }), host.signOut ? (_jsxs(_Fragment, { children: [_jsx(Rule, {}), _jsx(Item, { icon: _jsx(LogOut, { size: 15, "aria-hidden": true }), label: "Log out", onPress: () => (menu.onOpenChange(false), host.signOut?.()) })] })) : null] }) }) })] }), _jsx(Create, { open: creating, onOpenChange: setCreating }), supporting ? _jsx(SupportPicker, { onClose: () => setSupporting(false) }) : null] }));
|
|
172
176
|
}
|
|
173
177
|
/**
|
|
174
178
|
* The sheet that creates an organization: a name, then `POST /v1/account/orgs`,
|
package/dist/Prose.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Anchor, Text, View, XStack, YStack } from '@hanzo/gui';
|
|
4
|
+
import { Code } from '@hanzo/ui/chat';
|
|
4
5
|
import { destination, read } from './lib/prose.js';
|
|
5
6
|
const INK = 'var(--foreground)';
|
|
6
7
|
const QUIET = 'var(--muted-foreground)';
|
|
@@ -86,11 +87,11 @@ function blocks(tokens, tone) {
|
|
|
86
87
|
return _jsx(List, { list: t, tone: tone }, i);
|
|
87
88
|
case 'table':
|
|
88
89
|
return _jsx(Table, { table: t, tone: tone }, i);
|
|
89
|
-
// A fenced block
|
|
90
|
-
//
|
|
91
|
-
//
|
|
90
|
+
// A fenced block is @hanzo/ui's `Code`, the frame Dev's answers draw:
|
|
91
|
+
// its language, a copy control, and a body that scrolls SIDEWAYS on its
|
|
92
|
+
// own, because wrapping code changes what it says.
|
|
92
93
|
case 'code':
|
|
93
|
-
return (_jsx(
|
|
94
|
+
return (_jsx(Code, { language: t.lang || 'text', value: t.text, children: t.text }, i));
|
|
94
95
|
case 'blockquote':
|
|
95
96
|
return (_jsx(YStack, { render: "blockquote", m: 0, pl: "$3", gap: "$2", borderWidth: 0, borderLeftWidth: 2, borderStyle: "solid", borderColor: EDGE, children: blocks(t.tokens, QUIET) }, i));
|
|
96
97
|
case 'hr':
|